schemes: update for scripts refactor
This commit is contained in:
parent
9d1ee02d2e
commit
d46b368606
6 changed files with 171 additions and 85 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import { Apps } from "@/services/apps";
|
||||
import type { IPalette } from "@/services/palette";
|
||||
import Palette from "@/services/palette";
|
||||
import Schemes from "@/services/schemes";
|
||||
import Schemes, { type Colours } from "@/services/schemes";
|
||||
import Wallpapers from "@/services/wallpapers";
|
||||
import { basename } from "@/utils/strings";
|
||||
import { notify } from "@/utils/system";
|
||||
|
|
@ -30,6 +29,12 @@ const autocomplete = (entry: Widget.Entry, action: string) => {
|
|||
entry.set_position(-1);
|
||||
};
|
||||
|
||||
const hasMode = (mode: "light" | "dark") => {
|
||||
const scheme = Schemes.get_default().map[Palette.get_default().scheme];
|
||||
if (scheme.colours?.[mode]) return true;
|
||||
return scheme.flavours?.[Palette.get_default().flavour ?? ""]?.colours?.[mode] !== undefined;
|
||||
};
|
||||
|
||||
const actions = (mode: Variable<Mode>, entry: Widget.Entry): ActionMap => ({
|
||||
apps: {
|
||||
icon: "apps",
|
||||
|
|
@ -70,22 +75,22 @@ const actions = (mode: Variable<Mode>, entry: Widget.Entry): ActionMap => ({
|
|||
light: {
|
||||
icon: "light_mode",
|
||||
name: "Light",
|
||||
description: "Change dynamic scheme to light mode",
|
||||
description: "Change scheme to light mode",
|
||||
action: () => {
|
||||
execAsync(`caelestia wallpaper -T light -f ${STATE}/wallpaper/current`).catch(console.error);
|
||||
close();
|
||||
},
|
||||
available: () => Palette.get_default().name === "dynamic",
|
||||
available: () => hasMode("light"),
|
||||
},
|
||||
dark: {
|
||||
icon: "dark_mode",
|
||||
name: "Dark",
|
||||
description: "Change dynamic scheme to dark mode",
|
||||
description: "Change scheme to dark mode",
|
||||
action: () => {
|
||||
execAsync(`caelestia wallpaper -T dark -f ${STATE}/wallpaper/current`).catch(console.error);
|
||||
close();
|
||||
},
|
||||
available: () => Palette.get_default().name === "dynamic",
|
||||
available: () => hasMode("dark"),
|
||||
},
|
||||
scheme: {
|
||||
icon: "palette",
|
||||
|
|
@ -244,44 +249,47 @@ const Action = ({ args, icon, name, description, action }: IAction & { args: str
|
|||
|
||||
const Swatch = ({ colour }: { colour: string }) => <box className="swatch" css={"background-color: " + colour + ";"} />;
|
||||
|
||||
const Scheme = ({ name, colours }: { name: string; colours: IPalette }) => (
|
||||
<Gtk.FlowBoxChild visible canFocus={false}>
|
||||
<button
|
||||
className="result"
|
||||
cursor="pointer"
|
||||
onClicked={() => {
|
||||
execAsync(`caelestia scheme ${name}`).catch(console.error);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
<box>
|
||||
<box valign={Gtk.Align.CENTER}>
|
||||
<box className="swatch big left" css={"background-color: " + colours.base + ";"} />
|
||||
<box className="swatch big right" css={"background-color: " + colours.accent + ";"} />
|
||||
</box>
|
||||
<box vertical className="has-sublabel">
|
||||
<label truncate xalign={0} label={name} />
|
||||
<box className="swatches">
|
||||
<Swatch colour={colours.rosewater} />
|
||||
<Swatch colour={colours.flamingo} />
|
||||
<Swatch colour={colours.pink} />
|
||||
<Swatch colour={colours.mauve} />
|
||||
<Swatch colour={colours.red} />
|
||||
<Swatch colour={colours.maroon} />
|
||||
<Swatch colour={colours.peach} />
|
||||
<Swatch colour={colours.yellow} />
|
||||
<Swatch colour={colours.green} />
|
||||
<Swatch colour={colours.teal} />
|
||||
<Swatch colour={colours.sky} />
|
||||
<Swatch colour={colours.sapphire} />
|
||||
<Swatch colour={colours.blue} />
|
||||
<Swatch colour={colours.lavender} />
|
||||
const Scheme = ({ scheme, name, colours }: { scheme?: string; name: string; colours?: Colours }) => {
|
||||
const palette = colours![Palette.get_default().mode] ?? colours!.light ?? colours!.dark!;
|
||||
return (
|
||||
<Gtk.FlowBoxChild visible canFocus={false}>
|
||||
<button
|
||||
className="result"
|
||||
cursor="pointer"
|
||||
onClicked={() => {
|
||||
execAsync(`caelestia scheme ${scheme ?? ""} ${name}`).catch(console.error);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
<box>
|
||||
<box valign={Gtk.Align.CENTER}>
|
||||
<box className="swatch big left" css={"background-color: " + palette.base + ";"} />
|
||||
<box className="swatch big right" css={"background-color: " + palette.accent + ";"} />
|
||||
</box>
|
||||
<box vertical className="has-sublabel">
|
||||
<label truncate xalign={0} label={scheme ? `${scheme} (${name})` : name} />
|
||||
<box className="swatches">
|
||||
<Swatch colour={palette.rosewater} />
|
||||
<Swatch colour={palette.flamingo} />
|
||||
<Swatch colour={palette.pink} />
|
||||
<Swatch colour={palette.mauve} />
|
||||
<Swatch colour={palette.red} />
|
||||
<Swatch colour={palette.maroon} />
|
||||
<Swatch colour={palette.peach} />
|
||||
<Swatch colour={palette.yellow} />
|
||||
<Swatch colour={palette.green} />
|
||||
<Swatch colour={palette.teal} />
|
||||
<Swatch colour={palette.sky} />
|
||||
<Swatch colour={palette.sapphire} />
|
||||
<Swatch colour={palette.blue} />
|
||||
<Swatch colour={palette.lavender} />
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
</button>
|
||||
</Gtk.FlowBoxChild>
|
||||
);
|
||||
</button>
|
||||
</Gtk.FlowBoxChild>
|
||||
);
|
||||
};
|
||||
|
||||
const Wallpaper = ({ path, thumbnail }: { path: string; thumbnail?: string }) => (
|
||||
<Gtk.FlowBoxChild visible canFocus={false}>
|
||||
|
|
@ -334,8 +342,17 @@ export default class Actions extends Widget.Box implements LauncherContent {
|
|||
|
||||
if (action === "scheme") {
|
||||
const scheme = args[1] ?? "";
|
||||
for (const { target } of fuzzysort.go(scheme, Object.keys(Schemes.get_default().map), { all: true }))
|
||||
this.#content.add(<Scheme name={target} colours={Schemes.get_default().map[target]} />);
|
||||
const schemes = Object.values(Schemes.get_default().map)
|
||||
.flatMap(s => (s.colours ? s.name : Object.values(s.flavours!).map(f => `${f.scheme}-${f.name}`)))
|
||||
.filter(s => s !== undefined);
|
||||
for (const { target } of fuzzysort.go(scheme, schemes, { all: true })) {
|
||||
if (Schemes.get_default().map.hasOwnProperty(target))
|
||||
this.#content.add(<Scheme {...Schemes.get_default().map[target]} />);
|
||||
else {
|
||||
const [scheme, flavour] = target.split("-");
|
||||
this.#content.add(<Scheme {...Schemes.get_default().map[scheme].flavours![flavour]} />);
|
||||
}
|
||||
}
|
||||
} else if (action === "wallpaper") {
|
||||
const wallpaper = args[1] ?? "";
|
||||
for (const { obj } of fuzzysort.go(wallpaper, Wallpapers.get_default().list, { all: true, key: "path" }))
|
||||
|
|
|
|||
|
|
@ -41,18 +41,24 @@ export default class Palette extends GObject.Object {
|
|||
return this.instance;
|
||||
}
|
||||
|
||||
#isLight: boolean;
|
||||
#name: string;
|
||||
#mode: "light" | "dark";
|
||||
#scheme: string;
|
||||
#flavour?: string;
|
||||
#colours!: IPalette;
|
||||
|
||||
@property(Boolean)
|
||||
get isLight() {
|
||||
return this.#isLight;
|
||||
get mode() {
|
||||
return this.#mode;
|
||||
}
|
||||
|
||||
@property(String)
|
||||
get name() {
|
||||
return this.#name;
|
||||
get scheme() {
|
||||
return this.#scheme;
|
||||
}
|
||||
|
||||
@property(String)
|
||||
get flavour() {
|
||||
return this.#flavour;
|
||||
}
|
||||
|
||||
@property(Object)
|
||||
|
|
@ -246,16 +252,17 @@ export default class Palette extends GObject.Object {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.#isLight = readFile(`${STATE}/scheme/current-mode.txt`) === "light";
|
||||
this.#mode = readFile(`${STATE}/scheme/current-mode.txt`) === "light" ? "light" : "dark";
|
||||
monitorFile(`${STATE}/scheme/current-mode.txt`, async file => {
|
||||
this.#isLight = (await readFileAsync(file)) === "light";
|
||||
this.notify("is-light");
|
||||
this.#mode = (await readFileAsync(file)) === "light" ? "light" : "dark";
|
||||
this.notify("mode");
|
||||
});
|
||||
|
||||
this.#name = readFile(`${STATE}/scheme/current-name.txt`);
|
||||
[this.#scheme, this.#flavour] = readFile(`${STATE}/scheme/current-name.txt`).split("-");
|
||||
monitorFile(`${STATE}/scheme/current-name.txt`, async file => {
|
||||
this.#name = await readFileAsync(file);
|
||||
this.notify("name");
|
||||
[this.#scheme, this.#flavour] = (await readFileAsync(file)).split("-");
|
||||
this.notify("scheme");
|
||||
this.notify("flavour");
|
||||
});
|
||||
|
||||
this.update();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,25 @@
|
|||
import { basename } from "@/utils/strings";
|
||||
import { execAsync, GLib, GObject, monitorFile, property, readFileAsync, register } from "astal";
|
||||
import { monitorDirectory } from "@/utils/system";
|
||||
import { execAsync, GLib, GObject, property, readFileAsync, register } from "astal";
|
||||
import type { IPalette } from "./palette";
|
||||
|
||||
export interface Colours {
|
||||
light?: IPalette;
|
||||
dark?: IPalette;
|
||||
}
|
||||
|
||||
export interface Flavour {
|
||||
name: string;
|
||||
scheme: string;
|
||||
colours: Colours;
|
||||
}
|
||||
|
||||
export interface Scheme {
|
||||
name: string;
|
||||
flavours?: { [k: string]: Flavour };
|
||||
colours?: Colours;
|
||||
}
|
||||
|
||||
const DATA = `${GLib.get_user_data_dir()}/caelestia`;
|
||||
|
||||
@register({ GTypeName: "Schemes" })
|
||||
|
|
@ -13,21 +31,58 @@ export default class Schemes extends GObject.Object {
|
|||
return this.instance;
|
||||
}
|
||||
|
||||
#map: { [k: string]: IPalette } = {};
|
||||
readonly #schemeDir: string = `${DATA}/scripts/data/schemes`;
|
||||
|
||||
#map: { [k: string]: Scheme } = {};
|
||||
|
||||
@property(Object)
|
||||
get map() {
|
||||
return this.#map;
|
||||
}
|
||||
|
||||
async parseScheme(path: string) {
|
||||
async parseMode(path: string): Promise<IPalette> {
|
||||
const schemeColours = (await readFileAsync(path)).split("\n").map(l => l.split(" "));
|
||||
return schemeColours.reduce((acc, [name, hex]) => ({ ...acc, [name]: `#${hex}` }), {} as IPalette);
|
||||
}
|
||||
|
||||
async parseFlavour(scheme: string, name: string): Promise<Flavour> {
|
||||
const path = `${this.#schemeDir}/${scheme}/${name}`;
|
||||
|
||||
let light = undefined;
|
||||
let dark = undefined;
|
||||
if (GLib.file_test(`${path}/light.txt`, GLib.FileTest.EXISTS))
|
||||
light = await this.parseMode(`${path}/light.txt`);
|
||||
if (GLib.file_test(`${path}/dark.txt`, GLib.FileTest.EXISTS)) dark = await this.parseMode(`${path}/dark.txt`);
|
||||
|
||||
return { name, scheme, colours: { light, dark } };
|
||||
}
|
||||
|
||||
async parseScheme(name: string): Promise<Scheme> {
|
||||
const path = `${this.#schemeDir}/${name}`;
|
||||
|
||||
const flavours = await execAsync(`find ${path}/ -mindepth 1 -maxdepth 1 -type d`);
|
||||
if (flavours.trim())
|
||||
return {
|
||||
name,
|
||||
flavours: (
|
||||
await Promise.all(flavours.split("\n").map(f => this.parseFlavour(name, basename(f))))
|
||||
).reduce((acc, f) => ({ ...acc, [f.name]: f }), {} as { [k: string]: Flavour }),
|
||||
};
|
||||
|
||||
let light = undefined;
|
||||
let dark = undefined;
|
||||
if (GLib.file_test(`${path}/light.txt`, GLib.FileTest.EXISTS))
|
||||
light = await this.parseMode(`${path}/light.txt`);
|
||||
if (GLib.file_test(`${path}/dark.txt`, GLib.FileTest.EXISTS)) dark = await this.parseMode(`${path}/dark.txt`);
|
||||
return { name, colours: { light, dark } };
|
||||
}
|
||||
|
||||
async update() {
|
||||
const schemes = await execAsync(`find ${DATA}/scripts/data/schemes/ -type f`);
|
||||
for (const scheme of schemes.split("\n")) this.#map[basename(scheme)] = await this.parseScheme(scheme);
|
||||
const schemes = await execAsync(`find ${this.#schemeDir}/ -mindepth 1 -maxdepth 1 -type d`);
|
||||
for (const scheme of schemes.split("\n")) {
|
||||
const name = basename(scheme);
|
||||
this.#map[name] = await this.parseScheme(name);
|
||||
}
|
||||
this.notify("map");
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +90,6 @@ export default class Schemes extends GObject.Object {
|
|||
super();
|
||||
|
||||
this.update().catch(console.error);
|
||||
monitorFile(`${DATA}/scripts/data/schemes`, () => this.update().catch(console.error));
|
||||
monitorDirectory(this.#schemeDir, () => this.update().catch(console.error), true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { basename } from "@/utils/strings";
|
||||
import { execAsync, Gio, GLib, GObject, property, register } from "astal";
|
||||
import { monitorDirectory } from "@/utils/system";
|
||||
import { execAsync, GLib, GObject, property, register } from "astal";
|
||||
import { wallpapers as config } from "config";
|
||||
|
||||
export interface Wallpaper {
|
||||
|
|
@ -55,28 +56,12 @@ export default class Wallpapers extends GObject.Object {
|
|||
|
||||
this.update().catch(console.error);
|
||||
|
||||
const monitorDir = ({ path, recursive }: { path: string; recursive: boolean }) => {
|
||||
const file = Gio.file_new_for_path(path.replace("~", HOME));
|
||||
const monitor = file.monitor_directory(null, null);
|
||||
monitor.connect("changed", () => this.update().catch(console.error));
|
||||
|
||||
const monitors = [monitor];
|
||||
|
||||
if (recursive) {
|
||||
const enumerator = file.enumerate_children("standard::*", null, null);
|
||||
let child;
|
||||
while ((child = enumerator.next_file(null)))
|
||||
if (child.get_file_type() === Gio.FileType.DIRECTORY)
|
||||
monitors.push(...monitorDir({ path: `${path}/${child.get_name()}`, recursive }));
|
||||
}
|
||||
|
||||
return monitors;
|
||||
};
|
||||
|
||||
let monitors = config.paths.get().flatMap(monitorDir);
|
||||
let monitors = config.paths
|
||||
.get()
|
||||
.flatMap(p => monitorDirectory(p.path, () => this.update().catch(console.error), p.recursive));
|
||||
config.paths.subscribe(v => {
|
||||
for (const m of monitors) m.cancel();
|
||||
monitors = v.flatMap(monitorDir);
|
||||
monitors = v.flatMap(p => monitorDirectory(p.path, () => this.update().catch(console.error), p.recursive));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
export const ellipsize = (str: string, len: number) => (str.length > len ? `${str.slice(0, len - 1)}…` : str);
|
||||
|
||||
export const basename = (path: string) => path.slice(path.lastIndexOf("/") + 1, path.lastIndexOf("."));
|
||||
export const basename = (path: string) => {
|
||||
const lastSlash = path.lastIndexOf("/");
|
||||
const lastDot = path.lastIndexOf(".");
|
||||
return path.slice(lastSlash + 1, lastDot > lastSlash ? lastDot : undefined);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { bind, execAsync, GLib, Variable, type Binding, type Gio } from "astal";
|
||||
import { bind, execAsync, Gio, GLib, Variable, type Binding } from "astal";
|
||||
import type AstalApps from "gi://AstalApps";
|
||||
import { osIcons } from "./icons";
|
||||
|
||||
|
|
@ -75,3 +75,21 @@ export const bindCurrentTime = (
|
|||
self?.connect("destroy", () => time.drop());
|
||||
return bind(time);
|
||||
};
|
||||
|
||||
export const monitorDirectory = (path: string, callback: (path: string) => void, recursive?: boolean) => {
|
||||
const file = Gio.file_new_for_path(path.replace("~", HOME));
|
||||
const monitor = file.monitor_directory(null, null);
|
||||
monitor.connect("changed", callback);
|
||||
|
||||
const monitors = [monitor];
|
||||
|
||||
if (recursive) {
|
||||
const enumerator = file.enumerate_children("standard::*", null, null);
|
||||
let child;
|
||||
while ((child = enumerator.next_file(null)))
|
||||
if (child.get_file_type() === Gio.FileType.DIRECTORY)
|
||||
monitors.push(...monitorDirectory(`${path}/${child.get_name()}`, callback, recursive));
|
||||
}
|
||||
|
||||
return monitors;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue