feat(menu): globalize output-format picker + tighter asset wizard
iconHelpers.js: * Add SVG.outputProcess/outputJson/outputCsv/outputInflux icons. * Add renderOutputFormatPicker(select, holder) shared helper. * Add idempotent upgradeOutputFormatSelects() that scans for node-input-processOutputFormat / dbaseOutputFormat selects and replaces them with icon pickers — no per-node HTML edits required. * Redesign SVG.upstream/atEquipment/downstream to a pump volute + sensor marker matching the rotatingMachine pump banner style. menu/index.js: * Auto-invoke upgradeOutputFormatSelects from MenuManager's initEditor wrapper so every node inherits the picker. asset.js: * Tighter chip styling: 1px border, 4px radius, 3x8 padding, no uppercase transform — single-line "Label: Value" instead of stacked card. * Narrower Asset Tag input (max 200px); wizard max-width 460px. * Restore curve preview to 220x110 (was over-compacted). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -104,49 +104,55 @@ class AssetMenu {
|
||||
const id = 'evolv-asset-wizard-css';
|
||||
if (document.getElementById(id)) return;
|
||||
const css = [
|
||||
// Asset wizard — tightened layout (smaller radius/padding, no
|
||||
// uppercase label transform, single-line chip text) so the strip
|
||||
// reads as a compact form control instead of a row of pill cards.
|
||||
'.evolv-asset-hidden-natives { position:absolute !important; left:-9999px !important; height:0 !important; overflow:hidden; }',
|
||||
'.evolv-asset-wizard { display:flex; flex-direction:column; gap:10px; margin:6px 0 4px 0; }',
|
||||
'.evolv-asset-chips { display:flex; flex-wrap:wrap; gap:6px; align-items:center; }',
|
||||
'.evolv-asset-wizard { display:flex; flex-direction:column; gap:8px; margin:6px 0 4px 0; max-width:460px; }',
|
||||
'.evolv-asset-chips { display:flex; flex-wrap:wrap; gap:4px; align-items:center; }',
|
||||
'.evolv-asset-chip {',
|
||||
' display:flex; align-items:center; gap:8px;',
|
||||
' border:2px solid #d0d0d0; border-radius:18px; background:#fafafa;',
|
||||
' padding:6px 12px; cursor:pointer; user-select:none;',
|
||||
' font:inherit; color:#333;',
|
||||
' display:inline-flex; align-items:baseline; gap:6px;',
|
||||
' border:1px solid #d0d0d0; border-radius:4px; background:#fff;',
|
||||
' padding:3px 8px; cursor:pointer; user-select:none;',
|
||||
' font:inherit; color:#333; height:26px; box-sizing:border-box;',
|
||||
' transition:border-color 80ms ease-out, background 80ms ease-out;',
|
||||
'}',
|
||||
'.evolv-asset-chip:hover { border-color:#86bbdd; background:#f5fafd; }',
|
||||
'.evolv-asset-chip[aria-selected="true"] { border-color:#1F4E79; background:#eaf4fb; }',
|
||||
'.evolv-asset-chip[disabled] { opacity:0.5; cursor:not-allowed; }',
|
||||
'.evolv-asset-chip-icon { color:#1F4E79; font-size:14px; }',
|
||||
'.evolv-asset-chip-text { display:flex; flex-direction:column; line-height:1.15; text-align:left; }',
|
||||
'.evolv-asset-chip-label { font-size:10px; font-weight:600; color:#888; letter-spacing:0.5px; text-transform:uppercase; }',
|
||||
'.evolv-asset-chip-value { font-size:13px; font-weight:600; color:#1F4E79; max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }',
|
||||
'.evolv-asset-chip-value[data-empty="true"] { color:#aaa; font-weight:500; font-style:italic; }',
|
||||
'.evolv-asset-chip-sep { color:#aaa; font-size:18px; line-height:1; user-select:none; }',
|
||||
'.evolv-asset-combobox { display:flex; flex-direction:column; gap:4px; border:1px solid #d0d0d0; border-radius:4px; background:#fff; padding:8px; }',
|
||||
'.evolv-asset-combobox-search { width:100%; box-sizing:border-box; padding:6px 8px; border:1px solid #ccc; border-radius:3px; font:inherit; }',
|
||||
'.evolv-asset-chip-icon { color:#607484; font-size:11px; align-self:center; }',
|
||||
'.evolv-asset-chip-text { display:inline-flex; align-items:baseline; gap:5px; line-height:1; }',
|
||||
'.evolv-asset-chip-label { font-size:11px; font-weight:normal; color:#888; letter-spacing:0; text-transform:none; }',
|
||||
'.evolv-asset-chip-label::after { content:":"; color:#bbb; margin-left:1px; }',
|
||||
'.evolv-asset-chip-value { font-size:12px; font-weight:600; color:#1F4E79; max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }',
|
||||
'.evolv-asset-chip-value[data-empty="true"] { color:#aaa; font-weight:400; font-style:italic; }',
|
||||
'.evolv-asset-chip-sep { color:#bbb; font-size:13px; line-height:1; user-select:none; padding:0 2px; }',
|
||||
'.evolv-asset-combobox { display:flex; flex-direction:column; gap:4px; border:1px solid #d0d0d0; border-radius:3px; background:#fff; padding:6px; }',
|
||||
'.evolv-asset-combobox-search { width:100%; box-sizing:border-box; padding:5px 7px; border:1px solid #ccc; border-radius:3px; font:inherit; font-size:12px; }',
|
||||
'.evolv-asset-combobox-search:focus { outline:none; border-color:#1F4E79; box-shadow:0 0 0 2px rgba(31,78,121,0.15); }',
|
||||
'.evolv-asset-combobox-list { max-height:220px; overflow-y:auto; }',
|
||||
'.evolv-asset-combobox-list { max-height:200px; overflow-y:auto; }',
|
||||
'.evolv-asset-combobox-option {',
|
||||
' padding:6px 10px; cursor:pointer; border-radius:3px;',
|
||||
' font-size:13px; color:#333;',
|
||||
' padding:5px 8px; cursor:pointer; border-radius:2px;',
|
||||
' font-size:12px; color:#333;',
|
||||
'}',
|
||||
'.evolv-asset-combobox-option:hover,',
|
||||
'.evolv-asset-combobox-option.evolv-asset-combobox-option-active { background:#eaf4fb; color:#1F4E79; }',
|
||||
'.evolv-asset-combobox-empty { padding:6px 10px; color:#888; font-size:12px; font-style:italic; }',
|
||||
'.evolv-asset-summary { display:grid; grid-template-columns:1fr 200px; gap:12px; border:1px solid #e2e2e2; border-radius:4px; padding:10px 12px; background:#fafafa; align-items:center; }',
|
||||
'.evolv-asset-specs { font-size:12px; color:#333; display:flex; flex-direction:column; gap:3px; }',
|
||||
'.evolv-asset-combobox-empty { padding:5px 8px; color:#888; font-size:11px; font-style:italic; }',
|
||||
'.evolv-asset-summary { display:grid; grid-template-columns:1fr 220px; gap:10px; border:1px solid #e2e2e2; border-radius:3px; padding:8px 10px; background:#fafafa; align-items:center; }',
|
||||
'.evolv-asset-specs { font-size:11.5px; color:#333; display:flex; flex-direction:column; gap:2px; }',
|
||||
'.evolv-asset-spec-row { display:flex; gap:6px; }',
|
||||
'.evolv-asset-spec-key { color:#888; min-width:80px; }',
|
||||
'.evolv-asset-spec-key { color:#888; min-width:74px; }',
|
||||
'.evolv-asset-spec-val { color:#1F4E79; font-weight:600; }',
|
||||
'.evolv-asset-curve { width:200px; height:90px; }',
|
||||
'.evolv-asset-curve { width:220px; height:110px; }',
|
||||
'.evolv-asset-curve svg { width:100%; height:100%; display:block; }',
|
||||
'.evolv-asset-curve-empty { display:flex; align-items:center; justify-content:center; color:#aaa; font-size:11px; font-style:italic; text-align:center; }',
|
||||
'.evolv-asset-tag-row { margin-top:4px; }',
|
||||
'.evolv-asset-tag-row { margin-top:2px; align-items:center; }',
|
||||
'.evolv-asset-tag-row > label { width:110px; white-space:nowrap; }',
|
||||
'.evolv-asset-tag-row input[type=text] { width:auto !important; max-width:200px; min-width:140px; font-size:12px; padding:3px 6px; }',
|
||||
'@media (max-width:560px) {',
|
||||
' .evolv-asset-chips { flex-direction:column; align-items:stretch; }',
|
||||
' .evolv-asset-chip-sep { display:none; }',
|
||||
' .evolv-asset-chip { width:100%; }',
|
||||
' .evolv-asset-chip { width:100%; justify-content:flex-start; }',
|
||||
' .evolv-asset-summary { grid-template-columns:1fr; }',
|
||||
' .evolv-asset-curve { width:100%; }',
|
||||
'}'
|
||||
@@ -1020,7 +1026,7 @@ class AssetMenu {
|
||||
|
||||
<div class="form-row evolv-asset-tag-row">
|
||||
<label for="node-input-assetTagNumber"><i class="fa fa-hashtag"></i> Asset Tag</label>
|
||||
<input type="text" id="node-input-assetTagNumber" readonly style="width:70%;" />
|
||||
<input type="text" id="node-input-assetTagNumber" readonly />
|
||||
<div class="form-tips" id="node-input-assetTagNumber-hint">Not registered yet</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -97,29 +97,95 @@ class IconHelpers {
|
||||
<line x1="14" y1="12" x2="66" y2="46"/>
|
||||
</g>
|
||||
</svg>\`,
|
||||
// Position icons — depict the PARENT equipment (pump volute +
|
||||
// motor stub) plus a sensor marker located in the suction pipe
|
||||
// (upstream), atop the equipment (atEquipment), or in the
|
||||
// discharge pipe (downstream). Flow direction: left → right.
|
||||
upstream: \`
|
||||
<svg viewBox="0 0 80 58" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<rect x="4" y="22" width="42" height="14" fill="#f7fafc" stroke="\${STEEL}" stroke-width="2.4"/>
|
||||
<line x1="9" y1="29" x2="38" y2="29" stroke="\${BLUE}" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<path d="M33 25 L39 29 L33 33" fill="none" stroke="\${BLUE}" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="58" cy="29" r="12" fill="#f7fafc" stroke="\${STEEL}" stroke-width="2.4"/>
|
||||
<polygon points="52,22 52,36 66,29" fill="\${STEEL}"/>
|
||||
<!-- suction pipe + flow arrow -->
|
||||
<rect x="2" y="26" width="40" height="10" fill="#dde7f0" stroke="\${BLUE}" stroke-width="1.6"/>
|
||||
<line x1="6" y1="31" x2="34" y2="31" stroke="\${BLUE}" stroke-width="1.6"/>
|
||||
<polygon points="32,27 32,35 39,31" fill="\${BLUE}"/>
|
||||
<!-- sensor marker on suction pipe -->
|
||||
<line x1="20" y1="14" x2="20" y2="26" stroke="\${RED}" stroke-width="1.8"/>
|
||||
<circle cx="20" cy="11" r="5" fill="#fff" stroke="\${RED}" stroke-width="2"/>
|
||||
<circle cx="20" cy="11" r="1.6" fill="\${RED}"/>
|
||||
<!-- pump (volute) + impeller hint + motor stub -->
|
||||
<circle cx="60" cy="31" r="13" fill="#fff" stroke="\${STEEL}" stroke-width="2"/>
|
||||
<path d="M 60 22 Q 68 26 68 31 Q 68 36 60 40 Q 52 36 52 31 Q 52 26 60 22" fill="none" stroke="#86bbdd" stroke-width="1.3"/>
|
||||
<rect x="55" y="13" width="10" height="6" fill="\${STEEL}" stroke="#333" stroke-width="0.8"/>
|
||||
</svg>\`,
|
||||
atEquipment: \`
|
||||
<svg viewBox="0 0 80 58" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<circle cx="40" cy="34" r="13" fill="#f7fafc" stroke="\${STEEL}" stroke-width="2.4"/>
|
||||
<polygon points="33,25 33,43 48,34" fill="\${STEEL}"/>
|
||||
<line x1="40" y1="14" x2="40" y2="21" stroke="\${STEEL}" stroke-width="2"/>
|
||||
<circle cx="40" cy="9" r="6.5" fill="#fff" stroke="\${BLUE}" stroke-width="2.2"/>
|
||||
<line x1="34" y1="9" x2="46" y2="9" stroke="\${BLUE}" stroke-width="1.6"/>
|
||||
<!-- inlet stub -->
|
||||
<rect x="2" y="26" width="22" height="10" fill="#dde7f0" stroke="\${BLUE}" stroke-width="1.6"/>
|
||||
<line x1="4" y1="31" x2="20" y2="31" stroke="\${BLUE}" stroke-width="1.6"/>
|
||||
<polygon points="18,27 18,35 24,31" fill="\${BLUE}"/>
|
||||
<!-- outlet stub -->
|
||||
<rect x="56" y="26" width="22" height="10" fill="#dde7f0" stroke="\${BLUE}" stroke-width="1.6"/>
|
||||
<line x1="58" y1="31" x2="74" y2="31" stroke="\${BLUE}" stroke-width="1.6"/>
|
||||
<polygon points="72,27 72,35 78,31" fill="\${BLUE}"/>
|
||||
<!-- pump (volute) + impeller hint -->
|
||||
<circle cx="40" cy="31" r="13" fill="#fff" stroke="\${STEEL}" stroke-width="2"/>
|
||||
<path d="M 40 22 Q 48 26 48 31 Q 48 36 40 40 Q 32 36 32 31 Q 32 26 40 22" fill="none" stroke="#86bbdd" stroke-width="1.3"/>
|
||||
<!-- sensor marker AT equipment (top, on the volute itself) -->
|
||||
<line x1="40" y1="6" x2="40" y2="18" stroke="\${RED}" stroke-width="1.8"/>
|
||||
<circle cx="40" cy="6" r="5" fill="#fff" stroke="\${RED}" stroke-width="2"/>
|
||||
<circle cx="40" cy="6" r="1.6" fill="\${RED}"/>
|
||||
</svg>\`,
|
||||
downstream: \`
|
||||
<svg viewBox="0 0 80 58" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<circle cx="22" cy="29" r="12" fill="#f7fafc" stroke="\${STEEL}" stroke-width="2.4"/>
|
||||
<polygon points="16,22 16,36 30,29" fill="\${STEEL}"/>
|
||||
<rect x="34" y="22" width="42" height="14" fill="#f7fafc" stroke="\${STEEL}" stroke-width="2.4"/>
|
||||
<line x1="42" y1="29" x2="71" y2="29" stroke="\${BLUE}" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<path d="M65 25 L71 29 L65 33" fill="none" stroke="\${BLUE}" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<!-- pump (volute) + impeller hint + motor stub -->
|
||||
<circle cx="20" cy="31" r="13" fill="#fff" stroke="\${STEEL}" stroke-width="2"/>
|
||||
<path d="M 20 22 Q 28 26 28 31 Q 28 36 20 40 Q 12 36 12 31 Q 12 26 20 22" fill="none" stroke="#86bbdd" stroke-width="1.3"/>
|
||||
<rect x="15" y="13" width="10" height="6" fill="\${STEEL}" stroke="#333" stroke-width="0.8"/>
|
||||
<!-- discharge pipe + flow arrow -->
|
||||
<rect x="38" y="26" width="40" height="10" fill="#dde7f0" stroke="\${BLUE}" stroke-width="1.6"/>
|
||||
<line x1="42" y1="31" x2="70" y2="31" stroke="\${BLUE}" stroke-width="1.6"/>
|
||||
<polygon points="68,27 68,35 75,31" fill="\${BLUE}"/>
|
||||
<!-- sensor marker on discharge pipe -->
|
||||
<line x1="60" y1="14" x2="60" y2="26" stroke="\${RED}" stroke-width="1.8"/>
|
||||
<circle cx="60" cy="11" r="5" fill="#fff" stroke="\${RED}" stroke-width="2"/>
|
||||
<circle cx="60" cy="11" r="1.6" fill="\${RED}"/>
|
||||
</svg>\`,
|
||||
// Output-format icons — used by the shared
|
||||
// renderOutputFormatPicker helper so every node renders the
|
||||
// process/json/csv/influxdb dropdowns with the same visuals.
|
||||
outputProcess: \`
|
||||
<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>\`,
|
||||
outputJson: \`
|
||||
<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>\`,
|
||||
outputCsv: \`
|
||||
<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>\`,
|
||||
outputInflux: \`
|
||||
<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>\`,
|
||||
distance: \`
|
||||
<svg viewBox="0 0 80 58" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
@@ -229,7 +295,61 @@ class IconHelpers {
|
||||
sync();
|
||||
}
|
||||
|
||||
return { SVG, renderSelectPicker, renderToggle };
|
||||
// renderOutputFormatPicker: shared widget for the process &
|
||||
// dbase output-format <select>s carried by most EVOLV nodes.
|
||||
// Encapsulates the icon set + labels so every node renders the
|
||||
// same visuals. Pass the native <select> and an empty holder
|
||||
// <div class="evolv-icon-picker">.
|
||||
const OUTPUT_FORMAT_ICONS = {
|
||||
process: SVG.outputProcess,
|
||||
json: SVG.outputJson,
|
||||
csv: SVG.outputCsv,
|
||||
influxdb: SVG.outputInflux,
|
||||
};
|
||||
const OUTPUT_FORMAT_LABELS = {
|
||||
process: 'Process',
|
||||
json: 'JSON',
|
||||
csv: 'CSV',
|
||||
influxdb: 'Influx',
|
||||
};
|
||||
function renderOutputFormatPicker(select, holder) {
|
||||
renderSelectPicker(select, holder, OUTPUT_FORMAT_ICONS, OUTPUT_FORMAT_LABELS);
|
||||
}
|
||||
|
||||
// upgradeOutputFormatSelects: idempotent platform-wide upgrade.
|
||||
// Scans the open editor dialog for the two canonical output-format
|
||||
// selects and replaces each with the icon picker. Skips selects
|
||||
// that are already upgraded (class evolv-native-hidden) or that
|
||||
// already have a sibling picker placed by the node's HTML.
|
||||
// Called from MenuManager's initEditor wrapper so every node
|
||||
// inherits the picker without per-node template edits.
|
||||
function upgradeOutputFormatSelects() {
|
||||
const specs = [
|
||||
{ id: 'node-input-processOutputFormat', aria: 'Process output format' },
|
||||
{ id: 'node-input-dbaseOutputFormat', aria: 'Database output format' }
|
||||
];
|
||||
specs.forEach((spec) => {
|
||||
const select = document.getElementById(spec.id);
|
||||
if (!select) return;
|
||||
if (select.classList && select.classList.contains('evolv-native-hidden')) return;
|
||||
const parent = select.parentNode;
|
||||
if (!parent) return;
|
||||
// Skip if a sibling picker already exists (manual wiring).
|
||||
const siblings = parent.children || [];
|
||||
for (let i = 0; i < siblings.length; i += 1) {
|
||||
const sib = siblings[i];
|
||||
if (sib !== select && sib.classList && sib.classList.contains('evolv-icon-picker')) return;
|
||||
}
|
||||
const holder = document.createElement('div');
|
||||
holder.className = 'evolv-icon-picker';
|
||||
holder.setAttribute('role', 'radiogroup');
|
||||
holder.setAttribute('aria-label', spec.aria);
|
||||
parent.appendChild(holder);
|
||||
renderOutputFormatPicker(select, holder);
|
||||
});
|
||||
}
|
||||
|
||||
return { SVG, renderSelectPicker, renderToggle, renderOutputFormatPicker, upgradeOutputFormatSelects };
|
||||
})();
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -175,6 +175,18 @@ class MenuManager {
|
||||
} catch (${type}Error) {
|
||||
console.error('Error initializing ${type} menu for ${nodeName}:', ${type}Error);
|
||||
}`).join('')}
|
||||
|
||||
// Platform-wide: upgrade output-format <select>s
|
||||
// (process/dbase) to icon pickers. Idempotent — no-op
|
||||
// for nodes whose HTML already wires the picker, and
|
||||
// skips when the selects aren't present.
|
||||
try {
|
||||
if (window.EVOLV && window.EVOLV.iconHelpers && window.EVOLV.iconHelpers.upgradeOutputFormatSelects) {
|
||||
window.EVOLV.iconHelpers.upgradeOutputFormatSelects();
|
||||
}
|
||||
} catch (outputUpgradeError) {
|
||||
console.error('Error upgrading output-format selects for ${nodeName}:', outputUpgradeError);
|
||||
}
|
||||
} catch (editorError) {
|
||||
console.error('Error in main editor initialization for ${nodeName}:', editorError);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user