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,1537 @@
|
|
|
1
|
+
/* Alles, was Objekte anfasst: bauen, pruefen, Trockenlauf, schreiben,
|
|
2
|
+
loeschen, verlegen. Geschrieben wird nur, was der Trockenlauf vorher
|
|
3
|
+
gezeigt hat. */
|
|
4
|
+
|
|
5
|
+
import { S } from './zustand.js';
|
|
6
|
+
import { socket } from './verbindung.js';
|
|
7
|
+
import { $, el } from './basis.js';
|
|
8
|
+
import { tr, txt } from './sprache.js';
|
|
9
|
+
import { enums, ladeEnums } from './enums.js';
|
|
10
|
+
import { enumsVon } from './aufzaehlungen.js';
|
|
11
|
+
import './katalog.js';
|
|
12
|
+
import { holeZweig, indexNeu , nachziehenErledigt } from './objekte.js';
|
|
13
|
+
import { zeichneBaum } from './baum.js';
|
|
14
|
+
import './werte.js';
|
|
15
|
+
import { ausgangName, vorschlag } from './vorlagen.js';
|
|
16
|
+
import {
|
|
17
|
+
waehle,
|
|
18
|
+
ordnerUnterAlias,
|
|
19
|
+
gemeinsameKanaele,
|
|
20
|
+
kennungtauglich,
|
|
21
|
+
entwurfFuer
|
|
22
|
+
} from './entwurf.js';
|
|
23
|
+
import { zeichneErgebnis } from './ergebnis.js';
|
|
24
|
+
|
|
25
|
+
import { uebernehmeBestand , enumAenderungen, setzeZiel } from './zuordnung.js';
|
|
26
|
+
|
|
27
|
+
import { mqttEinzelnStill } from './mqtt.js';
|
|
28
|
+
|
|
29
|
+
import { rateBehalten } from './vorschlagen.js';
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export var EIGENE_COMMON = ['name', 'role', 'type', 'read', 'write', 'alias', 'unit', 'states'];
|
|
33
|
+
|
|
34
|
+
export function mitFremdem(id, common) {
|
|
35
|
+
var alt = S.objects[id] && S.objects[id].common;
|
|
36
|
+
if (!alt) { return common; }
|
|
37
|
+
Object.keys(alt).forEach(function (k) {
|
|
38
|
+
if (EIGENE_COMMON.indexOf(k) > -1) { return; }
|
|
39
|
+
if (common[k] !== undefined) { return; }
|
|
40
|
+
common[k] = JSON.parse(JSON.stringify(alt[k]));
|
|
41
|
+
});
|
|
42
|
+
return common;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Aendert dieses Objekt ueberhaupt etwas?
|
|
46
|
+
|
|
47
|
+
Beim Aktualisieren stand an jeder Zeile „ueberschreibt" — auch an den
|
|
48
|
+
neun von zehn, die hinterher Zeichen fuer Zeichen dasselbe enthielten.
|
|
49
|
+
Das las sich, als wuerde ein fertiger Alias bei jedem Durchgang neu
|
|
50
|
+
gebaut, und verdeckte die eine Zeile, in der wirklich etwas passiert.
|
|
51
|
+
|
|
52
|
+
Verglichen wird ohne die Felder, die der Controller selbst setzt:
|
|
53
|
+
Zeitstempel, Urheber, Rechte. Und mit sortierten Schluesseln — sonst
|
|
54
|
+
zaehlte eine andere Reihenfolge im JSON schon als Unterschied. */
|
|
55
|
+
export function stabil(o) {
|
|
56
|
+
if (o === null || typeof o !== 'object') { return o; }
|
|
57
|
+
if (Array.isArray(o)) { return o.map(stabil); }
|
|
58
|
+
var raus = {};
|
|
59
|
+
Object.keys(o).sort().forEach(function (k) { raus[k] = stabil(o[k]); });
|
|
60
|
+
return raus;
|
|
61
|
+
}
|
|
62
|
+
/* Zwei Textfassungen zeilenweise gegenueberstellen - wie ein
|
|
63
|
+
Vergleichswerkzeug es tut: links der Bestand, rechts was entstuende,
|
|
64
|
+
gleiche Zeilen auf gleicher Hoehe.
|
|
65
|
+
|
|
66
|
+
Erst die laengste gemeinsame Teilfolge (LCS) ueber die Zeilen, dann
|
|
67
|
+
werden Loesch- und Einfuegebloecke an derselben Stelle paarweise
|
|
68
|
+
nebeneinander gelegt - sonst stuende eine geaenderte Zeile zweimal
|
|
69
|
+
untereinander statt einmal daneben. Bei JSON-Bloecken von ein paar
|
|
70
|
+
Dutzend Zeilen ist die Tabelle winzig, das kostet nichts. */
|
|
71
|
+
export function zeilenVergleich(altText, neuText) {
|
|
72
|
+
var a = String(altText).split('\n'), b = String(neuText).split('\n');
|
|
73
|
+
var n = a.length, m = b.length, i, j;
|
|
74
|
+
/* Verglichen wird ohne das abschliessende Komma: es haengt nur davon
|
|
75
|
+
ab, ob nach der Zeile noch etwas folgt. Kommt ein Feld dazu, bekaeme
|
|
76
|
+
die Zeile davor sonst ein Komma und stuende als „geaendert“ da,
|
|
77
|
+
obwohl an ihr nichts anders ist. Angezeigt wird die Zeile samt
|
|
78
|
+
Komma - verglichen ohne. */
|
|
79
|
+
var kern = function (z) { return String(z).replace(/,$/, ''); };
|
|
80
|
+
var ak = a.map(kern), bk = b.map(kern);
|
|
81
|
+
var t = [];
|
|
82
|
+
for (i = 0; i <= n; i++) {
|
|
83
|
+
var reihe = [];
|
|
84
|
+
for (j = 0; j <= m; j++) { reihe.push(0); }
|
|
85
|
+
t.push(reihe);
|
|
86
|
+
}
|
|
87
|
+
for (i = n - 1; i >= 0; i--) {
|
|
88
|
+
for (j = m - 1; j >= 0; j--) {
|
|
89
|
+
t[i][j] = (ak[i] === bk[j]) ? t[i + 1][j + 1] + 1
|
|
90
|
+
: Math.max(t[i + 1][j], t[i][j + 1]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
var roh = [];
|
|
94
|
+
i = 0; j = 0;
|
|
95
|
+
while (i < n && j < m) {
|
|
96
|
+
if (ak[i] === bk[j]) { roh.push({ art: 'gleich', l: a[i], r: b[j] }); i++; j++; }
|
|
97
|
+
else if (t[i + 1][j] >= t[i][j + 1]) { roh.push({ art: 'weg', l: a[i] }); i++; }
|
|
98
|
+
else { roh.push({ art: 'dazu', r: b[j] }); j++; }
|
|
99
|
+
}
|
|
100
|
+
while (i < n) { roh.push({ art: 'weg', l: a[i++] }); }
|
|
101
|
+
while (j < m) { roh.push({ art: 'dazu', r: b[j++] }); }
|
|
102
|
+
|
|
103
|
+
var raus = [];
|
|
104
|
+
for (var k = 0; k < roh.length; k++) {
|
|
105
|
+
if (roh[k].art !== 'weg') { raus.push(roh[k]); continue; }
|
|
106
|
+
var wegs = [], dazus = [];
|
|
107
|
+
while (k < roh.length && roh[k].art === 'weg') { wegs.push(roh[k].l); k++; }
|
|
108
|
+
while (k < roh.length && roh[k].art === 'dazu') { dazus.push(roh[k].r); k++; }
|
|
109
|
+
k--;
|
|
110
|
+
var viele = Math.max(wegs.length, dazus.length);
|
|
111
|
+
for (var p = 0; p < viele; p++) {
|
|
112
|
+
if (p < wegs.length && p < dazus.length) { raus.push({ art: 'anders', l: wegs[p], r: dazus[p] }); }
|
|
113
|
+
else if (p < wegs.length) { raus.push({ art: 'weg', l: wegs[p] }); }
|
|
114
|
+
else { raus.push({ art: 'dazu', r: dazus[p] }); }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return raus;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Ein Objekt als JSON-Text fuer den Vergleich - ohne die Felder, die der
|
|
121
|
+
Controller selbst setzt (sonst haenge der Vergleich an Zeitstempeln).
|
|
122
|
+
|
|
123
|
+
`geordnet` sortiert die Schluessel auf beiden Seiten gleich. Das ist
|
|
124
|
+
keine Schoenung: die Reihenfolge in einem JSON-Objekt bedeutet nichts,
|
|
125
|
+
und sie ist auch keine stillschweigende Vorgabe - gemessen an Ricardos
|
|
126
|
+
Anlage schreibt derselbe Admin Alias-Objekte in VIER verschiedenen
|
|
127
|
+
Reihenfolgen (common,native,type / type,common,native /
|
|
128
|
+
native,type,common / common,type,native), javascript.0 in zwei.
|
|
129
|
+
Ohne Ordnen sieht ein Vergleich nach einem Dutzend Unterschieden aus,
|
|
130
|
+
wo zwei sind (Ricardo, 25.08.2026). Wer das Original sehen will,
|
|
131
|
+
schaltet um. */
|
|
132
|
+
/* Die Reihenfolge, in der die Doku die Felder auffuehrt - und in der sie
|
|
133
|
+
zusammengehoeren: `read` und `write` nebeneinander, `alias` als groesster
|
|
134
|
+
Block am Ende. Alphabetisch sortiert stuende `read` hinter `name` und
|
|
135
|
+
`write` hinter `unit`, also weit auseinander (Ricardo, 25.08.2026).
|
|
136
|
+
Was hier nicht steht - icon, color, desc, custom -, kommt dahinter,
|
|
137
|
+
alphabetisch. */
|
|
138
|
+
var VORRANG = {
|
|
139
|
+
'': ['type', 'common', 'native'],
|
|
140
|
+
common: ['name', 'role', 'type', 'read', 'write', 'unit', 'states', 'alias'],
|
|
141
|
+
'common.alias': ['id', 'read', 'write']
|
|
142
|
+
};
|
|
143
|
+
function ordne(o, pfad) {
|
|
144
|
+
if (o === null || typeof o !== 'object') { return o; }
|
|
145
|
+
if (Array.isArray(o)) { return o.map(function (x) { return ordne(x, pfad); }); }
|
|
146
|
+
var vor = VORRANG[pfad] || [];
|
|
147
|
+
var raus = {};
|
|
148
|
+
Object.keys(o).sort(function (a, b) {
|
|
149
|
+
var ia = vor.indexOf(a), ib = vor.indexOf(b);
|
|
150
|
+
if (ia > -1 && ib > -1) { return ia - ib; }
|
|
151
|
+
if (ia > -1) { return -1; }
|
|
152
|
+
if (ib > -1) { return 1; }
|
|
153
|
+
return a < b ? -1 : (a > b ? 1 : 0);
|
|
154
|
+
}).forEach(function (k) { raus[k] = ordne(o[k], pfad ? pfad + '.' + k : k); });
|
|
155
|
+
return raus;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function objektText(o, geordnet) {
|
|
159
|
+
if (!o) { return ''; }
|
|
160
|
+
var k = JSON.parse(JSON.stringify(o));
|
|
161
|
+
delete k.ts; delete k.from; delete k.user; delete k.acl; delete k._id;
|
|
162
|
+
return JSON.stringify(geordnet ? ordne(k, '') : k, null, 2);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function unveraendert(id, obj) {
|
|
166
|
+
var alt = S.objects[id];
|
|
167
|
+
if (!alt) { return false; }
|
|
168
|
+
var putz = function (x) {
|
|
169
|
+
var k = JSON.parse(JSON.stringify(x || {}));
|
|
170
|
+
delete k.ts; delete k.from; delete k.user; delete k.acl; delete k._id;
|
|
171
|
+
return stabil(k);
|
|
172
|
+
};
|
|
173
|
+
return JSON.stringify(putz(alt)) === JSON.stringify(putz(obj));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
/* ================== Objekte bauen, prüfen, schreiben ================== */
|
|
178
|
+
|
|
179
|
+
/* Aus dem Entwurf die Objekte machen, die entstehen wuerden. */
|
|
180
|
+
export function baueObjekte(e) {
|
|
181
|
+
var ziel = e.ziel || e.kanal;
|
|
182
|
+
var raus = [];
|
|
183
|
+
|
|
184
|
+
/* Ordner darueber, falls es ihn noch nicht gibt */
|
|
185
|
+
var teile = ziel.split('.');
|
|
186
|
+
/* Jede fehlende Ebene zwischen alias.0 und dem Geraet anlegen. */
|
|
187
|
+
for (var i = 3; i < teile.length; i++) {
|
|
188
|
+
var pfad = teile.slice(0, i).join('.');
|
|
189
|
+
if (!S.objects[pfad]) {
|
|
190
|
+
raus.push({ id: pfad, neu: true, obj: {
|
|
191
|
+
type: 'folder',
|
|
192
|
+
common: mitFremdem(pfad, { name: teile[i - 1] }),
|
|
193
|
+
native: {}
|
|
194
|
+
} });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* Der Kanal — hier wird die Vorlage vermerkt, damit ein spaeteres
|
|
199
|
+
Nachziehen weiss, woraus das Geraet entstanden ist. */
|
|
200
|
+
/* Wird der Alias aus sich selbst heraus bearbeitet — Baummodus
|
|
201
|
+
„Aliase" —, darf „quelle" nicht auf ihn selbst zeigen. Vorher
|
|
202
|
+
verschwanden dabei Vorlage, Version und Geraetetyp, und damit die
|
|
203
|
+
ganze Herkunft: das Abzeichen „gemerkt" war weg, die Erkennung
|
|
204
|
+
nahm womoeglich eine andere Vorlage, und ein spaeteres Nachziehen
|
|
205
|
+
der Versionen haette nichts mehr zu greifen. */
|
|
206
|
+
var altN = (S.objects[ziel] && S.objects[ziel].native) || {};
|
|
207
|
+
var ausAlias = (e.kanal.indexOf('alias.') === 0);
|
|
208
|
+
var kanalNative = ausAlias ? JSON.parse(JSON.stringify(altN)) : { quelle: e.kanal };
|
|
209
|
+
if (e.vorlage) {
|
|
210
|
+
kanalNative.vorlage = e.vorlage;
|
|
211
|
+
kanalNative.vorlageVersion = e.vorlageVersion;
|
|
212
|
+
kanalNative.geraetetyp = e.want;
|
|
213
|
+
if (e.want !== e.wantAuto) { kanalNative.musterVonHand = true; }
|
|
214
|
+
}
|
|
215
|
+
/* Einen von Hand vergebenen Kanalnamen nicht durch die Kennung
|
|
216
|
+
ersetzen. */
|
|
217
|
+
var altName = txt(S.objects[ziel] && S.objects[ziel].common && S.objects[ziel].common.name);
|
|
218
|
+
/* Neu angelegt wird ein Kanal — das ist die verbreitete Form und die,
|
|
219
|
+
die dem Objektschema am naechsten kommt. Was es schon gibt, behaelt
|
|
220
|
+
aber seinen Typ: ein Aktualisieren soll nicht nebenbei aus einem
|
|
221
|
+
Ordner einen Kanal machen. Der Aliasmechanismus schaut ohnehin nur
|
|
222
|
+
auf die Datenpunkte, nicht auf ihren Behaelter. */
|
|
223
|
+
var altTyp = S.objects[ziel] && S.objects[ziel].type;
|
|
224
|
+
/* Die Rolle des Kanals ist der Geraetetyp - so haelt es der
|
|
225
|
+
Geraete-Adapter des Admin, und so steht es in Ricardos Anlage:
|
|
226
|
+
115 von 118 Alias-Kanaelen tragen einen Detector-Typ als Rolle
|
|
227
|
+
(socket, blind, light, thermostat ...). Wir setzten sie nie, und
|
|
228
|
+
weil `role` zugleich auf EIGENE_COMMON steht, rettete `mitFremdem`
|
|
229
|
+
eine vorhandene auch nicht herueber - ein Aktualisieren loeschte
|
|
230
|
+
sie also stillschweigend (gefunden 25.08.2026 an
|
|
231
|
+
alias.0.Badezimmer.Rollladen, role „blind"). Ohne gewaehltes
|
|
232
|
+
Muster bleibt die vorhandene stehen. */
|
|
233
|
+
var kanalCommon = { name: altName || teile[teile.length - 1] };
|
|
234
|
+
var altRolle = S.objects[ziel] && S.objects[ziel].common && S.objects[ziel].common.role;
|
|
235
|
+
if (e.want) { kanalCommon.role = e.want; }
|
|
236
|
+
else if (altRolle) { kanalCommon.role = altRolle; }
|
|
237
|
+
raus.push({ id: ziel, neu: !S.objects[ziel], obj: {
|
|
238
|
+
type: (altTyp === 'folder' || altTyp === 'device') ? altTyp : 'channel',
|
|
239
|
+
common: mitFremdem(ziel, kanalCommon),
|
|
240
|
+
native: kanalNative
|
|
241
|
+
} });
|
|
242
|
+
|
|
243
|
+
e.states.forEach(function (s) {
|
|
244
|
+
if (!s.on || !s.n || !s.srcR) { return; }
|
|
245
|
+
var id = ziel + '.' + s.n;
|
|
246
|
+
|
|
247
|
+
var alias = {};
|
|
248
|
+
if (s.srcW && s.srcW !== s.srcR) { alias.id = { read: s.srcR, write: s.srcW }; }
|
|
249
|
+
else { alias.id = s.srcR; }
|
|
250
|
+
if (s.f) { alias.read = s.f; }
|
|
251
|
+
if (s.fw && s.srcW) { alias.write = s.fw; }
|
|
252
|
+
|
|
253
|
+
var common = {
|
|
254
|
+
name: s.caption || s.n,
|
|
255
|
+
role: s.role,
|
|
256
|
+
type: s.typ || 'mixed',
|
|
257
|
+
read: true,
|
|
258
|
+
write: !!s.srcW,
|
|
259
|
+
alias: alias
|
|
260
|
+
};
|
|
261
|
+
if (s.unit) { common.unit = s.unit; }
|
|
262
|
+
/* Ohne Werteliste erkennt der Detektor den fertigen Alias spaeter
|
|
263
|
+
nicht wieder — dieselbe Bedingung wie im Abbild. */
|
|
264
|
+
if (s.states) { common.states = s.states; }
|
|
265
|
+
|
|
266
|
+
var nat = {};
|
|
267
|
+
var altNat = (S.objects[id] && S.objects[id].native)
|
|
268
|
+
? JSON.parse(JSON.stringify(S.objects[id].native)) : null;
|
|
269
|
+
if (s.nurImAlias && altNat) {
|
|
270
|
+
/* Ein Punkt, den es nur im Alias gibt, behaelt seinen Vermerk —
|
|
271
|
+
sonst schreibt ihn ein Aktualisieren der Vorlage zu, aus der er
|
|
272
|
+
nie kam. */
|
|
273
|
+
nat = altNat;
|
|
274
|
+
} else if (s.manuell) { nat.vonHand = true; }
|
|
275
|
+
else if (e.vorlage) { nat.vorlage = e.vorlage; nat.ausZustand = s.ausVorlage || s.n; }
|
|
276
|
+
else if (altNat) {
|
|
277
|
+
/* Aus dem Aliasmodus heraus gibt es keine Vorlage, und `manuell`
|
|
278
|
+
steht nur beim frischen Hinzufuegen. Ohne diesen Fall schrieb
|
|
279
|
+
ein Aktualisieren `native: {}` ueber jeden Punkt und loeschte
|
|
280
|
+
damit Vorlage, Herkunftsplatz und die Marke `vonHand` — der
|
|
281
|
+
Kanal behielt seine Herkunft, die Punkte verloren sie. */
|
|
282
|
+
nat = altNat;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
raus.push({ id: id, neu: !S.objects[id],
|
|
286
|
+
obj: { type: 'state', common: mitFremdem(id, common), native: nat } });
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
/* Zum Schluss die Aufzaehlungen. Sie stehen am Ende, weil sie erst
|
|
290
|
+
dann sinnvoll sind, wenn das Ziel feststeht - und weil der
|
|
291
|
+
Trockenlauf sie so unter den Objekten zeigt, zu denen sie gehoeren. */
|
|
292
|
+
enumAenderungen(e).forEach(function (x) { raus.push(x); });
|
|
293
|
+
|
|
294
|
+
return raus;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* Was im Ziel liegt, aber nicht mehr im Entwurf vorkommt.
|
|
298
|
+
„abgewaehlt" = du hast das Haekchen entfernt, also gewollt.
|
|
299
|
+
„nicht im Vorschlag" = existiert nur im Alias, etwa von Hand ergaenzt —
|
|
300
|
+
das wird nicht ungefragt geloescht. */
|
|
301
|
+
export function verwaiste(e) {
|
|
302
|
+
var ziel = e.ziel || e.kanal;
|
|
303
|
+
if (!S.objects[ziel]) { return []; }
|
|
304
|
+
var behalten = {};
|
|
305
|
+
e.states.forEach(function (st) {
|
|
306
|
+
if (st.on && st.n) { behalten[ziel + '.' + st.n] = 1; }
|
|
307
|
+
});
|
|
308
|
+
var imEntwurf = {};
|
|
309
|
+
e.states.forEach(function (st) { if (st.n) { imEntwurf[ziel + '.' + st.n] = 1; } });
|
|
310
|
+
|
|
311
|
+
var pre = ziel + '.';
|
|
312
|
+
return S.keysSorted.filter(function (k) {
|
|
313
|
+
return k.indexOf(pre) === 0 && S.objects[k] && S.objects[k].type === 'state' && !behalten[k];
|
|
314
|
+
}).map(function (k) {
|
|
315
|
+
var abgewaehlt = !!imEntwurf[k];
|
|
316
|
+
return { id: k, grund: abgewaehlt ? tr('write.deselected') : tr('write.notInProposal'), vorgabe: abgewaehlt };
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* Was dem Schreiben im Weg steht. */
|
|
321
|
+
export function pruefeSchreiben(e) {
|
|
322
|
+
var probleme = [];
|
|
323
|
+
var an = e.states.filter(function (s) { return s.on; });
|
|
324
|
+
if (!an.length) { probleme.push(tr('write.noneSelected')); }
|
|
325
|
+
|
|
326
|
+
var namen = {};
|
|
327
|
+
an.forEach(function (s) {
|
|
328
|
+
if (!s.n) { probleme.push(tr('write.noName')); return; }
|
|
329
|
+
if (namen[s.n]) { probleme.push(tr('write.duplicateName', s.n)); }
|
|
330
|
+
namen[s.n] = 1;
|
|
331
|
+
|
|
332
|
+
if (!s.srcR) {
|
|
333
|
+
probleme.push(tr('write.noSourceChosen', s.n));
|
|
334
|
+
} else if (!S.objects[s.srcR]) {
|
|
335
|
+
/* Der teuerste Fehler: der js-controller merkt sich „Quelle fehlt"
|
|
336
|
+
dauerhaft und korrigiert das nie. Nur loeschen und neu anlegen
|
|
337
|
+
hilft. Deshalb hier hart sperren. */
|
|
338
|
+
probleme.push(tr('write.sourceMissing', s.n, s.srcR));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/* Dasselbe fuer das Schreibziel: die Erkennung darf sich auf einen
|
|
342
|
+
Befehl stuetzen, den das Geraet kennt — geschrieben wird aber erst,
|
|
343
|
+
wenn der Punkt wirklich da ist. Sonst zeigte der Alias auf ein
|
|
344
|
+
Objekt, das es nicht gibt, und der Controller merkt sich auch das
|
|
345
|
+
dauerhaft. */
|
|
346
|
+
if (s.srcW && !S.objects[s.srcW]) {
|
|
347
|
+
probleme.push(tr('write.writeSourceMissing', s.n, s.srcW));
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
return probleme;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* Trockenlauf zeigen */
|
|
354
|
+
|
|
355
|
+
/* Welche Entwuerfe geschrieben werden. Bei mehreren Ausgaengen wahlweise
|
|
356
|
+
nur der gezeigte oder alle — die uebrigen kommen frisch aus der Vorlage,
|
|
357
|
+
Aenderungen von Hand gelten nur fuer den gezeigten Ausgang. */
|
|
358
|
+
export function zuSchreiben(alleAusgaenge) {
|
|
359
|
+
/* Je Kanal ein Geraet: fuer jeden steuerbaren Kanal ein eigener
|
|
360
|
+
Entwurf, benannt nach dem Kanal — nicht nach seiner Nummer. Aus
|
|
361
|
+
„Kanal 1" wird `Licht_Bar`, weil das der Name ist, den jemand ihm
|
|
362
|
+
gegeben hat. */
|
|
363
|
+
if (alleAusgaenge && S.entwurf.kanalGeraete && S.entwurf.kanalGeraete.length > 1) {
|
|
364
|
+
/* Ein Ordner fuer das Geraet, darunter je Kanal ein Kanal — dieselbe
|
|
365
|
+
Form, die die Tasmota-Mehrfachvorlage seit jeher erzeugt. */
|
|
366
|
+
var ordner = (S.entwurf.zielOrdner || 'alias.0') + '.' + S.entwurf.zielName;
|
|
367
|
+
var gem = gemeinsameKanaele(S.entwurf.kanal, S.entwurf.kanalGeraete);
|
|
368
|
+
return S.entwurf.kanalGeraete.map(function (k) {
|
|
369
|
+
var v = entwurfFuer(k.id, gem, S.entwurf.gemeinsamMitPlatz);
|
|
370
|
+
if (!v) { return null; }
|
|
371
|
+
v.zielOrdner = ordner;
|
|
372
|
+
v.zielName = k.name;
|
|
373
|
+
v.ziel = ordner + '.' + k.name;
|
|
374
|
+
uebernehmeBestand(v);
|
|
375
|
+
return v;
|
|
376
|
+
}).filter(Boolean);
|
|
377
|
+
}
|
|
378
|
+
if (!alleAusgaenge || !S.entwurf.instanzen || S.entwurf.instanzen.length < 2) { return [S.entwurf]; }
|
|
379
|
+
return S.entwurf.instanzen.map(function (n) {
|
|
380
|
+
if (String(n) === String(S.entwurf.instanz)) { return S.entwurf; }
|
|
381
|
+
var v = vorschlag(S.current, S.entwurf.vorlage, n);
|
|
382
|
+
if (v) {
|
|
383
|
+
v.zielOrdner = S.entwurf.zielOrdner;
|
|
384
|
+
v.zielName = ausgangName(v, n);
|
|
385
|
+
v.ziel = v.zielOrdner + '.' + v.zielName;
|
|
386
|
+
}
|
|
387
|
+
return v;
|
|
388
|
+
}).filter(Boolean);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function zeigeTrockenlauf(alleAusgaenge) {
|
|
392
|
+
if (!S.entwurf) { return; }
|
|
393
|
+
S.trockenAlle = !!alleAusgaenge;
|
|
394
|
+
/* Das Ziel steht sonst erst, wenn zeichneErgebnis gelaufen ist. Wird
|
|
395
|
+
der Trockenlauf aus einem Rueckruf heraus geoeffnet — etwa nachdem
|
|
396
|
+
fehlende Sendepunkte angelegt wurden —, ist der Entwurf frisch und
|
|
397
|
+
hat noch keins. Dann faellt zuSchreiben auf e.kanal zurueck, und das
|
|
398
|
+
ist die Quelle: der Trockenlauf zeigte Objekte unter mqtt-client an. */
|
|
399
|
+
if (S.entwurf) { setzeZiel(S.entwurf); }
|
|
400
|
+
|
|
401
|
+
var entwuerfe = zuSchreiben(S.trockenAlle);
|
|
402
|
+
var liste = [];
|
|
403
|
+
var probleme = [];
|
|
404
|
+
entwuerfe.forEach(function (en) {
|
|
405
|
+
baueObjekte(en).forEach(function (o) {
|
|
406
|
+
if (!liste.some(function (x) { return x.id === o.id; })) { liste.push(o); }
|
|
407
|
+
});
|
|
408
|
+
pruefeSchreiben(en).forEach(function (pr) { probleme.push(pr); });
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
/* Letzte Sicherung, unabhaengig von allem davor: ein Alias gehoert
|
|
412
|
+
unter alias. — sonst schreibt die Werkbank in fremde Zweige. Die
|
|
413
|
+
Pruefung kostet nichts und faengt jeden Weg ab, auf dem das Ziel
|
|
414
|
+
verlorengeht.
|
|
415
|
+
|
|
416
|
+
Eine Ausnahme, und nur diese: Aufzaehlungen. Raum und Funktion stehen
|
|
417
|
+
nicht am Alias, sondern in `enum.rooms.*` und `enum.functions.*` — ohne
|
|
418
|
+
diese Ausnahme liessen sie sich gar nicht setzen. Sie ist eng gefasst:
|
|
419
|
+
nur `enum.`, nur Objekte, die aus `enumAenderungen` stammen und deshalb
|
|
420
|
+
`istEnum` tragen. Alles andere bleibt gesperrt. */
|
|
421
|
+
liste.forEach(function (x) {
|
|
422
|
+
if (x.istEnum) {
|
|
423
|
+
if (x.id.indexOf('enum.rooms.') !== 0 && x.id.indexOf('enum.functions.') !== 0) {
|
|
424
|
+
probleme.push(tr('write.notUnderAlias', x.id));
|
|
425
|
+
}
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (x.id.indexOf('alias.') !== 0) {
|
|
429
|
+
probleme.push(tr('write.notUnderAlias', x.id));
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
var weg = [];
|
|
433
|
+
entwuerfe.forEach(function (en) {
|
|
434
|
+
verwaiste(en).forEach(function (v) {
|
|
435
|
+
if (!weg.some(function (x) { return x.id === v.id; })) { weg.push(v); }
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
S.loeschListe = weg;
|
|
439
|
+
var body = $('#dry-body');
|
|
440
|
+
body.textContent = '';
|
|
441
|
+
|
|
442
|
+
var kopf = el('div');
|
|
443
|
+
kopf.style.marginBottom = '11px';
|
|
444
|
+
kopf.style.fontSize = '12.5px';
|
|
445
|
+
kopf.appendChild(el('b', null, tr('app.objectsCount', liste.length)));
|
|
446
|
+
liste.forEach(function (x) { x.gleich = !x.neu && unveraendert(x.id, x.obj); });
|
|
447
|
+
var neu = liste.filter(function (x) { return x.neu; }).length;
|
|
448
|
+
var gleich = liste.filter(function (x) { return x.gleich; }).length;
|
|
449
|
+
kopf.appendChild(document.createTextNode(' — ' + (gleich
|
|
450
|
+
? tr('write.summaryTail3', neu, liste.length - neu - gleich, gleich)
|
|
451
|
+
: tr('write.summaryTail', neu, liste.length - neu))));
|
|
452
|
+
/* „uebrig geblieben" hiess in Wahrheit „wird geloescht" — und
|
|
453
|
+
dieselbe rote Zahl stand auch fuer Punkte, die stehenbleiben. Zwei
|
|
454
|
+
gegensaetzliche Faelle unter einer Zahl, ausgerechnet dort, wo
|
|
455
|
+
etwas verschwindet. Jetzt getrennt, und die Zahlen folgen den
|
|
456
|
+
Kaestchen. */
|
|
457
|
+
var wRaus = el('b'), wBleibt = el('span');
|
|
458
|
+
wRaus.style.color = 'var(--bad)';
|
|
459
|
+
wBleibt.style.color = 'var(--ink-3)';
|
|
460
|
+
function zaehlerAuffrischen() {
|
|
461
|
+
var n = S.loeschListe.filter(function (v) { return v.vorgabe; }).length;
|
|
462
|
+
wRaus.textContent = n ? (' — ' + tr('write.willRemove', n)) : '';
|
|
463
|
+
var b = S.loeschListe.length - n;
|
|
464
|
+
wBleibt.textContent = b ? (' — ' + tr('write.willKeep', b)) : '';
|
|
465
|
+
}
|
|
466
|
+
if (weg.length) { kopf.appendChild(wRaus); kopf.appendChild(wBleibt); }
|
|
467
|
+
|
|
468
|
+
/* Der Umschalter fuer die Feldreihenfolge - einmal fuer den ganzen
|
|
469
|
+
Dialog, nicht je Karte: bei acht Objekten legt niemand achtmal um.
|
|
470
|
+
Er erscheint nur, wenn es ueberhaupt etwas zu vergleichen gibt, und
|
|
471
|
+
nur, wenn sich die Reihenfolge irgendwo unterscheidet - sonst
|
|
472
|
+
verspraeche er eine Wirkung, die er nicht hat. */
|
|
473
|
+
var vergleichbar = liste.filter(function (x) { return !x.neu && !x.gleich; });
|
|
474
|
+
var rohAnders = vergleichbar.some(function (x) {
|
|
475
|
+
return objektText(S.objects[x.id], false) !== objektText(S.objects[x.id], true)
|
|
476
|
+
|| objektText(x.obj, false) !== objektText(x.obj, true);
|
|
477
|
+
});
|
|
478
|
+
if (vergleichbar.length && rohAnders) {
|
|
479
|
+
var um = el('button', 'btn mini leise', S.trockenRoh
|
|
480
|
+
? tr('write.diffSorted') : tr('write.diffRaw'));
|
|
481
|
+
um.style.marginLeft = '10px';
|
|
482
|
+
um.title = tr('write.diffSortedHint');
|
|
483
|
+
um.addEventListener('click', function () {
|
|
484
|
+
S.trockenRoh = !S.trockenRoh;
|
|
485
|
+
zeigeTrockenlauf(S.trockenAlle);
|
|
486
|
+
});
|
|
487
|
+
kopf.appendChild(um);
|
|
488
|
+
}
|
|
489
|
+
body.appendChild(kopf);
|
|
490
|
+
|
|
491
|
+
if (weg.length) {
|
|
492
|
+
var wk = el('div', 'card');
|
|
493
|
+
wk.style.marginBottom = '11px';
|
|
494
|
+
wk.style.borderColor = 'var(--bad)';
|
|
495
|
+
var wch = el('div', 'ch');
|
|
496
|
+
wch.appendChild(el('span', 'typ', tr('write.remove')));
|
|
497
|
+
wch.appendChild(el('span', 'chip bad', tr('write.removeCount', weg.length)));
|
|
498
|
+
wch.appendChild(el('span', 'chip mut', tr('write.removeHint')));
|
|
499
|
+
wk.appendChild(wch);
|
|
500
|
+
weg.forEach(function (v, i) {
|
|
501
|
+
var r = el('div', 'slot');
|
|
502
|
+
r.style.gridTemplateColumns = '19px 1fr 150px';
|
|
503
|
+
var cbw = el('span');
|
|
504
|
+
var cb = document.createElement('input');
|
|
505
|
+
cb.type = 'checkbox';
|
|
506
|
+
cb.checked = v.vorgabe;
|
|
507
|
+
cbw.appendChild(cb);
|
|
508
|
+
r.appendChild(cbw);
|
|
509
|
+
r.appendChild(el('span', 'sn', v.id.slice((S.entwurf.ziel || S.entwurf.kanal).length + 1)));
|
|
510
|
+
var g = el('span', 'rx', v.grund);
|
|
511
|
+
if (!v.vorgabe) { g.style.color = 'var(--warn)'; }
|
|
512
|
+
r.appendChild(g);
|
|
513
|
+
cb.addEventListener('change', function () {
|
|
514
|
+
S.loeschListe[i].vorgabe = cb.checked;
|
|
515
|
+
g.style.color = cb.checked ? '' : 'var(--warn)';
|
|
516
|
+
zaehlerAuffrischen();
|
|
517
|
+
});
|
|
518
|
+
wk.appendChild(r);
|
|
519
|
+
});
|
|
520
|
+
body.appendChild(wk);
|
|
521
|
+
zaehlerAuffrischen();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (probleme.length) {
|
|
525
|
+
var pw = el('div', 'aside w');
|
|
526
|
+
pw.style.marginBottom = '11px';
|
|
527
|
+
pw.appendChild(el('b', null, tr('write.cannotWrite')));
|
|
528
|
+
var ul = el('ul');
|
|
529
|
+
ul.style.margin = '6px 0 0';
|
|
530
|
+
ul.style.paddingLeft = '18px';
|
|
531
|
+
probleme.forEach(function (t) { ul.appendChild(el('li', null, t)); });
|
|
532
|
+
pw.appendChild(ul);
|
|
533
|
+
|
|
534
|
+
/* Was fehlt, laesst sich von hier aus anlegen — sonst muesste man
|
|
535
|
+
den Trockenlauf schliessen, in der Karte nachziehen und von vorn
|
|
536
|
+
anfangen. */
|
|
537
|
+
var fehlend = [];
|
|
538
|
+
entwuerfe.forEach(function (en) {
|
|
539
|
+
en.states.forEach(function (s2) {
|
|
540
|
+
if (s2.on && s2.srcW && !S.objects[s2.srcW] && s2.srcW.indexOf('.cmnd.') > -1 &&
|
|
541
|
+
fehlend.indexOf(s2.srcW) === -1) { fehlend.push(s2.srcW); }
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
if (fehlend.length) {
|
|
545
|
+
var kb = el('div');
|
|
546
|
+
kb.style.marginTop = '9px';
|
|
547
|
+
var kn2 = el('button', 'btn', tr('write.createSendPoints', fehlend.length));
|
|
548
|
+
kn2.addEventListener('click', function () {
|
|
549
|
+
kn2.disabled = true;
|
|
550
|
+
kn2.textContent = tr('write.writing');
|
|
551
|
+
var offen2 = fehlend.length;
|
|
552
|
+
fehlend.forEach(function (fid) {
|
|
553
|
+
var t2 = fid.split('.cmnd.');
|
|
554
|
+
mqttEinzelnStill(t2[0], t2[1], function () {
|
|
555
|
+
if (--offen2 === 0) {
|
|
556
|
+
/* Erst den Entwurf neu aufbauen lassen, dann den
|
|
557
|
+
Trockenlauf noch einmal rechnen. Sofort geoeffnet zeigte
|
|
558
|
+
er den Rohentwurf, weil die Vorlage noch nicht wieder
|
|
559
|
+
angewandt war. */
|
|
560
|
+
indexNeu();
|
|
561
|
+
nachziehenErledigt();
|
|
562
|
+
zeichneBaum();
|
|
563
|
+
/* Der Neuaufbau wirft das Ziel weg, das im Feld steht —
|
|
564
|
+
danach zeigte der Dialog Objekte fuer einen anderen
|
|
565
|
+
Ordner an als eingegeben, und „Jetzt anlegen" haette
|
|
566
|
+
sie dort auch angelegt. Also merken und zurueckgeben. */
|
|
567
|
+
var altZiel = S.entwurf
|
|
568
|
+
? { o: S.entwurf.zielOrdner, n: S.entwurf.zielName } : null;
|
|
569
|
+
if (S.current) {
|
|
570
|
+
waehle(S.current, function () {
|
|
571
|
+
if (altZiel && S.entwurf) {
|
|
572
|
+
S.entwurf.zielOrdner = altZiel.o;
|
|
573
|
+
S.entwurf.zielName = altZiel.n;
|
|
574
|
+
S.entwurf.angefasst = true;
|
|
575
|
+
zeichneErgebnis();
|
|
576
|
+
}
|
|
577
|
+
zeigeTrockenlauf(S.trockenAlle);
|
|
578
|
+
});
|
|
579
|
+
} else { zeigeTrockenlauf(S.trockenAlle); }
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
kb.appendChild(kn2);
|
|
585
|
+
pw.appendChild(kb);
|
|
586
|
+
}
|
|
587
|
+
body.appendChild(pw);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* Je Objekt eine Zeile, das JSON erst beim Aufklappen.
|
|
591
|
+
|
|
592
|
+
Vorher stand unter jedem Eintrag der ganze JSON-Block. Bei zehn
|
|
593
|
+
Objekten waren das ueber zweihundert Zeilen, durch die man scrollen
|
|
594
|
+
musste, um zu sehen, was ueberhaupt entsteht — und die Frage, die
|
|
595
|
+
der Trockenlauf beantworten soll, ist zuerst „was wird angelegt und
|
|
596
|
+
woher liest es", nicht „wie sieht das Objekt innen aus".
|
|
597
|
+
|
|
598
|
+
details/summary statt eigenem Klick-Handler: das klappt von selbst,
|
|
599
|
+
laesst sich mit der Tastatur bedienen und braucht keinen Zustand,
|
|
600
|
+
der ueber das Neuzeichnen gerettet werden muesste. */
|
|
601
|
+
liste.forEach(function (x) {
|
|
602
|
+
var k = el('details', 'card zeile');
|
|
603
|
+
k.style.marginBottom = '5px';
|
|
604
|
+
|
|
605
|
+
var ch = document.createElement('summary');
|
|
606
|
+
ch.className = 'ch';
|
|
607
|
+
ch.appendChild(el('span',
|
|
608
|
+
'chip ' + (x.neu ? 'ok' : (x.gleich ? 'mut' : 'warn')),
|
|
609
|
+
x.neu ? tr('write.new') : (x.gleich ? tr('write.unchanged') : tr('write.changes'))));
|
|
610
|
+
ch.appendChild(el('span', 'chip mut', x.obj.type));
|
|
611
|
+
|
|
612
|
+
/* Ziel und Quelle untereinander, beide linksbuendig und beschriftet.
|
|
613
|
+
Nebeneinander in eine Zeile passte beides fast nie: allein die
|
|
614
|
+
Ziel-ID ist bei einem Homematic-Geraet mit Namen und Seriennummer
|
|
615
|
+
schon ueber sechzig Zeichen lang, und die Quelle rutschte dann
|
|
616
|
+
rechts an den Rand, wo sie niemand mehr las. */
|
|
617
|
+
var paar = el('span', 'zpaar');
|
|
618
|
+
paar.appendChild(el('span', 'zlabel', tr('write.rowTarget')));
|
|
619
|
+
paar.appendChild(el('span', 'zid', x.id));
|
|
620
|
+
|
|
621
|
+
/* Bei einer Aufzaehlung ist die Auskunft eine andere: nicht „woher
|
|
622
|
+
liest der Punkt", sondern „was passiert mit dieser Liste". */
|
|
623
|
+
if (x.istEnum) {
|
|
624
|
+
var was = x.dazu ? tr('enums.addTo') : (x.weg ? tr('enums.removeFrom') : '');
|
|
625
|
+
if (was) {
|
|
626
|
+
paar.appendChild(el('span', 'zlabel', ''));
|
|
627
|
+
paar.appendChild(el('span', 'zq', was));
|
|
628
|
+
}
|
|
629
|
+
/* Ein nachgetragenes Bild ist eine Aenderung an einem fremden
|
|
630
|
+
Objekt. Sie gehoert benannt, nicht nur ins aufgeklappte JSON. */
|
|
631
|
+
if (x.ikonDazu) {
|
|
632
|
+
paar.appendChild(el('span', 'zlabel', ''));
|
|
633
|
+
paar.appendChild(el('span', 'zq', tr('enums.iconAdded')));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
var al = x.obj.common && x.obj.common.alias;
|
|
638
|
+
if (al) {
|
|
639
|
+
var q = (typeof al.id === 'string') ? al.id : (al.id && (al.id.read || al.id.write)) || '';
|
|
640
|
+
/* Liest und schreibt der Punkt an verschiedenen Stellen, steht
|
|
641
|
+
hier die Lesequelle; die ganze Wahrheit zeigt das JSON. */
|
|
642
|
+
var beides = (al.id && typeof al.id === 'object' && al.id.read && al.id.write
|
|
643
|
+
&& al.id.read !== al.id.write);
|
|
644
|
+
if (q) {
|
|
645
|
+
paar.appendChild(el('span', 'zlabel', tr('write.rowSource')));
|
|
646
|
+
paar.appendChild(el('span', 'zq', q + (beides ? ' \u2026' : '')));
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
ch.appendChild(paar);
|
|
650
|
+
k.appendChild(ch);
|
|
651
|
+
|
|
652
|
+
/* Aendert sich etwas, stehen Bestand und Entwurf nebeneinander -
|
|
653
|
+
Zeile gegen Zeile, wie in einem Vergleichswerkzeug. Sonst genuegt
|
|
654
|
+
das eine JSON (bei „neu" gibt es keinen Vorher-Stand, bei
|
|
655
|
+
„unveraendert" nichts zu vergleichen). */
|
|
656
|
+
if (!x.neu && !x.gleich) {
|
|
657
|
+
var vgl = el('div', 'dvgl');
|
|
658
|
+
vgl.appendChild(el('span', 'dkopf', tr('write.diffOld')));
|
|
659
|
+
vgl.appendChild(el('span', 'dkopf', tr('write.diffNew')));
|
|
660
|
+
zeilenVergleich(objektText(S.objects[x.id], !S.trockenRoh),
|
|
661
|
+
objektText(x.obj, !S.trockenRoh))
|
|
662
|
+
.forEach(function (z) {
|
|
663
|
+
vgl.appendChild(el('span', 'dz l ' + z.art, z.l === undefined ? '' : z.l));
|
|
664
|
+
vgl.appendChild(el('span', 'dz r ' + z.art, z.r === undefined ? '' : z.r));
|
|
665
|
+
});
|
|
666
|
+
k.appendChild(vgl);
|
|
667
|
+
} else {
|
|
668
|
+
var pre = el('div', 'raw');
|
|
669
|
+
pre.style.margin = '2px 12px 10px';
|
|
670
|
+
pre.style.whiteSpace = 'pre';
|
|
671
|
+
pre.style.maxHeight = 'none';
|
|
672
|
+
pre.textContent = JSON.stringify(x.obj, null, 2);
|
|
673
|
+
k.appendChild(pre);
|
|
674
|
+
}
|
|
675
|
+
body.appendChild(k);
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
var zt = $('#dry-titel');
|
|
679
|
+
if (zt) {
|
|
680
|
+
var zielName = S.entwurf.ziel || S.entwurf.kanal;
|
|
681
|
+
zt.textContent = (S.objects[zielName] ? tr('write.dryTitleUpdate') : tr('write.dryTitleCreate')) +
|
|
682
|
+
(entwuerfe.length > 1 ? tr('write.dryOutputs', entwuerfe.length) : '');
|
|
683
|
+
}
|
|
684
|
+
/* Beim Oeffnen wieder herstellen: der Dialog wird wiederverwendet, und
|
|
685
|
+
nach einem erfolgreichen Durchgang ist der Knopf ausgeblendet. */
|
|
686
|
+
$('#btn-dry-write').hidden = false;
|
|
687
|
+
$('#btn-dry-write').disabled = probleme.length > 0;
|
|
688
|
+
$('#btn-dry-write').textContent = S.objects[S.entwurf.ziel || S.entwurf.kanal] ? tr('write.nowUpdate') : tr('write.now');
|
|
689
|
+
$('#dlg-dry').showModal();
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/* Einen Alias wieder loswerden.
|
|
693
|
+
|
|
694
|
+
Anlegen und Aktualisieren gab es, Entfernen nicht — und der
|
|
695
|
+
naheliegende Umweg lief ins Leere: haekelt man alle Punkte ab, listet
|
|
696
|
+
der Trockenlauf sie zwar auf, sperrt aber das Schreiben mit „kein
|
|
697
|
+
einziger Datenpunkt ausgewaehlt". Ein einmal angelegter Alias liess
|
|
698
|
+
sich nur ueber die Objektverwaltung entfernen. */
|
|
699
|
+
|
|
700
|
+
/* Die Geschwister eines Ausgangs: bei einem Mehrfachgeraet tragen alle
|
|
701
|
+
Kanaele dieselbe Quelle. Ohne sie loescht „Alias entfernen" nur einen
|
|
702
|
+
von vier Ausgaengen, und der Rest bleibt verwaist stehen — man glaubt,
|
|
703
|
+
das Geraet sei weg. */
|
|
704
|
+
function geschwisterVon(ziel) {
|
|
705
|
+
var o = S.objects[ziel];
|
|
706
|
+
var q = o && o.native && o.native.quelle;
|
|
707
|
+
if (!q) { return []; }
|
|
708
|
+
return S.keysSorted.filter(function (id) {
|
|
709
|
+
if (id === ziel || id.indexOf('alias.') !== 0) { return false; }
|
|
710
|
+
var x = S.objects[id];
|
|
711
|
+
return x && x.type === 'channel' && x.native && x.native.quelle === q;
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export function zeigeLoeschen() {
|
|
716
|
+
var ziel = (S.entwurf && (S.entwurf.ziel || S.entwurf.kanal)) || S.current;
|
|
717
|
+
if (!ziel || ziel.indexOf('alias.') !== 0 || !S.objects[ziel]) { return; }
|
|
718
|
+
S.loeschZiel = ziel;
|
|
719
|
+
S.loeschAlleAusgaenge = false;
|
|
720
|
+
zeichneLoeschen();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function loeschUmfang() {
|
|
724
|
+
var ziel = S.loeschZiel;
|
|
725
|
+
var wurzeln = [ziel];
|
|
726
|
+
if (S.loeschAlleAusgaenge) { wurzeln = wurzeln.concat(geschwisterVon(ziel)); }
|
|
727
|
+
var alle = [];
|
|
728
|
+
wurzeln.forEach(function (w) {
|
|
729
|
+
alle.push(w);
|
|
730
|
+
S.keysSorted.forEach(function (id) {
|
|
731
|
+
if (id.indexOf(w + '.') === 0 && alle.indexOf(id) === -1) { alle.push(id); }
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
return alle;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/* Welche selbst angelegten Aufzaehlungen wuerden durch dieses Loeschen
|
|
738
|
+
leer? Ein Ergebnis fuer Dialog und Tat — der Dialog nennt genau das,
|
|
739
|
+
was hinterher geschieht, keine stille Zugabe. Gerechnet gegen die
|
|
740
|
+
Loeschliste, nicht gegen den Speicherstand: ob der Admin seine
|
|
741
|
+
Mitglieder-Austragung schon gemeldet hat, ist Zufall. */
|
|
742
|
+
function enumsDieLeerWuerden(alle) {
|
|
743
|
+
var raus = [];
|
|
744
|
+
['rooms', 'functions'].forEach(function (art) {
|
|
745
|
+
var betroffen = {};
|
|
746
|
+
alle.forEach(function (id) {
|
|
747
|
+
enumsVon(id, art).forEach(function (eid) { betroffen[eid] = true; });
|
|
748
|
+
});
|
|
749
|
+
Object.keys(betroffen).forEach(function (eid) {
|
|
750
|
+
var o = enums[eid];
|
|
751
|
+
if (!o || !o.native || o.native.angelegtVon !== 'alias-workbench') { return; }
|
|
752
|
+
var uebrig = ((o.common && o.common.members) || []).filter(function (m) {
|
|
753
|
+
return alle.indexOf(m) === -1;
|
|
754
|
+
});
|
|
755
|
+
if (!uebrig.length) {
|
|
756
|
+
raus.push({ id: eid, art: art,
|
|
757
|
+
name: txt((o.common || {}).name) || eid.split('.').pop() });
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
return raus;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
export function zeichneLoeschen() {
|
|
765
|
+
var ziel = S.loeschZiel;
|
|
766
|
+
var body = $('#del-body');
|
|
767
|
+
body.textContent = '';
|
|
768
|
+
var alle = loeschUmfang();
|
|
769
|
+
|
|
770
|
+
var kopf = el('div');
|
|
771
|
+
kopf.style.marginBottom = '11px';
|
|
772
|
+
kopf.style.fontSize = '12.5px';
|
|
773
|
+
kopf.appendChild(el('b', null, tr('del.count', alle.length)));
|
|
774
|
+
body.appendChild(kopf);
|
|
775
|
+
|
|
776
|
+
var hin = el('div', 'aside w');
|
|
777
|
+
hin.style.marginBottom = '11px';
|
|
778
|
+
hin.textContent = tr('del.hint');
|
|
779
|
+
body.appendChild(hin);
|
|
780
|
+
|
|
781
|
+
var gesch = geschwisterVon(ziel);
|
|
782
|
+
if (gesch.length) {
|
|
783
|
+
var gw = el('div', 'aside');
|
|
784
|
+
gw.style.marginBottom = '11px';
|
|
785
|
+
var lab = document.createElement('label');
|
|
786
|
+
lab.style.display = 'flex';
|
|
787
|
+
lab.style.gap = '8px';
|
|
788
|
+
lab.style.alignItems = 'flex-start';
|
|
789
|
+
lab.style.cursor = 'pointer';
|
|
790
|
+
var cb = document.createElement('input');
|
|
791
|
+
cb.type = 'checkbox';
|
|
792
|
+
cb.checked = S.loeschAlleAusgaenge;
|
|
793
|
+
cb.addEventListener('change', function () {
|
|
794
|
+
S.loeschAlleAusgaenge = cb.checked;
|
|
795
|
+
zeichneLoeschen();
|
|
796
|
+
});
|
|
797
|
+
lab.appendChild(cb);
|
|
798
|
+
var txt2 = el('span');
|
|
799
|
+
txt2.appendChild(el('b', null, tr('del.allOutputs', gesch.length + 1)));
|
|
800
|
+
txt2.appendChild(el('div', 'hint', tr('del.allOutputsHint',
|
|
801
|
+
gesch.map(function (x) { return x.split('.').pop(); }).join(', '))));
|
|
802
|
+
lab.appendChild(txt2);
|
|
803
|
+
gw.appendChild(lab);
|
|
804
|
+
body.appendChild(gw);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
var k = el('div', 'card');
|
|
808
|
+
var ch = el('div', 'ch');
|
|
809
|
+
ch.appendChild(el('span', 'typ', tr('del.objects')));
|
|
810
|
+
ch.appendChild(el('span', 'chip bad', String(alle.length)));
|
|
811
|
+
k.appendChild(ch);
|
|
812
|
+
alle.forEach(function (id) {
|
|
813
|
+
var r = el('div', 'slot');
|
|
814
|
+
r.style.gridTemplateColumns = '1fr 90px';
|
|
815
|
+
r.appendChild(el('span', 'sn', id));
|
|
816
|
+
r.appendChild(el('span', 'rx', (S.objects[id] && S.objects[id].type) || ''));
|
|
817
|
+
k.appendChild(r);
|
|
818
|
+
});
|
|
819
|
+
body.appendChild(k);
|
|
820
|
+
|
|
821
|
+
/* Mitangelegte Aufzaehlungen, die dadurch leer wuerden, gehen mit —
|
|
822
|
+
und stehen deshalb hier, nicht nur im Nachhinein im Objektbaum. */
|
|
823
|
+
var mitEnums = enumsDieLeerWuerden(alle);
|
|
824
|
+
if (mitEnums.length) {
|
|
825
|
+
var ew = el('div', 'aside');
|
|
826
|
+
ew.style.marginTop = '11px';
|
|
827
|
+
mitEnums.forEach(function (x) {
|
|
828
|
+
ew.appendChild(el('div', null,
|
|
829
|
+
tr(x.art === 'rooms' ? 'del.alsoRoom' : 'del.alsoFunction', x.name)));
|
|
830
|
+
});
|
|
831
|
+
body.appendChild(ew);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
var b = $('#btn-del-go');
|
|
835
|
+
b.disabled = false;
|
|
836
|
+
b.hidden = false;
|
|
837
|
+
b.textContent = tr('del.now');
|
|
838
|
+
if (!$('#dlg-del').open) { $('#dlg-del').showModal(); }
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
export function loescheAlias() {
|
|
842
|
+
if (!S.loeschZiel) { return; }
|
|
843
|
+
var ziel = S.loeschZiel;
|
|
844
|
+
var alle = loeschUmfang();
|
|
845
|
+
/* Kinder vor den Eltern, sonst bleiben Waisen stehen. */
|
|
846
|
+
alle.sort(function (a, b) { return b.length - a.length; });
|
|
847
|
+
|
|
848
|
+
/* Vor dem Loeschen bestimmt — danach steht die Mitgliedschaft
|
|
849
|
+
nirgends mehr (die traegt der Admin beim delObject selbst aus).
|
|
850
|
+
Dieselbe Rechnung hat der Dialog gezeigt; hier wird getan, was
|
|
851
|
+
angekuendigt war. */
|
|
852
|
+
var enumsMitzunehmen = enumsDieLeerWuerden(alle).map(function (x) { return x.id; });
|
|
853
|
+
|
|
854
|
+
var b = $('#btn-del-go');
|
|
855
|
+
b.disabled = true;
|
|
856
|
+
b.textContent = tr('write.writing');
|
|
857
|
+
|
|
858
|
+
var offen = alle.length, fehler = [];
|
|
859
|
+
alle.forEach(function (id) {
|
|
860
|
+
socket.emit('delObject', id, function (err) {
|
|
861
|
+
if (err) { fehler.push(id + ': ' + err); }
|
|
862
|
+
if (--offen === 0) {
|
|
863
|
+
var body = $('#del-body');
|
|
864
|
+
body.textContent = '';
|
|
865
|
+
var m = el('div');
|
|
866
|
+
m.style.fontSize = '13px';
|
|
867
|
+
if (fehler.length) {
|
|
868
|
+
m.appendChild(el('b', null, tr('write.failed', fehler.length)));
|
|
869
|
+
} else {
|
|
870
|
+
m.appendChild(el('b', null, tr('del.done', alle.length)));
|
|
871
|
+
}
|
|
872
|
+
body.appendChild(m);
|
|
873
|
+
alle.forEach(function (x) { delete S.objects[x]; });
|
|
874
|
+
S.keysSorted = Object.keys(S.objects).sort();
|
|
875
|
+
|
|
876
|
+
/* Ordner, die dadurch leer geworden sind, mitnehmen — aber nur
|
|
877
|
+
die eigenen Zwischenebenen unterhalb von alias.0, und nur
|
|
878
|
+
solange wirklich nichts mehr darin liegt. Und Aufzaehlungen,
|
|
879
|
+
die die Werkbank selbst angelegt hat und die jetzt leer sind,
|
|
880
|
+
gleich mit. */
|
|
881
|
+
/* Nach dem Erfolg gibt es nichts mehr zu entfernen - der Knopf
|
|
882
|
+
verschwindet, statt ausgegraut neben „Schliessen" zu stehen.
|
|
883
|
+
Nach einem Fehlschlag bleibt er als „Noch einmal". */
|
|
884
|
+
if (fehler.length) {
|
|
885
|
+
b.disabled = false;
|
|
886
|
+
b.textContent = tr('write.retry');
|
|
887
|
+
} else {
|
|
888
|
+
b.hidden = true;
|
|
889
|
+
}
|
|
890
|
+
leereEnumsRaeumen(enumsMitzunehmen, alle, function () {
|
|
891
|
+
leereOrdnerRaeumen(ziel, function () {
|
|
892
|
+
S.loeschZiel = null;
|
|
893
|
+
S.entwurf = null;
|
|
894
|
+
S.current = null;
|
|
895
|
+
zeichneBaum();
|
|
896
|
+
zeichneErgebnis();
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/* Aufzaehlungen, die die Werkbank selbst angelegt hat (Herkunftsmarke in
|
|
905
|
+
native) und deren letztes Mitglied gerade geloescht wurde, verschwinden
|
|
906
|
+
mit. Fremde und vorbestehende Aufzaehlungen bleiben — dort ist leer
|
|
907
|
+
kein Fehler, sondern ein Zustand, den jemand anders verantwortet.
|
|
908
|
+
Gerechnet wird gegen die eigene Loeschliste, nicht gegen den Stand im
|
|
909
|
+
Speicher: der Admin traegt Mitgliedschaften beim delObject selbst aus,
|
|
910
|
+
und ob seine Meldung schon angekommen ist, ist Zufall. */
|
|
911
|
+
function leereEnumsRaeumen(eids, geloescht, fertig) {
|
|
912
|
+
var offen = eids.length;
|
|
913
|
+
if (!offen) { return fertig(); }
|
|
914
|
+
eids.forEach(function (eid) {
|
|
915
|
+
var o = enums[eid];
|
|
916
|
+
var uebrig = ((o && o.common && o.common.members) || []).filter(function (m) {
|
|
917
|
+
return geloescht.indexOf(m) === -1;
|
|
918
|
+
});
|
|
919
|
+
var eigene = !!(o && o.native && o.native.angelegtVon === 'alias-workbench');
|
|
920
|
+
if (!o || uebrig.length || !eigene) {
|
|
921
|
+
if (--offen === 0) { fertig(); }
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
socket.emit('delObject', eid, function () {
|
|
925
|
+
delete enums[eid];
|
|
926
|
+
if (--offen === 0) { fertig(); }
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/* ================== Einen Alias verlegen ==================
|
|
932
|
+
|
|
933
|
+
Verschieben und Umbenennen sind im Objektspeicher dasselbe, und beides
|
|
934
|
+
gibt es dort nicht: Eine Kennung laesst sich nicht aendern. Was hier
|
|
935
|
+
„verlegen“ heisst, ist immer anlegen unter der neuen Kennung und
|
|
936
|
+
loeschen unter der alten. Ob sich dabei der Ordner aendert, der Name
|
|
937
|
+
oder beides, ist nur eine Frage der Felder.
|
|
938
|
+
|
|
939
|
+
Das Unangenehme daran ist nicht der Umzug, sondern was zurueckbleibt.
|
|
940
|
+
Eine Messreihe in InfluxDB haengt an der Kennung, nicht am Geraet: Die
|
|
941
|
+
Aufzeichnung wandert mit, die bisherigen Werte bleiben unter der alten
|
|
942
|
+
Kennung liegen und werden nicht mehr ergaenzt. Dasselbe gilt fuer
|
|
943
|
+
jedes Skript und jede Ansicht, die die alte Kennung nennt. Die
|
|
944
|
+
Werkbank kann das nicht reparieren - sie kann es aber benennen, und
|
|
945
|
+
genau das tut der Dialog, bevor irgendetwas geschieht. */
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
function verlegeUmfang(alt) {
|
|
949
|
+
return S.keysSorted.filter(function (id) {
|
|
950
|
+
return id === alt || id.indexOf(alt + '.') === 0;
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/* Wer nennt diese Kennung sonst noch?
|
|
955
|
+
|
|
956
|
+
Die Skripte des javascript-Adapters lassen sich durchsuchen; sie sind
|
|
957
|
+
der haeufigste Ort, an dem eine Kennung im Klartext steht. vis-
|
|
958
|
+
Ansichten und fremde Adapter kann die Werkbank nicht durchsehen -
|
|
959
|
+
darauf weist der Dialog eigens hin, statt Vollstaendigkeit
|
|
960
|
+
vorzutaeuschen. */
|
|
961
|
+
export function sucheInSkripten(kennung, fertig) {
|
|
962
|
+
var treffer = [];
|
|
963
|
+
var fertigMit = function () { fertig(treffer); };
|
|
964
|
+
try {
|
|
965
|
+
socket.emit('getObjectView', 'script', 'javascript',
|
|
966
|
+
{ startkey: 'script.js.', endkey: 'script.js.\u9999' }, function (err, doc) {
|
|
967
|
+
if (err || !doc || !doc.rows) { return fertigMit(); }
|
|
968
|
+
doc.rows.forEach(function (r) {
|
|
969
|
+
var o = r.value;
|
|
970
|
+
var q = o && o.common && o.common.source;
|
|
971
|
+
if (!q || q.indexOf(kennung) === -1) { return; }
|
|
972
|
+
var zeilen = String(q).split('\n');
|
|
973
|
+
var stellen = [];
|
|
974
|
+
zeilen.forEach(function (z, i) {
|
|
975
|
+
if (z.indexOf(kennung) > -1) { stellen.push(i + 1); }
|
|
976
|
+
});
|
|
977
|
+
treffer.push({
|
|
978
|
+
id: r.id,
|
|
979
|
+
name: txt((o.common || {}).name) || r.id.split('.').pop(),
|
|
980
|
+
an: !!(o.common && o.common.enabled),
|
|
981
|
+
zeilen: stellen
|
|
982
|
+
});
|
|
983
|
+
});
|
|
984
|
+
fertigMit();
|
|
985
|
+
});
|
|
986
|
+
} catch { fertigMit(); }
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export function zeigeVerlegen() {
|
|
990
|
+
/* Der Knopf steht auch an der Quelle, sobald es den Alias gibt (T1).
|
|
991
|
+
Dort ist S.current die Quelle, nicht der Alias - gemeint ist immer
|
|
992
|
+
das Ziel des Entwurfs. Genauso haelt es zeigeLoeschen; zeigeTausch
|
|
993
|
+
tat es bis 05.09.2026 nicht, und beide Knoepfe waren an der Quelle
|
|
994
|
+
sichtbar, aber wirkungslos: kein Dialog, keine Meldung. */
|
|
995
|
+
var alt = (S.entwurf && (S.entwurf.ziel || S.entwurf.kanal)) || S.current;
|
|
996
|
+
if (!alt || alt.indexOf('alias.') !== 0 || !S.objects[alt]) { return; }
|
|
997
|
+
S.verlegeZiel = alt;
|
|
998
|
+
|
|
999
|
+
var teile = alt.split('.');
|
|
1000
|
+
var name0 = teile.pop();
|
|
1001
|
+
var ordner0 = teile.join('.');
|
|
1002
|
+
|
|
1003
|
+
var body = $('#move-body');
|
|
1004
|
+
body.textContent = '';
|
|
1005
|
+
|
|
1006
|
+
/* --- die beiden Felder --- */
|
|
1007
|
+
var bar = el('div', 'zielbar');
|
|
1008
|
+
bar.appendChild(el('span', 'zl', tr('target.folder')));
|
|
1009
|
+
|
|
1010
|
+
/* Dasselbe Feld wie beim Anlegen, mit derselben Vorschlagsliste. Ohne
|
|
1011
|
+
sie muesste man den Zielordner auswendig wissen und fehlerfrei
|
|
1012
|
+
tippen - bei einem Vorgang, der Objekte loescht, die falscheste
|
|
1013
|
+
Stelle fuer einen Tippfehler. */
|
|
1014
|
+
var wrapO = el('div', 'feldwrap');
|
|
1015
|
+
var iO = el('input', 'tx ordnerfeld');
|
|
1016
|
+
iO.type = 'text';
|
|
1017
|
+
iO.setAttribute('autocomplete', 'off');
|
|
1018
|
+
iO.value = ordner0.indexOf('alias.0.') === 0 ? ordner0.slice('alias.0.'.length) : '';
|
|
1019
|
+
iO.placeholder = tr('target.folderPlaceholder');
|
|
1020
|
+
wrapO.appendChild(iO);
|
|
1021
|
+
|
|
1022
|
+
/* Kreuz zum Leeren - dann liegt der Alias direkt unter alias.0. */
|
|
1023
|
+
var xO = el('span', 'feldx');
|
|
1024
|
+
xO.title = tr('target.clearField');
|
|
1025
|
+
xO.setAttribute('aria-label', tr('target.clearField'));
|
|
1026
|
+
xO.addEventListener('mousedown', function (ev) {
|
|
1027
|
+
ev.preventDefault();
|
|
1028
|
+
iO.value = '';
|
|
1029
|
+
malen();
|
|
1030
|
+
ordnerMalen(true);
|
|
1031
|
+
iO.focus();
|
|
1032
|
+
});
|
|
1033
|
+
wrapO.appendChild(xO);
|
|
1034
|
+
|
|
1035
|
+
var listeO = el('div', 'vorschlaege');
|
|
1036
|
+
listeO.hidden = true;
|
|
1037
|
+
wrapO.appendChild(listeO);
|
|
1038
|
+
bar.appendChild(wrapO);
|
|
1039
|
+
|
|
1040
|
+
/* Beim Aufklappen die ganze Liste, erst beim Tippen filtern.
|
|
1041
|
+
|
|
1042
|
+
Vorher filterte auch das Aufklappen mit dem Feldinhalt - und der ist
|
|
1043
|
+
vorbelegt. Wer den Alias nur umbenennen wollte, sah genau einen
|
|
1044
|
+
Eintrag: den Ordner, in dem er ohnehin liegt. Um irgendwohin sonst
|
|
1045
|
+
zu verschieben, musste man das Feld erst leeren. Dasselbe hatte das
|
|
1046
|
+
Musterfeld schon gelernt: aufklappen zeigt alles, tippen sucht. */
|
|
1047
|
+
var ordnerMalen = function (alleZeigen) {
|
|
1048
|
+
var such = alleZeigen ? '' : String(iO.value || '').trim().toLowerCase();
|
|
1049
|
+
var jetzt = String(iO.value || '').trim();
|
|
1050
|
+
var alle = ordnerUnterAlias()
|
|
1051
|
+
.map(function (o) { return o.slice('alias.0.'.length); })
|
|
1052
|
+
.filter(Boolean)
|
|
1053
|
+
.filter(function (o) { return !such || o.toLowerCase().indexOf(such) > -1; });
|
|
1054
|
+
|
|
1055
|
+
listeO.textContent = '';
|
|
1056
|
+
|
|
1057
|
+
/* „kein Ordner“ gehoert dazu: alias.0 selbst ist ein gueltiger Platz,
|
|
1058
|
+
steht aber in keiner Ordnerliste. */
|
|
1059
|
+
if (!such && jetzt) {
|
|
1060
|
+
var z0 = el('div', 'vz');
|
|
1061
|
+
z0.appendChild(el('span', null, tr('move.noFolder')));
|
|
1062
|
+
z0.addEventListener('mousedown', function (ev) {
|
|
1063
|
+
ev.preventDefault();
|
|
1064
|
+
iO.value = '';
|
|
1065
|
+
listeO.hidden = true;
|
|
1066
|
+
malen();
|
|
1067
|
+
});
|
|
1068
|
+
listeO.appendChild(z0);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
alle.forEach(function (o) {
|
|
1072
|
+
var z = el('div', 'vz' + (o === jetzt ? ' gewaehlt' : ''));
|
|
1073
|
+
z.appendChild(el('span', null, o));
|
|
1074
|
+
z.addEventListener('mousedown', function (ev) {
|
|
1075
|
+
ev.preventDefault();
|
|
1076
|
+
iO.value = o;
|
|
1077
|
+
listeO.hidden = true;
|
|
1078
|
+
malen();
|
|
1079
|
+
});
|
|
1080
|
+
listeO.appendChild(z);
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
listeO.hidden = !listeO.childNodes.length;
|
|
1084
|
+
};
|
|
1085
|
+
iO.addEventListener('focus', function () { ordnerMalen(true); });
|
|
1086
|
+
iO.addEventListener('blur', function () {
|
|
1087
|
+
setTimeout(function () { listeO.hidden = true; }, 140);
|
|
1088
|
+
});
|
|
1089
|
+
bar.appendChild(el('span', 'zl', tr('target.name')));
|
|
1090
|
+
var iN = el('input', 'tx');
|
|
1091
|
+
iN.type = 'text';
|
|
1092
|
+
iN.value = name0;
|
|
1093
|
+
iN.style.width = '200px';
|
|
1094
|
+
bar.appendChild(iN);
|
|
1095
|
+
body.appendChild(bar);
|
|
1096
|
+
|
|
1097
|
+
var vorschau = el('div');
|
|
1098
|
+
body.appendChild(vorschau);
|
|
1099
|
+
|
|
1100
|
+
var saeubern = function (t) {
|
|
1101
|
+
return String(t).trim()
|
|
1102
|
+
.replace(/[^\w.\- \u00e4\u00f6\u00fc\u00c4\u00d6\u00dc\u00df]/g, '_')
|
|
1103
|
+
.replace(/\s+/g, '_')
|
|
1104
|
+
.replace(/\.{2,}/g, '.')
|
|
1105
|
+
.replace(/^\.+|\.+$/g, '');
|
|
1106
|
+
};
|
|
1107
|
+
var neuesZiel = function () {
|
|
1108
|
+
/* Zwei verschiedene Regeln, und das mit Absicht: im ORDNER trennen
|
|
1109
|
+
Punkte die Unterebenen und muessen bleiben, im NAMEN wuerden sie
|
|
1110
|
+
eine neue Ebene aufmachen. Der Name geht deshalb durch dieselbe
|
|
1111
|
+
Funktion wie der automatische Vorschlag - vorher hatte dieser
|
|
1112
|
+
Dialog seine eigene, und aus „Dimmer Neu, Test." wurde
|
|
1113
|
+
`Dimmer_Neu__Test` statt `Dimmer_Neu_Test` (26.08.2026). */
|
|
1114
|
+
var o = saeubern(iO.value), n = kennungtauglich(iN.value);
|
|
1115
|
+
if (!n) { return ''; }
|
|
1116
|
+
return 'alias.0.' + (o ? o + '.' : '') + n;
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
var malen = function () {
|
|
1120
|
+
vorschau.textContent = '';
|
|
1121
|
+
var neu = neuesZiel();
|
|
1122
|
+
var b = $('#btn-move-go');
|
|
1123
|
+
|
|
1124
|
+
if (!neu) {
|
|
1125
|
+
vorschau.appendChild(el('div', 'aside w', tr('move.needName')));
|
|
1126
|
+
b.disabled = true;
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (neu === alt) {
|
|
1130
|
+
vorschau.appendChild(el('div', 'aside', tr('move.unchanged')));
|
|
1131
|
+
b.disabled = true;
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
if (S.objects[neu]) {
|
|
1135
|
+
vorschau.appendChild(el('div', 'aside w', tr('move.exists', neu)));
|
|
1136
|
+
b.disabled = true;
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
b.disabled = false;
|
|
1140
|
+
|
|
1141
|
+
var umfang = verlegeUmfang(alt);
|
|
1142
|
+
|
|
1143
|
+
/* Quelle und Ziel einmal, nicht je Datenpunkt.
|
|
1144
|
+
|
|
1145
|
+
Hier stand zuerst jedes Objekt mit voller alter und neuer Kennung
|
|
1146
|
+
untereinander - acht Zeilen, in denen sich immer nur die ersten
|
|
1147
|
+
beiden Glieder unterschieden und die Punktnamen wortgleich
|
|
1148
|
+
wiederholt wurden. Was sich aendert, ist der Pfad; was gleich
|
|
1149
|
+
bleibt, gehoert in eine Zeile. */
|
|
1150
|
+
var k = el('div', 'card');
|
|
1151
|
+
var ch = el('div', 'ch');
|
|
1152
|
+
ch.appendChild(el('span', 'typ', tr('move.willMove')));
|
|
1153
|
+
ch.appendChild(el('span', 'chip ok', String(umfang.length)));
|
|
1154
|
+
k.appendChild(ch);
|
|
1155
|
+
|
|
1156
|
+
var vz = el('div', 'slot');
|
|
1157
|
+
vz.style.gridTemplateColumns = '52px 1fr';
|
|
1158
|
+
vz.appendChild(el('span', 'rx', tr('move.from')));
|
|
1159
|
+
vz.appendChild(el('span', 'sn', alt));
|
|
1160
|
+
k.appendChild(vz);
|
|
1161
|
+
|
|
1162
|
+
var nz = el('div', 'slot');
|
|
1163
|
+
nz.style.gridTemplateColumns = '52px 1fr';
|
|
1164
|
+
nz.appendChild(el('span', 'rx', tr('move.to')));
|
|
1165
|
+
nz.appendChild(el('span', 'sn', neu));
|
|
1166
|
+
k.appendChild(nz);
|
|
1167
|
+
|
|
1168
|
+
var punkte = umfang.filter(function (id) { return id !== alt; })
|
|
1169
|
+
.map(function (id) { return id.slice(alt.length + 1); });
|
|
1170
|
+
if (punkte.length) {
|
|
1171
|
+
var pz = el('div', 'slot');
|
|
1172
|
+
pz.style.gridTemplateColumns = '52px 1fr';
|
|
1173
|
+
pz.appendChild(el('span', 'rx', tr('move.with')));
|
|
1174
|
+
pz.appendChild(el('span', 'sn', punkte.join(', ')));
|
|
1175
|
+
k.appendChild(pz);
|
|
1176
|
+
}
|
|
1177
|
+
/* Fehlende Ordner ueber dem Ziel werden mit angelegt - das steht
|
|
1178
|
+
hier, weil nichts still geschehen soll (verlegeAlias tut es). */
|
|
1179
|
+
var teileV = neu.split('.'), ordnerNeu = [];
|
|
1180
|
+
for (var iV = 3; iV < teileV.length; iV++) {
|
|
1181
|
+
var pfadV = teileV.slice(0, iV).join('.');
|
|
1182
|
+
if (!S.objects[pfadV]) { ordnerNeu.push(pfadV); }
|
|
1183
|
+
}
|
|
1184
|
+
if (ordnerNeu.length) {
|
|
1185
|
+
var oz = el('div', 'slot');
|
|
1186
|
+
oz.style.gridTemplateColumns = '52px 1fr';
|
|
1187
|
+
oz.appendChild(el('span', 'rx', tr('move.newFolder')));
|
|
1188
|
+
oz.appendChild(el('span', 'sn', ordnerNeu.join(', ')));
|
|
1189
|
+
k.appendChild(oz);
|
|
1190
|
+
}
|
|
1191
|
+
vorschau.appendChild(k);
|
|
1192
|
+
|
|
1193
|
+
/* --- Warnungen --- */
|
|
1194
|
+
var mitCustom = umfang.filter(function (id) {
|
|
1195
|
+
var c = S.objects[id] && S.objects[id].common;
|
|
1196
|
+
return c && c.custom && Object.keys(c.custom).length;
|
|
1197
|
+
});
|
|
1198
|
+
if (mitCustom.length) {
|
|
1199
|
+
var w1 = el('div', 'aside w');
|
|
1200
|
+
w1.appendChild(el('b', null, tr('move.warnHistoryHead')));
|
|
1201
|
+
w1.appendChild(document.createTextNode(' ' + tr('move.warnHistory')));
|
|
1202
|
+
var ul = el('ul');
|
|
1203
|
+
mitCustom.forEach(function (id) {
|
|
1204
|
+
var adapter = Object.keys(S.objects[id].common.custom).join(', ');
|
|
1205
|
+
ul.appendChild(el('li', null, id + ' \u2192 ' + adapter));
|
|
1206
|
+
});
|
|
1207
|
+
w1.appendChild(ul);
|
|
1208
|
+
vorschau.appendChild(w1);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
var w2 = el('div', 'aside');
|
|
1212
|
+
w2.appendChild(el('b', null, tr('move.warnRefsHead')));
|
|
1213
|
+
w2.appendChild(document.createTextNode(' ' + tr('move.warnRefs')));
|
|
1214
|
+
vorschau.appendChild(w2);
|
|
1215
|
+
|
|
1216
|
+
/* Skripte erst suchen, wenn das Ziel steht - sonst sucht sie bei
|
|
1217
|
+
jedem Tastendruck. */
|
|
1218
|
+
var skriptFeld = el('div');
|
|
1219
|
+
vorschau.appendChild(skriptFeld);
|
|
1220
|
+
sucheInSkripten(alt, function (treffer) {
|
|
1221
|
+
if (neuesZiel() !== neu) { return; } /* inzwischen weitergetippt */
|
|
1222
|
+
skriptFeld.textContent = '';
|
|
1223
|
+
if (!treffer.length) {
|
|
1224
|
+
skriptFeld.appendChild(el('div', 'aside', tr('move.noScripts')));
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
var w3 = el('div', 'aside w');
|
|
1228
|
+
/* Ein Skript ist kein „1 Skripte“. */
|
|
1229
|
+
w3.appendChild(el('b', null, treffer.length === 1
|
|
1230
|
+
? tr('move.scriptsHead1')
|
|
1231
|
+
: tr('move.scriptsHead', treffer.length)));
|
|
1232
|
+
var ul3 = el('ul');
|
|
1233
|
+
treffer.forEach(function (t) {
|
|
1234
|
+
ul3.appendChild(el('li', null, t.name + (t.an ? '' : ' (' + tr('move.scriptOff') + ')') +
|
|
1235
|
+
' \u2014 ' + (t.zeilen.length === 1
|
|
1236
|
+
? tr('move.scriptLines', t.zeilen[0])
|
|
1237
|
+
: tr('move.scriptLinesN', t.zeilen.join(', ')))));
|
|
1238
|
+
});
|
|
1239
|
+
w3.appendChild(ul3);
|
|
1240
|
+
skriptFeld.appendChild(w3);
|
|
1241
|
+
});
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
/* Den Knopf zuruecksetzen, BEVOR `malen` urteilt.
|
|
1245
|
+
|
|
1246
|
+
Stand das Zuruecksetzen dahinter, hob es das Urteil gleich wieder
|
|
1247
|
+
auf: beim Oeffnen steht „nichts zu tun“ da, und der Knopf war
|
|
1248
|
+
trotzdem freigegeben. Gedrueckt hat er nichts getan - `verlegeAlias`
|
|
1249
|
+
faengt den Fall ab -, aber ein Knopf, der nichts tut, gehoert nicht
|
|
1250
|
+
freigegeben. */
|
|
1251
|
+
var b0 = $('#btn-move-go');
|
|
1252
|
+
b0.hidden = false;
|
|
1253
|
+
b0.disabled = false;
|
|
1254
|
+
b0.textContent = tr('move.now');
|
|
1255
|
+
|
|
1256
|
+
iO.addEventListener('input', function () { ordnerMalen(false); malen(); });
|
|
1257
|
+
iN.addEventListener('input', malen);
|
|
1258
|
+
/* Beim Verlassen steht im Feld, was auch geschrieben wird. Die
|
|
1259
|
+
Zielleiste haelt es seit dem 26.08.2026 so; hier blieb „Dimmer Neu,
|
|
1260
|
+
Test." stehen, waehrend die Vorschau darunter schon
|
|
1261
|
+
`Dimmer_Neu_Test` zeigte - dieselbe Lage wie D19, nur umgekehrt
|
|
1262
|
+
herum: das Ziel stimmte, das Feld log. Gefunden 05.09.2026 (T19). */
|
|
1263
|
+
iN.addEventListener('change', function () {
|
|
1264
|
+
var sauber = kennungtauglich(iN.value);
|
|
1265
|
+
if (sauber && sauber !== iN.value) { iN.value = sauber; malen(); }
|
|
1266
|
+
});
|
|
1267
|
+
malen();
|
|
1268
|
+
if (!$('#dlg-move').open) { $('#dlg-move').showModal(); }
|
|
1269
|
+
setTimeout(function () { iN.focus(); iN.select(); }, 0);
|
|
1270
|
+
|
|
1271
|
+
/* Der Knopf braucht das Ziel beim Druecken, nicht beim Bauen. */
|
|
1272
|
+
b0.onclick = function () { verlegeAlias(alt, neuesZiel()); };
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
export function verlegeAlias(alt, neu) {
|
|
1276
|
+
if (!alt || !neu || alt === neu) { return; }
|
|
1277
|
+
var umfang = verlegeUmfang(alt);
|
|
1278
|
+
var b = $('#btn-move-go');
|
|
1279
|
+
b.disabled = true;
|
|
1280
|
+
b.textContent = tr('write.writing');
|
|
1281
|
+
|
|
1282
|
+
/* Erst alles Neue anlegen. Geht dabei etwas schief, steht der alte
|
|
1283
|
+
Alias noch unversehrt da - besser zwei als keiner. */
|
|
1284
|
+
/* Fehlende Ebenen ueber dem Ziel zuerst - das Erzeugen legt sie an
|
|
1285
|
+
(baueObjekte), das Verlegen liess sie aus: alias.0.Testecke gab es
|
|
1286
|
+
nach einem Umzug dorthin nur als Baumknoten, nicht als Objekt
|
|
1287
|
+
(Systemtest 25.08.2026). Gleiche Form wie dort: folder. */
|
|
1288
|
+
var arbeit = [];
|
|
1289
|
+
var teile = neu.split('.');
|
|
1290
|
+
for (var i = 3; i < teile.length; i++) {
|
|
1291
|
+
var pfad = teile.slice(0, i).join('.');
|
|
1292
|
+
if (!S.objects[pfad]) {
|
|
1293
|
+
arbeit.push({ id: pfad, obj: {
|
|
1294
|
+
type: 'folder',
|
|
1295
|
+
common: { name: teile[i - 1] },
|
|
1296
|
+
native: {}
|
|
1297
|
+
} });
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
umfang.forEach(function (id) {
|
|
1301
|
+
var o = JSON.parse(JSON.stringify(S.objects[id]));
|
|
1302
|
+
o._id = neu + id.slice(alt.length);
|
|
1303
|
+
delete o.ts; delete o.from; delete o.user; delete o.acl;
|
|
1304
|
+
arbeit.push({ id: o._id, obj: o });
|
|
1305
|
+
});
|
|
1306
|
+
var offen = arbeit.length, fehler = [];
|
|
1307
|
+
arbeit.forEach(function (a) {
|
|
1308
|
+
var zielId = a.id;
|
|
1309
|
+
var o = a.obj;
|
|
1310
|
+
socket.emit('setObject', zielId, o, function (err) {
|
|
1311
|
+
if (err) { fehler.push(zielId + ': ' + err); }
|
|
1312
|
+
else {
|
|
1313
|
+
/* Sofort in den eigenen Bestand, nicht erst wenn das Abo es
|
|
1314
|
+
nachtraegt. Das Abo haelt sich zurueck, solange ein Dialog
|
|
1315
|
+
offen steht - und der steht hier offen. Ohne diese Zeile kannte
|
|
1316
|
+
die Werkbank ihren eben angelegten Alias nicht, `delKnopf`
|
|
1317
|
+
blieb aus, und ein zweites Verlegen war nicht moeglich. */
|
|
1318
|
+
S.objects[zielId] = o;
|
|
1319
|
+
}
|
|
1320
|
+
if (--offen === 0) { S.keysSorted = Object.keys(S.objects).sort(); weiter(); }
|
|
1321
|
+
});
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
function weiter() {
|
|
1325
|
+
if (fehler.length) { return melden(fehler); }
|
|
1326
|
+
|
|
1327
|
+
/* Die Aufzaehlungen mitnehmen: alte Kennung raus, neue rein. Nur die
|
|
1328
|
+
eigene - alles andere darin gehoert anderen. */
|
|
1329
|
+
var enumArbeit = [];
|
|
1330
|
+
['rooms', 'functions'].forEach(function (art) {
|
|
1331
|
+
enumsVon(alt, art).forEach(function (eid) {
|
|
1332
|
+
var o = JSON.parse(JSON.stringify(enums[eid]));
|
|
1333
|
+
var m = (o.common.members || []).filter(function (x) { return x !== alt; });
|
|
1334
|
+
if (m.indexOf(neu) === -1) { m.push(neu); m.sort(); }
|
|
1335
|
+
o.common.members = m;
|
|
1336
|
+
delete o.ts; delete o.from; delete o.user; delete o.acl;
|
|
1337
|
+
enumArbeit.push({ id: eid, obj: o });
|
|
1338
|
+
});
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
var offen2 = enumArbeit.length;
|
|
1342
|
+
if (!offen2) { return loeschen(); }
|
|
1343
|
+
enumArbeit.forEach(function (a) {
|
|
1344
|
+
socket.emit('setObject', a.id, a.obj, function (err) {
|
|
1345
|
+
if (err) { fehler.push(a.id + ': ' + err); }
|
|
1346
|
+
if (--offen2 === 0) { loeschen(); }
|
|
1347
|
+
});
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function loeschen() {
|
|
1352
|
+
/* Kinder vor den Eltern, sonst bleiben Waisen stehen. */
|
|
1353
|
+
var weg = umfang.slice().sort(function (a, b) { return b.length - a.length; });
|
|
1354
|
+
var offen3 = weg.length;
|
|
1355
|
+
weg.forEach(function (id) {
|
|
1356
|
+
socket.emit('delObject', id, function (err) {
|
|
1357
|
+
if (err) { fehler.push(id + ': ' + err); }
|
|
1358
|
+
if (--offen3 === 0) {
|
|
1359
|
+
weg.forEach(function (x) { delete S.objects[x]; });
|
|
1360
|
+
S.keysSorted = Object.keys(S.objects).sort();
|
|
1361
|
+
leereOrdnerRaeumen(alt, function () {
|
|
1362
|
+
S.current = neu;
|
|
1363
|
+
S.entwurf = null;
|
|
1364
|
+
S.verlegeZiel = null;
|
|
1365
|
+
melden(fehler, neu);
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function melden(f, zielId) {
|
|
1373
|
+
var body = $('#move-body');
|
|
1374
|
+
body.textContent = '';
|
|
1375
|
+
var m = el('div');
|
|
1376
|
+
m.style.fontSize = '13px';
|
|
1377
|
+
if (f.length) {
|
|
1378
|
+
m.appendChild(el('b', null, tr('write.failed', f.length)));
|
|
1379
|
+
var ul = el('ul');
|
|
1380
|
+
f.forEach(function (x) { ul.appendChild(el('li', null, x)); });
|
|
1381
|
+
m.appendChild(ul);
|
|
1382
|
+
} else {
|
|
1383
|
+
m.appendChild(el('b', null, tr('move.done', zielId)));
|
|
1384
|
+
}
|
|
1385
|
+
body.appendChild(m);
|
|
1386
|
+
/* Nur in diesem Dialog verschwinden lassen - beim naechsten Oeffnen
|
|
1387
|
+
baut ihn `zeigeVerlegen` wieder auf. Vorher blieb er dauerhaft
|
|
1388
|
+
versteckt, und ein zweites Verlegen ging nicht mehr. */
|
|
1389
|
+
b.hidden = true;
|
|
1390
|
+
b.disabled = false;
|
|
1391
|
+
ladeEnums(function () {
|
|
1392
|
+
zeichneBaum();
|
|
1393
|
+
/* Den Alias unter der neuen Kennung wieder aufschlagen.
|
|
1394
|
+
|
|
1395
|
+
`current` allein reicht nicht: der Entwurf haengt an der alten
|
|
1396
|
+
Kennung und wurde geloescht, also zeichnete `zeichneErgebnis`
|
|
1397
|
+
die leere rechte Seite - obwohl der Baum den neuen Knoten schon
|
|
1398
|
+
markiert hatte. Wer danach nochmal verlegen wollte, fand keinen
|
|
1399
|
+
Knopf und musste den Alias erst wieder anklicken. `waehle` baut
|
|
1400
|
+
den Entwurf neu auf, und damit steht auch der Knopf wieder da. */
|
|
1401
|
+
if (zielId && S.objects[zielId]) { waehle(zielId); }
|
|
1402
|
+
else { zeichneErgebnis(); }
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
export function delKnopf(zeigen) {
|
|
1408
|
+
var b = $('#btn-del');
|
|
1409
|
+
if (b) { b.hidden = !zeigen; }
|
|
1410
|
+
/* Verlegen und Quellentausch gelten fuer dasselbe: einen Alias, den
|
|
1411
|
+
es gibt. */
|
|
1412
|
+
var m = $('#btn-move');
|
|
1413
|
+
if (m) { m.hidden = !zeigen; }
|
|
1414
|
+
var t = $('#btn-swap');
|
|
1415
|
+
if (t) { t.hidden = !zeigen; }
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
export function leereOrdnerRaeumen(weg, fertig) {
|
|
1419
|
+
var teile = weg.split('.');
|
|
1420
|
+
var kette = [];
|
|
1421
|
+
for (var i = teile.length - 1; i > 2; i--) {
|
|
1422
|
+
kette.push(teile.slice(0, i).join('.'));
|
|
1423
|
+
}
|
|
1424
|
+
var i2 = 0;
|
|
1425
|
+
(function weiter() {
|
|
1426
|
+
if (i2 >= kette.length) { return fertig(); }
|
|
1427
|
+
var pfad = kette[i2++];
|
|
1428
|
+
var o = S.objects[pfad];
|
|
1429
|
+
var belegt = S.keysSorted.some(function (id) { return id.indexOf(pfad + '.') === 0; });
|
|
1430
|
+
if (!o || o.type !== 'folder' || belegt) { return fertig(); }
|
|
1431
|
+
socket.emit('delObject', pfad, function () {
|
|
1432
|
+
delete S.objects[pfad];
|
|
1433
|
+
S.keysSorted = Object.keys(S.objects).sort();
|
|
1434
|
+
weiter();
|
|
1435
|
+
});
|
|
1436
|
+
})();
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/* Wirklich schreiben — nur aus dem Trockenlauf heraus erreichbar. */
|
|
1440
|
+
|
|
1441
|
+
export function schreibeObjekte() {
|
|
1442
|
+
if (!S.entwurf) { return; }
|
|
1443
|
+
var entwuerfe2 = zuSchreiben(S.trockenAlle);
|
|
1444
|
+
var liste = [];
|
|
1445
|
+
var schlecht = 0;
|
|
1446
|
+
entwuerfe2.forEach(function (en) {
|
|
1447
|
+
baueObjekte(en).forEach(function (o) {
|
|
1448
|
+
if (!liste.some(function (x) { return x.id === o.id; })) { liste.push(o); }
|
|
1449
|
+
});
|
|
1450
|
+
schlecht += pruefeSchreiben(en).length;
|
|
1451
|
+
});
|
|
1452
|
+
if (schlecht) { return; }
|
|
1453
|
+
|
|
1454
|
+
var body = $('#dry-body');
|
|
1455
|
+
var offen = liste.length;
|
|
1456
|
+
var fehler = [];
|
|
1457
|
+
var fertig = 0;
|
|
1458
|
+
|
|
1459
|
+
$('#btn-dry-write').disabled = true;
|
|
1460
|
+
$('#btn-dry-write').textContent = tr('write.writing');
|
|
1461
|
+
/* Vor dem Schreiben festhalten: danach gibt es das Ziel, und die Frage
|
|
1462
|
+
„war das ein Update?" beantwortete sich sonst immer mit ja. */
|
|
1463
|
+
var warUpdate = !!S.objects[S.entwurf.ziel || S.entwurf.kanal];
|
|
1464
|
+
|
|
1465
|
+
var loeschen = S.loeschListe.filter(function (v) { return v.vorgabe; });
|
|
1466
|
+
offen += loeschen.length;
|
|
1467
|
+
loeschen.forEach(function (v) {
|
|
1468
|
+
socket.emit('delObject', v.id, function (err) {
|
|
1469
|
+
if (err) { fehler.push(v.id + ': ' + err); } else { fertig++; }
|
|
1470
|
+
if (--offen === 0) { abschluss(); }
|
|
1471
|
+
});
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
liste.forEach(function (x) {
|
|
1475
|
+
socket.emit('setObject', x.id, x.obj, function (err) {
|
|
1476
|
+
if (err) { fehler.push(x.id + ': ' + err); } else { fertig++; }
|
|
1477
|
+
if (--offen === 0) { abschluss(); }
|
|
1478
|
+
});
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
function abschluss() {
|
|
1482
|
+
body.textContent = '';
|
|
1483
|
+
var m = el('div');
|
|
1484
|
+
m.style.fontSize = '13px';
|
|
1485
|
+
if (fehler.length) {
|
|
1486
|
+
m.appendChild(el('b', null, tr('write.failed', fehler.length)));
|
|
1487
|
+
var ul = el('ul');
|
|
1488
|
+
fehler.forEach(function (t) { ul.appendChild(el('li', null, t)); });
|
|
1489
|
+
m.appendChild(ul);
|
|
1490
|
+
} else {
|
|
1491
|
+
m.appendChild(el('b', null, loeschen.length
|
|
1492
|
+
? tr('write.doneWithRemoved', fertig, loeschen.length)
|
|
1493
|
+
: tr('write.done', fertig)));
|
|
1494
|
+
m.appendChild(el('div', null, tr('write.aliasNowAt', S.entwurf.ziel || S.entwurf.kanal)));
|
|
1495
|
+
}
|
|
1496
|
+
body.appendChild(m);
|
|
1497
|
+
|
|
1498
|
+
/* Der Dialog bleibt nach dem Schreiben stehen, damit man das Ergebnis
|
|
1499
|
+
liest. Was dann noch dastand, passte aber nicht mehr: die
|
|
1500
|
+
Ueberschrift sprach weiter davon, was entstehen *wuerde*, und der
|
|
1501
|
+
Knopf trug wieder „Jetzt anlegen" - deaktiviert, weil das Schreiben
|
|
1502
|
+
ihn gesperrt hatte und niemand ihn wieder freigab. Ein Knopf, der
|
|
1503
|
+
nichts mehr tun kann, gehoert weg, nicht ausgegraut hin.
|
|
1504
|
+
|
|
1505
|
+
Ging etwas schief, bleibt er stehen und heisst „Noch einmal
|
|
1506
|
+
versuchen" - da hat er wieder eine Aufgabe. */
|
|
1507
|
+
var zt2 = $('#dry-titel');
|
|
1508
|
+
if (fehler.length) {
|
|
1509
|
+
if (zt2) { zt2.textContent = tr('write.dryTitleFailed'); }
|
|
1510
|
+
$('#btn-dry-write').hidden = false;
|
|
1511
|
+
$('#btn-dry-write').disabled = false;
|
|
1512
|
+
$('#btn-dry-write').textContent = tr('write.retry');
|
|
1513
|
+
} else {
|
|
1514
|
+
if (zt2) {
|
|
1515
|
+
zt2.textContent = warUpdate ? tr('write.dryTitleDoneUpdate')
|
|
1516
|
+
: tr('write.dryTitleDoneCreate');
|
|
1517
|
+
}
|
|
1518
|
+
$('#btn-dry-write').hidden = true;
|
|
1519
|
+
}
|
|
1520
|
+
S.loeschListe = [];
|
|
1521
|
+
|
|
1522
|
+
/* Die Marke „geaendert“ hat ihre Aufgabe erfuellt.
|
|
1523
|
+
|
|
1524
|
+
Sie sagt „das hast du angefasst, es steht noch nicht so am Alias“.
|
|
1525
|
+
Nach dem Schreiben steht es so am Alias — die Marke blieb aber
|
|
1526
|
+
stehen, bis man den Reiter neu lud, und behauptete damit eine
|
|
1527
|
+
Abweichung, die es nicht mehr gibt. Nur bei Erfolg loeschen: ging
|
|
1528
|
+
etwas schief, ist die Aenderung tatsaechlich noch nicht drin. */
|
|
1529
|
+
if (!fehler.length && S.entwurf && S.entwurf.states) {
|
|
1530
|
+
S.entwurf.states.forEach(function (s) { s.geaendert = false; rateBehalten(s); });
|
|
1531
|
+
S.entwurf.rateMeldung = null;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
/* Nur der Aliaszweig, nicht die ganze Datenbank. */
|
|
1535
|
+
holeZweig((S.entwurf.ziel || S.entwurf.kanal).split('.').slice(0, 2).join('.') + '.');
|
|
1536
|
+
}
|
|
1537
|
+
}
|