@procaaso/alphinity-ui-components 1.1.2 → 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 +173 -171
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +215 -102
- package/dist/index.d.ts +215 -102
- package/dist/index.js +174 -174
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -477,11 +477,12 @@ var ControlPanel = ({
|
|
|
477
477
|
secondSetpointValue,
|
|
478
478
|
onSecondSetpointChange,
|
|
479
479
|
modeConfigs = {},
|
|
480
|
-
defaultUnit
|
|
480
|
+
defaultUnit,
|
|
481
481
|
defaultLabel = "Setpoint",
|
|
482
|
-
defaultMin
|
|
483
|
-
defaultMax
|
|
484
|
-
defaultPrecision
|
|
482
|
+
defaultMin,
|
|
483
|
+
defaultMax,
|
|
484
|
+
defaultPrecision,
|
|
485
|
+
scaling,
|
|
485
486
|
status = "normal",
|
|
486
487
|
collapsed = false,
|
|
487
488
|
onCollapseChange,
|
|
@@ -503,11 +504,11 @@ var ControlPanel = ({
|
|
|
503
504
|
onCollapseChange?.(!newExpanded);
|
|
504
505
|
};
|
|
505
506
|
const currentModeConfig = modeConfigs[selectedMode] || {};
|
|
506
|
-
const currentUnit = currentModeConfig.unit || defaultUnit;
|
|
507
|
+
const currentUnit = currentModeConfig.unit || defaultUnit || scaling?.units || "Units";
|
|
507
508
|
const currentLabel = currentModeConfig.label || defaultLabel;
|
|
508
|
-
const currentMin = currentModeConfig.min ?? defaultMin;
|
|
509
|
-
const currentMax = currentModeConfig.max ?? defaultMax;
|
|
510
|
-
const currentPrecision = currentModeConfig.precision ?? defaultPrecision;
|
|
509
|
+
const currentMin = currentModeConfig.min ?? defaultMin ?? scaling?.euLow ?? 0;
|
|
510
|
+
const currentMax = currentModeConfig.max ?? defaultMax ?? scaling?.euHigh ?? 100;
|
|
511
|
+
const currentPrecision = currentModeConfig.precision ?? defaultPrecision ?? scaling?.decimals ?? 2;
|
|
511
512
|
const hasSecondSetpoint = currentModeConfig.hasSecondSetpoint || false;
|
|
512
513
|
const baseClass = "ui-control-panel";
|
|
513
514
|
const statusClass = status ? `ui-control-panel--${status}` : "";
|
|
@@ -895,6 +896,31 @@ var NodeControlsPanel = ({
|
|
|
895
896
|
|
|
896
897
|
// src/components/DeviceControlPanel/DeviceControlPanel.tsx
|
|
897
898
|
import { useState as useState5, useEffect as useEffect2 } from "react";
|
|
899
|
+
|
|
900
|
+
// src/theme/statusColors.ts
|
|
901
|
+
var DEFAULT_STATUS_COLORS = {
|
|
902
|
+
normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
|
|
903
|
+
warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
|
|
904
|
+
alarm: { fill: "#ef4444", stroke: "#dc2626", bg: "#FF0000", text: "#ffffff", border: "#dc2626" },
|
|
905
|
+
fault: { fill: "#dc2626", stroke: "#b91c1c", bg: "#FF0000", text: "#ffffff", border: "#b91c1c" },
|
|
906
|
+
off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
|
|
907
|
+
starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
|
|
908
|
+
stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" }
|
|
909
|
+
};
|
|
910
|
+
var STATUS_ALIASES = {
|
|
911
|
+
running: "normal",
|
|
912
|
+
stopped: "off"
|
|
913
|
+
};
|
|
914
|
+
function resolveStatusColor(status, customMap) {
|
|
915
|
+
const raw = (status || "off").toLowerCase();
|
|
916
|
+
const key = STATUS_ALIASES[raw] ?? raw;
|
|
917
|
+
const defaults = DEFAULT_STATUS_COLORS[key] ?? DEFAULT_STATUS_COLORS.off;
|
|
918
|
+
const overrides = customMap?.[key];
|
|
919
|
+
if (!overrides) return defaults;
|
|
920
|
+
return { ...defaults, ...overrides };
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// src/components/DeviceControlPanel/DeviceControlPanel.tsx
|
|
898
924
|
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
899
925
|
function DeviceControlPanel({
|
|
900
926
|
binding,
|
|
@@ -911,6 +937,8 @@ function DeviceControlPanel({
|
|
|
911
937
|
onParameterChange,
|
|
912
938
|
onStart,
|
|
913
939
|
onStop,
|
|
940
|
+
startPending = false,
|
|
941
|
+
stopPending = false,
|
|
914
942
|
onCustomAction,
|
|
915
943
|
onOpenConfig,
|
|
916
944
|
showConfigButton = true,
|
|
@@ -969,23 +997,8 @@ function DeviceControlPanel({
|
|
|
969
997
|
const textColor = binding.theme?.textColor || "#000000";
|
|
970
998
|
const mutedTextColor = "#666666";
|
|
971
999
|
const selectedColor = "#b4d0fe";
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
// Gray (no alarm)
|
|
975
|
-
warning: binding.theme?.statusColors?.warning || "#FFD700",
|
|
976
|
-
// Yellow
|
|
977
|
-
alarm: binding.theme?.statusColors?.alarm || "#FF0000",
|
|
978
|
-
// Red
|
|
979
|
-
fault: binding.theme?.statusColors?.fault || "#FF0000",
|
|
980
|
-
// Red
|
|
981
|
-
off: binding.theme?.statusColors?.off || "#666666",
|
|
982
|
-
// Dark gray
|
|
983
|
-
starting: binding.theme?.statusColors?.starting || "#90EE90",
|
|
984
|
-
// Light green
|
|
985
|
-
stopping: binding.theme?.statusColors?.stopping || "#FFD700"
|
|
986
|
-
// Yellow
|
|
987
|
-
};
|
|
988
|
-
const statusColor = statusColors[binding.state.status || "normal"];
|
|
1000
|
+
const statusEntry = resolveStatusColor(binding.state.status, binding.theme?.statusColors);
|
|
1001
|
+
const statusColor = statusEntry.bg;
|
|
989
1002
|
const handleModeChange = (mode) => {
|
|
990
1003
|
onModeChange?.(mode);
|
|
991
1004
|
};
|
|
@@ -1272,7 +1285,10 @@ function DeviceControlPanel({
|
|
|
1272
1285
|
/* @__PURE__ */ jsxs7(
|
|
1273
1286
|
"div",
|
|
1274
1287
|
{
|
|
1275
|
-
onClick: () =>
|
|
1288
|
+
onClick: () => {
|
|
1289
|
+
setPendingMode(currentMode);
|
|
1290
|
+
setShowModeModal(true);
|
|
1291
|
+
},
|
|
1276
1292
|
style: {
|
|
1277
1293
|
padding: "12px",
|
|
1278
1294
|
backgroundColor: surfaceColor,
|
|
@@ -1669,11 +1685,10 @@ function DeviceControlPanel({
|
|
|
1669
1685
|
"button",
|
|
1670
1686
|
{
|
|
1671
1687
|
onClick: onStart,
|
|
1672
|
-
disabled: binding.state.isRunning,
|
|
1673
1688
|
style: {
|
|
1674
1689
|
flex: 1,
|
|
1675
1690
|
height: touchSize,
|
|
1676
|
-
backgroundColor:
|
|
1691
|
+
backgroundColor: "#10b981",
|
|
1677
1692
|
color: "#ffffff",
|
|
1678
1693
|
border: "none",
|
|
1679
1694
|
borderRadius: "6px",
|
|
@@ -1681,21 +1696,20 @@ function DeviceControlPanel({
|
|
|
1681
1696
|
fontWeight: 700,
|
|
1682
1697
|
fontFamily: "Arial, sans-serif",
|
|
1683
1698
|
letterSpacing: "0.5px",
|
|
1684
|
-
cursor:
|
|
1685
|
-
opacity:
|
|
1699
|
+
cursor: "pointer",
|
|
1700
|
+
opacity: 1
|
|
1686
1701
|
},
|
|
1687
|
-
children: "START"
|
|
1702
|
+
children: startPending ? "STARTING\u2026" : "START"
|
|
1688
1703
|
}
|
|
1689
1704
|
),
|
|
1690
1705
|
activeCapabilities?.canStop && /* @__PURE__ */ jsx8(
|
|
1691
1706
|
"button",
|
|
1692
1707
|
{
|
|
1693
1708
|
onClick: onStop,
|
|
1694
|
-
disabled: !binding.state.isRunning,
|
|
1695
1709
|
style: {
|
|
1696
1710
|
flex: 1,
|
|
1697
1711
|
height: touchSize,
|
|
1698
|
-
backgroundColor:
|
|
1712
|
+
backgroundColor: "#ef4444",
|
|
1699
1713
|
color: "#ffffff",
|
|
1700
1714
|
border: "none",
|
|
1701
1715
|
borderRadius: "6px",
|
|
@@ -1703,10 +1717,10 @@ function DeviceControlPanel({
|
|
|
1703
1717
|
fontWeight: 700,
|
|
1704
1718
|
fontFamily: "Arial, sans-serif",
|
|
1705
1719
|
letterSpacing: "0.5px",
|
|
1706
|
-
cursor:
|
|
1707
|
-
opacity:
|
|
1720
|
+
cursor: "pointer",
|
|
1721
|
+
opacity: 1
|
|
1708
1722
|
},
|
|
1709
|
-
children: "STOP"
|
|
1723
|
+
children: stopPending ? "STOPPING\u2026" : "STOP"
|
|
1710
1724
|
}
|
|
1711
1725
|
)
|
|
1712
1726
|
] }),
|
|
@@ -4012,7 +4026,8 @@ function NodeRenderer({
|
|
|
4012
4026
|
onNodeDragStart,
|
|
4013
4027
|
draggingNodeId,
|
|
4014
4028
|
dragOffset = { x: 0, y: 0 },
|
|
4015
|
-
enableDrag = false
|
|
4029
|
+
enableDrag = false,
|
|
4030
|
+
statusColorMap
|
|
4016
4031
|
}) {
|
|
4017
4032
|
return /* @__PURE__ */ jsx15("g", { id: "layer-nodes", children: nodes2.filter((node) => node.visible !== false).map((node) => {
|
|
4018
4033
|
const symbol = getSymbolDefinition(node.symbolId);
|
|
@@ -4071,24 +4086,9 @@ function NodeRenderer({
|
|
|
4071
4086
|
`translate(${centerX}, ${centerY}) scale(${scaleX}, ${scaleY}) translate(${-centerX}, ${-centerY})`
|
|
4072
4087
|
);
|
|
4073
4088
|
}
|
|
4074
|
-
const
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
return "#22c55e";
|
|
4078
|
-
// Green
|
|
4079
|
-
case "alarm":
|
|
4080
|
-
return "#ef4444";
|
|
4081
|
-
// Red
|
|
4082
|
-
case "warning":
|
|
4083
|
-
return "#f59e0b";
|
|
4084
|
-
// Orange
|
|
4085
|
-
case "stopped":
|
|
4086
|
-
default:
|
|
4087
|
-
return "#edeeef";
|
|
4088
|
-
}
|
|
4089
|
-
};
|
|
4090
|
-
const fillColor = node.customColors?.fill ?? getStatusColor(node.status);
|
|
4091
|
-
const strokeColor = node.customColors?.stroke ?? "rgb(0, 0, 0)";
|
|
4089
|
+
const statusEntry = resolveStatusColor(node.status, statusColorMap);
|
|
4090
|
+
const fillColor = node.customColors?.fill ?? statusEntry.fill;
|
|
4091
|
+
const strokeColor = node.customColors?.stroke ?? statusEntry.stroke;
|
|
4092
4092
|
return /* @__PURE__ */ jsxs11(
|
|
4093
4093
|
"g",
|
|
4094
4094
|
{
|
|
@@ -4352,14 +4352,16 @@ function DataOverlay({
|
|
|
4352
4352
|
x,
|
|
4353
4353
|
y,
|
|
4354
4354
|
telemetry,
|
|
4355
|
-
scale = 1
|
|
4355
|
+
scale = 1,
|
|
4356
|
+
statusColorMap
|
|
4356
4357
|
}) {
|
|
4357
|
-
const { value, display, history } = telemetry;
|
|
4358
|
+
const { value, display, history, scaling } = telemetry;
|
|
4358
4359
|
const showValue = display?.showValue ?? true;
|
|
4359
4360
|
const showStatus = display?.showStatus ?? true;
|
|
4360
4361
|
const showSparkline = display?.showSparkline ?? false;
|
|
4361
4362
|
const label = display?.label;
|
|
4362
|
-
const precision = display?.precision;
|
|
4363
|
+
const precision = scaling?.decimals ?? display?.precision;
|
|
4364
|
+
const unit = scaling?.units ?? value.unit;
|
|
4363
4365
|
const sparklineColor = display?.sparklineColor;
|
|
4364
4366
|
const textColor = display?.textColor;
|
|
4365
4367
|
const backgroundColor = display?.backgroundColor;
|
|
@@ -4377,25 +4379,13 @@ function DataOverlay({
|
|
|
4377
4379
|
}
|
|
4378
4380
|
return val.toString();
|
|
4379
4381
|
};
|
|
4380
|
-
const
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
alarm: { bg: "#ef4444", text: "#ffffff", border: "#dc2626" },
|
|
4384
|
-
fault: { bg: "#dc2626", text: "#ffffff", border: "#b91c1c" },
|
|
4385
|
-
off: { bg: "#6b7280", text: "#ffffff", border: "#4b5563" }
|
|
4386
|
-
};
|
|
4387
|
-
const getStatusColors = () => {
|
|
4388
|
-
const customColor = telemetry.statusColors?.[value.status];
|
|
4389
|
-
if (customColor) {
|
|
4390
|
-
return { bg: customColor, text: "#ffffff", border: customColor };
|
|
4391
|
-
}
|
|
4392
|
-
return defaultStatusColors[value.status] || defaultStatusColors.normal;
|
|
4393
|
-
};
|
|
4394
|
-
const statusColors = getStatusColors();
|
|
4382
|
+
const resolvedEntry = resolveStatusColor(value.status, statusColorMap);
|
|
4383
|
+
const customColor = telemetry.statusColors?.[value.status];
|
|
4384
|
+
const statusColors = customColor ? { bg: customColor, text: "#ffffff", border: customColor } : { bg: resolvedEntry.bg, text: resolvedEntry.text, border: resolvedEntry.border };
|
|
4395
4385
|
const colors = backgroundColor && backgroundColor !== "status" ? { bg: backgroundColor, text: textColor || "#ffffff", border: backgroundColor } : statusColors;
|
|
4396
4386
|
const finalTextColor = textColor || colors.text;
|
|
4397
4387
|
const formattedValue = formatValue(value.value);
|
|
4398
|
-
const displayText =
|
|
4388
|
+
const displayText = unit ? `${formattedValue} ${unit}` : formattedValue;
|
|
4399
4389
|
const sparklinePath = (() => {
|
|
4400
4390
|
if (!showSparkline || !history || history.length < 2) return "";
|
|
4401
4391
|
const width = 70 * scale;
|
|
@@ -4403,8 +4393,9 @@ function DataOverlay({
|
|
|
4403
4393
|
const padding = 5 * scale;
|
|
4404
4394
|
const effectiveWidth = width - padding * 2;
|
|
4405
4395
|
const effectiveHeight = height - padding * 2;
|
|
4406
|
-
const
|
|
4407
|
-
const
|
|
4396
|
+
const hasEuRange = scaling?.euLow !== void 0 && scaling?.euHigh !== void 0 && scaling.euHigh !== scaling.euLow;
|
|
4397
|
+
const min = hasEuRange ? scaling.euLow : Math.min(...history);
|
|
4398
|
+
const max = hasEuRange ? scaling.euHigh : Math.max(...history);
|
|
4408
4399
|
const range = max - min || 1;
|
|
4409
4400
|
const points = history.map((val, i) => {
|
|
4410
4401
|
const xPos = i / (history.length - 1) * effectiveWidth + padding;
|
|
@@ -5662,6 +5653,7 @@ function PIDCanvas({
|
|
|
5662
5653
|
onPipeCreated,
|
|
5663
5654
|
onMounted,
|
|
5664
5655
|
telemetry,
|
|
5656
|
+
statusColorMap,
|
|
5665
5657
|
...props
|
|
5666
5658
|
}) {
|
|
5667
5659
|
const [localDiagram, setLocalDiagram] = useState13(diagram);
|
|
@@ -6315,7 +6307,8 @@ function PIDCanvas({
|
|
|
6315
6307
|
enableDrag: dragEnabled && !isConnecting,
|
|
6316
6308
|
onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
|
|
6317
6309
|
draggingNodeId: dragState.draggedId,
|
|
6318
|
-
dragOffset: dragState.offset
|
|
6310
|
+
dragOffset: dragState.offset,
|
|
6311
|
+
statusColorMap
|
|
6319
6312
|
}
|
|
6320
6313
|
),
|
|
6321
6314
|
(features.editPipeRoutes ?? false) && /* @__PURE__ */ jsx19(
|
|
@@ -6349,7 +6342,8 @@ function PIDCanvas({
|
|
|
6349
6342
|
x: overlayX,
|
|
6350
6343
|
y: overlayY,
|
|
6351
6344
|
telemetry: binding,
|
|
6352
|
-
scale: features.telemetryScale ?? 1
|
|
6345
|
+
scale: features.telemetryScale ?? 1,
|
|
6346
|
+
statusColorMap
|
|
6353
6347
|
},
|
|
6354
6348
|
`telemetry-${node.id}`
|
|
6355
6349
|
);
|
|
@@ -7562,8 +7556,12 @@ function createControlBinding(nodeId, deviceConfig, actions) {
|
|
|
7562
7556
|
}
|
|
7563
7557
|
|
|
7564
7558
|
// src/hooks/useDeviceControls.ts
|
|
7565
|
-
import { useState as useState15, useCallback as useCallback8 } from "react";
|
|
7566
|
-
function
|
|
7559
|
+
import { useState as useState15, useCallback as useCallback8, useRef as useRef6 } from "react";
|
|
7560
|
+
function commandKey(nodeId, kind, suffix) {
|
|
7561
|
+
return suffix ? `${nodeId}:${kind}:${suffix}` : `${nodeId}:${kind}`;
|
|
7562
|
+
}
|
|
7563
|
+
function useDeviceControls(initialBindings, options) {
|
|
7564
|
+
const { commandTimeoutMs } = options ?? {};
|
|
7567
7565
|
const [controls, setControls] = useState15(() => {
|
|
7568
7566
|
const map = /* @__PURE__ */ new Map();
|
|
7569
7567
|
if (initialBindings) {
|
|
@@ -7573,6 +7571,11 @@ function useDeviceControls(initialBindings) {
|
|
|
7573
7571
|
}
|
|
7574
7572
|
return map;
|
|
7575
7573
|
});
|
|
7574
|
+
const pendingRef = useRef6(/* @__PURE__ */ new Map());
|
|
7575
|
+
const [pendingCommands, setPendingCommands] = useState15(() => /* @__PURE__ */ new Set());
|
|
7576
|
+
const syncPending = useCallback8(() => {
|
|
7577
|
+
setPendingCommands(new Set(pendingRef.current.keys()));
|
|
7578
|
+
}, []);
|
|
7576
7579
|
const updateControl = useCallback8((nodeId, updates) => {
|
|
7577
7580
|
setControls((prev) => {
|
|
7578
7581
|
const newMap = new Map(prev);
|
|
@@ -7662,121 +7665,112 @@ function useDeviceControls(initialBindings) {
|
|
|
7662
7665
|
},
|
|
7663
7666
|
[controls]
|
|
7664
7667
|
);
|
|
7665
|
-
const
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
lastCommandTime: Date.now(),
|
|
7674
|
-
lastCommandResult: { success: true, message: `Mode changed to ${mode}` }
|
|
7675
|
-
});
|
|
7676
|
-
} catch (error) {
|
|
7677
|
-
updateDeviceState(nodeId, {
|
|
7678
|
-
currentMode: binding.state.currentMode,
|
|
7679
|
-
lastCommandResult: {
|
|
7680
|
-
success: false,
|
|
7681
|
-
message: error instanceof Error ? error.message : "Mode change failed"
|
|
7682
|
-
}
|
|
7683
|
-
});
|
|
7668
|
+
const isCommandPending = useCallback8(
|
|
7669
|
+
(nodeId, kind) => {
|
|
7670
|
+
if (kind) {
|
|
7671
|
+
const prefix2 = `${nodeId}:${kind}`;
|
|
7672
|
+
for (const key of pendingCommands) {
|
|
7673
|
+
if (key === prefix2 || key.startsWith(`${prefix2}:`)) return true;
|
|
7674
|
+
}
|
|
7675
|
+
return false;
|
|
7684
7676
|
}
|
|
7677
|
+
const prefix = `${nodeId}:`;
|
|
7678
|
+
for (const key of pendingCommands) {
|
|
7679
|
+
if (key.startsWith(prefix)) return true;
|
|
7680
|
+
}
|
|
7681
|
+
return false;
|
|
7685
7682
|
},
|
|
7686
|
-
[
|
|
7683
|
+
[pendingCommands]
|
|
7687
7684
|
);
|
|
7688
|
-
const
|
|
7689
|
-
|
|
7690
|
-
const
|
|
7691
|
-
if (
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
await binding.actions.onParameterChange?.(parameterId, value);
|
|
7695
|
-
updateDeviceState(nodeId, {
|
|
7696
|
-
lastCommandTime: Date.now(),
|
|
7697
|
-
lastCommandResult: { success: true, message: `Parameter ${parameterId} updated` }
|
|
7698
|
-
});
|
|
7699
|
-
} catch (error) {
|
|
7700
|
-
updateDeviceState(nodeId, {
|
|
7701
|
-
lastCommandResult: {
|
|
7702
|
-
success: false,
|
|
7703
|
-
message: error instanceof Error ? error.message : "Parameter change failed"
|
|
7704
|
-
}
|
|
7705
|
-
});
|
|
7706
|
-
}
|
|
7685
|
+
const markPending = useCallback8(
|
|
7686
|
+
(key, delta) => {
|
|
7687
|
+
const next = (pendingRef.current.get(key) ?? 0) + delta;
|
|
7688
|
+
if (next > 0) pendingRef.current.set(key, next);
|
|
7689
|
+
else pendingRef.current.delete(key);
|
|
7690
|
+
syncPending();
|
|
7707
7691
|
},
|
|
7708
|
-
[
|
|
7692
|
+
[syncPending]
|
|
7709
7693
|
);
|
|
7710
|
-
const
|
|
7711
|
-
async (nodeId) => {
|
|
7694
|
+
const runCommand = useCallback8(
|
|
7695
|
+
async (nodeId, key, invoke, messages) => {
|
|
7712
7696
|
const binding = controls.get(nodeId);
|
|
7713
7697
|
if (!binding) return;
|
|
7714
|
-
|
|
7698
|
+
markPending(key, 1);
|
|
7699
|
+
let timer;
|
|
7715
7700
|
try {
|
|
7716
|
-
|
|
7701
|
+
const action = Promise.resolve(invoke(binding));
|
|
7702
|
+
if (commandTimeoutMs && commandTimeoutMs > 0) {
|
|
7703
|
+
await Promise.race([
|
|
7704
|
+
action,
|
|
7705
|
+
new Promise((_, reject) => {
|
|
7706
|
+
timer = setTimeout(
|
|
7707
|
+
() => reject(new Error(`${messages.success.replace(/ sent$/, "")} timed out (unconfirmed)`)),
|
|
7708
|
+
commandTimeoutMs
|
|
7709
|
+
);
|
|
7710
|
+
})
|
|
7711
|
+
]);
|
|
7712
|
+
} else {
|
|
7713
|
+
await action;
|
|
7714
|
+
}
|
|
7717
7715
|
updateDeviceState(nodeId, {
|
|
7718
|
-
status: "normal",
|
|
7719
7716
|
lastCommandTime: Date.now(),
|
|
7720
|
-
lastCommandResult: { success: true, message:
|
|
7717
|
+
lastCommandResult: { success: true, message: messages.success }
|
|
7721
7718
|
});
|
|
7722
7719
|
} catch (error) {
|
|
7723
7720
|
updateDeviceState(nodeId, {
|
|
7724
|
-
|
|
7725
|
-
status: "fault",
|
|
7721
|
+
lastCommandTime: Date.now(),
|
|
7726
7722
|
lastCommandResult: {
|
|
7727
7723
|
success: false,
|
|
7728
|
-
message: error instanceof Error ? error.message :
|
|
7724
|
+
message: error instanceof Error ? error.message : messages.failure
|
|
7729
7725
|
}
|
|
7730
7726
|
});
|
|
7727
|
+
} finally {
|
|
7728
|
+
if (timer) clearTimeout(timer);
|
|
7729
|
+
markPending(key, -1);
|
|
7731
7730
|
}
|
|
7732
7731
|
},
|
|
7733
|
-
[controls, updateDeviceState]
|
|
7732
|
+
[controls, updateDeviceState, markPending, commandTimeoutMs]
|
|
7733
|
+
);
|
|
7734
|
+
const sendModeChange = useCallback8(
|
|
7735
|
+
(nodeId, mode) => runCommand(
|
|
7736
|
+
nodeId,
|
|
7737
|
+
commandKey(nodeId, "mode"),
|
|
7738
|
+
(binding) => binding.actions.onModeChange?.(mode),
|
|
7739
|
+
{ success: `Mode ${mode} command sent`, failure: "Mode change failed" }
|
|
7740
|
+
),
|
|
7741
|
+
[runCommand]
|
|
7742
|
+
);
|
|
7743
|
+
const sendParameterChange = useCallback8(
|
|
7744
|
+
(nodeId, parameterId, value) => runCommand(
|
|
7745
|
+
nodeId,
|
|
7746
|
+
commandKey(nodeId, "parameter", parameterId),
|
|
7747
|
+
(binding) => binding.actions.onParameterChange?.(parameterId, value),
|
|
7748
|
+
{ success: `Parameter ${parameterId} command sent`, failure: "Parameter change failed" }
|
|
7749
|
+
),
|
|
7750
|
+
[runCommand]
|
|
7751
|
+
);
|
|
7752
|
+
const sendStartCommand = useCallback8(
|
|
7753
|
+
(nodeId) => runCommand(nodeId, commandKey(nodeId, "start"), (binding) => binding.actions.onStart?.(), {
|
|
7754
|
+
success: "Start command sent",
|
|
7755
|
+
failure: "Start command failed"
|
|
7756
|
+
}),
|
|
7757
|
+
[runCommand]
|
|
7734
7758
|
);
|
|
7735
7759
|
const sendStopCommand = useCallback8(
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
await binding.actions.onStop?.();
|
|
7742
|
-
updateDeviceState(nodeId, {
|
|
7743
|
-
status: "off",
|
|
7744
|
-
lastCommandTime: Date.now(),
|
|
7745
|
-
lastCommandResult: { success: true, message: "Device stopped" }
|
|
7746
|
-
});
|
|
7747
|
-
} catch (error) {
|
|
7748
|
-
updateDeviceState(nodeId, {
|
|
7749
|
-
isRunning: true,
|
|
7750
|
-
status: "fault",
|
|
7751
|
-
lastCommandResult: {
|
|
7752
|
-
success: false,
|
|
7753
|
-
message: error instanceof Error ? error.message : "Stop failed"
|
|
7754
|
-
}
|
|
7755
|
-
});
|
|
7756
|
-
}
|
|
7757
|
-
},
|
|
7758
|
-
[controls, updateDeviceState]
|
|
7760
|
+
(nodeId) => runCommand(nodeId, commandKey(nodeId, "stop"), (binding) => binding.actions.onStop?.(), {
|
|
7761
|
+
success: "Stop command sent",
|
|
7762
|
+
failure: "Stop command failed"
|
|
7763
|
+
}),
|
|
7764
|
+
[runCommand]
|
|
7759
7765
|
);
|
|
7760
7766
|
const sendCustomAction = useCallback8(
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
lastCommandResult: { success: true, message: `Action ${actionId} executed` }
|
|
7769
|
-
});
|
|
7770
|
-
} catch (error) {
|
|
7771
|
-
updateDeviceState(nodeId, {
|
|
7772
|
-
lastCommandResult: {
|
|
7773
|
-
success: false,
|
|
7774
|
-
message: error instanceof Error ? error.message : "Action failed"
|
|
7775
|
-
}
|
|
7776
|
-
});
|
|
7777
|
-
}
|
|
7778
|
-
},
|
|
7779
|
-
[controls, updateDeviceState]
|
|
7767
|
+
(nodeId, actionId) => runCommand(
|
|
7768
|
+
nodeId,
|
|
7769
|
+
commandKey(nodeId, "action", actionId),
|
|
7770
|
+
(binding) => binding.actions.onCustomAction?.(actionId),
|
|
7771
|
+
{ success: `Action ${actionId} command sent`, failure: "Action failed" }
|
|
7772
|
+
),
|
|
7773
|
+
[runCommand]
|
|
7780
7774
|
);
|
|
7781
7775
|
return {
|
|
7782
7776
|
controls,
|
|
@@ -7787,6 +7781,10 @@ function useDeviceControls(initialBindings) {
|
|
|
7787
7781
|
setControlBinding,
|
|
7788
7782
|
removeControlBinding,
|
|
7789
7783
|
getControlBinding,
|
|
7784
|
+
// In-flight command visibility (interaction state, not device state).
|
|
7785
|
+
// `isCommandPending` is the stable query surface; the raw key set is
|
|
7786
|
+
// intentionally not exported (its key format is an implementation detail).
|
|
7787
|
+
isCommandPending,
|
|
7790
7788
|
// Command helpers
|
|
7791
7789
|
sendModeChange,
|
|
7792
7790
|
sendParameterChange,
|
|
@@ -8075,7 +8073,7 @@ function useDeviceConfigs(initialConfigs) {
|
|
|
8075
8073
|
}
|
|
8076
8074
|
|
|
8077
8075
|
// src/components/DeviceConfigPanel/DeviceConfigPanel.tsx
|
|
8078
|
-
import { useState as useState17, useEffect as useEffect12, useMemo, useRef as
|
|
8076
|
+
import { useState as useState17, useEffect as useEffect12, useMemo, useRef as useRef7 } from "react";
|
|
8079
8077
|
import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
8080
8078
|
function DeviceConfigPanel({
|
|
8081
8079
|
binding,
|
|
@@ -8117,7 +8115,7 @@ function DeviceConfigPanel({
|
|
|
8117
8115
|
});
|
|
8118
8116
|
const [showToast, setShowToast] = useState17(true);
|
|
8119
8117
|
const [bindingId, setBindingId] = useState17(binding?.nodeId || null);
|
|
8120
|
-
const lastResultTimestampRef =
|
|
8118
|
+
const lastResultTimestampRef = useRef7(0);
|
|
8121
8119
|
if (binding && binding.nodeId !== bindingId) {
|
|
8122
8120
|
const values = {};
|
|
8123
8121
|
binding.parameters.forEach((param) => {
|
|
@@ -8844,6 +8842,7 @@ export {
|
|
|
8844
8842
|
CardValue,
|
|
8845
8843
|
CircularGauge,
|
|
8846
8844
|
ControlPanel,
|
|
8845
|
+
DEFAULT_STATUS_COLORS,
|
|
8847
8846
|
DEFAULT_THRESHOLDS,
|
|
8848
8847
|
DashboardCard,
|
|
8849
8848
|
DeviceConfigPanel,
|
|
@@ -8893,6 +8892,7 @@ export {
|
|
|
8893
8892
|
overlayStyles,
|
|
8894
8893
|
registerSymbol,
|
|
8895
8894
|
registerSymbols,
|
|
8895
|
+
resolveStatusColor,
|
|
8896
8896
|
useDeviceConfigs,
|
|
8897
8897
|
useDeviceControls,
|
|
8898
8898
|
useDrag,
|