summaryrefslogtreecommitdiff
path: root/src/services/schemes.ts
blob: 2808b5566d3831182209514d809739742efe2a33 (plain)
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);
        });
    }
}