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,695 @@
1
+ /* Raum, Funktion und Ziel am Entwurf: die Felderzeile, was sich an den
2
+ Aufzaehlungen aendert, und was der Bestand dem Vorschlag voraus hat. */
3
+
4
+ import { S } from './zustand.js';
5
+ import { el } from './basis.js';
6
+ import { tr, txt } from './sprache.js';
7
+ import { enums } from './enums.js';
8
+ import {
9
+ enumListe,
10
+ enumsVon,
11
+ neueEnum,
12
+ raumVorschlag,
13
+ funktionVorschlag
14
+ } from './aufzaehlungen.js';
15
+ import { enumVorlagen, katalogFehlt, ikonCache, ikonErlaubt, ikonNachtrag,
16
+ ikonTaugt, vorlageZu, holeIkon } from './katalog.js';
17
+ import { kindZustaende, aliasQuellen } from './werte.js';
18
+ import { aliasFuer, zielId } from './entwurf.js';
19
+ import { zeichneErgebnis, entwurfAngefasst, angebotFrisch } from './ergebnis.js';
20
+
21
+
22
+ import { ausgangName } from './vorlagen.js';
23
+ import { musterVon } from './erkennung.js';
24
+
25
+ /* Welche Aufzaehlungen aendern sich, wenn dieser Entwurf geschrieben wird?
26
+
27
+ Liefert je Aufzaehlung das fertige Objekt, so wie es hinterher aussehen
28
+ soll. Damit kann der Trockenlauf es zeigen wie jedes andere Objekt auch,
29
+ und geschrieben wird genau das, was dort stand.
30
+
31
+ Wichtig: gelesen wird der *aktuelle* Stand und nur die eigene Kennung
32
+ hinzugefuegt oder entfernt. Eine Aufzaehlung ist geteiltes Gut - in
33
+ `enum.rooms.Bastelzimmer` stehen acht fremde Eintraege. Wer die Liste neu
34
+ baut, statt sie zu ergaenzen, loescht anderer Leute Zuordnungen. */
35
+ export function enumAenderungen(e) {
36
+ var raus = [];
37
+ if (!e || !e.kanal) { return raus; }
38
+ /* Im Aliasmodus gibt es kein gewaehltes Ziel — der Alias *ist* das Ziel.
39
+ Eine frueher hier stehende Abfrage brach in genau diesem Fall ab, und
40
+ eine im Aliasmodus geaenderte Zuordnung wurde stillschweigend nicht
41
+ geschrieben. */
42
+ var ziel = e.ziel || e.kanal;
43
+
44
+ [['rooms', e.raum], ['functions', e.funktion]].forEach(function (paar) {
45
+ var art = paar[0], gewaehlt = paar[1] || '';
46
+ var betroffen = {};
47
+
48
+ /* die, in denen das Ziel jetzt steht */
49
+ enumsVon(ziel, art).forEach(function (id) { betroffen[id] = true; });
50
+ /* und die gewaehlte */
51
+ if (gewaehlt) { betroffen[gewaehlt] = true; }
52
+
53
+ Object.keys(betroffen).forEach(function (id) {
54
+ var o = enums[id];
55
+ var mAlt = (o && o.common && o.common.members) ? o.common.members.slice() : [];
56
+ var mNeu = mAlt.filter(function (x) {
57
+ /* Nur die eigene Kennung anfassen: sie bleibt, wenn diese
58
+ Aufzaehlung die gewaehlte ist, sonst faellt sie heraus. Alles
59
+ andere ist fremdes Eigentum und wird nicht beruehrt. */
60
+ if (x === ziel) { return id === gewaehlt; }
61
+ return true;
62
+ });
63
+ if (id === gewaehlt && mNeu.indexOf(ziel) === -1) { mNeu.push(ziel); mNeu.sort(); }
64
+ /* Sonst faellt das nachgetragene Bild durch: die Mitglieder
65
+ aendern sich dabei ja nicht. */
66
+ if (JSON.stringify(mAlt) === JSON.stringify(mNeu) && !ikonNachtrag(id, art, o)) { return; }
67
+
68
+ var obj = o ? JSON.parse(JSON.stringify(o)) : neueEnum(id, art);
69
+ obj.common = obj.common || {};
70
+ obj.common.members = mNeu;
71
+
72
+ /* Einer vorhandenen Aufzaehlung das fehlende Bild nachtragen.
73
+
74
+ Neu angelegte bekommen ihres aus der Vorlage. Vorhandene nicht -
75
+ bis hierher wurde nur die Mitgliederliste ergaenzt, und das Feld
76
+ blieb, wie es war. Bei Ricardo heisst das: keine einzige seiner
77
+ elf Funktionen und keiner seiner vierzehn Raeume hat je ein Bild
78
+ bekommen, denn hm-rega spiegelt aus der CCU nur Namen und
79
+ Mitglieder. Und die sieben Vorgaberaeume zeigen ein kaputtes
80
+ Kaestchen, weil dort ein blosser Name steht.
81
+
82
+ Angefasst wird nur, wo gar nichts Brauchbares steht. Ein Bild,
83
+ das der Nutzer selbst gesetzt hat, bleibt unberuehrt. */
84
+ var ikonDazu = false;
85
+ if (o && ikonErlaubt(art, obj)) {
86
+ var vv = vorlageZu(id, art, txt(obj.common.name));
87
+ if (vv && vv.icon) {
88
+ var ik = ikonCache[art + ':' + vv.icon];
89
+ if (ik) { obj.common.icon = ik; ikonDazu = true; }
90
+ /* Noch nicht da? Dann wenigstens jetzt holen, damit es beim
91
+ naechsten Schreiben bereitsteht. */
92
+ else { holeIkon(art, vv.icon); }
93
+ }
94
+ }
95
+ delete obj.ts; delete obj.from; delete obj.user; delete obj.acl;
96
+ raus.push({ id: id, neu: !o, obj: obj, istEnum: true, ikonDazu: ikonDazu,
97
+ dazu: mNeu.indexOf(ziel) > -1 && mAlt.indexOf(ziel) === -1,
98
+ weg: mAlt.indexOf(ziel) > -1 && mNeu.indexOf(ziel) === -1 });
99
+ });
100
+ });
101
+ return raus;
102
+ }
103
+
104
+ /* Die Zeile mit Raum und Funktion.
105
+
106
+ Steht sowohl an der Quelle als auch am fertigen Alias. An der Quelle
107
+ gehoert sie unter das Ziel — dort entscheidet sich, wohin der Alias
108
+ kommt und wozu er zaehlt. Am Alias ist sie der einzige Weg, die
109
+ Zuordnung spaeter noch zu aendern; ohne sie waere die Sache eine
110
+ Einbahnstrasse. */
111
+ export function enumZeile(host, e) {
112
+ setzeEnums(e);
113
+
114
+ /* Die Bilder der beiden gewaehlten Aufzaehlungen schon jetzt holen.
115
+
116
+ Beim Klick auf eine Vorlagenzeile geschieht das ohnehin. Eine
117
+ vorhandene Aufzaehlung stoesst aber niemand an - und genau die
118
+ braucht das Bild, wenn ihr eines fehlt. Ohne diesen Anstoss traegt
119
+ der erste Schreibvorgang nichts nach und erst der zweite tut es, was
120
+ von aussen wie Zufall aussieht.
121
+
122
+ `holeIkon` haelt sich selbst zurueck, wenn es laeuft oder das Bild
123
+ vorliegt - hier entsteht kein wiederholtes Laden. */
124
+ [['rooms', e.raum], ['functions', e.funktion]].forEach(function (paar) {
125
+ var art = paar[0], id = paar[1];
126
+ if (!id) { return; }
127
+ var o = enums[id];
128
+ if (o && !ikonErlaubt(art, o)) { return; }
129
+ var v = vorlageZu(id, art, o ? txt(o.common && o.common.name) : '');
130
+ if (v && v.icon) { holeIkon(art, v.icon, zeichneErgebnis); }
131
+ });
132
+
133
+ /* --- Raum und Funktion ---
134
+
135
+ Eigene Zeile unter dem Ziel, weil beides zum Alias gehoert, aber
136
+ woanders abgelegt wird: nicht am Objekt, sondern in einer
137
+ Aufzaehlung. Aufbau wie das Ordnerfeld daneben - Eingabefeld mit
138
+ eigener Liste, kein <select>, damit man tippen und suchen kann. */
139
+ var ebar = el('div', 'zielbar enumbar');
140
+ /* Das Angebot wird in der Schleife gebaut, aber erst danach angehaengt -
141
+ sonst stuende es zwischen Raum und Funktion und schoebe das zweite
142
+ Feld in die naechste Zeile. */
143
+ var enumAngebot = null;
144
+ [{ art: 'rooms', feld: 'raum', her: 'raumHer',
145
+ label: tr('enums.room'), hint: tr('enums.roomHint') },
146
+ { art: 'functions', feld: 'funktion', her: 'funktionHer',
147
+ label: tr('enums.function'), hint: tr('enums.functionHint') }
148
+ ].forEach(function (spec) {
149
+ ebar.appendChild(el('span', 'zl', spec.label));
150
+ var ew = el('div', 'feldwrap');
151
+ var ei = el('input', 'tx enumfeld');
152
+ ei.type = 'text';
153
+ ei.setAttribute('autocomplete', 'off');
154
+ ei.placeholder = tr('enums.placeholder');
155
+ ei.title = spec.hint;
156
+
157
+ var gew = e[spec.feld] || '';
158
+ var nameVon = function (id) {
159
+ if (!id) { return ''; }
160
+ if (enums[id]) { return txt((enums[id].common || {}).name) || id.split('.').pop(); }
161
+ /* Noch nicht angelegt: dann steht der Name in der Vorlage. Ohne
162
+ das zeigte das Feld nach der Wahl von „Stehlampe“ die nackte
163
+ Kennung „floor_lamp“ - der Nutzer sieht etwas anderes, als er
164
+ angeklickt hat. */
165
+ var kurz = id.split('.').pop(), gefunden = '';
166
+ (enumVorlagen[spec.art] || []).forEach(function (v) {
167
+ if (v._id === kurz) { gefunden = txt(v.name) || kurz; }
168
+ });
169
+ /* Und dass es sie noch nicht gibt, steht dahinter - im Feld, nicht
170
+ als Marke am Zeilenende. Dort hing sie zuerst und rutschte durch
171
+ den Umbruch unter das Raumfeld, wo sie aussah, als gehoere sie
172
+ zum Raum. Was ueber ein Feld etwas aussagt, gehoert in das Feld.
173
+
174
+ Zurueckgelesen wird der Feldinhalt nie: beim Hineinklicken leert
175
+ er sich zum Tippen, beim Verlassen wird er neu aufgebaut. */
176
+ return (gefunden || kurz) + ' (' + tr('enums.newSuffix') + ')';
177
+ };
178
+ ei.value = nameVon(gew);
179
+ ew.appendChild(ei);
180
+
181
+ /* Das Bild der gewaehlten Aufzaehlung - rechts NEBEN dem Feld, nicht
182
+ darin. Dort steht es frei: zwischen Feldende und der naechsten
183
+ Beschriftung liegen 54 px, hinter dem Funktionsfeld 91 px
184
+ (gemessen 26.08.2026). Im Feld haette es Text verdraengt, und der
185
+ ist knapper als der Platz daneben (Ricardo, 26.08.2026).
186
+
187
+ Absolut zum Feldrahmen, damit das Raster unangetastet bleibt -
188
+ sonst stuende die Zielzeile darunter nicht mehr buendig.
189
+
190
+ Drei Lagen, und man sieht sie am Feld statt erst im Trockenlauf:
191
+
192
+ - die Aufzaehlung hat schon ein taugliches Bild -> es steht da
193
+ - sie hat keins, die Werkbank wuerde eins nachtragen -> blass,
194
+ und weil das eine Aussage ueber diese eine Aufzaehlung ist,
195
+ mit einem kleinen „neu" daneben
196
+ - sonst nichts: kein Bild im Katalog, oder der Schalter ist aus.
197
+ Dann sieht das Feld aus wie frueher, und das ist die Wahrheit.
198
+
199
+ Eine Aufzaehlung, die es noch gar nicht gibt, bekommt ihr Bild
200
+ ohnehin aus der Vorlage (`neueEnum`) - dort steht das „neu" schon
201
+ im Feldtext, also hier nur blass und ohne Wort. */
202
+ var bild = '', blass = false, marke = false;
203
+ if (gew) {
204
+ var oIk = enums[gew];
205
+ var daIk = oIk && oIk.common && oIk.common.icon;
206
+ if (ikonTaugt(daIk)) {
207
+ bild = daIk;
208
+ } else if (oIk ? ikonErlaubt(spec.art, oIk) : true) {
209
+ var vIk = vorlageZu(gew, spec.art,
210
+ oIk ? txt(oIk.common && oIk.common.name) : '');
211
+ var bIk = (vIk && vIk.icon) ? ikonCache[spec.art + ':' + vIk.icon] : null;
212
+ if (bIk) { bild = bIk; blass = true; marke = !!oIk; }
213
+ }
214
+ }
215
+ if (bild) {
216
+ var iw = el('div', 'feldikon' + (blass ? ' blass' : ''));
217
+ var img = document.createElement('img');
218
+ img.src = bild;
219
+ img.alt = '';
220
+ iw.appendChild(img);
221
+ if (marke) { iw.appendChild(el('span', 'ikneu', tr('enums.iconNewMark'))); }
222
+ iw.title = blass ? tr('enums.iconNew') : tr('enums.iconThere');
223
+ ew.appendChild(iw);
224
+ }
225
+
226
+ var ex = el('button', 'feldx');
227
+ ex.type = 'button';
228
+ ex.title = tr('enums.clear');
229
+ ex.setAttribute('aria-label', tr('enums.clear'));
230
+ ex.hidden = !ei.value;
231
+ ex.addEventListener('mousedown', function (ev) {
232
+ ev.preventDefault();
233
+ e[spec.feld] = '';
234
+ e[spec.her] = 'hand';
235
+ entwurfAngefasst();
236
+ zeichneErgebnis();
237
+ });
238
+ ew.appendChild(ex);
239
+
240
+ var el2 = el('div', 'vorschlaege');
241
+ el2.hidden = true;
242
+ ew.appendChild(el2);
243
+ ebar.appendChild(ew);
244
+
245
+ /* Ist das ein Vorschlag oder steht es schon so im Bestand? Ohne
246
+ diesen Unterschied sieht ein Vorschlag aus wie eine Tatsache.
247
+ Die Marke bekommt im Raster die Spalte ihres Feldes zugewiesen -
248
+ frei fliessend landete sie unter dem falschen Feld, und in den
249
+ Feldrahmen gehaengt schob sie Kreuz und Aufklappliste aus der
250
+ Position. */
251
+ if (gew && e[spec.her] === 'vorschlag') {
252
+ ebar.appendChild(el('span', 'chip mut vorschlagsmarke '
253
+ + (spec.art === 'rooms' ? 'raum' : 'funktion'), tr('enums.suggested')));
254
+ }
255
+
256
+
257
+ /* Die Gegenrichtung, nach derselben Regel: wer den Raum geaendert
258
+ hat, bekommt hier angeboten, den Ablageort nachzuziehen. Nur im
259
+ Quellenmodus - am fertigen Alias gibt es kein Ziel zu waehlen. */
260
+ if (spec.art === 'rooms' && e.zuletzt === 'raum' && e.ziel && !aliasFuer(e.kanal)) {
261
+ var ro2 = enumAlsOrdner(e.raum);
262
+ if (ro2 && e.zielOrdner !== 'alias.0.' + ro2) {
263
+ var oz = el('div', 'uebernahme' +
264
+ (angebotFrisch('raum:' + ro2) ? ' frisch' : ''));
265
+ oz.appendChild(el('span', 'zq', tr('enums.alsoFolder', ro2)));
266
+ var ob = el('button', 'btn mini wichtig', tr('target.yes'));
267
+ ob.addEventListener('click', function () {
268
+ e.zielOrdner = 'alias.0.' + ro2;
269
+ e.ordnerVonHand = true;
270
+ entwurfAngefasst();
271
+ zeichneErgebnis();
272
+ });
273
+ oz.appendChild(ob);
274
+ enumAngebot = oz;
275
+ }
276
+ }
277
+
278
+ var mListe = [];
279
+ var mMark = -1;
280
+ var zu = function () { el2.hidden = true; mMark = -1; };
281
+
282
+ var malen = function () {
283
+ var such = String(ei.value || '').trim().toLowerCase();
284
+ var alle = enumListe(spec.art);
285
+ mListe = such
286
+ ? alle.filter(function (x) {
287
+ return x.name.toLowerCase().indexOf(such) > -1 ||
288
+ x.kurz.toLowerCase().indexOf(such) > -1; })
289
+ : alle;
290
+ el2.textContent = '';
291
+
292
+ if (!mListe.length) {
293
+ /* Was es nicht gibt, laesst sich anlegen - aber nur hier, im Feld
294
+ selbst. Wer im Raumfeld steht und einen Namen tippt, will einen
295
+ Raum; das ist eine Entscheidung, kein Nebeneffekt. Aus einem
296
+ Ordnernamen entsteht dagegen nie von allein eine Aufzaehlung -
297
+ sonst faenden sich bald „test“, „alt“ und „neu“ unter den
298
+ Raeumen wieder.
299
+
300
+ Der Name wird nur gesaeubert, wo er als Kennung nicht taugt:
301
+ Punkte machten eine weitere Ebene auf. */
302
+ var frei = String(ei.value || '').trim().replace(/\./g, '_');
303
+ if (frei) {
304
+ var nz = el('div', 'vz');
305
+ nz.appendChild(el('span', null, frei));
306
+ nz.appendChild(el('span', 'tiefer', tr('enums.willCreate')));
307
+ nz.addEventListener('mousedown', function (ev) {
308
+ ev.preventDefault();
309
+ e[spec.feld] = 'enum.' + spec.art + '.' + frei;
310
+ e[spec.her] = 'hand';
311
+ if (spec.art === 'rooms') { e.zuletzt = 'raum'; }
312
+ entwurfAngefasst();
313
+ zu();
314
+ zeichneErgebnis();
315
+ });
316
+ el2.appendChild(nz);
317
+ } else {
318
+ var neu2 = el('div', 'vz zurueck');
319
+ neu2.appendChild(el('span', null, tr('enums.noMatch', ei.value)));
320
+ el2.appendChild(neu2);
321
+ }
322
+ el2.hidden = false;
323
+ return;
324
+ }
325
+
326
+ mListe.forEach(function (x, n) {
327
+ var z = el('div', 'vz' + (n === mMark ? ' an' : '') + (x.id === e[spec.feld] ? ' gewaehlt' : ''));
328
+ z.appendChild(el('span', null, x.name));
329
+ z.appendChild(el('span', 'tiefer',
330
+ x.vorlage ? tr('enums.willCreate')
331
+ : (x.anzahl ? tr('enums.members', x.anzahl) : tr('enums.unused'))));
332
+ z.addEventListener('mousedown', function (ev) {
333
+ ev.preventDefault();
334
+ e[spec.feld] = x.id;
335
+ e[spec.her] = 'hand';
336
+ if (spec.art === 'rooms') { e.zuletzt = 'raum'; }
337
+ /* Das Icon jetzt holen, nicht erst beim Schreiben: dort ist
338
+ kein Warten mehr moeglich, der Objektbau laeuft synchron. */
339
+ if (x.vorlage && x.vorlage.icon) { holeIkon(spec.art, x.vorlage.icon); }
340
+ entwurfAngefasst();
341
+ zu();
342
+ zeichneErgebnis();
343
+ });
344
+ el2.appendChild(z);
345
+ });
346
+ if (katalogFehlt) {
347
+ var kf = el('div', 'vz zurueck');
348
+ kf.appendChild(el('span', null, tr('catalog.missing')));
349
+ el2.appendChild(kf);
350
+ }
351
+ el2.hidden = false;
352
+ };
353
+
354
+ ei.addEventListener('focus', function () { ei.value = ''; mMark = -1; malen(); });
355
+ ei.addEventListener('input', function () { mMark = -1; malen(); });
356
+ ei.addEventListener('blur', function () {
357
+ setTimeout(function () { zu(); ei.value = nameVon(e[spec.feld] || ''); }, 140);
358
+ });
359
+ ei.addEventListener('keydown', function (ev) {
360
+ if (ev.key === 'Escape') { zu(); ei.value = nameVon(e[spec.feld] || ''); ei.blur(); return; }
361
+ if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') {
362
+ if (el2.hidden) { malen(); return; }
363
+ ev.preventDefault();
364
+ if (!mListe.length) { return; }
365
+ mMark += (ev.key === 'ArrowDown' ? 1 : -1);
366
+ if (mMark < 0) { mMark = mListe.length - 1; }
367
+ if (mMark >= mListe.length) { mMark = 0; }
368
+ malen();
369
+ var akt = el2.children[mMark];
370
+ if (akt && akt.scrollIntoView) { akt.scrollIntoView({ block: 'nearest' }); }
371
+ return;
372
+ }
373
+ if (ev.key === 'Enter' && !el2.hidden && mListe.length) {
374
+ ev.preventDefault();
375
+ e[spec.feld] = mListe[mMark >= 0 ? mMark : 0].id;
376
+ e[spec.her] = 'hand';
377
+ entwurfAngefasst();
378
+ zu();
379
+ zeichneErgebnis();
380
+ }
381
+ });
382
+ });
383
+ if (enumAngebot) { ebar.appendChild(enumAngebot); }
384
+ host.appendChild(ebar);
385
+ }
386
+
387
+ /* Welcher Raum, welche Funktion - und woher.
388
+
389
+ Gibt es den Alias schon, gilt, was dort steht: seine Zuordnung ist eine
390
+ Tatsache, kein Vorschlag. Gibt es ihn noch nicht, wird vorgeschlagen.
391
+ `raumHer`/`funktionHer` haelt fest, welcher Fall es war - die Zeile sagt
392
+ das dazu, damit ein Vorschlag nicht wie eine bestehende Zuordnung
393
+ aussieht. */
394
+ export function setzeEnums(e) {
395
+ if (e.raum !== undefined && e.funktion !== undefined) { return; }
396
+ var ziel = e.ziel || e.kanal;
397
+ var da = !!S.objects[ziel];
398
+
399
+ if (e.raum === undefined) {
400
+ var r = da ? (enumsVon(ziel, 'rooms')[0] || '') : '';
401
+ e.raumHer = r ? 'bestand' : '';
402
+ if (!r) { r = raumVorschlag(e); if (r) { e.raumHer = 'vorschlag'; } }
403
+ e.raum = r || '';
404
+ }
405
+ if (e.funktion === undefined) {
406
+ var f = da ? (enumsVon(ziel, 'functions')[0] || '') : '';
407
+ e.funktionHer = f ? 'bestand' : '';
408
+ if (!f) { f = funktionVorschlag(e); if (f) { e.funktionHer = 'vorschlag'; } }
409
+ e.funktion = f || '';
410
+ }
411
+ }
412
+
413
+ /* Der Name einer Aufzaehlung, so wie er als Ordner taugen wuerde.
414
+
415
+ Ein Ordner unter `alias.0` darf keine Punkte enthalten - sie wuerden
416
+ eine weitere Ebene aufmachen. Alles andere bleibt, wie der Nutzer es
417
+ kennt: „Wohnkueche“ heisst auch im Pfad „Wohnkueche“. */
418
+ export function enumAlsOrdner(id) {
419
+ if (!id) { return ''; }
420
+ var o = enums[id];
421
+ var n = o ? (txt((o.common || {}).name) || '') : '';
422
+ if (!n) {
423
+ /* Noch nicht angelegt? Dann steht der Name in der Vorlage. */
424
+ var art = id.indexOf('enum.rooms.') === 0 ? 'rooms' : 'functions';
425
+ var v = vorlageZu(id, art, '');
426
+ n = v ? (txt(v.name) || '') : '';
427
+ }
428
+ if (!n) { n = id.split('.').pop(); }
429
+ return String(n).replace(/\./g, '_').trim();
430
+ }
431
+
432
+ export function setzeZiel(e) {
433
+ if (S.current.indexOf('alias.') === 0) { return; }
434
+ if (e.zielOrdner === undefined) {
435
+ var zt0 = (e.ziel || zielId(S.current)).split('.');
436
+ var geraet = zt0.pop();
437
+
438
+ /* Gibt es fuer diese Quelle schon einen Alias, ist der das Ziel —
439
+ auch wenn er woanders liegt als der Vorschlag. Sonst zeigt die
440
+ Werkbank auf einen freien Platz, meldet „wird neu angelegt" und
441
+ legt beim Klick ein zweites Geraet an, waehrend das erste
442
+ verwaist stehenbleibt. Beim Karbonator lag der Alias unter
443
+ alias.0.Strom, vorgeschlagen wurde alias.0.SmartHome. */
444
+ var vorhanden = aliasFuer(S.current);
445
+ /* Hier stand einmal ein `e.altesZiel = vorhanden`, damit beim
446
+ Verschieben die alte Kennung aus den Aufzaehlungen fliegt. Gemessen
447
+ hat sich das als falsch erwiesen: die Werkbank verschiebt nicht,
448
+ sie legt am neuen Ort an - der alte Alias bleibt stehen. Ihm dabei
449
+ den Raum wegzunehmen, waere ein stiller Schaden an einem Objekt,
450
+ das es weiterhin gibt.
451
+
452
+ Wer wirklich verschwindet, wird geloescht, und dabei traegt der
453
+ js-controller selbst aus allen Aufzaehlungen aus. Es gibt also
454
+ nichts zu tun. */
455
+
456
+ if (e.instanzen && e.instanzen.length > 1) {
457
+ /* Mehrere Ausgaenge: das Geraet wird zum Ordner, jeder Ausgang
458
+ ein Kanal darin. Ein folder darueber, kein device — sonst
459
+ rutscht die Erkennung hoch und macht aus dreien wieder eins. */
460
+ if (vorhanden) {
461
+ /* Der gefundene Kanal ist einer der Ausgaenge; der Ordner liegt
462
+ darueber. Wie der einzelne Ausgang heisst, kann die Werkbank
463
+ nicht wissen — wer sie umbenannt hat, sieht den Vorschlag in
464
+ der Zielzeile und kann ihn anpassen. */
465
+ var tm = vorhanden.split('.');
466
+ tm.pop();
467
+ e.zielOrdner = tm.join('.');
468
+ } else {
469
+ e.zielOrdner = zt0.join('.') + '.' + geraet;
470
+ }
471
+ e.zielName = ausgangName(e, e.instanz);
472
+ } else if (vorhanden) {
473
+ var te = vorhanden.split('.');
474
+ e.zielName = te.pop();
475
+ e.zielOrdner = te.join('.');
476
+ } else {
477
+ e.zielOrdner = zt0.join('.');
478
+ e.zielName = geraet;
479
+
480
+ /* Steht ein Raum fest, gehoert der Alias dorthin.
481
+
482
+ Das ist der haeufigste Fall bei Homematic: der Kanal haengt in
483
+ „Bastelzimmer“, und dann soll der Alias nicht unter dem
484
+ Adapterpfad landen, sondern unter `alias.0.Bastelzimmer`. Nur
485
+ beim Vorschlag - wer den Ordner selbst tippt, behaelt ihn
486
+ (`e.ordnerVonHand`), und wer schon einen Alias hat, wird nicht
487
+ umgezogen (der Zweig hier greift nur, wenn es keinen gibt). */
488
+ if (!e.ordnerVonHand) {
489
+ setzeEnums(e);
490
+ var ro = enumAlsOrdner(e.raum);
491
+ if (ro) { e.zielOrdner = 'alias.0.' + ro; }
492
+ }
493
+ }
494
+ }
495
+ e.ziel = e.zielOrdner + '.' + e.zielName;
496
+ uebernehmeBestand(e);
497
+ }
498
+
499
+ export function uebernehmeBestand(e) {
500
+ if (!e.ziel || e.bestandGeprueft === e.ziel) { return; }
501
+ e.bestandGeprueft = e.ziel;
502
+ if (!S.objects[e.ziel]) { return; }
503
+ /* Im Aliasmodus ist der Entwurf schon der Bestand. */
504
+ if (e.kanal === e.ziel) { return; }
505
+
506
+ var kennt = {};
507
+ var kenntQuelle = {};
508
+ e.states.forEach(function (s) {
509
+ if (s.n) { kennt[s.n] = s; }
510
+ if (s.srcR) { kenntQuelle[s.srcR] = s; }
511
+ });
512
+
513
+ /* Was im Alias steht, gilt - nicht nur, wie er heisst.
514
+
515
+ Uebernommen wurde bisher allein der Name. Rolle, Typ, Einheit,
516
+ Werteliste, Formeln und vor allem die Schreibquelle blieben die der
517
+ Quelle. Solange jeder Alias entweder aus einer Vorlage entstand oder
518
+ die Rollen der Quelle uebernahm, fiel das nicht auf - beide waren
519
+ gleich. Beim ersten Alias, dessen Rollen sich von denen der Quelle
520
+ unterscheiden (geratene Plaetze), fiel es sofort auf: der Sprung zur
521
+ Quelle zeigte `thermostat` statt `airCondition`, die Schreibquelle
522
+ war weg, und von neun Punkten blieben zwei angehakt.
523
+
524
+ Nur ohne Vorlage. Hat eine gegriffen, ist ihr Vorschlag gemeint -
525
+ sonst uebertoente der Bestand sie stillschweigend, und „aendert
526
+ sich" haette nichts mehr zu melden. */
527
+ function vomAliasUebernehmen(z, o) {
528
+ if (e.vorlage) { return; }
529
+ var c = o.common || {}, a = c.alias || {}, q2 = aliasQuellen(o);
530
+ if (c.role) { z.role = c.role; }
531
+ if (c.type) { z.typ = c.type; }
532
+ z.unit = c.unit || '';
533
+ z.states = c.states || undefined;
534
+ z.f = (typeof a.read === 'string') ? a.read : '';
535
+ z.fw = (typeof a.write === 'string') ? a.write : '';
536
+ if (q2.read) { z.srcR = q2.read; }
537
+ if (q2.write && !q2.einfach && q2.write !== q2.read) {
538
+ z.srcW = q2.write;
539
+ /* Die Gegenzeile eines Paars ist keine eigene mehr - ihre Quelle
540
+ steckt jetzt als Schreibquelle in dieser Zeile. */
541
+ e.states.forEach(function (x) {
542
+ if (x !== z && x.srcR === q2.write) { x.on = false; }
543
+ });
544
+ }
545
+ e.bestandZog = true;
546
+ }
547
+
548
+ /* Erstens: was der Vorschlag kennt und im Alias steht, wird angehakt. */
549
+ Object.keys(kennt).forEach(function (n) {
550
+ var s = kennt[n];
551
+ var o0 = S.objects[e.ziel + '.' + n];
552
+ if (!o0) { return; }
553
+ if (!s.on) { s.on = true; }
554
+ s.ausBestand = true;
555
+ vomAliasUebernehmen(s, o0);
556
+ });
557
+
558
+ /* Zweitens — und das ist das Wichtigere: was NUR im Alias steht, kommt
559
+ als eigene Zeile dazu. Vorher war es unsichtbar. Man sah seine selbst
560
+ angelegten Punkte nie wieder, konnte sie weder aendern noch
561
+ entfernen, die Aenderungs-Karte war blind fuer sie, und beim
562
+ Aktualisieren standen sie stumm unter „Entfernen". */
563
+ kindZustaende(e.ziel).forEach(function (id) {
564
+ var n = id.slice(e.ziel.length + 1);
565
+ if (n.indexOf('.') !== -1 || kennt[n]) { return; }
566
+ var o = S.objects[id], c = o.common || {}, a = c.alias || {}, q = aliasQuellen(o);
567
+
568
+ /* Derselbe Punkt unter anderem Namen? Dann ist es kein zweiter,
569
+ sondern derselbe — und der Name, der schon im Alias steht, gilt.
570
+
571
+ Sonst entstuenden Dubletten, sobald die Werkbank ihre Namensgebung
572
+ aendert: der Entwurf nennt einen Punkt heute `Licht_Bar_STATE`, im
573
+ Alias steht er als `1_STATE`, und beide kaemen in die Liste. Und
574
+ schlimmer: ein Aktualisieren wuerde `1_STATE` loeschen und
575
+ `Licht_Bar_STATE` anlegen — jedes Widget und jedes Skript, das auf
576
+ den alten Namen zeigt, liefe ins Leere. Was einmal steht, bleibt
577
+ stehen; neue Punkte bekommen den neuen Namen. */
578
+ if (q.read && kenntQuelle[q.read] && !S.objects[e.ziel + '.' + kenntQuelle[q.read].n]) {
579
+ var vorhanden = kenntQuelle[q.read];
580
+ vorhanden.n = n;
581
+ vorhanden.on = true;
582
+ vorhanden.ausBestand = true;
583
+ vomAliasUebernehmen(vorhanden, o);
584
+ kennt[n] = vorhanden;
585
+ return;
586
+ }
587
+ var nm = txt(c.name);
588
+ e.states.push({
589
+ n: n, on: true, ausBestand: true, nurImAlias: true,
590
+ role: c.role || '', typ: c.type || '', unit: c.unit || '',
591
+ states: c.states || undefined,
592
+ wr: !!c.write,
593
+ srcR: q.read || '', srcW: q.einfach ? '' : (q.write || ''),
594
+ f: (typeof a.read === 'string') ? a.read : '',
595
+ fw: (typeof a.write === 'string') ? a.write : '',
596
+ caption: (nm && nm !== n) ? nm : '',
597
+ manuell: false
598
+ });
599
+ });
600
+
601
+ /* Einmal nachzeichnen.
602
+
603
+ Die Uebernahme aendert Rollen, auf denen die Erkennung dieses
604
+ Durchgangs schon aufgesetzt hatte. `wantAuto` und `want` zieht der
605
+ Aufrufer sofort nach, die Platzhinweise der einzelnen Zeilen und das
606
+ Infogeraet aber stammen aus dem Lauf davor - beim ersten Aufbau
607
+ stand deshalb an der ACTUAL-Zeile „kein Platz im airCondition-Muster",
608
+ obwohl das Feld darueber 9/29 meldete. Nach einem beliebigen
609
+ Neuzeichnen war es weg. Einmal von selbst neu zeichnen, statt den
610
+ Nutzer darauf zu stossen. */
611
+ if (e.bestandZog && !e.bestandNachgezeichnet) {
612
+ e.bestandNachgezeichnet = true;
613
+ setTimeout(function () { if (S.entwurf === e) { zeichneErgebnis(); } }, 0);
614
+ }
615
+ }
616
+
617
+ export function unterschiede(e) {
618
+ var raus = [];
619
+ var mu = e.want && musterVon(e.want);
620
+ function hatPlatz(rolle) {
621
+ if (!mu || !rolle) { return false; }
622
+ return mu.states.some(function (st) {
623
+ if (!st.role) { return false; }
624
+ try { return new RegExp(st.role.source || st.role).test(rolle); } catch { return false; }
625
+ });
626
+ }
627
+ /* Was die Vorlage von sich aus vorgeschlagen hat. Ein Punkt, der
628
+ dort schon auf „Vorgabe aus" steht, ist nicht abgewaehlt worden —
629
+ er war nie an. Vorher landeten bei einer Tasmota alle zwoelf
630
+ Diagnosepunkte in dieser Liste, obwohl niemand etwas geaendert
631
+ hatte. */
632
+ var vorlage = null;
633
+ S.VORLAGEN.forEach(function (v) { if (v.id === e.vorlage) { vorlage = v; } });
634
+ function warVorgabeAn(s) {
635
+ /* Ein Punkt, der nur zu sehen ist, weil „alle Datenpunkte des
636
+ Geraets" eingeschaltet wurde, war nie Teil des Vorschlags. Ihn
637
+ abgewaehlt zu lassen ist keine Abweichung, sondern der Zustand
638
+ von vorher - vorher stand er ueberhaupt nicht in der Liste.
639
+ Gemeldet wird er erst, wenn jemand ihn anhakt. */
640
+ if (s.ausAnsicht) { return false; }
641
+ if (!vorlage) { return true; } /* ohne Vorlage zaehlt jedes Abwaehlen */
642
+ var z = null;
643
+ (vorlage.zustaende || []).forEach(function (x) {
644
+ if (x.name === (s.ausVorlage || s.n)) { z = x; }
645
+ });
646
+ if (!z) { return true; }
647
+ return !z.vorgabeAus;
648
+ }
649
+ var zielBasis = e.ziel || e.kanal;
650
+
651
+ /* Womit wird verglichen? Gibt es den Alias schon, ist er die
652
+ Grundlinie — dann zaehlt, was sich gegenueber dem Gespeicherten
653
+ aendert. Gibt es ihn noch nicht, ist es der Vorschlag der
654
+ Vorlage. */
655
+ var aliasDa = !!S.objects[zielBasis];
656
+
657
+ e.states.forEach(function (s) {
658
+ if (s.manuell) { raus.push({ n: s.n || '(ohne Namen)', was: tr('diff.newByHand') }); return; }
659
+
660
+ var imAlias = !!S.objects[zielBasis + '.' + s.n];
661
+ var grundlinieAn = aliasDa ? imAlias : warVorgabeAn(s);
662
+
663
+ /* Beide Richtungen melden. Vorher wurde nur das Abwaehlen
664
+ betrachtet — wer einen Punkt zusaetzlich anhakte, sah davon
665
+ nichts, obwohl das genauso eine Abweichung ist. */
666
+ if (!s.on && grundlinieAn) {
667
+ raus.push({ n: s.n, was: imAlias ? tr('diff.wouldBeRemoved')
668
+ : (hatPlatz(s.role) ? tr('diff.deselectedWithPlace', e.want)
669
+ : tr('diff.deselectedNoPlace', e.want)) });
670
+ return;
671
+ }
672
+ if (s.on && !grundlinieAn) {
673
+ raus.push({ n: s.n, was: aliasDa ? tr('diff.wouldBeAdded')
674
+ : (hatPlatz(s.role) ? tr('diff.addedWithPlace', e.want)
675
+ : tr('diff.addedNoPlace', e.want)) });
676
+ return;
677
+ }
678
+ if (!s.on) { return; }
679
+
680
+ /* Der Vergleich der Einzelheiten lief bisher ueber s.urId, das
681
+ nur bei einem Entwurf aus einem fertigen Alias gesetzt ist. Bei
682
+ einem Entwurf aus der Quelle blieb er stumm, obwohl es den
683
+ Alias gibt — Rollenwechsel fielen dort durch. */
684
+ var o = S.objects[s.urId] || S.objects[zielBasis + '.' + s.n];
685
+ if (!o) { return; }
686
+ var c = o.common || {}, a = c.alias || {}, q = aliasQuellen(o);
687
+ if ((c.role || '') !== s.role) { raus.push({ n: s.n, was: tr('diff.roleChange', c.role || '—', s.role) }); }
688
+ if ((c.unit || '') !== s.unit) { raus.push({ n: s.n, was: tr('diff.unitChange', c.unit || '—', s.unit || '—') }); }
689
+ var altF = (typeof a.read === 'string') ? a.read : '';
690
+ if (altF !== s.f) { raus.push({ n: s.n, was: tr('diff.formulaChange', altF || '—', s.f || '—') }); }
691
+ var altR = q.read || '';
692
+ if (altR !== s.srcR) { raus.push({ n: s.n, was: tr('diff.sourceChange', altR || '—', s.srcR || '—') }); }
693
+ });
694
+ return raus;
695
+ }