iobroker.alias-workbench 0.9.12 → 0.10.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 +54 -0
- package/admin/css/werkbank.css +8 -0
- package/admin/js/aufzaehlungen.js +24 -8
- package/admin/js/baum.js +13 -2
- package/admin/js/detail.js +302 -87
- package/admin/js/entwurf.js +44 -14
- package/admin/js/enums.js +7 -0
- package/admin/js/ergebnis.js +26 -6
- package/admin/js/erkennung.js +45 -0
- package/admin/js/mqtt.js +128 -67
- package/admin/js/musterwahl.js +103 -26
- package/admin/js/objekte.js +3 -0
- package/admin/js/pruefungen.js +1 -2
- package/admin/js/quellentausch.js +59 -12
- package/admin/js/schreiben.js +22 -18
- package/admin/js/vorlagen.js +28 -5
- package/admin/js/vorlagenblatt.js +49 -7
- package/admin/js/vorschlagen.js +60 -7
- package/admin/js/werte.js +98 -20
- package/admin/js/zuordnung.js +39 -16
- package/admin/js/zustand.js +5 -1
- package/admin/js/zustandsliste.js +185 -25
- package/admin/sprachen/de.json +35 -11
- package/admin/sprachen/en.json +35 -11
- package/admin/vorlagen/hm-rollladen.json +3 -2
- package/admin/vorlagen/messpunkt.json +6 -2
- package/admin/vorlagen/tasmota-lampe.json +6 -2
- package/admin/vorlagen/tasmota-mehrfach.json +6 -2
- package/admin/vorlagen/tasmota-rgb.json +6 -2
- package/admin/vorlagen/tasmota-steckdose.json +6 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -592,6 +592,60 @@ imported, and that both language files carry the same keys.
|
|
|
592
592
|
|
|
593
593
|
## Changelog
|
|
594
594
|
|
|
595
|
+
### 0.10.0 (2026-09-20)
|
|
596
|
+
**Detection and patterns**
|
|
597
|
+
* Picking another pattern re-ticks the rows and leaves them alone while the new pattern does not apply yet.
|
|
598
|
+
* Guessing gives every datapoint one slot only, and a contradicting unit rules a slot out — a start delay in seconds does not become an actual temperature.
|
|
599
|
+
* The pattern field names what is really missing, groups included (“SET / SET_HEATING / SET_COOLING”). The legend says why a pattern does not apply and offers the matching button.
|
|
600
|
+
* The hint “might fit better” only suggests patterns that apply afterwards.
|
|
601
|
+
* For “Information” the field reads “✓ 24 points” instead of “24/31 filled” — that pattern takes any number of points.
|
|
602
|
+
* The message “No remaining datapoint fits” no longer sits above the orange legend and names the rows decided by hand.
|
|
603
|
+
|
|
604
|
+
**Free slots**
|
|
605
|
+
* A slot can be created once, and every slot is listed once.
|
|
606
|
+
* “Suggest free slots” now sits in the blue box next to the pattern; the button for the whole device is called “Load all datapoints of the device” and sits above the list.
|
|
607
|
+
|
|
608
|
+
**The detail row of a point**
|
|
609
|
+
* New: “Name” and “Slot in the pattern” — the slot fills in role, type, unit and value list.
|
|
610
|
+
* “reads from” and “writes to” are wider, the formula is called “read formula” and sits below.
|
|
611
|
+
* If the slot already belongs to another row, the workbench says so instead of blaming the type. A pure measured value no longer gets a write target.
|
|
612
|
+
|
|
613
|
+
**Templates**
|
|
614
|
+
* “Apply template values” no longer deletes a value list.
|
|
615
|
+
* A template without a name can be imported and then named.
|
|
616
|
+
* A skipped row gives the reason: point missing, no JSON, or field missing.
|
|
617
|
+
* “Update template” leaves the detection as it was; an exported template can be imported again.
|
|
618
|
+
|
|
619
|
+
**Source swap**
|
|
620
|
+
* If one point fails, the note on the alias stays on the old source — the swap can be finished afterwards.
|
|
621
|
+
* The greyed-out button gives its own reason.
|
|
622
|
+
* The hint “one level up” only appears where it helps.
|
|
623
|
+
|
|
624
|
+
**Deleting and moving**
|
|
625
|
+
* The delete dialog only warns about channels that really lie below the alias.
|
|
626
|
+
* After a move the source knows its alias again right away; “to the source →” no longer jumps somewhere vague.
|
|
627
|
+
|
|
628
|
+
**Room and function**
|
|
629
|
+
* A room is found in the middle of a device name too (“Heizung_Badezimmer INT0000001”).
|
|
630
|
+
* Picking with the keyboard works like the mouse, and the offer “file it under … as well?” no longer opens a row of its own.
|
|
631
|
+
* If rooms and functions cannot be loaded, the fields say so themselves.
|
|
632
|
+
|
|
633
|
+
**Tasmota and MQTT**
|
|
634
|
+
* The IP is found on older firmware too (`tele.INFO2` in both forms).
|
|
635
|
+
* The card no longer claims a switched-off point sends; “allow sending” switches the link back on as well.
|
|
636
|
+
* Creating several send points at once yields the same objects as creating them one by one — `POWER`, `Fade` and `LedTable` as `mixed`, so the write formula takes effect.
|
|
637
|
+
* A HomeMatic actuator without a template no longer drags maintenance points along.
|
|
638
|
+
* “Ask” for SetOption59 only counts what arrived after sending; if no answer comes, “No answer …” is shown and the button stays for a re-check.
|
|
639
|
+
* The “MQTT device” card opens again on devices without a row from `tele/…`.
|
|
640
|
+
|
|
641
|
+
**Display**
|
|
642
|
+
* The number on a tree node counts what lies below it.
|
|
643
|
+
* A node without an object of its own is marked “VIR”, the device card sorts like the tree.
|
|
644
|
+
* A button reads along and stays a button; the misleading “never changes” badge is gone.
|
|
645
|
+
* JSON fields with a dot in their name can be selected.
|
|
646
|
+
|
|
647
|
+
All changes in detail, with cause and measurement: [docs/aenderungen-ausfuehrlich.md](docs/aenderungen-ausfuehrlich.md).
|
|
648
|
+
|
|
595
649
|
### 0.9.12 (2026-09-18)
|
|
596
650
|
* The small **type badges in the tree** (DEV, CHA, FOL …) now all have the same width. A node without an object of its own shows just a dot there, and its badge was narrower — its name fell out of line.
|
|
597
651
|
* **“Move …” did nothing on some aliases.** Creating alias points by hand in the admin often yields an alias without its own channel object — the node shows in the tree but is not an object itself. The workbench displayed the “Move …” button there, but clicking it opened no dialog and said nothing. Such an alias is now moved exactly like one with a channel; a missing channel is not created along the way.
|
package/admin/css/werkbank.css
CHANGED
|
@@ -959,6 +959,14 @@
|
|
|
959
959
|
margin:0;
|
|
960
960
|
}
|
|
961
961
|
.uebernahme .zq { color:var(--ink-2); font-size:11.5px; }
|
|
962
|
+
/* Das Raum-Angebot steht unter dem Raumfeld, in der Zeile der Marken
|
|
963
|
+
„vorgeschlagen“ — nicht in einer eigenen. Mit voller Breite fand es
|
|
964
|
+
dort keinen Platz, sobald die Funktion ihre Marke trug, und oeffnete
|
|
965
|
+
eine dritte Zeile (Ricardo, 19.09.2026: „nimmt zu viel Platz weg“).
|
|
966
|
+
Platz ist genau dann: das Angebot kommt nur nach einer Raumwahl von
|
|
967
|
+
Hand, und dann traegt der Raum keine Marke. Die Spalte der
|
|
968
|
+
Funktionsbeschriftung darunter ist frei und darf mitgenutzt werden. */
|
|
969
|
+
.zielbar .uebernahme.raumangebot { grid-row:2; grid-column:2 / 4; margin-top:-2px; }
|
|
962
970
|
.btn.mini { padding:2px 9px; font-size:11px; line-height:1.6; }
|
|
963
971
|
.btn.mini.wichtig {
|
|
964
972
|
background:var(--accent); border-color:var(--accent); color:#fff; font-weight:600;
|
|
@@ -238,15 +238,31 @@ export function raumVorschlag(e) {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
/* 4. Aus dem Geraetenamen: „HK_Bastelzimmer" -> Bastelzimmer.
|
|
242
|
-
Von hinten, weil der Raum am Ende steht, und stueckweise, weil
|
|
243
|
-
„Max_Spielzimmer" aus zwei Teilen besteht. */
|
|
241
|
+
/* 4. Aus dem Geraetenamen: „HK_Bastelzimmer" -> Bastelzimmer. */
|
|
244
242
|
var name = zusatzName(e.kanal) || (quelle ? zusatzName(quelle) : '') || '';
|
|
245
|
-
if (name) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
243
|
+
if (name) { return raumAusNamen(name, suche); }
|
|
244
|
+
return '';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* Einen Raum im Geraetenamen finden.
|
|
248
|
+
|
|
249
|
+
Probiert wird jedes zusammenhaengende Wortstueck: laengere vor
|
|
250
|
+
kuerzeren, damit „Jonas_Schlafzimmer" vor „Schlafzimmer" gewinnt, und
|
|
251
|
+
bei gleicher Laenge das weiter hinten stehende zuerst.
|
|
252
|
+
|
|
253
|
+
Bis 19.09.2026 kamen nur Stuecke in Frage, die bis zum Ende reichen.
|
|
254
|
+
An „Heizung_Badezimmer INT0000001" hing dann immer der Anhang mit dran,
|
|
255
|
+
„Badezimmer" allein wurde nie probiert — vier Heizgruppen am
|
|
256
|
+
Testsystem und alle sieben am Produktivsystem blieben ohne Raum (Lauf
|
|
257
|
+
vom 19.09.2026, N5). Ein Raum mitten im Namen ist die Regel, nicht die
|
|
258
|
+
Ausnahme („Kueche_Licht", „Wohnzimmer_Decke_Links"); und es bleibt ein
|
|
259
|
+
Vorschlag, sichtbar markiert und vor dem Schreiben zu aendern (Ricardo,
|
|
260
|
+
19.09.2026). */
|
|
261
|
+
export function raumAusNamen(name, suche) {
|
|
262
|
+
var nt = String(name).split(/[_ .]+/).filter(function (x) { return x; });
|
|
263
|
+
for (var laenge = nt.length; laenge >= 1; laenge--) {
|
|
264
|
+
for (var start = nt.length - laenge; start >= 0; start--) {
|
|
265
|
+
var nr = suche(nt.slice(start, start + laenge).join('_'));
|
|
250
266
|
if (nr) { return nr; }
|
|
251
267
|
}
|
|
252
268
|
}
|
package/admin/js/baum.js
CHANGED
|
@@ -127,7 +127,14 @@ function baueBaum() {
|
|
|
127
127
|
|
|
128
128
|
function zaehleZustaende(k, eltern) {
|
|
129
129
|
k._eltern = eltern || null;
|
|
130
|
-
|
|
130
|
+
/* Ein `state` mit eigenen Kindern ist ein Behaelter und zaehlt sich
|
|
131
|
+
selbst nicht mit — die Zahl am Knoten nennt, was DARUNTER liegt, wie
|
|
132
|
+
rechts die Liste. Vorher stand an `0_userdata.0.Bastelzimmer` eine 7,
|
|
133
|
+
waehrend rechts „6 Zustaende" und sechs Zeilen standen (Ricardo,
|
|
134
|
+
20.09.2026; Lauf vom 19.09.2026, B1). Ein Blatt zaehlt weiter sich
|
|
135
|
+
selbst, sonst zaehlte kein Elternknoten mehr etwas. */
|
|
136
|
+
var hatKinder = Object.keys(k.kinder).length > 0;
|
|
137
|
+
var n = (k.art === 'state' && !hatKinder) ? 1 : 0;
|
|
131
138
|
var tiefe = -1;
|
|
132
139
|
Object.keys(k.kinder).forEach(function (name) {
|
|
133
140
|
var kind = k.kinder[name];
|
|
@@ -399,7 +406,11 @@ function renderKinder(knoten, filter, tiefe) {
|
|
|
399
406
|
});
|
|
400
407
|
}
|
|
401
408
|
d.appendChild(tw);
|
|
402
|
-
|
|
409
|
+
/* Ohne eigenes Objekt: „VIR“ statt eines Punkts. ioBroker selbst nennt
|
|
410
|
+
so eine Stufe `type: "virtual"` (web-Adapter, `/object` mit `depth`).
|
|
411
|
+
Gleich gestaltet wie DEV/CHA/FOL — die Reihe sieht einheitlich aus,
|
|
412
|
+
und wer es wissen will, liest es trotzdem ab (Ricardo, 19.09.2026). */
|
|
413
|
+
var kk = el('span', 'kind', k.art ? k.art.slice(0, 3) : 'vir');
|
|
403
414
|
if (!k.art) { kk.title = tr('tree.noObjectHint'); }
|
|
404
415
|
d.appendChild(kk);
|
|
405
416
|
d.appendChild(el('span', 'idstueck', k.name));
|
package/admin/js/detail.js
CHANGED
|
@@ -4,8 +4,8 @@ import { S } from './zustand.js';
|
|
|
4
4
|
import { socket } from './verbindung.js';
|
|
5
5
|
import { el } from './basis.js';
|
|
6
6
|
import { tr } from './sprache.js';
|
|
7
|
-
import { wertVon, jsonFelder, feldAusFormel, feldFormel, feldWert, jsonVon, zielKann } from './werte.js';
|
|
8
|
-
import { musterVon, erkenneEntwurf, platzFuerRolle, typenFuerRolle } from './erkennung.js';
|
|
7
|
+
import { wertVon, jsonFelder, feldAusFormel, feldFormel, feldWert, jsonVon, zielKann, knopfZeile } from './werte.js';
|
|
8
|
+
import { musterVon, erkenneEntwurf, platzFuerRolle, typenFuerRolle, rolleVonPlatz } from './erkennung.js';
|
|
9
9
|
import { musterName } from './musternamen.js';
|
|
10
10
|
import { rollenFeld } from './rollenwahl.js';
|
|
11
11
|
import { opt , quellenAuswahl, vorlagenAbweichung, bestandsAbweichung, aboErgaenzen } from './entwurf.js';
|
|
@@ -106,9 +106,7 @@ export function detailZeile(e, s, idx) {
|
|
|
106
106
|
if (!o || !o.common) { return null; }
|
|
107
107
|
var c = o.common;
|
|
108
108
|
|
|
109
|
-
var maengel = zielKann(s)
|
|
110
|
-
return seite === 'w' ? x.art !== 'meldetNichts' : x.art === 'meldetNichts';
|
|
111
|
-
});
|
|
109
|
+
var maengel = seite === 'w' ? zielKann(s) : [];
|
|
112
110
|
|
|
113
111
|
var text, hinweis;
|
|
114
112
|
if (seite === 'w') {
|
|
@@ -120,9 +118,13 @@ export function detailZeile(e, s, idx) {
|
|
|
120
118
|
: (bl ? tr('detail.targetTypeVoid', c.type) : tr('detail.targetTypeHint', c.type));
|
|
121
119
|
if (nw) { text = c.type + ' \u00b7 ' + tr('detail.notWritable'); }
|
|
122
120
|
} else {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
/* Nur noch der Knopf, und ruhig: kein Mangel, eine Auskunft -
|
|
122
|
+
was `read: false` am Alias bewirkt und warum die Lesequelle
|
|
123
|
+
trotzdem da ist. Die Marke „aendert sich nie" an jeder Quelle
|
|
124
|
+
mit `read: false` ist seit 19.09.2026 weg (siehe `zielKann`). */
|
|
125
|
+
if (!knopfZeile(s)) { return null; }
|
|
126
|
+
text = tr('detail.readIsButton');
|
|
127
|
+
hinweis = tr('detail.readIsButtonHint');
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
var m = el('span', 'chip ' + (maengel.length ? 'warn' : 'mut'), text);
|
|
@@ -249,6 +251,121 @@ export function detailZeile(e, s, idx) {
|
|
|
249
251
|
zeigeBestand(r, felder);
|
|
250
252
|
}
|
|
251
253
|
|
|
254
|
+
/* Welcher Platz ist schon vergeben, und von welcher Zeile? Das steht
|
|
255
|
+
als Vermerk am Gruppenkopf der Rollenliste. */
|
|
256
|
+
var belegtVon = {};
|
|
257
|
+
/* Eigener Platz direkt aus dem Befund — ueber `belegtVon` allein fand
|
|
258
|
+
ihn nur die LETZTE Zeile eines mehrfach vergebenen Platzes (ACTUAL
|
|
259
|
+
bei `info`), alle anderen standen auf „kein Platz“. */
|
|
260
|
+
var eigenerPlatz = null;
|
|
261
|
+
if (e.want) {
|
|
262
|
+
var fundR = erkenneEntwurf(e, e.want);
|
|
263
|
+
if (fundR.length) {
|
|
264
|
+
fundR[0].states.forEach(function (x) {
|
|
265
|
+
if (!x.id) { return; }
|
|
266
|
+
var nx = x.id.slice(e.kanal.length + 1);
|
|
267
|
+
belegtVon[x.name] = nx;
|
|
268
|
+
if (nx === s.n) { eigenerPlatz = x.name; }
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/* --- Platz im Muster ---
|
|
273
|
+
|
|
274
|
+
Umgekehrt zur Rolle: erst den Platz waehlen, und die Werkbank traegt
|
|
275
|
+
ein, was das Muster dafuer vorgibt — Rolle, Typ, Einheit, Werteliste.
|
|
276
|
+
Wer eine Zeile auf SET setzen wollte, musste bisher wissen, dass SET
|
|
277
|
+
`switch` heisst (Ricardo, 19.09.2026). Datenpunkt und Name bleiben;
|
|
278
|
+
dieselbe Vorgabe nimmt der Klick auf einen freien Platz unten, nur
|
|
279
|
+
dass der eine neue Zeile anlegt. */
|
|
280
|
+
var platzBox = null;
|
|
281
|
+
var mPl = e.want && musterVon(e.want);
|
|
282
|
+
if (mPl) {
|
|
283
|
+
var meinPlatz = eigenerPlatz;
|
|
284
|
+
var pb = el('div');
|
|
285
|
+
var selP = el('select', 'tx');
|
|
286
|
+
selP.style.width = '240px';
|
|
287
|
+
var oKein = opt('', tr('detail.slotNone'));
|
|
288
|
+
if (meinPlatz) { oKein.disabled = true; }
|
|
289
|
+
selP.appendChild(oKein);
|
|
290
|
+
var gesehen = {};
|
|
291
|
+
mPl.states.forEach(function (st) {
|
|
292
|
+
if (gesehen[st.name]) { return; }
|
|
293
|
+
gesehen[st.name] = 1;
|
|
294
|
+
var fremd = belegtVon[st.name] && belegtVon[st.name] !== s.n && !st.multiple;
|
|
295
|
+
var txt = st.name + (st.required ? ' · ' + tr('detail.slotRequired') : '') +
|
|
296
|
+
(fremd ? ' · ' + tr('roles.slotTaken', belegtVon[st.name]) : '');
|
|
297
|
+
var o = opt(st.name, txt);
|
|
298
|
+
if (fremd) { o.disabled = true; }
|
|
299
|
+
selP.appendChild(o);
|
|
300
|
+
});
|
|
301
|
+
/* Gewaehlt, aber nicht bekommen (etwa weil die Schreibrichtung nicht
|
|
302
|
+
passt): die Wahl bleibt im Feld stehen, darunter steht warum. */
|
|
303
|
+
var gewuenscht = !meinPlatz && s.platzWahl && gesehen[s.platzWahl] ? s.platzWahl : '';
|
|
304
|
+
selP.value = meinPlatz || gewuenscht;
|
|
305
|
+
selP.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
306
|
+
selP.addEventListener('change', function () {
|
|
307
|
+
var st = null;
|
|
308
|
+
mPl.states.forEach(function (x) { if (!st && x.name === selP.value) { st = x; } });
|
|
309
|
+
if (!st) { return; }
|
|
310
|
+
s.platzWahl = st.name;
|
|
311
|
+
s.role = rolleVonPlatz(st);
|
|
312
|
+
var ty = Array.isArray(st.type) ? st.type[0] : st.type;
|
|
313
|
+
if (ty) { s.typ = ty; }
|
|
314
|
+
if (st.defaultUnit) { s.unit = st.defaultUnit; }
|
|
315
|
+
/* Ohne Werteliste bleibt so ein Platz leer, egal wie gut Rolle und
|
|
316
|
+
Typ passen — wie beim Klick auf einen freien Platz. */
|
|
317
|
+
if (st.statesDefined && !s.states) { s.states = st.defaultStates || { 0: 'None' }; }
|
|
318
|
+
/* Ein Platz, den das Muster nur lesen laesst (ELECTRIC_POWER,
|
|
319
|
+
ACTUAL …), bekommt kein Schreibziel. mqtt-client legt jeden Punkt
|
|
320
|
+
beschreibbar an, und die Werkbank trug deshalb die Quelle auch
|
|
321
|
+
bei „schreibt auf" ein — am 3D-Drucker schrieb die Leistung auf
|
|
322
|
+
`tele.SENSOR` (Ricardo, 19.09.2026). Weggenommen wird nur, nie
|
|
323
|
+
geraten: ein fehlendes Schreibziel bei SET setzt das Feld nicht. */
|
|
324
|
+
if (st.write === false) { s.srcW = ''; s.wr = false; }
|
|
325
|
+
neu();
|
|
326
|
+
});
|
|
327
|
+
pb.appendChild(selP);
|
|
328
|
+
/* Neben dem Namen ist kein Platz fuer einen Satz darunter — er steht
|
|
329
|
+
als Tooltip am Feld. */
|
|
330
|
+
selP.title = tr('detail.slotHint');
|
|
331
|
+
/* Gewaehlt, aber nicht bekommen — warum?
|
|
332
|
+
|
|
333
|
+
Die Schreibrichtung ist es fast nie: traegt ein Punkt genau die
|
|
334
|
+
Vorgaberolle des Platzes, prueft der type-detector `read`/`write`
|
|
335
|
+
gar nicht (ChannelDetector: „When the default role is assigned …
|
|
336
|
+
we can be a bit more laxe"). Und das Feld setzt immer die
|
|
337
|
+
Vorgaberolle. Der erste Hinweis an dieser Stelle behauptete
|
|
338
|
+
deshalb etwas Falsches (Ricardo, 19.09.2026: an tele.SENSOR mit
|
|
339
|
+
Schreibziel blieb ELECTRIC_POWER vergeben, ohne Hinweis — zu
|
|
340
|
+
Recht). Der haeufige Grund ist ein anderer: das Muster greift als
|
|
341
|
+
Ganzes nicht, weil ein Pflichtplatz fehlt — dann vergibt der
|
|
342
|
+
Detektor gar keinen Platz, auch diesen nicht. */
|
|
343
|
+
/* Greift das Muster als Ganzes nicht, steht das in der Legende ueber
|
|
344
|
+
der Liste — hier waere es an der falschen Zeile. */
|
|
345
|
+
if (gewuenscht && Object.keys(belegtVon).length > 0) {
|
|
346
|
+
pb.appendChild(el('div', 'aside w', tr('detail.slotNotGiven', gewuenscht)));
|
|
347
|
+
}
|
|
348
|
+
platzBox = pb;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* Der Platz steht rechts neben dem Namen, mit seiner Bezeichnung links
|
|
352
|
+
daneben wie eine Zeilenbeschriftung: man waehlt ihn am Anfang, und
|
|
353
|
+
weiter unten musste man ihn suchen (Ricardo, 19.09.2026). */
|
|
354
|
+
function mitPlatz(inhalt) {
|
|
355
|
+
if (!platzBox) { return inhalt; }
|
|
356
|
+
var w = el('div', 'fields');
|
|
357
|
+
w.style.alignItems = 'flex-start';
|
|
358
|
+
w.appendChild(inhalt);
|
|
359
|
+
var pf = el('div');
|
|
360
|
+
pf.style.display = 'flex';
|
|
361
|
+
pf.style.gap = '8px';
|
|
362
|
+
pf.style.alignItems = 'flex-start';
|
|
363
|
+
pf.appendChild(el('div', 'k', tr('detail.slot')));
|
|
364
|
+
pf.appendChild(platzBox);
|
|
365
|
+
w.appendChild(pf);
|
|
366
|
+
return w;
|
|
367
|
+
}
|
|
368
|
+
|
|
252
369
|
/* --- Name, nur bei selbst angelegten --- */
|
|
253
370
|
if (s.manuell) {
|
|
254
371
|
var iN = el('input', 'tx w');
|
|
@@ -261,24 +378,76 @@ export function detailZeile(e, s, idx) {
|
|
|
261
378
|
s.n = iN.value.trim().toUpperCase().replace(/\s+/g, '_');
|
|
262
379
|
neu();
|
|
263
380
|
});
|
|
264
|
-
zeile(tr('detail.name'), iN);
|
|
381
|
+
zeile(tr('detail.name'), mitPlatz(iN));
|
|
265
382
|
} else if (s.urId) {
|
|
266
|
-
zeile(tr('detail.objectId'), s.urId);
|
|
383
|
+
zeile(tr('detail.objectId'), mitPlatz(el('div', null, s.urId)));
|
|
267
384
|
} else {
|
|
268
|
-
|
|
385
|
+
/* Der letzte Teil ist ein Feld: der Name bestimmt nur die Kennung,
|
|
386
|
+
nicht was der Punkt tut — das macht die Rolle. Wer `cmnd_POWER`
|
|
387
|
+
lieber `POWER` oder `SET` nennen will, konnte das bisher nirgends
|
|
388
|
+
(Ricardo, 19.09.2026). Nur hier, beim Neuanlegen: an einem
|
|
389
|
+
bestehenden Punkt (`urId`, oben) haengen Skripte und vis am Namen.
|
|
390
|
+
|
|
391
|
+
Bereinigt wie die Zielkennung (zielleiste.js), aber ohne Punkt —
|
|
392
|
+
ein Punkt machte aus dem Namen einen Unterordner. Gross- und
|
|
393
|
+
Kleinschreibung bleiben, wie sie getippt wurden. */
|
|
394
|
+
/* Aufgebaut wie „liest aus": oben das Feld, darunter klein die
|
|
395
|
+
volle Kennung, die daraus entsteht (Ricardo, 19.09.2026). */
|
|
396
|
+
var vorspann = (e.ziel || e.kanal) + '.';
|
|
397
|
+
var iNa = el('input', 'tx');
|
|
398
|
+
iNa.type = 'text';
|
|
399
|
+
iNa.value = s.n;
|
|
400
|
+
iNa.style.width = '260px';
|
|
401
|
+
iNa.style.maxWidth = '100%';
|
|
402
|
+
iNa.style.fontFamily = 'var(--mono)';
|
|
403
|
+
var nameFelder = el('div', 'fields');
|
|
404
|
+
nameFelder.appendChild(iNa);
|
|
405
|
+
var nameStapel = mitVollId(nameFelder, vorspann + s.n);
|
|
406
|
+
var nameVoll = nameStapel.querySelector('.vollid');
|
|
407
|
+
var bereinigt = function (v) {
|
|
408
|
+
return v.trim().replace(/\s+/g, '_').replace(/[^\w\-äöüÄÖÜß]/g, '_');
|
|
409
|
+
};
|
|
410
|
+
iNa.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
411
|
+
/* Beim Tippen zieht die Kennung darunter mit — man sieht, was
|
|
412
|
+
entsteht, bevor man das Feld verlaesst. */
|
|
413
|
+
iNa.addEventListener('input', function () {
|
|
414
|
+
var v = bereinigt(iNa.value) || s.n;
|
|
415
|
+
nameVoll.textContent = vorspann + v;
|
|
416
|
+
nameVoll.title = vorspann + v;
|
|
417
|
+
});
|
|
418
|
+
iNa.addEventListener('change', function () {
|
|
419
|
+
var neuN = bereinigt(iNa.value);
|
|
420
|
+
if (!neuN) { iNa.value = s.n; return; }
|
|
421
|
+
if (neuN !== s.n && e.states.some(function (x) { return x !== s && x.n === neuN; })) {
|
|
422
|
+
iNa.value = s.n;
|
|
423
|
+
iNa.title = tr('detail.nameTaken', neuN);
|
|
424
|
+
iNa.classList.add('miss');
|
|
425
|
+
nameVoll.textContent = vorspann + s.n;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
s.n = neuN;
|
|
429
|
+
neu();
|
|
430
|
+
});
|
|
431
|
+
zeile(tr('detail.name'), mitPlatz(nameStapel));
|
|
269
432
|
}
|
|
270
433
|
|
|
271
434
|
/* --- Quelle lesen + JSON-Feld --- */
|
|
272
435
|
var qf = el('div', 'fields');
|
|
273
436
|
|
|
274
437
|
var selR = el('select', 'tx');
|
|
275
|
-
|
|
438
|
+
/* 420 statt 260 px: gekuerzt wird hinten, und dort steht, was den
|
|
439
|
+
Punkt unterscheidet — `…3D_Drucker.` ohne `cmnd.POWER` sagte nichts
|
|
440
|
+
(Ricardo, 19.09.2026). `maxWidth` haelt es in schmalen Fenstern. */
|
|
441
|
+
selR.style.width = '420px';
|
|
442
|
+
selR.style.maxWidth = '100%';
|
|
276
443
|
selR.appendChild(opt('', quellen.length ? tr('detail.pickOne') : tr('detail.noSource')));
|
|
277
444
|
quellenIn(selR, quellen);
|
|
278
445
|
if (s.srcR && quellen.indexOf(s.srcR) === -1) { selR.appendChild(opt(s.srcR, s.srcR + ' ' + tr('detail.elsewhere'))); }
|
|
279
446
|
selR.appendChild(opt('__frei__', tr('detail.otherObject')));
|
|
280
447
|
selR.value = s.srcR || '';
|
|
281
|
-
|
|
448
|
+
/* Ein Knopf darf ohne Lesequelle sein - dann ist das leere Feld kein
|
|
449
|
+
Fehler und wird nicht rot (Ricardo, 19.09.2026). */
|
|
450
|
+
if (!s.srcR && !knopfZeile(s)) { selR.className += ' miss'; }
|
|
282
451
|
selR.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
283
452
|
selR.addEventListener('change', function () {
|
|
284
453
|
if (selR.value === '__frei__') { s.frei = true; s.srcR = ''; }
|
|
@@ -299,7 +468,6 @@ export function detailZeile(e, s, idx) {
|
|
|
299
468
|
neu();
|
|
300
469
|
});
|
|
301
470
|
var lR = el('label', 'fld');
|
|
302
|
-
lR.appendChild(el('span', null, tr('detail.datapoint')));
|
|
303
471
|
lR.appendChild(selR);
|
|
304
472
|
qf.appendChild(lR);
|
|
305
473
|
|
|
@@ -344,10 +512,31 @@ export function detailZeile(e, s, idx) {
|
|
|
344
512
|
zeile('Objekt-ID', iFrei);
|
|
345
513
|
}
|
|
346
514
|
|
|
515
|
+
/* --- Leseformel ---
|
|
516
|
+
|
|
517
|
+
Direkt unter „liest aus“, wie die Schreibformel unter „schreibt auf“:
|
|
518
|
+
jede Formel steht bei der Quelle, fuer die sie rechnet (Ricardo,
|
|
519
|
+
19.09.2026). */
|
|
520
|
+
var fb = el('div');
|
|
521
|
+
var iF = el('input', 'tx w');
|
|
522
|
+
iF.type = 'text';
|
|
523
|
+
iF.value = s.f || '';
|
|
524
|
+
iF.placeholder = tr('detail.formulaPlaceholder');
|
|
525
|
+
iF.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
526
|
+
iF.addEventListener('change', function () { s.f = iF.value.trim(); neu(); });
|
|
527
|
+
fb.appendChild(iF);
|
|
528
|
+
/* Der Hinweis gilt nur fuer Schaltpunkte. Bei einem Spannungswert
|
|
529
|
+
stand er auch — und ergab dort keinen Sinn. */
|
|
530
|
+
if ((s.typ || '') === 'boolean') {
|
|
531
|
+
fb.appendChild(el('div', 'sugg', tr('detail.formulaHint')));
|
|
532
|
+
}
|
|
533
|
+
zeigeBeide(zeile(tr('detail.formula'), fb), ['f']);
|
|
534
|
+
|
|
347
535
|
/* --- Quelle schreiben --- */
|
|
348
536
|
var wf = el('div', 'fields');
|
|
349
537
|
var selW = el('select', 'tx');
|
|
350
|
-
selW.style.width = '
|
|
538
|
+
selW.style.width = '420px';
|
|
539
|
+
selW.style.maxWidth = '100%';
|
|
351
540
|
/* Leer heisst „schreibt nicht" - und muss auch so heissen. Frueher
|
|
352
541
|
stand hier „dieselbe wie lesen", also das Gegenteil dessen, was
|
|
353
542
|
passierte (Ricardo, 25.08.2026). Wer wirklich auf die Lesequelle
|
|
@@ -361,34 +550,40 @@ export function detailZeile(e, s, idx) {
|
|
|
361
550
|
selW.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
362
551
|
selW.addEventListener('change', function () {
|
|
363
552
|
s.srcW = (selW.value === '=lesen') ? (s.srcR || '') : selW.value;
|
|
553
|
+
/* Wer hier waehlt, hat die Schreibrichtung entschieden. `wr` stammt
|
|
554
|
+
sonst vom Quellobjekt (`common.write`) und hielt die Zeile fuer den
|
|
555
|
+
Detektor beschreibbar, auch nach „nicht schreiben" — angelegt wird
|
|
556
|
+
aber nach `srcW` (`write: !!s.srcW`). Die Vorschau sagte also
|
|
557
|
+
„Platz bleibt frei", der fertige Alias haette ihn bekommen
|
|
558
|
+
(Ricardo, 19.09.2026, an 3D_Drucker tele.SENSOR). */
|
|
559
|
+
s.wr = !!s.srcW;
|
|
364
560
|
neu();
|
|
365
561
|
});
|
|
366
562
|
var lW = el('label', 'fld');
|
|
367
|
-
lW.appendChild(el('span', null, tr('detail.datapoint')));
|
|
368
563
|
lW.appendChild(selW);
|
|
369
564
|
wf.appendChild(lW);
|
|
370
565
|
/* Der Hinweis gilt nur, wenn die beiden Quellen wirklich
|
|
371
566
|
auseinandergehen - bei gleicher Quelle ist nichts getrennt. */
|
|
372
|
-
|
|
373
|
-
|
|
567
|
+
/* Unter die Kennung, nicht daneben: seit die Auswahl 420 px breit ist,
|
|
568
|
+
brach der Satz um und schob sich mit einer Luecke zwischen Auswahl und
|
|
569
|
+
Kennung (Ricardo, 19.09.2026). */
|
|
570
|
+
var wStapel = mitVollId(wf, s.srcW, zielMarke(s, 'w'));
|
|
571
|
+
/* Nur-lesen-Platz mit Schreibziel: der Detektor sieht es bei der
|
|
572
|
+
Vorgaberolle nicht so streng, gemeint ist es aber so — ein Messwert,
|
|
573
|
+
den jemand setzt, geht ans Geraet. */
|
|
574
|
+
var platzDef = null;
|
|
575
|
+
if (mPl && eigenerPlatz) {
|
|
576
|
+
mPl.states.forEach(function (x) { if (!platzDef && x.name === eigenerPlatz) { platzDef = x; } });
|
|
374
577
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
/* --- Leseformel --- */
|
|
378
|
-
var fb = el('div');
|
|
379
|
-
var iF = el('input', 'tx w');
|
|
380
|
-
iF.type = 'text';
|
|
381
|
-
iF.value = s.f || '';
|
|
382
|
-
iF.placeholder = tr('detail.formulaPlaceholder');
|
|
383
|
-
iF.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
384
|
-
iF.addEventListener('change', function () { s.f = iF.value.trim(); neu(); });
|
|
385
|
-
fb.appendChild(iF);
|
|
386
|
-
/* Der Hinweis gilt nur fuer Schaltpunkte. Bei einem Spannungswert
|
|
387
|
-
stand er auch — und ergab dort keinen Sinn. */
|
|
388
|
-
if ((s.typ || '') === 'boolean') {
|
|
389
|
-
fb.appendChild(el('div', 'sugg', tr('detail.formulaHint')));
|
|
578
|
+
if (platzDef && platzDef.write === false && s.srcW) {
|
|
579
|
+
wStapel.appendChild(el('div', 'aside w', tr('detail.readOnlySlotWrites', platzDef.name)));
|
|
390
580
|
}
|
|
391
|
-
|
|
581
|
+
/* Nur, wo wirklich woanders gelesen wird - ohne Lesequelle liest die
|
|
582
|
+
Zeile nirgends, und der Satz stimmte nicht. */
|
|
583
|
+
if (s.srcR && s.srcW && s.srcW !== s.srcR) {
|
|
584
|
+
wStapel.appendChild(el('div', 'sugg', tr('detail.separateHint')));
|
|
585
|
+
}
|
|
586
|
+
zeigeBeide(zeile(tr('detail.writesTo'), wStapel), ['srcW']);
|
|
392
587
|
|
|
393
588
|
if (s.srcW) {
|
|
394
589
|
var wb = el('div');
|
|
@@ -405,17 +600,7 @@ export function detailZeile(e, s, idx) {
|
|
|
405
600
|
|
|
406
601
|
/* --- Rolle, mit Begründung --- */
|
|
407
602
|
var rb = el('div');
|
|
408
|
-
|
|
409
|
-
als Vermerk am Gruppenkopf der Rollenliste. */
|
|
410
|
-
var belegtVon = {};
|
|
411
|
-
if (e.want) {
|
|
412
|
-
var fundR = erkenneEntwurf(e, e.want);
|
|
413
|
-
if (fundR.length) {
|
|
414
|
-
fundR[0].states.forEach(function (x) {
|
|
415
|
-
if (x.id) { belegtVon[x.name] = x.id.slice(e.kanal.length + 1); }
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
}
|
|
603
|
+
|
|
419
604
|
rb.appendChild(rollenFeld({
|
|
420
605
|
wert: s.role,
|
|
421
606
|
muster: e.want,
|
|
@@ -428,6 +613,9 @@ export function detailZeile(e, s, idx) {
|
|
|
428
613
|
die Rolle erneut wechselt. */
|
|
429
614
|
beiWahl: function (r) {
|
|
430
615
|
s.role = r;
|
|
616
|
+
/* Eine Rolle von Hand ersetzt eine fruehere Platzwahl — sonst
|
|
617
|
+
stuende ein Hinweis zu einem Platz da, den keiner mehr will. */
|
|
618
|
+
delete s.platzWahl;
|
|
431
619
|
/* Nur wo die Rolle im Muster genau einen Datentyp zulaesst, gibt
|
|
432
620
|
es etwas vorzugeben. Trifft sie mehrere Plaetze mit
|
|
433
621
|
verschiedenen Typen - wie die Fahrtrichtung boolean und number -
|
|
@@ -443,22 +631,41 @@ export function detailZeile(e, s, idx) {
|
|
|
443
631
|
var treffer = platzFuerRolle(e.want, s.role, s.typ);
|
|
444
632
|
var erlaubt = typenFuerRolle(e.want, s.role);
|
|
445
633
|
var hin = el('div', 'sugg');
|
|
634
|
+
/* Hat den Platz schon eine ANDERE Zeile, hilft weder der Typ noch die
|
|
635
|
+
Rolle — es gibt ihn nur einmal. Am KeinJson-Testgeraet hielt
|
|
636
|
+
`cmnd_POWER` SET, und unter `stat_POWER` (Rolle `switch` von
|
|
637
|
+
mqtt-client, Typ string) stand „SET verlangt boolean … so faellt der
|
|
638
|
+
Punkt aus dem Muster" — als muesste man nur den Typ stellen. Richtig
|
|
639
|
+
waere ACTUAL gewesen (Ricardo, 19.09.2026). */
|
|
640
|
+
var besetztVon = treffer && !treffer.multiple && belegtVon[treffer.name] &&
|
|
641
|
+
belegtVon[treffer.name] !== s.n ? belegtVon[treffer.name] : null;
|
|
446
642
|
if (treffer) {
|
|
447
643
|
hin.appendChild(document.createTextNode(tr('pattern.fitsOn')));
|
|
448
644
|
hin.appendChild(el('b', null, String(treffer.role)));
|
|
449
645
|
hin.appendChild(document.createTextNode(tr('pattern.toSlot', treffer.name, musterName(e.want) || e.want)));
|
|
450
|
-
if (erlaubt.length) {
|
|
646
|
+
if (!besetztVon && erlaubt.length) {
|
|
451
647
|
hin.appendChild(document.createTextNode(
|
|
452
648
|
tr('pattern.expectsType', erlaubt.join(tr('pattern.typeOr')))));
|
|
453
649
|
}
|
|
454
|
-
} else {
|
|
650
|
+
} else if (!eigenerPlatz) {
|
|
455
651
|
hin.textContent = tr('pattern.noPlaceLong', musterName(e.want) || e.want);
|
|
456
652
|
}
|
|
457
|
-
|
|
653
|
+
/* Findet die Rollenliste nichts, hat die Zeile aber doch einen Platz,
|
|
654
|
+
steht hier nichts: das sagt schon „Platz im Muster“ oben. Der Fall
|
|
655
|
+
ist der Auffangplatz ACTUAL bei `info` — er verlangt keine Rolle und
|
|
656
|
+
steht deshalb in keiner Rollenliste. Hier stand vorher „kein Platz im
|
|
657
|
+
Information-Muster … Alexa & Co. zählen ihn separat“, direkt unter
|
|
658
|
+
einem Feld, das ACTUAL zeigte (Ricardo, 19.09.2026). */
|
|
659
|
+
if (hin.childNodes.length) { rb.appendChild(hin); }
|
|
458
660
|
/* Passt der Typ nicht zum Platz, faellt der Punkt aus dem Muster -
|
|
459
661
|
lautlos, denn die Rolle stimmt ja. Genau das ist am 11.09.2026
|
|
460
662
|
an einem Tasmota-Stromzaehler passiert (I33). */
|
|
461
|
-
|
|
663
|
+
/* Gelb wie die Typ-Warnung: in beiden Faellen bekommt die Zeile keinen
|
|
664
|
+
Platz. Als grauer Nachsatz ging es unter (Ricardo, 19.09.2026). */
|
|
665
|
+
if (besetztVon) {
|
|
666
|
+
rb.appendChild(el('div', 'aside w', tr('pattern.slotHeldBy', treffer.name, besetztVon)));
|
|
667
|
+
}
|
|
668
|
+
if (treffer && !besetztVon && s.typ && erlaubt.length && erlaubt.indexOf(s.typ) === -1) {
|
|
462
669
|
rb.appendChild(el('div', 'aside w',
|
|
463
670
|
tr('pattern.typeMismatch', treffer.name, erlaubt.join(tr('pattern.typeOr')), s.typ)));
|
|
464
671
|
}
|
|
@@ -529,48 +736,56 @@ export function detailZeile(e, s, idx) {
|
|
|
529
736
|
|
|
530
737
|
/* --- Rohwert und Ergebnis --- */
|
|
531
738
|
var a = wertVon(s);
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
wbox.appendChild(el('span', 'chip bad', tr('detail.sourceGone')));
|
|
538
|
-
zeile(tr('detail.attention'), wbox);
|
|
739
|
+
/* Ein Knopf ohne Lesequelle hat nichts anzuzeigen - das ist gewollt
|
|
740
|
+
und kein „liefert nichts" in Rot. */
|
|
741
|
+
if (!s.srcR && knopfZeile(s)) {
|
|
742
|
+
zeile(tr('detail.yields'), el('span', 'chip mut', tr('detail.buttonNoRead')));
|
|
743
|
+
a = null;
|
|
539
744
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (a.ok) {
|
|
549
|
-
var t = typeof a.val;
|
|
550
|
-
var passt = !s.typ || s.typ === t || s.typ === 'mixed';
|
|
551
|
-
box.appendChild(el('span', 'chip ' + (passt ? 'ok' : 'warn'),
|
|
552
|
-
(passt ? '✓ ' : '≠ ') + (t === 'number' ? tr('detail.typeNumber') : t)));
|
|
553
|
-
if (a.gewandelt) {
|
|
554
|
-
box.appendChild(document.createTextNode(' '));
|
|
555
|
-
box.appendChild(el('span', 'chip mut', tr('detail.converted')));
|
|
556
|
-
}
|
|
557
|
-
if (!passt) {
|
|
558
|
-
box.appendChild(document.createTextNode(' '));
|
|
559
|
-
box.appendChild(el('span', 'chip warn', tr('detail.expects', s.typ)));
|
|
745
|
+
if (a) {
|
|
746
|
+
zeile(tr('detail.rawValue'), a.roh === undefined ? tr('detail.rawNothing')
|
|
747
|
+
: (typeof a.roh === 'object' ? JSON.stringify(a.roh) : String(a.roh)), 'raw');
|
|
748
|
+
|
|
749
|
+
if (s.srcR && !S.objects[s.srcR]) {
|
|
750
|
+
var wbox = el('div');
|
|
751
|
+
wbox.appendChild(el('span', 'chip bad', tr('detail.sourceGone')));
|
|
752
|
+
zeile(tr('detail.attention'), wbox);
|
|
560
753
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
754
|
+
|
|
755
|
+
var box = el('div');
|
|
756
|
+
var rv = el('span', 'res', a.ok
|
|
757
|
+
? (typeof a.val === 'object' ? JSON.stringify(a.val) : String(a.val)) + (s.unit ? ' ' + s.unit : '')
|
|
758
|
+
: a.txt);
|
|
759
|
+
if (!a.ok) { rv.style.color = 'var(--bad)'; }
|
|
760
|
+
box.appendChild(rv);
|
|
761
|
+
box.appendChild(document.createTextNode(' '));
|
|
762
|
+
if (a.ok) {
|
|
763
|
+
var t = typeof a.val;
|
|
764
|
+
var passt = !s.typ || s.typ === t || s.typ === 'mixed';
|
|
765
|
+
box.appendChild(el('span', 'chip ' + (passt ? 'ok' : 'warn'),
|
|
766
|
+
(passt ? '✓ ' : '≠ ') + (t === 'number' ? tr('detail.typeNumber') : t)));
|
|
767
|
+
if (a.gewandelt) {
|
|
768
|
+
box.appendChild(document.createTextNode(' '));
|
|
769
|
+
box.appendChild(el('span', 'chip mut', tr('detail.converted')));
|
|
770
|
+
}
|
|
771
|
+
if (!passt) {
|
|
772
|
+
box.appendChild(document.createTextNode(' '));
|
|
773
|
+
box.appendChild(el('span', 'chip warn', tr('detail.expects', s.typ)));
|
|
774
|
+
}
|
|
775
|
+
var ist = s.urId ? S.werte[s.urId] : null;
|
|
776
|
+
if (ist && ist.val !== undefined && ist.val !== null) {
|
|
777
|
+
var gleich = String(ist.val) === String(a.val);
|
|
778
|
+
box.appendChild(document.createTextNode(' '));
|
|
779
|
+
box.appendChild(el('span', 'chip ' + (gleich ? 'ok' : 'warn'),
|
|
780
|
+
gleich ? tr('detail.matchesState') : tr('detail.stateSays', String(ist.val))));
|
|
781
|
+
}
|
|
782
|
+
} else if (a.fehler) {
|
|
783
|
+
box.appendChild(el('span', 'chip bad', tr('detail.formulaBroken')));
|
|
784
|
+
} else {
|
|
785
|
+
box.appendChild(el('span', 'chip bad', tr('detail.yieldsNothing')));
|
|
567
786
|
}
|
|
568
|
-
|
|
569
|
-
box.appendChild(el('span', 'chip bad', tr('detail.formulaBroken')));
|
|
570
|
-
} else {
|
|
571
|
-
box.appendChild(el('span', 'chip bad', tr('detail.yieldsNothing')));
|
|
787
|
+
zeile(tr('detail.yields'), box);
|
|
572
788
|
}
|
|
573
|
-
zeile(tr('detail.yields'), box);
|
|
574
789
|
|
|
575
790
|
if (s.manuell) {
|
|
576
791
|
var db = el('div');
|