iobroker.alias-workbench 0.9.0 → 0.9.8
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/README.md +44 -5
- package/admin/geraetewissen/tasmota-befehle.json +3 -1
- package/admin/i18n/de.json +17 -6
- package/admin/i18n/en.json +17 -6
- package/admin/js/aufzaehlungen.js +11 -1
- package/admin/js/baum.js +90 -32
- package/admin/js/detail.js +18 -3
- package/admin/js/entwurf.js +176 -42
- package/admin/js/enums.js +50 -7
- package/admin/js/ergebnis.js +47 -10
- package/admin/js/katalog.js +12 -3
- package/admin/js/mqtt.js +76 -13
- package/admin/js/musterwahl.js +10 -0
- package/admin/js/objekte.js +29 -3
- package/admin/js/pruefungen.js +10 -2
- package/admin/js/quellentausch.js +31 -2
- package/admin/js/schreiben.js +209 -24
- package/admin/js/sprache.js +51 -7
- package/admin/js/start.js +32 -2
- package/admin/js/vorlagen.js +92 -4
- package/admin/js/vorlagenblatt.js +193 -23
- package/admin/js/werte.js +113 -6
- package/admin/js/zuordnung.js +30 -9
- package/admin/js/zustandsliste.js +14 -10
- package/admin/tab.html +17 -3
- package/admin/vorlagen/hm-dimmer.json +1 -1
- package/admin/vorlagen/hm-schaltaktor-lampe.json +2 -2
- package/admin/vorlagen/hm-schaltaktor-mehrfach.json +2 -2
- package/admin/vorlagen/hm-schaltaktor-steckdose.json +2 -2
- package/io-package.json +27 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -189,11 +189,6 @@ Open the ioBroker admin, go to **Adapters**, look for **Alias Workbench** and
|
|
|
189
189
|
create an instance. Then reload the admin and pick **Alias Workbench** in the
|
|
190
190
|
left menu.
|
|
191
191
|
|
|
192
|
-
The adapter has not been accepted into the ioBroker repository yet, so it does
|
|
193
|
-
not show up in that list for the moment. Until it does, install it from npm:
|
|
194
|
-
under **Adapters** press **install from custom URL** (the octocat button), tab
|
|
195
|
-
**From npm**, and enter `iobroker.alias-workbench`.
|
|
196
|
-
|
|
197
192
|
## Why the tree shows devices that do not exist
|
|
198
193
|
|
|
199
194
|
With MQTT, ioBroker only creates objects for the datapoints themselves. A topic
|
|
@@ -567,6 +562,50 @@ imported, and that both language files carry the same keys.
|
|
|
567
562
|
|
|
568
563
|
## Changelog
|
|
569
564
|
|
|
565
|
+
### 0.9.8
|
|
566
|
+
* The changelog only lists versions that actually exist on npm. The entries 0.9.1 to 0.9.6 were local intermediate steps and were never published; their content is now summarised under 0.9.7. Found by the repochecker (E2004, E1032)
|
|
567
|
+
* The README no longer explains installation via “install from custom URL” — in the official repository that goes through the adapter list (E6013)
|
|
568
|
+
* `dependabot.yml` gets a seven-day cooldown for npm packages: what appears today is proposed a week later. If a compromised release is pulled within that time, it never arrives here (E8915). And `@types/node` stays on its major version — the types must match the Node release the adapter runs on (E8917)
|
|
569
|
+
|
|
570
|
+
### 0.9.7
|
|
571
|
+
*Versions 0.9.1 to 0.9.6 were local intermediate steps and were never
|
|
572
|
+
published — their changes are part of 0.9.7.*
|
|
573
|
+
|
|
574
|
+
Seven packages from a code review — 45 fixes between 0.9.0 and this release. The intermediate steps 0.9.1 to 0.9.6 stayed local and were never published; they are summarised here.
|
|
575
|
+
|
|
576
|
+
**Data loss and failure paths**
|
|
577
|
+
* An enumeration error now stops a move. Previously the old alias was deleted anyway — and afterwards it was gone while the new one sat in no enumeration at all
|
|
578
|
+
* A partial failure while deleting no longer turns the retry button into a prop: only what really went is dropped
|
|
579
|
+
* Orphaned points are deleted on the second attempt too
|
|
580
|
+
* A load error on the enumerations leaves the previous state standing instead of emptying it — and while they are unknown, nothing is written. Before, one hiccup could drop an alias out of every room and function
|
|
581
|
+
* A load error on your own templates is reported instead of swallowed; otherwise the next save would have replaced an existing template with version 1
|
|
582
|
+
|
|
583
|
+
**Connection and display**
|
|
584
|
+
* The tab works again after a dropped connection. The admin's WebSocket fires `connect` once per page lifetime and `reconnect` afterwards — nobody listened, and neither object nor state changes arrived
|
|
585
|
+
* Template mode no longer rebuilds itself every second, and where the selection goes, its subscriptions go too
|
|
586
|
+
* “Differs from the alias” now also shows in alias mode — where it says the most
|
|
587
|
+
* Switching sources quickly no longer fetches the wrong value; the source jump list never appears twice
|
|
588
|
+
* Object names follow the admin language; the function match looks at every language and no longer creates a duplicate
|
|
589
|
+
|
|
590
|
+
**Templates**
|
|
591
|
+
* A broken name hint no longer takes down the whole source view — including the nested form `(a+)+` where the expression engine stalls
|
|
592
|
+
* The formula of a fallback source survives updating, an unknown device type no longer throws
|
|
593
|
+
* Importing checks more than id and states; a hand-typed id no longer replaces someone else's template
|
|
594
|
+
* The `%N%` placeholder only hits the output number now, not every trailing digit
|
|
595
|
+
* The blind gets its STOP: a button press has no state to read, and that case was unknown to template application — **not a single** blind alias ever had one
|
|
596
|
+
* ON_TIME finds its slot (four templates carried the wrong role)
|
|
597
|
+
|
|
598
|
+
**MQTT and Tasmota**
|
|
599
|
+
* An empty `custom` entry no longer takes anything down, `enabled: false` counts as switched off
|
|
600
|
+
* No more false “cannot publish” for ioBroker.mqtt and sonoff — there, publishing capability is simply unknown, not denied
|
|
601
|
+
* “Answer received” only for a real answer; `Vcc` and `Command` are not commands
|
|
602
|
+
|
|
603
|
+
**Speed, measured on a production system with 24,786 objects**
|
|
604
|
+
* The search box no longer blocks: **236 ms per keystroke → 0**, the whole tree **263 → 67 ms**
|
|
605
|
+
* A redraw no longer costs half a second — binary search instead of linear scans: drawing a source **15.8 → 1.6 ms**, an alias with 50 points **51.9 → 14.2 ms**
|
|
606
|
+
* A read formula can no longer freeze the tab for good
|
|
607
|
+
* What is subscribed is what is read — before, a single alias could subscribe to half an adapter
|
|
608
|
+
|
|
570
609
|
### 0.9.0
|
|
571
610
|
* The role list is complete again: the detector hands out the expressions of its slots as text WITH slashes (`/^indicator…$/`), not as a regular expression. Take the slashes for part of the expression and you find neither start nor end afterwards — 210 roles instead of 232, and `indicator.lowbat` of all things was missing, the role every Homematic device writes. In the role picker, no slot of the pattern was offered at all any more, only the line "show all roles"
|
|
572
611
|
* From the same cause, BRIGHTNESS lost its role in six colour-light patterns — the slot could be added over and over and could not be filled by any route. Seven places in the code read a slot expression; they all go through one function now, and a test checks the role list against what the detector actually delivers instead of against hand-written expressions
|
package/admin/i18n/de.json
CHANGED
|
@@ -132,6 +132,7 @@
|
|
|
132
132
|
"enums.room": "Raum",
|
|
133
133
|
"enums.roomHint": "Ordnet den Alias einem Raum zu. Das steht nicht am Objekt, sondern in der Aufzählung „enum.rooms\" — Alexa, Google und die Oberflächen gruppieren danach.",
|
|
134
134
|
"enums.suggested": "vorgeschlagen",
|
|
135
|
+
"enums.unknown": "Aufzählungen nicht geladen — Raum und Funktion bleiben unangetastet.",
|
|
135
136
|
"enums.unused": "leer",
|
|
136
137
|
"enums.willCreate": "neu anlegen",
|
|
137
138
|
"folder.aliasMissing": "fehlt",
|
|
@@ -165,12 +166,9 @@
|
|
|
165
166
|
"help.aliasesAdd": "Zum Hinzufügen von Punkten in einem Alias ist der Weg über „zur Quelle →“ (rechts oben neben dem Alias-Namen) bequemer: Dort greift die Vorlage, und alle Datenpunkte des Geräts stehen mit fertigen Rollen zur Auswahl — hier müsstest du jeden einzeln von Hand eintragen.",
|
|
166
167
|
"help.sources": "Links ein Gerät oder einen Kanal wählen. Die Werkbank erkennt, worum es sich handelt, und schlägt einen fertigen Alias vor. Gibt es ihn schon, wird er stattdessen aktualisiert.",
|
|
167
168
|
"help.templates": "Links eine Vorlage wählen. Mitgelieferte lassen sich ansehen und duplizieren, eigene bearbeiten. Der Probelauf zeigt, welche Geräte sie trifft.",
|
|
168
|
-
"info.countedSeparately": "für die meisten ohne Folgen",
|
|
169
169
|
"info.dlgTitle": "Gehört nicht zum {0}-Muster",
|
|
170
170
|
"info.explain": "funktionieren ganz normal und liegen da, wo du sie angelegt hast. Es entsteht dadurch kein zusätzliches Objekt — der Alias bleibt, wie er ist. Sie gehören nur nicht zu dem, was ioBroker unter einem {0} versteht; dafür passt ihre Rolle nicht. Bedeutung hat das erst, wenn du den Alias an Alexa, Google Home oder eine Gerätesteuerung hängst: die sehen dann ein {0} ohne diese Punkte. In VIS, in Skripten und im Verlauf benutzt du sie wie jeden anderen Datenpunkt.",
|
|
171
171
|
"info.more": "Was das bedeutet",
|
|
172
|
-
"info.notPartOf": "Gehört nicht zum {0}",
|
|
173
|
-
"info.sameDatapoints": "{0} — ",
|
|
174
172
|
"leave.body": "An {0} sind Änderungen offen, die noch nicht geschrieben wurden. Wechselst du jetzt, sind sie weg.",
|
|
175
173
|
"leave.discard": "Verwerfen und wechseln",
|
|
176
174
|
"leave.stay": "Hierbleiben",
|
|
@@ -208,6 +206,7 @@
|
|
|
208
206
|
"move.exists": "{0} gibt es schon. Erst entfernen oder einen anderen Namen wählen.",
|
|
209
207
|
"move.from": "von",
|
|
210
208
|
"move.needName": "Ein Name muss stehen bleiben.",
|
|
209
|
+
"move.nested": "Das Ziel liegt unter dem Alias selbst (oder umgekehrt). Beim Verlegen würde der eigene Elternknoten mitgelöscht — bitte einen Ort daneben wählen.",
|
|
211
210
|
"move.newFolder": "Ordner neu",
|
|
212
211
|
"move.noFolder": "(kein Ordner — direkt unter alias.0)",
|
|
213
212
|
"move.noScripts": "In den Skripten kommt die Kennung nicht vor.",
|
|
@@ -284,7 +283,6 @@
|
|
|
284
283
|
"pattern.manualInstead": "von Hand statt {0}",
|
|
285
284
|
"pattern.missing": "{0} fehlt",
|
|
286
285
|
"pattern.noMatch": "kein Muster passt zu „{0}“",
|
|
287
|
-
"pattern.noPlaceIn": "kein Platz im {0}-Muster",
|
|
288
286
|
"pattern.noPlaceLegend": "So hinterlegte Zeilen haben keinen Platz im {0}-Muster",
|
|
289
287
|
"pattern.noPlaceLong": "kein Platz im {0}-Muster — der Punkt wird trotzdem angelegt und ist voll benutzbar, nur Alexa & Co. zählen ihn separat",
|
|
290
288
|
"pattern.notDetected": "nicht erkannt",
|
|
@@ -335,6 +333,7 @@
|
|
|
335
333
|
"swap.mindHint": "Die Formeln des Alias bleiben unverändert — rechnet die neue Quelle anders als die alte, stimmen die Werte danach nicht mehr. Die Formel steht in der Detailzeile des Punkts.",
|
|
336
334
|
"swap.noDevice": "Kein Gerät gefunden.",
|
|
337
335
|
"swap.noMatch": "kein passender Punkt gefunden",
|
|
336
|
+
"swap.noWriteMatch": "kein Schreibziel gefunden — dieser Punkt schreibt danach nirgendwohin",
|
|
338
337
|
"swap.now": "Jetzt tauschen",
|
|
339
338
|
"swap.removeIt": "diesen Punkt beim Tauschen entfernen",
|
|
340
339
|
"swap.same": "Das ist dieselbe Quelle wie bisher.",
|
|
@@ -424,7 +423,7 @@
|
|
|
424
423
|
"tpls.id": "Kennung",
|
|
425
424
|
"tpls.json": "Das wird gespeichert",
|
|
426
425
|
"tpls.loadFailed": "eigene Vorlagen nicht geladen",
|
|
427
|
-
"tpls.loadFailedHint": "Die Instanzkonfiguration hat nicht
|
|
426
|
+
"tpls.loadFailedHint": "Die Instanzkonfiguration hat nicht geantwortet oder einen Fehler gemeldet. Deine eigenen Vorlagen sind nicht verschwunden — sie fehlen nur in dieser Ansicht. Solange dieser Hinweis steht, kann ein Speichern eine vorhandene Vorlage überschreiben: erst neu laden.",
|
|
428
427
|
"tpls.losesTo": "verliert gegen {0}",
|
|
429
428
|
"tpls.multiple": "Dieses Gerät hat mehrere Ausgänge",
|
|
430
429
|
"tpls.multipleHelp": "Die {0} in den Pfaden wird durch %N% ersetzt — nur am Ende eines Abschnitts, damit ENERGY.Power heil bleibt.",
|
|
@@ -549,6 +548,7 @@
|
|
|
549
548
|
"write.dryTitleFailed": "Nicht alles ist durchgegangen",
|
|
550
549
|
"write.dryTitleUpdate": "Alias aktualisieren — das würde entstehen",
|
|
551
550
|
"write.duplicateName": "{0}: Name kommt doppelt vor.",
|
|
551
|
+
"write.enumsUnknown": "Raum und Funktion werden nicht geschrieben: die Aufzählungen konnten nicht geladen werden. Der Alias selbst entsteht wie gezeigt. Ohne verlässlichen Stand würde eine vorhandene Aufzählung durch eine frische mit nur diesem einen Mitglied ersetzt.",
|
|
552
552
|
"write.failed": "{0} Vorgänge gingen schief:",
|
|
553
553
|
"write.new": "neu",
|
|
554
554
|
"write.noName": "Ein Punkt hat keinen Namen.",
|
|
@@ -575,5 +575,16 @@
|
|
|
575
575
|
"write.willKeep": "bleiben stehen: {0}",
|
|
576
576
|
"write.willRemove": "entfernt: {0}",
|
|
577
577
|
"write.writeSourceMissing": "{0}: den Sendepunkt {1} gibt es noch nicht",
|
|
578
|
-
"write.writing": "schreibe …"
|
|
578
|
+
"write.writing": "schreibe …",
|
|
579
|
+
"tpls.idTaken": "Diese Kennung gehört schon „{0}“ (v{1}). Speichern würde sie ersetzen.",
|
|
580
|
+
"tpls.nameHintBad": "Kein brauchbarer Ausdruck — er zählt als „trifft nicht“.",
|
|
581
|
+
"tv.badName": "Der Name muss ein Text oder ein Sprachobjekt sein.",
|
|
582
|
+
"tv.badRecog": "„erkennung“ muss ein Objekt sein.",
|
|
583
|
+
"tv.badList": "„{0}“ muss eine Liste von Punkten sein.",
|
|
584
|
+
"tv.badContent": "„erkennung.inhalt“ muss ein Objekt sein.",
|
|
585
|
+
"tv.badNameHint": "Der Namenshinweis ist kein brauchbarer Ausdruck.",
|
|
586
|
+
"tv.badType": "Den Gerätetyp „{0}“ kennt dieser Detector nicht.",
|
|
587
|
+
"check.sendUnknown": "Ob {0} senden darf, steht nicht im Objekt — das Thema liegt nur in `native`. Bei ioBroker.mqtt und sonoff ist das normal.",
|
|
588
|
+
"del.channelsBelow": "Achtung: {0} eigenständige Kanäle liegen darunter und werden mitgelöscht.",
|
|
589
|
+
"detail.formulaTooSlow": "Diese Formel hat {0} ms gebraucht und wird nicht mehr ausgeführt. Nach dem Neuladen des Reiters wird sie einmal neu versucht."
|
|
579
590
|
}
|
package/admin/i18n/en.json
CHANGED
|
@@ -132,6 +132,7 @@
|
|
|
132
132
|
"enums.room": "Room",
|
|
133
133
|
"enums.roomHint": "Assigns the alias to a room. This is not stored on the object but in the \"enum.rooms\" enumeration — Alexa, Google and the dashboards group by it.",
|
|
134
134
|
"enums.suggested": "suggested",
|
|
135
|
+
"enums.unknown": "Enumerations not loaded — room and function stay untouched.",
|
|
135
136
|
"enums.unused": "empty",
|
|
136
137
|
"enums.willCreate": "create new",
|
|
137
138
|
"folder.aliasMissing": "missing",
|
|
@@ -165,12 +166,9 @@
|
|
|
165
166
|
"help.aliasesAdd": "To add points to an alias, the way via “to the source →” (top right, next to the alias name) is easier: the template applies there, and every data point of the device is offered with a ready role — here you would have to enter each one by hand.",
|
|
166
167
|
"help.sources": "Pick a device or channel on the left. The workbench recognises what it is and proposes a finished alias. If one already exists, it is updated instead.",
|
|
167
168
|
"help.templates": "Pick a template on the left. Bundled ones can be viewed and duplicated, your own edited. The trial run shows which devices it matches.",
|
|
168
|
-
"info.countedSeparately": "no consequences for most setups",
|
|
169
169
|
"info.dlgTitle": "Not part of the {0} pattern",
|
|
170
170
|
"info.explain": "work perfectly normally and stay where you created them. Nothing extra is written — the alias stays as it is. They simply are not part of what ioBroker considers a {0}; their role does not fit any of its slots. It only starts to matter once you hand the alias to Alexa, Google Home or a device UI: those will see a {0} without these points. In VIS, in scripts and in history you use them like any other datapoint.",
|
|
171
171
|
"info.more": "What this means",
|
|
172
|
-
"info.notPartOf": "Not part of the {0}",
|
|
173
|
-
"info.sameDatapoints": "{0} — ",
|
|
174
172
|
"leave.body": "There are changes on {0} that have not been written yet. Switching now discards them.",
|
|
175
173
|
"leave.discard": "Discard and switch",
|
|
176
174
|
"leave.stay": "Stay here",
|
|
@@ -208,6 +206,7 @@
|
|
|
208
206
|
"move.exists": "{0} already exists. Remove it first or pick another name.",
|
|
209
207
|
"move.from": "from",
|
|
210
208
|
"move.needName": "A name is required.",
|
|
209
|
+
"move.nested": "The target sits below the alias itself (or the other way round). Moving would delete its own parent node — please pick a place beside it.",
|
|
211
210
|
"move.newFolder": "new folder",
|
|
212
211
|
"move.noFolder": "(no folder — directly under alias.0)",
|
|
213
212
|
"move.noScripts": "The ID does not appear in any script.",
|
|
@@ -284,7 +283,6 @@
|
|
|
284
283
|
"pattern.manualInstead": "by hand instead of {0}",
|
|
285
284
|
"pattern.missing": "{0} missing",
|
|
286
285
|
"pattern.noMatch": "no pattern matches \"{0}\"",
|
|
287
|
-
"pattern.noPlaceIn": "no slot in the {0} pattern",
|
|
288
286
|
"pattern.noPlaceLegend": "Rows shaded like this have no slot in the {0} pattern",
|
|
289
287
|
"pattern.noPlaceLong": "no slot in the {0} pattern — the point is still created and fully usable, only Alexa and friends count it separately",
|
|
290
288
|
"pattern.notDetected": "not detected",
|
|
@@ -335,6 +333,7 @@
|
|
|
335
333
|
"swap.mindHint": "The alias formulas stay as they are — if the new source scales differently, the values will be wrong afterwards. The formula sits in the point's detail row.",
|
|
336
334
|
"swap.noDevice": "No device found.",
|
|
337
335
|
"swap.noMatch": "no matching point found",
|
|
336
|
+
"swap.noWriteMatch": "no write target found — this point will not write anywhere",
|
|
338
337
|
"swap.now": "Swap now",
|
|
339
338
|
"swap.removeIt": "remove this point while swapping",
|
|
340
339
|
"swap.same": "That is the same source as before.",
|
|
@@ -424,7 +423,7 @@
|
|
|
424
423
|
"tpls.id": "Id",
|
|
425
424
|
"tpls.json": "This is what gets stored",
|
|
426
425
|
"tpls.loadFailed": "own templates not loaded",
|
|
427
|
-
"tpls.loadFailedHint": "The instance configuration did not answer
|
|
426
|
+
"tpls.loadFailedHint": "The instance configuration did not answer or reported an error. Your own templates are not gone — they are only missing from this view. While this notice stands, saving may overwrite an existing template: reload first.",
|
|
428
427
|
"tpls.losesTo": "loses to {0}",
|
|
429
428
|
"tpls.multiple": "This device has several outputs",
|
|
430
429
|
"tpls.multipleHelp": "The {0} in the paths is replaced by %N% — only at the end of a segment, so ENERGY.Power stays intact.",
|
|
@@ -549,6 +548,7 @@
|
|
|
549
548
|
"write.dryTitleFailed": "Not everything went through",
|
|
550
549
|
"write.dryTitleUpdate": "Update alias — this is what would appear",
|
|
551
550
|
"write.duplicateName": "{0}: name appears twice.",
|
|
551
|
+
"write.enumsUnknown": "Room and function will not be written: the enumerations could not be loaded. The alias itself is created as shown. Without a reliable state an existing enumeration would be replaced by a fresh one holding just this single member.",
|
|
552
552
|
"write.failed": "{0} operations failed:",
|
|
553
553
|
"write.new": "new",
|
|
554
554
|
"write.noName": "One point has no name.",
|
|
@@ -575,5 +575,16 @@
|
|
|
575
575
|
"write.willKeep": "kept: {0}",
|
|
576
576
|
"write.willRemove": "removed: {0}",
|
|
577
577
|
"write.writeSourceMissing": "{0}: the send datapoint {1} does not exist yet",
|
|
578
|
-
"write.writing": "writing …"
|
|
578
|
+
"write.writing": "writing …",
|
|
579
|
+
"tpls.idTaken": "This id already belongs to “{0}” (v{1}). Saving would replace it.",
|
|
580
|
+
"tpls.nameHintBad": "Not a usable expression — it counts as “no match”.",
|
|
581
|
+
"tv.badName": "The name must be a string or a language object.",
|
|
582
|
+
"tv.badRecog": "“erkennung” must be an object.",
|
|
583
|
+
"tv.badList": "“{0}” must be a list of points.",
|
|
584
|
+
"tv.badContent": "“erkennung.inhalt” must be an object.",
|
|
585
|
+
"tv.badNameHint": "The name hint is not a usable expression.",
|
|
586
|
+
"tv.badType": "This detector does not know the device type “{0}”.",
|
|
587
|
+
"check.sendUnknown": "Whether {0} may publish is not stated in the object — the topic sits in `native` only. Normal for ioBroker.mqtt and sonoff.",
|
|
588
|
+
"del.channelsBelow": "Careful: {0} channels of their own sit below this and will be deleted too.",
|
|
589
|
+
"detail.formulaTooSlow": "This formula took {0} ms and is no longer executed. Reloading the tab gives it one more try."
|
|
579
590
|
}
|
|
@@ -221,8 +221,18 @@ export function raumVorschlag(e) {
|
|
|
221
221
|
|
|
222
222
|
/* 3. Aus dem Pfad der Quelle */
|
|
223
223
|
if (quelle) {
|
|
224
|
+
/* Von hinten, das LETZTE Segment eingeschlossen.
|
|
225
|
+
|
|
226
|
+
Frueher begann die Schleife eins davor — gedacht fuer
|
|
227
|
+
`…Wohnzimmer.Lampe`, wo hinten das Geraet steht. Bei einem flachen
|
|
228
|
+
Adapter wie `sonoff.0.Wohnzimmer` IST das letzte Segment aber der
|
|
229
|
+
Raum, und die Schleife lief kein einziges Mal: kein Vorschlag,
|
|
230
|
+
obwohl der Raum im Namen steht (gemessen 09.09.2026 —
|
|
231
|
+
`sonoff.0.Wohnzimmer.Lampe` ergab `enum.rooms.living_room`,
|
|
232
|
+
`sonoff.0.Wohnzimmer` nichts). Der Weg ueber den Geraetenamen
|
|
233
|
+
faengt es nicht auf: der geht bei Name gleich Segment leer aus. */
|
|
224
234
|
var qt = quelle.split('.');
|
|
225
|
-
for (var j = qt.length -
|
|
235
|
+
for (var j = qt.length - 1; j >= 2; j--) {
|
|
226
236
|
var qr = suche(qt[j]);
|
|
227
237
|
if (qr) { return qr; }
|
|
228
238
|
}
|
package/admin/js/baum.js
CHANGED
|
@@ -8,7 +8,7 @@ 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 } from './entwurf.js';
|
|
11
|
+
import { mitNachfrage, aboLoesen } from './entwurf.js';
|
|
12
12
|
import { zeichneErgebnis } from './ergebnis.js';
|
|
13
13
|
import { zeichneVorlagenListe } from './vorlagenblatt.js';
|
|
14
14
|
|
|
@@ -70,7 +70,7 @@ window.addEventListener('storage', function (e) {
|
|
|
70
70
|
/* Steht die Auswahl in einem Zweig, den es gleich nicht mehr gibt, muss
|
|
71
71
|
sie weg — sonst zeigt die rechte Seite ein Geraet, das links fehlt. */
|
|
72
72
|
if (S.current && !experte() && nurFuerExperten(S.current, S.objects[S.current])) {
|
|
73
|
-
S.current = null;
|
|
73
|
+
S.current = null; aboLoesen();
|
|
74
74
|
S.entwurf = null;
|
|
75
75
|
}
|
|
76
76
|
zeichneBaum();
|
|
@@ -94,10 +94,6 @@ export function zusatzName(id) {
|
|
|
94
94
|
return (n === String(id).split('.').pop()) ? '' : n;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
/* Wonach die Zeile gelesen und sortiert wird. */
|
|
98
|
-
export function anzeigeText(k) {
|
|
99
|
-
return zusatzName(k.id) || k.name;
|
|
100
|
-
}
|
|
101
97
|
|
|
102
98
|
function baueBaum() {
|
|
103
99
|
var wurzel = { kinder: {}, id: '', name: '' };
|
|
@@ -152,6 +148,33 @@ function zaehleZustaende(k, eltern) {
|
|
|
152
148
|
}
|
|
153
149
|
|
|
154
150
|
|
|
151
|
+
/* Ist dieser Knoten aufgeklappt?
|
|
152
|
+
|
|
153
|
+
Stand frueher mitten im Zeichnen einer Zeile — und damit erst,
|
|
154
|
+
nachdem der ganze Teilbaum schon als DOM entstanden war. Fuer
|
|
155
|
+
„zugeklapptes gar nicht erst zeichnen" muss die Antwort vorher da
|
|
156
|
+
sein, also hier. */
|
|
157
|
+
function klappStand(k, tiefe) {
|
|
158
|
+
if ((($('#q') || {}).value || '').trim()) { return true; }
|
|
159
|
+
var vorgabe = (S.alleKlapp === null) ? (tiefe <= 1) : S.alleKlapp;
|
|
160
|
+
/* Im Aliasbaum gibt es unterhalb der Wurzel nur eigene Ordner - ganz
|
|
161
|
+
zugeklappt bliebe eine einzige Zeile `alias` uebrig, und jeder Weg
|
|
162
|
+
begaenne mit zwei Pflichtklicks. Bis `alias.0` bleibt deshalb offen.
|
|
163
|
+
Das steht hier und nicht im Zuklapp-Knopf: sonst gilt es nur, wenn
|
|
164
|
+
man IM Aliasmodus zuklappt - wer in den Quellen zuklappt und dann
|
|
165
|
+
herueberwechselt, fand den Baum zu weit zu (Ricardo, 25.08.2026).
|
|
166
|
+
Einzeln zuklappen darf man sie weiterhin, `S.aufgeklappt` gewinnt. */
|
|
167
|
+
if (S.baumModus === 'aliase' && (k.id === 'alias' || k.id === 'alias.0')) {
|
|
168
|
+
vorgabe = true;
|
|
169
|
+
}
|
|
170
|
+
return S.aufgeklappt[k.id] !== undefined ? S.aufgeklappt[k.id] : vorgabe;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* Ein Vergleicher fuer den ganzen Baum. `localeCompare` mit
|
|
174
|
+
Optionsobjekt baut bei jedem Aufruf einen neuen — bei 24.800 Elementen
|
|
175
|
+
sind das je Sortierlauf zehntausende. */
|
|
176
|
+
var VERGLEICH = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
|
177
|
+
|
|
155
178
|
export function zeichneBaum() {
|
|
156
179
|
if (S.baumModus === 'vorlagen') { return zeichneVorlagenListe(); }
|
|
157
180
|
S.baum = baueBaum();
|
|
@@ -232,6 +255,23 @@ export function istWaehlbar(k) {
|
|
|
232
255
|
/* Notbehelf nur dort, wo im ganzen Zweig keine Typen gepflegt sind.
|
|
233
256
|
Wo es getypte Kanäle gibt, sollen die das Gerät sein. */
|
|
234
257
|
if (k._hatTypen) { return false; }
|
|
258
|
+
/* Aber niemals die Wurzel selbst.
|
|
259
|
+
|
|
260
|
+
`mqtt-client` und `mqtt-client.0` sind kein Geraet, sondern der
|
|
261
|
+
Adapter und seine Instanz. Waehlbar waren sie trotzdem, sobald im
|
|
262
|
+
Zweig keine Typen gepflegt sind — und die Auswahl abonniert
|
|
263
|
+
`<id>.*` und holt die Werte des ganzen Namensraums (am Testsystem
|
|
264
|
+
322 Objekte, in einer gewachsenen Anlage ein Vielfaches). Aufklappen
|
|
265
|
+
ja, auswaehlen nein.
|
|
266
|
+
|
|
267
|
+
Gemeint ist die STELLE im Baum, nicht `_tiefe`: Das ist die Hoehe
|
|
268
|
+
des Teilbaums UNTER dem Knoten, nicht seine Position darin. In
|
|
269
|
+
0.9.6 stand hier `k._tiefe <= 1`, und das schloss am Testsystem 131
|
|
270
|
+
Knoten aus — genau einen davon zu Recht. Die anderen 130 waren
|
|
271
|
+
Sparten wie `…Decklenlicht_RGB.cmnd` und Ordner wie `iot.0.smart`,
|
|
272
|
+
also Knoten, deren Datenpunkte direkt darunter liegen (gefunden
|
|
273
|
+
10.09.2026 beim Nachmessen von B23). */
|
|
274
|
+
if (k.id.split('.').length <= 2) { return false; }
|
|
235
275
|
/* Sonst: alles waehlbar. Was ein Geraet ist, weiss nur der Mensch —
|
|
236
276
|
bei Duneweaver ist es der Knoten darueber, bei Tasmota der darunter.
|
|
237
277
|
Statt zu sperren wird geraten (siehe wohlGeraet) und gewarnt. */
|
|
@@ -271,12 +311,17 @@ function renderKinder(knoten, filter, tiefe) {
|
|
|
271
311
|
return c._zust && !(c.art === 'state' && !Object.keys(c.kinder).length);
|
|
272
312
|
});
|
|
273
313
|
}
|
|
314
|
+
/* Einmal je Reihe, nicht bei jedem Vergleich.
|
|
315
|
+
|
|
316
|
+
`klappbar` laeuft ueber alle Enkel, und ein Sortiervergleich ruft es
|
|
317
|
+
zweimal — bei n Reihen also 2·n·log(n) Enkel-Schleifen statt n.
|
|
318
|
+
Dasselbe gilt fuer `localeCompare` mit Optionsobjekt: Das baut je
|
|
319
|
+
Aufruf einen neuen Vergleicher. Ein `Intl.Collator` tut dasselbe
|
|
320
|
+
einmal. */
|
|
321
|
+
reihen.forEach(function (r) { r.kl = klappbar(r.k) ? 0 : 1; r.txt = String(r.zus || r.k.name); });
|
|
274
322
|
reihen.sort(function (a, b) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (ba !== bb) { return ba - bb; }
|
|
278
|
-
return String(a.zus || a.k.name).localeCompare(String(b.zus || b.k.name),
|
|
279
|
-
undefined, { numeric: true, sensitivity: 'base' });
|
|
323
|
+
if (a.kl !== b.kl) { return a.kl - b.kl; }
|
|
324
|
+
return VERGLEICH.compare(a.txt, b.txt);
|
|
280
325
|
});
|
|
281
326
|
|
|
282
327
|
reihen.forEach(function (reihe) {
|
|
@@ -320,8 +365,21 @@ function renderKinder(knoten, filter, tiefe) {
|
|
|
320
365
|
? true
|
|
321
366
|
: (!leer && (k.id.toLowerCase().indexOf(filter) !== -1 ||
|
|
322
367
|
(reihe.zus && reihe.zus.toLowerCase().indexOf(filter) !== -1)));
|
|
323
|
-
|
|
324
|
-
|
|
368
|
+
/* Zugeklappte Knoten gar nicht erst zeichnen.
|
|
369
|
+
|
|
370
|
+
Vorher entstand IMMER der ganze Teilbaum als DOM — auch der eines
|
|
371
|
+
zugeklappten Knotens, der gleich darauf weggeworfen wurde. Bei
|
|
372
|
+
einer gewachsenen Anlage ist das der ganz ueberwiegende Teil der
|
|
373
|
+
Arbeit.
|
|
374
|
+
|
|
375
|
+
Zwei Ausnahmen, in denen der Teilbaum gebraucht wird: beim Filtern
|
|
376
|
+
(ein Treffer weiter unten macht den Elternknoten sichtbar) und
|
|
377
|
+
wenn der Knoten aufgeklappt ist. Sonst genuegt die Frage, OB es
|
|
378
|
+
zeigbare Kinder gibt — und die beantwortet `klappbar` ohne DOM. */
|
|
379
|
+
var aufJetzt = klappStand(k, tiefe);
|
|
380
|
+
var brauchtKinder = !!filter || aufJetzt;
|
|
381
|
+
var kinderUl = brauchtKinder ? renderKinder(k, filter, tiefe + 1) : null;
|
|
382
|
+
var kinderPassen = kinderUl ? kinderUl.childNodes.length > 0 : klappbar(k);
|
|
325
383
|
if (!passt && !kinderPassen) { return; }
|
|
326
384
|
|
|
327
385
|
var li = el('li');
|
|
@@ -329,20 +387,8 @@ function renderKinder(knoten, filter, tiefe) {
|
|
|
329
387
|
var d = el('div', 'node' + (waehlbar ? ' pick' : ' grp') + (leer ? ' leer' : '') +
|
|
330
388
|
(k.id === S.current ? ' sel' : ''));
|
|
331
389
|
|
|
332
|
-
var hatKinder =
|
|
333
|
-
var
|
|
334
|
-
/* Im Aliasbaum gibt es unterhalb der Wurzel nur eigene Ordner - ganz
|
|
335
|
-
zugeklappt bliebe eine einzige Zeile `alias` uebrig, und jeder Weg
|
|
336
|
-
begaenne mit zwei Pflichtklicks. Bis `alias.0` bleibt deshalb offen.
|
|
337
|
-
Das steht hier und nicht im Zuklapp-Knopf: sonst gilt es nur, wenn
|
|
338
|
-
man IM Aliasmodus zuklappt - wer in den Quellen zuklappt und dann
|
|
339
|
-
herueberwechselt, fand den Baum zu weit zu (Ricardo, 25.08.2026).
|
|
340
|
-
Einzeln zuklappen darf man sie weiterhin, `S.aufgeklappt` gewinnt. */
|
|
341
|
-
if (S.baumModus === 'aliase' && (k.id === 'alias' || k.id === 'alias.0')) {
|
|
342
|
-
vorgabe = true;
|
|
343
|
-
}
|
|
344
|
-
var auf = filter ? true
|
|
345
|
-
: (S.aufgeklappt[k.id] !== undefined ? S.aufgeklappt[k.id] : vorgabe);
|
|
390
|
+
var hatKinder = kinderPassen;
|
|
391
|
+
var auf = aufJetzt;
|
|
346
392
|
var tw = el('span', 'tw', hatKinder ? (auf ? '▾' : '▸') : '');
|
|
347
393
|
if (hatKinder) {
|
|
348
394
|
tw.addEventListener('click', function (e) {
|
|
@@ -394,13 +440,25 @@ function renderKinder(knoten, filter, tiefe) {
|
|
|
394
440
|
});
|
|
395
441
|
}
|
|
396
442
|
li.appendChild(d);
|
|
397
|
-
if (hatKinder && auf) { li.appendChild(kinderUl); }
|
|
443
|
+
if (hatKinder && auf && kinderUl) { li.appendChild(kinderUl); }
|
|
398
444
|
ul.appendChild(li);
|
|
399
445
|
});
|
|
400
446
|
return ul;
|
|
401
447
|
}
|
|
402
448
|
|
|
403
|
-
|
|
449
|
+
/* Entprellt, nicht bei jedem Anschlag.
|
|
450
|
+
|
|
451
|
+
Der Baum wurde bei JEDEM Tastendruck komplett neu gebaut. Am
|
|
452
|
+
Produktivsystem mit 24.786 Objekten gemessen (10.09.2026): 236 ms je
|
|
453
|
+
Anschlag, acht Anschlaege 1,9 Sekunden — die Eingabe hinkte sichtbar
|
|
454
|
+
hinterher. 180 ms sind kurz genug, dass es nach dem Tippen sofort
|
|
455
|
+
dasteht, und lang genug, dass eine fluessig getippte Folge nur einmal
|
|
456
|
+
zeichnet. */
|
|
457
|
+
var suchTimer = null;
|
|
458
|
+
$('#q').addEventListener('input', function () {
|
|
459
|
+
if (suchTimer) { clearTimeout(suchTimer); }
|
|
460
|
+
suchTimer = setTimeout(function () { suchTimer = null; zeichneBaum(); }, 180);
|
|
461
|
+
});
|
|
404
462
|
|
|
405
463
|
/* Alles auf, alles zu. Die einzeln gemerkten Knoten fallen dabei weg -
|
|
406
464
|
sonst bliebe ein Zweig zu, den man eben ausdruecklich zugeklappt hat,
|
|
@@ -416,7 +474,7 @@ function klappeAlle(auf) {
|
|
|
416
474
|
Abschalten des Expertenmodus. Geprueft wird an der gezeichneten
|
|
417
475
|
Liste, nicht an der Datenlage: sichtbar ist, was im Baum steht. */
|
|
418
476
|
if (S.current && !auf && !imBaumSichtbar()) {
|
|
419
|
-
S.current = null;
|
|
477
|
+
S.current = null; aboLoesen();
|
|
420
478
|
S.entwurf = null;
|
|
421
479
|
S.openRow = null;
|
|
422
480
|
zeichneErgebnis();
|
|
@@ -448,10 +506,10 @@ export function setzeModus(m) {
|
|
|
448
506
|
Der Vorlagenbaum ist ausgenommen: er hat eine eigene Ansicht, und
|
|
449
507
|
wer von dort zurueckwechselt, findet seine Auswahl wieder vor. */
|
|
450
508
|
if (m === 'quellen' && S.current && S.current.indexOf('alias.') === 0) {
|
|
451
|
-
S.current = null; S.entwurf = null; S.openRow = null;
|
|
509
|
+
S.current = null; aboLoesen(); S.entwurf = null; S.openRow = null;
|
|
452
510
|
}
|
|
453
511
|
if (m === 'aliase' && S.current && S.current.indexOf('alias.') !== 0) {
|
|
454
|
-
S.current = null; S.entwurf = null; S.openRow = null;
|
|
512
|
+
S.current = null; aboLoesen(); S.entwurf = null; S.openRow = null;
|
|
455
513
|
}
|
|
456
514
|
/* Beim Moduswechsel den Filter fallen lassen und die Geraeteknoepfe
|
|
457
515
|
zurueckholen. Sonst stand man im Vorlagenbaum vor „nichts gefunden"
|
package/admin/js/detail.js
CHANGED
|
@@ -149,7 +149,14 @@ export function detailZeile(e, s, idx) {
|
|
|
149
149
|
Eine Zeile kann beides tragen — etwa nach einem Musterwechsel, der
|
|
150
150
|
die Rolle anpasst: Die neue Rolle weicht dann vom Alias ab, und je
|
|
151
151
|
nachdem auch von der Vorlage (Ricardo, 06.09.2026). */
|
|
152
|
-
|
|
152
|
+
/* `e.ziel || e.kanal` — wie bei den beiden Nachbarn.
|
|
153
|
+
|
|
154
|
+
Im Aliasmodus ist `e.ziel` nie gesetzt: Man steht ja schon auf dem
|
|
155
|
+
Alias, das Ziel waehlt man erst im Quellenmodus. Die Marke „weicht vom
|
|
156
|
+
Alias ab" erschien dort also NIE — ausgerechnet dort, wo sie am
|
|
157
|
+
meisten sagt (gemessen 09.09.2026: mit `e.ziel` null Abweichungen, mit
|
|
158
|
+
`e.ziel || e.kanal` eine). */
|
|
159
|
+
var bstListe = bestandsAbweichung(s, e.ziel || e.kanal);
|
|
153
160
|
var bstNach = {};
|
|
154
161
|
bstListe.forEach(function (x) { bstNach[x.feld] = x; });
|
|
155
162
|
|
|
@@ -227,8 +234,16 @@ export function detailZeile(e, s, idx) {
|
|
|
227
234
|
if (selR.value === '__frei__') { s.frei = true; s.srcR = ''; }
|
|
228
235
|
else { s.frei = false; s.srcR = selR.value; }
|
|
229
236
|
if (s.srcR && !S.werte[s.srcR]) {
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
/* Die Kennung festhalten, bevor gefragt wird.
|
|
238
|
+
|
|
239
|
+
Der Rueckruf legte den Wert unter der Quelle ab, die BEIM
|
|
240
|
+
EINTREFFEN eingestellt war. Schaltet man in der Zwischenzeit
|
|
241
|
+
weiter, landet der Wert der alten Quelle unter der neuen — und
|
|
242
|
+
da nur geholt wird, was noch fehlt, bleibt es dabei: die neue
|
|
243
|
+
gilt als bekannt (gemessen 09.09.2026). */
|
|
244
|
+
var geholt = s.srcR;
|
|
245
|
+
socket.emit('getState', geholt, function (err, st) {
|
|
246
|
+
if (!err && st) { S.werte[geholt] = st; zeichneErgebnis(); }
|
|
232
247
|
});
|
|
233
248
|
}
|
|
234
249
|
neu();
|