feat(mgc): consume shared icon-picker visuals + modernize editor menu
* compact-fields.js (new): trimmed to MGC-only output-format pickers (processOutputFormat / dbaseOutputFormat). The logger toggle/level and physical-position visuals now come from generalFunctions' shared iconHelpers, auto-injected via /machineGroupControl/menu.js. * mode-cards.js: strategy cards re-styled — Most-efficient (BEP bell with dot on the curve peak), Priority (clean staircase), Maintenance (Font Awesome fa-wrench). Rendezvous toggle flips Active / Inactive label dynamically. * mgc.html: dropped the duplicated .mgc-icon-* CSS rules (now live in the shared iconHelpers stylesheet). Strategy + rendezvous CSS stays local (MGC-specific). Output picker holders switched to the shared .evolv-icon-picker class. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
84
mgc.html
84
mgc.html
@@ -15,24 +15,41 @@
|
||||
dependency order: index.js (namespace + helpers) → modules → oneditprepare. -->
|
||||
<script src="/machineGroupControl/editor/index.js"></script>
|
||||
<script src="/machineGroupControl/editor/mode-cards.js"></script>
|
||||
<script src="/machineGroupControl/editor/compact-fields.js"></script>
|
||||
<script src="/machineGroupControl/editor/oneditprepare.js"></script>
|
||||
|
||||
<style>
|
||||
/* Mode-card picker. Three cards stack horizontally; on a narrow editor pane
|
||||
they wrap. Selected card gets a thick #50a8d9 (Unit-colour) border. */
|
||||
.mgc-mode-cards { display:flex; gap:8px; flex-wrap:wrap; margin:6px 0 4px 0; }
|
||||
.mgc-mode-card {
|
||||
flex:1 1 0; min-width:140px; box-sizing:border-box;
|
||||
/* MGC-specific UI: strategy mode cards + rendezvous toggle.
|
||||
Generic .evolv-icon-picker / .evolv-icon-option styles for the
|
||||
output-format pickers come from generalFunctions' iconHelpers (auto-
|
||||
injected by /menu.js). */
|
||||
.mgc-mode-cards,
|
||||
.mgc-toggle-row { display:flex; gap:6px; flex-wrap:wrap; margin:6px 0 4px 0; }
|
||||
.mgc-mode-card,
|
||||
.mgc-toggle-card {
|
||||
width:94px; height:86px; box-sizing:border-box;
|
||||
border:2px solid #d0d0d0; border-radius:4px; background:#fafafa;
|
||||
padding:6px 8px 8px 8px; cursor:pointer; user-select:none;
|
||||
display:flex; flex-direction:column; gap:4px;
|
||||
padding:4px; cursor:pointer; user-select:none;
|
||||
display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
|
||||
transition:border-color 80ms ease-out, background 80ms ease-out;
|
||||
}
|
||||
.mgc-mode-card:hover { border-color:#86bbdd; background:#f5fafd; }
|
||||
.mgc-mode-card-on { border-color:#50a8d9; background:#eaf4fb; }
|
||||
.mgc-mode-card-svg svg { width:100%; height:auto; max-height:90px; display:block; }
|
||||
.mgc-mode-card-label { font-weight:600; font-size:12px; color:#333; }
|
||||
.mgc-mode-card-caption { font-size:10px; color:#666; line-height:1.3; }
|
||||
.mgc-mode-card:hover,
|
||||
.mgc-toggle-card:hover { border-color:#86bbdd; background:#f5fafd; }
|
||||
.mgc-mode-card:focus,
|
||||
.mgc-toggle-card:focus { outline:2px solid #1F4E79; outline-offset:2px; }
|
||||
.mgc-mode-card-on,
|
||||
.mgc-toggle-card-on { border-color:#50a8d9; background:#eaf4fb; }
|
||||
.mgc-mode-card-svg,
|
||||
.mgc-toggle-card-svg { width:100%; height:54px; display:flex; align-items:center; justify-content:center; }
|
||||
.mgc-mode-card-svg svg,
|
||||
.mgc-toggle-card-svg svg { width:100%; height:100%; display:block; }
|
||||
.mgc-mode-card-label,
|
||||
.mgc-toggle-card-label { font-size:10px; line-height:1; font-weight:600; color:#333; white-space:nowrap; letter-spacing:0; }
|
||||
.mgc-toggle-card:not(.mgc-toggle-card-on) .mgc-toggle-card-svg { opacity:0.45; filter:grayscale(1); }
|
||||
.mgc-toggle-card:not(.mgc-toggle-card-on) .mgc-toggle-card-label { color:#888; }
|
||||
.mgc-hidden-checkbox { position:absolute; opacity:0; width:1px; height:1px; pointer-events:none; }
|
||||
.mgc-section-divider { border:0; border-top:1px solid #d6d6d6; margin:12px 0; }
|
||||
.mgc-output-row > label { white-space:nowrap; width:130px; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -89,7 +106,7 @@
|
||||
// Initialize the menu data for the node, then the visual modules.
|
||||
// Both attach to window.EVOLV.nodes.machineGroupControl.* — the
|
||||
// menu endpoint populates loggerMenu/positionMenu/initEditor; the
|
||||
// editor scripts populate editor.modeCards/demandContract.
|
||||
// editor scripts populate editor.modeCards/rendezvousToggle/compactFields.
|
||||
const waitForMenuData = () => {
|
||||
if (window.EVOLV?.nodes?.machineGroupControl?.initEditor) {
|
||||
window.EVOLV.nodes.machineGroupControl.initEditor(self);
|
||||
@@ -133,48 +150,39 @@
|
||||
role="radiogroup" aria-label="Control strategy mode">
|
||||
<!-- mode-cards.js renders three card divs here -->
|
||||
</div>
|
||||
|
||||
<p style="margin-top:8px;color:#666;font-size:11px;">
|
||||
Demand is self-describing per <code>set.demand</code> message: a bare number is
|
||||
treated as % of group capacity; <code>{value, unit}</code> with a flow unit
|
||||
(<code>m3/h</code>, <code>l/s</code>, <code>m3/s</code>, …) is dispatched
|
||||
in absolute terms. Negative value stops all pumps.
|
||||
</p>
|
||||
<hr class="mgc-section-divider" />
|
||||
|
||||
<h3>Rendezvous planner</h3>
|
||||
<div class="form-row" style="display:flex;align-items:center;gap:8px;">
|
||||
<input type="checkbox" id="node-input-useRendezvous"
|
||||
style="width:auto;margin:0;vertical-align:middle;" />
|
||||
<label for="node-input-useRendezvous" style="width:auto;margin:0;cursor:pointer;">
|
||||
Same-time landing
|
||||
</label>
|
||||
<div class="form-row mgc-toggle-row">
|
||||
<input type="checkbox" id="node-input-useRendezvous" class="mgc-hidden-checkbox" />
|
||||
<div id="mgc-rendezvous-toggle" class="mgc-toggle-card"
|
||||
role="switch" tabindex="0" aria-label="Same-time landing"
|
||||
aria-checked="false" title="Same-time landing"></div>
|
||||
</div>
|
||||
<p style="margin-top:4px;color:#666;font-size:11px;">
|
||||
When enabled (default), every dispatch is routed through the rendezvous
|
||||
planner regardless of control strategy: per-pump moves are delayed so all
|
||||
pumps reach their setpoint at the same wall-clock instant
|
||||
<code>t* = max(eta<sub>i</sub>)</code>. When disabled, every
|
||||
<code>flowmovement</code> fires immediately and each pump ramps at its
|
||||
own configured reaction speed (legacy behaviour).
|
||||
</p>
|
||||
<hr class="mgc-section-divider" />
|
||||
|
||||
<h3>Output Formats</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-row mgc-output-row">
|
||||
<label for="node-input-processOutputFormat"><i class="fa fa-random"></i> Process Output</label>
|
||||
<select id="node-input-processOutputFormat" style="width:60%;">
|
||||
<select id="node-input-processOutputFormat" class="evolv-native-hidden" style="width:60%;">
|
||||
<option value="process">process</option>
|
||||
<option value="json">json</option>
|
||||
<option value="csv">csv</option>
|
||||
</select>
|
||||
<div id="mgc-process-output-picker" class="evolv-icon-picker"
|
||||
role="radiogroup" aria-label="Process output format"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-row mgc-output-row">
|
||||
<label for="node-input-dbaseOutputFormat"><i class="fa fa-database"></i> Database Output</label>
|
||||
<select id="node-input-dbaseOutputFormat" style="width:60%;">
|
||||
<select id="node-input-dbaseOutputFormat" class="evolv-native-hidden" style="width:60%;">
|
||||
<option value="influxdb">influxdb</option>
|
||||
<option value="json">json</option>
|
||||
<option value="csv">csv</option>
|
||||
</select>
|
||||
<div id="mgc-dbase-output-picker" class="evolv-icon-picker"
|
||||
role="radiogroup" aria-label="Database output format"></div>
|
||||
</div>
|
||||
<hr class="mgc-section-divider" />
|
||||
|
||||
<!-- Logger fields injected here -->
|
||||
<div id="logger-fields-placeholder"></div>
|
||||
|
||||
92
src/editor/compact-fields.js
Normal file
92
src/editor/compact-fields.js
Normal file
@@ -0,0 +1,92 @@
|
||||
// compact-fields.js — MGC-only output-format icon picker.
|
||||
//
|
||||
// Logger toggle/level and physical-position visuals now live in the shared
|
||||
// generalFunctions/src/menu/iconHelpers.js (auto-injected by MenuManager), so
|
||||
// the only MGC-local visuals left are the two output-format dropdowns
|
||||
// (processOutputFormat, dbaseOutputFormat) — those fields aren't part of any
|
||||
// shared menu.
|
||||
|
||||
(function () {
|
||||
const editor = window.EVOLV?.nodes?.machineGroupControl?.editor;
|
||||
if (!editor) return;
|
||||
|
||||
const BLUE = '#1F4E79';
|
||||
const STEEL = '#607484';
|
||||
|
||||
// MGC-only SVGs (output formats only — logger/position SVGs come from
|
||||
// window.EVOLV.iconHelpers.SVG).
|
||||
const SVG = {
|
||||
process: `
|
||||
<svg viewBox="0 0 80 58" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<rect x="10" y="14" width="20" height="30" rx="2" fill="#f7fafc" stroke="${STEEL}" stroke-width="2.4"/>
|
||||
<rect x="50" y="14" width="20" height="30" rx="2" fill="#f7fafc" stroke="${STEEL}" stroke-width="2.4"/>
|
||||
<line x1="30" y1="29" x2="46" y2="29" stroke="${BLUE}" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M42 24 L48 29 L42 34" fill="none" stroke="${BLUE}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>`,
|
||||
json: `
|
||||
<svg viewBox="0 0 80 58" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<g fill="none" stroke="${BLUE}" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M30 14 C22 16 22 26 27 29 C22 32 22 42 30 44"/>
|
||||
<path d="M50 14 C58 16 58 26 53 29 C58 32 58 42 50 44"/>
|
||||
</g>
|
||||
<g fill="${STEEL}">
|
||||
<circle cx="36" cy="29" r="2.2"/>
|
||||
<circle cx="44" cy="29" r="2.2"/>
|
||||
</g>
|
||||
</svg>`,
|
||||
csv: `
|
||||
<svg viewBox="0 0 80 58" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<rect x="12" y="12" width="56" height="34" rx="2" fill="#fff" stroke="${STEEL}" stroke-width="2.4"/>
|
||||
<line x1="12" y1="22" x2="68" y2="22" stroke="${STEEL}" stroke-width="2"/>
|
||||
<g stroke="${STEEL}" stroke-width="1.6">
|
||||
<line x1="12" y1="34" x2="68" y2="34"/>
|
||||
<line x1="31" y1="12" x2="31" y2="46"/>
|
||||
<line x1="49" y1="12" x2="49" y2="46"/>
|
||||
</g>
|
||||
</svg>`,
|
||||
influxdb: `
|
||||
<svg viewBox="0 0 80 58" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<ellipse cx="40" cy="15" rx="22" ry="6" fill="#f7fafc" stroke="${STEEL}" stroke-width="2.4"/>
|
||||
<path d="M18 15 V42 C18 46 28 49 40 49 C52 49 62 46 62 42 V15" fill="#f7fafc" stroke="${STEEL}" stroke-width="2.4"/>
|
||||
<path d="M18 28 C26 32 54 32 62 28" fill="none" stroke="${STEEL}" stroke-width="1.6" opacity="0.6"/>
|
||||
<path d="M22 39 L30 32 L38 41 L46 34 L54 38" fill="none" stroke="${BLUE}" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>`,
|
||||
};
|
||||
|
||||
const outputIcons = {
|
||||
process: SVG.process,
|
||||
json: SVG.json,
|
||||
csv: SVG.csv,
|
||||
influxdb: SVG.influxdb,
|
||||
};
|
||||
|
||||
const outputLabels = {
|
||||
process: 'Process',
|
||||
json: 'JSON',
|
||||
csv: 'CSV',
|
||||
influxdb: 'Influx',
|
||||
};
|
||||
|
||||
function initOutputFormats() {
|
||||
const helpers = window.EVOLV?.iconHelpers;
|
||||
if (!helpers) return;
|
||||
|
||||
const processSelect = document.getElementById('node-input-processOutputFormat');
|
||||
const processHolder = document.getElementById('mgc-process-output-picker');
|
||||
if (processSelect && processHolder) {
|
||||
helpers.renderSelectPicker(processSelect, processHolder, outputIcons, outputLabels);
|
||||
}
|
||||
|
||||
const dbaseSelect = document.getElementById('node-input-dbaseOutputFormat');
|
||||
const dbaseHolder = document.getElementById('mgc-dbase-output-picker');
|
||||
if (dbaseSelect && dbaseHolder) {
|
||||
helpers.renderSelectPicker(dbaseSelect, dbaseHolder, outputIcons, outputLabels);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
initOutputFormats();
|
||||
}
|
||||
|
||||
editor.compactFields = { init };
|
||||
})();
|
||||
@@ -1,14 +1,10 @@
|
||||
// mode-cards.js — visual radio picker for the three control-strategy modes.
|
||||
// mode-cards.js — visual pickers for control-strategy modes and planner flags.
|
||||
//
|
||||
// Replaces the plain <select id="node-input-mode"> with three illustrated
|
||||
// cards. The original <input> stays in the DOM but is hidden — Node-RED reads
|
||||
// its value on save, exactly as before. Clicking a card sets that value and
|
||||
// fires editor.emitModeChange so downstream UI (none today, future widgets
|
||||
// such as a parameter panel) can re-render.
|
||||
// Replaces the plain mode field with compact illustrated controls. The
|
||||
// original inputs stay in the DOM but are hidden — Node-RED reads their values
|
||||
// on save, exactly as before.
|
||||
//
|
||||
// Three cards: optimalControl (BEP-curve), priorityControl (flow ladder),
|
||||
// maintenance (status-only badge). SVGs are inline so the editor doesn't
|
||||
// need to fetch additional assets.
|
||||
// SVGs are inline so the editor doesn't need to fetch additional assets.
|
||||
|
||||
(function () {
|
||||
const editor = window.EVOLV?.nodes?.machineGroupControl?.editor;
|
||||
@@ -17,95 +13,61 @@
|
||||
const MODES = [
|
||||
{
|
||||
value: 'optimalControl',
|
||||
label: 'optimalControl',
|
||||
caption: 'Picks the pump combination whose BEP sits closest to current demand.',
|
||||
ariaLabel: 'Optimal control',
|
||||
label: 'Most-efficient',
|
||||
svg: `
|
||||
<svg viewBox="0 0 160 90" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<text x="6" y="14" font-size="9" fill="#444">η</text>
|
||||
<line x1="14" y1="78" x2="154" y2="78" stroke="#444" stroke-width="1"/>
|
||||
<line x1="14" y1="78" x2="14" y2="14" stroke="#444" stroke-width="1"/>
|
||||
<text x="118" y="88" font-size="8" fill="#666">demand →</text>
|
||||
|
||||
<!-- Three pump-combination efficiency humps. Each combination has
|
||||
its own BEP (peak). The optimizer "gravitates" toward whichever
|
||||
peak sits closest to the current demand. Quadratic-Bezier peak
|
||||
formula: peak_y = (y0 + 2*cy + y1)/4 — so for y0=y1=78 (foot on
|
||||
x-axis), cy=22 → peak_y=50, cy=-26 → peak_y=26, cy=10 → peak_y=44. -->
|
||||
<path d="M 16 78 Q 32 22 50 78" fill="none" stroke="#888" stroke-width="1.1"/>
|
||||
<path d="M 44 78 Q 72 -26 100 78" fill="none" stroke="#1E8449" stroke-width="2"/>
|
||||
<path d="M 92 78 Q 122 10 152 78" fill="none" stroke="#888" stroke-width="1.1"/>
|
||||
|
||||
<!-- BEP markers sit ON each hump's apex — small grey for unpicked
|
||||
combos, large red for the selected (winner) combination. -->
|
||||
<circle cx="33" cy="50" r="2" fill="#888"/>
|
||||
<circle cx="72" cy="26" r="3.2" fill="#C0392B" stroke="#fff" stroke-width="1"/>
|
||||
<circle cx="122" cy="44" r="2" fill="#888"/>
|
||||
|
||||
<!-- Current demand (dashed line) lines up with combo #2's BEP, so
|
||||
combo #2 wins — drawn thicker/green above. -->
|
||||
<line x1="72" y1="14" x2="72" y2="78" stroke="#1F4E79" stroke-dasharray="2 2" stroke-width="0.9"/>
|
||||
<text x="46" y="20" font-size="7" fill="#1F4E79">demand</text>
|
||||
<text x="80" y="22" font-size="7" fill="#C0392B" font-weight="bold">BEP</text>
|
||||
|
||||
<!-- Combination labels under each curve. -->
|
||||
<text x="33" y="86" font-size="6" fill="#666" text-anchor="middle">P1</text>
|
||||
<text x="72" y="86" font-size="6" fill="#1E8449" text-anchor="middle" font-weight="bold">P1+P2</text>
|
||||
<text x="122" y="86" font-size="6" fill="#666" text-anchor="middle">P1+P2+P3</text>
|
||||
<svg viewBox="0 0 120 72" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<line x1="12" y1="60" x2="112" y2="60" stroke="#888" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<line x1="12" y1="60" x2="12" y2="10" stroke="#888" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<path d="M 16 60 Q 62 -30 108 60" fill="none" stroke="#1E8449" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="62" y1="15" x2="62" y2="60" stroke="#1F4E79" stroke-dasharray="3 3" stroke-width="1.2"/>
|
||||
<circle cx="62" cy="15" r="5.5" fill="#1E8449" stroke="#fff" stroke-width="1.6"/>
|
||||
</svg>`,
|
||||
},
|
||||
{
|
||||
value: 'priorityControl',
|
||||
label: 'priorityControl',
|
||||
caption: 'Sequential equal-flow ramp — fill pumps one-by-one in priority order.',
|
||||
ariaLabel: 'Priority control',
|
||||
label: 'Priority',
|
||||
svg: `
|
||||
<svg viewBox="0 0 160 90" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<text x="6" y="14" font-size="9" fill="#444">flow</text>
|
||||
<line x1="14" y1="78" x2="154" y2="78" stroke="#444" stroke-width="1"/>
|
||||
<line x1="14" y1="78" x2="14" y2="14" stroke="#444" stroke-width="1"/>
|
||||
<text x="118" y="88" font-size="8" fill="#666">demand →</text>
|
||||
<polyline points="14,72 50,72 50,52 86,52 86,32 122,32 122,16 154,16"
|
||||
fill="none" stroke="#1F4E79" stroke-width="2"/>
|
||||
<text x="28" y="86" font-size="7" fill="#666">P1</text>
|
||||
<text x="64" y="86" font-size="7" fill="#666">P2</text>
|
||||
<text x="100" y="86" font-size="7" fill="#666">P3</text>
|
||||
<svg viewBox="0 0 120 72" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<line x1="12" y1="60" x2="112" y2="60" stroke="#888" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<line x1="12" y1="60" x2="12" y2="10" stroke="#888" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<polyline points="14,54 38,54 38,40 62,40 62,26 86,26 86,14 110,14"
|
||||
fill="none" stroke="#1F4E79" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>`,
|
||||
},
|
||||
{
|
||||
value: 'maintenance',
|
||||
label: 'maintenance',
|
||||
caption: 'Monitor only. Dispatch and stop-all commands are rejected; status messages still flow.',
|
||||
svg: `
|
||||
<svg viewBox="0 0 160 90" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<circle cx="80" cy="42" r="22" fill="none" stroke="#888" stroke-width="2"/>
|
||||
<circle cx="80" cy="42" r="8" fill="#888"/>
|
||||
<g stroke="#888" stroke-width="3" stroke-linecap="round">
|
||||
<line x1="80" y1="14" x2="80" y2="24"/>
|
||||
<line x1="80" y1="60" x2="80" y2="70"/>
|
||||
<line x1="52" y1="42" x2="62" y2="42"/>
|
||||
<line x1="98" y1="42" x2="108" y2="42"/>
|
||||
<line x1="60" y1="22" x2="67" y2="29"/>
|
||||
<line x1="93" y1="55" x2="100" y2="62"/>
|
||||
<line x1="60" y1="62" x2="67" y2="55"/>
|
||||
<line x1="93" y1="29" x2="100" y2="22"/>
|
||||
</g>
|
||||
<text x="80" y="84" text-anchor="middle" font-size="8" fill="#888">monitor only</text>
|
||||
</svg>`,
|
||||
ariaLabel: 'Maintenance',
|
||||
label: 'Maintenance',
|
||||
svg: `<i class="fa fa-wrench" style="font-size:40px;color:#607484;" aria-hidden="true"></i>`,
|
||||
},
|
||||
];
|
||||
|
||||
// Render the three cards into the placeholder div. The hidden <select> stays
|
||||
// intact — the card click handler writes its value back to that <select> so
|
||||
// Node-RED's save path is unchanged.
|
||||
const RENDEZVOUS_SVG = `
|
||||
<svg viewBox="0 0 120 72" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<line x1="12" y1="60" x2="112" y2="60" stroke="#888" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<line x1="12" y1="60" x2="12" y2="10" stroke="#888" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<line x1="96" y1="12" x2="96" y2="60" stroke="#1F4E79" stroke-dasharray="3 3" stroke-width="1.4"/>
|
||||
<path d="M 18 52 C 38 50, 64 38, 96 20" fill="none" stroke="#1E8449" stroke-width="2.6" stroke-linecap="round"/>
|
||||
<path d="M 18 58 C 40 56, 64 42, 96 20" fill="none" stroke="#50a8d9" stroke-width="2.6" stroke-linecap="round"/>
|
||||
<path d="M 18 44 C 42 44, 66 34, 96 20" fill="none" stroke="#C0392B" stroke-width="2.6" stroke-linecap="round"/>
|
||||
<circle cx="96" cy="20" r="6" fill="#1F4E79" stroke="#fff" stroke-width="1.6"/>
|
||||
</svg>`;
|
||||
|
||||
// Render the three cards into the placeholder div. The hidden input stays
|
||||
// intact; the card click handler writes its value back so Node-RED's save
|
||||
// path is unchanged.
|
||||
function init(/* node */) {
|
||||
const placeholder = document.getElementById('mgc-mode-cards');
|
||||
const hidden = document.getElementById('node-input-mode');
|
||||
if (!placeholder || !hidden) return;
|
||||
|
||||
placeholder.innerHTML = MODES.map((m) => `
|
||||
<div class="mgc-mode-card" data-mode="${m.value}" role="radio" tabindex="0" aria-checked="false">
|
||||
<div class="mgc-mode-card" data-mode="${m.value}" role="radio" tabindex="0"
|
||||
aria-label="${m.ariaLabel}" aria-checked="false" title="${m.ariaLabel}">
|
||||
<div class="mgc-mode-card-svg">${m.svg}</div>
|
||||
<div class="mgc-mode-card-label">${m.label}</div>
|
||||
<div class="mgc-mode-card-caption">${m.caption}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
@@ -138,5 +100,40 @@
|
||||
syncHighlight();
|
||||
}
|
||||
|
||||
function initRendezvousToggle(/* node */) {
|
||||
const placeholder = document.getElementById('mgc-rendezvous-toggle');
|
||||
const checkbox = document.getElementById('node-input-useRendezvous');
|
||||
if (!placeholder || !checkbox) return;
|
||||
|
||||
placeholder.innerHTML = `
|
||||
<div class="mgc-toggle-card-svg">${RENDEZVOUS_SVG}</div>
|
||||
<div class="mgc-toggle-card-label">Inactive</div>
|
||||
`;
|
||||
const labelEl = placeholder.querySelector('.mgc-toggle-card-label');
|
||||
|
||||
function syncHighlight() {
|
||||
const on = checkbox.checked;
|
||||
placeholder.classList.toggle('mgc-toggle-card-on', on);
|
||||
placeholder.setAttribute('aria-checked', String(on));
|
||||
if (labelEl) labelEl.textContent = on ? 'Active' : 'Inactive';
|
||||
}
|
||||
function toggle() {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
syncHighlight();
|
||||
}
|
||||
|
||||
placeholder.addEventListener('click', toggle);
|
||||
placeholder.addEventListener('keydown', (e) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
toggle();
|
||||
}
|
||||
});
|
||||
checkbox.addEventListener('change', syncHighlight);
|
||||
syncHighlight();
|
||||
}
|
||||
|
||||
editor.modeCards = { init };
|
||||
editor.rendezvousToggle = { init: initRendezvousToggle };
|
||||
})();
|
||||
|
||||
@@ -12,5 +12,11 @@
|
||||
if (ns.editor.modeCards && typeof ns.editor.modeCards.init === 'function') {
|
||||
ns.editor.modeCards.init(node);
|
||||
}
|
||||
if (ns.editor.rendezvousToggle && typeof ns.editor.rendezvousToggle.init === 'function') {
|
||||
ns.editor.rendezvousToggle.init(node);
|
||||
}
|
||||
if (ns.editor.compactFields && typeof ns.editor.compactFields.init === 'function') {
|
||||
ns.editor.compactFields.init(node);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user