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.
- package/LICENSE +21 -0
- package/README.md +516 -0
- package/admin/alias-workbench.png +0 -0
- package/admin/css/werkbank.css +916 -0
- package/admin/detector.js +1 -0
- package/admin/geraetewissen/tasmota-befehle.json +116 -0
- package/admin/i18n/de.json +552 -0
- package/admin/i18n/en.json +552 -0
- package/admin/js/aufzaehlungen.js +332 -0
- package/admin/js/basis.js +36 -0
- package/admin/js/baum.js +453 -0
- package/admin/js/detail.js +335 -0
- package/admin/js/einstellungen.js +40 -0
- package/admin/js/entwurf.js +908 -0
- package/admin/js/enums.js +23 -0
- package/admin/js/ergebnis.js +934 -0
- package/admin/js/erkennung.js +126 -0
- package/admin/js/katalog.js +313 -0
- package/admin/js/mqtt.js +885 -0
- package/admin/js/musternamen.js +94 -0
- package/admin/js/musterwahl.js +334 -0
- package/admin/js/objekte.js +243 -0
- package/admin/js/pruefungen.js +130 -0
- package/admin/js/quellentausch.js +570 -0
- package/admin/js/rollenwahl.js +199 -0
- package/admin/js/schreiben.js +1537 -0
- package/admin/js/sprache.js +137 -0
- package/admin/js/start.js +39 -0
- package/admin/js/verbindung.js +10 -0
- package/admin/js/vorlagen.js +572 -0
- package/admin/js/vorlagenblatt.js +1907 -0
- package/admin/js/vorschlagen.js +391 -0
- package/admin/js/werte.js +193 -0
- package/admin/js/zielleiste.js +339 -0
- package/admin/js/zuordnung.js +695 -0
- package/admin/js/zustand.js +47 -0
- package/admin/js/zustandsliste.js +515 -0
- package/admin/jsonConfig.json +249 -0
- package/admin/tab.html +148 -0
- package/admin/vorlagen/hm-dimmer.json +204 -0
- package/admin/vorlagen/hm-fensterkontakt-hm.json +176 -0
- package/admin/vorlagen/hm-fensterkontakt-hmip.json +191 -0
- package/admin/vorlagen/hm-heizgruppe.json +214 -0
- package/admin/vorlagen/hm-heizkoerper.json +236 -0
- package/admin/vorlagen/hm-rollladen.json +190 -0
- package/admin/vorlagen/hm-schaltaktor-lampe.json +197 -0
- package/admin/vorlagen/hm-schaltaktor-mehrfach.json +203 -0
- package/admin/vorlagen/hm-schaltaktor-steckdose.json +197 -0
- package/admin/vorlagen/hm-temperatur-feuchte.json +166 -0
- package/admin/vorlagen/hm-wandthermostat.json +228 -0
- package/admin/vorlagen/index.json +21 -0
- package/admin/vorlagen/messpunkt.json +225 -0
- package/admin/vorlagen/tasmota-lampe.json +263 -0
- package/admin/vorlagen/tasmota-mehrfach.json +203 -0
- package/admin/vorlagen/tasmota-rgb.json +239 -0
- package/admin/vorlagen/tasmota-steckdose.json +265 -0
- package/io-package.json +122 -0
- package/package.json +48 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* Der geteilte Zustand der Werkbank.
|
|
2
|
+
|
|
3
|
+
Ein Objekt statt loser Variablen, weil ES-Module importierte Bindungen
|
|
4
|
+
schreibschuetzen: `S.current = ...` geht ueberall, `current = ...`
|
|
5
|
+
ginge nur im eigenen Modul. Hier liegt nur, was wirklich mehreren
|
|
6
|
+
Modulen gehoert - was ein einzelnes Modul fuer sich behaelt, bleibt
|
|
7
|
+
dort als gewoehnliche Variable.
|
|
8
|
+
|
|
9
|
+
VORLAGEN sind die eigenen und mitgelieferten Geraetevorlagen; der
|
|
10
|
+
Katalog des Admin (Raum-/Funktionsnamen samt Bildern) liegt getrennt
|
|
11
|
+
in katalog.js. */
|
|
12
|
+
|
|
13
|
+
export var S = {
|
|
14
|
+
objects: {},
|
|
15
|
+
keysSorted: [],
|
|
16
|
+
kleinIndex: {},
|
|
17
|
+
werte: {},
|
|
18
|
+
current: null,
|
|
19
|
+
openRow: null,
|
|
20
|
+
entwurf: null,
|
|
21
|
+
baum: null,
|
|
22
|
+
baumModus: 'quellen',
|
|
23
|
+
baumModusVorher: 'quellen',
|
|
24
|
+
aufgeklappt: {},
|
|
25
|
+
alleKlapp: null,
|
|
26
|
+
abo: null,
|
|
27
|
+
trockenAlle: null,
|
|
28
|
+
/* Vergleich im Trockenlauf: Felder geordnet (Vorgabe) oder in der
|
|
29
|
+
Reihenfolge, in der sie wirklich im Speicher stehen. */
|
|
30
|
+
trockenRoh: false,
|
|
31
|
+
/* Quellentausch: welcher Alias, welche neue Quelle, die Zuordnung
|
|
32
|
+
und die Punkte, die dabei entfallen sollen. */
|
|
33
|
+
tauschZiel: null,
|
|
34
|
+
tauschNeu: '',
|
|
35
|
+
tauschPlan: null,
|
|
36
|
+
tauschWeg: {},
|
|
37
|
+
tauschFilter: '',
|
|
38
|
+
loeschListe: [],
|
|
39
|
+
loeschZiel: null,
|
|
40
|
+
loeschAlleAusgaenge: false,
|
|
41
|
+
verlegeZiel: null,
|
|
42
|
+
VORLAGEN: [],
|
|
43
|
+
vorlagenUnvollstaendig: false,
|
|
44
|
+
fokusOrdnerfeld: false,
|
|
45
|
+
fokusMusterfeld: false,
|
|
46
|
+
tplZ: null
|
|
47
|
+
};
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/* Die Zustandsliste: erst die Rechnung, welcher Punkt auf welchem
|
|
2
|
+
Platz des Musters sitzt (samt Vorbelegung der Haken), dann die Karte
|
|
3
|
+
mit Filterleiste, Ratleiste, den Zeilen selbst, den freien Plaetzen
|
|
4
|
+
des Musters und dem Zufuegen von Hand.
|
|
5
|
+
|
|
6
|
+
Herausgeloest aus zeichneErgebnis. `rateKnopf` und `musterBlock`
|
|
7
|
+
werden oben bei der Musterwahl gebaut und hier nur eingehaengt. */
|
|
8
|
+
|
|
9
|
+
import { S } from './zustand.js';
|
|
10
|
+
import { el } from './basis.js';
|
|
11
|
+
import { tr } from './sprache.js';
|
|
12
|
+
import { erkenneEntwurf, musterVon, rolleVonPlatz } from './erkennung.js';
|
|
13
|
+
import { kindZustaende, wertVon, fmt } from './werte.js';
|
|
14
|
+
import { steuerKanaele, vorlagenAbweichung, waehle } from './entwurf.js';
|
|
15
|
+
import { musterName } from './musternamen.js';
|
|
16
|
+
import { detailZeile } from './detail.js';
|
|
17
|
+
import { rateAnzahl, rateZurueck, rateZurueckEine, rateBehalten } from './vorschlagen.js';
|
|
18
|
+
import { mqttEinzeln } from './mqtt.js';
|
|
19
|
+
import { zeichneErgebnis, entwurfAngefasst } from './ergebnis.js';
|
|
20
|
+
|
|
21
|
+
export function berechnePlaetze(e, haupt) {
|
|
22
|
+
/* --- welcher Punkt sitzt auf welchem Platz --- */
|
|
23
|
+
var platzVon = {};
|
|
24
|
+
if (haupt) {
|
|
25
|
+
haupt.states.forEach(function (x) {
|
|
26
|
+
if (x.id) { platzVon[x.id.slice(S.current.length + 1)] = x.name; }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/* Manche Muster haben einen Platz, der beliebig viele Punkte
|
|
30
|
+
aufnimmt — im info-Muster heisst jeder Punkt ACTUAL. Diesen Namen
|
|
31
|
+
an jede Zeile zu schreiben sagt nichts, es sieht nur aus wie ein
|
|
32
|
+
Uebersetzungsloch. Also nur zeigen, wenn er wirklich einen
|
|
33
|
+
bestimmten Punkt bezeichnet. */
|
|
34
|
+
var platzAnzahl = {};
|
|
35
|
+
Object.keys(platzVon).forEach(function (n) {
|
|
36
|
+
platzAnzahl[platzVon[n]] = (platzAnzahl[platzVon[n]] || 0) + 1;
|
|
37
|
+
});
|
|
38
|
+
var infoFund = erkenneEntwurf(e, 'info');
|
|
39
|
+
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
|
+
|
|
49
|
+
/* Mehrere Kanaele, die fuer sich ein Geraet ergeben? Einmal rechnen,
|
|
50
|
+
am Entwurf merken — der Hinweis in der Karte und die Knoepfe unten
|
|
51
|
+
lesen dasselbe Ergebnis. */
|
|
52
|
+
if (e.kanalGeraete === undefined) {
|
|
53
|
+
e.kanalGeraete = (!(e.instanzen && e.instanzen.length > 1) && S.current.indexOf('alias.') !== 0)
|
|
54
|
+
? steuerKanaele(S.current) : [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Welche Punkte aus den gemeinsamen Kanaelen haben hier, auf der
|
|
58
|
+
Geraeteebene, einen Platz gefunden? Diese Liste bekommen die
|
|
59
|
+
Kanal-Entwuerfe mit — dort erkennt der Detektor sie nicht. */
|
|
60
|
+
if (e.kanalGeraete && e.kanalGeraete.length > 1) {
|
|
61
|
+
var eigenerKanal = {};
|
|
62
|
+
e.kanalGeraete.forEach(function (k) { eigenerKanal[k.id] = 1; });
|
|
63
|
+
e.gemeinsamMitPlatz = {};
|
|
64
|
+
e.states.forEach(function (st) {
|
|
65
|
+
if (!st.srcR || !platzVon[st.n]) { return; }
|
|
66
|
+
var elt = st.srcR.slice(0, st.srcR.lastIndexOf('.'));
|
|
67
|
+
if (elt.indexOf(S.current + '.') === 0 && !eigenerKanal[elt]) {
|
|
68
|
+
e.gemeinsamMitPlatz[st.srcR] = platzVon[st.n];
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Vorbelegung eines Rohentwurfs: angehakt ist, was im erkannten Muster
|
|
74
|
+
einen Platz findet.
|
|
75
|
+
|
|
76
|
+
Vorher war alles an. Bei einer Tasmota mit acht Punkten ging das
|
|
77
|
+
durch; ein Homematic-Fensterkontakt bringt 26 mit, ein Thermostat
|
|
78
|
+
42, und die meisten davon sind Alarmspiegel und Wartungsinnereien,
|
|
79
|
+
die in keinem Muster vorkommen. Wer die alle in einen Alias schreibt,
|
|
80
|
+
hat das Geraet nicht abgebildet, sondern abgeschrieben.
|
|
81
|
+
|
|
82
|
+
Drei Faelle bleiben ausgenommen:
|
|
83
|
+
- **Ein bestehender Alias.** Dort ist jeder Haken eine Entscheidung,
|
|
84
|
+
die schon getroffen wurde; abwaehlen hiesse loeschen.
|
|
85
|
+
- **Eine Vorlage.** Die bestimmt selbst, was an ist (`vorgabeAus`).
|
|
86
|
+
- **Kein einziger Punkt mit Platz.** Dann bliebe die Liste leer, und
|
|
87
|
+
eine leere Liste hilft niemandem.
|
|
88
|
+
|
|
89
|
+
Einmalig, gemerkt in `vorbelegt` — sonst wuerde jedes Neuzeichnen
|
|
90
|
+
zurueckstellen, was der Nutzer gerade dazugewaehlt hat. Die drei
|
|
91
|
+
Knoepfe darueber bleiben, „alle" ist einen Klick entfernt. */
|
|
92
|
+
/* Steht der Alias schon da, ist er die Antwort - nicht der Vorschlag.
|
|
93
|
+
|
|
94
|
+
Die Vorbelegung hakt an, was im erkannten Muster einen Platz
|
|
95
|
+
findet. Das ist richtig fuer einen Alias, den es noch nicht gibt.
|
|
96
|
+
Gibt es ihn, hat `uebernehmeBestand` gerade angehakt, was drinsteht
|
|
97
|
+
- und die Vorbelegung machte es sofort wieder zunichte: die neun
|
|
98
|
+
uebernommenen Zeilen hatten im thermostat-Muster keinen Platz und
|
|
99
|
+
wurden abgewaehlt, uebrig blieben zwei. */
|
|
100
|
+
var bestandDa = e.ziel && S.objects[e.ziel] && kindZustaende(e.ziel).length > 0;
|
|
101
|
+
if (!e.vorbelegt && !e.vorlage && S.current.indexOf('alias.') !== 0) {
|
|
102
|
+
e.vorbelegt = true;
|
|
103
|
+
if (bestandDa) {
|
|
104
|
+
/* Angehakt ist, was im Alias steht - nicht mehr und nicht weniger.
|
|
105
|
+
|
|
106
|
+
Die Vorbelegung ganz zu ueberspringen war zu grob: der
|
|
107
|
+
Rohentwurf startet mit allem an, und dann standen bei der
|
|
108
|
+
Klimaanlage 49 Zeilen angehakt statt neun. Schlimmer noch, der
|
|
109
|
+
Detektor gab den ACTUAL-Platz danach an `status_outdoorTemperature`
|
|
110
|
+
- die Aussentemperatur, weil sie dieselbe Rolle traegt und
|
|
111
|
+
frueher in der Liste steht. Wer nur das Erwartete anhakt,
|
|
112
|
+
bekommt auch nur das Erwartete erkannt. */
|
|
113
|
+
e.states.forEach(function (s) { s.on = !!s.ausBestand; });
|
|
114
|
+
} else if (e.states.some(function (s) { return !!platzVon[s.n]; })) {
|
|
115
|
+
e.states.forEach(function (s) { s.on = !!platzVon[s.n]; });
|
|
116
|
+
}
|
|
117
|
+
/* „Als ein Geraet" muss halten, was es sagt: alles, was dieses Geraet
|
|
118
|
+
schaltet, gehoert hinein. Das Muster hat aber nur einen Platz dafuer
|
|
119
|
+
— bei einem Zweifach-Aktor bekaeme ihn Kanal 2, und Kanal 1 fiele
|
|
120
|
+
stillschweigend weg. Ein Alias, dem heimlich ein Licht fehlt, ist
|
|
121
|
+
schlimmer als einer mit einem Punkt zu viel. Also kommen die
|
|
122
|
+
Pflichtpunkte der anderen Kanaele mit dazu; dass sie keinen Platz
|
|
123
|
+
finden, steht als Hinweis darueber. */
|
|
124
|
+
(e.kanalGeraete || []).forEach(function (k) {
|
|
125
|
+
(k.pflicht || []).forEach(function (pid) {
|
|
126
|
+
var kurz = pid.slice(S.current.length + 1).replace(/\./g, '_');
|
|
127
|
+
e.states.forEach(function (st) { if (st.n === kurz) { st.on = true; } });
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { platzVon: platzVon, platzAnzahl: platzAnzahl, imInfo: imInfo };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
136
|
+
var platzVon = pl.platzVon, platzAnzahl = pl.platzAnzahl;
|
|
137
|
+
|
|
138
|
+
/* --- Gerätekarte ---
|
|
139
|
+
|
|
140
|
+
Hier stand ein Streifen mit Schaltbild, Geraetenamen und bis zu drei
|
|
141
|
+
Messwerten. Er sagte nichts, was nicht schon dastand: der Name steht
|
|
142
|
+
als Ueberschrift darueber, die Messwerte stehen in der Liste
|
|
143
|
+
darunter mit Rolle und Einheit. Zwei Zeilen Hoehe fuer eine
|
|
144
|
+
Wiederholung - weg damit, in beiden Ansichten. */
|
|
145
|
+
var card = el('div', 'card');
|
|
146
|
+
|
|
147
|
+
/* Ein Satz dazu, warum unten zwei Knoepfe stehen. Ohne ihn klickt man
|
|
148
|
+
auf eine Zahl und weiss nicht, was sie bedeutet. */
|
|
149
|
+
if (e.kanalGeraete && e.kanalGeraete.length > 1) {
|
|
150
|
+
var hk = el('div', 'aside');
|
|
151
|
+
hk.style.margin = '0 0 9px';
|
|
152
|
+
hk.textContent = tr('result.channelsHint', e.kanalGeraete.length);
|
|
153
|
+
card.appendChild(hk);
|
|
154
|
+
|
|
155
|
+
/* Wie viele angehakte Punkte sind Schalter, fuer die das Muster
|
|
156
|
+
keinen Platz hat? Genau die, die eben dazugekommen sind. */
|
|
157
|
+
var ohnePlatz = 0;
|
|
158
|
+
e.kanalGeraete.forEach(function (k) {
|
|
159
|
+
(k.pflicht || []).forEach(function (pid) {
|
|
160
|
+
var kurz = pid.slice(S.current.length + 1).replace(/\./g, '_');
|
|
161
|
+
e.states.forEach(function (st) {
|
|
162
|
+
if (st.n === kurz && st.on && !platzVon[st.n]) { ohnePlatz++; }
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
if (ohnePlatz) {
|
|
167
|
+
var hw = el('div', 'aside w');
|
|
168
|
+
hw.style.margin = '0 0 9px';
|
|
169
|
+
hw.textContent = tr('result.extraSwitches', ohnePlatz, e.want || '?');
|
|
170
|
+
card.appendChild(hw);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* --- Werkzeugleiste ---
|
|
175
|
+
|
|
176
|
+
Hier sitzt auch die Musterwahl. Sie stand vorher oben rechts im
|
|
177
|
+
Kopf, weit weg von der Liste, deren Inhalt sie bestimmt: welches
|
|
178
|
+
Muster gilt, entscheidet, welcher Punkt einen Platz hat und welcher
|
|
179
|
+
ins info-Geraet wandert. Jetzt steht sie direkt ueber dieser Liste,
|
|
180
|
+
in derselben Zeile wie die Filter. */
|
|
181
|
+
/* Angehaktes nach oben - einmal, beim ersten Aufbau.
|
|
182
|
+
|
|
183
|
+
Wer links einen Knoten anklickt, will sehen, was daraus entsteht.
|
|
184
|
+
Bei einem Fensterkontakt sind das vier Punkte von 26, bei der
|
|
185
|
+
Klimaanlage neun von 55 - und die standen bisher ueber die ganze
|
|
186
|
+
Liste verstreut, weil sortiert wird, wie das Geraet seine Punkte
|
|
187
|
+
fuehrt.
|
|
188
|
+
|
|
189
|
+
Einmal und nicht bei jedem Haken: eine Liste, die unter der Hand
|
|
190
|
+
umspringt, sobald man etwas abwaehlt, ist schlimmer als eine
|
|
191
|
+
unsortierte. Die Marke sitzt am Entwurf, und der Entwurf wird bei
|
|
192
|
+
jeder neuen Auswahl neu gebaut - beim naechsten Anklicken steht
|
|
193
|
+
also wieder das Angehakte oben.
|
|
194
|
+
|
|
195
|
+
Stabil innerhalb der beiden Gruppen: die Reihenfolge der
|
|
196
|
+
angehakten Punkte bleibt, wie sie war. Das ist nicht nur
|
|
197
|
+
Ordnungsliebe - `abbild` nimmt nur angehakte Zeilen, und welcher
|
|
198
|
+
von zwei gleich passenden Punkten einen Platz bekommt, entscheidet
|
|
199
|
+
der Detektor nach der Reihenfolge. Wuerde die sich verschieben,
|
|
200
|
+
verschoebe sich auch die Erkennung. */
|
|
201
|
+
if (!e.sortiert) {
|
|
202
|
+
e.sortiert = true;
|
|
203
|
+
var an0 = [], aus0 = [];
|
|
204
|
+
e.states.forEach(function (s) { (s.on ? an0 : aus0).push(s); });
|
|
205
|
+
e.states = an0.concat(aus0);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
var bar = el('div', 'listbar');
|
|
209
|
+
var an = e.states.filter(function (s) { return s.on; }).length;
|
|
210
|
+
bar.appendChild(document.createTextNode(tr('list.willBeCreated')));
|
|
211
|
+
bar.appendChild(el('span', 'cnt', tr('list.countOf', an, e.states.length)));
|
|
212
|
+
bar.appendChild(document.createTextNode(tr('list.ofDatapoints')));
|
|
213
|
+
bar.appendChild(el('span', 'sp2'));
|
|
214
|
+
/* Mittig zwischen der Zahl und den drei Filtern - zwischen zwei
|
|
215
|
+
Dehnfugen steht er von selbst in der Mitte. */
|
|
216
|
+
if (rateKnopf) { bar.appendChild(rateKnopf); rateKnopf = null; }
|
|
217
|
+
bar.appendChild(el('span', 'sp2'));
|
|
218
|
+
/* Die vier Auswahlknoepfe nur an einer Quelle. Am fertigen Alias sind
|
|
219
|
+
sie keine Auswahl, sondern ein Loeschwerkzeug: ein Haken weg heisst
|
|
220
|
+
dort, dass der Punkt beim Aktualisieren verschwindet - „nur was ins
|
|
221
|
+
Muster passt" wuerde einen Punkt ohne Platz sofort abwaehlen
|
|
222
|
+
(Ricardo, 25.08.2026). Und „Standard"/„alle" haben am Alias nichts,
|
|
223
|
+
worauf sie sich beziehen koennten. */
|
|
224
|
+
var amAlias = S.current && S.current.indexOf('alias.') === 0;
|
|
225
|
+
/* Die Auswahlknoepfe stehen in einer eigenen zweiten Zeile. In derselben
|
|
226
|
+
Zeile wie Zaehler und Musterwahl brachen sie je nach Fensterbreite
|
|
227
|
+
unterschiedlich um, und die Leiste sprang bei jedem Neuzeichnen anders
|
|
228
|
+
(Ricardo, 26.08.2026). „Freie Plaetze vorschlagen" bleibt oben - er
|
|
229
|
+
gehoert zur Erkennung, nicht zur Auswahl. */
|
|
230
|
+
var chipzeile = el('div', 'chipzeile');
|
|
231
|
+
(amAlias ? [] :
|
|
232
|
+
[[tr('list.tpl'), function () {
|
|
233
|
+
/* Die Haken, die die Vorlage vorgibt. Gibt es keinen Vorlagenwert
|
|
234
|
+
(kein bestehender Alias), ist der Oeffnungszustand die Vorgabe -
|
|
235
|
+
der stammt dann ja aus der Vorlage. */
|
|
236
|
+
e.states.forEach(function (s) {
|
|
237
|
+
s.on = s.vorlagenWert ? !!s.vorlagenWert.on : !!s.onVorgabe;
|
|
238
|
+
});
|
|
239
|
+
}, tr('list.tplHint')],
|
|
240
|
+
[tr('list.all'), function () { e.states.forEach(function (s) { s.on = true; }); }],
|
|
241
|
+
[tr('list.onlyWithValue'), function () { e.states.forEach(function (s) { s.on = wertVon(s).ok; }); }],
|
|
242
|
+
[tr('list.onlyPattern'), function () {
|
|
243
|
+
/* Frueher hiess der Chip „nur was ins Geraet gehoert“ und behielt
|
|
244
|
+
bloss die aktuellen Platzhalter — als einziger der Reihe hatte
|
|
245
|
+
er damit ein Gedaechtnis: nach „alle“ lieferte er mehr als
|
|
246
|
+
direkt nach dem Oeffnen (Ricardo, 25.08.2026). Jetzt treten
|
|
247
|
+
alle Zeilen zur Platzvergabe an, unabhaengig vom Haken, und
|
|
248
|
+
angehakt wird genau, wer einen Platz bekommt. */
|
|
249
|
+
var kopie = JSON.parse(JSON.stringify({ kanal: e.kanal, states: e.states }));
|
|
250
|
+
kopie.states.forEach(function (s) { s.on = true; });
|
|
251
|
+
var f = erkenneEntwurf(kopie, e.want);
|
|
252
|
+
var drin = {};
|
|
253
|
+
if (f.length) {
|
|
254
|
+
f[0].states.forEach(function (x) {
|
|
255
|
+
if (x.id) { drin[x.id.slice(e.kanal.length + 1)] = true; }
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
e.states.forEach(function (s) { s.on = !!drin[s.n]; });
|
|
259
|
+
}, tr('list.onlyPatternHint')]]).forEach(function (pr) {
|
|
260
|
+
var b = el('button', null, pr[0]);
|
|
261
|
+
if (pr[2]) { b.title = pr[2]; }
|
|
262
|
+
b.addEventListener('click', function () {
|
|
263
|
+
pr[1](); entwurfAngefasst(); S.openRow = null; zeichneErgebnis();
|
|
264
|
+
});
|
|
265
|
+
chipzeile.appendChild(b);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
/* Alles verwerfen und den Stand aus dem System neu holen. Steht nur da,
|
|
269
|
+
wenn es etwas zu verwerfen gibt - ein Knopf, der nichts tut, gehoert
|
|
270
|
+
nicht in die Leiste (Ricardo, 26.08.2026). Abgesetzt, weil er als
|
|
271
|
+
einziger nicht nur Haken setzt. */
|
|
272
|
+
if (e.angefasst) {
|
|
273
|
+
var bZurueck = el('button', 'weit', tr('list.reset'));
|
|
274
|
+
bZurueck.title = tr('list.resetHint');
|
|
275
|
+
bZurueck.addEventListener('click', function () {
|
|
276
|
+
var id = S.current;
|
|
277
|
+
S.entwurf = null;
|
|
278
|
+
S.openRow = null;
|
|
279
|
+
waehle(id);
|
|
280
|
+
});
|
|
281
|
+
chipzeile.appendChild(bZurueck);
|
|
282
|
+
}
|
|
283
|
+
if (chipzeile.childNodes.length) { bar.appendChild(chipzeile); }
|
|
284
|
+
if (musterBlock) {
|
|
285
|
+
var mspacer = el('span', 'sp2');
|
|
286
|
+
bar.appendChild(mspacer);
|
|
287
|
+
musterBlock.classList.add('inbar');
|
|
288
|
+
bar.appendChild(musterBlock);
|
|
289
|
+
musterBlock = null;
|
|
290
|
+
}
|
|
291
|
+
card.appendChild(bar);
|
|
292
|
+
|
|
293
|
+
/* Was geraten wurde, sagt es selbst - mit dem Schnellweg daneben, falls
|
|
294
|
+
alles stimmt. Sonst muesste man bei acht Plaetzen achtmal haken. */
|
|
295
|
+
if (rateAnzahl(e)) {
|
|
296
|
+
var rl = el('div', 'ratleiste');
|
|
297
|
+
rl.appendChild(el('span', 'zq', tr('guess.bar', rateAnzahl(e))));
|
|
298
|
+
var bAlle = el('button', 'btn mini wichtig', tr('guess.keepAll'));
|
|
299
|
+
bAlle.addEventListener('click', function () {
|
|
300
|
+
e.states.forEach(rateBehalten);
|
|
301
|
+
entwurfAngefasst(); zeichneErgebnis();
|
|
302
|
+
});
|
|
303
|
+
rl.appendChild(bAlle);
|
|
304
|
+
var bWeg = el('button', 'btn mini', tr('guess.undo'));
|
|
305
|
+
bWeg.addEventListener('click', function () {
|
|
306
|
+
rateZurueck(e); entwurfAngefasst(); S.openRow = null; zeichneErgebnis();
|
|
307
|
+
});
|
|
308
|
+
rl.appendChild(bWeg);
|
|
309
|
+
card.appendChild(rl);
|
|
310
|
+
}
|
|
311
|
+
if (e.rateMeldung) {
|
|
312
|
+
var rm = el('div', 'aside');
|
|
313
|
+
rm.style.margin = '8px 15px';
|
|
314
|
+
rm.textContent = e.rateMeldung;
|
|
315
|
+
card.appendChild(rm);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
var cap = el('div', 'lhead');
|
|
319
|
+
[tr('list.colOn'), '', tr('list.colState'), tr('list.colRole'), tr('list.colValue'), ''].forEach(function (t, i) {
|
|
320
|
+
cap.appendChild(el('span', i === 4 ? 'r' : null, t));
|
|
321
|
+
});
|
|
322
|
+
card.appendChild(cap);
|
|
323
|
+
|
|
324
|
+
/* --- Zustandsliste --- */
|
|
325
|
+
e.states.forEach(function (s, i) {
|
|
326
|
+
var pl = platzVon[s.n];
|
|
327
|
+
var unfertig = (!s.n || !s.srcR);
|
|
328
|
+
var row = el('div', 'erow' + (s.on ? '' : ' skip') + (S.openRow === i ? ' open' : ''));
|
|
329
|
+
row.tabIndex = 0;
|
|
330
|
+
|
|
331
|
+
var cbw = el('span');
|
|
332
|
+
var cb = document.createElement('input');
|
|
333
|
+
cb.type = 'checkbox';
|
|
334
|
+
cb.checked = s.on;
|
|
335
|
+
cb.setAttribute('aria-label', s.n + ' anlegen');
|
|
336
|
+
cb.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
337
|
+
cb.addEventListener('change', function () {
|
|
338
|
+
s.on = cb.checked; entwurfAngefasst(); zeichneErgebnis();
|
|
339
|
+
});
|
|
340
|
+
cbw.appendChild(cb);
|
|
341
|
+
row.appendChild(cbw);
|
|
342
|
+
|
|
343
|
+
/* Nur noch bei einem Problem ein Zeichen. Vorher stand hier ein
|
|
344
|
+
Haken, sobald der Punkt angehakt war und einen Platz im Muster
|
|
345
|
+
hatte — das las sich wie eine Wiederholung des Kaestchens
|
|
346
|
+
daneben, und den Platz nennt die Zeile ohnehin im Klartext
|
|
347
|
+
(„ACTUAL" hinter dem Namen). Uebrig bleibt, was man beim
|
|
348
|
+
Ueberfliegen wirklich sucht: wo klemmt es. */
|
|
349
|
+
var mk = el('span', 'mk2 bad');
|
|
350
|
+
mk.textContent = (s.on && unfertig) ? '!' : '';
|
|
351
|
+
if (mk.textContent) { mk.title = !s.n ? tr('list.nameMissing') : tr('list.sourceMissing'); }
|
|
352
|
+
row.appendChild(mk);
|
|
353
|
+
|
|
354
|
+
var n3 = el('span', 'nm3', s.n || tr('list.newPoint'));
|
|
355
|
+
/* Die Beschriftung (kuenftiger Anzeigename) steht gedaempft hinter
|
|
356
|
+
der Kennung - wie die Namen im Baum. Vorher tippte man sie im
|
|
357
|
+
Detail und sah nirgends eine Wirkung. */
|
|
358
|
+
if (s.caption && s.caption !== s.n) { n3.appendChild(el('span', 'nam', s.caption)); }
|
|
359
|
+
if (s.manuell) { n3.appendChild(el('span', 'hand', tr('list.byHand'))); }
|
|
360
|
+
if (s.geaendert) { n3.appendChild(el('span', 'geae', tr('list.changed'))); }
|
|
361
|
+
/* Was die Vorlage anders will, steht an der Zeile - sonst sieht man
|
|
362
|
+
es erst im Trockenlauf (Ricardo, 25.08.2026). */
|
|
363
|
+
if (s.on && !s.geaendert) {
|
|
364
|
+
var abw = vorlagenAbweichung(s);
|
|
365
|
+
if (abw.length) {
|
|
366
|
+
/* Formeln koennen lang sein - die Marke soll die Zeile nicht
|
|
367
|
+
sprengen; der Tooltip zeigt alles. */
|
|
368
|
+
var wert = String(abw[0].vorlage || '—');
|
|
369
|
+
if (wert.length > 28) { wert = wert.slice(0, 27) + '…'; }
|
|
370
|
+
var av = el('span', 'geae tplabw', tr('list.tplDiffers', wert));
|
|
371
|
+
av.title = abw.map(function (x) {
|
|
372
|
+
return x.feld + ': ' + (x.jetzt || '—') + ' → ' + (x.vorlage || '—');
|
|
373
|
+
}).join(String.fromCharCode(10));
|
|
374
|
+
n3.appendChild(av);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/* Die Marke ist zugleich der Griff: solange sie steht, ist die Zeile
|
|
378
|
+
unbestaetigt und faellt beim Zuruecknehmen. Ein Haken laesst sie
|
|
379
|
+
verschwinden, ein Kreuz nimmt genau diese eine zurueck. */
|
|
380
|
+
if (s.geraten) {
|
|
381
|
+
n3.appendChild(el('span', 'rat', tr('guess.mark')));
|
|
382
|
+
var jaK = el('span', 'ratknopf', '\u2713');
|
|
383
|
+
jaK.title = tr('guess.keep');
|
|
384
|
+
jaK.addEventListener('click', function (ev) {
|
|
385
|
+
ev.stopPropagation(); rateBehalten(s);
|
|
386
|
+
entwurfAngefasst(); zeichneErgebnis();
|
|
387
|
+
});
|
|
388
|
+
n3.appendChild(jaK);
|
|
389
|
+
var neinK = el('span', 'ratknopf', '\u2715');
|
|
390
|
+
neinK.title = tr('guess.drop');
|
|
391
|
+
neinK.addEventListener('click', function (ev) {
|
|
392
|
+
ev.stopPropagation(); rateZurueckEine(e, s);
|
|
393
|
+
entwurfAngefasst(); S.openRow = null; zeichneErgebnis();
|
|
394
|
+
});
|
|
395
|
+
n3.appendChild(neinK);
|
|
396
|
+
}
|
|
397
|
+
/* Das Geraet kann den Befehl, der Punkt fehlt noch. Die Handlung
|
|
398
|
+
gehoert dorthin, wo das Problem sichtbar wird — nicht nur in die
|
|
399
|
+
MQTT-Karte weiter oben. */
|
|
400
|
+
/* Frisch nachgeschaut statt gemerkt — siehe wendeAn. */
|
|
401
|
+
if (s.on && s.srcW && !S.objects[s.srcW]) {
|
|
402
|
+
var fw = el('span', 'fehltnoch', tr('list.sendPointMissing'));
|
|
403
|
+
fw.title = s.srcW;
|
|
404
|
+
n3.appendChild(fw);
|
|
405
|
+
var fb2 = el('button', 'btn schmal');
|
|
406
|
+
fb2.textContent = tr('mq.rowCreate');
|
|
407
|
+
fb2.style.marginLeft = '7px';
|
|
408
|
+
fb2.addEventListener('click', function (ev) {
|
|
409
|
+
ev.stopPropagation();
|
|
410
|
+
var teile = s.srcW.split('.cmnd.');
|
|
411
|
+
if (teile.length === 2) { mqttEinzeln(teile[0], teile[1], 'neu', fb2); }
|
|
412
|
+
});
|
|
413
|
+
n3.appendChild(fb2);
|
|
414
|
+
}
|
|
415
|
+
if (s.on) {
|
|
416
|
+
if (unfertig) { n3.appendChild(el('em', null, !s.n ? tr('list.nameMissing') : tr('list.sourceMissing'))); }
|
|
417
|
+
else if (pl && pl !== s.n && platzAnzahl[pl] === 1) {
|
|
418
|
+
var pe = el('em', null, pl);
|
|
419
|
+
pe.title = tr('pattern.slotName', pl, musterName(e.want) || e.want || '?');
|
|
420
|
+
n3.appendChild(pe);
|
|
421
|
+
} else if (!pl) {
|
|
422
|
+
/* Frueher stand der Vermerk nur an Zeilen, die das info-Muster
|
|
423
|
+
aufnaehme (imInfo). SABOTAGE mit indicator.alarm nimmt aber
|
|
424
|
+
nicht mal das - solche Zeilen blieben stumm, und oben stand
|
|
425
|
+
„3/7 belegt“ bei vier Haken ohne jede Erklaerung (Ricardo,
|
|
426
|
+
25.08.2026). Angehakt ohne Platz heisst immer: sagen. */
|
|
427
|
+
n3.appendChild(el('em', null, tr('pattern.noPlaceIn', musterName(e.want) || e.want || '?')));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
row.appendChild(n3);
|
|
431
|
+
|
|
432
|
+
var warn = (s.n === 'ACTUAL' && s.on && !pl);
|
|
433
|
+
row.appendChild(el('span', 'ro2' + (warn ? ' warnrole' : ''), s.role || tr('list.noRole')));
|
|
434
|
+
row.appendChild(el('span', 'vl2', fmt(s)));
|
|
435
|
+
row.appendChild(el('span', 'ca2', S.openRow === i ? '▾' : '▸'));
|
|
436
|
+
|
|
437
|
+
var um = function () { S.openRow = (S.openRow === i ? null : i); zeichneErgebnis(); };
|
|
438
|
+
row.addEventListener('click', um);
|
|
439
|
+
row.addEventListener('keydown', function (ev) {
|
|
440
|
+
if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); um(); }
|
|
441
|
+
});
|
|
442
|
+
card.appendChild(row);
|
|
443
|
+
|
|
444
|
+
if (S.openRow === i) { card.appendChild(detailZeile(e, s, i)); }
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
/* --- Freie Plaetze des gewaehlten Musters --- */
|
|
448
|
+
var belegteSlots = {};
|
|
449
|
+
Object.keys(platzVon).forEach(function (n) { belegteSlots[platzVon[n]] = 1; });
|
|
450
|
+
var muster = e.want && musterVon(e.want);
|
|
451
|
+
var freie = muster ? muster.states.filter(function (st) {
|
|
452
|
+
return !belegteSlots[st.name] && !st.multiple;
|
|
453
|
+
}) : [];
|
|
454
|
+
|
|
455
|
+
if (freie.length) {
|
|
456
|
+
var fk = el('div', 'freikopf');
|
|
457
|
+
fk.appendChild(el('span', null, tr('pattern.freeSlots', musterName(e.want) || e.want)));
|
|
458
|
+
fk.appendChild(el('span', 'cnt2', tr('list.countOf', freie.length, muster.states.length)));
|
|
459
|
+
card.appendChild(fk);
|
|
460
|
+
|
|
461
|
+
freie.forEach(function (st) {
|
|
462
|
+
var fr = el('div', 'freirow' + (st.required ? ' pflicht' : ''));
|
|
463
|
+
fr.tabIndex = 0;
|
|
464
|
+
fr.setAttribute('role', 'button');
|
|
465
|
+
fr.appendChild(el('span', 'mk3', st.required ? '✕' : '+'));
|
|
466
|
+
fr.appendChild(el('span', 'sn2', st.name));
|
|
467
|
+
fr.appendChild(el('span', 'rl2', rolleVonPlatz(st) || String(st.role || '')));
|
|
468
|
+
fr.appendChild(el('span', 'rx2', st.required ? tr('pattern.required') : String(st.role || '')));
|
|
469
|
+
|
|
470
|
+
var nimm = function () {
|
|
471
|
+
e.states.push({
|
|
472
|
+
n: st.name,
|
|
473
|
+
on: true,
|
|
474
|
+
role: rolleVonPlatz(st),
|
|
475
|
+
/* Manche Plaetze lassen mehrere Typen zu und nennen sie als
|
|
476
|
+
Liste — light.EFFECT etwa [number, string]. Ungefiltert
|
|
477
|
+
landete die Liste in common.type, der Detektor lehnte den
|
|
478
|
+
Punkt ab, der Platz blieb frei und liess sich endlos
|
|
479
|
+
nochmal hinzufuegen. Der erste Eintrag ist der gemeinte. */
|
|
480
|
+
typ: (Array.isArray(st.type) ? st.type[0] : st.type) || '',
|
|
481
|
+
unit: st.defaultUnit || '',
|
|
482
|
+
/* statesDefined heisst: ohne Werteliste bleibt der Platz leer,
|
|
483
|
+
egal wie gut Rolle und Typ passen. Also die Vorgabe aus dem
|
|
484
|
+
Muster mitnehmen, sonst legt man den Punkt an und der Platz
|
|
485
|
+
gilt weiter als frei — beliebig oft. */
|
|
486
|
+
states: st.statesDefined ? (st.defaultStates || { 0: 'None' }) : undefined,
|
|
487
|
+
wr: !!st.write,
|
|
488
|
+
srcR: '', srcW: '', f: '', fw: '',
|
|
489
|
+
caption: '', manuell: true, ausPlatz: st.name
|
|
490
|
+
});
|
|
491
|
+
S.openRow = e.states.length - 1;
|
|
492
|
+
entwurfAngefasst();
|
|
493
|
+
zeichneErgebnis();
|
|
494
|
+
};
|
|
495
|
+
fr.addEventListener('click', nimm);
|
|
496
|
+
fr.addEventListener('keydown', function (ev) {
|
|
497
|
+
if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); nimm(); }
|
|
498
|
+
});
|
|
499
|
+
card.appendChild(fr);
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
var add = el('button', 'addrow');
|
|
504
|
+
add.appendChild(el('span', null, '+'));
|
|
505
|
+
add.appendChild(el('span', null, tr('list.addByHand')));
|
|
506
|
+
add.addEventListener('click', function () {
|
|
507
|
+
e.states.push({ n: '', on: true, role: 'value', typ: 'number', unit: '', wr: false,
|
|
508
|
+
srcR: '', srcW: '', f: '', fw: '', caption: '', manuell: true, geaendert: true });
|
|
509
|
+
S.openRow = e.states.length - 1;
|
|
510
|
+
entwurfAngefasst();
|
|
511
|
+
zeichneErgebnis();
|
|
512
|
+
});
|
|
513
|
+
card.appendChild(add);
|
|
514
|
+
host.appendChild(card);
|
|
515
|
+
}
|