summaryrefslogtreecommitdiff
path: root/src/services/schemes.ts
blob: f8a71f9f542f1962574a9a31e3ffb88558ef82d9 (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
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 | undefined> {
        const schemeColours = (await readFileAsync(path).catch(() => undefined))?.split("\n").map(l => l.split(" "));
        return schemeColours?.reduce((acc, [name, hex]) => ({ ...acc, [name]: `#${hex}` }), {} as IPalette);
    }

    async parseColours(path: string): Promise<Colours> {
        const light = await this.parseMode(`${path}/light.txt`);
        const dark = await this.parseMode(`${path}/dark.txt`);
        return { light, dark };
    }

    async parseFlavour(scheme: string, name: string): Promise<Flavour> {
        const path = `${this.#schemeDir}/${scheme}/${name}`;
        return { name, scheme, colours: await this.parseColours(path) };
    }

    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 }),
            };

        return { name, colours: await this.parseColours(path) };
    }

    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, (_, file) => this.updateFile(file).catch(console.error));
    }
}