refactor(ags): start type checking
This commit is contained in:
parent
ae545731e7
commit
b85542091d
31 changed files with 774 additions and 639 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
|||
*.egg-info
|
||||
*.temp
|
||||
*node_modules/
|
||||
*types/
|
||||
*package-lock.json
|
||||
**/ags/style.css
|
||||
result*
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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}%`),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -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'),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
|
@ -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(),
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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)),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
})],
|
||||
],
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
label {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
|
20
devices/wim/config/ags/tsconfig.json
Normal file
20
devices/wim/config/ags/tsconfig.json
Normal file
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue