Bounds (new src/editor/bounds.js):
- Sets HTML5 min/max on every level + percent input each redraw,
derived from the current values of related inputs so the spinner
stops at the basin hierarchy:
0 < outflowLevel < dryRunLevel < startLevel ≤ inflowLevel
≤ shiftLevel ≤ maxLevel ≤ overflowLevel ≤ basinHeight
- dryRunPercent capped so dryRunLevel ≤ startLevel given current outflow.
- shiftArmPercent ∈ [1, 100]; highVolumeSafety% ∈ [1, 100].
Validation:
- New visible ribbon above the basin diagram (#ps-basin-validation)
listing every hierarchy violation. The in-SVG warning text is now a
small reminder ("⚠ N ordering issues").
- basin-diagram.js owns hierarchy issues; mode-preview.js trimmed to
only own shift-specific issues (shift > start, shift ≤ max,
shiftArmPercent range, shiftLevel required-when-enabled).
- oneditsave blocks Deploy on the union of _psBasinValidationIssues
and _psModeValidationIssues with a RED.notify listing all problems.
Layout polish:
- Side panel widened to 220 px with minmax(0, 1fr) first column so long
labels can no longer push the rows past the panel edge.
- Basin SVG max-width 380 → 360, gap between side panel and SVG bumped
14 → 28 px. Tank shifted right (x=145 width=110) so the inlet
"bottom of pipe" sub-label is no longer clipped on the left edge.
- "0 m (datum)" moved below the tank (y=395, centred) so it can't
collide with "Outlet / top of pipe" when outflowLevel is near floor.
- Zone labels shortened (Spare / Sewage + buffer / Buffer / Dead vol)
and only show when the bracketing thresholds are ≥ 28 px apart, so
they never sit on a threshold label.
- Mode preview axis labels under the chart removed — line colour +
side-panel labels + hover-couple already identify each line. Stub
<text> elements left hidden to keep the redraw loop simple. Arm-%
line + label trimmed in 10 px on the right so they're not clipped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
3.5 KiB
JavaScript
70 lines
3.5 KiB
JavaScript
// PumpingStation editor — oneditsave handler. Validates, saves shared
|
||
// menu sections (logger/position), then persists pumpingStation-specific
|
||
// fields onto the node. Throws if validation fails to keep the editor open.
|
||
|
||
(function () {
|
||
const ns = window.PSEditor = window.PSEditor || {};
|
||
|
||
const parseNum = (id) => parseFloat(document.getElementById(id)?.value);
|
||
|
||
ns.oneditsave = function () {
|
||
const node = this;
|
||
|
||
// Block save if EITHER validator surfaced any issues. basin-diagram
|
||
// owns hierarchy issues (start ≤ inlet ≤ max ≤ overflow ≤ basinHeight,
|
||
// dryRun < start). mode-preview owns shift-specific issues.
|
||
const basinIssues = window._psBasinValidationIssues || [];
|
||
const modeIssues = window._psModeValidationIssues || [];
|
||
const issues = [...basinIssues, ...modeIssues];
|
||
if (issues.length) {
|
||
if (typeof RED !== 'undefined' && RED.notify) {
|
||
RED.notify('PumpingStation config invalid:<br>• ' + issues.join('<br>• '),
|
||
{ type: 'error', timeout: 6000 });
|
||
}
|
||
throw new Error('PumpingStation: invalid config — ' + issues.join('; '));
|
||
}
|
||
|
||
window.EVOLV?.nodes?.pumpingStation?.loggerMenu?.saveEditor?.(node);
|
||
window.EVOLV?.nodes?.pumpingStation?.positionMenu?.saveEditor?.(node);
|
||
|
||
node.refHeight = document.getElementById('node-input-refHeight').value || 'NAP';
|
||
node.simulator = document.getElementById('node-input-simulator').checked;
|
||
|
||
[
|
||
'basinVolume', 'basinHeight', 'inflowLevel', 'outflowLevel', 'overflowLevel',
|
||
'basinBottomRef',
|
||
'dryRunThresholdPercent', 'highVolumeSafetyThresholdPercent',
|
||
].forEach((field) => {
|
||
const el = document.getElementById(`node-input-${field}`);
|
||
if (el) node[field] = parseFloat(el.value) || 0;
|
||
});
|
||
|
||
node.enableDryRunProtection = document.getElementById('node-input-enableDryRunProtection').checked;
|
||
node.enableHighVolumeSafety = document.getElementById('node-input-enableHighVolumeSafety').checked;
|
||
// Deprecated aliases kept for existing runtime/schema compatibility.
|
||
node.enableOverfillProtection = node.enableHighVolumeSafety;
|
||
node.overfillThresholdPercent = node.highVolumeSafetyThresholdPercent;
|
||
|
||
node.controlMode = document.getElementById('node-input-controlMode').value || 'levelbased';
|
||
node.levelCurveType = document.getElementById('node-input-levelCurveType')?.value || 'linear';
|
||
node.logCurveFactor = parseNum('node-input-logCurveFactor');
|
||
node.startLevel = parseNum('node-input-startLevel');
|
||
node.maxLevel = parseNum('node-input-maxLevel');
|
||
// minLevel is no longer a user input — it's the derived dryRunLevel
|
||
// (outflowLevel × (1 + dryRunThresholdPercent/100)). The runtime still
|
||
// uses node.minLevel as the unconditional STOP threshold; we set it
|
||
// here so that semantic survives the UI change.
|
||
const _dryRun = ns.deriveDryRunLevel?.();
|
||
if (Number.isFinite(_dryRun)) node.minLevel = _dryRun;
|
||
node.enableShiftedRamp = !!document.getElementById('node-input-enableShiftedRamp')?.checked;
|
||
const shiftLevelVal = parseNum('node-input-shiftLevel');
|
||
node.shiftLevel = Number.isFinite(shiftLevelVal) ? shiftLevelVal : 0;
|
||
const armPctVal = parseNum('node-input-shiftArmPercent');
|
||
node.shiftArmPercent = Number.isFinite(armPctVal) ? armPctVal : 95;
|
||
const flowSetpoint = parseNum('node-input-flowSetpoint');
|
||
const flowDeadband = parseNum('node-input-flowDeadband');
|
||
if (Number.isFinite(flowSetpoint)) node.flowSetpoint = flowSetpoint;
|
||
if (Number.isFinite(flowDeadband)) node.flowDeadband = flowDeadband;
|
||
};
|
||
})();
|