From b85542091d24d21d38ea366de495b0db016e89c6 Mon Sep 17 00:00:00 2001 From: matt1432 Date: Mon, 18 Dec 2023 18:00:30 -0500 Subject: [PATCH] refactor(ags): start type checking --- .gitignore | 1 + devices/wim/config/ags/.eslintrc.json | 2 - .../wim/config/ags/js/applauncher/app-item.js | 36 ++- devices/wim/config/ags/js/applauncher/main.js | 28 +- .../wim/config/ags/js/bar/buttons/audio.js | 55 ++-- .../wim/config/ags/js/bar/buttons/battery.js | 43 ++- .../config/ags/js/bar/buttons/bluetooth.js | 73 +++-- .../config/ags/js/bar/buttons/brightness.js | 46 ++-- .../wim/config/ags/js/bar/buttons/clock.js | 26 +- .../ags/js/bar/buttons/current-window.js | 24 +- .../wim/config/ags/js/bar/buttons/heart.js | 6 +- .../ags/js/bar/buttons/keyboard-layout.js | 74 ++--- .../wim/config/ags/js/bar/buttons/network.js | 94 +++---- .../config/ags/js/bar/buttons/notif-button.js | 37 ++- .../config/ags/js/bar/buttons/osk-toggle.js | 20 +- .../ags/js/bar/buttons/quick-settings.js | 9 +- .../wim/config/ags/js/bar/buttons/systray.js | 44 +-- .../ags/js/bar/buttons/tablet-toggle.js | 15 +- .../config/ags/js/bar/buttons/workspaces.js | 115 ++++---- devices/wim/config/ags/js/bar/fullscreen.js | 13 +- devices/wim/config/ags/js/bar/main.js | 9 +- .../config/ags/js/corners/screen-corners.js | 3 +- devices/wim/config/ags/js/date.js | 36 +-- .../wim/config/ags/js/media-player/gesture.js | 107 ++++---- .../wim/config/ags/js/media-player/mpris.js | 254 +++++++++++------- .../wim/config/ags/js/media-player/player.js | 144 ++++++---- devices/wim/config/ags/js/misc/cursorbox.js | 38 ++- devices/wim/config/ags/js/misc/persist.js | 15 +- devices/wim/config/ags/package.json | 24 +- .../config/ags/scss/widgets/traybuttons.scss | 2 +- devices/wim/config/ags/tsconfig.json | 20 ++ 31 files changed, 774 insertions(+), 639 deletions(-) create mode 100644 devices/wim/config/ags/tsconfig.json diff --git a/.gitignore b/.gitignore index 9321474..53ce5d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.egg-info *.temp *node_modules/ +*types/ *package-lock.json **/ags/style.css result* diff --git a/devices/wim/config/ags/.eslintrc.json b/devices/wim/config/ags/.eslintrc.json index 451dcd9..97de06a 100644 --- a/devices/wim/config/ags/.eslintrc.json +++ b/devices/wim/config/ags/.eslintrc.json @@ -31,7 +31,6 @@ "curly": ["warn"], "default-case-last": ["warn"], "default-param-last": ["error"], - "dot-notation": ["warn", { "allowPattern": ".*-|_.*" }], "eqeqeq": ["error", "smart"], "func-names": ["warn", "never"], "func-style": ["warn", "expression"], @@ -53,7 +52,6 @@ "ignoreDefaultValues": true }], "no-multi-assign": ["error"], - "no-negated-condition": ["warn"], "no-new": ["error"], "no-new-func": ["error"], "no-new-wrappers": ["error"], diff --git a/devices/wim/config/ags/js/applauncher/app-item.js b/devices/wim/config/ags/js/applauncher/app-item.js index 31e49e1..63c6a5a 100644 --- a/devices/wim/config/ags/js/applauncher/app-item.js +++ b/devices/wim/config/ags/js/applauncher/app-item.js @@ -7,15 +7,24 @@ import { lookUpIcon } from 'resource:///com/github/Aylur/ags/utils.js'; import EventBox from '../misc/cursorbox.js'; +/** + * @param {import('types/service/applications.js').Application} app + */ export default (app) => { - const icon = Icon({ - icon: lookUpIcon(app.icon_name) ? - app.icon_name : - app.app.get_string('Icon') === 'nix-snowflake' ? - '' : - app.app.get_string('Icon'), - size: 42, - }); + const icon = Icon({ size: 42 }); + const iconString = app.app.get_string('Icon'); + + if (app.icon_name) { + if (lookUpIcon(app.icon_name)) { + icon.icon = app.icon_name; + } + else if (iconString && iconString !== 'nix-snowflake') { + icon.icon = iconString; + } + else { + icon.icon = ''; + } + } const textBox = Box({ vertical: true, @@ -46,14 +55,13 @@ export default (app) => { hexpand: true, class_name: 'app', - setup: (self) => { - self.app = app; - }, + attribute: { app }, - onPrimaryClickRelease: () => { + onPrimaryClickRelease: (self) => { App.closeWindow('applauncher'); - Hyprland.sendMessage(`dispatch exec sh -c ${app.executable}`); - ++app.frequency; + Hyprland.sendMessage(`dispatch exec sh -c + ${self.attribute.app.executable}`); + ++self.attribute.app.frequency; }, child: Box({ diff --git a/devices/wim/config/ags/js/applauncher/main.js b/devices/wim/config/ags/js/applauncher/main.js index 41be888..d9d1c81 100644 --- a/devices/wim/config/ags/js/applauncher/main.js +++ b/devices/wim/config/ags/js/applauncher/main.js @@ -14,17 +14,20 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => { let fzfResults; const list = ListBox(); + /** @param {String} text */ const setSort = (text) => { const fzf = new Fzf(Applications.list, { selector: (app) => app.name, - tiebreakers: [(a, b) => b._frequency - - a._frequency], + tiebreakers: [(a, b) => b.frequency - + a.frequency], }); fzfResults = fzf.find(text); list.set_sort_func((a, b) => { - const row1 = a.get_children()[0]?.app.name; - const row2 = b.get_children()[0]?.app.name; + // @ts-expect-error + const row1 = a.get_children()[0]?.attribute.app.name; + // @ts-expect-error + const row2 = b.get_children()[0]?.attribute.app.name; if (!row1 || !row2) { return 0; @@ -51,9 +54,10 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => { makeNewChildren(); + // FIXME: always visible const placeholder = Label({ label: " Couldn't find a match", - className: 'placeholder', + class_name: 'placeholder', }); const entry = Entry({ @@ -73,17 +77,23 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => { }, on_change: ({ text }) => { + if (!text) { + return; + } + setSort(text); let visibleApps = 0; list.get_children().forEach((row) => { + // @ts-expect-error row.changed(); + // @ts-expect-error const item = row.get_children()[0]; - if (item?.app) { + if (item?.attribute.app) { const isMatching = fzfResults.find((r) => { - return r.item.name === item.app.name; + return r.item.name === item.attribute.app.name; }); row.visible = isMatching; @@ -98,7 +108,7 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => { }); return Box({ - className: 'applauncher', + class_name: 'applauncher', vertical: true, setup: (self) => { @@ -120,7 +130,7 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => { children: [ Box({ - className: 'header', + class_name: 'header', children: [ Icon('preferences-system-search-symbolic'), entry, diff --git a/devices/wim/config/ags/js/bar/buttons/audio.js b/devices/wim/config/ags/js/bar/buttons/audio.js index 643146d..0291d7d 100644 --- a/devices/wim/config/ags/js/bar/buttons/audio.js +++ b/devices/wim/config/ags/js/bar/buttons/audio.js @@ -5,54 +5,49 @@ import { Label, Box, EventBox, Icon, Revealer } from 'resource:///com/github/Ayl import { SpeakerIcon } from '../../misc/audio-icons.js'; import Separator from '../../misc/separator.js'; - -const SpeakerIndicator = (props) => Icon({ - ...props, - binds: [['icon', SpeakerIcon, 'value']], -}); - -const SpeakerPercentLabel = (props) => Label({ - ...props, - setup: (self) => { - self.hook(Audio, (label) => { - if (Audio.speaker) { - label.label = `${Math.round(Audio.speaker.volume * 100)}%`; - } - }, 'speaker-changed'); - }, -}); - const SPACING = 5; + export default () => { - const rev = Revealer({ + const icon = Icon({ + icon: SpeakerIcon.bind(), + }); + + const hoverRevLabel = Revealer({ transition: 'slide_right', + child: Box({ + children: [ Separator(SPACING), - SpeakerPercentLabel(), + + Label().hook(Audio, (self) => { + if (Audio.speaker?.volume) { + self.label = + `${Math.round(Audio.speaker?.volume * 100)}%`; + } + }, 'speaker-changed'), ], }), }); const widget = EventBox({ - onHover: () => { - rev.revealChild = true; + on_hover: () => { + hoverRevLabel.reveal_child = true; }, - onHoverLost: () => { - rev.revealChild = false; + on_hover_lost: () => { + hoverRevLabel.reveal_child = false; }, - child: Box({ - className: 'audio', - children: [ - SpeakerIndicator(), - rev, + child: Box({ + class_name: 'audio', + + children: [ + icon, + hoverRevLabel, ], }), }); - widget.rev = rev; - return widget; }; diff --git a/devices/wim/config/ags/js/bar/buttons/battery.js b/devices/wim/config/ags/js/bar/buttons/battery.js index 4a7aafd..a0da5f2 100644 --- a/devices/wim/config/ags/js/bar/buttons/battery.js +++ b/devices/wim/config/ags/js/bar/buttons/battery.js @@ -5,41 +5,28 @@ import { Label, Icon, Box } from 'resource:///com/github/Aylur/ags/widget.js'; import Separator from '../../misc/separator.js'; const LOW_BATT = 20; +const SPACING = 5; -const Indicator = () => Icon({ - className: 'battery-indicator', +export default () => Box({ + class_name: 'toggle-off battery', - binds: [['icon', Battery, 'icon-name']], - - setup: (self) => { - self.hook(Battery, () => { + children: [ + Icon({ + class_name: 'battery-indicator', + // @ts-expect-error + icon: Battery.bind('icon_name'), + }).hook(Battery, (self) => { self.toggleClassName('charging', Battery.charging); self.toggleClassName('charged', Battery.charged); self.toggleClassName('low', Battery.percent < LOW_BATT); - }); - }, -}); + }), -const LevelLabel = (props) => Label({ - ...props, - className: 'label', - - setup: (self) => { - self.hook(Battery, () => { - self.label = `${Battery.percent}%`; - }); - }, -}); - -const SPACING = 5; - -export default () => Box({ - className: 'toggle-off battery', - - children: [ - Indicator(), Separator(SPACING), - LevelLabel(), + + Label({ + label: Battery.bind('percent') + .transform((v) => `${v}%`), + }), ], }); diff --git a/devices/wim/config/ags/js/bar/buttons/bluetooth.js b/devices/wim/config/ags/js/bar/buttons/bluetooth.js index 4ee8643..4c29603 100644 --- a/devices/wim/config/ags/js/bar/buttons/bluetooth.js +++ b/devices/wim/config/ags/js/bar/buttons/bluetooth.js @@ -1,68 +1,59 @@ +// @ts-expect-error import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js'; import { Label, Box, EventBox, Icon, Revealer } from 'resource:///com/github/Aylur/ags/widget.js'; import Separator from '../../misc/separator.js'; - -const Indicator = (props) => Icon({ - ...props, - setup: (self) => { - self.hook(Bluetooth, () => { - if (Bluetooth.enabled) { - self.icon = Bluetooth.connectedDevices[0] ? - Bluetooth.connectedDevices[0].iconName : - 'bluetooth-active-symbolic'; - } - else { - self.icon = 'bluetooth-disabled-symbolic'; - } - }); - }, -}); - -const ConnectedLabel = (props) => Label({ - ...props, - setup: (self) => { - self.hook(Bluetooth, () => { - self.label = Bluetooth.connectedDevices[0] ? - `${Bluetooth.connectedDevices[0]}` : - 'Disconnected'; - }, 'notify::connected-devices'); - }, -}); - const SPACING = 5; + export default () => { - const rev = Revealer({ + const icon = Icon().hook(Bluetooth, (self) => { + if (Bluetooth.enabled) { + self.icon = Bluetooth.connectedDevices[0] ? + Bluetooth.connectedDevices[0].iconName : + 'bluetooth-active-symbolic'; + } + else { + self.icon = 'bluetooth-disabled-symbolic'; + } + }); + + const hoverRevLabel = Revealer({ transition: 'slide_right', + child: Box({ + children: [ Separator(SPACING), - ConnectedLabel(), + + Label().hook(Bluetooth, (self) => { + self.label = Bluetooth.connectedDevices[0] ? + `${Bluetooth.connectedDevices[0]}` : + 'Disconnected'; + }, 'notify::connected-devices'), ], }), }); const widget = EventBox({ - onHover: () => { - rev.revealChild = true; + on_hover: () => { + hoverRevLabel.reveal_child = true; }, - onHoverLost: () => { - rev.revealChild = false; + on_hover_lost: () => { + hoverRevLabel.reveal_child = false; }, - child: Box({ - className: 'bluetooth', - children: [ - Indicator(), - rev, + child: Box({ + class_name: 'bluetooth', + + children: [ + icon, + hoverRevLabel, ], }), }); - widget.rev = rev; - return widget; }; diff --git a/devices/wim/config/ags/js/bar/buttons/brightness.js b/devices/wim/config/ags/js/bar/buttons/brightness.js index 3d9b427..5a4568e 100644 --- a/devices/wim/config/ags/js/bar/buttons/brightness.js +++ b/devices/wim/config/ags/js/bar/buttons/brightness.js @@ -6,48 +6,44 @@ import Separator from '../../misc/separator.js'; const SPACING = 5; -const Indicator = (props) => Icon({ - ...props, - binds: [['icon', Brightness, 'screen-icon']], -}); - -const BrightnessPercentLabel = (props) => Label({ - ...props, - setup: (self) => { - self.hook(Brightness, () => { - self.label = `${Math.round(Brightness.screen * 100)}%`; - }, 'screen'); - }, -}); - export default () => { - const rev = Revealer({ + const icon = Icon({ + // @ts-expect-error + icon: Brightness.bind('screenIcon'), + }); + + const hoverRevLabel = Revealer({ transition: 'slide_right', + child: Box({ + children: [ Separator(SPACING), - BrightnessPercentLabel(), + + Label().hook(Brightness, (self) => { + self.label = `${Math.round(Brightness.screen * 100)}%`; + }, 'screen'), ], }), }); const widget = EventBox({ - onHover: () => { - rev.revealChild = true; + on_hover: () => { + hoverRevLabel.reveal_child = true; }, - onHoverLost: () => { - rev.revealChild = false; + on_hover_lost: () => { + hoverRevLabel.reveal_child = false; }, + child: Box({ - className: 'brightness', + class_name: 'brightness', + children: [ - Indicator(), - rev, + icon, + hoverRevLabel, ], }), }); - widget.rev = rev; - return widget; }; diff --git a/devices/wim/config/ags/js/bar/buttons/clock.js b/devices/wim/config/ags/js/bar/buttons/clock.js index 1810766..da6ca24 100644 --- a/devices/wim/config/ags/js/bar/buttons/clock.js +++ b/devices/wim/config/ags/js/bar/buttons/clock.js @@ -2,26 +2,11 @@ import App from 'resource:///com/github/Aylur/ags/app.js'; import { Label } from 'resource:///com/github/Aylur/ags/widget.js'; -import GLib from 'gi://GLib'; -const { DateTime } = GLib; +const { DateTime } = imports.gi.GLib; import EventBox from '../../misc/cursorbox.js'; -const ClockModule = () => Label({ - className: 'clock', - - setup: (self) => { - self.poll(1000, () => { - const time = DateTime.new_now_local(); - - self.label = time.format('%a. ') + - time.get_day_of_month() + - time.format(' %b. %H:%M'); - }); - }, -}); - export default () => EventBox({ className: 'toggle-off', @@ -35,5 +20,12 @@ export default () => EventBox({ }); }, - child: ClockModule(), + child: Label({ class_name: 'clock' }) + .poll(1000, (self) => { + const time = DateTime.new_now_local(); + + self.label = time.format('%a. ') + + time.get_day_of_month() + + time.format(' %b. %H:%M'); + }), }); diff --git a/devices/wim/config/ags/js/bar/buttons/current-window.js b/devices/wim/config/ags/js/bar/buttons/current-window.js index 49b045a..0f641db 100644 --- a/devices/wim/config/ags/js/bar/buttons/current-window.js +++ b/devices/wim/config/ags/js/bar/buttons/current-window.js @@ -11,27 +11,23 @@ export default () => Box({ children: [ Separator(SPACING / 2), - Icon({ - size: 30, - setup: (self) => { - self.hook(Hyprland.active.client, () => { - const app = Applications - .query(Hyprland.active.client.class)[0]; + Icon({ size: 30 }) + .hook(Hyprland.active.client, (self) => { + const app = Applications + .query(Hyprland.active.client.class)[0]; - if (app) { - self.icon = app.iconName; - self.visible = Hyprland.active.client.title !== ''; - } - }); - }, - }), + if (app) { + self.icon = app.icon_name; + self.visible = Hyprland.active.client.title !== ''; + } + }), Separator(SPACING), Label({ css: 'color: #CBA6F7; font-size: 18px', truncate: 'end', - binds: [['label', Hyprland.active.client, 'title']], + label: Hyprland.active.client.bind('title'), }), ], }); diff --git a/devices/wim/config/ags/js/bar/buttons/heart.js b/devices/wim/config/ags/js/bar/buttons/heart.js index c4e25c9..c72b53e 100644 --- a/devices/wim/config/ags/js/bar/buttons/heart.js +++ b/devices/wim/config/ags/js/bar/buttons/heart.js @@ -5,7 +5,7 @@ import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import EventBox from '../../misc/cursorbox.js'; import Persist from '../../misc/persist.js'; -const HeartState = Variable(); +const HeartState = Variable(''); Persist({ name: 'heart', @@ -22,7 +22,7 @@ export default () => EventBox({ }, child: Label({ - className: 'heart-toggle', - binds: [['label', HeartState, 'value']], + class_name: 'heart-toggle', + label: HeartState.bind(), }), }); diff --git a/devices/wim/config/ags/js/bar/buttons/keyboard-layout.js b/devices/wim/config/ags/js/bar/buttons/keyboard-layout.js index c02c763..3da7566 100644 --- a/devices/wim/config/ags/js/bar/buttons/keyboard-layout.js +++ b/devices/wim/config/ags/js/bar/buttons/keyboard-layout.js @@ -8,69 +8,75 @@ const DEFAULT_KB = 'at-translated-set-2-keyboard'; const SPACING = 4; -const Indicator = () => Label({ - css: 'font-size: 20px;', - setup: (self) => { - self.hook(Hyprland, (_, _n, layout) => { - if (layout) { - if (layout === 'error') { - return; - } +/** + * @param {Label} self + * @param {string} layout + * @param {void} _ + */ +const getKbdLayout = (self, _, layout) => { + if (layout) { + if (layout === 'error') { + return; + } - const shortName = layout.match(/\(([A-Za-z]+)\)/); + const shortName = layout.match(/\(([A-Za-z]+)\)/); - self.label = shortName ? shortName[1] : layout; - } - else { - // At launch, kb layout is undefined - Hyprland.sendMessage('j/devices').then((obj) => { - const kb = JSON.parse(obj).keyboards - .find((val) => val.name === DEFAULT_KB); + // @ts-expect-error + self.label = shortName ? shortName[1] : layout; + } + else { + // At launch, kb layout is undefined + Hyprland.sendMessage('j/devices').then((obj) => { + const kb = Array.from(JSON.parse(obj).keyboards) + .find((v) => v.name === DEFAULT_KB); - layout = kb['active_keymap']; + layout = kb['active_keymap']; - const shortName = layout - .match(/\(([A-Za-z]+)\)/); + const shortName = layout + .match(/\(([A-Za-z]+)\)/); - self.label = shortName ? shortName[1] : layout; - }).catch(print); - } - }, 'keyboard-layout'); - }, -}); + // @ts-expect-error + self.label = shortName ? shortName[1] : layout; + }).catch(print); + } +}; export default () => { - const rev = Revealer({ + const hoverRevLabel = Revealer({ transition: 'slide_right', + child: Box({ + children: [ Separator(SPACING), - Indicator(), + + Label({ css: 'font-size: 20px;' }) + .hook(Hyprland, getKbdLayout, 'keyboard-layout'), ], }), }); const widget = EventBox({ - onHover: () => { - rev.revealChild = true; + on_hover: () => { + hoverRevLabel.reveal_child = true; }, - onHoverLost: () => { - rev.revealChild = false; + on_hover_lost: () => { + hoverRevLabel.reveal_child = false; }, + child: Box({ css: 'padding: 0 10px; margin-right: -10px;', + children: [ Icon({ icon: 'input-keyboard-symbolic', size: 20, }), - rev, + hoverRevLabel, ], }), }); - widget.rev = rev; - return widget; }; diff --git a/devices/wim/config/ags/js/bar/buttons/network.js b/devices/wim/config/ags/js/bar/buttons/network.js index c2b5f2b..f5214e8 100644 --- a/devices/wim/config/ags/js/bar/buttons/network.js +++ b/devices/wim/config/ags/js/bar/buttons/network.js @@ -4,76 +4,66 @@ import { Label, Box, EventBox, Icon, Revealer } from 'resource:///com/github/Ayl import Separator from '../../misc/separator.js'; - -const Indicator = (props) => Icon({ - ...props, - setup: (self) => { - self.hook(Network, () => { - if (Network.wifi.internet === 'connected' || - Network.wifi.internet === 'connecting') { - self.icon = Network.wifi.iconName; - } - else if (Network.wired.internet === 'connected' || - Network.wired.internet === 'connecting') { - self.icon = Network.wired.iconName; - } - else { - self.icon = Network.wifi.iconName; - } - }); - }, -}); - -const APLabel = (props) => Label({ - ...props, - setup: (self) => { - self.hook(Network, () => { - if (Network.wifi.internet === 'connected' || - Network.wifi.internet === 'connecting') { - self.label = Network.wifi.ssid; - } - else if (Network.wired.internet === 'connected' || - Network.wired.internet === 'connecting') { - self.label = 'Connected'; - } - else { - self.label = 'Disconnected'; - } - }); - }, -}); - const SPACING = 5; + export default () => { - const rev = Revealer({ + const indicator = Icon().hook(Network, (self) => { + if (Network.wifi.internet === 'connected' || + Network.wifi.internet === 'connecting') { + self.icon = Network.wifi.icon_name; + } + else if (Network.wired.internet === 'connected' || + Network.wired.internet === 'connecting') { + self.icon = Network.wired.icon_name; + } + else { + self.icon = Network.wifi.icon_name; + } + }); + + const label = Label().hook(Network, (self) => { + if (Network.wifi.internet === 'connected' || + Network.wifi.internet === 'connecting') { + self.label = Network.wifi.ssid; + } + else if (Network.wired.internet === 'connected' || + Network.wired.internet === 'connecting') { + self.label = 'Connected'; + } + else { + self.label = 'Disconnected'; + } + }); + + const hoverRevLabel = Revealer({ transition: 'slide_right', child: Box({ children: [ Separator(SPACING), - APLabel(), + label, ], }), }); const widget = EventBox({ - onHover: () => { - rev.revealChild = true; + on_hover: () => { + hoverRevLabel.reveal_child = true; }, - onHoverLost: () => { - rev.revealChild = false; + on_hover_lost: () => { + hoverRevLabel.reveal_child = false; }, - child: Box({ - className: 'network', - children: [ - Indicator(), - rev, + child: Box({ + class_name: 'network', + + children: [ + indicator, + + hoverRevLabel, ], }), }); - widget.rev = rev; - return widget; }; diff --git a/devices/wim/config/ags/js/bar/buttons/notif-button.js b/devices/wim/config/ags/js/bar/buttons/notif-button.js index 2038bf4..3a4dfd9 100644 --- a/devices/wim/config/ags/js/bar/buttons/notif-button.js +++ b/devices/wim/config/ags/js/bar/buttons/notif-button.js @@ -13,8 +13,11 @@ export default () => EventBox({ className: 'toggle-off', onPrimaryClickRelease: (self) => { - App.getWindow('notification-center') - .setXPos(self.get_allocation(), 'right'); + // @ts-expect-error + App.getWindow('notification-center')?.setXPos( + self.get_allocation(), + 'right', + ); App.toggleWindow('notification-center'); }, @@ -28,31 +31,27 @@ export default () => EventBox({ }, child: CenterBox({ - className: 'notif-panel', + class_name: 'notif-panel', center_widget: Box({ children: [ - Icon({ - setup: (self) => { - self.hook(Notifications, () => { - if (Notifications.dnd) { - self.icon = 'notification-disabled-symbolic'; - } - else if (Notifications.notifications.length > 0) { - self.icon = 'notification-new-symbolic'; - } - else { - self.icon = 'notification-symbolic'; - } - }); - }, + Icon().hook(Notifications, (self) => { + if (Notifications.dnd) { + self.icon = 'notification-disabled-symbolic'; + } + else if (Notifications.notifications.length > 0) { + self.icon = 'notification-new-symbolic'; + } + else { + self.icon = 'notification-symbolic'; + } }), Separator(SPACING), Label({ - binds: [['label', Notifications, 'notifications', - (n) => String(n.length)]], + label: Notifications.bind('notifications') + .transform((n) => String(n.length)), }), ], }), diff --git a/devices/wim/config/ags/js/bar/buttons/osk-toggle.js b/devices/wim/config/ags/js/bar/buttons/osk-toggle.js index bda505b..58d29db 100644 --- a/devices/wim/config/ags/js/bar/buttons/osk-toggle.js +++ b/devices/wim/config/ags/js/bar/buttons/osk-toggle.js @@ -1,4 +1,4 @@ -import { Box, Label } from 'resource:///com/github/Aylur/ags/widget.js'; +import { Label } from 'resource:///com/github/Aylur/ags/widget.js'; import Tablet from '../../../services/tablet.js'; import EventBox from '../../misc/cursorbox.js'; @@ -9,14 +9,12 @@ export default () => EventBox({ onPrimaryClickRelease: () => Tablet.toggleOsk(), - setup: (self) => { - self.hook(Tablet, () => { - self.toggleClassName('toggle-on', Tablet.oskState); - }, 'osk-toggled'); - }, - - child: Box({ - className: 'osk-toggle', - children: [Label(' 󰌌 ')], + child: Label({ + class_name: 'osk-toggle', + xalign: 0.6, + label: '󰌌 ', }), -}); + +}).hook(Tablet, (self) => { + self.toggleClassName('toggle-on', Tablet.oskState); +}, 'osk-toggled'); diff --git a/devices/wim/config/ags/js/bar/buttons/quick-settings.js b/devices/wim/config/ags/js/bar/buttons/quick-settings.js index e7e566e..dab23e2 100644 --- a/devices/wim/config/ags/js/bar/buttons/quick-settings.js +++ b/devices/wim/config/ags/js/bar/buttons/quick-settings.js @@ -20,8 +20,11 @@ export default () => EventBox({ onHoverLost: () => { /**/ }, onPrimaryClickRelease: (self) => { - App.getWindow('notification-center') - .setXPos(self.get_allocation(), 'right'); + // @ts-expect-error + App.getWindow('notification-center').setXPos( + self.get_allocation(), + 'right', + ); App.toggleWindow('quick-settings'); }, @@ -35,7 +38,7 @@ export default () => EventBox({ }, child: Box({ - className: 'quick-settings-toggle', + class_name: 'quick-settings-toggle', vertical: false, children: [ Separator(SPACING), diff --git a/devices/wim/config/ags/js/bar/buttons/systray.js b/devices/wim/config/ags/js/bar/buttons/systray.js index 1c18d0f..f75ac1b 100644 --- a/devices/wim/config/ags/js/bar/buttons/systray.js +++ b/devices/wim/config/ags/js/bar/buttons/systray.js @@ -9,35 +9,40 @@ const REVEAL_DURATION = 500; const SPACING = 12; +/** @param {import('types/service/systemtray').TrayItem} item */ const SysTrayItem = (item) => { if (item.id === 'spotify-client') { return; } return MenuItem({ + // @ts-expect-error + submenu: item.menu, + tooltip_markup: item.bind('tooltip_markup'), + child: Revealer({ transition: 'slide_right', - transitionDuration: REVEAL_DURATION, + transition_duration: REVEAL_DURATION, child: Icon({ size: 24, - binds: [['icon', item, 'icon']], + icon: item.bind('icon'), }), }), - submenu: item.menu, - binds: [['tooltipMarkup', item, 'tooltip-markup']], }); }; const SysTray = () => MenuBar({ - setup: (self) => { - self.items = new Map(); + attribute: { + items: new Map(), + }, + setup: (self) => { self .hook(SystemTray, (_, id) => { const item = SystemTray.getItem(id); - if (self.items.has(id) || !item) { + if (self.attribute.items.has(id) || !item) { return; } @@ -48,21 +53,22 @@ const SysTray = () => MenuBar({ return; } - self.items.set(id, w); - self.add(w); + self.attribute.items.set(id, w); + self.child = w; self.show_all(); - w.child.revealChild = true; + // @ts-expect-error + w.child.reveal_child = true; }, 'added') .hook(SystemTray, (_, id) => { - if (!self.items.has(id)) { + if (!self.attribute.items.has(id)) { return; } - self.items.get(id).child.revealChild = false; + self.attribute.items.get(id).child.reveal_child = false; timeout(REVEAL_DURATION, () => { - self.items.get(id).destroy(); - self.items.delete(id); + self.attribute.items.get(id).destroy(); + self.attribute.items.delete(id); }); }, 'removed'); }, @@ -74,21 +80,17 @@ export default () => { return Revealer({ transition: 'slide_right', - setup: (self) => { - self.hook(SystemTray, () => { - self.revealChild = systray.get_children().length > 0; - }); - }, - child: Box({ children: [ Box({ - className: 'sys-tray', + class_name: 'sys-tray', children: [systray], }), Separator(SPACING), ], }), + }).hook(SystemTray, (self) => { + self.reveal_child = systray.get_children().length > 0; }); }; diff --git a/devices/wim/config/ags/js/bar/buttons/tablet-toggle.js b/devices/wim/config/ags/js/bar/buttons/tablet-toggle.js index 3b5240f..b939e35 100644 --- a/devices/wim/config/ags/js/bar/buttons/tablet-toggle.js +++ b/devices/wim/config/ags/js/bar/buttons/tablet-toggle.js @@ -5,19 +5,16 @@ import EventBox from '../../misc/cursorbox.js'; export default () => EventBox({ - className: 'toggle-off', + class_name: 'toggle-off', onPrimaryClickRelease: () => Tablet.toggleMode(), - setup: (self) => { - self.hook(Tablet, () => { - self.toggleClassName('toggle-on', Tablet.tabletMode); - }, 'mode-toggled'); - }, - child: Box({ - className: 'tablet-toggle', + class_name: 'tablet-toggle', vertical: false, children: [Label(' 󰦧 ')], }), -}); + +}).hook(Tablet, (self) => { + self.toggleClassName('toggle-on', Tablet.tabletMode); +}, 'mode-toggled'); diff --git a/devices/wim/config/ags/js/bar/buttons/workspaces.js b/devices/wim/config/ags/js/bar/buttons/workspaces.js index 5ff53ce..e0df140 100644 --- a/devices/wim/config/ags/js/bar/buttons/workspaces.js +++ b/devices/wim/config/ags/js/bar/buttons/workspaces.js @@ -7,36 +7,52 @@ import EventBox from '../../misc/cursorbox.js'; const URGENT_DURATION = 1000; +/** @typedef {import('types/widget.js').Widget} Widget */ -const Workspace = ({ i } = {}) => { + +/** @property {number} id */ +const Workspace = ({ id }) => { return Revealer({ transition: 'slide_right', - properties: [['id', i]], + attribute: { id }, child: EventBox({ - tooltipText: `${i}`, + tooltipText: `${id}`, onPrimaryClickRelease: () => { - Hyprland.sendMessage(`dispatch workspace ${i}`); + Hyprland.sendMessage(`dispatch workspace ${id}`); }, child: Box({ vpack: 'center', - className: 'button', + class_name: 'button', setup: (self) => { - self.update = (addr) => { - const occupied = Hyprland.getWorkspace(i)?.windows > 0; + /** + * @param {Widget} _ + * @param {string|undefined} addr + */ + const update = (_, addr) => { + const workspace = Hyprland.getWorkspace(id); + const occupied = workspace && workspace['windows'] > 0; self.toggleClassName('occupied', occupied); self.toggleClassName('empty', !occupied); + if (!addr) { + return; + } + // Deal with urgent windows - if (Hyprland.getClient(addr)?.workspace.id === i) { + const client = Hyprland.getClient(addr); + const isThisUrgent = client && + client['workspace']['id'] === id; + + if (isThisUrgent) { self.toggleClassName('urgent', true); // Only show for a sec when urgent is current workspace - if (Hyprland.active.workspace.id === i) { + if (Hyprland.active.workspace.id === id) { timeout(URGENT_DURATION, () => { self.toggleClassName('urgent', false); }); @@ -45,13 +61,13 @@ const Workspace = ({ i } = {}) => { }; self - .hook(Hyprland, () => self.update()) + .hook(Hyprland, update) + // Deal with urgent windows - .hook(Hyprland, (_, a) => { - self.update(a); - }, 'urgent-window') + .hook(Hyprland, update, 'urgent-window') + .hook(Hyprland.active.workspace, () => { - if (Hyprland.active.workspace.id === i) { + if (Hyprland.active.workspace.id === id) { self.toggleClassName('urgent', false); } }); @@ -65,75 +81,80 @@ export default () => { const L_PADDING = 16; const WS_WIDTH = 30; + /** @param {Widget} self */ const updateHighlight = (self) => { const currentId = Hyprland.active.workspace.id; + // @ts-expect-error const indicators = self.get_parent().get_children()[0].child.children; - const currentIndex = indicators.findIndex((w) => w._id === currentId); + const currentIndex = Array.from(indicators) + .findIndex((w) => w.attribute.id === currentId); if (currentIndex < 0) { return; } + // @ts-expect-error self.setCss(`margin-left: ${L_PADDING + (currentIndex * WS_WIDTH)}px`); }; const highlight = Box({ vpack: 'center', hpack: 'start', - className: 'button active', - setup: (self) => { - self.hook(Hyprland.active.workspace, updateHighlight); - }, - }); + class_name: 'button active', + }).hook(Hyprland.active.workspace, updateHighlight); const widget = Overlay({ pass_through: true, overlays: [highlight], child: EventBox({ child: Box({ - className: 'workspaces', + class_name: 'workspaces', - properties: [ - ['workspaces'], + attribute: { workspaces: [] }, - ['refresh', (self) => { - self.children.forEach((rev) => { + setup: (self) => { + const refresh = () => { + Array.from(self.children).forEach((rev) => { + // @ts-expect-error rev.reveal_child = false; }); - self._workspaces.forEach((ws) => { + Array.from(self.attribute.workspaces).forEach((ws) => { ws.revealChild = true; }); - }], + }; - ['updateWorkspaces', (self) => { + const updateWorkspaces = () => { Hyprland.workspaces.forEach((ws) => { - const currentWs = self.children.find((ch) => { - return ch._id === ws.id; - }); + const currentWs = Array.from(self.children) + // @ts-expect-error + .find((ch) => ch.attribute.id === ws['id']); - if (!currentWs && ws.id > 0) { - self.add(Workspace({ i: ws.id })); + if (!currentWs && ws['id'] > 0) { + self.add(Workspace({ id: ws['id'] })); } }); self.show_all(); // Make sure the order is correct - self._workspaces.forEach((workspace, i) => { - workspace.get_parent().reorder_child(workspace, i); - }); - }], - ], - - setup: (self) => { - self.hook(Hyprland, () => { - self._workspaces = self.children.filter((ch) => { - return Hyprland.workspaces.find((ws) => { - return ws.id === ch._id; + Array.from(self.attribute.workspaces) + .forEach((workspace, i) => { + workspace.get_parent() + .reorder_child(workspace, i); }); - }).sort((a, b) => a._id - b._id); + }; - self._updateWorkspaces(self); - self._refresh(self); + self.hook(Hyprland, () => { + self.attribute.workspaces = + self.children.filter((ch) => { + return Hyprland.workspaces.find((ws) => { + // @ts-expect-error + return ws['id'] === ch.attribute.id; + }); + // @ts-expect-error + }).sort((a, b) => a.attribute.id - b.attribute.id); + + updateWorkspaces(); + refresh(); // Make sure the highlight doesn't go too far const TEMP_TIMEOUT = 10; diff --git a/devices/wim/config/ags/js/bar/fullscreen.js b/devices/wim/config/ags/js/bar/fullscreen.js index 75ecbf4..f60277e 100644 --- a/devices/wim/config/ags/js/bar/fullscreen.js +++ b/devices/wim/config/ags/js/bar/fullscreen.js @@ -4,6 +4,7 @@ import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import { Box, EventBox, Revealer, Window } from 'resource:///com/github/Aylur/ags/widget.js'; +/** @param {import('types/variable.js').Variable} variable */ const BarCloser = (variable) => Window({ name: 'bar-closer', visible: false, @@ -11,8 +12,9 @@ const BarCloser = (variable) => Window({ layer: 'overlay', child: EventBox({ - onHover: (self) => { + on_hover: (self) => { variable.value = false; + // @ts-expect-error self.get_parent().visible = false; }, @@ -22,6 +24,7 @@ const BarCloser = (variable) => Window({ }), }); +/** @param {import('types/widgets/revealer').RevealerProps} props */ export default (props) => { const Revealed = Variable(true); const barCloser = BarCloser(Revealed); @@ -38,7 +41,9 @@ export default (props) => { Hyprland.active.workspace.id, ); - Revealed.value = !workspace?.hasfullscreen; + if (workspace) { + Revealed.value = !workspace['hasfullscreen']; + } }) .hook(Hyprland, (_, fullscreen) => { @@ -50,7 +55,7 @@ export default (props) => { Revealer({ ...props, transition: 'slide_down', - revealChild: true, + reveal_child: true, binds: [['revealChild', Revealed, 'value']], }), @@ -59,7 +64,7 @@ export default (props) => { binds: [['revealChild', Revealed, 'value', (v) => !v]], child: EventBox({ - onHover: () => { + on_hover: () => { barCloser.visible = true; Revealed.value = true; }, diff --git a/devices/wim/config/ags/js/bar/main.js b/devices/wim/config/ags/js/bar/main.js index 10952d5..14573c4 100644 --- a/devices/wim/config/ags/js/bar/main.js +++ b/devices/wim/config/ags/js/bar/main.js @@ -27,10 +27,9 @@ export default () => Window({ child: BarReveal({ child: CenterBox({ css: 'margin: 6px 5px 5px 5px', - className: 'bar', - vertical: false, + class_name: 'bar', - startWidget: Box({ + start_widget: Box({ hpack: 'start', children: [ @@ -53,7 +52,7 @@ export default () => Window({ ], }), - centerWidget: Box({ + center_widget: Box({ children: [ Separator(SPACING), @@ -63,7 +62,7 @@ export default () => Window({ ], }), - endWidget: Box({ + end_widget: Box({ hpack: 'end', children: [ Heart(), diff --git a/devices/wim/config/ags/js/corners/screen-corners.js b/devices/wim/config/ags/js/corners/screen-corners.js index fb4bdc9..fa184fe 100644 --- a/devices/wim/config/ags/js/corners/screen-corners.js +++ b/devices/wim/config/ags/js/corners/screen-corners.js @@ -4,7 +4,7 @@ import Gtk from 'gi://Gtk'; const Lang = imports.lang; export default ( - place, + place = 'top left', css = 'background-color: black;', ) => Box({ hpack: place.includes('left') ? 'start' : 'end', @@ -27,6 +27,7 @@ export default ( .get_property('border-radius', Gtk.StateFlags.NORMAL); widget.set_size_request(r, r); + // @ts-expect-error widget.connect('draw', Lang.bind(widget, (_, cr) => { const c = widget.get_style_context() .get_property('background-color', Gtk.StateFlags.NORMAL); diff --git a/devices/wim/config/ags/js/date.js b/devices/wim/config/ags/js/date.js index 42c0033..b257711 100644 --- a/devices/wim/config/ags/js/date.js +++ b/devices/wim/config/ags/js/date.js @@ -1,29 +1,28 @@ import { Box, Calendar, Label } from 'resource:///com/github/Aylur/ags/widget.js'; -import GLib from 'gi://GLib'; -const { DateTime } = GLib; +const { DateTime } = imports.gi.GLib; import PopupWindow from './misc/popup.js'; const Divider = () => Box({ - className: 'divider', + class_name: 'divider', vertical: true, }); const Time = () => Box({ - className: 'timebox', + class_name: 'timebox', vertical: true, - children: [ + children: [ Box({ - className: 'time-container', + class_name: 'time-container', hpack: 'center', vpack: 'center', - children: [ + children: [ Label({ - className: 'content', + class_name: 'content', label: 'hour', setup: (self) => { self.poll(1000, () => { @@ -35,7 +34,7 @@ const Time = () => Box({ Divider(), Label({ - className: 'content', + class_name: 'content', label: 'minute', setup: (self) => { self.poll(1000, () => { @@ -48,11 +47,13 @@ const Time = () => Box({ }), Box({ - className: 'date-container', + class_name: 'date-container', hpack: 'center', + child: Label({ css: 'font-size: 20px', label: 'complete date', + setup: (self) => { self.poll(1000, () => { const time = DateTime.new_now_local(); @@ -69,23 +70,26 @@ const Time = () => Box({ }); const CalendarWidget = () => Box({ - className: 'cal-box', + class_name: 'cal-box', + child: Calendar({ - showDayNames: true, - showHeading: true, - className: 'cal', + show_day_names: true, + show_heading: true, + class_name: 'cal', }), }); const TOP_MARGIN = 6; export default () => PopupWindow({ + name: 'calendar', anchor: ['top'], margins: [TOP_MARGIN, 0, 0, 0], - name: 'calendar', + child: Box({ - className: 'date', + class_name: 'date', vertical: true, + children: [ Time(), CalendarWidget(), diff --git a/devices/wim/config/ags/js/media-player/gesture.js b/devices/wim/config/ags/js/media-player/gesture.js index 87e477f..60465f4 100644 --- a/devices/wim/config/ags/js/media-player/gesture.js +++ b/devices/wim/config/ags/js/media-player/gesture.js @@ -11,25 +11,49 @@ const TRANSITION = `transition: margin ${ANIM_DURATION}ms ease, export default ({ - properties, - setup = () => {/**/}, - props, -} = {}) => { + attribute = {}, + setup = (self) => { + self; + }, + ...props +}) => { const widget = EventBox(); const gesture = Gtk.GestureDrag.new(widget); // Have empty PlayerBox to define the size of the widget - const emptyPlayer = Box({ className: 'player' }); - - // Set this prop to differentiate it easily - emptyPlayer.empty = true; + const emptyPlayer = Box({ + class_name: 'player', + attribute: { empty: true }, + }); const content = Overlay({ ...props, - properties: [ - ...properties, - ['dragging', false], - ], + attribute: { + ...attribute, + dragging: false, + + list: () => content.get_children() + // @ts-expect-error + .filter((ch) => !ch.attribute?.empty), + + includesWidget: (playerW) => { + return Array.from(content.attribute.list()) + .find((w) => w === playerW); + }, + + showTopOnly: () => Array.from(content.attribute.list()) + .forEach((over) => { + over.visible = over === content.attribute.list().at(-1); + }), + + moveToTop: (player) => { + player.visible = true; + content.reorder_overlay(player, -1); + timeout(ANIM_DURATION, () => { + content.attribute.showTopOnly(); + }); + }, + }, child: emptyPlayer, @@ -37,32 +61,36 @@ export default ({ setup(self); self + // @ts-expect-error .hook(gesture, (overlay, realGesture) => { if (realGesture) { - overlay.list().forEach((over) => { + overlay.attribute.list().forEach((over) => { over.visible = true; }); } else { - overlay.showTopOnly(); + overlay.attribute.showTopOnly(); } // Don't allow gesture when only one player - if (overlay.list().length <= 1) { + if (overlay.attribute.list().length <= 1) { return; } - overlay._dragging = true; + overlay.attribute.dragging = true; let offset = gesture.get_offset()[1]; + const playerBox = overlay.attribute.list().at(-1); - const playerBox = overlay.list().at(-1); + if (!offset) { + return; + } // Slide right if (offset >= 0) { playerBox.setCss(` margin-left: ${offset}px; margin-right: -${offset}px; - ${playerBox._bgStyle} + ${playerBox.attribute.bgStyle} `); } @@ -72,24 +100,24 @@ export default ({ playerBox.setCss(` margin-left: -${offset}px; margin-right: ${offset}px; - ${playerBox._bgStyle} + ${playerBox.attribute.bgStyle} `); } }, 'drag-update') .hook(gesture, (overlay) => { // Don't allow gesture when only one player - if (overlay.list().length <= 1) { + if (overlay.attribute.list().length <= 1) { return; } - overlay._dragging = false; + overlay.attribute.dragging = false; const offset = gesture.get_offset()[1]; - const playerBox = overlay.list().at(-1); + const playerBox = overlay.attribute.list().at(-1); // If crosses threshold after letting go, slide away - if (Math.abs(offset) > MAX_OFFSET) { + if (offset && Math.abs(offset) > MAX_OFFSET) { // Disable inputs during animation widget.sensitive = false; @@ -99,7 +127,7 @@ export default ({ ${TRANSITION} margin-left: ${OFFSCREEN}px; margin-right: -${OFFSCREEN}px; - opacity: 0.7; ${playerBox._bgStyle} + opacity: 0.7; ${playerBox.attribute.bgStyle} `); } @@ -109,7 +137,7 @@ export default ({ ${TRANSITION} margin-left: -${OFFSCREEN}px; margin-right: ${OFFSCREEN}px; - opacity: 0.7; ${playerBox._bgStyle} + opacity: 0.7; ${playerBox.attribute.bgStyle} `); } @@ -117,16 +145,17 @@ export default ({ // Put the player in the back after anim overlay.reorder_overlay(playerBox, 0); // Recenter player - playerBox.setCss(playerBox._bgStyle); + playerBox.setCss(playerBox.attribute.bgStyle); widget.sensitive = true; - overlay.showTopOnly(); + overlay.attribute.showTopOnly(); }); } else { // Recenter with transition for animation - playerBox.setCss(`${TRANSITION} ${playerBox._bgStyle}`); + playerBox.setCss(`${TRANSITION} + ${playerBox.attribute.bgStyle}`); } }, 'drag-end'); }, @@ -134,27 +163,5 @@ export default ({ widget.add(content); - // Overlay methods - content.list = () => content.get_children() - .filter((ch) => !ch.empty); - - content.includesWidget = (playerW) => { - return content.list().find((w) => w === playerW); - }; - - content.showTopOnly = () => content.list().forEach((over) => { - over.visible = over === content.list().at(-1); - }); - - content.moveToTop = (player) => { - player.visible = true; - content.reorder_overlay(player, -1); - timeout(ANIM_DURATION, () => { - content.showTopOnly(); - }); - }; - - widget.getOverlay = () => content; - return widget; }; diff --git a/devices/wim/config/ags/js/media-player/mpris.js b/devices/wim/config/ags/js/media-player/mpris.js index 8a97d16..2ad5daa 100644 --- a/devices/wim/config/ags/js/media-player/mpris.js +++ b/devices/wim/config/ags/js/media-player/mpris.js @@ -6,6 +6,12 @@ import { execAsync, lookUpIcon, readFileAsync } from 'resource:///com/github/Ayl import Separator from '../misc/separator.js'; import EventBox from '../misc/cursorbox.js'; +/** + * @typedef {import('types/service/mpris').MprisPlayer} Player + * @typedef {import('types/variable').Variable} Variable + * @typedef {import('types/widgets/overlay').default} Overlay + */ + const ICON_SIZE = 32; const icons = { @@ -29,59 +35,66 @@ const icons = { }; -export const CoverArt = (player, props) => CenterBox({ +/** + * @param {Player} player + * @param {Variable} colors + * @param {import('types/widgets/centerbox').CenterBoxProps=} props + */ +export const CoverArt = (player, colors, props) => CenterBox({ ...props, + // @ts-expect-error vertical: true, - properties: [ - ['bgStyle', ''], - ['player', player], - ], + attribute: { + bgStyle: '', + player, + }, setup: (self) => { // Give temp cover art - readFileAsync(player.coverPath).catch(() => { - if (!player.colors.value && !player.trackCoverUrl) { - player.colors.value = { + readFileAsync(player.cover_path).catch(() => { + if (!colors.value && !player.track_cover_url) { + colors.value = { imageAccent: '#6b4fa2', buttonAccent: '#ecdcff', buttonText: '#25005a', hoverAccent: '#d4baff', }; - self._bgStyle = ` + self.attribute.bgStyle = ` background: radial-gradient(circle, rgba(0, 0, 0, 0.4) 30%, - ${player.colors.value.imageAccent}), + ${colors.value.imageAccent}), rgb(0, 0, 0); background-size: cover; background-position: center; `; - self.setCss(self._bgStyle); + self.setCss(self.attribute.bgStyle); } }); self.hook(player, () => { - execAsync(['bash', '-c', `[[ -f "${player.coverPath}" ]] && - coloryou "${player.coverPath}" | grep -v Warning`]) + execAsync(['bash', '-c', `[[ -f "${player.cover_path}" ]] && + coloryou "${player.cover_path}" | grep -v Warning`]) .then((out) => { if (!Mpris.players.find((p) => player === p)) { return; } - player.colors.value = JSON.parse(out); + colors.value = JSON.parse(out); - self._bgStyle = ` - background: radial-gradient(circle, - rgba(0, 0, 0, 0.4) 30%, - ${player.colors.value.imageAccent}), - url("${player.coverPath}"); - background-size: cover; - background-position: center; - `; + self.attribute.bgStyle = ` + background: radial-gradient(circle, + rgba(0, 0, 0, 0.4) 30%, + ${colors.value.imageAccent}), + url("${player.cover_path}"); + background-size: cover; + background-position: center; + `; - if (!self.get_parent()._dragging) { - self.setCss(self._bgStyle); + // @ts-expect-error + if (!self?.get_parent()?.attribute.dragging) { + self.setCss(self.attribute.bgStyle); } }).catch((err) => { if (err !== '') { @@ -92,40 +105,48 @@ export const CoverArt = (player, props) => CenterBox({ }, }); -export const TitleLabel = (player, props) => Label({ - ...props, +/** @param {Player} player */ +export const TitleLabel = (player) => Label({ xalign: 0, - maxWidthChars: 40, + max_width_chars: 40, truncate: 'end', justification: 'left', - className: 'title', - - binds: [['label', player, 'track-title']], + class_name: 'title', + label: player.bind('track_title'), }); -export const ArtistLabel = (player, props) => Label({ - ...props, +/** @param {Player} player */ +export const ArtistLabel = (player) => Label({ xalign: 0, - maxWidthChars: 40, + max_width_chars: 40, truncate: 'end', justification: 'left', - className: 'artist', - - binds: [['label', player, 'track-artists', (a) => a.join(', ') || '']], + class_name: 'artist', + label: player.bind('track_artists') + .transform((a) => a.join(', ') || ''), }); -export const PlayerIcon = (player, overlay, props) => { + +/** + * @param {Player} player + * @param {Overlay} overlay + */ +export const PlayerIcon = (player, overlay) => { + /** + * @param {Player} p + * @param {Overlay=} widget + * @param {Overlay=} over + */ const playerIcon = (p, widget, over) => EventBox({ - ...props, - tooltipText: p.identity || '', + tooltip_text: p.identity || '', onPrimaryClickRelease: () => { - widget?.moveToTop(over); + widget?.attribute.moveToTop(over); }, child: Icon({ - className: widget ? 'position-indicator' : 'player-icon', - size: widget ? '' : ICON_SIZE, + class_name: widget ? 'position-indicator' : 'player-icon', + size: widget ? 0 : ICON_SIZE, setup: (self) => { self.hook(p, () => { @@ -137,33 +158,34 @@ export const PlayerIcon = (player, overlay, props) => { }), }); - return Box({ - setup: (self) => { - self.hook(Mpris, () => { - const thisIndex = overlay.list() - .indexOf(self.get_parent().get_parent()); + return Box().hook(Mpris, (self) => { + const thisIndex = overlay.attribute.list() + .indexOf(self?.get_parent()?.get_parent()); - self.children = overlay.list().map((over, i) => { - self.children.push(Separator(2)); + self.children = Array.from(overlay.attribute.list()) + .map((over, i) => { + self.children.push(Separator(2)); - return i === thisIndex ? - playerIcon(player) : - playerIcon(over._player, overlay, over); - }).reverse(); - }); - }, + return i === thisIndex ? + playerIcon(player) : + playerIcon(over.attribute.player, overlay, over); + }) + .reverse(); }); }; -export const PositionSlider = (player, props) => Slider({ - ...props, - className: 'position-slider', +/** + * @param {Player} player + * @param {Variable} colors + */ +export const PositionSlider = (player, colors) => Slider({ + class_name: 'position-slider', cursor: 'pointer', vpack: 'center', hexpand: true, - drawValue: false, + draw_value: false, - onChange: ({ value }) => { + on_change: ({ value }) => { player.position = player.length * value; }, @@ -180,9 +202,9 @@ export const PositionSlider = (player, props) => Slider({ self .poll(1000, () => update()) .hook(player, () => update(), 'position') - .hook(player.colors, () => { - if (player.colors.value) { - const c = player.colors.value; + .hook(colors, () => { + if (colors.value) { + const c = colors.value; self.setCss(` highlight { background-color: ${c.buttonAccent}; } @@ -192,29 +214,29 @@ export const PositionSlider = (player, props) => Slider({ `); } }) - .on('button-press-event', (s) => { - s.cursor = 'grabbing'; + .on('button-press-event', () => { + self.cursor = 'grabbing'; }) - .on('button-release-event', (s) => { - s.cursor = 'pointer'; + .on('button-release-event', () => { + self.cursor = 'pointer'; }); }, }); -const PlayerButton = ({ player, items, onClick, prop }) => EventBox({ +const PlayerButton = ({ player, colors, items, onClick, prop }) => EventBox({ child: Button({ - properties: [['hovered', false]], + attribute: { hovered: false }, child: Stack({ items }), - onPrimaryClickRelease: () => player[onClick](), + on_primary_click_release: () => player[onClick](), - onHover: (self) => { - self._hovered = true; + on_hover: (self) => { + self.attribute.hovered = true; - if (prop === 'playBackStatus' && player.colors.value) { - const c = player.colors.value; + if (prop === 'playBackStatus' && colors.value) { + const c = colors.value; - items.forEach((item) => { + Array.from(items).forEach((item) => { item[1].setCss(` background-color: ${c.hoverAccent}; color: ${c.buttonText}; @@ -227,12 +249,12 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({ } }, - onHoverLost: (self) => { - self._hovered = false; - if (prop === 'playBackStatus' && player.colors.value) { - const c = player.colors.value; + on_hover_lost: (self) => { + self.attribute.hovered = false; + if (prop === 'playBackStatus' && colors.value) { + const c = colors.value; - items.forEach((item) => { + Array.from(items).forEach((item) => { item[1].setCss(` background-color: ${c.buttonAccent}; color: ${c.buttonText}; @@ -246,19 +268,20 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({ setup: (self) => { self .hook(player, () => { + // @ts-expect-error self.child.shown = `${player[prop]}`; }) - .hook(player.colors, () => { + .hook(colors, () => { if (!Mpris.players.find((p) => player === p)) { return; } - if (player.colors.value) { - const c = player.colors.value; + if (colors.value) { + const c = colors.value; if (prop === 'playBackStatus') { - if (self._hovered) { - items.forEach((item) => { + if (self.attribute.hovered) { + Array.from(items).forEach((item) => { item[1].setCss(` background-color: ${c.hoverAccent}; color: ${c.buttonText}; @@ -270,7 +293,7 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({ }); } else { - items.forEach((item) => { + Array.from(items).forEach((item) => { item[1].setCss(` background-color: ${c.buttonAccent}; color: ${c.buttonText}; @@ -292,15 +315,20 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({ }), }); -export const ShuffleButton = (player) => PlayerButton({ +/** + * @param {Player} player + * @param {Variable} colors + */ +export const ShuffleButton = (player, colors) => PlayerButton({ player, + colors, items: [ ['true', Label({ - className: 'shuffle enabled', + class_name: 'shuffle enabled', label: icons.mpris.shuffle.enabled, })], ['false', Label({ - className: 'shuffle disabled', + class_name: 'shuffle disabled', label: icons.mpris.shuffle.disabled, })], ], @@ -308,19 +336,24 @@ export const ShuffleButton = (player) => PlayerButton({ prop: 'shuffleStatus', }); -export const LoopButton = (player) => PlayerButton({ +/** + * @param {Player} player + * @param {Variable} colors + */ +export const LoopButton = (player, colors) => PlayerButton({ player, + colors, items: [ ['None', Label({ - className: 'loop none', + class_name: 'loop none', label: icons.mpris.loop.none, })], ['Track', Label({ - className: 'loop track', + class_name: 'loop track', label: icons.mpris.loop.track, })], ['Playlist', Label({ - className: 'loop playlist', + class_name: 'loop playlist', label: icons.mpris.loop.playlist, })], ], @@ -328,19 +361,24 @@ export const LoopButton = (player) => PlayerButton({ prop: 'loopStatus', }); -export const PlayPauseButton = (player) => PlayerButton({ +/** + * @param {Player} player + * @param {Variable} colors + */ +export const PlayPauseButton = (player, colors) => PlayerButton({ player, + colors, items: [ ['Playing', Label({ - className: 'pausebutton playing', + class_name: 'pausebutton playing', label: icons.mpris.playing, })], ['Paused', Label({ - className: 'pausebutton paused', + class_name: 'pausebutton paused', label: icons.mpris.paused, })], ['Stopped', Label({ - className: 'pausebutton stopped paused', + class_name: 'pausebutton stopped paused', label: icons.mpris.stopped, })], ], @@ -348,15 +386,20 @@ export const PlayPauseButton = (player) => PlayerButton({ prop: 'playBackStatus', }); -export const PreviousButton = (player) => PlayerButton({ +/** + * @param {Player} player + * @param {Variable} colors + */ +export const PreviousButton = (player, colors) => PlayerButton({ player, + colors, items: [ ['true', Label({ - className: 'previous', + class_name: 'previous', label: icons.mpris.prev, })], ['false', Label({ - className: 'previous', + class_name: 'previous', label: icons.mpris.prev, })], ], @@ -364,15 +407,20 @@ export const PreviousButton = (player) => PlayerButton({ prop: 'canGoPrev', }); -export const NextButton = (player) => PlayerButton({ +/** + * @param {Player} player + * @param {Variable} colors + */ +export const NextButton = (player, colors) => PlayerButton({ player, + colors, items: [ ['true', Label({ - className: 'next', + class_name: 'next', label: icons.mpris.next, })], ['false', Label({ - className: 'next', + class_name: 'next', label: icons.mpris.next, })], ], diff --git a/devices/wim/config/ags/js/media-player/player.js b/devices/wim/config/ags/js/media-player/player.js index 9cba5a4..f269c9f 100644 --- a/devices/wim/config/ags/js/media-player/player.js +++ b/devices/wim/config/ags/js/media-player/player.js @@ -8,10 +8,21 @@ import PlayerGesture from './gesture.js'; import Separator from '../misc/separator.js'; const FAVE_PLAYER = 'org.mpris.MediaPlayer2.spotify'; +const SPACING = 8; + +/** + * @typedef {import('types/service/mpris').MprisPlayer} Player + * @typedef {import('types/widgets/overlay').default} Overlay + * @typedef {import('types/variable').Variable} Variable + */ +/** + * @param {Player} player + * @param {Overlay} overlay + */ const Top = (player, overlay) => Box({ - className: 'top', + class_name: 'top', hpack: 'start', vpack: 'start', @@ -20,16 +31,21 @@ const Top = (player, overlay) => Box({ ], }); -const Center = (player) => Box({ - className: 'center', +/** + * @param {Player} player + * @param {Variable} colors + */ +const Center = (player, colors) => Box({ + class_name: 'center', children: [ CenterBox({ + // @ts-expect-error vertical: true, children: [ Box({ - className: 'metadata', + class_name: 'metadata', vertical: true, hpack: 'start', vpack: 'center', @@ -46,11 +62,12 @@ const Center = (player) => Box({ }), CenterBox({ + // @ts-expect-error vertical: true, children: [ null, - mpris.PlayPauseButton(player), + mpris.PlayPauseButton(player, colors), null, ], }), @@ -58,41 +75,43 @@ const Center = (player) => Box({ ], }); -const SPACING = 8; - -const Bottom = (player) => Box({ - className: 'bottom', +/** + * @param {Player} player + * @param {Variable} colors + */ +const Bottom = (player, colors) => Box({ + class_name: 'bottom', children: [ - mpris.PreviousButton(player, { - vpack: 'end', - hpack: 'start', - }), + mpris.PreviousButton(player, colors), Separator(SPACING), - mpris.PositionSlider(player), + mpris.PositionSlider(player, colors), Separator(SPACING), - mpris.NextButton(player), + mpris.NextButton(player, colors), Separator(SPACING), - mpris.ShuffleButton(player), + mpris.ShuffleButton(player, colors), Separator(SPACING), - mpris.LoopButton(player), + mpris.LoopButton(player, colors), ], }); -const PlayerBox = (player, overlay) => { - const widget = mpris.CoverArt(player, { - className: `player ${player.name}`, +/** + * @param {Player} player + * @param {Variable} colors + * @param {Overlay} overlay + */ +const PlayerBox = (player, colors, overlay) => { + const widget = mpris.CoverArt(player, colors, { + class_name: `player ${player.name}`, hexpand: true, - children: [ - Top(player, overlay), - Center(player), - Bottom(player), - ], + start_widget: Top(player, overlay), + center_widget: Center(player, colors), + end_widget: Bottom(player, colors), }); widget.visible = false; @@ -102,26 +121,28 @@ const PlayerBox = (player, overlay) => { export default () => { const content = PlayerGesture({ - properties: [ - ['players', new Map()], - ['setup', false], - ], + attribute: { + players: new Map(), + setup: false, + }, setup: (self) => { self - .hook(Mpris, (overlay, busName) => { - if (overlay._players.has(busName)) { + .hook(Mpris, (overlay, bus_name) => { + const players = overlay.attribute.players; + + if (players.has(bus_name)) { return; } - // Sometimes the signal doesn't give the busName - if (!busName) { + // Sometimes the signal doesn't give the bus_name + if (!bus_name) { const player = Mpris.players.find((p) => { - return !overlay._players.has(p.busName); + return !players.has(p.bus_name); }); if (player) { - busName = player.busName; + bus_name = player.bus_name; } else { return; @@ -129,54 +150,67 @@ export default () => { } // Get the one on top so we can move it up later - const previousFirst = overlay.list().at(-1); + const previousFirst = overlay.attribute.list().at(-1); // Make the new player - const player = Mpris.getPlayer(busName); + const player = Mpris.getPlayer(bus_name); + const Colors = Variable(null); - player.colors = Variable(); - overlay._players.set( - busName, - PlayerBox(player, content.getOverlay()), + if (!player) { + return; + } + + players.set( + bus_name, + // @ts-expect-error + PlayerBox(player, Colors, content.child), ); - overlay.overlays = Array.from(overlay._players.values()) + overlay.overlays = Array.from(players.values()) .map((widget) => widget); + const includes = overlay.attribute + .includesWidget(previousFirst); + // Select favorite player at startup - if (!overlay._setup && overlay._players.has(FAVE_PLAYER)) { - overlay.moveToTop(overlay._players.get(FAVE_PLAYER)); - overlay._setup = true; + if (!overlay.attribute.setup && players.has(FAVE_PLAYER)) { + overlay.attribute.moveToTop(players.get(FAVE_PLAYER)); + overlay.attribute.setup = true; } // Move previousFirst on top again - else if (overlay.includesWidget(previousFirst)) { - overlay.moveToTop(previousFirst); + else if (includes) { + overlay.attribute.moveToTop(previousFirst); } }, 'player-added') - .hook(Mpris, (overlay, busName) => { - if (!busName || !overlay._players.has(busName)) { + .hook(Mpris, (overlay, bus_name) => { + const players = overlay.attribute.players; + + if (!bus_name || !players.has(bus_name)) { return; } // Get the one on top so we can move it up later - const previousFirst = overlay.list().at(-1); + const previousFirst = overlay.attribute.list().at(-1); // Remake overlays without deleted one - overlay._players.delete(busName); - overlay.overlays = Array.from(overlay._players.values()) + players.delete(bus_name); + overlay.overlays = Array.from(players.values()) .map((widget) => widget); // Move previousFirst on top again - if (overlay.includesWidget(previousFirst)) { - overlay.moveToTop(previousFirst); + const includes = overlay.attribute + .includesWidget(previousFirst); + + if (includes) { + overlay.attribute.moveToTop(previousFirst); } }, 'player-closed'); }, }); return Box({ - className: 'media', + class_name: 'media', child: content, }); }; diff --git a/devices/wim/config/ags/js/misc/cursorbox.js b/devices/wim/config/ags/js/misc/cursorbox.js index 6413f84..d6643cb 100644 --- a/devices/wim/config/ags/js/misc/cursorbox.js +++ b/devices/wim/config/ags/js/misc/cursorbox.js @@ -6,19 +6,39 @@ import Gtk from 'gi://Gtk'; // TODO: wrap in another EventBox for disabled cursor +/** + * @typedef {import('types/widget.js').Widget} Widget + * @typedef {Widget & Object} CursorProps + * @property {boolean=} isButton + * @property {function(Widget):void=} onPrimaryClickRelease + * + * @param {CursorProps} obj + */ export default ({ isButton = false, - onPrimaryClickRelease = () => { /**/ }, + + onPrimaryClickRelease = (self) => { + self; + }, ...props }) => { // Make this variable to know if the function should // be executed depending on where the click is released const CanRun = Variable(true); - const properties = { + let widgetFunc; + + if (isButton) { + widgetFunc = Button; + } + else { + widgetFunc = EventBox; + } + + const widget = widgetFunc({ ...props, cursor: 'pointer', - onPrimaryClickRelease: (self) => { + on_primary_click_release: (self) => { // Every click, do a one shot connect to // CanRun to wait for location of click const id = CanRun.connect('changed', () => { @@ -29,19 +49,11 @@ export default ({ CanRun.disconnect(id); }); }, - }; - - let widget; - - if (isButton) { - widget = Button(properties); - } - else { - widget = EventBox(properties); - } + }); const gesture = Gtk.GestureLongPress.new(widget); + // @ts-expect-error widget.hook(gesture, () => { const pointer = gesture.get_point(null); const x = pointer[1]; diff --git a/devices/wim/config/ags/js/misc/persist.js b/devices/wim/config/ags/js/misc/persist.js index d1b7961..23ca918 100644 --- a/devices/wim/config/ags/js/misc/persist.js +++ b/devices/wim/config/ags/js/misc/persist.js @@ -1,6 +1,19 @@ import { execAsync, readFileAsync, timeout } from 'resource:///com/github/Aylur/ags/utils.js'; const { get_home_dir } = imports.gi.GLib; + +/** + * @typedef {Object} Persist + * @property {string} name + * @property {typeof imports.gi.GObject} gobject + * @property {string} prop + * @property {boolean|string=} condition - if string, compare following props to this + * @property {boolean|string=} whenTrue + * @property {boolean|string=} whenFalse + * @property {string=} signal + * + * @param {Persist} props + */ export default ({ name, gobject, @@ -9,7 +22,7 @@ export default ({ whenTrue = condition, whenFalse = false, signal = 'changed', -} = {}) => { +}) => { const cacheFile = `${get_home_dir()}/.cache/ags/.${name}`; const stateCmd = () => ['bash', '-c', diff --git a/devices/wim/config/ags/package.json b/devices/wim/config/ags/package.json index 996d98a..e14b4b6 100644 --- a/devices/wim/config/ags/package.json +++ b/devices/wim/config/ags/package.json @@ -1,17 +1,19 @@ { + "main": "config.js", "dependencies": { - "@girs/dbusmenugtk3-0.4": "^0.4.0-3.2.2", - "@girs/gtk-3.0": "^3.24.39-3.2.2", - "@girs/gudev-1.0": "^1.0.0-3.2.2", - "@girs/gvc-1.0": "^1.0.0-3.2.2", - "@girs/nm-1.0": "^1.45.1-3.2.2", - "@typescript-eslint/eslint-plugin": "^6.9.1", - "@typescript-eslint/parser": "^6.9.1", - "eslint": "^8.52.0", - "fzf": "^0.5.2", - "stylelint-config-standard-scss": "^11.0.0" + "fzf": "^0.5.2" }, "devDependencies": { - "@stylistic/eslint-plugin": "^1.4.0" + "eslint": "^8.52.0", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "stylelint-config-standard-scss": "^11.0.0", + "@stylistic/eslint-plugin": "^1.4.0", + "@girs/dbusmenugtk3-0.4": "^0.4.0-3.2.0", + "@girs/gudev-1.0": "^1.0.0-3.2.2", + "@girs/gobject-2.0": "^2.76.1-3.2.3", + "@girs/gtk-3.0": "^3.24.39-3.2.2", + "@girs/gvc-1.0": "^1.0.0-3.1.0", + "@girs/nm-1.0": "^1.43.1-3.1.0" } } diff --git a/devices/wim/config/ags/scss/widgets/traybuttons.scss b/devices/wim/config/ags/scss/widgets/traybuttons.scss index df5a04b..4b9eebd 100644 --- a/devices/wim/config/ags/scss/widgets/traybuttons.scss +++ b/devices/wim/config/ags/scss/widgets/traybuttons.scss @@ -105,7 +105,7 @@ } } - .label { + label { font-size: 20px; } } diff --git a/devices/wim/config/ags/tsconfig.json b/devices/wim/config/ags/tsconfig.json new file mode 100644 index 0000000..e1d84ef --- /dev/null +++ b/devices/wim/config/ags/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": [ + "ES2022" + ], + "allowJs": true, + "checkJs": true, + "strict": true, + "noImplicitAny": false, + "baseUrl": ".", + "typeRoots": [ + "./types/ags.d.ts", + "./node_modules/@girs" + ], + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +}