iobroker.alias-workbench 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -7
- package/admin/css/werkbank.css +73 -0
- package/admin/i18n/de.json +65 -0
- package/admin/i18n/en.json +65 -0
- package/admin/i18n/es.json +65 -0
- package/admin/i18n/fr.json +65 -0
- package/admin/i18n/it.json +65 -0
- package/admin/i18n/nl.json +65 -0
- package/admin/i18n/pl.json +65 -0
- package/admin/i18n/pt.json +65 -0
- package/admin/i18n/ru.json +65 -0
- package/admin/i18n/uk.json +65 -0
- package/admin/i18n/zh-cn.json +65 -0
- package/admin/js/attrappe.js +749 -0
- package/admin/js/baum.js +52 -5
- package/admin/js/entwurf.js +133 -6
- package/admin/js/ergebnis.js +77 -2
- package/admin/js/quellentausch.js +823 -16
- package/admin/js/sprache.js +4 -0
- package/admin/js/zustand.js +28 -0
- package/admin/tab.html +24 -0
- package/io-package.json +14 -14
- package/package.json +2 -2
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
/* Einen Teilbaum als Attrappe ausgeben.
|
|
2
|
+
|
|
3
|
+
Zweck ist nicht das Sichern, sondern das Nachbauen: aus der Datei
|
|
4
|
+
entsteht anderswo ein Geraet, das sich wie das Original verhaelt — ohne
|
|
5
|
+
dass das Original da ist. Damit laesst sich eine Vorlage fuer ein
|
|
6
|
+
Geraet bauen, das man nicht besitzt. Wer im Forum schreibt „fuer mein
|
|
7
|
+
Geraet gibt es keine Vorlage", gibt seinen Teilbaum aus und schickt die
|
|
8
|
+
Datei.
|
|
9
|
+
|
|
10
|
+
Was mitgeht, steht in einer POSITIVLISTE, nicht in einer Sperrliste.
|
|
11
|
+
Eine Sperrliste („password", „token", …) laesst immer ein Feld durch,
|
|
12
|
+
an das niemand gedacht hat. `native` faellt deshalb vollstaendig weg —
|
|
13
|
+
dort stehen IP-Adressen, Seriennummern und bei manchen Adaptern
|
|
14
|
+
Zugangsdaten, und fuer den Zweck ist davon nichts noetig.
|
|
15
|
+
|
|
16
|
+
Siehe plan_export_attrappe.md. */
|
|
17
|
+
|
|
18
|
+
import { S } from './zustand.js';
|
|
19
|
+
import { el } from './basis.js';
|
|
20
|
+
import { tr } from './sprache.js';
|
|
21
|
+
import { holeWerte, kindZustaende } from './werte.js';
|
|
22
|
+
/* Zirkulaer zu baum.js/ergebnis.js - das ist in Ordnung, weil nichts
|
|
23
|
+
davon beim Laden gerufen wird, nur in Rueckrufen. */
|
|
24
|
+
import { mitNachfrage, zweigUmschalten, zweigPfade } from './entwurf.js';
|
|
25
|
+
import { zeichneErgebnis } from './ergebnis.js';
|
|
26
|
+
import { socket } from './verbindung.js';
|
|
27
|
+
import { INSTANZ_ID } from './vorlagen.js';
|
|
28
|
+
|
|
29
|
+
/* Die Nummer steigt, sobald sich das Format aendert — sonst liesse sich
|
|
30
|
+
eine alte Datei nicht von einer neuen unterscheiden.
|
|
31
|
+
|
|
32
|
+
Dass der Wert vom eigenen Block an den Eintrag wanderte (12.10.2026),
|
|
33
|
+
hat sie NICHT erhoeht: zu dem Zeitpunkt war keine Datei im Umlauf, es
|
|
34
|
+
gab das Einlesen noch nicht, und gelesen hat das Format nur der eigene
|
|
35
|
+
Pruefpunkt Z2. Ab jetzt waere derselbe Schritt ein Versionssprung. */
|
|
36
|
+
export var FORMAT = 1;
|
|
37
|
+
|
|
38
|
+
/* Nur diese Felder aus `common` gehen mit.
|
|
39
|
+
|
|
40
|
+
`states` MUSS dabei sein: ohne die Werteliste erkennt der
|
|
41
|
+
type-detector die Plaetze mit `statesDefined` nicht, und genau die
|
|
42
|
+
Erkennung will man an der Attrappe pruefen. */
|
|
43
|
+
var COMMON_FELDER = ['name', 'role', 'type', 'unit', 'read', 'write', 'min', 'max', 'def', 'states'];
|
|
44
|
+
|
|
45
|
+
/* Ab wann ist ein Teilbaum zu gross fuer eine Attrappe?
|
|
46
|
+
|
|
47
|
+
Gemessen am 12.10.2026 an beiden Systemen. Produktiv, auf Geraeteebene:
|
|
48
|
+
Median 20 Objekte, p90 136, p99 2551 — die Ausreisser sind
|
|
49
|
+
`yr.0.forecastHourly` (3173), `dreame.0.852247104` (2857) und
|
|
50
|
+
`mercedesme.0....` (2551). Am Testsystem: Median 25, p90 63.
|
|
51
|
+
|
|
52
|
+
200 liegt deutlich ueber p90, schweigt also bei normalen Geraeten, und
|
|
53
|
+
weit unter den Unfaellen. Bei 100 ginge die Warnung schon bei jedem
|
|
54
|
+
siebten Geraet an — und man gewoehnt sich das Wegklicken an.
|
|
55
|
+
|
|
56
|
+
Es ist eine Warnung, keine Sperre: wer 3000 Objekte ausgeben will,
|
|
57
|
+
darf das, er soll es nur nicht aus Versehen tun. (Ricardo hatte am
|
|
58
|
+
12.10.2026 `0_userdata` ausgegeben und wieder eingelesen — 393
|
|
59
|
+
Objekte, darunter eine Attrappe in der Attrappe.) */
|
|
60
|
+
export var GROSS_AB = 200;
|
|
61
|
+
|
|
62
|
+
/* So viele Zeilen zeigt die Vorschau hoechstens. Bei 3000 Zeilen waere
|
|
63
|
+
der Dialog unbrauchbar, und gelesen wuerde er ohnehin nicht. */
|
|
64
|
+
var ZEILEN_MAX = 300;
|
|
65
|
+
|
|
66
|
+
/* Alles unterhalb der Wurzel, die Wurzel eingeschlossen. */
|
|
67
|
+
function unterhalb(wurzel) {
|
|
68
|
+
var pre = wurzel + '.';
|
|
69
|
+
return Object.keys(S.objects).filter(function (id) {
|
|
70
|
+
return id === wurzel || id.indexOf(pre) === 0;
|
|
71
|
+
}).sort();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Die Ebenen zwischen Wurzel und einem Objekt, die selbst kein Objekt
|
|
75
|
+
haben. ioBroker nennt so eine Stufe `virtual`; der Baum zeigt sie als
|
|
76
|
+
„VIR". Sie gehoeren in die Datei, sonst beschriebe sie den Baum
|
|
77
|
+
unvollstaendig — aber sie tragen kein `common`, und beim Einlesen soll
|
|
78
|
+
erkennbar bleiben, dass dort nie ein Objekt stand. */
|
|
79
|
+
function virtuelleEbenen(wurzel, ids) {
|
|
80
|
+
var da = {};
|
|
81
|
+
ids.forEach(function (id) { da[id] = 1; });
|
|
82
|
+
var raus = {};
|
|
83
|
+
var tiefe = wurzel.split('.').length;
|
|
84
|
+
ids.forEach(function (id) {
|
|
85
|
+
var teile = id.split('.');
|
|
86
|
+
for (var i = tiefe; i < teile.length; i++) {
|
|
87
|
+
var pfad = teile.slice(0, i).join('.');
|
|
88
|
+
if (!da[pfad] && !raus[pfad]) { raus[pfad] = 1; }
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return Object.keys(raus).sort();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function gesiebtesCommon(c) {
|
|
95
|
+
var raus = {};
|
|
96
|
+
COMMON_FELDER.forEach(function (f) {
|
|
97
|
+
if (c && c[f] !== undefined) { raus[f] = JSON.parse(JSON.stringify(c[f])); }
|
|
98
|
+
});
|
|
99
|
+
return raus;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Der Pfad RELATIV zur Wurzel — der Grund, warum die Attrappe an einem
|
|
103
|
+
anderen Ort dieselben Vorlagen trifft. Die Wurzel selbst bekommt den
|
|
104
|
+
leeren Pfad. */
|
|
105
|
+
function relativ(wurzel, id) {
|
|
106
|
+
return id === wurzel ? '' : id.slice(wurzel.length + 1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Die beiden Versionen, die in die Datei gehoeren.
|
|
110
|
+
|
|
111
|
+
`erzeugtVon` sagt, welche Werkbank die Datei geschrieben hat — wichtig,
|
|
112
|
+
wenn jemand eine Datei schickt und etwas fehlt, das eine neuere Fassung
|
|
113
|
+
mitschreibt.
|
|
114
|
+
|
|
115
|
+
`quelle` ist die wichtigere: der Aufbau der Objekte kommt vom
|
|
116
|
+
Quelladapter, und derselbe `tele.SENSOR` sieht unter zwei Fassungen des
|
|
117
|
+
mqtt-Adapters verschieden aus. Aus `wurzel` liest man die Instanz ab,
|
|
118
|
+
ihre Version nicht.
|
|
119
|
+
|
|
120
|
+
`system.adapter.*` steht NICHT in S.objects — der Reiter laedt die
|
|
121
|
+
Objekte des Baums, nicht die der Instanzen. Darum ueber den Socket. */
|
|
122
|
+
export function holeVersionen(wurzel, fertig) {
|
|
123
|
+
var teile = String(wurzel).split('.');
|
|
124
|
+
var quellInstanz = teile.length > 1 ? teile[0] + '.' + teile[1] : '';
|
|
125
|
+
var raus = { erzeugtVon: null, quelle: null };
|
|
126
|
+
var offen = 2;
|
|
127
|
+
var ab = function () { if (--offen <= 0) { fertig(raus); } };
|
|
128
|
+
|
|
129
|
+
socket.emit('getObject', INSTANZ_ID, function (err, o) {
|
|
130
|
+
if (!err && o && o.common) {
|
|
131
|
+
raus.erzeugtVon = { adapter: 'alias-workbench', version: o.common.version };
|
|
132
|
+
}
|
|
133
|
+
ab();
|
|
134
|
+
});
|
|
135
|
+
if (!quellInstanz) { ab(); return; }
|
|
136
|
+
socket.emit('getObject', 'system.adapter.' + quellInstanz, function (err, o) {
|
|
137
|
+
/* Kein Treffer ist kein Fehler: `0_userdata.0` und `alias.0` haben
|
|
138
|
+
keine eigene Instanz in diesem Sinn. */
|
|
139
|
+
if (!err && o && o.common) {
|
|
140
|
+
raus.quelle = { adapter: quellInstanz, version: o.common.version };
|
|
141
|
+
}
|
|
142
|
+
ab();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Aus dem, was geladen ist, die Datei bauen. Liest nichts nach — die
|
|
147
|
+
Werte muessen vorher in S.werte stehen und die Versionen uebergeben
|
|
148
|
+
sein (siehe `ausgeben`). */
|
|
149
|
+
export function sammle(wurzel, versionen) {
|
|
150
|
+
var ids = unterhalb(wurzel);
|
|
151
|
+
var objekte = [];
|
|
152
|
+
|
|
153
|
+
virtuelleEbenen(wurzel, ids).forEach(function (id) {
|
|
154
|
+
objekte.push({ pfad: relativ(wurzel, id), virtuell: true });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/* Der Wert steht AM Eintrag, nicht in einem eigenen Block.
|
|
158
|
+
|
|
159
|
+
Getrennt waere es die sauberere Trennung — die Objekte beschreiben
|
|
160
|
+
das Geraet, die Werte sind eine Momentaufnahme. Nur liest diese
|
|
161
|
+
Datei ein Mensch: man bekommt sie geschickt und will bei
|
|
162
|
+
`ENERGY.Power` sofort sehen, was dort steht, statt vierzig Zeilen
|
|
163
|
+
weiter unten nachzuschlagen. Ricardo, 12.10.2026.
|
|
164
|
+
|
|
165
|
+
Ein Punkt ohne Wert hat den Schluessel einfach nicht — es gibt ihn,
|
|
166
|
+
er traegt nur gerade nichts. */
|
|
167
|
+
ids.forEach(function (id) {
|
|
168
|
+
var o = S.objects[id] || {};
|
|
169
|
+
var eintrag = {
|
|
170
|
+
pfad: relativ(wurzel, id),
|
|
171
|
+
type: o.type,
|
|
172
|
+
common: gesiebtesCommon(o.common),
|
|
173
|
+
};
|
|
174
|
+
var st = S.werte[id];
|
|
175
|
+
if (st && st.val !== undefined && st.val !== null) { eintrag.wert = st.val; }
|
|
176
|
+
objekte.push(eintrag);
|
|
177
|
+
});
|
|
178
|
+
objekte.sort(function (a, b) { return a.pfad.localeCompare(b.pfad); });
|
|
179
|
+
|
|
180
|
+
var v = versionen || {};
|
|
181
|
+
return {
|
|
182
|
+
format: FORMAT,
|
|
183
|
+
wurzel: wurzel,
|
|
184
|
+
erzeugt: new Date().toISOString(),
|
|
185
|
+
erzeugtVon: v.erzeugtVon || null,
|
|
186
|
+
quelle: v.quelle || null,
|
|
187
|
+
nativeEnthalten: false,
|
|
188
|
+
objekte: objekte,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* ================== Auffaelliges in den Werten ==================
|
|
193
|
+
|
|
194
|
+
Die Werte werden gebraucht — eine JSON-Quelle wie `tele/SENSOR` ist
|
|
195
|
+
ohne ihren Inhalt nutzlos —, koennen aber eine Adresse oder eine
|
|
196
|
+
Kennung tragen. Hier wird nichts entfernt, nur hervorgehoben: was
|
|
197
|
+
auffaellig ist, entscheidet der Mensch, der die Datei verschickt. */
|
|
198
|
+
var MUSTER = [
|
|
199
|
+
{ art: 'ip', re: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/ },
|
|
200
|
+
{ art: 'mac', re: /\b[0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5}\b/ },
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
/* Wann ist ein Stueck eine Kennung?
|
|
204
|
+
|
|
205
|
+
Gesucht wird in Stuecken ab acht Zeichen aus `[0-9A-Za-z_]`. Der
|
|
206
|
+
Bindestrich gehoert NICHT dazu: sonst waere `HM-LC-Sw1PBU-FM` ein
|
|
207
|
+
Treffer, und eine Geraetebezeichnung ist keine Kennung. So zerfaellt
|
|
208
|
+
sie in kurze Stuecke und faellt durch.
|
|
209
|
+
|
|
210
|
+
Ein solches Stueck gilt als Kennung, wenn es
|
|
211
|
+
- Buchstaben UND Ziffern traegt und ziffernlastig ist (ab 40 Prozent), oder
|
|
212
|
+
- sehr lang ist (ab 20 Zeichen) und Ziffern traegt.
|
|
213
|
+
|
|
214
|
+
Die Regel entstand in drei Anlaeufen, jeder mit einer Messung:
|
|
215
|
+
|
|
216
|
+
1. „ab zwanzig Zeichen": verfehlte `QEQ0695220` und `000A9D899204D8`
|
|
217
|
+
— genau die Seriennummern, um die es geht — und markierte
|
|
218
|
+
`countSystemVariables`, einen Datenpunktnamen.
|
|
219
|
+
2. „Buchstaben UND Ziffern": fing die Seriennummern, markierte aber
|
|
220
|
+
`SetOption59`. Ein Tasmota-Befehl stuende damit in jeder
|
|
221
|
+
Tasmota-Attrappe als Kennung (Ricardo, 12.10.2026 am Bild).
|
|
222
|
+
3. Der Ziffernanteil trennt beides: `SetOption59` hat 18 Prozent,
|
|
223
|
+
`QEQ0695220` 70, `000A9D899204D8` 79. Namen mit angehaengter Nummer
|
|
224
|
+
(`Status11`, `SetOption19`, `POWER1`) bleiben darunter.
|
|
225
|
+
|
|
226
|
+
Die zweite Bedingung, die Laenge, holt lange Schluessel herein, die
|
|
227
|
+
nicht ziffernlastig sind — ein Base64-Zeichen hat oft nur ein Drittel
|
|
228
|
+
Ziffern. Ohne Ziffern faellt auch ein langes Stueck durch, sonst waere
|
|
229
|
+
`countSystemVariables` wieder dabei.
|
|
230
|
+
|
|
231
|
+
4. Dass die ziffernlastige Haelfte auch Buchstaben verlangt, kam aus
|
|
232
|
+
derselben Messung: `3.89.11.20260919` ist eine Firmware-Version, und
|
|
233
|
+
`20260919` ist zu hundert Prozent Ziffern. Daten, Versionen und
|
|
234
|
+
Zaehler sind reine Ziffern, Kennungen tragen fast immer Buchstaben.
|
|
235
|
+
|
|
236
|
+
Aufgegeben wird damit die rein numerische Seriennummer. Die ist von
|
|
237
|
+
einem Zaehler aber auch durch keine Regel zu unterscheiden — dort hilft
|
|
238
|
+
nur, dass die Werte ueberhaupt in der Vorschau stehen. */
|
|
239
|
+
var KENNUNG = /[0-9A-Za-z_]{8,}/g;
|
|
240
|
+
function istKennung(stueck) {
|
|
241
|
+
var ziffern = (stueck.match(/[0-9]/g) || []).length;
|
|
242
|
+
if (!ziffern) { return false; }
|
|
243
|
+
if (stueck.length >= 20) { return true; }
|
|
244
|
+
return /[A-Za-z]/.test(stueck) && (ziffern / stueck.length) >= 0.4;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function auffaellig(wert) {
|
|
248
|
+
var t = typeof wert === 'string' ? wert : JSON.stringify(wert);
|
|
249
|
+
if (!t) { return []; }
|
|
250
|
+
var raus = MUSTER.filter(function (m) { return m.re.test(t); }).map(function (m) { return m.art; });
|
|
251
|
+
if ((t.match(KENNUNG) || []).some(istKennung)) { raus.push('kennung'); }
|
|
252
|
+
return raus;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Die Zeilen der Vorschau — dieselbe Quelle fuer den Dialog und fuer den
|
|
256
|
+
Pruefpunkt, damit „was die Vorschau zeigt, steht auch in der Datei"
|
|
257
|
+
nicht zwei Rechenwege hat. */
|
|
258
|
+
export function vorschauZeilen(daten) {
|
|
259
|
+
return daten.objekte.map(function (o) {
|
|
260
|
+
var c = o.common || {};
|
|
261
|
+
return {
|
|
262
|
+
pfad: o.pfad,
|
|
263
|
+
type: o.virtuell ? '(ohne Objekt)' : o.type,
|
|
264
|
+
virtuell: !!o.virtuell,
|
|
265
|
+
name: typeof c.name === 'string' ? c.name : (c.name ? JSON.stringify(c.name) : ''),
|
|
266
|
+
rolle: c.role || '',
|
|
267
|
+
datentyp: c.type || '',
|
|
268
|
+
einheit: c.unit || '',
|
|
269
|
+
hatWert: 'wert' in o,
|
|
270
|
+
wert: 'wert' in o ? o.wert : undefined,
|
|
271
|
+
auffaellig: 'wert' in o ? auffaellig(o.wert) : [],
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Kennungen stehen nicht nur in den Werten.
|
|
277
|
+
|
|
278
|
+
Gemessen am 12.10.2026 an `hm-rega.0.QEQ0695220`: die Seriennummer
|
|
279
|
+
steht in `wurzel` und in zwoelf `common.name`-Werten, obwohl `native`
|
|
280
|
+
nachweislich fehlt. Das Weglassen von `native` entfernt also keine
|
|
281
|
+
Kennung, die auch im Namen oder im Pfad steht — und davon muss die
|
|
282
|
+
Vorschau sprechen, sonst verspricht sie mehr, als sie halten kann. */
|
|
283
|
+
export function auffaelligImGeruest(daten) {
|
|
284
|
+
var raus = [];
|
|
285
|
+
if (auffaellig(daten.wurzel).length) { raus.push({ wo: 'wurzel', text: daten.wurzel }); }
|
|
286
|
+
daten.objekte.forEach(function (o) {
|
|
287
|
+
var c = o.common || {};
|
|
288
|
+
var n = typeof c.name === 'string' ? c.name : (c.name ? JSON.stringify(c.name) : '');
|
|
289
|
+
if (n && auffaellig(n).length) { raus.push({ wo: o.pfad || '(Wurzel)', text: n }); }
|
|
290
|
+
});
|
|
291
|
+
return raus;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* Gleiche Bauweise wie `vAusgeben` im Vorlagenblatt. */
|
|
295
|
+
function dateiGeben(daten) {
|
|
296
|
+
var name = String(daten.wurzel).split('.').pop() || 'attrappe';
|
|
297
|
+
var text = JSON.stringify(daten, null, 2) + '\n';
|
|
298
|
+
var a = document.createElement('a');
|
|
299
|
+
a.href = URL.createObjectURL(new Blob([text], { type: 'application/json' }));
|
|
300
|
+
a.download = name + '.attrappe.json';
|
|
301
|
+
document.body.appendChild(a);
|
|
302
|
+
a.click();
|
|
303
|
+
setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 2000);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* Werte holen, dann ausgeben. `holeWerte` fragt `getForeignStates` fuer
|
|
307
|
+
alles unterhalb ab; die Wurzel selbst kommt als Einzelabfrage dazu,
|
|
308
|
+
weil das Muster `<wurzel>.*` sie nicht einschliesst. */
|
|
309
|
+
export function ausgeben(wurzel, fertig) {
|
|
310
|
+
holeWerte(wurzel, [wurzel], function () {
|
|
311
|
+
holeVersionen(wurzel, function (versionen) {
|
|
312
|
+
var daten = sammle(wurzel, versionen);
|
|
313
|
+
dateiGeben(daten);
|
|
314
|
+
if (typeof fertig === 'function') { fertig(daten); }
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* Abgewaehlte Werte herausnehmen.
|
|
320
|
+
|
|
321
|
+
Der Wert faellt weg, das Objekt bleibt — und `werteAbgewaehlt` sagt,
|
|
322
|
+
dass jemand ihn entfernt hat. Ohne diese Liste saehe ein entfernter
|
|
323
|
+
Wert aus wie ein Punkt, der gerade nichts liefert. */
|
|
324
|
+
export function ohneAbgewaehlte(daten, abgewaehlt) {
|
|
325
|
+
var raus = JSON.parse(JSON.stringify(daten));
|
|
326
|
+
var weg = [];
|
|
327
|
+
raus.objekte.forEach(function (o) {
|
|
328
|
+
if (abgewaehlt.indexOf(o.pfad) > -1 && 'wert' in o) { delete o.wert; weg.push(o.pfad); }
|
|
329
|
+
});
|
|
330
|
+
if (weg.length) { raus.werteAbgewaehlt = weg.sort(); }
|
|
331
|
+
return raus;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* ================== Die Vorschau ================== */
|
|
335
|
+
|
|
336
|
+
export function zeigeVorschau(wurzel, fertig) {
|
|
337
|
+
holeWerte(wurzel, [wurzel], function () {
|
|
338
|
+
holeVersionen(wurzel, function (versionen) {
|
|
339
|
+
var daten = sammle(wurzel, versionen);
|
|
340
|
+
baueVorschau(daten);
|
|
341
|
+
if (typeof fertig === 'function') { fertig(daten); }
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function baueVorschau(daten) {
|
|
347
|
+
var dlg = document.getElementById('dlg-mock');
|
|
348
|
+
var body = document.getElementById('mock-body');
|
|
349
|
+
var knopf = document.getElementById('btn-mock-go');
|
|
350
|
+
if (!dlg || !body || !knopf) { return; }
|
|
351
|
+
body.textContent = '';
|
|
352
|
+
|
|
353
|
+
var zeilen = vorschauZeilen(daten);
|
|
354
|
+
var mitWert = zeilen.filter(function (z) { return z.hatWert; });
|
|
355
|
+
var abgewaehlt = [];
|
|
356
|
+
|
|
357
|
+
var kopf = el('div', 'mk-kopf');
|
|
358
|
+
kopf.appendChild(el('div', 'mk-wurzel', daten.wurzel));
|
|
359
|
+
kopf.appendChild(el('div', 'mk-zahlen', tr('mock.counts',
|
|
360
|
+
zeilen.length, mitWert.length,
|
|
361
|
+
(daten.erzeugtVon && daten.erzeugtVon.version) || '?',
|
|
362
|
+
(daten.quelle && (daten.quelle.adapter + ' ' + daten.quelle.version)) || '—')));
|
|
363
|
+
kopf.appendChild(el('div', 'mk-ohne', tr('mock.noNative')));
|
|
364
|
+
body.appendChild(kopf);
|
|
365
|
+
|
|
366
|
+
/* Zu gross? Warnen, nicht sperren. */
|
|
367
|
+
if (zeilen.length > GROSS_AB) {
|
|
368
|
+
var gw = el('div', 'mk-warn mk-gross');
|
|
369
|
+
gw.appendChild(el('div', 'mk-warn-kopf', tr('mock.tooBig', zeilen.length, GROSS_AB)));
|
|
370
|
+
gw.appendChild(el('div', null, tr('mock.tooBigHint')));
|
|
371
|
+
body.appendChild(gw);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* Was auch ohne `native` mitfaehrt — Pfad und Namen. */
|
|
375
|
+
var geruest = auffaelligImGeruest(daten);
|
|
376
|
+
if (geruest.length) {
|
|
377
|
+
var warn = el('div', 'mk-warn');
|
|
378
|
+
warn.appendChild(el('div', 'mk-warn-kopf', tr('mock.inNames', geruest.length)));
|
|
379
|
+
var ul = el('ul', 'mk-warn-liste');
|
|
380
|
+
geruest.slice(0, 8).forEach(function (g) {
|
|
381
|
+
ul.appendChild(el('li', null, g.wo + ' — ' + g.text));
|
|
382
|
+
});
|
|
383
|
+
warn.appendChild(ul);
|
|
384
|
+
body.appendChild(warn);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
var tab = el('table', 'mk-tab');
|
|
388
|
+
var kz = el('tr');
|
|
389
|
+
[tr('mock.colValue'), tr('mock.colPath'), tr('mock.colType'),
|
|
390
|
+
tr('mock.colRole'), tr('mock.colName')].forEach(function (t) {
|
|
391
|
+
kz.appendChild(el('th', null, t));
|
|
392
|
+
});
|
|
393
|
+
tab.appendChild(kz);
|
|
394
|
+
|
|
395
|
+
zeilen.slice(0, ZEILEN_MAX).forEach(function (z) {
|
|
396
|
+
var tz = el('tr', z.virtuell ? 'mk-virt' : '');
|
|
397
|
+
var tdW = el('td', 'mk-wert');
|
|
398
|
+
if (z.hatWert) {
|
|
399
|
+
var lab = el('label');
|
|
400
|
+
var box = el('input');
|
|
401
|
+
box.type = 'checkbox';
|
|
402
|
+
box.checked = true;
|
|
403
|
+
box.addEventListener('change', function () {
|
|
404
|
+
var i = abgewaehlt.indexOf(z.pfad);
|
|
405
|
+
if (box.checked) { if (i > -1) { abgewaehlt.splice(i, 1); } }
|
|
406
|
+
else if (i === -1) { abgewaehlt.push(z.pfad); }
|
|
407
|
+
tz.classList.toggle('mk-aus', !box.checked);
|
|
408
|
+
});
|
|
409
|
+
lab.appendChild(box);
|
|
410
|
+
var wt = el('span', 'mk-wtext', String(z.wert));
|
|
411
|
+
if (z.auffaellig.length) {
|
|
412
|
+
wt.classList.add('auff');
|
|
413
|
+
wt.title = tr('mock.suspicious', z.auffaellig.join(', '));
|
|
414
|
+
}
|
|
415
|
+
lab.appendChild(wt);
|
|
416
|
+
tdW.appendChild(lab);
|
|
417
|
+
}
|
|
418
|
+
tz.appendChild(tdW);
|
|
419
|
+
tz.appendChild(el('td', 'mk-pfad', z.pfad || '·'));
|
|
420
|
+
tz.appendChild(el('td', null, z.type + (z.datentyp ? ' · ' + z.datentyp : '') +
|
|
421
|
+
(z.einheit ? ' ' + z.einheit : '')));
|
|
422
|
+
tz.appendChild(el('td', null, z.rolle));
|
|
423
|
+
tz.appendChild(el('td', 'mk-name', z.name));
|
|
424
|
+
tab.appendChild(tz);
|
|
425
|
+
});
|
|
426
|
+
body.appendChild(tab);
|
|
427
|
+
if (zeilen.length > ZEILEN_MAX) {
|
|
428
|
+
body.appendChild(el('div', 'mk-mehr', tr('mock.more', zeilen.length - ZEILEN_MAX)));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/* Den Knopf neu setzen, damit kein Zuhoerer eines frueheren Aufrufs
|
|
432
|
+
haengen bleibt und die Datei zweimal schreibt. */
|
|
433
|
+
var neu = knopf.cloneNode(true);
|
|
434
|
+
knopf.parentNode.replaceChild(neu, knopf);
|
|
435
|
+
neu.addEventListener('click', function () {
|
|
436
|
+
dateiGeben(ohneAbgewaehlte(daten, abgewaehlt));
|
|
437
|
+
dlg.close();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
if (!dlg.open) { dlg.showModal(); }
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/* ================== Das Menue am Baum ==================
|
|
444
|
+
|
|
445
|
+
Der Baum hatte bisher nur `click`. Ein eigenes Menue statt des
|
|
446
|
+
Browsermenues, weil dort nichts steht, was hier hilft. */
|
|
447
|
+
|
|
448
|
+
var offenesMenue = null;
|
|
449
|
+
|
|
450
|
+
/* Welche Zeile das Menue meint.
|
|
451
|
+
|
|
452
|
+
Ein Rechtsklick waehlt nicht aus — das bleibt dem Linksklick. Damit
|
|
453
|
+
stand aber nirgends, von WELCHER Zeile die Attrappe entstehen wuerde,
|
|
454
|
+
wenn man eine andere als die gewaehlte anklickt (Ricardo, 12.10.2026).
|
|
455
|
+
Das ist nicht nur Kosmetik: so erzeugt man die Attrappe vom falschen
|
|
456
|
+
Knoten. Die Marke ist bewusst eine andere als die Auswahl `sel`, sonst
|
|
457
|
+
saehen zwei Zeilen gleich aus. */
|
|
458
|
+
var markierteZeile = null;
|
|
459
|
+
|
|
460
|
+
export function menueZu() {
|
|
461
|
+
if (offenesMenue) { offenesMenue.remove(); offenesMenue = null; }
|
|
462
|
+
if (markierteZeile) { markierteZeile.classList.remove('menueziel'); markierteZeile = null; }
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function beiKlickDaneben(ev) {
|
|
466
|
+
if (offenesMenue && !offenesMenue.contains(ev.target)) { menueZu(); }
|
|
467
|
+
}
|
|
468
|
+
document.addEventListener('mousedown', beiKlickDaneben);
|
|
469
|
+
document.addEventListener('keydown', function (ev) { if (ev.key === 'Escape') { menueZu(); } });
|
|
470
|
+
window.addEventListener('scroll', menueZu, true);
|
|
471
|
+
window.addEventListener('resize', menueZu);
|
|
472
|
+
|
|
473
|
+
export function zeigeMenue(ev, knotenId, zeile) {
|
|
474
|
+
ev.preventDefault();
|
|
475
|
+
menueZu();
|
|
476
|
+
|
|
477
|
+
var ziel = zeile || (ev.currentTarget && ev.currentTarget.classList
|
|
478
|
+
&& ev.currentTarget.classList.contains('node') ? ev.currentTarget : null);
|
|
479
|
+
if (ziel) { ziel.classList.add('menueziel'); markierteZeile = ziel; }
|
|
480
|
+
|
|
481
|
+
var m = el('div', 'baummenue');
|
|
482
|
+
var z = el('div', 'bm-zeile', tr('tree.exportMock'));
|
|
483
|
+
z.title = tr('tree.exportMockHint', knotenId);
|
|
484
|
+
z.addEventListener('click', function () {
|
|
485
|
+
menueZu();
|
|
486
|
+
/* Erst zeigen, dann schreiben — wie ueberall sonst in der Werkbank.
|
|
487
|
+
Hier geht nichts in die Objekte, aber die Datei verlaesst das
|
|
488
|
+
Haus, und was darin steht, soll vorher jemand gesehen haben. */
|
|
489
|
+
zeigeVorschau(knotenId);
|
|
490
|
+
});
|
|
491
|
+
m.appendChild(z);
|
|
492
|
+
|
|
493
|
+
/* Einlesen haengt nicht am geklickten Knoten — eine Attrappe entsteht
|
|
494
|
+
immer unter `0_userdata.0.wb-attrappe`. Der Eintrag steht trotzdem
|
|
495
|
+
hier: alles zur Attrappe an einer Stelle, und der Dialog sagt, wo
|
|
496
|
+
sie landet. */
|
|
497
|
+
var zi = el('div', 'bm-zeile', tr('tree.importMock'));
|
|
498
|
+
zi.title = tr('tree.importMockHint', ATTRAPPEN_WURZEL);
|
|
499
|
+
zi.addEventListener('click', function () {
|
|
500
|
+
menueZu();
|
|
501
|
+
var f = document.getElementById('datei-attrappe');
|
|
502
|
+
if (f) { f.click(); }
|
|
503
|
+
});
|
|
504
|
+
m.appendChild(zi);
|
|
505
|
+
|
|
506
|
+
/* Mehrere Zweige als ein Geraet - dasselbe wie Strg-Klick im Baum,
|
|
507
|
+
nur sichtbar. Ohne diesen Eintrag faende die Funktion nur, wer
|
|
508
|
+
weiss, dass es sie gibt; und am Tablet gibt es gar keine Taste.
|
|
509
|
+
|
|
510
|
+
Nur an waehlbaren Zweigen und nur, wenn schon etwas gewaehlt ist -
|
|
511
|
+
ohne eine erste Auswahl gibt es nichts, wozu man etwas dazunimmt. */
|
|
512
|
+
if (S.current && knotenId !== S.current && kindZustaende(knotenId).length) {
|
|
513
|
+
var drin = zweigPfade().indexOf(knotenId) !== -1;
|
|
514
|
+
var zm = el('div', 'bm-zeile', drin ? tr('tree.branchRemove') : tr('tree.branchAdd'));
|
|
515
|
+
zm.title = tr('tree.branchHint');
|
|
516
|
+
zm.addEventListener('click', function () {
|
|
517
|
+
menueZu();
|
|
518
|
+
mitNachfrage(knotenId, function () {
|
|
519
|
+
var grund = zweigUmschalten(knotenId);
|
|
520
|
+
if (grund === 'fremd') {
|
|
521
|
+
S.zweigMeldung = tr('tree.branchForeign');
|
|
522
|
+
zeichneErgebnis();
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
m.appendChild(zm);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/* Erst anhaengen, dann messen: vorher hat das Menue keine Groesse und
|
|
530
|
+
liesse sich nicht am Fensterrand ausrichten. */
|
|
531
|
+
m.style.left = '0px';
|
|
532
|
+
m.style.top = '0px';
|
|
533
|
+
document.body.appendChild(m);
|
|
534
|
+
var r = m.getBoundingClientRect();
|
|
535
|
+
var x = Math.min(ev.clientX, window.innerWidth - r.width - 6);
|
|
536
|
+
var y = Math.min(ev.clientY, window.innerHeight - r.height - 6);
|
|
537
|
+
m.style.left = Math.max(6, x) + 'px';
|
|
538
|
+
m.style.top = Math.max(6, y) + 'px';
|
|
539
|
+
|
|
540
|
+
offenesMenue = m;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/* ================== Einlesen ==================
|
|
544
|
+
|
|
545
|
+
Eingelesen wird NIE an den Originalpfad. Eine Attrappe landet immer
|
|
546
|
+
unter `0_userdata.0.wb-attrappe.<name>`:
|
|
547
|
+
|
|
548
|
+
- Der schlimme Fall kommt baulich nicht vor. Ein fremder Export von
|
|
549
|
+
`hm-rpc.0.ABC123` zurueckzuschreiben, wo er herkam, traefe fremde
|
|
550
|
+
Geraete; ein eigener ueberschriebe das Echte. Eine Einstellung
|
|
551
|
+
(„Einlesen erlauben") leistet das nicht: wer sie einschaltet, laesst
|
|
552
|
+
sie an, und sie schuetzt nicht vor dem Ueberschreiben.
|
|
553
|
+
- Die Vorlagen greifen trotzdem, weil sie RELATIVE Namen pruefen
|
|
554
|
+
(`stat.POWER`, `1.LEVEL`), nicht die Wurzel. Der Pruefstand zeigt das
|
|
555
|
+
seit Monaten: `wbFake` liegt unter `0_userdata.0.wbfake.mqtt.…`, und
|
|
556
|
+
die mitgelieferten Vorlagen passen unveraendert.
|
|
557
|
+
|
|
558
|
+
`0_userdata.0` und nicht `alias-workbench.0`: dort gehoeren vom Nutzer
|
|
559
|
+
angelegte Objekte hin, der Pruefstand benutzt es schon so, und
|
|
560
|
+
`alias-workbench.0` ist ein `meta`-Objekt fuer die Dateiablage.
|
|
561
|
+
|
|
562
|
+
Geschrieben wird erst, wenn der Dialog gezeigt hat, was entstehen
|
|
563
|
+
wuerde — dieselbe Zusage wie beim Trockenlauf. Der Trockenlauf des
|
|
564
|
+
Alias selbst passt nicht: er haengt an `S.entwurf` und verlangt, dass
|
|
565
|
+
jedes Objekt unter `alias.` liegt. */
|
|
566
|
+
|
|
567
|
+
export var ATTRAPPEN_WURZEL = '0_userdata.0.wb-attrappe';
|
|
568
|
+
|
|
569
|
+
/* Die Datei pruefen, bevor irgendetwas daraus gebaut wird. Gibt null
|
|
570
|
+
zurueck, wenn sie taugt, sonst den Grund. */
|
|
571
|
+
export function pruefeDatei(roh) {
|
|
572
|
+
if (!roh || typeof roh !== 'object') { return tr('mockin.badJson'); }
|
|
573
|
+
if (roh.format !== FORMAT) { return tr('mockin.badFormat', String(roh.format), String(FORMAT)); }
|
|
574
|
+
if (!Array.isArray(roh.objekte) || !roh.objekte.length) { return tr('mockin.badObjects'); }
|
|
575
|
+
var schlecht = roh.objekte.filter(function (o) {
|
|
576
|
+
return !o || typeof o !== 'object' || typeof o.pfad !== 'string';
|
|
577
|
+
});
|
|
578
|
+
if (schlecht.length) { return tr('mockin.badEntry', schlecht.length); }
|
|
579
|
+
if (!roh.objekte.some(function (o) { return o.pfad === ''; })) { return tr('mockin.noRoot'); }
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function nameAus(wurzel) {
|
|
584
|
+
var letzt = String(wurzel || '').split('.').pop() || 'attrappe';
|
|
585
|
+
return letzt.replace(/[^\w-]/g, '_');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/* Was entstehen wuerde. Virtuelle Ebenen bekommen KEIN Objekt: im
|
|
589
|
+
Original stand dort auch keins, und ioBroker zeigt die Stufe von selbst
|
|
590
|
+
als `VIR` an. */
|
|
591
|
+
export function baueAttrappe(daten, name) {
|
|
592
|
+
var basis = ATTRAPPEN_WURZEL + '.' + name;
|
|
593
|
+
return daten.objekte.filter(function (o) { return !o.virtuell; }).map(function (o) {
|
|
594
|
+
var id = o.pfad ? basis + '.' + o.pfad : basis;
|
|
595
|
+
var obj = {
|
|
596
|
+
type: o.type || 'folder',
|
|
597
|
+
common: JSON.parse(JSON.stringify(o.common || {})),
|
|
598
|
+
native: { angelegtVon: 'alias-workbench', attrappeAus: daten.wurzel },
|
|
599
|
+
};
|
|
600
|
+
if (!obj.common.name) { obj.common.name = o.pfad || name; }
|
|
601
|
+
return { id: id, obj: obj, hatWert: 'wert' in o, wert: o.wert, schonDa: !!S.objects[id] };
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export function schreibeAttrappe(liste, fertig) {
|
|
606
|
+
var offen = liste.length;
|
|
607
|
+
if (!offen) { return fertig(); }
|
|
608
|
+
liste.forEach(function (x) {
|
|
609
|
+
socket.emit('setObject', x.id, x.obj, function () {
|
|
610
|
+
if (!x.hatWert) { if (--offen <= 0) { fertig(); } return; }
|
|
611
|
+
socket.emit('setState', x.id, { val: x.wert, ack: true }, function () {
|
|
612
|
+
if (--offen <= 0) { fertig(); }
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export function einlesenDatei(datei) {
|
|
619
|
+
var leser = new FileReader();
|
|
620
|
+
leser.onload = function () {
|
|
621
|
+
var roh = null;
|
|
622
|
+
try { roh = JSON.parse(String(leser.result)); } catch { roh = null; }
|
|
623
|
+
var fehler = pruefeDatei(roh);
|
|
624
|
+
zeigeEinlesen(roh, fehler);
|
|
625
|
+
};
|
|
626
|
+
leser.readAsText(datei);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function zeigeEinlesen(daten, fehler) {
|
|
630
|
+
var dlg = document.getElementById('dlg-mockin');
|
|
631
|
+
var body = document.getElementById('mockin-body');
|
|
632
|
+
var knopf = document.getElementById('btn-mockin-go');
|
|
633
|
+
if (!dlg || !body || !knopf) { return; }
|
|
634
|
+
body.textContent = '';
|
|
635
|
+
|
|
636
|
+
var neuerKnopf = knopf.cloneNode(true);
|
|
637
|
+
knopf.parentNode.replaceChild(neuerKnopf, knopf);
|
|
638
|
+
|
|
639
|
+
if (fehler) {
|
|
640
|
+
var f = el('div', 'mk-fehler');
|
|
641
|
+
f.appendChild(el('div', 'mk-warn-kopf', tr('mockin.bad')));
|
|
642
|
+
f.appendChild(el('div', null, fehler));
|
|
643
|
+
body.appendChild(f);
|
|
644
|
+
neuerKnopf.disabled = true;
|
|
645
|
+
if (!dlg.open) { dlg.showModal(); }
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
neuerKnopf.disabled = false;
|
|
649
|
+
|
|
650
|
+
var name = nameAus(daten.wurzel);
|
|
651
|
+
var kopf = el('div', 'mk-kopf');
|
|
652
|
+
kopf.appendChild(el('div', 'mk-ohne', tr('mockin.from', daten.wurzel,
|
|
653
|
+
(daten.erzeugtVon && daten.erzeugtVon.version) || '?',
|
|
654
|
+
(daten.quelle && (daten.quelle.adapter + ' ' + daten.quelle.version)) || '—')));
|
|
655
|
+
|
|
656
|
+
var zeile = el('div', 'mk-namenszeile');
|
|
657
|
+
zeile.appendChild(el('span', 'mk-lbl', tr('mockin.name')));
|
|
658
|
+
var feld = el('input', 'tx');
|
|
659
|
+
feld.type = 'text';
|
|
660
|
+
feld.value = name;
|
|
661
|
+
zeile.appendChild(feld);
|
|
662
|
+
kopf.appendChild(zeile);
|
|
663
|
+
var zielZeile = el('div', 'mk-wurzel');
|
|
664
|
+
kopf.appendChild(zielZeile);
|
|
665
|
+
body.appendChild(kopf);
|
|
666
|
+
|
|
667
|
+
var tab = el('table', 'mk-tab');
|
|
668
|
+
body.appendChild(tab);
|
|
669
|
+
|
|
670
|
+
function malen() {
|
|
671
|
+
name = nameAus(feld.value);
|
|
672
|
+
var liste = baueAttrappe(daten, name);
|
|
673
|
+
var basis = ATTRAPPEN_WURZEL + '.' + name;
|
|
674
|
+
zielZeile.textContent = basis;
|
|
675
|
+
|
|
676
|
+
/* Einen vergebenen Namen gibt es nicht zweimal: dann bleibt der
|
|
677
|
+
Knopf gesperrt, bis jemand einen anderen eintippt.
|
|
678
|
+
|
|
679
|
+
Der Grund steht in einer Messung vom 12.10.2026. Geschrieben wird
|
|
680
|
+
je Objekt, also wird nur ueberschrieben, wo der Pfad gleich ist.
|
|
681
|
+
Dieselbe Datei erneut einzulesen waere ein sauberer Ersatz — ein
|
|
682
|
+
ANDERES Geraet unter demselben Namen ergibt aber eine Mischung:
|
|
683
|
+
aus einem Geraet mit 46 Objekten und einem Kanal mit 16 wurden 61,
|
|
684
|
+
weil nur die Wurzel beiden gemeinsam war und 45 Objekte des ersten
|
|
685
|
+
stehenblieben. Dabei entsteht ein Geraet, das es nie gab — und
|
|
686
|
+
genau daran wuerde dann jemand eine Vorlage bauen.
|
|
687
|
+
|
|
688
|
+
Ricardo hat am 12.10.2026 entschieden: sperren, nicht warnen. */
|
|
689
|
+
var altWarn = kopf.querySelector('.mk-namewarn');
|
|
690
|
+
if (altWarn) { altWarn.remove(); }
|
|
691
|
+
var vorhanden = Object.keys(S.objects).filter(function (id) {
|
|
692
|
+
return id === basis || id.indexOf(basis + '.') === 0;
|
|
693
|
+
});
|
|
694
|
+
if (vorhanden.length) {
|
|
695
|
+
var nw = el('div', 'mk-fehler mk-namewarn');
|
|
696
|
+
nw.appendChild(el('div', 'mk-warn-kopf', tr('mockin.nameTaken', name, vorhanden.length)));
|
|
697
|
+
nw.appendChild(el('div', null, tr('mockin.nameTakenHint')));
|
|
698
|
+
kopf.appendChild(nw);
|
|
699
|
+
}
|
|
700
|
+
neuerKnopf.disabled = vorhanden.length > 0;
|
|
701
|
+
|
|
702
|
+
kopf.querySelector('.mk-zahlen') && kopf.querySelector('.mk-zahlen').remove();
|
|
703
|
+
kopf.appendChild(el('div', 'mk-zahlen', tr('mockin.counts', liste.length,
|
|
704
|
+
liste.filter(function (x) { return x.hatWert; }).length,
|
|
705
|
+
liste.filter(function (x) { return x.schonDa; }).length)));
|
|
706
|
+
tab.textContent = '';
|
|
707
|
+
var kz = el('tr');
|
|
708
|
+
[tr('mock.colPath'), tr('mock.colType'), tr('mock.colRole'), tr('mock.colValue')]
|
|
709
|
+
.forEach(function (t) { kz.appendChild(el('th', null, t)); });
|
|
710
|
+
tab.appendChild(kz);
|
|
711
|
+
liste.forEach(function (x) {
|
|
712
|
+
var tz = el('tr');
|
|
713
|
+
var tdId = el('td', 'mk-pfad', x.id);
|
|
714
|
+
if (x.schonDa) { tdId.appendChild(el('span', 'mk-schon', ' ' + tr('mockin.exists'))); }
|
|
715
|
+
tz.appendChild(tdId);
|
|
716
|
+
tz.appendChild(el('td', null, (x.obj.type || '') +
|
|
717
|
+
(x.obj.common.type ? ' · ' + x.obj.common.type : '')));
|
|
718
|
+
tz.appendChild(el('td', null, x.obj.common.role || ''));
|
|
719
|
+
tz.appendChild(el('td', 'mk-wert', x.hatWert ? String(x.wert) : ''));
|
|
720
|
+
tab.appendChild(tz);
|
|
721
|
+
});
|
|
722
|
+
return liste;
|
|
723
|
+
}
|
|
724
|
+
var liste = malen();
|
|
725
|
+
feld.addEventListener('input', function () { liste = malen(); });
|
|
726
|
+
|
|
727
|
+
neuerKnopf.addEventListener('click', function () {
|
|
728
|
+
neuerKnopf.disabled = true;
|
|
729
|
+
schreibeAttrappe(liste, function () {
|
|
730
|
+
dlg.close();
|
|
731
|
+
/* Der Baum weiss noch nichts von den neuen Objekten. */
|
|
732
|
+
if (typeof window.__wbNachEinlesen === 'function') { window.__wbNachEinlesen(); }
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
if (!dlg.open) { dlg.showModal(); }
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/* Das Dateifeld anmelden. Dasselbe Muster wie beim Vorlagen-Einlesen in
|
|
740
|
+
ergebnis.js: der Wert wird geleert, damit dieselbe Datei erneut geht. */
|
|
741
|
+
(function () {
|
|
742
|
+
var f = document.getElementById('datei-attrappe');
|
|
743
|
+
if (!f) { return; }
|
|
744
|
+
f.addEventListener('change', function () {
|
|
745
|
+
var datei = this.files && this.files[0];
|
|
746
|
+
this.value = '';
|
|
747
|
+
if (datei) { einlesenDatei(datei); }
|
|
748
|
+
});
|
|
749
|
+
})();
|