iobroker.alias-workbench 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +516 -0
- package/admin/alias-workbench.png +0 -0
- package/admin/css/werkbank.css +916 -0
- package/admin/detector.js +1 -0
- package/admin/geraetewissen/tasmota-befehle.json +116 -0
- package/admin/i18n/de.json +552 -0
- package/admin/i18n/en.json +552 -0
- package/admin/js/aufzaehlungen.js +332 -0
- package/admin/js/basis.js +36 -0
- package/admin/js/baum.js +453 -0
- package/admin/js/detail.js +335 -0
- package/admin/js/einstellungen.js +40 -0
- package/admin/js/entwurf.js +908 -0
- package/admin/js/enums.js +23 -0
- package/admin/js/ergebnis.js +934 -0
- package/admin/js/erkennung.js +126 -0
- package/admin/js/katalog.js +313 -0
- package/admin/js/mqtt.js +885 -0
- package/admin/js/musternamen.js +94 -0
- package/admin/js/musterwahl.js +334 -0
- package/admin/js/objekte.js +243 -0
- package/admin/js/pruefungen.js +130 -0
- package/admin/js/quellentausch.js +570 -0
- package/admin/js/rollenwahl.js +199 -0
- package/admin/js/schreiben.js +1537 -0
- package/admin/js/sprache.js +137 -0
- package/admin/js/start.js +39 -0
- package/admin/js/verbindung.js +10 -0
- package/admin/js/vorlagen.js +572 -0
- package/admin/js/vorlagenblatt.js +1907 -0
- package/admin/js/vorschlagen.js +391 -0
- package/admin/js/werte.js +193 -0
- package/admin/js/zielleiste.js +339 -0
- package/admin/js/zuordnung.js +695 -0
- package/admin/js/zustand.js +47 -0
- package/admin/js/zustandsliste.js +515 -0
- package/admin/jsonConfig.json +249 -0
- package/admin/tab.html +148 -0
- package/admin/vorlagen/hm-dimmer.json +204 -0
- package/admin/vorlagen/hm-fensterkontakt-hm.json +176 -0
- package/admin/vorlagen/hm-fensterkontakt-hmip.json +191 -0
- package/admin/vorlagen/hm-heizgruppe.json +214 -0
- package/admin/vorlagen/hm-heizkoerper.json +236 -0
- package/admin/vorlagen/hm-rollladen.json +190 -0
- package/admin/vorlagen/hm-schaltaktor-lampe.json +197 -0
- package/admin/vorlagen/hm-schaltaktor-mehrfach.json +203 -0
- package/admin/vorlagen/hm-schaltaktor-steckdose.json +197 -0
- package/admin/vorlagen/hm-temperatur-feuchte.json +166 -0
- package/admin/vorlagen/hm-wandthermostat.json +228 -0
- package/admin/vorlagen/index.json +21 -0
- package/admin/vorlagen/messpunkt.json +225 -0
- package/admin/vorlagen/tasmota-lampe.json +263 -0
- package/admin/vorlagen/tasmota-mehrfach.json +203 -0
- package/admin/vorlagen/tasmota-rgb.json +239 -0
- package/admin/vorlagen/tasmota-steckdose.json +265 -0
- package/io-package.json +122 -0
- package/package.json +48 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* Der Draht zum type-detector: Rollen, Muster, das Objekt-Abbild des
|
|
2
|
+
Entwurfs und der Erkennungslauf darauf. */
|
|
3
|
+
|
|
4
|
+
import { S } from './zustand.js';
|
|
5
|
+
import { D } from './basis.js';
|
|
6
|
+
|
|
7
|
+
/* Die Rollen kommen aus dem Paket, nicht aus einer eigenen Liste —
|
|
8
|
+
sonst pflegt man eine zweite Wahrheit, die auseinanderlaeuft. */
|
|
9
|
+
/* Manche Plaetze nennen keine Standardrolle, sondern nur einen
|
|
10
|
+
Ausdruck: BRIGHTNESS im rgbSingle-Muster hat blossen Anker und Wort.
|
|
11
|
+
Steht darin genau ein Rollenname, ist das die Rolle. Ohne diese
|
|
12
|
+
Ableitung bekam so ein Punkt gar keine: er belegte seinen Platz nie,
|
|
13
|
+
liess sich beliebig oft hinzufuegen, und die Rolle fehlte auch in der
|
|
14
|
+
Auswahlliste — der Platz war auf keinem Weg zu befuellen. */
|
|
15
|
+
export function rolleAusAusdruck(r) {
|
|
16
|
+
if (!r) { return ''; }
|
|
17
|
+
var q = (r.source !== undefined) ? r.source : String(r);
|
|
18
|
+
if (q.charAt(0) !== '^' || q.slice(-1) !== '$') { return ''; }
|
|
19
|
+
var t = q.slice(1, -1).split(String.fromCharCode(92)).join('');
|
|
20
|
+
return /^[A-Za-z0-9_]+([.][A-Za-z0-9_]+)*$/.test(t) ? t : '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function rolleVonPlatz(st) { return st.defaultRole || rolleAusAusdruck(st.role); }
|
|
24
|
+
|
|
25
|
+
export var ROLLEN = (function () {
|
|
26
|
+
if (!D) { return []; }
|
|
27
|
+
var m = {};
|
|
28
|
+
Object.keys(D.patterns).forEach(function (t) {
|
|
29
|
+
D.patterns[t].states.forEach(function (st) {
|
|
30
|
+
var r = rolleVonPlatz(st);
|
|
31
|
+
if (r) { m[r] = 1; }
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
['state', 'text', 'json', 'value'].forEach(function (r) { m[r] = 1; });
|
|
35
|
+
return Object.keys(m).sort();
|
|
36
|
+
})();
|
|
37
|
+
|
|
38
|
+
/* Der Detektor meldet einen Typ, sein Musterverzeichnis ist aber nach
|
|
39
|
+
Schluesseln geordnet — und bei dreien von 51 sind das zwei
|
|
40
|
+
verschiedene Woerter:
|
|
41
|
+
|
|
42
|
+
blinds -> type blind
|
|
43
|
+
mediaPlayer -> type media
|
|
44
|
+
levelSlider -> type slider
|
|
45
|
+
|
|
46
|
+
Wer den gemeldeten Typ als Schluessel nimmt, greift ins Leere. Beim
|
|
47
|
+
Terassenrollladen fiel dadurch „blind" aus der Musterliste, das
|
|
48
|
+
Auswahlfeld stand auf „socket", und ein Wechsel des Musters passte die
|
|
49
|
+
Rollen nicht mehr an — gerechnet wurde die ganze Zeit richtig, nur
|
|
50
|
+
angezeigt und angeboten wurde etwas anderes. */
|
|
51
|
+
export var TYP_ZU_MUSTER = (function () {
|
|
52
|
+
var m = {};
|
|
53
|
+
if (D) {
|
|
54
|
+
Object.keys(D.patterns).forEach(function (k) {
|
|
55
|
+
var t = D.patterns[k].type;
|
|
56
|
+
if (t && t !== k) { m[t] = k; }
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return m;
|
|
60
|
+
})();
|
|
61
|
+
|
|
62
|
+
/* Das Muster zu einem Typ — oder zu einem Schluessel, beides geht. */
|
|
63
|
+
export function musterVon(typ) {
|
|
64
|
+
if (!D || !typ) { return null; }
|
|
65
|
+
return D.patterns[typ] || (TYP_ZU_MUSTER[typ] ? D.patterns[TYP_ZU_MUSTER[typ]] : null) || null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Welche Rolle faende im gewaehlten Muster einen Platz? */
|
|
69
|
+
export function plaetzeFuerRollen(musterName) {
|
|
70
|
+
var raus = {};
|
|
71
|
+
var mu = musterName && musterVon(musterName);
|
|
72
|
+
if (!mu) { return raus; }
|
|
73
|
+
ROLLEN.forEach(function (r) {
|
|
74
|
+
mu.states.forEach(function (st) {
|
|
75
|
+
if (raus[r] || !st.role) { return; }
|
|
76
|
+
try { if (new RegExp(st.role.source || st.role).test(r)) { raus[r] = st.name; } } catch { /* unbrauchbarer Ausdruck in der Vorlage: zaehlt nicht */ }
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
return raus;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Typen, keine Musterschluessel — `e.want` und das Auswahlfeld fuehren
|
|
83
|
+
ueberall den Typ. Bei „blind" sind die beiden verschieden. */
|
|
84
|
+
export var VORSCHAU = ['socket', 'light', 'dimmer', 'blind', 'thermostat', 'temperature', 'humidity', 'info'];
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/* Objekt-Abbild des Entwurfs — genau das, was der Detektor sehen wuerde. */
|
|
88
|
+
export function abbild(e) {
|
|
89
|
+
var m = {};
|
|
90
|
+
var k = S.objects[e.kanal];
|
|
91
|
+
m[e.kanal] = k
|
|
92
|
+
? { _id: e.kanal, type: (k.type === 'device' ? 'device' : 'channel'), common: k.common || {}, native: {} }
|
|
93
|
+
: { _id: e.kanal, type: 'channel', common: { name: e.kanal.split('.').pop() }, native: {} };
|
|
94
|
+
e.states.forEach(function (s) {
|
|
95
|
+
if (!s.on || !s.n) { return; }
|
|
96
|
+
var id = e.kanal + '.' + s.n;
|
|
97
|
+
m[id] = { _id: id, type: 'state', native: {}, common: {
|
|
98
|
+
name: s.caption || s.n,
|
|
99
|
+
role: s.role,
|
|
100
|
+
type: s.typ || undefined,
|
|
101
|
+
unit: s.unit || undefined,
|
|
102
|
+
read: true,
|
|
103
|
+
write: !!(s.wr || s.srcW),
|
|
104
|
+
/* Manche Plaetze verlangen eine Werteliste — light.EFFECT etwa
|
|
105
|
+
wird ohne common.states gar nicht erkannt (statesDefined). */
|
|
106
|
+
states: s.states || undefined
|
|
107
|
+
} };
|
|
108
|
+
});
|
|
109
|
+
return m;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function erkenneEntwurf(e, nurTyp) {
|
|
113
|
+
if (!D) { return []; }
|
|
114
|
+
var m = abbild(e);
|
|
115
|
+
var keys = Object.keys(m).sort();
|
|
116
|
+
var opt2 = { id: e.kanal, objects: m, _keysOptional: keys, _keysOptionalSorted: true,
|
|
117
|
+
_usedIdsOptional: [], ignoreCache: true };
|
|
118
|
+
if (nurTyp) { opt2.allowedTypes = [nurTyp]; }
|
|
119
|
+
try { return new D.ChannelDetector().detect(opt2) || []; } catch { return []; }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
/* ================== Vorlagen ==================
|
|
125
|
+
Eine Datei je Vorlage unter admin/vorlagen/. Die Verarbeitung kennt
|
|
126
|
+
kein einziges Geraet — alles Geraetewissen steckt in den Dateien. */
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { holeText } from './basis.js';
|
|
2
|
+
import { txt } from './sprache.js';
|
|
3
|
+
import { einst } from './einstellungen.js';
|
|
4
|
+
import './enums.js';
|
|
5
|
+
|
|
6
|
+
/* Die Vorlagen des Admin - geliehen, nicht kopiert.
|
|
7
|
+
|
|
8
|
+
Der Admin bringt 62 Funktions- und 58 Raumvorlagen mit, mehrsprachig
|
|
9
|
+
und mit Icon: „Rollladen", „Steckdose", „Waschmaschine". Sie in unseren
|
|
10
|
+
Adapter zu kopieren waere doppelte Pflege - bei jedem Admin-Update
|
|
11
|
+
altert die Kopie still vor sich hin. Also holen wir sie zur Laufzeit
|
|
12
|
+
aus dem Bundle, in dem sie ohnehin liegen. Wir laufen in derselben
|
|
13
|
+
Herkunft wie der Admin, `fetch` genuegt.
|
|
14
|
+
|
|
15
|
+
Der Weg fuehrt ueber drei Vite-Eigenheiten: die Bundles tragen Hashes
|
|
16
|
+
im Namen, der Chunk heisst „Enums", und die Listen stehen darin als
|
|
17
|
+
JSON.parse mit Backticks. Bricht eine davon, bleiben die Listen leer
|
|
18
|
+
und das Feld zeigt nur den Bestand - wie vorher. Kein Fehler, keine
|
|
19
|
+
Meldung, nur weniger Auswahl. Deshalb faengt hier alles ab, was
|
|
20
|
+
schiefgehen kann, ohne ein Wort zu verlieren. */
|
|
21
|
+
export var enumVorlagen = { rooms: [], functions: [] };
|
|
22
|
+
/* Der Katalog des Admin sollte kommen, kam aber nicht.
|
|
23
|
+
|
|
24
|
+
Der Rueckfall auf den Bestand ist mit Absicht still - er soll niemanden
|
|
25
|
+
mit einer Fehlermeldung behelligen, wenn ohnehin alles weiterlaeuft.
|
|
26
|
+
Genau deshalb faellt er aber auch niemandem auf: Ricardos
|
|
27
|
+
Produktivanlage lief wochenlang ohne Katalog, weil Admin 8.0.2 sein
|
|
28
|
+
Bundle anders schreibt als 7.8.23, und das einzige Anzeichen war eine
|
|
29
|
+
Auswahlliste mit zehn statt zweiundsiebzig Eintraegen. Wer nicht weiss,
|
|
30
|
+
dass zweiundsiebzig kommen muessten, sieht zehn und denkt nichts.
|
|
31
|
+
|
|
32
|
+
Still bleiben, ja - aber nicht unsichtbar. Wer die Liste aufklappt,
|
|
33
|
+
soll erfahren, dass sie unvollstaendig ist. */
|
|
34
|
+
export var katalogFehlt = false;
|
|
35
|
+
var enumIkonen = { rooms: {}, functions: {} }; /* Iconname -> Chunkdatei */
|
|
36
|
+
export var ikonCache = {}; /* art:Iconname -> data-URI */
|
|
37
|
+
var ikonLaeuft = {}; /* was gerade geholt wird */
|
|
38
|
+
var vorlagenGeholt = false;
|
|
39
|
+
|
|
40
|
+
/* Welches Icon liegt in welchem Chunk? Der Chunk fuehrt eine Karte
|
|
41
|
+
„../../assets/devices/Amplifier.svg" -> import("./Amplifier-XXXX.js"). */
|
|
42
|
+
/* Welche Datei haelt das Bild zu welchem Namen?
|
|
43
|
+
|
|
44
|
+
Der Bundler schreibt das als Zuordnung von Pfad auf Nachladefunktion.
|
|
45
|
+
Womit er die Zeichenketten einfasst, wechselt er von Fassung zu
|
|
46
|
+
Fassung: Admin 7.8.23 nahm Anfuehrungszeichen, 8.0.2 Backticks. Wer
|
|
47
|
+
sich auf ein Zeichen festlegt, verliert die Bilder beim naechsten
|
|
48
|
+
Update - lautlos, denn fehlende Bilder sehen aus wie „keine da". */
|
|
49
|
+
function ikonMap(quelle, ordner) {
|
|
50
|
+
var re = new RegExp('["\'`]\\.\\./\\.\\./assets/' + ordner +
|
|
51
|
+
'/([^"\'`]+?)\\.svg["\'`]:\\(\\)=>[\\s\\S]{0,40}?import\\(["\'`]\\./([^"\'`]+?)["\'`]', 'g');
|
|
52
|
+
var map = {}, m;
|
|
53
|
+
while ((m = re.exec(quelle))) { map[m[1]] = m[2]; }
|
|
54
|
+
return map;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Den Enums-Chunk finden. Sein Name steht nicht in index.html, sondern
|
|
58
|
+
eine Ebene tiefer im bootstrap-Bundle. Gemessen: sechs Dateien, alle
|
|
59
|
+
schon im Browsercache, weil der Admin sie selbst geladen hat. */
|
|
60
|
+
function sucheEnumChunk(welle, gesehen, tiefe) {
|
|
61
|
+
if (!welle.length || tiefe > 2) { return Promise.resolve(null); }
|
|
62
|
+
var naechste = [], treffer = null;
|
|
63
|
+
return welle.reduce(function (kette, f) {
|
|
64
|
+
return kette.then(function () {
|
|
65
|
+
if (treffer || gesehen[f]) { return; }
|
|
66
|
+
gesehen[f] = true;
|
|
67
|
+
return holeText('/' + f).then(function (t) {
|
|
68
|
+
var m = t.match(/Enums-[A-Za-z0-9_-]+\.js/);
|
|
69
|
+
if (m) { treffer = m[0]; return; }
|
|
70
|
+
(t.match(/[A-Za-z0-9_.-]+-[A-Za-z0-9_-]{8}\.js/g) || []).forEach(function (x) {
|
|
71
|
+
var q = x.indexOf('assets/') === 0 ? x : 'assets/' + x;
|
|
72
|
+
if (!gesehen[q]) { naechste.push(q); }
|
|
73
|
+
});
|
|
74
|
+
})['catch'](function () { });
|
|
75
|
+
});
|
|
76
|
+
}, Promise.resolve()).then(function () {
|
|
77
|
+
return treffer || sucheEnumChunk(naechste, gesehen, tiefe + 1);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Woran man eine der beiden Listen erkennt.
|
|
82
|
+
|
|
83
|
+
Nicht am Variablennamen und nicht an einer Importzeile - beides
|
|
84
|
+
schreibt der Bundler bei jedem Update neu. Vorher hing die Erkennung
|
|
85
|
+
genau daran (`startsWith("enum.functions")?Le:Se` und
|
|
86
|
+
`import Se from"./Rooms-xxx.js"`). In Admin 7.8.23 stand das so da, in
|
|
87
|
+
8.0.2 nicht mehr: die Funktionsliste heisst dort anders, und die
|
|
88
|
+
Raeume liegen in einer Datei, die nicht importiert wird. Die Werkbank
|
|
89
|
+
fiel still auf den Bestand zurueck - sichtbar erst daran, dass im
|
|
90
|
+
Vorlagenblatt die Funktionsauswahl leer blieb.
|
|
91
|
+
|
|
92
|
+
Ein Buchstabe aendert sich, `living_room` nicht. */
|
|
93
|
+
var RAUM_MARKE = ['living_room', 'bathroom', 'kitchen', 'bedroom', 'garden'];
|
|
94
|
+
var FUNK_MARKE = ['light', 'heating', 'security', 'blinds', 'weather'];
|
|
95
|
+
|
|
96
|
+
function listenAus(text) {
|
|
97
|
+
var raus = [];
|
|
98
|
+
var re = /JSON\.parse\(`(\[\{[\s\S]*?\}\])`\)/g, m;
|
|
99
|
+
while ((m = re.exec(text))) {
|
|
100
|
+
try {
|
|
101
|
+
var l = JSON.parse(m[1]);
|
|
102
|
+
if (Array.isArray(l) && l.length > 20 && l[0] && l[0]._id && l[0].name) {
|
|
103
|
+
raus.push(l);
|
|
104
|
+
}
|
|
105
|
+
} catch { /* keine Liste, weiter */ }
|
|
106
|
+
}
|
|
107
|
+
return raus;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function istListe(liste, marken) {
|
|
111
|
+
var ids = {};
|
|
112
|
+
liste.forEach(function (x) { ids[x._id] = 1; });
|
|
113
|
+
var treffer = 0;
|
|
114
|
+
marken.forEach(function (k) { if (ids[k]) { treffer++; } });
|
|
115
|
+
return treffer >= 2;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function ordneListenZu(text) {
|
|
119
|
+
listenAus(text).forEach(function (l) {
|
|
120
|
+
if (!enumVorlagen.rooms.length && istListe(l, RAUM_MARKE)) { enumVorlagen.rooms = l; }
|
|
121
|
+
else if (!enumVorlagen.functions.length && istListe(l, FUNK_MARKE)) { enumVorlagen.functions = l; }
|
|
122
|
+
});
|
|
123
|
+
return enumVorlagen.rooms.length && enumVorlagen.functions.length;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function ladeEnumVorlagen(nachher) {
|
|
127
|
+
if (vorlagenGeholt || !einst('vorlagenVomAdmin')) { return; }
|
|
128
|
+
vorlagenGeholt = true;
|
|
129
|
+
holeText('/index.html').then(function (html) {
|
|
130
|
+
return sucheEnumChunk(html.match(/assets\/[A-Za-z0-9_\-.]+\.js/g) || [], {}, 0);
|
|
131
|
+
}).then(function (chunk) {
|
|
132
|
+
if (!chunk) { throw new Error('kein Enums-Chunk'); }
|
|
133
|
+
return holeText('/assets/' + chunk);
|
|
134
|
+
}).then(function (src) {
|
|
135
|
+
enumIkonen.functions = ikonMap(src, 'devices');
|
|
136
|
+
enumIkonen.rooms = ikonMap(src, 'rooms');
|
|
137
|
+
if (ordneListenZu(src)) { return; }
|
|
138
|
+
|
|
139
|
+
/* Fehlt eine, liegt sie in einer der Dateien, die der Chunk nennt.
|
|
140
|
+
Kurze Namen zuerst: die Listen stecken in kleinen Bausteinen, das
|
|
141
|
+
Riesenbuendel des Geraetemanagers waere sonst der erste Griff. */
|
|
142
|
+
var dateien = (src.match(/[A-Za-z0-9_.-]+-[A-Za-z0-9_-]{8}\.js/g) || [])
|
|
143
|
+
.filter(function (x, i, a) { return a.indexOf(x) === i; })
|
|
144
|
+
.sort(function (a, b) { return a.length - b.length; })
|
|
145
|
+
.slice(0, 60);
|
|
146
|
+
|
|
147
|
+
return dateien.reduce(function (kette, f) {
|
|
148
|
+
return kette.then(function (fertig) {
|
|
149
|
+
if (fertig) { return true; }
|
|
150
|
+
return holeText('/assets/' + f).then(function (t) {
|
|
151
|
+
/* Ein Buendel von mehreren Megabyte durchsucht man nicht -
|
|
152
|
+
die Listen stehen nie darin, das Durchsuchen kostet nur. */
|
|
153
|
+
if (t.length > 3000000) { return false; }
|
|
154
|
+
return ordneListenZu(t);
|
|
155
|
+
})['catch'](function () { return false; });
|
|
156
|
+
});
|
|
157
|
+
}, Promise.resolve(false));
|
|
158
|
+
})['catch'](function () { /* still bleiben: der Bestand genuegt */ })
|
|
159
|
+
.then(function () {
|
|
160
|
+
if (!enumVorlagen.rooms.length || !enumVorlagen.functions.length) {
|
|
161
|
+
katalogFehlt = true;
|
|
162
|
+
}
|
|
163
|
+
/* Der Aufrufer entscheidet, ob und was neu zu zeichnen ist -
|
|
164
|
+
dieses Modul kennt die Oberflaeche nicht. */
|
|
165
|
+
if (typeof nachher === 'function') { nachher(); }
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Das Icon einer Vorlage. Es steht als fertige data-URI im Chunk - genau
|
|
170
|
+
das Format, das `common.icon` erwartet. Geholt wird erst, wenn jemand
|
|
171
|
+
die Vorlage waehlt; 120 Icons auf Vorrat waeren Verschwendung.
|
|
172
|
+
|
|
173
|
+
Kommt es nicht rechtzeitig an, entsteht die Aufzaehlung ohne Icon. Das
|
|
174
|
+
ist kein Schaden, nur ein leeres Kaestchen im Admin. */
|
|
175
|
+
/* Wer auf ein Bild wartet, hinterlegt hier seinen Rueckruf. Ohne ihn
|
|
176
|
+
blieb der Platz leer, bis irgendetwas anderes ein Neuzeichnen ausloeste:
|
|
177
|
+
die Zeile mit Raum und Funktion steht sofort, das Bild kommt Sekunden
|
|
178
|
+
spaeter aus dem Buendel des Admin. Genau daran hing R12 - der
|
|
179
|
+
Trockenlauf meldete „Bild wird ergaenzt" nur, wenn man langsam genug
|
|
180
|
+
war (26.08.2026). */
|
|
181
|
+
var ikonWarter = {};
|
|
182
|
+
|
|
183
|
+
export function holeIkon(art, name, fertig) {
|
|
184
|
+
var k = art + ':' + name;
|
|
185
|
+
if (typeof fertig === 'function') { ikonWarter[k] = fertig; }
|
|
186
|
+
if (ikonCache[k] !== undefined || ikonLaeuft[k]) { return; }
|
|
187
|
+
var datei = enumIkonen[art] && enumIkonen[art][name];
|
|
188
|
+
if (!datei) {
|
|
189
|
+
/* „Nicht in der Karte“ und „Karte noch nicht da“ sind zweierlei.
|
|
190
|
+
|
|
191
|
+
Der Katalog kommt ueber mehrere Abrufe herein und braucht ein paar
|
|
192
|
+
Sekunden; die Zeile mit Raum und Funktion steht sofort. Wer beim
|
|
193
|
+
ersten Zeichnen nachschlaegt, findet eine leere Karte - und hier
|
|
194
|
+
stand `ikonCache[k] = null` ohne Bedingung. Die Wache oben laesst
|
|
195
|
+
dann nie wieder einen Versuch zu, und das Bild fehlt bis zum
|
|
196
|
+
Neuladen der Seite. Sichtbar wurde es an einem Alias, dessen
|
|
197
|
+
Funktion schon beim Aufschlagen feststand: `Sicherheit` bekam nie
|
|
198
|
+
ein Bild, waehrend dieselbe Funktion von Hand gewaehlt eines
|
|
199
|
+
bekam - von aussen sah das aus wie Zufall.
|
|
200
|
+
|
|
201
|
+
Festgehalten wird deshalb nur, wenn der Katalog vorliegt und den
|
|
202
|
+
Namen wirklich nicht kennt. */
|
|
203
|
+
if ((enumVorlagen[art] || []).length) { ikonCache[k] = null; }
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* „Laeuft gerade“ und „gibt es nicht“ sind zweierlei.
|
|
208
|
+
|
|
209
|
+
Hier stand einmal `ikonCache[k] = null` schon vor dem Laden, damit
|
|
210
|
+
nicht zweimal geholt wird. Damit war der Eintrag aber auch dann auf
|
|
211
|
+
null festgenagelt, wenn das Laden fehlschlug oder die Auswahl
|
|
212
|
+
mittendrin wechselte - und die Wache oben liess nie einen zweiten
|
|
213
|
+
Versuch zu. Das Icon fehlte dann bis zum Neuladen der Seite, und die
|
|
214
|
+
Aufzaehlung entstand ohne. Gefunden beim Raum „Garten“, dessen
|
|
215
|
+
Funktions-Gegenstueck „Steckdose“ sein Icon anstandslos bekam. */
|
|
216
|
+
ikonLaeuft[k] = true;
|
|
217
|
+
holeText('/assets/' + datei).then(function (t) {
|
|
218
|
+
/* Anfuehrungszeichen, Apostroph oder Backtick - dem Bundler ist es
|
|
219
|
+
gleich, uns muss es das auch sein. Admin 7.8.23 schrieb
|
|
220
|
+
`"data:image/svg+xml,..."`, 8.0.2 schreibt dieselbe Zeichenkette
|
|
221
|
+
in Backticks. Wer sich auf ein Zeichen festlegt, verliert beim
|
|
222
|
+
naechsten Update alle Bilder - und zwar lautlos, denn ein
|
|
223
|
+
fehlendes Bild sieht aus wie „gibt es nicht". */
|
|
224
|
+
/* Bis zum **gleichen** Zeichen lesen, mit dem es anfing.
|
|
225
|
+
|
|
226
|
+
Ein `[^"'`]+` waere falsch: die data-URI enthaelt selbst
|
|
227
|
+
Apostrophe (`version='1.1'`), und die Klasse bricht am ersten ab -
|
|
228
|
+
herausgekommen sind 36 Zeichen statt 3.400, und die Aufzaehlung
|
|
229
|
+
bekam ein Bild, das keines ist. Der Rueckverweis \1 liest bis zum
|
|
230
|
+
passenden Schlusszeichen und laesst die anderen beiden in Ruhe. */
|
|
231
|
+
var m = t.match(/(["'`])(data:image\/svg\+xml[\s\S]*?)\1/);
|
|
232
|
+
if (m) { ikonCache[k] = m[2]; return; }
|
|
233
|
+
|
|
234
|
+
/* Zweite Bauart. Kleine Bilder stehen als data-URI im Chunk, grosse
|
|
235
|
+
liegen als eigene Datei daneben und der Chunk nennt nur ihre
|
|
236
|
+
Adresse. Wer nur die erste Bauart kennt, findet ausgerechnet die
|
|
237
|
+
ausfuehrlichen nicht - Garten, Heizung und Licht gehoeren alle
|
|
238
|
+
dazu. Genau daran blieb ein neu angelegter Raum ohne Bild. */
|
|
239
|
+
var u = t.match(/new URL\((["'`])([^"'`]+\.svg)\1/);
|
|
240
|
+
if (!u) { return; }
|
|
241
|
+
return holeText('/assets/' + u[2]).then(function (svg) {
|
|
242
|
+
ikonCache[k] = 'data:image/svg+xml;base64,' +
|
|
243
|
+
btoa(unescape(encodeURIComponent(svg)));
|
|
244
|
+
});
|
|
245
|
+
})['catch'](function () { })
|
|
246
|
+
.then(function () {
|
|
247
|
+
delete ikonLaeuft[k];
|
|
248
|
+
/* Nur melden, wenn wirklich ein Bild herauskam - ein Fehlschlag
|
|
249
|
+
soll kein Neuzeichnen ausloesen. */
|
|
250
|
+
var warter = ikonWarter[k];
|
|
251
|
+
delete ikonWarter[k];
|
|
252
|
+
if (warter && ikonCache[k]) { warter(); }
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Waere hier ein Bild nachzutragen? Dieselbe Frage wie beim Objektbau,
|
|
257
|
+
nur vorab - die Abbruchpruefung braucht sie, bevor gebaut wird. */
|
|
258
|
+
/* Darf hier ueberhaupt ein Bild nachgetragen werden?
|
|
259
|
+
|
|
260
|
+
Drei Schalter, die auf der Einstellungsseite stehen: getrennt fuer
|
|
261
|
+
Raeume und Funktionen, und ein dritter fuer den einzigen Fall, in dem
|
|
262
|
+
ein gefuelltes Feld ueberschrieben wird - der Einrichtungsassistent
|
|
263
|
+
des Admin hinterlaesst dort einen blossen Namen wie „Playroom“, der
|
|
264
|
+
zu keinem Bild fuehrt. */
|
|
265
|
+
export function ikonErlaubt(art, o) {
|
|
266
|
+
if (!einst(art === 'rooms' ? 'ikonRaeume' : 'ikonFunktionen')) { return false; }
|
|
267
|
+
var da = o && o.common && o.common.icon;
|
|
268
|
+
if (ikonTaugt(da)) { return false; } /* schon ein Bild - Finger weg */
|
|
269
|
+
if (da && !einst('ikonErsetzen')) { return false; }
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Waere hier ein Bild nachzutragen? Dieselbe Frage wie beim Objektbau,
|
|
274
|
+
nur vorab - die Abbruchpruefung braucht sie, bevor gebaut wird. */
|
|
275
|
+
export function ikonNachtrag(id, art, o) {
|
|
276
|
+
if (!o || !ikonErlaubt(art, o)) { return false; }
|
|
277
|
+
var v = vorlageZu(id, art, txt(o.common && o.common.name));
|
|
278
|
+
return !!(v && v.icon && ikonCache[art + ':' + v.icon]);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* Taugt das, was im Icon-Feld steht, ueberhaupt als Bild?
|
|
282
|
+
|
|
283
|
+
Die sieben englischen Vorgaberaeume der Erstinstallation fuehren dort
|
|
284
|
+
nur einen Namen: `icon: "Playroom"`. Fruehere Admin-Fassungen haben
|
|
285
|
+
daraus einen Dateipfad gebaut, die heutige nicht mehr - sie zeichnet
|
|
286
|
+
das kaputte Kaestchen. Als Bild zaehlt deshalb nur, was auch auf eines
|
|
287
|
+
zeigt: eine data-URI, ein Pfad oder wenigstens ein Dateiname. */
|
|
288
|
+
export function ikonTaugt(v) {
|
|
289
|
+
if (!v || typeof v !== 'string') { return false; }
|
|
290
|
+
return v.indexOf('data:') === 0 || v.indexOf('/') === 0 ||
|
|
291
|
+
v.indexOf('http') === 0 || v.indexOf('.') > -1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* Welche Vorlage gehoert zu einer vorhandenen Aufzaehlung?
|
|
295
|
+
|
|
296
|
+
Erst ueber die Kennung (`enum.rooms.garden` -> `garden`), dann ueber
|
|
297
|
+
den Namen. Das Zweite ist noetig, weil Ricardos Aufzaehlungen von
|
|
298
|
+
hm-rega aus den CCU-Gewerken stammen und deutsche Kennungen tragen:
|
|
299
|
+
`enum.functions.Heizung` waehrend die Vorlage `heating` heisst. Ueber
|
|
300
|
+
den Namen finden sich beide. */
|
|
301
|
+
export function vorlageZu(id, art, name) {
|
|
302
|
+
var kurz = id.slice(('enum.' + art + '.').length);
|
|
303
|
+
var liste = enumVorlagen[art] || [];
|
|
304
|
+
var v = null;
|
|
305
|
+
liste.forEach(function (x) { if (!v && x._id === kurz) { v = x; } });
|
|
306
|
+
if (v) { return v; }
|
|
307
|
+
var n = String(name || '').toLowerCase();
|
|
308
|
+
if (!n) { return null; }
|
|
309
|
+
liste.forEach(function (x) {
|
|
310
|
+
if (!v && String(txt(x.name) || '').toLowerCase() === n) { v = x; }
|
|
311
|
+
});
|
|
312
|
+
return v;
|
|
313
|
+
}
|