Implemented strict linting standards and prettier formatting config. (#248)

* Implemented strict linting standards and prettier formatting config.

* More linter fixes and type updates.

* More linter updates and type fixes

* Remove noisy comments

* Linter and type updates

* Linter, formatting and type updates.

* Linter updates

* Type updates

* Type updates

* fixed all linter errors

* Fixed all linting, formatting and type issues.

* Resolve merge conflicts.
This commit is contained in:
Jas Singh
2024-09-14 16:20:05 -07:00
committed by GitHub
parent ff13e3dd3c
commit 2c72cc66d8
222 changed files with 13141 additions and 8433 deletions

View File

@@ -1,8 +1,10 @@
const hyprland = await Service.import("hyprland");
const hyprland = await Service.import('hyprland');
import {
Menu,
Workspaces, ClientTitle, Media,
Workspaces,
ClientTitle,
Media,
Notifications,
Volume,
Network,
@@ -20,54 +22,57 @@ import {
Updates,
Weather,
Power,
} from "./Exports"
} from './Exports';
import { BarItemBox as WidgetContainer } from "../shared/barItemBox.js";
import options from "options";
import Gdk from "gi://Gdk?version=3.0";
import Button from "types/widgets/button.js";
import Gtk from "types/@girs/gtk-3.0/gtk-3.0.js";
import { BarItemBox as WidgetContainer } from '../shared/barItemBox.js';
import options from 'options';
import Gdk from 'gi://Gdk?version=3.0';
import Button from 'types/widgets/button.js';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0.js';
import './SideEffects';
import { WindowLayer } from "lib/types/options.js";
import { WindowLayer } from 'lib/types/options.js';
import { Attribute, Child } from 'lib/types/widget.js';
import Window from 'types/widgets/window.js';
const { layouts } = options.bar;
export type BarWidget = keyof typeof widget;
type Section = "battery"
| "dashboard"
| "workspaces"
| "windowtitle"
| "media"
| "notifications"
| "volume"
| "network"
| "bluetooth"
| "clock"
| "ram"
| "cpu"
| "storage"
| "netstat"
| "kbinput"
| "updates"
| "weather"
| "power"
| "systray";
type Section =
| 'battery'
| 'dashboard'
| 'workspaces'
| 'windowtitle'
| 'media'
| 'notifications'
| 'volume'
| 'network'
| 'bluetooth'
| 'clock'
| 'ram'
| 'cpu'
| 'storage'
| 'netstat'
| 'kbinput'
| 'updates'
| 'weather'
| 'power'
| 'systray';
type Layout = {
left: Section[],
middle: Section[],
right: Section[],
}
left: Section[];
middle: Section[];
right: Section[];
};
type BarLayout = {
[key: string]: Layout
}
[key: string]: Layout;
};
const getLayoutForMonitor = (monitor: number, layouts: BarLayout): Layout => {
const matchingKey = Object.keys(layouts).find(key => key === monitor.toString());
const wildcard = Object.keys(layouts).find(key => key === "*");
const matchingKey = Object.keys(layouts).find((key) => key === monitor.toString());
const wildcard = Object.keys(layouts).find((key) => key === '*');
if (matchingKey) {
return layouts[matchingKey];
@@ -77,62 +82,48 @@ const getLayoutForMonitor = (monitor: number, layouts: BarLayout): Layout => {
return layouts[wildcard];
}
return {
left: [
"dashboard",
"workspaces",
"windowtitle"
],
middle: [
"media"
],
right: [
"volume",
"network",
"bluetooth",
"battery",
"systray",
"clock",
"notifications"
]
return {
left: ['dashboard', 'workspaces', 'windowtitle'],
middle: ['media'],
right: ['volume', 'network', 'bluetooth', 'battery', 'systray', 'clock', 'notifications'],
};
}
};
const widget = {
battery: () => WidgetContainer(BatteryLabel()),
dashboard: () => WidgetContainer(Menu()),
workspaces: (monitor: number) => WidgetContainer(Workspaces(monitor)),
windowtitle: () => WidgetContainer(ClientTitle()),
media: () => WidgetContainer(Media()),
notifications: () => WidgetContainer(Notifications()),
volume: () => WidgetContainer(Volume()),
network: () => WidgetContainer(Network()),
bluetooth: () => WidgetContainer(Bluetooth()),
clock: () => WidgetContainer(Clock()),
systray: () => WidgetContainer(SysTray()),
ram: () => WidgetContainer(Ram()),
cpu: () => WidgetContainer(Cpu()),
storage: () => WidgetContainer(Storage()),
netstat: () => WidgetContainer(Netstat()),
kbinput: () => WidgetContainer(KbInput()),
updates: () => WidgetContainer(Updates()),
weather: () => WidgetContainer(Weather()),
power: () => WidgetContainer(Power()),
battery: (): Button<Child, Attribute> => WidgetContainer(BatteryLabel()),
dashboard: (): Button<Child, Attribute> => WidgetContainer(Menu()),
workspaces: (monitor: number): Button<Child, Attribute> => WidgetContainer(Workspaces(monitor)),
windowtitle: (): Button<Child, Attribute> => WidgetContainer(ClientTitle()),
media: (): Button<Child, Attribute> => WidgetContainer(Media()),
notifications: (): Button<Child, Attribute> => WidgetContainer(Notifications()),
volume: (): Button<Child, Attribute> => WidgetContainer(Volume()),
network: (): Button<Child, Attribute> => WidgetContainer(Network()),
bluetooth: (): Button<Child, Attribute> => WidgetContainer(Bluetooth()),
clock: (): Button<Child, Attribute> => WidgetContainer(Clock()),
systray: (): Button<Child, Attribute> => WidgetContainer(SysTray()),
ram: (): Button<Child, Attribute> => WidgetContainer(Ram()),
cpu: (): Button<Child, Attribute> => WidgetContainer(Cpu()),
storage: (): Button<Child, Attribute> => WidgetContainer(Storage()),
netstat: (): Button<Child, Attribute> => WidgetContainer(Netstat()),
kbinput: (): Button<Child, Attribute> => WidgetContainer(KbInput()),
updates: (): Button<Child, Attribute> => WidgetContainer(Updates()),
weather: (): Button<Child, Attribute> => WidgetContainer(Weather()),
power: (): Button<Child, Attribute> => WidgetContainer(Power()),
};
type GdkMonitors = {
[key: string]: {
key: string,
model: string,
used: boolean
}
key: string;
model: string;
used: boolean;
};
};
function getGdkMonitors(): GdkMonitors {
const display = Gdk.Display.get_default();
if (display === null) {
console.error("Failed to get Gdk display.");
console.error('Failed to get Gdk display.');
return {};
}
@@ -162,35 +153,35 @@ function getGdkMonitors(): GdkMonitors {
* NOTE: Some more funky stuff being done by GDK.
* We render windows/bar based on the monitor ID. So if you have 3 monitors, then your
* monitor IDs will be [0, 1, 2]. Hyprland will NEVER change what ID belongs to what monitor.
*
*
* So if hyprland determines id 0 = DP-1, even after you unplug, shut off or restart your monitor,
* the id 0 will ALWAYS be DP-1.
*
*
* However, GDK (the righteous genius that it is) will change the order of ID anytime your monitor
* setup is changed. So if you unplug your monitor and plug it back it, it now becomes the last id.
* So if DP-1 was id 0 and you unplugged it, it will reconfigure to id 2. This sucks because now
* there's a mismtach between what GDK determines the monitor is at id 2 and what Hyprland determines
* is at id 2.
*
*
* So for that reason, we need to redirect the input `monitor` that the Bar module takes in, to the
* proper Hyprland monitor. So when monitor id 0 comes in, we need to find what the id of that monitor
* is being determined as by Hyprland so the bars show up on the right monitors.
*
*
* Since GTK3 doesn't contain connection names and only monitor models, we have to make the best guess
* in the case that there are multiple models in the same resolution with the same scale. We find the
* 'right' monitor by checking if the model matches along with the resolution and scale. If monitor at
* ID 0 for GDK is being reported as 'MSI MAG271CQR' we find the same model in the Hyprland monitor list
* and check if the resolution and scaling is the same... if it is then we determine it's a match.
*
*
* The edge-case that we just can't handle is if you have the same monitors in the same resolution at the same
* scale. So if you've got 2 'MSI MAG271CQR' monitors at 2560x1440 at scale 1, then we just match the first
* monitor in the list as the first match and then the second 'MSI MAG271CQR' as a match in the 2nd iteration.
* You may have the bar showing up on the wrong one in this case because we don't know what the connector id
* monitor in the list as the first match and then the second 'MSI MAG271CQR' as a match in the 2nd iteration.
* You may have the bar showing up on the wrong one in this case because we don't know what the connector id
* is of either of these monitors (DP-1, DP-2) which are unique values - as these are only in GTK4.
*
*
* Keep in mind though, this is ONLY an issue if you change your monitor setup by plugging in a new one, restarting
* an existing one or shutting it off.
*
*
* If your monitors aren't changed in the current session you're in then none of this safeguarding is relevant.
*
* Fun stuff really... :facepalm:
@@ -200,7 +191,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
const gdkMonitors = getGdkMonitors();
if (Object.keys(gdkMonitors).length === 0) {
console.error("No GDK monitors were found.");
console.error('No GDK monitors were found.');
return monitor;
}
@@ -208,7 +199,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
const gdkMonitor = gdkMonitors[monitor];
// First pass: Strict matching including the monitor index (i.e., hypMon.id === monitor + resolution+scale criteria)
const directMatch = hyprland.monitors.find(hypMon => {
const directMatch = hyprland.monitors.find((hypMon) => {
const hyprlandKey = `${hypMon.model}_${hypMon.width}x${hypMon.height}_${hypMon.scale}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id) && hypMon.id === monitor;
});
@@ -219,7 +210,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
}
// Second pass: Relaxed matching without considering the monitor index
const hyprlandMonitor = hyprland.monitors.find(hypMon => {
const hyprlandMonitor = hyprland.monitors.find((hypMon) => {
const hyprlandKey = `${hypMon.model}_${hypMon.width}x${hypMon.height}_${hypMon.scale}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id);
});
@@ -230,7 +221,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
}
// Fallback: Find the first available monitor ID that hasn't been used
const fallbackMonitor = hyprland.monitors.find(hypMon => !usedHyprlandMonitors.has(hypMon.id));
const fallbackMonitor = hyprland.monitors.find((hypMon) => !usedHyprlandMonitors.has(hypMon.id));
if (fallbackMonitor) {
usedHyprlandMonitors.add(fallbackMonitor.id);
@@ -253,62 +244,63 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
export const Bar = (() => {
const usedHyprlandMonitors = new Set<number>();
return (monitor: number) => {
return (monitor: number): Window<Child, Attribute> => {
const hyprlandMonitor = gdkMonitorIdToHyprlandId(monitor, usedHyprlandMonitors);
return Widget.Window({
name: `bar-${hyprlandMonitor}`,
class_name: "bar",
class_name: 'bar',
monitor,
visible: true,
anchor: ["top", "left", "right"],
exclusivity: "exclusive",
anchor: ['top', 'left', 'right'],
exclusivity: 'exclusive',
layer: Utils.merge(
[
options.theme.bar.layer.bind("value"),
options.tear.bind("value")
],
(
barLayer: WindowLayer,
tear: boolean
) => {
if (tear && barLayer === "overlay") {
return "top";
[options.theme.bar.layer.bind('value'), options.tear.bind('value')],
(barLayer: WindowLayer, tear: boolean) => {
if (tear && barLayer === 'overlay') {
return 'top';
}
return barLayer;
}),
},
),
child: Widget.Box({
class_name: 'bar-panel-container',
child: Widget.CenterBox({
class_name: 'bar-panel',
css: 'padding: 1px',
startWidget: Widget.Box({
class_name: "box-left",
class_name: 'box-left',
hexpand: true,
setup: self => {
setup: (self) => {
self.hook(layouts, (self) => {
const foundLayout = getLayoutForMonitor(hyprlandMonitor, layouts.value as BarLayout);
self.children = foundLayout.left.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>);
self.children = foundLayout.left
.filter((mod) => Object.keys(widget).includes(mod))
.map((w) => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>);
});
},
}),
centerWidget: Widget.Box({
class_name: "box-center",
hpack: "center",
setup: self => {
class_name: 'box-center',
hpack: 'center',
setup: (self) => {
self.hook(layouts, (self) => {
const foundLayout = getLayoutForMonitor(hyprlandMonitor, layouts.value as BarLayout);
self.children = foundLayout.middle.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>);
self.children = foundLayout.middle
.filter((mod) => Object.keys(widget).includes(mod))
.map((w) => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>);
});
},
}),
endWidget: Widget.Box({
class_name: "box-right",
hpack: "end",
setup: self => {
class_name: 'box-right',
hpack: 'end',
setup: (self) => {
self.hook(layouts, (self) => {
const foundLayout = getLayoutForMonitor(hyprlandMonitor, layouts.value as BarLayout);
self.children = foundLayout.right.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>);
self.children = foundLayout.right
.filter((mod) => Object.keys(widget).includes(mod))
.map((w) => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>);
});
},
}),

View File

@@ -1,24 +1,24 @@
import { Menu } from "./menu/index";
import { Workspaces } from "./workspaces/index";
import { ClientTitle } from "./window_title/index";
import { Media } from "./media/index";
import { Notifications } from "./notifications/index";
import { Volume } from "./volume/index";
import { Network } from "./network/index";
import { Bluetooth } from "./bluetooth/index";
import { BatteryLabel } from "./battery/index";
import { Clock } from "./clock/index";
import { SysTray } from "./systray/index";
import { Menu } from './menu/index';
import { Workspaces } from './workspaces/index';
import { ClientTitle } from './window_title/index';
import { Media } from './media/index';
import { Notifications } from './notifications/index';
import { Volume } from './volume/index';
import { Network } from './network/index';
import { Bluetooth } from './bluetooth/index';
import { BatteryLabel } from './battery/index';
import { Clock } from './clock/index';
import { SysTray } from './systray/index';
// Custom Modules
import { Ram } from "../../customModules/ram/index";
import { Cpu } from "../../customModules/cpu/index";
import { Storage } from "customModules/storage/index";
import { Netstat } from "customModules/netstat/index";
import { KbInput } from "customModules/kblayout/index";
import { Updates } from "customModules/updates/index";
import { Weather } from "customModules/weather/index";
import { Power } from "customModules/power/index";
import { Ram } from '../../customModules/ram/index';
import { Cpu } from '../../customModules/cpu/index';
import { Storage } from 'customModules/storage/index';
import { Netstat } from 'customModules/netstat/index';
import { KbInput } from 'customModules/kblayout/index';
import { Updates } from 'customModules/updates/index';
import { Weather } from 'customModules/weather/index';
import { Power } from 'customModules/power/index';
export {
Menu,

View File

@@ -1,14 +1,14 @@
import options from "options";
import options from 'options';
const { showIcon, showTime } = options.bar.clock;
showIcon.connect("changed", () => {
showIcon.connect('changed', () => {
if (!showTime.value && !showIcon.value) {
showTime.value = true;
}
});
showTime.connect("changed", () => {
showTime.connect('changed', () => {
if (!showTime.value && !showIcon.value) {
showIcon.value = true;
}

View File

@@ -1,34 +1,37 @@
const battery = await Service.import("battery");
const battery = await Service.import('battery');
import Gdk from 'gi://Gdk?version=3.0';
import { openMenu } from "../utils.js";
import options from "options";
import { openMenu } from '../utils.js';
import options from 'options';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { label: show_label } = options.bar.battery;
const BatteryLabel = () => {
const BatteryLabel = (): BarBoxChild => {
const isVis = Variable(battery.available);
const batIcon = Utils.merge([battery.bind("percent"), battery.bind("charging"), battery.bind("charged")],
const batIcon = Utils.merge(
[battery.bind('percent'), battery.bind('charging'), battery.bind('charged')],
(batPercent: number, batCharging, batCharged) => {
if (batCharged)
return `battery-level-100-charged-symbolic`;
else
return `battery-level-${Math.floor(batPercent / 10) * 10}${batCharging ? '-charging' : ''}-symbolic`;
});
if (batCharged) return `battery-level-100-charged-symbolic`;
else return `battery-level-${Math.floor(batPercent / 10) * 10}${batCharging ? '-charging' : ''}-symbolic`;
},
);
battery.connect("changed", ({ available }) => {
battery.connect('changed', ({ available }) => {
isVis.value = available;
});
const formatTime = (seconds: number) => {
const formatTime = (seconds: number): Record<string, number> => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return { hours, minutes };
};
const generateTooltip = (timeSeconds: number, isCharging: boolean, isCharged: boolean) => {
const generateTooltip = (timeSeconds: number, isCharging: boolean, isCharged: boolean): string => {
if (isCharged) {
return "Fully Charged!!!";
return 'Fully Charged!!!';
}
const { hours, minutes } = formatTime(timeSeconds);
@@ -41,60 +44,56 @@ const BatteryLabel = () => {
return {
component: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), show_label.bind("value")], (style, showLabel) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
wave2: "style3",
};
return `battery ${styleMap[style]} ${!showLabel ? "no-label" : ""}`;
}),
visible: battery.bind("available"),
tooltip_text: battery.bind("time_remaining").as((t) => t.toString()),
children: Utils.merge(
[battery.bind("available"), show_label.bind("value")],
(batAvail, showLabel) => {
if (batAvail && showLabel) {
return [
Widget.Icon({
class_name: "bar-button-icon battery",
icon: batIcon
}),
Widget.Label({
class_name: "bar-button-label battery",
label: battery.bind("percent").as((p) => `${Math.floor(p)}%`),
}),
];
} else if (batAvail && !showLabel) {
return [
Widget.Icon({
class_name: "bar-button-icon battery",
icon: batIcon
})
];
} else {
return [];
}
className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), show_label.bind('value')],
(style, showLabel) => {
const styleMap = {
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `battery ${styleMap[style]} ${!showLabel ? 'no-label' : ''}`;
},
),
visible: battery.bind('available'),
tooltip_text: battery.bind('time_remaining').as((t) => t.toString()),
children: Utils.merge([battery.bind('available'), show_label.bind('value')], (batAvail, showLabel) => {
if (batAvail && showLabel) {
return [
Widget.Icon({
class_name: 'bar-button-icon battery',
icon: batIcon,
}),
Widget.Label({
class_name: 'bar-button-label battery',
label: battery.bind('percent').as((p) => `${Math.floor(p)}%`),
}),
];
} else if (batAvail && !showLabel) {
return [
Widget.Icon({
class_name: 'bar-button-icon battery',
icon: batIcon,
}),
];
} else {
return [];
}
}),
setup: (self) => {
self.hook(battery, () => {
if (battery.available) {
self.tooltip_text = generateTooltip(
battery.time_remaining,
battery.charging,
battery.charged,
);
self.tooltip_text = generateTooltip(battery.time_remaining, battery.charging, battery.charged);
}
});
},
}),
isVis,
boxClass: "battery",
boxClass: 'battery',
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "energymenu");
on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, 'energymenu');
},
},
};

View File

@@ -1,42 +1,41 @@
const bluetooth = await Service.import('bluetooth')
const bluetooth = await Service.import('bluetooth');
import Gdk from 'gi://Gdk?version=3.0';
import options from "options";
import { openMenu } from "../utils.js";
import options from 'options';
import { openMenu } from '../utils.js';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { label } = options.bar.bluetooth;
const Bluetooth = () => {
const Bluetooth = (): BarBoxChild => {
const btIcon = Widget.Label({
label: bluetooth.bind("enabled").as((v) => v ? "󰂯" : "󰂲"),
class_name: "bar-button-icon bluetooth txt-icon bar",
label: bluetooth.bind('enabled').as((v) => (v ? '󰂯' : '󰂲')),
class_name: 'bar-button-icon bluetooth txt-icon bar',
});
const btText = Widget.Label({
label: Utils.merge([
bluetooth.bind("enabled"),
bluetooth.bind("connected_devices"),
],
(btEnabled, btDevices) => {
return btEnabled && btDevices.length ? ` Connected (${btDevices.length})`
: btEnabled ? "On"
: "Off"
}),
class_name: "bar-button-label bluetooth",
label: Utils.merge([bluetooth.bind('enabled'), bluetooth.bind('connected_devices')], (btEnabled, btDevices) => {
return btEnabled && btDevices.length ? ` Connected (${btDevices.length})` : btEnabled ? 'On' : 'Off';
}),
class_name: 'bar-button-label bluetooth',
});
return {
component: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), label.bind("value")], (style, showLabel) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
wave2: "style3",
};
return `bluetooth ${styleMap[style]} ${!showLabel ? "no-label" : ""}`;
}),
children: options.bar.bluetooth.label.bind("value").as((showLabel) => {
className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), label.bind('value')],
(style, showLabel) => {
const styleMap = {
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `bluetooth ${styleMap[style]} ${!showLabel ? 'no-label' : ''}`;
},
),
children: options.bar.bluetooth.label.bind('value').as((showLabel) => {
if (showLabel) {
return [btIcon, btText];
}
@@ -44,14 +43,13 @@ const Bluetooth = () => {
}),
}),
isVisible: true,
boxClass: "bluetooth",
boxClass: 'bluetooth',
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "bluetoothmenu");
on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, 'bluetoothmenu');
},
},
};
};
}
export { Bluetooth }
export { Bluetooth };

View File

@@ -1,45 +1,46 @@
import Gdk from 'gi://Gdk?version=3.0';
import GLib from "gi://GLib";
import { openMenu } from "../utils.js";
import options from "options";
import GLib from 'gi://GLib';
import { openMenu } from '../utils.js';
import options from 'options';
import { DateTime } from 'types/@girs/glib-2.0/glib-2.0.cjs';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { format, icon, showIcon, showTime } = options.bar.clock;
const { style } = options.theme.bar.buttons;
const date = Variable(GLib.DateTime.new_now_local(), {
poll: [1000, () => GLib.DateTime.new_now_local()],
poll: [1000, (): DateTime => GLib.DateTime.new_now_local()],
});
const time = Utils.derive([date, format], (c, f) => c.format(f) || "");
const Clock = () => {
const time = Utils.derive([date, format], (c, f) => c.format(f) || '');
const Clock = (): BarBoxChild => {
const clockTime = Widget.Label({
class_name: "bar-button-label clock bar",
class_name: 'bar-button-label clock bar',
label: time.bind(),
});
const clockIcon = Widget.Label({
label: icon.bind("value"),
class_name: "bar-button-icon clock txt-icon bar",
label: icon.bind('value'),
class_name: 'bar-button-icon clock txt-icon bar',
});
return {
component: Widget.Box({
className: Utils.merge([
style.bind("value"),
showIcon.bind("value"), showTime.bind("value")
], (btnStyle, shwIcn, shwLbl) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
wave2: "style3",
};
return `bluetooth ${styleMap[btnStyle]} ${!shwLbl ? "no-label" : ""} ${!shwIcn ? "no-icon" : ""}`;
}),
children: Utils.merge([showIcon.bind("value"), showTime.bind("value")], (shIcn, shTm) => {
className: Utils.merge(
[style.bind('value'), showIcon.bind('value'), showTime.bind('value')],
(btnStyle, shwIcn, shwLbl) => {
const styleMap = {
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `bluetooth ${styleMap[btnStyle]} ${!shwLbl ? 'no-label' : ''} ${!shwIcn ? 'no-icon' : ''}`;
},
),
children: Utils.merge([showIcon.bind('value'), showTime.bind('value')], (shIcn, shTm) => {
if (shIcn && !shTm) {
return [clockIcon];
} else if (shTm && !shIcn) {
@@ -47,13 +48,13 @@ const Clock = () => {
}
return [clockIcon, clockTime];
})
}),
}),
isVisible: true,
boxClass: "clock",
boxClass: 'clock',
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "calendarmenu");
on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, 'calendarmenu');
},
},
};

View File

@@ -1,20 +1,23 @@
import Gdk from 'gi://Gdk?version=3.0';
const mpris = await Service.import("mpris");
import { openMenu } from "../utils.js";
import options from "options";
const mpris = await Service.import('mpris');
import { openMenu } from '../utils.js';
import options from 'options';
import { getCurrentPlayer } from 'lib/shared/media.js';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { show_artist, truncation, truncation_size, show_label, show_active_only } = options.bar.media;
const Media = () => {
const Media = (): BarBoxChild => {
const activePlayer = Variable(mpris.players[0]);
const isVis = Variable(!show_active_only.value);
show_active_only.connect("changed", () => {
show_active_only.connect('changed', () => {
isVis.value = !show_active_only.value || mpris.players.length > 0;
});
mpris.connect("changed", () => {
mpris.connect('changed', () => {
const curPlayer = getCurrentPlayer(activePlayer.value);
activePlayer.value = curPlayer;
isVis.value = !show_active_only.value || mpris.players.length > 0;
@@ -22,41 +25,37 @@ const Media = () => {
const getIconForPlayer = (playerName: string): string => {
const windowTitleMap = [
["Firefox", "󰈹"],
["Microsoft Edge", "󰇩"],
["Discord", ""],
["Plex", "󰚺"],
["Spotify", "󰓇"],
["(.*)", "󰝚"],
['Firefox', '󰈹'],
['Microsoft Edge', '󰇩'],
['Discord', ''],
['Plex', '󰚺'],
['Spotify', '󰓇'],
['(.*)', '󰝚'],
];
const foundMatch = windowTitleMap.find((wt) =>
RegExp(wt[0], "i").test(playerName),
);
const foundMatch = windowTitleMap.find((wt) => RegExp(wt[0], 'i').test(playerName));
return foundMatch ? foundMatch[1] : "󰝚";
return foundMatch ? foundMatch[1] : '󰝚';
};
const songIcon = Variable("");
const songIcon = Variable('');
const mediaLabel = Utils.watch("Media", [mpris, show_artist, truncation, truncation_size, show_label], () => {
const mediaLabel = Utils.watch('Media', [mpris, show_artist, truncation, truncation_size, show_label], () => {
if (activePlayer.value && show_label.value) {
const { track_title, identity, track_artists } = activePlayer.value;
songIcon.value = getIconForPlayer(identity);
const trackArtist = show_artist.value
? ` - ${track_artists.join(', ')}`
: ``;
const trackArtist = show_artist.value ? ` - ${track_artists.join(', ')}` : ``;
const truncatedLabel = truncation.value
? `${track_title + trackArtist}`.substring(0, truncation_size.value)
: `${track_title + trackArtist}`;
return track_title.length === 0
? `No media playing...`
: ((truncatedLabel.length < truncation_size.value) || !truncation.value)
? `${truncatedLabel}`
: `${truncatedLabel.substring(0, truncatedLabel.length - 3)}...`;
: truncatedLabel.length < truncation_size.value || !truncation.value
? `${truncatedLabel}`
: `${truncatedLabel.substring(0, truncatedLabel.length - 3)}...`;
} else {
songIcon.value = getIconForPlayer(activePlayer.value?.identity || "");
songIcon.value = getIconForPlayer(activePlayer.value?.identity || '');
return `Media`;
}
});
@@ -65,23 +64,26 @@ const Media = () => {
component: Widget.Box({
visible: false,
child: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), show_label.bind("value")], (style, showLabel) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
wave2: "style3",
};
return `media ${styleMap[style]}`;
}),
className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), show_label.bind('value')],
(style) => {
const styleMap = {
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `media ${styleMap[style]}`;
},
),
child: Widget.Box({
children: [
Widget.Label({
class_name: "bar-button-icon media txt-icon bar",
label: songIcon.bind("value").as(v => v || "󰝚"),
class_name: 'bar-button-icon media txt-icon bar',
label: songIcon.bind('value').as((v) => v || '󰝚'),
}),
Widget.Label({
class_name: "bar-button-label media",
class_name: 'bar-button-label media',
label: mediaLabel,
}),
],
@@ -89,13 +91,13 @@ const Media = () => {
}),
}),
isVis,
boxClass: "media",
name: "media",
boxClass: 'media',
name: 'media',
props: {
on_scroll_up: () => activePlayer.value?.next(),
on_scroll_down: () => activePlayer.value?.previous(),
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "mediamenu");
on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, 'mediamenu');
},
},
};

View File

@@ -1,29 +1,32 @@
import Gdk from 'gi://Gdk?version=3.0';
import { openMenu } from "../utils.js";
import options from "options";
import { openMenu } from '../utils.js';
import options from 'options';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const Menu = () => {
const Menu = (): BarBoxChild => {
return {
component: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value")], (style) => {
className: Utils.merge([options.theme.bar.buttons.style.bind('value')], (style) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
wave2: "style3",
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `dashboard ${styleMap[style]}`;
}),
child: Widget.Label({
class_name: "bar-menu_label bar-button_icon txt-icon bar",
label: options.bar.launcher.icon.bind("value"),
class_name: 'bar-menu_label bar-button_icon txt-icon bar',
label: options.bar.launcher.icon.bind('value'),
}),
}),
isVisible: true,
boxClass: "dashboard",
boxClass: 'dashboard',
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "dashboardmenu");
on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, 'dashboardmenu');
},
},
};

View File

@@ -1,70 +1,77 @@
import Gdk from 'gi://Gdk?version=3.0';
const network = await Service.import("network");
import options from "options";
import { openMenu } from "../utils.js";
const network = await Service.import('network');
import options from 'options';
import { openMenu } from '../utils.js';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { label: networkLabel, truncation, truncation_size } = options.bar.network;
const Network = () => {
const Network = (): BarBoxChild => {
return {
component: Widget.Box({
vpack: "fill",
vpack: 'fill',
vexpand: true,
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), networkLabel.bind("value")], (style, showLabel) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
wave2: "style3",
};
return `network ${styleMap[style]}${!showLabel ? " no-label" : ""}`;
}),
className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), networkLabel.bind('value')],
(style, showLabel) => {
const styleMap = {
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `network ${styleMap[style]}${!showLabel ? ' no-label' : ''}`;
},
),
children: [
Widget.Icon({
class_name: "bar-button-icon network",
icon: Utils.merge([
network.bind("primary"),
network.bind("wifi"),
network.bind("wired")
], (pmry, wfi, wrd) => {
if (pmry === "wired") {
return wrd.icon_name;
}
return wfi.icon_name;
})
class_name: 'bar-button-icon network',
icon: Utils.merge(
[network.bind('primary'), network.bind('wifi'), network.bind('wired')],
(pmry, wfi, wrd) => {
if (pmry === 'wired') {
return wrd.icon_name;
}
return wfi.icon_name;
},
),
}),
Widget.Box({
vpack: "center",
child: Utils.merge([
network.bind("primary"),
network.bind("wifi"),
networkLabel.bind("value"),
truncation.bind("value"),
truncation_size.bind("value")
], (pmry, wfi, showLbl, trunc, tSize) => {
if (!showLbl) {
return Widget.Box();
}
if (pmry === "wired") {
vpack: 'center',
child: Utils.merge(
[
network.bind('primary'),
network.bind('wifi'),
networkLabel.bind('value'),
truncation.bind('value'),
truncation_size.bind('value'),
],
(pmry, wfi, showLbl, trunc, tSize) => {
if (!showLbl) {
return Widget.Box();
}
if (pmry === 'wired') {
return Widget.Label({
class_name: 'bar-button-label network',
label: 'Wired'.substring(0, tSize),
});
}
return Widget.Label({
class_name: "bar-button-label network",
label: "Wired".substring(0, tSize),
})
}
return Widget.Label({
class_name: "bar-button-label network",
label: wfi.ssid ? `${trunc ? wfi.ssid.substring(0, tSize) : wfi.ssid}` : "--",
})
})
class_name: 'bar-button-label network',
label: wfi.ssid ? `${trunc ? wfi.ssid.substring(0, tSize) : wfi.ssid}` : '--',
});
},
),
}),
]
],
}),
isVisible: true,
boxClass: "network",
boxClass: 'network',
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "networkmenu");
on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, 'networkmenu');
},
},
};

View File

@@ -1,51 +1,49 @@
import Gdk from 'gi://Gdk?version=3.0';
import { openMenu } from "../utils.js";
import options from "options";
import { openMenu } from '../utils.js';
import options from 'options';
import { filterNotifications } from 'lib/shared/notifications.js';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { show_total } = options.bar.notifications;
const { ignore } = options.notifications;
const notifs = await Service.import("notifications");
const notifs = await Service.import('notifications');
export const Notifications = () => {
export const Notifications = (): BarBoxChild => {
return {
component: Widget.Box({
hpack: "start",
hpack: 'start',
className: Utils.merge(
[
options.theme.bar.buttons.style.bind("value"),
show_total.bind("value")
],
(
style,
showTotal
) => {
[options.theme.bar.buttons.style.bind('value'), show_total.bind('value')],
(style, showTotal) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
wave2: "style3",
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `notifications ${styleMap[style]} ${!showTotal ? "no-label" : ""}`;
}),
return `notifications ${styleMap[style]} ${!showTotal ? 'no-label' : ''}`;
},
),
child: Widget.Box({
hpack: "start",
class_name: "bar-notifications",
hpack: 'start',
class_name: 'bar-notifications',
children: Utils.merge(
[notifs.bind("notifications"), notifs.bind("dnd"), show_total.bind("value"), ignore.bind("value")],
[notifs.bind('notifications'), notifs.bind('dnd'), show_total.bind('value'), ignore.bind('value')],
(notif, dnd, showTotal, ignoredNotifs) => {
const filteredNotifications = filterNotifications(notif, ignoredNotifs);
const notifIcon = Widget.Label({
hpack: "center",
class_name: "bar-button-icon notifications txt-icon bar",
label: dnd ? "󰂛" : filteredNotifications.length > 0 ? "󱅫" : "󰂚",
hpack: 'center',
class_name: 'bar-button-icon notifications txt-icon bar',
label: dnd ? '󰂛' : filteredNotifications.length > 0 ? '󱅫' : '󰂚',
});
const notifLabel = Widget.Label({
hpack: "center",
class_name: "bar-button-label notifications",
hpack: 'center',
class_name: 'bar-button-label notifications',
label: filteredNotifications.length.toString(),
});
@@ -58,10 +56,10 @@ export const Notifications = () => {
}),
}),
isVisible: true,
boxClass: "notifications",
boxClass: 'notifications',
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "notificationsmenu");
on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, 'notificationsmenu');
},
},
};

View File

@@ -1,49 +1,43 @@
import Gdk from 'gi://Gdk?version=3.0';
import { BarBoxChild, SelfButton } from 'lib/types/bar';
import { Notify } from 'lib/utils';
const systemtray = await Service.import("systemtray");
import options from "options";
const systemtray = await Service.import('systemtray');
import options from 'options';
const { ignore } = options.bar.systray;
const SysTray = () => {
const SysTray = (): BarBoxChild => {
const isVis = Variable(false);
const items = Utils.merge(
[systemtray.bind("items"), ignore.bind("value")],
(items, ignored) => {
const filteredTray = items.filter(({ id }) => !ignored.includes(id));
const items = Utils.merge([systemtray.bind('items'), ignore.bind('value')], (items, ignored) => {
const filteredTray = items.filter(({ id }) => !ignored.includes(id));
isVis.value = filteredTray.length > 0;
isVis.value = filteredTray.length > 0;
return filteredTray.map((item) => {
if (item.menu !== undefined) {
item.menu["class_name"] = "systray-menu";
}
return Widget.Button({
cursor: "pointer",
child: Widget.Icon({
class_name: "systray-icon",
icon: item.bind("icon"),
}),
on_primary_click: (_: any, event: Gdk.Event) => item.activate(event),
on_secondary_click: (_, event) => item.openMenu(event),
onMiddleClick: () => Notify({ summary: "App Name", body: item.id }),
tooltip_markup: item.bind("tooltip_markup"),
});
return filteredTray.map((item) => {
return Widget.Button({
cursor: 'pointer',
child: Widget.Icon({
class_name: 'systray-icon',
icon: item.bind('icon'),
}),
on_primary_click: (_: SelfButton, event: Gdk.Event) => item.activate(event),
on_secondary_click: (_, event) => item.openMenu(event),
onMiddleClick: () => Notify({ summary: 'App Name', body: item.id }),
tooltip_markup: item.bind('tooltip_markup'),
});
},
);
});
});
return {
component: Widget.Box({
class_name: "systray",
class_name: 'systray',
children: items,
}),
isVisible: true,
boxClass: "systray",
boxClass: 'systray',
isVis,
props: {}
props: {},
};
};

View File

@@ -1,6 +1,8 @@
import Gdk from 'gi://Gdk?version=3.0';
import { Child } from 'lib/types/widget';
import Button from 'types/widgets/button';
export const closeAllMenus = () => {
export const closeAllMenus = (): void => {
const menuWindows = App.windows
.filter((w) => {
if (w.name) {
@@ -18,7 +20,7 @@ export const closeAllMenus = () => {
});
};
export const openMenu = (clicked: any, event: Gdk.Event, window: string) => {
export const openMenu = (clicked: Button<Child, Child>, event: Gdk.Event, window: string): void => {
/*
* NOTE: We have to make some adjustments so the menu pops up relatively
* to the center of the button clicked. We don't want the menu to spawn

View File

@@ -1,59 +1,63 @@
import Gdk from 'gi://Gdk?version=3.0';
const audio = await Service.import("audio");
import { openMenu } from "../utils.js";
import options from "options";
const audio = await Service.import('audio');
import { openMenu } from '../utils.js';
import options from 'options';
import { Binding } from 'lib/utils.js';
import { VolumeIcons } from 'lib/types/volume.js';
import { BarBoxChild } from 'lib/types/bar.js';
import { Bind } from 'lib/types/variable.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const Volume = () => {
const Volume = (): BarBoxChild => {
const icons: VolumeIcons = {
101: "󰕾",
66: "󰕾",
34: "󰖀",
1: "󰕿",
0: "󰝟",
101: '󰕾',
66: '󰕾',
34: '󰖀',
1: '󰕿',
0: '󰝟',
};
const getIcon = () => {
const getIcon = (): Bind => {
const icon: Binding<number> = Utils.merge(
[audio.speaker.bind("is_muted"), audio.speaker.bind("volume")],
[audio.speaker.bind('is_muted'), audio.speaker.bind('volume')],
(isMuted, vol) => {
return isMuted
? 0
: [101, 66, 34, 1, 0].find((threshold) => threshold <= vol * 100) || 101;
return isMuted ? 0 : [101, 66, 34, 1, 0].find((threshold) => threshold <= vol * 100) || 101;
},
);
return icon.as((i: number) => i !== undefined ? icons[i] : icons[101]);
return icon.as((i: number) => (i !== undefined ? icons[i] : icons[101]));
};
const volIcn = Widget.Label({
hexpand: true,
label: getIcon(),
class_name: "bar-button-icon volume txt-icon bar",
class_name: 'bar-button-icon volume txt-icon bar',
});
const volPct = Widget.Label({
hexpand: true,
label: audio.speaker.bind("volume").as((v) => `${Math.round(v * 100)}%`),
class_name: "bar-button-label volume",
label: audio.speaker.bind('volume').as((v) => `${Math.round(v * 100)}%`),
class_name: 'bar-button-label volume',
});
return {
component: Widget.Box({
hexpand: true,
vexpand: true,
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), options.bar.volume.label.bind("value")], (style, showLabel) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
wave2: "style3",
};
return `volume ${styleMap[style]} ${!showLabel ? "no-label" : ""}`;
}),
children: options.bar.volume.label.bind("value").as((showLabel) => {
className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), options.bar.volume.label.bind('value')],
(style, showLabel) => {
const styleMap = {
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `volume ${styleMap[style]} ${!showLabel ? 'no-label' : ''}`;
},
),
children: options.bar.volume.label.bind('value').as((showLabel) => {
if (showLabel) {
return [volIcn, volPct];
}
@@ -61,10 +65,10 @@ const Volume = () => {
}),
}),
isVisible: true,
boxClass: "volume",
boxClass: 'volume',
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "audiomenu");
on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, 'audiomenu');
},
},
};

View File

@@ -1,122 +1,119 @@
const hyprland = await Service.import("hyprland");
const hyprland = await Service.import('hyprland');
import { BarBoxChild } from 'lib/types/bar';
import options from 'options';
import { ActiveClient } from 'types/service/hyprland'
import Label from "types/widgets/label";
import { ActiveClient } from 'types/service/hyprland';
const filterTitle = (windowtitle: ActiveClient) => {
const filterTitle = (windowtitle: ActiveClient): Record<string, string> => {
const windowTitleMap = [
// user provided values
...options.bar.windowtitle.title_map.value,
// Original Entries
["kitty", "󰄛", "Kitty Terminal"],
["firefox", "󰈹", "Firefox"],
["microsoft-edge", "󰇩", "Edge"],
["discord", "", "Discord"],
["vesktop", "", "Vesktop"],
["org.kde.dolphin", "", "Dolphin"],
["plex", "󰚺", "Plex"],
["steam", "", "Steam"],
["spotify", "󰓇", "Spotify"],
["ristretto", "󰋩", "Ristretto"],
["obsidian", "󱓧", "Obsidian"],
['kitty', '󰄛', 'Kitty Terminal'],
['firefox', '󰈹', 'Firefox'],
['microsoft-edge', '󰇩', 'Edge'],
['discord', '', 'Discord'],
['vesktop', '', 'Vesktop'],
['org.kde.dolphin', '', 'Dolphin'],
['plex', '󰚺', 'Plex'],
['steam', '', 'Steam'],
['spotify', '󰓇', 'Spotify'],
['ristretto', '󰋩', 'Ristretto'],
['obsidian', '󱓧', 'Obsidian'],
// Browsers
["google-chrome", "", "Google Chrome"],
["brave-browser", "󰖟", "Brave Browser"],
["chromium", "", "Chromium"],
["opera", "", "Opera"],
["vivaldi", "󰖟", "Vivaldi"],
["waterfox", "󰖟", "Waterfox"],
["thorium", "󰖟", "Waterfox"],
["tor-browser", "", "Tor Browser"],
["floorp", "󰈹", "Floorp"],
['google-chrome', '', 'Google Chrome'],
['brave-browser', '󰖟', 'Brave Browser'],
['chromium', '', 'Chromium'],
['opera', '', 'Opera'],
['vivaldi', '󰖟', 'Vivaldi'],
['waterfox', '󰖟', 'Waterfox'],
['thorium', '󰖟', 'Waterfox'],
['tor-browser', '', 'Tor Browser'],
['floorp', '󰈹', 'Floorp'],
// Terminals
["gnome-terminal", "", "GNOME Terminal"],
["konsole", "", "Konsole"],
["alacritty", "", "Alacritty"],
["wezterm", "", "Wezterm"],
["foot", "󰽒", "Foot Terminal"],
["tilix", "", "Tilix"],
["xterm", "", "XTerm"],
["urxvt", "", "URxvt"],
["st", "", "st Terminal"],
['gnome-terminal', '', 'GNOME Terminal'],
['konsole', '', 'Konsole'],
['alacritty', '', 'Alacritty'],
['wezterm', '', 'Wezterm'],
['foot', '󰽒', 'Foot Terminal'],
['tilix', '', 'Tilix'],
['xterm', '', 'XTerm'],
['urxvt', '', 'URxvt'],
['st', '', 'st Terminal'],
// Development Tools
["code", "󰨞", "Visual Studio Code"],
["vscode", "󰨞", "VS Code"],
["sublime-text", "", "Sublime Text"],
["atom", "", "Atom"],
["android-studio", "󰀴", "Android Studio"],
["intellij-idea", "", "IntelliJ IDEA"],
["pycharm", "󱃖", "PyCharm"],
["webstorm", "󱃖", "WebStorm"],
["phpstorm", "󱃖", "PhpStorm"],
["eclipse", "", "Eclipse"],
["netbeans", "", "NetBeans"],
["docker", "", "Docker"],
["vim", "", "Vim"],
["neovim", "", "Neovim"],
["neovide", "", "Neovide"],
["emacs", "", "Emacs"],
['code', '󰨞', 'Visual Studio Code'],
['vscode', '󰨞', 'VS Code'],
['sublime-text', '', 'Sublime Text'],
['atom', '', 'Atom'],
['android-studio', '󰀴', 'Android Studio'],
['intellij-idea', '', 'IntelliJ IDEA'],
['pycharm', '󱃖', 'PyCharm'],
['webstorm', '󱃖', 'WebStorm'],
['phpstorm', '󱃖', 'PhpStorm'],
['eclipse', '', 'Eclipse'],
['netbeans', '', 'NetBeans'],
['docker', '', 'Docker'],
['vim', '', 'Vim'],
['neovim', '', 'Neovim'],
['neovide', '', 'Neovide'],
['emacs', '', 'Emacs'],
// Communication Tools
["slack", "󰒱", "Slack"],
["telegram-desktop", "", "Telegram"],
["org.telegram.desktop", "", "Telegram"],
["whatsapp", "󰖣", "WhatsApp"],
["teams", "󰊻", "Microsoft Teams"],
["skype", "󰒯", "Skype"],
["thunderbird", "", "Thunderbird"],
['slack', '󰒱', 'Slack'],
['telegram-desktop', '', 'Telegram'],
['org.telegram.desktop', '', 'Telegram'],
['whatsapp', '󰖣', 'WhatsApp'],
['teams', '󰊻', 'Microsoft Teams'],
['skype', '󰒯', 'Skype'],
['thunderbird', '', 'Thunderbird'],
// File Managers
["nautilus", "󰝰", "Files (Nautilus)"],
["thunar", "󰝰", "Thunar"],
["pcmanfm", "󰝰", "PCManFM"],
["nemo", "󰝰", "Nemo"],
["ranger", "󰝰", "Ranger"],
["doublecmd", "󰝰", "Double Commander"],
["krusader", "󰝰", "Krusader"],
['nautilus', '󰝰', 'Files (Nautilus)'],
['thunar', '󰝰', 'Thunar'],
['pcmanfm', '󰝰', 'PCManFM'],
['nemo', '󰝰', 'Nemo'],
['ranger', '󰝰', 'Ranger'],
['doublecmd', '󰝰', 'Double Commander'],
['krusader', '󰝰', 'Krusader'],
// Media Players
["vlc", "󰕼", "VLC Media Player"],
["mpv", "", "MPV"],
["rhythmbox", "󰓃", "Rhythmbox"],
['vlc', '󰕼', 'VLC Media Player'],
['mpv', '', 'MPV'],
['rhythmbox', '󰓃', 'Rhythmbox'],
// Graphics Tools
["gimp", "", "GIMP"],
["inkscape", "", "Inkscape"],
["krita", "", "Krita"],
["blender", "󰂫", "Blender"],
['gimp', '', 'GIMP'],
['inkscape', '', 'Inkscape'],
['krita', '', 'Krita'],
['blender', '󰂫', 'Blender'],
// Video Editing
["kdenlive", "", "Kdenlive"],
['kdenlive', '', 'Kdenlive'],
// Games and Gaming Platforms
["lutris", "󰺵", "Lutris"],
["heroic", "󰺵", "Heroic Games Launcher"],
["minecraft", "󰍳", "Minecraft"],
["csgo", "󰺵", "CS:GO"],
["dota2", "󰺵", "Dota 2"],
['lutris', '󰺵', 'Lutris'],
['heroic', '󰺵', 'Heroic Games Launcher'],
['minecraft', '󰍳', 'Minecraft'],
['csgo', '󰺵', 'CS:GO'],
['dota2', '󰺵', 'Dota 2'],
// Office and Productivity
["evernote", "", "Evernote"],
["sioyek", "", "Sioyek"],
['evernote', '', 'Evernote'],
['sioyek', '', 'Sioyek'],
// Cloud Services and Sync
["dropbox", "󰇣", "Dropbox"],
['dropbox', '󰇣', 'Dropbox'],
// Desktop
["^$", "󰇄", "Desktop"],
['^$', '󰇄', 'Desktop'],
// Fallback icon
["(.+)", "󰣆", `${windowtitle.class.charAt(0).toUpperCase() + windowtitle.class.slice(1)}`],
['(.+)', '󰣆', `${windowtitle.class.charAt(0).toUpperCase() + windowtitle.class.slice(1)}`],
];
const foundMatch = windowTitleMap.find((wt) =>
RegExp(wt[0]).test(windowtitle.class.toLowerCase()),
);
const foundMatch = windowTitleMap.find((wt) => RegExp(wt[0]).test(windowtitle.class.toLowerCase()));
// return the default icon if no match is found or
// if the array element matched is not of size 3
@@ -129,15 +126,15 @@ const filterTitle = (windowtitle: ActiveClient) => {
return {
icon: foundMatch[1],
label: foundMatch[2]
label: foundMatch[2],
};
};
const getTitle = (client: ActiveClient, useCustomTitle: boolean, useClassName: boolean) => {
const getTitle = (client: ActiveClient, useCustomTitle: boolean, useClassName: boolean): string => {
if (useCustomTitle) return filterTitle(client).label;
if (useClassName) return client.class;
let title = client.title;
const title = client.title;
// If the title is empty or only filled with spaces, fallback to the class name
if (title.length === 0 || title.match(/^ *$/)) {
return client.class;
@@ -145,51 +142,76 @@ const getTitle = (client: ActiveClient, useCustomTitle: boolean, useClassName: b
return title;
};
const truncateTitle = (title: string, max_size: number) => {
const truncateTitle = (title: string, max_size: number): string => {
if (max_size > 0 && title.length > max_size) {
return title.substring(0, max_size).trim() + "...";
return title.substring(0, max_size).trim() + '...';
}
return title;
};
const ClientTitle = () => {
const ClientTitle = (): BarBoxChild => {
const { custom_title, class_name, label, icon, truncation, truncation_size } = options.bar.windowtitle;
return {
component: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), label.bind("value")], (style, showLabel) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
wave2: "style3",
};
return `windowtitle ${styleMap[style]} ${!showLabel ? "no-label" : ""}`;
}),
children:
Utils.merge(
[hyprland.active.bind("client"), custom_title.bind("value"), class_name.bind("value"), label.bind("value"),
icon.bind("value"), truncation.bind("value"), truncation_size.bind("value")],
(client, useCustomTitle, useClassName, showLabel, showIcon, truncate, truncationSize) => {
const children: Label<any>[] = [];
if (showIcon) {
children.push(Widget.Label({
class_name: "bar-button-icon windowtitle txt-icon bar",
className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), label.bind('value')],
(style, showLabel) => {
const styleMap = {
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `windowtitle ${styleMap[style]} ${!showLabel ? 'no-label' : ''}`;
},
),
children: Utils.merge(
[
hyprland.active.bind('client'),
custom_title.bind('value'),
class_name.bind('value'),
label.bind('value'),
icon.bind('value'),
truncation.bind('value'),
truncation_size.bind('value'),
],
(client, useCustomTitle, useClassName, showLabel, showIcon, truncate, truncationSize) => {
if (showIcon) {
return [
Widget.Label({
class_name: 'bar-button-icon windowtitle txt-icon bar',
label: filterTitle(client).icon,
}));
}
if (showLabel) {
children.push(Widget.Label({
class_name: `bar-button-label windowtitle ${showIcon ? "" : "no-icon"}`,
label: truncateTitle(getTitle(client, useCustomTitle, useClassName), truncate ? truncationSize : -1),
}));
}
return children;
}),
}),
Widget.Label({
class_name: `bar-button-label windowtitle ${showIcon ? '' : 'no-icon'}`,
label: truncateTitle(
getTitle(client, useCustomTitle, useClassName),
truncate ? truncationSize : -1,
),
}),
];
}
if (showLabel) {
return [
Widget.Label({
class_name: `bar-button-label windowtitle ${showIcon ? '' : 'no-icon'}`,
label: truncateTitle(
getTitle(client, useCustomTitle, useClassName),
truncate ? truncationSize : -1,
),
}),
];
}
return [];
},
),
}),
isVisible: true,
boxClass: "windowtitle",
props: {}
boxClass: 'windowtitle',
props: {},
};
};

View File

@@ -1,23 +1,21 @@
const hyprland = await Service.import("hyprland");
const hyprland = await Service.import('hyprland');
import { WorkspaceMap, WorkspaceRule } from "lib/types/workspace";
import options from "options";
import { Variable } from "types/variable";
const {
workspaces,
reverse_scroll,
} = options.bar.workspaces;
import { MonitorMap, WorkspaceMap, WorkspaceRule } from 'lib/types/workspace';
import options from 'options';
import { Variable } from 'types/variable';
const { workspaces, reverse_scroll } = options.bar.workspaces;
export const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap, monitor: number): boolean => {
if (!wsRules || !Object.keys(wsRules).length) {
return true;
}
const monitorMap = {};
const workspaceMonitorList = hyprland?.workspaces?.map(m => ({ id: m.monitorID, name: m.monitor }));
const monitors = [...new Map([...workspaceMonitorList, ...hyprland.monitors].map(item => [item.id, item])).values()];
const monitorMap: MonitorMap = {};
const workspaceMonitorList = hyprland?.workspaces?.map((m) => ({ id: m.monitorID, name: m.monitor }));
const monitors = [
...new Map([...workspaceMonitorList, ...hyprland.monitors].map((item) => [item.id, item])).values(),
];
monitors.forEach((m) => (monitorMap[m.id] = m.name));
@@ -32,9 +30,9 @@ export const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap, mo
export const getWorkspaceRules = (): WorkspaceMap => {
try {
const rules = Utils.exec("hyprctl workspacerules -j");
const rules = Utils.exec('hyprctl workspacerules -j');
const workspaceRules = {};
const workspaceRules: WorkspaceMap = {};
JSON.parse(rules).forEach((rule: WorkspaceRule, index: number) => {
if (Object.hasOwnProperty.call(workspaceRules, rule.monitor)) {
@@ -60,13 +58,13 @@ export const getCurrentMonitorWorkspaces = (monitor: number): number[] => {
}
const monitorWorkspaces = getWorkspaceRules();
const monitorMap = {};
const monitorMap: MonitorMap = {};
hyprland.monitors.forEach((m) => (monitorMap[m.id] = m.name));
const currentMonitorName = monitorMap[monitor];
return monitorWorkspaces[currentMonitorName];
}
};
export const goToNextWS = (currentMonitorWorkspaces: Variable<number[]>, activeWorkspaces: boolean): void => {
if (activeWorkspaces === true) {
@@ -74,18 +72,17 @@ export const goToNextWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
let nextIndex = hyprland.active.workspace.id + 1;
if (nextIndex > activeWses[activeWses.length - 1].id) {
nextIndex = activeWses[0].id;
}
hyprland.messageAsync(`dispatch workspace ${nextIndex}`)
hyprland.messageAsync(`dispatch workspace ${nextIndex}`);
} else if (currentMonitorWorkspaces.value === undefined) {
let nextIndex = hyprland.active.workspace.id + 1;
if (nextIndex > workspaces.value) {
nextIndex = 0;
}
hyprland.messageAsync(`dispatch workspace ${nextIndex}`)
hyprland.messageAsync(`dispatch workspace ${nextIndex}`);
} else {
const curWorkspace = hyprland.active.workspace.id;
const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace);
@@ -94,9 +91,9 @@ export const goToNextWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
nextIndex = 0;
}
hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[nextIndex]}`)
hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[nextIndex]}`);
}
}
};
export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeWorkspaces: boolean): void => {
if (activeWorkspaces === true) {
@@ -104,11 +101,10 @@ export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
let prevIndex = hyprland.active.workspace.id - 1;
if (prevIndex < activeWses[0].id) {
prevIndex = activeWses[activeWses.length - 1].id;
}
hyprland.messageAsync(`dispatch workspace ${prevIndex}`)
hyprland.messageAsync(`dispatch workspace ${prevIndex}`);
} else if (currentMonitorWorkspaces.value === undefined) {
let prevIndex = hyprland.active.workspace.id - 1;
@@ -116,7 +112,7 @@ export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
prevIndex = workspaces.value;
}
hyprland.messageAsync(`dispatch workspace ${prevIndex}`)
hyprland.messageAsync(`dispatch workspace ${prevIndex}`);
} else {
const curWorkspace = hyprland.active.workspace.id;
const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace);
@@ -125,11 +121,11 @@ export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
prevIndex = currentMonitorWorkspaces.value.length - 1;
}
hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[prevIndex]}`)
hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[prevIndex]}`);
}
}
};
export function throttle<T extends (...args: any[]) => void>(func: T, limit: number): T {
export function throttle<T extends (...args: unknown[]) => void>(func: T, limit: number): T {
let inThrottle: boolean;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (!inThrottle) {
@@ -147,7 +143,11 @@ type ThrottledScrollHandlers = {
throttledScrollDown: () => void;
};
export const createThrottledScrollHandlers = (scrollSpeed: number, currentMonitorWorkspaces: Variable<number[]>, activeWorkspaces: boolean = false): ThrottledScrollHandlers => {
export const createThrottledScrollHandlers = (
scrollSpeed: number,
currentMonitorWorkspaces: Variable<number[]>,
activeWorkspaces: boolean = false,
): ThrottledScrollHandlers => {
const throttledScrollUp = throttle(() => {
if (reverse_scroll.value === true) {
goToPrevWS(currentMonitorWorkspaces, activeWorkspaces);
@@ -165,4 +165,4 @@ export const createThrottledScrollHandlers = (scrollSpeed: number, currentMonito
}, 200 / scrollSpeed);
return { throttledScrollUp, throttledScrollDown };
}
};

View File

@@ -1,42 +1,54 @@
const hyprland = await Service.import("hyprland");
import options from "options";
import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces, getWorkspaceRules, getWorkspacesForMonitor } from "./helpers";
import { Workspace } from "types/service/hyprland";
const hyprland = await Service.import('hyprland');
import options from 'options';
import {
createThrottledScrollHandlers,
getCurrentMonitorWorkspaces,
getWorkspaceRules,
getWorkspacesForMonitor,
} from './helpers';
import { Workspace } from 'types/service/hyprland';
import { BoxWidget } from 'lib/types/widget';
import { BarBoxChild, SelfButton } from 'lib/types/bar';
const {
workspaces,
monitorSpecific,
workspaceMask,
scroll_speed,
spacing
} = options.bar.workspaces;
const { workspaces, monitorSpecific, workspaceMask, scroll_speed, spacing } = options.bar.workspaces;
function range(length: number, start = 1) {
function range(length: number, start = 1): number[] {
return Array.from({ length }, (_, i) => i + start);
}
const Workspaces = (monitor = -1) => {
const Workspaces = (monitor = -1): BarBoxChild => {
const currentMonitorWorkspaces = Variable(getCurrentMonitorWorkspaces(monitor));
workspaces.connect("changed", () => {
currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces(monitor)
})
workspaces.connect('changed', () => {
currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces(monitor);
});
const renderClassnames = (showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string, i: number) => {
const renderClassnames = (
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
i: number,
): string => {
if (showIcons) {
return `workspace-icon txt-icon bar`;
}
if (showNumbered) {
const numActiveInd = hyprland.active.workspace.id === i
? numberedActiveIndicator
: "";
const numActiveInd = hyprland.active.workspace.id === i ? numberedActiveIndicator : '';
return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`;
}
return "default";
}
return 'default';
};
const renderLabel = (showIcons: boolean, available: string, active: string, occupied: string, workspaceMask: boolean, i: number, index: number) => {
const renderLabel = (
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
i: number,
index: number,
): string => {
if (showIcons) {
if (hyprland.active.workspace.id === i) {
return active;
@@ -44,20 +56,16 @@ const Workspaces = (monitor = -1) => {
if ((hyprland.getWorkspace(i)?.windows || 0) > 0) {
return occupied;
}
if (
monitor !== -1
) {
if (monitor !== -1) {
return available;
}
}
return workspaceMask
? `${index + 1}`
: `${i}`;
}
const defaultWses = () => {
return workspaceMask ? `${index + 1}` : `${i}`;
};
const defaultWses = (): BoxWidget => {
return Widget.Box({
children: Utils.merge(
[workspaces.bind("value"), monitorSpecific.bind()],
[workspaces.bind('value'), monitorSpecific.bind()],
(workspaces: number, monitorSpecific: boolean) => {
return range(workspaces || 8)
.filter((i) => {
@@ -72,49 +80,57 @@ const Workspaces = (monitor = -1) => {
})
.map((i, index) => {
return Widget.Button({
class_name: "workspace-button",
class_name: 'workspace-button',
on_primary_click: () => {
hyprland.messageAsync(`dispatch workspace ${i}`)
hyprland.messageAsync(`dispatch workspace ${i}`);
},
child: Widget.Label({
attribute: i,
vpack: "center",
css: spacing.bind("value").as(sp => `margin: 0rem ${0.375 * sp}rem;`),
vpack: 'center',
css: spacing.bind('value').as((sp) => `margin: 0rem ${0.375 * sp}rem;`),
class_name: Utils.merge(
[
options.bar.workspaces.show_icons.bind("value"),
options.bar.workspaces.show_numbered.bind("value"),
options.bar.workspaces.numbered_active_indicator.bind("value"),
options.bar.workspaces.icons.available.bind("value"),
options.bar.workspaces.icons.active.bind("value"),
options.bar.workspaces.icons.occupied.bind("value"),
hyprland.active.workspace.bind("id")
options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.show_numbered.bind('value'),
options.bar.workspaces.numbered_active_indicator.bind('value'),
options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind('value'),
hyprland.active.workspace.bind('id'),
],
(showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string) => {
(
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
) => {
if (showIcons) {
return `workspace-icon txt-icon bar`;
}
if (showNumbered) {
const numActiveInd = hyprland.active.workspace.id === i
? numberedActiveIndicator
: "";
const numActiveInd =
hyprland.active.workspace.id === i ? numberedActiveIndicator : '';
return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`;
}
return "default";
return 'default';
},
),
label: Utils.merge(
[
options.bar.workspaces.show_icons.bind("value"),
options.bar.workspaces.icons.available.bind("value"),
options.bar.workspaces.icons.active.bind("value"),
options.bar.workspaces.icons.occupied.bind("value"),
workspaceMask.bind("value"),
hyprland.active.workspace.bind("id")
options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind('value'),
workspaceMask.bind('value'),
hyprland.active.workspace.bind('id'),
],
(showIcons: boolean, available: string, active: string, occupied: string, workspaceMask: boolean, _: number) => {
(
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
) => {
if (showIcons) {
if (hyprland.active.workspace.id === i) {
return active;
@@ -122,52 +138,45 @@ const Workspaces = (monitor = -1) => {
if ((hyprland.getWorkspace(i)?.windows || 0) > 0) {
return occupied;
}
if (
monitor !== -1
) {
if (monitor !== -1) {
return available;
}
}
return workspaceMask
? `${index + 1}`
: `${i}`;
return workspaceMask ? `${index + 1}` : `${i}`;
},
),
setup: (self) => {
self.hook(hyprland, () => {
self.toggleClassName('active', hyprland.active.workspace.id === i);
self.toggleClassName(
"active",
hyprland.active.workspace.id === i,
);
self.toggleClassName(
"occupied",
'occupied',
(hyprland.getWorkspace(i)?.windows || 0) > 0,
);
});
},
})
}),
});
});
},
)
})
}
const occupiedWses = () => {
),
});
};
const occupiedWses = (): BoxWidget => {
return Widget.Box({
children: Utils.merge(
[
monitorSpecific.bind("value"),
hyprland.bind("workspaces"),
workspaceMask.bind("value"),
workspaces.bind("value"),
options.bar.workspaces.show_icons.bind("value"),
options.bar.workspaces.icons.available.bind("value"),
options.bar.workspaces.icons.active.bind("value"),
options.bar.workspaces.icons.occupied.bind("value"),
options.bar.workspaces.show_numbered.bind("value"),
options.bar.workspaces.numbered_active_indicator.bind("value"),
spacing.bind("value"),
hyprland.active.workspace.bind("id"),
monitorSpecific.bind('value'),
hyprland.bind('workspaces'),
workspaceMask.bind('value'),
workspaces.bind('value'),
options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind('value'),
options.bar.workspaces.show_numbered.bind('value'),
options.bar.workspaces.numbered_active_indicator.bind('value'),
spacing.bind('value'),
hyprland.active.workspace.bind('id'),
],
(
monitorSpecific: boolean,
@@ -185,29 +194,37 @@ const Workspaces = (monitor = -1) => {
) => {
let allWkspcs = range(totalWkspcs || 8);
const activeWorkspaces = wkSpaces.map(w => w.id);
const activeWorkspaces = wkSpaces.map((w) => w.id);
const workspaceRules = getWorkspaceRules();
// Sometimes hyprland doesn't have all the monitors in the list
// so we complement it with monitors from the workspace list
const workspaceMonitorList = hyprland?.workspaces?.map(m => ({ id: m.monitorID, name: m.monitor }));
const curMonitor = hyprland.monitors.find(m => m.id === monitor)
|| workspaceMonitorList.find(m => m.id === monitor);
const workspaceMonitorList = hyprland?.workspaces?.map((m) => ({
id: m.monitorID,
name: m.monitor,
}));
const curMonitor =
hyprland.monitors.find((m) => m.id === monitor) ||
workspaceMonitorList.find((m) => m.id === monitor);
// go through each key in workspaceRules and flatten the array
const workspacesWithRules = Object.keys(workspaceRules).reduce((acc: number[], k: string) => {
return [...acc, ...workspaceRules[k]];
}, [] as number[]);
const activesForMonitor = activeWorkspaces.filter(w => {
if (curMonitor && Object.hasOwnProperty.call(workspaceRules, curMonitor.name) && workspacesWithRules.includes(w)) {
const activesForMonitor = activeWorkspaces.filter((w) => {
if (
curMonitor &&
Object.hasOwnProperty.call(workspaceRules, curMonitor.name) &&
workspacesWithRules.includes(w)
) {
return workspaceRules[curMonitor.name].includes(w);
}
return true;
});
if (monitorSpecific) {
const wrkspcsInRange = range(totalWkspcs).filter(w => {
const wrkspcsInRange = range(totalWkspcs).filter((w) => {
return getWorkspacesForMonitor(w, workspaceRules, monitor);
});
allWkspcs = [...new Set([...activesForMonitor, ...wrkspcsInRange])];
@@ -221,51 +238,53 @@ const Workspaces = (monitor = -1) => {
})
.map((i, index) => {
return Widget.Button({
class_name: "workspace-button",
class_name: 'workspace-button',
on_primary_click: () => {
hyprland.messageAsync(`dispatch workspace ${i}`)
hyprland.messageAsync(`dispatch workspace ${i}`);
},
child: Widget.Label({
attribute: i,
vpack: "center",
vpack: 'center',
css: `margin: 0rem ${0.375 * spacing}rem;`,
class_name: renderClassnames(showIcons, showNumbered, numberedActiveIndicator, i),
label: renderLabel(showIcons, available, active, occupied, workspaceMask, i, index),
setup: (self) => {
self.toggleClassName(
"active",
activeId === i,
);
self.toggleClassName(
"occupied",
(hyprland.getWorkspace(i)?.windows || 0) > 0,
);
self.toggleClassName('active', activeId === i);
self.toggleClassName('occupied', (hyprland.getWorkspace(i)?.windows || 0) > 0);
},
})
}),
});
});
},
)
})
}
),
});
};
return {
component: Widget.Box({
class_name: "workspaces",
child: options.bar.workspaces.hideUnoccupied.bind("value").as(hideUnoccupied => hideUnoccupied ? occupiedWses() : defaultWses()),
class_name: 'workspaces',
child: options.bar.workspaces.hideUnoccupied
.bind('value')
.as((hideUnoccupied) => (hideUnoccupied ? occupiedWses() : defaultWses())),
}),
isVisible: true,
boxClass: "workspaces",
boxClass: 'workspaces',
props: {
setup: (self: any) => {
Utils.merge([scroll_speed.bind("value"), options.bar.workspaces.hideUnoccupied.bind("value")], (scroll_speed, hideUnoccupied) => {
const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(scroll_speed, currentMonitorWorkspaces, hideUnoccupied)
self.on_scroll_up = throttledScrollUp;
self.on_scroll_down = throttledScrollDown;
});
}
}
setup: (self: SelfButton): void => {
Utils.merge(
[scroll_speed.bind('value'), options.bar.workspaces.hideUnoccupied.bind('value')],
(scroll_speed, hideUnoccupied) => {
const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(
scroll_speed,
currentMonitorWorkspaces,
hideUnoccupied,
);
self.on_scroll_up = throttledScrollUp;
self.on_scroll_down = throttledScrollDown;
},
);
},
},
};
};

View File

@@ -0,0 +1,43 @@
const hyprland = await Service.import('hyprland');
export const renderClassnames = (
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
i: number,
): string => {
if (showIcons) {
return `workspace-icon txt-icon bar`;
}
if (showNumbered) {
const numActiveInd = hyprland.active.workspace.id === i ? numberedActiveIndicator : '';
return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`;
}
return 'default';
};
export const renderLabel = (
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
i: number,
index: number,
monitor: number,
): string => {
if (showIcons) {
if (hyprland.active.workspace.id === i) {
return active;
}
if ((hyprland.getWorkspace(i)?.windows || 0) > 0) {
return occupied;
}
if (monitor !== -1) {
return available;
}
}
return workspaceMask ? `${index + 1}` : `${i}`;
};

View File

@@ -0,0 +1,99 @@
const hyprland = await Service.import('hyprland');
import options from 'options';
import { getWorkspaceRules, getWorkspacesForMonitor } from '../helpers';
import { range } from 'lib/utils';
import { BoxWidget } from 'lib/types/widget';
const { workspaces, monitorSpecific, workspaceMask, spacing } = options.bar.workspaces;
export const defaultWses = (monitor: number): BoxWidget => {
return Widget.Box({
children: Utils.merge(
[workspaces.bind('value'), monitorSpecific.bind()],
(workspaces: number, monitorSpecific: boolean) => {
return range(workspaces || 8)
.filter((i) => {
if (!monitorSpecific) {
return true;
}
const workspaceRules = getWorkspaceRules();
return getWorkspacesForMonitor(i, workspaceRules, monitor);
})
.sort((a, b) => {
return a - b;
})
.map((i, index) => {
return Widget.Button({
class_name: 'workspace-button',
on_primary_click: () => {
hyprland.messageAsync(`dispatch workspace ${i}`);
},
child: Widget.Label({
attribute: i,
vpack: 'center',
css: spacing.bind('value').as((sp) => `margin: 0rem ${0.375 * sp}rem;`),
class_name: Utils.merge(
[
options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.show_numbered.bind('value'),
options.bar.workspaces.numbered_active_indicator.bind('value'),
options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind('value'),
hyprland.active.workspace.bind('id'),
],
(showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string) => {
if (showIcons) {
return `workspace-icon txt-icon bar`;
}
if (showNumbered) {
const numActiveInd =
hyprland.active.workspace.id === i ? numberedActiveIndicator : '';
return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`;
}
return 'default';
},
),
label: Utils.merge(
[
options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind('value'),
workspaceMask.bind('value'),
hyprland.active.workspace.bind('id'),
],
(
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
) => {
if (showIcons) {
if (hyprland.active.workspace.id === i) {
return active;
}
if ((hyprland.getWorkspace(i)?.windows || 0) > 0) {
return occupied;
}
if (monitor !== -1) {
return available;
}
}
return workspaceMask ? `${index + 1}` : `${i}`;
},
),
setup: (self) => {
self.hook(hyprland, () => {
self.toggleClassName('active', hyprland.active.workspace.id === i);
self.toggleClassName('occupied', (hyprland.getWorkspace(i)?.windows || 0) > 0);
});
},
}),
});
});
},
),
});
};

View File

@@ -0,0 +1,114 @@
const hyprland = await Service.import('hyprland');
import options from 'options';
import { getWorkspaceRules, getWorkspacesForMonitor } from '../helpers';
import { Workspace } from 'types/service/hyprland';
import { renderClassnames, renderLabel } from '../utils';
import { range } from 'lib/utils';
import { BoxWidget } from 'lib/types/widget';
const { workspaces, monitorSpecific, workspaceMask, spacing } = options.bar.workspaces;
export const occupiedWses = (monitor: number): BoxWidget => {
return Widget.Box({
children: Utils.merge(
[
monitorSpecific.bind('value'),
hyprland.bind('workspaces'),
workspaceMask.bind('value'),
workspaces.bind('value'),
options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind('value'),
options.bar.workspaces.show_numbered.bind('value'),
options.bar.workspaces.numbered_active_indicator.bind('value'),
spacing.bind('value'),
hyprland.active.workspace.bind('id'),
],
(
monitorSpecific: boolean,
wkSpaces: Workspace[],
workspaceMask: boolean,
totalWkspcs: number,
showIcons: boolean,
available: string,
active: string,
occupied: string,
showNumbered: boolean,
numberedActiveIndicator: string,
spacing: number,
activeId: number,
) => {
let allWkspcs = range(totalWkspcs || 8);
const activeWorkspaces = wkSpaces.map((w) => w.id);
const workspaceRules = getWorkspaceRules();
// Sometimes hyprland doesn't have all the monitors in the list
// so we complement it with monitors from the workspace list
const workspaceMonitorList = hyprland?.workspaces?.map((m) => ({ id: m.monitorID, name: m.monitor }));
const curMonitor =
hyprland.monitors.find((m) => m.id === monitor) ||
workspaceMonitorList.find((m) => m.id === monitor);
// go through each key in workspaceRules and flatten the array
const workspacesWithRules = Object.keys(workspaceRules).reduce((acc: number[], k: string) => {
return [...acc, ...workspaceRules[k]];
}, [] as number[]);
const activesForMonitor = activeWorkspaces.filter((w) => {
if (
curMonitor &&
Object.hasOwnProperty.call(workspaceRules, curMonitor.name) &&
workspacesWithRules.includes(w)
) {
return workspaceRules[curMonitor.name].includes(w);
}
return true;
});
if (monitorSpecific) {
const wrkspcsInRange = range(totalWkspcs).filter((w) => {
return getWorkspacesForMonitor(w, workspaceRules, monitor);
});
allWkspcs = [...new Set([...activesForMonitor, ...wrkspcsInRange])];
} else {
allWkspcs = [...new Set([...allWkspcs, ...activeWorkspaces])];
}
return allWkspcs
.sort((a, b) => {
return a - b;
})
.map((i, index) => {
return Widget.Button({
class_name: 'workspace-button',
on_primary_click: () => {
hyprland.messageAsync(`dispatch workspace ${i}`);
},
child: Widget.Label({
attribute: i,
vpack: 'center',
css: `margin: 0rem ${0.375 * spacing}rem;`,
class_name: renderClassnames(showIcons, showNumbered, numberedActiveIndicator, i),
label: renderLabel(
showIcons,
available,
active,
occupied,
workspaceMask,
i,
index,
monitor,
),
setup: (self) => {
self.toggleClassName('active', activeId === i);
self.toggleClassName('occupied', (hyprland.getWorkspace(i)?.windows || 0) > 0);
},
}),
});
});
},
),
});
};

View File

@@ -1,199 +1,199 @@
export const substitutes = {
"transmission-gtk": "transmission",
"blueberry.py": "blueberry",
Caprine: "facebook-messenger",
"com.raggesilver.BlackBox-symbolic": "terminal-symbolic",
"org.wezfurlong.wezterm-symbolic": "terminal-symbolic",
"audio-headset-bluetooth": "audio-headphones-symbolic",
"audio-card-analog-usb": "audio-speakers-symbolic",
"audio-card-analog-pci": "audio-card-symbolic",
"preferences-system": "emblem-system-symbolic",
"com.github.Aylur.ags-symbolic": "controls-symbolic",
"com.github.Aylur.ags": "controls-symbolic",
'transmission-gtk': 'transmission',
'blueberry.py': 'blueberry',
Caprine: 'facebook-messenger',
'com.raggesilver.BlackBox-symbolic': 'terminal-symbolic',
'org.wezfurlong.wezterm-symbolic': 'terminal-symbolic',
'audio-headset-bluetooth': 'audio-headphones-symbolic',
'audio-card-analog-usb': 'audio-speakers-symbolic',
'audio-card-analog-pci': 'audio-card-symbolic',
'preferences-system': 'emblem-system-symbolic',
'com.github.Aylur.ags-symbolic': 'controls-symbolic',
'com.github.Aylur.ags': 'controls-symbolic',
};
export default {
missing: "image-missing-symbolic",
missing: 'image-missing-symbolic',
nix: {
nix: "nix-snowflake-symbolic",
nix: 'nix-snowflake-symbolic',
},
app: {
terminal: "terminal-symbolic",
terminal: 'terminal-symbolic',
},
fallback: {
executable: "application-x-executable",
notification: "dialog-information-symbolic",
video: "video-x-generic-symbolic",
audio: "audio-x-generic-symbolic",
executable: 'application-x-executable',
notification: 'dialog-information-symbolic',
video: 'video-x-generic-symbolic',
audio: 'audio-x-generic-symbolic',
},
ui: {
close: "window-close-symbolic",
colorpicker: "color-select-symbolic",
info: "info-symbolic",
link: "external-link-symbolic",
lock: "system-lock-screen-symbolic",
menu: "open-menu-symbolic",
refresh: "view-refresh-symbolic",
search: "system-search-symbolic",
settings: "emblem-system-symbolic",
themes: "preferences-desktop-theme-symbolic",
tick: "object-select-symbolic",
time: "hourglass-symbolic",
toolbars: "toolbars-symbolic",
warning: "dialog-warning-symbolic",
avatar: "avatar-default-symbolic",
close: 'window-close-symbolic',
colorpicker: 'color-select-symbolic',
info: 'info-symbolic',
link: 'external-link-symbolic',
lock: 'system-lock-screen-symbolic',
menu: 'open-menu-symbolic',
refresh: 'view-refresh-symbolic',
search: 'system-search-symbolic',
settings: 'emblem-system-symbolic',
themes: 'preferences-desktop-theme-symbolic',
tick: 'object-select-symbolic',
time: 'hourglass-symbolic',
toolbars: 'toolbars-symbolic',
warning: 'dialog-warning-symbolic',
avatar: 'avatar-default-symbolic',
arrow: {
right: "pan-end-symbolic",
left: "pan-start-symbolic",
down: "pan-down-symbolic",
up: "pan-up-symbolic",
right: 'pan-end-symbolic',
left: 'pan-start-symbolic',
down: 'pan-down-symbolic',
up: 'pan-up-symbolic',
},
},
audio: {
mic: {
muted: "microphone-disabled-symbolic",
low: "microphone-sensitivity-low-symbolic",
medium: "microphone-sensitivity-medium-symbolic",
high: "microphone-sensitivity-high-symbolic",
muted: 'microphone-disabled-symbolic',
low: 'microphone-sensitivity-low-symbolic',
medium: 'microphone-sensitivity-medium-symbolic',
high: 'microphone-sensitivity-high-symbolic',
},
volume: {
muted: "audio-volume-muted-symbolic",
low: "audio-volume-low-symbolic",
medium: "audio-volume-medium-symbolic",
high: "audio-volume-high-symbolic",
overamplified: "audio-volume-overamplified-symbolic",
muted: 'audio-volume-muted-symbolic',
low: 'audio-volume-low-symbolic',
medium: 'audio-volume-medium-symbolic',
high: 'audio-volume-high-symbolic',
overamplified: 'audio-volume-overamplified-symbolic',
},
type: {
headset: "audio-headphones-symbolic",
speaker: "audio-speakers-symbolic",
card: "audio-card-symbolic",
headset: 'audio-headphones-symbolic',
speaker: 'audio-speakers-symbolic',
card: 'audio-card-symbolic',
},
mixer: "mixer-symbolic",
mixer: 'mixer-symbolic',
},
powerprofile: {
balanced: "power-profile-balanced-symbolic",
"power-saver": "power-profile-power-saver-symbolic",
performance: "power-profile-performance-symbolic",
balanced: 'power-profile-balanced-symbolic',
'power-saver': 'power-profile-power-saver-symbolic',
performance: 'power-profile-performance-symbolic',
},
asusctl: {
profile: {
Balanced: "power-profile-balanced-symbolic",
Quiet: "power-profile-power-saver-symbolic",
Performance: "power-profile-performance-symbolic",
Balanced: 'power-profile-balanced-symbolic',
Quiet: 'power-profile-power-saver-symbolic',
Performance: 'power-profile-performance-symbolic',
},
mode: {
Integrated: "processor-symbolic",
Hybrid: "controller-symbolic",
Integrated: 'processor-symbolic',
Hybrid: 'controller-symbolic',
},
},
battery: {
charging: "battery-flash-symbolic",
warning: "battery-empty-symbolic",
charging: 'battery-flash-symbolic',
warning: 'battery-empty-symbolic',
},
bluetooth: {
enabled: "bluetooth-active-symbolic",
disabled: "bluetooth-disabled-symbolic",
enabled: 'bluetooth-active-symbolic',
disabled: 'bluetooth-disabled-symbolic',
},
brightness: {
indicator: "display-brightness-symbolic",
keyboard: "keyboard-brightness-symbolic",
screen: "display-brightness-symbolic",
indicator: 'display-brightness-symbolic',
keyboard: 'keyboard-brightness-symbolic',
screen: 'display-brightness-symbolic',
},
powermenu: {
sleep: "weather-clear-night-symbolic",
reboot: "system-reboot-symbolic",
logout: "system-log-out-symbolic",
shutdown: "system-shutdown-symbolic",
sleep: 'weather-clear-night-symbolic',
reboot: 'system-reboot-symbolic',
logout: 'system-log-out-symbolic',
shutdown: 'system-shutdown-symbolic',
},
recorder: {
recording: "media-record-symbolic",
recording: 'media-record-symbolic',
},
notifications: {
noisy: "org.gnome.Settings-notifications-symbolic",
silent: "notifications-disabled-symbolic",
message: "chat-bubbles-symbolic",
noisy: 'org.gnome.Settings-notifications-symbolic',
silent: 'notifications-disabled-symbolic',
message: 'chat-bubbles-symbolic',
},
trash: {
full: "user-trash-full-symbolic",
empty: "user-trash-symbolic",
full: 'user-trash-full-symbolic',
empty: 'user-trash-symbolic',
},
mpris: {
shuffle: {
enabled: "media-playlist-shuffle-symbolic",
disabled: "media-playlist-consecutive-symbolic",
enabled: 'media-playlist-shuffle-symbolic',
disabled: 'media-playlist-consecutive-symbolic',
},
loop: {
none: "media-playlist-repeat-symbolic",
track: "media-playlist-repeat-song-symbolic",
playlist: "media-playlist-repeat-symbolic",
none: 'media-playlist-repeat-symbolic',
track: 'media-playlist-repeat-song-symbolic',
playlist: 'media-playlist-repeat-symbolic',
},
playing: "media-playback-pause-symbolic",
paused: "media-playback-start-symbolic",
stopped: "media-playback-start-symbolic",
prev: "media-skip-backward-symbolic",
next: "media-skip-forward-symbolic",
playing: 'media-playback-pause-symbolic',
paused: 'media-playback-start-symbolic',
stopped: 'media-playback-start-symbolic',
prev: 'media-skip-backward-symbolic',
next: 'media-skip-forward-symbolic',
},
system: {
cpu: "org.gnome.SystemMonitor-symbolic",
ram: "drive-harddisk-solidstate-symbolic",
temp: "temperature-symbolic",
cpu: 'org.gnome.SystemMonitor-symbolic',
ram: 'drive-harddisk-solidstate-symbolic',
temp: 'temperature-symbolic',
},
color: {
dark: "dark-mode-symbolic",
light: "light-mode-symbolic",
dark: 'dark-mode-symbolic',
light: 'light-mode-symbolic',
},
weather: {
warning: "dialog-warning-symbolic",
sunny: "weather-clear-symbolic",
clear: "weather-clear-night-symbolic",
partly_cloudy: "weather-few-clouds-symbolic",
partly_cloudy_night: "weather-few-clouds-night-symbolic",
cloudy: "weather-overcast-symbolic",
overcast: "weather-overcast-symbolic",
mist: "weather-overcast-symbolic",
patchy_rain_nearby: "weather-showers-scattered-symbolic",
patchy_rain_possible: "weather-showers-scattered-symbolic",
patchy_snow_possible: "weather-snow-symbolic",
patchy_sleet_possible: "weather-snow-symbolic",
patchy_freezing_drizzle_possible: "weather-showers-scattered-symbolic",
thundery_outbreaks_possible: "weather-overcast-symbolic",
blowing_snow: "weather-snow-symbolic",
blizzard: "weather-snow-symbolic",
fog: "weather-fog-symbolic",
freezing_fog: "weather-fog-symbolic",
patchy_light_drizzle: "weather-showers-scattered-symbolic",
light_drizzle: "weather-showers-symbolic",
freezing_drizzle: "weather-showers-symbolic",
heavy_freezing_drizzle: "weather-showers-symbolic",
patchy_light_rain: "weather-showers-scattered-symbolic",
light_rain: "weather-showers-symbolic",
moderate_rain_at_times: "weather-showers-symbolic",
moderate_rain: "weather-showers-symbolic",
heavy_rain_at_times: "weather-showers-symbolic",
heavy_rain: "weather-showers-symbolic",
light_freezing_rain: "weather-showers-symbolic",
moderate_or_heavy_freezing_rain: "weather-showers-symbolic",
light_sleet: "weather-snow-symbolic",
moderate_or_heavy_sleet: "weather-snow-symbolic",
patchy_light_snow: "weather-snow-symbolic",
light_snow: "weather-snow-symbolic",
patchy_moderate_snow: "weather-snow-symbolic",
moderate_snow: "weather-snow-symbolic",
patchy_heavy_snow: "weather-snow-symbolic",
heavy_snow: "weather-snow-symbolic",
ice_pellets: "weather-showers-symbolic",
light_rain_shower: "weather-showers-symbolic",
moderate_or_heavy_rain_shower: "weather-showers-symbolic",
torrential_rain_shower: "weather-showers-symbolic",
light_sleet_showers: "weather-showers-symbolic",
moderate_or_heavy_sleet_showers: "weather-showers-symbolic",
light_snow_showers: "weather-snow-symbolic",
moderate_or_heavy_snow_showers: "weather-snow-symbolic",
light_showers_of_ice_pellets: "weather-showers-symbolic",
moderate_or_heavy_showers_of_ice_pellets: "weather-showers-symbolic",
patchy_light_rain_with_thunder: "weather-showers-scattered-symbolic",
moderate_or_heavy_rain_with_thunder: "weather-showers-symbolic",
patchy_light_snow_with_thunder: "weather-snow-symbolic",
moderate_or_heavy_snow_with_thunder: "weather-snow-symbolic",
warning: 'dialog-warning-symbolic',
sunny: 'weather-clear-symbolic',
clear: 'weather-clear-night-symbolic',
partly_cloudy: 'weather-few-clouds-symbolic',
partly_cloudy_night: 'weather-few-clouds-night-symbolic',
cloudy: 'weather-overcast-symbolic',
overcast: 'weather-overcast-symbolic',
mist: 'weather-overcast-symbolic',
patchy_rain_nearby: 'weather-showers-scattered-symbolic',
patchy_rain_possible: 'weather-showers-scattered-symbolic',
patchy_snow_possible: 'weather-snow-symbolic',
patchy_sleet_possible: 'weather-snow-symbolic',
patchy_freezing_drizzle_possible: 'weather-showers-scattered-symbolic',
thundery_outbreaks_possible: 'weather-overcast-symbolic',
blowing_snow: 'weather-snow-symbolic',
blizzard: 'weather-snow-symbolic',
fog: 'weather-fog-symbolic',
freezing_fog: 'weather-fog-symbolic',
patchy_light_drizzle: 'weather-showers-scattered-symbolic',
light_drizzle: 'weather-showers-symbolic',
freezing_drizzle: 'weather-showers-symbolic',
heavy_freezing_drizzle: 'weather-showers-symbolic',
patchy_light_rain: 'weather-showers-scattered-symbolic',
light_rain: 'weather-showers-symbolic',
moderate_rain_at_times: 'weather-showers-symbolic',
moderate_rain: 'weather-showers-symbolic',
heavy_rain_at_times: 'weather-showers-symbolic',
heavy_rain: 'weather-showers-symbolic',
light_freezing_rain: 'weather-showers-symbolic',
moderate_or_heavy_freezing_rain: 'weather-showers-symbolic',
light_sleet: 'weather-snow-symbolic',
moderate_or_heavy_sleet: 'weather-snow-symbolic',
patchy_light_snow: 'weather-snow-symbolic',
light_snow: 'weather-snow-symbolic',
patchy_moderate_snow: 'weather-snow-symbolic',
moderate_snow: 'weather-snow-symbolic',
patchy_heavy_snow: 'weather-snow-symbolic',
heavy_snow: 'weather-snow-symbolic',
ice_pellets: 'weather-showers-symbolic',
light_rain_shower: 'weather-showers-symbolic',
moderate_or_heavy_rain_shower: 'weather-showers-symbolic',
torrential_rain_shower: 'weather-showers-symbolic',
light_sleet_showers: 'weather-showers-symbolic',
moderate_or_heavy_sleet_showers: 'weather-showers-symbolic',
light_snow_showers: 'weather-snow-symbolic',
moderate_or_heavy_snow_showers: 'weather-snow-symbolic',
light_showers_of_ice_pellets: 'weather-showers-symbolic',
moderate_or_heavy_showers_of_ice_pellets: 'weather-showers-symbolic',
patchy_light_rain_with_thunder: 'weather-showers-scattered-symbolic',
moderate_or_heavy_rain_with_thunder: 'weather-showers-symbolic',
patchy_light_snow_with_thunder: 'weather-snow-symbolic',
moderate_or_heavy_snow_with_thunder: 'weather-snow-symbolic',
},
} as const;

View File

@@ -1,54 +1,54 @@
export const weatherIcons = {
warning: "󰼯",
sunny: "󰖙",
clear: "󰖔",
partly_cloudy: "󰖕",
partly_cloudy_night: "󰼱",
cloudy: "󰖐",
overcast: "󰖕",
mist: "󰖑",
patchy_rain_nearby: "󰼳",
patchy_rain_possible: "󰼳",
patchy_snow_possible: "󰼴",
patchy_sleet_possible: "󰙿",
patchy_freezing_drizzle_possible: "󰙿",
thundery_outbreaks_possible: "󰙾",
blowing_snow: "󰼶",
blizzard: "󰼶",
fog: "󰖑",
freezing_fog: "󰖑",
patchy_light_drizzle: "󰼳",
light_drizzle: "󰼳",
freezing_drizzle: "󰙿",
heavy_freezing_drizzle: "󰙿",
patchy_light_rain: "󰼳",
light_rain: "󰼳",
moderate_rain_at_times: "󰖗",
moderate_rain: "󰼳",
heavy_rain_at_times: "󰖖",
heavy_rain: "󰖖",
light_freezing_rain: "󰙿",
moderate_or_heavy_freezing_rain: "󰙿",
light_sleet: "󰙿",
moderate_or_heavy_sleet: "󰙿",
patchy_light_snow: "󰼴",
light_snow: "󰼴",
patchy_moderate_snow: "󰼴",
moderate_snow: "󰼶",
patchy_heavy_snow: "󰼶",
heavy_snow: "󰼶",
ice_pellets: "󰖒",
light_rain_shower: "󰖖",
moderate_or_heavy_rain_shower: "󰖖",
torrential_rain_shower: "󰖖",
light_sleet_showers: "󰼵",
moderate_or_heavy_sleet_showers: "󰼵",
light_snow_showers: "󰼵",
moderate_or_heavy_snow_showers: "󰼵",
light_showers_of_ice_pellets: "󰖒",
moderate_or_heavy_showers_of_ice_pellets: "󰖒",
patchy_light_rain_with_thunder: "󰙾",
moderate_or_heavy_rain_with_thunder: "󰙾",
patchy_light_snow_with_thunder: "󰼶",
moderate_or_heavy_snow_with_thunder: "󰼶",
warning: '󰼯',
sunny: '󰖙',
clear: '󰖔',
partly_cloudy: '󰖕',
partly_cloudy_night: '󰼱',
cloudy: '󰖐',
overcast: '󰖕',
mist: '󰖑',
patchy_rain_nearby: '󰼳',
patchy_rain_possible: '󰼳',
patchy_snow_possible: '󰼴',
patchy_sleet_possible: '󰙿',
patchy_freezing_drizzle_possible: '󰙿',
thundery_outbreaks_possible: '󰙾',
blowing_snow: '󰼶',
blizzard: '󰼶',
fog: '󰖑',
freezing_fog: '󰖑',
patchy_light_drizzle: '󰼳',
light_drizzle: '󰼳',
freezing_drizzle: '󰙿',
heavy_freezing_drizzle: '󰙿',
patchy_light_rain: '󰼳',
light_rain: '󰼳',
moderate_rain_at_times: '󰖗',
moderate_rain: '󰼳',
heavy_rain_at_times: '󰖖',
heavy_rain: '󰖖',
light_freezing_rain: '󰙿',
moderate_or_heavy_freezing_rain: '󰙿',
light_sleet: '󰙿',
moderate_or_heavy_sleet: '󰙿',
patchy_light_snow: '󰼴',
light_snow: '󰼴',
patchy_moderate_snow: '󰼴',
moderate_snow: '󰼶',
patchy_heavy_snow: '󰼶',
heavy_snow: '󰼶',
ice_pellets: '󰖒',
light_rain_shower: '󰖖',
moderate_or_heavy_rain_shower: '󰖖',
torrential_rain_shower: '󰖖',
light_sleet_showers: '󰼵',
moderate_or_heavy_sleet_showers: '󰼵',
light_snow_showers: '󰼵',
moderate_or_heavy_snow_showers: '󰼵',
light_showers_of_ice_pellets: '󰖒',
moderate_or_heavy_showers_of_ice_pellets: '󰖒',
patchy_light_rain_with_thunder: '󰙾',
moderate_or_heavy_rain_with_thunder: '󰙾',
patchy_light_snow_with_thunder: '󰼶',
moderate_or_heavy_snow_with_thunder: '󰼶',
} as const;

View File

@@ -1,25 +1,34 @@
const hyprland = await Service.import("hyprland");
import { DropdownMenuProps } from "lib/types/dropdownmenu";
import { Exclusivity } from "lib/types/widget";
import { bash } from "lib/utils";
import { Monitor } from "types/service/hyprland";
const hyprland = await Service.import('hyprland');
import { DropdownMenuProps } from 'lib/types/dropdownmenu';
import { Attribute, Child, Exclusivity, GtkWidget } from 'lib/types/widget';
import { bash } from 'lib/utils';
import { Widget as TWidget } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
import { Monitor } from 'types/service/hyprland';
import Box from 'types/widgets/box';
import EventBox from 'types/widgets/eventbox';
import Revealer from 'types/widgets/revealer';
import Window from 'types/widgets/window';
export const Padding = (name: string) =>
type NestedRevealer = Revealer<Box<TWidget, unknown>, unknown>;
type NestedBox = Box<NestedRevealer, unknown>;
type NestedEventBox = EventBox<NestedBox, unknown>;
export const Padding = (name: string): EventBox<Box<GtkWidget, Attribute>, Attribute> =>
Widget.EventBox({
hexpand: true,
vexpand: true,
can_focus: true,
child: Widget.Box(),
setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)),
setup: (w) => w.on('button-press-event', () => App.toggleWindow(name)),
});
const moveBoxToCursor = (self: any, fixed: boolean) => {
const moveBoxToCursor = <T extends NestedEventBox>(self: T, fixed: boolean): void => {
if (fixed) {
return;
}
globalMousePos.connect("changed", async ({ value }) => {
const curHyprlandMonitor = hyprland.monitors.find(m => m.id === hyprland.active.monitor.id);
globalMousePos.connect('changed', async ({ value }) => {
const curHyprlandMonitor = hyprland.monitors.find((m) => m.id === hyprland.active.monitor.id);
const dropdownWidth = self.child.get_allocation().width;
let hyprScaling = 1;
@@ -27,8 +36,8 @@ const moveBoxToCursor = (self: any, fixed: boolean) => {
const monitorInfo = await bash('hyprctl monitors -j');
const parsedMonitorInfo = JSON.parse(monitorInfo);
const foundMonitor = parsedMonitorInfo.find((monitor: Monitor) =>
monitor.id === hyprland.active.monitor.id
const foundMonitor = parsedMonitorInfo.find(
(monitor: Monitor) => monitor.id === hyprland.active.monitor.id,
);
hyprScaling = foundMonitor?.scale || 1;
} catch (error) {
@@ -58,9 +67,7 @@ const moveBoxToCursor = (self: any, fixed: boolean) => {
}
// If monitor is vertical (transform = 1 || 3) swap height and width
const isVertical = curHyprlandMonitor?.transform !== undefined
? curHyprlandMonitor.transform % 2 !== 0
: false;
const isVertical = curHyprlandMonitor?.transform !== undefined ? curHyprlandMonitor.transform % 2 !== 0 : false;
if (isVertical) {
[monWidth, monHeight] = [monHeight, monWidth];
@@ -100,58 +107,55 @@ setTimeout(() => {
initRender.value = false;
}, 2000);
export default (
{
name,
child,
layout = "center",
transition,
exclusivity = "ignore" as Exclusivity,
fixed = false,
...props
}: DropdownMenuProps
) =>
export default ({
name,
child,
transition,
exclusivity = 'ignore' as Exclusivity,
fixed = false,
...props
}: DropdownMenuProps): Window<Child, Attribute> =>
Widget.Window({
name,
class_names: [name, "dropdown-menu"],
setup: (w) => w.keybind("Escape", () => App.closeWindow(name)),
visible: initRender.bind("value"),
keymode: "on-demand",
class_names: [name, 'dropdown-menu'],
setup: (w) => w.keybind('Escape', () => App.closeWindow(name)),
visible: initRender.bind('value'),
keymode: 'on-demand',
exclusivity,
layer: "top",
anchor: ["top", "left"],
layer: 'top',
anchor: ['top', 'left'],
child: Widget.EventBox({
class_name: "parent-event",
class_name: 'parent-event',
on_primary_click: () => App.closeWindow(name),
on_secondary_click: () => App.closeWindow(name),
child: Widget.Box({
class_name: "top-eb",
class_name: 'top-eb',
vertical: true,
children: [
Widget.EventBox({
class_name: "mid-eb event-top-padding-static",
class_name: 'mid-eb event-top-padding-static',
hexpand: true,
vexpand: false,
can_focus: false,
child: Widget.Box(),
setup: (w) => {
w.on("button-press-event", () => App.toggleWindow(name));
w.on('button-press-event', () => App.toggleWindow(name));
w.set_margin_top(1);
},
}),
Widget.EventBox({
class_name: "mid-eb event-top-padding",
class_name: 'mid-eb event-top-padding',
hexpand: true,
vexpand: false,
can_focus: false,
child: Widget.Box(),
setup: (w) => {
w.on("button-press-event", () => App.toggleWindow(name));
w.on('button-press-event', () => App.toggleWindow(name));
w.set_margin_top(1);
},
}),
Widget.EventBox({
class_name: "in-eb menu-event-box",
class_name: 'in-eb menu-event-box',
on_primary_click: () => {
return true;
},
@@ -162,18 +166,18 @@ export default (
moveBoxToCursor(self, fixed);
},
child: Widget.Box({
class_name: "dropdown-menu-container",
css: "padding: 1px; margin: -1px;",
class_name: 'dropdown-menu-container',
css: 'padding: 1px; margin: -1px;',
child: Widget.Revealer({
revealChild: false,
setup: (self) =>
self.hook(App, (_, wname, visible) => {
if (wname === name) self.reveal_child = visible;
}),
transition: "crossfade",
transition,
transitionDuration: 350,
child: Widget.Box({
class_name: "dropdown-menu-container",
class_name: 'dropdown-menu-container',
can_focus: true,
children: [child],
}),

View File

@@ -1,25 +1,32 @@
import { WINDOW_LAYOUTS } from "globals/window";
import { LayoutFunction, Layouts, PopupWindowProps } from "lib/types/popupwindow";
import { Exclusivity, Transition } from "lib/types/widget";
import { WINDOW_LAYOUTS } from 'globals/window';
import { LayoutFunction, Layouts, PopupWindowProps } from 'lib/types/popupwindow';
import { Attribute, Child, Exclusivity, GtkWidget, Transition } from 'lib/types/widget';
import Box from 'types/widgets/box';
import EventBox from 'types/widgets/eventbox';
import Window from 'types/widgets/window';
type Opts = {
className: string
vexpand: boolean
}
className: string;
vexpand: boolean;
};
export const Padding = (name: string, opts: Opts) =>
export const Padding = (name: string, opts: Opts): EventBox<Box<GtkWidget, Attribute>, unknown> =>
Widget.EventBox({
class_name: opts?.className || "",
class_name: opts?.className || '',
hexpand: true,
vexpand: typeof opts?.vexpand === "boolean" ? opts.vexpand : true,
vexpand: typeof opts?.vexpand === 'boolean' ? opts.vexpand : true,
can_focus: false,
child: Widget.Box(),
setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)),
setup: (w) => w.on('button-press-event', () => App.toggleWindow(name)),
});
const PopupRevealer = (name: string, child: any, transition = "slide_down" as Transition) =>
const PopupRevealer = (
name: string,
child: GtkWidget,
transition = 'slide_down' as Transition,
): Box<Child, Attribute> =>
Widget.Box(
{ css: "padding: 1px;" },
{ css: 'padding: 1px;' },
Widget.Revealer({
transition,
child: Widget.Box({
@@ -34,7 +41,7 @@ const PopupRevealer = (name: string, child: any, transition = "slide_down" as Tr
}),
);
const Layout: LayoutFunction = (name: string, child: any, transition: Transition) => ({
const Layout: LayoutFunction = (name: string, child: GtkWidget, transition: Transition) => ({
center: () =>
Widget.CenterBox(
{},
@@ -51,14 +58,10 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
Widget.CenterBox(
{},
Padding(name, {} as Opts),
Widget.Box(
{ vertical: true },
PopupRevealer(name, child, transition),
Padding(name, {} as Opts),
),
Widget.Box({ vertical: true }, PopupRevealer(name, child, transition), Padding(name, {} as Opts)),
Padding(name, {} as Opts),
),
"top-right": () =>
'top-right': () =>
Widget.Box(
{},
Padding(name, {} as Opts),
@@ -69,13 +72,13 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
},
Padding(name, {
vexpand: false,
className: "event-top-padding",
className: 'event-top-padding',
}),
PopupRevealer(name, child, transition),
Padding(name, {} as Opts),
),
),
"top-center": () =>
'top-center': () =>
Widget.Box(
{},
Padding(name, {} as Opts),
@@ -86,14 +89,14 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
},
Padding(name, {
vexpand: false,
className: "event-top-padding",
className: 'event-top-padding',
}),
PopupRevealer(name, child, transition),
Padding(name, {} as Opts),
),
Padding(name, {} as Opts),
),
"top-left": () =>
'top-left': () =>
Widget.Box(
{},
Widget.Box(
@@ -103,14 +106,14 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
},
Padding(name, {
vexpand: false,
className: "event-top-padding",
className: 'event-top-padding',
}),
PopupRevealer(name, child, transition),
Padding(name, {} as Opts),
),
Padding(name, {} as Opts),
),
"bottom-left": () =>
'bottom-left': () =>
Widget.Box(
{},
Widget.Box(
@@ -123,7 +126,7 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
),
Padding(name, {} as Opts),
),
"bottom-center": () =>
'bottom-center': () =>
Widget.Box(
{},
Padding(name, {} as Opts),
@@ -137,7 +140,7 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
),
Padding(name, {} as Opts),
),
"bottom-right": () =>
'bottom-right': () =>
Widget.Box(
{},
Padding(name, {} as Opts),
@@ -159,25 +162,25 @@ const isValidLayout = (layout: string): layout is Layouts => {
export default ({
name,
child,
layout = "center",
layout = 'center',
transition,
exclusivity = "ignore" as Exclusivity,
exclusivity = 'ignore' as Exclusivity,
...props
}: PopupWindowProps) => {
const layoutFn = isValidLayout(layout) ? layout : "center";
}: PopupWindowProps): Window<Child, Attribute> => {
const layoutFn = isValidLayout(layout) ? layout : 'center';
const layoutWidget = Layout(name, child, transition)[layoutFn]();
return Widget.Window({
name,
class_names: [name, "popup-window"],
setup: (w) => w.keybind("Escape", () => App.closeWindow(name)),
class_names: [name, 'popup-window'],
setup: (w) => w.keybind('Escape', () => App.closeWindow(name)),
visible: false,
keymode: "on-demand",
keymode: 'on-demand',
exclusivity,
layer: "top",
anchor: ["top", "bottom", "right", "left"],
layer: 'top',
anchor: ['top', 'bottom', 'right', 'left'],
child: layoutWidget,
...props,
});
}
};

View File

@@ -1,31 +1,36 @@
const audio = await Service.import("audio");
const audio = await Service.import('audio');
import { BarBoxChild } from 'lib/types/bar.js';
import { getIcon } from '../utils.js';
const renderActiveInput = () => {
const renderActiveInput = (): BarBoxChild => {
return [
Widget.Box({
class_name: "menu-slider-container input",
class_name: 'menu-slider-container input',
children: [
Widget.Button({
vexpand: false,
vpack: "end",
vpack: 'end',
setup: (self) => {
self.hook(audio, () => {
const mic = audio.microphone;
const className = `menu-active-button input ${mic.is_muted ? "muted" : ""}`;
const className = `menu-active-button input ${mic.is_muted ? 'muted' : ''}`;
return (self.class_name = className);
});
},
on_primary_click: () =>
(audio.microphone.is_muted = !audio.microphone.is_muted),
on_primary_click: () => (audio.microphone.is_muted = !audio.microphone.is_muted),
child: Widget.Icon({
class_name: "menu-active-icon input",
class_name: 'menu-active-icon input',
setup: (self) => {
self.hook(audio, () => {
self.icon = getIcon(
audio.microphone.volume,
audio.microphone.is_muted,
)["mic"];
const isMicMuted =
audio.microphone.is_muted !== null ? audio.microphone.is_muted : true;
if (audio.microphone.volume > 0) {
self.icon = getIcon(audio.microphone.volume, isMicMuted)['mic'];
return;
}
self.icon = getIcon(100, false)['mic'];
});
},
}),
@@ -34,15 +39,17 @@ const renderActiveInput = () => {
vertical: true,
children: [
Widget.Label({
class_name: "menu-active input",
hpack: "start",
truncate: "end",
class_name: 'menu-active input',
hpack: 'start',
truncate: 'end',
wrap: true,
label: audio.bind("microphone").as((v) => v.description === null ? "No input device found..." : v.description),
label: audio
.bind('microphone')
.as((v) => (v.description === null ? 'No input device found...' : v.description)),
}),
Widget.Slider({
value: audio.microphone.bind("volume").as((v) => v),
class_name: "menu-active-slider menu-slider inputs",
value: audio.microphone.bind('volume').as((v) => v),
class_name: 'menu-active-slider menu-slider inputs',
draw_value: false,
hexpand: true,
min: 0,
@@ -52,11 +59,9 @@ const renderActiveInput = () => {
],
}),
Widget.Label({
class_name: "menu-active-percentage input",
vpack: "end",
label: audio.microphone
.bind("volume")
.as((v) => `${Math.round(v * 100)}%`),
class_name: 'menu-active-percentage input',
vpack: 'end',
label: audio.microphone.bind('volume').as((v) => `${Math.round(v * 100)}%`),
}),
],
}),

View File

@@ -1,31 +1,29 @@
const audio = await Service.import("audio");
import { getIcon } from "../utils.js";
const audio = await Service.import('audio');
import { BarBoxChild } from 'lib/types/bar.js';
import { getIcon } from '../utils.js';
const renderActivePlayback = () => {
const renderActivePlayback = (): BarBoxChild => {
return [
Widget.Box({
class_name: "menu-slider-container playback",
class_name: 'menu-slider-container playback',
children: [
Widget.Button({
vexpand: false,
vpack: "end",
vpack: 'end',
setup: (self) => {
self.hook(audio, () => {
const spkr = audio.speaker;
const className = `menu-active-button playback ${spkr.is_muted ? "muted" : ""}`;
const className = `menu-active-button playback ${spkr.is_muted ? 'muted' : ''}`;
return (self.class_name = className);
});
},
on_primary_click: () =>
(audio.speaker.is_muted = !audio.speaker.is_muted),
on_primary_click: () => (audio.speaker.is_muted = !audio.speaker.is_muted),
child: Widget.Icon({
class_name: "menu-active-icon playback",
class_name: 'menu-active-icon playback',
setup: (self) => {
self.hook(audio, () => {
self.icon = getIcon(
audio.speaker.volume,
audio.speaker.is_muted,
)["spkr"];
const isSpeakerMuted = audio.speaker.is_muted !== null ? audio.speaker.is_muted : true;
self.icon = getIcon(audio.speaker.volume, isSpeakerMuted)['spkr'];
});
},
}),
@@ -34,16 +32,16 @@ const renderActivePlayback = () => {
vertical: true,
children: [
Widget.Label({
class_name: "menu-active playback",
hpack: "start",
truncate: "end",
class_name: 'menu-active playback',
hpack: 'start',
truncate: 'end',
expand: true,
wrap: true,
label: audio.bind("speaker").as((v) => v.description || ""),
label: audio.bind('speaker').as((v) => v.description || ''),
}),
Widget.Slider({
value: audio["speaker"].bind("volume"),
class_name: "menu-active-slider menu-slider playback",
value: audio['speaker'].bind('volume'),
class_name: 'menu-active-slider menu-slider playback',
draw_value: false,
hexpand: true,
min: 0,
@@ -53,11 +51,9 @@ const renderActivePlayback = () => {
],
}),
Widget.Label({
vpack: "end",
class_name: "menu-active-percentage playback",
label: audio.speaker
.bind("volume")
.as((v) => `${Math.round(v * 100)}%`),
vpack: 'end',
class_name: 'menu-active-percentage playback',
label: audio.speaker.bind('volume').as((v) => `${Math.round(v * 100)}%`),
}),
],
}),

View File

@@ -1,39 +1,40 @@
import { renderActiveInput } from "./SelectedInput.js";
import { renderActivePlayback } from "./SelectedPlayback.js";
import { BarBoxChild } from 'lib/types/bar.js';
import { renderActiveInput } from './SelectedInput.js';
import { renderActivePlayback } from './SelectedPlayback.js';
const activeDevices = () => {
return Widget.Box({
class_name: "menu-section-container volume",
vertical: true,
children: [
Widget.Box({
class_name: "menu-label-container volume selected",
hpack: "fill",
child: Widget.Label({
class_name: "menu-label audio volume",
hexpand: true,
hpack: "start",
label: "Volume",
}),
}),
Widget.Box({
class_name: "menu-items-section selected",
const activeDevices = (): BarBoxChild => {
return Widget.Box({
class_name: 'menu-section-container volume',
vertical: true,
children: [
Widget.Box({
class_name: "menu-active-container playback",
vertical: true,
children: renderActivePlayback(),
}),
Widget.Box({
class_name: "menu-active-container input",
vertical: true,
children: renderActiveInput(),
}),
Widget.Box({
class_name: 'menu-label-container volume selected',
hpack: 'fill',
child: Widget.Label({
class_name: 'menu-label audio volume',
hexpand: true,
hpack: 'start',
label: 'Volume',
}),
}),
Widget.Box({
class_name: 'menu-items-section selected',
vertical: true,
children: [
Widget.Box({
class_name: 'menu-active-container playback',
vertical: true,
children: renderActivePlayback(),
}),
Widget.Box({
class_name: 'menu-active-container input',
vertical: true,
children: renderActiveInput(),
}),
],
}),
],
}),
],
});
});
};
export { activeDevices };

View File

@@ -1,7 +1,8 @@
const audio = await Service.import("audio");
import { Stream } from "types/service/audio";
const audio = await Service.import('audio');
import { InputDevices } from 'lib/types/audio';
import { Stream } from 'types/service/audio';
const renderInputDevices = (inputDevices: Stream[]) => {
const renderInputDevices = (inputDevices: Stream[]): InputDevices => {
if (inputDevices.length === 0) {
return [
Widget.Button({
@@ -9,11 +10,11 @@ const renderInputDevices = (inputDevices: Stream[]) => {
child: Widget.Box({
children: [
Widget.Box({
hpack: "start",
hpack: 'start',
children: [
Widget.Label({
class_name: "menu-button-name input",
label: "No input devices found...",
class_name: 'menu-button-name input',
label: 'No input devices found...',
}),
],
}),
@@ -29,28 +30,28 @@ const renderInputDevices = (inputDevices: Stream[]) => {
child: Widget.Box({
children: [
Widget.Box({
hpack: "start",
hpack: 'start',
children: [
Widget.Label({
wrap: true,
class_name: audio.microphone
.bind("description")
.bind('description')
.as((v) =>
device.description === v
? "menu-button-icon active input txt-icon"
: "menu-button-icon input txt-icon",
? 'menu-button-icon active input txt-icon'
: 'menu-button-icon input txt-icon',
),
label: "",
label: '',
}),
Widget.Label({
truncate: "end",
truncate: 'end',
wrap: true,
class_name: audio.microphone
.bind("description")
.bind('description')
.as((v) =>
device.description === v
? "menu-button-name active input"
: "menu-button-name input",
? 'menu-button-name active input'
: 'menu-button-name input',
),
label: device.description,
}),

View File

@@ -1,16 +1,17 @@
const audio = await Service.import("audio");
import { Stream } from "types/service/audio";
const audio = await Service.import('audio');
import { PlaybackDevices } from 'lib/types/audio';
import { Stream } from 'types/service/audio';
const renderPlaybacks = (playbackDevices: Stream[]) => {
const renderPlaybacks = (playbackDevices: Stream[]): PlaybackDevices => {
return playbackDevices.map((device) => {
if (device.description === "Dummy Output") {
if (device.description === 'Dummy Output') {
return Widget.Box({
class_name: "menu-unfound-button playback",
class_name: 'menu-unfound-button playback',
child: Widget.Box({
children: [
Widget.Label({
class_name: "menu-button-name playback",
label: "No playback devices found...",
class_name: 'menu-button-name playback',
label: 'No playback devices found...',
}),
],
}),
@@ -22,29 +23,29 @@ const renderPlaybacks = (playbackDevices: Stream[]) => {
child: Widget.Box({
children: [
Widget.Box({
hpack: "start",
hpack: 'start',
children: [
Widget.Label({
truncate: "end",
truncate: 'end',
wrap: true,
class_name: audio.speaker
.bind("description")
.bind('description')
.as((v) =>
device.description === v
? "menu-button-icon active playback txt-icon"
: "menu-button-icon playback txt-icon",
? 'menu-button-icon active playback txt-icon'
: 'menu-button-icon playback txt-icon',
),
label: "",
label: '',
}),
Widget.Label({
truncate: "end",
truncate: 'end',
wrap: true,
class_name: audio.speaker
.bind("description")
.bind('description')
.as((v) =>
device.description === v
? "menu-button-name active playback"
: "menu-button-name playback",
? 'menu-button-name active playback'
: 'menu-button-name playback',
),
label: device.description,
}),

View File

@@ -1,66 +1,63 @@
const audio = await Service.import("audio");
import { renderInputDevices } from "./InputDevices.js";
import { renderPlaybacks } from "./PlaybackDevices.js";
const audio = await Service.import('audio');
import { BoxWidget } from 'lib/types/widget.js';
import { renderInputDevices } from './InputDevices.js';
import { renderPlaybacks } from './PlaybackDevices.js';
const availableDevices = () => {
const availableDevices = (): BoxWidget => {
return Widget.Box({
vertical: true,
children: [
Widget.Box({
class_name: "menu-section-container playback",
class_name: 'menu-section-container playback',
vertical: true,
children: [
Widget.Box({
class_name: "menu-label-container playback",
hpack: "fill",
class_name: 'menu-label-container playback',
hpack: 'fill',
child: Widget.Label({
class_name: "menu-label audio playback",
class_name: 'menu-label audio playback',
hexpand: true,
hpack: "start",
label: "Playback Devices",
hpack: 'start',
label: 'Playback Devices',
}),
}),
Widget.Box({
class_name: "menu-items-section playback",
class_name: 'menu-items-section playback',
vertical: true,
children: [
Widget.Box({
class_name: "menu-container playback",
class_name: 'menu-container playback',
vertical: true,
children: [
Widget.Box({
vertical: true,
children: audio
.bind("speakers")
.as((v) => renderPlaybacks(v)),
children: audio.bind('speakers').as((v) => renderPlaybacks(v)),
}),
],
}),
],
}),
Widget.Box({
class_name: "menu-label-container input",
hpack: "fill",
class_name: 'menu-label-container input',
hpack: 'fill',
child: Widget.Label({
class_name: "menu-label audio input",
class_name: 'menu-label audio input',
hexpand: true,
hpack: "start",
label: "Input Devices",
hpack: 'start',
label: 'Input Devices',
}),
}),
Widget.Box({
class_name: "menu-items-section input",
class_name: 'menu-items-section input',
vertical: true,
children: [
Widget.Box({
class_name: "menu-container input",
class_name: 'menu-container input',
vertical: true,
children: [
Widget.Box({
vertical: true,
children: audio
.bind("microphones")
.as((v) => renderInputDevices(v)),
children: audio.bind('microphones').as((v) => renderInputDevices(v)),
}),
],
}),

View File

@@ -1,24 +1,23 @@
import DropdownMenu from "../DropdownMenu.js";
import { activeDevices } from "./active/index.js";
import { availableDevices } from "./available/index.js";
import Window from 'types/widgets/window.js';
import DropdownMenu from '../DropdownMenu.js';
import { activeDevices } from './active/index.js';
import { availableDevices } from './available/index.js';
import { Attribute, Child } from 'lib/types/widget.js';
export default () => {
export default (): Window<Child, Attribute> => {
return DropdownMenu({
name: "audiomenu",
transition: "crossfade",
name: 'audiomenu',
transition: 'crossfade',
child: Widget.Box({
class_name: "menu-items audio",
hpack: "fill",
class_name: 'menu-items audio',
hpack: 'fill',
hexpand: true,
child: Widget.Box({
vertical: true,
hpack: "fill",
hpack: 'fill',
hexpand: true,
class_name: "menu-items-container audio",
children: [
activeDevices(),
availableDevices(),
],
class_name: 'menu-items-container audio',
children: [activeDevices(), availableDevices()],
}),
}),
});

View File

@@ -1,26 +1,24 @@
const speakerIcons = {
101: "audio-volume-overamplified-symbolic",
66: "audio-volume-high-symbolic",
34: "audio-volume-medium-symbolic",
1: "audio-volume-low-symbolic",
0: "audio-volume-muted-symbolic",
101: 'audio-volume-overamplified-symbolic',
66: 'audio-volume-high-symbolic',
34: 'audio-volume-medium-symbolic',
1: 'audio-volume-low-symbolic',
0: 'audio-volume-muted-symbolic',
} as const;
const inputIcons = {
101: "microphone-sensitivity-high-symbolic",
66: "microphone-sensitivity-high-symbolic",
34: "microphone-sensitivity-medium-symbolic",
1: "microphone-sensitivity-low-symbolic",
0: "microphone-disabled-symbolic",
101: 'microphone-sensitivity-high-symbolic',
66: 'microphone-sensitivity-high-symbolic',
34: 'microphone-sensitivity-medium-symbolic',
1: 'microphone-sensitivity-low-symbolic',
0: 'microphone-disabled-symbolic',
};
type IconVolumes = keyof typeof speakerIcons;
const getIcon = (audioVol: IconVolumes, isMuted: boolean) => {
const getIcon = (audioVol: number, isMuted: boolean): Record<string, string> => {
const thresholds: IconVolumes[] = [101, 66, 34, 1, 0];
const icon = isMuted
? 0
: thresholds.find((threshold) => threshold <= audioVol * 100) || 0;
const icon = isMuted ? 0 : thresholds.find((threshold) => threshold <= audioVol * 100) || 0;
return {
spkr: speakerIcons[icon],

View File

@@ -1,74 +1,67 @@
import { BluetoothDevice } from "types/service/bluetooth";
import { BoxWidget } from 'lib/types/widget';
import { BluetoothDevice } from 'types/service/bluetooth';
const connectedControls = (dev: BluetoothDevice, connectedDevices: BluetoothDevice[]) => {
const connectedControls = (dev: BluetoothDevice, connectedDevices: BluetoothDevice[]): BoxWidget => {
if (!connectedDevices.includes(dev.address)) {
return Widget.Box({});
}
return Widget.Box({
vpack: "start",
class_name: "bluetooth-controls",
vpack: 'start',
class_name: 'bluetooth-controls',
children: [
Widget.Button({
class_name: "menu-icon-button unpair bluetooth",
class_name: 'menu-icon-button unpair bluetooth',
child: Widget.Label({
tooltip_text: dev.paired ? "Unpair" : "Pair",
class_name: "menu-icon-button-label unpair bluetooth txt-icon",
label: dev.paired ? "" : "",
tooltip_text: dev.paired ? 'Unpair' : 'Pair',
class_name: 'menu-icon-button-label unpair bluetooth txt-icon',
label: dev.paired ? '' : '',
}),
on_primary_click: () =>
Utils.execAsync([
"bash",
"-c",
`bluetoothctl ${dev.paired ? "unpair" : "pair"} ${dev.address}`,
'bash',
'-c',
`bluetoothctl ${dev.paired ? 'unpair' : 'pair'} ${dev.address}`,
]).catch((err) =>
console.error(
`bluetoothctl ${dev.paired ? "unpair" : "pair"} ${dev.address}`,
err,
),
console.error(`bluetoothctl ${dev.paired ? 'unpair' : 'pair'} ${dev.address}`, err),
),
}),
Widget.Button({
class_name: "menu-icon-button disconnect bluetooth",
class_name: 'menu-icon-button disconnect bluetooth',
child: Widget.Label({
tooltip_text: dev.connected ? "Disconnect" : "Connect",
class_name: "menu-icon-button-label disconnect bluetooth txt-icon",
label: dev.connected ? "󱘖" : "",
tooltip_text: dev.connected ? 'Disconnect' : 'Connect',
class_name: 'menu-icon-button-label disconnect bluetooth txt-icon',
label: dev.connected ? '󱘖' : '',
}),
on_primary_click: () => dev.setConnection(!dev.connected),
}),
Widget.Button({
class_name: "menu-icon-button untrust bluetooth",
class_name: 'menu-icon-button untrust bluetooth',
child: Widget.Label({
tooltip_text: dev.trusted ? "Untrust" : "Trust",
class_name: "menu-icon-button-label untrust bluetooth txt-icon",
label: dev.trusted ? "" : "󱖡",
tooltip_text: dev.trusted ? 'Untrust' : 'Trust',
class_name: 'menu-icon-button-label untrust bluetooth txt-icon',
label: dev.trusted ? '' : '󱖡',
}),
on_primary_click: () =>
Utils.execAsync([
"bash",
"-c",
`bluetoothctl ${dev.trusted ? "untrust" : "trust"} ${dev.address}`,
'bash',
'-c',
`bluetoothctl ${dev.trusted ? 'untrust' : 'trust'} ${dev.address}`,
]).catch((err) =>
console.error(
`bluetoothctl ${dev.trusted ? "untrust" : "trust"} ${dev.address}`,
err,
),
console.error(`bluetoothctl ${dev.trusted ? 'untrust' : 'trust'} ${dev.address}`, err),
),
}),
Widget.Button({
class_name: "menu-icon-button delete bluetooth",
class_name: 'menu-icon-button delete bluetooth',
child: Widget.Label({
tooltip_text: "Forget",
class_name: "menu-icon-button-label delete bluetooth txt-icon",
label: "󰆴",
tooltip_text: 'Forget',
class_name: 'menu-icon-button-label delete bluetooth txt-icon',
label: '󰆴',
}),
on_primary_click: () => {
Utils.execAsync([
"bash",
"-c",
`bluetoothctl remove ${dev.address}`,
]).catch((err) => console.error("Bluetooth Remove", err));
Utils.execAsync(['bash', '-c', `bluetoothctl remove ${dev.address}`]).catch((err) =>
console.error('Bluetooth Remove', err),
);
},
}),
],

View File

@@ -1,32 +1,31 @@
import { Bluetooth } from "types/service/bluetooth.js";
import Box from "types/widgets/box.js";
import { connectedControls } from "./connectedControls.js";
import { getBluetoothIcon } from "../utils.js";
import Gtk from "types/@girs/gtk-3.0/gtk-3.0.js";
import { Bluetooth } from 'types/service/bluetooth.js';
import Box from 'types/widgets/box.js';
import { connectedControls } from './connectedControls.js';
import { getBluetoothIcon } from '../utils.js';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0.js';
import { Attribute, Child } from 'lib/types/widget.js';
const devices = (bluetooth: Bluetooth, self: Box<Gtk.Widget, unknown>) => {
const devices = (bluetooth: Bluetooth, self: Box<Gtk.Widget, unknown>): Box<Child, Attribute> => {
return self.hook(bluetooth, () => {
if (!bluetooth.enabled) {
return (self.child = Widget.Box({
class_name: "bluetooth-items",
class_name: 'bluetooth-items',
vertical: true,
expand: true,
vpack: "center",
hpack: "center",
vpack: 'center',
hpack: 'center',
children: [
Widget.Label({
class_name: "bluetooth-disabled dim",
class_name: 'bluetooth-disabled dim',
hexpand: true,
label: "Bluetooth is disabled",
label: 'Bluetooth is disabled',
}),
],
}));
}
const availableDevices = bluetooth.devices
.filter(
(btDev) => btDev.name !== null,
)
.filter((btDev) => btDev.name !== null)
.sort((a, b) => {
if (a.connected || a.paired) {
return -1;
@@ -39,25 +38,23 @@ const devices = (bluetooth: Bluetooth, self: Box<Gtk.Widget, unknown>) => {
return b.name - a.name;
});
const conDevNames = availableDevices
.filter((d) => d.connected || d.paired)
.map((d) => d.address);
const conDevNames = availableDevices.filter((d) => d.connected || d.paired).map((d) => d.address);
if (!availableDevices.length) {
return (self.child = Widget.Box({
class_name: "bluetooth-items",
class_name: 'bluetooth-items',
vertical: true,
expand: true,
vpack: "center",
hpack: "center",
vpack: 'center',
hpack: 'center',
children: [
Widget.Label({
class_name: "no-bluetooth-devices dim",
class_name: 'no-bluetooth-devices dim',
hexpand: true,
label: "No devices currently found",
label: 'No devices currently found',
}),
Widget.Label({
class_name: "search-bluetooth-label dim",
class_name: 'search-bluetooth-label dim',
hexpand: true,
label: "Press '󰑐' to search",
}),
@@ -74,41 +71,40 @@ const devices = (bluetooth: Bluetooth, self: Box<Gtk.Widget, unknown>) => {
hexpand: true,
class_name: `bluetooth-element-item ${device}`,
on_primary_click: () => {
if (!conDevNames.includes(device.address))
device.setConnection(true);
if (!conDevNames.includes(device.address)) device.setConnection(true);
},
child: Widget.Box({
hexpand: true,
children: [
Widget.Box({
hexpand: true,
hpack: "start",
class_name: "menu-button-container",
hpack: 'start',
class_name: 'menu-button-container',
children: [
Widget.Label({
vpack: "start",
class_name: `menu-button-icon bluetooth ${conDevNames.includes(device.address) ? "active" : ""} txt-icon`,
label: getBluetoothIcon(`${device["icon_name"]}-symbolic`),
vpack: 'start',
class_name: `menu-button-icon bluetooth ${conDevNames.includes(device.address) ? 'active' : ''} txt-icon`,
label: getBluetoothIcon(`${device['icon_name']}-symbolic`),
}),
Widget.Box({
vertical: true,
vpack: "center",
vpack: 'center',
children: [
Widget.Label({
vpack: "center",
hpack: "start",
class_name: "menu-button-name bluetooth",
truncate: "end",
vpack: 'center',
hpack: 'start',
class_name: 'menu-button-name bluetooth',
truncate: 'end',
wrap: true,
label: device.alias,
}),
Widget.Revealer({
hpack: "start",
hpack: 'start',
reveal_child: device.connected || device.paired,
child: Widget.Label({
hpack: "start",
class_name: "connection-status dim",
label: device.connected ? "Connected" : "Paired",
hpack: 'start',
class_name: 'connection-status dim',
label: device.connected ? 'Connected' : 'Paired',
}),
}),
],
@@ -116,14 +112,14 @@ const devices = (bluetooth: Bluetooth, self: Box<Gtk.Widget, unknown>) => {
],
}),
Widget.Box({
hpack: "end",
hpack: 'end',
children: device.connecting
? [
Widget.Spinner({
vpack: "start",
class_name: "spinner bluetooth",
}),
]
Widget.Spinner({
vpack: 'start',
class_name: 'spinner bluetooth',
}),
]
: [],
}),
],

View File

@@ -1,17 +1,18 @@
const bluetooth = await Service.import("bluetooth");
import { label } from "./label.js";
import { devices } from "./devicelist.js";
const bluetooth = await Service.import('bluetooth');
import { label } from './label.js';
import { devices } from './devicelist.js';
import { BoxWidget } from 'lib/types/widget.js';
const Devices = () => {
const Devices = (): BoxWidget => {
return Widget.Box({
class_name: "menu-section-container",
class_name: 'menu-section-container',
vertical: true,
children: [
label(bluetooth),
Widget.Box({
class_name: "menu-items-section",
class_name: 'menu-items-section',
child: Widget.Box({
class_name: "menu-content",
class_name: 'menu-content',
vertical: true,
setup: (self) => {
devices(bluetooth, self);

View File

@@ -1,7 +1,10 @@
import { Bluetooth } from "types/service/bluetooth";
const label = (bluetooth: Bluetooth) => {
import { BoxWidget } from 'lib/types/widget';
import { Bluetooth } from 'types/service/bluetooth';
const label = (bluetooth: Bluetooth): BoxWidget => {
const searchInProgress = Variable(false);
const startRotation = () => {
const startRotation = (): void => {
searchInProgress.value = true;
setTimeout(() => {
searchInProgress.value = false;
@@ -9,61 +12,48 @@ const label = (bluetooth: Bluetooth) => {
};
return Widget.Box({
class_name: "menu-label-container",
hpack: "fill",
vpack: "start",
class_name: 'menu-label-container',
hpack: 'fill',
vpack: 'start',
children: [
Widget.Label({
class_name: "menu-label",
vpack: "center",
hpack: "start",
label: "Bluetooth",
class_name: 'menu-label',
vpack: 'center',
hpack: 'start',
label: 'Bluetooth',
}),
Widget.Box({
class_name: "controls-container",
vpack: "start",
class_name: 'controls-container',
vpack: 'start',
children: [
Widget.Switch({
class_name: "menu-switch bluetooth",
class_name: 'menu-switch bluetooth',
hexpand: true,
hpack: "end",
active: bluetooth.bind("enabled"),
hpack: 'end',
active: bluetooth.bind('enabled'),
on_activate: ({ active }) => {
searchInProgress.value = false;
Utils.execAsync([
"bash",
"-c",
`bluetoothctl power ${active ? "on" : "off"}`,
]).catch((err) =>
console.error(
`bluetoothctl power ${active ? "on" : "off"}`,
err,
),
Utils.execAsync(['bash', '-c', `bluetoothctl power ${active ? 'on' : 'off'}`]).catch(
(err) => console.error(`bluetoothctl power ${active ? 'on' : 'off'}`, err),
);
},
}),
Widget.Separator({
class_name: "menu-separator bluetooth",
class_name: 'menu-separator bluetooth',
}),
Widget.Button({
vpack: "center",
class_name: "menu-icon-button search",
vpack: 'center',
class_name: 'menu-icon-button search',
on_primary_click: () => {
startRotation();
Utils.execAsync([
"bash",
"-c",
"bluetoothctl --timeout 120 scan on",
]).catch((err) => {
Utils.execAsync(['bash', '-c', 'bluetoothctl --timeout 120 scan on']).catch((err) => {
searchInProgress.value = false;
console.error("bluetoothctl --timeout 120 scan on", err);
console.error('bluetoothctl --timeout 120 scan on', err);
});
},
child: Widget.Icon({
class_name: searchInProgress
.bind("value")
.as((v) => (v ? "spinning" : "")),
icon: "view-refresh-symbolic",
class_name: searchInProgress.bind('value').as((v) => (v ? 'spinning' : '')),
icon: 'view-refresh-symbolic',
}),
}),
],

View File

@@ -1,21 +1,23 @@
import DropdownMenu from "../DropdownMenu.js";
import { Devices } from "./devices/index.js";
import Window from 'types/widgets/window.js';
import DropdownMenu from '../DropdownMenu.js';
import { Devices } from './devices/index.js';
import { Attribute, Child } from 'lib/types/widget.js';
export default () => {
return DropdownMenu({
name: "bluetoothmenu",
transition: "crossfade",
child: Widget.Box({
class_name: "menu-items bluetooth",
hpack: "fill",
hexpand: true,
child: Widget.Box({
vertical: true,
hpack: "fill",
hexpand: true,
class_name: "menu-items-container bluetooth",
child: Devices(),
}),
}),
});
export default (): Window<Child, Attribute> => {
return DropdownMenu({
name: 'bluetoothmenu',
transition: 'crossfade',
child: Widget.Box({
class_name: 'menu-items bluetooth',
hpack: 'fill',
hexpand: true,
child: Widget.Box({
vertical: true,
hpack: 'fill',
hexpand: true,
class_name: 'menu-items-container bluetooth',
child: Devices(),
}),
}),
});
};

View File

@@ -1,31 +1,29 @@
const getBluetoothIcon = (iconName: string) => {
const getBluetoothIcon = (iconName: string): string => {
const deviceIconMap = [
["^audio-card*", "󰎄"],
["^audio-headphones*", "󰋋"],
["^audio-headset*", "󰋎"],
["^audio-input*", "󰍬"],
["^audio-speakers*", "󰓃"],
["^bluetooth*", "󰂯"],
["^camera*", "󰄀"],
["^computer*", "󰟀"],
["^input-gaming*", "󰍬"],
["^input-keyboard*", "󰌌"],
["^input-mouse*", "󰍽"],
["^input-tablet*", "󰓶"],
["^media*", "󱛟"],
["^modem*", "󱂇"],
["^network*", "󱂇"],
["^phone*", "󰄞"],
["^printer*", "󰐪"],
["^scanner*", "󰚫"],
["^video-camera*", "󰕧"],
['^audio-card*', '󰎄'],
['^audio-headphones*', '󰋋'],
['^audio-headset*', '󰋎'],
['^audio-input*', '󰍬'],
['^audio-speakers*', '󰓃'],
['^bluetooth*', '󰂯'],
['^camera*', '󰄀'],
['^computer*', '󰟀'],
['^input-gaming*', '󰍬'],
['^input-keyboard*', '󰌌'],
['^input-mouse*', '󰍽'],
['^input-tablet*', '󰓶'],
['^media*', '󱛟'],
['^modem*', '󱂇'],
['^network*', '󱂇'],
['^phone*', '󰄞'],
['^printer*', '󰐪'],
['^scanner*', '󰚫'],
['^video-camera*', '󰕧'],
];
const foundMatch = deviceIconMap.find((icon) =>
RegExp(icon[0]).test(iconName.toLowerCase()),
);
const foundMatch = deviceIconMap.find((icon) => RegExp(icon[0]).test(iconName.toLowerCase()));
return foundMatch ? foundMatch[1] : "󰂯";
return foundMatch ? foundMatch[1] : '󰂯';
};
export { getBluetoothIcon };

View File

@@ -1,22 +1,24 @@
const CalendarWidget = () => {
return Widget.Box({
class_name: "calendar-menu-item-container calendar",
hpack: "fill",
vpack: "fill",
expand: true,
child: Widget.Box({
class_name: "calendar-container-box",
child: Widget.Calendar({
import { BoxWidget } from 'lib/types/widget';
const CalendarWidget = (): BoxWidget => {
return Widget.Box({
class_name: 'calendar-menu-item-container calendar',
hpack: 'fill',
vpack: 'fill',
expand: true,
hpack: "fill",
vpack: "fill",
class_name: "calendar-menu-widget",
showDayNames: true,
showDetails: false,
showHeading: true,
}),
}),
});
child: Widget.Box({
class_name: 'calendar-container-box',
child: Widget.Calendar({
expand: true,
hpack: 'fill',
vpack: 'fill',
class_name: 'calendar-menu-widget',
showDayNames: true,
showDetails: false,
showHeading: true,
}),
}),
});
};
export { CalendarWidget };

View File

@@ -1,29 +1,31 @@
import DropdownMenu from "../DropdownMenu.js";
import { TimeWidget } from "./time/index.js";
import { CalendarWidget } from "./calendar.js";
import { WeatherWidget } from "./weather/index.js";
import DropdownMenu from '../DropdownMenu.js';
import { TimeWidget } from './time/index.js';
import { CalendarWidget } from './calendar.js';
import { WeatherWidget } from './weather/index.js';
import Window from 'types/widgets/window.js';
import { Attribute, Child } from 'lib/types/widget.js';
export default () => {
return DropdownMenu({
name: "calendarmenu",
transition: "crossfade",
child: Widget.Box({
class_name: "calendar-menu-content",
css: "padding: 1px; margin: -1px;",
vexpand: false,
children: [
Widget.Box({
class_name: "calendar-content-container",
vertical: true,
children: [
Widget.Box({
class_name: "calendar-content-items",
vertical: true,
children: [TimeWidget(), CalendarWidget(), WeatherWidget()],
}),
],
export default (): Window<Child, Attribute> => {
return DropdownMenu({
name: 'calendarmenu',
transition: 'crossfade',
child: Widget.Box({
class_name: 'calendar-menu-content',
css: 'padding: 1px; margin: -1px;',
vexpand: false,
children: [
Widget.Box({
class_name: 'calendar-content-container',
vertical: true,
children: [
Widget.Box({
class_name: 'calendar-content-items',
vertical: true,
children: [TimeWidget(), CalendarWidget(), WeatherWidget()],
}),
],
}),
],
}),
],
}),
});
});
};

View File

@@ -1,69 +1,70 @@
import options from "options";
import { BoxWidget } from 'lib/types/widget';
import options from 'options';
const { military } = options.menus.clock.time;
const time = Variable("", {
poll: [1000, 'date "+%I:%M:%S"'],
const time = Variable('', {
poll: [1000, 'date "+%I:%M:%S"'],
});
const period = Variable("", {
poll: [1000, 'date "+%p"'],
const period = Variable('', {
poll: [1000, 'date "+%p"'],
});
const militaryTime = Variable("", {
poll: [1000, 'date "+%H:%M:%S"'],
const militaryTime = Variable('', {
poll: [1000, 'date "+%H:%M:%S"'],
});
const TimeWidget = () => {
return Widget.Box({
class_name: "calendar-menu-item-container clock",
hexpand: true,
vpack: "center",
hpack: "fill",
child: Widget.Box({
hexpand: true,
vpack: "center",
hpack: "center",
class_name: "clock-content-items",
children: military.bind("value").as((is24hr) => {
if (!is24hr) {
return [
Widget.Box({
hpack: "center",
children: [
Widget.Label({
class_name: "clock-content-time",
label: time.bind(),
}),
],
const TimeWidget = (): BoxWidget => {
return Widget.Box({
class_name: 'calendar-menu-item-container clock',
hexpand: true,
vpack: 'center',
hpack: 'fill',
child: Widget.Box({
hexpand: true,
vpack: 'center',
hpack: 'center',
class_name: 'clock-content-items',
children: military.bind('value').as((is24hr) => {
if (!is24hr) {
return [
Widget.Box({
hpack: 'center',
children: [
Widget.Label({
class_name: 'clock-content-time',
label: time.bind(),
}),
],
}),
Widget.Box({
hpack: 'center',
children: [
Widget.Label({
vpack: 'end',
class_name: 'clock-content-period',
label: period.bind(),
}),
],
}),
];
}
return [
Widget.Box({
hpack: 'center',
children: [
Widget.Label({
class_name: 'clock-content-time',
label: militaryTime.bind(),
}),
],
}),
];
}),
Widget.Box({
hpack: "center",
children: [
Widget.Label({
vpack: "end",
class_name: "clock-content-period",
label: period.bind(),
}),
],
}),
];
}
return [
Widget.Box({
hpack: "center",
children: [
Widget.Label({
class_name: "clock-content-time",
label: militaryTime.bind(),
}),
],
}),
];
}),
}),
});
}),
});
};
export { TimeWidget };

View File

@@ -1,49 +1,43 @@
import { Weather, WeatherIcon, WeatherIconTitle } from "lib/types/weather.js";
import { Variable } from "types/variable.js";
import { weatherIcons } from "modules/icons/weather.js";
import { isValidWeatherIconTitle } from "globals/weather";
import { Weather, WeatherIconTitle } from 'lib/types/weather.js';
import { Variable } from 'types/variable.js';
import { weatherIcons } from 'modules/icons/weather.js';
import { isValidWeatherIconTitle } from 'globals/weather';
import { BoxWidget } from 'lib/types/widget';
import { getNextEpoch } from '../utils';
export const HourlyIcon = (theWeather: Variable<Weather>, getNextEpoch: any) => {
export const HourlyIcon = (theWeather: Variable<Weather>, hoursFromNow: number): BoxWidget => {
const getIconQuery = (wthr: Weather): WeatherIconTitle => {
const nextEpoch = getNextEpoch(wthr);
const weatherAtEpoch = wthr.forecast.forecastday[0].hour.find(
(h) => h.time_epoch === nextEpoch,
);
const nextEpoch = getNextEpoch(wthr, hoursFromNow);
const weatherAtEpoch = wthr.forecast.forecastday[0].hour.find((h) => h.time_epoch === nextEpoch);
if (weatherAtEpoch === undefined) {
return "warning";
return 'warning';
}
let iconQuery = weatherAtEpoch.condition.text
.trim()
.toLowerCase()
.replaceAll(" ", "_");
let iconQuery = weatherAtEpoch.condition.text.trim().toLowerCase().replaceAll(' ', '_');
if (!weatherAtEpoch?.is_day && iconQuery === "partly_cloudy") {
iconQuery = "partly_cloudy_night";
if (!weatherAtEpoch?.is_day && iconQuery === 'partly_cloudy') {
iconQuery = 'partly_cloudy_night';
}
if (isValidWeatherIconTitle(iconQuery)) {
return iconQuery;
} else {
return "warning";
return 'warning';
}
}
};
return Widget.Box({
hpack: "center",
child: theWeather.bind("value").as((w) => {
let weatherIcn: WeatherIcon;
hpack: 'center',
child: theWeather.bind('value').as((w) => {
const iconQuery = getIconQuery(w);
weatherIcn = weatherIcons[iconQuery] || weatherIcons["warning"];
const weatherIcn = weatherIcons[iconQuery] || weatherIcons['warning'];
return Widget.Label({
hpack: "center",
class_name: "hourly-weather-icon txt-icon",
hpack: 'center',
class_name: 'hourly-weather-icon txt-icon',
label: weatherIcn,
});
})
})
}),
});
};

View File

@@ -1,44 +1,25 @@
import { Weather } from "lib/types/weather";
import { Variable } from "types/variable";
import { HourlyIcon } from "./icon/index.js";
import { HourlyTemp } from "./temperature/index.js";
import { HourlyTime } from "./time/index.js";
import { Weather } from 'lib/types/weather';
import { Variable } from 'types/variable';
import { HourlyIcon } from './icon/index.js';
import { HourlyTemp } from './temperature/index.js';
import { HourlyTime } from './time/index.js';
import { BoxWidget } from 'lib/types/widget.js';
export const Hourly = (theWeather: Variable<Weather>) => {
export const Hourly = (theWeather: Variable<Weather>): BoxWidget => {
return Widget.Box({
vertical: false,
hexpand: true,
hpack: "fill",
class_name: "hourly-weather-container",
hpack: 'fill',
class_name: 'hourly-weather-container',
children: [1, 2, 3, 4].map((hoursFromNow) => {
const getNextEpoch = (wthr: Weather) => {
const currentEpoch = wthr.location.localtime_epoch;
const epochAtHourStart = currentEpoch - (currentEpoch % 3600);
let nextEpoch = 3600 * hoursFromNow + epochAtHourStart;
const curHour = new Date(currentEpoch * 1000).getHours();
/*
* NOTE: Since the API is only capable of showing the current day; if
* the hours left in the day are less than 4 (aka spilling into the next day),
* then rewind to contain the prediction within the current day.
*/
if (curHour > 19) {
const hoursToRewind = curHour - 19;
nextEpoch =
3600 * hoursFromNow + epochAtHourStart - hoursToRewind * 3600;
}
return nextEpoch;
};
return Widget.Box({
class_name: "hourly-weather-item",
class_name: 'hourly-weather-item',
hexpand: true,
vertical: true,
children: [
HourlyTime(theWeather, getNextEpoch),
HourlyIcon(theWeather, getNextEpoch),
HourlyTemp(theWeather, getNextEpoch),
HourlyTime(theWeather, hoursFromNow),
HourlyIcon(theWeather, hoursFromNow),
HourlyTemp(theWeather, hoursFromNow),
],
});
}),

View File

@@ -1,29 +1,27 @@
import { Weather } from "lib/types/weather";
import { Variable } from "types/variable";
import options from "options";
import { Weather } from 'lib/types/weather';
import { Variable } from 'types/variable';
import options from 'options';
import Label from 'types/widgets/label';
import { Child } from 'lib/types/widget';
import { getNextEpoch } from '../utils';
const { unit } = options.menus.clock.weather;
export const HourlyTemp = (theWeather: Variable<Weather>, getNextEpoch: any) => {
export const HourlyTemp = (theWeather: Variable<Weather>, hoursFromNow: number): Label<Child> => {
return Widget.Label({
class_name: "hourly-weather-temp",
label: Utils.merge(
[theWeather.bind("value"), unit.bind("value")],
(wthr, unt) => {
if (!Object.keys(wthr).length) {
return "-";
}
class_name: 'hourly-weather-temp',
label: Utils.merge([theWeather.bind('value'), unit.bind('value')], (wthr, unt) => {
if (!Object.keys(wthr).length) {
return '-';
}
const nextEpoch = getNextEpoch(wthr);
const weatherAtEpoch = wthr.forecast.forecastday[0].hour.find(
(h) => h.time_epoch === nextEpoch,
);
const nextEpoch = getNextEpoch(wthr, hoursFromNow);
const weatherAtEpoch = wthr.forecast.forecastday[0].hour.find((h) => h.time_epoch === nextEpoch);
if (unt === "imperial") {
return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_f) : "-"}° F`;
}
return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_c) : "-"}° C`;
},
),
if (unt === 'imperial') {
return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_f) : '-'}° F`;
}
return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_c) : '-'}° C`;
}),
});
};

View File

@@ -1,18 +1,21 @@
import { Weather } from "lib/types/weather";
import { Variable } from "types/variable";
import { Weather } from 'lib/types/weather';
import { Child } from 'lib/types/widget';
import { Variable } from 'types/variable';
import Label from 'types/widgets/label';
import { getNextEpoch } from '../utils';
export const HourlyTime = (theWeather: Variable<Weather>, getNextEpoch: any) => {
export const HourlyTime = (theWeather: Variable<Weather>, hoursFromNow: number): Label<Child> => {
return Widget.Label({
class_name: "hourly-weather-time",
label: theWeather.bind("value").as((w) => {
class_name: 'hourly-weather-time',
label: theWeather.bind('value').as((w) => {
if (!Object.keys(w).length) {
return "-";
return '-';
}
const nextEpoch = getNextEpoch(w);
const nextEpoch = getNextEpoch(w, hoursFromNow);
const dateAtEpoch = new Date(nextEpoch * 1000);
let hours = dateAtEpoch.getHours();
const ampm = hours >= 12 ? "PM" : "AM";
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12 || 12;
return `${hours}${ampm}`;

View File

@@ -0,0 +1,20 @@
import { Weather } from 'lib/types/weather';
export const getNextEpoch = (wthr: Weather, hoursFromNow: number): number => {
const currentEpoch = wthr.location.localtime_epoch;
const epochAtHourStart = currentEpoch - (currentEpoch % 3600);
let nextEpoch = 3600 * hoursFromNow + epochAtHourStart;
const curHour = new Date(currentEpoch * 1000).getHours();
/*
* NOTE: Since the API is only capable of showing the current day; if
* the hours left in the day are less than 4 (aka spilling into the next day),
* then rewind to contain the prediction within the current day.
*/
if (curHour > 19) {
const hoursToRewind = curHour - 19;
nextEpoch = 3600 * hoursFromNow + epochAtHourStart - hoursToRewind * 3600;
}
return nextEpoch;
};

View File

@@ -1,17 +1,18 @@
import { Weather } from "lib/types/weather";
import { Variable } from "types/variable";
import { getWeatherStatusTextIcon } from "globals/weather.js";
import { Weather } from 'lib/types/weather';
import { Variable } from 'types/variable';
import { getWeatherStatusTextIcon } from 'globals/weather.js';
import { BoxWidget } from 'lib/types/widget';
export const TodayIcon = (theWeather: Variable<Weather>) => {
export const TodayIcon = (theWeather: Variable<Weather>): BoxWidget => {
return Widget.Box({
vpack: "center",
hpack: "start",
class_name: "calendar-menu-weather today icon container",
vpack: 'center',
hpack: 'start',
class_name: 'calendar-menu-weather today icon container',
child: Widget.Label({
class_name: "calendar-menu-weather today icon txt-icon",
label: theWeather.bind("value").as((w) => {
class_name: 'calendar-menu-weather today icon txt-icon',
label: theWeather.bind('value').as((w) => {
return getWeatherStatusTextIcon(w);
}),
})
})
}),
});
};

View File

@@ -1,21 +1,22 @@
import { TodayIcon } from "./icon/index.js";
import { TodayStats } from "./stats/index.js";
import { TodayTemperature } from "./temperature/index.js";
import { Hourly } from "./hourly/index.js";
import { globalWeatherVar } from "globals/weather.js";
import { TodayIcon } from './icon/index.js';
import { TodayStats } from './stats/index.js';
import { TodayTemperature } from './temperature/index.js';
import { Hourly } from './hourly/index.js';
import { globalWeatherVar } from 'globals/weather.js';
import { BoxWidget } from 'lib/types/widget.js';
const WeatherWidget = () => {
const WeatherWidget = (): BoxWidget => {
return Widget.Box({
class_name: "calendar-menu-item-container weather",
class_name: 'calendar-menu-item-container weather',
child: Widget.Box({
class_name: "weather-container-box",
class_name: 'weather-container-box',
setup: (self) => {
return (self.child = Widget.Box({
vertical: true,
hexpand: true,
children: [
Widget.Box({
class_name: "calendar-menu-weather today",
class_name: 'calendar-menu-weather today',
hexpand: true,
children: [
TodayIcon(globalWeatherVar),
@@ -24,7 +25,7 @@ const WeatherWidget = () => {
],
}),
Widget.Separator({
class_name: "menu-separator weather",
class_name: 'menu-separator weather',
}),
Hourly(globalWeatherVar),
],

View File

@@ -1,29 +1,30 @@
import { Weather } from "lib/types/weather";
import { Variable } from "types/variable";
import options from "options";
import { Unit } from "lib/types/options";
import { getRainChance, getWindConditions } from "globals/weather";
import { Weather } from 'lib/types/weather';
import { Variable } from 'types/variable';
import options from 'options';
import { Unit } from 'lib/types/options';
import { getRainChance, getWindConditions } from 'globals/weather';
import { BoxWidget } from 'lib/types/widget';
const { unit } = options.menus.clock.weather;
export const TodayStats = (theWeather: Variable<Weather>) => {
export const TodayStats = (theWeather: Variable<Weather>): BoxWidget => {
return Widget.Box({
class_name: "calendar-menu-weather today stats container",
hpack: "end",
vpack: "center",
class_name: 'calendar-menu-weather today stats container',
hpack: 'end',
vpack: 'center',
vertical: true,
children: [
Widget.Box({
class_name: "weather wind",
class_name: 'weather wind',
children: [
Widget.Label({
class_name: "weather wind icon txt-icon",
label: "",
class_name: 'weather wind icon txt-icon',
label: '',
}),
Widget.Label({
class_name: "weather wind label",
class_name: 'weather wind label',
label: Utils.merge(
[theWeather.bind("value"), unit.bind("value")],
[theWeather.bind('value'), unit.bind('value')],
(wthr: Weather, unt: Unit) => {
return getWindConditions(wthr, unt);
},
@@ -32,19 +33,15 @@ export const TodayStats = (theWeather: Variable<Weather>) => {
],
}),
Widget.Box({
class_name: "weather precip",
class_name: 'weather precip',
children: [
Widget.Label({
class_name: "weather precip icon txt-icon",
label: "",
class_name: 'weather precip icon txt-icon',
label: '',
}),
Widget.Label({
class_name: "weather precip label",
label: theWeather
.bind("value")
.as(
(v) => getRainChance(v),
),
class_name: 'weather precip label',
label: theWeather.bind('value').as((v) => getRainChance(v)),
}),
],
}),

View File

@@ -1,43 +1,41 @@
import { Weather } from "lib/types/weather";
import { Variable } from "types/variable";
import options from "options";
import { getTemperature, getWeatherIcon } from "globals/weather";
import { Weather } from 'lib/types/weather';
import { Variable } from 'types/variable';
import options from 'options';
import { getTemperature, getWeatherIcon } from 'globals/weather';
import { BoxWidget } from 'lib/types/widget';
const { unit } = options.menus.clock.weather;
export const TodayTemperature = (theWeather: Variable<Weather>) => {
export const TodayTemperature = (theWeather: Variable<Weather>): BoxWidget => {
return Widget.Box({
hpack: "center",
vpack: "center",
hpack: 'center',
vpack: 'center',
vertical: true,
children: [
Widget.Box({
hexpand: true,
vpack: "center",
class_name: "calendar-menu-weather today temp container",
vpack: 'center',
class_name: 'calendar-menu-weather today temp container',
vertical: false,
children: [
Widget.Box({
hexpand: true,
hpack: "center",
hpack: 'center',
children: [
Widget.Label({
class_name: "calendar-menu-weather today temp label",
label: Utils.merge(
[theWeather.bind("value"), unit.bind("value")],
(wthr, unt) => {
return getTemperature(wthr, unt);
},
),
class_name: 'calendar-menu-weather today temp label',
label: Utils.merge([theWeather.bind('value'), unit.bind('value')], (wthr, unt) => {
return getTemperature(wthr, unt);
}),
}),
Widget.Label({
class_name: theWeather
.bind("value")
.bind('value')
.as(
(v) =>
`calendar-menu-weather today temp label icon txt-icon ${getWeatherIcon(Math.ceil(v.current.temp_f)).color}`,
),
label: theWeather
.bind("value")
.bind('value')
.as((v) => getWeatherIcon(Math.ceil(v.current.temp_f)).icon),
}),
],
@@ -45,18 +43,18 @@ export const TodayTemperature = (theWeather: Variable<Weather>) => {
],
}),
Widget.Box({
hpack: "center",
hpack: 'center',
child: Widget.Label({
max_width_chars: 17,
truncate: "end",
truncate: 'end',
lines: 2,
class_name: theWeather
.bind("value")
.bind('value')
.as(
(v) =>
`calendar-menu-weather today condition label ${getWeatherIcon(Math.ceil(v.current.temp_f)).color}`,
),
label: theWeather.bind("value").as((v) => v.current.condition.text),
label: theWeather.bind('value').as((v) => v.current.condition.text),
}),
}),
],

View File

@@ -1,95 +1,91 @@
const network = await Service.import("network");
const bluetooth = await Service.import("bluetooth");
const notifications = await Service.import("notifications");
const audio = await Service.import("audio");
import { BoxWidget } from 'lib/types/widget';
const Controls = () => {
const network = await Service.import('network');
const bluetooth = await Service.import('bluetooth');
const notifications = await Service.import('notifications');
const audio = await Service.import('audio');
const Controls = (): BoxWidget => {
return Widget.Box({
class_name: "dashboard-card controls-container",
hpack: "fill",
vpack: "fill",
class_name: 'dashboard-card controls-container',
hpack: 'fill',
vpack: 'fill',
expand: true,
children: [
Widget.Button({
tooltip_text: "Toggle Wifi",
tooltip_text: 'Toggle Wifi',
expand: true,
setup: (self) => {
self.hook(network, () => {
return (self.class_name = `dashboard-button wifi ${!network.wifi.enabled ? "disabled" : ""}`);
return (self.class_name = `dashboard-button wifi ${!network.wifi.enabled ? 'disabled' : ''}`);
});
},
on_primary_click: () => network.toggleWifi(),
child: Widget.Label({
class_name: "txt-icon",
class_name: 'txt-icon',
setup: (self) => {
self.hook(network, () => {
return (self.label = network.wifi.enabled ? "󰤨" : "󰤭");
return (self.label = network.wifi.enabled ? '󰤨' : '󰤭');
});
},
}),
}),
Widget.Button({
tooltip_text: "Toggle Bluetooth",
tooltip_text: 'Toggle Bluetooth',
expand: true,
class_name: bluetooth
.bind("enabled")
.as(
(btOn) => `dashboard-button bluetooth ${!btOn ? "disabled" : ""}`,
),
.bind('enabled')
.as((btOn) => `dashboard-button bluetooth ${!btOn ? 'disabled' : ''}`),
on_primary_click: () => bluetooth.toggle(),
child: Widget.Label({
class_name: "txt-icon",
label: bluetooth.bind("enabled").as((btOn) => (btOn ? "󰂯" : "󰂲")),
class_name: 'txt-icon',
label: bluetooth.bind('enabled').as((btOn) => (btOn ? '󰂯' : '󰂲')),
}),
}),
Widget.Button({
tooltip_text: "Toggle Notifications",
tooltip_text: 'Toggle Notifications',
expand: true,
class_name: notifications
.bind("dnd")
.as(
(dnd) => `dashboard-button notifications ${dnd ? "disabled" : ""}`,
),
.bind('dnd')
.as((dnd) => `dashboard-button notifications ${dnd ? 'disabled' : ''}`),
on_primary_click: () => (notifications.dnd = !notifications.dnd),
child: Widget.Label({
class_name: "txt-icon",
label: notifications.bind("dnd").as((dnd) => (dnd ? "󰂛" : "󰂚")),
class_name: 'txt-icon',
label: notifications.bind('dnd').as((dnd) => (dnd ? '󰂛' : '󰂚')),
}),
}),
Widget.Button({
tooltip_text: "Toggle Mute (Playback)",
tooltip_text: 'Toggle Mute (Playback)',
expand: true,
on_primary_click: () =>
(audio.speaker.is_muted = !audio.speaker.is_muted),
on_primary_click: () => (audio.speaker.is_muted = !audio.speaker.is_muted),
setup: (self) => {
self.hook(audio, () => {
return (self.class_name = `dashboard-button playback ${audio.speaker.is_muted ? "disabled" : ""}`);
return (self.class_name = `dashboard-button playback ${audio.speaker.is_muted ? 'disabled' : ''}`);
});
},
child: Widget.Label({
class_name: "txt-icon",
class_name: 'txt-icon',
setup: (self) => {
self.hook(audio, () => {
return (self.label = audio.speaker.is_muted ? "󰖁" : "󰕾");
return (self.label = audio.speaker.is_muted ? '󰖁' : '󰕾');
});
},
}),
}),
Widget.Button({
tooltip_text: "Toggle Mute (Microphone)",
tooltip_text: 'Toggle Mute (Microphone)',
expand: true,
on_primary_click: () =>
(audio.microphone.is_muted = !audio.microphone.is_muted),
on_primary_click: () => (audio.microphone.is_muted = !audio.microphone.is_muted),
setup: (self) => {
self.hook(audio, () => {
return (self.class_name = `dashboard-button input ${audio.microphone.is_muted ? "disabled" : ""}`);
return (self.class_name = `dashboard-button input ${audio.microphone.is_muted ? 'disabled' : ''}`);
});
},
child: Widget.Label({
class_name: "txt-icon",
class_name: 'txt-icon',
setup: (self) => {
self.hook(audio, () => {
return (self.label = audio.microphone.is_muted ? "󰍭" : "󰍬");
return (self.label = audio.microphone.is_muted ? '󰍭' : '󰍬');
});
},
}),

View File

@@ -1,68 +1,63 @@
import options from "options";
import { BoxWidget } from 'lib/types/widget';
import options from 'options';
const { left, right } = options.menus.dashboard.directories;
const Directories = () => {
const Directories = (): BoxWidget => {
return Widget.Box({
class_name: "dashboard-card directories-container",
vpack: "fill",
hpack: "fill",
class_name: 'dashboard-card directories-container',
vpack: 'fill',
hpack: 'fill',
expand: true,
children: [
Widget.Box({
vertical: true,
expand: true,
class_name: "section right",
class_name: 'section right',
children: [
Widget.Button({
hpack: "start",
hpack: 'start',
expand: true,
class_name: "directory-link left top",
on_primary_click: left.directory1.command
.bind("value")
.as((cmd) => {
return () => {
App.closeWindow("dashboardmenu");
Utils.execAsync(cmd);
};
}),
class_name: 'directory-link left top',
on_primary_click: left.directory1.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: "start",
label: left.directory1.label.bind("value"),
hpack: 'start',
label: left.directory1.label.bind('value'),
}),
}),
Widget.Button({
expand: true,
hpack: "start",
class_name: "directory-link left middle",
on_primary_click: left.directory2.command
.bind("value")
.as((cmd) => {
return () => {
App.closeWindow("dashboardmenu");
Utils.execAsync(cmd);
};
}),
hpack: 'start',
class_name: 'directory-link left middle',
on_primary_click: left.directory2.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: "start",
label: left.directory2.label.bind("value"),
hpack: 'start',
label: left.directory2.label.bind('value'),
}),
}),
Widget.Button({
expand: true,
hpack: "start",
class_name: "directory-link left bottom",
on_primary_click: left.directory3.command
.bind("value")
.as((cmd) => {
return () => {
App.closeWindow("dashboardmenu");
Utils.execAsync(cmd);
};
}),
hpack: 'start',
class_name: 'directory-link left bottom',
on_primary_click: left.directory3.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: "start",
label: left.directory3.label.bind("value"),
hpack: 'start',
label: left.directory3.label.bind('value'),
}),
}),
],
@@ -70,57 +65,51 @@ const Directories = () => {
Widget.Box({
vertical: true,
expand: true,
class_name: "section left",
class_name: 'section left',
children: [
Widget.Button({
hpack: "start",
hpack: 'start',
expand: true,
class_name: "directory-link right top",
on_primary_click: right.directory1.command
.bind("value")
.as((cmd) => {
return () => {
App.closeWindow("dashboardmenu");
Utils.execAsync(cmd);
};
}),
class_name: 'directory-link right top',
on_primary_click: right.directory1.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: "start",
label: right.directory1.label.bind("value"),
hpack: 'start',
label: right.directory1.label.bind('value'),
}),
}),
Widget.Button({
expand: true,
hpack: "start",
class_name: "directory-link right middle",
on_primary_click: right.directory2.command
.bind("value")
.as((cmd) => {
return () => {
App.closeWindow("dashboardmenu");
Utils.execAsync(cmd);
};
}),
hpack: 'start',
class_name: 'directory-link right middle',
on_primary_click: right.directory2.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: "start",
label: right.directory2.label.bind("value"),
hpack: 'start',
label: right.directory2.label.bind('value'),
}),
}),
Widget.Button({
expand: true,
hpack: "start",
class_name: "directory-link right bottom",
on_primary_click: right.directory3.command
.bind("value")
.as((cmd) => {
return () => {
App.closeWindow("dashboardmenu");
Utils.execAsync(cmd);
};
}),
hpack: 'start',
class_name: 'directory-link right bottom',
on_primary_click: right.directory3.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: "start",
label: right.directory3.label.bind("value"),
hpack: 'start',
label: right.directory3.label.bind('value'),
}),
}),
],

View File

@@ -1,37 +1,33 @@
import DropdownMenu from "../DropdownMenu.js";
import { Profile } from "./profile/index.js";
import { Shortcuts } from "./shortcuts/index.js";
import { Controls } from "./controls/index.js";
import { Stats } from "./stats/index.js";
import { Directories } from "./directories/index.js";
import DropdownMenu from '../DropdownMenu.js';
import { Profile } from './profile/index.js';
import { Shortcuts } from './shortcuts/index.js';
import { Controls } from './controls/index.js';
import { Stats } from './stats/index.js';
import { Directories } from './directories/index.js';
import Window from 'types/widgets/window.js';
import { Attribute, Child } from 'lib/types/widget.js';
export default () => {
return DropdownMenu({
name: "dashboardmenu",
transition: "crossfade",
child: Widget.Box({
class_name: "dashboard-menu-content",
css: "padding: 1px; margin: -1px;",
vexpand: false,
children: [
Widget.Box({
class_name: "dashboard-content-container",
vertical: true,
children: [
Widget.Box({
class_name: "dashboard-content-items",
vertical: true,
children: [
Profile(),
Shortcuts(),
Controls(),
Directories(),
Stats(),
],
}),
],
export default (): Window<Child, Attribute> => {
return DropdownMenu({
name: 'dashboardmenu',
transition: 'crossfade',
child: Widget.Box({
class_name: 'dashboard-menu-content',
css: 'padding: 1px; margin: -1px;',
vexpand: false,
children: [
Widget.Box({
class_name: 'dashboard-content-container',
vertical: true,
children: [
Widget.Box({
class_name: 'dashboard-content-items',
vertical: true,
children: [Profile(), Shortcuts(), Controls(), Directories(), Stats()],
}),
],
}),
],
}),
],
}),
});
});
};

View File

@@ -1,64 +1,67 @@
import powermenu from "../../power/helpers/actions.js";
import { PowerOptions } from "lib/types/options.js";
import GdkPixbuf from "gi://GdkPixbuf";
import powermenu from '../../power/helpers/actions.js';
import { PowerOptions } from 'lib/types/options.js';
import GdkPixbuf from 'gi://GdkPixbuf';
import options from "options";
import options from 'options';
import { BoxWidget, Child } from 'lib/types/widget.js';
import Label from 'types/widgets/label.js';
const { image, name } = options.menus.dashboard.powermenu.avatar;
const { confirmation, shutdown, logout, sleep, reboot } = options.menus.dashboard.powermenu;
const Profile = () => {
const handleClick = (action: PowerOptions) => {
const Profile = (): BoxWidget => {
const handleClick = (action: PowerOptions): void => {
const actions = {
shutdown: shutdown.value,
reboot: reboot.value,
logout: logout.value,
sleep: sleep.value,
};
App.closeWindow("dashboardmenu");
App.closeWindow('dashboardmenu');
if (!confirmation.value) {
Utils.execAsync(actions[action])
.catch((err) => console.error(`Failed to execute ${action} command. Error: ${err}`));
Utils.execAsync(actions[action]).catch((err) =>
console.error(`Failed to execute ${action} command. Error: ${err}`),
);
} else {
powermenu.action(action);
}
};
const getIconForButton = (txtIcon: string) => {
const getIconForButton = (txtIcon: string): Label<Child> => {
return Widget.Label({
className: "txt-icon",
label: txtIcon
})
}
className: 'txt-icon',
label: txtIcon,
});
};
return Widget.Box({
class_name: "profiles-container",
hpack: "fill",
class_name: 'profiles-container',
hpack: 'fill',
hexpand: true,
children: [
Widget.Box({
class_name: "profile-picture-container dashboard-card",
class_name: 'profile-picture-container dashboard-card',
hexpand: true,
vertical: true,
children: [
Widget.Box({
hpack: "center",
class_name: "profile-picture",
css: image.bind("value").as(i => {
hpack: 'center',
class_name: 'profile-picture',
css: image.bind('value').as((i) => {
try {
GdkPixbuf.Pixbuf.new_from_file(i);
return `background-image: url("${i}")`
return `background-image: url("${i}")`;
} catch {
return `background-image: url("${App.configDir}/assets/hyprpanel.png")`
return `background-image: url("${App.configDir}/assets/hyprpanel.png")`;
}
}),
}),
Widget.Label({
hpack: "center",
class_name: "profile-name",
label: name.bind("value").as((v) => {
if (v === "system") {
return Utils.exec("bash -c whoami");
hpack: 'center',
class_name: 'profile-name',
label: name.bind('value').as((v) => {
if (v === 'system') {
return Utils.exec('bash -c whoami');
}
return v;
}),
@@ -66,39 +69,39 @@ const Profile = () => {
],
}),
Widget.Box({
class_name: "power-menu-container dashboard-card",
class_name: 'power-menu-container dashboard-card',
vertical: true,
vexpand: true,
children: [
Widget.Button({
class_name: "dashboard-button shutdown",
on_clicked: () => handleClick("shutdown"),
tooltip_text: "Shut Down",
class_name: 'dashboard-button shutdown',
on_clicked: () => handleClick('shutdown'),
tooltip_text: 'Shut Down',
vexpand: true,
child: getIconForButton("󰐥")
child: getIconForButton('󰐥'),
}),
Widget.Button({
class_name: "dashboard-button restart",
on_clicked: () => handleClick("reboot"),
tooltip_text: "Restart",
class_name: 'dashboard-button restart',
on_clicked: () => handleClick('reboot'),
tooltip_text: 'Restart',
vexpand: true,
child: getIconForButton("󰜉")
child: getIconForButton('󰜉'),
}),
Widget.Button({
class_name: "dashboard-button lock",
on_clicked: () => handleClick("logout"),
tooltip_text: "Log Out",
class_name: 'dashboard-button lock',
on_clicked: () => handleClick('logout'),
tooltip_text: 'Log Out',
vexpand: true,
child: getIconForButton("󰿅")
child: getIconForButton('󰿅'),
}),
Widget.Button({
class_name: "dashboard-button sleep",
on_clicked: () => handleClick("sleep"),
tooltip_text: "Sleep",
class_name: 'dashboard-button sleep',
on_clicked: () => handleClick('sleep'),
tooltip_text: 'Sleep',
vexpand: true,
child: getIconForButton("󰤄")
child: getIconForButton('󰤄'),
}),
]
],
}),
],
});

View File

@@ -1,24 +1,28 @@
const hyprland = await Service.import("hyprland");
import options from "options";
import { Variable as VarType } from "types/variable";
const hyprland = await Service.import('hyprland');
import { Attribute, BoxWidget, Child } from 'lib/types/widget';
import options from 'options';
import { Variable as VarType } from 'types/variable';
import Box from 'types/widgets/box';
import Button from 'types/widgets/button';
import Label from 'types/widgets/label';
const { left, right } = options.menus.dashboard.shortcuts;
const Shortcuts = () => {
const Shortcuts = (): BoxWidget => {
const isRecording = Variable(false, {
poll: [
1000,
`${App.configDir}/services/screen_record.sh status`,
(out) => {
if (out === "recording") {
(out): boolean => {
if (out === 'recording') {
return true;
}
return false;
},
],
});
const handleClick = (action: any, tOut: number = 250) => {
App.closeWindow("dashboardmenu");
const handleClick = (action: string, tOut: number = 250): void => {
App.closeWindow('dashboardmenu');
setTimeout(() => {
Utils.execAsync(action)
@@ -30,8 +34,8 @@ const Shortcuts = () => {
};
const recordingDropdown = Widget.Menu({
class_name: "dropdown recording",
hpack: "fill",
class_name: 'dropdown recording',
hpack: 'fill',
hexpand: true,
setup: (self) => {
self.hook(hyprland, () => {
@@ -39,26 +43,26 @@ const Shortcuts = () => {
return Widget.MenuItem({
label: `Display ${mon.name}`,
on_activate: () => {
App.closeWindow("dashboardmenu");
Utils.execAsync(
`${App.configDir}/services/screen_record.sh start ${mon.name}`,
).catch((err) => console.error(err));
App.closeWindow('dashboardmenu');
Utils.execAsync(`${App.configDir}/services/screen_record.sh start ${mon.name}`).catch(
(err) => console.error(err),
);
},
});
});
// NOTE: This is disabled since window recording isn't available on wayland
const apps = hyprland.clients.map((clt) => {
return Widget.MenuItem({
label: `${clt.class.charAt(0).toUpperCase() + clt.class.slice(1)} (Workspace ${clt.workspace.name})`,
on_activate: () => {
App.closeWindow("dashboardmenu");
Utils.execAsync(
`${App.configDir}/services/screen_record.sh start ${clt.focusHistoryID}`,
).catch((err) => console.error(err));
},
});
});
// const apps = hyprland.clients.map((clt) => {
// return Widget.MenuItem({
// label: `${clt.class.charAt(0).toUpperCase() + clt.class.slice(1)} (Workspace ${clt.workspace.name})`,
// on_activate: () => {
// App.closeWindow('dashboardmenu');
// Utils.execAsync(
// `${App.configDir}/services/screen_record.sh start ${clt.focusHistoryID}`,
// ).catch((err) => console.error(err));
// },
// });
// });
return (self.children = [
...displays,
@@ -85,15 +89,15 @@ const Shortcuts = () => {
type Shortcut = ShortcutFixed | ShortcutVariable;
const cmdLn = (sCut: ShortcutVariable) => {
return sCut.command.value.length > 0
const cmdLn = (sCut: ShortcutVariable): boolean => {
return sCut.command.value.length > 0;
};
const leftCardHidden = Variable(
!(cmdLn(left.shortcut1) || cmdLn(left.shortcut2) || cmdLn(left.shortcut3) || cmdLn(left.shortcut4))
!(cmdLn(left.shortcut1) || cmdLn(left.shortcut2) || cmdLn(left.shortcut3) || cmdLn(left.shortcut4)),
);
function createButton(shortcut: Shortcut, className: string) {
const createButton = (shortcut: Shortcut, className: string): Button<Label<Attribute>, Attribute> => {
if (shortcut.configurable !== false) {
return Widget.Button({
vexpand: true,
@@ -101,7 +105,7 @@ const Shortcuts = () => {
class_name: className,
on_primary_click: () => handleClick(shortcut.command.value),
child: Widget.Label({
class_name: "button-label txt-icon",
class_name: 'button-label txt-icon',
label: shortcut.icon.value,
}),
});
@@ -112,166 +116,197 @@ const Shortcuts = () => {
tooltip_text: shortcut.tooltip,
class_name: className,
on_primary_click: (_, event) => {
if (shortcut.command === "settings-dialog") {
App.closeWindow("dashboardmenu");
App.toggleWindow("settings-dialog");
} else if (shortcut.command === "record") {
if (shortcut.command === 'settings-dialog') {
App.closeWindow('dashboardmenu');
App.toggleWindow('settings-dialog');
} else if (shortcut.command === 'record') {
if (isRecording.value === true) {
App.closeWindow("dashboardmenu");
return Utils.execAsync(
`${App.configDir}/services/screen_record.sh stop`,
).catch((err) => console.error(err));
App.closeWindow('dashboardmenu');
return Utils.execAsync(`${App.configDir}/services/screen_record.sh stop`).catch((err) =>
console.error(err),
);
} else {
recordingDropdown.popup_at_pointer(event);
}
}
},
child: Widget.Label({
class_name: "button-label txt-icon",
class_name: 'button-label txt-icon',
label: shortcut.icon,
}),
});
}
}
};
function createButtonIfCommandExists(shortcut: Shortcut, className: string, command: string) {
const createButtonIfCommandExists = (
shortcut: Shortcut,
className: string,
command: string,
): Button<Label<Attribute>, Attribute> | Box<Child, Attribute> => {
if (command.length > 0) {
return createButton(shortcut, className);
}
return Widget.Box();
}
};
return Widget.Box({
class_name: "shortcuts-container",
hpack: "fill",
class_name: 'shortcuts-container',
hpack: 'fill',
hexpand: true,
children: [
Widget.Box({
child: Utils.merge([
left.shortcut1.command.bind("value"),
left.shortcut2.command.bind("value"),
left.shortcut1.tooltip.bind("value"),
left.shortcut2.tooltip.bind("value"),
left.shortcut1.icon.bind("value"),
left.shortcut2.icon.bind("value"),
left.shortcut3.command.bind("value"),
left.shortcut4.command.bind("value"),
left.shortcut3.tooltip.bind("value"),
left.shortcut4.tooltip.bind("value"),
left.shortcut3.icon.bind("value"),
left.shortcut4.icon.bind("value")
], () => {
const isVisibleLeft = cmdLn(left.shortcut1) || cmdLn(left.shortcut2);
const isVisibleRight = cmdLn(left.shortcut3) || cmdLn(left.shortcut4);
child: Utils.merge(
[
left.shortcut1.command.bind('value'),
left.shortcut2.command.bind('value'),
left.shortcut1.tooltip.bind('value'),
left.shortcut2.tooltip.bind('value'),
left.shortcut1.icon.bind('value'),
left.shortcut2.icon.bind('value'),
left.shortcut3.command.bind('value'),
left.shortcut4.command.bind('value'),
left.shortcut3.tooltip.bind('value'),
left.shortcut4.tooltip.bind('value'),
left.shortcut3.icon.bind('value'),
left.shortcut4.icon.bind('value'),
],
() => {
const isVisibleLeft = cmdLn(left.shortcut1) || cmdLn(left.shortcut2);
const isVisibleRight = cmdLn(left.shortcut3) || cmdLn(left.shortcut4);
if (!isVisibleLeft && !isVisibleRight) {
leftCardHidden.value = true;
return Widget.Box();
}
if (!isVisibleLeft && !isVisibleRight) {
leftCardHidden.value = true;
return Widget.Box();
}
leftCardHidden.value = false;
leftCardHidden.value = false;
return Widget.Box({
class_name: "container most-used dashboard-card",
children: [
Widget.Box({
className: `card-button-section-container ${isVisibleRight && isVisibleLeft ? "visible" : ""}`,
child: isVisibleLeft ? Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(
left.shortcut1,
`dashboard-button top-button ${cmdLn(left.shortcut2) ? "paired" : ""}`,
left.shortcut1.command.value),
createButtonIfCommandExists(
left.shortcut2,
"dashboard-button",
left.shortcut2.command.value
),
],
}) : Widget.Box({
children: [],
})
}),
Widget.Box({
className: "card-button-section-container",
child: isVisibleRight ? Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(
left.shortcut3,
`dashboard-button top-button ${cmdLn(left.shortcut4) ? "paired" : ""}`,
left.shortcut3.command.value),
createButtonIfCommandExists(
left.shortcut4,
"dashboard-button",
left.shortcut4.command.value
),
],
}) : Widget.Box({
children: [],
return Widget.Box({
class_name: 'container most-used dashboard-card',
children: [
Widget.Box({
className: `card-button-section-container ${isVisibleRight && isVisibleLeft ? 'visible' : ''}`,
child: isVisibleLeft
? Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(
left.shortcut1,
`dashboard-button top-button ${cmdLn(left.shortcut2) ? 'paired' : ''}`,
left.shortcut1.command.value,
),
createButtonIfCommandExists(
left.shortcut2,
'dashboard-button',
left.shortcut2.command.value,
),
],
})
: Widget.Box({
children: [],
}),
}),
}),
]
});
})
Widget.Box({
className: 'card-button-section-container',
child: isVisibleRight
? Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(
left.shortcut3,
`dashboard-button top-button ${cmdLn(left.shortcut4) ? 'paired' : ''}`,
left.shortcut3.command.value,
),
createButtonIfCommandExists(
left.shortcut4,
'dashboard-button',
left.shortcut4.command.value,
),
],
})
: Widget.Box({
children: [],
}),
}),
],
});
},
),
}),
Widget.Box({
child: Utils.merge([
right.shortcut1.command.bind("value"),
right.shortcut1.tooltip.bind("value"),
right.shortcut1.icon.bind("value"),
right.shortcut3.command.bind("value"),
right.shortcut3.tooltip.bind("value"),
right.shortcut3.icon.bind("value"),
leftCardHidden.bind("value"),
isRecording.bind("value")
], () => {
return Widget.Box({
class_name: `container utilities dashboard-card ${!leftCardHidden.value ? "paired" : ""}`,
children: [
Widget.Box({
className: `card-button-section-container visible`,
child: Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(right.shortcut1, "dashboard-button top-button paired", right.shortcut1.command.value),
createButtonIfCommandExists(
{
tooltip: "HyprPanel Configuration",
command: "settings-dialog",
icon: "󰒓",
configurable: false
}, "dashboard-button", "settings-dialog"),
],
})
}),
Widget.Box({
className: "card-button-section-container",
child: Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(right.shortcut3, "dashboard-button top-button paired", right.shortcut3.command.value),
createButtonIfCommandExists({
tooltip: "Record Screen",
command: "record",
icon: "󰑊",
configurable: false
}, `dashboard-button record ${isRecording.value ? "active" : ""}`, "record"),
],
child: Utils.merge(
[
right.shortcut1.command.bind('value'),
right.shortcut1.tooltip.bind('value'),
right.shortcut1.icon.bind('value'),
right.shortcut3.command.bind('value'),
right.shortcut3.tooltip.bind('value'),
right.shortcut3.icon.bind('value'),
leftCardHidden.bind('value'),
isRecording.bind('value'),
],
() => {
return Widget.Box({
class_name: `container utilities dashboard-card ${!leftCardHidden.value ? 'paired' : ''}`,
children: [
Widget.Box({
className: `card-button-section-container visible`,
child: Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(
right.shortcut1,
'dashboard-button top-button paired',
right.shortcut1.command.value,
),
createButtonIfCommandExists(
{
tooltip: 'HyprPanel Configuration',
command: 'settings-dialog',
icon: '󰒓',
configurable: false,
},
'dashboard-button',
'settings-dialog',
),
],
}),
}),
}),
]
});
})
Widget.Box({
className: 'card-button-section-container',
child: Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(
right.shortcut3,
'dashboard-button top-button paired',
right.shortcut3.command.value,
),
createButtonIfCommandExists(
{
tooltip: 'Record Screen',
command: 'record',
icon: '󰑊',
configurable: false,
},
`dashboard-button record ${isRecording.value ? 'active' : ''}`,
'record',
),
],
}),
}),
],
});
},
),
}),
],
});

View File

@@ -1,32 +1,33 @@
import options from "options";
import { GPU_Stat } from "lib/types/gpustat";
import { dependencies } from "lib/utils";
import options from 'options';
import { GPU_Stat } from 'lib/types/gpustat';
import { dependencies } from 'lib/utils';
import { BoxWidget } from 'lib/types/widget';
import { GenericResourceMetrics } from 'lib/types/customModules/generic';
const { terminal } = options;
const { enable_gpu } = options.menus.dashboard.stats;
const Stats = () => {
const divide = ([total, free]: number[]) => free / total;
const Stats = (): BoxWidget => {
const divide = ([total, free]: number[]): number => free / total;
const formatSizeInGB = (sizeInKB: number) =>
Number((sizeInKB / 1024 ** 2).toFixed(2));
const formatSizeInGB = (sizeInKB: number): number => Number((sizeInKB / 1024 ** 2).toFixed(2));
const cpu = Variable(0, {
poll: [
2000,
"top -b -n 1",
(out) => {
if (typeof out !== "string") {
'top -b -n 1',
(out): number => {
if (typeof out !== 'string') {
return 0;
}
const cpuOut = out.split("\n").find((line) => line.includes("Cpu(s)"));
const cpuOut = out.split('\n').find((line) => line.includes('Cpu(s)'));
if (cpuOut === undefined) {
return 0;
}
const freeCpu = parseFloat(cpuOut.split(/\s+/)[1].replace(",", "."));
const freeCpu = parseFloat(cpuOut.split(/\s+/)[1].replace(',', '.'));
return divide([100, freeCpu]);
},
],
@@ -37,22 +38,19 @@ const Stats = () => {
{
poll: [
2000,
"free",
(out) => {
if (typeof out !== "string") {
'free',
(out): GenericResourceMetrics => {
if (typeof out !== 'string') {
return { total: 0, used: 0, percentage: 0 };
}
const ramOut = out.split("\n").find((line) => line.includes("Mem:"));
const ramOut = out.split('\n').find((line) => line.includes('Mem:'));
if (ramOut === undefined) {
return { total: 0, used: 0, percentage: 0 };
}
const [totalRam, usedRam] = ramOut
.split(/\s+/)
.splice(1, 2)
.map(Number);
const [totalRam, usedRam] = ramOut.split(/\s+/).splice(1, 2).map(Number);
return {
percentage: divide([totalRam, usedRam]),
@@ -67,8 +65,8 @@ const Stats = () => {
const gpu = Variable(0);
const GPUStat = Widget.Box({
child: enable_gpu.bind("value").as((gpStat) => {
if (!gpStat || !dependencies("gpustat")) {
child: enable_gpu.bind('value').as((gpStat) => {
if (!gpStat || !dependencies('gpustat')) {
return Widget.Box();
}
@@ -76,19 +74,19 @@ const Stats = () => {
vertical: true,
children: [
Widget.Box({
class_name: "stat gpu",
class_name: 'stat gpu',
hexpand: true,
vpack: "center",
setup: self => {
const getGpuUsage = () => {
vpack: 'center',
setup: (self) => {
const getGpuUsage = (): void => {
if (!enable_gpu.value) {
gpu.value = 0;
return;
}
Utils.execAsync("gpustat --json")
Utils.execAsync('gpustat --json')
.then((out) => {
if (typeof out !== "string") {
if (typeof out !== 'string') {
return 0;
}
try {
@@ -97,80 +95,79 @@ const Stats = () => {
const totalGpu = 100;
const usedGpu =
data.gpus.reduce((acc: number, gpu: GPU_Stat) => {
return acc + gpu["utilization.gpu"]
return acc + gpu['utilization.gpu'];
}, 0) / data.gpus.length;
gpu.value = divide([totalGpu, usedGpu]);
} catch (e) {
console.error("Error getting GPU stats:", e);
console.error('Error getting GPU stats:', e);
gpu.value = 0;
}
})
.catch((err) => {
console.error(`An error occurred while fetching GPU stats: ${err}`)
})
}
console.error(`An error occurred while fetching GPU stats: ${err}`);
});
};
self.poll(2000, getGpuUsage)
self.poll(2000, getGpuUsage);
Utils.merge([gpu.bind("value"), enable_gpu.bind("value")], (gpu, enableGpu) => {
Utils.merge([gpu.bind('value'), enable_gpu.bind('value')], (gpu, enableGpu) => {
if (!enableGpu) {
return self.children = [];
return (self.children = []);
}
return self.children = [
return (self.children = [
Widget.Button({
on_primary_click: terminal.bind("value").as(term => {
return () => {
App.closeWindow("dashboardmenu");
on_primary_click: terminal.bind('value').as((term) => {
return (): void => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
}
};
}),
child: Widget.Label({
class_name: "txt-icon",
label: "󰢮",
})
class_name: 'txt-icon',
label: '󰢮',
}),
}),
Widget.Button({
on_primary_click: terminal.bind("value").as(term => {
return () => {
App.closeWindow("dashboardmenu");
on_primary_click: terminal.bind('value').as((term) => {
return (): void => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
}
};
}),
child: Widget.LevelBar({
class_name: "stats-bar",
class_name: 'stats-bar',
hexpand: true,
vpack: "center",
vpack: 'center',
value: gpu,
}),
}),
]
})
]);
});
},
}),
Widget.Box({
hpack: "end",
children: Utils.merge([gpu.bind("value"), enable_gpu.bind("value")], (gpuUsed, enableGpu) => {
hpack: 'end',
children: Utils.merge([gpu.bind('value'), enable_gpu.bind('value')], (gpuUsed, enableGpu) => {
if (!enableGpu) {
return [];
}
return [
Widget.Label({
class_name: "stat-value gpu",
class_name: 'stat-value gpu',
label: `${Math.floor(gpuUsed * 100)}%`,
})
}),
];
})
})
]
})
})
}),
}),
],
});
}),
});
const storage = Variable(
@@ -178,13 +175,13 @@ const Stats = () => {
{
poll: [
2000,
"df -B1 /",
(out) => {
if (typeof out !== "string") {
'df -B1 /',
(out): GenericResourceMetrics => {
if (typeof out !== 'string') {
return { total: 0, used: 0, percentage: 0 };
}
const dfOut = out.split("\n").find((line) => line.startsWith("/"));
const dfOut = out.split('\n').find((line) => line.startsWith('/'));
if (dfOut === undefined) {
return { total: 0, used: 0, percentage: 0 };
@@ -208,58 +205,58 @@ const Stats = () => {
);
return Widget.Box({
class_name: "dashboard-card stats-container",
class_name: 'dashboard-card stats-container',
vertical: true,
vpack: "fill",
hpack: "fill",
vpack: 'fill',
hpack: 'fill',
expand: true,
children: [
Widget.Box({
vertical: true,
children: [
Widget.Box({
class_name: "stat cpu",
class_name: 'stat cpu',
hexpand: true,
vpack: "center",
vpack: 'center',
children: [
Widget.Button({
on_primary_click: terminal.bind("value").as(term => {
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow("dashboardmenu");
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
}
};
}),
child: Widget.Label({
class_name: "txt-icon",
label: "",
})
class_name: 'txt-icon',
label: '',
}),
}),
Widget.Button({
on_primary_click: terminal.bind("value").as(term => {
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow("dashboardmenu");
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
}
};
}),
child: Widget.LevelBar({
class_name: "stats-bar",
class_name: 'stats-bar',
hexpand: true,
vpack: "center",
bar_mode: "continuous",
vpack: 'center',
bar_mode: 'continuous',
max_value: 1,
value: cpu.bind("value"),
value: cpu.bind('value'),
}),
}),
],
}),
Widget.Label({
hpack: "end",
class_name: "stat-value cpu",
label: cpu.bind("value").as((v) => `${Math.floor(v * 100)}%`),
hpack: 'end',
class_name: 'stat-value cpu',
label: cpu.bind('value').as((v) => `${Math.floor(v * 100)}%`),
}),
],
}),
@@ -267,46 +264,46 @@ const Stats = () => {
vertical: true,
children: [
Widget.Box({
class_name: "stat ram",
vpack: "center",
class_name: 'stat ram',
vpack: 'center',
hexpand: true,
children: [
Widget.Button({
on_primary_click: terminal.bind("value").as(term => {
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow("dashboardmenu");
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
}
};
}),
child: Widget.Label({
class_name: "txt-icon",
label: "",
})
class_name: 'txt-icon',
label: '',
}),
}),
Widget.Button({
on_primary_click: terminal.bind("value").as(term => {
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow("dashboardmenu");
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
}
};
}),
child: Widget.LevelBar({
class_name: "stats-bar",
class_name: 'stats-bar',
hexpand: true,
vpack: "center",
value: ram.bind("value").as((v) => v.percentage),
vpack: 'center',
value: ram.bind('value').as((v) => v.percentage),
}),
}),
],
}),
Widget.Label({
hpack: "end",
class_name: "stat-value ram",
label: ram.bind("value").as((v) => `${v.used}/${v.total} GB`),
hpack: 'end',
class_name: 'stat-value ram',
label: ram.bind('value').as((v) => `${v.used}/${v.total} GB`),
}),
],
}),
@@ -315,46 +312,46 @@ const Stats = () => {
vertical: true,
children: [
Widget.Box({
class_name: "stat storage",
class_name: 'stat storage',
hexpand: true,
vpack: "center",
vpack: 'center',
children: [
Widget.Button({
on_primary_click: terminal.bind("value").as(term => {
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow("dashboardmenu");
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
}
};
}),
child: Widget.Label({
class_name: "txt-icon",
label: "󰋊",
})
class_name: 'txt-icon',
label: '󰋊',
}),
}),
Widget.Button({
on_primary_click: terminal.bind("value").as(term => {
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow("dashboardmenu");
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
}
};
}),
child: Widget.LevelBar({
class_name: "stats-bar",
class_name: 'stats-bar',
hexpand: true,
vpack: "center",
value: storage.bind("value").as((v) => v.percentage),
vpack: 'center',
value: storage.bind('value').as((v) => v.percentage),
}),
})
}),
],
}),
Widget.Label({
hpack: "end",
class_name: "stat-value storage",
label: storage.bind("value").as((v) => `${v.used}/${v.total} GB`),
hpack: 'end',
class_name: 'stat-value storage',
label: storage.bind('value').as((v) => `${v.used}/${v.total} GB`),
}),
],
}),

View File

@@ -1,40 +1,41 @@
import brightness from "../../../../services/Brightness.js";
import icons from "../../../icons/index.js";
import { BoxWidget } from 'lib/types/widget.js';
import brightness from '../../../../services/Brightness.js';
import icons from '../../../icons/index.js';
const Brightness = () => {
const Brightness = (): BoxWidget => {
return Widget.Box({
class_name: "menu-section-container brightness",
class_name: 'menu-section-container brightness',
vertical: true,
children: [
Widget.Box({
class_name: "menu-label-container",
hpack: "fill",
class_name: 'menu-label-container',
hpack: 'fill',
child: Widget.Label({
class_name: "menu-label",
class_name: 'menu-label',
hexpand: true,
hpack: "start",
label: "Brightness",
hpack: 'start',
label: 'Brightness',
}),
}),
Widget.Box({
class_name: "menu-items-section",
vpack: "fill",
class_name: 'menu-items-section',
vpack: 'fill',
vexpand: true,
vertical: true,
child: Widget.Box({
class_name: "brightness-container",
class_name: 'brightness-container',
children: [
Widget.Icon({
vexpand: true,
vpack: "center",
class_name: "brightness-slider-icon",
vpack: 'center',
class_name: 'brightness-slider-icon',
icon: icons.brightness.screen,
}),
Widget.Slider({
vpack: "center",
vpack: 'center',
vexpand: true,
value: brightness.bind("screen"),
class_name: "menu-active-slider menu-slider brightness",
value: brightness.bind('screen'),
class_name: 'menu-active-slider menu-slider brightness',
draw_value: false,
hexpand: true,
min: 0,
@@ -42,12 +43,10 @@ const Brightness = () => {
onChange: ({ value }) => (brightness.screen = value),
}),
Widget.Label({
vpack: "center",
vpack: 'center',
vexpand: true,
class_name: "brightness-slider-label",
label: brightness
.bind("screen")
.as((b) => `${Math.round(b * 100)}%`),
class_name: 'brightness-slider-label',
label: brightness.bind('screen').as((b) => `${Math.round(b * 100)}%`),
}),
],
}),

View File

@@ -1,24 +1,23 @@
import DropdownMenu from "../DropdownMenu.js";
import { EnergyProfiles } from "./profiles/index.js";
import { Brightness } from "./brightness/index.js";
import DropdownMenu from '../DropdownMenu.js';
import { EnergyProfiles } from './profiles/index.js';
import { Brightness } from './brightness/index.js';
import { Attribute, Child } from 'lib/types/widget.js';
import Window from 'types/widgets/window.js';
export default () => {
export default (): Window<Child, Attribute> => {
return DropdownMenu({
name: "energymenu",
transition: "crossfade",
name: 'energymenu',
transition: 'crossfade',
child: Widget.Box({
class_name: "menu-items energy",
hpack: "fill",
class_name: 'menu-items energy',
hpack: 'fill',
hexpand: true,
child: Widget.Box({
vertical: true,
hpack: "fill",
hpack: 'fill',
hexpand: true,
class_name: "menu-items-container energy",
children: [
Brightness(),
EnergyProfiles(),
],
class_name: 'menu-items-container energy',
children: [Brightness(), EnergyProfiles()],
}),
}),
});

View File

@@ -1,36 +1,37 @@
const powerProfiles = await Service.import("powerprofiles");
import { PowerProfile, PowerProfileObject, PowerProfiles } from "lib/types/powerprofiles.js";
import icons from "../../../icons/index.js";
const powerProfiles = await Service.import('powerprofiles');
import { PowerProfile, PowerProfileObject, PowerProfiles } from 'lib/types/powerprofiles.js';
import icons from '../../../icons/index.js';
import { BoxWidget } from 'lib/types/widget.js';
const EnergyProfiles = () => {
const EnergyProfiles = (): BoxWidget => {
const isValidProfile = (profile: string): profile is PowerProfile =>
profile === "power-saver" || profile === "balanced" || profile === "performance";
profile === 'power-saver' || profile === 'balanced' || profile === 'performance';
return Widget.Box({
class_name: "menu-section-container energy",
class_name: 'menu-section-container energy',
vertical: true,
children: [
Widget.Box({
class_name: "menu-label-container",
hpack: "fill",
class_name: 'menu-label-container',
hpack: 'fill',
child: Widget.Label({
class_name: "menu-label",
class_name: 'menu-label',
hexpand: true,
hpack: "start",
label: "Power Profile",
hpack: 'start',
label: 'Power Profile',
}),
}),
Widget.Box({
class_name: "menu-items-section",
vpack: "fill",
class_name: 'menu-items-section',
vpack: 'fill',
vexpand: true,
vertical: true,
children: powerProfiles.bind("profiles").as((profiles: PowerProfiles) => {
children: powerProfiles.bind('profiles').as((profiles: PowerProfiles) => {
return profiles.map((prof: PowerProfileObject) => {
const profileLabels = {
"power-saver": "Power Saver",
balanced: "Balanced",
performance: "Performance",
'power-saver': 'Power Saver',
balanced: 'Balanced',
performance: 'Performance',
};
const profileType = prof.Profile;
@@ -43,17 +44,17 @@ const EnergyProfiles = () => {
on_primary_click: () => {
powerProfiles.active_profile = prof.Profile;
},
class_name: powerProfiles.bind("active_profile").as((active) => {
return `power-profile-item ${active === prof.Profile ? "active" : ""}`;
class_name: powerProfiles.bind('active_profile').as((active) => {
return `power-profile-item ${active === prof.Profile ? 'active' : ''}`;
}),
child: Widget.Box({
children: [
Widget.Icon({
class_name: "power-profile-icon",
class_name: 'power-profile-icon',
icon: icons.powerprofile[profileType],
}),
Widget.Label({
class_name: "power-profile-label",
class_name: 'power-profile-label',
label: profileLabels[profileType],
}),
],

View File

@@ -1,14 +1,14 @@
import PowerMenu from "./power/index.js";
import Verification from "./power/verification.js";
import AudioMenu from "./audio/index.js";
import NetworkMenu from "./network/index.js";
import BluetoothMenu from "./bluetooth/index.js";
import MediaMenu from "./media/index.js";
import NotificationsMenu from "./notifications/index.js";
import CalendarMenu from "./calendar/index.js";
import EnergyMenu from "./energy/index.js";
import DashboardMenu from "./dashboard/index.js";
import PowerDropdown from "./powerDropdown/index.js";
import PowerMenu from './power/index.js';
import Verification from './power/verification.js';
import AudioMenu from './audio/index.js';
import NetworkMenu from './network/index.js';
import BluetoothMenu from './bluetooth/index.js';
import MediaMenu from './media/index.js';
import NotificationsMenu from './notifications/index.js';
import CalendarMenu from './calendar/index.js';
import EnergyMenu from './energy/index.js';
import DashboardMenu from './dashboard/index.js';
import PowerDropdown from './powerDropdown/index.js';
export default [
PowerMenu(),

View File

@@ -1,16 +1,19 @@
const media = await Service.import("mpris");
import { BoxWidget } from 'lib/types/widget';
import { Mpris, MprisPlayer } from 'types/service/mpris';
const Bar = (getPlayerInfo: Function) => {
const media = await Service.import('mpris');
const Bar = (getPlayerInfo: (media: Mpris) => MprisPlayer): BoxWidget => {
return Widget.Box({
class_name: "media-indicator-current-progress-bar",
class_name: 'media-indicator-current-progress-bar',
hexpand: true,
children: [
Widget.Box({
hexpand: true,
child: Widget.Slider({
hexpand: true,
tooltip_text: "--",
class_name: "menu-slider media progress",
tooltip_text: '--',
class_name: 'menu-slider media progress',
draw_value: false,
on_change: ({ value }) => {
const foundPlayer = getPlayerInfo(media);
@@ -20,7 +23,7 @@ const Bar = (getPlayerInfo: Function) => {
return (foundPlayer.position = value * foundPlayer.length);
},
setup: (self) => {
const update = () => {
const update = (): void => {
const foundPlayer = getPlayerInfo(media);
if (foundPlayer !== undefined) {
const value = foundPlayer.length ? foundPlayer.position / foundPlayer.length : 0;
@@ -32,28 +35,25 @@ const Bar = (getPlayerInfo: Function) => {
self.hook(media, update);
self.poll(1000, update);
function updateTooltip() {
const updateTooltip = (): void => {
const foundPlayer = getPlayerInfo(media);
if (foundPlayer === undefined) {
return self.tooltip_text = '00:00'
self.tooltip_text = '00:00';
return;
}
const curHour = Math.floor(foundPlayer.position / 3600);
const curMin = Math.floor((foundPlayer.position % 3600) / 60);
const curSec = Math.floor(foundPlayer.position % 60);
if (
typeof foundPlayer.position === "number" &&
foundPlayer.position >= 0
) {
if (typeof foundPlayer.position === 'number' && foundPlayer.position >= 0) {
// WARN: These nested ternaries are absolutely disgusting lol
self.tooltip_text = `${curHour > 0
? (curHour < 10 ? "0" + curHour : curHour) + ":"
: ""
}${curMin < 10 ? "0" + curMin : curMin}:${curSec < 10 ? "0" + curSec : curSec}`;
self.tooltip_text = `${
curHour > 0 ? (curHour < 10 ? '0' + curHour : curHour) + ':' : ''
}${curMin < 10 ? '0' + curMin : curMin}:${curSec < 10 ? '0' + curSec : curSec}`;
} else {
self.tooltip_text = `00:00`;
}
}
};
self.poll(1000, updateTooltip);
self.hook(media, updateTooltip);
},

View File

@@ -1,82 +1,81 @@
import { MprisPlayer } from "types/service/mpris.js";
import icons from "../../../icons/index.js";
import { LoopStatus, PlaybackStatus } from "lib/types/mpris.js";
const media = await Service.import("mpris");
import { MprisPlayer } from 'types/service/mpris.js';
import icons from '../../../icons/index.js';
import { LoopStatus, PlaybackStatus } from 'lib/types/mpris.js';
import { BoxWidget } from 'lib/types/widget.js';
const media = await Service.import('mpris');
const Controls = (getPlayerInfo: Function) => {
const isValidLoopStatus = (status: string): status is LoopStatus =>
["none", "track", "playlist"].includes(status);
const Controls = (getPlayerInfo: () => MprisPlayer): BoxWidget => {
const isValidLoopStatus = (status: string): status is LoopStatus => ['none', 'track', 'playlist'].includes(status);
const isValidPlaybackStatus = (status: string): status is PlaybackStatus =>
["playing", "paused", "stopped"].includes(status);
['playing', 'paused', 'stopped'].includes(status);
const isLoopActive = (player: MprisPlayer) => {
return player["loop_status"] !== null &&
["track", "playlist"].includes(player["loop_status"].toLowerCase())
? "active"
: "";
const isLoopActive = (player: MprisPlayer): string => {
return player['loop_status'] !== null && ['track', 'playlist'].includes(player['loop_status'].toLowerCase())
? 'active'
: '';
};
const isShuffleActive = (player: MprisPlayer) => {
return player["shuffle_status"] !== null && player["shuffle_status"]
? "active"
: "";
const isShuffleActive = (player: MprisPlayer): string => {
return player['shuffle_status'] !== null && player['shuffle_status'] ? 'active' : '';
};
return Widget.Box({
class_name: "media-indicator-current-player-controls",
class_name: 'media-indicator-current-player-controls',
vertical: true,
children: [
Widget.Box({
class_name: "media-indicator-current-controls",
hpack: "center",
class_name: 'media-indicator-current-controls',
hpack: 'center',
children: [
Widget.Box({
class_name: "media-indicator-control shuffle",
class_name: 'media-indicator-control shuffle',
children: [
Widget.Button({
hpack: "center",
hpack: 'center',
hasTooltip: true,
setup: (self) => {
self.hook(media, () => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
self.tooltip_text = "Unavailable";
self.class_name =
"media-indicator-control-button shuffle disabled";
self.tooltip_text = 'Unavailable';
self.class_name = 'media-indicator-control-button shuffle disabled';
return;
}
self.tooltip_text =
foundPlayer.shuffle_status !== null
? foundPlayer.shuffle_status
? "Shuffling"
: "Not Shuffling"
? 'Shuffling'
: 'Not Shuffling'
: null;
self.on_primary_click = () => foundPlayer.shuffle();
self.class_name = `media-indicator-control-button shuffle ${isShuffleActive(foundPlayer)} ${foundPlayer.shuffle_status !== null ? "enabled" : "disabled"}`;
self.on_primary_click = (): void => {
foundPlayer.shuffle();
};
self.class_name = `media-indicator-control-button shuffle ${isShuffleActive(foundPlayer)} ${foundPlayer.shuffle_status !== null ? 'enabled' : 'disabled'}`;
});
},
child: Widget.Icon(icons.mpris.shuffle["enabled"]),
child: Widget.Icon(icons.mpris.shuffle['enabled']),
}),
],
}),
Widget.Box({
children: [
Widget.Button({
hpack: "center",
hpack: 'center',
child: Widget.Icon(icons.mpris.prev),
setup: (self) => {
self.hook(media, () => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
self.class_name =
"media-indicator-control-button prev disabled";
self.class_name = 'media-indicator-control-button prev disabled';
return;
}
self.on_primary_click = () => foundPlayer.previous();
self.class_name = `media-indicator-control-button prev ${foundPlayer.can_go_prev !== null && foundPlayer.can_go_prev ? "enabled" : "disabled"}`;
self.on_primary_click = (): void => {
foundPlayer.previous();
};
self.class_name = `media-indicator-control-button prev ${foundPlayer.can_go_prev !== null && foundPlayer.can_go_prev ? 'enabled' : 'disabled'}`;
});
},
}),
@@ -85,40 +84,35 @@ const Controls = (getPlayerInfo: Function) => {
Widget.Box({
children: [
Widget.Button({
hpack: "center",
hpack: 'center',
setup: (self) => {
self.hook(media, () => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
self.class_name =
"media-indicator-control-button play disabled";
self.class_name = 'media-indicator-control-button play disabled';
return;
}
self.on_primary_click = () => foundPlayer.playPause();
self.class_name = `media-indicator-control-button play ${foundPlayer.can_play !== null ? "enabled" : "disabled"}`;
self.on_primary_click = (): void => {
foundPlayer.playPause();
};
self.class_name = `media-indicator-control-button play ${foundPlayer.can_play !== null ? 'enabled' : 'disabled'}`;
});
},
child: Widget.Icon({
icon: Utils.watch(
icons.mpris.paused,
media,
"changed",
() => {
const foundPlayer: MprisPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
return icons.mpris["paused"];
}
const playbackStatus = foundPlayer.play_back_status?.toLowerCase();
icon: Utils.watch(icons.mpris.paused, media, 'changed', () => {
const foundPlayer: MprisPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
return icons.mpris['paused'];
}
const playbackStatus = foundPlayer.play_back_status?.toLowerCase();
if (playbackStatus && isValidPlaybackStatus(playbackStatus)) {
return icons.mpris[playbackStatus];
}
else {
return icons.mpris["paused"];
}
},
),
if (playbackStatus && isValidPlaybackStatus(playbackStatus)) {
return icons.mpris[playbackStatus];
} else {
return icons.mpris['paused'];
}
}),
}),
}),
],
@@ -127,47 +121,49 @@ const Controls = (getPlayerInfo: Function) => {
class_name: `media-indicator-control next`,
children: [
Widget.Button({
hpack: "center",
hpack: 'center',
child: Widget.Icon(icons.mpris.next),
setup: (self) => {
self.hook(media, () => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
self.class_name =
"media-indicator-control-button next disabled";
self.class_name = 'media-indicator-control-button next disabled';
return;
}
self.on_primary_click = () => foundPlayer.next();
self.class_name = `media-indicator-control-button next ${foundPlayer.can_go_next !== null && foundPlayer.can_go_next ? "enabled" : "disabled"}`;
self.on_primary_click = (): void => {
foundPlayer.next();
};
self.class_name = `media-indicator-control-button next ${foundPlayer.can_go_next !== null && foundPlayer.can_go_next ? 'enabled' : 'disabled'}`;
});
},
}),
],
}),
Widget.Box({
class_name: "media-indicator-control loop",
class_name: 'media-indicator-control loop',
children: [
Widget.Button({
hpack: "center",
hpack: 'center',
setup: (self) => {
self.hook(media, () => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
self.tooltip_text = "Unavailable";
self.class_name =
"media-indicator-control-button shuffle disabled";
self.tooltip_text = 'Unavailable';
self.class_name = 'media-indicator-control-button shuffle disabled';
return;
}
self.tooltip_text =
foundPlayer.loop_status !== null
? foundPlayer.loop_status
? "Shuffling"
: "Not Shuffling"
? 'Shuffling'
: 'Not Shuffling'
: null;
self.on_primary_click = () => foundPlayer.loop();
self.class_name = `media-indicator-control-button loop ${isLoopActive(foundPlayer)} ${foundPlayer.loop_status !== null ? "enabled" : "disabled"}`;
self.on_primary_click = (): void => {
foundPlayer.loop();
};
self.class_name = `media-indicator-control-button loop ${isLoopActive(foundPlayer)} ${foundPlayer.loop_status !== null ? 'enabled' : 'disabled'}`;
});
},
child: Widget.Icon({
@@ -176,7 +172,7 @@ const Controls = (getPlayerInfo: Function) => {
const foundPlayer: MprisPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
self.icon = icons.mpris.loop["none"];
self.icon = icons.mpris.loop['none'];
return;
}
@@ -184,9 +180,8 @@ const Controls = (getPlayerInfo: Function) => {
if (loopStatus && isValidLoopStatus(loopStatus)) {
self.icon = icons.mpris.loop[loopStatus];
}
else {
self.icon = icons.mpris.loop["none"];
} else {
self.icon = icons.mpris.loop['none'];
}
});
},

View File

@@ -1,78 +1,82 @@
const media = await Service.import("mpris");
import { BoxWidget } from 'lib/types/widget';
import { MprisPlayer } from 'types/service/mpris';
const MediaInfo = (getPlayerInfo: Function) => {
const media = await Service.import('mpris');
const MediaInfo = (getPlayerInfo: () => MprisPlayer): BoxWidget => {
return Widget.Box({
class_name: "media-indicator-current-media-info",
hpack: "center",
class_name: 'media-indicator-current-media-info',
hpack: 'center',
hexpand: true,
vertical: true,
children: [
Widget.Box({
class_name: "media-indicator-current-song-name",
hpack: "center",
class_name: 'media-indicator-current-song-name',
hpack: 'center',
children: [
Widget.Label({
truncate: "end",
truncate: 'end',
max_width_chars: 31,
wrap: true,
class_name: "media-indicator-current-song-name-label",
class_name: 'media-indicator-current-song-name-label',
setup: (self) => {
self.hook(media, () => {
const curPlayer = getPlayerInfo();
return (self.label =
curPlayer !== undefined && curPlayer["track-title"].length
? curPlayer["track-title"]
: "No Media Currently Playing");
curPlayer !== undefined && curPlayer['track_title'].length
? curPlayer['track_title']
: 'No Media Currently Playing');
});
},
}),
],
}),
Widget.Box({
class_name: "media-indicator-current-song-author",
hpack: "center",
class_name: 'media-indicator-current-song-author',
hpack: 'center',
children: [
Widget.Label({
truncate: "end",
truncate: 'end',
wrap: true,
max_width_chars: 35,
class_name: "media-indicator-current-song-author-label",
class_name: 'media-indicator-current-song-author-label',
setup: (self) => {
self.hook(media, () => {
const curPlayer = getPlayerInfo();
const makeArtistList = (trackArtists: string[]) => {
const makeArtistList = (trackArtists: string[]): string => {
if (trackArtists.length === 1 && !trackArtists[0].length) {
return "-----";
return '-----';
}
return trackArtists.join(", ");
return trackArtists.join(', ');
};
return (self.label =
curPlayer !== undefined && curPlayer["track-artists"].length
? makeArtistList(curPlayer["track-artists"])
: "-----");
curPlayer !== undefined && curPlayer['track_artists'].length
? makeArtistList(curPlayer['track_artists'])
: '-----');
});
},
}),
],
}),
Widget.Box({
class_name: "media-indicator-current-song-album",
hpack: "center",
class_name: 'media-indicator-current-song-album',
hpack: 'center',
children: [
Widget.Label({
truncate: "end",
truncate: 'end',
wrap: true,
max_width_chars: 40,
class_name: "media-indicator-current-song-album-label",
class_name: 'media-indicator-current-song-album-label',
setup: (self) => {
self.hook(media, () => {
const curPlayer = getPlayerInfo();
return (self.label =
curPlayer !== undefined && curPlayer["track-album"].length
? curPlayer["track-album"]
: "---");
curPlayer !== undefined && curPlayer['track_album'].length
? curPlayer['track_album']
: '---');
});
},
}),

View File

@@ -1,20 +1,22 @@
import DropdownMenu from "../DropdownMenu.js";
import { Media } from "./media.js";
import Window from 'types/widgets/window.js';
import DropdownMenu from '../DropdownMenu.js';
import { Media } from './media.js';
import { Attribute, Child } from 'lib/types/widget.js';
export default () => {
return DropdownMenu({
name: "mediamenu",
transition: "crossfade",
child: Widget.Box({
class_name: "menu-items media",
hpack: "fill",
hexpand: true,
child: Widget.Box({
class_name: "menu-items-container media",
hpack: "fill",
hexpand: true,
child: Media(),
}),
}),
});
export default (): Window<Child, Attribute> => {
return DropdownMenu({
name: 'mediamenu',
transition: 'crossfade',
child: Widget.Box({
class_name: 'menu-items media',
hpack: 'fill',
hexpand: true,
child: Widget.Box({
class_name: 'menu-items-container media',
hpack: 'fill',
hexpand: true,
child: Media(),
}),
}),
});
};

View File

@@ -1,16 +1,16 @@
const media = await Service.import("mpris");
import { MediaInfo } from "./components/mediainfo.js";
import { Controls } from "./components/controls.js";
import { Bar } from "./components/bar.js";
import { MprisPlayer } from "types/service/mpris.js";
import options from "options.js";
const media = await Service.import('mpris');
import { MediaInfo } from './components/mediainfo.js';
import { Controls } from './components/controls.js';
import { Bar } from './components/bar.js';
import { MprisPlayer } from 'types/service/mpris.js';
import options from 'options.js';
import { BoxWidget } from 'lib/types/widget.js';
const { tint, color } = options.theme.bar.menus.menu.media.card;
const generateAlbumArt = (imageUrl: string): string => {
const userTint = tint.value;
const userHexColor = color.value;
let css: string;
const r = parseInt(userHexColor.slice(1, 3), 16);
const g = parseInt(userHexColor.slice(3, 5), 16);
@@ -18,36 +18,30 @@ const generateAlbumArt = (imageUrl: string): string => {
const alpha = userTint / 100;
css = `background-image: linear-gradient(
const css = `background-image: linear-gradient(
rgba(${r}, ${g}, ${b}, ${alpha}),
rgba(${r}, ${g}, ${b}, ${alpha}),
${userHexColor} 65em
), url("${imageUrl}");`;
return css;
}
const Media = () => {
const curPlayer = Variable("");
};
const Media = (): BoxWidget => {
const curPlayer = Variable('');
media.connect("changed", () => {
media.connect('changed', () => {
const statusOrder = {
Playing: 1,
Paused: 2,
Stopped: 3,
};
const isPlaying = media.players.find(
(p) => p["play_back_status"] === "Playing",
);
const isPlaying = media.players.find((p) => p['play_back_status'] === 'Playing');
const playerStillExists = media.players.some(
(p) => curPlayer.value === p["bus_name"],
);
const playerStillExists = media.players.some((p) => curPlayer.value === p['bus_name']);
const nextPlayerUp = media.players.sort(
(a, b) =>
statusOrder[a["play_back_status"]] -
statusOrder[b["play_back_status"]],
(a, b) => statusOrder[a['play_back_status']] - statusOrder[b['play_back_status']],
)[0].bus_name;
if (isPlaying || !playerStillExists) {
@@ -60,26 +54,22 @@ const Media = () => {
};
return Widget.Box({
class_name: "menu-section-container",
class_name: 'menu-section-container',
children: [
Widget.Box({
class_name: "menu-items-section",
class_name: 'menu-items-section',
vertical: false,
child: Widget.Box({
class_name: "menu-content",
class_name: 'menu-content',
children: [
Widget.Box({
class_name: "media-content",
class_name: 'media-content',
child: Widget.Box({
class_name: "media-indicator-right-section",
hpack: "fill",
class_name: 'media-indicator-right-section',
hpack: 'fill',
hexpand: true,
vertical: true,
children: [
MediaInfo(getPlayerInfo),
Controls(getPlayerInfo),
Bar(getPlayerInfo),
],
children: [MediaInfo(getPlayerInfo), Controls(getPlayerInfo), Bar(getPlayerInfo)],
}),
}),
],
@@ -91,7 +81,7 @@ const Media = () => {
}
});
Utils.merge([color.bind("value"), tint.bind("value")], () => {
Utils.merge([color.bind('value'), tint.bind('value')], () => {
const curPlayer = getPlayerInfo();
if (curPlayer !== undefined) {
self.css = generateAlbumArt(curPlayer.track_cover_url);

View File

@@ -1,52 +1,54 @@
const network = await Service.import("network");
import { BoxWidget } from 'lib/types/widget';
const Ethernet = () => {
const network = await Service.import('network');
const Ethernet = (): BoxWidget => {
return Widget.Box({
class_name: "menu-section-container ethernet",
class_name: 'menu-section-container ethernet',
vertical: true,
children: [
Widget.Box({
class_name: "menu-label-container",
hpack: "fill",
class_name: 'menu-label-container',
hpack: 'fill',
child: Widget.Label({
class_name: "menu-label",
class_name: 'menu-label',
hexpand: true,
hpack: "start",
label: "Ethernet",
hpack: 'start',
label: 'Ethernet',
}),
}),
Widget.Box({
class_name: "menu-items-section",
class_name: 'menu-items-section',
vertical: true,
child: Widget.Box({
class_name: "menu-content",
class_name: 'menu-content',
vertical: true,
setup: (self) => {
self.hook(network, () => {
return (self.child = Widget.Box({
class_name: "network-element-item",
class_name: 'network-element-item',
child: Widget.Box({
hpack: "start",
hpack: 'start',
children: [
Widget.Icon({
class_name: `network-icon ethernet ${network.wired.state === "activated" ? "active" : ""}`,
class_name: `network-icon ethernet ${network.wired.state === 'activated' ? 'active' : ''}`,
tooltip_text: network.wired.internet,
icon: `${network.wired["icon_name"]}`,
icon: `${network.wired['icon_name']}`,
}),
Widget.Box({
class_name: "connection-container",
class_name: 'connection-container',
vertical: true,
children: [
Widget.Label({
class_name: "active-connection",
hpack: "start",
truncate: "end",
class_name: 'active-connection',
hpack: 'start',
truncate: 'end',
wrap: true,
label: `Ethernet Connection ${network.wired.state !== "unknown" && typeof network.wired?.speed === "number" ? `(${network.wired?.speed / 1000} Gbps)` : ""}`,
label: `Ethernet Connection ${network.wired.state !== 'unknown' && typeof network.wired?.speed === 'number' ? `(${network.wired?.speed / 1000} Gbps)` : ''}`,
}),
Widget.Label({
hpack: "start",
class_name: "connection-status dim",
hpack: 'start',
class_name: 'connection-status dim',
label:
network.wired.internet.charAt(0).toUpperCase() +
network.wired.internet.slice(1),

View File

@@ -1,19 +1,21 @@
import DropdownMenu from "../DropdownMenu.js";
import { Ethernet } from "./ethernet/index.js";
import { Wifi } from "./wifi/index.js";
import Window from 'types/widgets/window.js';
import DropdownMenu from '../DropdownMenu.js';
import { Ethernet } from './ethernet/index.js';
import { Wifi } from './wifi/index.js';
import { Attribute, Child } from 'lib/types/widget.js';
export default () => {
return DropdownMenu({
name: "networkmenu",
transition: "crossfade",
child: Widget.Box({
class_name: "menu-items network",
child: Widget.Box({
vertical: true,
hexpand: true,
class_name: "menu-items-container network",
children: [Ethernet(), Wifi()],
}),
}),
});
export default (): Window<Child, Attribute> => {
return DropdownMenu({
name: 'networkmenu',
transition: 'crossfade',
child: Widget.Box({
class_name: 'menu-items network',
child: Widget.Box({
vertical: true,
hexpand: true,
class_name: 'menu-items-container network',
children: [Ethernet(), Wifi()],
}),
}),
});
};

View File

@@ -1,23 +1,23 @@
const getWifiIcon = (iconName: string) => {
const deviceIconMap = [
["network-wireless-acquiring", "󰤩"],
["network-wireless-connected", "󰤨"],
["network-wireless-encrypted", "󰤪"],
["network-wireless-hotspot", "󰤨"],
["network-wireless-no-route", "󰤩"],
["network-wireless-offline", "󰤮"],
["network-wireless-signal-excellent", "󰤨"],
["network-wireless-signal-good", "󰤥"],
["network-wireless-signal-ok", "󰤢"],
["network-wireless-signal-weak", "󰤟"],
["network-wireless-signal-none", "󰤯"],
import { WifiIcon } from 'lib/types/network';
const getWifiIcon = (iconName: string): WifiIcon => {
const deviceIconMap: [string, WifiIcon][] = [
['network-wireless-acquiring', '󰤩'],
['network-wireless-connected', '󰤨'],
['network-wireless-encrypted', '󰤪'],
['network-wireless-hotspot', '󰤨'],
['network-wireless-no-route', '󰤩'],
['network-wireless-offline', '󰤮'],
['network-wireless-signal-excellent', '󰤨'],
['network-wireless-signal-good', '󰤥'],
['network-wireless-signal-ok', '󰤢'],
['network-wireless-signal-weak', '󰤟'],
['network-wireless-signal-none', '󰤯'],
];
const foundMatch = deviceIconMap.find((icon) =>
RegExp(icon[0]).test(iconName.toLowerCase()),
);
const foundMatch = deviceIconMap.find((icon) => RegExp(icon[0]).test(iconName.toLowerCase()));
return foundMatch ? foundMatch[1] : "󰤨";
return foundMatch ? foundMatch[1] : '󰤨';
};
export { getWifiIcon };

View File

@@ -1,18 +1,26 @@
import { Network } from "types/service/network";
import { Variable } from "types/variable";
import { AccessPoint } from "lib/types/network";
const renderWapStaging = (self: any, network: Network, staging: Variable<AccessPoint>, connecting: Variable<string>) => {
Utils.merge([network.bind("wifi"), staging.bind("value")], () => {
import { Network } from 'types/service/network';
import { Variable } from 'types/variable';
import { AccessPoint } from 'lib/types/network';
import Box from 'types/widgets/box';
import { Attribute, Child } from 'lib/types/widget';
const renderWapStaging = (
self: Box<Child, Attribute>,
network: Network,
staging: Variable<AccessPoint>,
connecting: Variable<string>,
): void => {
Utils.merge([network.bind('wifi'), staging.bind('value')], () => {
if (!Object.keys(staging.value).length) {
return (self.child = Widget.Box());
}
return (self.child = Widget.Box({
class_name: "network-element-item staging",
class_name: 'network-element-item staging',
vertical: true,
children: [
Widget.Box({
hpack: "fill",
hpack: 'fill',
hexpand: true,
children: [
Widget.Icon({
@@ -20,74 +28,70 @@ const renderWapStaging = (self: any, network: Network, staging: Variable<AccessP
icon: `${staging.value.iconName}`,
}),
Widget.Box({
class_name: "connection-container",
class_name: 'connection-container',
hexpand: true,
vertical: true,
children: [
Widget.Label({
class_name: "active-connection",
hpack: "start",
truncate: "end",
class_name: 'active-connection',
hpack: 'start',
truncate: 'end',
wrap: true,
label: staging.value.ssid,
}),
],
}),
Widget.Revealer({
hpack: "end",
reveal_child: connecting
.bind("value")
.as((c) => staging.value.bssid === c),
hpack: 'end',
reveal_child: connecting.bind('value').as((c) => staging.value.bssid === c),
child: Widget.Spinner({
class_name: "spinner wap",
class_name: 'spinner wap',
}),
}),
],
}),
Widget.Box({
class_name: "network-password-input-container",
hpack: "fill",
class_name: 'network-password-input-container',
hpack: 'fill',
hexpand: true,
children: [
Widget.Entry({
hpack: "start",
hpack: 'start',
hexpand: true,
visibility: false,
class_name: "network-password-input",
placeholder_text: "enter password",
class_name: 'network-password-input',
placeholder_text: 'enter password',
onAccept: (selfInp) => {
connecting.value = staging.value.bssid || "";
connecting.value = staging.value.bssid || '';
Utils.execAsync(
`nmcli dev wifi connect ${staging.value.bssid} password ${selfInp.text}`,
)
.catch((err) => {
connecting.value = "";
console.error(
`Failed to connect to wifi: ${staging.value.ssid}... ${err}`,
);
connecting.value = '';
console.error(`Failed to connect to wifi: ${staging.value.ssid}... ${err}`);
Utils.notify({
summary: "Network",
summary: 'Network',
body: err,
timeout: 5000,
});
})
.then(() => {
connecting.value = "";
connecting.value = '';
staging.value = {} as AccessPoint;
});
selfInp.text = "";
selfInp.text = '';
},
}),
Widget.Button({
hpack: "end",
class_name: "close-network-password-input-button",
hpack: 'end',
class_name: 'close-network-password-input-button',
on_primary_click: () => {
connecting.value = "";
connecting.value = '';
staging.value = {} as AccessPoint;
},
child: Widget.Icon({
class_name: "close-network-password-input-icon",
icon: "window-close-symbolic",
class_name: 'close-network-password-input-icon',
icon: 'window-close-symbolic',
}),
}),
],

View File

@@ -1,36 +1,41 @@
import { Network } from "types/service/network.js";
import { AccessPoint, WifiStatus } from "lib/types/network.js";
import { Variable } from "types/variable.js";
import { getWifiIcon } from "../utils.js";
import { WIFI_STATUS_MAP } from "globals/network.js";
const renderWAPs = (self: any, network: Network, staging: Variable<AccessPoint>, connecting: Variable<string>) => {
const getIdBySsid = (ssid: string, nmcliOutput: string) => {
const lines = nmcliOutput.trim().split("\n");
import { Network } from 'types/service/network.js';
import { AccessPoint, WifiStatus } from 'lib/types/network.js';
import { Variable } from 'types/variable.js';
import { getWifiIcon } from '../utils.js';
import { WIFI_STATUS_MAP } from 'globals/network.js';
import { Attribute, Child } from 'lib/types/widget.js';
import Box from 'types/widgets/box.js';
const renderWAPs = (
self: Box<Child, Attribute>,
network: Network,
staging: Variable<AccessPoint>,
connecting: Variable<string>,
): void => {
const getIdBySsid = (ssid: string, nmcliOutput: string): string | undefined => {
const lines = nmcliOutput.trim().split('\n');
for (const line of lines) {
const columns = line.trim().split(/\s{2,}/);
if (columns[0].includes(ssid)) {
return columns[1];
}
}
return null;
};
const isValidWifiStatus = (status: string): status is WifiStatus => {
return status in WIFI_STATUS_MAP;
};
const getWifiStatus = () => {
const getWifiStatus = (): string => {
const wifiState = network.wifi.state?.toLowerCase();
if (wifiState && isValidWifiStatus(wifiState)) {
return WIFI_STATUS_MAP[wifiState];
}
return WIFI_STATUS_MAP["unknown"];
}
return WIFI_STATUS_MAP['unknown'];
};
self.hook(network, () => {
Utils.merge([staging.bind("value"), connecting.bind("value")], () => {
Utils.merge([staging.bind('value'), connecting.bind('value')], () => {
// NOTE: Sometimes the network service will yield a "this._device is undefined" when
// trying to access the "access_points" property. So we must validate that
// it's not 'undefined'
@@ -38,12 +43,10 @@ const renderWAPs = (self: any, network: Network, staging: Variable<AccessPoint>,
// Also this is an AGS bug that needs to be fixed
// TODO: Remove @ts-ignore once AGS bug is fixed
// @ts-ignore
let WAPs = network.wifi._device !== undefined
? network.wifi["access_points"]
: [];
// @ts-expect-error to fix AGS bug
let WAPs = network.wifi._device !== undefined ? network.wifi['access_points'] : [];
const dedupeWAPs = () => {
const dedupeWAPs = (): AccessPoint[] => {
const dedupMap: Record<string, AccessPoint> = {};
WAPs.forEach((item: AccessPoint) => {
if (item.ssid !== null && !Object.hasOwnProperty.call(dedupMap, item.ssid)) {
@@ -56,7 +59,7 @@ const renderWAPs = (self: any, network: Network, staging: Variable<AccessPoint>,
WAPs = dedupeWAPs();
const isInStaging = (wap: AccessPoint) => {
const isInStaging = (wap: AccessPoint): boolean => {
if (Object.keys(staging.value).length === 0) {
return false;
}
@@ -64,15 +67,15 @@ const renderWAPs = (self: any, network: Network, staging: Variable<AccessPoint>,
return wap.bssid === staging.value.bssid;
};
const isDisconnecting = (wap: AccessPoint) => {
const isDisconnecting = (wap: AccessPoint): boolean => {
if (wap.ssid === network.wifi.ssid) {
return network.wifi.state.toLowerCase() === "deactivating";
return network.wifi.state.toLowerCase() === 'deactivating';
}
return false;
};
const filteredWAPs = WAPs.filter((ap: AccessPoint) => {
return ap.ssid !== "Unknown" && !isInStaging(ap);
return ap.ssid !== 'Unknown' && !isInStaging(ap);
}).sort((a: AccessPoint, b: AccessPoint) => {
if (network.wifi.ssid === a.ssid) {
return -1;
@@ -87,11 +90,11 @@ const renderWAPs = (self: any, network: Network, staging: Variable<AccessPoint>,
if (filteredWAPs.length <= 0 && Object.keys(staging.value).length === 0) {
return (self.child = Widget.Label({
class_name: "waps-not-found dim",
class_name: 'waps-not-found dim',
expand: true,
hpack: "center",
vpack: "center",
label: "No Wi-Fi Networks Found",
hpack: 'center',
vpack: 'center',
label: 'No Wi-Fi Networks Found',
}));
}
return (self.children = filteredWAPs.map((ap: AccessPoint) => {
@@ -103,61 +106,57 @@ const renderWAPs = (self: any, network: Network, staging: Variable<AccessPoint>,
return;
}
connecting.value = ap.bssid || "";
connecting.value = ap.bssid || '';
Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`)
.then(() => {
connecting.value = "";
connecting.value = '';
staging.value = {} as AccessPoint;
})
.catch((err) => {
if (
err
.toLowerCase()
.includes("secrets were required, but not provided")
) {
if (err.toLowerCase().includes('secrets were required, but not provided')) {
staging.value = ap;
} else {
Utils.notify({
summary: "Network",
summary: 'Network',
body: err,
timeout: 5000,
});
}
connecting.value = "";
connecting.value = '';
});
},
class_name: "network-element-item",
class_name: 'network-element-item',
child: Widget.Box({
hexpand: true,
children: [
Widget.Box({
hpack: "start",
hpack: 'start',
hexpand: true,
children: [
Widget.Label({
vpack: "start",
class_name: `network-icon wifi ${ap.ssid === network.wifi.ssid ? "active" : ""} txt-icon`,
label: getWifiIcon(`${ap["iconName"]}`),
vpack: 'start',
class_name: `network-icon wifi ${ap.ssid === network.wifi.ssid ? 'active' : ''} txt-icon`,
label: getWifiIcon(`${ap['iconName']}`),
}),
Widget.Box({
class_name: "connection-container",
vpack: "center",
class_name: 'connection-container',
vpack: 'center',
vertical: true,
children: [
Widget.Label({
vpack: "center",
class_name: "active-connection",
hpack: "start",
truncate: "end",
vpack: 'center',
class_name: 'active-connection',
hpack: 'start',
truncate: 'end',
wrap: true,
label: ap.ssid,
}),
Widget.Revealer({
revealChild: ap.ssid === network.wifi.ssid,
child: Widget.Label({
hpack: "start",
class_name: "connection-status dim",
label: getWifiStatus()
hpack: 'start',
class_name: 'connection-status dim',
label: getWifiStatus(),
}),
}),
],
@@ -165,48 +164,48 @@ const renderWAPs = (self: any, network: Network, staging: Variable<AccessPoint>,
],
}),
Widget.Revealer({
hpack: "end",
vpack: "start",
reveal_child:
ap.bssid === connecting.value || isDisconnecting(ap),
hpack: 'end',
vpack: 'start',
reveal_child: ap.bssid === connecting.value || isDisconnecting(ap),
child: Widget.Spinner({
vpack: "start",
class_name: "spinner wap",
vpack: 'start',
class_name: 'spinner wap',
}),
}),
],
}),
}),
Widget.Revealer({
vpack: "start",
vpack: 'start',
reveal_child: ap.bssid !== connecting.value && ap.active,
child: Widget.Button({
tooltip_text: "Delete/Forget Network",
class_name: "menu-icon-button network disconnect",
tooltip_text: 'Delete/Forget Network',
class_name: 'menu-icon-button network disconnect',
on_primary_click: () => {
connecting.value = ap.bssid || "";
Utils.execAsync("nmcli connection show --active").then(() => {
Utils.execAsync("nmcli connection show --active").then(
(res) => {
const connectionId = getIdBySsid(ap.ssid || "", res);
connecting.value = ap.bssid || '';
Utils.execAsync('nmcli connection show --active').then(() => {
Utils.execAsync('nmcli connection show --active').then((res) => {
const connectionId = getIdBySsid(ap.ssid || '', res);
Utils.execAsync(
`nmcli connection delete ${connectionId} "${ap.ssid}"`,
)
.then(() => (connecting.value = ""))
.catch((err) => {
connecting.value = "";
console.error(
`Error while forgetting "${ap.ssid}": ${err}`,
);
});
},
);
if (connectionId === undefined) {
console.error(
`Error while forgetting "${ap.ssid}": Connection ID not found`,
);
return;
}
Utils.execAsync(`nmcli connection delete ${connectionId} "${ap.ssid}"`)
.then(() => (connecting.value = ''))
.catch((err) => {
connecting.value = '';
console.error(`Error while forgetting "${ap.ssid}": ${err}`);
});
});
});
},
child: Widget.Label({
class_name: "txt-icon delete-network",
label: "󰚃",
class_name: 'txt-icon delete-network',
label: '󰚃',
}),
}),
}),

View File

@@ -1,64 +1,63 @@
const network = await Service.import("network");
import { renderWAPs } from "./WirelessAPs.js";
import { renderWapStaging } from "./APStaging.js";
import { AccessPoint } from "lib/types/network.js";
const network = await Service.import('network');
import { renderWAPs } from './WirelessAPs.js';
import { renderWapStaging } from './APStaging.js';
import { AccessPoint } from 'lib/types/network.js';
import { BoxWidget } from 'lib/types/widget.js';
const Staging = Variable({} as AccessPoint);
const Connecting = Variable("");
const Connecting = Variable('');
const searchInProgress = Variable(false);
const startRotation = () => {
const startRotation = (): void => {
searchInProgress.value = true;
setTimeout(() => {
searchInProgress.value = false;
}, 5 * 1000);
};
const Wifi = () => {
const Wifi = (): BoxWidget => {
return Widget.Box({
class_name: "menu-section-container wifi",
class_name: 'menu-section-container wifi',
vertical: true,
children: [
Widget.Box({
class_name: "menu-label-container",
hpack: "fill",
class_name: 'menu-label-container',
hpack: 'fill',
children: [
Widget.Label({
class_name: "menu-label",
class_name: 'menu-label',
hexpand: true,
hpack: "start",
label: "Wi-Fi",
hpack: 'start',
label: 'Wi-Fi',
}),
Widget.Button({
vpack: "center",
hpack: "end",
class_name: "menu-icon-button search network",
vpack: 'center',
hpack: 'end',
class_name: 'menu-icon-button search network',
on_primary_click: () => {
startRotation();
network.wifi.scan();
},
child: Widget.Icon({
class_name: searchInProgress
.bind("value")
.as((v) => (v ? "spinning" : "")),
icon: "view-refresh-symbolic",
class_name: searchInProgress.bind('value').as((v) => (v ? 'spinning' : '')),
icon: 'view-refresh-symbolic',
}),
}),
],
}),
Widget.Box({
class_name: "menu-items-section",
class_name: 'menu-items-section',
vertical: true,
children: [
Widget.Box({
class_name: "wap-staging",
class_name: 'wap-staging',
setup: (self) => {
renderWapStaging(self, network, Staging, Connecting);
},
}),
Widget.Box({
class_name: "available-waps",
class_name: 'available-waps',
vertical: true,
setup: (self) => {
renderWAPs(self, network, Staging, Connecting);

View File

@@ -1,33 +1,34 @@
import { closeNotifications } from "globals/notification";
import { Notifications } from "types/service/notifications";
import { closeNotifications } from 'globals/notification';
import { BoxWidget } from 'lib/types/widget';
import { Notifications } from 'types/service/notifications';
const Controls = (notifs: Notifications) => {
const Controls = (notifs: Notifications): BoxWidget => {
return Widget.Box({
class_name: "notification-menu-controls",
class_name: 'notification-menu-controls',
expand: false,
vertical: false,
children: [
Widget.Box({
class_name: "menu-label-container notifications",
hpack: "start",
vpack: "center",
class_name: 'menu-label-container notifications',
hpack: 'start',
vpack: 'center',
expand: true,
children: [
Widget.Label({
class_name: "menu-label notifications",
label: "Notifications",
class_name: 'menu-label notifications',
label: 'Notifications',
}),
],
}),
Widget.Box({
hpack: "end",
vpack: "center",
hpack: 'end',
vpack: 'center',
expand: false,
children: [
Widget.Switch({
class_name: "menu-switch notifications",
vpack: "center",
active: notifs.bind("dnd").as((dnd: boolean) => !dnd),
class_name: 'menu-switch notifications',
vpack: 'center',
active: notifs.bind('dnd').as((dnd: boolean) => !dnd),
on_activate: ({ active }) => {
notifs.dnd = !active;
},
@@ -35,14 +36,14 @@ const Controls = (notifs: Notifications) => {
Widget.Box({
children: [
Widget.Separator({
hpack: "center",
hpack: 'center',
vexpand: true,
vertical: true,
class_name: "menu-separator notification-controls",
class_name: 'menu-separator notification-controls',
}),
Widget.Button({
className: "clear-notifications-button",
tooltip_text: "Clear Notifications",
className: 'clear-notifications-button',
tooltip_text: 'Clear Notifications',
on_primary_click: () => {
if (removingNotifications.value) {
return;
@@ -51,12 +52,12 @@ const Controls = (notifs: Notifications) => {
closeNotifications(notifs.notifications);
},
child: Widget.Label({
class_name: removingNotifications.bind("value").as((removing: boolean) => {
class_name: removingNotifications.bind('value').as((removing: boolean) => {
return removing
? "clear-notifications-label txt-icon removing"
: "clear-notifications-label txt-icon";
? 'clear-notifications-label txt-icon removing'
: 'clear-notifications-label txt-icon';
}),
label: "",
label: '',
}),
}),
],

View File

@@ -1,50 +1,45 @@
import { Notification } from "types/service/notifications.js";
import DropdownMenu from "../DropdownMenu.js";
const notifs = await Service.import("notifications");
import { Controls } from "./controls/index.js";
import { NotificationCard } from "./notification/index.js";
import { NotificationPager } from "./pager/index.js";
import { Notification } from 'types/service/notifications.js';
import DropdownMenu from '../DropdownMenu.js';
const notifs = await Service.import('notifications');
import { Controls } from './controls/index.js';
import { NotificationCard } from './notification/index.js';
import { NotificationPager } from './pager/index.js';
import options from "options";
import options from 'options';
import Window from 'types/widgets/window.js';
import { Attribute, Child } from 'lib/types/widget.js';
const { displayedTotal } = options.notifications;
export default () => {
export default (): Window<Child, Attribute> => {
const curPage = Variable(1);
Utils.merge(
[
curPage.bind("value"),
displayedTotal.bind("value"),
notifs.bind("notifications"),
],
(
currentPage: number,
dispTotal: number,
notifications: Notification[],
) => {
[curPage.bind('value'), displayedTotal.bind('value'), notifs.bind('notifications')],
(currentPage: number, dispTotal: number, notifications: Notification[]) => {
// If the page doesn't have enough notifications to display, go back
// to the previous page.
if (notifications.length <= (currentPage - 1) * dispTotal) {
curPage.value = currentPage <= 1 ? 1 : currentPage - 1;
}
});
},
);
return DropdownMenu({
name: "notificationsmenu",
transition: "crossfade",
name: 'notificationsmenu',
transition: 'crossfade',
child: Widget.Box({
class_name: "notification-menu-content",
css: "padding: 1px; margin: -1px;",
class_name: 'notification-menu-content',
css: 'padding: 1px; margin: -1px;',
hexpand: true,
vexpand: false,
children: [
Widget.Box({
class_name: "notification-card-container menu",
class_name: 'notification-card-container menu',
vertical: true,
hexpand: false,
vexpand: false,
children: [Controls(notifs), NotificationCard(notifs, curPage), NotificationPager(curPage)]
children: [Controls(notifs), NotificationCard(notifs, curPage), NotificationPager(curPage)],
}),
],
}),

View File

@@ -1,35 +1,36 @@
import { Notification, Notifications } from "types/service/notifications";
const Actions = (notif: Notification, notifs: Notifications) => {
import { BoxWidget } from 'lib/types/widget';
import { Notification, Notifications } from 'types/service/notifications';
const Actions = (notif: Notification, notifs: Notifications): BoxWidget => {
if (notif.actions !== undefined && notif.actions.length > 0) {
return Widget.Box({
class_name: "notification-card-actions menu",
class_name: 'notification-card-actions menu',
hexpand: true,
vpack: "end",
vpack: 'end',
children: notif.actions.map((action) => {
return Widget.Button({
hexpand: true,
class_name: "notification-action-buttons menu",
class_name: 'notification-action-buttons menu',
on_primary_click: () => {
if (action.id.includes("scriptAction:-")) {
App.closeWindow("notificationsmenu");
Utils.execAsync(
`${action.id.replace("scriptAction:-", "")}`,
).catch((err) => console.error(err));
if (action.id.includes('scriptAction:-')) {
App.closeWindow('notificationsmenu');
Utils.execAsync(`${action.id.replace('scriptAction:-', '')}`).catch((err) =>
console.error(err),
);
notifs.CloseNotification(notif.id);
} else {
App.closeWindow("notificationsmenu");
App.closeWindow('notificationsmenu');
notif.invoke(action.id);
}
},
child: Widget.Box({
hpack: "center",
hpack: 'center',
hexpand: true,
children: [
Widget.Label({
class_name: "notification-action-buttons-label menu",
class_name: 'notification-action-buttons-label menu',
hexpand: true,
max_width_chars: 15,
truncate: "end",
truncate: 'end',
wrap: true,
label: action.label,
}),
@@ -41,7 +42,7 @@ const Actions = (notif: Notification, notifs: Notifications) => {
}
return Widget.Box({
class_name: "spacer",
class_name: 'spacer',
});
};

View File

@@ -1,23 +1,24 @@
import { notifHasImg } from "../../utils.js";
import { Notification } from "types/service/notifications";
import { BoxWidget } from 'lib/types/widget.js';
import { notifHasImg } from '../../utils.js';
import { Notification } from 'types/service/notifications';
export const Body = (notif: Notification) => {
export const Body = (notif: Notification): BoxWidget => {
return Widget.Box({
vpack: "start",
vpack: 'start',
hexpand: true,
class_name: "notification-card-body menu",
class_name: 'notification-card-body menu',
children: [
Widget.Label({
hexpand: true,
use_markup: true,
xalign: 0,
justification: "left",
truncate: "end",
justification: 'left',
truncate: 'end',
lines: 2,
max_width_chars: !notifHasImg(notif) ? 35 : 28,
wrap: true,
class_name: "notification-card-body-label menu",
label: notif["body"],
class_name: 'notification-card-body-label menu',
label: notif['body'],
}),
],
});

View File

@@ -1,14 +1,17 @@
import { Notification, Notifications } from "types/service/notifications";
export const CloseButton = (notif: Notification, notifs: Notifications) => {
import { Attribute } from 'lib/types/widget';
import { Notification, Notifications } from 'types/service/notifications';
import Button from 'types/widgets/button';
import Label from 'types/widgets/label';
export const CloseButton = (notif: Notification, notifs: Notifications): Button<Label<Attribute>, Attribute> => {
return Widget.Button({
class_name: "close-notification-button menu",
class_name: 'close-notification-button menu',
on_primary_click: () => {
notifs.CloseNotification(notif.id);
},
child: Widget.Label({
class_name: "txt-icon notif-close",
label: "󰅜",
hpack: "center",
class_name: 'txt-icon notif-close',
label: '󰅜',
hpack: 'center',
}),
});
};

View File

@@ -1,15 +1,16 @@
import { Notification } from "types/service/notifications.js";
import { NotificationIcon } from "lib/types/notification.js";
import { getNotificationIcon } from "globals/notification";
import { Notification } from 'types/service/notifications.js';
import { NotificationIcon } from 'lib/types/notification.js';
import { getNotificationIcon } from 'globals/notification';
import { BoxWidget } from 'lib/types/widget';
const NotificationIcon = ({ app_entry = "", app_icon = "", app_name = "" }: Partial<Notification>) => {
const NotificationIcon = ({ app_entry = '', app_icon = '', app_name = '' }: Partial<Notification>): BoxWidget => {
return Widget.Box({
css: `
min-width: 2rem;
min-height: 2rem;
`,
child: Widget.Icon({
class_name: "notification-icon menu",
class_name: 'notification-icon menu',
icon: getNotificationIcon(app_name, app_icon, app_entry),
}),
});

View File

@@ -1,50 +1,51 @@
import GLib from "gi://GLib";
import { Notification } from "types/service/notifications";
import { NotificationIcon } from "./icon.js";
import { notifHasImg } from "../../utils.js";
import options from "options.js";
import GLib from 'gi://GLib';
import { Notification } from 'types/service/notifications';
import { NotificationIcon } from './icon.js';
import { notifHasImg } from '../../utils.js';
import options from 'options.js';
import { BoxWidget } from 'lib/types/widget.js';
const { military } = options.menus.clock.time;
export const Header = (notif: Notification) => {
const time = (time: number, format = "%I:%M %p") => {
return GLib.DateTime.new_from_unix_local(time).format(military.value ? "%H:%M" : format);
}
export const Header = (notif: Notification): BoxWidget => {
const time = (time: number, format = '%I:%M %p'): string => {
return GLib.DateTime.new_from_unix_local(time).format(military.value ? '%H:%M' : format) || '--:--';
};
return Widget.Box({
vertical: false,
hexpand: true,
children: [
Widget.Box({
class_name: "notification-card-header menu",
hpack: "start",
class_name: 'notification-card-header menu',
hpack: 'start',
children: [NotificationIcon(notif)],
}),
Widget.Box({
class_name: "notification-card-header menu",
class_name: 'notification-card-header menu',
hexpand: true,
vpack: "start",
vpack: 'start',
children: [
Widget.Label({
class_name: "notification-card-header-label menu",
hpack: "start",
class_name: 'notification-card-header-label menu',
hpack: 'start',
hexpand: true,
vexpand: true,
max_width_chars: !notifHasImg(notif) ? 34 : 22,
truncate: "end",
truncate: 'end',
wrap: true,
label: notif["summary"],
label: notif['summary'],
}),
],
}),
Widget.Box({
class_name: "notification-card-header menu",
hpack: "end",
vpack: "start",
class_name: 'notification-card-header menu',
hpack: 'end',
vpack: 'start',
hexpand: true,
child: Widget.Label({
vexpand: true,
class_name: "notification-time",
class_name: 'notification-time',
label: time(notif.time),
}),
}),

View File

@@ -1,17 +1,18 @@
import { Notification } from "types/service/notifications";
import { notifHasImg } from "../../utils.js";
import { Notification } from 'types/service/notifications';
import { notifHasImg } from '../../utils.js';
import { BoxWidget } from 'lib/types/widget.js';
const Image = (notif: Notification) => {
const Image = (notif: Notification): BoxWidget => {
if (notifHasImg(notif)) {
return Widget.Box({
class_name: "notification-card-image-container menu",
hpack: "center",
vpack: "center",
class_name: 'notification-card-image-container menu',
hpack: 'center',
vpack: 'center',
vexpand: false,
child: Widget.Box({
hpack: "center",
hpack: 'center',
vexpand: false,
class_name: "notification-card-image menu",
class_name: 'notification-card-image menu',
css: `background-image: url("${notif.image}")`,
}),
});

View File

@@ -1,45 +1,40 @@
import { Notifications, Notification } from "types/service/notifications";
import { notifHasImg } from "../utils.js";
import { Header } from "./header/index.js";
import { Actions } from "./actions/index.js";
import { Image } from "./image/index.js";
import { Placeholder } from "./placeholder/index.js";
import { Body } from "./body/index.js";
import { CloseButton } from "./close/index.js";
import options from "options.js";
import { Variable } from "types/variable.js";
import { filterNotifications } from "lib/shared/notifications.js";
import { Notifications, Notification } from 'types/service/notifications';
import { notifHasImg } from '../utils.js';
import { Header } from './header/index.js';
import { Actions } from './actions/index.js';
import { Image } from './image/index.js';
import { Placeholder } from './placeholder/index.js';
import { Body } from './body/index.js';
import { CloseButton } from './close/index.js';
import options from 'options.js';
import { Variable } from 'types/variable.js';
import { filterNotifications } from 'lib/shared/notifications.js';
import Scrollable from 'types/widgets/scrollable.js';
import { Attribute, Child } from 'lib/types/widget.js';
const { displayedTotal, ignore } = options.notifications;
const NotificationCard = (notifs: Notifications, curPage: Variable<number>) => {
const NotificationCard = (notifs: Notifications, curPage: Variable<number>): Scrollable<Child, Attribute> => {
return Widget.Scrollable({
vscroll: "automatic",
vscroll: 'automatic',
child: Widget.Box({
class_name: "menu-content-container notifications",
hpack: "center",
class_name: 'menu-content-container notifications',
hpack: 'center',
vexpand: true,
spacing: 0,
vertical: true,
setup: (self) => {
Utils.merge(
[
notifs.bind("notifications"),
curPage.bind("value"),
displayedTotal.bind("value"),
ignore.bind("value")
notifs.bind('notifications'),
curPage.bind('value'),
displayedTotal.bind('value'),
ignore.bind('value'),
],
(
notifications,
currentPage,
dispTotal,
ignoredNotifs
) => {
(notifications, currentPage, dispTotal, ignoredNotifs) => {
const filteredNotifications = filterNotifications(notifications, ignoredNotifs);
const sortedNotifications = filteredNotifications.sort(
(a, b) => b.time - a.time,
);
const sortedNotifications = filteredNotifications.sort((a, b) => b.time - a.time);
if (filteredNotifications.length <= 0) {
return (self.children = [Placeholder(notifs)]);
@@ -47,37 +42,36 @@ const NotificationCard = (notifs: Notifications, curPage: Variable<number>) => {
const pageStart = (currentPage - 1) * dispTotal;
const pageEnd = currentPage * dispTotal;
return (self.children = sortedNotifications.slice(pageStart, pageEnd).map((notif: Notification) => {
return Widget.Box({
class_name: "notification-card-content-container",
children: [
Widget.Box({
class_name: "notification-card menu",
vpack: "start",
hexpand: true,
vexpand: false,
children: [
Image(notif),
Widget.Box({
vpack: "center",
vertical: true,
hexpand: true,
class_name: `notification-card-content ${!notifHasImg(notif) ? "noimg" : " menu"}`,
children: [
Header(notif),
Body(notif),
Actions(notif, notifs),
],
}),
],
}),
CloseButton(notif, notifs),
],
});
}));
});
return (self.children = sortedNotifications
.slice(pageStart, pageEnd)
.map((notif: Notification) => {
return Widget.Box({
class_name: 'notification-card-content-container',
children: [
Widget.Box({
class_name: 'notification-card menu',
vpack: 'start',
hexpand: true,
vexpand: false,
children: [
Image(notif),
Widget.Box({
vpack: 'center',
vertical: true,
hexpand: true,
class_name: `notification-card-content ${!notifHasImg(notif) ? 'noimg' : ' menu'}`,
children: [Header(notif), Body(notif), Actions(notif, notifs)],
}),
],
}),
CloseButton(notif, notifs),
],
});
}));
},
);
},
})
}),
});
};

View File

@@ -1,24 +1,25 @@
import { Notifications } from "types/service/notifications";
import { BoxWidget } from 'lib/types/widget';
import { Notifications } from 'types/service/notifications';
const Placeholder = (notifs: Notifications) => {
const Placeholder = (notifs: Notifications): BoxWidget => {
return Widget.Box({
class_name: "notification-label-container",
vpack: "fill",
hpack: "center",
class_name: 'notification-label-container',
vpack: 'fill',
hpack: 'center',
expand: true,
child: Widget.Box({
vpack: "center",
vpack: 'center',
vertical: true,
expand: true,
children: [
Widget.Label({
vpack: "center",
class_name: "placeholder-label dim bell",
label: notifs.bind("dnd").as((dnd) => (dnd ? "󰂛" : "󰂚")),
vpack: 'center',
class_name: 'placeholder-label dim bell',
label: notifs.bind('dnd').as((dnd) => (dnd ? '󰂛' : '󰂚')),
}),
Widget.Label({
vpack: "start",
class_name: "placehold-label dim message",
vpack: 'start',
class_name: 'placehold-label dim message',
label: "You're all caught up :)",
}),
],

View File

@@ -1,91 +1,88 @@
const notifs = await Service.import("notifications");
const notifs = await Service.import('notifications');
import options from "options";
import { Notification } from "types/service/notifications";
import { Variable } from "types/variable";
import { BoxWidget } from 'lib/types/widget';
import options from 'options';
import { Notification } from 'types/service/notifications';
import { Variable } from 'types/variable';
const { displayedTotal } = options.notifications;
const { show: showPager } = options.theme.bar.menus.menu.notifications.pager;
export const NotificationPager = (curPage: Variable<number>) => {
export const NotificationPager = (curPage: Variable<number>): BoxWidget => {
return Widget.Box({
class_name: "notification-menu-pager",
class_name: 'notification-menu-pager',
hexpand: true,
vexpand: false,
children: Utils.merge(
[
curPage.bind("value"),
displayedTotal.bind("value"),
notifs.bind("notifications"),
showPager.bind("value")
curPage.bind('value'),
displayedTotal.bind('value'),
notifs.bind('notifications'),
showPager.bind('value'),
],
(
currentPage: number,
dispTotal: number,
_: Notification[],
showPgr: boolean
) => {
(currentPage: number, dispTotal: number, _: Notification[], showPgr: boolean) => {
if (showPgr === false) {
return [];
}
return [
Widget.Button({
hexpand: true,
hpack: "start",
class_name: `pager-button left ${currentPage <= 1 ? "disabled" : ""}`,
hpack: 'start',
class_name: `pager-button left ${currentPage <= 1 ? 'disabled' : ''}`,
onPrimaryClick: () => {
curPage.value = 1;
},
child: Widget.Label({
className: "pager-button-label",
label: ""
className: 'pager-button-label',
label: '',
}),
}),
Widget.Button({
hexpand: true,
hpack: "start",
class_name: `pager-button left ${currentPage <= 1 ? "disabled" : ""}`,
hpack: 'start',
class_name: `pager-button left ${currentPage <= 1 ? 'disabled' : ''}`,
onPrimaryClick: () => {
curPage.value = currentPage <= 1 ? 1 : currentPage - 1;
},
child: Widget.Label({
className: "pager-button-label",
label: ""
className: 'pager-button-label',
label: '',
}),
}),
Widget.Label({
hexpand: true,
hpack: "center",
class_name: "pager-label",
label: `${currentPage} / ${Math.ceil(notifs.notifications.length / dispTotal) || 1}`
hpack: 'center',
class_name: 'pager-label',
label: `${currentPage} / ${Math.ceil(notifs.notifications.length / dispTotal) || 1}`,
}),
Widget.Button({
hexpand: true,
hpack: "end",
class_name: `pager-button right ${currentPage >= Math.ceil(notifs.notifications.length / dispTotal) ? "disabled" : ""}`,
hpack: 'end',
class_name: `pager-button right ${currentPage >= Math.ceil(notifs.notifications.length / dispTotal) ? 'disabled' : ''}`,
onPrimaryClick: () => {
const maxPage = Math.ceil(notifs.notifications.length / displayedTotal.value);
curPage.value = currentPage >= maxPage ? currentPage : currentPage + 1;
},
child: Widget.Label({
className: "pager-button-label",
label: ""
className: 'pager-button-label',
label: '',
}),
}),
Widget.Button({
hexpand: true,
hpack: "end",
class_name: `pager-button right ${currentPage >= Math.ceil(notifs.notifications.length / dispTotal) ? "disabled" : ""}`,
hpack: 'end',
class_name: `pager-button right ${currentPage >= Math.ceil(notifs.notifications.length / dispTotal) ? 'disabled' : ''}`,
onPrimaryClick: () => {
const maxPage = Math.ceil(notifs.notifications.length / displayedTotal.value);
curPage.value = maxPage;
},
child: Widget.Label({
className: "pager-button-label",
label: "󰄾"
className: 'pager-button-label',
label: '󰄾',
}),
}),
]
})
})
}
];
},
),
});
};

View File

@@ -1,7 +1,7 @@
import { Notification } from "types/service/notifications";
import { Notification } from 'types/service/notifications';
const notifHasImg = (notif: Notification) => {
return notif.image !== undefined && notif.image.length;
const notifHasImg = (notif: Notification): boolean => {
return notif.image !== undefined && notif.image.length ? true : false;
};
export { notifHasImg };

View File

@@ -1,5 +1,5 @@
import { Action } from "lib/types/power";
import options from "options";
import { Action } from 'lib/types/power';
import options from 'options';
const { sleep, reboot, logout, shutdown } = options.menus.dashboard.powermenu;
class PowerMenu extends Service {
@@ -8,50 +8,50 @@ class PowerMenu extends Service {
this,
{},
{
title: ["string"],
cmd: ["string"],
title: ['string'],
cmd: ['string'],
},
);
}
#title = "";
#cmd = "";
#title = '';
#cmd = '';
get title() {
get title(): string {
return this.#title;
}
action(action: Action) {
action(action: Action): void {
[this.#cmd, this.#title] = {
sleep: [sleep.value, "Sleep"],
reboot: [reboot.value, "Reboot"],
logout: [logout.value, "Log Out"],
shutdown: [shutdown.value, "Shutdown"],
sleep: [sleep.value, 'Sleep'],
reboot: [reboot.value, 'Reboot'],
logout: [logout.value, 'Log Out'],
shutdown: [shutdown.value, 'Shutdown'],
}[action];
this.notify("cmd");
this.notify("title");
this.emit("changed");
App.closeWindow("powermenu");
App.openWindow("verification");
this.notify('cmd');
this.notify('title');
this.emit('changed');
App.closeWindow('powermenu');
App.openWindow('verification');
}
customAction(action: Action, cmnd: string) {
customAction(action: Action, cmnd: string): void {
[this.#cmd, this.#title] = [cmnd, action];
this.notify("cmd");
this.notify("title");
this.emit("changed");
App.closeWindow("powermenu");
App.openWindow("verification");
this.notify('cmd');
this.notify('title');
this.emit('changed');
App.closeWindow('powermenu');
App.openWindow('verification');
}
shutdown = () => {
this.action("shutdown");
shutdown = (): void => {
this.action('shutdown');
};
exec = () => {
App.closeWindow("verification");
exec = (): void => {
App.closeWindow('verification');
Utils.execAsync(this.#cmd);
};
}

View File

@@ -1,15 +1,17 @@
import { Action } from "lib/types/power.js";
import PopupWindow from "../PopupWindow.js";
import powermenu from "./helpers/actions.js";
import icons from "../../icons/index.js";
import { Action } from 'lib/types/power.js';
import PopupWindow from '../PopupWindow.js';
import powermenu from './helpers/actions.js';
import icons from '../../icons/index.js';
import Window from 'types/widgets/window.js';
import { Attribute, Child, GButton } from 'lib/types/widget.js';
const SysButton = (action: Action, label: string) =>
const SysButton = (action: Action, label: string): GButton =>
Widget.Button({
class_name: `widget-button powermenu-button-${action}`,
on_clicked: () => powermenu.action(action),
child: Widget.Box({
vertical: true,
class_name: "system-button widget-box",
class_name: 'system-button widget-box',
children: [
Widget.Icon({
class_name: `system-button_icon ${action}`,
@@ -22,17 +24,17 @@ const SysButton = (action: Action, label: string) =>
],
}),
});
export default () =>
export default (): Window<Child, Attribute> =>
PopupWindow({
name: "powermenu",
transition: "crossfade",
name: 'powermenu',
transition: 'crossfade',
child: Widget.Box({
class_name: "powermenu horizontal",
class_name: 'powermenu horizontal',
children: [
SysButton("shutdown", "SHUTDOWN"),
SysButton("logout", "LOG OUT"),
SysButton("reboot", "REBOOT"),
SysButton("sleep", "SLEEP"),
SysButton('shutdown', 'SHUTDOWN'),
SysButton('logout', 'LOG OUT'),
SysButton('reboot', 'REBOOT'),
SysButton('sleep', 'SLEEP'),
],
}),
});

View File

@@ -1,52 +1,54 @@
import PopupWindow from "../PopupWindow.js";
import powermenu from "./helpers/actions.js";
import Window from 'types/widgets/window.js';
import PopupWindow from '../PopupWindow.js';
import powermenu from './helpers/actions.js';
import { Attribute, Child } from 'lib/types/widget.js';
export default () =>
PopupWindow({
name: "verification",
transition: "crossfade",
child: Widget.Box({
class_name: "verification",
child: Widget.Box({
class_name: "verification-content",
expand: true,
vertical: true,
children: [
Widget.Box({
class_name: "text-box",
vertical: true,
children: [
Widget.Label({
class_name: "title",
label: powermenu.bind("title").as((t) => t.toUpperCase()),
}),
Widget.Label({
class_name: "desc",
label: powermenu
.bind("title")
.as((p) => `Are you sure you want to ${p.toLowerCase()}?`),
}),
],
}),
Widget.Box({
class_name: "buttons horizontal",
vexpand: true,
vpack: "end",
homogeneous: true,
children: [
Widget.Button({
class_name: "verification-button bar-verification_yes",
child: Widget.Label("Yes"),
on_clicked: powermenu.exec,
}),
Widget.Button({
class_name: "verification-button bar-verification_no",
child: Widget.Label("No"),
on_clicked: () => App.toggleWindow("verification"),
}),
],
}),
],
}),
}),
});
export default (): Window<Child, Attribute> =>
PopupWindow({
name: 'verification',
transition: 'crossfade',
child: Widget.Box({
class_name: 'verification',
child: Widget.Box({
class_name: 'verification-content',
expand: true,
vertical: true,
children: [
Widget.Box({
class_name: 'text-box',
vertical: true,
children: [
Widget.Label({
class_name: 'title',
label: powermenu.bind('title').as((t) => t.toUpperCase()),
}),
Widget.Label({
class_name: 'desc',
label: powermenu
.bind('title')
.as((p) => `Are you sure you want to ${p.toLowerCase()}?`),
}),
],
}),
Widget.Box({
class_name: 'buttons horizontal',
vexpand: true,
vpack: 'end',
homogeneous: true,
children: [
Widget.Button({
class_name: 'verification-button bar-verification_yes',
child: Widget.Label('Yes'),
on_clicked: powermenu.exec,
}),
Widget.Button({
class_name: 'verification-button bar-verification_no',
child: Widget.Label('No'),
on_clicked: () => App.toggleWindow('verification'),
}),
],
}),
],
}),
}),
});

View File

@@ -1,42 +1,44 @@
import { PowerOptions } from "lib/types/options";
import options from "options";
import powermenu from "../power/helpers/actions";
import { PowerOptions } from 'lib/types/options';
import options from 'options';
import powermenu from '../power/helpers/actions';
import { GButton } from 'lib/types/widget';
const { confirmation, shutdown, logout, sleep, reboot, showLabel } = options.menus.power;
export const PowerButton = (action: PowerOptions) => {
const handleClick = (action: PowerOptions) => {
export const PowerButton = (action: PowerOptions): GButton => {
const handleClick = (action: PowerOptions): void => {
const actions = {
shutdown: shutdown.value,
reboot: reboot.value,
logout: logout.value,
sleep: sleep.value,
};
App.closeWindow("powerdropdownmenu");
App.closeWindow('powerdropdownmenu');
if (!confirmation.value) {
Utils.execAsync(actions[action])
.catch((err) => console.error(`Failed to execute ${action} command. Error: ${err}`));
Utils.execAsync(actions[action]).catch((err) =>
console.error(`Failed to execute ${action} command. Error: ${err}`),
);
} else {
powermenu.customAction(action, actions[action]);
}
};
const powerIconMap = {
shutdown: "󰐥",
reboot: "󰜉",
logout: "󰿅",
sleep: "󰤄",
shutdown: '󰐥',
reboot: '󰜉',
logout: '󰿅',
sleep: '󰤄',
};
return Widget.Button({
className: showLabel.bind("value").as(shwLbl => {
return `power-menu-button ${action} ${!shwLbl ? "no-label" : ""}`;
className: showLabel.bind('value').as((shwLbl) => {
return `power-menu-button ${action} ${!shwLbl ? 'no-label' : ''}`;
}),
on_clicked: () => handleClick(action),
child: Widget.Box({
vertical: false,
children: showLabel.bind("value").as(shwLbl => {
children: showLabel.bind('value').as((shwLbl) => {
if (shwLbl) {
return [
Widget.Label({
@@ -44,7 +46,7 @@ export const PowerButton = (action: PowerOptions) => {
className: `power-button-icon ${action}-icon txt-icon`,
}),
Widget.Label({
hpack: "center",
hpack: 'center',
hexpand: true,
label: action.charAt(0).toUpperCase() + action.slice(1),
className: `power-button-label ${action}-label show-label`,
@@ -58,6 +60,6 @@ export const PowerButton = (action: PowerOptions) => {
}),
];
}),
})
}),
});
};

View File

@@ -1,24 +1,20 @@
import DropdownMenu from "../DropdownMenu.js";
import { PowerButton } from "./button.js";
import Window from 'types/widgets/window.js';
import DropdownMenu from '../DropdownMenu.js';
import { PowerButton } from './button.js';
import { Attribute, Child } from 'lib/types/widget.js';
export default () => {
export default (): Window<Child, Attribute> => {
return DropdownMenu({
name: "powerdropdownmenu",
transition: "crossfade",
name: 'powerdropdownmenu',
transition: 'crossfade',
child: Widget.Box({
class_name: "menu-items power-dropdown",
class_name: 'menu-items power-dropdown',
child: Widget.Box({
vertical: true,
hexpand: true,
class_name: "menu-items-container power-dropdown",
children: [
PowerButton('shutdown'),
PowerButton('reboot'),
PowerButton('logout'),
PowerButton('sleep'),
],
class_name: 'menu-items-container power-dropdown',
children: [PowerButton('shutdown'), PowerButton('reboot'), PowerButton('logout'), PowerButton('sleep')],
}),
}),
});
};

View File

@@ -1,35 +1,37 @@
import { Notification, Notifications } from "types/service/notifications";
import { Attribute, Child } from 'lib/types/widget';
import { Notification, Notifications } from 'types/service/notifications';
import Box from 'types/widgets/box';
const Action = (notif: Notification, notifs: Notifications) => {
const Action = (notif: Notification, notifs: Notifications): Box<Child, Attribute> => {
if (notif.actions !== undefined && notif.actions.length > 0) {
return Widget.Box({
class_name: "notification-card-actions",
class_name: 'notification-card-actions',
hexpand: true,
vpack: "end",
vpack: 'end',
children: notif.actions.map((action) => {
return Widget.Button({
hexpand: true,
class_name: "notification-action-buttons",
class_name: 'notification-action-buttons',
on_primary_click: () => {
if (action.id.includes("scriptAction:-")) {
Utils.execAsync(
`${action.id.replace("scriptAction:-", "")}`,
).catch((err) => console.error(err));
if (action.id.includes('scriptAction:-')) {
Utils.execAsync(`${action.id.replace('scriptAction:-', '')}`).catch((err) =>
console.error(err),
);
notifs.CloseNotification(notif.id);
} else {
notif.invoke(action.id);
}
},
child: Widget.Box({
hpack: "center",
hpack: 'center',
hexpand: true,
children: [
Widget.Label({
class_name: "notification-action-buttons-label",
class_name: 'notification-action-buttons-label',
hexpand: true,
label: action.label,
max_width_chars: 15,
truncate: "end",
truncate: 'end',
wrap: true,
}),
],

View File

@@ -1,23 +1,25 @@
import { Notification } from "types/service/notifications";
import { notifHasImg } from "../../menus/notifications/utils.js";
import { Notification } from 'types/service/notifications';
import { notifHasImg } from '../../menus/notifications/utils.js';
import Box from 'types/widgets/box.js';
import { Attribute, Child } from 'lib/types/widget.js';
export const Body = (notif: Notification) => {
export const Body = (notif: Notification): Box<Child, Attribute> => {
return Widget.Box({
vpack: "start",
vpack: 'start',
hexpand: true,
class_name: "notification-card-body",
class_name: 'notification-card-body',
children: [
Widget.Label({
hexpand: true,
use_markup: true,
xalign: 0,
justification: "left",
truncate: "end",
justification: 'left',
truncate: 'end',
lines: 2,
max_width_chars: !notifHasImg(notif) ? 35 : 28,
wrap: true,
class_name: "notification-card-body-label",
label: notif["body"],
class_name: 'notification-card-body-label',
label: notif['body'],
}),
],
});

View File

@@ -1,15 +1,18 @@
import { Notification, Notifications } from "types/service/notifications";
import { Attribute, Child } from 'lib/types/widget';
import { Notification, Notifications } from 'types/service/notifications';
import Button from 'types/widgets/button';
import Label from 'types/widgets/label';
export const CloseButton = (notif: Notification, notifs: Notifications) => {
export const CloseButton = (notif: Notification, notifs: Notifications): Button<Label<Child>, Attribute> => {
return Widget.Button({
class_name: "close-notification-button",
class_name: 'close-notification-button',
on_primary_click: () => {
notifs.CloseNotification(notif.id);
},
child: Widget.Label({
class_name: "txt-icon notif-close",
label: "󰅜",
hpack: "center",
class_name: 'txt-icon notif-close',
label: '󰅜',
hpack: 'center',
}),
});
};

View File

@@ -1,15 +1,21 @@
import { Notification } from "types/service/notifications.js";
import { getNotificationIcon } from "globals/notification.js";
import { Notification } from 'types/service/notifications.js';
import { getNotificationIcon } from 'globals/notification.js';
import Box from 'types/widgets/box';
import { Attribute, Child } from 'lib/types/widget';
const NotificationIcon = ({ app_entry = "", app_icon = "", app_name = "" }: Partial<Notification>) => {
const NotificationIcon = ({
app_entry = '',
app_icon = '',
app_name = '',
}: Partial<Notification>): Box<Child, Attribute> => {
return Widget.Box({
css: `
min-width: 2rem;
min-height: 2rem;
`,
child: Widget.Icon({
class_name: "notification-icon",
icon: getNotificationIcon(app_name, app_icon, app_entry)
class_name: 'notification-icon',
icon: getNotificationIcon(app_name, app_icon, app_entry),
}),
});
};

View File

@@ -1,51 +1,53 @@
import GLib from "gi://GLib";
import { notifHasImg } from "../../menus/notifications/utils.js";
import { NotificationIcon } from "./icon.js";
import { Notification } from "types/service/notifications";
import options from "options.js";
import GLib from 'gi://GLib';
import { notifHasImg } from '../../menus/notifications/utils.js';
import { NotificationIcon } from './icon.js';
import { Notification } from 'types/service/notifications';
import options from 'options.js';
import Box from 'types/widgets/box.js';
import { Attribute, Child } from 'lib/types/widget.js';
const { military } = options.menus.clock.time;
export const Header = (notif: Notification) => {
const time = (time: number, format = "%I:%M %p") => {
return GLib.DateTime.new_from_unix_local(time).format(military.value ? "%H:%M" : format);
}
export const Header = (notif: Notification): Box<Child, Attribute> => {
const time = (time: number, format = '%I:%M %p'): string => {
return GLib.DateTime.new_from_unix_local(time).format(military.value ? '%H:%M' : format) || '--';
};
return Widget.Box({
vertical: false,
hexpand: true,
children: [
Widget.Box({
class_name: "notification-card-header",
hpack: "start",
class_name: 'notification-card-header',
hpack: 'start',
children: [NotificationIcon(notif)],
}),
Widget.Box({
class_name: "notification-card-header",
class_name: 'notification-card-header',
hexpand: true,
hpack: "start",
vpack: "start",
hpack: 'start',
vpack: 'start',
children: [
Widget.Label({
class_name: "notification-card-header-label",
hpack: "start",
class_name: 'notification-card-header-label',
hpack: 'start',
hexpand: true,
vexpand: true,
max_width_chars: !notifHasImg(notif) ? 30 : 19,
truncate: "end",
truncate: 'end',
wrap: true,
label: notif["summary"],
label: notif['summary'],
}),
],
}),
Widget.Box({
class_name: "notification-card-header menu",
hpack: "end",
vpack: "start",
class_name: 'notification-card-header menu',
hpack: 'end',
vpack: 'start',
hexpand: true,
child: Widget.Label({
vexpand: true,
class_name: "notification-time",
class_name: 'notification-time',
label: time(notif.time),
}),
}),

View File

@@ -1,17 +1,19 @@
import { Notification } from "types/service/notifications";
import { notifHasImg } from "../../menus/notifications/utils.js";
import { Notification } from 'types/service/notifications';
import { notifHasImg } from '../../menus/notifications/utils.js';
import Box from 'types/widgets/box.js';
import { Attribute, Child } from 'lib/types/widget.js';
const Image = (notif: Notification) => {
const Image = (notif: Notification): Box<Child, Attribute> => {
if (notifHasImg(notif)) {
return Widget.Box({
class_name: "notification-card-image-container",
hpack: "center",
vpack: "center",
class_name: 'notification-card-image-container',
hpack: 'center',
vpack: 'center',
vexpand: false,
child: Widget.Box({
hpack: "center",
hpack: 'center',
vexpand: false,
class_name: "notification-card-image",
class_name: 'notification-card-image',
css: `background-image: url("${notif.image}")`,
}),
});

View File

@@ -1,75 +1,79 @@
const notifs = await Service.import("notifications");
import options from "options";
import { notifHasImg } from "../menus/notifications/utils.js";
import { Image } from "./image/index.js";
import { Action } from "./actions/index.js";
import { Header } from "./header/index.js";
import { Body } from "./body/index.js";
import { CloseButton } from "./close/index.js";
import { getPosition } from "lib/utils.js";
import { filterNotifications } from "lib/shared/notifications.js";
import { Notification } from "types/service/notifications.js";
const hyprland = await Service.import("hyprland");
const notifs = await Service.import('notifications');
import options from 'options';
import { notifHasImg } from '../menus/notifications/utils.js';
import { Image } from './image/index.js';
import { Action } from './actions/index.js';
import { Header } from './header/index.js';
import { Body } from './body/index.js';
import { CloseButton } from './close/index.js';
import { getPosition } from 'lib/utils.js';
import { filterNotifications } from 'lib/shared/notifications.js';
import { Notification } from 'types/service/notifications.js';
import Window from 'types/widgets/window.js';
import Box from 'types/widgets/box.js';
import { Attribute, Child } from 'lib/types/widget.js';
const hyprland = await Service.import('hyprland');
const { position, timeout, cache_actions, monitor, active_monitor, displayedTotal, ignore } = options.notifications;
const curMonitor = Variable(monitor.value);
hyprland.active.connect("changed", () => {
hyprland.active.connect('changed', () => {
curMonitor.value = hyprland.active.monitor.id;
})
});
export default () => {
Utils.merge([timeout.bind("value"), cache_actions.bind("value")], (timeout, doCaching) => {
export default (): Window<Box<Child, Attribute>, unknown> => {
Utils.merge([timeout.bind('value'), cache_actions.bind('value')], (timeout, doCaching) => {
notifs.popupTimeout = timeout;
notifs.cacheActions = doCaching;
});
return Widget.Window({
name: "notifications-window",
class_name: "notifications-window",
monitor: Utils.merge([
curMonitor.bind("value"),
monitor.bind("value"),
active_monitor.bind("value")], (curMon, mon, activeMonitor) => {
name: 'notifications-window',
class_name: 'notifications-window',
monitor: Utils.merge(
[curMonitor.bind('value'), monitor.bind('value'), active_monitor.bind('value')],
(curMon, mon, activeMonitor) => {
if (activeMonitor === true) {
return curMon;
}
return mon;
}
},
),
layer: options.tear.bind("value").as(tear => tear ? "top" : "overlay"),
anchor: position.bind("value").as(v => getPosition(v)),
exclusivity: "normal",
layer: options.tear.bind('value').as((tear) => (tear ? 'top' : 'overlay')),
anchor: position.bind('value').as((v) => getPosition(v)),
exclusivity: 'normal',
child: Widget.Box({
class_name: "notification-card-container",
class_name: 'notification-card-container',
vertical: true,
hexpand: true,
setup: (self) => {
Utils.merge([notifs.bind("popups"), ignore.bind("value")], (notifications: Notification[], ignoredNotifs: string[]) => {
const filteredNotifications = filterNotifications(notifications, ignoredNotifs);
Utils.merge(
[notifs.bind('popups'), ignore.bind('value')],
(notifications: Notification[], ignoredNotifs: string[]) => {
const filteredNotifications = filterNotifications(notifications, ignoredNotifs);
return (self.children = filteredNotifications.slice(0, displayedTotal.value).map((notif) => {
return Widget.Box({
class_name: "notification-card",
vpack: "start",
hexpand: true,
children: [
Image(notif),
Widget.Box({
vpack: "start",
vertical: true,
hexpand: true,
class_name: `notification-card-content ${!notifHasImg(notif) ? "noimg" : ""}`,
children: [Header(notif), Body(notif), Action(notif, notifs)],
}),
CloseButton(notif, notifs),
],
});
}));
})
return (self.children = filteredNotifications.slice(0, displayedTotal.value).map((notif) => {
return Widget.Box({
class_name: 'notification-card',
vpack: 'start',
hexpand: true,
children: [
Image(notif),
Widget.Box({
vpack: 'start',
vertical: true,
hexpand: true,
class_name: `notification-card-content ${!notifHasImg(notif) ? 'noimg' : ''}`,
children: [Header(notif), Body(notif), Action(notif, notifs)],
}),
CloseButton(notif, notifs),
],
});
}));
},
);
},
}),
});

View File

@@ -1,44 +1,89 @@
import { OSDOrientation } from "lib/types/options";
import brightness from "services/Brightness"
import options from "options"
const audio = await Service.import("audio")
import { OSDOrientation } from 'lib/types/options';
import brightness from 'services/Brightness';
import options from 'options';
import Box from 'types/widgets/box';
import { Attribute, Child } from 'lib/types/widget';
const audio = await Service.import('audio');
export const OSDBar = (ort: OSDOrientation) => {
export const OSDBar = (ort: OSDOrientation): Box<Child, Attribute> => {
return Widget.Box({
class_name: "osd-bar-container",
class_name: 'osd-bar-container',
children: [
Widget.LevelBar({
class_name: "osd-bar",
vertical: ort === "vertical",
inverted: ort === "vertical",
bar_mode: "continuous",
setup: self => {
self.hook(brightness, () => {
self.class_names = self.class_names.filter(c => c !== "overflow");
self.value = brightness.screen;
}, "notify::screen")
self.hook(brightness, () => {
self.class_names = self.class_names.filter(c => c !== "overflow");
self.value = brightness.kbd;
}, "notify::kbd")
self.hook(audio.microphone, () => {
self.toggleClassName("overflow", audio.microphone.volume > 1)
self.value = audio.microphone.volume <= 1 ? audio.microphone.volume : audio.microphone.volume - 1;
}, "notify::volume")
self.hook(audio.microphone, () => {
self.toggleClassName("overflow", audio.microphone.volume > 1 && (!options.theme.osd.muted_zero.value || audio.microphone.is_muted === false));
self.value = (options.theme.osd.muted_zero.value && audio.microphone.is_muted !== false) ? 0 : audio.microphone.volume <= 1 ? audio.microphone.volume : audio.microphone.volume - 1;
}, "notify::is-muted")
self.hook(audio.speaker, () => {
self.toggleClassName("overflow", audio.speaker.volume > 1)
self.value = audio.speaker.volume <= 1 ? audio.speaker.volume : audio.speaker.volume - 1;
}, "notify::volume")
self.hook(audio.speaker, () => {
self.toggleClassName("overflow", audio.speaker.volume > 1 && (!options.theme.osd.muted_zero.value || audio.speaker.is_muted === false));
self.value = options.theme.osd.muted_zero.value && audio.speaker.is_muted !== false ? 0 : audio.speaker.volume <= 1 ? audio.speaker.volume : audio.speaker.volume - 1;
}, "notify::is-muted")
}
})
]
class_name: 'osd-bar',
vertical: ort === 'vertical',
inverted: ort === 'vertical',
bar_mode: 'continuous',
setup: (self) => {
self.hook(
brightness,
() => {
self.class_names = self.class_names.filter((c) => c !== 'overflow');
self.value = brightness.screen;
},
'notify::screen',
);
self.hook(
brightness,
() => {
self.class_names = self.class_names.filter((c) => c !== 'overflow');
self.value = brightness.kbd;
},
'notify::kbd',
);
self.hook(
audio.microphone,
() => {
self.toggleClassName('overflow', audio.microphone.volume > 1);
self.value =
audio.microphone.volume <= 1 ? audio.microphone.volume : audio.microphone.volume - 1;
},
'notify::volume',
);
self.hook(
audio.microphone,
() => {
self.toggleClassName(
'overflow',
audio.microphone.volume > 1 &&
(!options.theme.osd.muted_zero.value || audio.microphone.is_muted === false),
);
self.value =
options.theme.osd.muted_zero.value && audio.microphone.is_muted !== false
? 0
: audio.microphone.volume <= 1
? audio.microphone.volume
: audio.microphone.volume - 1;
},
'notify::is-muted',
);
self.hook(
audio.speaker,
() => {
self.toggleClassName('overflow', audio.speaker.volume > 1);
self.value = audio.speaker.volume <= 1 ? audio.speaker.volume : audio.speaker.volume - 1;
},
'notify::volume',
);
self.hook(
audio.speaker,
() => {
self.toggleClassName(
'overflow',
audio.speaker.volume > 1 &&
(!options.theme.osd.muted_zero.value || audio.speaker.is_muted === false),
);
self.value =
options.theme.osd.muted_zero.value && audio.speaker.is_muted !== false
? 0
: audio.speaker.volume <= 1
? audio.speaker.volume
: audio.speaker.volume - 1;
},
'notify::is-muted',
);
},
}),
],
});
}
};

View File

@@ -1,36 +1,62 @@
import brightness from "services/Brightness"
const audio = await Service.import("audio")
import { Attribute, Child } from 'lib/types/widget';
import brightness from 'services/Brightness';
import Box from 'types/widgets/box';
const audio = await Service.import('audio');
export const OSDIcon = () => {
export const OSDIcon = (): Box<Child, Attribute> => {
return Widget.Box({
class_name: "osd-icon-container",
class_name: 'osd-icon-container',
hexpand: true,
child: Widget.Label({
class_name: "osd-icon txt-icon",
class_name: 'osd-icon txt-icon',
hexpand: true,
vexpand: true,
hpack: "center",
vpack: "center",
setup: self => {
self.hook(brightness, () => {
self.label = "󱍖";
}, "notify::screen")
self.hook(brightness, () => {
self.label = "󰥻";
}, "notify::kbd")
self.hook(audio.microphone, () => {
self.label = audio.microphone.is_muted ? "󰍭" : "󰍬";
}, "notify::volume")
self.hook(audio.microphone, () => {
self.label = audio.microphone.is_muted ? "󰍭" : "󰍬";
}, "notify::is-muted")
self.hook(audio.speaker, () => {
self.label = audio.speaker.is_muted ? "󰝟" : "󰕾";
}, "notify::volume")
self.hook(audio.speaker, () => {
self.label = audio.speaker.is_muted ? "󰝟" : "󰕾";
}, "notify::is-muted")
}
})
hpack: 'center',
vpack: 'center',
setup: (self) => {
self.hook(
brightness,
() => {
self.label = '󱍖';
},
'notify::screen',
);
self.hook(
brightness,
() => {
self.label = '󰥻';
},
'notify::kbd',
);
self.hook(
audio.microphone,
() => {
self.label = audio.microphone.is_muted ? '󰍭' : '󰍬';
},
'notify::volume',
);
self.hook(
audio.microphone,
() => {
self.label = audio.microphone.is_muted ? '󰍭' : '󰍬';
},
'notify::is-muted',
);
self.hook(
audio.speaker,
() => {
self.label = audio.speaker.is_muted ? '󰝟' : '󰕾';
},
'notify::volume',
);
self.hook(
audio.speaker,
() => {
self.label = audio.speaker.is_muted ? '󰝟' : '󰕾';
},
'notify::is-muted',
);
},
}),
});
}
};

View File

@@ -1,132 +1,199 @@
import options from "options";
import brightness from "services/Brightness"
import { OSDLabel } from "./label/index";
import { OSDBar } from "./bar/index";
import { OSDIcon } from "./icon/index";
import { getPosition } from "lib/utils";
const hyprland = await Service.import("hyprland");
const audio = await Service.import("audio")
import options from 'options';
import brightness from 'services/Brightness';
import { OSDLabel } from './label/index';
import { OSDBar } from './bar/index';
import { OSDIcon } from './icon/index';
import { getPosition } from 'lib/utils';
import { Attribute, Child } from 'lib/types/widget';
import { Revealer } from 'resource:///com/github/Aylur/ags/widgets/revealer.js';
import { Window } from 'resource:///com/github/Aylur/ags/widgets/window.js';
const hyprland = await Service.import('hyprland');
const audio = await Service.import('audio');
const {
enable,
duration,
orientation,
location,
active_monitor,
monitor
} = options.theme.osd;
const { enable, duration, orientation, location, active_monitor, monitor } = options.theme.osd;
const curMonitor = Variable(monitor.value);
hyprland.active.connect("changed", () => {
hyprland.active.connect('changed', () => {
curMonitor.value = hyprland.active.monitor.id;
})
});
let count = 0
const handleReveal = (self: any, property: string) => {
if (!enable.value) {
let count = 0;
const handleRevealRevealer = (self: Revealer<Child, Attribute>, property: 'reveal_child' | 'visible'): void => {
if (!enable.value || property !== 'reveal_child') {
return;
}
self[property] = true
count++
self.reveal_child = true;
count++;
Utils.timeout(duration.value, () => {
count--
count--;
if (count === 0)
self[property] = false
})
}
if (count === 0) {
self.reveal_child = false;
}
});
};
const renderOSD = () => {
const handleRevealWindow = (self: Window<Child, Attribute>, property: 'reveal_child' | 'visible'): void => {
if (!enable.value || property !== 'visible') {
return;
}
self.visible = true;
count++;
Utils.timeout(duration.value, () => {
count--;
if (count === 0) {
self.visible = false;
}
});
};
const handleReveal = (
self: Revealer<Child, Attribute> | Window<Child, Attribute>,
property: 'reveal_child' | 'visible',
): void => {
if (self instanceof Revealer) {
handleRevealRevealer(self, property);
} else if (self instanceof Window) {
handleRevealWindow(self, property);
}
};
const renderOSD = (): Revealer<Child, Attribute> => {
return Widget.Revealer({
transition: "crossfade",
transition: 'crossfade',
reveal_child: false,
setup: self => {
self.hook(brightness, () => {
handleReveal(self, "reveal_child");
}, "notify::screen")
self.hook(brightness, () => {
handleReveal(self, "reveal_child");
}, "notify::kbd")
self.hook(audio.microphone, () => {
handleReveal(self, "reveal_child");
}, "notify::volume")
self.hook(audio.microphone, () => {
handleReveal(self, "reveal_child");
}, "notify::is-muted")
self.hook(audio.speaker, () => {
handleReveal(self, "reveal_child");
}, "notify::volume")
self.hook(audio.speaker, () => {
handleReveal(self, "reveal_child");
}, "notify::is-muted")
setup: (self) => {
self.hook(
brightness,
() => {
handleReveal(self, 'reveal_child');
},
'notify::screen',
);
self.hook(
brightness,
() => {
handleReveal(self, 'reveal_child');
},
'notify::kbd',
);
self.hook(
audio.microphone,
() => {
handleReveal(self, 'reveal_child');
},
'notify::volume',
);
self.hook(
audio.microphone,
() => {
handleReveal(self, 'reveal_child');
},
'notify::is-muted',
);
self.hook(
audio.speaker,
() => {
handleReveal(self, 'reveal_child');
},
'notify::volume',
);
self.hook(
audio.speaker,
() => {
handleReveal(self, 'reveal_child');
},
'notify::is-muted',
);
},
child: Widget.Box({
class_name: "osd-container",
vertical: orientation.bind("value").as(ort => ort === "vertical"),
children: orientation.bind("value").as(ort => {
if (ort === "vertical") {
return [
OSDLabel(),
OSDBar(ort),
OSDIcon()
]
class_name: 'osd-container',
vertical: orientation.bind('value').as((ort) => ort === 'vertical'),
children: orientation.bind('value').as((ort) => {
if (ort === 'vertical') {
return [OSDLabel(), OSDBar(ort), OSDIcon()];
}
return [
OSDIcon(),
OSDBar(ort),
OSDLabel(),
]
})
})
})
}
export default () => Widget.Window({
monitor: Utils.merge([
curMonitor.bind("value"),
monitor.bind("value"),
active_monitor.bind("value")], (curMon, mon, activeMonitor) => {
if (activeMonitor === true) {
return curMon;
}
return mon;
return [OSDIcon(), OSDBar(ort), OSDLabel()];
}),
}),
name: `indicator`,
class_name: "indicator",
layer: options.tear.bind("value").as(tear => tear ? "top" : "overlay"),
anchor: location.bind("value").as(v => getPosition(v)),
click_through: true,
child: Widget.Box({
css: "padding: 1px;",
expand: true,
child: renderOSD(),
}),
setup: self => {
self.hook(enable, () => {
handleReveal(self, "visible");
})
self.hook(brightness, () => {
handleReveal(self, "visible");
}, "notify::screen")
self.hook(brightness, () => {
handleReveal(self, "visible");
}, "notify::kbd")
self.hook(audio.microphone, () => {
handleReveal(self, "visible");
}, "notify::volume")
self.hook(audio.microphone, () => {
handleReveal(self, "visible");
}, "notify::is-muted")
self.hook(audio.speaker, () => {
handleReveal(self, "visible");
}, "notify::volume")
self.hook(audio.speaker, () => {
handleReveal(self, "visible");
}, "notify::is-muted")
},
})
});
};
export default (): Window<Child, Attribute> =>
Widget.Window({
monitor: Utils.merge(
[curMonitor.bind('value'), monitor.bind('value'), active_monitor.bind('value')],
(curMon, mon, activeMonitor) => {
if (activeMonitor === true) {
return curMon;
}
return mon;
},
),
name: `indicator`,
class_name: 'indicator',
layer: options.tear.bind('value').as((tear) => (tear ? 'top' : 'overlay')),
anchor: location.bind('value').as((v) => getPosition(v)),
click_through: true,
child: Widget.Box({
css: 'padding: 1px;',
expand: true,
child: renderOSD(),
}),
setup: (self) => {
self.hook(enable, () => {
handleReveal(self, 'visible');
});
self.hook(
brightness,
() => {
handleReveal(self, 'visible');
},
'notify::screen',
);
self.hook(
brightness,
() => {
handleReveal(self, 'visible');
},
'notify::kbd',
);
self.hook(
audio.microphone,
() => {
handleReveal(self, 'visible');
},
'notify::volume',
);
self.hook(
audio.microphone,
() => {
handleReveal(self, 'visible');
},
'notify::is-muted',
);
self.hook(
audio.speaker,
() => {
handleReveal(self, 'visible');
},
'notify::volume',
);
self.hook(
audio.speaker,
() => {
handleReveal(self, 'visible');
},
'notify::is-muted',
);
},
});

View File

@@ -1,44 +1,86 @@
import brightness from "services/Brightness"
import options from "options"
const audio = await Service.import("audio")
import brightness from 'services/Brightness';
import options from 'options';
import Box from 'types/widgets/box';
import { Attribute, Child } from 'lib/types/widget';
const audio = await Service.import('audio');
export const OSDLabel = () => {
export const OSDLabel = (): Box<Child, Attribute> => {
return Widget.Box({
class_name: "osd-label-container",
class_name: 'osd-label-container',
hexpand: true,
vexpand: true,
child: Widget.Label({
class_name: "osd-label",
class_name: 'osd-label',
hexpand: true,
vexpand: true,
hpack: "center",
vpack: "center",
setup: self => {
self.hook(brightness, () => {
self.class_names = self.class_names.filter(c => c !== "overflow");
self.label = `${Math.round(brightness.screen * 100)}`;
}, "notify::screen")
self.hook(brightness, () => {
self.class_names = self.class_names.filter(c => c !== "overflow");
self.label = `${Math.round(brightness.kbd * 100)}`;
}, "notify::kbd")
self.hook(audio.microphone, () => {
self.toggleClassName("overflow", audio.microphone.volume > 1)
self.label = `${Math.round(audio.microphone.volume * 100)}`;
}, "notify::volume")
self.hook(audio.microphone, () => {
self.toggleClassName("overflow", audio.microphone.volume > 1 && (!options.theme.osd.muted_zero.value || audio.microphone.is_muted === false));
self.label = `${options.theme.osd.muted_zero.value && audio.microphone.is_muted !== false ? 0 : Math.round(audio.microphone.volume * 100)}`;
}, "notify::is-muted")
self.hook(audio.speaker, () => {
self.toggleClassName("overflow", audio.speaker.volume > 1)
self.label = `${Math.round(audio.speaker.volume * 100)}`;
}, "notify::volume")
self.hook(audio.speaker, () => {
self.toggleClassName("overflow", audio.speaker.volume > 1 && (!options.theme.osd.muted_zero.value || audio.speaker.is_muted === false));
self.label = `${options.theme.osd.muted_zero.value && audio.speaker.is_muted !== false ? 0 : Math.round(audio.speaker.volume * 100)}`;
}, "notify::is-muted")
}
})
hpack: 'center',
vpack: 'center',
setup: (self) => {
self.hook(
brightness,
() => {
self.class_names = self.class_names.filter((c) => c !== 'overflow');
self.label = `${Math.round(brightness.screen * 100)}`;
},
'notify::screen',
);
self.hook(
brightness,
() => {
self.class_names = self.class_names.filter((c) => c !== 'overflow');
self.label = `${Math.round(brightness.kbd * 100)}`;
},
'notify::kbd',
);
self.hook(
audio.microphone,
() => {
self.toggleClassName('overflow', audio.microphone.volume > 1);
self.label = `${Math.round(audio.microphone.volume * 100)}`;
},
'notify::volume',
);
self.hook(
audio.microphone,
() => {
self.toggleClassName(
'overflow',
audio.microphone.volume > 1 &&
(!options.theme.osd.muted_zero.value || audio.microphone.is_muted === false),
);
const inputVolume =
options.theme.osd.muted_zero.value && audio.microphone.is_muted !== false
? 0
: Math.round(audio.microphone.volume * 100);
self.label = `${inputVolume}`;
},
'notify::is-muted',
);
self.hook(
audio.speaker,
() => {
self.toggleClassName('overflow', audio.speaker.volume > 1);
self.label = `${Math.round(audio.speaker.volume * 100)}`;
},
'notify::volume',
);
self.hook(
audio.speaker,
() => {
self.toggleClassName(
'overflow',
audio.speaker.volume > 1 &&
(!options.theme.osd.muted_zero.value || audio.speaker.is_muted === false),
);
const speakerVolume =
options.theme.osd.muted_zero.value && audio.speaker.is_muted !== false
? 0
: Math.round(audio.speaker.volume * 100);
self.label = `${speakerVolume}`;
},
'notify::is-muted',
);
},
}),
});
}
};

View File

@@ -1,26 +1,29 @@
import Gtk from "gi://Gtk?version=3.0";
import { Child } from "lib/types/bar";
import options from "options";
import { ButtonProps } from "types/widgets/button";
import { BarBoxChild } from 'lib/types/bar';
import { Bind } from 'lib/types/variable';
import { Attribute, GtkWidget } from 'lib/types/widget';
import options from 'options';
import Button from 'types/widgets/button';
export const BarItemBox = (child: Child): ButtonProps<Gtk.Widget> => {
const computeVisible = () => {
export const BarItemBox = (child: BarBoxChild): Button<GtkWidget, Attribute> => {
const computeVisible = (): Bind | boolean => {
if (child.isVis !== undefined) {
return child.isVis.bind("value");
return child.isVis.bind('value');
}
return child.isVisible;
};
return Widget.Button({
class_name: options.theme.bar.buttons.style.bind("value").as((style) => {
class_name: options.theme.bar.buttons.style.bind('value').as((style) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
wave2: "style4",
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style4',
};
return `bar_item_box_visible ${styleMap[style]} ${Object.hasOwnProperty.call(child, "boxClass") ? child.boxClass : ""}`;
const boxClassName = Object.hasOwnProperty.call(child, 'boxClass') ? child.boxClass : '';
return `bar_item_box_visible ${styleMap[style]} ${boxClassName}`;
}),
child: child.component,
visible: computeVisible(),