iobroker.alias-workbench 0.11.0 → 0.12.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/admin/js/baum.js CHANGED
@@ -8,9 +8,10 @@ import { S } from './zustand.js';
8
8
  import { $, el } from './basis.js';
9
9
  import { tr, txt, expertenVorgabe } from './sprache.js';
10
10
  import './enums.js';
11
- import { mitNachfrage, aboLoesen } from './entwurf.js';
11
+ import { mitNachfrage, aboLoesen, zweigUmschalten, zweigPfade, waehle } from './entwurf.js';
12
12
  import { zeichneErgebnis } from './ergebnis.js';
13
13
  import { zeichneVorlagenListe } from './vorlagenblatt.js';
14
+ import { zeigeMenue } from './attrappe.js';
14
15
 
15
16
  var KLAPP_SCHLUESSEL = 'alias-workbench.baum';
16
17
 
@@ -205,6 +206,17 @@ export function zeichneBaum() {
205
206
  }
206
207
  }
207
208
 
209
+ /* Warum ein Strg-Klick nicht angekommen ist.
210
+
211
+ „schon drin" und „der letzte" brauchen keine Worte - da passiert
212
+ sichtbar nichts, und das versteht sich. „Aus einer anderen Instanz"
213
+ braucht sie: sonst klickt jemand dreimal und haelt es fuer kaputt. */
214
+ function meldeZweig(grund) {
215
+ if (grund !== 'fremd') { return; }
216
+ S.zweigMeldung = tr('tree.branchForeign');
217
+ zeichneErgebnis();
218
+ }
219
+
208
220
  /* Waehlbar ist alles, unter dem irgendwo Zustaende liegen — nicht nur
209
221
  direkt darunter. MQTT legt fuer Zwischenebenen gar keine Objekte an:
210
222
  unter …SmartHome.Karbonator existieren nur tele.LWT, tele.SENSOR und
@@ -391,8 +403,14 @@ function renderKinder(knoten, filter, tiefe) {
391
403
 
392
404
  var li = el('li');
393
405
  var waehlbar = istWaehlbar(k);
406
+ /* Bei einer Mehrfachauswahl ist JEDER gewaehlte Zweig markiert, und
407
+ der gemeinsame Elternknoten schwaecher - sonst sieht man nicht,
408
+ worauf der Entwurf steht. Denselben Mangel hatte das
409
+ Attrappen-Menue (Ricardo, 12.10.2026). */
410
+ var gewaehlt = zweigPfade().indexOf(k.id) !== -1;
411
+ var istEltern = !!(S.zweige && S.zweige.length) && k.id === S.current;
394
412
  var d = el('div', 'node' + (waehlbar ? ' pick' : ' grp') + (leer ? ' leer' : '') +
395
- (k.id === S.current ? ' sel' : ''));
413
+ (gewaehlt ? ' sel' : '') + (istEltern ? ' selEltern' : ''));
396
414
 
397
415
  var hatKinder = kinderPassen;
398
416
  var auf = aufJetzt;
@@ -439,6 +457,11 @@ function renderKinder(knoten, filter, tiefe) {
439
457
  d.appendChild(el('span', 'cnt', k._zust + ''));
440
458
  }
441
459
 
460
+ /* Rechtsklick auf JEDE Zeile, nicht nur auf waehlbare: ausgeben
461
+ laesst sich auch ein Geraet oder ein Ordner, und gerade der ist
462
+ beim Nachbauen der interessante Knoten. */
463
+ d.addEventListener('contextmenu', function (e) { zeigeMenue(e, k.id, d); });
464
+
442
465
  if (waehlbar) {
443
466
  d.tabIndex = 0;
444
467
  d.setAttribute('role', 'button');
@@ -452,10 +475,34 @@ function renderKinder(knoten, filter, tiefe) {
452
475
  dazu, und die stehen nicht in den Zeilen. Geschrieben wird
453
476
  dadurch nichts — es geht nur darum, dass niemand etwas verliert,
454
477
  ohne gefragt zu werden. */
455
- var go = function () { mitNachfrage(k.id); };
456
- d.addEventListener('click', go);
478
+ /* Ein gewoehnlicher Klick waehlt EINEN Knoten - auch dann, wenn es
479
+ derselbe ist, auf dem gerade eine Mehrfachauswahl steht. Wer den
480
+ Elternknoten anklickt, will ihn ganz. */
481
+ var go = function () {
482
+ mitNachfrage(k.id, function () { S.zweige = null; waehle(k.id); });
483
+ };
484
+ /* Strg-Klick nimmt einen Zweig dazu oder heraus, statt die Auswahl
485
+ zu ersetzen. Strg und nicht Umschalt: Umschalt heisst ueberall
486
+ „Bereich von…bis", gewollt sind einzelne Zweige. Auf dem Mac
487
+ dieselbe Taste wie sonst dort (Cmd).
488
+
489
+ Wer keine Tastatur hat oder nicht weiss, dass es das gibt,
490
+ findet dasselbe im Rechtsklickmenue. */
491
+ var dazu = function () {
492
+ mitNachfrage(k.id, function () {
493
+ var grund = zweigUmschalten(k.id);
494
+ if (grund) { meldeZweig(grund); }
495
+ });
496
+ };
497
+ d.addEventListener('click', function (e) {
498
+ if (e.ctrlKey || e.metaKey) { e.preventDefault(); dazu(); return; }
499
+ go();
500
+ });
457
501
  d.addEventListener('keydown', function (e) {
458
- if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); go(); }
502
+ if (e.key === 'Enter' || e.key === ' ') {
503
+ e.preventDefault();
504
+ if (e.ctrlKey || e.metaKey) { dazu(); } else { go(); }
505
+ }
459
506
  });
460
507
  }
461
508
  li.appendChild(d);
@@ -8,7 +8,7 @@ import { $ } from './basis.js';
8
8
  import { txt, tr } from './sprache.js';
9
9
  import './enums.js';
10
10
  import { zusatzName, setzeModus, zeichneBaum, merkeKlappstand } from './baum.js';
11
- import { kindZustaende, geraetePunkte, aliasQuellen, holeWerte,
11
+ import { kindZustaende, geraetePunkte, aliasQuellen, holeWerte, istZerlegt,
12
12
  mitVorspann, schreibQuelle, zeileAusAlias, statesText, statesGleich, istTaster as istTasterObjekt
13
13
  } from './werte.js';
14
14
  import { erkenneEntwurf } from './erkennung.js';
@@ -77,10 +77,56 @@ export function baueEntwurf(kanal) {
77
77
  relativer Name kommt von dort. Sonst hiesse die Zeile
78
78
  `tele_Weihnachten_aussen_STATE` statt `tele_STATE`, und derselbe
79
79
  Tasmota ergaebe je nach FullTopic einen anderen Alias. */
80
- geraetePunkte(kanal).forEach(function (p) {
80
+ var punkte = geraetePunkte(kanal);
81
+ /* Mehrere Zweige als ein Geraet: nur die gewaehlten. Gefiltert wird
82
+ NACH `geraetePunkte`, nicht davor - dessen Ergebnis liegt je Knoten
83
+ im Zwischenspeicher, und die Auswahl ist fluechtig. */
84
+ var mehrzweig = false;
85
+ if (S.zweige && S.zweige.length && kanal === S.current) {
86
+ mehrzweig = true;
87
+ punkte = punkte.filter(function (p) {
88
+ return S.zweige.some(function (z) { return p.rel === z || p.rel.indexOf(z + '.') === 0; });
89
+ });
90
+ }
91
+ /* Kurze Namen, wo sie eindeutig sind.
92
+
93
+ Aus `OnOffPlugInUnit-1.SET` soll `SET` werden - sonst hiesse der
94
+ Alias-Punkt `OnOffPlugInUnit-1_SET`, und die Zeile traegt den Namen
95
+ eines Endpoints, den ausser dem Adapter niemand kennt. Gekuerzt wird
96
+ nur, wenn das letzte Glied unter den GEWAEHLTEN Zweigen genau einmal
97
+ vorkommt; sonst bliebe nicht unterscheidbar, welcher Zweig gemeint
98
+ ist.
99
+
100
+ Nicht bei einem zerlegten Geraet (Tasmota): dort ist der Zweig
101
+ bedeutungstragend. `cmnd.POWER` und `stat.POWER` kuerzten sich
102
+ ohnehin nicht, weil `POWER` zweimal vorkommt - aber `tele.SENSOR`
103
+ waere eindeutig und wuerde zu `SENSOR`. Das haette jeden
104
+ bestehenden MQTT-Alias gebrochen.
105
+
106
+ Dasselbe gilt, wenn jemand die Sparten eines Tasmota ausdruecklich
107
+ zusammenklickt (`…Steckdose.stat` + `…Steckdose.tele`). Dort heisst
108
+ der Zweig `cmnd`, `stat` oder `tele`, und die Vorlagen adressieren
109
+ genau so (`stat.POWER`). Gekuerzt faende keine von ihnen mehr ihr
110
+ Ziel. */
111
+ var kurz = {};
112
+ var sparte = (S.zweige || []).some(function (zg) {
113
+ return SPARTEN.indexOf(zg.split('.').pop()) !== -1;
114
+ });
115
+ if (mehrzweig && !sparte && !istZerlegt(kanal)) {
116
+ var zaehler = {};
117
+ punkte.forEach(function (p) {
118
+ var k = p.rel.split('.').pop();
119
+ zaehler[k] = (zaehler[k] || 0) + 1;
120
+ });
121
+ punkte.forEach(function (p) {
122
+ var k = p.rel.split('.').pop();
123
+ if (zaehler[k] === 1 && k !== p.rel) { kurz[p.rel] = k; }
124
+ });
125
+ }
126
+ punkte.forEach(function (p) {
81
127
  var id = p.id;
82
128
  var o = S.objects[id], c = o.common || {}, a = c.alias || {}, q = aliasQuellen(o);
83
- var kurzName = p.rel;
129
+ var kurzName = kurz[p.rel] || p.rel;
84
130
  /* Ein Knopfdruck hat keinen Zustand — sonst hat auch ein
85
131
  nur-schreibender Punkt einen.
86
132
 
@@ -980,7 +1026,85 @@ export function aboErgaenzen(quellen) {
980
1026
  zeigeAbozahl();
981
1027
  }
982
1028
 
983
- export function waehle(id, fertig) {
1029
+ /* Die gewaehlten Zweige als volle Kennungen - eine Liste, auch wenn es
1030
+ nur einer ist. */
1031
+ export function zweigPfade() {
1032
+ if (!S.current) { return []; }
1033
+ if (!S.zweige || !S.zweige.length) { return [S.current]; }
1034
+ return S.zweige.map(function (z) { return S.current + '.' + z; });
1035
+ }
1036
+
1037
+ /* Der laengste gemeinsame Elternknoten mehrerer Kennungen - auf
1038
+ Punktgrenzen, nicht auf Zeichen. */
1039
+ function gemeinsamerEltern(ids) {
1040
+ var teile = ids[0].split('.');
1041
+ for (var i = 1; i < ids.length; i++) {
1042
+ var t2 = ids[i].split('.');
1043
+ var n = 0;
1044
+ while (n < teile.length && n < t2.length && teile[n] === t2[n]) { n++; }
1045
+ teile = teile.slice(0, n);
1046
+ }
1047
+ return teile.join('.');
1048
+ }
1049
+
1050
+ /* Einen Zweig dazunehmen oder herausnehmen.
1051
+
1052
+ Gedacht fuer Geraete, die ueber Geschwisterzweige verteilt liegen
1053
+ (Matter, siehe `S.zweige`). Der gemeinsame Elternknoten wird zur
1054
+ Auswahl, die angeklickten Zweige zu `S.zweige`.
1055
+
1056
+ Gibt einen Grund zurueck, wenn es nicht geht - dann hat sich nichts
1057
+ geaendert. */
1058
+ export function zweigUmschalten(id) {
1059
+ if (!S.current) { return 'leer'; }
1060
+ var pfade = zweigPfade();
1061
+ var drin = pfade.indexOf(id);
1062
+ if (drin !== -1) {
1063
+ /* Der letzte laesst sich nicht abwaehlen - sonst stuende rechts
1064
+ nichts mehr, und der Klick saehe aus wie ein Fehler. */
1065
+ if (pfade.length < 2) { return 'letzter'; }
1066
+ pfade.splice(drin, 1);
1067
+ } else {
1068
+ /* Ein Zweig, der einen anderen enthaelt (oder in ihm liegt), ist
1069
+ kein zweiter Zweig - sonst zaehlten dieselben Punkte doppelt. */
1070
+ var verschachtelt = pfade.some(function (p) {
1071
+ return p === id || id.indexOf(p + '.') === 0 || p.indexOf(id + '.') === 0;
1072
+ });
1073
+ if (verschachtelt) { return 'verschachtelt'; }
1074
+ pfade.push(id);
1075
+ }
1076
+ var eltern = gemeinsamerEltern(pfade);
1077
+ /* Nie ueber die Instanz hinaus. Zwei Zweige aus verschiedenen
1078
+ Adaptern haetten gar keinen gemeinsamen Elternknoten, und zwei
1079
+ Instanzen desselben Adapters nur den Adapternamen - in beiden
1080
+ Faellen stuende die Werkbank auf einem Knoten, der kein Ort ist.
1081
+ Ein Geraet liegt in einer Instanz; das ist keine kuenstliche
1082
+ Schranke, sondern die Lage. */
1083
+ if (!eltern || eltern.split('.').length < 2) { return 'fremd'; }
1084
+ var zweige = pfade.map(function (p) { return p.slice(eltern.length + 1); })
1085
+ .filter(function (z) { return z; });
1086
+ /* Steht ein gewaehlter Pfad selbst auf dem Elternknoten, ist „alles"
1087
+ gemeint - dann ist es wieder eine gewoehnliche Auswahl. */
1088
+ S.zweige = (zweige.length === pfade.length && zweige.length > 1) ? zweige.sort() : null;
1089
+ waehle(eltern, null, true);
1090
+ return '';
1091
+ }
1092
+
1093
+ export function waehle(id, fertig, zweigeBehalten) {
1094
+ /* Ein ANDERER Knoten hebt die Mehrfachauswahl auf, derselbe nicht.
1095
+
1096
+ `waehle(S.current)` heisst an drei Stellen „bau den Entwurf neu" -
1097
+ nach dem Nachladen der Objekte (`objekte.js`), nach einer Aenderung
1098
+ am Bestand (`ergebnis.js`) und nach dem Schreiben (`schreiben.js`).
1099
+ Eine Regel, die hier bei JEDEM Aufruf aufraeumt, loescht die
1100
+ Auswahl beim naechsten Objektereignis aus dem Admin - gemessen
1101
+ 13.10.2026 in AA3, wo ein neu angelegter Datenpunkt sie mitnahm.
1102
+
1103
+ Dass ein gewoehnlicher KLICK auf den Elternknoten „alles" bedeutet,
1104
+ entscheidet der Klick selbst (`baum.js`), nicht diese Funktion.
1105
+ `zweigeBehalten` setzt nur `zweigUmschalten`, das den Knoten
1106
+ absichtlich wechselt. */
1107
+ if (!zweigeBehalten && id !== S.current) { S.zweige = null; }
984
1108
  /* Wer links etwas anderes anklickt, will es rechts sehen - auch wenn
985
1109
  der Fokus noch in einem Feld der rechten Seite steht. `tipptGerade`
986
1110
  haelt das Neuzeichnen zurueck, damit ein eintreffender Messwert
@@ -1042,8 +1166,11 @@ export function waehle(id, fertig) {
1042
1166
  Zeilen mit neuen Quellen dazukommen. Eine Liste, damit beim
1043
1167
  naechsten Wechsel `aboLoesen` alles abmeldet. */
1044
1168
  aboLoesen();
1045
- S.abo = [id + '.*'];
1046
- socket.emit('subscribe', id + '.*');
1169
+ /* Bei mehreren Zweigen nur diese, nicht den ganzen Elternknoten. Der
1170
+ traegt oft mehr, als zum Geraet gehoert - bei Matter den Kanal
1171
+ `info` mit der Verbindungsmeldung, anderswo gleich mehrere Geraete. */
1172
+ S.abo = zweigPfade().map(function (p) { return p + '.*'; });
1173
+ S.abo.forEach(function (m) { socket.emit('subscribe', m); });
1047
1174
  if (id.indexOf('alias.') === 0) {
1048
1175
  var ausAlias = [];
1049
1176
  kindZustaende(id).forEach(function (kid) {
@@ -37,7 +37,9 @@ import {
37
37
  bestandVorrang,
38
38
  kanalGruppen,
39
39
  aufteilBehaelter,
40
- rohEntwurf
40
+ rohEntwurf,
41
+ zweigPfade,
42
+ zweigUmschalten
41
43
  } from './entwurf.js';
42
44
  import { setzeEnums, setzeZiel, unterschiede } from './zuordnung.js';
43
45
  import './detail.js';
@@ -55,7 +57,7 @@ import {
55
57
  } from './schreiben.js';
56
58
 
57
59
  import { mqttKarte, mqttSchreiben } from './mqtt.js';
58
- import { kindZustaende, direkteZustaende } from './werte.js';
60
+ import { kindZustaende, direkteZustaende, mitVorspann } from './werte.js';
59
61
  import './aufzaehlungen.js';
60
62
  import { erkenneEntwurf } from './erkennung.js';
61
63
  import { zusatzName } from './baum.js';
@@ -308,6 +310,57 @@ var warumAuf = false;
308
310
  /* Der Kopf: was links angeklickt wurde - Name, Kennung, Bestand
309
311
  und der Sprungknopf zum Gegenstueck. Nicht, was daraus werden
310
312
  soll: das sagt die Zielzeile darunter. */
313
+ /* Geschwisterzweige mit eigenen Datenpunkten, die noch nicht zur
314
+ Auswahl gehoeren.
315
+
316
+ Gemeint sind Geraete, die ein Adapter auf mehrere Zweige verteilt -
317
+ Matter legt den Schalter in `OnOffPlugInUnit-1` und die Messwerte in
318
+ `ElectricalSensor-2`. Angeboten wird das nur, nie ausgefuehrt.
319
+
320
+ Nicht angeboten wird es an einem Alias (dort gibt es nichts
321
+ zusammenzufassen) und nicht unter einer Instanzwurzel, wo die
322
+ „Nachbarn" schlicht alle anderen Geraete waeren. */
323
+ function nachbarZweige() {
324
+ if (!S.current || S.current.indexOf('alias.') === 0) { return []; }
325
+ var pfade = zweigPfade();
326
+ var eltern = (S.zweige && S.zweige.length)
327
+ ? S.current
328
+ : S.current.slice(0, S.current.lastIndexOf('.'));
329
+ if (!eltern || eltern.split('.').length < 3) { return []; }
330
+ var vor = eltern + '.';
331
+ var kinder = {};
332
+ mitVorspann(vor).forEach(function (id) {
333
+ var rest = id.slice(vor.length);
334
+ var erst = rest.split('.')[0];
335
+ if (!erst) { return; }
336
+ if (S.objects[id] && S.objects[id].type === 'state') { kinder[vor + erst] = 1; }
337
+ });
338
+ return Object.keys(kinder).filter(function (k) {
339
+ return pfade.indexOf(k) === -1 &&
340
+ !pfade.some(function (p) { return k.indexOf(p + '.') === 0 || p.indexOf(k + '.') === 0; });
341
+ }).sort();
342
+ }
343
+
344
+ /* Warum das ein Geraet sein koennte - wo es belegbar ist.
345
+
346
+ Matter schreibt an jeden Endpoint dieselbe `nodeId` und dieselbe
347
+ `statusStates.onlineId` (gemessen an matter.0.controller.2,
348
+ 13.10.2026). Wo das zutrifft, ist es keine Vermutung. Wo nicht,
349
+ bleibt es ein nuechternes Angebot. */
350
+ function nachbarnGrund(nachbarn) {
351
+ var eigen = S.objects[zweigPfade()[0]] || {};
352
+ var kennung = function (o) {
353
+ var st = ((o.common || {}).statusStates || {}).onlineId || '';
354
+ var nid = (o.native || {}).nodeId;
355
+ return st + '|' + (nid === undefined ? '' : nid);
356
+ };
357
+ var meins = kennung(eigen);
358
+ var gleich = meins !== '|' && nachbarn.every(function (n) {
359
+ return kennung(S.objects[n] || {}) === meins;
360
+ });
361
+ return gleich ? tr('result.addNeighboursSame') : tr('result.addNeighboursHint');
362
+ }
363
+
311
364
  function baueKopf() {
312
365
  var obj = S.objects[S.current] || {};
313
366
  var alleZust = kindZustaende(S.current);
@@ -322,6 +375,18 @@ var warumAuf = false;
322
375
  oben steht, nicht unter den Bestandszaehler. */
323
376
  var namensZeile = el('div', 'namzeile');
324
377
  namensZeile.appendChild(el('div', 'id', kopfName || S.current.split('.').pop()));
378
+ /* Liegt das Geraet auf mehrere Geschwisterzweige verteilt, steht der
379
+ Weg dorthin hier - sichtbar. Strg-Klick im Baum tut dasselbe, aber
380
+ den findet nur, wer schon weiss, dass es ihn gibt. */
381
+ var nachbarn = nachbarZweige();
382
+ if (nachbarn.length) {
383
+ var nk = el('button', 'btn klein', tr('result.addNeighbours', nachbarn.length));
384
+ nk.title = nachbarnGrund(nachbarn);
385
+ nk.addEventListener('click', function () {
386
+ nachbarn.forEach(function (n) { zweigUmschalten(n); });
387
+ });
388
+ namensZeile.appendChild(nk);
389
+ }
325
390
  links.appendChild(namensZeile);
326
391
  links.appendChild(el('div', 'pfad', S.current));
327
392
 
@@ -1050,6 +1115,16 @@ var warumAuf = false;
1050
1115
  var sg0 = $('#sperrgrund');
1051
1116
  if (sg0) { sg0.hidden = true; sg0.textContent = ''; }
1052
1117
  if (S.baumModus === 'vorlagen') { return zeichneVorlagenBlatt(host); }
1118
+ /* Eine Meldung aus dem Baum - einmal zeigen, dann weg. Sie steht
1119
+ ganz oben, weil sie auf einen Klick antwortet, der sonst wirkungslos
1120
+ aussieht. */
1121
+ if (S.zweigMeldung) {
1122
+ var zm = el('div', 'aside w');
1123
+ zm.style.borderLeftColor = 'var(--warn)';
1124
+ zm.textContent = S.zweigMeldung;
1125
+ host.appendChild(zm);
1126
+ S.zweigMeldung = '';
1127
+ }
1053
1128
  var bck = $('#btn-checks');
1054
1129
  if (bck) { bck.disabled = false; }
1055
1130
  if (!S.current || !S.entwurf) { return zeichneLeer(host); }