iobroker.alias-workbench 0.8.3 → 0.9.7
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 +146 -3
- package/admin/css/werkbank.css +57 -8
- package/admin/detector.js +1 -1
- package/admin/geraetewissen/tasmota-befehle.json +3 -1
- package/admin/i18n/de.json +30 -6
- package/admin/i18n/en.json +30 -6
- package/admin/js/aufzaehlungen.js +11 -1
- package/admin/js/baum.js +118 -37
- package/admin/js/detail.js +76 -9
- package/admin/js/entwurf.js +313 -43
- package/admin/js/enums.js +50 -7
- package/admin/js/ergebnis.js +161 -37
- package/admin/js/erkennung.js +54 -5
- package/admin/js/katalog.js +12 -3
- package/admin/js/mqtt.js +76 -13
- package/admin/js/musternamen.js +91 -26
- 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 +54 -7
- package/admin/js/start.js +32 -2
- package/admin/js/vorlagen.js +114 -6
- package/admin/js/vorlagenblatt.js +195 -26
- package/admin/js/werte.js +113 -6
- package/admin/js/zuordnung.js +71 -17
- package/admin/js/zustand.js +7 -1
- package/admin/js/zustandsliste.js +157 -26
- package/admin/tab.html +25 -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 +105 -1
- package/package.json +1 -1
package/admin/js/werte.js
CHANGED
|
@@ -52,10 +52,34 @@ export function feldWert(obj, pfad) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
/* Alle Kennungen mit diesem Vorspann — ohne die Gesamtliste zu lesen.
|
|
56
|
+
|
|
57
|
+
`S.keysSorted` ist sortiert, die gesuchten Eintraege liegen also am
|
|
58
|
+
Stueck. Vorher ging jeder dieser Aufrufe die ganze Liste durch:
|
|
59
|
+
24.786 Vergleiche fuer zehn Treffer, rund zwanzigmal je Neuzeichnen,
|
|
60
|
+
und bei laufenden Werten alle 700 ms (gemessen am Produktivsystem
|
|
61
|
+
10.09.2026: Alias 51,9 ms, Quelle 15,8 ms je Zeichnen).
|
|
62
|
+
|
|
63
|
+
Die Binaersuche findet den Anfang in ~15 Schritten, danach wird nur
|
|
64
|
+
noch der Bereich selbst gelesen. */
|
|
65
|
+
export function mitVorspann(pre) {
|
|
66
|
+
var liste = S.keysSorted;
|
|
67
|
+
var lo = 0, hi = liste.length;
|
|
68
|
+
while (lo < hi) {
|
|
69
|
+
var m = (lo + hi) >> 1;
|
|
70
|
+
if (liste[m] < pre) { lo = m + 1; } else { hi = m; }
|
|
71
|
+
}
|
|
72
|
+
var raus = [];
|
|
73
|
+
for (var i = lo; i < liste.length && liste[i].indexOf(pre) === 0; i++) {
|
|
74
|
+
raus.push(liste[i]);
|
|
75
|
+
}
|
|
76
|
+
return raus;
|
|
77
|
+
}
|
|
78
|
+
|
|
55
79
|
export function kindZustaende(kanal) {
|
|
56
80
|
var pre = kanal + '.';
|
|
57
|
-
return
|
|
58
|
-
return
|
|
81
|
+
return mitVorspann(pre).filter(function (k) {
|
|
82
|
+
return S.objects[k] && S.objects[k].type === 'state';
|
|
59
83
|
});
|
|
60
84
|
}
|
|
61
85
|
|
|
@@ -64,6 +88,30 @@ export function direkteZustaende(kanal) {
|
|
|
64
88
|
return kindZustaende(kanal).filter(function (k) { return k.slice(pre.length).indexOf('.') === -1; });
|
|
65
89
|
}
|
|
66
90
|
|
|
91
|
+
/* Wohin schreibt dieser Aliaspunkt?
|
|
92
|
+
|
|
93
|
+
Fuenfmal woertlich gleich ausprogrammiert gewesen — dreimal in
|
|
94
|
+
`entwurf.js`, zweimal in `zuordnung.js` —, und die Kommentare an zwei
|
|
95
|
+
dieser Stellen dokumentieren je einen Fehler, der genau daraus
|
|
96
|
+
entstanden ist: einmal wurde aus einem schaltenden Punkt ein lesender
|
|
97
|
+
ohne Umrechnung, einmal verloren OPEN, SET und pct ihre Schreibquelle,
|
|
98
|
+
weil dort `q.einfach ? '' : …` stand und `common.write` gar nicht
|
|
99
|
+
angesehen wurde.
|
|
100
|
+
|
|
101
|
+
Die Regel selbst: Bei getrennten Quellen (`alias.id.read`/`.write`)
|
|
102
|
+
gilt, was unter `write` steht. Bei einer schlichten `alias.id` zeigen
|
|
103
|
+
Lesen und Schreiben auf denselben Punkt — geschrieben wird dorthin
|
|
104
|
+
aber nur, wenn der Alias das auch sagt: durch `common.write` oder
|
|
105
|
+
durch eine hinterlegte Schreibformel. Wer eine Schreibformel
|
|
106
|
+
hinterlegt hat, wollte schreiben. */
|
|
107
|
+
export function schreibQuelle(o) {
|
|
108
|
+
var c = (o && o.common) || {};
|
|
109
|
+
var a = c.alias || {};
|
|
110
|
+
var q = aliasQuellen(o);
|
|
111
|
+
if (!q.einfach) { return q.write || ''; }
|
|
112
|
+
return (c.write === true || typeof a.write === 'string') ? (q.write || '') : '';
|
|
113
|
+
}
|
|
114
|
+
|
|
67
115
|
export function aliasQuellen(obj) {
|
|
68
116
|
var a = obj && obj.common && obj.common.alias;
|
|
69
117
|
if (!a) { return {}; }
|
|
@@ -114,17 +162,76 @@ export function holeEinzelne(ids, fertig) {
|
|
|
114
162
|
}
|
|
115
163
|
|
|
116
164
|
/* ================== Formel und Wert ================== */
|
|
165
|
+
|
|
166
|
+
/* Leseformeln sind fremder Code, der hier laeuft.
|
|
167
|
+
|
|
168
|
+
Sie stehen in `common.alias.read` jedes Alias und in `leseformel`
|
|
169
|
+
jeder Vorlage, und sie werden je Zeile bei JEDEM Zeichnen ausgefuehrt
|
|
170
|
+
— ungefragt, nicht auf Knopfdruck. Das ist keine neue
|
|
171
|
+
Rechteausweitung: Wer `common.alias.read` schreiben darf, hat
|
|
172
|
+
dieselbe Formel schon im js-controller laufen. Es ist aber ein
|
|
173
|
+
zusaetzlicher Ausfuehrungsort, und einer ohne Grenzen: `try/catch`
|
|
174
|
+
faengt nur Ausnahmen, kein `while(true){}`.
|
|
175
|
+
|
|
176
|
+
Zwei Schranken, beide billig:
|
|
177
|
+
|
|
178
|
+
1. Gemerkt wird je (Formel, Rohwert). Dieselbe Formel auf demselben
|
|
179
|
+
Wert laeuft genau einmal — und genau das ist der Regelfall, wenn
|
|
180
|
+
alle 700 ms neu gezeichnet wird, ohne dass sich etwas geaendert
|
|
181
|
+
hat.
|
|
182
|
+
2. Wer einmal zu lange gebraucht hat, laeuft nicht wieder. Die Zeile
|
|
183
|
+
sagt dann, warum. Den Reiter kann man damit noch einmal
|
|
184
|
+
einfrieren, aber nicht dauerhaft — und nach dem Neuladen steht
|
|
185
|
+
die Sperre wieder. */
|
|
186
|
+
var GEDULD_MS = 250;
|
|
187
|
+
var formelCache = new Map();
|
|
188
|
+
var formelGesperrt = {};
|
|
189
|
+
|
|
190
|
+
function cacheSchluessel(formel, roh) {
|
|
191
|
+
var t = typeof roh;
|
|
192
|
+
if (t === 'string') { return roh.length > 300 ? null : ('s:' + formel + '\u0000' + roh); }
|
|
193
|
+
if (t === 'number' || t === 'boolean') { return t.charAt(0) + ':' + formel + '\u0000' + roh; }
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
117
197
|
export function auswerten(formel, roh) {
|
|
118
198
|
if (roh === undefined || roh === null) { return { ok: false, txt: tr('detail.noValue') }; }
|
|
119
199
|
if (!formel) { return { ok: true, val: roh }; }
|
|
200
|
+
if (formelGesperrt[formel]) {
|
|
201
|
+
return { ok: false, txt: tr('detail.formulaTooSlow', formelGesperrt[formel]), fehler: true };
|
|
202
|
+
}
|
|
203
|
+
var sch = cacheSchluessel(formel, roh);
|
|
204
|
+
if (sch !== null) {
|
|
205
|
+
var da = formelCache.get(sch);
|
|
206
|
+
if (da !== undefined) { return da; }
|
|
207
|
+
}
|
|
208
|
+
var raus;
|
|
209
|
+
var t0 = performance.now();
|
|
120
210
|
try {
|
|
121
211
|
var v = (new Function('val', 'return (' + formel + ');'))(roh);
|
|
122
|
-
if (v === undefined) {
|
|
123
|
-
if (typeof v === 'number' && isNaN(v)) {
|
|
124
|
-
|
|
212
|
+
if (v === undefined) { raus = { ok: false, txt: 'undefined', leer: true }; }
|
|
213
|
+
else if (typeof v === 'number' && isNaN(v)) { raus = { ok: false, txt: 'NaN', leer: true }; }
|
|
214
|
+
else { raus = { ok: true, val: v }; }
|
|
125
215
|
} catch (e) {
|
|
126
|
-
|
|
216
|
+
raus = { ok: false, txt: tr('detail.formulaThrows', e.message), fehler: true };
|
|
127
217
|
}
|
|
218
|
+
var gedauert = performance.now() - t0;
|
|
219
|
+
if (gedauert > GEDULD_MS) {
|
|
220
|
+
formelGesperrt[formel] = Math.round(gedauert);
|
|
221
|
+
console.warn('[alias-workbench] Formel gesperrt nach ' + Math.round(gedauert) + ' ms: ' + formel);
|
|
222
|
+
return { ok: false, txt: tr('detail.formulaTooSlow', Math.round(gedauert)), fehler: true };
|
|
223
|
+
}
|
|
224
|
+
if (sch !== null) {
|
|
225
|
+
/* Nicht unbegrenzt wachsen lassen: bei laufenden Werten entsteht je
|
|
226
|
+
neuem Rohwert ein Eintrag. Zweitausend reichen fuer ein Zeichnen
|
|
227
|
+
um ein Vielfaches; darueber wird der aelteste verworfen. */
|
|
228
|
+
if (formelCache.size >= 2000) {
|
|
229
|
+
var ersteR = formelCache.keys().next();
|
|
230
|
+
if (!ersteR.done) { formelCache.delete(ersteR.value); }
|
|
231
|
+
}
|
|
232
|
+
formelCache.set(sch, raus);
|
|
233
|
+
}
|
|
234
|
+
return raus;
|
|
128
235
|
}
|
|
129
236
|
|
|
130
237
|
var JA = ['ON', 'TRUE', 'YES', 'JA', '1'];
|
package/admin/js/zuordnung.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { S } from './zustand.js';
|
|
5
5
|
import { el } from './basis.js';
|
|
6
6
|
import { tr, txt } from './sprache.js';
|
|
7
|
-
import { enums } from './enums.js';
|
|
7
|
+
import { enums, enumsUnbekannt } from './enums.js';
|
|
8
8
|
import {
|
|
9
9
|
enumListe,
|
|
10
10
|
enumsVon,
|
|
@@ -14,13 +14,13 @@ import {
|
|
|
14
14
|
} from './aufzaehlungen.js';
|
|
15
15
|
import { enumVorlagen, katalogFehlt, ikonCache, ikonErlaubt, ikonNachtrag,
|
|
16
16
|
ikonTaugt, vorlageZu, holeIkon } from './katalog.js';
|
|
17
|
-
import { kindZustaende, aliasQuellen, holeEinzelne } from './werte.js';
|
|
17
|
+
import { kindZustaende, aliasQuellen, holeEinzelne, schreibQuelle } from './werte.js';
|
|
18
18
|
import { aliasFuer, zielId, knotenDa } from './entwurf.js';
|
|
19
19
|
import { zeichneErgebnis, entwurfAngefasst, angebotFrisch } from './ergebnis.js';
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
import { ausgangName } from './vorlagen.js';
|
|
23
|
-
import { musterVon } from './erkennung.js';
|
|
23
|
+
import { musterVon, rolleTrifft } from './erkennung.js';
|
|
24
24
|
|
|
25
25
|
/* Welche Aufzaehlungen aendern sich, wenn dieser Entwurf geschrieben wird?
|
|
26
26
|
|
|
@@ -35,6 +35,14 @@ import { musterVon } from './erkennung.js';
|
|
|
35
35
|
export function enumAenderungen(e) {
|
|
36
36
|
var raus = [];
|
|
37
37
|
if (!e || !e.kanal) { return raus; }
|
|
38
|
+
/* Ohne verlaesslichen Vorrat wird keine Aufzaehlung angefasst.
|
|
39
|
+
|
|
40
|
+
Was hier entstuende, waere aus der Vorlage gebaut und truege genau
|
|
41
|
+
ein Mitglied - `setObject` ersetzt das vorhandene Objekt damit
|
|
42
|
+
vollstaendig. Lieber gar kein Raum als ein Raum, der die anderen
|
|
43
|
+
Mitglieder verschluckt (09.09.2026). Die Oberflaeche sagt es am
|
|
44
|
+
Feld und im Trockenlauf. */
|
|
45
|
+
if (enumsUnbekannt()) { return raus; }
|
|
38
46
|
/* Im Aliasmodus gibt es kein gewaehltes Ziel — der Alias *ist* das Ziel.
|
|
39
47
|
Eine frueher hier stehende Abfrage brach in genau diesem Fall ab, und
|
|
40
48
|
eine im Aliasmodus geaenderte Zuordnung wurde stillschweigend nicht
|
|
@@ -343,6 +351,11 @@ export function enumZeile(host, e) {
|
|
|
343
351
|
});
|
|
344
352
|
el2.appendChild(z);
|
|
345
353
|
});
|
|
354
|
+
if (enumsUnbekannt()) {
|
|
355
|
+
var ef = el('div', 'vz zurueck');
|
|
356
|
+
ef.appendChild(el('span', null, tr('enums.unknown')));
|
|
357
|
+
el2.appendChild(ef);
|
|
358
|
+
}
|
|
346
359
|
if (katalogFehlt) {
|
|
347
360
|
var kf = el('div', 'vz zurueck');
|
|
348
361
|
kf.appendChild(el('span', null, tr('catalog.missing')));
|
|
@@ -539,14 +552,47 @@ export function uebernehmeBestand(e) {
|
|
|
539
552
|
Quelle zeigte `thermostat` statt `airCondition`, die Schreibquelle
|
|
540
553
|
war weg, und von neun Punkten blieben zwei angehakt.
|
|
541
554
|
|
|
542
|
-
|
|
543
|
-
sonst
|
|
544
|
-
sich" haette nichts mehr zu melden.
|
|
555
|
+
Auch mit Vorlage. Hier stand `if (e.vorlage) return;`, mit der
|
|
556
|
+
Begruendung, sonst uebertoene der Bestand die Vorlage stillschweigend
|
|
557
|
+
und „aendert sich" haette nichts mehr zu melden. Das war zweimal
|
|
558
|
+
falsch.
|
|
559
|
+
|
|
560
|
+
Erstens ist es nicht mehr stillschweigend: `vorlagenWert` haelt fest,
|
|
561
|
+
was die Vorlage wollte, die Zeile traegt dann „weicht von der Vorlage
|
|
562
|
+
ab", und der ←-Knopf holt jedes Feld einzeln zurueck. Genau so haelt
|
|
563
|
+
es die Zwillingsfunktion `bestandVorrang` seit jeher - sie uebernimmt
|
|
564
|
+
den Bestand auch bei gesetzter Vorlage.
|
|
565
|
+
|
|
566
|
+
Zweitens widersprachen sich damit die beiden Ansichten, und das ist
|
|
567
|
+
der Punkt, den U21 verlangt: derselbe Alias, derselbe Stand. Gemessen
|
|
568
|
+
am 08.09.2026 produktiv an `alias.0.Wohnkueche.Lavalampe`: sieben der
|
|
569
|
+
acht Punkte stimmten (sie liefen ueber `bestandVorrang`), ACTUAL
|
|
570
|
+
nicht. Der laeuft ueber den Umbenennungs-Zweig unten - die Vorlage
|
|
571
|
+
`tasmota-lampe` nennt den Punkt ON_ACTUAL, im Alias heisst er ACTUAL
|
|
572
|
+
- und blieb deshalb auf dem Vorlagenstand stehen: `sensor.light`
|
|
573
|
+
statt `state`, Beschriftung leer statt „Lavalampe ACTUAL". Ein
|
|
574
|
+
Aktualisieren aus der Quellenansicht haette beides ueberschrieben,
|
|
575
|
+
und die Marke nannte im Tooltip nur die Rolle, nicht den Namen.
|
|
576
|
+
|
|
577
|
+
`vorlagenWert` wird nur gesetzt, wo noch keiner steht: fuer die
|
|
578
|
+
Zeilen, deren Name schon passt, hat `bestandVorrang` ihn vorher
|
|
579
|
+
gefuellt - mit dem echten Vorlagenstand. Ihn hier zu ueberschreiben
|
|
580
|
+
hiesse, dem Vorlagenknopf den Wert zu nehmen, den er zurueckholen
|
|
581
|
+
soll. */
|
|
545
582
|
function vomAliasUebernehmen(z, o) {
|
|
546
|
-
if (e.vorlage) { return; }
|
|
547
583
|
var c = o.common || {}, a = c.alias || {}, q2 = aliasQuellen(o);
|
|
548
|
-
if (
|
|
549
|
-
|
|
584
|
+
if (!z.vorlagenWert) {
|
|
585
|
+
z.vorlagenWert = {
|
|
586
|
+
on: z.on, role: z.role, typ: z.typ, unit: z.unit, caption: z.caption,
|
|
587
|
+
f: z.f, fw: z.fw, srcR: z.srcR, srcW: z.srcW, states: z.states
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/* `!== undefined`, nicht bloss wahr: ein Punkt mit leerer Rolle im
|
|
591
|
+
Alias hat sie bewusst leer, und die Zwillingsfunktion
|
|
592
|
+
`bestandVorrang` fragt genauso. Solange die beiden dieselbe Frage
|
|
593
|
+
stellen, kann der Stand nicht wieder auseinanderlaufen. */
|
|
594
|
+
if (c.role !== undefined) { z.role = c.role; }
|
|
595
|
+
if (c.type !== undefined) { z.typ = c.type; }
|
|
550
596
|
z.unit = c.unit || '';
|
|
551
597
|
z.states = c.states || undefined;
|
|
552
598
|
z.f = (typeof a.read === 'string') ? a.read : '';
|
|
@@ -576,14 +622,24 @@ export function uebernehmeBestand(e) {
|
|
|
576
622
|
Belegt ist das Schreiben durch `common.write` oder durch eine
|
|
577
623
|
hinterlegte Schreibformel; bei getrennten Quellen zaehlt, was in
|
|
578
624
|
`alias.id.write` steht. */
|
|
579
|
-
z.srcW =
|
|
580
|
-
? ((c.write === true || typeof a.write === 'string') ? (q2.write || '') : '')
|
|
581
|
-
: (q2.write || '');
|
|
625
|
+
z.srcW = schreibQuelle(o);
|
|
582
626
|
if (q2.write && !q2.einfach && q2.write !== q2.read) {
|
|
583
627
|
/* Die Gegenzeile eines Paars ist keine eigene mehr - ihre Quelle
|
|
584
628
|
steckt jetzt als Schreibquelle in dieser Zeile. */
|
|
585
629
|
e.states.forEach(function (x) {
|
|
586
|
-
if (x
|
|
630
|
+
if (x === z || x.srcR !== q2.write) { return; }
|
|
631
|
+
/* Aber nur, was nicht selbst im Alias steht.
|
|
632
|
+
|
|
633
|
+
Sonst haengt es an der Reihenfolge von `Object.keys`, ob ein
|
|
634
|
+
bestehender Punkt ueberlebt: wird er vor der Paarzeile
|
|
635
|
+
verarbeitet, bleibt er aus, die Aenderungs-Karte meldet „wird
|
|
636
|
+
entfernt", und das Schreiben loescht ihn — ohne dass ihn
|
|
637
|
+
jemand abgewaehlt haette. Heute rettet ihn die Schleife
|
|
638
|
+
darunter (`if (!s.on) s.on = true`), weil in allen 16
|
|
639
|
+
mitgelieferten Vorlagen die Stellzeile vor der Melderzeile
|
|
640
|
+
steht; das ist Glueck, keine Zusicherung (09.09.2026). */
|
|
641
|
+
if (x.n && S.objects[e.ziel + '.' + x.n]) { return; }
|
|
642
|
+
x.on = false;
|
|
587
643
|
});
|
|
588
644
|
}
|
|
589
645
|
e.bestandZog = true;
|
|
@@ -662,9 +718,7 @@ export function uebernehmeBestand(e) {
|
|
|
662
718
|
Schreibquelle, OPEN, SET und pct verloren sie - ein
|
|
663
719
|
Aktualisieren haette den Rollladen ueber den Alias unfahrbar
|
|
664
720
|
gemacht (Ricardo, 08.09.2026). */
|
|
665
|
-
srcW:
|
|
666
|
-
? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
|
|
667
|
-
: (q.write || ''),
|
|
721
|
+
srcW: schreibQuelle(o),
|
|
668
722
|
f: (typeof a.read === 'string') ? a.read : '',
|
|
669
723
|
fw: (typeof a.write === 'string') ? a.write : '',
|
|
670
724
|
caption: (nm && nm !== n) ? nm : '',
|
|
@@ -727,7 +781,7 @@ export function unterschiede(e) {
|
|
|
727
781
|
if (!mu || !rolle) { return false; }
|
|
728
782
|
return mu.states.some(function (st) {
|
|
729
783
|
if (!st.role) { return false; }
|
|
730
|
-
|
|
784
|
+
return rolleTrifft(st.role, rolle);
|
|
731
785
|
});
|
|
732
786
|
}
|
|
733
787
|
/* Was die Vorlage von sich aus vorgeschlagen hat. Ein Punkt, der
|
package/admin/js/zustand.js
CHANGED
|
@@ -23,7 +23,10 @@ export var S = {
|
|
|
23
23
|
baumModusVorher: 'quellen',
|
|
24
24
|
aufgeklappt: {},
|
|
25
25
|
alleKlapp: null,
|
|
26
|
-
|
|
26
|
+
/* Die laufenden Wertabos, als Liste von Mustern. War einmal ein
|
|
27
|
+
einzelnes Muster - seit im Aliasmodus auch die Quellen abonniert
|
|
28
|
+
werden, sind es mehrere (08.09.2026). */
|
|
29
|
+
abo: [],
|
|
27
30
|
trockenAlle: null,
|
|
28
31
|
/* Vergleich im Trockenlauf: Felder geordnet (Vorgabe) oder in der
|
|
29
32
|
Reihenfolge, in der sie wirklich im Speicher stehen. */
|
|
@@ -39,6 +42,9 @@ export var S = {
|
|
|
39
42
|
loeschZiel: null,
|
|
40
43
|
loeschAlleAusgaenge: false,
|
|
41
44
|
verlegeZiel: null,
|
|
45
|
+
/* Einmal registrierter Schliesser fuer die Quellenauswahl am
|
|
46
|
+
Sprungknopf - siehe ergebnis.js. */
|
|
47
|
+
sprungZuhoerer: false,
|
|
42
48
|
VORLAGEN: [],
|
|
43
49
|
vorlagenUnvollstaendig: false,
|
|
44
50
|
fokusOrdnerfeld: false,
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
werden oben bei der Musterwahl gebaut und hier nur eingehaengt. */
|
|
8
8
|
|
|
9
9
|
import { S } from './zustand.js';
|
|
10
|
-
import { el } from './basis.js';
|
|
10
|
+
import { el, $ } from './basis.js';
|
|
11
11
|
import { tr } from './sprache.js';
|
|
12
12
|
import { erkenneEntwurf, musterVon, rolleVonPlatz } from './erkennung.js';
|
|
13
13
|
import { kindZustaende, wertVon, fmt } from './werte.js';
|
|
14
|
-
import { steuerKanaele, vorlagenAbweichung, bestandsAbweichung, waehle } from './entwurf.js';
|
|
14
|
+
import { steuerKanaele, vorlagenAbweichung, bestandsAbweichung, waehle, quelleHier, quellenVerteilung } from './entwurf.js';
|
|
15
15
|
import { musterName } from './musternamen.js';
|
|
16
16
|
import { detailZeile } from './detail.js';
|
|
17
17
|
import { rateAnzahl, rateZurueck, rateZurueckEine, rateBehalten } from './vorschlagen.js';
|
|
@@ -35,16 +35,13 @@ export function berechnePlaetze(e, haupt) {
|
|
|
35
35
|
Object.keys(platzVon).forEach(function (n) {
|
|
36
36
|
platzAnzahl[platzVon[n]] = (platzAnzahl[platzVon[n]] || 0) + 1;
|
|
37
37
|
});
|
|
38
|
-
|
|
38
|
+
/* Hier stand ein voller Detektorlauf gegen das info-Muster. Sein
|
|
39
|
+
Ergebnis wanderte ueber `imInfo` und `infoNamen` in einen Parameter
|
|
40
|
+
von `setzeKnoepfe`, den dort niemand liest — bei JEDEM Zeichnen,
|
|
41
|
+
also bei laufenden Werten alle 700 ms (gefunden 09.09.2026).
|
|
42
|
+
`imInfo` bleibt als leeres Objekt stehen, damit die Aufrufer
|
|
43
|
+
unveraendert bleiben. */
|
|
39
44
|
var imInfo = {};
|
|
40
|
-
if (infoFund.length && haupt && haupt.type !== 'info') {
|
|
41
|
-
infoFund[0].states.forEach(function (x) {
|
|
42
|
-
if (x.id) {
|
|
43
|
-
var kurz = x.id.slice(S.current.length + 1);
|
|
44
|
-
if (!platzVon[kurz]) { imInfo[kurz] = true; }
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
45
|
|
|
49
46
|
/* Mehrere Kanaele, die fuer sich ein Geraet ergeben? Einmal rechnen,
|
|
50
47
|
am Entwurf merken — der Hinweis in der Karte und die Knoepfe unten
|
|
@@ -98,6 +95,8 @@ export function berechnePlaetze(e, haupt) {
|
|
|
98
95
|
uebernommenen Zeilen hatten im thermostat-Muster keinen Platz und
|
|
99
96
|
wurden abgewaehlt, uebrig blieben zwei. */
|
|
100
97
|
var bestandDa = !!(e.ziel && kindZustaende(e.ziel).length > 0);
|
|
98
|
+
|
|
99
|
+
|
|
101
100
|
if (!e.vorbelegt && !e.vorlage && S.current.indexOf('alias.') !== 0) {
|
|
102
101
|
e.vorbelegt = true;
|
|
103
102
|
if (bestandDa) {
|
|
@@ -133,6 +132,38 @@ export function berechnePlaetze(e, haupt) {
|
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
135
|
+
/* Bezugspunkt der Marken: die Quelle, an der ich gerade STEHE.
|
|
136
|
+
|
|
137
|
+
Markiert wird, was nicht von hier kommt. Erst lag der Bezug beim
|
|
138
|
+
angeklickten Knoten - dann bekamen an einer Sparte (`…Licht.stat`)
|
|
139
|
+
alle Zeilen aus `…Licht.tele` eine Marke, obwohl es dasselbe Geraet
|
|
140
|
+
ist. Danach lag er bei der Hauptquelle - dann waren an
|
|
141
|
+
`…Stromzaehler.stat` genau die Zeilen markiert, die von DORT kamen,
|
|
142
|
+
also die falschen (Ricardo, 08.09.2026).
|
|
143
|
+
|
|
144
|
+
`quelleHier` loest beides: es sucht den Eintrag der Verteilung, zu
|
|
145
|
+
dem dieser Knoten gehoert - Sparten fallen darin schon zusammen. Am
|
|
146
|
+
Alias selbst steht man nirgends; dort gilt die Hauptquelle. Findet
|
|
147
|
+
sich gar kein Bezug (ein Sammelknoten weit ueber den Quellen), wird
|
|
148
|
+
nichts markiert - eine Marke ohne Standpunkt sagt nichts. */
|
|
149
|
+
var istAliasSicht = S.current.indexOf('alias.') === 0;
|
|
150
|
+
var aliasFuerMarke = istAliasSicht
|
|
151
|
+
? S.current
|
|
152
|
+
: (e.ziel && kindZustaende(e.ziel).length ? e.ziel : '');
|
|
153
|
+
/* Am Alias steht man nirgends - dort tragen ALLE Zeilen ihren Tag,
|
|
154
|
+
sobald es mehrere Quellen gibt.
|
|
155
|
+
|
|
156
|
+
Vorher galt auch dort die Mehrheit, und nur die Minderheit war
|
|
157
|
+
markiert. Das ist willkuerlich: bei drei zu drei entscheidet, wessen
|
|
158
|
+
Punkt alphabetisch zuerst kommt, und ein Umbenennen kippt die
|
|
159
|
+
Markierung auf die andere Haelfte (Ricardo, 08.09.2026). Traegt
|
|
160
|
+
jede Zeile ihren Tag, gibt es keine Mehrheit mehr zu bestimmen. */
|
|
161
|
+
var vert = aliasFuerMarke ? quellenVerteilung(aliasFuerMarke) : [];
|
|
162
|
+
var alleTaggen = istAliasSicht && vert.length > 1;
|
|
163
|
+
var hauptQuelle = aliasFuerMarke
|
|
164
|
+
? (istAliasSicht ? '' : (quelleHier(aliasFuerMarke, S.current) || ''))
|
|
165
|
+
: (e.kanal || '');
|
|
166
|
+
|
|
136
167
|
var platzVon = pl.platzVon, platzAnzahl = pl.platzAnzahl;
|
|
137
168
|
|
|
138
169
|
/* --- Gerätekarte ---
|
|
@@ -210,6 +241,7 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
210
241
|
dazu haengt sich erst danach in die Leiste, weil vorher niemand
|
|
211
242
|
weiss, ob es ueberhaupt eine braucht. */
|
|
212
243
|
var ohnePlatzZahl = 0;
|
|
244
|
+
var ohnePlatzNamen = [];
|
|
213
245
|
var an = e.states.filter(function (s) { return s.on; }).length;
|
|
214
246
|
bar.appendChild(document.createTextNode(tr('list.willBeCreated')));
|
|
215
247
|
bar.appendChild(el('span', 'cnt', tr('list.countOf', an, e.states.length)));
|
|
@@ -325,6 +357,16 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
325
357
|
});
|
|
326
358
|
card.appendChild(cap);
|
|
327
359
|
|
|
360
|
+
/* Platz fuer die Legende. Sie steht zwischen Kopfzeile und erster
|
|
361
|
+
Zeile — nicht mehr oben in der Leiste, wo sie wie eine Marke zum
|
|
362
|
+
Geraet aussah und niemand sie mit der Toenung der Zeilen darunter
|
|
363
|
+
verband (Ricardo, 08.09.2026). Hier ist sie selbst ein Beispiel:
|
|
364
|
+
dieselbe Toenung, dieselbe Breite, unmittelbar ueber dem, was sie
|
|
365
|
+
erklaert. Gefuellt wird sie erst nach dem Zeichnen der Zeilen —
|
|
366
|
+
vorher weiss niemand, ob es etwas zu erklaeren gibt. */
|
|
367
|
+
var legendenPlatz = el('div');
|
|
368
|
+
card.appendChild(legendenPlatz);
|
|
369
|
+
|
|
328
370
|
/* --- Zustandsliste --- */
|
|
329
371
|
e.states.forEach(function (s, i) {
|
|
330
372
|
var pl = platzVon[s.n];
|
|
@@ -337,7 +379,7 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
337
379
|
einmal als Legende ueber der Liste, und der lange Text haengt als
|
|
338
380
|
Hinweis an der Zeile. */
|
|
339
381
|
var ohnePlatz = (s.on && !unfertig && !pl);
|
|
340
|
-
if (ohnePlatz) { ohnePlatzZahl++; }
|
|
382
|
+
if (ohnePlatz) { ohnePlatzZahl++; ohnePlatzNamen.push(s.n); }
|
|
341
383
|
var row = el('div', 'erow' + (s.on ? '' : ' skip') + (S.openRow === i ? ' open' : '') +
|
|
342
384
|
(ohnePlatz ? ' ohneplatz' : ''));
|
|
343
385
|
row.tabIndex = 0;
|
|
@@ -367,10 +409,16 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
367
409
|
row.appendChild(mk);
|
|
368
410
|
|
|
369
411
|
var n3 = el('span', 'nm3', s.n || tr('list.newPoint'));
|
|
370
|
-
/* Die Beschriftung (kuenftiger Anzeigename) steht
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
412
|
+
/* Die Beschriftung (kuenftiger Anzeigename) steht im Hinweis an der
|
|
413
|
+
Kennung, nicht in der Zeile.
|
|
414
|
+
|
|
415
|
+
Sie stand einmal gedaempft dahinter - wie die Namen im Baum, und
|
|
416
|
+
aus demselben Grund: wer sie im Detail tippt, soll eine Wirkung
|
|
417
|
+
sehen. Seit die Zeile auch Marken traegt („aus mqtt-client.0",
|
|
418
|
+
„geaendert", „weicht ab") wurde sie damit zu voll, und Rolle und
|
|
419
|
+
Wert rutschten nach rechts weg (Ricardo, 08.09.2026). Die Kennung
|
|
420
|
+
traegt jetzt den Hinweis; sichtbar bleibt sie im Detail. */
|
|
421
|
+
if (s.caption && s.caption !== s.n) { n3.title = s.caption; }
|
|
374
422
|
if (s.manuell) { n3.appendChild(el('span', 'hand', tr('list.byHand'))); }
|
|
375
423
|
if (s.geaendert) { n3.appendChild(el('span', 'geae', tr('list.changed'))); }
|
|
376
424
|
/* Was die Vorlage anders will, steht an der Zeile - sonst sieht man
|
|
@@ -409,7 +457,14 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
409
457
|
Marken, jede mit eigener Bedeutung: „geaendert" heisst, der
|
|
410
458
|
Nutzer war es; „weicht von der Vorlage ab" vergleicht mit der
|
|
411
459
|
Vorlage; diese hier mit der Datenbank (Ricardo, 06.09.2026). */
|
|
412
|
-
|
|
460
|
+
/* `e.ziel || e.kanal` — wie bei den beiden Nachbarn.
|
|
461
|
+
|
|
462
|
+
Im Aliasmodus ist `e.ziel` nie gesetzt: Man steht ja schon auf dem
|
|
463
|
+
Alias, das Ziel waehlt man erst im Quellenmodus. Die Marke „weicht vom
|
|
464
|
+
Alias ab" erschien dort also NIE — ausgerechnet dort, wo sie am
|
|
465
|
+
meisten sagt (gemessen 09.09.2026: mit `e.ziel` null Abweichungen, mit
|
|
466
|
+
`e.ziel || e.kanal` eine). */
|
|
467
|
+
var bAbw = bestandsAbweichung(s, e.ziel || e.kanal);
|
|
413
468
|
if (bAbw.length) {
|
|
414
469
|
var bv = el('span', 'geae bstabw', tr('list.aliasDiffers'));
|
|
415
470
|
bv.title = bAbw.map(function (x) {
|
|
@@ -421,6 +476,41 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
421
476
|
/* Die Marke ist zugleich der Griff: solange sie steht, ist die Zeile
|
|
422
477
|
unbestaetigt und faellt beim Zuruecknehmen. Ein Haken laesst sie
|
|
423
478
|
verschwinden, ein Kreuz nimmt genau diese eine zurueck. */
|
|
479
|
+
/* Der Punkt liest woanders her als der Rest.
|
|
480
|
+
|
|
481
|
+
Bewusst eine Marke und keine Flaechentoenung: die Flaeche ist
|
|
482
|
+
schon vergeben („kein Platz im Muster"), und beides gilt
|
|
483
|
+
unabhaengig voneinander - eine Zeile kann getoent und fremd sein.
|
|
484
|
+
Die Marke traegt nur den Adapter, nicht die ganze Kennung; die
|
|
485
|
+
volle steht im Hinweis. Und sie ist ruhig gehalten: sie meldet
|
|
486
|
+
keinen Fehler, sie sagt, woher der Wert kommt. */
|
|
487
|
+
var vonHier = !!hauptQuelle && s.srcR &&
|
|
488
|
+
(s.srcR === hauptQuelle || s.srcR.indexOf(hauptQuelle + '.') === 0);
|
|
489
|
+
if (s.on && s.srcR && (alleTaggen || (hauptQuelle && !vonHier))) {
|
|
490
|
+
/* Der Tag nennt die QUELLE, nicht den Adapter.
|
|
491
|
+
|
|
492
|
+
Er trug erst nur `mqtt-client.0` - was reichte, solange nur die
|
|
493
|
+
Ausreisser markiert waren. Seit am Alias jede Zeile einen Tag
|
|
494
|
+
traegt, muss er unterscheiden: `alias.0.Solar.Balkon.Einspeisung`
|
|
495
|
+
liest aus zwei mqtt-Quellen, und beide hiessen „aus
|
|
496
|
+
mqtt-client.0" (08.09.2026).
|
|
497
|
+
|
|
498
|
+
Gezeigt werden die letzten zwei Glieder des Quellknotens -
|
|
499
|
+
`Solar_Balkon`, `HM600.ch0`, `Balkon.Einspeisung`. Kurz genug
|
|
500
|
+
fuer die Zeile, lang genug zum Unterscheiden; die volle Kennung
|
|
501
|
+
steht im Hinweis. */
|
|
502
|
+
var qHier = aliasFuerMarke ? (quelleHier(aliasFuerMarke, s.srcR) || '') : '';
|
|
503
|
+
var basis = qHier || s.srcR.slice(0, s.srcR.lastIndexOf('.'));
|
|
504
|
+
var teileQ = basis.split('.');
|
|
505
|
+
var kurzQ = teileQ.slice(Math.max(2, teileQ.length - 2)).join('.') || basis;
|
|
506
|
+
var fq = el('span', 'geae fremdquelle', tr('list.otherSource', kurzQ));
|
|
507
|
+
/* Am Alias ist es eine blosse Herkunftsangabe, an einer Quelle
|
|
508
|
+
ein Unterschied - der Hinweis sagt beides jeweils richtig. */
|
|
509
|
+
fq.title = alleTaggen
|
|
510
|
+
? tr('list.sourceHint', s.srcR)
|
|
511
|
+
: tr('list.otherSourceHint', hauptQuelle, s.srcR);
|
|
512
|
+
n3.appendChild(fq);
|
|
513
|
+
}
|
|
424
514
|
if (s.geraten) {
|
|
425
515
|
n3.appendChild(el('span', 'rat', tr('guess.mark')));
|
|
426
516
|
var jaK = el('span', 'ratknopf', '\u2713');
|
|
@@ -554,16 +644,57 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
554
644
|
});
|
|
555
645
|
card.appendChild(add);
|
|
556
646
|
|
|
557
|
-
/* Die Legende
|
|
558
|
-
|
|
559
|
-
|
|
647
|
+
/* Die Legende — einmal, und nur wenn es etwas zu erklaeren gibt.
|
|
648
|
+
|
|
649
|
+
Zwei Anlaeufe hat sie gebraucht. Zuerst stand sie in der Leiste mit
|
|
650
|
+
einem 14 px grossen Farbkaestchen daneben: die Toenung ist
|
|
651
|
+
absichtlich blass, das Kaestchen war winzig, der Text in --ink-3 —
|
|
652
|
+
zusammen sah man nichts. Dann als hinterlegte Marke in der Leiste:
|
|
653
|
+
sichtbar, aber sie las sich wie eine Aussage ueber das GERAET, und
|
|
654
|
+
dass die getoenten Zeilen darunter gemeint sind, sagte sie nicht
|
|
655
|
+
(Ricardo, 08.09.2026, beide Male).
|
|
656
|
+
|
|
657
|
+
Jetzt steht sie in der Liste selbst, direkt ueber der ersten Zeile,
|
|
658
|
+
in derselben Toenung und ueber dieselbe Breite. Sie erklaert die
|
|
659
|
+
Toenung nicht, sie zeigt sie.
|
|
660
|
+
|
|
661
|
+
Die lange Erklaerung steht hinter einem „i" daneben — sie ersetzt
|
|
662
|
+
die Karte, die frueher unten stand („Gehoert nicht zum <Muster>").
|
|
663
|
+
Zweimal dieselbe Auskunft, einmal oben und einmal weit unten, war
|
|
664
|
+
eine zu viel.
|
|
665
|
+
|
|
666
|
+
Als Tooltip taugte sie nicht: der Kasten wurde ueber zehn Zeilen
|
|
667
|
+
hoch und legte sich ueber die Liste, die man gerade lesen wollte
|
|
668
|
+
(Ricardo, 08.09.2026). Ein Klick auf das „i" oeffnet sie stattdessen
|
|
669
|
+
als Dialog — dort darf sie so lang sein, wie sie ist. */
|
|
560
670
|
if (ohnePlatzZahl) {
|
|
561
|
-
var
|
|
562
|
-
lg
|
|
563
|
-
lg.appendChild(document.createTextNode(
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
671
|
+
var mn = musterName(e.want) || e.want || '?';
|
|
672
|
+
var lg = el('div', 'legendenzeile');
|
|
673
|
+
lg.appendChild(document.createTextNode(tr('pattern.noPlaceLegend', mn)));
|
|
674
|
+
|
|
675
|
+
var ib = el('button', 'infoknopf', 'i');
|
|
676
|
+
ib.type = 'button';
|
|
677
|
+
ib.title = tr('info.more');
|
|
678
|
+
ib.setAttribute('aria-label', tr('info.more'));
|
|
679
|
+
ib.addEventListener('click', function (ev) {
|
|
680
|
+
ev.stopPropagation();
|
|
681
|
+
var t = $('#info-titel');
|
|
682
|
+
/* „Gehoert nicht zum Steckdose" waere falsches Deutsch — mit
|
|
683
|
+
„-Muster" dahinter stimmt es fuer jeden Musternamen. */
|
|
684
|
+
if (t) { t.textContent = tr('info.dlgTitle', mn); }
|
|
685
|
+
var b = $('#info-body');
|
|
686
|
+
if (b) {
|
|
687
|
+
b.textContent = '';
|
|
688
|
+
var k = el('div', 'aside w');
|
|
689
|
+
k.appendChild(el('b', null, ohnePlatzNamen.join(', ')));
|
|
690
|
+
k.appendChild(document.createTextNode(' — ' + tr('info.explain', mn)));
|
|
691
|
+
b.appendChild(k);
|
|
692
|
+
}
|
|
693
|
+
var d = $('#dlg-info');
|
|
694
|
+
if (d && !d.open) { d.showModal(); }
|
|
695
|
+
});
|
|
696
|
+
lg.appendChild(ib);
|
|
697
|
+
legendenPlatz.appendChild(lg);
|
|
567
698
|
}
|
|
568
699
|
|
|
569
700
|
host.appendChild(card);
|
package/admin/tab.html
CHANGED
|
@@ -11,10 +11,24 @@
|
|
|
11
11
|
Vorgabepalette auf. Ohne Parameter (Reiter direkt geoeffnet)
|
|
12
12
|
entscheidet die Systemeinstellung hell/dunkel. */
|
|
13
13
|
(function () {
|
|
14
|
+
/* Der Admin hat mehr Themen als vier.
|
|
15
|
+
|
|
16
|
+
`io-package.json` laesst Admin ab 7.6.20 zu, und dort gibt es
|
|
17
|
+
auch `blue` (dunkel) und `colored` (hell). Beide fielen durch
|
|
18
|
+
diese Pruefung, das Attribut blieb ungesetzt — und dann
|
|
19
|
+
entscheidet die SYSTEMeinstellung: ein dunkler Admin auf einem
|
|
20
|
+
hell eingestellten Rechner bekam die helle Palette mitten in
|
|
21
|
+
seine dunkle Umgebung. Entschieden wird deshalb nach der
|
|
22
|
+
Helligkeit, nicht nach dem Namen; unbekannte Namen fallen
|
|
23
|
+
weiterhin auf die Systemeinstellung zurueck, das ist die
|
|
24
|
+
ehrlichste Vermutung. */
|
|
25
|
+
var HELLIGKEIT = {
|
|
26
|
+
modernLight: 'modernLight', light: 'light', colored: 'modernLight',
|
|
27
|
+
modernDark: 'modernDark', dark: 'dark', blue: 'modernDark'
|
|
28
|
+
};
|
|
14
29
|
var setze = function (name) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
30
|
+
var w = HELLIGKEIT[name];
|
|
31
|
+
if (w) { document.documentElement.setAttribute('data-adminthema', w); }
|
|
18
32
|
};
|
|
19
33
|
var m = /[?&]react=([A-Za-z]+)(?:&|$)/.exec(location.search);
|
|
20
34
|
if (m) { setze(m[1]); }
|
|
@@ -120,6 +134,14 @@
|
|
|
120
134
|
</div>
|
|
121
135
|
</dialog>
|
|
122
136
|
|
|
137
|
+
<dialog id="dlg-info" style="max-width:600px;">
|
|
138
|
+
<h3 id="info-titel">Gehört nicht zum Muster</h3>
|
|
139
|
+
<div class="dbody" id="info-body"></div>
|
|
140
|
+
<div class="dfoot">
|
|
141
|
+
<button class="btn primary" data-close>Schließen</button>
|
|
142
|
+
</div>
|
|
143
|
+
</dialog>
|
|
144
|
+
|
|
123
145
|
<dialog id="dlg-guess" style="max-width:560px;">
|
|
124
146
|
<h3 id="guess-titel">Freie Plätze trotz Vorlage?</h3>
|
|
125
147
|
<div class="dbody" id="guess-body"></div>
|
|
@@ -50,14 +50,14 @@
|
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"name": "ON_TIME",
|
|
53
|
-
"rolle": "level.timer",
|
|
53
|
+
"rolle": "level.timer.off",
|
|
54
54
|
"typ": "number",
|
|
55
55
|
"einheit": "s",
|
|
56
56
|
"lesen": "1.ON_TIME",
|
|
57
57
|
"schreiben": "1.ON_TIME",
|
|
58
58
|
"optional": true,
|
|
59
59
|
"vorgabeAus": true,
|
|
60
|
-
"hinweis": "Einschaltdauer in Sekunden.
|
|
60
|
+
"hinweis": "Einschaltdauer in Sekunden. Der Platz ON_TIME im Muster nimmt ihn auf."
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
"name": "INHIBIT",
|