diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-02-22 21:25:42 +1100 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-02-22 21:25:42 +1100 |
| commit | 6a043ce106dd853da4837a4fa1e68c2a6d67093b (patch) | |
| tree | 6ba54289db310b31da8c6d9c97a3f086c4271b64 | |
| parent | config: dynamic service confs (diff) | |
| download | caelestia-shell-6a043ce106dd853da4837a4fa1e68c2a6d67093b.tar.gz caelestia-shell-6a043ce106dd853da4837a4fa1e68c2a6d67093b.tar.bz2 caelestia-shell-6a043ce106dd853da4837a4fa1e68c2a6d67093b.zip | |
config: restore to default if not specified
Restore configs to default if not given in the config file instead of not changing them
Also sideleft configs
| -rw-r--r-- | config.ts | 167 | ||||
| -rw-r--r-- | src/modules/popdowns/sideleft.tsx | 18 |
2 files changed, 109 insertions, 76 deletions
@@ -2,165 +2,196 @@ import { GLib, monitorFile, readFileAsync, Variable } from "astal"; import { Astal } from "astal/gtk3"; import { loadStyleAsync } from "./app"; +type Settings<T> = { [P in keyof T]: T[P] extends object ? Settings<T[P]> : Variable<T[P]> }; + const CONFIG = `${GLib.get_user_config_dir()}/caelestia/shell.json`; -const s = <T>(v: T): Variable<T> => Variable(v); +const isObject = (o: any) => typeof o === "object" && o !== null && !Array.isArray(o); + +const deepMerge = <T extends object, U extends object>(a: T, b: U, path = ""): T & U => { + const merged: { [k: string]: any } = { ...b }; + for (const [k, v] of Object.entries(a)) { + if (b.hasOwnProperty(k)) { + const bv = b[k as keyof U]; + if (isObject(v) && isObject(bv)) merged[k] = deepMerge(v, bv as object, `${path}${k}.`); + else if (typeof v !== typeof bv) { + console.warn(`Invalid type for ${path}${k}: ${typeof v} != ${typeof bv}`); + merged[k] = v; + } + } else merged[k] = v; + } + return merged as any; +}; -const warn = (e: Error) => console.warn(`Invalid config: ${e}`); +const convertSettings = <T extends object>(obj: T): Settings<T> => + Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, isObject(v) ? convertSettings(v) : Variable(v)])) as any; -const updateSection = (from: { [k: string]: any }, to: { [k: string]: any }, path: string) => { +const updateSection = (from: { [k: string]: any }, to: { [k: string]: any }, path = "") => { for (const [k, v] of Object.entries(from)) { + print(`${path}${k}:`, to.hasOwnProperty(k)); if (to.hasOwnProperty(k)) { - if (typeof v === "object" && v !== null && !Array.isArray(v)) updateSection(v, to[k], `${path}.${k}`); - else if (typeof v === typeof to[k].get()) to[k].set(v); - else console.warn(`Invalid type for ${path}.${k}: ${typeof v} != ${typeof to[k].get()}`); - } else console.warn(`Unknown config key: ${path}.${k}`); + if (isObject(v)) updateSection(v, to[k], `${path}${k}.`); + else to[k].set(v); + } else console.warn(`Unknown config key: ${path}${k}`); } }; export const updateConfig = async () => { - const conf: { [k: string]: any } = JSON.parse(await readFileAsync(CONFIG)); - for (const [k, v] of Object.entries(conf)) { - if (config.hasOwnProperty(k)) updateSection(v, config[k as keyof typeof config], k); - else console.warn(`Unknown config key: ${k}`); - } + updateSection(deepMerge(DEFAULTS, JSON.parse(await readFileAsync(CONFIG))), config); loadStyleAsync().catch(console.error); }; export const initConfig = () => { - monitorFile(CONFIG, () => updateConfig().catch(warn)); - updateConfig().catch(warn); + monitorFile(CONFIG, () => updateConfig().catch(e => console.warn(`Invalid config: ${e}`))); + updateConfig().catch(e => console.warn(`Invalid config: ${e}`)); }; -const config = { +const DEFAULTS = { // Modules bar: { - vertical: s(true), + vertical: true, modules: { osIcon: { - enabled: s(true), + enabled: true, }, activeWindow: { - enabled: s(true), + enabled: true, }, mediaPlaying: { - enabled: s(true), + enabled: true, }, workspaces: { - enabled: s(true), - shown: s(5), + enabled: true, + shown: 5, }, tray: { - enabled: s(true), + enabled: true, }, statusIcons: { - enabled: s(true), + enabled: true, }, pkgUpdates: { - enabled: s(true), + enabled: true, }, notifCount: { - enabled: s(true), + enabled: true, }, battery: { - enabled: s(true), + enabled: true, }, dateTime: { - enabled: s(true), - format: s("%d/%m/%y %R"), - detailedFormat: s("%c"), + enabled: true, + format: "%d/%m/%y %R", + detailedFormat: "%c", }, power: { - enabled: s(true), + enabled: true, }, }, }, launcher: { - maxResults: s(15), // Max shown results at one time (i.e. max height of the launcher) + maxResults: 15, // Max shown results at one time (i.e. max height of the launcher) apps: { - maxResults: s(30), // Actual max results, -1 for infinite - pins: s([ + maxResults: 30, // Actual max results, -1 for infinite + pins: [ ["zen", "firefox", "waterfox", "google-chrome", "chromium", "brave-browser"], ["foot", "alacritty", "kitty", "wezterm"], ["thunar", "nemo", "nautilus"], ["codium", "code", "clion", "intellij-idea-ultimate-edition"], ["spotify-adblock", "spotify", "audacious", "elisa"], - ]), + ], }, files: { - maxResults: s(40), // Actual max results, -1 for infinite - fdOpts: s(["-a", "-t", "f"]), // Options to pass to `fd` + maxResults: 40, // Actual max results, -1 for infinite + fdOpts: ["-a", "-t", "f"], // Options to pass to `fd` }, math: { - maxResults: s(40), // Actual max results, -1 for infinite + maxResults: 40, // Actual max results, -1 for infinite }, windows: { - maxResults: s(-1), // Actual max results, -1 for infinite + maxResults: -1, // Actual max results, -1 for infinite weights: { // Weights for fuzzy sort - title: s(1), - class: s(1), - initialTitle: s(0.5), - initialClass: s(0.5), + title: 1, + class: 1, + initialTitle: 0.5, + initialClass: 0.5, }, }, todo: { - notify: s(true), + notify: true, }, }, notifpopups: { - maxPopups: s(-1), - expire: s(false), - agoTime: s(true), // Whether to show time in ago format, e.g. 10 mins ago, or raw time, e.g. 10:42 + maxPopups: -1, + expire: false, + agoTime: true, // Whether to show time in ago format, e.g. 10 mins ago, or raw time, e.g. 10:42 }, osds: { volume: { - position: s(Astal.WindowAnchor.RIGHT), - margin: s(20), - hideDelay: s(1500), - showValue: s(true), + position: Astal.WindowAnchor.RIGHT, + margin: 20, + hideDelay: 1500, + showValue: true, }, brightness: { - position: s(Astal.WindowAnchor.LEFT), - margin: s(20), - hideDelay: s(1500), - showValue: s(true), + position: Astal.WindowAnchor.LEFT, + margin: 20, + hideDelay: 1500, + showValue: true, }, lock: { - spacing: s(5), + spacing: 5, caps: { - hideDelay: s(1000), + hideDelay: 1000, }, num: { - hideDelay: s(1000), + hideDelay: 1000, + }, + }, + }, + sideleft: { + directories: { + left: { + top: " Downloads", + + middle: " Documents", + bottom: " Music", + }, + right: { + top: " Pictures", + middle: " Videos", + bottom: " Home", }, }, }, // Services math: { - maxHistory: s(100), + maxHistory: 100, }, updates: { - interval: s(900000), + interval: 900000, }, weather: { - interval: s(600000), - key: s("assets/weather-api-key.txt"), // Path to file containing api key relative to the base directory. To get a key, visit https://weatherapi.com/ - location: s(""), // Location as a string or empty to autodetect - imperial: s(false), + interval: 600000, + key: "assets/weather-api-key.txt", // Path to file containing api key relative to the base directory. To get a key, visit https://weatherapi.com/ + location: "", // Location as a string or empty to autodetect + imperial: false, }, cpu: { - interval: s(2000), + interval: 2000, }, gpu: { - interval: s(2000), + interval: 2000, }, memory: { - interval: s(5000), + interval: 5000, }, storage: { - interval: s(5000), + interval: 5000, }, }; -export const { bar, launcher, notifpopups, osds, math, updates, weather, cpu, gpu, memory, storage } = config; +const config = convertSettings(DEFAULTS); + +export const { bar, launcher, notifpopups, osds, sideleft, math, updates, weather, cpu, gpu, memory, storage } = config; diff --git a/src/modules/popdowns/sideleft.tsx b/src/modules/popdowns/sideleft.tsx index fdf3e4f..753026e 100644 --- a/src/modules/popdowns/sideleft.tsx +++ b/src/modules/popdowns/sideleft.tsx @@ -7,6 +7,7 @@ import PopupWindow from "@/widgets/popupwindow"; import { bind, execAsync, GLib, type Binding } from "astal"; import { App, Gtk, type Widget } from "astal/gtk3"; import type cairo from "cairo"; +import { sideleft } from "config"; const fmt = (bytes: number, pow: number) => +(bytes / 1024 ** pow).toFixed(2); const format = ({ total, used }: { total: number; used: number }) => { @@ -62,13 +63,14 @@ const QuickLaunch = () => ( </box> ); -const Location = ({ home, label, num }: { home?: boolean; label: string; num: number }) => ( +const Location = ({ label, num }: { label: Binding<string>; num: number }) => ( <button className={"loc" + num} cursor="pointer" onClicked={self => { self.get_toplevel().hide(); - execAsync(`xdg-open ${HOME}/${home ? "" : label.split(" ").at(-1) + "/"}`).catch(console.error); + const dir = label.get().split(" ").at(-1); + execAsync(`xdg-open ${HOME}/${dir?.toLowerCase() === "home" ? "" : `${dir}/`}`).catch(console.error); }} > <label xalign={0} label={label} /> @@ -78,14 +80,14 @@ const Location = ({ home, label, num }: { home?: boolean; label: string; num: nu const Locations = () => ( <box className="locations"> <box vertical> - <Location label=" Downloads" num={1} /> - <Location label=" Documents" num={2} /> - <Location label=" Music" num={3} /> + <Location label={bind(sideleft.directories.left.top)} num={1} /> + <Location label={bind(sideleft.directories.left.middle)} num={2} /> + <Location label={bind(sideleft.directories.left.bottom)} num={3} /> </box> <box vertical> - <Location label=" Pictures" num={4} /> - <Location label=" Videos" num={5} /> - <Location label=" Home" num={6} home /> + <Location label={bind(sideleft.directories.right.top)} num={4} /> + <Location label={bind(sideleft.directories.right.middle)} num={5} /> + <Location label={bind(sideleft.directories.right.bottom)} num={6} /> </box> </box> ); |