mirror of
https://github.com/tuxdotrs/tpanel.git
synced 2025-10-10 12:51:54 +05:30
feat: add notifications widget
This commit is contained in:
@@ -36,6 +36,7 @@
|
|||||||
battery
|
battery
|
||||||
tray
|
tray
|
||||||
network
|
network
|
||||||
|
notifd
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -88,6 +89,7 @@
|
|||||||
battery
|
battery
|
||||||
tray
|
tray
|
||||||
network
|
network
|
||||||
|
notifd
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
@@ -15,3 +15,6 @@ $font-size: 1rem;
|
|||||||
|
|
||||||
// app-launcher
|
// app-launcher
|
||||||
@import "./widgets/app-launcher/style.scss";
|
@import "./widgets/app-launcher/style.scss";
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
@import "./widgets/notifications/style.scss";
|
||||||
|
21
widgets/notifications/icon.tsx
Normal file
21
widgets/notifications/icon.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Gtk } from "astal/gtk4";
|
||||||
|
import Notifd from "gi://AstalNotifd";
|
||||||
|
import { fileExists, isIcon } from "./notifd";
|
||||||
|
|
||||||
|
export const Icon = (notification: Notifd.Notification) => {
|
||||||
|
const icon =
|
||||||
|
notification.image || notification.appIcon || notification.desktopEntry;
|
||||||
|
if (!icon) return null;
|
||||||
|
if (fileExists(icon))
|
||||||
|
return (
|
||||||
|
<box hexpand={false} vexpand={false} valign={Gtk.Align.CENTER}>
|
||||||
|
<image file={icon} />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
else if (isIcon(icon))
|
||||||
|
return (
|
||||||
|
<box hexpand={false} vexpand={false} valign={Gtk.Align.CENTER}>
|
||||||
|
<image iconName={icon} />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
29
widgets/notifications/index.tsx
Normal file
29
widgets/notifications/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Astal, Gdk } from "astal/gtk4";
|
||||||
|
import Notifd from "gi://AstalNotifd";
|
||||||
|
import { bind } from "astal";
|
||||||
|
import { NotificationWidget } from "./notification";
|
||||||
|
|
||||||
|
export const WINDOW_NAME = "notifications";
|
||||||
|
|
||||||
|
export const Notifications = (gdkmonitor: Gdk.Monitor) => {
|
||||||
|
const notifd = Notifd.get_default();
|
||||||
|
const { TOP, RIGHT } = Astal.WindowAnchor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<window
|
||||||
|
name={WINDOW_NAME}
|
||||||
|
cssClasses={["notifications"]}
|
||||||
|
gdkmonitor={gdkmonitor}
|
||||||
|
anchor={TOP | RIGHT}
|
||||||
|
visible={bind(notifd, "notifications").as(
|
||||||
|
(notifications) => notifications.length > 0,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<box vertical={true} spacing={10}>
|
||||||
|
{bind(notifd, "notifications").as((notifications) =>
|
||||||
|
notifications.map((n) => <NotificationWidget n={n} />),
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
</window>
|
||||||
|
);
|
||||||
|
};
|
79
widgets/notifications/notifd.ts
Normal file
79
widgets/notifications/notifd.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import Notifd from "gi://AstalNotifd";
|
||||||
|
import { GLib } from "astal";
|
||||||
|
import { Gtk, Gdk } from "astal/gtk4";
|
||||||
|
|
||||||
|
type TimeoutManager = {
|
||||||
|
setupTimeout: () => void;
|
||||||
|
clearTimeout: () => void;
|
||||||
|
handleHover: () => void;
|
||||||
|
handleHoverLost: () => void;
|
||||||
|
cleanup: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createTimeoutManager = (
|
||||||
|
dismissCallback: () => void,
|
||||||
|
timeoutDelay: number,
|
||||||
|
): TimeoutManager => {
|
||||||
|
let isHovered = false;
|
||||||
|
let timeoutId: number | null = null;
|
||||||
|
|
||||||
|
const clearTimeout = () => {
|
||||||
|
if (timeoutId !== null) {
|
||||||
|
GLib.source_remove(timeoutId);
|
||||||
|
timeoutId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupTimeout = () => {
|
||||||
|
clearTimeout();
|
||||||
|
|
||||||
|
if (!isHovered) {
|
||||||
|
timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, timeoutDelay, () => {
|
||||||
|
clearTimeout();
|
||||||
|
dismissCallback();
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
setupTimeout,
|
||||||
|
clearTimeout,
|
||||||
|
handleHover: () => {
|
||||||
|
isHovered = true;
|
||||||
|
clearTimeout();
|
||||||
|
},
|
||||||
|
handleHoverLost: () => {
|
||||||
|
isHovered = false;
|
||||||
|
setupTimeout();
|
||||||
|
},
|
||||||
|
cleanup: clearTimeout,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const time = (time: number, format = "%H:%M") =>
|
||||||
|
GLib.DateTime.new_from_unix_local(time).format(format)!;
|
||||||
|
|
||||||
|
export const urgency = (notification: Notifd.Notification) => {
|
||||||
|
const { LOW, NORMAL, CRITICAL } = Notifd.Urgency;
|
||||||
|
|
||||||
|
switch (notification.urgency) {
|
||||||
|
case LOW:
|
||||||
|
return "low";
|
||||||
|
case CRITICAL:
|
||||||
|
return "critical";
|
||||||
|
case NORMAL:
|
||||||
|
default:
|
||||||
|
return "normal";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isIcon = (icon: string) => {
|
||||||
|
const display = Gdk.Display.get_default();
|
||||||
|
if (!display) return false;
|
||||||
|
const iconTheme = Gtk.IconTheme.get_for_display(display);
|
||||||
|
return iconTheme.has_icon(icon);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fileExists = (path: string) =>
|
||||||
|
GLib.file_test(path, GLib.FileTest.EXISTS);
|
53
widgets/notifications/notification.tsx
Normal file
53
widgets/notifications/notification.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { bind } from "astal";
|
||||||
|
import { Gtk } from "astal/gtk4";
|
||||||
|
import Notifd from "gi://AstalNotifd";
|
||||||
|
import { urgency, createTimeoutManager } from "./notifd";
|
||||||
|
|
||||||
|
export const NotificationWidget = ({ n }: { n: Notifd.Notification }) => {
|
||||||
|
const { START, CENTER } = Gtk.Align;
|
||||||
|
const actions = n.actions || [];
|
||||||
|
const TIMEOUT_DELAY = 3000;
|
||||||
|
|
||||||
|
// Keep track of notification validity
|
||||||
|
const timeoutManager = createTimeoutManager(() => n.dismiss(), TIMEOUT_DELAY);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box
|
||||||
|
setup={(self) => {
|
||||||
|
// Set up timeout
|
||||||
|
timeoutManager.setupTimeout();
|
||||||
|
|
||||||
|
self.connect("unrealize", () => {
|
||||||
|
timeoutManager.cleanup();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
cssClasses={["notification", `${urgency(n)}`]}
|
||||||
|
name={n.id.toString()}
|
||||||
|
spacing={10}
|
||||||
|
>
|
||||||
|
<box vertical cssClasses={["text"]} spacing={10}>
|
||||||
|
<label
|
||||||
|
cssClasses={["title"]}
|
||||||
|
label={bind(n, "summary")}
|
||||||
|
halign={START}
|
||||||
|
/>
|
||||||
|
{n.body && (
|
||||||
|
<label cssClasses={["body"]} label={bind(n, "body")} halign={START} />
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
{actions.length > 0 && (
|
||||||
|
<box cssClasses={["actions"]}>
|
||||||
|
{actions.map(({ label, id }) => (
|
||||||
|
<button
|
||||||
|
hexpand
|
||||||
|
cssClasses={["action-button"]}
|
||||||
|
onClicked={() => n.invoke(id)}
|
||||||
|
>
|
||||||
|
<label label={label} halign={CENTER} hexpand />
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
30
widgets/notifications/style.scss
Normal file
30
widgets/notifications/style.scss
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
@use "sass:math";
|
||||||
|
@use "sass:list";
|
||||||
|
|
||||||
|
window.notifications {
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
background: $bg-color;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: $rounded;
|
||||||
|
min-width: 20rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
&.critical {
|
||||||
|
border: 1px solid rgba(red, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.low {
|
||||||
|
border: 1px solid rgba(yellow, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: rgba($fg-color, 0.8);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
import { Bar } from "./widgets/bar";
|
import { Bar } from "./widgets/bar";
|
||||||
import { AppLauncher } from "./widgets/app-launcher";
|
import { AppLauncher } from "./widgets/app-launcher";
|
||||||
|
import { Notifications } from "./widgets/notifications";
|
||||||
|
|
||||||
export default [Bar, AppLauncher];
|
export default [Bar, AppLauncher, Notifications];
|
||||||
|
Reference in New Issue
Block a user