[AGS] GTK4 Migration: Partially complete
This commit is contained in:
		| @@ -1,16 +1,16 @@ | ||||
| import { App } from "astal/gtk3" | ||||
| import { App } from "astal/gtk4" | ||||
| import style from "./style.scss" | ||||
|  | ||||
| import notifications from "./components/notifications/handler"; | ||||
| import Bar from "./components/bar/ui/Bar"; | ||||
| // import Bar from "./components/bar/ui/Bar"; | ||||
|  | ||||
| App.start({ | ||||
|     instanceName: "runner", | ||||
|     css: style, | ||||
|     main() { | ||||
|         notifications.startNotificationHandler( 0, App.get_monitors()[0] ); | ||||
|         notifications.startNotificationHandler( App.get_monitors()[0] ); | ||||
|         // TODO: Monitor handling | ||||
|         Bar.Bar( App.get_monitors()[0] ); | ||||
|         // Bar.Bar( App.get_monitors()[0] ); | ||||
|     }, | ||||
|     requestHandler(request, res) { | ||||
|         const args = request.trimStart().split( ' ' ); | ||||
| @@ -19,7 +19,7 @@ App.start({ | ||||
|         if ( args[ 0 ] === 'notifier' ) { | ||||
|             res( notifications.cliHandler( args ) ); | ||||
|         } else if ( args[ 0 ] === 'bar' ) { | ||||
|             res( Bar.cliHandler( args ) ); | ||||
|             // res( Bar.cliHandler( args ) ); | ||||
|         } | ||||
|     }, | ||||
| }) | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import AstalTray from "gi://AstalTray"; | ||||
| import { bind, Variable } from "astal"; | ||||
| import AstalHyprland from "gi://AstalHyprland"; | ||||
| import { Gtk } from "astal/gtk4"; | ||||
|  | ||||
| const SysTray = () => { | ||||
|     const tray = AstalTray.get_default(); | ||||
| @@ -48,12 +49,12 @@ const ActiveWindow = () => { | ||||
|         visible.set( !visible.get() ); | ||||
|     } | ||||
|  | ||||
|     return <box className={"HyprlandFocusedClients"} visible={focused.as(Boolean)}> | ||||
|         <button onClicked={toggleOverlay}> | ||||
|     return <box cssName={"HyprlandFocusedClients"} visible={focused.as(Boolean)}> | ||||
|         <Gtk.Button onClicked={toggleOverlay}> | ||||
|             {focused.as( client => ( | ||||
|                 client && <label label={bind( client, "title" ).as( String )} /> | ||||
|             ))} | ||||
|         </button> | ||||
|         </Gtk.Button> | ||||
|         <eventbox visible={bind(visible).as( v => v )} name="popover-container"> | ||||
|             <label label="This is a test"></label> | ||||
|         </eventbox> | ||||
|   | ||||
| @@ -7,209 +7,267 @@ | ||||
| * | ||||
| */ | ||||
|  | ||||
| import { Astal, Gtk, Gdk } from "astal/gtk3" | ||||
| import { Astal, Gdk } from "astal/gtk4" | ||||
| import Notifd from "gi://AstalNotifd"; | ||||
| import Notification from "./notifications"; | ||||
| import { type Subscribable } from "astal/binding"; | ||||
| import { Variable, bind, timeout } from "astal" | ||||
| import { timeout, Variable } from "astal" | ||||
|  | ||||
| // Config | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
| //                              Config | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
| const TIMEOUT_DELAY = 5000; | ||||
| let isRunning = false; | ||||
| let notificationMenuOpen = false; | ||||
|  | ||||
| class Notifier implements Subscribable { | ||||
|     private display: Map<number, Gtk.Widget> = new Map(); | ||||
|     private notifications: Map<number, Notifd.Notification> = new Map(); | ||||
| interface NotificationDetails { | ||||
|     notification: Notifd.Notification; | ||||
|     backendID: number; | ||||
|     notifdID: number; | ||||
| } | ||||
|  | ||||
|     private notifd: Notifd.Notifd; | ||||
|     private subscriberData: Variable<Gtk.Widget[]> = Variable( [] ); | ||||
|     private instanceID: number; | ||||
|     private menuOpen: boolean; | ||||
|  | ||||
|     /** | ||||
|      * Sets up the notifier | ||||
|      */ | ||||
|     constructor( id: number ) { | ||||
|         this.instanceID = id; | ||||
|         this.menuOpen = false; | ||||
|         this.notifd = Notifd.get_default(); | ||||
|         this.notifd.ignoreTimeout = true; | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
| //          ╭───────────────────────────────────────────────╮ | ||||
| //          │                   Handler                     │ | ||||
| //          ╰───────────────────────────────────────────────╯ | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
| let ShownNotifications: number[] = []; | ||||
| const ShownNotificationsCount: Variable<number> = Variable( 0 ); | ||||
| let Notifications: NotificationDetails[] = []; | ||||
|  | ||||
|         this.notifd.connect( 'notified', ( _, id ) => { | ||||
|             this.add( id ); | ||||
|         } ); | ||||
| const notifd = Notifd.get_default(); | ||||
| notifd.ignoreTimeout = true; | ||||
|  | ||||
|         this.notifd.connect( 'resolved', ( _, id ) => { | ||||
|             this.delete( id ); | ||||
|         } ); | ||||
|  | ||||
| /** | ||||
|  * Delete a notification by its internal ID | ||||
|  * @param index The notifd ID of the notification | ||||
|  */ | ||||
| const deleteNotification = ( index: number ): void => { | ||||
|     hideNotification( index ); | ||||
|     Notifications.splice( index, 1 ); | ||||
| } | ||||
|  | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Delete a notification by notifd id | ||||
|  * @param id The notifd ID of the notification | ||||
|  */ | ||||
| const deleteNotificationByNotificationID = ( id: number ): void => { | ||||
|     const index = findNotificationByNotificationID( id ); | ||||
|     if ( index > -1 ) { | ||||
|         deleteNotification( index ); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     private notify () { | ||||
|         this.subscriberData.set( [ ...this.display.values() ].reverse() ); | ||||
|     } | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|     private add ( id: number ) { | ||||
|         this.notifications.set( id, this.notifd.get_notification( id )! ); | ||||
|         this.show( id ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Show an element on screen | ||||
|      * @param id - The id of the element to be shown | ||||
|      */ | ||||
|     private show ( id: number ) { | ||||
|         this.set( id, Notification( { | ||||
|             notification: this.notifications.get( id )!, | ||||
|             onHoverLost: () => {  | ||||
|                 if ( !this.menuOpen ) { | ||||
|                     this.hide( id ); | ||||
|                 } | ||||
|             }, | ||||
|             setup: () => timeout( TIMEOUT_DELAY, () => { | ||||
|                 if ( !this.menuOpen ) { | ||||
|                     this.hide( id ); | ||||
|                 } | ||||
|             } ), | ||||
|             id: id, | ||||
|             delete: deleteHelper, | ||||
|             instanceID: this.instanceID | ||||
|         } ) ) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set a selected widget to be shown | ||||
|      * @param id - The id of the element to be referenced for later | ||||
|      * @param widget - A GTK widget instance | ||||
|      */ | ||||
|     private set ( id: number, widget: Gtk.Widget ) { | ||||
|         this.display.get( id )?.destroy(); | ||||
|         this.display.set( id, widget ); | ||||
|         this.notify(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Hide, not delete notification (= send to notification centre) | ||||
|      * @param id - The id of the notification to hide | ||||
|      */ | ||||
|     private hide ( id: number ) { | ||||
|         this.display.get( id )?.destroy(); | ||||
|         this.display.delete( id ); | ||||
|         this.notify(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Delete a notification (from notification centre too) | ||||
|      * @param id - The id of the notification to hide | ||||
|      */ | ||||
|     delete ( id: number ) { | ||||
|         this.hide( id ); | ||||
|         this.notifications.get( id )?.dismiss(); | ||||
|         this.notifications.delete( id ); | ||||
|         if ( this.notifications.size == 0 ) { | ||||
|             this.menuOpen = false; | ||||
| /** | ||||
|  * Find the internal ID from the notifd id for a notification (helper function) | ||||
|  * @param id The notifd ID of the notification | ||||
|  * @returns The internal ID or -1 if not found | ||||
|  */ | ||||
| const findNotificationByNotificationID = ( id: number ): number => { | ||||
|     // Find index in Notifications array | ||||
|     for (let index = 0; index < Notifications.length; index++) { | ||||
|         if ( Notifications[ index ].notifdID === id ) { | ||||
|             return index; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     openNotificationMenu () { | ||||
|         // Show all notifications that have not been cleared | ||||
|         if ( this.notifications.size > 0 ) { | ||||
|             this.menuOpen = true; | ||||
|             this.notifications.forEach( ( _, id ) => { | ||||
|                 this.show( id ); | ||||
|             } ) | ||||
|         } | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
|     hideNotifications () { | ||||
|         this.menuOpen = false; | ||||
|         this.notifications.forEach( ( _, id ) => { | ||||
|             this.hide( id ); | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Add a notification to the notification handler | ||||
|  * @param id The notifd ID of the notification | ||||
|  */ | ||||
| const addNotification = ( id: number ): void => { | ||||
|     print( '[ Notifications ] Notification with id ' + id + ' added.' ); | ||||
|     const currIndex = Notifications.length; | ||||
|     Notifications.push( { | ||||
|         notifdID: id, | ||||
|         backendID: currIndex, | ||||
|         notification: notifd.get_notification( id ) | ||||
|     } ); | ||||
|      | ||||
|     showNotification( currIndex ); | ||||
| } | ||||
|  | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Start the notifd runner and handle notifications. | ||||
|  */ | ||||
| const hookToNotificationDaemon = (): void => { | ||||
|     if ( isRunning ) { | ||||
|         printerr( '[ Notifications ] Error: Already running' ); | ||||
|         return; | ||||
|     } | ||||
|     isRunning = true; | ||||
|  | ||||
|     notifd.connect( 'notified', ( _, id ) => { | ||||
|         addNotification( id ); | ||||
|     } ); | ||||
|  | ||||
|     notifd.connect( 'resolved', ( _, id ) => { | ||||
|         deleteNotificationByNotificationID( id ); | ||||
|     } ); | ||||
| } | ||||
|  | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Show a notification. It will stay on screen (regardless of removeAgain passed in), if  | ||||
|  * critical urgency | ||||
|  * @param id The internal id (index in Notifications array) | ||||
|  * @param removeAgain = true If to remove the notification from the screen again automatically | ||||
|  */ | ||||
| const showNotification = ( id: number, removeAgain: boolean = true ) => { | ||||
|     // Add notification to UI for display | ||||
|     ShownNotifications.reverse().push( id ); | ||||
|     ShownNotifications.reverse(); | ||||
|     ShownNotificationsCount.set( ShownNotifications.length ); | ||||
|  | ||||
|     // Set delay to remove the notification again | ||||
|     if ( removeAgain && Notifications[ id ].notification.get_urgency() !== Notifd.Urgency.CRITICAL ) { | ||||
|         timeout( TIMEOUT_DELAY, () => { | ||||
|             hideNotification( id ); | ||||
|         } ); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     toggleNotificationMenu () { | ||||
|         if ( this.menuOpen ) { | ||||
|             this.hideNotifications(); | ||||
|         } else { | ||||
|             this.openNotificationMenu(); | ||||
|         } | ||||
|     } | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|     clearAllNotifications () { | ||||
|         this.menuOpen = false; | ||||
|         this.notifications.forEach( ( _, id ) => { | ||||
|             this.delete( id ); | ||||
|         } ) | ||||
|     } | ||||
|  | ||||
|     clearNewestNotification () { | ||||
|         this.delete( [ ...this.notifications.keys() ][0] ); | ||||
|     } | ||||
|  | ||||
|     subscribe(callback: (value: unknown) => void): () => void { | ||||
|         return this.subscriberData.subscribe( callback ); | ||||
|     } | ||||
|  | ||||
|     get() { | ||||
|         return this.subscriberData.get(); | ||||
| /** | ||||
|  * Stop displaying notification  | ||||
|  * @param id The internal id (index in the Notifications array) | ||||
|  */ | ||||
| const hideNotification = ( id: number ) => { | ||||
|     if ( !notificationMenuOpen ) { | ||||
|         ShownNotifications.splice( ShownNotifications.indexOf( id ), 1 ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| const notifiers: Map<number, Notifier> = new Map(); | ||||
| const deleteHelper = ( id: number, instanceID: number ) => { | ||||
|     notifiers.get( instanceID )?.delete( id ); | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Open the notification menu. Called by toggleNotificationMenu too | ||||
|  */ | ||||
| const openNotificationMenu = () => { | ||||
|     // Simply show all notifications | ||||
|     notificationMenuOpen = true; | ||||
|     const ShownNotifications = []; | ||||
|     for (let index = 0; index < Notifications.length; index++) { | ||||
|         ShownNotifications.push( index ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| const openNotificationMenu = ( id: number ) => { | ||||
|     notifiers.get( id )?.openNotificationMenu(); | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Close the notification menu. Called by toggleNotificationMenu too | ||||
|  */ | ||||
| const closeNotificationMenu = () => { | ||||
|     // Hide all notifications | ||||
|     notificationMenuOpen = true; | ||||
|  | ||||
|     ShownNotifications = []; | ||||
|     ShownNotificationsCount.set( 0 ); | ||||
| } | ||||
|  | ||||
| const closeNotificationMenu = ( id: number ) => { | ||||
|     notifiers.get( id )?.hideNotifications(); | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Toggle the notification menu (i.e. show all notifications) | ||||
|  */ | ||||
| const toggleNotificationMenu = () => { | ||||
|     if ( notificationMenuOpen ) { | ||||
|         closeNotificationMenu(); | ||||
|     } else { | ||||
|         openNotificationMenu(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| const toggleNotificationMenu = ( id: number ) => { | ||||
|     notifiers.get( id )?.toggleNotificationMenu(); | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Delete all notifications | ||||
|  */ | ||||
| const clearAllNotifications = () => { | ||||
|     Notifications = []; | ||||
|     ShownNotifications = []; | ||||
|     ShownNotificationsCount.set( 0 ); | ||||
| } | ||||
|  | ||||
| const clearAllNotifications = ( id: number ) => { | ||||
|     notifiers.get( id )?.clearAllNotifications(); | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Delete the newest notifications | ||||
|  */ | ||||
| const clearNewestNotifications = () => { | ||||
|     ShownNotifications.splice( 0, 1 ); | ||||
|  | ||||
|     Notifications.splice( Notifications.length - 1, 1 ); | ||||
| } | ||||
|  | ||||
| const clearNewestNotifications = ( id: number ) => { | ||||
|     notifiers.get( id )?.clearNewestNotification(); | ||||
| } | ||||
|  | ||||
| const startNotificationHandler = (id: number, gdkmonitor: Gdk.Monitor) => { | ||||
|  | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
| //          ╭───────────────────────────────────────────────╮ | ||||
| //          │                User Interface                 │ | ||||
| //          ╰───────────────────────────────────────────────╯ | ||||
| // ─────────────────────────────────────────────────────────────────── | ||||
| const startNotificationHandler = (gdkmonitor: Gdk.Monitor) => { | ||||
|     const { TOP, RIGHT } = Astal.WindowAnchor | ||||
|     const notifier: Notifier = new Notifier( id ); | ||||
|     notifiers.set( id, notifier ); | ||||
|  | ||||
|     hookToNotificationDaemon(); | ||||
|     const range = (n: number) => [...Array(n).keys()]; | ||||
|  | ||||
|     return <window | ||||
|         className="NotificationHandler" | ||||
|         cssClasses={["NotificationHandler"]} | ||||
|         gdkmonitor={gdkmonitor} | ||||
|         exclusivity={Astal.Exclusivity.EXCLUSIVE} | ||||
|         anchor={TOP | RIGHT}> | ||||
|         <box vertical noImplicitDestroy> | ||||
|             {bind(notifier)} | ||||
|             {ShownNotificationsCount( n => range( n ).map( i => { | ||||
|                 print( 'Rendering' ); | ||||
|                 // i is index in ShownNotifications array | ||||
|                 return <Notification id={i} delete={deleteNotification} notification={Notifications[ i ].notification}></Notification> | ||||
|             } ) ) } | ||||
|         </box> | ||||
|     </window> | ||||
| } | ||||
|  | ||||
| const cliHandler = ( args: string[] ): string => { | ||||
|     if ( args[ 1 ] == 'show' ) { | ||||
|         openNotificationMenu( 0 ); | ||||
|         openNotificationMenu(); | ||||
|         return 'Showing all open notifications'; | ||||
|     } else if ( args[ 1 ] == 'hide' ) { | ||||
|         closeNotificationMenu( 0 ); | ||||
|         closeNotificationMenu(); | ||||
|         return 'Hid all notifications'; | ||||
|     } else if ( args[ 1 ] == 'clear' ) { | ||||
|         clearAllNotifications( 0 ); | ||||
|         clearAllNotifications(); | ||||
|         return 'Cleared all notifications'; | ||||
|     } else if ( args[ 1 ] == 'clear-newest' ) { | ||||
|         clearNewestNotifications( 0 ); | ||||
|         clearNewestNotifications(); | ||||
|         return 'Cleared newest notification'; | ||||
|     } else if ( args[ 1 ] == 'toggle' ) { | ||||
|         toggleNotificationMenu( 0 ); | ||||
|         toggleNotificationMenu(); | ||||
|         return 'Toggled notifications'; | ||||
|     } else { | ||||
|         return 'Unknown command!'; | ||||
|   | ||||
| @@ -9,11 +9,17 @@ $fg-color: #{"@theme_fg_color"}; | ||||
| $bg-color: #{"@theme_bg_color"}; | ||||
| $error: red; | ||||
|  | ||||
| window.NotificationPopups { | ||||
| window.NotificationHandler { | ||||
|     all: unset; | ||||
| } | ||||
|  | ||||
| eventbox.Notification { | ||||
| box.Notification { | ||||
|     min-width: 400px; | ||||
|     border-radius: 13px; | ||||
|     background-color: $bg-color; | ||||
|     margin: .5rem 1rem .5rem 1rem; | ||||
|     box-shadow: 2px 3px 8px 0 gtkalpha(black, .4); | ||||
|     border: 1pt solid gtkalpha($fg-color, .03); | ||||
|  | ||||
|     &:first-child>box { | ||||
|         margin-top: 1rem; | ||||
| @@ -23,16 +29,6 @@ eventbox.Notification { | ||||
|         margin-bottom: 1rem; | ||||
|     } | ||||
|  | ||||
|     // eventboxes can not take margins so we style its inner box instead | ||||
|     >box { | ||||
|         min-width: 400px; | ||||
|         border-radius: 13px; | ||||
|         background-color: $bg-color; | ||||
|         margin: .5rem 1rem .5rem 1rem; | ||||
|         box-shadow: 2px 3px 8px 0 gtkalpha(black, .4); | ||||
|         border: 1pt solid gtkalpha($fg-color, .03); | ||||
|     } | ||||
|  | ||||
|     &.critical>box { | ||||
|         border: 1pt solid gtkalpha($error, .4); | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,9 @@ | ||||
| // From astal examples  | ||||
|  | ||||
| import { GLib } from "astal" | ||||
| import { Gtk, Astal } from "astal/gtk3" | ||||
| import { type EventBox } from "astal/gtk3/widget" | ||||
| import { Gtk } from "astal/gtk4" | ||||
| import Notifd from "gi://AstalNotifd" | ||||
|  | ||||
| const isIcon = (icon: string) => | ||||
|     !!Astal.Icon.lookup_icon(icon) | ||||
| // import Pango from "gi://Pango?version=1.0" | ||||
|  | ||||
| const fileExists = (path: string) => | ||||
|     GLib.file_test(path, GLib.FileTest.EXISTS) | ||||
| @@ -27,86 +24,80 @@ const urgency = (n: Notifd.Notification) => { | ||||
| } | ||||
|  | ||||
| type Props = { | ||||
|     delete( id: number, instanceID: number ): void | ||||
|     setup(self: EventBox): void | ||||
|     onHoverLost(self: EventBox): void | ||||
|     delete( id: number ): void | ||||
|     notification: Notifd.Notification | ||||
|     id: number | ||||
|     instanceID: number | ||||
| } | ||||
|  | ||||
| export default function Notification(props: Props) { | ||||
|     const { notification: n, onHoverLost, setup, id: id, delete: del, instanceID: instance } = props | ||||
|     const { notification: n, id: id, delete: del } = props | ||||
|     const { START, CENTER, END } = Gtk.Align | ||||
|  | ||||
|     return <eventbox | ||||
|         className={`Notification ${urgency(n)}`} | ||||
|         setup={setup} | ||||
|         onHoverLost={onHoverLost}> | ||||
|         <box vertical> | ||||
|             <box className="header"> | ||||
|                 {(n.appIcon || n.desktopEntry) && <icon | ||||
|                     className="app-icon" | ||||
|                     visible={Boolean(n.appIcon || n.desktopEntry)} | ||||
|                     icon={n.appIcon || n.desktopEntry} | ||||
|                 />} | ||||
|                 <label | ||||
|                     className="app-name" | ||||
|                     halign={START} | ||||
|                     truncate | ||||
|                     label={n.appName || "Unknown"} | ||||
|                 /> | ||||
|                 <label | ||||
|                     className="time" | ||||
|                     hexpand | ||||
|                     halign={END} | ||||
|                     label={time(n.time)} | ||||
|                 /> | ||||
|                 <button onClicked={() => del( id, instance )}> | ||||
|                     <icon icon="window-close-symbolic" /> | ||||
|                 </button> | ||||
|             </box> | ||||
|             <Gtk.Separator visible /> | ||||
|             <box className="content"> | ||||
|                 {n.image && fileExists(n.image) && <box | ||||
|                     valign={START} | ||||
|                     className="image" | ||||
|                     css={`background-image: url('${n.image}')`} | ||||
|                 />} | ||||
|                 {n.image && isIcon(n.image) && <box | ||||
|                     expand={false} | ||||
|                     valign={START} | ||||
|                     className="icon-image"> | ||||
|                     <icon icon={n.image} expand halign={CENTER} valign={CENTER} /> | ||||
|                 </box>} | ||||
|                 <box vertical> | ||||
|                     <label | ||||
|                         className="summary" | ||||
|                         halign={START} | ||||
|                         xalign={0} | ||||
|                         label={n.summary} | ||||
|                         truncate | ||||
|                     /> | ||||
|                     {n.body && <label | ||||
|                         className="body" | ||||
|                         wrap | ||||
|                         useMarkup | ||||
|                         halign={START} | ||||
|                         xalign={0} | ||||
|                         justifyFill | ||||
|                         label={n.body} | ||||
|                     />} | ||||
|                 </box> | ||||
|             </box> | ||||
|             {n.get_actions().length > 0 && <box className="actions"> | ||||
|                 {n.get_actions().map(({ label, id }) => ( | ||||
|                     <button | ||||
|                         hexpand | ||||
|                         onClicked={() => n.invoke(id)}> | ||||
|                         <label label={label} halign={CENTER} hexpand /> | ||||
|                     </button> | ||||
|                 ))} | ||||
|             </box>} | ||||
|     return <box vertical | ||||
|         cssClasses={['Notification', urgency(n)]}> | ||||
|         <box cssName="header"> | ||||
|             {(n.appIcon || n.desktopEntry) ? <Gtk.Image | ||||
|                 cssName="app-icon" | ||||
|                 visible={Boolean(n.appIcon || n.desktopEntry)} | ||||
|                 iconName={n.appIcon || n.desktopEntry} | ||||
|             /> : <Gtk.Image iconName={'window-close-symbolic'}></Gtk.Image>} | ||||
|             <label | ||||
|                 cssName="app-name" | ||||
|                 halign={START} | ||||
|                 // ellipsize={Pango.EllipsizeMode.END} | ||||
|                 label={n.appName || "Unknown"} | ||||
|             /> | ||||
|             <label | ||||
|                 cssName="time" | ||||
|                 hexpand | ||||
|                 halign={END} | ||||
|                 label={time(n.time)} | ||||
|             /> | ||||
|             <button onClicked={() => del( id )}> | ||||
|                 <Gtk.Image iconName="window-close-symbolic" /> | ||||
|             </button> | ||||
|         </box> | ||||
|     </eventbox> | ||||
|         <Gtk.Separator visible /> | ||||
|         <box cssName="content"> | ||||
|             {n.image && fileExists(n.image) ? <box | ||||
|                 valign={START} | ||||
|                 cssName="image"> | ||||
|                 <Gtk.Image file={n.image}></Gtk.Image> | ||||
|             </box> | ||||
|                 : <box></box>} | ||||
|             {n.image ? <box | ||||
|                 expand={false} | ||||
|                 valign={START} | ||||
|                 className="icon-image"> | ||||
|                 <Gtk.Image iconName={n.image} expand halign={CENTER} valign={CENTER} /> | ||||
|             </box> | ||||
|                 : <box></box>} | ||||
|             <box vertical> | ||||
|                 <Gtk.Label | ||||
|                     cssName="summary" | ||||
|                     halign={START} | ||||
|                     xalign={0} | ||||
|                     label={n.summary} | ||||
|                     // ellipsize={Pango.EllipsizeMode.END} | ||||
|                 /> | ||||
|                 {n.body ? <label | ||||
|                     cssName="body" | ||||
|                     wrap | ||||
|                     useMarkup | ||||
|                     halign={START} | ||||
|                     xalign={0} | ||||
|                     label={n.body} | ||||
|                 /> : <label></label>} | ||||
|             </box> | ||||
|         </box> | ||||
|         {n.get_actions().length > 0 ? <box cssName="actions"> | ||||
|             {n.get_actions().map(({ label, id }) => ( | ||||
|                 <button | ||||
|                     hexpand | ||||
|                     onClicked={() => n.invoke(id)}> | ||||
|                     <label label={label} halign={CENTER} hexpand /> | ||||
|                 </button> | ||||
|             ))} | ||||
|         </box> : <box></box>} | ||||
|     </box> | ||||
| } | ||||
|   | ||||
| @@ -1,20 +1,22 @@ | ||||
| // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss | ||||
| $fg-color: #{"@theme_fg_color"}; | ||||
| $bg-color: #{"@theme_bg_color"}; | ||||
| /* $fg-color: #{"@theme_fg_color"}; */ | ||||
| /* $bg-color: #{"@theme_bg_color"}; */ | ||||
| /**/ | ||||
| /* window.Bar { */ | ||||
| /*     background: transparent; */ | ||||
| /*     color: $fg-color; */ | ||||
| /*     font-weight: bold; */ | ||||
| /**/ | ||||
| /*     >centerbox { */ | ||||
| /*         background: $bg-color; */ | ||||
| /*         border-radius: 10px; */ | ||||
| /*         margin: 8px; */ | ||||
| /*     } */ | ||||
| /**/ | ||||
| /*     button { */ | ||||
| /*         border-radius: 8px; */ | ||||
| /*         margin: 2px; */ | ||||
| /*     } */ | ||||
| /* } */ | ||||
|  | ||||
| window.Bar { | ||||
|     background: transparent; | ||||
|     color: $fg-color; | ||||
|     font-weight: bold; | ||||
|  | ||||
|     >centerbox { | ||||
|         background: $bg-color; | ||||
|         border-radius: 10px; | ||||
|         margin: 8px; | ||||
|     } | ||||
|  | ||||
|     button { | ||||
|         border-radius: 8px; | ||||
|         margin: 2px; | ||||
|     } | ||||
| } | ||||
| @use './components/notifications/notifications.scss' | ||||
|   | ||||
| @@ -9,6 +9,6 @@ | ||||
|         // "checkJs": true, | ||||
|         // "allowJs": true, | ||||
|         "jsx": "react-jsx", | ||||
|         "jsxImportSource": "astal/gtk3", | ||||
|         "jsxImportSource": "astal/gtk4", | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user