Implement framework for custom modules and out of the box custom modules as well. (#213)
* Create declarative module scaffolding * Added ram module (WIP) * Updates to options, styling and more. * Added function for styling custom modules. * Added utility functions and cleaned up code * Type and fn name updates. * Update module utils to handle absent values. * Added icon color in style2 that was missing. * Linted utils.ts * Add CPU module and update RAM module to use /proc/meminfo. * Added disk storage module. * Consolidate code * Added netstat module and removed elements from systray default ignore list. * Added keyboard layout module. * Fix hook types and move module to customModules directory * Added updates modules. * Spacing updates * Added weather module. * Added power menu and power module in bar. Increased update default interval to 6 ours. * Updated styling of bar buttons, made power menu label toggleable, etc. * Consolidate code and add dynamic tooltips based on data being used. * Make default custom mogules matugen compatible * Update base theme * Fix custom module background coloring * Remove testing opacity. * Update themes to account for new modules * Update nix stuff for libgtop (Need someone to test this) * Update nix * Update fractions to multiplications * Move styling in style directory * Implement a polling framework for variables that can dynamically adjust polling intervals. * Netstat module updates when interface name is changed. * Readme update
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Weather } from "lib/types/weather";
|
||||
import { Variable } from "types/variable";
|
||||
import icons from "../../../../icons/index.js";
|
||||
import { getWeatherStatusIcon } from "globals/weather.js";
|
||||
|
||||
export const TodayIcon = (theWeather: Variable<Weather>) => {
|
||||
return Widget.Box({
|
||||
@@ -11,15 +11,7 @@ export const TodayIcon = (theWeather: Variable<Weather>) => {
|
||||
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];
|
||||
return getWeatherStatusIcon(v);
|
||||
}),
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -1,53 +1,8 @@
|
||||
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";
|
||||
import GLib from "gi://GLib?version=2.0"
|
||||
|
||||
const { key, interval, location } = options.menus.clock.weather;
|
||||
|
||||
const theWeather = Variable<Weather>(DEFAULT_WEATHER);
|
||||
|
||||
let weatherIntervalInstance: null | number = null;
|
||||
|
||||
const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: string) => {
|
||||
if (weatherIntervalInstance !== null) {
|
||||
GLib.source_remove(weatherIntervalInstance);
|
||||
}
|
||||
|
||||
const formattedLocation = loc.replace(" ", "%20");
|
||||
|
||||
weatherIntervalInstance = Utils.interval(weatherInterval, () => {
|
||||
Utils.execAsync(
|
||||
`curl "https://api.weatherapi.com/v1/forecast.json?key=${weatherKey}&q=${formattedLocation}&days=1&aqi=no&alerts=no"`,
|
||||
)
|
||||
.then((res) => {
|
||||
try {
|
||||
if (typeof res !== "string") {
|
||||
return theWeather.value = DEFAULT_WEATHER;
|
||||
}
|
||||
|
||||
const parsedWeather = JSON.parse(res);
|
||||
|
||||
if (Object.keys(parsedWeather).includes("error")) {
|
||||
return theWeather.value = DEFAULT_WEATHER;
|
||||
}
|
||||
|
||||
return theWeather.value = parsedWeather;
|
||||
} catch (error) {
|
||||
theWeather.value = DEFAULT_WEATHER;
|
||||
console.warn(`Failed to parse weather data: ${error}`);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`Failed to fetch weather: ${err}`);
|
||||
theWeather.value = DEFAULT_WEATHER;
|
||||
});
|
||||
})
|
||||
};
|
||||
import { globalWeatherVar } from "globals/weather.js";
|
||||
|
||||
const WeatherWidget = () => {
|
||||
return Widget.Box({
|
||||
@@ -55,16 +10,6 @@ const WeatherWidget = () => {
|
||||
child: Widget.Box({
|
||||
class_name: "weather-container-box",
|
||||
setup: (self) => {
|
||||
Utils.merge(
|
||||
[key.bind("value"), interval.bind("value"), location.bind("value")],
|
||||
(weatherKey, weatherInterval, loc) => {
|
||||
if (!weatherKey) {
|
||||
return theWeather.value = DEFAULT_WEATHER;
|
||||
}
|
||||
weatherIntervalFn(weatherInterval, loc, weatherKey);
|
||||
},
|
||||
);
|
||||
|
||||
return (self.child = Widget.Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
@@ -73,15 +18,15 @@ const WeatherWidget = () => {
|
||||
class_name: "calendar-menu-weather today",
|
||||
hexpand: true,
|
||||
children: [
|
||||
TodayIcon(theWeather),
|
||||
TodayTemperature(theWeather),
|
||||
TodayStats(theWeather),
|
||||
TodayIcon(globalWeatherVar),
|
||||
TodayTemperature(globalWeatherVar),
|
||||
TodayStats(globalWeatherVar),
|
||||
],
|
||||
}),
|
||||
Widget.Separator({
|
||||
class_name: "menu-separator weather",
|
||||
}),
|
||||
Hourly(theWeather),
|
||||
Hourly(globalWeatherVar),
|
||||
],
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Weather } from "lib/types/weather";
|
||||
import { Variable } from "types/variable";
|
||||
import options from "options";
|
||||
import { Unit } from "lib/types/options";
|
||||
import { getRainChance, getWindConditions } from "globals/weather";
|
||||
|
||||
const { unit } = options.menus.clock.weather;
|
||||
|
||||
@@ -24,10 +25,7 @@ export const TodayStats = (theWeather: Variable<Weather>) => {
|
||||
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`;
|
||||
return getWindConditions(wthr, unt);
|
||||
},
|
||||
),
|
||||
}),
|
||||
@@ -45,7 +43,7 @@ export const TodayStats = (theWeather: Variable<Weather>) => {
|
||||
label: theWeather
|
||||
.bind("value")
|
||||
.as(
|
||||
(v) => `${v.forecast.forecastday[0].day.daily_chance_of_rain}%`,
|
||||
(v) => getRainChance(v),
|
||||
),
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -1,36 +1,10 @@
|
||||
import { Weather } from "lib/types/weather";
|
||||
import { Variable } from "types/variable";
|
||||
import options from "options";
|
||||
import { getTemperature, getWeatherIcon } from "globals/weather";
|
||||
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",
|
||||
@@ -51,11 +25,7 @@ export const TodayTemperature = (theWeather: Variable<Weather>) => {
|
||||
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`;
|
||||
}
|
||||
return getTemperature(wthr, unt);
|
||||
},
|
||||
),
|
||||
}),
|
||||
@@ -64,11 +34,11 @@ export const TodayTemperature = (theWeather: Variable<Weather>) => {
|
||||
.bind("value")
|
||||
.as(
|
||||
(v) =>
|
||||
`calendar-menu-weather today temp label icon txt-icon ${getIcon(Math.ceil(v.current.temp_f)).color}`,
|
||||
`calendar-menu-weather today temp label icon txt-icon ${getWeatherIcon(Math.ceil(v.current.temp_f)).color}`,
|
||||
),
|
||||
label: theWeather
|
||||
.bind("value")
|
||||
.as((v) => getIcon(Math.ceil(v.current.temp_f)).icon),
|
||||
.as((v) => getWeatherIcon(Math.ceil(v.current.temp_f)).icon),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@@ -84,7 +54,7 @@ export const TodayTemperature = (theWeather: Variable<Weather>) => {
|
||||
.bind("value")
|
||||
.as(
|
||||
(v) =>
|
||||
`calendar-menu-weather today condition label ${getIcon(Math.ceil(v.current.temp_f)).color}`,
|
||||
`calendar-menu-weather today condition label ${getWeatherIcon(Math.ceil(v.current.temp_f)).color}`,
|
||||
),
|
||||
label: theWeather.bind("value").as((v) => v.current.condition.text),
|
||||
}),
|
||||
|
||||
@@ -8,6 +8,7 @@ import NotificationsMenu from "./notifications/index.js";
|
||||
import CalendarMenu from "./calendar/index.js";
|
||||
import EnergyMenu from "./energy/index.js";
|
||||
import DashboardMenu from "./dashboard/index.js";
|
||||
import PowerDropdown from "./powerDropdown/index.js";
|
||||
|
||||
export default [
|
||||
PowerMenu(),
|
||||
@@ -20,4 +21,5 @@ export default [
|
||||
CalendarMenu(),
|
||||
EnergyMenu(),
|
||||
DashboardMenu(),
|
||||
PowerDropdown(),
|
||||
];
|
||||
|
||||
@@ -36,6 +36,16 @@ class PowerMenu extends Service {
|
||||
App.openWindow("verification");
|
||||
}
|
||||
|
||||
customAction(action: Action, cmnd: string) {
|
||||
[this.#cmd, this.#title] = [cmnd, action];
|
||||
|
||||
this.notify("cmd");
|
||||
this.notify("title");
|
||||
this.emit("changed");
|
||||
App.closeWindow("powermenu");
|
||||
App.openWindow("verification");
|
||||
}
|
||||
|
||||
shutdown = () => {
|
||||
this.action("shutdown");
|
||||
};
|
||||
|
||||
57
modules/menus/powerDropdown/button.ts
Normal file
57
modules/menus/powerDropdown/button.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import icons from "lib/icons";
|
||||
import { PowerOptions } from "lib/types/options";
|
||||
import options from "options";
|
||||
import powermenu from "../power/helpers/actions";
|
||||
|
||||
const { confirmation, shutdown, logout, sleep, reboot, showLabel } = options.menus.power;
|
||||
|
||||
export const PowerButton = (action: PowerOptions) => {
|
||||
const handleClick = (action: PowerOptions) => {
|
||||
const actions = {
|
||||
shutdown: shutdown.value,
|
||||
reboot: reboot.value,
|
||||
logout: logout.value,
|
||||
sleep: sleep.value,
|
||||
};
|
||||
App.closeWindow("powerdropdownmenu");
|
||||
|
||||
if (!confirmation.value) {
|
||||
Utils.execAsync(actions[action])
|
||||
.catch((err) => console.error(`Failed to execute ${action} command. Error: ${err}`));
|
||||
} else {
|
||||
powermenu.customAction(action, actions[action]);
|
||||
}
|
||||
};
|
||||
|
||||
return Widget.Button({
|
||||
className: showLabel.bind("value").as(shwLbl => {
|
||||
return `power-menu-button ${action} ${!shwLbl ? "no-label" : ""}`;
|
||||
}),
|
||||
on_clicked: () => handleClick(action),
|
||||
child: Widget.Box({
|
||||
vertical: false,
|
||||
children: showLabel.bind("value").as(shwLbl => {
|
||||
if (shwLbl) {
|
||||
return [
|
||||
Widget.Icon({
|
||||
icon: icons.powermenu[action],
|
||||
className: `power-button-icon ${action}-icon`,
|
||||
}),
|
||||
Widget.Label({
|
||||
hpack: "center",
|
||||
hexpand: true,
|
||||
label: action.charAt(0).toUpperCase() + action.slice(1),
|
||||
className: `power-button-label ${action}-label show-label`,
|
||||
}),
|
||||
];
|
||||
}
|
||||
return [
|
||||
Widget.Icon({
|
||||
icon: icons.powermenu[action],
|
||||
className: `power-button-icon ${action}-icon no-label`,
|
||||
}),
|
||||
];
|
||||
}),
|
||||
})
|
||||
});
|
||||
};
|
||||
27
modules/menus/powerDropdown/index.ts
Normal file
27
modules/menus/powerDropdown/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import options from "options.js";
|
||||
import DropdownMenu from "../DropdownMenu.js";
|
||||
import { PowerButton } from "./button.js";
|
||||
|
||||
const { showLabel } = options.menus.power;
|
||||
|
||||
export default () => {
|
||||
return DropdownMenu({
|
||||
name: "powerdropdownmenu",
|
||||
transition: "crossfade",
|
||||
child: Widget.Box({
|
||||
class_name: "menu-items power-dropdown",
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
class_name: "menu-items-container power-dropdown",
|
||||
children: [
|
||||
PowerButton('shutdown'),
|
||||
PowerButton('reboot'),
|
||||
PowerButton('logout'),
|
||||
PowerButton('sleep'),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user