🚧 Change LED indicator alignments
This commit is contained in:
Vendored
+76
@@ -837,6 +837,82 @@
|
|||||||
],
|
],
|
||||||
"isBackground": false,
|
"isBackground": false,
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "wago-sim: pytest vertical",
|
||||||
|
"type": "shell",
|
||||||
|
"command": ".venv\\Scripts\\python.exe",
|
||||||
|
"args": [
|
||||||
|
"-m",
|
||||||
|
"pytest",
|
||||||
|
"tests",
|
||||||
|
"-q",
|
||||||
|
"--tb=short"
|
||||||
|
],
|
||||||
|
"isBackground": false,
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "wago-sim: serve vertical",
|
||||||
|
"type": "shell",
|
||||||
|
"command": ".venv\\Scripts\\python.exe",
|
||||||
|
"args": [
|
||||||
|
"-m",
|
||||||
|
"wago_sim.cli",
|
||||||
|
"serve",
|
||||||
|
"--node",
|
||||||
|
"config/pump_node.yaml",
|
||||||
|
"--host",
|
||||||
|
"127.0.0.1",
|
||||||
|
"--port",
|
||||||
|
"5020",
|
||||||
|
"--web-port",
|
||||||
|
"8088"
|
||||||
|
],
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "kill ports 3",
|
||||||
|
"type": "shell",
|
||||||
|
"command": ".venv\\Scripts\\python.exe",
|
||||||
|
"args": [
|
||||||
|
"-c",
|
||||||
|
"import subprocess; subprocess.run(['powershell','-NoProfile','-Command','Get-NetTCPConnection -LocalPort 5020,8088 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique | ForEach-Object { Stop-Process -Id $_ -Force }'], capture_output=True); print('killed')"
|
||||||
|
],
|
||||||
|
"isBackground": false,
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "wago-sim: serve vertical2",
|
||||||
|
"type": "shell",
|
||||||
|
"command": ".venv\\Scripts\\python.exe",
|
||||||
|
"args": [
|
||||||
|
"-m",
|
||||||
|
"wago_sim.cli",
|
||||||
|
"serve",
|
||||||
|
"--node",
|
||||||
|
"config/pump_node.yaml",
|
||||||
|
"--host",
|
||||||
|
"127.0.0.1",
|
||||||
|
"--port",
|
||||||
|
"5020",
|
||||||
|
"--web-port",
|
||||||
|
"8088"
|
||||||
|
],
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "stop vertical serve",
|
||||||
|
"type": "shell",
|
||||||
|
"command": ".venv\\Scripts\\python.exe",
|
||||||
|
"args": [
|
||||||
|
"-c",
|
||||||
|
"import subprocess; subprocess.run(['powershell','-NoProfile','-Command','Get-NetTCPConnection -LocalPort 5020,8088 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique | ForEach-Object { Stop-Process -Id $_ -Force }'], capture_output=True); print('stopped')"
|
||||||
|
],
|
||||||
|
"isBackground": false,
|
||||||
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -347,8 +347,8 @@ requirement ID for each item in parentheses.
|
|||||||
Signed support (2026-10-04): range bounds and simulated values
|
Signed support (2026-10-04): range bounds and simulated values
|
||||||
may be negative (-65534–65535; stored as 16-bit two's
|
may be negative (-65534–65535; stored as 16-bit two's
|
||||||
complement), for signed plant quantities. DI/DO channels render
|
complement), for signed plant quantities. DI/DO channels render
|
||||||
in two rows (0–7 upper, 8–15 lower), mirroring two-deck WAGO
|
in vertical columns of eight (0–7 left, 8–15 next) on thin,
|
||||||
modules.
|
tall module bezels mirroring real DIN-rail housings.
|
||||||
- [ ] M6: scenario run view + alias-ownership rejection (FR-10.2.6,
|
- [ ] M6: scenario run view + alias-ownership rejection (FR-10.2.6,
|
||||||
FR-10.2.9) — deferred.
|
FR-10.2.9) — deferred.
|
||||||
- [x] Tests: 46 web-UI tests (`tests/test_webui.py`) via httpx
|
- [x] Tests: 46 web-UI tests (`tests/test_webui.py`) via httpx
|
||||||
|
|||||||
@@ -897,9 +897,11 @@ Terminal monitors (§9) suffice for single engineers, but FAT benches are
|
|||||||
the channel indicators carry only the 0-based channel number: below
|
the channel indicators carry only the 0-based channel number: below
|
||||||
each DI/DO LED, and on analog modules outside the indicator stack,
|
each DI/DO LED, and on analog modules outside the indicator stack,
|
||||||
above the overflow and below the underflow LED (FR-10.2.14) so the
|
above the overflow and below the underflow LED (FR-10.2.14) so the
|
||||||
limit LEDs stay adjacent to the bar. DI/DO channels lay out in **two
|
limit LEDs stay adjacent to the bar. DI/DO channels lay out in
|
||||||
rows** — channels 0–7 on the upper row, 8–15 on the lower row —
|
**vertical columns of eight** — channels 0–7 in the left column,
|
||||||
mirroring the physical two-deck layout of 16-channel WAGO modules.
|
8–15 in the next — and modules are drawn as **thin, tall bezels**
|
||||||
|
mirroring the proportions of real WAGO DIN-rail housings (narrow
|
||||||
|
width, vertically stacked LEDs).
|
||||||
- FR-10.2.3: Values shall update live (WebSocket push or short-polling),
|
- FR-10.2.3: Values shall update live (WebSocket push or short-polling),
|
||||||
including changes made by the PLC/master over Modbus, by scenarios, by
|
including changes made by the PLC/master over Modbus, by scenarios, by
|
||||||
the console monitor, or by other UI clients — one shared source of truth.
|
the console monitor, or by other UI clients — one shared source of truth.
|
||||||
|
|||||||
@@ -271,9 +271,16 @@ function renderRail() {
|
|||||||
const el = document.createElement("div");
|
const el = document.createElement("div");
|
||||||
el.className = "module";
|
el.className = "module";
|
||||||
el.dataset.index = mod.index;
|
el.dataset.index = mod.index;
|
||||||
// width: 8 channels per row (two-deck layout), min 110px
|
const isDigital = mod.channels[0] &&
|
||||||
const perRow = Math.min(mod.channels.length, 8);
|
(mod.channels[0].kind === "di" || mod.channels[0].kind === "do");
|
||||||
el.style.width = `${Math.max(110, 30 * perRow)}px`;
|
// thin, tall bezels: digital modules get one 26px column per
|
||||||
|
// group of eight; analog modules keep their indicator-stack width
|
||||||
|
const digitalCount = mod.channels.filter(
|
||||||
|
(c) => c.kind === "di" || c.kind === "do").length;
|
||||||
|
const columns = Math.max(1, Math.ceil(digitalCount / 8));
|
||||||
|
el.style.width = isDigital
|
||||||
|
? `${Math.max(46, 26 * columns)}px`
|
||||||
|
: `${Math.max(90, 30 * mod.channels.length)}px`;
|
||||||
const kind = mod.channels[0] ? mod.channels[0].kind : "di";
|
const kind = mod.channels[0] ? mod.channels[0].kind : "di";
|
||||||
el.classList.add(`mod-${kind.startsWith("d") ? kind : "analog"}`);
|
el.classList.add(`mod-${kind.startsWith("d") ? kind : "analog"}`);
|
||||||
|
|
||||||
@@ -283,14 +290,15 @@ function renderRail() {
|
|||||||
const designation = firstCode.slice(0, firstCode.indexOf("."));
|
const designation = firstCode.slice(0, firstCode.indexOf("."));
|
||||||
|
|
||||||
let html = `<div class="mod-head" title="${mod.description}">${mod.article}</div>`;
|
let html = `<div class="mod-head" title="${mod.description}">${mod.article}</div>`;
|
||||||
html += `<div class="mod-channels">`;
|
html += `<div class="mod-channels ${isDigital ? "mod-channels-v" : ""}">`;
|
||||||
let chIndex = 0; // digital channels: two rows, 0-7 then 8-15
|
let chIndex = 0;
|
||||||
for (const ch of mod.channels) {
|
for (const ch of mod.channels) {
|
||||||
const id = `ch-${ch.kind}-${mod.index}-${channelNo(ch.address)}`;
|
const id = `ch-${ch.kind}-${mod.index}-${channelNo(ch.address)}`;
|
||||||
const chNo = String(channelNo(ch.address)); // 0-based channel only
|
const chNo = String(channelNo(ch.address)); // 0-based channel only
|
||||||
if (ch.kind === "di" || ch.kind === "do") {
|
if (isDigital) {
|
||||||
if (chIndex > 0 && chIndex % 8 === 0) {
|
if (chIndex > 0 && chIndex % 8 === 0) {
|
||||||
html += `<span class="row-break"></span>`; // start the 8-15 row
|
// new vertical column for channels 8-15
|
||||||
|
html += `</div><div class="mod-channels mod-channels-v col2">`;
|
||||||
}
|
}
|
||||||
html += `<span class="led-cell">
|
html += `<span class="led-cell">
|
||||||
<span class="led" id="${id}" title="${ch.name} (${ch.address}, MB ${ch.modbus})"
|
<span class="led" id="${id}" title="${ch.name} (${ch.address}, MB ${ch.modbus})"
|
||||||
|
|||||||
@@ -116,15 +116,32 @@ h2 { margin: 4px 0 14px; }
|
|||||||
background: #4caf50;
|
background: #4caf50;
|
||||||
box-shadow: 0 0 7px 2px rgba(76, 175, 80, 0.85);
|
box-shadow: 0 0 7px 2px rgba(76, 175, 80, 0.85);
|
||||||
}
|
}
|
||||||
.module { display: flex; flex-direction: column; justify-content: flex-end; }
|
.module {
|
||||||
|
/* thin, tall bezel: the module's channel columns sit side by side */
|
||||||
|
flex-shrink: 0; /* keep the computed width — do not squeeze rows */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
/* digital modules: tall enough for 8 vertically stacked LEDs + labels */
|
||||||
|
.module:has(.mod-channels-v) { min-height: 248px; }
|
||||||
|
.module { min-height: 124px; }
|
||||||
.mod-channels {
|
.mod-channels {
|
||||||
display: flex; gap: 5px; flex-wrap: wrap; align-items: flex-end;
|
display: flex; gap: 5px; flex-wrap: wrap; align-items: flex-end;
|
||||||
padding: 8px 6px 16px; /* bottom room for the designation label */
|
padding: 8px 6px 16px; /* bottom room for the designation label */
|
||||||
}
|
}
|
||||||
|
/* vertical digital columns: LEDs stacked top-to-bottom, 8 per column */
|
||||||
|
.mod-channels-v {
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 4px 16px; /* top room for the article header */
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
.row-break {
|
.row-break {
|
||||||
/* two-deck layout: force channels 8-15 onto a second row */
|
/* legacy two-row layout marker — vertical columns replace it */
|
||||||
flex-basis: 100%;
|
display: none;
|
||||||
height: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DI/DO LED cells with channel-number labels (FR-10.2.2) */
|
/* DI/DO LED cells with channel-number labels (FR-10.2.2) */
|
||||||
|
|||||||
Reference in New Issue
Block a user