diff --git a/devices/wim/config/ags/js/applauncher/main.js b/devices/wim/config/ags/js/applauncher/main.js index 0a548bff..8cd31ecb 100644 --- a/devices/wim/config/ags/js/applauncher/main.js +++ b/devices/wim/config/ags/js/applauncher/main.js @@ -9,7 +9,10 @@ import { Box, Entry, Icon, Label, ListBox, Revealer, Scrollable } from 'resource import PopupWindow from '../misc/popup.js'; import AppItem from './app-item.js'; -/** @typedef {import('types/service/applications.js').Application} App */ +/** + * @typedef {import('types/service/applications.js').Application} App + * @typedef {typeof imports.gi.Gtk.ListBoxRow} ListBoxRow + */ const Applauncher = ({ window_name = 'applauncher' } = {}) => { @@ -32,17 +35,23 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => { fzfResults = fzf.find(text); // @ts-expect-error - list.set_sort_func((a, b) => { - const row1 = a.get_children()[0]?.attribute.app.name; - const row2 = b.get_children()[0]?.attribute.app.name; + list.set_sort_func( + /** + * @param {ListBoxRow} a + * @param {ListBoxRow} b + */ + (a, b) => { + const row1 = a.get_children()[0]?.attribute.app.name; + const row2 = b.get_children()[0]?.attribute.app.name; - if (!row1 || !row2) { - return 0; - } + if (!row1 || !row2) { + return 0; + } - return fzfResults.indexOf(row1) - + return fzfResults.indexOf(row1) - fzfResults.indexOf(row1) || 0; - }); + }, + ); }; const makeNewChildren = () => { @@ -96,7 +105,7 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => { setSort(text); let visibleApps = 0; - /** @type Array */ + /** @type Array */ // @ts-expect-error const rows = list.get_children(); diff --git a/devices/wim/config/ags/js/bar/buttons/workspaces.js b/devices/wim/config/ags/js/bar/buttons/workspaces.js index 5d611ba0..9e6ad5b5 100644 --- a/devices/wim/config/ags/js/bar/buttons/workspaces.js +++ b/devices/wim/config/ags/js/bar/buttons/workspaces.js @@ -135,6 +135,7 @@ export default () => { const updateWorkspaces = () => { Hyprland.workspaces.forEach((ws) => { const currentWs = self.children.find((ch) => { + // @ts-expect-error return ch.attribute.id === ws.id; }); diff --git a/devices/wim/config/ags/js/media-player/gesture.js b/devices/wim/config/ags/js/media-player/gesture.js index a7870340..f206d07a 100644 --- a/devices/wim/config/ags/js/media-player/gesture.js +++ b/devices/wim/config/ags/js/media-player/gesture.js @@ -9,12 +9,13 @@ const ANIM_DURATION = 500; const TRANSITION = `transition: margin ${ANIM_DURATION}ms ease, opacity ${ANIM_DURATION}ms ease;`; - /** * @typedef {import('types/widgets/overlay').OverlayProps} OverlayProps * @typedef {import('types/widgets/overlay').default} Overlay - * - * @param {OverlayProps & { + */ + + +/** @param {OverlayProps & { * setup?: function(Overlay):void * }} o */ diff --git a/devices/wim/config/ags/js/misc/cursorbox.js b/devices/wim/config/ags/js/misc/cursorbox.js index daf6ba7f..dd6e9442 100644 --- a/devices/wim/config/ags/js/misc/cursorbox.js +++ b/devices/wim/config/ags/js/misc/cursorbox.js @@ -4,12 +4,13 @@ import { EventBox } from 'resource:///com/github/Aylur/ags/widget.js'; const { Gtk } = imports.gi; - /** * @typedef {import('types/widgets/eventbox').EventBoxProps} EventBoxProps * @typedef {import('types/widgets/eventbox').default} EventBox - * - * @param {EventBoxProps & { + */ + + +/** @param {EventBoxProps & { * on_primary_click_release?: function(EventBox):void * }} o */ @@ -23,7 +24,7 @@ export default ({ const CanRun = Variable(true); const Disabled = Variable(false); - let widget; // eslint-disable-line + let widget = EventBox(); const wrapper = EventBox({ cursor: 'pointer', @@ -35,7 +36,7 @@ export default ({ get_child: () => widget.child, - /** @param {import('types/widget').Widget} new_child */ + /** @param {import('types/widgets/box').default} new_child */ set_child: (new_child) => { widget.child = new_child; }, diff --git a/devices/wim/config/ags/js/misc/popup.js b/devices/wim/config/ags/js/misc/popup.js index f8f0fe6a..91fd3b09 100644 --- a/devices/wim/config/ags/js/misc/popup.js +++ b/devices/wim/config/ags/js/misc/popup.js @@ -23,8 +23,8 @@ import { timeout } from 'resource:///com/github/Aylur/ags/utils.js'; export default ({ transition = 'slide_down', transition_duration = 500, - onOpen = () => { /**/ }, - onClose = () => { /**/ }, + onOpen = () => {/**/}, + onClose = () => {/**/}, // Window props name, @@ -42,8 +42,12 @@ export default ({ ...props, attribute: { + /** + * @param {typeof imports.gi.Gtk.Allocation} alloc + * @param {'left'|'right'} side + */ set_x_pos: ( - alloc = {}, + alloc, side = 'right', ) => { const width = window.get_display() diff --git a/devices/wim/config/ags/js/notifications/base.js b/devices/wim/config/ags/js/notifications/base.js index beb589e7..e8061960 100644 --- a/devices/wim/config/ags/js/notifications/base.js +++ b/devices/wim/config/ags/js/notifications/base.js @@ -8,28 +8,35 @@ import { lookUpIcon } from 'resource:///com/github/Aylur/ags/utils.js'; const { GLib } = imports.gi; +/** @typedef {import('types/service/notifications').Notification} Notification */ + +/** @param {number} time */ const setTime = (time) => { return GLib.DateTime .new_from_unix_local(time) .format('%H:%M'); }; -const getDragState = (box) => box.get_parent().get_parent() - .get_parent().get_parent().get_parent()._dragging; +/** @param {import('types/widgets/eventbox').default} box */ +const getDragState = (box) => box.get_parent()?.get_parent() + // @ts-expect-error + ?.get_parent()?.get_parent()?.get_parent()?.attribute.dragging; import Gesture from './gesture.js'; import CursorBox from '../misc/cursorbox.js'; +/** @param {Notification} notif */ const NotificationIcon = (notif) => { - let iconCmd = () => { /**/ }; + /** @type function(import('types/widgets/eventbox').default):void */ + let iconCmd = () => {/**/}; - if (Applications.query(notif.appEntry).length > 0) { - const app = Applications.query(notif.appEntry)[0]; + if (notif._appEntry && Applications.query(notif._appEntry).length > 0) { + const app = Applications.query(notif._appEntry)[0]; let wmClass = app.app.get_string('StartupWMClass'); - if (app.app.get_filename().includes('discord')) { + if (app.app?.get_filename()?.includes('discord')) { wmClass = 'discord'; } @@ -45,17 +52,19 @@ const NotificationIcon = (notif) => { 'togglespecialworkspace spot'); } else { - Hyprland.sendMessage('j/clients').then((out) => { - out = JSON.parse(out); + Hyprland.sendMessage('j/clients').then((msg) => { + /** @type {Array} */ + const clients = JSON.parse(msg); + const classes = []; - for (const key of out) { + for (const key of clients) { if (key.class) { classes.push(key.class); } } - if (classes.includes(wmClass)) { + if (wmClass && classes.includes(wmClass)) { Hyprland.sendMessage('dispatch ' + `focuswindow ^(${wmClass})`); } @@ -81,7 +90,7 @@ const NotificationIcon = (notif) => { child: Box({ vpack: 'start', hexpand: false, - className: 'icon img', + class_name: 'icon img', css: ` background-image: url("${notif.image}"); background-size: contain; @@ -96,13 +105,13 @@ const NotificationIcon = (notif) => { let icon = 'dialog-information-symbolic'; - if (lookUpIcon(notif.appIcon)) { - icon = notif.appIcon; + if (lookUpIcon(notif._appIcon)) { + icon = notif._appIcon; } - if (lookUpIcon(notif.appEntry)) { - icon = notif.appEntry; + if (notif._appEntry && lookUpIcon(notif._appEntry)) { + icon = notif._appEntry; } @@ -112,7 +121,7 @@ const NotificationIcon = (notif) => { child: Box({ vpack: 'start', hexpand: false, - className: 'icon', + class_name: 'icon', css: ` min-width: 78px; min-height: 78px; @@ -132,11 +141,18 @@ const NotificationIcon = (notif) => { // to know when there are notifs or not export const HasNotifs = Variable(false); +/** + * @param {{ + * notif: Notification + * slideIn?: 'Left'|'Right' + * command?: () => void + * }} o + */ export const Notification = ({ notif, slideIn = 'Left', command = () => { /**/ }, -} = {}) => { +}) => { if (!notif) { return; } @@ -145,7 +161,7 @@ export const Notification = ({ 'Spotify', ]; - if (BlockedApps.find((app) => app === notif.appName)) { + if (BlockedApps.find((app) => app === notif._appName)) { notif.close(); return; @@ -161,8 +177,9 @@ export const Notification = ({ }); // Add body to notif + // @ts-expect-error notifWidget.child.add(Box({ - className: `notification ${notif.urgency}`, + class_name: `notification ${notif.urgency}`, vexpand: false, // Notification @@ -174,6 +191,7 @@ export const Notification = ({ Box({ children: [ NotificationIcon(notif), + Box({ hexpand: true, vertical: true, @@ -185,21 +203,21 @@ export const Notification = ({ // Title Label({ - className: 'title', + class_name: 'title', xalign: 0, justification: 'left', hexpand: true, - maxWidthChars: 24, + max_width_chars: 24, truncate: 'end', wrap: true, label: notif.summary, - useMarkup: notif.summary + use_markup: notif.summary .startsWith('<'), }), // Time Label({ - className: 'time', + class_name: 'time', vpack: 'start', label: setTime(notif.time), }), @@ -207,9 +225,12 @@ export const Notification = ({ // Close button CursorBox({ child: Button({ - className: 'close-button', + class_name: 'close-button', vpack: 'start', - onClicked: () => notif.close(), + + on_primary_click_release: () => + notif.close(), + child: Icon('window-close' + '-symbolic'), }), @@ -219,9 +240,9 @@ export const Notification = ({ // Description Label({ - className: 'description', + class_name: 'description', hexpand: true, - useMarkup: true, + use_markup: true, xalign: 0, justification: 'left', label: notif.body, @@ -234,11 +255,13 @@ export const Notification = ({ // Actions Box({ - className: 'actions', + class_name: 'actions', children: notif.actions.map((action) => Button({ - className: 'action-button', - onClicked: () => notif.invoke(action.id), + class_name: 'action-button', hexpand: true, + + on_primary_click_release: () => notif.invoke(action.id), + child: Label(action.label), })), }), diff --git a/devices/wim/config/ags/js/notifications/center.js b/devices/wim/config/ags/js/notifications/center.js index e70be8fa..e2a70f7d 100644 --- a/devices/wim/config/ags/js/notifications/center.js +++ b/devices/wim/config/ags/js/notifications/center.js @@ -7,7 +7,16 @@ import { timeout } from 'resource:///com/github/Aylur/ags/utils.js'; import { Notification, HasNotifs } from './base.js'; import CursorBox from '../misc/cursorbox.js'; +/** + * @typedef {import('types/service/notifications').Notification} NotifObj + * @typedef {import('types/widgets/box').default} Box + */ + +/** + * @param {Box} box + * @param {NotifObj} notif + */ const addNotif = (box, notif) => { if (notif) { const NewNotif = Notification({ @@ -27,7 +36,7 @@ const NotificationList = () => Box({ vertical: true, vexpand: true, vpack: 'start', - binds: [['visible', HasNotifs]], + visible: HasNotifs.bind(), setup: (self) => { self @@ -40,31 +49,42 @@ const NotificationList = () => Box({ } else if (id) { - addNotif(box, Notifications.getNotification(id)); + const notifObj = Notifications.getNotification(id); + + if (notifObj) { + addNotif(box, notifObj); + } } }, 'notified') .hook(Notifications, (box, id) => { - const notif = box.children.find((ch) => ch._id === id); + // @ts-expect-error + const notif = box.children.find((ch) => ch.attribute.id === id); if (notif?.sensitive) { - notif.slideAway('Right'); + // @ts-expect-error + notif.attribute.slideAway('Right'); } }, 'closed'); }, }); +// TODO: use cursorbox feature for this // Needs to be wrapped to still have onHover when disabled const ClearButton = () => CursorBox({ child: Button({ - onPrimaryClickRelease: () => { + sensitive: HasNotifs.bind(), + + on_primary_click_release: () => { Notifications.clear(); timeout(1000, () => App.closeWindow('notification-center')); }, - binds: [['sensitive', HasNotifs]], + child: Box({ + children: [ Label('Clear '), + Icon({ setup: (self) => { self.hook(Notifications, () => { @@ -80,7 +100,7 @@ const ClearButton = () => CursorBox({ }); const Header = () => Box({ - className: 'header', + class_name: 'header', children: [ Label({ label: 'Notifications', @@ -93,14 +113,17 @@ const Header = () => Box({ const Placeholder = () => Revealer({ transition: 'crossfade', - binds: [['revealChild', HasNotifs, 'value', (value) => !value]], + reveal_child: HasNotifs.bind() + .transform((v) => !v), + child: Box({ - className: 'placeholder', + class_name: 'placeholder', vertical: true, vpack: 'center', hpack: 'center', vexpand: true, hexpand: true, + children: [ Icon('notification-disabled-symbolic'), Label('Your inbox is empty'), @@ -109,22 +132,27 @@ const Placeholder = () => Revealer({ }); export default () => Box({ - className: 'notification-center', + class_name: 'notification-center', vertical: true, children: [ Header(), + Box({ - className: 'notification-wallpaper-box', + class_name: 'notification-wallpaper-box', + children: [ Scrollable({ - className: 'notification-list-box', + class_name: 'notification-list-box', hscroll: 'never', vscroll: 'automatic', + child: Box({ - className: 'notification-list', + class_name: 'notification-list', vertical: true, + children: [ NotificationList(), + Placeholder(), ], }), diff --git a/devices/wim/config/ags/js/notifications/gesture.js b/devices/wim/config/ags/js/notifications/gesture.js index 94c72098..26b84ec0 100644 --- a/devices/wim/config/ags/js/notifications/gesture.js +++ b/devices/wim/config/ags/js/notifications/gesture.js @@ -5,90 +5,97 @@ import { timeout } from 'resource:///com/github/Aylur/ags/utils.js'; import { HasNotifs } from './base.js'; -import Gtk from 'gi://Gtk'; +const { Gtk } = imports.gi; const MAX_OFFSET = 200; const OFFSCREEN = 300; const ANIM_DURATION = 500; const SLIDE_MIN_THRESHOLD = 10; +const TRANSITION = 'transition: margin 0.5s ease, opacity 0.5s ease;'; +const SQUEEZED = 'margin-bottom: -70px; margin-top: -70px;'; +const MAX_LEFT = ` + margin-left: -${Number(MAX_OFFSET + OFFSCREEN)}px; + margin-right: ${Number(MAX_OFFSET + OFFSCREEN)}px; +`; +const MAX_RIGHT = ` + margin-left: ${Number(MAX_OFFSET + OFFSCREEN)}px; + margin-right: -${Number(MAX_OFFSET + OFFSCREEN)}px; +`; + +const slideLeft = `${TRANSITION} ${MAX_LEFT} + margin-top: 0px; + margin-bottom: 0px; + opacity: 0;`; +const squeezeLeft = `${TRANSITION} ${MAX_LEFT} ${SQUEEZED} opacity: 0;`; + +const slideRight = `${TRANSITION} ${MAX_RIGHT} + margin-top: 0px; + margin-bottom: 0px; + opacity: 0;`; +const squeezeRight = `${TRANSITION} ${MAX_RIGHT} ${SQUEEZED} opacity: 0;`; + +const defaultStyle = `${TRANSITION} margin: unset; opacity: 1;`; export default ({ id, slideIn = 'Left', - command = () => { /**/ }, + command = () => {/**/}, ...props }) => { const widget = EventBox({ ...props, cursor: 'grab', - onHover: (self) => { - if (!self._hovered) { - self._hovered = true; + on_hover: (self) => { + if (!self.attribute.hovered) { + self.attribute.hovered = true; } }, - onHoverLost: (self) => { - if (self._hovered) { - self._hovered = false; + on_hover_lost: (self) => { + if (self.attribute.hovered) { + self.attribute.hovered = false; } }, + + attribute: { + dragging: false, + hovered: false, + ready: false, + id, + + /** @param {'Left'|'Right'} side */ + slideAway: (side) => { + // Slide away + // @ts-expect-error + widget.child.setCss(side === 'Left' ? slideLeft : slideRight); + + // Make it uninteractable + widget.sensitive = false; + + timeout(ANIM_DURATION - 100, () => { + // Reduce height after sliding away + // @ts-expect-error + widget.child?.setCss(side === 'Left' ? + squeezeLeft : + squeezeRight); + + timeout(ANIM_DURATION, () => { + // Kill notif and update HasNotifs after anim is done + command(); + + HasNotifs.value = Notifications + .notifications.length > 0; + + // @ts-expect-error + widget.get_parent()?.remove; + }); + }); + }, + }, }); - // Properties - widget._dragging = false; - widget._hovered = false; - widget._id = id; - widget.ready = false; - const gesture = Gtk.GestureDrag.new(widget); - const TRANSITION = 'transition: margin 0.5s ease, opacity 0.5s ease;'; - const SQUEEZED = 'margin-bottom: -70px; margin-top: -70px;'; - const MAX_LEFT = ` - margin-left: -${Number(MAX_OFFSET + OFFSCREEN)}px; - margin-right: ${Number(MAX_OFFSET + OFFSCREEN)}px; - `; - const MAX_RIGHT = ` - margin-left: ${Number(MAX_OFFSET + OFFSCREEN)}px; - margin-right: -${Number(MAX_OFFSET + OFFSCREEN)}px; - `; - - const slideLeft = `${TRANSITION} ${MAX_LEFT} - margin-top: 0px; - margin-bottom: 0px; - opacity: 0;`; - const squeezeLeft = `${TRANSITION} ${MAX_LEFT} ${SQUEEZED} opacity: 0;`; - - const slideRight = `${TRANSITION} ${MAX_RIGHT} - margin-top: 0px; - margin-bottom: 0px; - opacity: 0;`; - const squeezeRight = `${TRANSITION} ${MAX_RIGHT} ${SQUEEZED} opacity: 0;`; - - const defaultStyle = `${TRANSITION} margin: unset; opacity: 1;`; - - - // Notif methods - widget.slideAway = (side) => { - // Slide away - widget.child.setCss(side === 'Left' ? slideLeft : slideRight); - - // Makie it uninteractable - widget.sensitive = false; - - timeout(ANIM_DURATION - 100, () => { - // Reduce height after sliding away - widget.child?.setCss(side === 'Left' ? squeezeLeft : squeezeRight); - - timeout(ANIM_DURATION, () => { - // Kill notif and update HasNotifs after anim is done - command(); - HasNotifs.value = Notifications.notifications.length > 0; - widget.get_parent()?.remove(widget); - }); - }); - }; - widget.add(Box({ css: squeezeLeft, setup: (self) => { @@ -123,14 +130,16 @@ export default ({ } // Put a threshold on if a click is actually dragging - widget._dragging = Math.abs(offset) > SLIDE_MIN_THRESHOLD; + widget.attribute.dragging = + Math.abs(offset) > SLIDE_MIN_THRESHOLD; + widget.cursor = 'grabbing'; }, 'drag-update') // On drag end .hook(gesture, () => { // Make it slide in on init - if (!widget.ready) { + if (!widget.attribute.ready) { // Reverse of slideAway, so it started at squeeze, then we go to slide self.setCss(slideIn === 'Left' ? slideLeft : @@ -140,7 +149,7 @@ export default ({ // Then we go to center self.setCss(defaultStyle); timeout(ANIM_DURATION, () => { - widget.ready = true; + widget.attribute.ready = true; }); }); @@ -152,16 +161,16 @@ export default ({ // If crosses threshold after letting go, slide away if (Math.abs(offset) > MAX_OFFSET) { if (offset > 0) { - widget.slideAway('Right'); + widget.attribute.slideAway('Right'); } else { - widget.slideAway('Left'); + widget.attribute.slideAway('Left'); } } else { self.setCss(defaultStyle); widget.cursor = 'grab'; - widget._dragging = false; + widget.attribute.dragging = false; } }, 'drag-end'); }, diff --git a/devices/wim/config/ags/js/notifications/main.js b/devices/wim/config/ags/js/notifications/main.js index 9b1f0a27..1528c4be 100644 --- a/devices/wim/config/ags/js/notifications/main.js +++ b/devices/wim/config/ags/js/notifications/main.js @@ -16,7 +16,6 @@ export const NotifPopups = () => PopupWindow({ const TOP_MARGIN = 6; -// FIXME: opens at wrong place export const NotifCenter = () => PopupWindow({ name: 'notification-center', anchor: ['top', 'right'], diff --git a/devices/wim/config/ags/js/notifications/popup.js b/devices/wim/config/ags/js/notifications/popup.js index 00e8af70..c287d59a 100644 --- a/devices/wim/config/ags/js/notifications/popup.js +++ b/devices/wim/config/ags/js/notifications/popup.js @@ -14,6 +14,7 @@ export default () => Box({ vertical: true, setup: (self) => { + /** @param {number} id */ const addPopup = (id) => { if (!id) { return; @@ -21,38 +22,49 @@ export default () => Box({ const notif = Notifications.getNotification(id); - const NewNotif = Notification({ - notif, - command: () => notif.dismiss(), - }); + if (notif) { + const NewNotif = Notification({ + notif, + command: () => notif.dismiss(), + }); - if (NewNotif) { - // Use this instead of add to put it at the top - self.pack_end(NewNotif, false, false, 0); - self.show_all(); + if (NewNotif) { + // Use this instead of add to put it at the top + self.pack_end(NewNotif, false, false, 0); + self.show_all(); + } } }; + /** + * @param {number} id + * @param {boolean} force + */ const handleDismiss = (id, force = false) => { - const notif = self.children.find((ch) => ch._id === id); + // @ts-expect-error + const notif = self.children.find((ch) => ch.attribute.id === id); if (!notif) { return; } // If notif isn't hovered or was closed, slide away - if (!notif._hovered || force) { - notif.slideAway('Left'); + // @ts-expect-error + if (!notif.attribute.hovered || force) { + // @ts-expect-error + notif.attribute.slideAway('Left'); } // If notif is hovered, delay close - else if (notif._hovered) { - notif.interval = interval(DELAY, () => { - if (!notif._hovered && notif.interval) { - notif.slideAway('Left'); + // @ts-expect-error + else if (notif.attribute.hovered) { + const intervalId = interval(DELAY, () => { + // @ts-expect-error + if (!notif.attribute.hovered && intervalId) { + // @ts-expect-error + notif.attribute.slideAway('Left'); - GLib.source_remove(notif.interval); - notif.interval = null; + GLib.source_remove(intervalId); } }); }