@procaaso/alphinity-ui-components 1.0.3 → 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 },
2045
+ ports: [
2046
+ { id: "top-inlet", x: 47, y: 0, type: "control", direction: "in" },
2047
+ { id: "bottom-outlet", x: 0, y: 42, type: "control", direction: "out" }
2048
+ ],
2049
+ svgContent: `
2050
+ <g>
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 },
1903
2172
  ports: [
1904
- { id: "signal", x: 20, y: 40, type: "signal", direction: "bi" }
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" }
1905
2175
  ],
1906
2176
  svgContent: `
1907
2177
  <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"/>
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>
1914
2907
  </g>
1915
2908
  `,
1916
2909
  metadata: {
1917
- description: "Generic instrument indicator",
1918
- tags: ["instrument", "indicator", "measurement"]
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>
2949
+ </g>
2950
+ `,
2951
+ metadata: {
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,35 @@ function useViewBox(options) {
2438
3657
  };
2439
3658
  });
2440
3659
  }, [initialViewBox, maxZoom]);
2441
- (0, import_react8.useEffect)(() => {
3660
+ const fitToContent = (0, import_react9.useCallback)((bounds, padding = 50) => {
3661
+ const contentWidth = bounds.maxX - bounds.minX;
3662
+ const contentHeight = bounds.maxY - bounds.minY;
3663
+ if (contentWidth === 0 || contentHeight === 0) {
3664
+ return;
3665
+ }
3666
+ const paddedWidth = contentWidth + padding * 2;
3667
+ const paddedHeight = contentHeight + padding * 2;
3668
+ const aspectRatio = initialViewBox.width / initialViewBox.height;
3669
+ const contentAspectRatio = paddedWidth / paddedHeight;
3670
+ let newWidth;
3671
+ let newHeight;
3672
+ if (contentAspectRatio > aspectRatio) {
3673
+ newWidth = paddedWidth;
3674
+ newHeight = paddedWidth / aspectRatio;
3675
+ } else {
3676
+ newHeight = paddedHeight;
3677
+ newWidth = paddedHeight * aspectRatio;
3678
+ }
3679
+ const centerX = bounds.minX + contentWidth / 2;
3680
+ const centerY = bounds.minY + contentHeight / 2;
3681
+ setViewBox({
3682
+ x: centerX - newWidth / 2,
3683
+ y: centerY - newHeight / 2,
3684
+ width: newWidth,
3685
+ height: newHeight
3686
+ });
3687
+ }, [initialViewBox]);
3688
+ (0, import_react9.useEffect)(() => {
2442
3689
  if (!enableZoom || !svgRef.current) return;
2443
3690
  const svg = svgRef.current;
2444
3691
  const handleWheel = (e) => {
@@ -2471,7 +3718,7 @@ function useViewBox(options) {
2471
3718
  svg.addEventListener("wheel", handleWheel, { passive: false });
2472
3719
  return () => svg.removeEventListener("wheel", handleWheel);
2473
3720
  }, [enableZoom, zoomSensitivity, minZoom, initialViewBox]);
2474
- (0, import_react8.useEffect)(() => {
3721
+ (0, import_react9.useEffect)(() => {
2475
3722
  if (!enablePan || !svgRef.current) return;
2476
3723
  const svg = svgRef.current;
2477
3724
  const handleMouseDown = (e) => {
@@ -2547,7 +3794,7 @@ function useViewBox(options) {
2547
3794
  window.removeEventListener("keyup", handleKeyUp);
2548
3795
  };
2549
3796
  }, [enablePan, allowLeftClickPan]);
2550
- (0, import_react8.useEffect)(() => {
3797
+ (0, import_react9.useEffect)(() => {
2551
3798
  if (!svgRef.current) return;
2552
3799
  if (!enablePan && !enableZoom) return;
2553
3800
  const svg = svgRef.current;
@@ -2638,7 +3885,7 @@ function useViewBox(options) {
2638
3885
  svg.removeEventListener("touchend", handleTouchEnd);
2639
3886
  };
2640
3887
  }, [enablePan, enableZoom, minZoom, initialViewBox]);
2641
- const screenToWorld = (0, import_react8.useCallback)(
3888
+ const screenToWorld = (0, import_react9.useCallback)(
2642
3889
  (screenX, screenY) => {
2643
3890
  const svg = svgRef.current;
2644
3891
  if (!svg) return { x: screenX, y: screenY };
@@ -2669,21 +3916,23 @@ function useViewBox(options) {
2669
3916
  resetViewBox,
2670
3917
  zoomIn,
2671
3918
  zoomOut,
3919
+ fitToContent,
3920
+ setViewBox,
2672
3921
  screenToWorld,
2673
3922
  svgRef
2674
3923
  };
2675
3924
  }
2676
3925
 
2677
3926
  // src/diagram/hooks/useSelection.ts
2678
- var import_react9 = require("react");
3927
+ var import_react10 = require("react");
2679
3928
  function useSelection(options = {}) {
2680
3929
  const {
2681
3930
  multiSelect = true,
2682
3931
  initialSelection = { selectedNodeIds: /* @__PURE__ */ new Set(), selectedPipeIds: /* @__PURE__ */ new Set() },
2683
3932
  onSelectionChange
2684
3933
  } = options;
2685
- const [selection, setSelection] = (0, import_react9.useState)(initialSelection);
2686
- const notifyChange = (0, import_react9.useCallback)(
3934
+ const [selection, setSelection] = (0, import_react10.useState)(initialSelection);
3935
+ const notifyChange = (0, import_react10.useCallback)(
2687
3936
  (newSelection) => {
2688
3937
  if (onSelectionChange) {
2689
3938
  onSelectionChange(newSelection);
@@ -2691,15 +3940,15 @@ function useSelection(options = {}) {
2691
3940
  },
2692
3941
  [onSelectionChange]
2693
3942
  );
2694
- const isNodeSelected = (0, import_react9.useCallback)(
3943
+ const isNodeSelected = (0, import_react10.useCallback)(
2695
3944
  (nodeId) => selection.selectedNodeIds.has(nodeId),
2696
3945
  [selection.selectedNodeIds]
2697
3946
  );
2698
- const isPipeSelected = (0, import_react9.useCallback)(
3947
+ const isPipeSelected = (0, import_react10.useCallback)(
2699
3948
  (pipeId) => selection.selectedPipeIds.has(pipeId),
2700
3949
  [selection.selectedPipeIds]
2701
3950
  );
2702
- const selectNode = (0, import_react9.useCallback)(
3951
+ const selectNode = (0, import_react10.useCallback)(
2703
3952
  (nodeId, isMultiSelect = false) => {
2704
3953
  setSelection((prev) => {
2705
3954
  const newNodeIds = new Set(isMultiSelect && multiSelect ? prev.selectedNodeIds : []);
@@ -2718,7 +3967,7 @@ function useSelection(options = {}) {
2718
3967
  },
2719
3968
  [multiSelect, notifyChange]
2720
3969
  );
2721
- const selectPipe = (0, import_react9.useCallback)(
3970
+ const selectPipe = (0, import_react10.useCallback)(
2722
3971
  (pipeId, isMultiSelect = false) => {
2723
3972
  setSelection((prev) => {
2724
3973
  const newPipeIds = new Set(isMultiSelect && multiSelect ? prev.selectedPipeIds : []);
@@ -2737,7 +3986,7 @@ function useSelection(options = {}) {
2737
3986
  },
2738
3987
  [multiSelect, notifyChange]
2739
3988
  );
2740
- const clearSelection = (0, import_react9.useCallback)(() => {
3989
+ const clearSelection = (0, import_react10.useCallback)(() => {
2741
3990
  const newSelection = {
2742
3991
  selectedNodeIds: /* @__PURE__ */ new Set(),
2743
3992
  selectedPipeIds: /* @__PURE__ */ new Set()
@@ -2745,7 +3994,7 @@ function useSelection(options = {}) {
2745
3994
  setSelection(newSelection);
2746
3995
  notifyChange(newSelection);
2747
3996
  }, [notifyChange]);
2748
- const selectNodes = (0, import_react9.useCallback)(
3997
+ const selectNodes = (0, import_react10.useCallback)(
2749
3998
  (nodeIds) => {
2750
3999
  const newSelection = {
2751
4000
  selectedNodeIds: new Set(nodeIds),
@@ -2756,7 +4005,7 @@ function useSelection(options = {}) {
2756
4005
  },
2757
4006
  [notifyChange]
2758
4007
  );
2759
- const selectPipes = (0, import_react9.useCallback)(
4008
+ const selectPipes = (0, import_react10.useCallback)(
2760
4009
  (pipeIds) => {
2761
4010
  const newSelection = {
2762
4011
  selectedNodeIds: /* @__PURE__ */ new Set(),
@@ -2780,23 +4029,23 @@ function useSelection(options = {}) {
2780
4029
  }
2781
4030
 
2782
4031
  // src/diagram/hooks/useTelemetry.ts
2783
- var import_react10 = require("react");
4032
+ var import_react11 = require("react");
2784
4033
  function useTelemetry(options = {}) {
2785
4034
  const {
2786
4035
  initialBindings = [],
2787
4036
  onTelemetryChange,
2788
4037
  refreshInterval = 0
2789
4038
  } = options;
2790
- const [telemetry, setTelemetry] = (0, import_react10.useState)(() => {
4039
+ const [telemetry, setTelemetry] = (0, import_react11.useState)(() => {
2791
4040
  const map = /* @__PURE__ */ new Map();
2792
4041
  initialBindings.forEach((binding) => {
2793
4042
  map.set(binding.nodeId, binding);
2794
4043
  });
2795
4044
  return map;
2796
4045
  });
2797
- const telemetryRef = (0, import_react10.useRef)(telemetry);
4046
+ const telemetryRef = (0, import_react11.useRef)(telemetry);
2798
4047
  telemetryRef.current = telemetry;
2799
- const notifyChange = (0, import_react10.useCallback)(
4048
+ const notifyChange = (0, import_react11.useCallback)(
2800
4049
  (newTelemetry) => {
2801
4050
  if (onTelemetryChange) {
2802
4051
  onTelemetryChange(newTelemetry);
@@ -2804,7 +4053,7 @@ function useTelemetry(options = {}) {
2804
4053
  },
2805
4054
  [onTelemetryChange]
2806
4055
  );
2807
- const updateTelemetry = (0, import_react10.useCallback)(
4056
+ const updateTelemetry = (0, import_react11.useCallback)(
2808
4057
  (nodeId, value) => {
2809
4058
  setTelemetry((prev) => {
2810
4059
  const newMap = new Map(prev);
@@ -2834,7 +4083,7 @@ function useTelemetry(options = {}) {
2834
4083
  },
2835
4084
  [notifyChange]
2836
4085
  );
2837
- const updateTelemetryBatch = (0, import_react10.useCallback)(
4086
+ const updateTelemetryBatch = (0, import_react11.useCallback)(
2838
4087
  (updates) => {
2839
4088
  setTelemetry((prev) => {
2840
4089
  const newMap = new Map(prev);
@@ -2888,16 +4137,16 @@ function useTelemetry(options = {}) {
2888
4137
  },
2889
4138
  [notifyChange]
2890
4139
  );
2891
- const getTelemetry = (0, import_react10.useCallback)(
4140
+ const getTelemetry = (0, import_react11.useCallback)(
2892
4141
  (nodeId) => telemetryRef.current.get(nodeId),
2893
4142
  []
2894
4143
  );
2895
- const clearTelemetry = (0, import_react10.useCallback)(() => {
4144
+ const clearTelemetry = (0, import_react11.useCallback)(() => {
2896
4145
  const newMap = /* @__PURE__ */ new Map();
2897
4146
  setTelemetry(newMap);
2898
4147
  notifyChange(newMap);
2899
4148
  }, [notifyChange]);
2900
- const setBindings = (0, import_react10.useCallback)(
4149
+ const setBindings = (0, import_react11.useCallback)(
2901
4150
  (bindings) => {
2902
4151
  const newMap = /* @__PURE__ */ new Map();
2903
4152
  bindings.forEach((binding) => {
@@ -2908,7 +4157,7 @@ function useTelemetry(options = {}) {
2908
4157
  },
2909
4158
  [notifyChange]
2910
4159
  );
2911
- (0, import_react10.useEffect)(() => {
4160
+ (0, import_react11.useEffect)(() => {
2912
4161
  if (refreshInterval <= 0) return;
2913
4162
  const intervalId = setInterval(() => {
2914
4163
  notifyChange(telemetryRef.current);
@@ -2925,8 +4174,50 @@ function useTelemetry(options = {}) {
2925
4174
  };
2926
4175
  }
2927
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
+
2928
4219
  // src/diagram/hooks/useDrag.ts
2929
- var import_react11 = require("react");
4220
+ var import_react13 = require("react");
2930
4221
  function useDrag(options = {}) {
2931
4222
  const {
2932
4223
  onDragStart,
@@ -2936,23 +4227,23 @@ function useDrag(options = {}) {
2936
4227
  dragThreshold = 3,
2937
4228
  screenToWorld = (x, y) => ({ x, y })
2938
4229
  } = options;
2939
- const [dragState, setDragState] = (0, import_react11.useState)({
4230
+ const [dragState, setDragState] = (0, import_react13.useState)({
2940
4231
  isDragging: false,
2941
4232
  startPosition: null,
2942
4233
  offset: { x: 0, y: 0 },
2943
4234
  draggedId: null
2944
4235
  });
2945
- const [isListening, setIsListening] = (0, import_react11.useState)(false);
2946
- const dragStartRef = (0, import_react11.useRef)(null);
2947
- const hasMovedRef = (0, import_react11.useRef)(false);
2948
- 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)(
2949
4240
  (value) => {
2950
4241
  if (snapToGrid <= 0) return value;
2951
4242
  return Math.round(value / snapToGrid) * snapToGrid;
2952
4243
  },
2953
4244
  [snapToGrid]
2954
4245
  );
2955
- const handleMouseMove = (0, import_react11.useCallback)(
4246
+ const handleMouseMove = (0, import_react13.useCallback)(
2956
4247
  (event) => {
2957
4248
  if (!dragStartRef.current) return;
2958
4249
  const { id, screenStart: _screenStart, itemPosition, clickOffset } = dragStartRef.current;
@@ -2993,7 +4284,7 @@ function useDrag(options = {}) {
2993
4284
  },
2994
4285
  [screenToWorld, dragThreshold, applySnap, onDragStart, onDragMove]
2995
4286
  );
2996
- const handleMouseUp = (0, import_react11.useCallback)(
4287
+ const handleMouseUp = (0, import_react13.useCallback)(
2997
4288
  (event) => {
2998
4289
  if (!dragStartRef.current || !hasMovedRef.current) {
2999
4290
  dragStartRef.current = null;
@@ -3030,7 +4321,7 @@ function useDrag(options = {}) {
3030
4321
  },
3031
4322
  [screenToWorld, applySnap, onDragEnd]
3032
4323
  );
3033
- const handleTouchMove = (0, import_react11.useCallback)(
4324
+ const handleTouchMove = (0, import_react13.useCallback)(
3034
4325
  (event) => {
3035
4326
  if (!dragStartRef.current || event.touches.length !== 1) return;
3036
4327
  const touch = event.touches[0];
@@ -3073,7 +4364,7 @@ function useDrag(options = {}) {
3073
4364
  },
3074
4365
  [screenToWorld, dragThreshold, applySnap, onDragStart, onDragMove]
3075
4366
  );
3076
- const handleTouchEnd = (0, import_react11.useCallback)(
4367
+ const handleTouchEnd = (0, import_react13.useCallback)(
3077
4368
  (event) => {
3078
4369
  if (!dragStartRef.current || !hasMovedRef.current) {
3079
4370
  dragStartRef.current = null;
@@ -3111,7 +4402,7 @@ function useDrag(options = {}) {
3111
4402
  },
3112
4403
  [screenToWorld, applySnap, onDragEnd]
3113
4404
  );
3114
- (0, import_react11.useEffect)(() => {
4405
+ (0, import_react13.useEffect)(() => {
3115
4406
  if (!isListening) return;
3116
4407
  document.addEventListener("mousemove", handleMouseMove);
3117
4408
  document.addEventListener("mouseup", handleMouseUp);
@@ -3126,7 +4417,7 @@ function useDrag(options = {}) {
3126
4417
  document.removeEventListener("touchcancel", handleTouchEnd);
3127
4418
  };
3128
4419
  }, [isListening, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
3129
- const startDrag = (0, import_react11.useCallback)(
4420
+ const startDrag = (0, import_react13.useCallback)(
3130
4421
  (id, event, itemPosition) => {
3131
4422
  event.stopPropagation();
3132
4423
  let clientX;
@@ -3156,7 +4447,7 @@ function useDrag(options = {}) {
3156
4447
  },
3157
4448
  [screenToWorld]
3158
4449
  );
3159
- const cancelDrag = (0, import_react11.useCallback)(() => {
4450
+ const cancelDrag = (0, import_react13.useCallback)(() => {
3160
4451
  dragStartRef.current = null;
3161
4452
  hasMovedRef.current = false;
3162
4453
  setIsListening(false);
@@ -3167,7 +4458,7 @@ function useDrag(options = {}) {
3167
4458
  draggedId: null
3168
4459
  });
3169
4460
  }, []);
3170
- const isDraggingItem = (0, import_react11.useCallback)(
4461
+ const isDraggingItem = (0, import_react13.useCallback)(
3171
4462
  (id) => dragState.isDragging && dragState.draggedId === id,
3172
4463
  [dragState.isDragging, dragState.draggedId]
3173
4464
  );
@@ -3180,7 +4471,7 @@ function useDrag(options = {}) {
3180
4471
  }
3181
4472
 
3182
4473
  // src/diagram/hooks/useKeyboardShortcuts.ts
3183
- var import_react12 = require("react");
4474
+ var import_react14 = require("react");
3184
4475
  function useKeyboardShortcuts(options) {
3185
4476
  const {
3186
4477
  onDelete,
@@ -3191,7 +4482,7 @@ function useKeyboardShortcuts(options) {
3191
4482
  onSelectAll,
3192
4483
  enabled = true
3193
4484
  } = options;
3194
- (0, import_react12.useEffect)(() => {
4485
+ (0, import_react14.useEffect)(() => {
3195
4486
  if (!enabled) return;
3196
4487
  const handleKeyDown = (event) => {
3197
4488
  const target = event.target;
@@ -3300,7 +4591,7 @@ function nodeHasPort(node, portId) {
3300
4591
  }
3301
4592
 
3302
4593
  // src/components/PIDCanvas/PIDCanvas.tsx
3303
- var import_jsx_runtime16 = require("react/jsx-runtime");
4594
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3304
4595
  function PIDCanvas({
3305
4596
  diagram,
3306
4597
  className = "",
@@ -3323,36 +4614,291 @@ function PIDCanvas({
3323
4614
  onPaste,
3324
4615
  onSymbolDrop,
3325
4616
  onPipeCreated,
4617
+ onMounted,
3326
4618
  telemetry,
3327
4619
  ...props
3328
4620
  }) {
3329
- const [localDiagram, setLocalDiagram] = (0, import_react13.useState)(diagram);
3330
- const [dragOverPosition, setDragOverPosition] = (0, import_react13.useState)(null);
3331
- const [isConnecting, setIsConnecting] = (0, import_react13.useState)(false);
3332
- const [connectionSource, setConnectionSource] = (0, import_react13.useState)(null);
3333
- const [connectionSourcePort, setConnectionSourcePort] = (0, import_react13.useState)(null);
3334
- const [connectionCursor, setConnectionCursor] = (0, import_react13.useState)(null);
3335
- const [hoveredNode, setHoveredNode] = (0, import_react13.useState)(null);
3336
- const [hoveredPort, setHoveredPort] = (0, import_react13.useState)(null);
3337
- (0, import_react13.useEffect)(() => {
3338
- 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;
3339
4645
  }, [diagram]);
3340
4646
  const {
3341
4647
  viewBox: controlledViewBox,
3342
4648
  svgRef,
3343
- screenToWorld
4649
+ screenToWorld,
4650
+ fitToContent: fitToContentViewBox,
4651
+ setViewBox
3344
4652
  } = useViewBox({
3345
4653
  initialViewBox: diagram.viewBox,
3346
4654
  enablePan: features.pan ?? true,
3347
4655
  enableZoom: features.zoom ?? true
3348
4656
  });
4657
+ const calculateContentBounds = (0, import_react15.useCallback)(() => {
4658
+ const nodes2 = localDiagram.nodes.filter((n) => n.visible !== false);
4659
+ if (nodes2.length === 0) {
4660
+ return null;
4661
+ }
4662
+ let minX = Infinity;
4663
+ let minY = Infinity;
4664
+ let maxX = -Infinity;
4665
+ let maxY = -Infinity;
4666
+ nodes2.forEach((node) => {
4667
+ const symbol = getSymbolDefinition(node.symbolId);
4668
+ if (!symbol) return;
4669
+ const nodeMinX = node.transform.x;
4670
+ const nodeMinY = node.transform.y;
4671
+ const nodeMaxX = node.transform.x + symbol.viewBox.width;
4672
+ const nodeMaxY = node.transform.y + symbol.viewBox.height;
4673
+ minX = Math.min(minX, nodeMinX);
4674
+ minY = Math.min(minY, nodeMinY);
4675
+ maxX = Math.max(maxX, nodeMaxX);
4676
+ maxY = Math.max(maxY, nodeMaxY);
4677
+ });
4678
+ return { minX, minY, maxX, maxY };
4679
+ }, [localDiagram.nodes]);
4680
+ (0, import_react15.useEffect)(() => {
4681
+ if (onMounted) {
4682
+ const controls = {
4683
+ fitToContent: (padding = 50) => {
4684
+ const bounds = calculateContentBounds();
4685
+ if (bounds) {
4686
+ fitToContentViewBox(bounds, padding);
4687
+ }
4688
+ },
4689
+ setZoom: (zoomLevel) => {
4690
+ const centerX = controlledViewBox.x + controlledViewBox.width / 2;
4691
+ const centerY = controlledViewBox.y + controlledViewBox.height / 2;
4692
+ const newWidth = diagram.viewBox.width / zoomLevel;
4693
+ const newHeight = diagram.viewBox.height / zoomLevel;
4694
+ setViewBox({
4695
+ x: centerX - newWidth / 2,
4696
+ y: centerY - newHeight / 2,
4697
+ width: newWidth,
4698
+ height: newHeight
4699
+ });
4700
+ },
4701
+ getZoom: () => {
4702
+ return diagram.viewBox.width / controlledViewBox.width;
4703
+ }
4704
+ };
4705
+ onMounted(controls);
4706
+ }
4707
+ }, [
4708
+ onMounted,
4709
+ calculateContentBounds,
4710
+ fitToContentViewBox,
4711
+ setViewBox,
4712
+ controlledViewBox,
4713
+ diagram.viewBox
4714
+ ]);
3349
4715
  const { selection, selectNode, selectPipe, clearSelection } = useSelection({
3350
4716
  multiSelect: features.multiSelect ?? true,
3351
4717
  onSelectionChange
3352
4718
  });
3353
4719
  const dragEnabled = features.dragNodes ?? false;
3354
4720
  const selectionEnabled = features.selection ?? false;
3355
- 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)(
3356
4902
  (nodeId, offset, finalPosition) => {
3357
4903
  const dragDistance = Math.sqrt(offset.x ** 2 + offset.y ** 2);
3358
4904
  if (dragDistance < 2) {
@@ -3361,6 +4907,7 @@ function PIDCanvas({
3361
4907
  }
3362
4908
  return;
3363
4909
  }
4910
+ pushToHistory(localDiagram);
3364
4911
  const updatedNodes = localDiagram.nodes.map(
3365
4912
  (node) => node.id === nodeId ? {
3366
4913
  ...node,
@@ -3398,11 +4945,13 @@ function PIDCanvas({
3398
4945
  nodes: updatedNodes,
3399
4946
  pipes: updatedPipes
3400
4947
  };
4948
+ isInternalChange.current = true;
4949
+ lastInternalDiagram.current = updatedDiagram;
3401
4950
  setLocalDiagram(updatedDiagram);
3402
4951
  onDiagramChange?.(updatedDiagram);
3403
4952
  onNodeMove?.(nodeId, finalPosition.x, finalPosition.y);
3404
4953
  },
3405
- [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode]
4954
+ [localDiagram, onDiagramChange, onNodeMove, selectionEnabled, selectNode, pushToHistory]
3406
4955
  );
3407
4956
  const { dragState, startDrag } = useDrag({
3408
4957
  snapToGrid: features.snapToGrid ?? 0,
@@ -3413,6 +4962,7 @@ function PIDCanvas({
3413
4962
  snapToGrid: features.snapToGrid ?? 0,
3414
4963
  screenToWorld,
3415
4964
  onDragEnd: (pipeId, _offset, finalPosition) => {
4965
+ pushToHistory(localDiagram);
3416
4966
  const [actualPipeId, pointIndex] = pipeId.split(":");
3417
4967
  const idx = parseInt(pointIndex, 10);
3418
4968
  const updatedPipes = localDiagram.pipes.map((pipe) => {
@@ -3423,11 +4973,13 @@ function PIDCanvas({
3423
4973
  return { ...pipe, routePoints: updatedRoutePoints };
3424
4974
  });
3425
4975
  const updatedDiagram = { ...localDiagram, pipes: updatedPipes };
4976
+ isInternalChange.current = true;
4977
+ lastInternalDiagram.current = updatedDiagram;
3426
4978
  setLocalDiagram(updatedDiagram);
3427
4979
  onDiagramChange?.(updatedDiagram);
3428
4980
  }
3429
4981
  });
3430
- const handleNodeDragStart = (0, import_react13.useCallback)(
4982
+ const handleNodeDragStart = (0, import_react15.useCallback)(
3431
4983
  (nodeId, event) => {
3432
4984
  const node = localDiagram.nodes.find((n) => n.id === nodeId);
3433
4985
  if (!node) return;
@@ -3435,7 +4987,16 @@ function PIDCanvas({
3435
4987
  },
3436
4988
  [localDiagram.nodes, startDrag]
3437
4989
  );
3438
- 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)(
3439
5000
  (pipeId, pointIndex, event) => {
3440
5001
  const pipe = localDiagram.pipes.find((p) => p.id === pipeId);
3441
5002
  if (!pipe || pointIndex >= pipe.routePoints.length) return;
@@ -3444,8 +5005,9 @@ function PIDCanvas({
3444
5005
  },
3445
5006
  [localDiagram.pipes, startWaypointDrag]
3446
5007
  );
3447
- const handlePipeSegmentClick = (0, import_react13.useCallback)(
5008
+ const handlePipeSegmentClick = (0, import_react15.useCallback)(
3448
5009
  (pipeId, position, segmentIndex) => {
5010
+ pushToHistory(localDiagram);
3449
5011
  const updatedPipes = localDiagram.pipes.map((pipe) => {
3450
5012
  if (pipe.id !== pipeId) return pipe;
3451
5013
  const updatedRoutePoints = [
@@ -3459,13 +5021,14 @@ function PIDCanvas({
3459
5021
  setLocalDiagram(updatedDiagram);
3460
5022
  onDiagramChange?.(updatedDiagram);
3461
5023
  },
3462
- [localDiagram, onDiagramChange]
5024
+ [localDiagram, onDiagramChange, pushToHistory]
3463
5025
  );
3464
5026
  const telemetryEnabled = features.telemetry ?? false;
3465
- const handleDelete = (0, import_react13.useCallback)(() => {
5027
+ const handleDelete = (0, import_react15.useCallback)(() => {
3466
5028
  if (!selectionEnabled) return;
3467
5029
  const { selectedNodeIds, selectedPipeIds } = selection;
3468
5030
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
5031
+ pushToHistory(localDiagram);
3469
5032
  const updatedNodes = localDiagram.nodes.filter((node) => !selectedNodeIds.has(node.id));
3470
5033
  const updatedPipes = localDiagram.pipes.filter(
3471
5034
  (pipe) => !selectedPipeIds.has(pipe.id) && !selectedNodeIds.has(pipe.fromNodeId) && !selectedNodeIds.has(pipe.toNodeId)
@@ -3475,18 +5038,28 @@ function PIDCanvas({
3475
5038
  nodes: updatedNodes,
3476
5039
  pipes: updatedPipes
3477
5040
  };
5041
+ isInternalChange.current = true;
5042
+ lastInternalDiagram.current = updatedDiagram;
3478
5043
  setLocalDiagram(updatedDiagram);
3479
5044
  clearSelection();
3480
5045
  onDiagramChange?.(updatedDiagram);
3481
5046
  onDelete?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
3482
- }, [selection, localDiagram, selectionEnabled, clearSelection, onDiagramChange, onDelete]);
3483
- 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)(() => {
3484
5057
  if (!selectionEnabled) return;
3485
5058
  const { selectedNodeIds, selectedPipeIds } = selection;
3486
5059
  if (selectedNodeIds.size === 0 && selectedPipeIds.size === 0) return;
3487
5060
  onItemsCopied?.(Array.from(selectedNodeIds), Array.from(selectedPipeIds));
3488
5061
  }, [selection, selectionEnabled, onItemsCopied]);
3489
- const handlePaste = (0, import_react13.useCallback)(() => {
5062
+ const handlePaste = (0, import_react15.useCallback)(() => {
3490
5063
  if (!selectionEnabled) return;
3491
5064
  onPaste?.();
3492
5065
  }, [selectionEnabled, onPaste]);
@@ -3536,12 +5109,17 @@ function PIDCanvas({
3536
5109
  }
3537
5110
  onPipeClick?.(pipeId);
3538
5111
  };
3539
- const handleCanvasClick = () => {
5112
+ const handleCanvasClick = (e) => {
5113
+ const target = e.target;
5114
+ if (target.closest(".pipe-route-point")) {
5115
+ return;
5116
+ }
3540
5117
  if (selectionEnabled) {
3541
5118
  clearSelection();
3542
5119
  }
5120
+ setSelectedWaypoint(null);
3543
5121
  };
3544
- const handleDragOver = (0, import_react13.useCallback)(
5122
+ const handleDragOver = (0, import_react15.useCallback)(
3545
5123
  (event) => {
3546
5124
  if (!features.allowSymbolDrop) return;
3547
5125
  event.preventDefault();
@@ -3550,9 +5128,9 @@ function PIDCanvas({
3550
5128
  const worldPos = screenToWorld(event.clientX, event.clientY);
3551
5129
  setDragOverPosition(worldPos);
3552
5130
  },
3553
- [features.allowSymbolDrop, screenToWorld]
5131
+ [features.allowSymbolDrop, screenToWorld, svgRef]
3554
5132
  );
3555
- const handleDrop = (0, import_react13.useCallback)(
5133
+ const handleDrop = (0, import_react15.useCallback)(
3556
5134
  (event) => {
3557
5135
  if (!features.allowSymbolDrop) return;
3558
5136
  event.preventDefault();
@@ -3563,12 +5141,12 @@ function PIDCanvas({
3563
5141
  const worldPos = screenToWorld(event.clientX, event.clientY);
3564
5142
  onSymbolDrop?.(symbolId, worldPos);
3565
5143
  },
3566
- [features.allowSymbolDrop, screenToWorld, onSymbolDrop]
5144
+ [features.allowSymbolDrop, screenToWorld, onSymbolDrop, svgRef]
3567
5145
  );
3568
- const handleDragLeave = (0, import_react13.useCallback)(() => {
5146
+ const handleDragLeave = (0, import_react15.useCallback)(() => {
3569
5147
  setDragOverPosition(null);
3570
5148
  }, []);
3571
- const handleMouseMove = (0, import_react13.useCallback)(
5149
+ const handleMouseMove = (0, import_react15.useCallback)(
3572
5150
  (event) => {
3573
5151
  const connectionModeEnabled = features.connectionMode ?? false;
3574
5152
  if (connectionModeEnabled) {
@@ -3587,7 +5165,7 @@ function PIDCanvas({
3587
5165
  const distance = Math.sqrt(
3588
5166
  (worldPos.x - portPos.x) ** 2 + (worldPos.y - portPos.y) ** 2
3589
5167
  );
3590
- if (distance < 10) {
5168
+ if (distance < 15) {
3591
5169
  foundPort = port.id;
3592
5170
  foundNode = node.id;
3593
5171
  break;
@@ -3604,9 +5182,11 @@ function PIDCanvas({
3604
5182
  const { name } = diagram;
3605
5183
  const viewBox = controlledViewBox;
3606
5184
  const displayDiagram = localDiagram;
3607
- 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)(
3608
5187
  "div",
3609
5188
  {
5189
+ ref: containerRef,
3610
5190
  className: `pid-canvas ${className}`.trim(),
3611
5191
  style: {
3612
5192
  width: "100%",
@@ -3617,223 +5197,287 @@ function PIDCanvas({
3617
5197
  ...style
3618
5198
  },
3619
5199
  ...props,
3620
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3621
- "svg",
3622
- {
3623
- ref: svgRef,
3624
- width: "100%",
3625
- height: "100%",
3626
- viewBox: `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`,
3627
- preserveAspectRatio: "xMidYMid meet",
3628
- style: {
3629
- display: "block"
3630
- },
3631
- "aria-label": `P&ID diagram: ${name}`,
3632
- role: "img",
3633
- onClick: handleCanvasClick,
3634
- onMouseMove: handleMouseMove,
3635
- onDragOver: handleDragOver,
3636
- onDrop: handleDrop,
3637
- onDragLeave: handleDragLeave,
3638
- children: [
3639
- features.showGrid !== false ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3640
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("defs", { children: [
3641
- /* @__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" }) }),
3642
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("pattern", { id: "grid-major", width: "25", height: "25", patternUnits: "userSpaceOnUse", children: [
3643
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("rect", { width: "25", height: "25", fill: "url(#grid-minor)" }),
3644
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M 25 0 L 0 0 0 25", fill: "none", stroke: "#d1d5db", strokeWidth: "1" })
3645
- ] })
3646
- ] }),
3647
- /* @__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)(
3648
5239
  "rect",
3649
5240
  {
3650
5241
  x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3651
5242
  y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3652
5243
  width: Math.ceil(viewBox.width * 3 / 25) * 25,
3653
5244
  height: Math.ceil(viewBox.height * 3 / 25) * 25,
3654
- fill: "url(#grid-major)"
5245
+ fill: features.backgroundColor ?? "#ffffff"
3655
5246
  }
3656
- )
3657
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3658
- "rect",
3659
- {
3660
- x: Math.floor(viewBox.x / 25) * 25 - viewBox.width,
3661
- y: Math.floor(viewBox.y / 25) * 25 - viewBox.height,
3662
- width: Math.ceil(viewBox.width * 3 / 25) * 25,
3663
- height: Math.ceil(viewBox.height * 3 / 25) * 25,
3664
- fill: features.backgroundColor ?? "#ffffff"
3665
- }
3666
- ),
3667
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3668
- PipeRenderer,
3669
- {
3670
- pipes: displayDiagram.pipes,
3671
- selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
3672
- onPipeClick: selectionEnabled ? handlePipeClick : void 0,
3673
- enableWaypointEditing: false,
3674
- onWaypointDragStart: void 0,
3675
- editingPipeId: null,
3676
- draggingWaypointIndex: null,
3677
- waypointDragOffset: void 0,
3678
- onPipeSegmentClick: void 0
3679
- }
3680
- ),
3681
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3682
- NodeRenderer,
3683
- {
3684
- nodes: displayDiagram.nodes,
3685
- selectedNodeIds: selectionEnabled ? selection.selectedNodeIds : void 0,
3686
- onNodeClick: selectionEnabled || features.connectionMode ? handleNodeClick : void 0,
3687
- onNodeDoubleClick: selectionEnabled ? handleNodeDoubleClick : void 0,
3688
- enableDrag: dragEnabled && !isConnecting,
3689
- onNodeDragStart: dragEnabled && !isConnecting ? handleNodeDragStart : void 0,
3690
- draggingNodeId: dragState.draggedId,
3691
- dragOffset: dragState.offset
3692
- }
3693
- ),
3694
- (features.editPipeRoutes ?? false) && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3695
- PipeRenderer,
3696
- {
3697
- pipes: displayDiagram.pipes,
3698
- selectedPipeIds: selectionEnabled ? selection.selectedPipeIds : void 0,
3699
- onPipeClick: void 0,
3700
- enableWaypointEditing: true,
3701
- onWaypointDragStart: handleWaypointDragStart,
3702
- editingPipeId: waypointDragState.draggedId?.split(":")[0] || null,
3703
- draggingWaypointIndex: waypointDragState.draggedId ? parseInt(waypointDragState.draggedId.split(":")[1], 10) : null,
3704
- waypointDragOffset: waypointDragState.offset,
3705
- onPipeSegmentClick: handlePipeSegmentClick
3706
- }
3707
- ),
3708
- telemetryEnabled && telemetry && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { id: "layer-telemetry", children: displayDiagram.nodes.map((node) => {
3709
- const binding = telemetry.get(node.id);
3710
- if (!binding) return null;
3711
- const symbol = getSymbolDefinition(node.symbolId);
3712
- if (!symbol) return null;
3713
- const overlayY = node.transform.y - symbol.viewBox.height / 2 - 10;
3714
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3715
- DataOverlay,
5247
+ ),
5248
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5249
+ PipeRenderer,
3716
5250
  {
3717
- nodeId: node.id,
3718
- x: node.transform.x,
3719
- y: overlayY,
3720
- telemetry: binding
3721
- },
3722
- `telemetry-${node.id}`
3723
- );
3724
- }) }),
3725
- dragOverPosition && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3726
- "circle",
3727
- {
3728
- cx: dragOverPosition.x,
3729
- cy: dragOverPosition.y,
3730
- r: 20,
3731
- fill: "#3b82f6",
3732
- opacity: 0.3,
3733
- stroke: "#3b82f6",
3734
- strokeWidth: 2,
3735
- pointerEvents: "none"
3736
- }
3737
- ),
3738
- isConnecting && connectionSource && connectionCursor && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { id: "connection-preview", pointerEvents: "none", children: (() => {
3739
- const sourceNode = displayDiagram.nodes.find((n) => n.id === connectionSource);
3740
- if (!sourceNode) return null;
3741
- const symbol = getSymbolDefinition(sourceNode.symbolId);
3742
- let lineStartX = sourceNode.transform.x;
3743
- let lineStartY = sourceNode.transform.y;
3744
- if (connectionSourcePort && symbol?.ports) {
3745
- const port = symbol.ports.find((p) => p.id === connectionSourcePort);
3746
- if (port) {
3747
- const portPos = getPortWorldPosition(sourceNode, connectionSourcePort);
3748
- if (portPos) {
3749
- lineStartX = portPos.x;
3750
- lineStartY = portPos.y;
3751
- }
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
3752
5260
  }
3753
- }
3754
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3755
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3756
- "circle",
3757
- {
3758
- cx: sourceNode.transform.x,
3759
- cy: sourceNode.transform.y,
3760
- r: 30,
3761
- fill: "none",
3762
- stroke: "#10b981",
3763
- strokeWidth: 3,
3764
- opacity: 0.6
3765
- }
3766
- ),
3767
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3768
- "line",
3769
- {
3770
- x1: lineStartX,
3771
- y1: lineStartY,
3772
- x2: connectionCursor.x,
3773
- y2: connectionCursor.y,
3774
- stroke: "#10b981",
3775
- strokeWidth: 2,
3776
- strokeDasharray: "5,5",
3777
- opacity: 0.8
3778
- }
3779
- ),
3780
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3781
- "circle",
3782
- {
3783
- cx: connectionCursor.x,
3784
- cy: connectionCursor.y,
3785
- r: 8,
3786
- fill: "#10b981",
3787
- opacity: 0.6
3788
- }
3789
- )
3790
- ] });
3791
- })() }),
3792
- features.connectionMode && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { id: "port-indicators", pointerEvents: "none", children: displayDiagram.nodes.map((node) => {
3793
- const symbol = getSymbolDefinition(node.symbolId);
3794
- if (!symbol?.ports) return null;
3795
- return symbol.ports.map((port) => {
3796
- const portPos = getPortWorldPosition(node, port.id);
3797
- if (!portPos) return null;
3798
- const isHovered = hoveredPort === port.id && hoveredNode === node.id;
3799
- const isSourcePort = node.id === connectionSource && port.id === connectionSourcePort;
3800
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3801
- "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,
3802
5301
  {
3803
- cx: portPos.x,
3804
- cy: portPos.y,
3805
- r: isHovered ? 8 : 5,
3806
- fill: isSourcePort ? "#10b981" : isHovered ? "#3b82f6" : "#94a3b8",
3807
- stroke: isSourcePort ? "#059669" : isHovered ? "#2563eb" : "#64748b",
3808
- strokeWidth: isHovered || isSourcePort ? 2 : 1,
3809
- opacity: isHovered || isSourcePort ? 1 : 0.6
5302
+ nodeId: node.id,
5303
+ x: overlayX,
5304
+ y: overlayY,
5305
+ telemetry: binding,
5306
+ scale: features.telemetryScale ?? 1
3810
5307
  },
3811
- `${node.id}-${port.id}`
5308
+ `telemetry-${node.id}`
3812
5309
  );
3813
- });
3814
- }) })
3815
- ]
3816
- }
3817
- )
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
+ ]
3818
5454
  }
3819
5455
  );
3820
5456
  }
3821
5457
 
3822
5458
  // src/components/SymbolLibrary/SymbolLibrary.tsx
3823
- var import_react14 = require("react");
3824
- var import_jsx_runtime17 = require("react/jsx-runtime");
5459
+ var import_react16 = require("react");
5460
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3825
5461
  function SymbolLibrary({
3826
5462
  className = "",
3827
5463
  onSymbolDragStart,
3828
5464
  showCategories = true,
3829
5465
  categories = ["Valves", "Pumps", "Tanks", "Instruments", "Displays"]
3830
5466
  }) {
3831
- const [selectedCategory, setSelectedCategory] = (0, import_react14.useState)("all");
3832
- const [searchQuery, setSearchQuery] = (0, import_react14.useState)("");
5467
+ const [selectedCategory, setSelectedCategory] = (0, import_react16.useState)("all");
5468
+ const [searchQuery, setSearchQuery] = (0, import_react16.useState)("");
3833
5469
  const symbolIds = getAvailableSymbols();
3834
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
+ };
3835
5478
  const filteredSymbols = allSymbols.filter((symbol) => {
3836
- const matchesCategory = selectedCategory === "all" || symbol.category === selectedCategory;
5479
+ const actualCategory = categoryMap[selectedCategory] || selectedCategory;
5480
+ const matchesCategory = selectedCategory === "all" || symbol.category === actualCategory;
3837
5481
  const matchesSearch = searchQuery === "" || symbol.name.toLowerCase().includes(searchQuery.toLowerCase()) || symbol.id.toLowerCase().includes(searchQuery.toLowerCase());
3838
5482
  return matchesCategory && matchesSearch;
3839
5483
  });
@@ -3842,9 +5486,9 @@ function SymbolLibrary({
3842
5486
  event.dataTransfer.effectAllowed = "copy";
3843
5487
  onSymbolDragStart?.(symbol.id, event);
3844
5488
  };
3845
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: `symbol-library ${className}`.trim(), style: styles.container, children: [
3846
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h3", { style: styles.title, children: "Symbol Library" }) }),
3847
- /* @__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)(
3848
5492
  "input",
3849
5493
  {
3850
5494
  type: "text",
@@ -3854,8 +5498,8 @@ function SymbolLibrary({
3854
5498
  style: styles.searchInput
3855
5499
  }
3856
5500
  ) }),
3857
- showCategories && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: styles.categories, children: [
3858
- /* @__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)(
3859
5503
  "button",
3860
5504
  {
3861
5505
  onClick: () => setSelectedCategory("all"),
@@ -3866,7 +5510,7 @@ function SymbolLibrary({
3866
5510
  children: "All"
3867
5511
  }
3868
5512
  ),
3869
- categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5513
+ categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3870
5514
  "button",
3871
5515
  {
3872
5516
  onClick: () => setSelectedCategory(category),
@@ -3879,7 +5523,7 @@ function SymbolLibrary({
3879
5523
  category
3880
5524
  ))
3881
5525
  ] }),
3882
- /* @__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)(
3883
5527
  "div",
3884
5528
  {
3885
5529
  draggable: true,
@@ -3887,15 +5531,15 @@ function SymbolLibrary({
3887
5531
  style: styles.symbolCard,
3888
5532
  title: symbol.metadata?.description || symbol.name,
3889
5533
  children: [
3890
- /* @__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)(
3891
5535
  "svg",
3892
5536
  {
3893
5537
  viewBox: `${symbol.viewBox.x} ${symbol.viewBox.y} ${symbol.viewBox.width} ${symbol.viewBox.height}`,
3894
5538
  style: styles.symbolSvg,
3895
- 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 } })
3896
5540
  }
3897
5541
  ) }),
3898
- /* @__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 })
3899
5543
  ]
3900
5544
  },
3901
5545
  symbol.id
@@ -4004,7 +5648,7 @@ var styles = {
4004
5648
  };
4005
5649
 
4006
5650
  // src/components/NodeConfigPanel/NodeConfigPanel.tsx
4007
- var import_jsx_runtime18 = require("react/jsx-runtime");
5651
+ var import_jsx_runtime19 = require("react/jsx-runtime");
4008
5652
  function NodeConfigPanel({
4009
5653
  node,
4010
5654
  binding,
@@ -4023,15 +5667,15 @@ function NodeConfigPanel({
4023
5667
  padding: "20px",
4024
5668
  ...style
4025
5669
  };
4026
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className, style: defaultStyle, children: [
4027
- /* @__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: [
4028
5672
  "Configure: ",
4029
5673
  node.id
4030
5674
  ] }),
4031
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4032
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Node Settings" }),
4033
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label:" }),
4034
- /* @__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)(
4035
5679
  "input",
4036
5680
  {
4037
5681
  type: "text",
@@ -4047,10 +5691,10 @@ function NodeConfigPanel({
4047
5691
  }
4048
5692
  }
4049
5693
  ),
4050
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
4051
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4052
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset X:" }),
4053
- /* @__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)(
4054
5698
  "input",
4055
5699
  {
4056
5700
  type: "number",
@@ -4071,9 +5715,9 @@ function NodeConfigPanel({
4071
5715
  }
4072
5716
  )
4073
5717
  ] }),
4074
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4075
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Offset Y:" }),
4076
- /* @__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)(
4077
5721
  "input",
4078
5722
  {
4079
5723
  type: "number",
@@ -4095,9 +5739,9 @@ function NodeConfigPanel({
4095
5739
  )
4096
5740
  ] })
4097
5741
  ] }),
4098
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4099
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Label Font Size:" }),
4100
- /* @__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)(
4101
5745
  "input",
4102
5746
  {
4103
5747
  type: "number",
@@ -4117,9 +5761,9 @@ function NodeConfigPanel({
4117
5761
  }
4118
5762
  )
4119
5763
  ] }),
4120
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4121
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Rotation (degrees):" }),
4122
- /* @__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)(
4123
5767
  "input",
4124
5768
  {
4125
5769
  type: "number",
@@ -4144,9 +5788,9 @@ function NodeConfigPanel({
4144
5788
  )
4145
5789
  ] })
4146
5790
  ] }),
4147
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4148
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { style: { fontSize: "0.875rem", fontWeight: 600, marginBottom: "8px" }, children: "Telemetry Data" }),
4149
- !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)(
4150
5794
  "button",
4151
5795
  {
4152
5796
  onClick: () => {
@@ -4189,9 +5833,9 @@ function NodeConfigPanel({
4189
5833
  },
4190
5834
  children: "+ Add Telemetry"
4191
5835
  }
4192
- ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
4193
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Initial Value:" }),
4194
- /* @__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)(
4195
5839
  "input",
4196
5840
  {
4197
5841
  type: "number",
@@ -4213,8 +5857,8 @@ function NodeConfigPanel({
4213
5857
  }
4214
5858
  }
4215
5859
  ),
4216
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: "Unit:" }),
4217
- /* @__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)(
4218
5862
  "input",
4219
5863
  {
4220
5864
  type: "text",
@@ -4236,8 +5880,8 @@ function NodeConfigPanel({
4236
5880
  }
4237
5881
  }
4238
5882
  ),
4239
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: [
4240
- /* @__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)(
4241
5885
  "input",
4242
5886
  {
4243
5887
  type: "checkbox",
@@ -4259,8 +5903,8 @@ function NodeConfigPanel({
4259
5903
  ),
4260
5904
  "Show Sparkline"
4261
5905
  ] }),
4262
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginTop: "12px" }, children: [
4263
- /* @__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)(
4264
5908
  "label",
4265
5909
  {
4266
5910
  style: {
@@ -4272,10 +5916,10 @@ function NodeConfigPanel({
4272
5916
  children: "Telemetry Position Offset:"
4273
5917
  }
4274
5918
  ),
4275
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
4276
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4277
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "X Offset:" }),
4278
- /* @__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)(
4279
5923
  "input",
4280
5924
  {
4281
5925
  type: "number",
@@ -4302,9 +5946,9 @@ function NodeConfigPanel({
4302
5946
  }
4303
5947
  )
4304
5948
  ] }),
4305
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4306
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Y Offset:" }),
4307
- /* @__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)(
4308
5952
  "input",
4309
5953
  {
4310
5954
  type: "number",
@@ -4332,8 +5976,8 @@ function NodeConfigPanel({
4332
5976
  )
4333
5977
  ] })
4334
5978
  ] }),
4335
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginTop: "16px" }, children: [
4336
- /* @__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)(
4337
5981
  "label",
4338
5982
  {
4339
5983
  style: {
@@ -4345,10 +5989,10 @@ function NodeConfigPanel({
4345
5989
  children: "Display Colors:"
4346
5990
  }
4347
5991
  ),
4348
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4349
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Text Color:" }),
4350
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4351
- /* @__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)(
4352
5996
  "input",
4353
5997
  {
4354
5998
  type: "color",
@@ -4374,7 +6018,7 @@ function NodeConfigPanel({
4374
6018
  }
4375
6019
  }
4376
6020
  ),
4377
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6021
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4378
6022
  "input",
4379
6023
  {
4380
6024
  type: "text",
@@ -4404,10 +6048,10 @@ function NodeConfigPanel({
4404
6048
  )
4405
6049
  ] })
4406
6050
  ] }),
4407
- binding.display?.showSparkline && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4408
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Sparkline Color:" }),
4409
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4410
- /* @__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)(
4411
6055
  "input",
4412
6056
  {
4413
6057
  type: "color",
@@ -4433,7 +6077,7 @@ function NodeConfigPanel({
4433
6077
  }
4434
6078
  }
4435
6079
  ),
4436
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6080
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4437
6081
  "input",
4438
6082
  {
4439
6083
  type: "text",
@@ -4463,9 +6107,9 @@ function NodeConfigPanel({
4463
6107
  )
4464
6108
  ] })
4465
6109
  ] }),
4466
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4467
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.7rem", display: "block", marginBottom: "4px" }, children: "Background:" }),
4468
- /* @__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)(
4469
6113
  "input",
4470
6114
  {
4471
6115
  type: "text",
@@ -4493,20 +6137,20 @@ function NodeConfigPanel({
4493
6137
  }
4494
6138
  }
4495
6139
  ),
4496
- /* @__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" })
4497
6141
  ] })
4498
6142
  ] }),
4499
6143
  " "
4500
6144
  ] })
4501
6145
  ] })
4502
6146
  ] }),
4503
- binding && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4504
- /* @__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" }),
4505
6149
  ["highHigh", "high", "low", "lowLow"].map((key) => {
4506
6150
  const label = key === "highHigh" ? "High-High (\u2265)" : key === "high" ? "High (\u2265)" : key === "low" ? "Low (\u2264)" : "Low-Low (\u2264)";
4507
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4508
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("label", { style: { fontSize: "0.75rem", display: "block", marginBottom: "4px" }, children: label }),
4509
- /* @__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)(
4510
6154
  "input",
4511
6155
  {
4512
6156
  type: "number",
@@ -4530,8 +6174,8 @@ function NodeConfigPanel({
4530
6174
  ] }, key);
4531
6175
  })
4532
6176
  ] }),
4533
- binding && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "20px" }, children: [
4534
- /* @__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" }),
4535
6179
  ["normal", "warning", "alarm", "fault", "off"].map((status) => {
4536
6180
  const defaultColors = {
4537
6181
  normal: "#10b981",
@@ -4541,13 +6185,13 @@ function NodeConfigPanel({
4541
6185
  off: "#6b7280"
4542
6186
  };
4543
6187
  const label = status === "normal" ? "Normal" : status === "warning" ? "Warning" : status === "alarm" ? "Alarm" : status === "fault" ? "Fault" : "Off";
4544
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginBottom: "8px" }, children: [
4545
- /* @__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: [
4546
6190
  label,
4547
6191
  ":"
4548
6192
  ] }),
4549
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
4550
- /* @__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)(
4551
6195
  "input",
4552
6196
  {
4553
6197
  type: "color",
@@ -4568,7 +6212,7 @@ function NodeConfigPanel({
4568
6212
  }
4569
6213
  }
4570
6214
  ),
4571
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6215
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4572
6216
  "input",
4573
6217
  {
4574
6218
  type: "text",
@@ -4595,7 +6239,7 @@ function NodeConfigPanel({
4595
6239
  ] }, status);
4596
6240
  })
4597
6241
  ] }),
4598
- /* @__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)(
4599
6243
  "button",
4600
6244
  {
4601
6245
  onClick: onRemove,
@@ -4835,7 +6479,7 @@ var DEFAULT_THRESHOLDS = {
4835
6479
  };
4836
6480
 
4837
6481
  // src/diagram/hooks/useSimulation.ts
4838
- var import_react15 = require("react");
6482
+ var import_react17 = require("react");
4839
6483
  function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4840
6484
  const {
4841
6485
  interval = 2e3,
@@ -4846,7 +6490,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4846
6490
  historyLength = 10,
4847
6491
  generateValue
4848
6492
  } = options;
4849
- (0, import_react15.useEffect)(() => {
6493
+ (0, import_react17.useEffect)(() => {
4850
6494
  if (!enabled) return;
4851
6495
  const intervalId = setInterval(() => {
4852
6496
  const updates = [];
@@ -4949,6 +6593,7 @@ function useSimulation(telemetry, updateTelemetryBatch, options = {}) {
4949
6593
  useSelection,
4950
6594
  useSimulation,
4951
6595
  useTelemetry,
6596
+ useTelemetryStatus,
4952
6597
  useTheme,
4953
6598
  useViewBox,
4954
6599
  validateDiagram