aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/island/SceneIcons.js
blob: 28118b76f27f28b626018c3478cad1f2975d0b3d (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
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

.pragma library

// The starter map, used when the config file is missing or unreadable, and the
// seed for the settings page. The md-* glyphs are supplementary-plane, so they
// are surrogate pairs; a five-digit \u escape would be a different character.
var defaults = {
    generic: "\udb80\uded7",        // md-help_circle
    scenes: {
        "Desktop": "\udb80\udf79",      // md-monitor
        "Webcam Only": "\udb81\udd67",  // md-video
        "Window Only": "\uf2d0",        // fa-window_maximize
        "Intervallo": "\udb80\udd76",   // md-coffee
    },
};

// Exactly one code point; a surrogate pair counts as one.
function validGlyph(s) {
    if (typeof s !== "string" || s === "") return false;
    let n = 0;
    for (let i = 0; i < s.length; i++) {
        const c = s.charCodeAt(i);
        if (c >= 0xD800 && c <= 0xDBFF && i + 1 < s.length) {
            const d = s.charCodeAt(i + 1);
            if (d >= 0xDC00 && d <= 0xDFFF) i++;
        }
        n++;
    }
    return n === 1;
}

// The map as stored: an invalid generic falls back, invalid scenes drop.
function normalize(map) {
    const src = map || {};
    const out = {
        generic: validGlyph(src.generic) ? src.generic : defaults.generic,
        scenes: {},
    };
    const sc = src.scenes || {};
    for (const k in sc) if (validGlyph(sc[k])) out.scenes[k] = sc[k];
    return out;
}

// A missing or unparseable file is the starter map; a valid file is used as-is.
function parse(text) {
    let v = null;
    try { v = JSON.parse(text); } catch (e) {}
    if (!v || typeof v !== "object" || Array.isArray(v)) return normalize(defaults);
    return normalize(v);
}

function serialize(map) {
    return JSON.stringify({ generic: map.generic, scenes: map.scenes }, null, 2) + "\n";
}

function iconFor(map, name) {
    return (map && map.scenes && map.scenes[name]) || (map && map.generic) || defaults.generic;
}

// A draft may leave the generic empty; Save substitutes the default.
function validMap(map) {
    if (!map || typeof map !== "object") return false;
    if (map.generic !== "" && !validGlyph(map.generic)) return false;
    const sc = map.scenes || {};
    for (const k in sc) if (!validGlyph(sc[k])) return false;
    return true;
}