@procaaso/alphinity-ui-components 1.9.0 → 1.10.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/dist/index.cjs CHANGED
@@ -44,6 +44,7 @@ __export(index_exports, {
44
44
  DisplayModeToggle: () => DisplayModeToggle,
45
45
  EquipmentIndicator: () => EquipmentIndicator,
46
46
  EquipmentIndicatorWithSparkline: () => EquipmentIndicatorWithSparkline,
47
+ FORCED_MARK_COLOR: () => FORCED_MARK_COLOR,
47
48
  FixedSVGContainer: () => FixedSVGContainer,
48
49
  FooterPanel: () => FooterPanel,
49
50
  FullscreenContainer: () => FullscreenContainer,
@@ -278,6 +279,8 @@ var ValueEntry = ({
278
279
  inputType = "numeric",
279
280
  label,
280
281
  readOnly = false,
282
+ forced = false,
283
+ realValue,
281
284
  className = "",
282
285
  disabled,
283
286
  ...props
@@ -328,9 +331,14 @@ var ValueEntry = ({
328
331
  const unitClass = unit ? "ui-value-entry--with-unit" : "";
329
332
  const currentStatus = disabled || readOnly ? "disabled" : status;
330
333
  const statusVariantClass = currentStatus ? `ui-value-entry--${currentStatus}` : "";
334
+ const forcedClass = forced ? "ui-value-entry--forced" : "";
335
+ const hasRealValue = forced && realValue !== void 0;
331
336
  const displayText = isEditing ? displayValue : inputType === "numeric" ? Number(value).toFixed(precision) : String(value);
332
337
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ui-value-entry-container", children: [
333
- label && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "ui-value-entry-label", children: label }),
338
+ label && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "ui-value-entry-label", children: [
339
+ label,
340
+ forced && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ui-value-entry-forced-badge", "aria-label": "Forced", children: "F" })
341
+ ] }),
334
342
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ui-value-entry-wrapper", children: [
335
343
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
336
344
  "input",
@@ -343,19 +351,57 @@ var ValueEntry = ({
343
351
  onKeyDown: handleKeyDown,
344
352
  disabled,
345
353
  readOnly,
346
- className: `${baseClass} ${statusVariantClass} ${unitClass} ${className}`.trim(),
354
+ className: `${baseClass} ${statusVariantClass} ${forcedClass} ${unitClass} ${className}`.trim(),
347
355
  ...props
348
356
  }
349
357
  ),
350
358
  unit && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ui-value-entry-unit", children: unit }),
351
359
  status === "alarm" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ui-value-entry-badge ui-value-entry-badge--alarm" }),
352
- status === "warning" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ui-value-entry-badge ui-value-entry-badge--warning" })
360
+ status === "warning" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ui-value-entry-badge ui-value-entry-badge--warning" }),
361
+ !label && forced && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ui-value-entry-forced-badge", "aria-label": "Forced", children: "F" })
362
+ ] }),
363
+ hasRealValue && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ui-value-entry-real-value", children: [
364
+ "REAL ",
365
+ String(realValue),
366
+ unit ? ` ${unit}` : ""
353
367
  ] })
354
368
  ] });
355
369
  };
356
370
 
357
371
  // src/components/ValueEntry/LegacyValueEntry.tsx
358
372
  var import_react3 = require("react");
373
+
374
+ // src/theme/statusColors.ts
375
+ var DEFAULT_STATUS_COLORS = {
376
+ normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
377
+ warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
378
+ alarm: { fill: "#ef4444", stroke: "#dc2626", bg: "#FF0000", text: "#ffffff", border: "#dc2626" },
379
+ fault: { fill: "#dc2626", stroke: "#b91c1c", bg: "#FF0000", text: "#ffffff", border: "#b91c1c" },
380
+ off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
381
+ starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
382
+ stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" }
383
+ };
384
+ var FORCED_MARK_COLOR = {
385
+ fill: "#ede9fe",
386
+ stroke: "#5b21b6",
387
+ bg: "#ede9fe",
388
+ text: "#5b21b6",
389
+ border: "#7c3aed"
390
+ };
391
+ var STATUS_ALIASES = {
392
+ running: "normal",
393
+ stopped: "off"
394
+ };
395
+ function resolveStatusColor(status, customMap) {
396
+ const raw = (status || "off").toLowerCase();
397
+ const key = STATUS_ALIASES[raw] ?? raw;
398
+ const defaults = DEFAULT_STATUS_COLORS[key] ?? DEFAULT_STATUS_COLORS.off;
399
+ const overrides = customMap?.[key];
400
+ if (!overrides) return defaults;
401
+ return { ...defaults, ...overrides };
402
+ }
403
+
404
+ // src/components/ValueEntry/LegacyValueEntry.tsx
359
405
  var import_jsx_runtime4 = require("react/jsx-runtime");
360
406
  var LegacyValueEntry = ({
361
407
  value,
@@ -368,6 +414,8 @@ var LegacyValueEntry = ({
368
414
  inputType = "numeric",
369
415
  label,
370
416
  readOnly = false,
417
+ forced = false,
418
+ realValue,
371
419
  className = "",
372
420
  disabled,
373
421
  ...props
@@ -448,6 +496,27 @@ var LegacyValueEntry = ({
448
496
  }
449
497
  };
450
498
  const statusStyles = getStatusStyles();
499
+ const forcedBadgeStyle = {
500
+ display: "inline-flex",
501
+ alignItems: "center",
502
+ justifyContent: "center",
503
+ width: "1.1rem",
504
+ height: "1.1rem",
505
+ fontSize: "0.7rem",
506
+ fontWeight: 700,
507
+ color: FORCED_MARK_COLOR.text,
508
+ backgroundColor: FORCED_MARK_COLOR.bg,
509
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
510
+ borderRadius: "50%",
511
+ marginLeft: "8px",
512
+ lineHeight: 1
513
+ };
514
+ const realValueStyle = {
515
+ fontSize: "0.75rem",
516
+ fontWeight: 500,
517
+ color: "#94a3b8",
518
+ letterSpacing: "0.02em"
519
+ };
451
520
  const inputStyle = {
452
521
  width: "12rem",
453
522
  height: "4rem",
@@ -496,17 +565,22 @@ var LegacyValueEntry = ({
496
565
  gap: "0.75rem"
497
566
  },
498
567
  children: [
499
- label && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
568
+ (label || forced) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
500
569
  "label",
501
570
  {
502
571
  style: {
572
+ display: "flex",
573
+ alignItems: "center",
503
574
  fontSize: "0.875rem",
504
575
  fontWeight: "600",
505
576
  color: "#cbd5e1",
506
577
  textTransform: "uppercase",
507
578
  letterSpacing: "0.05em"
508
579
  },
509
- children: label
580
+ children: [
581
+ label,
582
+ forced && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: forcedBadgeStyle, "aria-label": "Forced", children: "F" })
583
+ ]
510
584
  }
511
585
  ),
512
586
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { position: "relative", display: "inline-flex", alignItems: "center" }, children: [
@@ -563,6 +637,11 @@ var LegacyValueEntry = ({
563
637
  }
564
638
  }
565
639
  )
640
+ ] }),
641
+ forced && realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: realValueStyle, children: [
642
+ "REAL ",
643
+ String(realValue),
644
+ unit ? ` ${unit}` : ""
566
645
  ] })
567
646
  ]
568
647
  }
@@ -578,6 +657,9 @@ var ControlPanel = ({
578
657
  onModeChange,
579
658
  setpointValue,
580
659
  onSetpointChange,
660
+ currentSetpointValue,
661
+ currentSetpointForced = false,
662
+ currentSetpointRealValue,
581
663
  secondSetpointValue,
582
664
  onSecondSetpointChange,
583
665
  modeConfigs = {},
@@ -674,6 +756,21 @@ var ControlPanel = ({
674
756
  disabled: status === "disabled"
675
757
  }
676
758
  ) }),
759
+ currentSetpointValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
760
+ LegacyValueEntry,
761
+ {
762
+ value: currentSetpointValue,
763
+ onChange: () => {
764
+ },
765
+ label: `${currentLabel} (Current)`,
766
+ unit: currentUnit,
767
+ precision: currentPrecision,
768
+ status,
769
+ readOnly: true,
770
+ forced: currentSetpointForced,
771
+ realValue: currentSetpointRealValue
772
+ }
773
+ ) }),
677
774
  hasSecondSetpoint && onSecondSetpointChange && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
678
775
  LegacyValueEntry,
679
776
  {
@@ -764,6 +861,8 @@ var StatusIndicator = ({
764
861
  labelPosition = "right",
765
862
  shape = "circle",
766
863
  led = true,
864
+ forced = false,
865
+ realValue,
767
866
  className = "",
768
867
  ...props
769
868
  }) => {
@@ -787,28 +886,43 @@ var StatusIndicator = ({
787
886
  return "ui-status-indicator-container ui-status-indicator-label-right";
788
887
  }
789
888
  };
889
+ const forcedClass = forced ? "ui-status-indicator--forced" : "";
890
+ const hasRealValue = forced && realValue !== void 0;
790
891
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: getContainerLayout(), children: [
791
892
  label && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-label", children: label }),
792
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
793
- "span",
794
- {
795
- role: "status",
796
- "aria-label": label || `Status: ${status}`,
797
- "aria-description": getStatusDescription(status),
798
- "data-status": status,
799
- className: `
800
- ${baseClass}
801
- ${statusClass}
802
- ${sizeClass}
803
- ${shapeClass}
804
- ${animationClass}
805
- ${ledClass}
806
- ${className}
807
- `.replace(/\s+/g, " ").trim(),
808
- ...props
809
- }
810
- ),
811
- label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-label", children: label })
893
+ hasRealValue && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ui-status-indicator-real-value", children: [
894
+ "REAL ",
895
+ realValue
896
+ ] }),
897
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { position: "relative", display: "inline-flex" }, children: [
898
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
899
+ "span",
900
+ {
901
+ role: "status",
902
+ "aria-label": label || `Status: ${status}`,
903
+ "aria-description": getStatusDescription(status),
904
+ "data-status": status,
905
+ "data-forced": forced,
906
+ className: `
907
+ ${baseClass}
908
+ ${statusClass}
909
+ ${sizeClass}
910
+ ${shapeClass}
911
+ ${animationClass}
912
+ ${ledClass}
913
+ ${forcedClass}
914
+ ${className}
915
+ `.replace(/\s+/g, " ").trim(),
916
+ ...props
917
+ }
918
+ ),
919
+ forced && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-forced-badge", "aria-label": "Forced", children: "F" })
920
+ ] }),
921
+ label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ui-status-indicator-label", children: label }),
922
+ hasRealValue && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ui-status-indicator-real-value", children: [
923
+ "REAL ",
924
+ realValue
925
+ ] })
812
926
  ] });
813
927
  };
814
928
 
@@ -936,6 +1050,9 @@ var NodeControlsPanel = ({
936
1050
  onModeChange: config.onModeChange,
937
1051
  setpointValue: config.setpoint,
938
1052
  onSetpointChange: config.onSetpointChange,
1053
+ currentSetpointValue: config.currentSetpoint,
1054
+ currentSetpointForced: config.currentSetpointForced,
1055
+ currentSetpointRealValue: config.currentSetpointRealValue,
939
1056
  secondSetpointValue: config.secondSetpoint,
940
1057
  onSecondSetpointChange: config.onSecondSetpointChange,
941
1058
  modeConfigs: config.modeConfigs,
@@ -1000,31 +1117,6 @@ var NodeControlsPanel = ({
1000
1117
 
1001
1118
  // src/components/DeviceControlPanel/DeviceControlPanel.tsx
1002
1119
  var import_react5 = __toESM(require("react"), 1);
1003
-
1004
- // src/theme/statusColors.ts
1005
- var DEFAULT_STATUS_COLORS = {
1006
- normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
1007
- warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
1008
- alarm: { fill: "#ef4444", stroke: "#dc2626", bg: "#FF0000", text: "#ffffff", border: "#dc2626" },
1009
- fault: { fill: "#dc2626", stroke: "#b91c1c", bg: "#FF0000", text: "#ffffff", border: "#b91c1c" },
1010
- off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
1011
- starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
1012
- stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" }
1013
- };
1014
- var STATUS_ALIASES = {
1015
- running: "normal",
1016
- stopped: "off"
1017
- };
1018
- function resolveStatusColor(status, customMap) {
1019
- const raw = (status || "off").toLowerCase();
1020
- const key = STATUS_ALIASES[raw] ?? raw;
1021
- const defaults = DEFAULT_STATUS_COLORS[key] ?? DEFAULT_STATUS_COLORS.off;
1022
- const overrides = customMap?.[key];
1023
- if (!overrides) return defaults;
1024
- return { ...defaults, ...overrides };
1025
- }
1026
-
1027
- // src/components/DeviceControlPanel/DeviceControlPanel.tsx
1028
1120
  var import_jsx_runtime8 = require("react/jsx-runtime");
1029
1121
  function DeviceControlPanelImpl({
1030
1122
  binding,
@@ -1672,10 +1764,13 @@ function DeviceControlPanelImpl({
1672
1764
  marginBottom: spacing.gap
1673
1765
  },
1674
1766
  children: [
1675
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1767
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1676
1768
  "span",
1677
1769
  {
1678
1770
  style: {
1771
+ display: "flex",
1772
+ alignItems: "center",
1773
+ gap: "6px",
1679
1774
  fontSize: `${fontSize.label}px`,
1680
1775
  fontWeight: 600,
1681
1776
  textTransform: "uppercase",
@@ -1683,7 +1778,29 @@ function DeviceControlPanelImpl({
1683
1778
  fontFamily: "Arial, sans-serif",
1684
1779
  color: mutedTextColor
1685
1780
  },
1686
- children: param.label
1781
+ children: [
1782
+ param.label,
1783
+ param.forced?.active && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1784
+ "span",
1785
+ {
1786
+ "aria-label": "Forced",
1787
+ style: {
1788
+ display: "inline-flex",
1789
+ alignItems: "center",
1790
+ justifyContent: "center",
1791
+ width: "16px",
1792
+ height: "16px",
1793
+ fontSize: "10px",
1794
+ fontWeight: 700,
1795
+ color: FORCED_MARK_COLOR.text,
1796
+ backgroundColor: FORCED_MARK_COLOR.bg,
1797
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
1798
+ borderRadius: "50%"
1799
+ },
1800
+ children: "F"
1801
+ }
1802
+ )
1803
+ ]
1687
1804
  }
1688
1805
  ),
1689
1806
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
@@ -1705,6 +1822,27 @@ function DeviceControlPanelImpl({
1705
1822
  ]
1706
1823
  }
1707
1824
  ),
1825
+ param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1826
+ "div",
1827
+ {
1828
+ style: {
1829
+ display: "flex",
1830
+ justifyContent: "flex-end",
1831
+ marginTop: "-4px",
1832
+ marginBottom: spacing.gap,
1833
+ fontSize: "10px",
1834
+ fontWeight: 500,
1835
+ fontFamily: "Arial, sans-serif",
1836
+ color: mutedTextColor
1837
+ },
1838
+ children: [
1839
+ "REAL",
1840
+ " ",
1841
+ typeof param.forced.realValue === "boolean" ? param.forced.realValue ? "ON" : "OFF" : String(param.forced.realValue),
1842
+ param.unit ? ` ${param.unit}` : ""
1843
+ ]
1844
+ }
1845
+ ),
1708
1846
  param.type === "boolean" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1709
1847
  "button",
1710
1848
  {
@@ -2803,19 +2941,38 @@ var CardUnit = (0, import_styled2.default)("div")({
2803
2941
 
2804
2942
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.visuals.tsx
2805
2943
  var import_jsx_runtime12 = require("react/jsx-runtime");
2944
+ var forcedTagStyle = {
2945
+ position: "absolute",
2946
+ top: "-6px",
2947
+ right: "-6px",
2948
+ width: "14px",
2949
+ height: "14px",
2950
+ display: "flex",
2951
+ alignItems: "center",
2952
+ justifyContent: "center",
2953
+ fontSize: "9px",
2954
+ fontWeight: 700,
2955
+ color: FORCED_MARK_COLOR.text,
2956
+ backgroundColor: FORCED_MARK_COLOR.bg,
2957
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
2958
+ borderRadius: "50%",
2959
+ pointerEvents: "none"
2960
+ };
2806
2961
  var TrendLine = ({
2807
2962
  values,
2808
2963
  width = 60,
2809
2964
  height = 20,
2810
2965
  color = "#4fc3f7",
2811
2966
  strokeWidth = 1.5,
2812
- backgroundColor = "rgba(255, 255, 255, 0.1)"
2967
+ backgroundColor = "rgba(255, 255, 255, 0.1)",
2968
+ forced = false
2813
2969
  }) => {
2814
2970
  if (!values || values.length < 2) {
2815
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2971
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2816
2972
  "div",
2817
2973
  {
2818
2974
  style: {
2975
+ position: "relative",
2819
2976
  width,
2820
2977
  height,
2821
2978
  backgroundColor,
@@ -2824,7 +2981,10 @@ var TrendLine = ({
2824
2981
  alignItems: "center",
2825
2982
  justifyContent: "center"
2826
2983
  },
2827
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" })
2984
+ children: [
2985
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" }),
2986
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
2987
+ ]
2828
2988
  }
2829
2989
  );
2830
2990
  }
@@ -2836,7 +2996,7 @@ var TrendLine = ({
2836
2996
  const x = 4 + i / (values.length - 1) * (width - 8);
2837
2997
  return i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;
2838
2998
  }).join(" ");
2839
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2999
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2840
3000
  "div",
2841
3001
  {
2842
3002
  style: {
@@ -2845,30 +3005,41 @@ var TrendLine = ({
2845
3005
  backgroundColor,
2846
3006
  borderRadius: "2px",
2847
3007
  position: "relative",
2848
- overflow: "hidden"
3008
+ overflow: "visible"
2849
3009
  },
2850
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width, height, style: { position: "absolute", top: 0, left: 0 }, children: [
2851
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2852
- "path",
3010
+ children: [
3011
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3012
+ "svg",
2853
3013
  {
2854
- d: pathData,
2855
- fill: "none",
2856
- stroke: color,
2857
- strokeWidth,
2858
- strokeLinecap: "round",
2859
- strokeLinejoin: "round"
3014
+ width,
3015
+ height,
3016
+ style: { position: "absolute", top: 0, left: 0, overflow: "hidden", borderRadius: "2px" },
3017
+ children: [
3018
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3019
+ "path",
3020
+ {
3021
+ d: pathData,
3022
+ fill: "none",
3023
+ stroke: color,
3024
+ strokeWidth,
3025
+ strokeLinecap: "round",
3026
+ strokeLinejoin: "round"
3027
+ }
3028
+ ),
3029
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3030
+ "circle",
3031
+ {
3032
+ cx: 4 + (width - 8),
3033
+ cy: normalizedValues[normalizedValues.length - 1],
3034
+ r: 1.5,
3035
+ fill: color
3036
+ }
3037
+ )
3038
+ ]
2860
3039
  }
2861
3040
  ),
2862
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2863
- "circle",
2864
- {
2865
- cx: 4 + (width - 8),
2866
- cy: normalizedValues[normalizedValues.length - 1],
2867
- r: 1.5,
2868
- fill: color
2869
- }
2870
- )
2871
- ] })
3041
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3042
+ ]
2872
3043
  }
2873
3044
  );
2874
3045
  };
@@ -2884,7 +3055,9 @@ var CircularGauge = ({
2884
3055
  unit,
2885
3056
  label,
2886
3057
  size = 60,
2887
- color = "rgba(255, 255, 255, 0.9)"
3058
+ color = "rgba(255, 255, 255, 0.9)",
3059
+ forced = false,
3060
+ realValue
2888
3061
  }) => {
2889
3062
  const percentage = Math.min(value / max * 100, 100);
2890
3063
  const strokeWidth = 4;
@@ -2915,7 +3088,7 @@ var CircularGauge = ({
2915
3088
  height: size,
2916
3089
  borderRadius: "50%",
2917
3090
  backgroundColor,
2918
- border: `1px solid ${borderStrokeColor}`
3091
+ border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : `1px solid ${borderStrokeColor}`
2919
3092
  },
2920
3093
  children: [
2921
3094
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
@@ -2991,12 +3164,39 @@ var CircularGauge = ({
2991
3164
  },
2992
3165
  children: label
2993
3166
  }
2994
- )
3167
+ ),
3168
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3169
+ "span",
3170
+ {
3171
+ "aria-label": "Forced",
3172
+ style: {
3173
+ display: "inline-flex",
3174
+ alignItems: "center",
3175
+ justifyContent: "center",
3176
+ width: "14px",
3177
+ height: "14px",
3178
+ fontSize: "9px",
3179
+ fontWeight: 700,
3180
+ color: FORCED_MARK_COLOR.text,
3181
+ backgroundColor: FORCED_MARK_COLOR.bg,
3182
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
3183
+ borderRadius: "50%",
3184
+ marginTop: "2px"
3185
+ },
3186
+ children: "F"
3187
+ }
3188
+ ),
3189
+ forced && realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { fontSize: "8px", opacity: 0.8, color: "white", marginTop: "1px" }, children: [
3190
+ "REAL ",
3191
+ realValue.toFixed(1),
3192
+ " ",
3193
+ unit
3194
+ ] })
2995
3195
  ]
2996
3196
  }
2997
3197
  );
2998
3198
  };
2999
- var Sparkline = ({ data, width, height, color }) => {
3199
+ var Sparkline = ({ data, width, height, color, forced = false }) => {
3000
3200
  if (!data || data.length < 2) {
3001
3201
  return null;
3002
3202
  }
@@ -3011,24 +3211,52 @@ var Sparkline = ({ data, width, height, color }) => {
3011
3211
  const y = height - 8 - (value - min) / range * (height - 8);
3012
3212
  return `${x + 4},${y + 4}`;
3013
3213
  }).join(" ");
3014
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3214
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3015
3215
  "div",
3016
3216
  {
3017
3217
  style: {
3218
+ position: "relative",
3018
3219
  width,
3019
3220
  height,
3020
3221
  backgroundColor: "rgba(0, 0, 0, 0.7)",
3021
- border: "1px solid rgba(255, 255, 255, 0.3)",
3222
+ border: forced ? `1px solid ${FORCED_MARK_COLOR.border}` : "1px solid rgba(255, 255, 255, 0.3)",
3022
3223
  borderRadius: "4px",
3023
3224
  display: "flex",
3024
3225
  alignItems: "center",
3025
3226
  justifyContent: "center",
3026
3227
  boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)"
3027
3228
  },
3028
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { width, height, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) })
3229
+ children: [
3230
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { width, height, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) }),
3231
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: forcedTagStyle, "aria-label": "Forced", children: "F" })
3232
+ ]
3029
3233
  }
3030
3234
  );
3031
3235
  };
3236
+ var EquipmentForcedBadge = () => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3237
+ "span",
3238
+ {
3239
+ "aria-label": "Forced",
3240
+ style: {
3241
+ position: "absolute",
3242
+ top: "-4px",
3243
+ right: "-4px",
3244
+ width: "11px",
3245
+ height: "11px",
3246
+ borderRadius: "50%",
3247
+ backgroundColor: FORCED_MARK_COLOR.bg,
3248
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
3249
+ color: FORCED_MARK_COLOR.text,
3250
+ fontSize: "7px",
3251
+ fontWeight: 700,
3252
+ display: "flex",
3253
+ alignItems: "center",
3254
+ justifyContent: "center",
3255
+ lineHeight: 1
3256
+ },
3257
+ children: "F"
3258
+ }
3259
+ );
3032
3260
  var EquipmentIndicatorWithSparkline = ({
3033
3261
  svgX,
3034
3262
  svgY,
@@ -3036,11 +3264,51 @@ var EquipmentIndicatorWithSparkline = ({
3036
3264
  label,
3037
3265
  size = 30,
3038
3266
  sparklineData,
3039
- sparklineOffset = { x: 40, y: 0 }
3267
+ sparklineOffset = { x: 40, y: 0 },
3268
+ forced = false
3040
3269
  }) => {
3041
3270
  const hasSparkline = sparklineData && sparklineData.length > 1 && Math.max(...sparklineData) !== Math.min(...sparklineData);
3042
3271
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
3043
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3272
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { position: "relative", width: size, height: size }, children: [
3273
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3274
+ "div",
3275
+ {
3276
+ style: {
3277
+ width: size,
3278
+ height: size,
3279
+ borderRadius: "50%",
3280
+ backgroundColor: color,
3281
+ display: "flex",
3282
+ alignItems: "center",
3283
+ justifyContent: "center",
3284
+ color: "white",
3285
+ fontSize: "7px",
3286
+ fontWeight: "700",
3287
+ textAlign: "center",
3288
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
3289
+ border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : "2px solid rgba(255, 255, 255, 0.3)"
3290
+ },
3291
+ children: label
3292
+ }
3293
+ ),
3294
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EquipmentForcedBadge, {})
3295
+ ] }) }),
3296
+ hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Sparkline, { data: sparklineData, width: 60, height: 20, color, forced }) })
3297
+ ] });
3298
+ };
3299
+ var EquipmentIndicator = ({
3300
+ svgX,
3301
+ svgY,
3302
+ color,
3303
+ label,
3304
+ size = 30,
3305
+ style,
3306
+ className,
3307
+ forced = false
3308
+ }) => {
3309
+ const overlayStyle = style !== void 0 ? { transform: "translate(-50%, -50%)", ...style } : { transform: "translate(-50%, -50%)" };
3310
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { position: "relative", width: size, height: size }, children: [
3311
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3044
3312
  "div",
3045
3313
  {
3046
3314
  style: {
@@ -3056,45 +3324,13 @@ var EquipmentIndicatorWithSparkline = ({
3056
3324
  fontWeight: "700",
3057
3325
  textAlign: "center",
3058
3326
  boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
3059
- border: "2px solid rgba(255, 255, 255, 0.3)"
3327
+ border: forced ? `2px solid ${FORCED_MARK_COLOR.border}` : "2px solid rgba(255, 255, 255, 0.3)"
3060
3328
  },
3061
3329
  children: label
3062
3330
  }
3063
- ) }),
3064
- hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Sparkline, { data: sparklineData, width: 60, height: 20, color }) })
3065
- ] });
3066
- };
3067
- var EquipmentIndicator = ({
3068
- svgX,
3069
- svgY,
3070
- color,
3071
- label,
3072
- size = 30,
3073
- style,
3074
- className
3075
- }) => {
3076
- const overlayStyle = style !== void 0 ? { transform: "translate(-50%, -50%)", ...style } : { transform: "translate(-50%, -50%)" };
3077
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3078
- "div",
3079
- {
3080
- style: {
3081
- width: size,
3082
- height: size,
3083
- borderRadius: "50%",
3084
- backgroundColor: color,
3085
- display: "flex",
3086
- alignItems: "center",
3087
- justifyContent: "center",
3088
- color: "white",
3089
- fontSize: "7px",
3090
- fontWeight: "700",
3091
- textAlign: "center",
3092
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
3093
- border: "2px solid rgba(255, 255, 255, 0.3)"
3094
- },
3095
- children: label
3096
- }
3097
- ) });
3331
+ ),
3332
+ forced && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EquipmentForcedBadge, {})
3333
+ ] }) });
3098
3334
  };
3099
3335
 
3100
3336
  // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
@@ -4974,7 +5210,9 @@ function DataOverlayImpl({
4974
5210
  scale = 1,
4975
5211
  statusColorMap
4976
5212
  }) {
4977
- const { value, display, history, scaling } = telemetry;
5213
+ const { value, display, history, scaling, forced } = telemetry;
5214
+ const isForced = forced?.active === true;
5215
+ const hasRealValue = isForced && forced?.realValue !== void 0;
4978
5216
  const showValue = display?.showValue ?? true;
4979
5217
  const showStatus = display?.showStatus ?? true;
4980
5218
  const showSparkline = display?.showSparkline ?? false;
@@ -5023,12 +5261,16 @@ function DataOverlayImpl({
5023
5261
  const finalTextColor = textColor || colors.text;
5024
5262
  const formattedValue = formatValue(value.value);
5025
5263
  const displayText = unit ? `${formattedValue} ${unit}` : formattedValue;
5264
+ const realDisplayText = hasRealValue ? unit ? `REAL ${formatValue(forced.realValue)} ${unit}` : `REAL ${formatValue(forced.realValue)}` : void 0;
5265
+ const borderColor = isForced ? FORCED_MARK_COLOR.border : colors.border;
5266
+ const borderWidth = (isForced ? 2 : 1) * scale;
5026
5267
  const hasSparkline = showSparkline && sparklinePath;
5027
- const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale;
5268
+ const extraMarkRows = realDisplayText ? 1 : 0;
5269
+ const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale + extraMarkRows * 9 * scale;
5028
5270
  const overlayWidth = 80 * scale;
5029
5271
  const displayX = x + offsetX;
5030
5272
  const displayY = y + offsetY;
5031
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("g", { "data-overlay-node": nodeId, "data-status": value.status, children: [
5273
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("g", { "data-overlay-node": nodeId, "data-status": value.status, "data-forced": isForced, children: [
5032
5274
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5033
5275
  "rect",
5034
5276
  {
@@ -5038,8 +5280,8 @@ function DataOverlayImpl({
5038
5280
  height: overlayHeight,
5039
5281
  rx: 4 * scale,
5040
5282
  fill: colors.bg,
5041
- stroke: colors.border,
5042
- strokeWidth: 1 * scale,
5283
+ stroke: borderColor,
5284
+ strokeWidth: borderWidth,
5043
5285
  opacity: "0.95",
5044
5286
  filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
5045
5287
  }
@@ -5088,6 +5330,48 @@ function DataOverlayImpl({
5088
5330
  ]
5089
5331
  }
5090
5332
  ),
5333
+ isForced && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5334
+ "g",
5335
+ {
5336
+ transform: `translate(${displayX + overlayWidth / 2}, ${displayY - 25 * scale})`,
5337
+ "aria-label": "Forced",
5338
+ children: [
5339
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5340
+ "circle",
5341
+ {
5342
+ r: 7 * scale,
5343
+ fill: FORCED_MARK_COLOR.bg,
5344
+ stroke: FORCED_MARK_COLOR.border,
5345
+ strokeWidth: 1.5 * scale
5346
+ }
5347
+ ),
5348
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5349
+ "text",
5350
+ {
5351
+ textAnchor: "middle",
5352
+ dominantBaseline: "central",
5353
+ fontSize: 8 * scale,
5354
+ fontWeight: "700",
5355
+ fill: FORCED_MARK_COLOR.text,
5356
+ children: "F"
5357
+ }
5358
+ )
5359
+ ]
5360
+ }
5361
+ ),
5362
+ realDisplayText && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5363
+ "text",
5364
+ {
5365
+ x: displayX,
5366
+ y: displayY + (value.quality !== void 0 && value.quality < 100 ? 14 : 5) * scale,
5367
+ textAnchor: "middle",
5368
+ fontSize: 7 * scale,
5369
+ fontWeight: "500",
5370
+ fill: finalTextColor,
5371
+ opacity: "0.85",
5372
+ children: realDisplayText
5373
+ }
5374
+ ),
5091
5375
  hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5092
5376
  "path",
5093
5377
  {
@@ -8890,7 +9174,49 @@ function DeviceConfigPanelImpl({
8890
9174
  " (",
8891
9175
  param.unit,
8892
9176
  ")"
8893
- ] })
9177
+ ] }),
9178
+ param.forced?.active && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9179
+ "span",
9180
+ {
9181
+ "aria-label": "Forced",
9182
+ style: {
9183
+ display: "inline-flex",
9184
+ alignItems: "center",
9185
+ justifyContent: "center",
9186
+ width: "16px",
9187
+ height: "16px",
9188
+ marginLeft: "6px",
9189
+ fontSize: "10px",
9190
+ fontWeight: 700,
9191
+ textTransform: "none",
9192
+ letterSpacing: "normal",
9193
+ color: FORCED_MARK_COLOR.text,
9194
+ backgroundColor: FORCED_MARK_COLOR.bg,
9195
+ border: `1px solid ${FORCED_MARK_COLOR.border}`,
9196
+ borderRadius: "50%",
9197
+ verticalAlign: "middle"
9198
+ },
9199
+ children: "F"
9200
+ }
9201
+ )
9202
+ ]
9203
+ }
9204
+ ),
9205
+ param.forced?.active && param.forced.realValue !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9206
+ "div",
9207
+ {
9208
+ style: {
9209
+ fontSize: `${fontSize.description}px`,
9210
+ color: mutedTextColor,
9211
+ marginTop: "-2px",
9212
+ marginBottom: "6px",
9213
+ fontFamily: "Arial, sans-serif"
9214
+ },
9215
+ children: [
9216
+ "REAL",
9217
+ " ",
9218
+ typeof param.forced.realValue === "boolean" ? param.forced.realValue ? "ON" : "OFF" : String(param.forced.realValue),
9219
+ param.unit ? ` ${param.unit}` : ""
8894
9220
  ]
8895
9221
  }
8896
9222
  ),
@@ -9479,6 +9805,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
9479
9805
  DisplayModeToggle,
9480
9806
  EquipmentIndicator,
9481
9807
  EquipmentIndicatorWithSparkline,
9808
+ FORCED_MARK_COLOR,
9482
9809
  FixedSVGContainer,
9483
9810
  FooterPanel,
9484
9811
  FullscreenContainer,