@procaaso/alphinity-ui-components 1.0.4 → 1.0.5

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
@@ -83,6 +83,7 @@ __export(index_exports, {
83
83
  useSelection: () => useSelection,
84
84
  useSimulation: () => useSimulation,
85
85
  useTelemetry: () => useTelemetry,
86
+ useTelemetryStatus: () => useTelemetryStatus,
86
87
  useTheme: () => useTheme,
87
88
  useViewBox: () => useViewBox,
88
89
  validateDiagram: () => validateDiagram
@@ -1785,35 +1786,10 @@ var ThemeToggle = ({
1785
1786
  };
1786
1787
 
1787
1788
  // src/components/PIDCanvas/PIDCanvas.tsx
1788
- var import_react13 = require("react");
1789
+ var import_react15 = require("react");
1789
1790
 
1790
1791
  // src/diagram/symbols/mockSymbolLibrary.ts
1791
1792
  var mockSymbolLibrary = {
1792
- "valve-ball": {
1793
- id: "valve-ball",
1794
- name: "Ball Valve",
1795
- category: "valve",
1796
- viewBox: { x: 0, y: 0, width: 50, height: 50 },
1797
- ports: [
1798
- { id: "inlet", x: 0, y: 25, type: "inlet", direction: "in" },
1799
- { id: "outlet", x: 50, y: 25, type: "outlet", direction: "out" }
1800
- ],
1801
- svgContent: `
1802
- <g>
1803
- <!-- Valve body -->
1804
- <rect x="15" y="10" width="20" height="30" fill="#546e7a" stroke="#263238" stroke-width="2" rx="2"/>
1805
- <!-- Ball indicator -->
1806
- <circle cx="25" cy="25" r="6" fill="#ffa726" stroke="#e65100" stroke-width="1.5"/>
1807
- <!-- Inlet/outlet lines -->
1808
- <line x1="0" y1="25" x2="15" y2="25" stroke="#263238" stroke-width="3" stroke-linecap="round"/>
1809
- <line x1="35" y1="25" x2="50" y2="25" stroke="#263238" stroke-width="3" stroke-linecap="round"/>
1810
- </g>
1811
- `,
1812
- metadata: {
1813
- description: "Ball valve for flow control",
1814
- tags: ["valve", "control", "shutoff"]
1815
- }
1816
- },
1817
1793
  "valve-gate": {
1818
1794
  id: "valve-gate",
1819
1795
  name: "Gate Valve (Powered)",
@@ -1826,11 +1802,11 @@ var mockSymbolLibrary = {
1826
1802
  svgContent: `
1827
1803
  <g>
1828
1804
  <!-- Actuator box -->
1829
- <rect x="16.74" y="0" width="18.02" height="18.02" fill="#edeeef" stroke="#666666" pointer-events="all"/>
1805
+ <rect x="16.74" y="0" width="18.02" height="18.02" fill="var(--symbol-fill, #edeeef)" stroke="var(--symbol-stroke, #666666)" pointer-events="all"/>
1830
1806
  <!-- Actuator stem -->
1831
- <path d="M 25.75 18.02 L 25.75 36.05" fill="none" stroke="#666666" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/>
1807
+ <path d="M 25.75 18.02 L 25.75 36.05" fill="none" stroke="var(--symbol-stroke, #666666)" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/>
1832
1808
  <!-- Valve body (two triangles forming diamond) -->
1833
- <path d="M 0 20.6 L 25.75 36.05 L 0 51.5 Z M 51.5 20.6 L 25.75 36.05 L 51.5 51.5 Z" fill="#edeeef" stroke="#666666" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/>
1809
+ <path d="M 0 20.6 L 25.75 36.05 L 0 51.5 Z M 51.5 20.6 L 25.75 36.05 L 51.5 51.5 Z" fill="var(--symbol-fill, #edeeef)" stroke="var(--symbol-stroke, #666666)" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/>
1834
1810
  </g>
1835
1811
  `,
1836
1812
  metadata: {
@@ -1838,84 +1814,1143 @@ var mockSymbolLibrary = {
1838
1814
  tags: ["valve", "gate", "powered", "actuator"]
1839
1815
  }
1840
1816
  },
1841
- "pump-centrifugal": {
1842
- id: "pump-centrifugal",
1843
- name: "Centrifugal Pump",
1817
+ "pump-positive-displacement": {
1818
+ id: "pump-positive-displacement",
1819
+ name: "Positive Displacement Pump",
1844
1820
  category: "pump",
1845
- viewBox: { x: 0, y: 0, width: 80, height: 60 },
1821
+ viewBox: { x: 0, y: 0, width: 56.25, height: 56.25 },
1846
1822
  ports: [
1847
- { id: "inlet", x: 10, y: 30, type: "inlet", direction: "in" },
1848
- { id: "outlet", x: 70, y: 30, type: "outlet", direction: "out" }
1823
+ { id: "inlet", x: 0, y: 28.125, type: "inlet", direction: "in" },
1824
+ { id: "outlet", x: 56.25, y: 28.125, type: "outlet", direction: "out" }
1825
+ ],
1826
+ svgContent: `
1827
+ <g transform="scale(0.5625)">
1828
+ <!-- Outer circle -->
1829
+ <ellipse
1830
+ cx="50"
1831
+ cy="50"
1832
+ rx="50"
1833
+ ry="50"
1834
+ fill="var(--symbol-fill, #edeeef)"
1835
+ stroke="var(--symbol-stroke, rgb(0, 0, 0))"
1836
+ stroke-width="1"
1837
+ />
1838
+
1839
+ <!-- Diagonal line from top to bottom-right -->
1840
+ <path
1841
+ d="M 50 0 L 100 50 L 50 100"
1842
+ fill="none"
1843
+ stroke="var(--symbol-stroke, rgb(0, 0, 0))"
1844
+ stroke-width="1"
1845
+ stroke-miterlimit="10"
1846
+ />
1847
+
1848
+ <!-- Center square -->
1849
+ <rect
1850
+ x="35"
1851
+ y="35"
1852
+ width="30"
1853
+ height="30"
1854
+ fill="none"
1855
+ stroke="var(--symbol-stroke, rgb(0, 0, 0))"
1856
+ stroke-width="1"
1857
+ />
1858
+ </g>
1859
+ `,
1860
+ metadata: {
1861
+ description: "Positive displacement pump (ISO standard)",
1862
+ tags: ["pump", "positive-displacement", "iso"]
1863
+ }
1864
+ },
1865
+ "vannefold-bottom": {
1866
+ id: "vannefold-bottom",
1867
+ name: "Vannefold Block (Bottom)",
1868
+ category: "valve",
1869
+ viewBox: { x: -0.5, y: -0.5, width: 88, height: 67.785156 },
1870
+ ports: [
1871
+ { id: "bottom-inlet", x: 0, y: 25, type: "control", direction: "in" },
1872
+ { id: "top-outlet", x: 48, y: 65, type: "control", direction: "out" }
1849
1873
  ],
1850
1874
  svgContent: `
1851
1875
  <g>
1852
- <!-- Pump casing -->
1853
- <circle cx="40" cy="30" r="22" fill="#4fc3f7" stroke="#0277bd" stroke-width="2.5"/>
1854
- <!-- Impeller -->
1855
- <path d="M 40 30 L 50 20 M 40 30 L 50 40 M 40 30 L 30 40 M 40 30 L 30 20"
1856
- stroke="#01579b" stroke-width="2.5" stroke-linecap="round"/>
1857
- <!-- Center hub -->
1858
- <circle cx="40" cy="30" r="5" fill="#0277bd" stroke="#01579b" stroke-width="1.5"/>
1859
- <!-- Inlet pipe -->
1860
- <line x1="0" y1="30" x2="18" y2="30" stroke="#0277bd" stroke-width="4" stroke-linecap="round"/>
1861
- <!-- Outlet pipe -->
1862
- <line x1="62" y1="30" x2="80" y2="30" stroke="#0277bd" stroke-width="4" stroke-linecap="round"/>
1876
+ <!-- Bottom inlet pipe -->
1877
+ <path
1878
+ d="M 3,29.785156 H 0 v -10 h 3 m 14,10 h 3 v -10 h -3 m -15,8 h 16 m -16,-6 h 16"
1879
+ fill="none"
1880
+ stroke="var(--symbol-stroke, #000000)"
1881
+ stroke-miterlimit="10"
1882
+ />
1883
+
1884
+ <!-- Top outlet pipe -->
1885
+ <path
1886
+ d="m 43,49.785156 v -3 h 10 v 3 m -10,14 v 3 h 10 v -3 m -8,-15 v 16 m 6,-16 v 16"
1887
+ fill="none"
1888
+ stroke="var(--symbol-stroke, #000000)"
1889
+ stroke-miterlimit="10"
1890
+ />
1891
+
1892
+ <!-- Main block outer -->
1893
+ <rect
1894
+ x="7"
1895
+ y="0"
1896
+ width="80"
1897
+ height="59.785156"
1898
+ fill="var(--symbol-fill, #eaeaea)"
1899
+ stroke="var(--symbol-stroke, #000000)"
1900
+ />
1901
+
1902
+ <!-- Main block inner -->
1903
+ <rect
1904
+ x="9"
1905
+ y="2"
1906
+ width="76"
1907
+ height="55.785156"
1908
+ fill="var(--symbol-fill, #eaeaea)"
1909
+ stroke="var(--symbol-stroke, #000000)"
1910
+ />
1911
+
1912
+ <!-- Bolt holes -->
1913
+ <ellipse cx="15" cy="7" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1914
+ <ellipse cx="15" cy="7" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1915
+ <ellipse cx="79" cy="7" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1916
+ <ellipse cx="79" cy="7" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1917
+ <ellipse cx="15" cy="51.785156" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1918
+ <ellipse cx="15" cy="51.785156" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1919
+ <ellipse cx="79" cy="51.785156" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1920
+ <ellipse cx="79" cy="51.785156" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1921
+
1863
1922
  </g>
1864
1923
  `,
1865
1924
  metadata: {
1866
- description: "Centrifugal pump for fluid transfer",
1867
- tags: ["pump", "centrifugal", "transfer"]
1925
+ description: "Vannefold heat exchanger block with bottom inlet configuration",
1926
+ tags: ["heat-exchanger", "vannefold", "fold", "bottom"]
1868
1927
  }
1869
1928
  },
1870
- "vessel-tank": {
1871
- id: "vessel-tank",
1872
- name: "Storage Tank",
1873
- category: "vessel",
1874
- viewBox: { x: 0, y: 0, width: 60, height: 80 },
1929
+ "vannefold-middle": {
1930
+ id: "vannefold-middle",
1931
+ name: "Vannefold Block (Middle)",
1932
+ category: "valve",
1933
+ viewBox: { x: -0.5, y: -0.5, width: 95, height: 53.097656 },
1875
1934
  ports: [
1876
- { id: "inlet", x: 30, y: 10, type: "inlet", direction: "in" },
1877
- { id: "outlet", x: 30, y: 70, type: "outlet", direction: "out" }
1935
+ { id: "left-inlet", x: 0, y: 27.3125, type: "control", direction: "in" },
1936
+ { id: "right-outlet", x: 95, y: 27.3125, type: "control", direction: "out" }
1878
1937
  ],
1879
1938
  svgContent: `
1880
1939
  <g>
1881
- <!-- Tank body -->
1882
- <rect x="10" y="15" width="40" height="50" fill="#66bb6a" stroke="#2e7d32" stroke-width="2.5" rx="3"/>
1883
- <!-- Tank bottom (rounded) -->
1884
- <ellipse cx="30" cy="65" rx="20" ry="8" fill="#66bb6a" stroke="#2e7d32" stroke-width="2.5"/>
1885
- <!-- Liquid level indicator -->
1886
- <rect x="12" y="35" width="36" height="28" fill="#4dd0e1" opacity="0.6"/>
1887
- <!-- Inlet pipe -->
1888
- <line x1="30" y1="0" x2="30" y2="15" stroke="#2e7d32" stroke-width="3" stroke-linecap="round"/>
1889
- <!-- Outlet pipe -->
1890
- <line x1="30" y1="65" x2="30" y2="80" stroke="#2e7d32" stroke-width="3" stroke-linecap="round"/>
1940
+ <!-- Left inlet pipe -->
1941
+ <path
1942
+ d="M 3,22.3125 H 0 v 10 H 3 m 14,-10 h 3 v 10 h -3 m -15,-8 h 16 m -16,6 h 16"
1943
+ fill="none"
1944
+ stroke="var(--symbol-stroke, #000000)"
1945
+ stroke-miterlimit="10"
1946
+ />
1947
+
1948
+ <!-- Main block outer -->
1949
+ <rect
1950
+ x="7"
1951
+ y="0"
1952
+ width="80"
1953
+ height="52.097656"
1954
+ fill="var(--symbol-fill, #eaeaea)"
1955
+ stroke="var(--symbol-stroke, #000000)"
1956
+ />
1957
+
1958
+ <!-- Main block inner -->
1959
+ <rect
1960
+ x="9"
1961
+ y="0"
1962
+ width="76"
1963
+ height="52.097656"
1964
+ fill="var(--symbol-fill, #eaeaea)"
1965
+ stroke="var(--symbol-stroke, #000000)"
1966
+ />
1967
+
1968
+ <!-- Bolt holes -->
1969
+ <ellipse cx="15" cy="5.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1970
+ <ellipse cx="15" cy="5.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1971
+ <ellipse cx="79" cy="5.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1972
+ <ellipse cx="79" cy="5.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1973
+ <ellipse cx="15" cy="47.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1974
+ <ellipse cx="15" cy="47.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1975
+ <ellipse cx="79" cy="47.3125" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
1976
+ <ellipse cx="79" cy="47.3125" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
1891
1977
  </g>
1892
1978
  `,
1893
1979
  metadata: {
1894
- description: "Storage vessel for liquids",
1895
- tags: ["vessel", "tank", "storage"]
1980
+ description: "Vannefold heat exchanger block with side inlet configuration",
1981
+ tags: ["heat-exchanger", "vannefold", "fold", "middle"]
1896
1982
  }
1897
1983
  },
1898
- "instrument-indicator": {
1899
- id: "instrument-indicator",
1900
- name: "Instrument Indicator",
1901
- category: "instrument",
1902
- viewBox: { x: 0, y: 0, width: 40, height: 40 },
1984
+ "vannefold-top": {
1985
+ id: "vannefold-top",
1986
+ name: "Vannefold Block (Top)",
1987
+ category: "valve",
1988
+ viewBox: { x: -0.5, y: -0.5, width: 95, height: 60.785156 },
1989
+ ports: [
1990
+ { id: "bottom-inlet", x: 0, y: 35, type: "control", direction: "in" },
1991
+ { id: "top-outlet", x: 48, y: 0, type: "control", direction: "out" }
1992
+ ],
1993
+ svgContent: `
1994
+ <g>
1995
+ <!-- Bottom inlet pipe -->
1996
+ <path
1997
+ d="M 3,30 H 0 V 40 H 3 M 17,30 h 3 V 40 H 17 M 2,32 H 18 M 2,38 H 18"
1998
+ fill="none"
1999
+ stroke="var(--symbol-stroke, #000000)"
2000
+ stroke-miterlimit="10"
2001
+ />
2002
+
2003
+ <!-- Main block outer -->
2004
+ <rect
2005
+ x="7"
2006
+ y="0"
2007
+ width="80"
2008
+ height="59.785156"
2009
+ fill="var(--symbol-fill, #eaeaea)"
2010
+ stroke="var(--symbol-stroke, #000000)"
2011
+ />
2012
+
2013
+ <!-- Main block inner -->
2014
+ <rect
2015
+ x="9"
2016
+ y="2"
2017
+ width="76"
2018
+ height="57.785156"
2019
+ fill="var(--symbol-fill, #eaeaea)"
2020
+ stroke="var(--symbol-stroke, #000000)"
2021
+ />
2022
+
2023
+ <!-- Bolt holes -->
2024
+ <ellipse cx="15" cy="8" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2025
+ <ellipse cx="15" cy="8" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2026
+ <ellipse cx="79" cy="8" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2027
+ <ellipse cx="79" cy="8" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2028
+ <ellipse cx="15" cy="55" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2029
+ <ellipse cx="15" cy="55" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2030
+ <ellipse cx="79" cy="55" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2031
+ <ellipse cx="79" cy="55" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2032
+
2033
+ </g>
2034
+ `,
2035
+ metadata: {
2036
+ description: "Vannefold heat exchanger block with bottom inlet and top outlet configuration",
2037
+ tags: ["heat-exchanger", "vannefold", "fold", "top"]
2038
+ }
2039
+ },
2040
+ "vannefold-top-top-inlet": {
2041
+ id: "vannefold-top-top-inlet",
2042
+ name: "Vannefold Block (Top, Top Inlet)",
2043
+ category: "valve",
2044
+ viewBox: { x: 0, y: 0, width: 88, height: 67.576004 },
1903
2045
  ports: [
1904
- { id: "signal", x: 20, y: 40, type: "signal", direction: "bi" }
2046
+ { id: "top-inlet", x: 47, y: 0, type: "control", direction: "in" },
2047
+ { id: "bottom-outlet", x: 0, y: 42, type: "control", direction: "out" }
1905
2048
  ],
1906
2049
  svgContent: `
1907
2050
  <g>
1908
- <!-- Instrument circle -->
1909
- <circle cx="20" cy="20" r="15" fill="#fff" stroke="#616161" stroke-width="2"/>
1910
- <!-- Inner circle -->
1911
- <circle cx="20" cy="20" r="12" fill="none" stroke="#616161" stroke-width="1"/>
1912
- <!-- Signal line -->
1913
- <line x1="20" y1="35" x2="20" y2="40" stroke="#616161" stroke-width="2" stroke-linecap="round"/>
2051
+ <!-- Top inlet pipe flange -->
2052
+ <path
2053
+ d="M 52,3 V 1.3598666e-8 H 42 V 3 m 10,14 v 3 H 42 V 17 M 50,2 V 18 M 44,2 v 16"
2054
+ fill="none"
2055
+ stroke="var(--symbol-stroke, #000000)"
2056
+ stroke-miterlimit="10"
2057
+ />
2058
+
2059
+ <!-- Bottom outlet pipe flange -->
2060
+ <path
2061
+ d="M 3,36.79085 H 0 v 10 h 3 m 14,-10 h 3 v 10 h -3 m -15,-8 h 16 m -16,6 h 16"
2062
+ fill="none"
2063
+ stroke="var(--symbol-stroke, #000000)"
2064
+ stroke-miterlimit="10"
2065
+ />
2066
+
2067
+ <!-- Main block outer -->
2068
+ <rect
2069
+ x="7"
2070
+ y="6.7908498"
2071
+ width="80"
2072
+ height="59.785156"
2073
+ fill="var(--symbol-fill, #eaeaea)"
2074
+ stroke="var(--symbol-stroke, #000000)"
2075
+ />
2076
+
2077
+ <!-- Main block inner -->
2078
+ <rect
2079
+ x="9"
2080
+ y="8.7908498"
2081
+ width="76"
2082
+ height="57.785156"
2083
+ fill="var(--symbol-fill, #eaeaea)"
2084
+ stroke="var(--symbol-stroke, #000000)"
2085
+ />
2086
+
2087
+ <!-- Bolt holes -->
2088
+ <ellipse cx="15" cy="14.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2089
+ <ellipse cx="15" cy="14.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2090
+ <ellipse cx="15" cy="61.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2091
+ <ellipse cx="15" cy="61.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2092
+ <ellipse cx="79" cy="14.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2093
+ <ellipse cx="79" cy="14.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2094
+ <ellipse cx="79" cy="61.79085" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2095
+ <ellipse cx="79" cy="61.79085" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2096
+
2097
+ </g>
2098
+ `,
2099
+ metadata: {
2100
+ description: "Vannefold heat exchanger block with top inlet and bottom outlet configuration",
2101
+ tags: ["heat-exchanger", "vannefold", "fold", "top", "top-inlet"]
2102
+ }
2103
+ },
2104
+ "vannefold-left": {
2105
+ id: "vannefold-left",
2106
+ name: "Vannefold Block (Left)",
2107
+ category: "valve",
2108
+ viewBox: { x: 0, y: 0, width: 67.785156, height: 88.5 },
2109
+ ports: [
2110
+ { id: "left-inlet", x: 0, y: 41.5, type: "control", direction: "in" },
2111
+ { id: "right-outlet", x: 42, y: 87, type: "control", direction: "out" }
2112
+ ],
2113
+ svgContent: `
2114
+ <g>
2115
+ <!-- Left inlet pipe flange -->
2116
+ <path
2117
+ d="M 3,46.5 H 2.8789924e-8 v -10 H 3 m 14,10 h 3 v -10 h -3 m -15,8 h 16 m -16,-6 h 16"
2118
+ fill="none"
2119
+ stroke="var(--symbol-stroke, #000000)"
2120
+ stroke-miterlimit="10"
2121
+ />
2122
+
2123
+ <!-- Right outlet pipe flange -->
2124
+ <path
2125
+ d="m 37,70.5 v -3 h 10 v 3 m -10,14 v 3 h 10 v -3 m -8,-15 v 16 m 6,-16 v 16"
2126
+ fill="none"
2127
+ stroke="var(--symbol-stroke, #000000)"
2128
+ stroke-miterlimit="10"
2129
+ />
2130
+
2131
+ <!-- Main block outer -->
2132
+ <rect
2133
+ x="7"
2134
+ y="0.5"
2135
+ width="59.785156"
2136
+ height="80"
2137
+ fill="var(--symbol-fill, #eaeaea)"
2138
+ stroke="var(--symbol-stroke, #000000)"
2139
+ />
2140
+
2141
+ <!-- Main block inner -->
2142
+ <rect
2143
+ x="9"
2144
+ y="2.5"
2145
+ width="57.785156"
2146
+ height="76"
2147
+ fill="var(--symbol-fill, #eaeaea)"
2148
+ stroke="var(--symbol-stroke, #000000)"
2149
+ />
2150
+
2151
+ <!-- Bolt holes -->
2152
+ <ellipse cx="15" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2153
+ <ellipse cx="15" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2154
+ <ellipse cx="62" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2155
+ <ellipse cx="62" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2156
+ <ellipse cx="15" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2157
+ <ellipse cx="15" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2158
+ <ellipse cx="62" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2159
+ <ellipse cx="62" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2160
+
2161
+ `,
2162
+ metadata: {
2163
+ description: "Vannefold heat exchanger block with left inlet and right outlet, vertical orientation",
2164
+ tags: ["heat-exchanger", "vannefold", "fold", "left", "vertical"]
2165
+ }
2166
+ },
2167
+ "vannefold-right": {
2168
+ id: "vannefold-right",
2169
+ name: "Vannefold Block (Right)",
2170
+ category: "valve",
2171
+ viewBox: { x: 0, y: 0, width: 67.785156, height: 88.5 },
2172
+ ports: [
2173
+ { id: "right-inlet", x: 67.785156, y: 41.5, type: "control", direction: "in" },
2174
+ { id: "left-outlet", x: 24.785156, y: 87.5, type: "control", direction: "out" }
2175
+ ],
2176
+ svgContent: `
2177
+ <g>
2178
+ <!-- Right inlet pipe flange -->
2179
+ <path
2180
+ d="m 63.785156,46.5 h 3 v -10 h -3 m -14,10 h -3 v -10 h 3 m 15,8 h -16 m 16,-6 h -16"
2181
+ fill="none"
2182
+ stroke="var(--symbol-stroke, #000000)"
2183
+ stroke-miterlimit="10"
2184
+ />
2185
+
2186
+ <!-- Left outlet pipe flange -->
2187
+ <path
2188
+ d="m 29.785156,70.5 v -3 h -10 v 3 m 10,14 v 3 h -10 v -3 m 8,-15 v 16 m -6,-16 v 16"
2189
+ fill="none"
2190
+ stroke="var(--symbol-stroke, #000000)"
2191
+ stroke-miterlimit="10"
2192
+ />
2193
+
2194
+ <!-- Main block outer -->
2195
+ <rect
2196
+ x="0"
2197
+ y="0.5"
2198
+ width="59.785156"
2199
+ height="80"
2200
+ fill="var(--symbol-fill, #eaeaea)"
2201
+ stroke="var(--symbol-stroke, #000000)"
2202
+ />
2203
+
2204
+ <!-- Main block inner -->
2205
+ <rect
2206
+ x="0"
2207
+ y="2.5"
2208
+ width="57.785156"
2209
+ height="76"
2210
+ fill="var(--symbol-fill, #eaeaea)"
2211
+ stroke="var(--symbol-stroke, #000000)"
2212
+ />
2213
+
2214
+ <!-- Bolt holes -->
2215
+ <ellipse cx="51.785156" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2216
+ <ellipse cx="51.785156" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2217
+ <ellipse cx="4.785156" cy="8.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2218
+ <ellipse cx="4.785156" cy="8.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2219
+ <ellipse cx="51.785156" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2220
+ <ellipse cx="51.785156" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2221
+ <ellipse cx="4.785156" cy="72.5" rx="5" ry="5" fill="#ffffff" stroke="var(--symbol-stroke, #000000)" />
2222
+ <ellipse cx="4.785156" cy="72.5" rx="3" ry="3" fill="none" stroke="var(--symbol-stroke, #000000)" />
2223
+
2224
+ `,
2225
+ metadata: {
2226
+ description: "Vannefold heat exchanger block with right inlet and left outlet, vertical orientation",
2227
+ tags: ["heat-exchanger", "vannefold", "fold", "right", "vertical"]
2228
+ }
2229
+ },
2230
+ "pixer": {
2231
+ id: "pixer",
2232
+ name: "Pixer",
2233
+ category: "pump",
2234
+ viewBox: { x: 0, y: 0, width: 50.625, height: 46.6875 },
2235
+ ports: [
2236
+ { id: "right-outlet", x: 50.625, y: 30.9375, type: "control", direction: "out" },
2237
+ { id: "left-outlet", x: 0, y: 30.9375, type: "control", direction: "out" },
2238
+ { id: "left-inlet", x: 50.625, y: 19.6875, type: "control", direction: "in" },
2239
+ { id: "right-inlet", x: 0, y: 19.6875, type: "control", direction: "in" },
2240
+ { id: "top-inlet", x: 25.3125, y: 0, type: "control", direction: "in" }
2241
+ ],
2242
+ svgContent: `
2243
+ <g transform="scale(0.196875) translate(-195, -186)">
2244
+ <!-- Top mounting flange -->
2245
+ <rect
2246
+ width="141.313"
2247
+ height="9.872"
2248
+ transform="translate(252.392 185.89)"
2249
+ stroke="var(--symbol-stroke, #000000)"
2250
+ stroke-miterlimit="10"
2251
+ stroke-width="1"
2252
+ fill="var(--symbol-fill, #eaeaea)"
2253
+ />
2254
+
2255
+ <!-- Motor housing top -->
2256
+ <rect
2257
+ width="106.614"
2258
+ height="2.468"
2259
+ transform="translate(269.742 199.918)"
2260
+ stroke="var(--symbol-stroke, #000000)"
2261
+ stroke-miterlimit="10"
2262
+ stroke-width="1"
2263
+ fill="var(--symbol-fill, #e0e0e0)"
2264
+ />
2265
+ <rect
2266
+ width="106.614"
2267
+ height="5.835"
2268
+ transform="translate(269.742 202.384)"
2269
+ stroke="var(--symbol-stroke, #000000)"
2270
+ stroke-miterlimit="10"
2271
+ stroke-width="1"
2272
+ fill="var(--symbol-fill, #e0e0e0)"
2273
+ />
2274
+ <rect
2275
+ width="106.614"
2276
+ height="2.468"
2277
+ transform="translate(269.742 208.222)"
2278
+ stroke="var(--symbol-stroke, #000000)"
2279
+ stroke-miterlimit="10"
2280
+ stroke-width="1"
2281
+ fill="var(--symbol-fill, #e0e0e0)"
2282
+ />
2283
+
2284
+ <!-- Bottom mounting flange -->
2285
+ <rect
2286
+ width="221.702"
2287
+ height="2.401"
2288
+ transform="translate(212.199 395.587)"
2289
+ stroke="var(--symbol-stroke, #000000)"
2290
+ stroke-miterlimit="10"
2291
+ stroke-width="1"
2292
+ fill="var(--symbol-fill, #e0e0e0)"
2293
+ />
2294
+ <rect
2295
+ width="221.702"
2296
+ height="6.269"
2297
+ transform="translate(212.199 397.988)"
2298
+ stroke="var(--symbol-stroke, #000000)"
2299
+ stroke-miterlimit="10"
2300
+ stroke-width="1"
2301
+ fill="var(--symbol-fill, #e0e0e0)"
2302
+ />
2303
+ <rect
2304
+ width="221.702"
2305
+ height="2.65"
2306
+ transform="translate(212.199 404.257)"
2307
+ stroke="var(--symbol-stroke, #000000)"
2308
+ stroke-miterlimit="10"
2309
+ stroke-width="1"
2310
+ fill="var(--symbol-fill, #e0e0e0)"
2311
+ />
2312
+
2313
+ <!-- Motor housing transition -->
2314
+ <path
2315
+ d="M444.7,228.776l17.117,4.1H568.429l17.089-4.085a.032.032,0,0,0,0-.062H444.705a.028.028,0,0,0-.007.046Z"
2316
+ transform="translate(-192.059 -32.959)"
2317
+ stroke="var(--symbol-stroke, #000000)"
2318
+ stroke-miterlimit="10"
2319
+ stroke-width="1"
2320
+ fill="var(--symbol-fill, #e0e0e0)"
2321
+ />
2322
+
2323
+ <!-- Bottom flange -->
2324
+ <rect
2325
+ width="256"
2326
+ height="9.872"
2327
+ transform="translate(195.05 411.066)"
2328
+ stroke="var(--symbol-stroke, #000000)"
2329
+ stroke-miterlimit="10"
2330
+ stroke-width="1"
2331
+ fill="var(--symbol-fill, #eaeaea)"
2332
+ />
2333
+ <path
2334
+ d="M195.05,1148.286l17.149-4.156H433.9l17.149,4.156Z"
2335
+ transform="translate(0 -737.221)"
2336
+ fill="var(--symbol-fill, #e2e2e2)"
2337
+ stroke="var(--symbol-stroke, #000000)"
2338
+ stroke-miterlimit="10"
2339
+ stroke-width="1"
2340
+ />
2341
+
2342
+ <!-- Vessel top cone -->
2343
+ <path
2344
+ d="M451.05,328.356h-256l74.692-34.946H376.358Z"
2345
+ transform="translate(0 -82.72)"
2346
+ stroke="var(--symbol-stroke, #000000)"
2347
+ stroke-miterlimit="10"
2348
+ stroke-width="1"
2349
+ fill="var(--symbol-fill, #eaeaea)"
2350
+ />
2351
+
2352
+ <!-- Main vessel body -->
2353
+ <rect
2354
+ width="256"
2355
+ height="59.803"
2356
+ transform="translate(195.05 245.636)"
2357
+ stroke="var(--symbol-stroke, #000000)"
2358
+ stroke-miterlimit="10"
2359
+ stroke-width="1"
2360
+ fill="var(--symbol-fill, #eaeaea)"
2361
+ />
2362
+ <rect
2363
+ width="256"
2364
+ height="76.892"
2365
+ transform="translate(195.05 308.417)"
2366
+ stroke="var(--symbol-stroke, #000000)"
2367
+ stroke-miterlimit="10"
2368
+ stroke-width="1"
2369
+ fill="var(--symbol-fill, #eaeaea)"
2370
+ />
2371
+
2372
+ <!-- Vessel bottom transitions -->
2373
+ <path
2374
+ d="M449.265,705.7H196.773l-1.654-1.465a.014.014,0,0,1,0-.023H451.078Z"
2375
+ transform="translate(-0.049 -398.769)"
2376
+ stroke="var(--symbol-stroke, #000000)"
2377
+ stroke-miterlimit="10"
2378
+ stroke-width="1"
2379
+ fill="var(--symbol-fill, #e0e0e0)"
2380
+ />
2381
+ <path
2382
+ d="M449.278,710.66H196.793l-1.663,1.462v.016H451.086s.012,0,0-.016Z"
2383
+ transform="translate(-0.062 -403.731)"
2384
+ stroke="var(--symbol-stroke, #000000)"
2385
+ stroke-miterlimit="10"
2386
+ stroke-width="1"
2387
+ fill="var(--symbol-fill, #e0e0e0)"
2388
+ />
2389
+ <path
2390
+ d="M433.9,1060.758H212.2L195.05,1050.48h256Z"
2391
+ transform="translate(0 -665.171)"
2392
+ stroke="var(--symbol-stroke, #000000)"
2393
+ stroke-miterlimit="10"
2394
+ stroke-width="1"
2395
+ fill="var(--symbol-fill, #e0e0e0)"
2396
+ />
2397
+
2398
+ <!-- Agitator blades/impellers (simplified without gradients) -->
2399
+ <ellipse
2400
+ cx="15.454"
2401
+ cy="6.096"
2402
+ rx="15.454"
2403
+ ry="6.096"
2404
+ transform="translate(307.486 228.161)"
2405
+ stroke="var(--symbol-stroke, #000000)"
2406
+ stroke-miterlimit="10"
2407
+ stroke-width="1"
2408
+ fill="var(--symbol-fill, #c0c0c0)"
2409
+ />
2410
+ <ellipse
2411
+ cx="15.454"
2412
+ cy="5.3"
2413
+ rx="15.454"
2414
+ ry="5.3"
2415
+ transform="translate(307.486 229.753)"
2416
+ stroke="var(--symbol-stroke, #000000)"
2417
+ stroke-miterlimit="10"
2418
+ stroke-width="1"
2419
+ fill="var(--symbol-fill, #d0d0d0)"
2420
+ />
2421
+ <ellipse
2422
+ cx="5.174"
2423
+ cy="15.117"
2424
+ rx="5.174"
2425
+ ry="15.117"
2426
+ transform="translate(367.144 236.079) rotate(-77.61)"
2427
+ stroke="var(--symbol-stroke, #000000)"
2428
+ stroke-miterlimit="10"
2429
+ stroke-width="1"
2430
+ fill="var(--symbol-fill, #d0d0d0)"
2431
+ />
2432
+ <path
2433
+ d="M947,380.271s1.631-2.83,8.084-2.235c5.427.5,16.927,2.855,20.574,8.26"
2434
+ transform="translate(-578.512 -147.764)"
2435
+ fill="none"
2436
+ stroke="var(--symbol-stroke, #000000)"
2437
+ stroke-miterlimit="10"
2438
+ stroke-width="1"
2439
+ />
2440
+ <ellipse
2441
+ cx="15.117"
2442
+ cy="5.174"
2443
+ rx="15.117"
2444
+ ry="5.174"
2445
+ transform="translate(247 232.438) rotate(-12.39)"
2446
+ stroke="var(--symbol-stroke, #000000)"
2447
+ stroke-miterlimit="10"
2448
+ stroke-width="1"
2449
+ fill="var(--symbol-fill, #d0d0d0)"
2450
+ />
2451
+ <path
2452
+ d="M456.439,380.271s-1.631-2.83-8.073-2.235c-5.43.5-16.93,2.855-20.576,8.26"
2453
+ transform="translate(-179.058 -147.764)"
2454
+ fill="none"
2455
+ stroke="var(--symbol-stroke, #000000)"
2456
+ stroke-miterlimit="10"
2457
+ stroke-width="1"
2458
+ />
2459
+ <ellipse
2460
+ cx="1.651"
2461
+ cy="15.082"
2462
+ rx="1.651"
2463
+ ry="15.082"
2464
+ transform="translate(406.71 230.174) rotate(-68.13)"
2465
+ stroke="var(--symbol-stroke, #000000)"
2466
+ stroke-miterlimit="10"
2467
+ stroke-width="1"
2468
+ fill="var(--symbol-fill, #d0d0d0)"
2469
+ />
2470
+ <path
2471
+ d="M1119,375.62a119.876,119.876,0,0,1,26.193,10.419"
2472
+ transform="translate(-710.84 -145.969)"
2473
+ fill="none"
2474
+ stroke="var(--symbol-stroke, #000000)"
2475
+ stroke-miterlimit="10"
2476
+ stroke-width="1"
2477
+ />
2478
+ <ellipse
2479
+ cx="15.082"
2480
+ cy="1.651"
2481
+ rx="15.082"
2482
+ ry="1.651"
2483
+ transform="translate(210.225 238.342) rotate(-21.87)"
2484
+ stroke="var(--symbol-stroke, #000000)"
2485
+ stroke-miterlimit="10"
2486
+ stroke-width="1"
2487
+ fill="var(--symbol-fill, #d0d0d0)"
2488
+ />
2489
+ <path
2490
+ d="M293.943,375.62a119.829,119.829,0,0,0-26.193,10.419"
2491
+ transform="translate(-55.932 -145.969)"
2492
+ fill="none"
2493
+ stroke="var(--symbol-stroke, #000000)"
2494
+ stroke-miterlimit="10"
2495
+ stroke-width="1"
2496
+ />
2497
+ </g>
2498
+ `,
2499
+ metadata: {
2500
+ description: "Pixer mixer/agitator vessel with top-mounted drive",
2501
+ tags: ["vessel", "mixer", "agitator", "pixer", "tank"]
2502
+ }
2503
+ },
2504
+ "pixer-barb": {
2505
+ id: "pixer-barb",
2506
+ name: "Pixer (Hose Barb)",
2507
+ category: "pump",
2508
+ viewBox: { x: 0, y: 0, width: 50.625, height: 46.6875 },
2509
+ ports: [
2510
+ { id: "right-outlet", x: 50.625, y: 30.9375, type: "control", direction: "out" },
2511
+ { id: "left-outlet", x: 0, y: 30.9375, type: "control", direction: "out" },
2512
+ { id: "left-inlet", x: 50.625, y: 19.6875, type: "control", direction: "in" },
2513
+ { id: "right-inlet", x: 0, y: 19.6875, type: "control", direction: "in" },
2514
+ { id: "top-inlet", x: 25.3125, y: 0, type: "control", direction: "in" }
2515
+ ],
2516
+ svgContent: `
2517
+ <g transform="scale(0.196875) translate(-195, -186)">
2518
+ <!-- Hose barb ridges at top -->
2519
+ <rect
2520
+ width="20"
2521
+ height="2"
2522
+ transform="translate(313 173)"
2523
+ stroke="var(--symbol-stroke, #000000)"
2524
+ stroke-miterlimit="10"
2525
+ stroke-width="0.5"
2526
+ fill="var(--symbol-fill, #d0d0d0)"
2527
+ />
2528
+ <rect
2529
+ width="20"
2530
+ height="2"
2531
+ transform="translate(313 177)"
2532
+ stroke="var(--symbol-stroke, #000000)"
2533
+ stroke-miterlimit="10"
2534
+ stroke-width="0.5"
2535
+ fill="var(--symbol-fill, #d0d0d0)"
2536
+ />
2537
+ <rect
2538
+ width="20"
2539
+ height="2"
2540
+ transform="translate(313 181)"
2541
+ stroke="var(--symbol-stroke, #000000)"
2542
+ stroke-miterlimit="10"
2543
+ stroke-width="0.5"
2544
+ fill="var(--symbol-fill, #d0d0d0)"
2545
+ />
2546
+
2547
+ <!-- Taper from barb to motor housing -->
2548
+ <path
2549
+ d="M 313,183 L 269.742,199.918 L 376.356,199.918 L 333,183 Z"
2550
+ stroke="var(--symbol-stroke, #000000)"
2551
+ stroke-miterlimit="10"
2552
+ stroke-width="1"
2553
+ fill="var(--symbol-fill, #d0d0d0)"
2554
+ />
2555
+
2556
+ <!-- Motor housing top -->
2557
+ <rect
2558
+ width="106.614"
2559
+ height="2.468"
2560
+ transform="translate(269.742 199.918)"
2561
+ stroke="var(--symbol-stroke, #000000)"
2562
+ stroke-miterlimit="10"
2563
+ stroke-width="1"
2564
+ fill="var(--symbol-fill, #e0e0e0)"
2565
+ />
2566
+ <rect
2567
+ width="106.614"
2568
+ height="5.835"
2569
+ transform="translate(269.742 202.384)"
2570
+ stroke="var(--symbol-stroke, #000000)"
2571
+ stroke-miterlimit="10"
2572
+ stroke-width="1"
2573
+ fill="var(--symbol-fill, #e0e0e0)"
2574
+ />
2575
+ <rect
2576
+ width="106.614"
2577
+ height="2.468"
2578
+ transform="translate(269.742 208.222)"
2579
+ stroke="var(--symbol-stroke, #000000)"
2580
+ stroke-miterlimit="10"
2581
+ stroke-width="1"
2582
+ fill="var(--symbol-fill, #e0e0e0)"
2583
+ />
2584
+
2585
+ <!-- Bottom mounting flange -->
2586
+ <rect
2587
+ width="221.702"
2588
+ height="2.401"
2589
+ transform="translate(212.199 395.587)"
2590
+ stroke="var(--symbol-stroke, #000000)"
2591
+ stroke-miterlimit="10"
2592
+ stroke-width="1"
2593
+ fill="var(--symbol-fill, #e0e0e0)"
2594
+ />
2595
+ <rect
2596
+ width="221.702"
2597
+ height="6.269"
2598
+ transform="translate(212.199 397.988)"
2599
+ stroke="var(--symbol-stroke, #000000)"
2600
+ stroke-miterlimit="10"
2601
+ stroke-width="1"
2602
+ fill="var(--symbol-fill, #e0e0e0)"
2603
+ />
2604
+ <rect
2605
+ width="221.702"
2606
+ height="2.65"
2607
+ transform="translate(212.199 404.257)"
2608
+ stroke="var(--symbol-stroke, #000000)"
2609
+ stroke-miterlimit="10"
2610
+ stroke-width="1"
2611
+ fill="var(--symbol-fill, #e0e0e0)"
2612
+ />
2613
+
2614
+ <!-- Bottom flange -->
2615
+ <rect
2616
+ width="256"
2617
+ height="9.872"
2618
+ transform="translate(195.05 411.066)"
2619
+ stroke="var(--symbol-stroke, #000000)"
2620
+ stroke-miterlimit="10"
2621
+ stroke-width="1"
2622
+ fill="var(--symbol-fill, #eaeaea)"
2623
+ />
2624
+ <path
2625
+ d="M195.05,1148.286l17.149-4.156H433.9l17.149,4.156Z"
2626
+ transform="translate(0 -737.221)"
2627
+ fill="var(--symbol-fill, #e2e2e2)"
2628
+ stroke="var(--symbol-stroke, #000000)"
2629
+ stroke-miterlimit="10"
2630
+ stroke-width="1"
2631
+ />
2632
+
2633
+ <!-- Vessel top cone -->
2634
+ <path
2635
+ d="M451.05,328.356h-256l74.692-34.946H376.358Z"
2636
+ transform="translate(0 -82.72)"
2637
+ stroke="var(--symbol-stroke, #000000)"
2638
+ stroke-miterlimit="10"
2639
+ stroke-width="1"
2640
+ fill="var(--symbol-fill, #eaeaea)"
2641
+ />
2642
+
2643
+ <!-- Main vessel body -->
2644
+ <rect
2645
+ width="256"
2646
+ height="59.803"
2647
+ transform="translate(195.05 245.636)"
2648
+ stroke="var(--symbol-stroke, #000000)"
2649
+ stroke-miterlimit="10"
2650
+ stroke-width="1"
2651
+ fill="var(--symbol-fill, #eaeaea)"
2652
+ />
2653
+ <rect
2654
+ width="256"
2655
+ height="76.892"
2656
+ transform="translate(195.05 308.417)"
2657
+ stroke="var(--symbol-stroke, #000000)"
2658
+ stroke-miterlimit="10"
2659
+ stroke-width="1"
2660
+ fill="var(--symbol-fill, #eaeaea)"
2661
+ />
2662
+
2663
+ <!-- Vessel bottom transitions -->
2664
+ <path
2665
+ d="M449.265,705.7H196.773l-1.654-1.465a.014.014,0,0,1,0-.023H451.078Z"
2666
+ transform="translate(-0.049 -398.769)"
2667
+ stroke="var(--symbol-stroke, #000000)"
2668
+ stroke-miterlimit="10"
2669
+ stroke-width="1"
2670
+ fill="var(--symbol-fill, #e0e0e0)"
2671
+ />
2672
+ <path
2673
+ d="M449.278,710.66H196.793l-1.663,1.462v.016H451.086s.012,0,0-.016Z"
2674
+ transform="translate(-0.062 -403.731)"
2675
+ stroke="var(--symbol-stroke, #000000)"
2676
+ stroke-miterlimit="10"
2677
+ stroke-width="1"
2678
+ fill="var(--symbol-fill, #e0e0e0)"
2679
+ />
2680
+ <path
2681
+ d="M433.9,1060.758H212.2L195.05,1050.48h256Z"
2682
+ transform="translate(0 -665.171)"
2683
+ stroke="var(--symbol-stroke, #000000)"
2684
+ stroke-miterlimit="10"
2685
+ stroke-width="1"
2686
+ fill="var(--symbol-fill, #e0e0e0)"
2687
+ />
2688
+
2689
+ <!-- Agitator blades/impellers (simplified without gradients) -->
2690
+ <ellipse
2691
+ cx="15.454"
2692
+ cy="6.096"
2693
+ rx="15.454"
2694
+ ry="6.096"
2695
+ transform="translate(307.486 228.161)"
2696
+ stroke="var(--symbol-stroke, #000000)"
2697
+ stroke-miterlimit="10"
2698
+ stroke-width="1"
2699
+ fill="var(--symbol-fill, #c0c0c0)"
2700
+ />
2701
+ <ellipse
2702
+ cx="15.454"
2703
+ cy="5.3"
2704
+ rx="15.454"
2705
+ ry="5.3"
2706
+ transform="translate(307.486 229.753)"
2707
+ stroke="var(--symbol-stroke, #000000)"
2708
+ stroke-miterlimit="10"
2709
+ stroke-width="1"
2710
+ fill="var(--symbol-fill, #d0d0d0)"
2711
+ />
2712
+ <ellipse
2713
+ cx="5.174"
2714
+ cy="15.117"
2715
+ rx="5.174"
2716
+ ry="15.117"
2717
+ transform="translate(367.144 236.079) rotate(-77.61)"
2718
+ stroke="var(--symbol-stroke, #000000)"
2719
+ stroke-miterlimit="10"
2720
+ stroke-width="1"
2721
+ fill="var(--symbol-fill, #d0d0d0)"
2722
+ />
2723
+ <path
2724
+ d="M947,380.271s1.631-2.83,8.084-2.235c5.427.5,16.927,2.855,20.574,8.26"
2725
+ transform="translate(-578.512 -147.764)"
2726
+ fill="none"
2727
+ stroke="var(--symbol-stroke, #000000)"
2728
+ stroke-miterlimit="10"
2729
+ stroke-width="1"
2730
+ />
2731
+ <ellipse
2732
+ cx="15.117"
2733
+ cy="5.174"
2734
+ rx="15.117"
2735
+ ry="5.174"
2736
+ transform="translate(247 232.438) rotate(-12.39)"
2737
+ stroke="var(--symbol-stroke, #000000)"
2738
+ stroke-miterlimit="10"
2739
+ stroke-width="1"
2740
+ fill="var(--symbol-fill, #d0d0d0)"
2741
+ />
2742
+ <path
2743
+ d="M456.439,380.271s-1.631-2.83-8.073-2.235c-5.43.5-16.93,2.855-20.576,8.26"
2744
+ transform="translate(-179.058 -147.764)"
2745
+ fill="none"
2746
+ stroke="var(--symbol-stroke, #000000)"
2747
+ stroke-miterlimit="10"
2748
+ stroke-width="1"
2749
+ />
2750
+ <ellipse
2751
+ cx="1.651"
2752
+ cy="15.082"
2753
+ rx="1.651"
2754
+ ry="15.082"
2755
+ transform="translate(406.71 230.174) rotate(-68.13)"
2756
+ stroke="var(--symbol-stroke, #000000)"
2757
+ stroke-miterlimit="10"
2758
+ stroke-width="1"
2759
+ fill="var(--symbol-fill, #d0d0d0)"
2760
+ />
2761
+ <path
2762
+ d="M1119,375.62a119.876,119.876,0,0,1,26.193,10.419"
2763
+ transform="translate(-710.84 -145.969)"
2764
+ fill="none"
2765
+ stroke="var(--symbol-stroke, #000000)"
2766
+ stroke-miterlimit="10"
2767
+ stroke-width="1"
2768
+ />
2769
+ <ellipse
2770
+ cx="15.082"
2771
+ cy="1.651"
2772
+ rx="15.082"
2773
+ ry="1.651"
2774
+ transform="translate(210.225 238.342) rotate(-21.87)"
2775
+ stroke="var(--symbol-stroke, #000000)"
2776
+ stroke-miterlimit="10"
2777
+ stroke-width="1"
2778
+ fill="var(--symbol-fill, #d0d0d0)"
2779
+ />
2780
+ <path
2781
+ d="M293.943,375.62a119.829,119.829,0,0,0-26.193,10.419"
2782
+ transform="translate(-55.932 -145.969)"
2783
+ fill="none"
2784
+ stroke="var(--symbol-stroke, #000000)"
2785
+ stroke-miterlimit="10"
2786
+ stroke-width="1"
2787
+ />
2788
+ </g>
2789
+ `,
2790
+ metadata: {
2791
+ description: "Pixer mixer/agitator vessel with tapered hose barb connection at top",
2792
+ tags: ["vessel", "mixer", "agitator", "pixer", "tank", "hose-barb"]
2793
+ }
2794
+ },
2795
+ "vessel-balloon": {
2796
+ id: "vessel-balloon",
2797
+ name: "Vessel",
2798
+ category: "vessel",
2799
+ viewBox: { x: 0, y: 0, width: 50.625, height: 91 },
2800
+ ports: [
2801
+ { id: "top", type: "inlet", x: 25.3125, y: 0 },
2802
+ { id: "left", type: "inlet", x: 5, y: 39 },
2803
+ { id: "right", type: "outlet", x: 45.625, y: 39 },
2804
+ { id: "bottom", type: "outlet", x: 25.3125, y: 91 }
2805
+ ],
2806
+ svgContent: `
2807
+ <g transform="scale(0.196875) translate(-195, -100)">
2808
+ <!-- Top opening nozzle -->
2809
+ <rect
2810
+ width="65"
2811
+ height="19.5"
2812
+ transform="translate(290.5, 100)"
2813
+ stroke="var(--symbol-stroke, #000000)"
2814
+ stroke-miterlimit="10"
2815
+ stroke-width="1"
2816
+ fill="var(--symbol-fill, #e0e0e0)"
2817
+ />
2818
+
2819
+ <!-- Transition from nozzle to dome -->
2820
+ <path
2821
+ d="M 290.5,119.5 L 238,152 L 408,152 L 355.5,119.5 Z"
2822
+ stroke="var(--symbol-stroke, #000000)"
2823
+ stroke-miterlimit="10"
2824
+ stroke-width="1"
2825
+ fill="var(--symbol-fill, #e0e0e0)"
2826
+ />
2827
+
2828
+ <!-- Upper balloon dome - rounded bulge -->
2829
+ <path
2830
+ d="M 238,152 C 185,210 150,320 180,395 L 466,395 C 496,320 461,210 408,152 Z"
2831
+ stroke="var(--symbol-stroke, #000000)"
2832
+ stroke-miterlimit="10"
2833
+ stroke-width="1"
2834
+ fill="var(--symbol-fill, #eaeaea)"
2835
+ />
2836
+
2837
+ <!-- Tapered vessel body -->
2838
+ <path
2839
+ d="M 180,395 L 269.742,530 L 376.356,530 L 466,395 Z"
2840
+ stroke="var(--symbol-stroke, #000000)"
2841
+ stroke-miterlimit="10"
2842
+ stroke-width="1"
2843
+ fill="var(--symbol-fill, #eaeaea)"
2844
+ />
2845
+
2846
+ <!-- Bottom cone -->
2847
+ <path
2848
+ d="M 269.742,530 L 280,555 L 366,555 L 376.356,530 Z"
2849
+ stroke="var(--symbol-stroke, #000000)"
2850
+ stroke-miterlimit="10"
2851
+ stroke-width="1"
2852
+ fill="var(--symbol-fill, #e0e0e0)"
2853
+ />
2854
+
2855
+ <!-- Bottom flange matching pixer top dimensions -->
2856
+ <rect
2857
+ width="141.313"
2858
+ height="9.872"
2859
+ transform="translate(252.392, 555)"
2860
+ stroke="var(--symbol-stroke, #000000)"
2861
+ stroke-miterlimit="10"
2862
+ stroke-width="1"
2863
+ fill="var(--symbol-fill, #eaeaea)"
2864
+ />
2865
+ </g>
2866
+ `,
2867
+ metadata: {
2868
+ description: "Balloon-shaped vessel with rounded top and tapered bottom, designed to fit on standard pixer",
2869
+ tags: ["vessel", "storage", "tank", "balloon"]
2870
+ }
2871
+ },
2872
+ "psr-f": {
2873
+ id: "psr-f",
2874
+ name: "PSR F",
2875
+ category: "instrument",
2876
+ viewBox: { x: 0, y: 0, width: 57, height: 57 },
2877
+ ports: [
2878
+ { id: "top", type: "signal", x: 28.5, y: 14.25 },
2879
+ { id: "right", type: "signal", x: 42.75, y: 28.5 },
2880
+ { id: "bottom", type: "signal", x: 28.5, y: 42.75 },
2881
+ { id: "left", type: "signal", x: 14.25, y: 28.5 }
2882
+ ],
2883
+ svgContent: `
2884
+ <g transform="scale(0.5) translate(28.5, 28.5)">
2885
+ <!-- Circle background -->
2886
+ <ellipse
2887
+ cx="28.5"
2888
+ cy="28.5"
2889
+ rx="28.5"
2890
+ ry="28.5"
2891
+ stroke="var(--symbol-stroke, #000000)"
2892
+ stroke-width="1"
2893
+ fill="var(--symbol-fill, #ffffff)"
2894
+ />
2895
+
2896
+ <!-- Letter F -->
2897
+ <text
2898
+ x="28.5"
2899
+ y="38"
2900
+ text-anchor="middle"
2901
+ font-family="'Times New Roman', Georgia, serif"
2902
+ font-size="34"
2903
+ font-weight="normal"
2904
+ font-style="italic"
2905
+ fill="var(--symbol-stroke, #000000)"
2906
+ >F</text>
2907
+ </g>
2908
+ `,
2909
+ metadata: {
2910
+ description: "PSR F instrument symbol - Flow indicator",
2911
+ tags: ["instrument", "psr", "flow", "indicator"]
2912
+ }
2913
+ },
2914
+ "psr-p": {
2915
+ id: "psr-p",
2916
+ name: "PSR P",
2917
+ category: "instrument",
2918
+ viewBox: { x: 0, y: 0, width: 57, height: 57 },
2919
+ ports: [
2920
+ { id: "top", type: "signal", x: 28.5, y: 14.25 },
2921
+ { id: "right", type: "signal", x: 42.75, y: 28.5 },
2922
+ { id: "bottom", type: "signal", x: 28.5, y: 42.75 },
2923
+ { id: "left", type: "signal", x: 14.25, y: 28.5 }
2924
+ ],
2925
+ svgContent: `
2926
+ <g transform="scale(0.5) translate(28.5, 28.5)">
2927
+ <!-- Circle background -->
2928
+ <ellipse
2929
+ cx="28.5"
2930
+ cy="28.5"
2931
+ rx="28.5"
2932
+ ry="28.5"
2933
+ stroke="var(--symbol-stroke, #000000)"
2934
+ stroke-width="1"
2935
+ fill="var(--symbol-fill, #ffffff)"
2936
+ />
2937
+
2938
+ <!-- Letter P -->
2939
+ <text
2940
+ x="28.5"
2941
+ y="38"
2942
+ text-anchor="middle"
2943
+ font-family="'Times New Roman', Georgia, serif"
2944
+ font-size="34"
2945
+ font-weight="normal"
2946
+ font-style="italic"
2947
+ fill="var(--symbol-stroke, #000000)"
2948
+ >P</text>
1914
2949
  </g>
1915
2950
  `,
1916
2951
  metadata: {
1917
- description: "Generic instrument indicator",
1918
- tags: ["instrument", "indicator", "measurement"]
2952
+ description: "PSR P instrument symbol - Pressure indicator",
2953
+ tags: ["instrument", "psr", "pressure", "indicator"]
1919
2954
  }
1920
2955
  }
1921
2956
  };
@@ -1995,6 +3030,24 @@ function NodeRenderer({
1995
3030
  `translate(${centerX}, ${centerY}) scale(${scaleX}, ${scaleY}) translate(${-centerX}, ${-centerY})`
1996
3031
  );
1997
3032
  }
3033
+ const getStatusColor = (status) => {
3034
+ switch (status) {
3035
+ case "running":
3036
+ return "#22c55e";
3037
+ // Green
3038
+ case "alarm":
3039
+ return "#ef4444";
3040
+ // Red
3041
+ case "warning":
3042
+ return "#f59e0b";
3043
+ // Orange
3044
+ case "stopped":
3045
+ default:
3046
+ return "#edeeef";
3047
+ }
3048
+ };
3049
+ const fillColor = node.customColors?.fill ?? getStatusColor(node.status);
3050
+ const strokeColor = node.customColors?.stroke ?? "rgb(0, 0, 0)";
1998
3051
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1999
3052
  "g",
2000
3053
  {
@@ -2003,6 +3056,11 @@ function NodeRenderer({
2003
3056
  "data-selected": isSelected,
2004
3057
  "data-dragging": isDragging,
2005
3058
  transform: transforms.join(" "),
3059
+ style: {
3060
+ "--symbol-fill": fillColor,
3061
+ "--symbol-stroke": strokeColor,
3062
+ cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default"
3063
+ },
2006
3064
  onClick: (e) => {
2007
3065
  e.stopPropagation();
2008
3066
  onNodeClick?.(node.id, e);
@@ -2022,9 +3080,6 @@ function NodeRenderer({
2022
3080
  onNodeDragStart?.(node.id, e);
2023
3081
  }
2024
3082
  },
2025
- style: {
2026
- cursor: enableDrag ? isDragging ? "grabbing" : "grab" : onNodeClick ? "pointer" : "default"
2027
- },
2028
3083
  children: [
2029
3084
  isSelected && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2030
3085
  "rect",
@@ -2077,6 +3132,9 @@ function PipeRenderer({
2077
3132
  onPipeClick,
2078
3133
  enableWaypointEditing = false,
2079
3134
  onWaypointDragStart,
3135
+ onWaypointClick,
3136
+ selectedWaypointPipeId,
3137
+ selectedWaypointIndex,
2080
3138
  editingPipeId,
2081
3139
  draggingWaypointIndex,
2082
3140
  waypointDragOffset,
@@ -2097,7 +3155,7 @@ function PipeRenderer({
2097
3155
  });
2098
3156
  const pointsString = displayPoints.map((point) => `${point.x},${point.y}`).join(" ");
2099
3157
  const pipeStyle = pipe.style || {};
2100
- const stroke = isSelected ? "#3b82f6" : pipeStyle.stroke || "#263238";
3158
+ const stroke = isSelected ? "#3b82f6" : pipeStyle.stroke || "#94a1b8";
2101
3159
  const strokeWidth = isSelected ? (pipeStyle.strokeWidth || 3) + 2 : pipeStyle.strokeWidth || 3;
2102
3160
  const strokeDasharray = pipeStyle.strokeDasharray;
2103
3161
  const opacity = pipeStyle.opacity !== void 0 ? pipeStyle.opacity : 1;
@@ -2146,10 +3204,11 @@ function PipeRenderer({
2146
3204
  ] }),
2147
3205
  displayPoints.map((point, idx) => {
2148
3206
  const isEndpoint = idx === 0 || idx === displayPoints.length - 1;
3207
+ const isSelectedWaypoint = selectedWaypointPipeId === pipe.id && selectedWaypointIndex === idx;
2149
3208
  const isDragging = isEditing && draggingWaypointIndex === idx;
2150
- const waypointSize = isDragging ? 8 : showEditingControls ? 6 : isSelected ? 3 : 2;
2151
- const waypointOpacity = isDragging ? 1 : showEditingControls ? 0.9 : isSelected ? 0.8 : 0.5;
2152
- const waypointColor = isEndpoint && showEditingControls ? "#f59e0b" : isDragging ? "#10b981" : stroke;
3209
+ const waypointSize = isSelectedWaypoint ? 8 : isDragging ? 8 : showEditingControls ? 6 : isSelected ? 3 : 2;
3210
+ const waypointOpacity = isSelectedWaypoint ? 1 : isDragging ? 1 : showEditingControls ? 0.9 : isSelected ? 0.8 : 0.5;
3211
+ const waypointColor = isSelectedWaypoint ? "#3b82f6" : isEndpoint && showEditingControls ? "#f59e0b" : isDragging ? "#10b981" : stroke;
2153
3212
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2154
3213
  "circle",
2155
3214
  {
@@ -2165,6 +3224,12 @@ function PipeRenderer({
2165
3224
  cursor: showEditingControls ? isDragging ? "grabbing" : "grab" : "default"
2166
3225
  },
2167
3226
  pointerEvents: showEditingControls ? "all" : "none",
3227
+ onClick: (e) => {
3228
+ if (showEditingControls) {
3229
+ e.stopPropagation();
3230
+ onWaypointClick?.(pipe.id, idx);
3231
+ }
3232
+ },
2168
3233
  onMouseDown: (e) => {
2169
3234
  if (showEditingControls) {
2170
3235
  e.stopPropagation();
@@ -2241,7 +3306,13 @@ function PipeRenderer({
2241
3306
 
2242
3307
  // src/components/PIDCanvas/DataOverlay.tsx
2243
3308
  var import_jsx_runtime15 = require("react/jsx-runtime");
2244
- function DataOverlay({ nodeId, x, y, telemetry }) {
3309
+ function DataOverlay({
3310
+ nodeId,
3311
+ x,
3312
+ y,
3313
+ telemetry,
3314
+ scale = 1
3315
+ }) {
2245
3316
  const { value, display, history } = telemetry;
2246
3317
  const showValue = display?.showValue ?? true;
2247
3318
  const showStatus = display?.showStatus ?? true;
@@ -2281,9 +3352,9 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2281
3352
  const displayText = value.unit ? `${formattedValue} ${value.unit}` : formattedValue;
2282
3353
  const sparklinePath = (() => {
2283
3354
  if (!showSparkline || !history || history.length < 2) return "";
2284
- const width = 70;
2285
- const height = 20;
2286
- const padding = 5;
3355
+ const width = 70 * scale;
3356
+ const height = 20 * scale;
3357
+ const padding = 5 * scale;
2287
3358
  const effectiveWidth = width - padding * 2;
2288
3359
  const effectiveHeight = height - padding * 2;
2289
3360
  const min = Math.min(...history);
@@ -2297,21 +3368,22 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2297
3368
  return `M ${points.join(" L ")}`;
2298
3369
  })();
2299
3370
  const hasSparkline = showSparkline && sparklinePath;
2300
- const overlayHeight = label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22;
3371
+ const overlayHeight = (label ? hasSparkline ? 55 : 30 : hasSparkline ? 47 : 22) * scale;
3372
+ const overlayWidth = 80 * scale;
2301
3373
  const displayX = x + offsetX;
2302
3374
  const displayY = y + offsetY;
2303
3375
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("g", { "data-overlay-node": nodeId, "data-status": value.status, children: [
2304
3376
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2305
3377
  "rect",
2306
3378
  {
2307
- x: displayX - 40,
2308
- y: displayY - 25,
2309
- width: "80",
3379
+ x: displayX - overlayWidth / 2,
3380
+ y: displayY - 25 * scale,
3381
+ width: overlayWidth,
2310
3382
  height: overlayHeight,
2311
- rx: "4",
3383
+ rx: 4 * scale,
2312
3384
  fill: colors.bg,
2313
3385
  stroke: colors.border,
2314
- strokeWidth: "1",
3386
+ strokeWidth: 1 * scale,
2315
3387
  opacity: "0.95",
2316
3388
  filter: "drop-shadow(0 2px 4px rgba(0,0,0,0.2))"
2317
3389
  }
@@ -2320,9 +3392,9 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2320
3392
  "text",
2321
3393
  {
2322
3394
  x: displayX,
2323
- y: displayY - 15,
3395
+ y: displayY - 15 * scale,
2324
3396
  textAnchor: "middle",
2325
- fontSize: "8",
3397
+ fontSize: 8 * scale,
2326
3398
  fontWeight: "600",
2327
3399
  fill: finalTextColor,
2328
3400
  opacity: "0.9",
@@ -2333,31 +3405,41 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2333
3405
  "text",
2334
3406
  {
2335
3407
  x: displayX,
2336
- y: label ? displayY - 3 : displayY - 9,
3408
+ y: label ? displayY - 3 * scale : displayY - 9 * scale,
2337
3409
  textAnchor: "middle",
2338
- fontSize: "11",
3410
+ fontSize: 11 * scale,
2339
3411
  fontWeight: "700",
2340
3412
  fill: finalTextColor,
2341
3413
  letterSpacing: "0.02em",
2342
3414
  children: displayText
2343
3415
  }
2344
3416
  ),
2345
- showStatus && value.status !== "normal" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("circle", { cx: displayX + 35, cy: displayY - 18, r: "3", fill: finalTextColor, opacity: "0.9", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2346
- "animate",
3417
+ showStatus && value.status !== "normal" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3418
+ "circle",
2347
3419
  {
2348
- attributeName: "opacity",
2349
- values: "0.9;0.3;0.9",
2350
- dur: "1.5s",
2351
- repeatCount: "indefinite"
3420
+ cx: displayX + 35 * scale,
3421
+ cy: displayY - 18 * scale,
3422
+ r: 3 * scale,
3423
+ fill: finalTextColor,
3424
+ opacity: "0.9",
3425
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3426
+ "animate",
3427
+ {
3428
+ attributeName: "opacity",
3429
+ values: "0.9;0.3;0.9",
3430
+ dur: "1.5s",
3431
+ repeatCount: "indefinite"
3432
+ }
3433
+ )
2352
3434
  }
2353
- ) }),
3435
+ ),
2354
3436
  value.quality !== void 0 && value.quality < 100 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2355
3437
  "text",
2356
3438
  {
2357
3439
  x: displayX,
2358
- y: displayY + 5,
3440
+ y: displayY + 5 * scale,
2359
3441
  textAnchor: "middle",
2360
- fontSize: "7",
3442
+ fontSize: 7 * scale,
2361
3443
  fontWeight: "500",
2362
3444
  fill: finalTextColor,
2363
3445
  opacity: "0.7",
@@ -2368,13 +3450,13 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2368
3450
  ]
2369
3451
  }
2370
3452
  ),
2371
- hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("g", { transform: `translate(${displayX - 35}, ${displayY + (label ? 5 : 0)})`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3453
+ hasSparkline && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("g", { transform: `translate(${displayX - 35 * scale}, ${displayY + (label ? 5 : 0) * scale})`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2372
3454
  "path",
2373
3455
  {
2374
3456
  d: sparklinePath,
2375
3457
  fill: "none",
2376
3458
  stroke: sparklineColor || finalTextColor,
2377
- strokeWidth: "1.5",
3459
+ strokeWidth: 1.5 * scale,
2378
3460
  strokeLinecap: "round",
2379
3461
  strokeLinejoin: "round",
2380
3462
  opacity: "0.9"
@@ -2383,8 +3465,145 @@ function DataOverlay({ nodeId, x, y, telemetry }) {
2383
3465
  ] });
2384
3466
  }
2385
3467
 
2386
- // src/diagram/hooks/useViewBox.ts
3468
+ // src/components/PIDCanvas/PositionPanel.tsx
2387
3469
  var import_react8 = require("react");
3470
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3471
+ function PositionPanel({
3472
+ selectedNode,
3473
+ selectedWaypoint,
3474
+ onPositionChange,
3475
+ onWaypointPositionChange,
3476
+ position = "top-right"
3477
+ }) {
3478
+ const [x, setX] = (0, import_react8.useState)("");
3479
+ const [y, setY] = (0, import_react8.useState)("");
3480
+ (0, import_react8.useEffect)(() => {
3481
+ if (selectedNode) {
3482
+ setX(selectedNode.transform.x.toFixed(2));
3483
+ setY(selectedNode.transform.y.toFixed(2));
3484
+ } else if (selectedWaypoint) {
3485
+ setX(selectedWaypoint.position.x.toFixed(2));
3486
+ setY(selectedWaypoint.position.y.toFixed(2));
3487
+ }
3488
+ }, [selectedNode, selectedWaypoint]);
3489
+ if (!selectedNode && !selectedWaypoint) {
3490
+ return null;
3491
+ }
3492
+ const handleXChange = (value) => {
3493
+ setX(value);
3494
+ const numValue = parseFloat(value);
3495
+ if (!isNaN(numValue)) {
3496
+ if (selectedNode && onPositionChange) {
3497
+ onPositionChange(selectedNode.id, numValue, selectedNode.transform.y);
3498
+ } else if (selectedWaypoint && onWaypointPositionChange) {
3499
+ onWaypointPositionChange(
3500
+ selectedWaypoint.pipeId,
3501
+ selectedWaypoint.pointIndex,
3502
+ numValue,
3503
+ selectedWaypoint.position.y
3504
+ );
3505
+ }
3506
+ }
3507
+ };
3508
+ const handleYChange = (value) => {
3509
+ setY(value);
3510
+ const numValue = parseFloat(value);
3511
+ if (!isNaN(numValue)) {
3512
+ if (selectedNode && onPositionChange) {
3513
+ onPositionChange(selectedNode.id, selectedNode.transform.x, numValue);
3514
+ } else if (selectedWaypoint && onWaypointPositionChange) {
3515
+ onWaypointPositionChange(
3516
+ selectedWaypoint.pipeId,
3517
+ selectedWaypoint.pointIndex,
3518
+ selectedWaypoint.position.x,
3519
+ numValue
3520
+ );
3521
+ }
3522
+ }
3523
+ };
3524
+ const handleBlur = () => {
3525
+ if (selectedNode) {
3526
+ setX(selectedNode.transform.x.toFixed(2));
3527
+ setY(selectedNode.transform.y.toFixed(2));
3528
+ } else if (selectedWaypoint) {
3529
+ setX(selectedWaypoint.position.x.toFixed(2));
3530
+ setY(selectedWaypoint.position.y.toFixed(2));
3531
+ }
3532
+ };
3533
+ const positionStyles = {
3534
+ "top-left": { top: 10, left: 10 },
3535
+ "top-right": { top: 10, right: 10 },
3536
+ "bottom-left": { bottom: 10, left: 10 },
3537
+ "bottom-right": { bottom: 10, right: 10 }
3538
+ };
3539
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3540
+ "div",
3541
+ {
3542
+ style: {
3543
+ position: "absolute",
3544
+ ...positionStyles[position],
3545
+ backgroundColor: "white",
3546
+ border: "1px solid #ccc",
3547
+ borderRadius: "4px",
3548
+ padding: "12px",
3549
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
3550
+ zIndex: 1e3,
3551
+ minWidth: "180px",
3552
+ fontFamily: "system-ui, -apple-system, sans-serif",
3553
+ fontSize: "13px"
3554
+ },
3555
+ children: [
3556
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { marginBottom: "8px", fontWeight: 600, color: "#333" }, children: "Position" }),
3557
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
3558
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3559
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("label", { style: { width: "16px", color: "#666" }, children: "X:" }),
3560
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3561
+ "input",
3562
+ {
3563
+ type: "number",
3564
+ value: x,
3565
+ onChange: (e) => handleXChange(e.target.value),
3566
+ onBlur: handleBlur,
3567
+ style: {
3568
+ flex: 1,
3569
+ padding: "4px 8px",
3570
+ border: "1px solid #ddd",
3571
+ borderRadius: "3px",
3572
+ fontSize: "13px"
3573
+ },
3574
+ step: "1"
3575
+ }
3576
+ )
3577
+ ] }),
3578
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3579
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("label", { style: { width: "16px", color: "#666" }, children: "Y:" }),
3580
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3581
+ "input",
3582
+ {
3583
+ type: "number",
3584
+ value: y,
3585
+ onChange: (e) => handleYChange(e.target.value),
3586
+ onBlur: handleBlur,
3587
+ style: {
3588
+ flex: 1,
3589
+ padding: "4px 8px",
3590
+ border: "1px solid #ddd",
3591
+ borderRadius: "3px",
3592
+ fontSize: "13px"
3593
+ },
3594
+ step: "1"
3595
+ }
3596
+ )
3597
+ ] })
3598
+ ] }),
3599
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { marginTop: "8px", fontSize: "11px", color: "#888" }, children: "Use arrow keys to nudge (\xB11px)" })
3600
+ ]
3601
+ }
3602
+ );
3603
+ }
3604
+
3605
+ // src/diagram/hooks/useViewBox.ts
3606
+ var import_react9 = require("react");
2388
3607
  function useViewBox(options) {
2389
3608
  const {
2390
3609
  initialViewBox,
@@ -2395,16 +3614,16 @@ function useViewBox(options) {
2395
3614
  zoomSensitivity = 1e-3,
2396
3615
  allowLeftClickPan = true
2397
3616
  } = options;
2398
- const [viewBox, setViewBox] = (0, import_react8.useState)(initialViewBox);
2399
- const svgRef = (0, import_react8.useRef)(null);
2400
- const isPanningRef = (0, import_react8.useRef)(false);
2401
- const lastMousePosRef = (0, import_react8.useRef)({ x: 0, y: 0 });
2402
- const spaceKeyDownRef = (0, import_react8.useRef)(false);
2403
- const lastTouchDistanceRef = (0, import_react8.useRef)(null);
2404
- const resetViewBox = (0, import_react8.useCallback)(() => {
3617
+ const [viewBox, setViewBox] = (0, import_react9.useState)(initialViewBox);
3618
+ const svgRef = (0, import_react9.useRef)(null);
3619
+ const isPanningRef = (0, import_react9.useRef)(false);
3620
+ const lastMousePosRef = (0, import_react9.useRef)({ x: 0, y: 0 });
3621
+ const spaceKeyDownRef = (0, import_react9.useRef)(false);
3622
+ const lastTouchDistanceRef = (0, import_react9.useRef)(null);
3623
+ const resetViewBox = (0, import_react9.useCallback)(() => {
2405
3624
  setViewBox(initialViewBox);
2406
3625
  }, [initialViewBox]);
2407
- const zoomIn = (0, import_react8.useCallback)(() => {
3626
+ const zoomIn = (0, import_react9.useCallback)(() => {
2408
3627
  setViewBox((prev) => {
2409
3628
  const currentZoom = initialViewBox.width / prev.width;
2410
3629
  const newZoom = Math.min(1 / minZoom, currentZoom * 1.25);
@@ -2421,7 +3640,7 @@ function useViewBox(options) {
2421
3640
  };
2422
3641
  });
2423
3642
  }, [initialViewBox, minZoom]);
2424
- const zoomOut = (0, import_react8.useCallback)(() => {
3643
+ const zoomOut = (0, import_react9.useCallback)(() => {
2425
3644
  setViewBox((prev) => {
2426
3645
  const currentZoom = initialViewBox.width / prev.width;
2427
3646
  const newZoom = Math.max(1 / maxZoom, currentZoom * 0.8);
@@ -2438,7 +3657,7 @@ function useViewBox(options) {
2438
3657
  };
2439
3658
  });
2440
3659
  }, [initialViewBox, maxZoom]);
2441
- const fitToContent = (0, import_react8.useCallback)((bounds, padding = 50) => {
3660
+ const fitToContent = (0, import_react9.useCallback)((bounds, padding = 50) => {
2442
3661
  const contentWidth = bounds.maxX - bounds.minX;
2443
3662
  const contentHeight = bounds.maxY - bounds.minY;
2444
3663
  if (contentWidth === 0 || contentHeight === 0) {
@@ -2466,7 +3685,7 @@ function useViewBox(options) {
2466
3685
  height: newHeight
2467
3686
  });
2468
3687
  }, [initialViewBox]);
2469
- (0, import_react8.useEffect)(() => {
3688
+ (0, import_react9.useEffect)(() => {
2470
3689
  if (!enableZoom || !svgRef.current) return;
2471
3690
  const svg = svgRef.current;
2472
3691
  const handleWheel = (e) => {
@@ -2499,7 +3718,7 @@ function useViewBox(options) {
2499
3718
  svg.addEventListener("wheel", handleWheel, { passive: false });
2500
3719
  return () => svg.removeEventListener("wheel", handleWheel);
2501
3720
  }, [enableZoom, zoomSensitivity, minZoom, initialViewBox]);
2502
- (0, import_react8.useEffect)(() => {
3721
+ (0, import_react9.useEffect)(() => {
2503
3722
  if (!enablePan || !svgRef.current) return;
2504
3723
  const svg = svgRef.current;
2505
3724
  const handleMouseDown = (e) => {
@@ -2575,7 +3794,7 @@ function useViewBox(options) {
2575
3794
  window.removeEventListener("keyup", handleKeyUp);
2576
3795
  };
2577
3796
  }, [enablePan, allowLeftClickPan]);
2578
- (0, import_react8.useEffect)(() => {
3797
+ (0, import_react9.useEffect)(() => {
2579
3798
  if (!svgRef.current) return;
2580
3799
  if (!enablePan && !enableZoom) return;
2581
3800
  const svg = svgRef.current;
@@ -2666,7 +3885,7 @@ function useViewBox(options) {
2666
3885
  svg.removeEventListener("touchend", handleTouchEnd);
2667
3886
  };
2668
3887
  }, [enablePan, enableZoom, minZoom, initialViewBox]);
2669
- const screenToWorld = (0, import_react8.useCallback)(
3888
+ const screenToWorld = (0, import_react9.useCallback)(
2670
3889
  (screenX, screenY) => {
2671
3890
  const svg = svgRef.current;
2672
3891
  if (!svg) return { x: screenX, y: screenY };
@@ -2705,15 +3924,15 @@ function useViewBox(options) {
2705
3924
  }
2706
3925
 
2707
3926
  // src/diagram/hooks/useSelection.ts
2708
- var import_react9 = require("react");
3927
+ var import_react10 = require("react");
2709
3928
  function useSelection(options = {}) {
2710
3929
  const {
2711
3930
  multiSelect = true,
2712
3931
  initialSelection = { selectedNodeIds: /* @__PURE__ */ new Set(), selectedPipeIds: /* @__PURE__ */ new Set() },
2713
3932
  onSelectionChange
2714
3933
  } = options;
2715
- const [selection, setSelection] = (0, import_react9.useState)(initialSelection);
2716
- const notifyChange = (0, import_react9.useCallback)(
3934
+ const [selection, setSelection] = (0, import_react10.useState)(initialSelection);
3935
+ const notifyChange = (0, import_react10.useCallback)(
2717
3936
  (newSelection) => {
2718
3937
  if (onSelectionChange) {
2719
3938
  onSelectionChange(newSelection);
@@ -2721,15 +3940,15 @@ function useSelection(options = {}) {
2721
3940
  },
2722
3941
  [onSelectionChange]
2723
3942
  );
2724
- const isNodeSelected = (0, import_react9.useCallback)(
3943
+ const isNodeSelected = (0, import_react10.useCallback)(
2725
3944
  (nodeId) => selection.selectedNodeIds.has(nodeId),
2726
3945
  [selection.selectedNodeIds]
2727
3946
  );
2728
- const isPipeSelected = (0, import_react9.useCallback)(
3947
+ const isPipeSelected = (0, import_react10.useCallback)(
2729
3948
  (pipeId) => selection.selectedPipeIds.has(pipeId),
2730
3949
  [selection.selectedPipeIds]
2731
3950
  );
2732
- const selectNode = (0, import_react9.useCallback)(
3951
+ const selectNode = (0, import_react10.useCallback)(
2733
3952
  (nodeId, isMultiSelect = false) => {
2734
3953
  setSelection((prev) => {
2735
3954
  const newNodeIds = new Set(isMultiSelect && multiSelect ? prev.selectedNodeIds : []);
@@ -2748,7 +3967,7 @@ function useSelection(options = {}) {
2748
3967
  },
2749
3968
  [multiSelect, notifyChange]
2750
3969
  );
2751
- const selectPipe = (0, import_react9.useCallback)(
3970
+ const selectPipe = (0, import_react10.useCallback)(
2752
3971
  (pipeId, isMultiSelect = false) => {
2753
3972
  setSelection((prev) => {
2754
3973
  const newPipeIds = new Set(isMultiSelect && multiSelect ? prev.selectedPipeIds : []);
@@ -2767,7 +3986,7 @@ function useSelection(options = {}) {
2767
3986
  },
2768
3987
  [multiSelect, notifyChange]
2769
3988
  );
2770
- const clearSelection = (0, import_react9.useCallback)(() => {
3989
+ const clearSelection = (0, import_react10.useCallback)(() => {
2771
3990
  const newSelection = {
2772
3991
  selectedNodeIds: /* @__PURE__ */ new Set(),
2773
3992
  selectedPipeIds: /* @__PURE__ */ new Set()
@@ -2775,7 +3994,7 @@ function useSelection(options = {}) {
2775
3994
  setSelection(newSelection);
2776
3995
  notifyChange(newSelection);
2777
3996
  }, [notifyChange]);
2778
- const selectNodes = (0, import_react9.useCallback)(
3997
+ const selectNodes = (0, import_react10.useCallback)(
2779
3998
  (nodeIds) => {
2780
3999
  const newSelection = {
2781
4000
  selectedNodeIds: new Set(nodeIds),
@@ -2786,7 +4005,7 @@ function useSelection(options = {}) {
2786
4005
  },
2787
4006
  [notifyChange]
2788
4007
  );
2789
- const selectPipes = (0, import_react9.useCallback)(
4008
+ const selectPipes = (0, import_react10.useCallback)(
2790
4009
  (pipeIds) => {
2791
4010
  const newSelection = {
2792
4011
  selectedNodeIds: /* @__PURE__ */ new Set(),
@@ -2810,23 +4029,23 @@ function useSelection(options = {}) {
2810
4029
  }
2811
4030
 
2812
4031
  // src/diagram/hooks/useTelemetry.ts
2813
- var import_react10 = require("react");
4032
+ var import_react11 = require("react");
2814
4033
  function useTelemetry(options = {}) {
2815
4034
  const {
2816
4035
  initialBindings = [],
2817
4036
  onTelemetryChange,
2818
4037
  refreshInterval = 0
2819
4038
  } = options;
2820
- const [telemetry, setTelemetry] = (0, import_react10.useState)(() => {
4039
+ const [telemetry, setTelemetry] = (0, import_react11.useState)(() => {
2821
4040
  const map = /* @__PURE__ */ new Map();
2822
4041
  initialBindings.forEach((binding) => {
2823
4042
  map.set(binding.nodeId, binding);
2824
4043
  });
2825
4044
  return map;
2826
4045
  });
2827
- const telemetryRef = (0, import_react10.useRef)(telemetry);
4046
+ const telemetryRef = (0, import_react11.useRef)(telemetry);
2828
4047
  telemetryRef.current = telemetry;
2829
- const notifyChange = (0, import_react10.useCallback)(
4048
+ const notifyChange = (0, import_react11.useCallback)(
2830
4049
  (newTelemetry) => {
2831
4050
  if (onTelemetryChange) {
2832
4051
  onTelemetryChange(newTelemetry);
@@ -2834,7 +4053,7 @@ function useTelemetry(options = {}) {
2834
4053
  },
2835
4054
  [onTelemetryChange]
2836
4055
  );
2837
- const updateTelemetry = (0, import_react10.useCallback)(
4056
+ const updateTelemetry = (0, import_react11.useCallback)(
2838
4057
  (nodeId, value) => {
2839
4058
  setTelemetry((prev) => {
2840
4059
  const newMap = new Map(prev);
@@ -2864,7 +4083,7 @@ function useTelemetry(options = {}) {
2864
4083
  },
2865
4084
  [notifyChange]
2866
4085
  );
2867
- const updateTelemetryBatch = (0, import_react10.useCallback)(
4086
+ const updateTelemetryBatch = (0, import_react11.useCallback)(
2868
4087
  (updates) => {
2869
4088
  setTelemetry((prev) => {
2870
4089
  const newMap = new Map(prev);
@@ -2918,16 +4137,16 @@ function useTelemetry(options = {}) {
2918
4137
  },
2919
4138
  [notifyChange]
2920
4139
  );
2921
- const getTelemetry = (0, import_react10.useCallback)(
4140
+ const getTelemetry = (0, import_react11.useCallback)(
2922
4141
  (nodeId) => telemetryRef.current.get(nodeId),
2923
4142
  []
2924
4143
  );
2925
- const clearTelemetry = (0, import_react10.useCallback)(() => {
4144
+ const clearTelemetry = (0, import_react11.useCallback)(() => {
2926
4145
  const newMap = /* @__PURE__ */ new Map();
2927
4146
  setTelemetry(newMap);
2928
4147
  notifyChange(newMap);
2929
4148
  }, [notifyChange]);
2930
- const setBindings = (0, import_react10.useCallback)(
4149
+ const setBindings = (0, import_react11.useCallback)(
2931
4150
  (bindings) => {
2932
4151
  const newMap = /* @__PURE__ */ new Map();
2933
4152
  bindings.forEach((binding) => {
@@ -2938,7 +4157,7 @@ function useTelemetry(options = {}) {
2938
4157
  },
2939
4158
  [notifyChange]
2940
4159
  );
2941
- (0, import_react10.useEffect)(() => {
4160
+ (0, import_react11.useEffect)(() => {
2942
4161
  if (refreshInterval <= 0) return;
2943
4162
  const intervalId = setInterval(() => {
2944
4163
  notifyChange(telemetryRef.current);
@@ -2955,8 +4174,50 @@ function useTelemetry(options = {}) {
2955
4174
  };
2956
4175
  }
2957
4176
 
4177
+ // src/diagram/hooks/useTelemetryStatus.ts
4178
+ var import_react12 = require("react");
4179
+ function useTelemetryStatus(telemetry, diagram, setDiagram, options = {}) {
4180
+ const { enabled = true, mapStatus } = options;
4181
+ (0, import_react12.useEffect)(() => {
4182
+ if (!enabled) return;
4183
+ const defaultMapStatus = (telemetryStatus) => {
4184
+ switch (telemetryStatus) {
4185
+ case "normal":
4186
+ return "running";
4187
+ case "warning":
4188
+ return "warning";
4189
+ case "alarm":
4190
+ case "fault":
4191
+ return "alarm";
4192
+ case "off":
4193
+ return "stopped";
4194
+ default:
4195
+ return "stopped";
4196
+ }
4197
+ };
4198
+ const statusMapper = mapStatus || defaultMapStatus;
4199
+ let needsUpdate = false;
4200
+ const updatedNodes = diagram.nodes.map((node) => {
4201
+ const binding = telemetry.get(node.id);
4202
+ if (!binding) return node;
4203
+ const newStatus = statusMapper(binding.value.status);
4204
+ if (node.status !== newStatus) {
4205
+ needsUpdate = true;
4206
+ return { ...node, status: newStatus };
4207
+ }
4208
+ return node;
4209
+ });
4210
+ if (needsUpdate) {
4211
+ setDiagram({
4212
+ ...diagram,
4213
+ nodes: updatedNodes
4214
+ });
4215
+ }
4216
+ }, [telemetry, diagram, setDiagram, enabled, mapStatus]);
4217
+ }
4218
+
2958
4219
  // src/diagram/hooks/useDrag.ts
2959
- var import_react11 = require("react");
4220
+ var import_react13 = require("react");
2960
4221
  function useDrag(options = {}) {
2961
4222
  const {
2962
4223
  onDragStart,
@@ -2966,23 +4227,23 @@ function useDrag(options = {}) {
2966
4227
  dragThreshold = 3,
2967
4228
  screenToWorld = (x, y) => ({ x, y })
2968
4229
  } = options;
2969
- const [dragState, setDragState] = (0, import_react11.useState)({
4230
+ const [dragState, setDragState] = (0, import_react13.useState)({
2970
4231
  isDragging: false,
2971
4232
  startPosition: null,
2972
4233
  offset: { x: 0, y: 0 },
2973
4234
  draggedId: null
2974
4235
  });
2975
- const [isListening, setIsListening] = (0, import_react11.useState)(false);
2976
- const dragStartRef = (0, import_react11.useRef)(null);
2977
- const hasMovedRef = (0, import_react11.useRef)(false);
2978
- const applySnap = (0, import_react11.useCallback)(
4236
+ const [isListening, setIsListening] = (0, import_react13.useState)(false);
4237
+ const dragStartRef = (0, import_react13.useRef)(null);
4238
+ const hasMovedRef = (0, import_react13.useRef)(false);
4239
+ const applySnap = (0, import_react13.useCallback)(
2979
4240
  (value) => {
2980
4241
  if (snapToGrid <= 0) return value;
2981
4242
  return Math.round(value / snapToGrid) * snapToGrid;
2982
4243
  },
2983
4244
  [snapToGrid]
2984
4245
  );
2985
- const handleMouseMove = (0, import_react11.useCallback)(
4246
+ const handleMouseMove = (0, import_react13.useCallback)(
2986
4247
  (event) => {
2987
4248
  if (!dragStartRef.current) return;
2988
4249
  const { id, screenStart: _screenStart, itemPosition, clickOffset } = dragStartRef.current;
@@ -3023,7 +4284,7 @@ function useDrag(options = {}) {
3023
4284
  },
3024
4285
  [screenToWorld, dragThreshold, applySnap, onDragStart, onDragMove]
3025
4286
  );
3026
- const handleMouseUp = (0, import_react11.useCallback)(
4287
+ const handleMouseUp = (0, import_react13.useCallback)(
3027
4288
  (event) => {
3028
4289
  if (!dragStartRef.current || !hasMovedRef.current) {
3029
4290
  dragStartRef.current = null;
@@ -3060,7 +4321,7 @@ function useDrag(options = {}) {
3060
4321
  },
3061
4322
  [screenToWorld, applySnap, onDragEnd]
3062
4323
  );
3063
- const handleTouchMove = (0, import_react11.useCallback)(
4324
+ const handleTouchMove = (0, import_react13.useCallback)(
3064
4325
  (event) => {
3065
4326
  if (!dragStartRef.current || event.touches.length !== 1) return;
3066
4327
  const touch = event.touches[0];
@@ -3103,7 +4364,7 @@ function useDrag(options = {}) {
3103
4364
  },
3104
4365
  [screenToWorld, dragThreshold, applySnap, onDragStart, onDragMove]
3105
4366
  );
3106
- const handleTouchEnd = (0, import_react11.useCallback)(
4367
+ const handleTouchEnd = (0, import_react13.useCallback)(
3107
4368
  (event) => {
3108
4369
  if (!dragStartRef.current || !hasMovedRef.current) {
3109
4370
  dragStartRef.current = null;
@@ -3141,7 +4402,7 @@ function useDrag(options = {}) {
3141
4402
  },
3142
4403
  [screenToWorld, applySnap, onDragEnd]
3143
4404
  );
3144
- (0, import_react11.useEffect)(() => {
4405
+ (0, import_react13.useEffect)(() => {
3145
4406
  if (!isListening) return;
3146
4407
  document.addEventListener("mousemove", handleMouseMove);
3147
4408
  document.addEventListener("mouseup", handleMouseUp);
@@ -3156,7 +4417,7 @@ function useDrag(options = {}) {
3156
4417
  document.removeEventListener("touchcancel", handleTouchEnd);
3157
4418
  };
3158
4419
  }, [isListening, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
3159
- const startDrag = (0, import_react11.useCallback)(
4420
+ const startDrag = (0, import_react13.useCallback)(
3160
4421
  (id, event, itemPosition) => {
3161
4422
  event.stopPropagation();
3162
4423
  let clientX;
@@ -3186,7 +4447,7 @@ function useDrag(options = {}) {
3186
4447
  },
3187
4448
  [screenToWorld]
3188
4449
  );
3189
- const cancelDrag = (0, import_react11.useCallback)(() => {
4450
+ const cancelDrag = (0, import_react13.useCallback)(() => {
3190
4451
  dragStartRef.current = null;
3191
4452
  hasMovedRef.current = false;
3192
4453
  setIsListening(false);
@@ -3197,7 +4458,7 @@ function useDrag(options = {}) {
3197
4458
  draggedId: null
3198
4459
  });
3199
4460
  }, []);
3200
- const isDraggingItem = (0, import_react11.useCallback)(
4461
+ const isDraggingItem = (0, import_react13.useCallback)(
3201
4462
  (id) => dragState.isDragging && dragState.draggedId === id,
3202
4463
  [dragState.isDragging, dragState.draggedId]
3203
4464
  );
@@ -3210,7 +4471,7 @@ function useDrag(options = {}) {
3210
4471
  }
3211
4472
 
3212
4473
  // src/diagram/hooks/useKeyboardShortcuts.ts
3213
- var import_react12 = require("react");
4474
+ var import_react14 = require("react");
3214
4475
  function useKeyboardShortcuts(options) {
3215
4476
  const {
3216
4477
  onDelete,
@@ -3221,7 +4482,7 @@ function useKeyboardShortcuts(options) {
3221
4482
  onSelectAll,
3222
4483
  enabled = true
3223
4484
  } = options;
3224
- (0, import_react12.useEffect)(() => {
4485
+ (0, import_react14.useEffect)(() => {
3225
4486
  if (!enabled) return;
3226
4487
  const handleKeyDown = (event) => {
3227
4488
  const target = event.target;
@@ -3330,7 +4591,7 @@ function nodeHasPort(node, portId) {
3330
4591
  }
3331
4592
 
3332
4593
  // src/components/PIDCanvas/PIDCanvas.tsx
3333
- var import_jsx_runtime16 = require("react/jsx-runtime");
4594
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3334
4595
  function PIDCanvas({
3335
4596
  diagram,
3336
4597
  className = "",
@@ -3357,16 +4618,30 @@ function PIDCanvas({
3357
4618
  telemetry,
3358
4619
  ...props
3359
4620
  }) {
3360
- const [localDiagram, setLocalDiagram] = (0, import_react13.useState)(diagram);
3361
- const [dragOverPosition, setDragOverPosition] = (0, import_react13.useState)(null);
3362
- const [isConnecting, setIsConnecting] = (0, import_react13.useState)(false);
3363
- const [connectionSource, setConnectionSource] = (0, import_react13.useState)(null);
3364
- const [connectionSourcePort, setConnectionSourcePort] = (0, import_react13.useState)(null);
3365
- const [connectionCursor, setConnectionCursor] = (0, import_react13.useState)(null);
3366
- const [hoveredNode, setHoveredNode] = (0, import_react13.useState)(null);
3367
- const [hoveredPort, setHoveredPort] = (0, import_react13.useState)(null);
3368
- (0, import_react13.useEffect)(() => {
3369
- setLocalDiagram(diagram);
4621
+ const [localDiagram, setLocalDiagram] = (0, import_react15.useState)(diagram);
4622
+ const [dragOverPosition, setDragOverPosition] = (0, import_react15.useState)(null);
4623
+ const [isConnecting, setIsConnecting] = (0, import_react15.useState)(false);
4624
+ const [connectionSource, setConnectionSource] = (0, import_react15.useState)(null);
4625
+ const [connectionSourcePort, setConnectionSourcePort] = (0, import_react15.useState)(null);
4626
+ const [connectionCursor, setConnectionCursor] = (0, import_react15.useState)(null);
4627
+ const [hoveredNode, setHoveredNode] = (0, import_react15.useState)(null);
4628
+ const [hoveredPort, setHoveredPort] = (0, import_react15.useState)(null);
4629
+ const [selectedWaypoint, setSelectedWaypoint] = (0, import_react15.useState)(null);
4630
+ const historyStack = (0, import_react15.useRef)([]);
4631
+ const redoStack = (0, import_react15.useRef)([]);
4632
+ const isUndoRedoAction = (0, import_react15.useRef)(false);
4633
+ const isInternalChange = (0, import_react15.useRef)(false);
4634
+ const lastInternalDiagram = (0, import_react15.useRef)(null);
4635
+ (0, import_react15.useEffect)(() => {
4636
+ const isExternal = !isInternalChange.current && diagram !== lastInternalDiagram.current;
4637
+ if (isExternal) {
4638
+ setLocalDiagram(diagram);
4639
+ if (!isUndoRedoAction.current) {
4640
+ historyStack.current = [];
4641
+ redoStack.current = [];
4642
+ }
4643
+ }
4644
+ isInternalChange.current = false;
3370
4645
  }, [diagram]);
3371
4646
  const {
3372
4647
  viewBox: controlledViewBox,
@@ -3379,7 +4654,7 @@ function PIDCanvas({
3379
4654
  enablePan: features.pan ?? true,
3380
4655
  enableZoom: features.zoom ?? true
3381
4656
  });
3382
- const calculateContentBounds = (0, import_react13.useCallback)(() => {
4657
+ const calculateContentBounds = (0, import_react15.useCallback)(() => {
3383
4658
  const nodes2 = localDiagram.nodes.filter((n) => n.visible !== false);
3384
4659
  if (nodes2.length === 0) {
3385
4660
  return null;
@@ -3402,7 +4677,7 @@ function PIDCanvas({
3402
4677
  });
3403
4678
  return { minX, minY, maxX, maxY };
3404
4679
  }, [localDiagram.nodes]);
3405
- (0, import_react13.useEffect)(() => {
4680
+ (0, import_react15.useEffect)(() => {
3406
4681
  if (onMounted) {
3407
4682
  const controls = {
3408
4683
  fitToContent: (padding = 50) => {
@@ -3443,7 +4718,187 @@ function PIDCanvas({
3443
4718
  });
3444
4719
  const dragEnabled = features.dragNodes ?? false;
3445
4720
  const selectionEnabled = features.selection ?? false;
3446
- const handleDragEnd = (0, import_react13.useCallback)(
4721
+ const containerRef = (0, import_react15.useRef)(null);
4722
+ const pushToHistory = (0, import_react15.useCallback)((currentDiagram) => {
4723
+ if (isUndoRedoAction.current) return;
4724
+ const snapshot = JSON.parse(JSON.stringify(currentDiagram));
4725
+ historyStack.current.push(snapshot);
4726
+ if (historyStack.current.length > 15) {
4727
+ historyStack.current.shift();
4728
+ }
4729
+ redoStack.current = [];
4730
+ }, []);
4731
+ const undo = (0, import_react15.useCallback)(() => {
4732
+ if (historyStack.current.length === 0) return;
4733
+ isUndoRedoAction.current = true;
4734
+ const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
4735
+ redoStack.current.push(currentSnapshot);
4736
+ const previousState = historyStack.current.pop();
4737
+ isInternalChange.current = true;
4738
+ lastInternalDiagram.current = previousState;
4739
+ setLocalDiagram(previousState);
4740
+ onDiagramChange?.(previousState);
4741
+ setTimeout(() => {
4742
+ isUndoRedoAction.current = false;
4743
+ }, 0);
4744
+ }, [localDiagram, onDiagramChange]);
4745
+ const redo = (0, import_react15.useCallback)(() => {
4746
+ if (redoStack.current.length === 0) return;
4747
+ isUndoRedoAction.current = true;
4748
+ const currentSnapshot = JSON.parse(JSON.stringify(localDiagram));
4749
+ historyStack.current.push(currentSnapshot);
4750
+ const nextState = redoStack.current.pop();
4751
+ isInternalChange.current = true;
4752
+ lastInternalDiagram.current = nextState;
4753
+ setLocalDiagram(nextState);
4754
+ onDiagramChange?.(nextState);
4755
+ setTimeout(() => {
4756
+ isUndoRedoAction.current = false;
4757
+ }, 0);
4758
+ }, [localDiagram, onDiagramChange]);
4759
+ (0, import_react15.useEffect)(() => {
4760
+ const handleKeyDown = (e) => {
4761
+ const isCtrlOrCmd = e.ctrlKey || e.metaKey;
4762
+ if (isCtrlOrCmd && e.key === "z" && !e.shiftKey) {
4763
+ e.preventDefault();
4764
+ undo();
4765
+ } else if (isCtrlOrCmd && e.key === "z" && e.shiftKey) {
4766
+ e.preventDefault();
4767
+ redo();
4768
+ } else if (isCtrlOrCmd && e.key === "y") {
4769
+ e.preventDefault();
4770
+ redo();
4771
+ }
4772
+ };
4773
+ window.addEventListener("keydown", handleKeyDown);
4774
+ return () => window.removeEventListener("keydown", handleKeyDown);
4775
+ }, [undo, redo]);
4776
+ (0, import_react15.useEffect)(() => {
4777
+ if (!features.dragNodes) return;
4778
+ const handleKeyDown = (e) => {
4779
+ if (selection.selectedNodeIds.size === 0 && !selectedWaypoint) return;
4780
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
4781
+ return;
4782
+ }
4783
+ const nudgeAmount = e.shiftKey ? 10 : 1;
4784
+ let dx = 0;
4785
+ let dy = 0;
4786
+ switch (e.key) {
4787
+ case "ArrowLeft":
4788
+ dx = -nudgeAmount;
4789
+ e.preventDefault();
4790
+ break;
4791
+ case "ArrowRight":
4792
+ dx = nudgeAmount;
4793
+ e.preventDefault();
4794
+ break;
4795
+ case "ArrowUp":
4796
+ dy = -nudgeAmount;
4797
+ e.preventDefault();
4798
+ break;
4799
+ case "ArrowDown":
4800
+ dy = nudgeAmount;
4801
+ e.preventDefault();
4802
+ break;
4803
+ default:
4804
+ return;
4805
+ }
4806
+ if (selectedWaypoint) {
4807
+ pushToHistory(localDiagram);
4808
+ const updatedPipes = localDiagram.pipes.map((pipe) => {
4809
+ if (pipe.id === selectedWaypoint.pipeId) {
4810
+ const updatedRoutePoints = pipe.routePoints.map(
4811
+ (point, idx) => idx === selectedWaypoint.pointIndex ? { x: point.x + dx, y: point.y + dy } : point
4812
+ );
4813
+ return { ...pipe, routePoints: updatedRoutePoints };
4814
+ }
4815
+ return pipe;
4816
+ });
4817
+ const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4818
+ isInternalChange.current = true;
4819
+ lastInternalDiagram.current = updatedDiagram;
4820
+ setLocalDiagram(updatedDiagram);
4821
+ onDiagramChange?.(updatedDiagram);
4822
+ return;
4823
+ }
4824
+ if (selectionEnabled && selection.selectedNodeIds.size > 0) {
4825
+ pushToHistory(localDiagram);
4826
+ const updatedNodes = localDiagram.nodes.map(
4827
+ (node) => selection.selectedNodeIds.has(node.id) ? {
4828
+ ...node,
4829
+ transform: {
4830
+ ...node.transform,
4831
+ x: node.transform.x + dx,
4832
+ y: node.transform.y + dy
4833
+ }
4834
+ } : node
4835
+ );
4836
+ const updatedDiagram = { ...localDiagram, nodes: updatedNodes };
4837
+ isInternalChange.current = true;
4838
+ lastInternalDiagram.current = updatedDiagram;
4839
+ setLocalDiagram(updatedDiagram);
4840
+ onDiagramChange?.(updatedDiagram);
4841
+ selection.selectedNodeIds.forEach((nodeId) => {
4842
+ const node = updatedNodes.find((n) => n.id === nodeId);
4843
+ if (node) {
4844
+ onNodeMove?.(nodeId, node.transform.x, node.transform.y);
4845
+ }
4846
+ });
4847
+ }
4848
+ };
4849
+ window.addEventListener("keydown", handleKeyDown);
4850
+ return () => window.removeEventListener("keydown", handleKeyDown);
4851
+ }, [
4852
+ selectionEnabled,
4853
+ features.dragNodes,
4854
+ selection.selectedNodeIds,
4855
+ selectedWaypoint,
4856
+ localDiagram,
4857
+ onDiagramChange,
4858
+ onNodeMove,
4859
+ pushToHistory
4860
+ ]);
4861
+ const handlePositionChange = (0, import_react15.useCallback)(
4862
+ (nodeId, x, y) => {
4863
+ pushToHistory(localDiagram);
4864
+ const updatedNodes = localDiagram.nodes.map(
4865
+ (node) => node.id === nodeId ? {
4866
+ ...node,
4867
+ transform: {
4868
+ ...node.transform,
4869
+ x,
4870
+ y
4871
+ }
4872
+ } : node
4873
+ );
4874
+ const updatedDiagram = { ...localDiagram, nodes: updatedNodes };
4875
+ isInternalChange.current = true;
4876
+ lastInternalDiagram.current = updatedDiagram;
4877
+ setLocalDiagram(updatedDiagram);
4878
+ onDiagramChange?.(updatedDiagram);
4879
+ onNodeMove?.(nodeId, x, y);
4880
+ },
4881
+ [localDiagram, onDiagramChange, onNodeMove, pushToHistory]
4882
+ );
4883
+ const handleWaypointPositionChange = (0, import_react15.useCallback)(
4884
+ (pipeId, pointIndex, x, y) => {
4885
+ pushToHistory(localDiagram);
4886
+ const updatedPipes = localDiagram.pipes.map((pipe) => {
4887
+ if (pipe.id === pipeId) {
4888
+ const updatedRoutePoints = pipe.routePoints.map(
4889
+ (point, idx) => idx === pointIndex ? { x, y } : point
4890
+ );
4891
+ return { ...pipe, routePoints: updatedRoutePoints };
4892
+ }
4893
+ return pipe;
4894
+ });
4895
+ const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4896
+ setLocalDiagram(updatedDiagram);
4897
+ onDiagramChange?.(updatedDiagram);
4898
+ },
4899
+ [localDiagram, onDiagramChange, pushToHistory]
4900
+ );
4901
+ const handleDragEnd = (0, import_react15.useCallback)(
3447
4902
  (nodeId, offset, finalPosition) => {
3448
4903
  const dragDistance = Math.sqrt(offset.x ** 2 + offset.y ** 2);
3449
4904
  if (dragDistance < 2) {
@@ -3452,6 +4907,7 @@ function PIDCanvas({
3452
4907
  }
3453
4908
  return;
3454
4909
  }
4910
+ pushToHistory(localDiagram);
3455
4911
  const updatedNodes = localDiagram.nodes.map(
3456
4912
  (node) => node.id === nodeId ? {
3457
4913
  ...node,
@@ -3489,11 +4945,13 @@ function PIDCanvas({
3489
4945
  nodes: updatedNodes,
3490
4946
  pipes: updatedPipes
3491
4947
  };
4948
+ isInternalChange.current = true;
4949
+ lastInternalDiagram.current = updatedDiagram;
3492
4950
  setLocalDiagram(updatedDiagram);
3493
4951
  onDiagramChange?.(updatedDiagram);
3494
4952
  onNodeMove?.(nodeId, finalPosition.x, finalPosition.y);
3495
4953
  },
3496
- [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode]
4954
+ [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode, pushToHistory]
3497
4955
  );
3498
4956
  const { dragState, startDrag } = useDrag({
3499
4957
  snapToGrid: features.snapToGrid ?? 0,
@@ -3504,6 +4962,7 @@ function PIDCanvas({
3504
4962
  snapToGrid: features.snapToGrid ?? 0,
3505
4963
  screenToWorld,
3506
4964
  onDragEnd: (pipeId, _offset, finalPosition) => {
4965
+ pushToHistory(localDiagram);
3507
4966
  const [actualPipeId, pointIndex] = pipeId.split(":");
3508
4967
  const idx = parseInt(pointIndex, 10);
3509
4968
  const updatedPipes = localDiagram.pipes.map((pipe) => {
@@ -3514,11 +4973,13 @@ function PIDCanvas({
3514
4973
  return { ...pipe, routePoints: updatedRoutePoints };
3515
4974
  });
3516
4975
  const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4976
+ isInternalChange.current = true;
4977
+ lastInternalDiagram.current = updatedDiagram;
3517
4978
  setLocalDiagram(updatedDiagram);
3518
4979
  onDiagramChange?.(updatedDiagram);
3519
4980
  }
3520
4981
  });
3521
- const handleNodeDragStart = (0, import_react13.useCallback)(
4982
+ const handleNodeDragStart = (0, import_react15.useCallback)(
3522
4983
  (nodeId, event) => {
3523
4984
  const node = localDiagram.nodes.find((n) => n.id === nodeId);
3524
4985
  if (!node) return;
@@ -3526,7 +4987,16 @@ function PIDCanvas({
3526
4987
  },
3527
4988
  [localDiagram.nodes, startDrag]
3528
4989
  );
3529
- const handleWaypointDragStart = (0, import_react13.useCallback)(
4990
+ const handleWaypointClick = (0, import_react15.useCallback)(
4991
+ (pipeId, pointIndex) => {
4992
+ setSelectedWaypoint({ pipeId, pointIndex });
4993
+ if (selectionEnabled) {
4994
+ clearSelection();
4995
+ }
4996
+ },
4997
+ [selectionEnabled, clearSelection]
4998
+ );
4999
+ const handleWaypointDragStart = (0, import_react15.useCallback)(
3530
5000
  (pipeId, pointIndex, event) => {
3531
5001
  const pipe = localDiagram.pipes.find((p) => p.id === pipeId);
3532
5002
  if (!pipe || pointIndex >= pipe.routePoints.length) return;
@@ -3535,8 +5005,9 @@ function PIDCanvas({
3535
5005
  },
3536
5006
  [localDiagram.pipes, startWaypointDrag]
3537
5007
  );
3538
- const handlePipeSegmentClick = (0, import_react13.useCallback)(
5008
+ const handlePipeSegmentClick = (0, import_react15.useCallback)(
3539
5009
  (pipeId, position, segmentIndex) => {
5010
+ pushToHistory(localDiagram);
3540
5011
  const updatedPipes = localDiagram.pipes.map((pipe) => {
3541
5012
  if (pipe.id !== pipeId) return pipe;
3542
5013
  const updatedRoutePoints = [
@@ -3550,13 +5021,14 @@ function PIDCanvas({
3550
5021
  setLocalDiagram(updatedDiagram);
3551
5022
  onDiagramChange?.(updatedDiagram);
3552
5023
  },
3553
- [localDiagram, onDiagramChange]
5024
+ [localDiagram, onDiagramChange, pushToHistory]
3554
5025
  );
3555
5026
  const telemetryEnabled = features.telemetry ?? false;
3556
- const handleDelete = (0, import_react13.useCallback)(() => {
5027
+ const handleDelete = (0, import_react15.useCallback)(() => {
3557
5028
  if (!selectionEnabled) return;
3558
5029
  const { selectedNodeIds, selectedPipeIds } = selection;
3559
5030
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
5031
+ pushToHistory(localDiagram);
3560
5032
  const updatedNodes = localDiagram.nodes.filter((node) => !selectedNodeIds.has(node.id));
3561
5033
  const updatedPipes = localDiagram.pipes.filter(
3562
5034
  (pipe) => !selectedPipeIds.has(pipe.id) && !selectedNodeIds.has(pipe.fromNodeId) && !selectedNodeIds.has(pipe.toNodeId)
@@ -3566,18 +5038,28 @@ function PIDCanvas({
3566
5038
  nodes: updatedNodes,
3567
5039
  pipes: updatedPipes
3568
5040
  };
5041
+ isInternalChange.current = true;
5042
+ lastInternalDiagram.current = updatedDiagram;
3569
5043
  setLocalDiagram(updatedDiagram);
3570
5044
  clearSelection();
3571
5045
  onDiagramChange?.(updatedDiagram);
3572
5046
  onDelete?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
3573
- }, [selection, localDiagram, selectionEnabled, clearSelection, onDiagramChange, onDelete]);
3574
- const handleCopy = (0, import_react13.useCallback)(() => {
5047
+ }, [
5048
+ selection,
5049
+ localDiagram,
5050
+ selectionEnabled,
5051
+ clearSelection,
5052
+ onDiagramChange,
5053
+ onDelete,
5054
+ pushToHistory
5055
+ ]);
5056
+ const handleCopy = (0, import_react15.useCallback)(() => {
3575
5057
  if (!selectionEnabled) return;
3576
5058
  const { selectedNodeIds, selectedPipeIds } = selection;
3577
5059
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
3578
5060
  onItemsCopied?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
3579
5061
  }, [selection, selectionEnabled, onItemsCopied]);
3580
- const handlePaste = (0, import_react13.useCallback)(() => {
5062
+ const handlePaste = (0, import_react15.useCallback)(() => {
3581
5063
  if (!selectionEnabled) return;
3582
5064
  onPaste?.();
3583
5065
  }, [selectionEnabled, onPaste]);
@@ -3627,12 +5109,17 @@ function PIDCanvas({
3627
5109
  }
3628
5110
  onPipeClick?.(pipeId);
3629
5111
  };
3630
- const handleCanvasClick = () => {
5112
+ const handleCanvasClick = (e) => {
5113
+ const target = e.target;
5114
+ if (target.closest(".pipe-route-point")) {
5115
+ return;
5116
+ }
3631
5117
  if (selectionEnabled) {
3632
5118
  clearSelection();
3633
5119
  }
5120
+ setSelectedWaypoint(null);
3634
5121
  };
3635
- const handleDragOver = (0, import_react13.useCallback)(
5122
+ const handleDragOver = (0, import_react15.useCallback)(
3636
5123
  (event) => {
3637
5124
  if (!features.allowSymbolDrop) return;
3638
5125
  event.preventDefault();
@@ -3641,9 +5128,9 @@ function PIDCanvas({
3641
5128
  const worldPos = screenToWorld(event.clientX, event.clientY);
3642
5129
  setDragOverPosition(worldPos);
3643
5130
  },
3644
- [features.allowSymbolDrop, screenToWorld]
5131
+ [features.allowSymbolDrop, screenToWorld, svgRef]
3645
5132
  );
3646
- const handleDrop = (0, import_react13.useCallback)(
5133
+ const handleDrop = (0, import_react15.useCallback)(
3647
5134
  (event) => {
3648
5135
  if (!features.allowSymbolDrop) return;
3649
5136
  event.preventDefault();
@@ -3654,12 +5141,12 @@ function PIDCanvas({
3654
5141
  const worldPos = screenToWorld(event.clientX, event.clientY);
3655
5142
  onSymbolDrop?.(symbolId, worldPos);
3656
5143
  },
3657
- [features.allowSymbolDrop, screenToWorld, onSymbolDrop]
5144
+ [features.allowSymbolDrop, screenToWorld, onSymbolDrop, svgRef]
3658
5145
  );
3659
- const handleDragLeave = (0, import_react13.useCallback)(() => {
5146
+ const handleDragLeave = (0, import_react15.useCallback)(() => {
3660
5147
  setDragOverPosition(null);
3661
5148
  }, []);
3662
- const handleMouseMove = (0, import_react13.useCallback)(
5149
+ const handleMouseMove = (0, import_react15.useCallback)(
3663
5150
  (event) => {
3664
5151
  const connectionModeEnabled = features.connectionMode ?? false;
3665
5152
  if (connectionModeEnabled) {
@@ -3678,7 +5165,7 @@ function PIDCanvas({
3678
5165
  const distance = Math.sqrt(
3679
5166
  (worldPos.x - portPos.x) ** 2 + (worldPos.y - portPos.y) ** 2
3680
5167
  );
3681
- if (distance < 10) {
5168
+ if (distance < 15) {
3682
5169
  foundPort = port.id;
3683
5170
  foundNode = node.id;
3684
5171
  break;
@@ -3695,9 +5182,11 @@ function PIDCanvas({
3695
5182
  const { name } = diagram;
3696
5183
  const viewBox = controlledViewBox;
3697
5184
  const displayDiagram = localDiagram;
3698
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5185
+ const selectedNode = selectionEnabled && selection.selectedNodeIds.size === 1 ? localDiagram.nodes.find((n) => selection.selectedNodeIds.has(n.id)) || null : null;
5186
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3699
5187
  "div",
3700
5188
  {
5189
+ ref: containerRef,
3701
5190
  className: `pid-canvas ${className}`.trim(),
3702
5191
  style: {
3703
5192
  width: "100%",
@@ -3708,223 +5197,287 @@ function PIDCanvas({
3708
5197
  ...style
3709
5198
  },
3710
5199
  ...props,
3711
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3712
- "svg",
3713
- {
3714
- ref: svgRef,
3715
- width: "100%",
3716
- height: "100%",
3717
- viewBox: `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`,
3718
- preserveAspectRatio: "xMidYMid meet",
3719
- style: {
3720
- display: "block"
3721
- },
3722
- "aria-label": `P&ID diagram: ${name}`,
3723
- role: "img",
3724
- onClick: handleCanvasClick,
3725
- onMouseMove: handleMouseMove,
3726
- onDragOver: handleDragOver,
3727
- onDrop: handleDrop,
3728
- onDragLeave: handleDragLeave,
3729
- children: [
3730
- features.showGrid !== false ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3731
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("defs", { children: [
3732
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
3733
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
3734
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
3735
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
3736
- ] })
3737
- ] }),
3738
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5200
+ children: [
5201
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5202
+ "svg",
5203
+ {
5204
+ ref: svgRef,
5205
+ width: "100%",
5206
+ height: "100%",
5207
+ viewBox: `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`,
5208
+ preserveAspectRatio: "xMidYMid meet",
5209
+ style: {
5210
+ display: "block"
5211
+ },
5212
+ "aria-label": `P&ID diagram: ${name}`,
5213
+ role: "img",
5214
+ onClick: handleCanvasClick,
5215
+ onMouseMove: handleMouseMove,
5216
+ onDragOver: handleDragOver,
5217
+ onDrop: handleDrop,
5218
+ onDragLeave: handleDragLeave,
5219
+ children: [
5220
+ features.showGrid !== false ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
5221
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("defs", { children: [
5222
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("pattern", { id: "grid-minor", width: "5", height: "5", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M 5 0 L 0 0 0 5", fill: "none", stroke: "#e5e7eb", strokeWidth: "0.5" }) }),
5223
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
5224
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
5225
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
5226
+ ] })
5227
+ ] }),
5228
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5229
+ "rect",
5230
+ {
5231
+ x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
5232
+ y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
5233
+ width: Math.ceil(viewBox.width * 3 / 25) * 25,
5234
+ height: Math.ceil(viewBox.height * 3 / 25) * 25,
5235
+ fill: "url(#grid-major)"
5236
+ }
5237
+ )
5238
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3739
5239
  "rect",
3740
5240
  {
3741
5241
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3742
5242
  y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3743
5243
  width: Math.ceil(viewBox.width * 3 / 25) * 25,
3744
5244
  height: Math.ceil(viewBox.height * 3 / 25) * 25,
3745
- fill: "url(#grid-major)"
5245
+ fill: features.backgroundColor ?? "#ffffff"
3746
5246
  }
3747
- )
3748
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3749
- "rect",
3750
- {
3751
- x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3752
- y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3753
- width: Math.ceil(viewBox.width * 3 / 25) * 25,
3754
- height: Math.ceil(viewBox.height * 3 / 25) * 25,
3755
- fill: features.backgroundColor ?? "#ffffff"
3756
- }
3757
- ),
3758
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3759
- PipeRenderer,
3760
- {
3761
- pipes: displayDiagram.pipes,
3762
- selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
3763
- onPipeClick: selectionEnabled ? handlePipeClick : void 0,
3764
- enableWaypointEditing: false,
3765
- onWaypointDragStart: void 0,
3766
- editingPipeId: null,
3767
- draggingWaypointIndex: null,
3768
- waypointDragOffset: void 0,
3769
- onPipeSegmentClick: void 0
3770
- }
3771
- ),
3772
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3773
- NodeRenderer,
3774
- {
3775
- nodes: displayDiagram.nodes,
3776
- selectedNodeIds: selectionEnabled ? selection.selectedNodeIds : void 0,
3777
- onNodeClick: selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
3778
- onNodeDoubleClick: selectionEnabled ? handleNodeDoubleClick : void 0,
3779
- enableDrag: dragEnabled && !isConnecting,
3780
- onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
3781
- draggingNodeId: dragState.draggedId,
3782
- dragOffset: dragState.offset
3783
- }
3784
- ),
3785
- (features.editPipeRoutes ?? false) && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3786
- PipeRenderer,
3787
- {
3788
- pipes: displayDiagram.pipes,
3789
- selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
3790
- onPipeClick: void 0,
3791
- enableWaypointEditing: true,
3792
- onWaypointDragStart: handleWaypointDragStart,
3793
- editingPipeId: waypointDragState.draggedId?.split(":")[0] || null,
3794
- draggingWaypointIndex: waypointDragState.draggedId ? parseInt(waypointDragState.draggedId.split(":")[1], 10) : null,
3795
- waypointDragOffset: waypointDragState.offset,
3796
- onPipeSegmentClick: handlePipeSegmentClick
3797
- }
3798
- ),
3799
- telemetryEnabled && telemetry && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
3800
- const binding = telemetry.get(node.id);
3801
- if (!binding) return null;
3802
- const symbol = getSymbolDefinition(node.symbolId);
3803
- if (!symbol) return null;
3804
- const overlayY = node.transform.y - symbol.viewBox.height / 2 - 10;
3805
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3806
- DataOverlay,
5247
+ ),
5248
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5249
+ PipeRenderer,
3807
5250
  {
3808
- nodeId: node.id,
3809
- x: node.transform.x,
3810
- y: overlayY,
3811
- telemetry: binding
3812
- },
3813
- `telemetry-${node.id}`
3814
- );
3815
- }) }),
3816
- dragOverPosition && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3817
- "circle",
3818
- {
3819
- cx: dragOverPosition.x,
3820
- cy: dragOverPosition.y,
3821
- r: 20,
3822
- fill: "#3b82f6",
3823
- opacity: 0.3,
3824
- stroke: "#3b82f6",
3825
- strokeWidth: 2,
3826
- pointerEvents: "none"
3827
- }
3828
- ),
3829
- isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
3830
- const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
3831
- if (!sourceNode) return null;
3832
- const symbol = getSymbolDefinition(sourceNode.symbolId);
3833
- let lineStartX = sourceNode.transform.x;
3834
- let lineStartY = sourceNode.transform.y;
3835
- if (connectionSourcePort && symbol?.ports) {
3836
- const port = symbol.ports.find((p) => p.id === connectionSourcePort);
3837
- if (port) {
3838
- const portPos = getPortWorldPosition(sourceNode, connectionSourcePort);
3839
- if (portPos) {
3840
- lineStartX = portPos.x;
3841
- lineStartY = portPos.y;
3842
- }
5251
+ pipes: displayDiagram.pipes,
5252
+ selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
5253
+ onPipeClick: selectionEnabled ? handlePipeClick : void 0,
5254
+ enableWaypointEditing: false,
5255
+ onWaypointDragStart: void 0,
5256
+ editingPipeId: null,
5257
+ draggingWaypointIndex: null,
5258
+ waypointDragOffset: void 0,
5259
+ onPipeSegmentClick: void 0
3843
5260
  }
3844
- }
3845
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3846
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3847
- "circle",
3848
- {
3849
- cx: sourceNode.transform.x,
3850
- cy: sourceNode.transform.y,
3851
- r: 30,
3852
- fill: "none",
3853
- stroke: "#10b981",
3854
- strokeWidth: 3,
3855
- opacity: 0.6
3856
- }
3857
- ),
3858
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3859
- "line",
3860
- {
3861
- x1: lineStartX,
3862
- y1: lineStartY,
3863
- x2: connectionCursor.x,
3864
- y2: connectionCursor.y,
3865
- stroke: "#10b981",
3866
- strokeWidth: 2,
3867
- strokeDasharray: "5,5",
3868
- opacity: 0.8
3869
- }
3870
- ),
3871
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3872
- "circle",
3873
- {
3874
- cx: connectionCursor.x,
3875
- cy: connectionCursor.y,
3876
- r: 8,
3877
- fill: "#10b981",
3878
- opacity: 0.6
3879
- }
3880
- )
3881
- ] });
3882
- })() }),
3883
- features.connectionMode && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { id: "port-indicators", pointerEvents: "none", children: displayDiagram.nodes.map((node) => {
3884
- const symbol = getSymbolDefinition(node.symbolId);
3885
- if (!symbol?.ports) return null;
3886
- return symbol.ports.map((port) => {
3887
- const portPos = getPortWorldPosition(node, port.id);
3888
- if (!portPos) return null;
3889
- const isHovered = hoveredPort === port.id && hoveredNode === node.id;
3890
- const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
3891
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3892
- "circle",
5261
+ ),
5262
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5263
+ NodeRenderer,
5264
+ {
5265
+ nodes: displayDiagram.nodes,
5266
+ selectedNodeIds: selectionEnabled ? selection.selectedNodeIds : void 0,
5267
+ onNodeClick: selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
5268
+ onNodeDoubleClick: selectionEnabled ? handleNodeDoubleClick : void 0,
5269
+ enableDrag: dragEnabled && !isConnecting,
5270
+ onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
5271
+ draggingNodeId: dragState.draggedId,
5272
+ dragOffset: dragState.offset
5273
+ }
5274
+ ),
5275
+ (features.editPipeRoutes ?? false) && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5276
+ PipeRenderer,
5277
+ {
5278
+ pipes: displayDiagram.pipes,
5279
+ selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
5280
+ onPipeClick: void 0,
5281
+ enableWaypointEditing: true,
5282
+ onWaypointClick: handleWaypointClick,
5283
+ onWaypointDragStart: handleWaypointDragStart,
5284
+ selectedWaypointPipeId: selectedWaypoint?.pipeId || null,
5285
+ selectedWaypointIndex: selectedWaypoint?.pointIndex ?? null,
5286
+ editingPipeId: waypointDragState.draggedId?.split(":")[0] || null,
5287
+ draggingWaypointIndex: waypointDragState.draggedId ? parseInt(waypointDragState.draggedId.split(":")[1], 10) : null,
5288
+ waypointDragOffset: waypointDragState.offset,
5289
+ onPipeSegmentClick: handlePipeSegmentClick
5290
+ }
5291
+ ),
5292
+ telemetryEnabled && telemetry && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
5293
+ const binding = telemetry.get(node.id);
5294
+ if (!binding) return null;
5295
+ const symbol = getSymbolDefinition(node.symbolId);
5296
+ if (!symbol) return null;
5297
+ const overlayX = node.transform.x + symbol.viewBox.width / 2;
5298
+ const overlayY = node.transform.y + symbol.viewBox.height + 50;
5299
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5300
+ DataOverlay,
3893
5301
  {
3894
- cx: portPos.x,
3895
- cy: portPos.y,
3896
- r: isHovered ? 8 : 5,
3897
- fill: isSourcePort ? "#10b981" : isHovered ? "#3b82f6" : "#94a3b8",
3898
- stroke: isSourcePort ? "#059669" : isHovered ? "#2563eb" : "#64748b",
3899
- strokeWidth: isHovered || isSourcePort ? 2 : 1,
3900
- opacity: isHovered || isSourcePort ? 1 : 0.6
5302
+ nodeId: node.id,
5303
+ x: overlayX,
5304
+ y: overlayY,
5305
+ telemetry: binding,
5306
+ scale: features.telemetryScale ?? 1
3901
5307
  },
3902
- `${node.id}-${port.id}`
5308
+ `telemetry-${node.id}`
3903
5309
  );
3904
- });
3905
- }) })
3906
- ]
3907
- }
3908
- )
5310
+ }) }),
5311
+ dragOverPosition && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5312
+ "circle",
5313
+ {
5314
+ cx: dragOverPosition.x,
5315
+ cy: dragOverPosition.y,
5316
+ r: 20,
5317
+ fill: "#3b82f6",
5318
+ opacity: 0.3,
5319
+ stroke: "#3b82f6",
5320
+ strokeWidth: 2,
5321
+ pointerEvents: "none"
5322
+ }
5323
+ ),
5324
+ isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
5325
+ const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
5326
+ if (!sourceNode) return null;
5327
+ const symbol = getSymbolDefinition(sourceNode.symbolId);
5328
+ let lineStartX = sourceNode.transform.x;
5329
+ let lineStartY = sourceNode.transform.y;
5330
+ if (connectionSourcePort && symbol?.ports) {
5331
+ const port = symbol.ports.find((p) => p.id === connectionSourcePort);
5332
+ if (port) {
5333
+ const portPos = getPortWorldPosition(sourceNode, connectionSourcePort);
5334
+ if (portPos) {
5335
+ lineStartX = portPos.x;
5336
+ lineStartY = portPos.y;
5337
+ }
5338
+ }
5339
+ }
5340
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
5341
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5342
+ "circle",
5343
+ {
5344
+ cx: lineStartX,
5345
+ cy: lineStartY,
5346
+ r: 30,
5347
+ fill: "none",
5348
+ stroke: "#10b981",
5349
+ strokeWidth: 3,
5350
+ opacity: 0.6
5351
+ }
5352
+ ),
5353
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5354
+ "line",
5355
+ {
5356
+ x1: lineStartX,
5357
+ y1: lineStartY,
5358
+ x2: connectionCursor.x,
5359
+ y2: connectionCursor.y,
5360
+ stroke: "#10b981",
5361
+ strokeWidth: 2,
5362
+ strokeDasharray: "5,5",
5363
+ opacity: 0.8
5364
+ }
5365
+ ),
5366
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5367
+ "circle",
5368
+ {
5369
+ cx: connectionCursor.x,
5370
+ cy: connectionCursor.y,
5371
+ r: 8,
5372
+ fill: "#10b981",
5373
+ opacity: 0.6
5374
+ }
5375
+ )
5376
+ ] });
5377
+ })() }),
5378
+ features.connectionMode && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("g", { id: "port-indicators", children: displayDiagram.nodes.map((node) => {
5379
+ const symbol = getSymbolDefinition(node.symbolId);
5380
+ if (!symbol?.ports) return null;
5381
+ return symbol.ports.map((port) => {
5382
+ const portPos = getPortWorldPosition(node, port.id);
5383
+ if (!portPos) return null;
5384
+ const isHovered = hoveredPort === port.id && hoveredNode === node.id;
5385
+ const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
5386
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("g", { children: [
5387
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5388
+ "circle",
5389
+ {
5390
+ cx: portPos.x,
5391
+ cy: portPos.y,
5392
+ r: 15,
5393
+ fill: "transparent",
5394
+ style: { cursor: "pointer" },
5395
+ pointerEvents: "auto",
5396
+ onClick: (e) => {
5397
+ e.stopPropagation();
5398
+ if (isConnecting && connectionSource) {
5399
+ if (connectionSource !== node.id) {
5400
+ onPipeCreated?.(
5401
+ connectionSource,
5402
+ node.id,
5403
+ connectionSourcePort || void 0,
5404
+ port.id
5405
+ );
5406
+ setIsConnecting(false);
5407
+ setConnectionSource(null);
5408
+ setConnectionSourcePort(null);
5409
+ setConnectionCursor(null);
5410
+ setHoveredPort(null);
5411
+ }
5412
+ } else {
5413
+ setIsConnecting(true);
5414
+ setConnectionSource(node.id);
5415
+ setConnectionSourcePort(port.id);
5416
+ }
5417
+ }
5418
+ }
5419
+ ),
5420
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5421
+ "circle",
5422
+ {
5423
+ cx: portPos.x,
5424
+ cy: portPos.y,
5425
+ r: isHovered ? 8 : 5,
5426
+ fill: isSourcePort ? "#10b981" : isHovered ? "#3b82f6" : "#94a3b8",
5427
+ stroke: isSourcePort ? "#059669" : isHovered ? "#2563eb" : "#64748b",
5428
+ strokeWidth: isHovered || isSourcePort ? 2 : 1,
5429
+ opacity: isHovered || isSourcePort ? 1 : 0.6,
5430
+ pointerEvents: "none"
5431
+ }
5432
+ )
5433
+ ] }, `${node.id}-${port.id}`);
5434
+ });
5435
+ }) })
5436
+ ]
5437
+ }
5438
+ ),
5439
+ features.dragNodes && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5440
+ PositionPanel,
5441
+ {
5442
+ selectedNode,
5443
+ selectedWaypoint: selectedWaypoint ? {
5444
+ pipeId: selectedWaypoint.pipeId,
5445
+ pointIndex: selectedWaypoint.pointIndex,
5446
+ position: localDiagram.pipes.find((p) => p.id === selectedWaypoint.pipeId)?.routePoints[selectedWaypoint.pointIndex] || { x: 0, y: 0 }
5447
+ } : null,
5448
+ onPositionChange: handlePositionChange,
5449
+ onWaypointPositionChange: handleWaypointPositionChange,
5450
+ position: "top-right"
5451
+ }
5452
+ )
5453
+ ]
3909
5454
  }
3910
5455
  );
3911
5456
  }
3912
5457
 
3913
5458
  // src/components/SymbolLibrary/SymbolLibrary.tsx
3914
- var import_react14 = require("react");
3915
- var import_jsx_runtime17 = require("react/jsx-runtime");
5459
+ var import_react16 = require("react");
5460
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3916
5461
  function SymbolLibrary({
3917
5462
  className = "",
3918
5463
  onSymbolDragStart,
3919
5464
  showCategories = true,
3920
5465
  categories = ["Valves", "Pumps", "Tanks", "Instruments", "Displays"]
3921
5466
  }) {
3922
- const [selectedCategory, setSelectedCategory] = (0, import_react14.useState)("all");
3923
- const [searchQuery, setSearchQuery] = (0, import_react14.useState)("");
5467
+ const [selectedCategory, setSelectedCategory] = (0, import_react16.useState)("all");
5468
+ const [searchQuery, setSearchQuery] = (0, import_react16.useState)("");
3924
5469
  const symbolIds = getAvailableSymbols();
3925
5470
  const allSymbols = symbolIds.map((id) => getSymbolDefinition(id)).filter((symbol) => symbol !== void 0);
5471
+ const categoryMap = {
5472
+ Valves: "valve",
5473
+ Pumps: "pump",
5474
+ Tanks: "vessel",
5475
+ Instruments: "instrument",
5476
+ Displays: "display"
5477
+ };
3926
5478
  const filteredSymbols = allSymbols.filter((symbol) => {
3927
- const matchesCategory = selectedCategory === "all" || symbol.category === selectedCategory;
5479
+ const actualCategory = categoryMap[selectedCategory] || selectedCategory;
5480
+ const matchesCategory = selectedCategory === "all" || symbol.category === actualCategory;
3928
5481
  const matchesSearch = searchQuery === "" || symbol.name.toLowerCase().includes(searchQuery.toLowerCase()) || symbol.id.toLowerCase().includes(searchQuery.toLowerCase());
3929
5482
  return matchesCategory && matchesSearch;
3930
5483
  });
@@ -3933,9 +5486,9 @@ function SymbolLibrary({
3933
5486
  event.dataTransfer.effectAllowed = "copy";
3934
5487
  onSymbolDragStart?.(symbol.id, event);
3935
5488
  };
3936
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
3937
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h3", { style: styles.title, children: "Symbol Library" }) }),
3938
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.searchContainer, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5489
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
5490
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: styles.title, children: "Symbol Library" }) }),
5491
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.searchContainer, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3939
5492
  "input",
3940
5493
  {
3941
5494
  type: "text",
@@ -3945,8 +5498,8 @@ function SymbolLibrary({
3945
5498
  style: styles.searchInput
3946
5499
  }
3947
5500
  ) }),
3948
- showCategories && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: styles.categories, children: [
3949
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5501
+ showCategories && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: styles.categories, children: [
5502
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3950
5503
  "button",
3951
5504
  {
3952
5505
  onClick: () => setSelectedCategory("all"),
@@ -3957,7 +5510,7 @@ function SymbolLibrary({
3957
5510
  children: "All"
3958
5511
  }
3959
5512
  ),
3960
- categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5513
+ categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3961
5514
  "button",
3962
5515
  {
3963
5516
  onClick: () => setSelectedCategory(category),
@@ -3970,7 +5523,7 @@ function SymbolLibrary({
3970
5523
  category
3971
5524
  ))
3972
5525
  ] }),
3973
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5526
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.symbolGrid, children: filteredSymbols.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.emptyState, children: "No symbols found" }) : filteredSymbols.map((symbol) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
3974
5527
  "div",
3975
5528
  {
3976
5529
  draggable: true,
@@ -3978,15 +5531,15 @@ function SymbolLibrary({
3978
5531
  style: styles.symbolCard,
3979
5532
  title: symbol.metadata?.description || symbol.name,
3980
5533
  children: [
3981
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.symbolPreview, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5534
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.symbolPreview, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3982
5535
  "svg",
3983
5536
  {
3984
5537
  viewBox: `${symbol.viewBox.x} ${symbol.viewBox.y} ${symbol.viewBox.width} ${symbol.viewBox.height}`,
3985
5538
  style: styles.symbolSvg,
3986
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
5539
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("g", { dangerouslySetInnerHTML: { __html: symbol.svgContent } })
3987
5540
  }
3988
5541
  ) }),
3989
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.symbolName, children: symbol.name })
5542
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.symbolName, children: symbol.name })
3990
5543
  ]
3991
5544
  },
3992
5545
  symbol.id
@@ -4095,7 +5648,7 @@ var styles = {
4095
5648
  };
4096
5649
 
4097
5650
  // src/components/NodeConfigPanel/NodeConfigPanel.tsx
4098
- var import_jsx_runtime18 = require("react/jsx-runtime");
5651
+ var import_jsx_runtime19 = require("react/jsx-runtime");
4099
5652
  function NodeConfigPanel({
4100
5653
  node,
4101
5654
  binding,
@@ -4114,15 +5667,15 @@ function NodeConfigPanel({
4114
5667
  padding: "20px",
4115
5668
  ...style
4116
5669
  };
4117
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className, style: defaultStyle, children: [
4118
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
5670
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className, style: defaultStyle, children: [
5671
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("h2", { style: { margin: "0 0 16px 0", fontSize: "1rem", fontWeight: 600 }, children: [
4119
5672
  "Configure: ",
4120
5673
  node.id
4121
5674
  ] }),
4122
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4123
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
4124
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
4125
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5675
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "20px" }, children: [
5676
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
5677
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
5678
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4126
5679
  "input",
4127
5680
  {
4128
5681
  type: "text",
@@ -4138,10 +5691,10 @@ function NodeConfigPanel({
4138
5691
  }
4139
5692
  }
4140
5693
  ),
4141
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
4142
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4143
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
4144
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5694
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
5695
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5696
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
5697
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4145
5698
  "input",
4146
5699
  {
4147
5700
  type: "number",
@@ -4162,9 +5715,9 @@ function NodeConfigPanel({
4162
5715
  }
4163
5716
  )
4164
5717
  ] }),
4165
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4166
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
4167
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5718
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5719
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
5720
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4168
5721
  "input",
4169
5722
  {
4170
5723
  type: "number",
@@ -4186,9 +5739,9 @@ function NodeConfigPanel({
4186
5739
  )
4187
5740
  ] })
4188
5741
  ] }),
4189
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4190
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
4191
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5742
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5743
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
5744
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4192
5745
  "input",
4193
5746
  {
4194
5747
  type: "number",
@@ -4208,9 +5761,9 @@ function NodeConfigPanel({
4208
5761
  }
4209
5762
  )
4210
5763
  ] }),
4211
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4212
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
4213
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5764
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5765
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
5766
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4214
5767
  "input",
4215
5768
  {
4216
5769
  type: "number",
@@ -4235,9 +5788,9 @@ function NodeConfigPanel({
4235
5788
  )
4236
5789
  ] })
4237
5790
  ] }),
4238
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4239
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
4240
- !binding ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5791
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "20px" }, children: [
5792
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
5793
+ !binding ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4241
5794
  "button",
4242
5795
  {
4243
5796
  onClick: () => {
@@ -4280,9 +5833,9 @@ function NodeConfigPanel({
4280
5833
  },
4281
5834
  children: "+ Add Telemetry"
4282
5835
  }
4283
- ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
4284
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
4285
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5836
+ ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
5837
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
5838
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4286
5839
  "input",
4287
5840
  {
4288
5841
  type: "number",
@@ -4304,8 +5857,8 @@ function NodeConfigPanel({
4304
5857
  }
4305
5858
  }
4306
5859
  ),
4307
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
4308
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5860
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
5861
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4309
5862
  "input",
4310
5863
  {
4311
5864
  type: "text",
@@ -4327,8 +5880,8 @@ function NodeConfigPanel({
4327
5880
  }
4328
5881
  }
4329
5882
  ),
4330
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
4331
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5883
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
5884
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4332
5885
  "input",
4333
5886
  {
4334
5887
  type: "checkbox",
@@ -4350,8 +5903,8 @@ function NodeConfigPanel({
4350
5903
  ),
4351
5904
  "Show Sparkline"
4352
5905
  ] }),
4353
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginTop: "12px" }, children: [
4354
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5906
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginTop: "12px" }, children: [
5907
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4355
5908
  "label",
4356
5909
  {
4357
5910
  style: {
@@ -4363,10 +5916,10 @@ function NodeConfigPanel({
4363
5916
  children: "Telemetry Position Offset:"
4364
5917
  }
4365
5918
  ),
4366
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
4367
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4368
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
4369
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5919
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
5920
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5921
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
5922
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4370
5923
  "input",
4371
5924
  {
4372
5925
  type: "number",
@@ -4393,9 +5946,9 @@ function NodeConfigPanel({
4393
5946
  }
4394
5947
  )
4395
5948
  ] }),
4396
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4397
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
4398
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5949
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
5950
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
5951
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4399
5952
  "input",
4400
5953
  {
4401
5954
  type: "number",
@@ -4423,8 +5976,8 @@ function NodeConfigPanel({
4423
5976
  )
4424
5977
  ] })
4425
5978
  ] }),
4426
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginTop: "16px" }, children: [
4427
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5979
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginTop: "16px" }, children: [
5980
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4428
5981
  "label",
4429
5982
  {
4430
5983
  style: {
@@ -4436,10 +5989,10 @@ function NodeConfigPanel({
4436
5989
  children: "Display Colors:"
4437
5990
  }
4438
5991
  ),
4439
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4440
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
4441
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4442
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5992
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "8px" }, children: [
5993
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
5994
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
5995
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4443
5996
  "input",
4444
5997
  {
4445
5998
  type: "color",
@@ -4465,7 +6018,7 @@ function NodeConfigPanel({
4465
6018
  }
4466
6019
  }
4467
6020
  ),
4468
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6021
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4469
6022
  "input",
4470
6023
  {
4471
6024
  type: "text",
@@ -4495,10 +6048,10 @@ function NodeConfigPanel({
4495
6048
  )
4496
6049
  ] })
4497
6050
  ] }),
4498
- binding.display?.showSparkline && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4499
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
4500
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4501
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6051
+ binding.display?.showSparkline && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "8px" }, children: [
6052
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
6053
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6054
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4502
6055
  "input",
4503
6056
  {
4504
6057
  type: "color",
@@ -4524,7 +6077,7 @@ function NodeConfigPanel({
4524
6077
  }
4525
6078
  }
4526
6079
  ),
4527
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6080
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4528
6081
  "input",
4529
6082
  {
4530
6083
  type: "text",
@@ -4554,9 +6107,9 @@ function NodeConfigPanel({
4554
6107
  )
4555
6108
  ] })
4556
6109
  ] }),
4557
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4558
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
4559
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6110
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "8px" }, children: [
6111
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
6112
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4560
6113
  "input",
4561
6114
  {
4562
6115
  type: "text",
@@ -4584,20 +6137,20 @@ function NodeConfigPanel({
4584
6137
  }
4585
6138
  }
4586
6139
  ),
4587
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
6140
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { fontSize: "0.65rem", color: "#6b7280", marginTop: "2px" }, children: "Use 'status' for status-based color or hex code" })
4588
6141
  ] })
4589
6142
  ] }),
4590
6143
  " "
4591
6144
  ] })
4592
6145
  ] })
4593
6146
  ] }),
4594
- binding && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4595
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
6147
+ binding && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "20px" }, children: [
6148
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Thresholds" }),
4596
6149
  ["highHigh", "high", "low", "lowLow"].map((key) => {
4597
6150
  const label = key === "highHigh" ? "High-High (\u2265)" : key === "high" ? "High (\u2265)" : key === "low" ? "Low (\u2264)" : "Low-Low (\u2264)";
4598
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4599
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
4600
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6151
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "8px" }, children: [
6152
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
6153
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4601
6154
  "input",
4602
6155
  {
4603
6156
  type: "number",
@@ -4621,8 +6174,8 @@ function NodeConfigPanel({
4621
6174
  ] }, key);
4622
6175
  })
4623
6176
  ] }),
4624
- binding && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4625
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
6177
+ binding && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "20px" }, children: [
6178
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Status Colors" }),
4626
6179
  ["normal", "warning", "alarm", "fault", "off"].map((status) => {
4627
6180
  const defaultColors = {
4628
6181
  normal: "#10b981",
@@ -4632,13 +6185,13 @@ function NodeConfigPanel({
4632
6185
  off: "#6b7280"
4633
6186
  };
4634
6187
  const label = status === "normal" ? "Normal" : status === "warning" ? "Warning" : status === "alarm" ? "Alarm" : status === "fault" ? "Fault" : "Off";
4635
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4636
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
6188
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { marginBottom: "8px" }, children: [
6189
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
4637
6190
  label,
4638
6191
  ":"
4639
6192
  ] }),
4640
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4641
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6193
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
6194
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4642
6195
  "input",
4643
6196
  {
4644
6197
  type: "color",
@@ -4659,7 +6212,7 @@ function NodeConfigPanel({
4659
6212
  }
4660
6213
  }
4661
6214
  ),
4662
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6215
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4663
6216
  "input",
4664
6217
  {
4665
6218
  type: "text",
@@ -4686,7 +6239,7 @@ function NodeConfigPanel({
4686
6239
  ] }, status);
4687
6240
  })
4688
6241
  ] }),
4689
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6242
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { marginTop: "20px", paddingTop: "20px", borderTop: "1px solid #cbd5e1" }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4690
6243
  "button",
4691
6244
  {
4692
6245
  onClick: onRemove,
@@ -4926,7 +6479,7 @@ var DEFAULT_THRESHOLDS = {
4926
6479
  };
4927
6480
 
4928
6481
  // src/diagram/hooks/useSimulation.ts
4929
- var import_react15 = require("react");
6482
+ var import_react17 = require("react");
4930
6483
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4931
6484
  const {
4932
6485
  interval = 2e3,
@@ -4937,7 +6490,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4937
6490
  historyLength = 10,
4938
6491
  generateValue
4939
6492
  } = options;
4940
- (0, import_react15.useEffect)(() => {
6493
+ (0, import_react17.useEffect)(() => {
4941
6494
  if (!enabled) return;
4942
6495
  const intervalId = setInterval(() => {
4943
6496
  const updates = [];
@@ -5040,6 +6593,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
5040
6593
  useSelection,
5041
6594
  useSimulation,
5042
6595
  useTelemetry,
6596
+ useTelemetryStatus,
5043
6597
  useTheme,
5044
6598
  useViewBox,
5045
6599
  validateDiagram