Converted a significant amount of files from js to ts.
This commit is contained in:
@@ -1,64 +1,65 @@
|
||||
const hyprland = await Service.import("hyprland");
|
||||
import { globalMousePos } from "globals";
|
||||
|
||||
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)),
|
||||
});
|
||||
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 (fixed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
globalMousePos.connect("changed", ({ value }) => {
|
||||
const currentWidth = self.child.get_allocation().width;
|
||||
|
||||
let marginRight = monWidth - currentWidth / 2;
|
||||
marginRight = fixed ? marginRight - monWidth / 2 : marginRight - value[0];
|
||||
let marginLeft = monWidth - currentWidth - marginRight;
|
||||
let monWidth = hyprland.monitors[hyprland.active.monitor.id].width;
|
||||
let monHeight = hyprland.monitors[hyprland.active.monitor.id].height;
|
||||
|
||||
const minimumMargin = 0;
|
||||
// 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 (marginRight < minimumMargin) {
|
||||
marginRight = minimumMargin;
|
||||
marginLeft = monWidth - currentWidth - minimumMargin;
|
||||
}
|
||||
if (/^\d+(.\d+)?$/.test(gdkScale)) {
|
||||
const scale = parseFloat(gdkScale);
|
||||
monWidth = monWidth / scale;
|
||||
monHeight = monHeight / scale;
|
||||
}
|
||||
|
||||
if (marginLeft < minimumMargin) {
|
||||
marginLeft = minimumMargin;
|
||||
marginRight = monWidth - currentWidth - minimumMargin;
|
||||
}
|
||||
// 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];
|
||||
}
|
||||
|
||||
const marginTop = 45;
|
||||
const marginBottom = monHeight - marginTop;
|
||||
self.set_margin_left(marginLeft);
|
||||
self.set_margin_right(marginRight);
|
||||
self.set_margin_bottom(marginBottom);
|
||||
});
|
||||
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
|
||||
@@ -68,78 +69,78 @@ const moveBoxToCursor = (self, fixed) => {
|
||||
const initRender = Variable(true);
|
||||
|
||||
setTimeout(() => {
|
||||
initRender.value = false;
|
||||
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,
|
||||
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: "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],
|
||||
}),
|
||||
}),
|
||||
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,
|
||||
});
|
||||
}),
|
||||
...props,
|
||||
});
|
||||
|
||||
@@ -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 };
|
||||
65
modules/menus/audio/available/InputDevices.ts
Normal file
65
modules/menus/audio/available/InputDevices.ts
Normal 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 };
|
||||
@@ -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 };
|
||||
59
modules/menus/audio/available/PlaybackDevices.ts
Normal file
59
modules/menus/audio/available/PlaybackDevices.ts
Normal 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 };
|
||||
@@ -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 };
|
||||
75
modules/menus/audio/available/index.ts
Normal file
75
modules/menus/audio/available/index.ts
Normal 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 };
|
||||
@@ -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 };
|
||||
78
modules/menus/bluetooth/devices/connectedControls.ts
Normal file
78
modules/menus/bluetooth/devices/connectedControls.ts
Normal 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 };
|
||||
@@ -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 };
|
||||
139
modules/menus/bluetooth/devices/devicelist.ts
Normal file
139
modules/menus/bluetooth/devices/devicelist.ts
Normal 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 };
|
||||
@@ -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 };
|
||||
25
modules/menus/bluetooth/devices/index.ts
Normal file
25
modules/menus/bluetooth/devices/index.ts
Normal 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 };
|
||||
@@ -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 };
|
||||
75
modules/menus/bluetooth/devices/label.ts
Normal file
75
modules/menus/bluetooth/devices/label.ts
Normal 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 };
|
||||
@@ -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 };
|
||||
31
modules/menus/bluetooth/utils.ts
Normal file
31
modules/menus/bluetooth/utils.ts
Normal 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 };
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
});
|
||||
}),
|
||||
});
|
||||
};
|
||||
46
modules/menus/calendar/weather/hourly/index.ts
Normal file
46
modules/menus/calendar/weather/hourly/index.ts
Normal 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),
|
||||
],
|
||||
});
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -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`;
|
||||
},
|
||||
),
|
||||
});
|
||||
};
|
||||
29
modules/menus/calendar/weather/hourly/temperature/index.ts
Normal file
29
modules/menus/calendar/weather/hourly/temperature/index.ts
Normal 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`;
|
||||
},
|
||||
),
|
||||
});
|
||||
};
|
||||
@@ -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}`;
|
||||
}),
|
||||
});
|
||||
};
|
||||
21
modules/menus/calendar/weather/hourly/time/index.ts
Normal file
21
modules/menus/calendar/weather/hourly/time/index.ts
Normal 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}`;
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -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];
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
27
modules/menus/calendar/weather/icon/index.ts
Normal file
27
modules/menus/calendar/weather/icon/index.ts
Normal 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];
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
import options from "options";
|
||||
import { TodayIcon } from "./icon/index.js";
|
||||
import { TodayStats } from "./stats/index.js";
|
||||
import { TodayTemperature } from "./temperature/index.js";
|
||||
import { Hourly } from "./hourly/index.js";
|
||||
|
||||
const { key, interval } = 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 WeatherWidget = () => {
|
||||
return Widget.Box({
|
||||
class_name: "calendar-menu-item-container weather",
|
||||
child: Widget.Box({
|
||||
class_name: "weather-container-box",
|
||||
setup: (self) => {
|
||||
Utils.merge(
|
||||
[key.bind("value"), interval.bind("value")],
|
||||
(weatherKey, weatherInterval) => {
|
||||
Utils.interval(weatherInterval, () => {
|
||||
Utils.execAsync(
|
||||
`curl "https://api.weatherapi.com/v1/forecast.json?key=${weatherKey}&q=93722&days=1&aqi=no&alerts=no"`,
|
||||
)
|
||||
.then((res) => {
|
||||
if (typeof res === "string") {
|
||||
theWeather.value = JSON.parse(res);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`Failed to fetch weather: ${err}`);
|
||||
theWeather.value = defaultWeather;
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return (self.child = Widget.Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
class_name: "calendar-menu-weather today",
|
||||
hexpand: true,
|
||||
children: [
|
||||
TodayIcon(theWeather),
|
||||
TodayTemperature(theWeather),
|
||||
TodayStats(theWeather),
|
||||
],
|
||||
}),
|
||||
Widget.Separator({
|
||||
class_name: "menu-separator weather",
|
||||
}),
|
||||
Hourly(theWeather),
|
||||
],
|
||||
}));
|
||||
},
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export { WeatherWidget };
|
||||
63
modules/menus/calendar/weather/index.ts
Normal file
63
modules/menus/calendar/weather/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import options from "options";
|
||||
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 } = options.menus.clock.weather;
|
||||
|
||||
const theWeather = Variable<Weather>(DEFAULT_WEATHER);
|
||||
|
||||
const WeatherWidget = () => {
|
||||
return Widget.Box({
|
||||
class_name: "calendar-menu-item-container weather",
|
||||
child: Widget.Box({
|
||||
class_name: "weather-container-box",
|
||||
setup: (self) => {
|
||||
Utils.merge(
|
||||
[key.bind("value"), interval.bind("value")],
|
||||
(weatherKey, weatherInterval) => {
|
||||
Utils.interval(weatherInterval, () => {
|
||||
Utils.execAsync(
|
||||
`curl "https://api.weatherapi.com/v1/forecast.json?key=${weatherKey}&q=93722&days=1&aqi=no&alerts=no"`,
|
||||
)
|
||||
.then((res) => {
|
||||
if (typeof res === "string") {
|
||||
theWeather.value = JSON.parse(res);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`Failed to fetch weather: ${err}`);
|
||||
theWeather.value = DEFAULT_WEATHER;
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return (self.child = Widget.Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
class_name: "calendar-menu-weather today",
|
||||
hexpand: true,
|
||||
children: [
|
||||
TodayIcon(theWeather),
|
||||
TodayTemperature(theWeather),
|
||||
TodayStats(theWeather),
|
||||
],
|
||||
}),
|
||||
Widget.Separator({
|
||||
class_name: "menu-separator weather",
|
||||
}),
|
||||
Hourly(theWeather),
|
||||
],
|
||||
}));
|
||||
},
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export { WeatherWidget };
|
||||
@@ -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}%`,
|
||||
),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
55
modules/menus/calendar/weather/stats/index.ts
Normal file
55
modules/menus/calendar/weather/stats/index.ts
Normal 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}%`,
|
||||
),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -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),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
94
modules/menus/calendar/weather/temperature/index.ts
Normal file
94
modules/menus/calendar/weather/temperature/index.ts
Normal 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),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user