From 0fedccdf05c56f08441130ec2fdeb474f11219f7 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 21 Apr 2025 15:22:43 -0300 Subject: [PATCH] :sparkles: ags(notifications): hold notification on hover --- ags/scripts/notifications.ts | 22 +++++++++++++++++++++- ags/widget/Notification.ts | 18 ++++++++++++------ ags/window/FloatingNotifications.ts | 5 ++++- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/ags/scripts/notifications.ts b/ags/scripts/notifications.ts index 3303db8..486cc81 100644 --- a/ags/scripts/notifications.ts +++ b/ags/scripts/notifications.ts @@ -23,6 +23,7 @@ class Notifications extends GObject.Object { #notifications: Array = []; #history: Array = []; + #notificationsOnHold: Set = new Set(); #connections: Array = []; #historyLimit: number = 10; @@ -48,7 +49,7 @@ class Notifications extends GObject.Object { @signal(Number) declare notificationRemoved: (id: number) => void; - @signal(Object) + @signal(Object) // It's an Object, beacuase HistoryNotification is just an interface declare historyAdded: (notification: AstalNotifd.Notification) => void; @signal(Number) @@ -85,6 +86,8 @@ class Notifications extends GObject.Object { const removeFun = () => { // Funny name haha lmao remove fun :skull: notifTimer = undefined; + if(this.#notificationsOnHold.has(notification.id)) return; + this.addHistory(notification, () => { replacedConnectionId && this.disconnect(replacedConnectionId); this.removeNotification(id); @@ -181,6 +184,9 @@ class Notifications extends GObject.Object { public removeNotification(notif: (AstalNotifd.Notification|number)): void { const notificationId = (notif instanceof AstalNotifd.Notification) ? notif.id : notif; + this.#notificationsOnHold.has(notificationId) && + this.#notificationsOnHold.delete(notificationId); + this.#notifications = this.#notifications.filter((item: AstalNotifd.Notification) => item.id !== notificationId); @@ -189,6 +195,20 @@ class Notifications extends GObject.Object { this.emit("notification-removed", notificationId); } + private getNotificationById(id: number): AstalNotifd.Notification|undefined { + return this.#notifications.filter(notif => notif.id === id)?.[0]; + } + + public holdNotification(notif: (AstalNotifd.Notification|number)): void { + notif = (typeof notif === "number") ? + this.getNotificationById(notif)! + : notif; + + if(!notif) return; + + this.#notificationsOnHold.add(notif.id); + } + public toggleDoNotDisturb(): boolean { if(AstalNotifd.get_default().dontDisturb) { AstalNotifd.get_default().dontDisturb = false; diff --git a/ags/widget/Notification.ts b/ags/widget/Notification.ts index 691def3..e58fd9d 100644 --- a/ags/widget/Notification.ts +++ b/ags/widget/Notification.ts @@ -1,7 +1,7 @@ import { Astal, Gtk, Widget } from "astal/gtk3"; import AstalNotifd from "gi://AstalNotifd"; import { Separator } from "./Separator"; -import { HistoryNotification } from "../scripts/notifications"; +import { HistoryNotification, Notifications } from "../scripts/notifications"; import { GLib } from "astal"; export function getUrgencyString(notif: AstalNotifd.Notification) { @@ -32,14 +32,16 @@ function getNotificationImage(notif: AstalNotifd.Notification|HistoryNotificatio export function NotificationWidget(notification: AstalNotifd.Notification|number|HistoryNotification, onClose?: (notif: AstalNotifd.Notification|HistoryNotification) => void, - showTime?: boolean /* It's showTime :speaking_head: :boom: :bangbang: */): Gtk.Widget { + showTime?: boolean /* It's showTime :speaking_head: :boom: :bangbang: */, + holdOnHover?: boolean): Gtk.Widget { notification = (typeof notification === "number") ? AstalNotifd.get_default().get_notification(notification) : notification; const body: string = notification.body.split(' ').map(strPart => { - if(/^\<.*\>.*\<.*\>$/.test(strPart)) return strPart; + if(/^\<(.*)\>/.test(strPart) || /<\/(.*)\>$/.test(strPart)) + return strPart; return strPart.length >= 25 ? `${strPart.substring(0, 22)}...` : strPart @@ -48,12 +50,16 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number return new Widget.EventBox({ onClick: () => { if(notification instanceof AstalNotifd.Notification) { - const viewAction = notification.actions.filter(action => action.label.toLowerCase() === "view")?.[0]; - if(viewAction) notification.invoke(viewAction.id); + const viewAction = notification.actions.filter(action => + action.label.toLowerCase() === "view")?.[0]; + + viewAction && notification.invoke(viewAction.id); } - onClose && onClose(notification); + onClose?.(notification); }, + onHover: () => holdOnHover && Notifications.getDefault().holdNotification(notification.id), + onHoverLost: () => onClose?.(notification), hexpand: true, vexpand: false, child: new Widget.Box({ diff --git a/ags/window/FloatingNotifications.ts b/ags/window/FloatingNotifications.ts index 937f8e5..732ed02 100644 --- a/ags/window/FloatingNotifications.ts +++ b/ags/window/FloatingNotifications.ts @@ -18,6 +18,9 @@ export const FloatingNotifications = (mon: number) => new Widget.Window({ homogeneous: false, visible: bind(Notifications.getDefault(), "notifications").as(notifs => notifs.length > 0), children: bind(Notifications.getDefault(), "notifications").as((notifs) => - notifs.map((item) => NotificationWidget(item, () => Notifications.getDefault().removeNotification(item)))), + notifs.map((item) => NotificationWidget(item, + () => Notifications.getDefault().removeNotification(item), + false, true)) + ), } as Widget.BoxProps) } as Widget.WindowProps);