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,339 @@
1
+ /* Die Zielleiste: wohin der Alias kommt - Ordnerfeld mit eigener
2
+ Vorschlagsliste, Namensfeld, der fertige Pfad und das Angebot, den
3
+ getippten Ordner als Raum zu uebernehmen. An einem fertigen Alias
4
+ bleibt davon nur die Raum/Funktion-Zeile.
5
+
6
+ Herausgeloest aus zeichneErgebnis - der Block hing nur an `e` und an
7
+ `host`, sonst an nichts. */
8
+
9
+ import { S } from './zustand.js';
10
+ import { el } from './basis.js';
11
+ import { tr } from './sprache.js';
12
+ import { enumListe } from './aufzaehlungen.js';
13
+ import { enumZeile, setzeZiel } from './zuordnung.js';
14
+ import { aliasFuer, ordnerUnterAlias, kennungtauglich } from './entwurf.js';
15
+ import { zeichneErgebnis, entwurfAngefasst, angebotFrisch } from './ergebnis.js';
16
+
17
+ export function baueZielleiste(host, e) {
18
+ var istQuelle = S.current.indexOf('alias.') !== 0;
19
+ if (!istQuelle && S.objects[S.current]) {
20
+ /* Am fertigen Alias gibt es kein Ziel zu waehlen — er liegt schon da.
21
+ Raum und Funktion lassen sich aber weiter aendern. */
22
+ enumZeile(host, e);
23
+ }
24
+ if (istQuelle) {
25
+ setzeZiel(e);
26
+
27
+ /* Raum und Funktion stehen ueber dem Ziel, nicht darunter.
28
+
29
+ Der Ordner entsteht aus dem Raum - eine Seite, auf der die Ursache
30
+ unter der Wirkung steht, liest sich falsch. Und inhaltlich ist es
31
+ die richtige Ordnung: erst wozu das Geraet gehoert, dann wo die
32
+ Kennung liegt. Die Zeile mit dem fertigen Pfad und „wird neu
33
+ angelegt“ bleibt unten - das Letzte vor dem Trockenlauf ist damit
34
+ das, was tatsaechlich entsteht.
35
+
36
+ `enumZeile` ruft `setzeEnums` selbst auf. */
37
+ enumZeile(host, e);
38
+
39
+ var zb = el('div', 'zielbar');
40
+ zb.appendChild(el('span', 'zl', tr('target.folder')));
41
+
42
+ /* Eingabefeld mit selbst gezeichneter Vorschlagsliste. Kein
43
+ <datalist>: dessen Auswahlfenster malt das Betriebssystem, und in
44
+ eingebetteten Ansichten landet es neben dem Fenster. */
45
+ var wrap = el('div', 'feldwrap');
46
+ var iO = el('input', 'tx ordnerfeld');
47
+ iO.type = 'text';
48
+ iO.setAttribute('autocomplete', 'off');
49
+ iO.placeholder = tr('target.folderPlaceholder');
50
+ iO.value = (e.zielOrdner === 'alias.0') ? '' : e.zielOrdner.slice('alias.0.'.length);
51
+ iO.title = tr('target.folderHint');
52
+ wrap.appendChild(iO);
53
+
54
+ /* Kreuz zum Leeren — sonst muss man den Text markieren und loeschen. */
55
+ var xBtn = el('button', 'feldx');
56
+ xBtn.type = 'button';
57
+ xBtn.title = tr('target.clearField');
58
+ xBtn.setAttribute('aria-label', tr('target.clearField'));
59
+ xBtn.hidden = !iO.value;
60
+ xBtn.addEventListener('mousedown', function (ev) {
61
+ ev.preventDefault();
62
+ iO.value = '';
63
+ S.fokusOrdnerfeld = true;
64
+ e.zielOrdner = 'alias.0';
65
+ e.ordnerVonHand = true;
66
+ e.zuletzt = 'ordner';
67
+ entwurfAngefasst();
68
+ zeichneErgebnis();
69
+ });
70
+ wrap.appendChild(xBtn);
71
+
72
+ var vlist = el('div', 'vorschlaege');
73
+ vlist.hidden = true;
74
+ wrap.appendChild(vlist);
75
+ zb.appendChild(wrap);
76
+
77
+ var saeubern = function (t) {
78
+ return String(t).trim()
79
+ .replace(/[^\w.\- äöüÄÖÜß]/g, '_')
80
+ .replace(/\s+/g, '_')
81
+ .replace(/\.{2,}/g, '.')
82
+ .replace(/^\.+|\.+$/g, '');
83
+ };
84
+ var uebernehmen = function () {
85
+ var t = saeubern(iO.value);
86
+ var neuerOrdner = t ? ('alias.0.' + t) : 'alias.0';
87
+ if (neuerOrdner === e.zielOrdner) { return; }
88
+ e.zielOrdner = neuerOrdner;
89
+ /* Ab jetzt gehoert der Ordner dem Nutzer - der Raum schreibt ihn
90
+ nicht mehr um. Angeboten wird die Uebernahme trotzdem, aber in
91
+ der anderen Richtung: wer den Ordner aendert, will nicht, dass
92
+ er gleich wieder zurueckspringt. */
93
+ e.ordnerVonHand = true;
94
+ e.zuletzt = 'ordner';
95
+ entwurfAngefasst();
96
+ /* Kurz warten, statt sofort neu zu zeichnen. Wer im Feld tippt und
97
+ dann auf „anlegen" drueckt, loest mit dem Mausdruck erst das
98
+ Verlassen des Feldes aus — zeichnet man da sofort neu, ist der
99
+ Knopf weg, bevor der Klick bei ihm ankommt, und der erste Klick
100
+ tut nichts. Gemessen an Kuehlschrank und Warmwasser. */
101
+ setTimeout(zeichneErgebnis, 250);
102
+ };
103
+
104
+ var markiert = -1;
105
+ var eintraege = [];
106
+ /* Ob die Liste gerade eine Zurueck-Zeile fuehrt. Die steht im DOM
107
+ vor den Eintraegen, zaehlt aber nicht zu ihnen - ohne diesen
108
+ Versatz zeigten die Pfeiltasten auf die falsche Zeile. */
109
+ var mitZurueck = false;
110
+
111
+ var schliessen = function () { vlist.hidden = true; markiert = -1; };
112
+
113
+ /* Nur die naechste Ebene anbieten: leer -> die obersten Ordner,
114
+ nach „SmartHome." dessen Unterordner. Eine flache Liste aller
115
+ Pfade wird bei vielen Ordnern unuebersichtlich. */
116
+ function ordnerKinder(basis) {
117
+ var alle = ordnerUnterAlias()
118
+ .map(function (o) { return o.slice('alias.0.'.length); })
119
+ .filter(Boolean);
120
+ var tiefe = basis ? basis.split('.').length : 0;
121
+ var raus = {};
122
+ alle.forEach(function (o) {
123
+ if (basis && o.indexOf(basis + '.') !== 0) { return; }
124
+ var teile = o.split('.');
125
+ if (teile.length <= tiefe) { return; }
126
+ var pfad = teile.slice(0, tiefe + 1).join('.');
127
+ if (!raus[pfad]) { raus[pfad] = { pfad: pfad, name: teile[tiefe], kinder: false }; }
128
+ if (teile.length > tiefe + 1) { raus[pfad].kinder = true; }
129
+ });
130
+ return Object.keys(raus).sort().map(function (k) { return raus[k]; });
131
+ }
132
+
133
+ var malen = function (alleZeigen) {
134
+ var t = iO.value;
135
+ var i = t.lastIndexOf('.');
136
+ var basis = (i > -1) ? t.slice(0, i) : '';
137
+ var rest = alleZeigen ? '' : ((i > -1) ? t.slice(i + 1) : t).toLowerCase();
138
+
139
+ eintraege = ordnerKinder(basis).filter(function (k) {
140
+ return !rest || k.name.toLowerCase().indexOf(rest) > -1;
141
+ });
142
+
143
+ vlist.textContent = '';
144
+ if (!eintraege.length) { schliessen(); return; }
145
+
146
+ mitZurueck = !!basis;
147
+ if (basis) {
148
+ var zurueck = el('div', 'vz zurueck');
149
+ zurueck.appendChild(el('span', null, '‹ ' + basis));
150
+ zurueck.addEventListener('mousedown', function (ev) {
151
+ ev.preventDefault();
152
+ var j = basis.lastIndexOf('.');
153
+ iO.value = (j > -1) ? basis.slice(0, j) + '.' : '';
154
+ markiert = -1;
155
+ malen(true);
156
+ });
157
+ vlist.appendChild(zurueck);
158
+ }
159
+
160
+ eintraege.forEach(function (k, n) {
161
+ var z = el('div', 'vz' + (n === markiert ? ' an' : ''));
162
+ z.appendChild(el('span', null, k.name));
163
+
164
+ /* Der Klick waehlt, der Pfeil steigt hinein.
165
+
166
+ Vorher tat ein Klick auf einen Ordner mit Unterordnern nur
167
+ das Zweite: aus „Bastelzimmer" wurde „Bastelzimmer." und die
168
+ Liste sprang eine Ebene tiefer. Bastelzimmer *selbst* liess
169
+ sich damit gar nicht anklicken - man musste den Punkt von
170
+ Hand wegloeschen. Und wer die Liste dann verliess, behielt
171
+ ihn im Feld stehen.
172
+
173
+ Jetzt trennen sich die beiden Dinge: die Zeile waehlt den
174
+ Ordner, das Winkelzeichen daneben oeffnet ihn. */
175
+ if (k.kinder) {
176
+ var tiefer = el('span', 'tiefer', '›');
177
+ tiefer.title = tr('target.openFolder', k.name);
178
+ tiefer.addEventListener('mousedown', function (ev) {
179
+ ev.preventDefault();
180
+ ev.stopPropagation();
181
+ iO.value = k.pfad + '.';
182
+ markiert = -1;
183
+ malen(true);
184
+ iO.focus();
185
+ });
186
+ z.appendChild(tiefer);
187
+ }
188
+
189
+ /* mousedown statt click: sonst greift der Fokusverlust zuerst
190
+ und die Liste ist weg, bevor der Klick ankommt. */
191
+ z.addEventListener('mousedown', function (ev) {
192
+ ev.preventDefault();
193
+ iO.value = k.pfad;
194
+ schliessen();
195
+ uebernehmen();
196
+ });
197
+ vlist.appendChild(z);
198
+ });
199
+ vlist.hidden = false;
200
+ };
201
+
202
+ /* Beim Hineinklicken alles zeigen — sonst muesste man erst loeschen,
203
+ um in einen anderen Ordner zu wechseln. */
204
+ iO.addEventListener('focus', function () { malen(true); });
205
+ iO.addEventListener('input', function () {
206
+ markiert = -1;
207
+ xBtn.hidden = !iO.value;
208
+ malen();
209
+ });
210
+ iO.addEventListener('blur', function () {
211
+ /* Wer beim Hineinsteigen abbricht, laesst einen Punkt am Ende
212
+ stehen. `saeubern` wirft ihn beim Uebernehmen ohnehin weg - im
213
+ Feld soll er dann auch nicht mehr stehen. */
214
+ if (/\.$/.test(iO.value)) { iO.value = iO.value.replace(/\.+$/, ''); }
215
+ /* Und was dann noch dasteht, gilt auch. `change` feuert nur nach
216
+ einer Tastatureingabe - das Hineinsteigen setzt `iO.value` von
217
+ Hand und loest keins aus. Wer also in „Bastelzimmer" hineinstieg
218
+ und das Feld verliess, sah dort „Bastelzimmer" stehen, waehrend
219
+ das Ziel darunter weiter auf „alias.0.Badezimmer.…" zeigte -
220
+ geschrieben worden waere der alte Ordner (26.08.2026).
221
+ `uebernehmen` steigt selbst aus, wenn sich nichts geaendert hat. */
222
+ uebernehmen();
223
+ setTimeout(schliessen, 120);
224
+ });
225
+ iO.addEventListener('change', uebernehmen);
226
+ iO.addEventListener('keydown', function (ev) {
227
+ if (ev.key === 'Escape') { schliessen(); return; }
228
+ if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') {
229
+ if (vlist.hidden) { malen(true); return; }
230
+ ev.preventDefault();
231
+ markiert += (ev.key === 'ArrowDown' ? 1 : -1);
232
+ if (markiert < 0) { markiert = eintraege.length - 1; }
233
+ if (markiert >= eintraege.length) { markiert = 0; }
234
+ malen();
235
+ var akt = vlist.children[markiert + (mitZurueck ? 1 : 0)];
236
+ if (akt && akt.scrollIntoView) { akt.scrollIntoView({ block: 'nearest' }); }
237
+ return;
238
+ }
239
+ /* Pfeil nach rechts steigt in den markierten Ordner - das
240
+ Gegenstueck zum Winkelzeichen fuer die Maus. */
241
+ if (ev.key === 'ArrowRight' && !vlist.hidden && markiert >= 0
242
+ && eintraege[markiert] && eintraege[markiert].kinder) {
243
+ ev.preventDefault();
244
+ iO.value = eintraege[markiert].pfad + '.';
245
+ markiert = -1;
246
+ malen(true);
247
+ return;
248
+ }
249
+ if (ev.key === 'Enter') {
250
+ if (!vlist.hidden && markiert >= 0) {
251
+ ev.preventDefault();
252
+ /* `eintraege` fuehrt Objekte, keine Zeichenketten. Hier stand
253
+ `iO.value = eintraege[markiert]` - im Feld landete
254
+ „[object Object]". */
255
+ iO.value = eintraege[markiert].pfad;
256
+ }
257
+ schliessen();
258
+ uebernehmen();
259
+ }
260
+ });
261
+
262
+ zb.appendChild(el('span', 'zl', tr('target.name')));
263
+ var iN2 = el('input', 'tx');
264
+ iN2.type = 'text';
265
+ iN2.value = e.zielName;
266
+ iN2.addEventListener('change', function () {
267
+ /* Dieselbe Regel wie im Vorschlag und im Verlege-Dialog. Die eigene
268
+ Fassung hier liess Punkte und doppelte Unterstriche stehen - ein
269
+ von Hand getippter Name wurde damit anders behandelt als ein
270
+ vorgeschlagener (26.08.2026). */
271
+ e.zielName = kennungtauglich(iN2.value) || S.current.split('.').pop();
272
+ entwurfAngefasst();
273
+ zeichneErgebnis();
274
+ });
275
+ zb.appendChild(iN2);
276
+
277
+ var ergebnis = el('span', 'zid', e.zielOrdner + '.' + e.zielName);
278
+ /* Beide Faelle, an einer Stelle. Vorher stand „wird neu angelegt"
279
+ oben im Kopf und „gibt es schon" hier — zwei Formulierungen fuer
280
+ dieselbe Auskunft, an zwei Orten. */
281
+ var gibtsZiel = !!S.objects[e.ziel];
282
+ var ch2 = el('span', 'chip ' + (gibtsZiel ? 'warn' : 'ok'),
283
+ gibtsZiel ? tr('target.exists') : tr('result.newlyCreated'));
284
+ ch2.style.marginLeft = '7px';
285
+ ergebnis.appendChild(ch2);
286
+ /* Das Angebot steht dort, wo gerade getippt wurde.
287
+
288
+ Zuerst hing es unter der Zielzeile und bot immer dieselbe Richtung
289
+ an - wer den Ordner geaendert hatte, bekam angeboten, ihn wieder
290
+ zurueckzudrehen. Beides war falsch: die Richtung muss der letzten
291
+ Aenderung folgen, und der Blick ist beim angefassten Feld, nicht
292
+ beim anderen. Der Text nennt deshalb die Wirkung woanders. */
293
+ var angebot = null;
294
+ if (e.zuletzt === 'ordner' && !aliasFuer(e.kanal)) {
295
+ var otx = String(e.zielOrdner || '').slice('alias.0.'.length);
296
+ if (otx && otx.indexOf('.') === -1) {
297
+ var vorhandenerRaum = enumListe('rooms', true).filter(function (r) {
298
+ return r.name.toLowerCase() === otx.toLowerCase() ||
299
+ r.kurz.toLowerCase() === otx.toLowerCase();
300
+ })[0];
301
+ var raumId = vorhandenerRaum ? vorhandenerRaum.id : ('enum.rooms.' + otx);
302
+ if (raumId !== e.raum) {
303
+ var az = el('div', 'uebernahme' +
304
+ (angebotFrisch('ordner:' + raumId) ? ' frisch' : ''));
305
+ az.appendChild(el('span', 'zq', vorhandenerRaum
306
+ ? tr('target.alsoRoom', otx)
307
+ : tr('target.alsoRoomNew', otx)));
308
+ var ab = el('button', 'btn mini wichtig', tr('target.yes'));
309
+ ab.addEventListener('click', function () {
310
+ e.raum = raumId;
311
+ e.raumHer = 'hand';
312
+ entwurfAngefasst();
313
+ zeichneErgebnis();
314
+ });
315
+ az.appendChild(ab);
316
+ angebot = az;
317
+ }
318
+ }
319
+ }
320
+ if (S.fokusOrdnerfeld) {
321
+ S.fokusOrdnerfeld = false;
322
+ setTimeout(function () { iO.focus(); }, 0);
323
+ }
324
+
325
+ /* Das Angebot ganz ans Ende der Leiste.
326
+
327
+ Zuerst hing es direkt hinter dem Ordnerfeld - und weil es eine
328
+ eigene Zeile beansprucht, rutschte alles Nachfolgende mit: das
329
+ Namensfeld stand ploetzlich darunter statt daneben. Eine Zeile,
330
+ die umbricht, gehoert hinter das, was in der Zeile bleiben soll. */
331
+ if (angebot) { zb.appendChild(angebot); }
332
+
333
+ zb.appendChild(ergebnis);
334
+
335
+ host.appendChild(zb);
336
+ }
337
+
338
+ return istQuelle;
339
+ }