🔧 chore(scripts/config): stop using singleton instance, add type declarations(for completion)
this will make easier to work with multiple configuration files at the same time
This commit is contained in:
+64
-3
@@ -19,6 +19,70 @@ import { Scope } from "/usr/share/ags/js/gnim/src/jsx/scope";
|
||||
import App from "ags/gtk4/app"
|
||||
import GObject from "ags/gobject";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import GLib from "gi://GLib?version=2.0";
|
||||
|
||||
|
||||
type ConfigEntries = {
|
||||
workspaces?: Partial<{
|
||||
/** this is the function that shows the Workspace's IDs
|
||||
* around the current workspace if one breaks the crescent order.
|
||||
* It basically helps keyboard navigation between workspaces.
|
||||
* ---
|
||||
* Example: 1(empty, current, shows ID), 2(empty, does not appear(makes
|
||||
* the previous not to be in a crescent order)), 3(not empty, shows ID) */
|
||||
enable_helper: boolean;
|
||||
/** breaks `enable_helper`, makes all workspaces show their respective ID
|
||||
* by default */
|
||||
always_show_id: boolean;
|
||||
}>;
|
||||
|
||||
clock?: Partial<{
|
||||
/** use the same format as gnu's `date` command */
|
||||
date_format: string;
|
||||
}>;
|
||||
|
||||
notifications?: Partial<{
|
||||
timeout_low: number;
|
||||
timeout_normal: number;
|
||||
timeout_critical: number;
|
||||
}>;
|
||||
|
||||
night_light?: Partial<{
|
||||
/** whether to save night light values to disk */
|
||||
save_on_shutdown: boolean;
|
||||
}>;
|
||||
|
||||
misc?: Partial<{
|
||||
play_bell_on_volume_change: boolean;
|
||||
}>;
|
||||
};
|
||||
|
||||
export const generalConfig = new Config<keyof ConfigEntries, ConfigEntries[keyof ConfigEntries]>(
|
||||
`${GLib.get_user_config_dir()}/colorshell/config.json`, {
|
||||
notifications: {
|
||||
timeout_low: 4000,
|
||||
timeout_normal: 6000,
|
||||
timeout_critical: 0
|
||||
},
|
||||
|
||||
night_light: {
|
||||
save_on_shutdown: true
|
||||
},
|
||||
|
||||
workspaces: {
|
||||
always_show_id: false,
|
||||
enable_helper: true
|
||||
},
|
||||
|
||||
clock: {
|
||||
date_format: "%A %d, %H:%M"
|
||||
},
|
||||
|
||||
misc: {
|
||||
play_bell_on_volume_change: true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const appScope: Scope = new Scope(null);
|
||||
|
||||
@@ -46,9 +110,6 @@ App.start({
|
||||
console.log(`Colorshell: initialized instance as: "${ App.instanceName || "astal" }"`);
|
||||
connections.set(App, App.connect("shutdown", () => appScope.dispose()));
|
||||
|
||||
console.log("Config: initializing configuration file");
|
||||
Config.getDefault();
|
||||
|
||||
Stylesheet.getDefault().compileApply();
|
||||
|
||||
// Init clipboard module
|
||||
|
||||
@@ -5,8 +5,8 @@ import { timeout } from "ags/time";
|
||||
import { Runner } from "../runner/Runner";
|
||||
import { showWorkspaceNumber } from "../widget/bar/Workspaces";
|
||||
import { playSystemBell } from "./utils";
|
||||
import { Config } from "./config";
|
||||
import { player, setPlayer } from "../widget/bar/Media";
|
||||
import { generalConfig } from "../app";
|
||||
|
||||
import AstalIO from "gi://AstalIO";
|
||||
import GLib from "gi://GLib?version=2.0";
|
||||
@@ -236,7 +236,7 @@ function handleVolumeArgs(args: Array<string>) {
|
||||
Wireplumber.getDefault().increaseSinkVolume(Number.parseInt(args[2]))
|
||||
: Wireplumber.getDefault().increaseSourceVolume(Number.parseInt(args[2]))
|
||||
|
||||
Config.getDefault().getProperty("misc.play_bell_on_volume_change", "boolean") === true &&
|
||||
generalConfig.getProperty("misc.play_bell_on_volume_change", "boolean") === true &&
|
||||
playSystemBell();
|
||||
|
||||
return `Done increasing volume by ${args[2]}`;
|
||||
@@ -246,7 +246,7 @@ function handleVolumeArgs(args: Array<string>) {
|
||||
Wireplumber.getDefault().decreaseSinkVolume(Number.parseInt(args[2]))
|
||||
: Wireplumber.getDefault().decreaseSourceVolume(Number.parseInt(args[2]))
|
||||
|
||||
Config.getDefault().getProperty("misc.play_bell_on_volume_change", "boolean") === true &&
|
||||
generalConfig.getProperty("misc.play_bell_on_volume_change", "boolean") === true &&
|
||||
playSystemBell();
|
||||
|
||||
return `Done decreasing volume to ${args[2]}`;
|
||||
|
||||
+19
-89
@@ -12,99 +12,36 @@ import { Accessor } from "ags";
|
||||
|
||||
|
||||
export { Config };
|
||||
|
||||
export type ConfigEntries = Partial<{
|
||||
workspaces: Partial<{
|
||||
/** this is the function that shows the Workspace's IDs
|
||||
* around the current workspace if one breaks the crescent order.
|
||||
* It basically helps keyboard navigation between workspaces.
|
||||
* ---
|
||||
* Example: 1(empty, current, shows ID), 2(empty, does not appear(makes
|
||||
* the previous not to be in a crescent order)), 3(not empty, shows ID) */
|
||||
enable_helper: boolean;
|
||||
/** breaks `enable_helper`, makes all workspaces show their respective ID
|
||||
* by default */
|
||||
always_show_id: boolean;
|
||||
}>;
|
||||
|
||||
clock: Partial<{
|
||||
/** use the same format as gnu's `date` command */
|
||||
date_format: string;
|
||||
}>;
|
||||
|
||||
notifications: Partial<{
|
||||
timeout_low: number;
|
||||
timeout_normal: number;
|
||||
timeout_critical: number;
|
||||
}>;
|
||||
|
||||
night_light: Partial<{
|
||||
/** whether to save night light values to disk */
|
||||
save_on_shutdown: boolean;
|
||||
}>;
|
||||
|
||||
misc: Partial<{
|
||||
play_bell_on_volume_change: boolean;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
type ValueTypes = "string" | "boolean" | "object" | "number" | "undefined" | "any";
|
||||
|
||||
interface ConfigSignals extends GObject.Object.SignalSignatures {
|
||||
"notify::entries": (entries: ConfigEntries) => void;
|
||||
}
|
||||
|
||||
@register({ GTypeName: "Config" })
|
||||
class Config extends GObject.Object {
|
||||
private static instance: Config;
|
||||
|
||||
declare $signals: ConfigSignals;
|
||||
|
||||
private readonly defaultFile = Gio.File.new_for_path(
|
||||
`${GLib.get_user_config_dir()}/colorshell/config.json`);
|
||||
class Config<K extends NonNullable<string|number|symbol>, V extends string|object|any> extends GObject.Object {
|
||||
declare $signals: GObject.Object.SignalSignatures & {
|
||||
"notify::entries": (entries: Record<K, V>) => void;
|
||||
};
|
||||
|
||||
/** unmodified object with default entries. User-values are stored
|
||||
* in the `entries` field */
|
||||
public readonly defaults: ConfigEntries = {
|
||||
notifications: {
|
||||
timeout_low: 4000,
|
||||
timeout_normal: 6000,
|
||||
timeout_critical: 0
|
||||
},
|
||||
|
||||
night_light: {
|
||||
save_on_shutdown: true
|
||||
},
|
||||
|
||||
workspaces: {
|
||||
always_show_id: false,
|
||||
enable_helper: true
|
||||
},
|
||||
|
||||
clock: {
|
||||
date_format: "%A %d, %H:%M"
|
||||
},
|
||||
|
||||
misc: {
|
||||
play_bell_on_volume_change: true
|
||||
}
|
||||
};
|
||||
public readonly defaults: Record<K, V>;
|
||||
|
||||
@getter(Object)
|
||||
public get entries() { return this.#entries; }
|
||||
public get entries(): object { return this.#entries; }
|
||||
|
||||
#file: Gio.File;
|
||||
#entries: ConfigEntries = this.defaults;
|
||||
#entries: Record<K, V>;
|
||||
|
||||
private timeout: (AstalIO.Time|boolean|undefined);
|
||||
public get file() { return this.#file; };
|
||||
|
||||
constructor(filePath?: (Gio.File|string)) {
|
||||
constructor(filePath: Gio.File|string, defaults?: Record<K, V>) {
|
||||
super();
|
||||
|
||||
this.defaults = (defaults ?? {}) as Record<K, V>;
|
||||
this.#entries = { ...defaults } as Record<K, V>;
|
||||
|
||||
this.#file = (typeof filePath === "string") ?
|
||||
Gio.File.new_for_path(filePath)
|
||||
: (filePath ?? this.defaultFile);
|
||||
: filePath;
|
||||
|
||||
if(!this.#file.query_exists(null)) {
|
||||
this.#file.make_directory_with_parents(null);
|
||||
@@ -156,19 +93,12 @@ class Config extends GObject.Object {
|
||||
);
|
||||
}
|
||||
|
||||
public static getDefault(): Config {
|
||||
if(!this.instance)
|
||||
this.instance = new Config();
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
private async readFile(): Promise<void> {
|
||||
await readFileAsync(this.#file.get_path()!).then((content) => {
|
||||
let config: (ConfigEntries|undefined);
|
||||
let config: (Record<K, V>|undefined);
|
||||
|
||||
try {
|
||||
config = JSON.parse(content) as ConfigEntries;
|
||||
config = JSON.parse(content) as Record<K, V>;
|
||||
} catch(e) {
|
||||
Notifications.getDefault().sendNotification({
|
||||
urgency: AstalNotifd.Urgency.NORMAL,
|
||||
@@ -189,7 +119,7 @@ class Config extends GObject.Object {
|
||||
return;
|
||||
|
||||
// TODO needs more work, like object-recursive(infinite depth) entry attributions
|
||||
this.entries[k as keyof typeof this.entries] = config[k as keyof typeof config];
|
||||
this.#entries[k as keyof Record<K, V>] = config[k as keyof typeof config];
|
||||
}
|
||||
|
||||
this.notify("entries");
|
||||
@@ -204,22 +134,22 @@ class Config extends GObject.Object {
|
||||
});
|
||||
}
|
||||
|
||||
public bindProperty(propertyPath: (keyof ConfigEntries|string), expectType?: ValueTypes): Accessor<any|undefined> {
|
||||
return new Accessor<ConfigEntries>(() => this.getProperty(propertyPath, expectType), (callback: () => void) => {
|
||||
public bindProperty(propertyPath: string, expectType?: ValueTypes): Accessor<any|undefined> {
|
||||
return new Accessor<Record<K, V>>(() => this.getProperty(propertyPath, expectType), (callback: () => void) => {
|
||||
const id = this.connect("notify::entries", () => callback());
|
||||
return () => this.disconnect(id);
|
||||
});
|
||||
}
|
||||
|
||||
public getProperty(path: string, expectType?: ValueTypes): (any|undefined) {
|
||||
return this._getProperty(path, this.entries, expectType);
|
||||
return this._getProperty(path, this.#entries, expectType);
|
||||
}
|
||||
|
||||
public getPropertyDefault(path: string, expectType?: ValueTypes): (any|undefined) {
|
||||
return this._getProperty(path, this.defaults, expectType);
|
||||
}
|
||||
|
||||
private _getProperty(path: string, entries: ConfigEntries, expectType?: ValueTypes): (any|undefined) {
|
||||
private _getProperty(path: string, entries: Record<K, V>, expectType?: ValueTypes): (any|undefined) {
|
||||
let property: any = entries;
|
||||
const pathArray = path.split('.').filter(str => str);
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Config } from "./config";
|
||||
import { timeout } from "ags/time";
|
||||
import { execAsync } from "ags/process";
|
||||
|
||||
import { readFile } from "ags/file";
|
||||
import { generalConfig } from "../app";
|
||||
import { onCleanup } from "ags";
|
||||
import GObject, { getter, property, register, signal } from "ags/gobject";
|
||||
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import AstalIO from "gi://AstalIO";
|
||||
import { onCleanup } from "ags";
|
||||
import Gio from "gi://Gio?version=2.0";
|
||||
import GLib from "gi://GLib?version=2.0";
|
||||
import { readFile } from "ags/file";
|
||||
|
||||
|
||||
export interface HistoryNotification {
|
||||
@@ -53,7 +53,7 @@ class Notifications extends GObject.Object {
|
||||
this.#connections.push(
|
||||
AstalNotifd.get_default().connect("notified", (notifd, id) => {
|
||||
const notification = notifd.get_notification(id);
|
||||
const notifTimeout = Config.getDefault().getProperty(
|
||||
const notifTimeout = generalConfig.getProperty(
|
||||
`notifications.timeout_${this.getUrgencyString(notification.urgency).toLowerCase()}`,
|
||||
"number") as number;
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import { Gtk } from "ags/gtk4";
|
||||
import { Windows } from "../../windows";
|
||||
import { createBinding } from "ags";
|
||||
import { time } from "../../scripts/utils";
|
||||
import { Config } from "../../scripts/config";
|
||||
import { generalConfig } from "../../app";
|
||||
|
||||
|
||||
export const Clock = () =>
|
||||
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((wins) =>
|
||||
@@ -14,7 +15,7 @@ export const Clock = () =>
|
||||
];
|
||||
}}
|
||||
label={time((dt) => dt.format(
|
||||
Config.getDefault().getProperty("clock.date_format", "string"))
|
||||
generalConfig.getProperty("clock.date_format", "string"))
|
||||
?? "An error occurred"
|
||||
)}
|
||||
/>;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Gtk } from "ags/gtk4";
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
import { getAppIcon, getSymbolicIcon } from "../../scripts/apps";
|
||||
import { Config } from "../../scripts/config";
|
||||
import { Separator } from "../Separator";
|
||||
import { generalConfig } from "../../app";
|
||||
import { createBinding, createComputed, createState, For, With } from "ags";
|
||||
import GObject from "gi://GObject?version=2.0";
|
||||
import { variableToBoolean } from "../../scripts/utils";
|
||||
|
||||
import GObject from "ags/gobject";
|
||||
|
||||
|
||||
const [showNumbers, setShowNumbers] = createState(false);
|
||||
export const showWorkspaceNumber = (show: boolean) =>
|
||||
setShowNumbers(show);
|
||||
@@ -83,8 +85,8 @@ export const Workspaces = () => {
|
||||
<For each={defaultWorkspaces}>
|
||||
{(ws: AstalHyprland.Workspace, i) => {
|
||||
const showId = createComputed([
|
||||
Config.getDefault().bindProperty("workspaces.always_show_id", "boolean").as(Boolean),
|
||||
Config.getDefault().bindProperty("workspaces.enable_helper", "boolean").as(Boolean),
|
||||
generalConfig.bindProperty("workspaces.always_show_id", "boolean").as(Boolean),
|
||||
generalConfig.bindProperty("workspaces.enable_helper", "boolean").as(Boolean),
|
||||
showNumbers,
|
||||
i
|
||||
], (alwaysShowIds, enableHelper, showIds, i) => {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Astal, Gdk, Gtk } from "ags/gtk4";
|
||||
import { execAsync } from "ags/process";
|
||||
import { generalConfig } from "../app";
|
||||
import { AskPopup } from "../widget/AskPopup";
|
||||
import { Notifications } from "../scripts/notifications";
|
||||
import { NightLight } from "../scripts/nightlight";
|
||||
import { Config } from "../scripts/config";
|
||||
import { time } from "../scripts/utils";
|
||||
|
||||
import GObject from "ags/gobject";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import Gio from "gi://Gio?version=2.0";
|
||||
import GObject from "gi://GObject?version=2.0";
|
||||
|
||||
|
||||
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
|
||||
@@ -64,7 +64,7 @@ export const LogoutMenu = (mon: number) =>
|
||||
title: "Power Off",
|
||||
text: "Are you sure you want to power off? Unsaved work will be lost.",
|
||||
onAccept: () => {
|
||||
Config.getDefault().getProperty("night_light.save_on_shutdown", "boolean") &&
|
||||
generalConfig.getProperty("night_light.save_on_shutdown", "boolean") &&
|
||||
NightLight.getDefault().saveData();
|
||||
|
||||
execAsync("systemctl poweroff");
|
||||
@@ -76,7 +76,7 @@ export const LogoutMenu = (mon: number) =>
|
||||
title: "Reboot",
|
||||
text: "Are you sure you want to Reboot? Unsaved work will be lost.",
|
||||
onAccept: () => {
|
||||
Config.getDefault().getProperty("night_light.save_on_shutdown", "boolean") &&
|
||||
generalConfig.getProperty("night_light.save_on_shutdown", "boolean") &&
|
||||
NightLight.getDefault().saveData();
|
||||
|
||||
execAsync("systemctl reboot");
|
||||
@@ -95,7 +95,7 @@ export const LogoutMenu = (mon: number) =>
|
||||
title: "Log out",
|
||||
text: "Are you sure you want to log out? Your session will be ended.",
|
||||
onAccept: () => {
|
||||
Config.getDefault().getProperty("night_light.save_on_shutdown", "boolean") &&
|
||||
generalConfig.getProperty("night_light.save_on_shutdown", "boolean") &&
|
||||
NightLight.getDefault().saveData();
|
||||
|
||||
execAsync(`hyprctl dispatch exit`).catch((err: Gio.IOErrorEnum) =>
|
||||
|
||||
Reference in New Issue
Block a user