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,934 @@
|
|
|
1
|
+
import { S } from './zustand.js';
|
|
2
|
+
import './start.js';
|
|
3
|
+
import { socket } from './verbindung.js';
|
|
4
|
+
import { D, $, el, klappZeichen } from './basis.js';
|
|
5
|
+
import { tr, sprachtext } from './sprache.js';
|
|
6
|
+
import './einstellungen.js';
|
|
7
|
+
import './enums.js';
|
|
8
|
+
import { ausgangName, pruefeVorlage, vorschlag } from './vorlagen.js';
|
|
9
|
+
import { rateAnzahl } from './vorschlagen.js';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
vEinlesenDatei,
|
|
13
|
+
zeichneVorlagenBlatt,
|
|
14
|
+
tplKnopf,
|
|
15
|
+
zeigeVorlagenDialog,
|
|
16
|
+
speichereVorlageAusDialog
|
|
17
|
+
} from './vorlagenblatt.js';
|
|
18
|
+
import { baueZielleiste } from './zielleiste.js';
|
|
19
|
+
import { baueMusterwahl } from './musterwahl.js';
|
|
20
|
+
import { berechnePlaetze, baueListe } from './zustandsliste.js';
|
|
21
|
+
import {
|
|
22
|
+
alleUndVorlage,
|
|
23
|
+
knotenDa,
|
|
24
|
+
quelleVon,
|
|
25
|
+
springeZu,
|
|
26
|
+
waehle,
|
|
27
|
+
geraeteDarunter,
|
|
28
|
+
aliasFuer,
|
|
29
|
+
tipptGerade,
|
|
30
|
+
opt,
|
|
31
|
+
vorlagenAbweichung,
|
|
32
|
+
vorlageAnwenden,
|
|
33
|
+
bestandVorrang
|
|
34
|
+
} from './entwurf.js';
|
|
35
|
+
import { setzeEnums, setzeZiel, unterschiede } from './zuordnung.js';
|
|
36
|
+
import './detail.js';
|
|
37
|
+
import { zeigeTausch, tauscheAus } from './quellentausch.js';
|
|
38
|
+
|
|
39
|
+
import { baueChecks } from './pruefungen.js';
|
|
40
|
+
import {
|
|
41
|
+
zeigeTrockenlauf,
|
|
42
|
+
zeigeLoeschen,
|
|
43
|
+
loescheAlias,
|
|
44
|
+
zeigeVerlegen,
|
|
45
|
+
delKnopf,
|
|
46
|
+
schreibeObjekte
|
|
47
|
+
} from './schreiben.js';
|
|
48
|
+
|
|
49
|
+
import { mqttKarte, mqttSchreiben } from './mqtt.js';
|
|
50
|
+
import { kindZustaende, direkteZustaende } from './werte.js';
|
|
51
|
+
import './aufzaehlungen.js';
|
|
52
|
+
import { erkenneEntwurf } from './erkennung.js';
|
|
53
|
+
import { zusatzName } from './baum.js';
|
|
54
|
+
import './objekte.js';
|
|
55
|
+
import './katalog.js';
|
|
56
|
+
|
|
57
|
+
/* Das Aufklapp-Flag von „Warum diese Vorlage“ - lag versehentlich im
|
|
58
|
+
MQTT-Block und wanderte beim Schnitt mit; es gehoert hierher. */
|
|
59
|
+
var warumAuf = false;
|
|
60
|
+
|
|
61
|
+
/* Das Ergebnis rechts: Entwurf aufbauen, waehlen, zeichnen - Kopf,
|
|
62
|
+
Zustandsliste, Raum/Funktion-Zeile, Ordneruebersicht, Sprungknoepfe,
|
|
63
|
+
Dialog-Verkabelung.
|
|
64
|
+
|
|
65
|
+
Frueher ein IIFE - als Modul ist der eigene Gueltigkeitsbereich
|
|
66
|
+
ohnehin da, und nur ohne Huelle lassen sich Funktionen exportieren. */
|
|
67
|
+
|
|
68
|
+
/* Der Klappzustand ist eine Ansichtssache, keine Konfiguration - er
|
|
69
|
+
gehoert in den Browser, nicht in die Objektdatenbank. Sonst schriebe
|
|
70
|
+
jeder Klick auf ein Dreieck in die Instanz. */
|
|
71
|
+
/* Nicht bei jedem einzelnen Wert die ganze rechte Seite neu bauen. Ein
|
|
72
|
+
Solarzaehler sendet im Sekundentakt — und mit jedem Takt verschwand,
|
|
73
|
+
was gerade offen war: die Ordnerliste beim Hineinklicken, ein
|
|
74
|
+
aufgeklapptes Detail, der Fokus im Eingabefeld. Gesammelt zeichnen,
|
|
75
|
+
und nur, wenn niemand tippt. */
|
|
76
|
+
var werteTimer = null;
|
|
77
|
+
function werteGezeichnet() {
|
|
78
|
+
werteTimer = null;
|
|
79
|
+
if (document.querySelector('dialog[open]')) { return; }
|
|
80
|
+
if (tipptGerade()) { werteTimer = setTimeout(werteGezeichnet, 700); return; }
|
|
81
|
+
if (S.current) { zeichneErgebnis(); }
|
|
82
|
+
}
|
|
83
|
+
socket.on('stateChange', function (id, state) {
|
|
84
|
+
if (!state) { return; }
|
|
85
|
+
S.werte[id] = state;
|
|
86
|
+
if (!S.current || werteTimer) { return; }
|
|
87
|
+
werteTimer = setTimeout(werteGezeichnet, 700);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/* Kopfzeile: die Vorlagenzahl kommt erst, wenn sie geladen sind —
|
|
91
|
+
hier steht S.VORLAGEN noch auf undefined. */
|
|
92
|
+
$('#meta').textContent = 'type-detector ' + (D ? D.version : '?');
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
/* Welches Angebot zuletzt zu sehen war und seit wann.
|
|
96
|
+
|
|
97
|
+
An ein einzelnes Zeichnen laesst sich das Pochen nicht haengen: nach
|
|
98
|
+
der Auswahl zeichnet die Werkbank ein zweites Mal, sobald die Werte
|
|
99
|
+
nachkommen, und beim zweiten Mal waere das Angebot schon „bekannt“ und
|
|
100
|
+
bliebe stumm. Also gilt ein Zeitfenster - drei Sekunden ab dem ersten
|
|
101
|
+
Erscheinen. Danach steht es ruhig da, solange es dasselbe bleibt. */
|
|
102
|
+
var letztesAngebot = '';
|
|
103
|
+
var angebotSeit = 0;
|
|
104
|
+
|
|
105
|
+
/* Dasselbe Zeitfenster fuer den Vorschlagsknopf.
|
|
106
|
+
|
|
107
|
+
Eine eigene Marke und nicht `angebotFrisch`: die fuehrt genau einen
|
|
108
|
+
Schluessel, und zwei Dinge, die sich einen merken, loeschen sich
|
|
109
|
+
gegenseitig aus - der Knopf wuerde das Pochen des Uebernahme-Angebots
|
|
110
|
+
abwuergen und umgekehrt. */
|
|
111
|
+
var letzterKnopf = '';
|
|
112
|
+
var knopfSeit = 0;
|
|
113
|
+
|
|
114
|
+
export function knopfFrisch(schluessel) {
|
|
115
|
+
if (schluessel !== letzterKnopf) {
|
|
116
|
+
letzterKnopf = schluessel;
|
|
117
|
+
knopfSeit = Date.now();
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
return (Date.now() - knopfSeit) < 3200;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function angebotFrisch(schluessel) {
|
|
124
|
+
if (schluessel !== letztesAngebot) {
|
|
125
|
+
letztesAngebot = schluessel;
|
|
126
|
+
angebotSeit = Date.now();
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return (Date.now() - angebotSeit) < 3200;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function entwurfAngefasst() {
|
|
133
|
+
if (S.entwurf) { S.entwurf.angefasst = true; }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function neuZeichnenOderAufbauen(danach) {
|
|
137
|
+
if (!S.current || S.baumModus === 'vorlagen') {
|
|
138
|
+
if (typeof danach === 'function') { danach(); }
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (S.entwurf && S.entwurf.angefasst) {
|
|
142
|
+
zeichneErgebnis();
|
|
143
|
+
if (typeof danach === 'function') { danach(); }
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
waehle(S.current, danach);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
(function () {
|
|
150
|
+
var b = $('#btn-move');
|
|
151
|
+
if (b) { b.addEventListener('click', zeigeVerlegen); }
|
|
152
|
+
var bt = $('#btn-swap');
|
|
153
|
+
if (bt) { bt.addEventListener('click', zeigeTausch); }
|
|
154
|
+
var btg = $('#btn-swap-go');
|
|
155
|
+
if (btg) { btg.addEventListener('click', tauscheAus); }
|
|
156
|
+
})();
|
|
157
|
+
|
|
158
|
+
$('#btn-einlesen').addEventListener('click', function () { $('#datei-einlesen').click(); });
|
|
159
|
+
$('#datei-einlesen').addEventListener('change', function () {
|
|
160
|
+
var f = this.files && this.files[0];
|
|
161
|
+
this.value = ''; /* dieselbe Datei soll erneut gehen */
|
|
162
|
+
if (f) { vEinlesenDatei(f); }
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
/* ================== Entwurf ==================
|
|
166
|
+
Alles, was du aenderst, lebt hier im Speicher. Geschrieben wird nichts.
|
|
167
|
+
Der echte Detektor laeuft gegen ein Objekt-Abbild dieses Entwurfs —
|
|
168
|
+
deshalb reagiert der Bericht sofort auf jede Aenderung. */
|
|
169
|
+
|
|
170
|
+
/* ================== Ergebnis ================== */
|
|
171
|
+
|
|
172
|
+
/* Nichts gewaehlt: Hinweis statt Entwurf, und alle Knoepfe aus. */
|
|
173
|
+
function zeichneLeer(host) {
|
|
174
|
+
/* Was man in diesem Modus tun kann, steht dort, wo man es beim
|
|
175
|
+
ersten Oeffnen sucht: auf der leeren rechten Seite. Die Reiter
|
|
176
|
+
benennen die Absicht, hier steht der Weg (Ricardo, 25.08.2026). */
|
|
177
|
+
var k = el('div', 'empty leerhilfe');
|
|
178
|
+
var modus = S.baumModus === 'aliase' ? 'aliases'
|
|
179
|
+
: (S.baumModus === 'vorlagen' ? 'templates' : 'sources');
|
|
180
|
+
k.appendChild(el('div', 'lh1', tr('help.' + modus)));
|
|
181
|
+
if (modus === 'aliases') { k.appendChild(el('div', 'lh2', tr('help.aliasesAdd'))); }
|
|
182
|
+
host.appendChild(k);
|
|
183
|
+
/* Nichts gewaehlt heisst: keine Knoepfe fuer ein Geraet, das es
|
|
184
|
+
gerade nicht gibt. Nach dem Loeschen eines Alias und nach der
|
|
185
|
+
Rueckkehr aus dem Vorlagen-Tab stand hier weiter „Alias
|
|
186
|
+
aktualisieren", „Alle 4 erzeugen", „Pruefungen 7 ✓" und unten
|
|
187
|
+
die Objektzahl von vorhin samt Geraetetyp. Die Knoepfe waren
|
|
188
|
+
wirkungslos, gelesen hat man sie trotzdem. */
|
|
189
|
+
var bd0 = $('#btn-dry');
|
|
190
|
+
bd0.disabled = true;
|
|
191
|
+
/* Auch die Beschriftung zuruecksetzen: „Alias aktualisieren" stand
|
|
192
|
+
sonst noch da, obwohl es den Alias nicht mehr gibt. */
|
|
193
|
+
bd0.textContent = tr('write.createAlias');
|
|
194
|
+
var dAlle = $('#btn-dry-alle'); if (dAlle) { dAlle.hidden = true; }
|
|
195
|
+
tplKnopf(false);
|
|
196
|
+
delKnopf(false);
|
|
197
|
+
var bc0 = $('#btn-checks');
|
|
198
|
+
if (bc0) { bc0.disabled = true; }
|
|
199
|
+
var chip0 = $('#checkchip');
|
|
200
|
+
if (chip0) { chip0.textContent = ''; chip0.className = ''; }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Sehr viele Punkte ohne Vorlage: Uebersicht oder Warnung statt
|
|
204
|
+
ungefragt hunderter Zeilen. */
|
|
205
|
+
function zeichneZuViel(host, e) {
|
|
206
|
+
/* Sind darunter mehrere eigenstaendige Geraete, ist eine Uebersicht
|
|
207
|
+
nuetzlicher als die blosse Warnung: sie zeigt je Geraet, welche
|
|
208
|
+
Vorlage greift und ob es schon einen Alias gibt. Die Funktion gab
|
|
209
|
+
es laengst, sie wurde nur nie aufgerufen. */
|
|
210
|
+
var drunter = geraeteDarunter(S.current);
|
|
211
|
+
if (drunter.length > 1) {
|
|
212
|
+
zeichneOrdner(host, S.current);
|
|
213
|
+
var bt2 = el('div');
|
|
214
|
+
bt2.style.margin = '11px 0 0';
|
|
215
|
+
var btn2 = el('button', 'btn', tr('result.allAnyway') + e.states.length);
|
|
216
|
+
btn2.addEventListener('click', function () { e.trotzdem = true; zeichneErgebnis(); });
|
|
217
|
+
bt2.appendChild(btn2);
|
|
218
|
+
host.appendChild(bt2);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
var zweige = {};
|
|
222
|
+
e.states.forEach(function (st) {
|
|
223
|
+
zweige[(st.n.split('_')[0] || st.n)] = 1;
|
|
224
|
+
});
|
|
225
|
+
var wr = el('div', 'aside w');
|
|
226
|
+
wr.appendChild(el('b', null, tr('result.pointsFromBranches',
|
|
227
|
+
e.states.length, Object.keys(zweige).length)));
|
|
228
|
+
wr.appendChild(document.createTextNode(tr('result.tooManyStates', e.states.length)));
|
|
229
|
+
var bt = el('div');
|
|
230
|
+
bt.style.marginTop = '9px';
|
|
231
|
+
var btn = el('button', 'btn', tr('result.allAnyway') + e.states.length);
|
|
232
|
+
btn.addEventListener('click', function () { e.trotzdem = true; zeichneErgebnis(); });
|
|
233
|
+
bt.appendChild(btn);
|
|
234
|
+
wr.appendChild(bt);
|
|
235
|
+
host.appendChild(wr);
|
|
236
|
+
var bd0 = $('#btn-dry');
|
|
237
|
+
if (bd0) { bd0.disabled = true; }
|
|
238
|
+
tplKnopf(false);
|
|
239
|
+
var ba0 = $('#btn-dry-alle');
|
|
240
|
+
if (ba0) { ba0.hidden = true; }
|
|
241
|
+
delKnopf(false);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Erkennung samt Ziel und Aufzaehlungen - und dem zweiten Lauf,
|
|
245
|
+
wenn der Bestand den Entwurf gerade umgebaut hat. */
|
|
246
|
+
function ermittleErkennung(e) {
|
|
247
|
+
var frei = erkenneEntwurf(e, null);
|
|
248
|
+
if (!e.wantAuto) {
|
|
249
|
+
e.wantAuto = frei.length ? frei[0].type : null;
|
|
250
|
+
if (!e.want) { e.want = e.wantAuto; }
|
|
251
|
+
}
|
|
252
|
+
var funde = e.want ? erkenneEntwurf(e, e.want) : frei;
|
|
253
|
+
var haupt = funde.length ? funde[0] : null;
|
|
254
|
+
|
|
255
|
+
/* --- Kopf --- */
|
|
256
|
+
setzeZiel(e);
|
|
257
|
+
|
|
258
|
+
/* Hat `uebernehmeBestand` den Entwurf auf den Bestand gezogen, lief die
|
|
259
|
+
Erkennung von eben noch auf den Rollen der Quelle - und `wantAuto`
|
|
260
|
+
stuende danach fest. Also noch einmal.
|
|
261
|
+
|
|
262
|
+
Das kann nur beim ersten Zeichnen nach der Auswahl passieren
|
|
263
|
+
(`bestandGeprueft` laesst es genau einmal zu), und da hat noch
|
|
264
|
+
niemand ein Muster von Hand gewaehlt - `want` zurueckzusetzen nimmt
|
|
265
|
+
also keine Entscheidung weg. */
|
|
266
|
+
if (e.bestandZog) {
|
|
267
|
+
e.bestandZog = false;
|
|
268
|
+
e.wantAuto = null;
|
|
269
|
+
e.want = null;
|
|
270
|
+
frei = erkenneEntwurf(e, null);
|
|
271
|
+
e.wantAuto = frei.length ? frei[0].type : null;
|
|
272
|
+
e.want = e.wantAuto;
|
|
273
|
+
funde = e.want ? erkenneEntwurf(e, e.want) : frei;
|
|
274
|
+
haupt = funde.length ? funde[0] : null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
setzeEnums(e);
|
|
278
|
+
return haupt;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* Der Kopf: was links angeklickt wurde - Name, Kennung, Bestand
|
|
282
|
+
und der Sprungknopf zum Gegenstueck. Nicht, was daraus werden
|
|
283
|
+
soll: das sagt die Zielzeile darunter. */
|
|
284
|
+
function baueKopf() {
|
|
285
|
+
var obj = S.objects[S.current] || {};
|
|
286
|
+
var alleZust = kindZustaende(S.current);
|
|
287
|
+
var direkt = direkteZustaende(S.current);
|
|
288
|
+
var head = el('div', 'rhead');
|
|
289
|
+
var links = el('div');
|
|
290
|
+
/* Oben steht, was links angeklickt wurde — und das ist jetzt der Name,
|
|
291
|
+
wo es einen gibt. Die Kennung steht unverkuerzt in der Zeile
|
|
292
|
+
darunter; doppelt braucht sie in der Unterzeile nicht zu stehen. */
|
|
293
|
+
var kopfName = zusatzName(S.current);
|
|
294
|
+
/* Name und Sprungknopf in einer Zeile: der Knopf gehoert zu dem, was
|
|
295
|
+
oben steht, nicht unter den Bestandszaehler. */
|
|
296
|
+
var namensZeile = el('div', 'namzeile');
|
|
297
|
+
namensZeile.appendChild(el('div', 'id', kopfName || S.current.split('.').pop()));
|
|
298
|
+
links.appendChild(namensZeile);
|
|
299
|
+
links.appendChild(el('div', 'pfad', S.current));
|
|
300
|
+
|
|
301
|
+
var sub = el('div', 'sub');
|
|
302
|
+
sub.appendChild(document.createTextNode((S.objects[S.current] ? obj.type : tr('result.noObjectShort')) + ' · ' +
|
|
303
|
+
(direkt.length !== alleZust.length
|
|
304
|
+
? tr('result.statesDirect', alleZust.length, direkt.length)
|
|
305
|
+
: tr('result.states', alleZust.length))));
|
|
306
|
+
/* Ein Weg zurueck zum Geraet. Im Aliasmodus laesst sich ein Alias
|
|
307
|
+
pflegen, aber nicht neu aus einer Vorlage aufbauen - Vorlagen
|
|
308
|
+
greifen nur an der Quelle. Wer von einem Alias aus weiterarbeiten
|
|
309
|
+
will, musste ihn bisher im Quellenbaum von Hand wiederfinden.
|
|
310
|
+
Umgekehrt gilt dasselbe: steht man am Geraet und will nachsehen,
|
|
311
|
+
was daraus geworden ist, fuehrt der zweite Knopf hin. */
|
|
312
|
+
var sprung = null, sprungZiel = null, sprungModus = null;
|
|
313
|
+
if (S.current.indexOf('alias.') === 0) {
|
|
314
|
+
sprungZiel = quelleVon(S.current);
|
|
315
|
+
sprungModus = 'quellen';
|
|
316
|
+
if (!sprungZiel) {
|
|
317
|
+
var vermerkt = S.objects[S.current] && S.objects[S.current].native && S.objects[S.current].native.quelle;
|
|
318
|
+
if (vermerkt) { sub.appendChild(el('span', 'chip warn', tr('result.sourceGone', vermerkt))); }
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
sprungZiel = aliasFuer(S.current);
|
|
322
|
+
/* Rueckprobe: fuehrt der Weg von diesem Alias wieder hierher?
|
|
323
|
+
|
|
324
|
+
`aliasFuer` sucht grosszuegig - es findet auch einen Alias, dessen
|
|
325
|
+
Punkte irgendwo *unterhalb* des Knotens lesen. Fuer die
|
|
326
|
+
Ordneruebersicht ist das richtig, fuer diesen Knopf zu weit: er
|
|
327
|
+
erschien dadurch an jeder Sparte und jedem Unterkanal, also an
|
|
328
|
+
`…RGB.stat` und `…RGB.tele` ebenso wie am Geraet. Der Knopf
|
|
329
|
+
gehoert nur dorthin, wo der Alias auch wirklich herkommt. */
|
|
330
|
+
if (sprungZiel && quelleVon(sprungZiel) !== S.current) { sprungZiel = null; }
|
|
331
|
+
sprungModus = 'aliase';
|
|
332
|
+
}
|
|
333
|
+
if (knotenDa(sprungZiel)) {
|
|
334
|
+
var zumAlias = (sprungModus === 'aliase');
|
|
335
|
+
sprung = el('button', 'btn schmal sprungknopf',
|
|
336
|
+
(zumAlias ? tr('result.toAlias') : tr('result.toSource')) + ' \u2192');
|
|
337
|
+
sprung.title = (zumAlias ? tr('result.toAliasHint') : tr('result.toSourceHint'))
|
|
338
|
+
+ '\n' + sprungZiel;
|
|
339
|
+
var ziel0 = sprungZiel, modus0 = sprungModus;
|
|
340
|
+
sprung.addEventListener('click', function () { springeZu(ziel0, modus0); });
|
|
341
|
+
namensZeile.appendChild(sprung);
|
|
342
|
+
}
|
|
343
|
+
links.appendChild(sub);
|
|
344
|
+
head.appendChild(links);
|
|
345
|
+
return head;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* Der Vorlagenkasten: welche Vorlage gegriffen hat, warum, ihre
|
|
349
|
+
Wahl und Knoepfe. Nimmt die Musterwahl bei sich auf; ohne Kasten
|
|
350
|
+
bleibt sie im Kopf - bis die Leiste sie sich holt. */
|
|
351
|
+
function baueVorlagenkasten(host, head, e, istQuelle, musterBlock) {
|
|
352
|
+
if (e.vorschlag || istQuelle) {
|
|
353
|
+
var sg = el('div', 'suggestbox');
|
|
354
|
+
if (!e.vorschlag) {
|
|
355
|
+
sg.style.borderColor = 'var(--line-strong)';
|
|
356
|
+
sg.style.background = 'var(--surface-2)';
|
|
357
|
+
}
|
|
358
|
+
var hd = el('div', 'hd', e.vorschlag
|
|
359
|
+
? tr('tpl.identifiedAs', e.tplName)
|
|
360
|
+
: tr('tpl.noneDetected'));
|
|
361
|
+
if (e.vorlageVersion) {
|
|
362
|
+
var vv2 = el('span', null, ' · ' + e.vorlage + ' v' + e.vorlageVersion);
|
|
363
|
+
vv2.style.fontFamily = 'var(--mono)';
|
|
364
|
+
vv2.style.fontSize = '10.5px';
|
|
365
|
+
vv2.style.fontWeight = '400';
|
|
366
|
+
vv2.style.color = 'var(--ink-3)';
|
|
367
|
+
hd.appendChild(vv2);
|
|
368
|
+
}
|
|
369
|
+
if (e.vonHandGewaehlt) {
|
|
370
|
+
var vh = el('span', 'chip warn', tr('tpl.manuallyChosen'));
|
|
371
|
+
vh.style.marginLeft = '7px';
|
|
372
|
+
hd.appendChild(vh);
|
|
373
|
+
}
|
|
374
|
+
if (e.ausBestand) {
|
|
375
|
+
var ab = el('span', 'chip mut', tr('tpl.remembered'));
|
|
376
|
+
ab.style.marginLeft = '7px';
|
|
377
|
+
ab.title = tr('tpl.rememberedHint');
|
|
378
|
+
hd.appendChild(ab);
|
|
379
|
+
}
|
|
380
|
+
/* Woher die Vorlage kommt — mitgeliefert oder selbst gebaut. */
|
|
381
|
+
var vq = null;
|
|
382
|
+
S.VORLAGEN.forEach(function (v) { if (v.id === e.vorlage) { vq = v; } });
|
|
383
|
+
if (vq && vq._quelle === 'benutzer') {
|
|
384
|
+
var qc = el('span', 'chip ok', tr('tpl.own'));
|
|
385
|
+
qc.style.marginLeft = '7px';
|
|
386
|
+
hd.appendChild(qc);
|
|
387
|
+
}
|
|
388
|
+
sg.appendChild(hd);
|
|
389
|
+
|
|
390
|
+
/* Warum diese Vorlage, und was sie weggelassen hat — beides an einer
|
|
391
|
+
Stelle und zugeklappt. Vorher stand die Begruendung im Tooltip
|
|
392
|
+
eines kleinen i, wo sie niemand findet, und die Liste der
|
|
393
|
+
weggelassenen Punkte offen darunter, wo sie den halben Bildschirm
|
|
394
|
+
fuellte. Zugeklappt bleibt die Zahl sichtbar; wer es genau wissen
|
|
395
|
+
will, klappt auf. */
|
|
396
|
+
var gruende = [];
|
|
397
|
+
if (e.vorschlag) {
|
|
398
|
+
gruende = e.grund.slice();
|
|
399
|
+
} else {
|
|
400
|
+
S.VORLAGEN.forEach(function (v) {
|
|
401
|
+
var t = pruefeVorlage(v, S.current);
|
|
402
|
+
gruende.push(sprachtext(v.name) + ': ' + (t.passt ? tr('tpl.fits') : t.fehler.join(', ')));
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
var weggelassen = (e.uebersprungen || []).slice();
|
|
406
|
+
|
|
407
|
+
/* Der Aufklapper sitzt in der Kopfzeile, rechts aussen.
|
|
408
|
+
|
|
409
|
+
Zugeklappt braucht er dann keine eigene Zeile mehr - er teilt
|
|
410
|
+
sie sich mit „Identifiziert als …“, wo ohnehin Platz frei ist.
|
|
411
|
+
Das Zeichen steht rechts vom Text, weil es dort am Rand des
|
|
412
|
+
Kastens liegt und der Blick beim Lesen dorthin laeuft. Der Leib
|
|
413
|
+
klappt weiterhin unter der Kopfzeile auf; dort hat er die volle
|
|
414
|
+
Breite, die eine Liste braucht. */
|
|
415
|
+
var warumKopf = null, warumLeib = null;
|
|
416
|
+
if (gruende.length || weggelassen.length) {
|
|
417
|
+
warumKopf = el('div', 'wkopf');
|
|
418
|
+
/* Ohne Vorlage waere „Warum diese Vorlage" die falsche Frage —
|
|
419
|
+
dann steht hier, warum keine greift. */
|
|
420
|
+
warumKopf.appendChild(el('span', null, weggelassen.length
|
|
421
|
+
? tr('tpl.skippedHead', weggelassen.length)
|
|
422
|
+
: tr(e.vorschlag ? 'tpl.whyThis' : 'tpl.whyNone')));
|
|
423
|
+
warumKopf.appendChild(klappZeichen(warumAuf));
|
|
424
|
+
warumKopf.tabIndex = 0;
|
|
425
|
+
var um2 = function () { warumAuf = !warumAuf; zeichneErgebnis(); };
|
|
426
|
+
warumKopf.addEventListener('click', um2);
|
|
427
|
+
warumKopf.addEventListener('keydown', function (ev) {
|
|
428
|
+
if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); um2(); }
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
if (warumAuf) {
|
|
432
|
+
warumLeib = el('div', 'wleib');
|
|
433
|
+
if (weggelassen.length) {
|
|
434
|
+
var ul2 = el('ul');
|
|
435
|
+
weggelassen.forEach(function (u) { ul2.appendChild(el('li', null, u)); });
|
|
436
|
+
warumLeib.appendChild(ul2);
|
|
437
|
+
}
|
|
438
|
+
if (gruende.length) {
|
|
439
|
+
if (weggelassen.length) { warumLeib.appendChild(el('div', 'wtrenn', tr(e.vorschlag ? 'tpl.whyThis' : 'tpl.whyNone'))); }
|
|
440
|
+
var ul3 = el('ul');
|
|
441
|
+
gruende.forEach(function (g2) { if (g2) { ul3.appendChild(el('li', null, g2)); } });
|
|
442
|
+
warumLeib.appendChild(ul3);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (e.erzwungen) {
|
|
447
|
+
var ezc = el('span', 'chip warn', tr('tpl.forced'));
|
|
448
|
+
ezc.style.marginLeft = '7px';
|
|
449
|
+
ezc.title = tr('tpl.forcedHint');
|
|
450
|
+
hd.appendChild(ezc);
|
|
451
|
+
}
|
|
452
|
+
/* Zuletzt, damit die Marken davor stehen und das Zeichen ganz
|
|
453
|
+
rechts landet. */
|
|
454
|
+
if (warumKopf) { hd.appendChild(warumKopf); }
|
|
455
|
+
if (warumLeib) {
|
|
456
|
+
var wk = el('div', 'warum auf');
|
|
457
|
+
wk.appendChild(warumLeib);
|
|
458
|
+
sg.appendChild(wk);
|
|
459
|
+
}
|
|
460
|
+
var acts = el('div', 'acts');
|
|
461
|
+
|
|
462
|
+
/* Mehrere Ausgaenge: je Ausgang ein eigenes Geraet, weil ein socket
|
|
463
|
+
genau ein SET hat. Hier wird gewaehlt, welcher gerade bearbeitet wird. */
|
|
464
|
+
var ausgangFeld = null;
|
|
465
|
+
if (e.instanzen && e.instanzen.length > 1) {
|
|
466
|
+
var la = el('label', 'feld');
|
|
467
|
+
la.appendChild(el('span', 'feldlabel', tr('tpl.output')));
|
|
468
|
+
var selA = el('select', 'tx');
|
|
469
|
+
e.instanzen.forEach(function (n) {
|
|
470
|
+
var o = opt(n, ausgangName(e, n));
|
|
471
|
+
if (String(n) === String(e.instanz)) { o.selected = true; }
|
|
472
|
+
selA.appendChild(o);
|
|
473
|
+
});
|
|
474
|
+
selA.addEventListener('change', function () {
|
|
475
|
+
var v2 = vorschlag(S.current, e.vorlage, selA.value);
|
|
476
|
+
if (v2) {
|
|
477
|
+
v2.zielOrdner = e.zielOrdner;
|
|
478
|
+
v2.zielName = ausgangName(v2, selA.value);
|
|
479
|
+
v2.ziel = v2.zielOrdner + '.' + v2.zielName;
|
|
480
|
+
S.entwurf = v2; S.openRow = null; zeichneErgebnis();
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
la.appendChild(selA);
|
|
484
|
+
ausgangFeld = la;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/* Vorlagenwahl: alle geladenen, mit Vermerk ob sie passen. */
|
|
488
|
+
var lv = el('label', 'feld');
|
|
489
|
+
lv.appendChild(el('span', 'feldlabel', tr('tpl.label')));
|
|
490
|
+
|
|
491
|
+
/* Aussehen kommt aus werkbank.css (.suggestbox .acts select.tx) -
|
|
492
|
+
die Inline-Stile hier machten das Vorlagenfeld zum Einzelgaenger:
|
|
493
|
+
das Ausgangsfeld daneben blieb ungestylt in der Browserschrift. */
|
|
494
|
+
var selV = el('select', 'tx');
|
|
495
|
+
|
|
496
|
+
selV.appendChild(opt('', e.vorschlag ? tr('tpl.switch') : tr('tpl.choose')));
|
|
497
|
+
S.VORLAGEN.forEach(function (v) {
|
|
498
|
+
var t = pruefeVorlage(v, S.current);
|
|
499
|
+
/* Auch nicht passende bleiben waehlbar — mit Grund daneben. Sonst
|
|
500
|
+
steht man bei einem Geraet, das noch nichts gesendet hat, ohne
|
|
501
|
+
jede Moeglichkeit da. */
|
|
502
|
+
var o = opt(v.id, (v._quelle === 'benutzer' ? '★ ' : '') +
|
|
503
|
+
sprachtext(v.name) + ' v' + v.version + ' ' +
|
|
504
|
+
(t.passt ? tr('tpl.fits') : tr('tpl.fitsNot', t.fehler.join(', '))));
|
|
505
|
+
if (v.id === e.vorlage) { o.selected = true; }
|
|
506
|
+
selV.appendChild(o);
|
|
507
|
+
});
|
|
508
|
+
selV.addEventListener('change', function () {
|
|
509
|
+
if (!selV.value) { return; }
|
|
510
|
+
var v = vorschlag(S.current, selV.value);
|
|
511
|
+
if (v) {
|
|
512
|
+
v.ziel = e.ziel;
|
|
513
|
+
S.entwurf = v;
|
|
514
|
+
S.openRow = null;
|
|
515
|
+
zeichneErgebnis();
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
lv.appendChild(selV);
|
|
519
|
+
acts.appendChild(lv);
|
|
520
|
+
|
|
521
|
+
var bRoh = el('button', 'btn schmal',
|
|
522
|
+
e.roh ? tr('tpl.showProposal') : tr('tpl.showAllPoints'));
|
|
523
|
+
/* Der Knopf verlaesst den Vorschlag der Vorlage und zeigt den ganzen
|
|
524
|
+
Bestand des Geraets — nicht zu verwechseln mit dem Filter darunter,
|
|
525
|
+
der nur die vorgeschlagenen Zeilen ein- und ausblendet. Deshalb
|
|
526
|
+
steht das Geraet im Namen und der Rest im Hinweis. */
|
|
527
|
+
bRoh.title = e.roh ? tr('tpl.showProposalHelp') : tr('tpl.showAllPointsHelp');
|
|
528
|
+
if (!e.vorschlag) { bRoh.disabled = true; }
|
|
529
|
+
bRoh.addEventListener('click', function () {
|
|
530
|
+
var rohJetzt = !e.roh;
|
|
531
|
+
/* Dieselben zwei Schritte wie in `waehle`: die Vorlage, aus der der
|
|
532
|
+
Alias entstanden ist, bleibt festgenagelt, und was im Alias steht,
|
|
533
|
+
gilt. Ohne sie sprang die Liste beim Umschalten auf die
|
|
534
|
+
Vorlagenwerte zurueck - ACTUAL von `sensor.door` (so steht es im
|
|
535
|
+
Alias) auf `sensor.window` (so will es die Vorlage) -, der
|
|
536
|
+
Vermerk „Vorlage: …" verschwand, und der naechste Trockenlauf
|
|
537
|
+
meldete „3 aendern sich" und haette es geschrieben, ohne dass
|
|
538
|
+
jemand „Werte der Vorlage uebernehmen" gedrueckt hatte. Genau die
|
|
539
|
+
stille Aenderung, die der Bestandsvorrang abschaffen sollte
|
|
540
|
+
(26.08.2026). */
|
|
541
|
+
var fest = null;
|
|
542
|
+
var vorh = aliasFuer(S.current);
|
|
543
|
+
if (vorh && S.objects[vorh] && S.objects[vorh].native &&
|
|
544
|
+
S.objects[vorh].native.vorlage) {
|
|
545
|
+
var gid = S.objects[vorh].native.vorlage;
|
|
546
|
+
if (S.VORLAGEN.some(function (x) { return x.id === gid; })) { fest = gid; }
|
|
547
|
+
}
|
|
548
|
+
var neuE = rohJetzt ? alleUndVorlage(S.current, fest)
|
|
549
|
+
: vorschlag(S.current, fest);
|
|
550
|
+
if (!neuE) { return; }
|
|
551
|
+
neuE.roh = rohJetzt;
|
|
552
|
+
neuE.vorschlag = true;
|
|
553
|
+
neuE.tplName = e.tplName; neuE.grund = e.grund; neuE.ziel = e.ziel;
|
|
554
|
+
if (fest) { neuE.vonHandGewaehlt = false; neuE.ausBestand = true; }
|
|
555
|
+
if (vorh && S.objects[vorh]) { bestandVorrang(neuE, vorh); }
|
|
556
|
+
S.entwurf = neuE; S.openRow = null; zeichneErgebnis();
|
|
557
|
+
});
|
|
558
|
+
/* An der Quelle bleibt die Musterwahl im Vorlagenkasten, wo sie
|
|
559
|
+
hingehoert: dort steht daneben, welche Vorlage gegriffen hat, und
|
|
560
|
+
beides zusammen ist die Erkennung. Nur im Aliasmodus gibt es
|
|
561
|
+
diesen Kasten nicht - dann wandert sie weiter unten in die Leiste
|
|
562
|
+
ueber der Liste. */
|
|
563
|
+
acts.appendChild(musterBlock);
|
|
564
|
+
musterBlock = null;
|
|
565
|
+
if (ausgangFeld) { acts.appendChild(ausgangFeld); }
|
|
566
|
+
acts.appendChild(el('span', 'luecke'));
|
|
567
|
+
var knoepfe = el('div', 'knoepfe');
|
|
568
|
+
/* Gilt der Bestand (weil es den Alias schon gibt) und will die
|
|
569
|
+
Vorlage etwas anderes, steht hier der Weg dorthin - als bewusster
|
|
570
|
+
Klick, nicht als stille Aenderung (Ricardo, 25.08.2026). */
|
|
571
|
+
if (e.bestandGilt) {
|
|
572
|
+
var anders = e.states.filter(function (s0) {
|
|
573
|
+
return s0.on && vorlagenAbweichung(s0).length;
|
|
574
|
+
}).length;
|
|
575
|
+
if (anders) {
|
|
576
|
+
var bTpl = el('button', 'btn mini wichtig', tr('tpl.applyTemplate', anders));
|
|
577
|
+
bTpl.title = tr('tpl.applyTemplateHint');
|
|
578
|
+
bTpl.addEventListener('click', function () {
|
|
579
|
+
vorlageAnwenden(e);
|
|
580
|
+
S.openRow = null;
|
|
581
|
+
zeichneErgebnis();
|
|
582
|
+
});
|
|
583
|
+
knoepfe.appendChild(bTpl);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
knoepfe.appendChild(bRoh);
|
|
587
|
+
acts.appendChild(knoepfe);
|
|
588
|
+
sg.appendChild(acts);
|
|
589
|
+
host.appendChild(sg);
|
|
590
|
+
}
|
|
591
|
+
/* Ohne Vorlagenkasten — etwa bei einem fertigen Alias — bleibt die
|
|
592
|
+
Musterwahl oben im Kopf stehen. */
|
|
593
|
+
if (musterBlock) {
|
|
594
|
+
head.appendChild(musterBlock);
|
|
595
|
+
}
|
|
596
|
+
return musterBlock;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/* Punkte, die im gewaehlten Muster keinen Platz haben, aber als
|
|
600
|
+
Messwerte durchgehen. */
|
|
601
|
+
function baueInfoKarte(host, e, infoNamen) {
|
|
602
|
+
if (infoNamen.length) {
|
|
603
|
+
var ic = el('div', 'card infod');
|
|
604
|
+
var ich = el('div', 'ch');
|
|
605
|
+
/* Hier stand „info" und „zaehlt der Detektor separat". Beides sind
|
|
606
|
+
unsere Woerter: „info" ist der interne Name des Sammeltyps, und
|
|
607
|
+
wer den Detektor nicht kennt, weiss nach dem Satz weniger als
|
|
608
|
+
vorher. Jetzt steht da, worum es geht - und der Zusatz sagt
|
|
609
|
+
gleich, ob man handeln muss. */
|
|
610
|
+
ich.appendChild(el('span', 'typ', tr('info.notPartOf', e.want || '')));
|
|
611
|
+
ich.appendChild(el('span', 'chip mut', tr('info.countedSeparately')));
|
|
612
|
+
ic.appendChild(ich);
|
|
613
|
+
var ie = el('div', 'infoerkl');
|
|
614
|
+
ie.style.padding = '10px 15px 12px';
|
|
615
|
+
ie.style.fontSize = '11.5px';
|
|
616
|
+
ie.style.color = 'var(--ink-3)';
|
|
617
|
+
ie.style.lineHeight = '1.6';
|
|
618
|
+
ie.appendChild(el('b', null, tr('info.sameDatapoints', infoNamen.join(', '))));
|
|
619
|
+
ie.appendChild(document.createTextNode(tr('info.explain', e.want || '')));
|
|
620
|
+
ic.appendChild(ie);
|
|
621
|
+
host.appendChild(ic);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/* Was sich gegenueber dem gespeicherten Stand aendern wuerde. */
|
|
626
|
+
function baueDiffKarte(host, e) {
|
|
627
|
+
var diff = unterschiede(e);
|
|
628
|
+
if (diff.length) {
|
|
629
|
+
var dc = el('div', 'card');
|
|
630
|
+
var dch = el('div', 'ch');
|
|
631
|
+
var gibtsSchon = !!S.objects[e.ziel || e.kanal];
|
|
632
|
+
dch.appendChild(el('span', 'typ', gibtsSchon ? tr('diff.changes') : tr('diff.deviations')));
|
|
633
|
+
dch.appendChild(el('span', 'chip warn', gibtsSchon ? tr('diff.open', diff.length) : tr('diff.points', diff.length)));
|
|
634
|
+
dch.appendChild(el('span', 'chip mut', gibtsSchon ? tr('diff.againstStored') : tr('diff.nothingExists')));
|
|
635
|
+
dc.appendChild(dch);
|
|
636
|
+
diff.forEach(function (z) {
|
|
637
|
+
var r = el('div', 'slot');
|
|
638
|
+
/* Der Name bekommt so viel Platz, wie er braucht, und der Text
|
|
639
|
+
steht danach immer an derselben Stelle. Vorher waren es feste
|
|
640
|
+
150 px: ein langer Name wie 1_VALVE_STATE_ERROR_POSITION lief
|
|
641
|
+
darueber und schob sich in den Text daneben. */
|
|
642
|
+
r.style.gridTemplateColumns = '15px minmax(0, max-content) 1fr';
|
|
643
|
+
r.style.columnGap = '14px';
|
|
644
|
+
r.appendChild(el('span', 'mk', '•'));
|
|
645
|
+
r.appendChild(el('span', 'sn', z.n));
|
|
646
|
+
r.appendChild(el('span', 'who', z.was));
|
|
647
|
+
dc.appendChild(r);
|
|
648
|
+
});
|
|
649
|
+
host.appendChild(dc);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/* Pruefungen, Fusszeile und die Knopfleiste unten - beschriftet
|
|
654
|
+
nach dem, was der Klick tatsaechlich tut. */
|
|
655
|
+
function setzeKnoepfe(e, haupt, pflichtFehlt, _infoNamen) {
|
|
656
|
+
baueChecks(e, haupt, pflichtFehlt);
|
|
657
|
+
|
|
658
|
+
/* Der Knopf soll sagen, was er tut — nicht, wie er es tut. */
|
|
659
|
+
var zielId2 = e.ziel || e.kanal;
|
|
660
|
+
var bd = $('#btn-dry');
|
|
661
|
+
var ba = $('#btn-dry-alle');
|
|
662
|
+
var mehr = e.instanzen && e.instanzen.length > 1;
|
|
663
|
+
var mehrK = !mehr && (e.kanalGeraete || []).length > 1;
|
|
664
|
+
/* Die Knoepfe gehoeren zum Modus, nicht nur zum Objekt: Im Modus
|
|
665
|
+
„Alias anlegen" wird angelegt, nicht verwaltet — Verlegen, Quelle
|
|
666
|
+
tauschen und Entfernen haben dort nichts zu suchen. Im Modus
|
|
667
|
+
„Alias bearbeiten" wird bearbeitet, keine Vorlage gebaut
|
|
668
|
+
(Ricardo, 05.09.2026). */
|
|
669
|
+
var imAliasmodus = S.baumModus === 'aliase';
|
|
670
|
+
var bTpl = $('#btn-tpl');
|
|
671
|
+
if (bTpl) { bTpl.hidden = imAliasmodus; }
|
|
672
|
+
tplKnopf(true);
|
|
673
|
+
delKnopf(imAliasmodus && !!S.objects[zielId2] && zielId2.indexOf('alias.') === 0);
|
|
674
|
+
if (bd) {
|
|
675
|
+
bd.disabled = false;
|
|
676
|
+
/* Bei mehreren Ausgaengen den Ausgang beim Namen nennen — „für den
|
|
677
|
+
ausgewählten Ausgang" waere lang und sagt weniger. */
|
|
678
|
+
bd.textContent = mehr
|
|
679
|
+
? (S.objects[zielId2] ? tr('write.updateNamed', e.zielName) : tr('write.createNamed', e.zielName))
|
|
680
|
+
: mehrK
|
|
681
|
+
? (S.objects[zielId2] ? tr('write.updateAsOne') : tr('write.createAsOne'))
|
|
682
|
+
: (S.objects[zielId2] ? tr('write.updateAlias') : tr('write.createAlias'));
|
|
683
|
+
bd.title = zielId2;
|
|
684
|
+
}
|
|
685
|
+
if (ba) {
|
|
686
|
+
ba.hidden = !mehr && !mehrK;
|
|
687
|
+
if (mehr) {
|
|
688
|
+
ba.textContent = tr('write.createAll', e.instanzen.length);
|
|
689
|
+
ba.title = e.instanzen.map(function (n) {
|
|
690
|
+
return (e.zielOrdner || '') + '.' + ausgangName(e, n);
|
|
691
|
+
}).join(String.fromCharCode(10));
|
|
692
|
+
} else if (mehrK) {
|
|
693
|
+
ba.textContent = tr('write.createPerChannel', e.kanalGeraete.length);
|
|
694
|
+
/* Im Hinweis stehen die Namen, die entstehen wuerden — sonst
|
|
695
|
+
klickt man auf eine Zahl und weiss nicht, was danach dasteht. */
|
|
696
|
+
ba.title = e.kanalGeraete.map(function (k) {
|
|
697
|
+
return (e.zielOrdner || '') + '.' + k.name + ' (' + k.typ + ')';
|
|
698
|
+
}).join(String.fromCharCode(10));
|
|
699
|
+
}
|
|
700
|
+
ba.disabled = false;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/* Offene Vermutungen sperren Erzeugen und Vorlage-Speichern:
|
|
704
|
+
„bitte pruefen" soll eine echte Entscheidung sein, kein stilles
|
|
705
|
+
Ja beim Anlegen (Ricardo, 25.08.2026). Der Grund steht daneben,
|
|
706
|
+
sonst raetselt man, warum der Knopf grau ist. */
|
|
707
|
+
var offen = rateAnzahl(e);
|
|
708
|
+
var sg = $('#sperrgrund');
|
|
709
|
+
if (offen) {
|
|
710
|
+
var grund = tr('guess.locked', offen);
|
|
711
|
+
if (bd) { bd.disabled = true; bd.title = grund; }
|
|
712
|
+
if (ba && !ba.hidden) { ba.disabled = true; ba.title = grund; }
|
|
713
|
+
var bt = $('#btn-tpl');
|
|
714
|
+
if (bt) { bt.disabled = true; bt.title = grund; }
|
|
715
|
+
if (sg) { sg.hidden = false; sg.textContent = grund; }
|
|
716
|
+
} else if (sg) {
|
|
717
|
+
sg.hidden = true;
|
|
718
|
+
sg.textContent = '';
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
export function zeichneErgebnis() {
|
|
723
|
+
/* Wandert weiter unten in die Leiste ueber der Zustandsliste. */
|
|
724
|
+
var rateKnopf = null;
|
|
725
|
+
var host = $('#res');
|
|
726
|
+
host.textContent = '';
|
|
727
|
+
/* Der Sperrgrund gehoert zum Entwurf — beim Wechsel auf Leer- oder
|
|
728
|
+
Vorlagenansicht wuerde er sonst stehen bleiben. */
|
|
729
|
+
var sg0 = $('#sperrgrund');
|
|
730
|
+
if (sg0) { sg0.hidden = true; sg0.textContent = ''; }
|
|
731
|
+
if (S.baumModus === 'vorlagen') { return zeichneVorlagenBlatt(host); }
|
|
732
|
+
var bck = $('#btn-checks');
|
|
733
|
+
if (bck) { bck.disabled = false; }
|
|
734
|
+
if (!S.current || !S.entwurf) { return zeichneLeer(host); }
|
|
735
|
+
|
|
736
|
+
var e = S.entwurf;
|
|
737
|
+
|
|
738
|
+
/* Ist das ueberhaupt ein Geraet? Ein Ordner mit vielen Geraeten darunter
|
|
739
|
+
ergibt keinen Entwurf, sondern eine Uebersicht. Vorher landeten beim
|
|
740
|
+
Klick auf mqtt-client.0 alle Messpunkte in einem einzigen Entwurf. */
|
|
741
|
+
/* Sehr viele Punkte heisst fast immer: falscher Knoten gewaehlt.
|
|
742
|
+
Nicht sperren, aber auch nicht ungefragt 1000 Zeilen zeichnen. */
|
|
743
|
+
var VIEL = 40;
|
|
744
|
+
/* Ein `device` ist die Ebene, auf der das Geraet gemeint ist — auch
|
|
745
|
+
wenn viele Punkte darunter haengen. Ohne diese Ausnahme landete
|
|
746
|
+
jedes Homematic-Geraet in der Uebersicht „2 Geraete darunter" und
|
|
747
|
+
liess sich nur kanalweise bearbeiten. */
|
|
748
|
+
var istGeraetObjekt = !!(S.objects[S.current] && S.objects[S.current].type === 'device');
|
|
749
|
+
if (!e.vorschlag && !istGeraetObjekt && e.states.length > VIEL && !e.trotzdem) {
|
|
750
|
+
return zeichneZuViel(host, e);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/* --- erkannt (frei) und erzwungen --- */
|
|
754
|
+
var haupt = ermittleErkennung(e);
|
|
755
|
+
|
|
756
|
+
var head = baueKopf();
|
|
757
|
+
|
|
758
|
+
/* --- Musterwahl --- */
|
|
759
|
+
var pflichtFehlt = haupt ? haupt.states.filter(function (s) { return s.required && !s.id; }) : [];
|
|
760
|
+
var mw = baueMusterwahl(e, haupt, pflichtFehlt);
|
|
761
|
+
rateKnopf = mw.rateKnopf;
|
|
762
|
+
var musterBlock = mw.block; /* wandert unten in den Vorlagenkasten */
|
|
763
|
+
host.appendChild(head);
|
|
764
|
+
|
|
765
|
+
var istQuelle = baueZielleiste(host, e);
|
|
766
|
+
|
|
767
|
+
musterBlock = baueVorlagenkasten(host, head, e, istQuelle, musterBlock);
|
|
768
|
+
|
|
769
|
+
/* Gleich unter die Erkennung: was hier fehlt, entscheidet mit
|
|
770
|
+
darueber, ob eine Vorlage ueberhaupt greift. */
|
|
771
|
+
mqttKarte(host, S.current);
|
|
772
|
+
|
|
773
|
+
if (!S.objects[S.current]) {
|
|
774
|
+
/* Hier stand eine Erklaerung, dass MQTT fuer Zwischenebenen keine
|
|
775
|
+
Objekte anlegt. Sie ist richtig, half aber niemandem: kein
|
|
776
|
+
Risiko, keine Handlung, nichts anders zu machen. Und sie
|
|
777
|
+
widersprach dem Erkennungskasten drei Zeilen darueber, der
|
|
778
|
+
gleichzeitig „Identifiziert als: Tasmota-Steckdose" meldete.
|
|
779
|
+
Wer nichts damit anfangen kann, soll es auch nicht lesen
|
|
780
|
+
muessen — das Wissen steht im README, nicht in der Oberflaeche. */
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
var pl = berechnePlaetze(e, haupt);
|
|
784
|
+
var imInfo = pl.imInfo;
|
|
785
|
+
|
|
786
|
+
baueListe(host, e, pl, rateKnopf, musterBlock);
|
|
787
|
+
rateKnopf = null;
|
|
788
|
+
musterBlock = null;
|
|
789
|
+
|
|
790
|
+
/* --- info-Karte --- */
|
|
791
|
+
var infoNamen = Object.keys(imInfo);
|
|
792
|
+
baueInfoKarte(host, e, infoNamen);
|
|
793
|
+
|
|
794
|
+
/* --- Änderungen gegenüber dem, was gespeichert ist --- */
|
|
795
|
+
baueDiffKarte(host, e);
|
|
796
|
+
|
|
797
|
+
/* --- Prüfungen, Fusszeile, Knopfleiste --- */
|
|
798
|
+
setzeKnoepfe(e, haupt, pflichtFehlt, infoNamen);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/* Uebersicht ueber alle Geraete unterhalb eines Ordners. */
|
|
802
|
+
function zeichneOrdner(host, wurzel) {
|
|
803
|
+
var geraete = geraeteDarunter(wurzel);
|
|
804
|
+
|
|
805
|
+
var head = el('div', 'rhead');
|
|
806
|
+
var links = el('div');
|
|
807
|
+
var ordnerName = zusatzName(wurzel);
|
|
808
|
+
links.appendChild(el('div', 'id', ordnerName || wurzel.split('.').pop()));
|
|
809
|
+
links.appendChild(el('div', 'pfad', wurzel));
|
|
810
|
+
links.appendChild(el('div', 'sub',
|
|
811
|
+
tr('folder.devicesBelow', geraete.length, kindZustaende(wurzel).length)));
|
|
812
|
+
head.appendChild(links);
|
|
813
|
+
host.appendChild(head);
|
|
814
|
+
|
|
815
|
+
if (!geraete.length) {
|
|
816
|
+
host.appendChild(el('div', 'empty', tr('folder.nothingHere')));
|
|
817
|
+
$('#btn-dry').disabled = true;
|
|
818
|
+
tplKnopf(false);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
var mitAlias = 0, ohneVorlage = 0;
|
|
823
|
+
var card = el('div', 'card');
|
|
824
|
+
var ch = el('div', 'ch');
|
|
825
|
+
ch.appendChild(el('span', 'typ', tr('folder.devices')));
|
|
826
|
+
var zaehler = el('span', 'chip mut', tr('folder.found', geraete.length));
|
|
827
|
+
ch.appendChild(zaehler);
|
|
828
|
+
card.appendChild(ch);
|
|
829
|
+
|
|
830
|
+
var cap = el('div', 'lhead');
|
|
831
|
+
cap.style.gridTemplateColumns = '1fr 190px 130px 12px';
|
|
832
|
+
[tr('folder.colSource'), tr('folder.colDetected'), tr('folder.colAlias'), ''].forEach(function (t) { cap.appendChild(el('span', null, t)); });
|
|
833
|
+
card.appendChild(cap);
|
|
834
|
+
|
|
835
|
+
geraete.forEach(function (g) {
|
|
836
|
+
var vorhanden = aliasFuer(g.id);
|
|
837
|
+
if (vorhanden) { mitAlias++; }
|
|
838
|
+
if (!g.vorlage) { ohneVorlage++; }
|
|
839
|
+
|
|
840
|
+
var row = el('div', 'erow');
|
|
841
|
+
row.style.gridTemplateColumns = '1fr 190px 130px 12px';
|
|
842
|
+
row.tabIndex = 0;
|
|
843
|
+
|
|
844
|
+
/* Ohne den Namen heissen die Kanaele eines Homematic-Geraets hier
|
|
845
|
+
schlicht „0" und „1". */
|
|
846
|
+
var gName = zusatzName(g.id);
|
|
847
|
+
var nm = el('span', 'nm3', g.id.slice(wurzel.length + 1));
|
|
848
|
+
if (gName) {
|
|
849
|
+
nm.appendChild(el('span', 'nam', gName));
|
|
850
|
+
nm.title = g.id + ' · ' + gName;
|
|
851
|
+
}
|
|
852
|
+
row.appendChild(nm);
|
|
853
|
+
|
|
854
|
+
var vl = el('span', 'ro2');
|
|
855
|
+
if (g.vorlage) { vl.textContent = sprachtext(g.vorlage.name); }
|
|
856
|
+
else { vl.textContent = tr('tpl.none'); vl.style.color = 'var(--warn)'; }
|
|
857
|
+
row.appendChild(vl);
|
|
858
|
+
|
|
859
|
+
var al = el('span');
|
|
860
|
+
al.style.textAlign = 'right';
|
|
861
|
+
al.appendChild(el('span', 'chip ' + (vorhanden ? 'ok' : 'mut'), vorhanden ? tr('folder.aliasThere') : tr('folder.aliasMissing')));
|
|
862
|
+
row.appendChild(al);
|
|
863
|
+
row.appendChild(el('span', 'ca2', '›'));
|
|
864
|
+
|
|
865
|
+
var geh = function () { waehle(g.id); };
|
|
866
|
+
row.addEventListener('click', geh);
|
|
867
|
+
row.addEventListener('keydown', function (ev) {
|
|
868
|
+
if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); geh(); }
|
|
869
|
+
});
|
|
870
|
+
card.appendChild(row);
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
ch.appendChild(el('span', 'chip ' + (mitAlias === geraete.length ? 'ok' : 'warn'),
|
|
874
|
+
tr('folder.withAlias', mitAlias)));
|
|
875
|
+
if (ohneVorlage) { ch.appendChild(el('span', 'chip warn', tr('folder.withoutTemplate', ohneVorlage))); }
|
|
876
|
+
host.appendChild(card);
|
|
877
|
+
|
|
878
|
+
var hin = el('div', 'aside');
|
|
879
|
+
hin.appendChild(el('b', null, tr('folder.clickHint')));
|
|
880
|
+
hin.appendChild(document.createTextNode(tr('folder.clickHintText')));
|
|
881
|
+
host.appendChild(hin);
|
|
882
|
+
|
|
883
|
+
var bd = $('#btn-dry');
|
|
884
|
+
if (bd) { bd.disabled = true; bd.textContent = tr('write.createAlias'); bd.title = tr('write.pickDeviceFirst'); }
|
|
885
|
+
tplKnopf(false);
|
|
886
|
+
var chip = $('#checkchip');
|
|
887
|
+
if (chip) { chip.className = 'chip mut'; chip.textContent = ''; }
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/* Gibt es den Alias schon, sollen seine Punkte angehakt sein.
|
|
891
|
+
|
|
892
|
+
Vorher kamen die Haken allein aus der Vorlage. Wer ein fertiges
|
|
893
|
+
Geraet aufmachte, sah den Vorschlag fuer ein neues — und ein Punkt,
|
|
894
|
+
den er sich damals von Hand dazugeholt hatte, stand ungehakt da.
|
|
895
|
+
Ein Klick auf „Alias aktualisieren" haette ihn entfernt. Der
|
|
896
|
+
Trockenlauf haette es gezeigt, aber darauf darf man sich nicht
|
|
897
|
+
verlassen.
|
|
898
|
+
|
|
899
|
+
Vereinigung statt Ersetzung: angehakt wird, was im Alias steht
|
|
900
|
+
ODER was die Vorlage vorschlaegt. In die gefaehrliche Richtung —
|
|
901
|
+
etwas verlieren — kann damit nichts mehr passieren, und ein
|
|
902
|
+
Vorschlag aus einer neueren Vorlage geht trotzdem nicht verloren.
|
|
903
|
+
Was dadurch dazukommt, meldet die Aenderungs-Karte. */
|
|
904
|
+
/* Wohin der Alias gehoert. Muss feststehen, bevor die Kopfzeile
|
|
905
|
+
gezeichnet wird — sonst meldet die „wird neu angelegt", waehrend die
|
|
906
|
+
Zielzeile darunter schon „gibt es schon" sagt. */
|
|
907
|
+
/* ================== Dialoge ================== */
|
|
908
|
+
/* Die <dialog>-Blöcke stehen im HTML hinter diesem Skript. Zur Laufzeit
|
|
909
|
+
des Skripts gibt es sie noch nicht — erst nach DOMContentLoaded
|
|
910
|
+
verkabeln, sonst wirft der erste Zugriff und alles danach fällt aus. */
|
|
911
|
+
function verkabeleDialoge() {
|
|
912
|
+
var bc = $('#btn-checks'), bd = $('#btn-dry'), bw = $('#btn-dry-write');
|
|
913
|
+
if (bc) { bc.addEventListener('click', function () { $('#dlg-checks').showModal(); }); }
|
|
914
|
+
if (bd) { bd.addEventListener('click', function () { zeigeTrockenlauf(false); }); }
|
|
915
|
+
var ba2 = $('#btn-dry-alle');
|
|
916
|
+
if (ba2) { ba2.addEventListener('click', function () { zeigeTrockenlauf(true); }); }
|
|
917
|
+
if (bw) { bw.addEventListener('click', schreibeObjekte); }
|
|
918
|
+
var bdel = $('#btn-del'), bdg = $('#btn-del-go');
|
|
919
|
+
if (bdel) { bdel.addEventListener('click', zeigeLoeschen); }
|
|
920
|
+
if (bdg) { bdg.addEventListener('click', loescheAlias); }
|
|
921
|
+
var bmq = $('#btn-mqtt-write');
|
|
922
|
+
if (bmq) { bmq.addEventListener('click', mqttSchreiben); }
|
|
923
|
+
var bt = $('#btn-tpl'), bts = $('#btn-tpl-save');
|
|
924
|
+
if (bt) { bt.addEventListener('click', zeigeVorlagenDialog); }
|
|
925
|
+
if (bts) { bts.addEventListener('click', speichereVorlageAusDialog); }
|
|
926
|
+
document.querySelectorAll('dialog [data-close]').forEach(function (b) {
|
|
927
|
+
b.addEventListener('click', function () { b.closest('dialog').close(); });
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
if (document.readyState === 'loading') {
|
|
931
|
+
document.addEventListener('DOMContentLoaded', verkabeleDialoge);
|
|
932
|
+
} else {
|
|
933
|
+
verkabeleDialoge();
|
|
934
|
+
}
|