iobroker.alias-workbench 0.9.10 → 0.9.12
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 +66 -4
- package/admin/css/werkbank.css +122 -6
- package/admin/js/baum.js +8 -0
- package/admin/js/detail.js +63 -10
- package/admin/js/entwurf.js +416 -190
- package/admin/js/enums.js +2 -2
- package/admin/js/ergebnis.js +156 -23
- package/admin/js/erkennung.js +7 -0
- package/admin/js/mqtt.js +75 -5
- package/admin/js/musterwahl.js +196 -29
- package/admin/js/objekte.js +44 -0
- package/admin/js/pruefungen.js +37 -8
- package/admin/js/schreiben.js +150 -74
- package/admin/js/verwendung.js +478 -0
- package/admin/js/vorlagen.js +75 -6
- package/admin/js/vorlagenblatt.js +134 -17
- package/admin/js/vorschlagen.js +50 -17
- package/admin/js/werte.js +96 -6
- package/admin/js/zielleiste.js +8 -2
- package/admin/js/zuordnung.js +43 -13
- package/admin/js/zustand.js +3 -0
- package/admin/js/zustandsliste.js +56 -20
- package/admin/sprachen/de.json +64 -10
- package/admin/sprachen/en.json +64 -10
- package/admin/tab.html +44 -44
- package/admin/vorlagen/messpunkt.json +1 -0
- package/admin/vorlagen/tasmota-lampe.json +1 -0
- package/admin/vorlagen/tasmota-mehrfach.json +1 -0
- package/admin/vorlagen/tasmota-steckdose.json +1 -0
- package/io-package.json +38 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,6 +25,18 @@ good is decided by things you **cannot see**:
|
|
|
25
25
|
A tool that only links datapoints helps with none of that. This adapter shows
|
|
26
26
|
all four while you are building, not hours later in the log.
|
|
27
27
|
|
|
28
|
+
### Why an admin tab, not a settings page
|
|
29
|
+
|
|
30
|
+
The workbench is an **admin tab** (`adminTab` in `io-package.json`) and runs
|
|
31
|
+
without an adapter process (`mode: none`). That is deliberate: it is an
|
|
32
|
+
interactive editor — an object tree on the left, the draft with live values on
|
|
33
|
+
the right, a dry run before anything is written, a source swap, a template
|
|
34
|
+
sheet. None of that fits into a jsonConfig page, which is built for settings.
|
|
35
|
+
The instance's actual settings do live in a normal jsonConfig page
|
|
36
|
+
(`adminUI.config: "json"`, four switches, translated into all eleven
|
|
37
|
+
languages). Without a process the adapter uses no memory while nobody has the
|
|
38
|
+
tab open.
|
|
39
|
+
|
|
28
40
|
## What it does
|
|
29
41
|
|
|
30
42
|

|
|
@@ -311,6 +323,18 @@ nothing on purpose. Fifty bare `LEVEL` channels are not a device.
|
|
|
311
323
|
When a template is rejected, the picker says why: *"1.LEVEL missing"*, not
|
|
312
324
|
*"does not fit"*. A reason you cannot act on is not a reason.
|
|
313
325
|
|
|
326
|
+
**About the role `value.power.consumption`.** Four templates (`messpunkt`,
|
|
327
|
+
`tasmota-lampe`, `tasmota-mehrfach`, `tasmota-steckdose`) give the row
|
|
328
|
+
`CONSUMPTION` the role `value.power.consumption`, which is struck through in
|
|
329
|
+
the ioBroker [state roles list](https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/stateroles.md).
|
|
330
|
+
It stays on purpose: `@iobroker/type-detector` 6.0.x recognises `CONSUMPTION`
|
|
331
|
+
only with exactly this role (`typePatterns.js`, pattern `consumption`,
|
|
332
|
+
`defaultRole: 'value.power.consumption'`). With `value.energy.consumed` the
|
|
333
|
+
point would drop out of the detected device, and vis, Alexa and the device
|
|
334
|
+
manager would no longer show any consumption. Each of the four rows carries
|
|
335
|
+
this reason in a field `_rolle`. The role will change once the type-detector
|
|
336
|
+
does.
|
|
337
|
+
|
|
314
338
|
### Role knowledge beside the rows
|
|
315
339
|
|
|
316
340
|
A template can carry `weiterePunkte`: roles for datapoints that get no row of
|
|
@@ -541,12 +565,16 @@ served as they are; editing one and reloading the tab is the whole cycle. On a
|
|
|
541
565
|
running installation the admin caches adapter files, so `iobroker upload
|
|
542
566
|
alias-workbench` after copying is what makes the browser see the change.
|
|
543
567
|
|
|
544
|
-
|
|
545
|
-
|
|
568
|
+
There are two dictionaries. `admin/i18n/` belongs to the **settings page**
|
|
569
|
+
(jsonConfig with `"i18n": true`, all eleven languages, loaded by the admin
|
|
570
|
+
itself). `admin/sprachen/` belongs to the **tab** (German and English, loaded by
|
|
571
|
+
`admin/js/sprache.js`; any other language falls back to English). A new tab
|
|
572
|
+
language is a new file in `admin/sprachen/` and an entry in `SPRACHEN` in
|
|
573
|
+
`admin/js/sprache.js` — nothing else. Watch for strings
|
|
546
574
|
written straight into `admin/tab.html`: they are replaced at runtime, and one
|
|
547
|
-
that nobody wired up stays
|
|
575
|
+
that nobody wired up stays English in every language without anyone noticing.
|
|
548
576
|
|
|
549
|
-
The tab is **
|
|
577
|
+
The tab is **30 ES modules** under `admin/js/`, loaded by the browser as
|
|
550
578
|
modules — there is no build step for them, and styling lives in one file,
|
|
551
579
|
`admin/css/werkbank.css`. Because a module boundary can be crossed wrongly in
|
|
552
580
|
ways the browser only notices while running, `npm test` checks before every
|
|
@@ -564,6 +592,40 @@ imported, and that both language files carry the same keys.
|
|
|
564
592
|
|
|
565
593
|
## Changelog
|
|
566
594
|
|
|
595
|
+
### 0.9.12 (2026-09-18)
|
|
596
|
+
* 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
|
+
* **“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.
|
|
598
|
+
* **No German left in the English interface.** Three places were hard-wired German: the type badge in the detail row (“Zahl”), the “Datenpunkt” field next to “writes to” and the screen-reader label of the tick boxes (“SET anlegen”). The fixed default texts in `tab.html` are now English — before, 44 German texts were only translated at runtime. The two browser-console messages are English now as well.
|
|
599
|
+
* The tab's **menu entry** now has the adapter's name in all eleven admin languages. Before, it existed in German and English only, and every other language showed “Alias Workbench”.
|
|
600
|
+
* **Why `value.power.consumption`** — four shipped templates give consumption this role, which the ioBroker roles list marks as deprecated. That is deliberate: the type-detector 6.0.x recognises consumption only with exactly this role. The reason is now written into every affected template (field `_rolle`) and into the README. The README also explains why the workbench is an admin tab rather than a settings page.
|
|
601
|
+
* **The object count in the header grew the longer the tab stayed open.** Only devices, channels, folders and states are meant to be counted — and only those are loaded. When following changes, however, the workbench took in every changed object type: after switching the system language, `system.config` sat in its store and the count read 3146 instead of 3145, likewise after every saved instance setting or adapter update. Following changes now applies the same rule as loading.
|
|
602
|
+
* **Values from another node did not update at a source.** When you select a source that already has an alias, the list also shows that alias’s rows — and they often read from somewhere else. The electricity meter shows three rows “from Solar.Netz”: import, smoothed import and house consumption. Their value was fetched once on selection and then froze; it stayed at 428 W for hours while the system reported 22 W. Only the selected node was subscribed; the rows’ sources were only added in “Edit alias” mode. The workbench now subscribes to everything it displays — including a source you point a row to by hand in the detail view. The header accordingly shows two subscriptions instead of one on the electricity meter.
|
|
603
|
+
* The legend above the list claimed **“The light pattern does not apply right now: SET requires boolean, number is set. That is why no row has a place.”** — on a device whose SET row carries `switch.light`/`boolean` and is perfectly fine. The culprit was `0_AES_KEY`: hm-rpc gives it the role `state` and the type `number`, and `state` is allowed on the light pattern’s SET place. The check asks per row “your role would fit a place, your type does not” — it never looks at who ended up holding the place, and the message then printed the PLACE instead of the row it was about. The sentence was wrong at the end too: five rows did have a place. The type reason now only applies when the named place really did stay empty; otherwise the ordinary legend is back, and every placeless row is tinted instead of just one. The case the message was built for — SET itself in the wrong type, place stays empty — is unchanged.
|
|
604
|
+
* Rows carried **“differs from the template”** where no template was in force at all — and flagged the better value while doing so. What was really compared was the raw draft, that is the bare role of the source point: on a switch actuator, after “— no template —”, `SET` showed the badge and below it “The template wants: **switch**”, while the alias holds `switch.light`, which even fits the `light` pattern. Same on `UNREACH`, where `indicator.maintenance.unreach` counted as the deviation and `indicator.unreach` as the target. And it was not only the dismissed case: on a device for which a template is **never** found, the same badge appeared. The rule now: with no template in force — dismissed or never found — no template value is recorded, so the badge, the detail line, the `←` button and the **template** filter button all drop out. Rows keep what the alias holds; that is the stock rule and has nothing to do with templates. With a template everything stays as it was — a real deviation is still shown.
|
|
605
|
+
* With **“— no template —”** the **output** picker stayed on screen if you came from the multi-output template — coming via any other template it was gone. Two routes, two answers on the same device. Worse: using the leftover picker brought the dismissed template **silently back** (measured: after switching to `Licht_Esstisch` the field read “HomeMatic switch actuator with several outputs” again). An output does not belong to the device but to the template’s `mehrfach` block — without it there is no placeholder and so nothing to choose; of 17 bundled templates exactly two carry such a block. The raw draft no longer inherits it, and the channels go through “one sub-folder per channel” instead. **And everything is ticked now.** The raw draft used to tick whatever had a place in the detected pattern; on two **identical** channels the detector picked one of them — the second — so `Licht_Esstisch` stood ticked and `Licht_Bar` empty. Which one it picks is chance, and guessing is not the job of a draft without a template: dropping the template means picking the points yourself.
|
|
606
|
+
* The expander in the template box read **“Why this template +”** and always wanted the right-hand edge. With a long name and two badges — “HomeMatic roller shutter actuator”, chosen by hand, forced — it no longer found room there and took **a line of its own**; the box grew without anything having been added. It is now a **question mark**, with the sentence in the tooltip. What was left out stays visible as a short badge (`4 WITHOUT SOURCE`) — nobody should have to go looking for the fact that points are missing — and the sentence behind it now reads “4 left out because the source is missing” instead of “Left out because the source is missing: 4”. The header no longer wraps at all: room is made by the template name, which is shortened and shown in full in its tooltip; the id stays put, because `hm-rollladen…` is no longer an id. Expanded, the question mark is filled.
|
|
607
|
+
* **A push-button is not simply any write-only point.** The workbench left the read source empty on every row whose source carried `read: false`. But HomeMatic uses that flag for two different things, counted across every device in the house: for a button press (`PRESS_SHORT` 100 points, `RAMP_STOP` 3, `STOP` 1, `OLD_LEVEL` 3 — all with `native.TYPE: ACTION`) **and** for a setting that stays put (`ON_TIME` 20 points, `RAMP_TIME` 3 — `TYPE: FLOAT`). That is why the living-room dimmer showed “never changes” on `ON_TIME` with an empty “reads from” field — on a value in seconds that one does set at the source now and then. Measured: an alias put on `1.ON_TIME`, the source set to 30 directly, and the alias showed 30. So `read: false` does not block reading. The read source is now left empty only where a button press is really meant: `native.TYPE: ACTION`, or a role starting with `button`. `OLD_LEVEL` is the case where the role is not enough — it is called `value.dimmer`.
|
|
608
|
+
* **“— no template —”** is now in the template picker. Once the workbench had detected a template it could only be swapped for another one, never dropped: the first entry of the list was a mere heading with an empty value, and the branch bailed out on an empty value. Anyone wanting to create all points of a device without a template’s defaults had no way to get there. The choice now rebuilds the draft without any template — ticked is whatever has a place in the detected pattern — and target, folder, name, room and function stay as they were.
|
|
609
|
+
* The mark on a row whose source only writes read **“reports nothing”**. That sounded like a fault — as if the device were mute. What is meant is different: the value stays where it was written and never reports back by itself. It now reads **“never changes”**, and the same in the “target does not read” check.
|
|
610
|
+
* **“One sub-folder per channel”** — the button next to “create as one device” serves a different purpose, and nothing said so: it **mirrors the folder structure of the source**. A container for the device, one channel each below it, the states below those. **The ticks now decide what appears**: one sub-folder for every channel that contributes at least one ticked row, holding exactly those rows. Before, the path built a fresh draft per channel and ticked whatever that channel’s pattern offered — things appeared that nobody had chosen (on the test device the maintenance channel got three points instead of the two ticked ones), and channels with nothing chosen were created all the same. Edits made by hand on a row — role, type, unit, formula, caption — now travel with it. **Information channels count too**: they used to be dropped, so the button said “3 of them” while six channels stood in the tree next to it — and clicking `general` on its own has long given a draft (“Information ✓ 7/14 filled”) that could be created. **The maintenance channel gets its own folder** instead of being copied into every output: `UNREACH` exists only in channel 0 of the dimmer, so that is where it belongs; whoever wants it with the light takes “create as one device”. The copying had produced nonsense — 206 raw data points of a lawn mower in each of four drafts, the three meter phases of a Shelly SHEM-3 counted as “shared” instead of as channels of their own. **Virtual channels drop out by themselves**: on a HomeMatic dimmer nothing is ticked in the two `VIRTUAL_DIMMER` channels, so nothing is created there. **States directly on the device** stay in the container, which is then created as a channel instead of a folder. **And the hint is right again**: it computed `targetFolder + channel` while writing uses `targetFolder + device + channel` — on the dishwasher it promised `alias.0.Commands` and created `alias.0.Dishwasher.Commands`, on the living-room dimmer it read `alias.0.1`, `alias.0.2`, `alias.0.3`. It now names the id, the number of ticked points and the detected type. The label says so too: “one sub-folder per channel, N of them …” instead of “one device per channel”
|
|
611
|
+
* On a **multi-output device** the draft adopted the ticks of a foreign alias. The workbench asked "is there already an alias for this device?" and answered with the one that reads **most often** from it. On Ricardo's power strip that is the consumption alias with six points from `tele.SENSOR` — it beat `RF1000` with two points from `stat.POWER1`. Output 1 then showed ELECTRIC_POWER and CONSUMPTION ticked, SET unticked, and the pattern field reported **"✕ SET missing"** on a device with a working SET. The majority rule is right for a single-output device and the wrong question for a multi-output one. The right question is: **does this alias belong to the output currently selected?** That is not guessed from names but asked of the template — which of its paths carry the placeholder? Exactly those tell the outputs apart (`stat.POWER%N%`, `cmnd.POWER%N%`); everything else (`tele.SENSOR`, `tele.LWT`) is shared. This also removes the trap from I31: `tele.INFO2` does carry a two, but no placeholder. For output 1 the bare `cmnd.POWER` counts as well — with Tasmota, POWER without a number is a second name for POWER1, measured at the device: a query to `cmnd/POWER` is answered with `{"POWER1":"ON"}`, and a topic `stat/POWER` does not exist. **The bug only hit the first glance**: coming through the tree brought the wrong stock, switching the output asked nothing at all and the template defaults won — the same output looked different depending on how you got there. All three paths now ask the same question. Single-output devices never enter the new branch by construction; without the new argument `aliasFuer` behaves line for line as before.
|
|
612
|
+
* **The target line now follows the same alias.** On a multi-output device it always showed the output number — `alias.0.Bastelzimmer.Steckdosenleiste.POWER1`, an ID that does not exist — and reported "will be created", while `RF1000` stood right next to it. Clicking would have put a second alias beside the existing ones. The source said: "the workbench cannot know what the individual output is called." That held as long as it could only ask about the device. The target line now points at `…Steckdosenleiste.RF1000` with the badge "already exists", and "update alias" hits it. Where no alias exists for the output yet — the garden valve island — the proposal from the output number stays, and so does "will be created".
|
|
613
|
+
* The **SetOption59** note appeared on every Tasmota device, including where it has no effect. It claimed an alias would show "the old value for minutes after switching" — but on the power strip not a single row reads from `tele/STATE`; they all read from `stat.POWERn`, which the device sends immediately on every command (measured: 1.07 s). "For minutes" also assumed the Tasmota default of 300 seconds; on that strip it is ten (measured: `tele/STATE` every ten seconds). The block now only appears when a row really reads from `tele/…` **and** writes to `cmnd/…`, names those rows, and reads the interval from `TelePeriod`; if it is not in the telegram the text says "on the telemetry cycle" instead of inventing a number. The same applies to the chip in the header. And the check "feedback is immediate" no longer reports "all fine" where there was nothing to check, but **"no row affected — all read from stat/…"** with a grey dot instead of a tick. Card and check ask the same function and can no longer drift apart.
|
|
614
|
+
|
|
615
|
+
### 0.9.11 (2026-09-16)
|
|
616
|
+
* After saving, the next click in the tree asked **“unsaved changes”** — about an alias that had just been written. The “something unwritten is here” mark was only ever set, never cleared; it went away solely when the draft itself was dropped, i.e. on delete and on move. It now falls after every successful write — and only then: if something went wrong, the change really is not in yet, and the question stays justified. **There is a second effect that had been missing for a long time**: while the mark stood, the workbench deliberately did not rebuild the draft. After a write the old one therefore stayed, instead of rebuilding from the freshly written alias.
|
|
617
|
+
* A second press of **“Suggest free slots”** bent back a row you had set yourself. On the air conditioner: guess nine slots, set the role of the SPEED row to `level.speed` by hand, discard — and the next suggestion put the guessed `level.mode.fan` back. The pattern slot looked free, so the same row was guessed a second time; it then carried **both** marks, “changed” and “guessed”, which exclude each other — what was just guessed cannot have been changed before. Guessing now leaves any row marked “changed” alone. The slot stays free, and the note below says why. Putting a second row next to it was the alternative — the same source point would then hold two slots, and the eighth check would rightly report it.
|
|
618
|
+
* As soon as guesses were pending, the button in the pattern bar renamed itself to **“Discard suggestions”** — and a second one with the same word and the same effect stood in the blue bar below it. Two buttons, one handle. The bar now always says “Suggest free slots”, and only while something is left to guess; once everything is guessed it disappears. Discarding happens where it belongs: in the blue bar, next to “keep all”.
|
|
619
|
+
* Instead of „either every slot is taken, or no remaining datapoint fits" the note now states the case that actually applies — with the **names** of the slots that stay free: “SPEED, SWING … stays free. Every remaining datapoint already serves as a read or write source in a filled row”, or “… No remaining datapoint fits”. If a hand decision holds a slot open, a sentence is appended: “1 rows were decided by hand and are left untouched.” The “every slot taken” case stays silent unless asked — it says “nothing to do”, and would otherwise appear on nearly every device.
|
|
620
|
+
* Ricardo’s air conditioner is detected as a **thermostat** and fills 3 of 24 slots. **airCondition** fails because its only required slot, MODE, does not carry the role — and that settles it, since “does not fit” has the last word. With automatic suggestions it would reach **9 of 29** against 8 of 24. That now stands next to the pattern field: “· airCondition might fit better” — a single line, both numbers in the tooltip. **One** pattern is named — the best one — and only when it really is better; measured in filled slots, not in ratio. Next to it sits a small **→** button that does both in one go: switch to that pattern and press “Suggest free slots” right away — otherwise the note would be an announcement without an action. In the pattern list every affected row carries “with suggestions: fits” behind its reason. It is computed once when the device is selected and after that only on a row change — suggesting reads names, value lists, units and types, no measured values.
|
|
621
|
+
* In the **template sheet** and in the „save as template" dialog the device type showed its **pattern key** instead of its name: a blind read „blinds", a media player „mediaPlayer", a slider „levelSlider" — words that appear nowhere else in the interface. The stored value was right all along (the type, since 11.09.2026); only the label came from the wrong field, and since the Admin does not know those three keys, the translated name fell away with them. It now says the same as the pattern picker next to it: „Jalousien · blind".
|
|
622
|
+
* A **button** can now be created. `STOP` on a blind only writes — a key press has no state to read — and that used to defeat the **whole** alias: the dry run said “STOP: no source chosen”, “create now” stayed greyed out, and not a single blind alias ever got a STOP. The first diagnosis was wrong, and that deserves saying: it was not the write check being too strict. **ioBroker rejects an alias without a read source** — `{ id: { write: … } }` yields “Alias id is invalid: The id is empty!”. The correct shape is the one Ricardo has long been using on his ten hand-built STOPs: `alias.id` as plain text pointing at the command point, plus `common.read: false` on the alias point. That tells vis, Alexa and matter to draw a **button** rather than a toggle stuck at “off” forever. What decides is the **target**, not the role: `read: false` is written by the adapter itself (measured on a real `hm-rpc` STOP). If the target is readable the block stays — then the missing read source really is a defect. The button also survives being read back: all three places that reconstruct an alias now know it, otherwise `read` would quietly flip to `true` on the next update. **The same question grew into a new check**: “target can do what is asked”. The workbench writes to foreign points and reads from foreign points without ever checking whether they can do that. Three ways it silently falls apart — the write formula produces text and the target takes none; the target carries `write: false` (ioBroker stores the value anyway but the adapter passes nothing on: a silent dud); the read source carries `read: false` and never reports anything. All three now appear in the checks panel and as a chip on the ID in the detail view, right where the formula is typed
|
|
623
|
+
* The workbench creates MQTT **send points** itself — a `cmnd` point only comes into existence in ioBroker once something has been published to its topic; before that the alias would have nothing to write to. It took the type from the command knowledge, and `POWER` is listed there as `boolean` — right next to the write formula `val ? "ON" : "OFF"`. The two do not add up: the js-controller converts the formula result back to the type of the **target** on write, the `"ON"` becomes `true` again, and the formula sits in the alias without effect. Switching still works — Tasmota takes `on` as well as `true` — but the point looked different from every point mqtt-client creates, and the formula claimed something it did not do. On the production system it was **one of 196** alias points with an MQTT write target, and it came from us. The rule now is: **where a write formula exists, the point is created as `mixed`** — no exception for `POWER` but a rule that also covers `Fade` and `LedTable`. Without a formula the exact type stays, because the admin derives min, max and unit from `number`. **Plus two places where it becomes visible**: the target point's type now shows as a chip behind its ID in the detail view — right where the formula is typed — in warning colour when the two do not match. And the checks panel has an eighth check, "write formula takes effect", that names it instead of keeping quiet
|
|
624
|
+
* A template now counts as fitting only where its **mandatory rows** actually find their source. Until now detection alone decided, and detection asks a different question than application: `pruefeVorlage` checks whether the named **points** exist, the rows need particular **fields inside them**. A hand-built power-meter template whose mandatory row reads `tele.SENSOR · SML.Total_Summe` thus won on **17** ordinary Tasmota devices and delivered exactly one — and unchecked — row there, where the socket template would have built eleven. The device said "identified as: Tasmota power meter", and the draft was empty. Now it does not even enter the race there and the next best one takes the place; what was skipped is stated in the "why". Optional rows do not count, that is what optional means — measured against the stock, **none** of the fifteen other templates loses any of its 51 wins. **And the try-out in the template sheet**: it now also fetches the values of the points the rows read. Without them it judged fields it could not see, and the same template reported different numbers depending on what had been clicked before. It now shows per device how many rows would really come out, and on "does not fit" **which mandatory row is missing** — turning "22 devices matched, 17 of them won" into the honest "22 matched, 1 won · 21× does not fit". **And the try-out now computes like the creation does**: at first it compared only against the single highest-scoring other template — if that one did not fit the device, the template under test declared itself the winner. "Plain measuring point" thus reported a win on a Tasmota although the socket template sits in between and really fits there. It now walks the match list like `vorschlag` does; measured afterwards, all 150 pairs of template and device agree
|
|
625
|
+
* An alias whose source has vanished from the system is now visible without clicking it. Until now it was only said in the expanded detail and in the checks panel — the row in the list stayed quiet because it asked whether a source was **entered**, not whether it **exists**. The row now carries the red `!` with the note "source does not exist", the header a chip "1 point points nowhere" with the list in its tooltip, and the alias tree a red `!` on the affected channel. This is not cosmetic: the js-controller remembers "source missing" permanently and never corrects it — only deleting and recreating helps. It is computed once when the objects load, not on every redraw; on the production system 531 alias points in 5.2 ms. Two were found straight away: a window contact still pointing at the long-replaced `zigbee` adapter, and an NSPanel point with a missing `0_userdata` source for both reading and writing. A row with **no** source at all keeps its own note — it is unfinished, not broken
|
|
626
|
+
* New: **"Referenced in"** on an existing alias. The box below the change comparison searches on click for whatever else mentions the ID — the **scripts** of the javascript adapter with folder, name, state and line numbers, the **files** of the instance folders (that is, the vis and vis-2 views), every loaded **object** (where the matter bridges keep their members, for instance), the **enumerations** and the entries on the point itself (`SET → influxdb.0`) — and the same again for the **source**, including further aliases on it. That answers the question of what was left behind when moving to the alias. Measured on the production system with 24,706 objects and 66 files: the first look takes 1.3 seconds, every further alias after that 0.17. The web interfaces of the adapters are not searched — 160 of the 177 MB of text in the file tree, identical on every ioBroker, and they carry examples like `alias.0.Floor.Room.MotionSensor`; without that boundary the workbench would find its own source files as users of itself. Deliberately without a verdict: a source that is still mentioned is not a fault — a script that feeds it belongs exactly there (of 376 aliases with a source, 61 still had the source mentioned directly, a good share of them rightly so). **And the delete dialog now searches the scripts too** — that only existed when moving, although deleting is the irreversible one
|
|
627
|
+
* The field picker on a JSON point now copes with every key. Until now the workbench glued the field name onto the access with dots — which only works as long as every part is a valid JavaScript name. `gpu_usages.amd-vaapi.gpu` turned into an arithmetic expression (`?.amd - vaapi?.gpu`), `service.storage./dev/shm.free` and `cpu_usages.1111740.mem` into a syntax error. Measured on a Frigate telegram: of 233 offered fields **27** were usable, the other 206 ended in the red "formula broken" message. `feldFormel` now uses brackets where the key is not a name — `JSON.parse(val)?.gpu_usages?.['amd-vaapi']?.gpu ?? null` — and reads them back again, so the picker stays on the chosen field instead of falling back to "own formula". For ordinary names **not a single character** changes, so the shipped templates are untouched. A dot inside the key itself (`frigate.full_system`) remains unresolvable and still shows "own formula"
|
|
628
|
+
|
|
567
629
|
### 0.9.10 (2026-09-13)
|
|
568
630
|
* Four things from the full test run. **A channel identifier** was not sanitised in the multi-output path: from the channel name “Licht, Bar” the js-controller quietly made `Licht_ Bar`, while the workbench displayed “Licht, Bar” and wrote it into the enumerations — an identifier that never existed, and after deleting the alias it stayed there as a leftover. `ausgangName` now sanitises itself, and where two channels produce the same name the second gets a `_2`. **The caption** differed between the two views: where the point's name equals the row name, “Edit alias” showed it and “Create alias” an empty field. **The row list** now counts like the tree — `POWER2` before `POWER10`. And **an enumeration the workbench created itself** now also disappears when it becomes empty through a change of function, not only when the alias is deleted
|
|
569
631
|
* Tidying: "turn an alias object into a draft row" was written out five times — each with the same write-source rule and slightly different surroundings. Four bugs in this version came from exactly that: the lost write source on the roller shutter, a switching point that became a read-only one, the missing caption on `alias.0.Solar.Netz`, and a deviation the workbench had produced itself. The reading now lives in one place; what the four callers do with it — compare, create, overwrite — stays with them. Behaviour is unchanged
|
package/admin/css/werkbank.css
CHANGED
|
@@ -199,10 +199,15 @@
|
|
|
199
199
|
.tree .node.sel .chip { background:rgba(255,255,255,.22); color:var(--accent-ink); border-color:transparent; }
|
|
200
200
|
.tree .node.sel .nm { color:var(--accent-ink); }
|
|
201
201
|
.tree .node.sel > span { color:var(--accent-ink) !important; }
|
|
202
|
+
/* Feste Breite: sonst bestimmt der Text die Breite, und ein Knoten ohne
|
|
203
|
+
eigenes Objekt (nur „·“) rueckte seinen Namen aus der Flucht der
|
|
204
|
+
anderen (Ricardo, 18.09.2026). Auch DEV/CHA/FOL/STA sind so gleich
|
|
205
|
+
breit. */
|
|
202
206
|
.tree .kind {
|
|
203
207
|
font-size:8px; padding:0 4px; border-radius:4px; background:var(--surface-3);
|
|
204
208
|
color:var(--ink-3); text-transform:uppercase; letter-spacing:.04em;
|
|
205
209
|
font-family:var(--sans); font-weight:700; flex:0 0 auto;
|
|
210
|
+
width:26px; box-sizing:border-box; text-align:center;
|
|
206
211
|
}
|
|
207
212
|
.tree .idstueck { flex:0 0 auto; }
|
|
208
213
|
/* Der Name weicht zurueck: gedaempft, und er ist es, der gekuerzt wird,
|
|
@@ -377,6 +382,50 @@
|
|
|
377
382
|
.aside.w { border-left-color:var(--warn); }
|
|
378
383
|
.aside b { color:var(--ink); }
|
|
379
384
|
|
|
385
|
+
/* Der Kasten „Wird genannt in“.
|
|
386
|
+
|
|
387
|
+
Zuerst mit den Klassen der Zustandsliste gebaut (.slot, .sn, .typ) -
|
|
388
|
+
die sind durchgehend Schreibmaschinenschrift, und der Kasten sah aus
|
|
389
|
+
wie Quelltext statt wie Text. Beschriftung links deshalb wie im
|
|
390
|
+
Detail (.k), Inhalt als gewoehnlicher Satz, und nur Kennungen in
|
|
391
|
+
Schreibmaschine. */
|
|
392
|
+
/* Der Blockkopf muss sich von den Zeilenbeschriftungen darunter
|
|
393
|
+
abheben - stand er in derselben grauen Versalschrift, las sich
|
|
394
|
+
„DER ALIAS“ wie eine weitere Zeile statt wie die Ueberschrift
|
|
395
|
+
darueber (Ricardo, 14.09.2026). Deshalb eigene Leiste mit Grund,
|
|
396
|
+
Akzentfarbe und etwas groesser. */
|
|
397
|
+
.vwkopf {
|
|
398
|
+
display:flex; align-items:baseline; gap:10px; flex-wrap:wrap;
|
|
399
|
+
padding:7px 13px; background:var(--surface-2);
|
|
400
|
+
border-top:1px solid var(--line); border-bottom:1px solid var(--line);
|
|
401
|
+
}
|
|
402
|
+
.vwkopf .t {
|
|
403
|
+
font-family:var(--sans); font-size:10px; text-transform:uppercase;
|
|
404
|
+
letter-spacing:.08em; color:var(--accent); font-weight:700;
|
|
405
|
+
}
|
|
406
|
+
.vwkopf .id { font-family:var(--mono); font-size:12px; color:var(--ink); word-break:break-all; }
|
|
407
|
+
.vwzeile {
|
|
408
|
+
display:grid; grid-template-columns:148px minmax(0,1fr); gap:10px;
|
|
409
|
+
align-items:start; padding:4px 13px; font-size:12px; color:var(--ink);
|
|
410
|
+
}
|
|
411
|
+
.vwzeile .k {
|
|
412
|
+
font-family:var(--sans); font-size:9px; text-transform:uppercase;
|
|
413
|
+
letter-spacing:.06em; color:var(--ink-3); font-weight:700; padding-top:3px;
|
|
414
|
+
}
|
|
415
|
+
.vwzeile .leer { color:var(--ink-3); }
|
|
416
|
+
.vwzeile .id { font-family:var(--mono); font-size:11.5px; word-break:break-all; }
|
|
417
|
+
.vwzeile ul { margin:0; padding-left:15px; }
|
|
418
|
+
.vwzeile li { margin:1px 0; line-height:1.5; }
|
|
419
|
+
.vwzeile li .id { font-weight:600; }
|
|
420
|
+
.vwzeile li .ord { color:var(--ink-3); }
|
|
421
|
+
.vwnote { padding:10px 13px 0; }
|
|
422
|
+
.vwnote:last-child { padding-bottom:12px; }
|
|
423
|
+
|
|
424
|
+
/* Der Alias im Baum, dessen Quelle es nicht mehr gibt. Klein und rot,
|
|
425
|
+
neben Geraetepunkt und Zahl - kein Kasten, keine Toenung: die Zeile
|
|
426
|
+
soll lesbar bleiben. */
|
|
427
|
+
.kaputt { color:var(--bad); font-weight:700; }
|
|
428
|
+
|
|
380
429
|
.empty { color:var(--ink-3); font-size:13px; padding:36px 8px; text-align:center; }
|
|
381
430
|
/* Der Erklaertext der leeren Seite: lesbar breit, nicht ueber die
|
|
382
431
|
ganze Flaeche gezogen, zweiter Absatz gedaempft. */
|
|
@@ -460,6 +509,29 @@
|
|
|
460
509
|
padding:2px 24px 2px 6px; width:290px; max-width:290px;
|
|
461
510
|
}
|
|
462
511
|
.vmeta { display:flex; gap:5px; align-items:center; flex-wrap:wrap; }
|
|
512
|
+
/* „mit Raten: Klimaanlage 9/29, Thermostat 8/24" - der Hinweis neben
|
|
513
|
+
dem Musterfeld. Kein Chip: es ist keine Marke am Geraet, sondern ein
|
|
514
|
+
Weg, den man gehen kann. Deshalb Text in der Farbe der Vermutung,
|
|
515
|
+
nicht in der von Gut oder Schlecht. */
|
|
516
|
+
/* Die Breite ist auf die des Feldes darueber gedeckelt, und der Satz
|
|
517
|
+
darf umbrechen.
|
|
518
|
+
|
|
519
|
+
Mit `nowrap` war er eine 315 px breite Zeile, und weil der Block
|
|
520
|
+
seine Breite aus dem breitesten Kind nimmt, wurde der ganze
|
|
521
|
+
Musterblock breiter. Im Vorlagenkasten passte die Knopfleiste damit
|
|
522
|
+
nicht mehr in dieselbe Zeile, rutschte in die naechste - und deren
|
|
523
|
+
`margin-top:16px` plus der Zeilenabstand von 18 px standen dann als
|
|
524
|
+
34 px Leere zwischen Hinweis und Knopf (Ricardo, 15.09.2026). Es
|
|
525
|
+
fehlten drei Pixel; jede Wortaenderung haette es wieder kippen
|
|
526
|
+
koennen. Ein Deckel ist verlaesslicher als ein knapper Umbruch. */
|
|
527
|
+
.ratAussicht { font-size:11px; color:var(--accent); max-width:290px;
|
|
528
|
+
display:flex; gap:5px; align-items:center; flex-wrap:wrap; }
|
|
529
|
+
/* Der Knopf traegt die Farbe des Hinweises, nicht die eines
|
|
530
|
+
gewoehnlichen Knopfes - er gehoert zum Satz daneben. Reicht die
|
|
531
|
+
Breite nicht, rutscht er unter den Text statt den Block zu
|
|
532
|
+
verbreitern (siehe den Deckel darueber). */
|
|
533
|
+
.rtgo { color:var(--accent); border-color:var(--accent); flex:none;
|
|
534
|
+
font-size:12px; line-height:1; padding:1px 6px; }
|
|
463
535
|
|
|
464
536
|
/* ---- Geraetekarte ---- */
|
|
465
537
|
|
|
@@ -715,6 +787,14 @@
|
|
|
715
787
|
.tree .geraetepunkt { margin-left:auto; font-size:8px; color:var(--accent); }
|
|
716
788
|
.tree .node.sel .geraetepunkt { color:var(--accent-ink); }
|
|
717
789
|
.tree .geraetepunkt + .cnt { margin-left:5px; }
|
|
790
|
+
/* Die Warnung steht in derselben rechten Gruppe wie Geraetepunkt und
|
|
791
|
+
Zahl - und VOR dem Punkt, damit Punkt und Zahl ihr Paar behalten.
|
|
792
|
+
Stand sie dazwischen, verlor `.geraetepunkt + .cnt` seinen Griff und
|
|
793
|
+
die Zahl rutschte allein an den rechten Rand (Ricardo, 14.09.2026). */
|
|
794
|
+
.tree .kaputt { margin-left:auto; font-size:10px; line-height:1; }
|
|
795
|
+
.tree .kaputt + .geraetepunkt { margin-left:6px; }
|
|
796
|
+
.tree .kaputt + .cnt { margin-left:6px; }
|
|
797
|
+
.tree .node.sel .kaputt { color:var(--accent-ink); }
|
|
718
798
|
|
|
719
799
|
.erow .fehltnoch {
|
|
720
800
|
margin-left:7px; font-style:normal; font-size:10px; font-weight:700;
|
|
@@ -765,15 +845,28 @@
|
|
|
765
845
|
/* Der Aufklapper steht in der Kopfzeile ganz rechts. `margin-left:auto`
|
|
766
846
|
schiebt ihn dorthin, ohne dass die Kopfzeile etwas davon wissen muss. */
|
|
767
847
|
.suggestbox .hd .wkopf {
|
|
768
|
-
display:inline-flex; align-items:center; gap:
|
|
769
|
-
margin-left:auto; padding:2px
|
|
848
|
+
display:inline-flex; align-items:center; gap:5px; cursor:pointer;
|
|
849
|
+
margin-left:auto; padding:2px 5px; border-radius:var(--r-sm);
|
|
770
850
|
font-size:11.5px; font-weight:400; color:var(--ink-3);
|
|
771
851
|
user-select:none; transition:background .12s, color .12s;
|
|
772
|
-
white-space:nowrap;
|
|
852
|
+
white-space:nowrap; flex:0 0 auto;
|
|
853
|
+
}
|
|
854
|
+
/* Statt „Warum diese Vorlage +" ein Fragezeichen mit Tooltip: die
|
|
855
|
+
Kopfzeile ist eng, der Satz ist lang, und gelesen wurde er ohnehin
|
|
856
|
+
nur einmal (Ricardo, 17.09.2026). Aufgeklappt wird das Zeichen
|
|
857
|
+
gefuellt - sonst saehe man dem Kopf nicht an, dass unten etwas steht. */
|
|
858
|
+
.suggestbox .hd .wkopf .fragez {
|
|
859
|
+
display:inline-flex; align-items:center; justify-content:center;
|
|
860
|
+
width:16px; height:16px; flex:0 0 auto; border-radius:50%;
|
|
861
|
+
border:1px solid var(--line-strong); color:var(--ink-2);
|
|
862
|
+
font-size:11px; font-weight:700; line-height:1;
|
|
863
|
+
}
|
|
864
|
+
.suggestbox .hd .wkopf.auf .fragez {
|
|
865
|
+
background:var(--accent); border-color:var(--accent); color:#fff;
|
|
773
866
|
}
|
|
774
|
-
.suggestbox .hd .wkopf .klappz { color:var(--ink-2); font-size:10px; }
|
|
775
867
|
.suggestbox .hd .wkopf:hover { color:var(--ink); background:var(--surface-2); }
|
|
776
|
-
.suggestbox .hd .wkopf:hover .
|
|
868
|
+
.suggestbox .hd .wkopf:hover .fragez { color:var(--accent); border-color:var(--accent); }
|
|
869
|
+
.suggestbox .hd .wkopf.auf:hover .fragez { color:#fff; }
|
|
777
870
|
.warum .wleib { margin:0; }
|
|
778
871
|
.warum .wleib ul { margin:0; padding-left:16px; }
|
|
779
872
|
.warum .wleib li { margin:1px 0; }
|
|
@@ -782,7 +875,25 @@
|
|
|
782
875
|
text-transform:uppercase; font-weight:700; color:var(--ink-3);
|
|
783
876
|
}
|
|
784
877
|
|
|
785
|
-
|
|
878
|
+
/* Die Kopfzeile bleibt EINE Zeile. Sie umbrechen zu lassen half nicht:
|
|
879
|
+
der Hinweis rechts hat `margin-left:auto`, fand bei langem Namen und
|
|
880
|
+
zwei Marken keinen Platz mehr und nahm sich eine ganze Zeile fuer
|
|
881
|
+
sich allein. Jetzt weicht stattdessen der Vorlagenname zurueck - er
|
|
882
|
+
steht vollstaendig im Tooltip und im Auswahlfeld darunter. */
|
|
883
|
+
.suggestbox .hd { display:flex; align-items:center; flex-wrap:nowrap; overflow:hidden; }
|
|
884
|
+
.suggestbox .hd > * { flex:0 0 auto; }
|
|
885
|
+
.suggestbox .hd .tname,
|
|
886
|
+
.suggestbox .hd .tid {
|
|
887
|
+
flex:0 1 auto; min-width:0; overflow:hidden;
|
|
888
|
+
text-overflow:ellipsis; white-space:nowrap;
|
|
889
|
+
}
|
|
890
|
+
.suggestbox .hd .tid {
|
|
891
|
+
font-family:var(--mono); font-size:10.5px; font-weight:400; color:var(--ink-3);
|
|
892
|
+
/* Die Kennung weicht gar nicht: sie ist kurz, und abgeschnitten sagt
|
|
893
|
+
sie nichts mehr - `hm-rollladen…` ist keine Kennung. Platz macht der
|
|
894
|
+
Name, der bis auf null zurueckgehen darf. */
|
|
895
|
+
flex-shrink:0;
|
|
896
|
+
}
|
|
786
897
|
|
|
787
898
|
.suggestbox { flex-direction:column; align-items:stretch; gap:9px; }
|
|
788
899
|
.suggestbox .hd { width:100%; }
|
|
@@ -814,6 +925,7 @@
|
|
|
814
925
|
/* Gleiche Strichstaerke wie Vorlage und Ausgang daneben - halbfett
|
|
815
926
|
wirkte wie eine andere Schrift in der Zeile. */
|
|
816
927
|
.suggestbox .verdictwrap .musterwahl { font-weight:400; width:250px; max-width:250px; }
|
|
928
|
+
.suggestbox .ratAussicht { max-width:250px; }
|
|
817
929
|
.suggestbox .vmeta { font-size:10.5px; color:var(--ink-3); gap:6px; }
|
|
818
930
|
.suggestbox .luecke { flex:1 1 20px; }
|
|
819
931
|
.suggestbox .knoepfe { display:flex; gap:7px; align-items:flex-end; }
|
|
@@ -936,6 +1048,10 @@
|
|
|
936
1048
|
}
|
|
937
1049
|
.listbar .verdictwrap.inbar .musterwahl { font-size:12px; width:230px; max-width:230px; }
|
|
938
1050
|
.listbar .verdictwrap.inbar .vmeta { margin:0; }
|
|
1051
|
+
/* In der Leiste steht der Hinweis NEBEN dem Feld (Reihe statt Spalte)
|
|
1052
|
+
und hat die ganze Breite der Leiste zur Verfuegung - dort ist eine
|
|
1053
|
+
Zeile richtig; reicht sie nicht, bricht die Leiste selbst um. */
|
|
1054
|
+
.listbar .verdictwrap.inbar .ratAussicht { max-width:none; white-space:nowrap; flex-wrap:nowrap; }
|
|
939
1055
|
|
|
940
1056
|
.feldwrap { position:relative; display:block; min-width:0; }
|
|
941
1057
|
/* Das Musterfeld ist ein Eingabefeld, sieht aber aus wie das Auswahlfeld
|
package/admin/js/baum.js
CHANGED
|
@@ -412,6 +412,14 @@ function renderKinder(knoten, filter, tiefe) {
|
|
|
412
412
|
ueberschreibt ihn die Zeile darueber wieder. */
|
|
413
413
|
if (leer) { d.title = (d.title ? d.title + ' · ' : '') + tr('tree.emptyNode'); }
|
|
414
414
|
if (waehlbar) {
|
|
415
|
+
/* Ein Alias, dessen Quelle es nicht mehr gibt, faellt sonst nur
|
|
416
|
+
auf, wenn man ihn anklickt und die Zeile aufklappt. Die Menge
|
|
417
|
+
steht fertig in S.aliasKaputt - hier wird nur nachgeschlagen. */
|
|
418
|
+
if (S.aliasKaputt[k.id]) {
|
|
419
|
+
var kp = el('span', 'kaputt', '!');
|
|
420
|
+
kp.title = tr('tree.sourceGone', S.aliasKaputt[k.id]);
|
|
421
|
+
d.appendChild(kp);
|
|
422
|
+
}
|
|
415
423
|
if (wohlGeraet(k)) {
|
|
416
424
|
var pk = el('span', 'geraetepunkt', '●');
|
|
417
425
|
pk.title = tr('tree.looksLikeDevice');
|
package/admin/js/detail.js
CHANGED
|
@@ -4,11 +4,11 @@ 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 } from './werte.js';
|
|
7
|
+
import { wertVon, jsonFelder, feldAusFormel, feldFormel, feldWert, jsonVon, zielKann } from './werte.js';
|
|
8
8
|
import { musterVon, erkenneEntwurf, platzFuerRolle, typenFuerRolle } from './erkennung.js';
|
|
9
9
|
import { musterName } from './musternamen.js';
|
|
10
10
|
import { rollenFeld } from './rollenwahl.js';
|
|
11
|
-
import { opt , quellenAuswahl, vorlagenAbweichung, bestandsAbweichung } from './entwurf.js';
|
|
11
|
+
import { opt , quellenAuswahl, vorlagenAbweichung, bestandsAbweichung, aboErgaenzen } from './entwurf.js';
|
|
12
12
|
import { zeichneErgebnis, entwurfAngefasst } from './ergebnis.js';
|
|
13
13
|
|
|
14
14
|
import { rateBehalten } from './vorschlagen.js';
|
|
@@ -26,7 +26,13 @@ export function detailZeile(e, s, idx) {
|
|
|
26
26
|
|
|
27
27
|
/* Wer die Zeile anfasst, hat entschieden - die Vermutung ist damit
|
|
28
28
|
keine mehr und faellt beim Zuruecknehmen nicht mehr weg. */
|
|
29
|
-
|
|
29
|
+
/* Eine von Hand umgestellte Quelle wird mit abonniert - sonst stuende
|
|
30
|
+
ihr Wert nach dem ersten Abholen still (J9b). */
|
|
31
|
+
function neu() {
|
|
32
|
+
s.geaendert = true; rateBehalten(s); entwurfAngefasst();
|
|
33
|
+
aboErgaenzen([s.srcR, s.srcW]);
|
|
34
|
+
zeichneErgebnis();
|
|
35
|
+
}
|
|
30
36
|
|
|
31
37
|
/* ---- Die Auswahl der Datenpunkte ---------------------------------
|
|
32
38
|
|
|
@@ -71,15 +77,59 @@ export function detailZeile(e, s, idx) {
|
|
|
71
77
|
steht die volle Kennung als eigene Zeile unter dem Feld. Dieselbe
|
|
72
78
|
Bauart wie „Die Vorlage will hier: …": sie darf umbrechen und hat
|
|
73
79
|
damit keine Breitengrenze. */
|
|
74
|
-
function mitVollId(felder, id) {
|
|
80
|
+
function mitVollId(felder, id, zusatz) {
|
|
75
81
|
var w = el('div', 'feldstapel');
|
|
76
82
|
w.appendChild(felder);
|
|
77
83
|
var v = el('div', 'vollid');
|
|
78
84
|
if (id) { v.textContent = id; v.title = id; }
|
|
85
|
+
/* Beim Schreibziel steht hinter der Kennung sein Typ. Er entscheidet,
|
|
86
|
+
ob die Schreibformel ueberhaupt ankommt - der js-controller rechnet
|
|
87
|
+
den Formelwert auf den Typ des ZIELS zurueck. Man tippt die Formel
|
|
88
|
+
also an einer Stelle, an der man bisher nicht sehen konnte, ob sie
|
|
89
|
+
etwas bewirkt (Ricardo, 15.09.2026). */
|
|
90
|
+
if (zusatz) { v.appendChild(zusatz); }
|
|
79
91
|
w.appendChild(v);
|
|
80
92
|
return w;
|
|
81
93
|
}
|
|
82
94
|
|
|
95
|
+
/* Was der fremde Punkt kann, als kleine Marke hinter seiner Kennung.
|
|
96
|
+
|
|
97
|
+
Beide Seiten zeigen dasselbe, nur aus verschiedener Richtung: am
|
|
98
|
+
Schreibziel seinen Typ (er entscheidet, was von der Formel ankommt)
|
|
99
|
+
und ob es sich ueberhaupt beschreiben laesst; an der Lesequelle, ob
|
|
100
|
+
sie etwas meldet. `zielKann` in werte.js ist die eine Stelle, die
|
|
101
|
+
darueber urteilt - die Pruefungen-Karte fragt dieselbe Funktion. */
|
|
102
|
+
function zielMarke(s, seite) {
|
|
103
|
+
var id = seite === 'w' ? s.srcW : s.srcR;
|
|
104
|
+
if (!id) { return null; }
|
|
105
|
+
var o = S.objects[id];
|
|
106
|
+
if (!o || !o.common) { return null; }
|
|
107
|
+
var c = o.common;
|
|
108
|
+
|
|
109
|
+
var maengel = zielKann(s).filter(function (x) {
|
|
110
|
+
return seite === 'w' ? x.art !== 'meldetNichts' : x.art === 'meldetNichts';
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
var text, hinweis;
|
|
114
|
+
if (seite === 'w') {
|
|
115
|
+
if (!c.type) { return null; }
|
|
116
|
+
text = c.type;
|
|
117
|
+
var nw = maengel.some(function (x) { return x.art === 'nichtSchreibbar'; });
|
|
118
|
+
var bl = maengel.some(function (x) { return x.art === 'formelBlind'; });
|
|
119
|
+
hinweis = nw ? tr('detail.targetNoWrite')
|
|
120
|
+
: (bl ? tr('detail.targetTypeVoid', c.type) : tr('detail.targetTypeHint', c.type));
|
|
121
|
+
if (nw) { text = c.type + ' \u00b7 ' + tr('detail.notWritable'); }
|
|
122
|
+
} else {
|
|
123
|
+
if (!maengel.length) { return null; }
|
|
124
|
+
text = tr('detail.reportsNothing');
|
|
125
|
+
hinweis = tr('detail.targetNoRead');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
var m = el('span', 'chip ' + (maengel.length ? 'warn' : 'mut'), text);
|
|
129
|
+
m.style.marginLeft = '7px';
|
|
130
|
+
m.title = hinweis;
|
|
131
|
+
return m;
|
|
132
|
+
}
|
|
83
133
|
/* ---- Wo die Vorlage etwas anderes will --------------------------
|
|
84
134
|
|
|
85
135
|
Die zugeklappte Zeile sagt nur, DASS etwas abweicht. Welches Feld,
|
|
@@ -282,7 +332,7 @@ export function detailZeile(e, s, idx) {
|
|
|
282
332
|
lF.appendChild(selF);
|
|
283
333
|
qf.appendChild(lF);
|
|
284
334
|
}
|
|
285
|
-
zeigeBeide(zeile(tr('detail.readsFrom'), mitVollId(qf, s.srcR)), ['srcR']);
|
|
335
|
+
zeigeBeide(zeile(tr('detail.readsFrom'), mitVollId(qf, s.srcR, zielMarke(s, 'r'))), ['srcR']);
|
|
286
336
|
|
|
287
337
|
if (s.frei) {
|
|
288
338
|
var iFrei = el('input', 'tx w');
|
|
@@ -314,7 +364,7 @@ export function detailZeile(e, s, idx) {
|
|
|
314
364
|
neu();
|
|
315
365
|
});
|
|
316
366
|
var lW = el('label', 'fld');
|
|
317
|
-
lW.appendChild(el('span', null, '
|
|
367
|
+
lW.appendChild(el('span', null, tr('detail.datapoint')));
|
|
318
368
|
lW.appendChild(selW);
|
|
319
369
|
wf.appendChild(lW);
|
|
320
370
|
/* Der Hinweis gilt nur, wenn die beiden Quellen wirklich
|
|
@@ -322,7 +372,7 @@ export function detailZeile(e, s, idx) {
|
|
|
322
372
|
if (s.srcW && s.srcW !== s.srcR) {
|
|
323
373
|
wf.appendChild(el('div', 'sugg', tr('detail.separateHint')));
|
|
324
374
|
}
|
|
325
|
-
zeigeBeide(zeile(tr('detail.writesTo'), mitVollId(wf, s.srcW)), ['srcW']);
|
|
375
|
+
zeigeBeide(zeile(tr('detail.writesTo'), mitVollId(wf, s.srcW, zielMarke(s, 'w'))), ['srcW']);
|
|
326
376
|
|
|
327
377
|
/* --- Leseformel --- */
|
|
328
378
|
var fb = el('div');
|
|
@@ -436,7 +486,10 @@ export function detailZeile(e, s, idx) {
|
|
|
436
486
|
iU.value = s.unit || '';
|
|
437
487
|
iU.placeholder = '—';
|
|
438
488
|
iU.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
439
|
-
|
|
489
|
+
/* `rateBehalten` gehoert zu jedem `geaendert` dazu - sonst traegt die
|
|
490
|
+
Zeile beim naechsten Zeichnen beide Marken („geaendert" UND
|
|
491
|
+
„vermutet"), und die schliessen sich aus (Ricardo, 15.09.2026). */
|
|
492
|
+
iU.addEventListener('input', function () { s.unit = iU.value; s.geaendert = true; rateBehalten(s); });
|
|
440
493
|
iU.addEventListener('change', function () { neu(); });
|
|
441
494
|
var lU = el('label', 'fld');
|
|
442
495
|
lU.appendChild(el('span', null, tr('detail.unit')));
|
|
@@ -461,7 +514,7 @@ export function detailZeile(e, s, idx) {
|
|
|
461
514
|
iC.value = s.caption || '';
|
|
462
515
|
iC.placeholder = s.n;
|
|
463
516
|
iC.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
464
|
-
iC.addEventListener('input', function () { s.caption = iC.value; s.geaendert = true; });
|
|
517
|
+
iC.addEventListener('input', function () { s.caption = iC.value; s.geaendert = true; rateBehalten(s); });
|
|
465
518
|
/* Was das Feld tut, stand nirgends - man tippte und sah keine
|
|
466
519
|
Wirkung. Der Wert wird der Anzeigename des Punkts (common.name)
|
|
467
520
|
und erscheint als Zusatz in der Zeile darueber. */
|
|
@@ -496,7 +549,7 @@ export function detailZeile(e, s, idx) {
|
|
|
496
549
|
var t = typeof a.val;
|
|
497
550
|
var passt = !s.typ || s.typ === t || s.typ === 'mixed';
|
|
498
551
|
box.appendChild(el('span', 'chip ' + (passt ? 'ok' : 'warn'),
|
|
499
|
-
(passt ? '✓ ' : '≠ ') + (t === 'number' ?
|
|
552
|
+
(passt ? '✓ ' : '≠ ') + (t === 'number' ? tr('detail.typeNumber') : t)));
|
|
500
553
|
if (a.gewandelt) {
|
|
501
554
|
box.appendChild(document.createTextNode(' '));
|
|
502
555
|
box.appendChild(el('span', 'chip mut', tr('detail.converted')));
|