@procaaso/alphinity-ui-components 1.1.3 → 1.3.0
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 +11 -0
- package/dist/index.cjs +123 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +167 -100
- package/dist/index.d.ts +167 -100
- package/dist/index.js +126 -119
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -72,6 +72,17 @@ function HMIPanel() {
|
|
|
72
72
|
- `npm test` - Run tests with Vitest
|
|
73
73
|
- `npm run lint` - Lint code with ESLint
|
|
74
74
|
- `npm run typecheck` - Type check with TypeScript
|
|
75
|
+
- `npm run dev` - Run the demo playground on http://localhost:3010
|
|
76
|
+
|
|
77
|
+
### Demo views
|
|
78
|
+
|
|
79
|
+
The demo (`npm run dev`) mounts one of three views, selected by a `?demo=` query param:
|
|
80
|
+
|
|
81
|
+
- `http://localhost:3010/` *(default)* - **Runtime**: full P&ID canvas with live telemetry, simulation, and device control/config panels
|
|
82
|
+
- `http://localhost:3010/?demo=single-node` - **Single-node HMI mockup** over the full plant SVG layout
|
|
83
|
+
- `http://localhost:3010/?demo=gallery` - **Component gallery** of individual widgets
|
|
84
|
+
|
|
85
|
+
> The demo is served, not opened as a file — `demo-dist/index.html` references assets by absolute path and will be blank if opened via `file://`. Use `npm run dev`, or `npx vite preview --config vite.demo.config.ts` for the built version.
|
|
75
86
|
|
|
76
87
|
### Components
|
|
77
88
|
|
package/dist/index.cjs
CHANGED
|
@@ -580,11 +580,12 @@ var ControlPanel = ({
|
|
|
580
580
|
secondSetpointValue,
|
|
581
581
|
onSecondSetpointChange,
|
|
582
582
|
modeConfigs = {},
|
|
583
|
-
defaultUnit
|
|
583
|
+
defaultUnit,
|
|
584
584
|
defaultLabel = "Setpoint",
|
|
585
|
-
defaultMin
|
|
586
|
-
defaultMax
|
|
587
|
-
defaultPrecision
|
|
585
|
+
defaultMin,
|
|
586
|
+
defaultMax,
|
|
587
|
+
defaultPrecision,
|
|
588
|
+
scaling,
|
|
588
589
|
status = "normal",
|
|
589
590
|
collapsed = false,
|
|
590
591
|
onCollapseChange,
|
|
@@ -606,11 +607,11 @@ var ControlPanel = ({
|
|
|
606
607
|
onCollapseChange?.(!newExpanded);
|
|
607
608
|
};
|
|
608
609
|
const currentModeConfig = modeConfigs[selectedMode] || {};
|
|
609
|
-
const currentUnit = currentModeConfig.unit || defaultUnit;
|
|
610
|
+
const currentUnit = currentModeConfig.unit || defaultUnit || scaling?.units || "Units";
|
|
610
611
|
const currentLabel = currentModeConfig.label || defaultLabel;
|
|
611
|
-
const currentMin = currentModeConfig.min ?? defaultMin;
|
|
612
|
-
const currentMax = currentModeConfig.max ?? defaultMax;
|
|
613
|
-
const currentPrecision = currentModeConfig.precision ?? defaultPrecision;
|
|
612
|
+
const currentMin = currentModeConfig.min ?? defaultMin ?? scaling?.euLow ?? 0;
|
|
613
|
+
const currentMax = currentModeConfig.max ?? defaultMax ?? scaling?.euHigh ?? 100;
|
|
614
|
+
const currentPrecision = currentModeConfig.precision ?? defaultPrecision ?? scaling?.decimals ?? 2;
|
|
614
615
|
const hasSecondSetpoint = currentModeConfig.hasSecondSetpoint || false;
|
|
615
616
|
const baseClass = "ui-control-panel";
|
|
616
617
|
const statusClass = status ? `ui-control-panel--${status}` : "";
|
|
@@ -1039,6 +1040,8 @@ function DeviceControlPanel({
|
|
|
1039
1040
|
onParameterChange,
|
|
1040
1041
|
onStart,
|
|
1041
1042
|
onStop,
|
|
1043
|
+
startPending = false,
|
|
1044
|
+
stopPending = false,
|
|
1042
1045
|
onCustomAction,
|
|
1043
1046
|
onOpenConfig,
|
|
1044
1047
|
showConfigButton = true,
|
|
@@ -1785,11 +1788,10 @@ function DeviceControlPanel({
|
|
|
1785
1788
|
"button",
|
|
1786
1789
|
{
|
|
1787
1790
|
onClick: onStart,
|
|
1788
|
-
disabled: binding.state.isRunning,
|
|
1789
1791
|
style: {
|
|
1790
1792
|
flex: 1,
|
|
1791
1793
|
height: touchSize,
|
|
1792
|
-
backgroundColor:
|
|
1794
|
+
backgroundColor: "#10b981",
|
|
1793
1795
|
color: "#ffffff",
|
|
1794
1796
|
border: "none",
|
|
1795
1797
|
borderRadius: "6px",
|
|
@@ -1797,21 +1799,20 @@ function DeviceControlPanel({
|
|
|
1797
1799
|
fontWeight: 700,
|
|
1798
1800
|
fontFamily: "Arial, sans-serif",
|
|
1799
1801
|
letterSpacing: "0.5px",
|
|
1800
|
-
cursor:
|
|
1801
|
-
opacity:
|
|
1802
|
+
cursor: "pointer",
|
|
1803
|
+
opacity: 1
|
|
1802
1804
|
},
|
|
1803
|
-
children: "START"
|
|
1805
|
+
children: startPending ? "STARTING\u2026" : "START"
|
|
1804
1806
|
}
|
|
1805
1807
|
),
|
|
1806
1808
|
activeCapabilities?.canStop && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1807
1809
|
"button",
|
|
1808
1810
|
{
|
|
1809
1811
|
onClick: onStop,
|
|
1810
|
-
disabled: !binding.state.isRunning,
|
|
1811
1812
|
style: {
|
|
1812
1813
|
flex: 1,
|
|
1813
1814
|
height: touchSize,
|
|
1814
|
-
backgroundColor:
|
|
1815
|
+
backgroundColor: "#ef4444",
|
|
1815
1816
|
color: "#ffffff",
|
|
1816
1817
|
border: "none",
|
|
1817
1818
|
borderRadius: "6px",
|
|
@@ -1819,10 +1820,10 @@ function DeviceControlPanel({
|
|
|
1819
1820
|
fontWeight: 700,
|
|
1820
1821
|
fontFamily: "Arial, sans-serif",
|
|
1821
1822
|
letterSpacing: "0.5px",
|
|
1822
|
-
cursor:
|
|
1823
|
-
opacity:
|
|
1823
|
+
cursor: "pointer",
|
|
1824
|
+
opacity: 1
|
|
1824
1825
|
},
|
|
1825
|
-
children: "STOP"
|
|
1826
|
+
children: stopPending ? "STOPPING\u2026" : "STOP"
|
|
1826
1827
|
}
|
|
1827
1828
|
)
|
|
1828
1829
|
] }),
|
|
@@ -4457,12 +4458,13 @@ function DataOverlay({
|
|
|
4457
4458
|
scale = 1,
|
|
4458
4459
|
statusColorMap
|
|
4459
4460
|
}) {
|
|
4460
|
-
const { value, display, history } = telemetry;
|
|
4461
|
+
const { value, display, history, scaling } = telemetry;
|
|
4461
4462
|
const showValue = display?.showValue ?? true;
|
|
4462
4463
|
const showStatus = display?.showStatus ?? true;
|
|
4463
4464
|
const showSparkline = display?.showSparkline ?? false;
|
|
4464
4465
|
const label = display?.label;
|
|
4465
|
-
const precision = display?.precision;
|
|
4466
|
+
const precision = scaling?.decimals ?? display?.precision;
|
|
4467
|
+
const unit = scaling?.units ?? value.unit;
|
|
4466
4468
|
const sparklineColor = display?.sparklineColor;
|
|
4467
4469
|
const textColor = display?.textColor;
|
|
4468
4470
|
const backgroundColor = display?.backgroundColor;
|
|
@@ -4486,7 +4488,7 @@ function DataOverlay({
|
|
|
4486
4488
|
const colors = backgroundColor && backgroundColor !== "status" ? { bg: backgroundColor, text: textColor || "#ffffff", border: backgroundColor } : statusColors;
|
|
4487
4489
|
const finalTextColor = textColor || colors.text;
|
|
4488
4490
|
const formattedValue = formatValue(value.value);
|
|
4489
|
-
const displayText =
|
|
4491
|
+
const displayText = unit ? `${formattedValue} ${unit}` : formattedValue;
|
|
4490
4492
|
const sparklinePath = (() => {
|
|
4491
4493
|
if (!showSparkline || !history || history.length < 2) return "";
|
|
4492
4494
|
const width = 70 * scale;
|
|
@@ -4494,8 +4496,9 @@ function DataOverlay({
|
|
|
4494
4496
|
const padding = 5 * scale;
|
|
4495
4497
|
const effectiveWidth = width - padding * 2;
|
|
4496
4498
|
const effectiveHeight = height - padding * 2;
|
|
4497
|
-
const
|
|
4498
|
-
const
|
|
4499
|
+
const hasEuRange = scaling?.euLow !== void 0 && scaling?.euHigh !== void 0 && scaling.euHigh !== scaling.euLow;
|
|
4500
|
+
const min = hasEuRange ? scaling.euLow : Math.min(...history);
|
|
4501
|
+
const max = hasEuRange ? scaling.euHigh : Math.max(...history);
|
|
4499
4502
|
const range = max - min || 1;
|
|
4500
4503
|
const points = history.map((val, i) => {
|
|
4501
4504
|
const xPos = i / (history.length - 1) * effectiveWidth + padding;
|
|
@@ -7657,7 +7660,11 @@ function createControlBinding(nodeId, deviceConfig, actions) {
|
|
|
7657
7660
|
|
|
7658
7661
|
// src/hooks/useDeviceControls.ts
|
|
7659
7662
|
var import_react18 = require("react");
|
|
7660
|
-
function
|
|
7663
|
+
function commandKey(nodeId, kind, suffix) {
|
|
7664
|
+
return suffix ? `${nodeId}:${kind}:${suffix}` : `${nodeId}:${kind}`;
|
|
7665
|
+
}
|
|
7666
|
+
function useDeviceControls(initialBindings, options) {
|
|
7667
|
+
const { commandTimeoutMs } = options ?? {};
|
|
7661
7668
|
const [controls, setControls] = (0, import_react18.useState)(() => {
|
|
7662
7669
|
const map = /* @__PURE__ */ new Map();
|
|
7663
7670
|
if (initialBindings) {
|
|
@@ -7667,6 +7674,11 @@ function useDeviceControls(initialBindings) {
|
|
|
7667
7674
|
}
|
|
7668
7675
|
return map;
|
|
7669
7676
|
});
|
|
7677
|
+
const pendingRef = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
|
|
7678
|
+
const [pendingCommands, setPendingCommands] = (0, import_react18.useState)(() => /* @__PURE__ */ new Set());
|
|
7679
|
+
const syncPending = (0, import_react18.useCallback)(() => {
|
|
7680
|
+
setPendingCommands(new Set(pendingRef.current.keys()));
|
|
7681
|
+
}, []);
|
|
7670
7682
|
const updateControl = (0, import_react18.useCallback)((nodeId, updates) => {
|
|
7671
7683
|
setControls((prev) => {
|
|
7672
7684
|
const newMap = new Map(prev);
|
|
@@ -7756,121 +7768,112 @@ function useDeviceControls(initialBindings) {
|
|
|
7756
7768
|
},
|
|
7757
7769
|
[controls]
|
|
7758
7770
|
);
|
|
7759
|
-
const
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
updateDeviceState(nodeId, {
|
|
7772
|
-
currentMode: binding.state.currentMode,
|
|
7773
|
-
lastCommandResult: {
|
|
7774
|
-
success: false,
|
|
7775
|
-
message: error instanceof Error ? error.message : "Mode change failed"
|
|
7776
|
-
}
|
|
7777
|
-
});
|
|
7771
|
+
const isCommandPending = (0, import_react18.useCallback)(
|
|
7772
|
+
(nodeId, kind) => {
|
|
7773
|
+
if (kind) {
|
|
7774
|
+
const prefix2 = `${nodeId}:${kind}`;
|
|
7775
|
+
for (const key of pendingCommands) {
|
|
7776
|
+
if (key === prefix2 || key.startsWith(`${prefix2}:`)) return true;
|
|
7777
|
+
}
|
|
7778
|
+
return false;
|
|
7779
|
+
}
|
|
7780
|
+
const prefix = `${nodeId}:`;
|
|
7781
|
+
for (const key of pendingCommands) {
|
|
7782
|
+
if (key.startsWith(prefix)) return true;
|
|
7778
7783
|
}
|
|
7784
|
+
return false;
|
|
7779
7785
|
},
|
|
7780
|
-
[
|
|
7786
|
+
[pendingCommands]
|
|
7781
7787
|
);
|
|
7782
|
-
const
|
|
7783
|
-
|
|
7784
|
-
const
|
|
7785
|
-
if (
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
await binding.actions.onParameterChange?.(parameterId, value);
|
|
7789
|
-
updateDeviceState(nodeId, {
|
|
7790
|
-
lastCommandTime: Date.now(),
|
|
7791
|
-
lastCommandResult: { success: true, message: `Parameter ${parameterId} updated` }
|
|
7792
|
-
});
|
|
7793
|
-
} catch (error) {
|
|
7794
|
-
updateDeviceState(nodeId, {
|
|
7795
|
-
lastCommandResult: {
|
|
7796
|
-
success: false,
|
|
7797
|
-
message: error instanceof Error ? error.message : "Parameter change failed"
|
|
7798
|
-
}
|
|
7799
|
-
});
|
|
7800
|
-
}
|
|
7788
|
+
const markPending = (0, import_react18.useCallback)(
|
|
7789
|
+
(key, delta) => {
|
|
7790
|
+
const next = (pendingRef.current.get(key) ?? 0) + delta;
|
|
7791
|
+
if (next > 0) pendingRef.current.set(key, next);
|
|
7792
|
+
else pendingRef.current.delete(key);
|
|
7793
|
+
syncPending();
|
|
7801
7794
|
},
|
|
7802
|
-
[
|
|
7795
|
+
[syncPending]
|
|
7803
7796
|
);
|
|
7804
|
-
const
|
|
7805
|
-
async (nodeId) => {
|
|
7797
|
+
const runCommand = (0, import_react18.useCallback)(
|
|
7798
|
+
async (nodeId, key, invoke, messages) => {
|
|
7806
7799
|
const binding = controls.get(nodeId);
|
|
7807
7800
|
if (!binding) return;
|
|
7808
|
-
|
|
7801
|
+
markPending(key, 1);
|
|
7802
|
+
let timer;
|
|
7809
7803
|
try {
|
|
7810
|
-
|
|
7804
|
+
const action = Promise.resolve(invoke(binding));
|
|
7805
|
+
if (commandTimeoutMs && commandTimeoutMs > 0) {
|
|
7806
|
+
await Promise.race([
|
|
7807
|
+
action,
|
|
7808
|
+
new Promise((_, reject) => {
|
|
7809
|
+
timer = setTimeout(
|
|
7810
|
+
() => reject(new Error(`${messages.success.replace(/ sent$/, "")} timed out (unconfirmed)`)),
|
|
7811
|
+
commandTimeoutMs
|
|
7812
|
+
);
|
|
7813
|
+
})
|
|
7814
|
+
]);
|
|
7815
|
+
} else {
|
|
7816
|
+
await action;
|
|
7817
|
+
}
|
|
7811
7818
|
updateDeviceState(nodeId, {
|
|
7812
|
-
status: "normal",
|
|
7813
7819
|
lastCommandTime: Date.now(),
|
|
7814
|
-
lastCommandResult: { success: true, message:
|
|
7820
|
+
lastCommandResult: { success: true, message: messages.success }
|
|
7815
7821
|
});
|
|
7816
7822
|
} catch (error) {
|
|
7817
7823
|
updateDeviceState(nodeId, {
|
|
7818
|
-
|
|
7819
|
-
status: "fault",
|
|
7824
|
+
lastCommandTime: Date.now(),
|
|
7820
7825
|
lastCommandResult: {
|
|
7821
7826
|
success: false,
|
|
7822
|
-
message: error instanceof Error ? error.message :
|
|
7827
|
+
message: error instanceof Error ? error.message : messages.failure
|
|
7823
7828
|
}
|
|
7824
7829
|
});
|
|
7830
|
+
} finally {
|
|
7831
|
+
if (timer) clearTimeout(timer);
|
|
7832
|
+
markPending(key, -1);
|
|
7825
7833
|
}
|
|
7826
7834
|
},
|
|
7827
|
-
[controls, updateDeviceState]
|
|
7835
|
+
[controls, updateDeviceState, markPending, commandTimeoutMs]
|
|
7836
|
+
);
|
|
7837
|
+
const sendModeChange = (0, import_react18.useCallback)(
|
|
7838
|
+
(nodeId, mode) => runCommand(
|
|
7839
|
+
nodeId,
|
|
7840
|
+
commandKey(nodeId, "mode"),
|
|
7841
|
+
(binding) => binding.actions.onModeChange?.(mode),
|
|
7842
|
+
{ success: `Mode ${mode} command sent`, failure: "Mode change failed" }
|
|
7843
|
+
),
|
|
7844
|
+
[runCommand]
|
|
7845
|
+
);
|
|
7846
|
+
const sendParameterChange = (0, import_react18.useCallback)(
|
|
7847
|
+
(nodeId, parameterId, value) => runCommand(
|
|
7848
|
+
nodeId,
|
|
7849
|
+
commandKey(nodeId, "parameter", parameterId),
|
|
7850
|
+
(binding) => binding.actions.onParameterChange?.(parameterId, value),
|
|
7851
|
+
{ success: `Parameter ${parameterId} command sent`, failure: "Parameter change failed" }
|
|
7852
|
+
),
|
|
7853
|
+
[runCommand]
|
|
7854
|
+
);
|
|
7855
|
+
const sendStartCommand = (0, import_react18.useCallback)(
|
|
7856
|
+
(nodeId) => runCommand(nodeId, commandKey(nodeId, "start"), (binding) => binding.actions.onStart?.(), {
|
|
7857
|
+
success: "Start command sent",
|
|
7858
|
+
failure: "Start command failed"
|
|
7859
|
+
}),
|
|
7860
|
+
[runCommand]
|
|
7828
7861
|
);
|
|
7829
7862
|
const sendStopCommand = (0, import_react18.useCallback)(
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
await binding.actions.onStop?.();
|
|
7836
|
-
updateDeviceState(nodeId, {
|
|
7837
|
-
status: "off",
|
|
7838
|
-
lastCommandTime: Date.now(),
|
|
7839
|
-
lastCommandResult: { success: true, message: "Device stopped" }
|
|
7840
|
-
});
|
|
7841
|
-
} catch (error) {
|
|
7842
|
-
updateDeviceState(nodeId, {
|
|
7843
|
-
isRunning: true,
|
|
7844
|
-
status: "fault",
|
|
7845
|
-
lastCommandResult: {
|
|
7846
|
-
success: false,
|
|
7847
|
-
message: error instanceof Error ? error.message : "Stop failed"
|
|
7848
|
-
}
|
|
7849
|
-
});
|
|
7850
|
-
}
|
|
7851
|
-
},
|
|
7852
|
-
[controls, updateDeviceState]
|
|
7863
|
+
(nodeId) => runCommand(nodeId, commandKey(nodeId, "stop"), (binding) => binding.actions.onStop?.(), {
|
|
7864
|
+
success: "Stop command sent",
|
|
7865
|
+
failure: "Stop command failed"
|
|
7866
|
+
}),
|
|
7867
|
+
[runCommand]
|
|
7853
7868
|
);
|
|
7854
7869
|
const sendCustomAction = (0, import_react18.useCallback)(
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
lastCommandResult: { success: true, message: `Action ${actionId} executed` }
|
|
7863
|
-
});
|
|
7864
|
-
} catch (error) {
|
|
7865
|
-
updateDeviceState(nodeId, {
|
|
7866
|
-
lastCommandResult: {
|
|
7867
|
-
success: false,
|
|
7868
|
-
message: error instanceof Error ? error.message : "Action failed"
|
|
7869
|
-
}
|
|
7870
|
-
});
|
|
7871
|
-
}
|
|
7872
|
-
},
|
|
7873
|
-
[controls, updateDeviceState]
|
|
7870
|
+
(nodeId, actionId) => runCommand(
|
|
7871
|
+
nodeId,
|
|
7872
|
+
commandKey(nodeId, "action", actionId),
|
|
7873
|
+
(binding) => binding.actions.onCustomAction?.(actionId),
|
|
7874
|
+
{ success: `Action ${actionId} command sent`, failure: "Action failed" }
|
|
7875
|
+
),
|
|
7876
|
+
[runCommand]
|
|
7874
7877
|
);
|
|
7875
7878
|
return {
|
|
7876
7879
|
controls,
|
|
@@ -7881,6 +7884,10 @@ function useDeviceControls(initialBindings) {
|
|
|
7881
7884
|
setControlBinding,
|
|
7882
7885
|
removeControlBinding,
|
|
7883
7886
|
getControlBinding,
|
|
7887
|
+
// In-flight command visibility (interaction state, not device state).
|
|
7888
|
+
// `isCommandPending` is the stable query surface; the raw key set is
|
|
7889
|
+
// intentionally not exported (its key format is an implementation detail).
|
|
7890
|
+
isCommandPending,
|
|
7884
7891
|
// Command helpers
|
|
7885
7892
|
sendModeChange,
|
|
7886
7893
|
sendParameterChange,
|