1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
import { basename } from "@/utils/strings";
import { monitorDirectory } from "@/utils/system";
import { execAsync, Gio, 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" })
export default class Schemes extends GObject.Object {
static instance: Schemes;
static get_default() {
if (!this.instance) this.instance = new Schemes();
return this.instance;
}
readonly #schemeDir: string = `${DATA}/scripts/data/schemes`;
#map: { [k: string]: Scheme } = {};
@property(Object)
get map() {
return this.#map;
}
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 ${this.#schemeDir}/ -mindepth 1 -maxdepth 1 -type d`);
(await Promise.all(schemes.split("\n").map(s => this.parseScheme(basename(s))))).forEach(
s => (this.#map[s.name] = s)
);
this.notify("map");
}
async updateFile(file: Gio.File) {
if (file.get_basename() !== "light.txt" && file.get_basename() !== "dark.txt") return;
const mode = file.get_basename()!.slice(0, -4) as "light" | "dark";
const parent = file.get_parent()!;
const parentParent = parent.get_parent()!;
if (parentParent.get_basename() === "schemes")
this.#map[parent.get_basename()!].colours![mode] = await this.parseMode(file.get_path()!);
else
this.#map[parentParent.get_basename()!].flavours![parent.get_basename()!].colours![mode] =
await this.parseMode(file.get_path()!);
this.notify("map");
}
constructor() {
super();
this.update().catch(console.error);
monitorDirectory(this.#schemeDir, (_m, file, _f, type) => {
if (type !== Gio.FileMonitorEvent.DELETED) this.updateFile(file).catch(console.error);
});
}
}
|