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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +516 -0
  3. package/admin/alias-workbench.png +0 -0
  4. package/admin/css/werkbank.css +916 -0
  5. package/admin/detector.js +1 -0
  6. package/admin/geraetewissen/tasmota-befehle.json +116 -0
  7. package/admin/i18n/de.json +552 -0
  8. package/admin/i18n/en.json +552 -0
  9. package/admin/js/aufzaehlungen.js +332 -0
  10. package/admin/js/basis.js +36 -0
  11. package/admin/js/baum.js +453 -0
  12. package/admin/js/detail.js +335 -0
  13. package/admin/js/einstellungen.js +40 -0
  14. package/admin/js/entwurf.js +908 -0
  15. package/admin/js/enums.js +23 -0
  16. package/admin/js/ergebnis.js +934 -0
  17. package/admin/js/erkennung.js +126 -0
  18. package/admin/js/katalog.js +313 -0
  19. package/admin/js/mqtt.js +885 -0
  20. package/admin/js/musternamen.js +94 -0
  21. package/admin/js/musterwahl.js +334 -0
  22. package/admin/js/objekte.js +243 -0
  23. package/admin/js/pruefungen.js +130 -0
  24. package/admin/js/quellentausch.js +570 -0
  25. package/admin/js/rollenwahl.js +199 -0
  26. package/admin/js/schreiben.js +1537 -0
  27. package/admin/js/sprache.js +137 -0
  28. package/admin/js/start.js +39 -0
  29. package/admin/js/verbindung.js +10 -0
  30. package/admin/js/vorlagen.js +572 -0
  31. package/admin/js/vorlagenblatt.js +1907 -0
  32. package/admin/js/vorschlagen.js +391 -0
  33. package/admin/js/werte.js +193 -0
  34. package/admin/js/zielleiste.js +339 -0
  35. package/admin/js/zuordnung.js +695 -0
  36. package/admin/js/zustand.js +47 -0
  37. package/admin/js/zustandsliste.js +515 -0
  38. package/admin/jsonConfig.json +249 -0
  39. package/admin/tab.html +148 -0
  40. package/admin/vorlagen/hm-dimmer.json +204 -0
  41. package/admin/vorlagen/hm-fensterkontakt-hm.json +176 -0
  42. package/admin/vorlagen/hm-fensterkontakt-hmip.json +191 -0
  43. package/admin/vorlagen/hm-heizgruppe.json +214 -0
  44. package/admin/vorlagen/hm-heizkoerper.json +236 -0
  45. package/admin/vorlagen/hm-rollladen.json +190 -0
  46. package/admin/vorlagen/hm-schaltaktor-lampe.json +197 -0
  47. package/admin/vorlagen/hm-schaltaktor-mehrfach.json +203 -0
  48. package/admin/vorlagen/hm-schaltaktor-steckdose.json +197 -0
  49. package/admin/vorlagen/hm-temperatur-feuchte.json +166 -0
  50. package/admin/vorlagen/hm-wandthermostat.json +228 -0
  51. package/admin/vorlagen/index.json +21 -0
  52. package/admin/vorlagen/messpunkt.json +225 -0
  53. package/admin/vorlagen/tasmota-lampe.json +263 -0
  54. package/admin/vorlagen/tasmota-mehrfach.json +203 -0
  55. package/admin/vorlagen/tasmota-rgb.json +239 -0
  56. package/admin/vorlagen/tasmota-steckdose.json +265 -0
  57. package/io-package.json +122 -0
  58. 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
+ }