Merge pull request #7 from Jas-SinghFSU/typescript

Convert project fully to TypeScript.
This commit is contained in:
Jas Singh
2024-07-26 23:20:10 -07:00
committed by GitHub
152 changed files with 4878 additions and 3473 deletions

130
.eslintrc.yml Normal file
View File

@@ -0,0 +1,130 @@
env:
es2022: true
extends:
- "eslint:recommended"
- "plugin:@typescript-eslint/recommended"
parser: "@typescript-eslint/parser"
parserOptions:
ecmaVersion: 2022
sourceType: "module"
project: "./tsconfig.json"
warnOnUnsupportedTypeScriptVersion: false
root: true
ignorePatterns:
- types/
plugins:
- "@typescript-eslint"
rules:
"@typescript-eslint/ban-ts-comment":
- "off"
"@typescript-eslint/no-non-null-assertion":
- "off"
# "@typescript-eslint/no-explicit-any":
# - "off"
"@typescript-eslint/no-unused-vars":
- error
- varsIgnorePattern: (^unused|_$)
argsIgnorePattern: ^(unused|_)
"@typescript-eslint/no-empty-interface":
- "off"
arrow-parens:
- error
- as-needed
comma-dangle:
- error
- always-multiline
comma-spacing:
- error
- before: false
after: true
comma-style:
- error
- last
curly:
- error
- multi-or-nest
- consistent
dot-location:
- error
- property
eol-last:
- error
eqeqeq:
- error
- always
indent:
- error
- 4
- SwitchCase: 1
keyword-spacing:
- error
- before: true
lines-between-class-members:
- error
- always
- exceptAfterSingleLine: true
padded-blocks:
- error
- never
- allowSingleLineBlocks: false
prefer-const:
- error
quotes:
- error
- double
- avoidEscape: true
semi:
- error
- never
nonblock-statement-body-position:
- error
- below
no-trailing-spaces:
- error
no-useless-escape:
- off
max-len:
- error
- code: 100
func-call-spacing:
- error
array-bracket-spacing:
- error
space-before-function-paren:
- error
- anonymous: never
named: never
asyncArrow: ignore
space-before-blocks:
- error
key-spacing:
- error
object-curly-spacing:
- error
- always
globals:
Widget: readonly
Utils: readonly
App: readonly
Variable: readonly
Service: readonly
pkg: readonly
ARGV: readonly
Debugger: readonly
GIRepositoryGType: readonly
globalThis: readonly
imports: readonly
Intl: readonly
log: readonly
logError: readonly
print: readonly
printerr: readonly
window: readonly
TextEncoder: readonly
TextDecoder: readonly
console: readonly
setTimeout: readonly
setInterval: readonly
clearTimeout: readonly
clearInterval: readonly

View File

@@ -1,41 +0,0 @@
import Service from "resource:///com/github/Aylur/ags/service.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import { monitorFile } from "resource:///com/github/Aylur/ags/utils.js";
import Gio from "gi://Gio";
class DirectoryMonitorService extends Service {
static {
Service.register(this, {}, {});
}
constructor() {
super();
this.recursiveDirectoryMonitor(`${App.configDir}/scss`);
}
recursiveDirectoryMonitor(directoryPath) {
monitorFile(directoryPath, (_, eventType) => {
if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) {
this.emit("changed");
}
});
const directory = Gio.File.new_for_path(directoryPath);
const enumerator = directory.enumerate_children(
"standard::*",
Gio.FileQueryInfoFlags.NONE,
null,
);
let fileInfo;
while ((fileInfo = enumerator.next_file(null)) !== null) {
const childPath = directoryPath + "/" + fileInfo.get_name();
if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) {
this.recursiveDirectoryMonitor(childPath);
}
}
}
}
const service = new DirectoryMonitorService();
export default service;

View File

@@ -0,0 +1,42 @@
import Service from "resource:///com/github/Aylur/ags/service.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import { monitorFile } from "resource:///com/github/Aylur/ags/utils.js";
import Gio from "gi://Gio";
import { FileInfo } from "types/@girs/gio-2.0/gio-2.0.cjs";
class DirectoryMonitorService extends Service {
static {
Service.register(this, {}, {});
}
constructor() {
super();
this.recursiveDirectoryMonitor(`${App.configDir}/scss`);
}
recursiveDirectoryMonitor(directoryPath: string) {
monitorFile(directoryPath, (_, eventType) => {
if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) {
this.emit("changed");
}
});
const directory = Gio.File.new_for_path(directoryPath);
const enumerator = directory.enumerate_children(
"standard::*",
Gio.FileQueryInfoFlags.NONE,
null,
);
let fileInfo: FileInfo;
while ((fileInfo = enumerator.next_file(null) as FileInfo) !== null) {
const childPath = directoryPath + "/" + fileInfo.get_name();
if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) {
this.recursiveDirectoryMonitor(childPath);
}
}
}
}
const service = new DirectoryMonitorService();
export default service;

View File

@@ -1,4 +1,4 @@
const globalMousePos = Variable([]);
const globalMousePos = Variable([0, 0]);
globalThis["globalMousePos"] = globalMousePos;

File diff suppressed because it is too large Load Diff

25
lib/types/gpustat.d.ts vendored Normal file
View File

@@ -0,0 +1,25 @@
export type GPU_Stat_Process = {
username: string;
command: string;
full_command: string[];
gpu_memory_usage: number;
cpu_percent: number;
cpu_memory_usage: number;
pid: number;
};
export type GPU_Stat = {
index: number;
uuid: string;
name: string;
"temperature.gpu": number;
"fan.speed": number;
"utilization.gpu": number;
"utilization.enc": number;
"utilization.dec": number;
"power.draw": number;
"enforced.power.limit": number;
"memory.used": number;
"memory.total": number;
processes: Process[];
};

10
lib/types/network.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
export type AccessPoint = {
bssid: string | null;
address: string | null;
lastSeen: number;
ssid: string | null;
active: boolean;
strength: number;
frequency: number;
iconName: string | undefined;
}

3
lib/types/options.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export type Unit = "imperial" | "metric";
export type PowerOptions = "sleep" | "reboot" | "logout" | "shutdown";
export type NotificationAnchor = "top" | "top right" | "top left" | "bottom" | "bottom right" | "bottom left";

1
lib/types/power.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export type Action = "sleep" | "reboot" | "logout" | "shutdown";

107
lib/types/weather.d.ts vendored Normal file
View File

@@ -0,0 +1,107 @@
export type Weather = {
location: Location;
current: Current;
forecast: Forecast;
}
export type Current = {
last_updated_epoch?: number;
last_updated?: string;
temp_c: number;
temp_f: number;
is_day: number;
condition: Condition;
wind_mph: number;
wind_kph: number;
wind_degree: number;
wind_dir: string;
pressure_mb: number;
pressure_in: number;
precip_mm: number;
precip_in: number;
humidity: number;
cloud: number;
feelslike_c: number;
feelslike_f: number;
windchill_c: number;
windchill_f: number;
heatindex_c: number;
heatindex_f: number;
dewpoint_c: number;
dewpoint_f: number;
vis_km: number;
vis_miles: number;
uv: number;
gust_mph: number;
gust_kph: number;
time_epoch?: number;
time?: string;
snow_cm?: number;
will_it_rain?: number;
chance_of_rain?: number;
will_it_snow?: number;
chance_of_snow?: number;
}
export type Condition = {
text: string;
icon: string;
code: number;
}
export type Forecast = {
forecastday: Forecastday[];
}
export type Forecastday = {
date: string;
date_epoch: number;
day: Day;
astro: Astro;
hour: Current[];
}
export type Astro = {
sunrise: string;
sunset: string;
moonrise: string;
moonset: string;
moon_phase: string;
moon_illumination: number;
is_moon_up: number;
is_sun_up: number;
}
export type Day = {
maxtemp_c: number;
maxtemp_f: number;
mintemp_c: number;
mintemp_f: number;
avgtemp_c: number;
avgtemp_f: number;
maxwind_mph: number;
maxwind_kph: number;
totalprecip_mm: number;
totalprecip_in: number;
totalsnow_cm: number;
avgvis_km: number;
avgvis_miles: number;
avghumidity: number;
daily_will_it_rain: number;
daily_chance_of_rain: number;
daily_will_it_snow: number;
daily_chance_of_snow: number;
condition: Condition;
uv: number;
}
export type Location = {
name: string;
region: string;
country: string;
lat: number;
lon: number;
tz_id: string;
localtime_epoch: number;
localtime: string;
}

3
lib/types/widget.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export type Exclusivity = 'normal' | 'ignore' | 'exclusive';
export type Anchor = "left" | "right" | "top" | "down";
export type Transition = "none" | "crossfade" | "slide_right" | "slide_left" | "slide_up" | "slide_down";

8
lib/types/workspace.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
export type WorkspaceRule = {
workspaceString: string,
monitor: string,
}
export type WorkspaceMap = {
[key: string]: number[],
}

View File

@@ -1,82 +0,0 @@
const battery = await Service.import("battery");
import { openMenu } from "../utils.js";
import options from "options";
const { label: show_label } = options.bar.battery;
const BatteryLabel = () => {
const isVis = Variable(battery.available);
const icon = () =>
battery
.bind("percent")
.as((p) => `battery-level-${Math.floor(p / 10) * 10}-symbolic`);
battery.connect("changed", ({ available }) => {
isVis.value = available;
});
const formatTime = (seconds) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return { hours, minutes };
};
const generateTooltip = (timeSeconds, isCharging, isCharged) => {
if (isCharged) {
return "Fully Charged!!!";
}
const { hours, minutes } = formatTime(timeSeconds);
if (isCharging) {
return `${hours} hours ${minutes} minutes until full`;
} else {
return `${hours} hours ${minutes} minutes left`;
}
};
return {
component: Widget.Box({
class_name: "battery",
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({ icon: icon() }),
Widget.Label({
label: battery.bind("percent").as((p) => ` ${p}%`),
}),
];
} else if (batAvail && !showLabel) {
return [Widget.Icon({ icon: icon() })];
} else {
return [];
}
},
),
setup: (self) => {
self.hook(battery, () => {
if (battery.available) {
self.tooltip_text = generateTooltip(
battery.time_remaining,
battery.charging,
battery.charged,
);
}
});
},
}),
isVis,
boxClass: "battery",
props: {
on_primary_click: (clicked, event) => {
openMenu(clicked, event, "energymenu");
},
},
};
};
export { BatteryLabel };

View File

@@ -0,0 +1,84 @@
const battery = await Service.import("battery");
import Gdk from 'gi://Gdk?version=3.0';
import EventHandler from 'types/widgets/button.ts'
import { openMenu } from "../utils.js";
import options from "options";
const { label: show_label } = options.bar.battery;
const BatteryLabel = () => {
const isVis = Variable(battery.available);
const icon = () =>
battery
.bind("percent")
.as((p) => `battery-level-${Math.floor(p / 10) * 10}-symbolic`);
battery.connect("changed", ({ available }) => {
isVis.value = available;
});
const formatTime = (seconds: 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) => {
if (isCharged) {
return "Fully Charged!!!";
}
const { hours, minutes } = formatTime(timeSeconds);
if (isCharging) {
return `${hours} hours ${minutes} minutes until full`;
} else {
return `${hours} hours ${minutes} minutes left`;
}
};
return {
component: Widget.Box({
class_name: "battery",
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({ icon: icon() }),
Widget.Label({
label: battery.bind("percent").as((p) => ` ${p}%`),
}),
];
} else if (batAvail && !showLabel) {
return [Widget.Icon({ icon: icon() })];
} else {
return [];
}
},
),
setup: (self) => {
self.hook(battery, () => {
if (battery.available) {
self.tooltip_text = generateTooltip(
battery.time_remaining,
battery.charging,
battery.charged,
);
}
});
},
}),
isVis,
boxClass: "battery",
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "energymenu");
},
},
};
};
export { BatteryLabel };

View File

@@ -1,38 +0,0 @@
const bluetooth = await Service.import('bluetooth')
import options from "options";
import { openMenu } from "../utils.js";
const Bluetooth = () => {
const btIcon = Widget.Label({
label: bluetooth.bind("enabled").as((v) => v ? "󰂯" : "󰂲"),
class_name: "bar-bt_icon",
});
const btText = Widget.Label({
label: Utils.merge([bluetooth.bind("enabled"), options.bar.bluetooth.label.bind("value")], (btEnabled, showLabel) => {
if (showLabel) {
return btEnabled ? " On" : " Off"
}
return "";
}),
class_name: "bar-bt_label",
});
return {
component: Widget.Box({
class_name: "volume",
children: [btIcon, btText],
}),
isVisible: true,
boxClass: "bluetooth",
props: {
on_primary_click: (clicked, event) => {
openMenu(clicked, event, "bluetoothmenu");
},
},
};
}
export { Bluetooth }

View File

@@ -0,0 +1,39 @@
const bluetooth = await Service.import('bluetooth')
import Gdk from 'gi://Gdk?version=3.0';
import options from "options";
import { openMenu } from "../utils.js";
const Bluetooth = () => {
const btIcon = Widget.Label({
label: bluetooth.bind("enabled").as((v) => v ? "󰂯" : "󰂲"),
class_name: "bar-bt_icon",
});
const btText = Widget.Label({
label: Utils.merge([bluetooth.bind("enabled"), options.bar.bluetooth.label.bind("value")], (btEnabled, showLabel) => {
if (showLabel) {
return btEnabled ? " On" : " Off"
}
return "";
}),
class_name: "bar-bt_label",
});
return {
component: Widget.Box({
class_name: "volume",
children: [btIcon, btText],
}),
isVisible: true,
boxClass: "bluetooth",
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "bluetoothmenu");
},
},
};
}
export { Bluetooth }

View File

@@ -1,27 +0,0 @@
import GLib from "gi://GLib";
import { openMenu } from "../utils.js";
import options from "options";
const { format } = options.bar.clock;
const date = Variable(GLib.DateTime.new_now_local(), {
poll: [1000, () => GLib.DateTime.new_now_local()],
});
const time = Utils.derive([date, format], (c, f) => c.format(f) || "");
const Clock = () => {
return {
component: Widget.Label({
class_name: "clock",
label: time.bind(),
}),
isVisible: true,
boxClass: "clock",
props: {
on_primary_click: (clicked, event) => {
openMenu(clicked, event, "calendarmenu");
},
},
};
};
export { Clock };

View File

@@ -0,0 +1,28 @@
import Gdk from 'gi://Gdk?version=3.0';
import GLib from "gi://GLib";
import { openMenu } from "../utils.js";
import options from "options";
const { format } = options.bar.clock;
const date = Variable(GLib.DateTime.new_now_local(), {
poll: [1000, () => GLib.DateTime.new_now_local()],
});
const time = Utils.derive([date, format], (c, f) => c.format(f) || "");
const Clock = () => {
return {
component: Widget.Label({
class_name: "clock",
label: time.bind(),
}),
isVisible: true,
boxClass: "clock",
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "calendarmenu");
},
},
};
};
export { Clock };

View File

@@ -1,101 +0,0 @@
const mpris = await Service.import("mpris");
import { openMenu } from "../utils.js";
const Media = () => {
const activePlayer = Variable(mpris.players[0]);
mpris.connect("changed", (value) => {
const statusOrder = {
Playing: 1,
Paused: 2,
Stopped: 3,
};
if (value.players.length === 0) {
activePlayer.value = mpris.players[0];
return;
}
const isPlaying = value.players.find(
(p) => p["play-back-status"] === "Playing",
);
if (isPlaying) {
activePlayer.value = value.players.sort(
(a, b) =>
statusOrder[a["play-back-status"]] -
statusOrder[b["play-back-status"]],
)[0];
}
});
const getIconForPlayer = (playerName) => {
const windowTitleMap = [
["Mozilla Firefox", "󰈹 "],
["Microsoft Edge", "󰇩 "],
["(.*)Discord(.*)", " "],
["Plex", "󰚺 "],
["(.*) Spotify Free", "󰓇 "],
["(.*)Spotify Premium", "󰓇 "],
["Spotify", "󰓇 "],
["(.*)", "󰝚 "],
];
const foundMatch = windowTitleMap.find((wt) =>
RegExp(wt[0]).test(playerName),
);
return foundMatch ? foundMatch[1] : "󰝚";
};
const songIcon = Variable("");
const label = Utils.watch("󰎇 Media 󰎇", mpris, "changed", () => {
if (activePlayer.value) {
const { track_title, identity } = activePlayer.value;
songIcon.value = getIconForPlayer(identity);
return track_title.length === 0
? ` No media playing...`
: ` ${track_title}`;
} else {
songIcon.value = "";
return "󰎇 Media 󰎇";
}
});
return {
component: Widget.Box({
visible: false,
child: Widget.Box({
class_name: "media",
child: Widget.Box({
children: [
Widget.Label({
class_name: "bar-media_icon",
label: songIcon.bind("value"),
maxWidthChars: 30,
}),
Widget.Label({
label,
truncate: "end",
wrap: true,
maxWidthChars: 30,
}),
],
}),
}),
}),
isVisible: false,
boxClass: "media",
name: "media",
props: {
on_scroll_up: () => mpris.getPlayer("")?.next(),
on_scroll_down: () => mpris.getPlayer("")?.previous(),
on_primary_click: (clicked, event) => {
openMenu(clicked, event, "mediamenu");
},
},
};
};
export { Media };

102
modules/bar/media/index.ts Normal file
View File

@@ -0,0 +1,102 @@
import Gdk from 'gi://Gdk?version=3.0';
const mpris = await Service.import("mpris");
import { openMenu } from "../utils.js";
const Media = () => {
const activePlayer = Variable(mpris.players[0]);
mpris.connect("changed", (value) => {
const statusOrder = {
Playing: 1,
Paused: 2,
Stopped: 3,
};
if (value.players.length === 0) {
activePlayer.value = mpris.players[0];
return;
}
const isPlaying = value.players.find(
(p) => p["play-back-status"] === "Playing",
);
if (isPlaying) {
activePlayer.value = value.players.sort(
(a, b) =>
statusOrder[a["play-back-status"]] -
statusOrder[b["play-back-status"]],
)[0];
}
});
const getIconForPlayer = (playerName: string) => {
const windowTitleMap = [
["Mozilla Firefox", "󰈹 "],
["Microsoft Edge", "󰇩 "],
["(.*)Discord(.*)", " "],
["Plex", "󰚺 "],
["(.*) Spotify Free", "󰓇 "],
["(.*)Spotify Premium", "󰓇 "],
["Spotify", "󰓇 "],
["(.*)", "󰝚 "],
];
const foundMatch = windowTitleMap.find((wt) =>
RegExp(wt[0]).test(playerName),
);
return foundMatch ? foundMatch[1] : "󰝚";
};
const songIcon = Variable("");
const label = Utils.watch("󰎇 Media 󰎇", mpris, "changed", () => {
if (activePlayer.value) {
const { track_title, identity } = activePlayer.value;
songIcon.value = getIconForPlayer(identity);
return track_title.length === 0
? ` No media playing...`
: ` ${track_title}`;
} else {
songIcon.value = "";
return "󰎇 Media 󰎇";
}
});
return {
component: Widget.Box({
visible: false,
child: Widget.Box({
class_name: "media",
child: Widget.Box({
children: [
Widget.Label({
class_name: "bar-media_icon",
label: songIcon.bind("value"),
maxWidthChars: 30,
}),
Widget.Label({
label,
truncate: "end",
wrap: true,
maxWidthChars: 30,
}),
],
}),
}),
}),
isVisible: false,
boxClass: "media",
name: "media",
props: {
on_scroll_up: () => mpris.getPlayer("")?.next(),
on_scroll_down: () => mpris.getPlayer("")?.previous(),
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "mediamenu");
},
},
};
};
export { Media };

View File

@@ -1,22 +0,0 @@
import { openMenu } from "../utils.js";
import options from "options";
const Menu = () => {
return {
component: Widget.Box({
child: Widget.Label({
class_name: "bar-menu_label",
label: options.bar.launcher.icon.bind("value"),
}),
}),
isVisible: true,
boxClass: "dashboard",
props: {
on_primary_click: (clicked, event) => {
openMenu(clicked, event, "dashboardmenu");
},
},
};
};
export { Menu };

23
modules/bar/menu/index.ts Normal file
View File

@@ -0,0 +1,23 @@
import Gdk from 'gi://Gdk?version=3.0';
import { openMenu } from "../utils.js";
import options from "options";
const Menu = () => {
return {
component: Widget.Box({
child: Widget.Label({
class_name: "bar-menu_label",
label: options.bar.launcher.icon.bind("value"),
}),
}),
isVisible: true,
boxClass: "dashboard",
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "dashboardmenu");
},
},
};
};
export { Menu };

View File

@@ -1,3 +1,4 @@
import Gdk from 'gi://Gdk?version=3.0';
const network = await Service.import("network");
import options from "options";
import { openMenu } from "../utils.js";
@@ -52,7 +53,7 @@ const Network = () => {
isVisible: true,
boxClass: "network",
props: {
on_primary_click: (clicked, event) => {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "networkmenu");
},
},

View File

@@ -1,46 +0,0 @@
import { openMenu } from "../utils.js";
import options from "options";
const { show_total } = options.bar.notifications;
const notifs = await Service.import("notifications");
export const Notifications = () => {
return {
component: Widget.Box({
hpack: "start",
child: Widget.Box({
hpack: "start",
class_name: "bar-notifications",
children: Utils.merge(
[notifs.bind("notifications"), notifs.bind("dnd"), show_total.bind("value")],
(notif, dnd, showTotal) => {
const notifIcon = Widget.Label({
hpack: "center",
class_name: "bar-notifications-label",
label: dnd ? "󰂛" : notif.length > 0 ? "󱅫" : "󰂚",
});
const notifLabel = Widget.Label({
hpack: "center",
class_name: "bar-notifications-total",
label: notif.length.toString(),
});
if (showTotal) {
return [notifIcon, notifLabel];
}
return [notifIcon];
},
),
}),
}),
isVisible: true,
boxClass: "notifications",
props: {
on_primary_click: (clicked, event) => {
openMenu(clicked, event, "notificationsmenu");
},
},
};
};

View File

@@ -0,0 +1,47 @@
import Gdk from 'gi://Gdk?version=3.0';
import { openMenu } from "../utils.js";
import options from "options";
const { show_total } = options.bar.notifications;
const notifs = await Service.import("notifications");
export const Notifications = () => {
return {
component: Widget.Box({
hpack: "start",
child: Widget.Box({
hpack: "start",
class_name: "bar-notifications",
children: Utils.merge(
[notifs.bind("notifications"), notifs.bind("dnd"), show_total.bind("value")],
(notif, dnd, showTotal) => {
const notifIcon = Widget.Label({
hpack: "center",
class_name: "bar-notifications-label",
label: dnd ? "󰂛" : notif.length > 0 ? "󱅫" : "󰂚",
});
const notifLabel = Widget.Label({
hpack: "center",
class_name: "bar-notifications-total",
label: notif.length.toString(),
});
if (showTotal) {
return [notifIcon, notifLabel];
}
return [notifIcon];
},
),
}),
}),
isVisible: true,
boxClass: "notifications",
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "notificationsmenu");
},
},
};
};

View File

@@ -1,47 +0,0 @@
const systemtray = await Service.import("systemtray");
import { bash } from "lib/utils";
import options from "options";
const { ignore } = options.bar.systray;
const SysTray = () => {
const isVis = Variable(false);
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;
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: (_, event) => item.activate(event),
on_secondary_click: (_, event) => item.openMenu(event),
tooltip_markup: item.bind("tooltip_markup"),
});
});
},
);
return {
component: Widget.Box({
class_name: "systray",
children: items,
}),
isVisible: true,
boxClass: "systray",
isVis,
};
};
export { SysTray };

View File

@@ -0,0 +1,47 @@
import Gdk from 'gi://Gdk?version=3.0';
const systemtray = await Service.import("systemtray");
import options from "options";
const { ignore } = options.bar.systray;
const SysTray = () => {
const isVis = Variable(false);
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;
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),
tooltip_markup: item.bind("tooltip_markup"),
});
});
},
);
return {
component: Widget.Box({
class_name: "systray",
children: items,
}),
isVisible: true,
boxClass: "systray",
isVis,
};
};
export { SysTray };

View File

@@ -1,47 +0,0 @@
export const closeAllMenus = () => {
const menuWindows = App.windows
.filter((w) => {
if (w.name) {
return /.*menu/.test(w.name);
}
return false;
})
.map((w) => w.name);
menuWindows.forEach((w) => {
if (w) {
App.closeWindow(w);
}
});
};
export const openMenu = (clicked, event, window) => {
/*
* 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
* offcenter dependending on which edge of the button you click on.
* -------------
* To fix this, we take the x coordinate of the click within the button's bounds.
* If you click the left edge of a 100 width button, then the x axis will be 0
* and if you click the right edge then the x axis will be 100.
* -------------
* Then we divide the width of the button by 2 to get the center of the button and then get
* the offset by subtracting the clicked x coordinate. Then we can apply that offset
* to the x coordinate of the click relative to the screen to get the center of the
* icon click.
*/
const middleOfButton = Math.floor(clicked.get_allocated_width() / 2);
const xAxisOfButtonClick = clicked.get_pointer()[0];
const middleOffset = middleOfButton - xAxisOfButtonClick;
const clickPos = event.get_root_coords();
const adjustedXCoord = clickPos[1] + middleOffset;
const coords = [adjustedXCoord, clickPos[2]];
globalMousePos.value = coords;
closeAllMenus();
App.toggleWindow(window);
};

51
modules/bar/utils.ts Normal file
View File

@@ -0,0 +1,51 @@
import Gdk from 'gi://Gdk?version=3.0';
import { globalMousePos } from 'globals';
export const closeAllMenus = () => {
const menuWindows = App.windows
.filter((w) => {
if (w.name) {
return /.*menu/.test(w.name);
}
return false;
})
.map((w) => w.name);
menuWindows.forEach((w) => {
if (w) {
App.closeWindow(w);
}
});
};
export const openMenu = (clicked: any, event: Gdk.Event, window: string) => {
/*
* 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
* offcenter dependending on which edge of the button you click on.
* -------------
* To fix this, we take the x coordinate of the click within the button's bounds.
* If you click the left edge of a 100 width button, then the x axis will be 0
* and if you click the right edge then the x axis will be 100.
* -------------
* Then we divide the width of the button by 2 to get the center of the button and then get
* the offset by subtracting the clicked x coordinate. Then we can apply that offset
* to the x coordinate of the click relative to the screen to get the center of the
* icon click.
*/
const middleOfButton = Math.floor(clicked.get_allocated_width() / 2);
const xAxisOfButtonClick = clicked.get_pointer()[0];
const middleOffset = middleOfButton - xAxisOfButtonClick;
const clickPos = event.get_root_coords();
const adjustedXCoord = clickPos[1] + middleOffset;
const coords = [adjustedXCoord, clickPos[2]];
globalMousePos.value = coords;
closeAllMenus();
App.toggleWindow(window);
};

View File

@@ -1,62 +0,0 @@
const audio = await Service.import("audio");
import { openMenu } from "../utils.js";
import options from "options";
import { globalMousePos } from "globals.js";
const Volume = () => {
const icons = {
101: "󰕾",
66: "󰕾",
34: "󰖀",
1: "󰕿",
0: "󰝟",
};
const getIcon = () => {
const icon = Utils.merge(
[audio.speaker.bind("is_muted"), audio.speaker.bind("volume")],
(isMuted, vol) => {
return isMuted
? 0
: [101, 66, 34, 1, 0].find((threshold) => threshold <= vol * 100);
},
);
return icon.as((i) => icons[i]);
};
const volIcn = Widget.Label({
vpack: "center",
label: getIcon(),
class_name: "bar-volume_icon",
});
const volPct = Widget.Label({
vpack: "center",
label: audio.speaker.bind("volume").as((v) => ` ${Math.floor(v * 100)}%`),
class_name: "bar-volume_percentage",
});
return {
component: Widget.Box({
vpack: "center",
class_name: "volume",
children: options.bar.volume.label.bind("value").as((showLabel) => {
if (showLabel) {
return [volIcn, volPct];
}
return [volIcn];
}),
}),
isVisible: true,
boxClass: "volume",
props: {
on_primary_click: (clicked, event) => {
openMenu(clicked, event, "audiomenu");
},
},
};
};
export { Volume };

View File

@@ -0,0 +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";
import { globalMousePos } from "globals.js";
const Volume = () => {
const icons = {
101: "󰕾",
66: "󰕾",
34: "󰖀",
1: "󰕿",
0: "󰝟",
};
const getIcon = () => {
const icon = Utils.merge(
[audio.speaker.bind("is_muted"), audio.speaker.bind("volume")],
(isMuted, vol) => {
return isMuted
? 0
: [101, 66, 34, 1, 0].find((threshold) => threshold <= vol * 100);
},
);
return icon.as((i) => i !== undefined ? icons[i] : 101);
};
const volIcn = Widget.Label({
vpack: "center",
label: getIcon(),
class_name: "bar-volume_icon",
});
const volPct = Widget.Label({
vpack: "center",
label: audio.speaker.bind("volume").as((v) => ` ${Math.floor(v * 100)}%`),
class_name: "bar-volume_percentage",
});
return {
component: Widget.Box({
vpack: "center",
class_name: "volume",
children: options.bar.volume.label.bind("value").as((showLabel) => {
if (showLabel) {
return [volIcn, volPct];
}
return [volIcn];
}),
}),
isVisible: true,
boxClass: "volume",
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "audiomenu");
},
},
};
};
export { Volume };

View File

@@ -1,36 +0,0 @@
const hyprland = await Service.import("hyprland");
const filterTitle = (windowtitle) => {
const windowTitleMap = [
["kitty", "󰄛 Kitty Terminal"],
["firefox", "󰈹 Firefox"],
["microsoft-edge", "󰇩 Edge"],
["discord", " Discord"],
["org.kde.dolphin", " Dolphin"],
["plex", "󰚺 Plex"],
["steam", " Steam"],
["spotify", "󰓇 Spotify"],
["obsidian", "󱓧 Obsidian"],
["^$", "󰇄 Desktop"],
["(.+)", `󰣆 ${windowtitle.class.charAt(0).toUpperCase() + windowtitle.class.slice(1)}`],
];
const foundMatch = windowTitleMap.find((wt) =>
RegExp(wt[0]).test(windowtitle.class.toLowerCase()),
);
return foundMatch ? foundMatch[1] : windowtitle.class;
};
const ClientTitle = () => {
return {
component: Widget.Label({
class_name: "window_title",
label: hyprland.active.bind("client").as((v) => filterTitle(v)),
}),
isVisible: true,
boxClass: "windowtitle",
};
};
export { ClientTitle };

View File

@@ -0,0 +1,37 @@
const hyprland = await Service.import("hyprland");
import { ActiveClient } from 'types/service/hyprland'
const filterTitle = (windowtitle: ActiveClient) => {
const windowTitleMap = [
["kitty", "󰄛 Kitty Terminal"],
["firefox", "󰈹 Firefox"],
["microsoft-edge", "󰇩 Edge"],
["discord", " Discord"],
["org.kde.dolphin", " Dolphin"],
["plex", "󰚺 Plex"],
["steam", " Steam"],
["spotify", "󰓇 Spotify"],
["obsidian", "󱓧 Obsidian"],
["^$", "󰇄 Desktop"],
["(.+)", `󰣆 ${windowtitle.class.charAt(0).toUpperCase() + windowtitle.class.slice(1)}`],
];
const foundMatch = windowTitleMap.find((wt) =>
RegExp(wt[0]).test(windowtitle.class.toLowerCase()),
);
return foundMatch ? foundMatch[1] : windowtitle.class;
};
const ClientTitle = () => {
return {
component: Widget.Label({
class_name: "window_title",
label: hyprland.active.bind("client").as((v) => filterTitle(v)),
}),
isVisible: true,
boxClass: "windowtitle",
};
};
export { ClientTitle };

View File

@@ -1,14 +1,15 @@
const hyprland = await Service.import("hyprland");
import { WorkspaceRule, WorkspaceMap } from "lib/types/workspace";
import options from "options";
const { workspaces, monitorSpecific } = options.bar.workspaces;
function range(length, start = 1) {
function range(length: number, start = 1) {
return Array.from({ length }, (_, i) => i + start);
}
const Workspaces = (monitor = -1, ws = 8) => {
const getWorkspacesForMonitor = (curWs, wsRules) => {
const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap) => {
if (!wsRules || !Object.keys(wsRules).length) {
return true;
}
@@ -20,13 +21,13 @@ const Workspaces = (monitor = -1, ws = 8) => {
return wsRules[currentMonitorName].includes(curWs);
};
const getWorkspaceRules = () => {
const getWorkspaceRules = (): WorkspaceMap => {
try {
const rules = Utils.exec("hyprctl workspacerules -j");
const workspaceRules = {};
JSON.parse(rules).forEach((rule, index) => {
JSON.parse(rules).forEach((rule: WorkspaceRule, index: number) => {
if (Object.hasOwnProperty.call(workspaceRules, rule.monitor)) {
workspaceRules[rule.monitor].push(index + 1);
} else {
@@ -37,6 +38,7 @@ const Workspaces = (monitor = -1, ws = 8) => {
return workspaceRules;
} catch (err) {
console.error(err);
return {};
}
};

View File

@@ -143,6 +143,7 @@ export default {
light: "light-mode-symbolic",
},
weather: {
warning: "dialog-warning-symbolic",
sunny: "weather-clear-symbolic",
clear: "weather-clear-night-symbolic",
partly_cloudy: "weather-few-clouds-symbolic",

View File

@@ -1,145 +0,0 @@
const hyprland = await Service.import("hyprland");
export const Padding = (name) =>
Widget.EventBox({
hexpand: true,
vexpand: true,
can_focus: true,
child: Widget.Box(),
setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)),
});
const moveBoxToCursor = (self, fixed) => {
if (fixed) {
return;
}
globalMousePos.connect("changed", ({ value }) => {
const currentWidth = self.child.get_allocation().width;
let monWidth = hyprland.monitors[hyprland.active.monitor.id].width;
let monHeight = hyprland.monitors[hyprland.active.monitor.id].height;
// If GDK Scaling is applied, then get divide width by scaling
// to get the proper coordinates.
// Ex: On a 2860px wide monitor... if scaling is set to 2, then the right
// end of the monitor is the 1430th pixel.
const gdkScale = Utils.exec('bash -c "echo $GDK_SCALE"');
if (/^\d+(.\d+)?$/.test(gdkScale)) {
const scale = parseFloat(gdkScale);
monWidth = monWidth / scale;
monHeight = monHeight / scale;
}
// If monitor is vertical (transform = 1 || 3) swap height and width
if (hyprland.monitors[hyprland.active.monitor.id].transform % 2 !== 0) {
[monWidth, monHeight] = [monHeight, monWidth];
}
let marginRight = monWidth - currentWidth / 2;
marginRight = fixed ? marginRight - monWidth / 2 : marginRight - value[0];
let marginLeft = monWidth - currentWidth - marginRight;
const minimumMargin = 0;
if (marginRight < minimumMargin) {
marginRight = minimumMargin;
marginLeft = monWidth - currentWidth - minimumMargin;
}
if (marginLeft < minimumMargin) {
marginLeft = minimumMargin;
marginRight = monWidth - currentWidth - minimumMargin;
}
const marginTop = 45;
const marginBottom = monHeight - marginTop;
self.set_margin_left(marginLeft);
self.set_margin_right(marginRight);
self.set_margin_bottom(marginBottom);
});
};
// NOTE: We make the window visible for 2 seconds (on startup) so the child
// elements can allocat their proper dimensions.
// Otherwise the width that we rely on for menu positioning is set improperly
// for the first time we open a menu of each type.
const initRender = Variable(true);
setTimeout(() => {
initRender.value = false;
}, 2000);
export default ({
name,
child,
layout = "center",
transition,
exclusivity = "ignore",
fixed = false,
...props
}) =>
Widget.Window({
name,
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"],
child: Widget.EventBox({
class_name: "parent-event",
on_primary_click: () => App.closeWindow(name),
on_secondary_click: () => App.closeWindow(name),
child: Widget.Box({
class_name: "top-eb",
vertical: true,
children: [
Widget.EventBox({
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.set_margin_top(1);
},
}),
Widget.EventBox({
class_name: "in-eb menu-event-box",
on_primary_click: () => {
return true;
},
on_secondary_click: () => {
return true;
},
setup: (self) => {
moveBoxToCursor(self, fixed);
},
child: Widget.Box({
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",
transitionDuration: 350,
child: Widget.Box({
class_name: "dropdown-menu-container",
can_focus: true,
children: [child],
}),
}),
}),
}),
],
}),
}),
...props,
});

View File

@@ -0,0 +1,147 @@
const hyprland = await Service.import("hyprland");
import { globalMousePos } from "globals";
import { Exclusivity } from "lib/types/widget";
export const Padding = (name: string) =>
Widget.EventBox({
hexpand: true,
vexpand: true,
can_focus: true,
child: Widget.Box(),
setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)),
});
const moveBoxToCursor = (self: any, fixed: boolean) => {
if (fixed) {
return;
}
globalMousePos.connect("changed", ({ value }) => {
const currentWidth = self.child.get_allocation().width;
let monWidth = hyprland.monitors[hyprland.active.monitor.id].width;
let monHeight = hyprland.monitors[hyprland.active.monitor.id].height;
// If GDK Scaling is applied, then get divide width by scaling
// to get the proper coordinates.
// Ex: On a 2860px wide monitor... if scaling is set to 2, then the right
// end of the monitor is the 1430th pixel.
const gdkScale = Utils.exec('bash -c "echo $GDK_SCALE"');
if (/^\d+(.\d+)?$/.test(gdkScale)) {
const scale = parseFloat(gdkScale);
monWidth = monWidth / scale;
monHeight = monHeight / scale;
}
// If monitor is vertical (transform = 1 || 3) swap height and width
if (hyprland.monitors[hyprland.active.monitor.id].transform % 2 !== 0) {
[monWidth, monHeight] = [monHeight, monWidth];
}
let marginRight = monWidth - currentWidth / 2;
marginRight = fixed ? marginRight - monWidth / 2 : marginRight - value[0];
let marginLeft = monWidth - currentWidth - marginRight;
const minimumMargin = 0;
if (marginRight < minimumMargin) {
marginRight = minimumMargin;
marginLeft = monWidth - currentWidth - minimumMargin;
}
if (marginLeft < minimumMargin) {
marginLeft = minimumMargin;
marginRight = monWidth - currentWidth - minimumMargin;
}
const marginTop = 45;
const marginBottom = monHeight - marginTop;
self.set_margin_left(marginLeft);
self.set_margin_right(marginRight);
self.set_margin_bottom(marginBottom);
});
};
// NOTE: We make the window visible for 2 seconds (on startup) so the child
// elements can allocat their proper dimensions.
// Otherwise the width that we rely on for menu positioning is set improperly
// for the first time we open a menu of each type.
const initRender = Variable(true);
setTimeout(() => {
initRender.value = false;
}, 2000);
export default ({
name,
child,
layout = "center",
transition,
exclusivity = "ignore" as Exclusivity,
fixed = false,
...props
}) =>
Widget.Window({
name,
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"],
child: Widget.EventBox({
class_name: "parent-event",
on_primary_click: () => App.closeWindow(name),
on_secondary_click: () => App.closeWindow(name),
child: Widget.Box({
class_name: "top-eb",
vertical: true,
children: [
Widget.EventBox({
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.set_margin_top(1);
},
}),
Widget.EventBox({
class_name: "in-eb menu-event-box",
on_primary_click: () => {
return true;
},
on_secondary_click: () => {
return true;
},
setup: (self) => {
moveBoxToCursor(self, fixed);
},
child: Widget.Box({
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",
transitionDuration: 350,
child: Widget.Box({
class_name: "dropdown-menu-container",
can_focus: true,
children: [child],
}),
}),
}),
}),
],
}),
}),
...props,
});

View File

@@ -1,165 +0,0 @@
export const Padding = (name, opts = {}) =>
Widget.EventBox({
class_name: opts?.className || "",
hexpand: 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)),
});
const PopupRevealer = (name, child, transition = "slide_down") =>
Widget.Box(
{ css: "padding: 1px;" },
Widget.Revealer({
transition,
child: Widget.Box({
class_name: `window-content ${name}-window`,
child,
}),
transitionDuration: 200,
setup: (self) =>
self.hook(App, (_, wname, visible) => {
if (wname === name) self.reveal_child = visible;
}),
}),
);
const Layout = (name, child, transition) => ({
center: () =>
Widget.CenterBox(
{},
Padding(name),
Widget.CenterBox(
{ vertical: true },
Padding(name),
PopupRevealer(name, child, transition),
Padding(name),
),
Padding(name),
),
top: () =>
Widget.CenterBox(
{},
Padding(name),
Widget.Box(
{ vertical: true },
PopupRevealer(name, child, transition),
Padding(name),
),
Padding(name),
),
"top-right": () =>
Widget.Box(
{},
Padding(name),
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name, {
vexpand: false,
className: "event-top-padding",
}),
PopupRevealer(name, child, transition),
Padding(name),
),
),
"top-center": () =>
Widget.Box(
{},
Padding(name),
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name, {
vexpand: false,
className: "event-top-padding",
}),
PopupRevealer(name, child, transition),
Padding(name),
),
Padding(name),
),
"top-left": () =>
Widget.Box(
{},
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name, {
vexpand: false,
className: "event-top-padding",
}),
PopupRevealer(name, child, transition),
Padding(name),
),
Padding(name),
),
"bottom-left": () =>
Widget.Box(
{},
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name),
PopupRevealer(name, child, transition),
),
Padding(name),
),
"bottom-center": () =>
Widget.Box(
{},
Padding(name),
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name),
PopupRevealer(name, child, transition),
),
Padding(name),
),
"bottom-right": () =>
Widget.Box(
{},
Padding(name),
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name),
PopupRevealer(name, child, transition),
),
),
});
export default ({
name,
child,
layout = "center",
transition,
exclusivity = "ignore",
...props
}) =>
Widget.Window({
name,
class_names: [name, "popup-window"],
setup: (w) => w.keybind("Escape", () => App.closeWindow(name)),
visible: false,
keymode: "on-demand",
exclusivity,
layer: "top",
anchor: ["top", "bottom", "right", "left"],
child: Layout(name, child, transition)[layout](),
...props,
});

View File

@@ -0,0 +1,172 @@
import { Exclusivity, Transition } from "lib/types/widget";
type Opts = {
className: string
vexpand: boolean
}
export const Padding = (name: string, opts: Opts) =>
Widget.EventBox({
class_name: opts?.className || "",
hexpand: 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)),
});
const PopupRevealer = (name: string, child: any, transition = "slide_down" as Transition) =>
Widget.Box(
{ css: "padding: 1px;" },
Widget.Revealer({
transition,
child: Widget.Box({
class_name: `window-content ${name}-window`,
child,
}),
transitionDuration: 200,
setup: (self) =>
self.hook(App, (_, wname, visible) => {
if (wname === name) self.reveal_child = visible;
}),
}),
);
const Layout = (name: string, child: any, transition: Transition) => ({
center: () =>
Widget.CenterBox(
{},
Padding(name, {} as Opts),
Widget.CenterBox(
{ vertical: true },
Padding(name, {} as Opts),
PopupRevealer(name, child, transition),
Padding(name, {} as Opts),
),
Padding(name, {} as Opts),
),
top: () =>
Widget.CenterBox(
{},
Padding(name, {} as Opts),
Widget.Box(
{ vertical: true },
PopupRevealer(name, child, transition),
Padding(name, {} as Opts),
),
Padding(name, {} as Opts),
),
"top-right": () =>
Widget.Box(
{},
Padding(name, {} as Opts),
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name, {
vexpand: false,
className: "event-top-padding",
}),
PopupRevealer(name, child, transition),
Padding(name, {} as Opts),
),
),
"top-center": () =>
Widget.Box(
{},
Padding(name, {} as Opts),
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name, {
vexpand: false,
className: "event-top-padding",
}),
PopupRevealer(name, child, transition),
Padding(name, {} as Opts),
),
Padding(name, {} as Opts),
),
"top-left": () =>
Widget.Box(
{},
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name, {
vexpand: false,
className: "event-top-padding",
}),
PopupRevealer(name, child, transition),
Padding(name, {} as Opts),
),
Padding(name, {} as Opts),
),
"bottom-left": () =>
Widget.Box(
{},
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name, {} as Opts),
PopupRevealer(name, child, transition),
),
Padding(name, {} as Opts),
),
"bottom-center": () =>
Widget.Box(
{},
Padding(name, {} as Opts),
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name, {} as Opts),
PopupRevealer(name, child, transition),
),
Padding(name, {} as Opts),
),
"bottom-right": () =>
Widget.Box(
{},
Padding(name, {} as Opts),
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name, {} as Opts),
PopupRevealer(name, child, transition),
),
),
});
export default ({
name,
child,
layout = "center",
transition,
exclusivity = "ignore" as Exclusivity,
...props
}) =>
Widget.Window({
name,
class_names: [name, "popup-window"],
setup: (w) => w.keybind("Escape", () => App.closeWindow(name)),
visible: false,
keymode: "on-demand",
exclusivity,
layer: "top",
anchor: ["top", "bottom", "right", "left"],
child: Layout(name, child, transition)[layout](),
...props,
});

View File

@@ -1,58 +0,0 @@
const audio = await Service.import("audio");
const renderInputDevices = (inputDevices) => {
if (!inputDevices.length) {
return [
Widget.Box({
class_name: `menu-unfound-button input`,
child: Widget.Box({
children: [
Widget.Label({
class_name: "menu-button-name input",
label: "No input devices found...",
}),
],
}),
}),
];
}
return inputDevices.map((device) => {
return Widget.Button({
on_primary_click: () => (audio.microphone = device),
class_name: `menu-button audio input ${device}`,
child: Widget.Box({
children: [
Widget.Box({
hpack: "start",
children: [
Widget.Label({
class_name: audio.microphone
.bind("description")
.as((v) =>
device.description === v
? "menu-button-icon active input"
: "menu-button-icon input",
),
label: "",
}),
Widget.Label({
truncate: "end",
wrap: true,
class_name: audio.microphone
.bind("description")
.as((v) =>
device.description === v
? "menu-button-name active input"
: "menu-button-name input",
),
label: device.description,
}),
],
}),
],
}),
});
});
};
export { renderInputDevices };

View File

@@ -0,0 +1,65 @@
const audio = await Service.import("audio");
import { Stream } from "types/service/audio";
const renderInputDevices = (inputDevices: Stream[]) => {
if (inputDevices.length === 0) {
return [
Widget.Button({
class_name: `menu-unfound-button input`,
child: Widget.Box({
children: [
Widget.Box({
hpack: "start",
children: [
Widget.Label({
class_name: "menu-button-name input",
label: "No input devices found...",
}),
],
}),
],
}),
}),
];
}
return inputDevices.map((device) => {
return Widget.Button({
on_primary_click: () => (audio.microphone = device),
class_name: `menu-button audio input ${device}`,
child: Widget.Box({
children: [
Widget.Box({
hpack: "start",
children: [
Widget.Label({
wrap: true,
class_name: audio.microphone
.bind("description")
.as((v) =>
device.description === v
? "menu-button-icon active input"
: "menu-button-icon input",
),
label: "",
}),
Widget.Label({
truncate: "end",
wrap: true,
class_name: audio.microphone
.bind("description")
.as((v) =>
device.description === v
? "menu-button-name active input"
: "menu-button-name input",
),
label: device.description,
}),
],
}),
],
}),
});
});
};
export { renderInputDevices };

View File

@@ -1,58 +0,0 @@
const audio = await Service.import("audio");
const renderPlaybacks = (playbackDevices) => {
return playbackDevices.map((device) => {
if (device.description === "Dummy Output") {
return Widget.Box({
class_name: "menu-unfound-button playback",
child: Widget.Box({
children: [
Widget.Label({
class_name: "menu-button-name playback",
label: "No playback devices found...",
}),
],
}),
});
}
return Widget.Button({
class_name: `menu-button audio playback ${device}`,
on_primary_click: () => (audio.speaker = device),
child: Widget.Box({
children: [
Widget.Box({
hpack: "start",
children: [
Widget.Label({
truncate: "end",
wrap: true,
class_name: audio.speaker
.bind("description")
.as((v) =>
device.description === v
? "menu-button-icon active playback"
: "menu-button-icon playback",
),
label: "",
}),
Widget.Label({
class_name: audio.speaker
.bind("description")
.as((v) =>
device.description === v
? "menu-button-name active playback"
: "menu-button-name playback",
),
truncate: "end",
wrap: true,
label: device.description,
}),
],
}),
],
}),
});
});
};
export { renderPlaybacks };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,137 +0,0 @@
import { connectedControls } from "./connectedControls.js";
import { getBluetoothIcon } from "../utils.js";
const devices = (bluetooth, self) => {
return self.hook(bluetooth, () => {
if (!bluetooth.enabled) {
return (self.child = Widget.Box({
class_name: "bluetooth-items",
vertical: true,
expand: true,
vpack: "center",
hpack: "center",
children: [
Widget.Label({
class_name: "dim",
hexpand: true,
label: "Bluetooth is disabled",
}),
],
}));
}
const availableDevices = bluetooth.devices
.filter(
(btDev) => btDev.name !== null,
)
.sort((a, b) => {
if (a.connected || a.paired) {
return -1;
}
if (b.connected || b.paired) {
return 1;
}
return b.name - a.name;
});
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",
vertical: true,
expand: true,
vpack: "center",
hpack: "center",
children: [
Widget.Label({
class_name: "no-bluetooth-devices dim",
hexpand: true,
label: "No devices currently found",
}),
Widget.Label({
class_name: "search-bluetooth-label dim",
hexpand: true,
label: "Press '󰑐' to search",
}),
],
}));
}
return (self.child = Widget.Box({
vertical: true,
children: availableDevices.map((device) => {
return Widget.Box({
children: [
Widget.Button({
hexpand: true,
class_name: `bluetooth-element-item ${device}`,
on_primary_click: () => {
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",
children: [
Widget.Label({
vpack: "start",
class_name: `menu-button-icon bluetooth ${conDevNames.includes(device.address) ? "active" : ""}`,
label: getBluetoothIcon(`${device["icon-name"]}-symbolic`),
}),
Widget.Box({
vertical: true,
vpack: "center",
children: [
Widget.Label({
vpack: "center",
hpack: "start",
class_name: "menu-button-name bluetooth",
truncate: "end",
wrap: true,
label: device.alias,
}),
Widget.Revealer({
hpack: "start",
reveal_child: device.connected || device.paired,
child: Widget.Label({
hpack: "start",
class_name: "connection-status dim",
label: device.connected ? "Connected" : "Paired",
}),
}),
],
}),
],
}),
Widget.Box({
hpack: "end",
children: device.connecting
? [
Widget.Spinner({
vpack: "start",
class_name: "spinner bluetooth",
}),
]
: [],
}),
],
}),
}),
connectedControls(device, conDevNames),
],
});
}),
}));
});
};
export { devices };

View File

@@ -0,0 +1,139 @@
import { Bluetooth } from "types/service/bluetooth.js";
import Box from "types/widgets/box.js";
import { connectedControls } from "./connectedControls.js";
import { getBluetoothIcon } from "../utils.js";
const devices = (bluetooth: Bluetooth, self: Box<any, any>) => {
return self.hook(bluetooth, () => {
if (!bluetooth.enabled) {
return (self.child = Widget.Box({
class_name: "bluetooth-items",
vertical: true,
expand: true,
vpack: "center",
hpack: "center",
children: [
Widget.Label({
class_name: "dim",
hexpand: true,
label: "Bluetooth is disabled",
}),
],
}));
}
const availableDevices = bluetooth.devices
.filter(
(btDev) => btDev.name !== null,
)
.sort((a, b) => {
if (a.connected || a.paired) {
return -1;
}
if (b.connected || b.paired) {
return 1;
}
return b.name - a.name;
});
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",
vertical: true,
expand: true,
vpack: "center",
hpack: "center",
children: [
Widget.Label({
class_name: "no-bluetooth-devices dim",
hexpand: true,
label: "No devices currently found",
}),
Widget.Label({
class_name: "search-bluetooth-label dim",
hexpand: true,
label: "Press '󰑐' to search",
}),
],
}));
}
return (self.child = Widget.Box({
vertical: true,
children: availableDevices.map((device) => {
return Widget.Box({
children: [
Widget.Button({
hexpand: true,
class_name: `bluetooth-element-item ${device}`,
on_primary_click: () => {
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",
children: [
Widget.Label({
vpack: "start",
class_name: `menu-button-icon bluetooth ${conDevNames.includes(device.address) ? "active" : ""}`,
label: getBluetoothIcon(`${device["icon-name"]}-symbolic`),
}),
Widget.Box({
vertical: true,
vpack: "center",
children: [
Widget.Label({
vpack: "center",
hpack: "start",
class_name: "menu-button-name bluetooth",
truncate: "end",
wrap: true,
label: device.alias,
}),
Widget.Revealer({
hpack: "start",
reveal_child: device.connected || device.paired,
child: Widget.Label({
hpack: "start",
class_name: "connection-status dim",
label: device.connected ? "Connected" : "Paired",
}),
}),
],
}),
],
}),
Widget.Box({
hpack: "end",
children: device.connecting
? [
Widget.Spinner({
vpack: "start",
class_name: "spinner bluetooth",
}),
]
: [],
}),
],
}),
}),
connectedControls(device, conDevNames),
],
});
}),
}));
});
};
export { devices };

View File

@@ -1,27 +0,0 @@
const bluetooth = await Service.import("bluetooth");
import { label } from "./label.js";
import { devices } from "./devicelist.js";
const Devices = () => {
return Widget.Box({
class_name: "menu-section-container",
vertical: true,
children: [
label(bluetooth),
Widget.Box({
class_name: "menu-items-section",
// hscroll: 'never',
// vscroll: 'always',
child: Widget.Box({
class_name: "menu-content",
vertical: true,
setup: (self) => {
devices(bluetooth, self);
},
}),
}),
],
});
};
export { Devices };

View File

@@ -0,0 +1,25 @@
const bluetooth = await Service.import("bluetooth");
import { label } from "./label.js";
import { devices } from "./devicelist.js";
const Devices = () => {
return Widget.Box({
class_name: "menu-section-container",
vertical: true,
children: [
label(bluetooth),
Widget.Box({
class_name: "menu-items-section",
child: Widget.Box({
class_name: "menu-content",
vertical: true,
setup: (self) => {
devices(bluetooth, self);
},
}),
}),
],
});
};
export { Devices };

View File

@@ -1,74 +0,0 @@
const label = (bluetooth) => {
const searchInProgress = Variable(false);
const startRotation = () => {
searchInProgress.value = true;
setTimeout(() => {
searchInProgress.value = false;
}, 10 * 1000);
};
return Widget.Box({
class_name: "menu-label-container",
hpack: "fill",
vpack: "start",
children: [
Widget.Label({
class_name: "menu-label",
vpack: "center",
hpack: "start",
label: "Bluetooth",
}),
Widget.Box({
class_name: "controls-container",
vpack: "start",
children: [
Widget.Switch({
class_name: "menu-switch bluetooth",
hexpand: true,
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,
),
);
},
}),
Widget.Separator({
class_name: "menu-separator bluetooth",
}),
Widget.Button({
vpack: "center",
class_name: "menu-icon-button search",
on_primary_click: () => {
startRotation();
Utils.execAsync([
"bash",
"-c",
"bluetoothctl --timeout 120 scan on",
]).catch((err) => {
searchInProgress.value = false;
console.error("bluetoothctl --timeout 120 scan on", err);
});
},
child: Widget.Icon({
class_name: searchInProgress
.bind("value")
.as((v) => (v ? "spinning" : "")),
icon: "view-refresh-symbolic",
}),
}),
],
}),
],
});
};
export { label };

View File

@@ -0,0 +1,75 @@
import { Bluetooth } from "types/service/bluetooth";
const label = (bluetooth: Bluetooth) => {
const searchInProgress = Variable(false);
const startRotation = () => {
searchInProgress.value = true;
setTimeout(() => {
searchInProgress.value = false;
}, 10 * 1000);
};
return Widget.Box({
class_name: "menu-label-container",
hpack: "fill",
vpack: "start",
children: [
Widget.Label({
class_name: "menu-label",
vpack: "center",
hpack: "start",
label: "Bluetooth",
}),
Widget.Box({
class_name: "controls-container",
vpack: "start",
children: [
Widget.Switch({
class_name: "menu-switch bluetooth",
hexpand: true,
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,
),
);
},
}),
Widget.Separator({
class_name: "menu-separator bluetooth",
}),
Widget.Button({
vpack: "center",
class_name: "menu-icon-button search",
on_primary_click: () => {
startRotation();
Utils.execAsync([
"bash",
"-c",
"bluetoothctl --timeout 120 scan on",
]).catch((err) => {
searchInProgress.value = false;
console.error("bluetoothctl --timeout 120 scan on", err);
});
},
child: Widget.Icon({
class_name: searchInProgress
.bind("value")
.as((v) => (v ? "spinning" : "")),
icon: "view-refresh-symbolic",
}),
}),
],
}),
],
});
};
export { label };

View File

@@ -1,31 +0,0 @@
const getBluetoothIcon = (iconName) => {
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*", "󰕧"],
];
const foundMatch = deviceIconMap.find((icon) =>
RegExp(icon[0]).test(iconName.toLowerCase()),
);
return foundMatch ? foundMatch[1] : "󰂯";
};
export { getBluetoothIcon };

View File

@@ -0,0 +1,31 @@
const getBluetoothIcon = (iconName: 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*", "󰕧"],
];
const foundMatch = deviceIconMap.find((icon) =>
RegExp(icon[0]).test(iconName.toLowerCase()),
);
return foundMatch ? foundMatch[1] : "󰂯";
};
export { getBluetoothIcon };

View File

@@ -1,6 +1,8 @@
import { Weather } from "lib/types/weather.js";
import { Variable } from "types/variable.js";
import icons from "../../../../../icons/index.js";
export const HourlyIcon = (theWeather, getNextEpoch) => {
export const HourlyIcon = (theWeather: Variable<Weather>, getNextEpoch: any) => {
return Widget.Icon({
class_name: "hourly-weather-icon",
icon: theWeather.bind("value").as((w) => {
@@ -16,9 +18,11 @@ export const HourlyIcon = (theWeather, getNextEpoch) => {
let iconQuery = weatherAtEpoch?.condition.text
.trim()
.toLowerCase()
.replaceAll(" ", "_");
.replaceAll(" ", "_")
|| "warning"
;
if (!weatherAtEpoch?.isDay && iconQuery === "partly_cloudy") {
if (!weatherAtEpoch?.is_day && iconQuery === "partly_cloudy") {
iconQuery = "partly_cloudy_night";
}

View File

@@ -1,44 +0,0 @@
import { HourlyIcon } from "./icon/index.js";
import { HourlyTemp } from "./temperature/index.js";
import { HourlyTime } from "./time/index.js";
export const Hourly = (theWeather) => {
return Widget.Box({
vertical: false,
hexpand: true,
hpack: "fill",
class_name: "hourly-weather-container",
children: [1, 2, 3, 4].map((hoursFromNow) => {
const getNextEpoch = (wthr) => {
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",
hexpand: true,
vertical: true,
children: [
HourlyTime(theWeather, getNextEpoch),
HourlyIcon(theWeather, getNextEpoch),
HourlyTemp(theWeather, getNextEpoch),
],
});
}),
});
};

View File

@@ -0,0 +1,46 @@
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";
export const Hourly = (theWeather: Variable<Weather>) => {
return Widget.Box({
vertical: false,
hexpand: true,
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",
hexpand: true,
vertical: true,
children: [
HourlyTime(theWeather, getNextEpoch),
HourlyIcon(theWeather, getNextEpoch),
HourlyTemp(theWeather, getNextEpoch),
],
});
}),
});
};

View File

@@ -1,27 +0,0 @@
import options from "options";
const { unit } = options.menus.clock.weather;
export const HourlyTemp = (theWeather, getNextEpoch) => {
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 "-";
}
const nextEpoch = getNextEpoch(wthr);
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`;
},
),
});
};

View File

@@ -0,0 +1,29 @@
import { Weather } from "lib/types/weather";
import { Variable } from "types/variable";
import options from "options";
const { unit } = options.menus.clock.weather;
export const HourlyTemp = (theWeather: Variable<Weather>, getNextEpoch: any) => {
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 "-";
}
const nextEpoch = getNextEpoch(wthr);
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`;
},
),
});
};

View File

@@ -1,18 +0,0 @@
export const HourlyTime = (theWeather, getNextEpoch) => {
return Widget.Label({
class_name: "hourly-weather-time",
label: theWeather.bind("value").as((w) => {
if (!Object.keys(w).length) {
return "-";
}
const nextEpoch = getNextEpoch(w);
const dateAtEpoch = new Date(nextEpoch * 1000);
let hours = dateAtEpoch.getHours();
const ampm = hours >= 12 ? "PM" : "AM";
hours = hours % 12 || 12;
return `${hours}${ampm}`;
}),
});
};

View File

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

View File

@@ -1,25 +0,0 @@
import icons from "../../../../icons/index.js";
export const TodayIcon = (theWeather) => {
return Widget.Box({
vpack: "center",
hpack: "start",
class_name: "calendar-menu-weather today icon container",
children: [
Widget.Icon({
class_name: "calendar-menu-weather today icon",
icon: theWeather.bind("value").as((v) => {
let iconQuery = v.current.condition.text
.trim()
.toLowerCase()
.replaceAll(" ", "_");
if (!v.current.isDay && iconQuery === "partly_cloudy") {
iconQuery = "partly_cloudy_night";
}
return icons.weather[iconQuery];
}),
}),
],
});
};

View File

@@ -0,0 +1,27 @@
import { Weather } from "lib/types/weather";
import { Variable } from "types/variable";
import icons from "../../../../icons/index.js";
export const TodayIcon = (theWeather: Variable<Weather>) => {
return Widget.Box({
vpack: "center",
hpack: "start",
class_name: "calendar-menu-weather today icon container",
children: [
Widget.Icon({
class_name: "calendar-menu-weather today icon",
icon: theWeather.bind("value").as((v) => {
let iconQuery = v.current.condition.text
.trim()
.toLowerCase()
.replaceAll(" ", "_");
if (!v.current.is_day && iconQuery === "partly_cloudy") {
iconQuery = "partly_cloudy_night";
}
return icons.weather[iconQuery];
}),
}),
],
});
};

View File

@@ -3,41 +3,12 @@ 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 { Weather } from "lib/types/weather.js";
import { DEFAULT_WEATHER } from "lib/types/defaults/weather.js";
const { key, interval, location } = options.menus.clock.weather;
const defaultWeather = {
location: {
localtime_epoch: 1719471600,
},
current: {
temp_f: 0,
wind_mph: 0,
condition: {
text: "Clear",
},
},
forecast: {
forecastday: [
{
day: {
daily_chance_of_rain: 0,
},
hour: [
{
time_epoch: 1719471600,
temp_f: 0,
condition: {
text: "Clear",
},
},
],
},
],
},
};
const theWeather = Variable(defaultWeather);
const theWeather = Variable<Weather>(DEFAULT_WEATHER);
const WeatherWidget = () => {
return Widget.Box({
@@ -64,7 +35,7 @@ const WeatherWidget = () => {
})
.catch((err) => {
console.error(`Failed to fetch weather: ${err}`);
theWeather.value = defaultWeather;
theWeather.value = DEFAULT_WEATHER;
});
});
},

View File

@@ -1,52 +0,0 @@
import options from "options";
const { unit } = options.menus.clock.weather;
export const TodayStats = (theWeather) => {
return Widget.Box({
class_name: "calendar-menu-weather today stats container",
hpack: "end",
vpack: "center",
vertical: true,
children: [
Widget.Box({
class_name: "weather wind",
children: [
Widget.Label({
class_name: "weather wind icon",
label: "",
}),
Widget.Label({
class_name: "weather wind label",
label: Utils.merge(
[theWeather.bind("value"), unit.bind("value")],
(wthr, unt) => {
if (unt === "imperial") {
return `${Math.floor(wthr.current.wind_mph)} mph`;
}
return `${Math.floor(wthr.current.wind_kph)} kph`;
},
),
}),
],
}),
Widget.Box({
class_name: "weather precip",
children: [
Widget.Label({
class_name: "weather precip icon",
label: "",
}),
Widget.Label({
class_name: "weather precip label",
label: theWeather
.bind("value")
.as(
(v) => `${v.forecast.forecastday[0].day.daily_chance_of_rain}%`,
),
}),
],
}),
],
});
};

View File

@@ -0,0 +1,55 @@
import { Weather } from "lib/types/weather";
import { Variable } from "types/variable";
import options from "options";
import { Unit } from "lib/types/options";
const { unit } = options.menus.clock.weather;
export const TodayStats = (theWeather: Variable<Weather>) => {
return Widget.Box({
class_name: "calendar-menu-weather today stats container",
hpack: "end",
vpack: "center",
vertical: true,
children: [
Widget.Box({
class_name: "weather wind",
children: [
Widget.Label({
class_name: "weather wind icon",
label: "",
}),
Widget.Label({
class_name: "weather wind label",
label: Utils.merge(
[theWeather.bind("value"), unit.bind("value")],
(wthr: Weather, unt: Unit) => {
if (unt === "imperial") {
return `${Math.floor(wthr.current.wind_mph)} mph`;
}
return `${Math.floor(wthr.current.wind_kph)} kph`;
},
),
}),
],
}),
Widget.Box({
class_name: "weather precip",
children: [
Widget.Label({
class_name: "weather precip icon",
label: "",
}),
Widget.Label({
class_name: "weather precip label",
label: theWeather
.bind("value")
.as(
(v) => `${v.forecast.forecastday[0].day.daily_chance_of_rain}%`,
),
}),
],
}),
],
});
};

View File

@@ -1,92 +0,0 @@
import options from "options";
const { unit } = options.menus.clock.weather;
export const TodayTemperature = (theWeather) => {
const getIcon = (fahren) => {
const icons = {
100: "",
75: "",
50: "",
25: "",
0: "",
};
const colors = {
100: "weather-color red",
75: "weather-color orange",
50: "weather-color lavender",
25: "weather-color blue",
0: "weather-color sky",
};
const threshold =
fahren < 0
? 0
: [100, 75, 50, 25, 0].find((threshold) => threshold <= fahren);
return {
icon: icons[threshold],
color: colors[threshold],
};
};
return Widget.Box({
hpack: "center",
vpack: "center",
vertical: true,
children: [
Widget.Box({
hexpand: true,
vpack: "center",
class_name: "calendar-menu-weather today temp container",
vertical: false,
children: [
Widget.Box({
hexpand: true,
hpack: "center",
children: [
Widget.Label({
class_name: "calendar-menu-weather today temp label",
label: Utils.merge(
[theWeather.bind("value"), unit.bind("value")],
(wthr, unt) => {
if (unt === "imperial") {
return `${Math.ceil(wthr.current.temp_f)}° F`;
} else {
return `${Math.ceil(wthr.current.temp_c)}° C`;
}
},
),
}),
Widget.Label({
class_name: theWeather
.bind("value")
.as(
(v) =>
`calendar-menu-weather today temp label icon ${getIcon(Math.ceil(v.current.temp_f)).color}`,
),
label: theWeather
.bind("value")
.as((v) => getIcon(Math.ceil(v.current.temp_f)).icon),
}),
],
}),
],
}),
Widget.Box({
hpack: "center",
child: Widget.Label({
max_width_chars: 17,
truncate: "end",
lines: 2,
class_name: theWeather
.bind("value")
.as(
(v) =>
`calendar-menu-weather today condition label ${getIcon(Math.ceil(v.current.temp_f)).color}`,
),
label: theWeather.bind("value").as((v) => v.current.condition.text),
}),
}),
],
});
};

View File

@@ -0,0 +1,94 @@
import { Weather } from "lib/types/weather";
import { Variable } from "types/variable";
import options from "options";
const { unit } = options.menus.clock.weather;
export const TodayTemperature = (theWeather: Variable<Weather>) => {
const getIcon = (fahren: number) => {
const icons = {
100: "",
75: "",
50: "",
25: "",
0: "",
};
const colors = {
100: "weather-color red",
75: "weather-color orange",
50: "weather-color lavender",
25: "weather-color blue",
0: "weather-color sky",
};
const threshold =
fahren < 0
? 0
: [100, 75, 50, 25, 0].find((threshold) => threshold <= fahren);
return {
icon: icons[threshold || 50],
color: colors[threshold || 50],
};
};
return Widget.Box({
hpack: "center",
vpack: "center",
vertical: true,
children: [
Widget.Box({
hexpand: true,
vpack: "center",
class_name: "calendar-menu-weather today temp container",
vertical: false,
children: [
Widget.Box({
hexpand: true,
hpack: "center",
children: [
Widget.Label({
class_name: "calendar-menu-weather today temp label",
label: Utils.merge(
[theWeather.bind("value"), unit.bind("value")],
(wthr, unt) => {
if (unt === "imperial") {
return `${Math.ceil(wthr.current.temp_f)}° F`;
} else {
return `${Math.ceil(wthr.current.temp_c)}° C`;
}
},
),
}),
Widget.Label({
class_name: theWeather
.bind("value")
.as(
(v) =>
`calendar-menu-weather today temp label icon ${getIcon(Math.ceil(v.current.temp_f)).color}`,
),
label: theWeather
.bind("value")
.as((v) => getIcon(Math.ceil(v.current.temp_f)).icon),
}),
],
}),
],
}),
Widget.Box({
hpack: "center",
child: Widget.Label({
max_width_chars: 17,
truncate: "end",
lines: 2,
class_name: theWeather
.bind("value")
.as(
(v) =>
`calendar-menu-weather today condition label ${getIcon(Math.ceil(v.current.temp_f)).color}`,
),
label: theWeather.bind("value").as((v) => v.current.condition.text),
}),
}),
],
});
};

View File

@@ -1,133 +0,0 @@
import GLib from "gi://GLib";
import options from "options";
const { left, right } = options.menus.dashboard.directories;
const Directories = () => {
return Widget.Box({
class_name: "dashboard-card directories-container",
vpack: "fill",
hpack: "fill",
expand: true,
children: [
Widget.Box({
vertical: true,
expand: true,
class_name: "section right",
children: [
Widget.Button({
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);
};
}),
child: Widget.Label({
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);
};
}),
child: Widget.Label({
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);
};
}),
child: Widget.Label({
hpack: "start",
label: left.directory3.label.bind("value"),
}),
}),
],
}),
Widget.Box({
vertical: true,
expand: true,
class_name: "section left",
children: [
Widget.Button({
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);
};
}),
child: Widget.Label({
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);
};
}),
child: Widget.Label({
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);
};
}),
child: Widget.Label({
hpack: "start",
label: right.directory3.label.bind("value"),
}),
}),
],
}),
],
});
};
export { Directories };

View File

@@ -0,0 +1,132 @@
import options from "options";
const { left, right } = options.menus.dashboard.directories;
const Directories = () => {
return Widget.Box({
class_name: "dashboard-card directories-container",
vpack: "fill",
hpack: "fill",
expand: true,
children: [
Widget.Box({
vertical: true,
expand: true,
class_name: "section right",
children: [
Widget.Button({
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);
};
}),
child: Widget.Label({
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);
};
}),
child: Widget.Label({
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);
};
}),
child: Widget.Label({
hpack: "start",
label: left.directory3.label.bind("value"),
}),
}),
],
}),
Widget.Box({
vertical: true,
expand: true,
class_name: "section left",
children: [
Widget.Button({
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);
};
}),
child: Widget.Label({
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);
};
}),
child: Widget.Label({
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);
};
}),
child: Widget.Label({
hpack: "start",
label: right.directory3.label.bind("value"),
}),
}),
],
}),
],
});
};
export { Directories };

View File

@@ -1,79 +0,0 @@
import icons from "../../../icons/index.js";
import powermenu from "../../power/helpers/actions.js";
import options from "options";
const { image, name } = options.menus.dashboard.powermenu.avatar;
const Profile = () => {
const handleClick = (action) => {
App.closeWindow("dashboardmenu");
return powermenu.action(action);
};
return Widget.Box({
class_name: "profiles-container",
hpack: "fill",
hexpand: true,
children: [
Widget.Box({
class_name: "profile-picture-container dashboard-card",
hexpand: true,
vertical: true,
children: [
Widget.Icon({
hpack: "center",
class_name: "profile-picture",
icon: image.bind("value"),
}),
Widget.Label({
hpack: "center",
class_name: "profile-name",
label: name.bind("value").as((v) => {
if (v === "system") {
return Utils.exec("bash -c whoami");
}
return v;
}),
}),
],
}),
Widget.Box({
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",
vexpand: true,
child: Widget.Icon(icons.powermenu.shutdown),
}),
Widget.Button({
class_name: "dashboard-button restart",
on_clicked: () => handleClick("reboot"),
tooltip_text: "Restart",
vexpand: true,
child: Widget.Icon(icons.powermenu.reboot),
}),
Widget.Button({
class_name: "dashboard-button lock",
on_clicked: () => handleClick("logout"),
tooltip_text: "Log Out",
vexpand: true,
child: Widget.Icon(icons.powermenu.logout),
}),
Widget.Button({
class_name: "dashboard-button sleep",
on_clicked: () => handleClick("sleep"),
tooltip_text: "Sleep",
vexpand: true,
child: Widget.Icon(icons.powermenu.sleep),
}),
],
}),
],
});
};
export { Profile };

View File

@@ -0,0 +1,80 @@
import icons from "../../../icons/index.js";
import powermenu from "../../power/helpers/actions.js";
import { PowerOptions } from "lib/types/options.js";
import options from "options";
const { image, name } = options.menus.dashboard.powermenu.avatar;
const Profile = () => {
const handleClick = (action: PowerOptions) => {
App.closeWindow("dashboardmenu");
return powermenu.action(action);
};
return Widget.Box({
class_name: "profiles-container",
hpack: "fill",
hexpand: true,
children: [
Widget.Box({
class_name: "profile-picture-container dashboard-card",
hexpand: true,
vertical: true,
children: [
Widget.Icon({
hpack: "center",
class_name: "profile-picture",
icon: image.bind("value"),
}),
Widget.Label({
hpack: "center",
class_name: "profile-name",
label: name.bind("value").as((v) => {
if (v === "system") {
return Utils.exec("bash -c whoami");
}
return v;
}),
}),
],
}),
Widget.Box({
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",
vexpand: true,
child: Widget.Icon(icons.powermenu.shutdown),
}),
Widget.Button({
class_name: "dashboard-button restart",
on_clicked: () => handleClick("reboot"),
tooltip_text: "Restart",
vexpand: true,
child: Widget.Icon(icons.powermenu.reboot),
}),
Widget.Button({
class_name: "dashboard-button lock",
on_clicked: () => handleClick("logout"),
tooltip_text: "Log Out",
vexpand: true,
child: Widget.Icon(icons.powermenu.logout),
}),
Widget.Button({
class_name: "dashboard-button sleep",
on_clicked: () => handleClick("sleep"),
tooltip_text: "Sleep",
vexpand: true,
child: Widget.Icon(icons.powermenu.sleep),
}),
],
}),
],
});
};
export { Profile };

View File

@@ -16,16 +16,12 @@ const Shortcuts = () => {
},
],
});
const handleClick = (action, resolver, tOut = 250) => {
const handleClick = (action: any, tOut: number = 250) => {
App.closeWindow("dashboardmenu");
setTimeout(() => {
Utils.execAsync(action)
.then((res) => {
if (typeof resolver === "function") {
return resolver(res);
}
return res;
})
.catch((err) => err);

View File

@@ -1,11 +1,12 @@
import options from "options";
import { GPU_Stat } from "lib/types/gpustat";
const { terminal } = options;
const Stats = () => {
const divide = ([total, free]) => free / total;
const formatSizeInGB = (sizeInKB) =>
const formatSizeInGB = (sizeInKB: number) =>
Number((sizeInKB / 1024 ** 2).toFixed(2));
const cpu = Variable(0, {
@@ -73,8 +74,10 @@ const Stats = () => {
const totalGpu = 100;
const usedGpu =
data.gpus.reduce((acc, gpu) => acc + gpu["utilization.gpu"], 0) /
data.gpus.length;
data.gpus.reduce((acc: number, gpu: GPU_Stat) => {
return acc + gpu["utilization.gpu"]
}, 0) / data.gpus.length;
return divide([totalGpu, usedGpu]);
} catch (e) {

View File

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

View File

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

View File

@@ -1,25 +0,0 @@
import DropdownMenu from "../DropdownMenu.js";
import { EnergyProfiles } from "./profiles/index.js";
import { Brightness } from "./brightness/index.js";
export default () => {
return DropdownMenu({
name: "energymenu",
transition: "crossfade",
child: Widget.Box({
class_name: "menu-items energy",
hpack: "fill",
hexpand: true,
child: Widget.Box({
vertical: true,
hpack: "fill",
hexpand: true,
class_name: "menu-items-container energy",
children: [
Brightness(),
EnergyProfiles(),
],
}),
}),
});
};

View File

@@ -0,0 +1,25 @@
import DropdownMenu from "../DropdownMenu.js";
import { EnergyProfiles } from "./profiles/index.js";
import { Brightness } from "./brightness/index.js";
export default () => {
return DropdownMenu({
name: "energymenu",
transition: "crossfade",
child: Widget.Box({
class_name: "menu-items energy",
hpack: "fill",
hexpand: true,
child: Widget.Box({
vertical: true,
hpack: "fill",
hexpand: true,
class_name: "menu-items-container energy",
children: [
Brightness(),
EnergyProfiles(),
],
}),
}),
});
};

View File

@@ -1,58 +0,0 @@
const powerProfiles = await Service.import("powerprofiles");
import icons from "../../../icons/index.js";
const EnergyProfiles = () => {
return Widget.Box({
class_name: "menu-section-container energy",
vertical: true,
children: [
Widget.Box({
class_name: "menu-label-container",
hpack: "fill",
child: Widget.Label({
class_name: "menu-label",
hexpand: true,
hpack: "start",
label: "Power Profile",
}),
}),
Widget.Box({
class_name: "menu-items-section",
vpack: "fill",
vexpand: true,
vertical: true,
children: powerProfiles.bind("profiles").as((profiles) => {
return profiles.map((prof) => {
const ProfileLabels = {
"power-saver": "Power Saver",
balanced: "Balanced",
performance: "Performance",
};
return Widget.Button({
on_primary_click: () => {
powerProfiles.active_profile = prof.Profile;
},
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",
icon: icons.powerprofile[prof.Profile],
}),
Widget.Label({
class_name: "power-profile-label",
label: ProfileLabels[prof.Profile],
}),
],
}),
});
});
}),
}),
],
});
};
export { EnergyProfiles };

View File

@@ -0,0 +1,58 @@
const powerProfiles = await Service.import("powerprofiles");
import icons from "../../../icons/index.js";
const EnergyProfiles = () => {
return Widget.Box({
class_name: "menu-section-container energy",
vertical: true,
children: [
Widget.Box({
class_name: "menu-label-container",
hpack: "fill",
child: Widget.Label({
class_name: "menu-label",
hexpand: true,
hpack: "start",
label: "Power Profile",
}),
}),
Widget.Box({
class_name: "menu-items-section",
vpack: "fill",
vexpand: true,
vertical: true,
children: powerProfiles.bind("profiles").as((profiles) => {
return profiles.map((prof) => {
const ProfileLabels = {
"power-saver": "Power Saver",
balanced: "Balanced",
performance: "Performance",
};
return Widget.Button({
on_primary_click: () => {
powerProfiles.active_profile = prof.Profile;
},
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",
icon: icons.powerprofile[prof.Profile],
}),
Widget.Label({
class_name: "power-profile-label",
label: ProfileLabels[prof.Profile],
}),
],
}),
});
});
}),
}),
],
});
};
export { EnergyProfiles };

View File

@@ -10,14 +10,14 @@ import EnergyMenu from "./energy/index.js";
import DashboardMenu from "./dashboard/index.js";
export default [
PowerMenu(),
Verification(),
AudioMenu(),
NetworkMenu(),
BluetoothMenu(),
MediaMenu(),
NotificationsMenu(),
CalendarMenu(),
EnergyMenu(),
DashboardMenu(),
PowerMenu(),
Verification(),
AudioMenu(),
NetworkMenu(),
BluetoothMenu(),
MediaMenu(),
NotificationsMenu(),
CalendarMenu(),
EnergyMenu(),
DashboardMenu(),
];

View File

@@ -1,67 +0,0 @@
const media = await Service.import("mpris");
const Bar = (getPlayerInfo) => {
return Widget.Box({
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",
draw_value: false,
on_change: ({ value }) => {
const foundPlayer = getPlayerInfo(media);
if (foundPlayer === undefined) {
return;
}
return (foundPlayer.position = value * foundPlayer.length);
},
setup: (self) => {
const update = () => {
const foundPlayer = getPlayerInfo(media);
if (foundPlayer !== undefined) {
const value = foundPlayer.position / foundPlayer.length;
self.value = value > 0 ? value : 0;
} else {
self.value = 0;
}
};
self.hook(media, update);
self.poll(1000, update);
function updateTooltip() {
const foundPlayer = getPlayerInfo(media);
if (foundPlayer === undefined) {
return self.tooltip_text = '00:00'
}
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
) {
// 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}`;
} else {
self.tooltip_text = `00:00`;
}
}
self.poll(1000, updateTooltip);
self.hook(media, updateTooltip);
},
}),
}),
],
});
};
export { Bar };

View File

@@ -0,0 +1,66 @@
const media = await Service.import("mpris");
const Bar = (getPlayerInfo: Function) => {
return Widget.Box({
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",
draw_value: false,
on_change: ({ value }) => {
const foundPlayer = getPlayerInfo(media);
if (foundPlayer === undefined) {
return;
}
return (foundPlayer.position = value * foundPlayer.length);
},
setup: (self) => {
const update = () => {
const foundPlayer = getPlayerInfo(media);
if (foundPlayer !== undefined) {
const value = foundPlayer.position / foundPlayer.length;
self.value = value > 0 ? value : 0;
} else {
self.value = 0;
}
};
self.hook(media, update);
self.poll(1000, update);
function updateTooltip() {
const foundPlayer = getPlayerInfo(media);
if (foundPlayer === undefined) {
return self.tooltip_text = '00:00'
}
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
) {
// 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}`;
} else {
self.tooltip_text = `00:00`;
}
}
self.poll(1000, updateTooltip);
self.hook(media, updateTooltip);
},
}),
}),
],
});
};
export { Bar };

View File

@@ -1,187 +0,0 @@
import icons from "../../../icons/index.js";
const media = await Service.import("mpris");
const Controls = (getPlayerInfo) => {
const isLoopActive = (player) => {
return player["loop-status"] !== null &&
["track", "playlist"].includes(player["loop-status"].toLowerCase())
? "active"
: "";
};
const isShuffleActive = (player) => {
return player["shuffle-status"] !== null && player["shuffle-status"]
? "active"
: "";
};
return Widget.Box({
class_name: "media-indicator-current-player-controls",
vertical: true,
children: [
Widget.Box({
class_name: "media-indicator-current-controls",
hpack: "center",
children: [
Widget.Box({
class_name: "media-indicator-control shuffle",
children: [
Widget.Button({
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";
return;
}
self.tooltip_text =
foundPlayer.shuffle_status !== null
? foundPlayer.shuffle_status
? "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"}`;
});
},
child: Widget.Icon(icons.mpris.shuffle["enabled"]),
}),
],
}),
Widget.Box({
children: [
Widget.Button({
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";
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"}`;
});
},
}),
],
}),
Widget.Box({
children: [
Widget.Button({
hpack: "center",
setup: (self) => {
self.hook(media, () => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
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"}`;
});
},
child: Widget.Icon({
icon: Utils.watch(
icons.mpris.paused,
media,
"changed",
() => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
return icons.mpris["paused"];
}
return icons.mpris[
foundPlayer.play_back_status.toLowerCase()
];
},
),
}),
}),
],
}),
Widget.Box({
class_name: `media-indicator-control next`,
children: [
Widget.Button({
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";
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"}`;
});
},
}),
],
}),
Widget.Box({
class_name: "media-indicator-control loop",
children: [
Widget.Button({
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";
return;
}
self.tooltip_text =
foundPlayer.loop_status !== null
? foundPlayer.loop_status
? "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"}`;
});
},
child: Widget.Icon({
setup: (self) => {
self.hook(media, () => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
self.icon = icons.mpris.loop["none"];
return;
}
self.icon =
foundPlayer.loop_status === null
? icons.mpris.loop["none"]
: icons.mpris.loop[
foundPlayer.loop_status?.toLowerCase()
];
});
},
}),
}),
],
}),
],
}),
],
});
};
export { Controls };

View File

@@ -0,0 +1,188 @@
import { MprisPlayer } from "types/service/mpris.js";
import icons from "../../../icons/index.js";
const media = await Service.import("mpris");
const Controls = (getPlayerInfo: Function) => {
const isLoopActive = (player: MprisPlayer) => {
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"
: "";
};
return Widget.Box({
class_name: "media-indicator-current-player-controls",
vertical: true,
children: [
Widget.Box({
class_name: "media-indicator-current-controls",
hpack: "center",
children: [
Widget.Box({
class_name: "media-indicator-control shuffle",
children: [
Widget.Button({
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";
return;
}
self.tooltip_text =
foundPlayer.shuffle_status !== null
? foundPlayer.shuffle_status
? "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"}`;
});
},
child: Widget.Icon(icons.mpris.shuffle["enabled"]),
}),
],
}),
Widget.Box({
children: [
Widget.Button({
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";
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"}`;
});
},
}),
],
}),
Widget.Box({
children: [
Widget.Button({
hpack: "center",
setup: (self) => {
self.hook(media, () => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
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"}`;
});
},
child: Widget.Icon({
icon: Utils.watch(
icons.mpris.paused,
media,
"changed",
() => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
return icons.mpris["paused"];
}
return icons.mpris[
foundPlayer.play_back_status.toLowerCase()
];
},
),
}),
}),
],
}),
Widget.Box({
class_name: `media-indicator-control next`,
children: [
Widget.Button({
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";
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"}`;
});
},
}),
],
}),
Widget.Box({
class_name: "media-indicator-control loop",
children: [
Widget.Button({
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";
return;
}
self.tooltip_text =
foundPlayer.loop_status !== null
? foundPlayer.loop_status
? "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"}`;
});
},
child: Widget.Icon({
setup: (self) => {
self.hook(media, () => {
const foundPlayer = getPlayerInfo();
if (foundPlayer === undefined) {
self.icon = icons.mpris.loop["none"];
return;
}
self.icon =
foundPlayer.loop_status === null
? icons.mpris.loop["none"]
: icons.mpris.loop[
foundPlayer.loop_status?.toLowerCase()
];
});
},
}),
}),
],
}),
],
}),
],
});
};
export { Controls };

View File

@@ -1,85 +0,0 @@
const media = await Service.import("mpris");
const MediaInfo = (getPlayerInfo) => {
return Widget.Box({
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",
children: [
Widget.Label({
truncate: "end",
max_width_chars: 31,
wrap: true,
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");
});
},
}),
],
}),
Widget.Box({
class_name: "media-indicator-current-song-author",
hpack: "center",
children: [
Widget.Label({
truncate: "end",
wrap: true,
max_width_chars: 35,
class_name: "media-indicator-current-song-author-label",
setup: (self) => {
self.hook(media, () => {
const curPlayer = getPlayerInfo();
const makeArtistList = (trackArtists) => {
if (trackArtists.length === 1 && !trackArtists[0].length) {
return "-----";
}
return trackArtists.join(", ");
};
return (self.label =
curPlayer !== undefined && curPlayer["track-artists"].length
? makeArtistList(curPlayer["track-artists"])
: "-----");
});
},
}),
],
}),
Widget.Box({
class_name: "media-indicator-current-song-album",
hpack: "center",
children: [
Widget.Label({
truncate: "end",
wrap: true,
max_width_chars: 40,
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"]
: "---");
});
},
}),
],
}),
],
});
};
export { MediaInfo };

View File

@@ -0,0 +1,85 @@
const media = await Service.import("mpris");
const MediaInfo = (getPlayerInfo: Function) => {
return Widget.Box({
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",
children: [
Widget.Label({
truncate: "end",
max_width_chars: 31,
wrap: true,
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");
});
},
}),
],
}),
Widget.Box({
class_name: "media-indicator-current-song-author",
hpack: "center",
children: [
Widget.Label({
truncate: "end",
wrap: true,
max_width_chars: 35,
class_name: "media-indicator-current-song-author-label",
setup: (self) => {
self.hook(media, () => {
const curPlayer = getPlayerInfo();
const makeArtistList = (trackArtists: string[]) => {
if (trackArtists.length === 1 && !trackArtists[0].length) {
return "-----";
}
return trackArtists.join(", ");
};
return (self.label =
curPlayer !== undefined && curPlayer["track-artists"].length
? makeArtistList(curPlayer["track-artists"])
: "-----");
});
},
}),
],
}),
Widget.Box({
class_name: "media-indicator-current-song-album",
hpack: "center",
children: [
Widget.Label({
truncate: "end",
wrap: true,
max_width_chars: 40,
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"]
: "---");
});
},
}),
],
}),
],
});
};
export { MediaInfo };

Some files were not shown because too many files have changed in this diff Show More