Added strict type checking to the project. (#236)
* Implement strict typing (WIP). * changes * Finish type checks * Fix notification icon, matugen settings and update tsconfig. * OSD Styling updates and added the ability to configure OSD duration.
This commit is contained in:
@@ -69,7 +69,6 @@ export const pollVariableBash = <T>(
|
||||
});
|
||||
};
|
||||
|
||||
// Set up the interval initially with the provided polling interval
|
||||
Utils.merge([pollingInterval, ...trackers], (pollIntrvl: number) => {
|
||||
intervalFn(pollIntrvl);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// @ts-expect-error
|
||||
import GTop from 'gi://GTop';
|
||||
|
||||
const defaultCpuData: number = 0;
|
||||
|
||||
let previousCpuData = new GTop.glibtop_cpu();
|
||||
GTop.glibtop_get_cpu(previousCpuData);
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import options from "options";
|
||||
|
||||
// @ts-expect-error
|
||||
import GTop from 'gi://GTop';
|
||||
|
||||
// Module initializer
|
||||
import { module } from "../module"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HyprctlDeviceLayout, HyprctlKeyboard, KbLabelType } from "lib/types/customModules/kbLayout";
|
||||
import { HyprctlDeviceLayout, HyprctlKeyboard, KbLabelType, LayoutKeys, LayoutValues } from "lib/types/customModules/kbLayout";
|
||||
import { layoutMap } from "./layouts";
|
||||
|
||||
export const getKeyboardLayout = (obj: string, format: KbLabelType) => {
|
||||
@@ -15,7 +15,8 @@ export const getKeyboardLayout = (obj: string, format: KbLabelType) => {
|
||||
mainKb = keyboards[keyboards.length - 1];
|
||||
}
|
||||
|
||||
let layout = mainKb['active_keymap'];
|
||||
let layout: LayoutKeys = mainKb['active_keymap'] as LayoutKeys;
|
||||
const foundLayout: LayoutValues = layoutMap[layout];
|
||||
|
||||
return format === "code" ? layoutMap[layout] || layout : layout;
|
||||
return format === "code" ? foundLayout || layout : layout;
|
||||
}
|
||||
|
||||
@@ -581,4 +581,4 @@ export const layoutMap = {
|
||||
"Wolof": "SN",
|
||||
"Yakut": "RU (Sah)",
|
||||
"Yoruba": "NG (Yoruba)"
|
||||
};
|
||||
} as const;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Module } from "lib/types/bar";
|
||||
import { BarButtonStyles } from "lib/types/options";
|
||||
import options from "options";
|
||||
import Gtk from "types/@girs/gtk-3.0/gtk-3.0";
|
||||
import { Binding } from "types/service";
|
||||
@@ -40,7 +41,7 @@ export const module = ({
|
||||
|
||||
return {
|
||||
component: Widget.Box({
|
||||
className: Utils.merge([style.bind("value"), showLabelBinding], (style: string, shwLabel: boolean) => {
|
||||
className: Utils.merge([style.bind("value"), showLabelBinding], (style: BarButtonStyles, shwLabel: boolean) => {
|
||||
const shouldShowLabel = shwLabel || showLabel;
|
||||
const styleMap = {
|
||||
default: "style1",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ResourceLabelType } from 'lib/types/bar';
|
||||
import { GenericResourceData } from 'lib/types/customModules/generic';
|
||||
import { InputHandlerEvents } from 'lib/types/customModules/utils';
|
||||
import { Binding } from 'lib/utils';
|
||||
import { openMenu } from 'modules/bar/utils';
|
||||
import options from 'options';
|
||||
@@ -75,7 +76,7 @@ export const inputHandler = (
|
||||
onMiddleClick,
|
||||
onScrollUp,
|
||||
onScrollDown,
|
||||
}
|
||||
}: InputHandlerEvents
|
||||
) => {
|
||||
const sanitizeInput = (input: VariableType<string>): string => {
|
||||
if (input === undefined) {
|
||||
|
||||
5
globals.d.ts
vendored
5
globals.d.ts
vendored
@@ -1,9 +1,12 @@
|
||||
// globals.d.ts
|
||||
|
||||
import { Variable as VariableType } from "types/variable";
|
||||
import { Options, Variable as VariableType } from "types/variable";
|
||||
|
||||
declare global {
|
||||
var globalMousePos: VariableType<number[]>;
|
||||
var useTheme: Function;
|
||||
var globalWeatherVar: VariableType<Weather>;
|
||||
var options: Options
|
||||
}
|
||||
|
||||
export { };
|
||||
|
||||
16
globals/network.ts
Normal file
16
globals/network.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export const WIFI_STATUS_MAP = {
|
||||
unknown: "Status Unknown",
|
||||
unmanaged: "Unmanaged",
|
||||
unavailable: "Unavailable",
|
||||
disconnected: "Disconnected",
|
||||
prepare: "Preparing Connecting",
|
||||
config: "Connecting",
|
||||
need_auth: "Needs Authentication",
|
||||
ip_config: "Requesting IP",
|
||||
ip_check: "Checking Access",
|
||||
secondaries: "Waiting on Secondaries",
|
||||
activated: "Connected",
|
||||
deactivating: "Disconnecting",
|
||||
failed: "Connection Failed",
|
||||
} as const;
|
||||
|
||||
24
globals/notification.ts
Normal file
24
globals/notification.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import icons from "modules/icons/index";
|
||||
|
||||
export const getNotificationIcon = (app_name: string, app_icon: string, app_entry: string) => {
|
||||
let icon: string = icons.fallback.notification;
|
||||
|
||||
if (Utils.lookUpIcon(app_name) || Utils.lookUpIcon(app_name.toLowerCase() || "")) {
|
||||
icon = Utils.lookUpIcon(app_name)
|
||||
? app_name
|
||||
: Utils.lookUpIcon(app_name.toLowerCase())
|
||||
? app_name.toLowerCase()
|
||||
: "";
|
||||
}
|
||||
|
||||
if (Utils.lookUpIcon(app_icon) && icon === "") {
|
||||
icon = app_icon;
|
||||
}
|
||||
|
||||
if (Utils.lookUpIcon(app_entry || "") && icon === "") {
|
||||
icon = app_entry || "";
|
||||
}
|
||||
|
||||
return icon;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@ import { bash, Notify } from "lib/utils";
|
||||
import icons from "lib/icons"
|
||||
import { filterConfigForThemeOnly, loadJsonFile, saveConfigToFile } from "widget/settings/shared/FileChooser";
|
||||
|
||||
export const hexColorPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
|
||||
|
||||
globalThis.useTheme = (filePath: string): void => {
|
||||
let importedConfig = loadJsonFile(filePath);
|
||||
|
||||
|
||||
13
globals/variables.ts
Normal file
13
globals/variables.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Opt } from "lib/option";
|
||||
import { HexColor, RecursiveOptionsObject } from "lib/types/options";
|
||||
|
||||
export const isOpt = <T>(value: unknown): value is Opt<T> =>
|
||||
typeof value === 'object' && value !== null && 'value' in value && value instanceof Opt;
|
||||
|
||||
export const isRecursiveOptionsObject = (value: unknown): value is RecursiveOptionsObject => {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export const isHexColor = (value: string): value is HexColor => {
|
||||
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value);
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import options from "options";
|
||||
import { UnitType, Weather } from "lib/types/weather.js";
|
||||
import { UnitType, Weather, WeatherIconTitle, WeatherIcon } from "lib/types/weather.js";
|
||||
import { DEFAULT_WEATHER } from "lib/types/defaults/weather.js";
|
||||
import GLib from "gi://GLib?version=2.0"
|
||||
|
||||
import icons from "../modules/icons/index.js";
|
||||
import GLib from "gi://GLib?version=2.0";
|
||||
import { weatherIcons } from "modules/icons/weather.js";
|
||||
|
||||
const { key, interval, location } = options.menus.clock.weather;
|
||||
@@ -26,16 +24,16 @@ const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: str
|
||||
.then((res) => {
|
||||
try {
|
||||
if (typeof res !== "string") {
|
||||
return globalWeatherVar.value = DEFAULT_WEATHER;
|
||||
return (globalWeatherVar.value = DEFAULT_WEATHER);
|
||||
}
|
||||
|
||||
const parsedWeather = JSON.parse(res);
|
||||
|
||||
if (Object.keys(parsedWeather).includes("error")) {
|
||||
return globalWeatherVar.value = DEFAULT_WEATHER;
|
||||
return (globalWeatherVar.value = DEFAULT_WEATHER);
|
||||
}
|
||||
|
||||
return globalWeatherVar.value = parsedWeather;
|
||||
return (globalWeatherVar.value = parsedWeather);
|
||||
} catch (error) {
|
||||
globalWeatherVar.value = DEFAULT_WEATHER;
|
||||
console.warn(`Failed to parse weather data: ${error}`);
|
||||
@@ -45,12 +43,12 @@ const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: str
|
||||
console.error(`Failed to fetch weather: ${err}`);
|
||||
globalWeatherVar.value = DEFAULT_WEATHER;
|
||||
});
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
Utils.merge([key.bind("value"), interval.bind("value"), location.bind("value")], (weatherKey, weatherInterval, loc) => {
|
||||
if (!weatherKey) {
|
||||
return globalWeatherVar.value = DEFAULT_WEATHER;
|
||||
return (globalWeatherVar.value = DEFAULT_WEATHER);
|
||||
}
|
||||
weatherIntervalFn(weatherInterval, loc, weatherKey);
|
||||
});
|
||||
@@ -70,23 +68,28 @@ export const getWeatherIcon = (fahren: number) => {
|
||||
50: "",
|
||||
25: "",
|
||||
0: "",
|
||||
};
|
||||
} as const;
|
||||
const colors = {
|
||||
100: "weather-color red",
|
||||
75: "weather-color orange",
|
||||
50: "weather-color lavender",
|
||||
25: "weather-color blue",
|
||||
0: "weather-color sky",
|
||||
};
|
||||
} as const;
|
||||
|
||||
const threshold =
|
||||
type IconKeys = keyof typeof icons;
|
||||
|
||||
const threshold: IconKeys =
|
||||
fahren < 0
|
||||
? 0
|
||||
: [100, 75, 50, 25, 0].find((threshold) => threshold <= fahren);
|
||||
: ([100, 75, 50, 25, 0] as IconKeys[]).find((threshold) => threshold <= fahren) || 0;
|
||||
|
||||
const icon = icons[threshold || 50];
|
||||
const color = colors[threshold || 50];
|
||||
|
||||
return {
|
||||
icon: icons[threshold || 50],
|
||||
color: colors[threshold || 50],
|
||||
icon,
|
||||
color,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -95,11 +98,15 @@ export const getWindConditions = (wthr: Weather, unt: UnitType) => {
|
||||
return `${Math.floor(wthr.current.wind_mph)} mph`;
|
||||
}
|
||||
return `${Math.floor(wthr.current.wind_kph)} kph`;
|
||||
}
|
||||
};
|
||||
|
||||
export const getRainChance = (wthr: Weather) => `${wthr.forecast.forecastday[0].day.daily_chance_of_rain}%`;
|
||||
|
||||
export const getWeatherStatusTextIcon = (wthr: Weather) => {
|
||||
export const isValidWeatherIconTitle = (title: string): title is WeatherIconTitle => {
|
||||
return title in weatherIcons;
|
||||
};
|
||||
|
||||
export const getWeatherStatusTextIcon = (wthr: Weather): WeatherIcon => {
|
||||
let iconQuery = wthr.current.condition.text
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
@@ -108,7 +115,14 @@ export const getWeatherStatusTextIcon = (wthr: Weather) => {
|
||||
if (!wthr.current.is_day && iconQuery === "partly_cloudy") {
|
||||
iconQuery = "partly_cloudy_night";
|
||||
}
|
||||
return weatherIcons[iconQuery];
|
||||
|
||||
if (isValidWeatherIconTitle(iconQuery)) {
|
||||
return weatherIcons[iconQuery];
|
||||
} else {
|
||||
console.warn(`Unknown weather icon title: ${iconQuery}`);
|
||||
return weatherIcons["warning"];
|
||||
}
|
||||
};
|
||||
|
||||
globalThis["globalWeatherVar"] = globalWeatherVar;
|
||||
|
||||
|
||||
10
globals/window.ts
Normal file
10
globals/window.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const WINDOW_LAYOUTS: string[] = [
|
||||
'center',
|
||||
'top',
|
||||
'top-right',
|
||||
'top-center',
|
||||
'top-left',
|
||||
'bottom-left',
|
||||
'bottom-center',
|
||||
'bottom-right'
|
||||
];
|
||||
@@ -10,7 +10,7 @@ export const substitutes = {
|
||||
"preferences-system": "emblem-system-symbolic",
|
||||
"com.github.Aylur.ags-symbolic": "controls-symbolic",
|
||||
"com.github.Aylur.ags": "controls-symbolic",
|
||||
}
|
||||
} as const;
|
||||
|
||||
export default {
|
||||
missing: "image-missing-symbolic",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isHexColor } from "globals/variables"
|
||||
import { Variable } from "resource:///com/github/Aylur/ags/variable.js"
|
||||
|
||||
type OptProps = {
|
||||
@@ -49,7 +50,7 @@ export class Opt<T = unknown> extends Variable<T> {
|
||||
if (this.persistent)
|
||||
return;
|
||||
|
||||
const isColor = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(`${this.value}`);
|
||||
const isColor = isHexColor(this.value as string);
|
||||
if ((JSON.stringify(this.value) !== JSON.stringify(this.initial)) && isColor) {
|
||||
this.value = this.initial
|
||||
return this.id
|
||||
@@ -60,35 +61,37 @@ export class Opt<T = unknown> extends Variable<T> {
|
||||
|
||||
export const opt = <T>(initial: T, opts?: OptProps) => new Opt(initial, opts)
|
||||
|
||||
function getOptions(object: object, path = ""): Opt[] {
|
||||
function getOptions(object: Record<string, unknown>, path = ""): Opt[] {
|
||||
return Object.keys(object).flatMap(key => {
|
||||
const obj: Opt = object[key]
|
||||
const id = path ? path + "." + key : key
|
||||
const obj = object[key];
|
||||
const id = path ? path + "." + key : key;
|
||||
|
||||
if (obj instanceof Variable) {
|
||||
obj.id = id
|
||||
return obj
|
||||
const optValue = obj as Opt;
|
||||
optValue.id = id;
|
||||
return optValue;
|
||||
}
|
||||
|
||||
if (typeof obj === "object")
|
||||
return getOptions(obj, id)
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
return getOptions(obj as Record<string, unknown>, id); // Recursively process nested objects
|
||||
}
|
||||
|
||||
return []
|
||||
})
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
export function mkOptions<T extends object>(cacheFile: string, object: T, confFile: string = "config.json") {
|
||||
for (const opt of getOptions(object))
|
||||
for (const opt of getOptions(object as Record<string, unknown>))
|
||||
opt.init(cacheFile)
|
||||
|
||||
Utils.ensureDirectory(cacheFile.split("/").slice(0, -1).join("/"))
|
||||
|
||||
const configFile = `${TMP}/${confFile}`
|
||||
const values = getOptions(object).reduce((obj, { id, value }) => ({ [id]: value, ...obj }), {})
|
||||
const values = getOptions(object as Record<string, unknown>).reduce((obj, { id, value }) => ({ [id]: value, ...obj }), {})
|
||||
Utils.writeFileSync(JSON.stringify(values, null, 2), configFile)
|
||||
Utils.monitorFile(configFile, () => {
|
||||
const cache = JSON.parse(Utils.readFile(configFile) || "{}")
|
||||
for (const opt of getOptions(object)) {
|
||||
for (const opt of getOptions(object as Record<string, unknown>)) {
|
||||
if (JSON.stringify(cache[opt.id]) !== JSON.stringify(opt.value))
|
||||
opt.value = cache[opt.id]
|
||||
}
|
||||
@@ -99,7 +102,7 @@ export function mkOptions<T extends object>(cacheFile: string, object: T, confFi
|
||||
}
|
||||
|
||||
async function reset(
|
||||
[opt, ...list] = getOptions(object),
|
||||
[opt, ...list] = getOptions(object as Record<string, unknown>),
|
||||
id = opt?.reset(),
|
||||
): Promise<Array<string>> {
|
||||
if (!opt)
|
||||
@@ -111,7 +114,7 @@ export function mkOptions<T extends object>(cacheFile: string, object: T, confFi
|
||||
}
|
||||
|
||||
async function resetTheme(
|
||||
[opt, ...list] = getOptions(object),
|
||||
[opt, ...list] = getOptions(object as Record<string, unknown>),
|
||||
id = opt?.doResetColor(),
|
||||
): Promise<Array<string>> {
|
||||
if (!opt)
|
||||
@@ -124,7 +127,7 @@ export function mkOptions<T extends object>(cacheFile: string, object: T, confFi
|
||||
|
||||
return Object.assign(object, {
|
||||
configFile,
|
||||
array: () => getOptions(object),
|
||||
array: () => getOptions(object as Record<string, unknown>),
|
||||
async reset() {
|
||||
return (await reset()).join("\n")
|
||||
},
|
||||
@@ -132,7 +135,7 @@ export function mkOptions<T extends object>(cacheFile: string, object: T, confFi
|
||||
return (await resetTheme()).join("\n")
|
||||
},
|
||||
handler(deps: string[], callback: () => void) {
|
||||
for (const opt of getOptions(object)) {
|
||||
for (const opt of getOptions(object as Record<string, unknown>)) {
|
||||
if (deps.some(i => opt.id.startsWith(i)))
|
||||
opt.connect("changed", callback)
|
||||
}
|
||||
|
||||
5
lib/types/customModules/kbLayout.d.ts
vendored
5
lib/types/customModules/kbLayout.d.ts
vendored
@@ -1,3 +1,5 @@
|
||||
import { layoutMap } from "customModules/kblayout/layouts";
|
||||
|
||||
export type KbLabelType = "layout" | "code";
|
||||
export type KbIcon = "" | "" | "" | "" | "";
|
||||
|
||||
@@ -26,3 +28,6 @@ export type HyprctlDeviceLayout = {
|
||||
touch: any[];
|
||||
switches: any[];
|
||||
};
|
||||
|
||||
export type LayoutKeys = keyof typeof layoutMap;
|
||||
export type LayoutValues = typeof layoutMap[LayoutKeys];
|
||||
|
||||
9
lib/types/customModules/utils.d.ts
vendored
9
lib/types/customModules/utils.d.ts
vendored
@@ -0,0 +1,9 @@
|
||||
import { Binding } from "lib/utils";
|
||||
|
||||
export type InputHandlerEvents = {
|
||||
onPrimaryClick?: Binding,
|
||||
onSecondaryClick?: Binding,
|
||||
onMiddleClick?: Binding,
|
||||
onScrollUp?: Binding,
|
||||
onScrollDown?: Binding,
|
||||
}
|
||||
|
||||
10
lib/types/dropdownmenu.d.ts
vendored
Normal file
10
lib/types/dropdownmenu.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { WindowProps } from "types/widgets/window";
|
||||
|
||||
export type DropdownMenuProps = {
|
||||
name: string;
|
||||
child: any;
|
||||
layout?: string;
|
||||
transition?: any;
|
||||
exclusivity?: Exclusivity;
|
||||
fixed?: boolean;
|
||||
} & WindowProps;
|
||||
3
lib/types/filechooser.d.ts
vendored
Normal file
3
lib/types/filechooser.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export type Config = {
|
||||
[key: string]: string | number | boolean | object;
|
||||
}
|
||||
3
lib/types/mpris.d.ts
vendored
Normal file
3
lib/types/mpris.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export type LoopStatus = 'none' | 'track' | 'playlist';
|
||||
export type PlaybackStatus = 'playing' | 'paused' | 'stopped';
|
||||
|
||||
4
lib/types/network.d.ts
vendored
4
lib/types/network.d.ts
vendored
@@ -1,3 +1,5 @@
|
||||
import { WIFI_STATUS_MAP } from "globals/network";
|
||||
|
||||
export type AccessPoint = {
|
||||
bssid: string | null;
|
||||
address: string | null;
|
||||
@@ -8,3 +10,5 @@ export type AccessPoint = {
|
||||
frequency: number;
|
||||
iconName: string | undefined;
|
||||
}
|
||||
|
||||
export type WifiStatus = keyof typeof WIFI_STATUS_MAP;
|
||||
|
||||
4
lib/types/notification.d.ts
vendored
4
lib/types/notification.d.ts
vendored
@@ -1,3 +1,5 @@
|
||||
import icons from "modules/icons/index";
|
||||
|
||||
export interface NotificationArgs {
|
||||
appName?: string;
|
||||
body?: string;
|
||||
@@ -9,3 +11,5 @@ export interface NotificationArgs {
|
||||
timeout?: number;
|
||||
transient?: boolean;
|
||||
}
|
||||
|
||||
export type NotificationIcon = keyof typeof icons.notifications;
|
||||
|
||||
5
lib/types/options.d.ts
vendored
5
lib/types/options.d.ts
vendored
@@ -1,6 +1,10 @@
|
||||
import { Opt } from "lib/option";
|
||||
import { Variable } from "types/variable";
|
||||
|
||||
export type RecursiveOptionsObject = {
|
||||
[key: string]: RecursiveOptionsObject | Opt<string | number | boolean> | Opt<any>;
|
||||
};
|
||||
|
||||
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" | "left" | "right";
|
||||
@@ -117,3 +121,4 @@ type MatugenVariation =
|
||||
| "vivid_3"
|
||||
|
||||
type MatugenTheme = "light" | "dark";
|
||||
|
||||
|
||||
27
lib/types/popupwindow.d.ts
vendored
Normal file
27
lib/types/popupwindow.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Widget } from "types/widgets/widget";
|
||||
import { WindowProps } from "types/widgets/window";
|
||||
import { Transition } from "./widget";
|
||||
|
||||
export type PopupWindowProps = {
|
||||
name: string;
|
||||
child: any;
|
||||
layout?: Layouts;
|
||||
transition?: any;
|
||||
exclusivity?: Exclusivity;
|
||||
} & WindowProps;
|
||||
|
||||
export type LayoutFunction = (
|
||||
name: string,
|
||||
child: Widget,
|
||||
transition: Transition
|
||||
) => {
|
||||
center: () => Widget;
|
||||
top: () => Widget;
|
||||
"top-right": () => Widget;
|
||||
"top-center": () => Widget;
|
||||
"top-left": () => Widget;
|
||||
"bottom-left": () => Widget;
|
||||
"bottom-center": () => Widget;
|
||||
"bottom-right": () => Widget;
|
||||
};
|
||||
export type Layouts = 'center' | 'top' | 'top-right' | 'top-center' | 'top-left' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
||||
8
lib/types/powerprofiles.d.ts
vendored
Normal file
8
lib/types/powerprofiles.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import icons from "modules/icons/index";
|
||||
import PowerProfiles from "types/service/powerprofiles.js"
|
||||
|
||||
export type PowerProfiles = InstanceType<typeof PowerProfiles>;
|
||||
export type PowerProfile = "power-saver" | "balanced" | "performance";
|
||||
export type PowerProfileObject = {
|
||||
[key: string]: string;
|
||||
}
|
||||
3
lib/types/utils.d.ts
vendored
Normal file
3
lib/types/utils.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { substitutes } from "lib/icons";
|
||||
|
||||
type SubstituteKeys = keyof typeof substitutes;
|
||||
9
lib/types/weather.d.ts
vendored
9
lib/types/weather.d.ts
vendored
@@ -1,3 +1,5 @@
|
||||
import { weatherIcons } from "modules/icons/weather";
|
||||
|
||||
export type UnitType = "imperial" | "metric";
|
||||
|
||||
export type Weather = {
|
||||
@@ -107,3 +109,10 @@ export type Location = {
|
||||
localtime_epoch: number;
|
||||
localtime: string;
|
||||
}
|
||||
|
||||
export type TemperatureIconColorMap = {
|
||||
[key: number]: string;
|
||||
}
|
||||
|
||||
export type WeatherIconTitle = keyof typeof weatherIcons;
|
||||
export type WeatherIcon = typeof weatherIcons[WeatherIconTitle];
|
||||
|
||||
3
lib/types/widget.d.ts
vendored
3
lib/types/widget.d.ts
vendored
@@ -1,3 +1,6 @@
|
||||
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";
|
||||
|
||||
// Window
|
||||
export type Layouts = 'center' | 'top' | 'top-right' | 'top-center' | 'top-left' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
||||
|
||||
11
lib/utils.ts
11
lib/utils.ts
@@ -8,20 +8,29 @@ import Gdk from "gi://Gdk"
|
||||
import GLib from "gi://GLib?version=2.0"
|
||||
import GdkPixbuf from "gi://GdkPixbuf";
|
||||
import { NotificationArgs } from "types/utils/notify"
|
||||
import { SubstituteKeys } from "./types/utils";
|
||||
|
||||
export type Binding<T> = import("types/service").Binding<any, any, T>
|
||||
|
||||
|
||||
/**
|
||||
* @returns substitute icon || name || fallback icon
|
||||
*/
|
||||
export function icon(name: string | null, fallback = icons.missing) {
|
||||
const validateSubstitute = (name: string): name is SubstituteKeys => name in substitutes;
|
||||
|
||||
if (!name)
|
||||
return fallback || ""
|
||||
|
||||
if (GLib.file_test(name, GLib.FileTest.EXISTS))
|
||||
return name
|
||||
|
||||
const icon = (substitutes[name] || name)
|
||||
let icon: string = name;
|
||||
|
||||
if (validateSubstitute(name)) {
|
||||
icon = substitutes[name];
|
||||
}
|
||||
|
||||
if (Utils.lookUpIcon(icon))
|
||||
return icon
|
||||
|
||||
|
||||
@@ -196,4 +196,4 @@ export default {
|
||||
patchy_light_snow_with_thunder: "weather-snow-symbolic",
|
||||
moderate_or_heavy_snow_with_thunder: "weather-snow-symbolic",
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
|
||||
@@ -51,4 +51,4 @@ export const weatherIcons = {
|
||||
moderate_or_heavy_rain_with_thunder: "",
|
||||
patchy_light_snow_with_thunder: "",
|
||||
moderate_or_heavy_snow_with_thunder: "",
|
||||
};
|
||||
} as const;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const hyprland = await Service.import("hyprland");
|
||||
import { DropdownMenuProps } from "lib/types/dropdownmenu";
|
||||
import { Exclusivity } from "lib/types/widget";
|
||||
import { bash } from "lib/utils";
|
||||
import { Monitor } from "types/service/hyprland";
|
||||
@@ -99,15 +100,17 @@ setTimeout(() => {
|
||||
initRender.value = false;
|
||||
}, 2000);
|
||||
|
||||
export default ({
|
||||
name,
|
||||
child,
|
||||
layout = "center",
|
||||
transition,
|
||||
exclusivity = "ignore" as Exclusivity,
|
||||
fixed = false,
|
||||
...props
|
||||
}) =>
|
||||
export default (
|
||||
{
|
||||
name,
|
||||
child,
|
||||
layout = "center",
|
||||
transition,
|
||||
exclusivity = "ignore" as Exclusivity,
|
||||
fixed = false,
|
||||
...props
|
||||
}: DropdownMenuProps
|
||||
) =>
|
||||
Widget.Window({
|
||||
name,
|
||||
class_names: [name, "dropdown-menu"],
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { WINDOW_LAYOUTS } from "globals/window";
|
||||
import { LayoutFunction, Layouts, PopupWindowProps } from "lib/types/popupwindow";
|
||||
import { Exclusivity, Transition } from "lib/types/widget";
|
||||
|
||||
type Opts = {
|
||||
@@ -32,7 +34,7 @@ const PopupRevealer = (name: string, child: any, transition = "slide_down" as Tr
|
||||
}),
|
||||
);
|
||||
|
||||
const Layout = (name: string, child: any, transition: Transition) => ({
|
||||
const Layout: LayoutFunction = (name: string, child: any, transition: Transition) => ({
|
||||
center: () =>
|
||||
Widget.CenterBox(
|
||||
{},
|
||||
@@ -150,6 +152,10 @@ const Layout = (name: string, child: any, transition: Transition) => ({
|
||||
),
|
||||
});
|
||||
|
||||
const isValidLayout = (layout: string): layout is Layouts => {
|
||||
return WINDOW_LAYOUTS.includes(layout);
|
||||
};
|
||||
|
||||
export default ({
|
||||
name,
|
||||
child,
|
||||
@@ -157,8 +163,12 @@ export default ({
|
||||
transition,
|
||||
exclusivity = "ignore" as Exclusivity,
|
||||
...props
|
||||
}) =>
|
||||
Widget.Window({
|
||||
}: PopupWindowProps) => {
|
||||
const layoutFn = isValidLayout(layout) ? layout : "center";
|
||||
|
||||
const layoutWidget = Layout(name, child, transition)[layoutFn]();
|
||||
|
||||
return Widget.Window({
|
||||
name,
|
||||
class_names: [name, "popup-window"],
|
||||
setup: (w) => w.keybind("Escape", () => App.closeWindow(name)),
|
||||
@@ -167,6 +177,7 @@ export default ({
|
||||
exclusivity,
|
||||
layer: "top",
|
||||
anchor: ["top", "bottom", "right", "left"],
|
||||
child: Layout(name, child, transition)[layout](),
|
||||
child: layoutWidget,
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
const getIcon = (audioVol, isMuted) => {
|
||||
const speakerIcons = {
|
||||
const speakerIcons = {
|
||||
101: "audio-volume-overamplified-symbolic",
|
||||
66: "audio-volume-high-symbolic",
|
||||
34: "audio-volume-medium-symbolic",
|
||||
1: "audio-volume-low-symbolic",
|
||||
0: "audio-volume-muted-symbolic",
|
||||
};
|
||||
} as const;
|
||||
|
||||
const inputIcons = {
|
||||
const inputIcons = {
|
||||
101: "microphone-sensitivity-high-symbolic",
|
||||
66: "microphone-sensitivity-high-symbolic",
|
||||
34: "microphone-sensitivity-medium-symbolic",
|
||||
1: "microphone-sensitivity-low-symbolic",
|
||||
0: "microphone-disabled-symbolic",
|
||||
};
|
||||
};
|
||||
|
||||
const icon = isMuted
|
||||
? 0
|
||||
: [101, 66, 34, 1, 0].find((threshold) => threshold <= audioVol * 100);
|
||||
type IconVolumes = keyof typeof speakerIcons;
|
||||
|
||||
return {
|
||||
spkr: speakerIcons[icon],
|
||||
mic: inputIcons[icon],
|
||||
};
|
||||
const getIcon = (audioVol: IconVolumes, isMuted: boolean) => {
|
||||
const thresholds: IconVolumes[] = [101, 66, 34, 1, 0];
|
||||
const icon = isMuted
|
||||
? 0
|
||||
: thresholds.find((threshold) => threshold <= audioVol * 100) || 0;
|
||||
|
||||
return {
|
||||
spkr: speakerIcons[icon],
|
||||
mic: inputIcons[icon],
|
||||
};
|
||||
};
|
||||
|
||||
export { getIcon };
|
||||
|
||||
@@ -88,7 +88,7 @@ const devices = (bluetooth: Bluetooth, self: Box<Gtk.Widget, unknown>) => {
|
||||
Widget.Label({
|
||||
vpack: "start",
|
||||
class_name: `menu-button-icon bluetooth ${conDevNames.includes(device.address) ? "active" : ""} txt-icon`,
|
||||
label: getBluetoothIcon(`${device["icon-name"]}-symbolic`),
|
||||
label: getBluetoothIcon(`${device["icon_name"]}-symbolic`),
|
||||
}),
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
|
||||
@@ -1,35 +1,43 @@
|
||||
import { Weather } from "lib/types/weather.js";
|
||||
import { Weather, WeatherIcon, WeatherIconTitle } from "lib/types/weather.js";
|
||||
import { Variable } from "types/variable.js";
|
||||
import { weatherIcons } from "modules/icons/weather.js";
|
||||
import { isValidWeatherIconTitle } from "globals/weather";
|
||||
|
||||
export const HourlyIcon = (theWeather: Variable<Weather>, getNextEpoch: any) => {
|
||||
const getIconQuery = (wthr: Weather) => {
|
||||
const getIconQuery = (wthr: Weather): WeatherIconTitle => {
|
||||
|
||||
const nextEpoch = getNextEpoch(wthr);
|
||||
const weatherAtEpoch = wthr.forecast.forecastday[0].hour.find(
|
||||
(h) => h.time_epoch === nextEpoch,
|
||||
);
|
||||
|
||||
let iconQuery = weatherAtEpoch?.condition.text
|
||||
if (weatherAtEpoch === undefined) {
|
||||
return "warning";
|
||||
}
|
||||
|
||||
let iconQuery = weatherAtEpoch.condition.text
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replaceAll(" ", "_")
|
||||
|| "warning"
|
||||
;
|
||||
.replaceAll(" ", "_");
|
||||
|
||||
if (!weatherAtEpoch?.is_day && iconQuery === "partly_cloudy") {
|
||||
iconQuery = "partly_cloudy_night";
|
||||
}
|
||||
return iconQuery;
|
||||
|
||||
if (isValidWeatherIconTitle(iconQuery)) {
|
||||
return iconQuery;
|
||||
} else {
|
||||
return "warning";
|
||||
}
|
||||
}
|
||||
|
||||
return Widget.Box({
|
||||
hpack: "center",
|
||||
child: theWeather.bind("value").as((w) => {
|
||||
let weatherIcn = "-";
|
||||
let weatherIcn: WeatherIcon;
|
||||
|
||||
const iconQuery = getIconQuery(w);
|
||||
weatherIcn = weatherIcons[iconQuery];
|
||||
weatherIcn = weatherIcons[iconQuery] || weatherIcons["warning"];
|
||||
return Widget.Label({
|
||||
hpack: "center",
|
||||
class_name: "hourly-weather-icon txt-icon",
|
||||
|
||||
@@ -6,7 +6,7 @@ const { terminal } = options;
|
||||
const { enable_gpu } = options.menus.dashboard.stats;
|
||||
|
||||
const Stats = () => {
|
||||
const divide = ([total, free]) => free / total;
|
||||
const divide = ([total, free]: number[]) => free / total;
|
||||
|
||||
const formatSizeInGB = (sizeInKB: number) =>
|
||||
Number((sizeInKB / 1024 ** 2).toFixed(2));
|
||||
@@ -26,7 +26,8 @@ const Stats = () => {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return divide([100, cpuOut.split(/\s+/)[1].replace(",", ".")]);
|
||||
const freeCpu = parseFloat(cpuOut.split(/\s+/)[1].replace(",", "."));
|
||||
return divide([100, freeCpu]);
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
const powerProfiles = await Service.import("powerprofiles");
|
||||
import { PowerProfile, PowerProfileObject, PowerProfiles } from "lib/types/powerprofiles.js";
|
||||
import icons from "../../../icons/index.js";
|
||||
|
||||
const EnergyProfiles = () => {
|
||||
const isValidProfile = (profile: string): profile is PowerProfile =>
|
||||
profile === "power-saver" || profile === "balanced" || profile === "performance";
|
||||
|
||||
return Widget.Box({
|
||||
class_name: "menu-section-container energy",
|
||||
vertical: true,
|
||||
@@ -21,13 +25,20 @@ const EnergyProfiles = () => {
|
||||
vpack: "fill",
|
||||
vexpand: true,
|
||||
vertical: true,
|
||||
children: powerProfiles.bind("profiles").as((profiles) => {
|
||||
return profiles.map((prof) => {
|
||||
const ProfileLabels = {
|
||||
children: powerProfiles.bind("profiles").as((profiles: PowerProfiles) => {
|
||||
return profiles.map((prof: PowerProfileObject) => {
|
||||
const profileLabels = {
|
||||
"power-saver": "Power Saver",
|
||||
balanced: "Balanced",
|
||||
performance: "Performance",
|
||||
};
|
||||
|
||||
const profileType = prof.Profile;
|
||||
|
||||
if (!isValidProfile(profileType)) {
|
||||
return profileLabels.balanced;
|
||||
}
|
||||
|
||||
return Widget.Button({
|
||||
on_primary_click: () => {
|
||||
powerProfiles.active_profile = prof.Profile;
|
||||
@@ -39,11 +50,11 @@ const EnergyProfiles = () => {
|
||||
children: [
|
||||
Widget.Icon({
|
||||
class_name: "power-profile-icon",
|
||||
icon: icons.powerprofile[prof.Profile],
|
||||
icon: icons.powerprofile[profileType],
|
||||
}),
|
||||
Widget.Label({
|
||||
class_name: "power-profile-label",
|
||||
label: ProfileLabels[prof.Profile],
|
||||
label: profileLabels[profileType],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import { MprisPlayer } from "types/service/mpris.js";
|
||||
import icons from "../../../icons/index.js";
|
||||
import { LoopStatus, PlaybackStatus } from "lib/types/mpris.js";
|
||||
const media = await Service.import("mpris");
|
||||
|
||||
const Controls = (getPlayerInfo: Function) => {
|
||||
const isValidLoopStatus = (status: string): status is LoopStatus =>
|
||||
["none", "track", "playlist"].includes(status);
|
||||
|
||||
const isValidPlaybackStatus = (status: string): status is PlaybackStatus =>
|
||||
["playing", "paused", "stopped"].includes(status);
|
||||
|
||||
const isLoopActive = (player: MprisPlayer) => {
|
||||
return player["loop-status"] !== null &&
|
||||
["track", "playlist"].includes(player["loop-status"].toLowerCase())
|
||||
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"]
|
||||
return player["shuffle_status"] !== null && player["shuffle_status"]
|
||||
? "active"
|
||||
: "";
|
||||
};
|
||||
@@ -98,13 +105,18 @@ const Controls = (getPlayerInfo: Function) => {
|
||||
media,
|
||||
"changed",
|
||||
() => {
|
||||
const foundPlayer = getPlayerInfo();
|
||||
const foundPlayer: MprisPlayer = getPlayerInfo();
|
||||
if (foundPlayer === undefined) {
|
||||
return icons.mpris["paused"];
|
||||
}
|
||||
return icons.mpris[
|
||||
foundPlayer.play_back_status.toLowerCase()
|
||||
];
|
||||
const playbackStatus = foundPlayer.play_back_status?.toLowerCase();
|
||||
|
||||
if (playbackStatus && isValidPlaybackStatus(playbackStatus)) {
|
||||
return icons.mpris[playbackStatus];
|
||||
}
|
||||
else {
|
||||
return icons.mpris["paused"];
|
||||
}
|
||||
},
|
||||
),
|
||||
}),
|
||||
@@ -161,18 +173,21 @@ const Controls = (getPlayerInfo: Function) => {
|
||||
child: Widget.Icon({
|
||||
setup: (self) => {
|
||||
self.hook(media, () => {
|
||||
const foundPlayer = getPlayerInfo();
|
||||
const foundPlayer: MprisPlayer = 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()
|
||||
];
|
||||
const loopStatus = foundPlayer.loop_status?.toLowerCase();
|
||||
|
||||
if (loopStatus && isValidLoopStatus(loopStatus)) {
|
||||
self.icon = icons.mpris.loop[loopStatus];
|
||||
}
|
||||
else {
|
||||
self.icon = icons.mpris.loop["none"];
|
||||
}
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -37,17 +37,17 @@ const Media = () => {
|
||||
};
|
||||
|
||||
const isPlaying = media.players.find(
|
||||
(p) => p["play-back-status"] === "Playing",
|
||||
(p) => p["play_back_status"] === "Playing",
|
||||
);
|
||||
|
||||
const playerStillExists = media.players.some(
|
||||
(p) => curPlayer.value === p["bus-name"],
|
||||
(p) => curPlayer.value === p["bus_name"],
|
||||
);
|
||||
|
||||
const nextPlayerUp = media.players.sort(
|
||||
(a, b) =>
|
||||
statusOrder[a["play-back-status"]] -
|
||||
statusOrder[b["play-back-status"]],
|
||||
statusOrder[a["play_back_status"]] -
|
||||
statusOrder[b["play_back_status"]],
|
||||
)[0].bus_name;
|
||||
|
||||
if (isPlaying || !playerStillExists) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Network } from "types/service/network.js";
|
||||
import { AccessPoint } from "lib/types/network.js";
|
||||
import { AccessPoint, WifiStatus } from "lib/types/network.js";
|
||||
import { Variable } from "types/variable.js";
|
||||
import { getWifiIcon } from "../utils.js";
|
||||
import { WIFI_STATUS_MAP } from "globals/network.js";
|
||||
const renderWAPs = (self: any, network: Network, staging: Variable<AccessPoint>, connecting: Variable<string>) => {
|
||||
const getIdBySsid = (ssid: string, nmcliOutput: string) => {
|
||||
const lines = nmcliOutput.trim().split("\n");
|
||||
@@ -14,33 +15,36 @@ const renderWAPs = (self: any, network: Network, staging: Variable<AccessPoint>,
|
||||
return null;
|
||||
};
|
||||
|
||||
const WifiStatusMap = {
|
||||
unknown: "Status Unknown",
|
||||
unmanaged: "Unmanaged",
|
||||
unavailable: "Unavailable",
|
||||
disconnected: "Disconnected",
|
||||
prepare: "Preparing Connecting",
|
||||
config: "Connecting",
|
||||
need_auth: "Needs Authentication",
|
||||
ip_config: "Requesting IP",
|
||||
ip_check: "Checking Access",
|
||||
secondaries: "Waiting on Secondaries",
|
||||
activated: "Connected",
|
||||
deactivating: "Disconnecting",
|
||||
failed: "Connection Failed",
|
||||
const isValidWifiStatus = (status: string): status is WifiStatus => {
|
||||
return status in WIFI_STATUS_MAP;
|
||||
};
|
||||
|
||||
|
||||
const getWifiStatus = () => {
|
||||
const wifiState = network.wifi.state?.toLowerCase();
|
||||
|
||||
if (wifiState && isValidWifiStatus(wifiState)) {
|
||||
return WIFI_STATUS_MAP[wifiState];
|
||||
}
|
||||
return WIFI_STATUS_MAP["unknown"];
|
||||
}
|
||||
|
||||
self.hook(network, () => {
|
||||
Utils.merge([staging.bind("value"), connecting.bind("value")], () => {
|
||||
// Sometimes the network service will yield a "this._device is undefined" when
|
||||
// NOTE: Sometimes the network service will yield a "this._device is undefined" when
|
||||
// trying to access the "access_points" property. So we must validate that
|
||||
// it's not 'undefined'
|
||||
//
|
||||
// --
|
||||
// Also this is an AGS bug that needs to be fixed
|
||||
let WAPs =
|
||||
network.wifi._device !== undefined ? network.wifi["access-points"] : [];
|
||||
|
||||
// TODO: Remove @ts-ignore once AGS bug is fixed
|
||||
// @ts-ignore
|
||||
let WAPs = network.wifi._device !== undefined
|
||||
? network.wifi["access_points"]
|
||||
: [];
|
||||
|
||||
const dedupeWAPs = () => {
|
||||
const dedupMap = {};
|
||||
const dedupMap: Record<string, AccessPoint> = {};
|
||||
WAPs.forEach((item: AccessPoint) => {
|
||||
if (item.ssid !== null && !Object.hasOwnProperty.call(dedupMap, item.ssid)) {
|
||||
dedupMap[item.ssid] = item;
|
||||
@@ -153,8 +157,7 @@ const renderWAPs = (self: any, network: Network, staging: Variable<AccessPoint>,
|
||||
child: Widget.Label({
|
||||
hpack: "start",
|
||||
class_name: "connection-status dim",
|
||||
label:
|
||||
WifiStatusMap[network.wifi.state.toLowerCase()],
|
||||
label: getWifiStatus()
|
||||
}),
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -1,32 +1,18 @@
|
||||
import icons from "../../../../icons/index.js";
|
||||
import { Notification } from "types/service/notifications.js";
|
||||
import { NotificationIcon } from "lib/types/notification.js";
|
||||
import { getNotificationIcon } from "globals/notification";
|
||||
|
||||
const NotificationIcon = ({ app_entry, app_icon, app_name }) => {
|
||||
let icon = icons.fallback.notification;
|
||||
|
||||
if (
|
||||
Utils.lookUpIcon(app_name) ||
|
||||
Utils.lookUpIcon(app_name.toLowerCase() || "")
|
||||
)
|
||||
icon = Utils.lookUpIcon(app_name)
|
||||
? app_name
|
||||
: Utils.lookUpIcon(app_name.toLowerCase())
|
||||
? app_name.toLowerCase()
|
||||
: "";
|
||||
|
||||
if (Utils.lookUpIcon(app_icon) && icon === "") icon = app_icon;
|
||||
|
||||
if (Utils.lookUpIcon(app_entry || "") && icon === "") icon = app_entry || "";
|
||||
|
||||
return Widget.Box({
|
||||
css: `
|
||||
const NotificationIcon = ({ app_entry = "", app_icon = "", app_name = "" }: Partial<Notification>) => {
|
||||
return Widget.Box({
|
||||
css: `
|
||||
min-width: 2rem;
|
||||
min-height: 2rem;
|
||||
`,
|
||||
child: Widget.Icon({
|
||||
class_name: "notification-icon menu",
|
||||
icon,
|
||||
}),
|
||||
});
|
||||
child: Widget.Icon({
|
||||
class_name: "notification-icon menu",
|
||||
icon: getNotificationIcon(app_name, app_icon, app_entry),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export { NotificationIcon };
|
||||
|
||||
@@ -10,7 +10,7 @@ export const NotificationPager = (curPage: Variable<number>) => {
|
||||
class_name: "notification-menu-pager",
|
||||
hexpand: true,
|
||||
vexpand: false,
|
||||
children: Utils.merge([curPage.bind("value"), displayedTotal.bind("value"), notifs.bind("notifications")], (currentPage, dispTotal, notifications) => {
|
||||
children: Utils.merge([curPage.bind("value"), displayedTotal.bind("value"), notifs.bind("notifications")], (currentPage, dispTotal, _) => {
|
||||
return [
|
||||
Widget.Button({
|
||||
hexpand: true,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import icons from "lib/icons";
|
||||
import { PowerOptions } from "lib/types/options";
|
||||
import options from "options";
|
||||
import powermenu from "../power/helpers/actions";
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
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",
|
||||
|
||||
@@ -1,32 +1,17 @@
|
||||
import icons from "../../icons/index.js";
|
||||
import { Notification } from "types/service/notifications.js";
|
||||
import { getNotificationIcon } from "globals/notification.js";
|
||||
|
||||
const NotificationIcon = ({ app_entry, app_icon, app_name }) => {
|
||||
let icon = icons.fallback.notification;
|
||||
|
||||
if (
|
||||
Utils.lookUpIcon(app_name) ||
|
||||
Utils.lookUpIcon(app_name.toLowerCase() || "")
|
||||
)
|
||||
icon = Utils.lookUpIcon(app_name)
|
||||
? app_name
|
||||
: Utils.lookUpIcon(app_name.toLowerCase())
|
||||
? app_name.toLowerCase()
|
||||
: "";
|
||||
|
||||
if (Utils.lookUpIcon(app_icon) && icon === "") icon = app_icon;
|
||||
|
||||
if (Utils.lookUpIcon(app_entry || "") && icon === "") icon = app_entry || "";
|
||||
|
||||
return Widget.Box({
|
||||
css: `
|
||||
const NotificationIcon = ({ app_entry = "", app_icon = "", app_name = "" }: Partial<Notification>) => {
|
||||
return Widget.Box({
|
||||
css: `
|
||||
min-width: 2rem;
|
||||
min-height: 2rem;
|
||||
`,
|
||||
child: Widget.Icon({
|
||||
class_name: "notification-icon",
|
||||
icon,
|
||||
}),
|
||||
});
|
||||
child: Widget.Icon({
|
||||
class_name: "notification-icon",
|
||||
icon: getNotificationIcon(app_name, app_icon, app_entry)
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export { NotificationIcon };
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { OSDOrientation } from "lib/types/options";
|
||||
import brightness from "services/Brightness"
|
||||
const audio = await Service.import("audio")
|
||||
|
||||
export const OSDIcon = (ort: OSDOrientation) => {
|
||||
export const OSDIcon = () => {
|
||||
return Widget.Box({
|
||||
class_name: "osd-icon-container",
|
||||
hexpand: true,
|
||||
|
||||
@@ -9,6 +9,7 @@ const audio = await Service.import("audio")
|
||||
|
||||
const {
|
||||
enable,
|
||||
duration,
|
||||
orientation,
|
||||
location,
|
||||
active_monitor,
|
||||
@@ -21,8 +22,6 @@ hyprland.active.connect("changed", () => {
|
||||
curMonitor.value = hyprland.active.monitor.id;
|
||||
})
|
||||
|
||||
const DELAY = 2500;
|
||||
|
||||
let count = 0
|
||||
const handleReveal = (self: any, property: string) => {
|
||||
if (!enable.value) {
|
||||
@@ -30,7 +29,7 @@ const handleReveal = (self: any, property: string) => {
|
||||
}
|
||||
self[property] = true
|
||||
count++
|
||||
Utils.timeout(DELAY, () => {
|
||||
Utils.timeout(duration.value, () => {
|
||||
count--
|
||||
|
||||
if (count === 0)
|
||||
@@ -39,8 +38,6 @@ const handleReveal = (self: any, property: string) => {
|
||||
}
|
||||
|
||||
const renderOSD = () => {
|
||||
|
||||
|
||||
return Widget.Revealer({
|
||||
transition: "crossfade",
|
||||
reveal_child: false,
|
||||
@@ -72,16 +69,16 @@ const renderOSD = () => {
|
||||
children: orientation.bind("value").as(ort => {
|
||||
if (ort === "vertical") {
|
||||
return [
|
||||
OSDLabel(ort),
|
||||
OSDLabel(),
|
||||
OSDBar(ort),
|
||||
OSDIcon(ort)
|
||||
OSDIcon()
|
||||
]
|
||||
}
|
||||
|
||||
return [
|
||||
OSDIcon(ort),
|
||||
OSDIcon(),
|
||||
OSDBar(ort),
|
||||
OSDLabel(ort),
|
||||
OSDLabel(),
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { OSDOrientation } from "lib/types/options";
|
||||
import brightness from "services/Brightness"
|
||||
import options from "options"
|
||||
const audio = await Service.import("audio")
|
||||
|
||||
export const OSDLabel = (ort: OSDOrientation) => {
|
||||
export const OSDLabel = () => {
|
||||
return Widget.Box({
|
||||
class_name: "osd-label-container",
|
||||
hexpand: true,
|
||||
|
||||
@@ -109,6 +109,7 @@ const options = mkOptions(OPTIONS, {
|
||||
},
|
||||
osd: {
|
||||
scaling: opt(100),
|
||||
duration: opt(2500),
|
||||
enable: opt(true),
|
||||
orientation: opt<OSDOrientation>("vertical"),
|
||||
opacity: opt(100),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import options from "options";
|
||||
import { bash, dependencies } from "lib/utils";
|
||||
import { MatugenColors } from "lib/types/options";
|
||||
import { MatugenColors, RecursiveOptionsObject } from "lib/types/options";
|
||||
import { initializeTrackers } from "./options_trackers";
|
||||
import { generateMatugenColors, replaceHexValues } from "../services/matugen/index";
|
||||
import { isHexColor, isOpt, isRecursiveOptionsObject } from "globals/variables";
|
||||
import { Opt } from "lib/option";
|
||||
|
||||
const deps = [
|
||||
"font",
|
||||
@@ -13,27 +15,37 @@ const deps = [
|
||||
"bar.battery.blocks",
|
||||
];
|
||||
|
||||
function extractVariables(theme: typeof options.theme, prefix = "", matugenColors: MatugenColors | undefined) {
|
||||
function extractVariables(
|
||||
theme: RecursiveOptionsObject,
|
||||
prefix = "",
|
||||
matugenColors?: MatugenColors
|
||||
): string[] {
|
||||
let result = [] as string[];
|
||||
for (let key in theme) {
|
||||
if (theme.hasOwnProperty(key)) {
|
||||
const value = theme[key];
|
||||
if (!theme.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const newPrefix = prefix ? `${prefix}-${key}` : key;
|
||||
const value = theme[key];
|
||||
|
||||
const isColor = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value.value);
|
||||
const replacedValue = isColor && matugenColors !== undefined ? replaceHexValues(value.value, matugenColors) : value.value;
|
||||
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
||||
if (typeof value.value !== 'undefined') {
|
||||
result.push(`$${newPrefix}: ${replacedValue};`);
|
||||
} else {
|
||||
result = result.concat(extractVariables(value, newPrefix, matugenColors));
|
||||
}
|
||||
} else if (typeof value === 'function' && value.name === 'opt') {
|
||||
result.push(`$${newPrefix}: ${replacedValue};`);
|
||||
}
|
||||
const newPrefix = prefix ? `${prefix}-${key}` : key;
|
||||
|
||||
const isColor = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value.value);
|
||||
const replacedValue = isColor && matugenColors !== undefined ? replaceHexValues(value.value, matugenColors) : value.value;
|
||||
|
||||
if (typeof value === 'function') {
|
||||
result.push(`$${newPrefix}: ${replacedValue};`);
|
||||
continue;
|
||||
}
|
||||
if (typeof value !== 'object' || value === null || Array.isArray(value)) continue;
|
||||
|
||||
if (typeof value.value !== 'undefined') {
|
||||
result.push(`$${newPrefix}: ${replacedValue};`);
|
||||
} else {
|
||||
result = result.concat(extractVariables(value as RecursiveOptionsObject, newPrefix, matugenColors));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
border-radius: if($osd-orientation =="vertical", 0em 0em $osd-radius $osd-radius, $osd-radius 0em 0em $osd-radius );
|
||||
|
||||
.osd-icon {
|
||||
font-size: 2em;
|
||||
padding: if($osd-orientation =="vertical", 0.2em 0em, 0em 0.2em);
|
||||
font-size: 2.1em;
|
||||
padding: if($osd-orientation =="vertical", 0.2em 0em, 0em 0.4em);
|
||||
color: $osd-icon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
@@ -10,11 +11,14 @@
|
||||
"checkJs": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitThis": true,
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"types",
|
||||
"lib/types/globals",
|
||||
],
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export const OSDSettings = () => {
|
||||
children: [
|
||||
Header('On Screen Display'),
|
||||
Option({ opt: options.theme.osd.enable, title: 'Enabled', type: 'boolean' }),
|
||||
Option({ opt: options.theme.osd.duration, title: 'Duration', type: 'number', min: 100, max: 10000, increment: 500 }),
|
||||
Option({ opt: options.theme.osd.orientation, title: 'Orientation', type: 'enum', enums: ["horizontal", "vertical"] }),
|
||||
Option({ opt: options.theme.osd.location, title: 'Position', subtitle: 'Position of the OSD on the screen', type: 'enum', enums: ["top left", "top", "top right", "right", "bottom right", "bottom", "bottom left", "left"] }),
|
||||
Option({ opt: options.theme.osd.monitor, title: 'Monitor', subtitle: 'The ID of the monitor on which to display the OSD', type: 'number' }),
|
||||
|
||||
@@ -2,14 +2,15 @@ import Gtk from "gi://Gtk?version=3.0";
|
||||
import Gio from "gi://Gio"
|
||||
import { bash, Notify } from "lib/utils";
|
||||
import icons from "lib/icons"
|
||||
import { Config } from "lib/types/filechooser";
|
||||
import { hexColorPattern } from "globals/useTheme";
|
||||
import { isHexColor } from "globals/variables";
|
||||
|
||||
const whiteListedThemeProp = [
|
||||
"theme.bar.buttons.style"
|
||||
];
|
||||
|
||||
|
||||
// Helper functions
|
||||
export const loadJsonFile = (filePath: string): object | null => {
|
||||
export const loadJsonFile = (filePath: string): Config | null => {
|
||||
let file = Gio.File.new_for_path(filePath as string);
|
||||
let [success, content] = file.load_contents(null);
|
||||
|
||||
@@ -32,10 +33,8 @@ export const saveConfigToFile = (config: object, filePath: string): void => {
|
||||
dataOutputStream.close(null);
|
||||
}
|
||||
|
||||
export const hexColorPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
|
||||
|
||||
export const filterConfigForThemeOnly = (config: object) => {
|
||||
let filteredConfig = {};
|
||||
export const filterConfigForThemeOnly = (config: Config): Config => {
|
||||
let filteredConfig: Config = {};
|
||||
|
||||
for (let key in config) {
|
||||
if (typeof config[key] === 'string' && hexColorPattern.test(config[key])) {
|
||||
@@ -47,8 +46,8 @@ export const filterConfigForThemeOnly = (config: object) => {
|
||||
return filteredConfig;
|
||||
};
|
||||
|
||||
export const filterConfigForNonTheme = (config: object) => {
|
||||
let filteredConfig = {};
|
||||
export const filterConfigForNonTheme = (config: Config): Config => {
|
||||
let filteredConfig: Config = {};
|
||||
for (let key in config) {
|
||||
if (whiteListedThemeProp.includes(key)) {
|
||||
continue;
|
||||
@@ -75,12 +74,11 @@ export const saveFileDialog = (filePath: string, themeOnly: boolean): void => {
|
||||
let jsonObject = JSON.parse(jsonString);
|
||||
|
||||
// Function to filter hex color pairs
|
||||
const filterHexColorPairs = (jsonObject: object) => {
|
||||
const hexColorPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
|
||||
let filteredObject = {};
|
||||
const filterHexColorPairs = (jsonObject: Config) => {
|
||||
let filteredObject: Config = {};
|
||||
|
||||
for (let key in jsonObject) {
|
||||
if (typeof jsonObject[key] === 'string' && hexColorPattern.test(jsonObject[key])) {
|
||||
if (typeof jsonObject[key] === 'string' && isHexColor(jsonObject[key])) {
|
||||
filteredObject[key] = jsonObject[key];
|
||||
} else if (whiteListedThemeProp.includes(key)) {
|
||||
filteredObject[key] = jsonObject[key];
|
||||
@@ -92,9 +90,8 @@ export const saveFileDialog = (filePath: string, themeOnly: boolean): void => {
|
||||
};
|
||||
|
||||
// Function to filter out hex color pairs (keep only non-hex color value)
|
||||
const filterOutHexColorPairs = (jsonObject: object) => {
|
||||
const hexColorPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
|
||||
let filteredObject = {};
|
||||
const filterOutHexColorPairs = (jsonObject: Config) => {
|
||||
let filteredObject: Config = {};
|
||||
|
||||
for (let key in jsonObject) {
|
||||
// do not add key-value pair if its in whiteListedThemeProp
|
||||
@@ -102,7 +99,7 @@ export const saveFileDialog = (filePath: string, themeOnly: boolean): void => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(typeof jsonObject[key] === 'string' && hexColorPattern.test(jsonObject[key]))) {
|
||||
if (!(typeof jsonObject[key] === 'string' && isHexColor(jsonObject[key]))) {
|
||||
filteredObject[key] = jsonObject[key];
|
||||
}
|
||||
}
|
||||
@@ -192,8 +189,19 @@ export const importFiles = (themeOnly: boolean = false): void => {
|
||||
return;
|
||||
}
|
||||
if (response === Gtk.ResponseType.ACCEPT) {
|
||||
let filePath = dialog.get_filename();
|
||||
let importedConfig = loadJsonFile(filePath as string);
|
||||
let filePath: string | null = dialog.get_filename();
|
||||
|
||||
if (filePath === null) {
|
||||
Notify({
|
||||
summary: "Failed to import",
|
||||
body: "No file selected.",
|
||||
iconName: icons.ui.warning,
|
||||
timeout: 5000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let importedConfig = loadJsonFile(filePath);
|
||||
|
||||
if (!importedConfig) {
|
||||
dialog.destroy();
|
||||
|
||||
Reference in New Issue
Block a user