mywhy-ui 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,12 +2,35 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var React9 = require('react');
5
+ var lucideReact = require('lucide-react');
6
+ var rosmsg = require('@foxglove/rosmsg');
7
+ var rosmsg2Serialization = require('@foxglove/rosmsg2-serialization');
8
+ var wsProtocol = require('@foxglove/ws-protocol');
9
+ var EventEmitter = require('eventemitter3');
10
+ var WebSocket = require('isomorphic-ws');
5
11
 
6
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
13
 
8
14
  var React9__default = /*#__PURE__*/_interopDefault(React9);
15
+ var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
16
+ var WebSocket__default = /*#__PURE__*/_interopDefault(WebSocket);
9
17
 
10
- // src/components/Spinner/Spinner.tsx
18
+ var __typeError = (msg) => {
19
+ throw TypeError(msg);
20
+ };
21
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
22
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
23
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
24
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
25
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
26
+ var __privateWrapper = (obj, member, setter, getter) => ({
27
+ set _(value) {
28
+ __privateSet(obj, member, value);
29
+ },
30
+ get _() {
31
+ return __privateGet(obj, member, getter);
32
+ }
33
+ });
11
34
  var sizeClasses = {
12
35
  xs: "w-3 h-3",
13
36
  sm: "w-4 h-4",
@@ -545,7 +568,7 @@ function MultiSelect({
545
568
  document.removeEventListener("mousedown", handleClickOutside);
546
569
  };
547
570
  }, [isOpen]);
548
- const sizeClasses11 = {
571
+ const sizeClasses13 = {
549
572
  sm: "h-7 px-2 text-sm",
550
573
  md: "h-8 px-3 text-base",
551
574
  lg: "h-9 px-4 text-base"
@@ -563,7 +586,7 @@ function MultiSelect({
563
586
  "focus-within:ring-2 focus-within:ring-brand focus-within:ring-offset-1",
564
587
  disabled ? "bg-surface-gray opacity-50 cursor-not-allowed" : "bg-white",
565
588
  error ? "border-danger-border" : "border-outline",
566
- sizeClasses11[size]
589
+ sizeClasses13[size]
567
590
  ].join(" "),
568
591
  onClick: () => !disabled && setIsOpen(!isOpen),
569
592
  children: [
@@ -1613,6 +1636,412 @@ function Sidebar({
1613
1636
  }
1614
1637
  );
1615
1638
  }
1639
+ function Table({
1640
+ columns,
1641
+ data,
1642
+ striped = true,
1643
+ hoverable = true,
1644
+ compact = false,
1645
+ className = "",
1646
+ emptyMessage = "No data"
1647
+ }) {
1648
+ if (data.length === 0) {
1649
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-8 text-ink-faint text-sm", children: emptyMessage });
1650
+ }
1651
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `overflow-x-auto border border-outline rounded-lg ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full", children: [
1652
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "bg-surface-gray border-b border-outline", children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
1653
+ "th",
1654
+ {
1655
+ style: { width: col.width },
1656
+ className: `
1657
+ ${compact ? "px-3 py-2" : "px-4 py-3"}
1658
+ text-left text-xs font-semibold text-ink-faint uppercase tracking-wide
1659
+ ${col.align === "center" ? "text-center" : col.align === "right" ? "text-right" : "text-left"}
1660
+ `,
1661
+ children: col.label
1662
+ },
1663
+ col.key
1664
+ )) }) }),
1665
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: data.map((row, rowIdx) => /* @__PURE__ */ jsxRuntime.jsx(
1666
+ "tr",
1667
+ {
1668
+ className: `
1669
+ border-b border-outline last:border-b-0
1670
+ ${striped && rowIdx % 2 === 1 ? "bg-surface-gray/50" : ""}
1671
+ ${hoverable ? "hover:bg-surface-overlay transition-colors" : ""}
1672
+ `,
1673
+ children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
1674
+ "td",
1675
+ {
1676
+ style: { width: col.width },
1677
+ className: `
1678
+ ${compact ? "px-3 py-2" : "px-4 py-3"}
1679
+ text-sm text-ink
1680
+ ${col.align === "center" ? "text-center" : col.align === "right" ? "text-right" : "text-left"}
1681
+ `,
1682
+ children: col.render ? col.render(row[col.key], row) : row[col.key]
1683
+ },
1684
+ `${rowIdx}-${col.key}`
1685
+ ))
1686
+ },
1687
+ rowIdx
1688
+ )) })
1689
+ ] }) });
1690
+ }
1691
+ var ChevronLeftIcon = ({ size }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "15 18 9 12 15 6" }) });
1692
+ var ChevronRightIcon = ({ size }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 18 15 12 9 6" }) });
1693
+ function Pagination({
1694
+ current,
1695
+ total,
1696
+ onChange,
1697
+ size = "md",
1698
+ showInfo = true,
1699
+ maxVisible = 5,
1700
+ className = ""
1701
+ }) {
1702
+ const sizeClass = size === "sm" ? "text-xs px-2 py-1" : "text-sm px-3 py-2";
1703
+ const getPages = () => {
1704
+ const pages2 = [];
1705
+ const halfVisible = Math.floor(maxVisible / 2);
1706
+ let start = Math.max(1, current - halfVisible);
1707
+ let end = Math.min(total, current + halfVisible);
1708
+ if (start === 1) {
1709
+ end = Math.min(total, maxVisible);
1710
+ } else if (end === total) {
1711
+ start = Math.max(1, total - maxVisible + 1);
1712
+ }
1713
+ if (start > 1) pages2.push(1);
1714
+ if (start > 2) pages2.push("...");
1715
+ for (let i = start; i <= end; i++) {
1716
+ pages2.push(i);
1717
+ }
1718
+ if (end < total - 1) pages2.push("...");
1719
+ if (end < total) pages2.push(total);
1720
+ return pages2;
1721
+ };
1722
+ const pages = getPages();
1723
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center gap-2 ${className}`, children: [
1724
+ showInfo && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-ink-faint", children: [
1725
+ "Page ",
1726
+ current,
1727
+ " of ",
1728
+ total
1729
+ ] }),
1730
+ /* @__PURE__ */ jsxRuntime.jsx(
1731
+ "button",
1732
+ {
1733
+ onClick: () => onChange(Math.max(1, current - 1)),
1734
+ disabled: current === 1,
1735
+ className: `
1736
+ flex items-center justify-center rounded border border-outline
1737
+ hover:bg-surface-overlay disabled:text-ink-faint disabled:cursor-not-allowed disabled:hover:bg-transparent
1738
+ transition-colors ${sizeClass}
1739
+ `,
1740
+ title: "Previous page",
1741
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeftIcon, { size: size === "sm" ? 12 : 16 })
1742
+ }
1743
+ ),
1744
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1", children: pages.map(
1745
+ (page, i) => page === "..." ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-2 text-ink-faint", children: "..." }, `ellipsis-${i}`) : /* @__PURE__ */ jsxRuntime.jsx(
1746
+ "button",
1747
+ {
1748
+ onClick: () => onChange(page),
1749
+ className: `
1750
+ flex items-center justify-center rounded border
1751
+ transition-colors ${sizeClass}
1752
+ ${current === page ? "bg-brand text-white border-brand" : "border-outline hover:bg-surface-overlay"}
1753
+ `,
1754
+ children: page
1755
+ },
1756
+ page
1757
+ )
1758
+ ) }),
1759
+ /* @__PURE__ */ jsxRuntime.jsx(
1760
+ "button",
1761
+ {
1762
+ onClick: () => onChange(Math.min(total, current + 1)),
1763
+ disabled: current === total,
1764
+ className: `
1765
+ flex items-center justify-center rounded border border-outline
1766
+ hover:bg-surface-overlay disabled:text-ink-faint disabled:cursor-not-allowed disabled:hover:bg-transparent
1767
+ transition-colors ${sizeClass}
1768
+ `,
1769
+ title: "Next page",
1770
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRightIcon, { size: size === "sm" ? 12 : 16 })
1771
+ }
1772
+ )
1773
+ ] });
1774
+ }
1775
+ function EmptyState({
1776
+ icon,
1777
+ title,
1778
+ description,
1779
+ action,
1780
+ className = ""
1781
+ }) {
1782
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col items-center justify-center py-12 px-4 ${className}`, children: [
1783
+ icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-4xl mb-4", children: icon }),
1784
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-ink mb-2", children: title }),
1785
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-ink-faint mb-6 text-center max-w-sm", children: description }),
1786
+ action && /* @__PURE__ */ jsxRuntime.jsx(
1787
+ "button",
1788
+ {
1789
+ onClick: action.onClick,
1790
+ className: "px-4 py-2 bg-brand text-white rounded-md hover:bg-brand-600 transition-colors text-sm font-medium",
1791
+ children: action.label
1792
+ }
1793
+ )
1794
+ ] });
1795
+ }
1796
+ var ChevronUpIcon = ({ size }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "18 15 12 9 6 15" }) });
1797
+ var ChevronDownIcon = ({ size }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "6 9 12 15 18 9" }) });
1798
+ var sizeClasses10 = {
1799
+ sm: "text-xs px-2 py-1 h-8",
1800
+ md: "text-sm px-3 py-2 h-10",
1801
+ lg: "text-base px-4 py-2.5 h-12"
1802
+ };
1803
+ function NumberInput({
1804
+ value,
1805
+ onChange,
1806
+ step = 1,
1807
+ min,
1808
+ max,
1809
+ size = "md",
1810
+ disabled = false,
1811
+ placeholder,
1812
+ className = ""
1813
+ }) {
1814
+ const [isEditing, setIsEditing] = React9.useState(false);
1815
+ const handleIncrement = () => {
1816
+ const newValue = value + step;
1817
+ if (max === void 0 || newValue <= max) {
1818
+ onChange(newValue);
1819
+ }
1820
+ };
1821
+ const handleDecrement = () => {
1822
+ const newValue = value - step;
1823
+ if (min === void 0 || newValue >= min) {
1824
+ onChange(newValue);
1825
+ }
1826
+ };
1827
+ const handleInputChange = (e) => {
1828
+ const newValue = parseFloat(e.target.value);
1829
+ if (!isNaN(newValue)) {
1830
+ let val = newValue;
1831
+ if (min !== void 0 && val < min) val = min;
1832
+ if (max !== void 0 && val > max) val = max;
1833
+ onChange(val);
1834
+ }
1835
+ };
1836
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative inline-flex items-center border border-outline rounded-md bg-surface ${className}`, children: [
1837
+ /* @__PURE__ */ jsxRuntime.jsx(
1838
+ "input",
1839
+ {
1840
+ type: "number",
1841
+ value,
1842
+ onChange: handleInputChange,
1843
+ onFocus: () => setIsEditing(true),
1844
+ onBlur: () => setIsEditing(false),
1845
+ disabled,
1846
+ placeholder,
1847
+ step,
1848
+ min,
1849
+ max,
1850
+ className: `
1851
+ flex-1 bg-transparent outline-none text-ink disabled:text-ink-faint disabled:cursor-not-allowed
1852
+ ${sizeClasses10[size]}
1853
+ `
1854
+ }
1855
+ ),
1856
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col border-l border-outline", children: [
1857
+ /* @__PURE__ */ jsxRuntime.jsx(
1858
+ "button",
1859
+ {
1860
+ onClick: handleIncrement,
1861
+ disabled: disabled || max !== void 0 && value >= max,
1862
+ className: "flex-1 px-1 hover:bg-surface-overlay disabled:text-ink-faint disabled:cursor-not-allowed transition-colors",
1863
+ title: "Increment",
1864
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronUpIcon, { size: size === "sm" ? 12 : size === "md" ? 14 : 16 })
1865
+ }
1866
+ ),
1867
+ /* @__PURE__ */ jsxRuntime.jsx(
1868
+ "button",
1869
+ {
1870
+ onClick: handleDecrement,
1871
+ disabled: disabled || min !== void 0 && value <= min,
1872
+ className: "flex-1 px-1 hover:bg-surface-overlay disabled:text-ink-faint disabled:cursor-not-allowed transition-colors border-t border-outline",
1873
+ title: "Decrement",
1874
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon, { size: size === "sm" ? 12 : size === "md" ? 14 : 16 })
1875
+ }
1876
+ )
1877
+ ] })
1878
+ ] });
1879
+ }
1880
+ var CopyIcon = ({ size }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1881
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }),
1882
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "8", y: "2", width: "8", height: "4", rx: "1", ry: "1" })
1883
+ ] });
1884
+ var CheckIcon = ({ size, className }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) });
1885
+ function CodeBlock({
1886
+ code,
1887
+ language = "text",
1888
+ copyable = true,
1889
+ numbered = false,
1890
+ maxHeight = "max-h-96",
1891
+ className = ""
1892
+ }) {
1893
+ const [copied, setCopied] = React9.useState(false);
1894
+ const handleCopy = async () => {
1895
+ try {
1896
+ await navigator.clipboard.writeText(code);
1897
+ setCopied(true);
1898
+ setTimeout(() => setCopied(false), 2e3);
1899
+ } catch (err) {
1900
+ console.error("Failed to copy:", err);
1901
+ }
1902
+ };
1903
+ const lines = code.split("\n");
1904
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative bg-ink rounded-lg overflow-hidden border border-outline ${className}`, children: [
1905
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between bg-surface-gray px-4 py-2 border-b border-outline", children: [
1906
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-ink-faint font-mono uppercase", children: language }),
1907
+ copyable && /* @__PURE__ */ jsxRuntime.jsx(
1908
+ "button",
1909
+ {
1910
+ onClick: handleCopy,
1911
+ className: "p-1 rounded hover:bg-surface-overlay transition-colors text-ink-faint hover:text-ink",
1912
+ title: "Copy to clipboard",
1913
+ children: copied ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "currentColor" }, className: "text-status-online", children: /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, { size: 14 }) }) : /* @__PURE__ */ jsxRuntime.jsx(CopyIcon, { size: 14 })
1914
+ }
1915
+ )
1916
+ ] }),
1917
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `overflow-auto ${maxHeight}`, children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "p-4 text-sm font-mono text-sky-100", children: numbered ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: lines.map((line, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
1918
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ink-faint select-none", children: String(i + 1).padStart(3, " ") }),
1919
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: line })
1920
+ ] }, i)) }) : code }) })
1921
+ ] });
1922
+ }
1923
+ var statusColors = {
1924
+ completed: {
1925
+ dot: "bg-status-online border-status-online",
1926
+ line: "bg-status-online",
1927
+ text: "text-status-online"
1928
+ },
1929
+ pending: {
1930
+ dot: "bg-ink-light border-ink-light",
1931
+ line: "bg-ink-faint",
1932
+ text: "text-ink-faint"
1933
+ },
1934
+ error: {
1935
+ dot: "bg-danger-icon border-danger-icon",
1936
+ line: "bg-danger-icon",
1937
+ text: "text-danger-text"
1938
+ },
1939
+ "in-progress": {
1940
+ dot: "bg-brand border-brand",
1941
+ line: "bg-brand",
1942
+ text: "text-brand"
1943
+ }
1944
+ };
1945
+ function Timeline({
1946
+ items,
1947
+ orientation = "vertical",
1948
+ className = ""
1949
+ }) {
1950
+ if (orientation === "vertical") {
1951
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `space-y-4 ${className}`, children: items.map((item, index) => {
1952
+ const colors = statusColors[item.status];
1953
+ const isLast = index === items.length - 1;
1954
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
1955
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center", children: [
1956
+ /* @__PURE__ */ jsxRuntime.jsx(
1957
+ "div",
1958
+ {
1959
+ className: `
1960
+ w-4 h-4 rounded-full border-2 flex-shrink-0
1961
+ flex items-center justify-center bg-white
1962
+ ${colors.dot}
1963
+ `,
1964
+ children: item.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white text-2xs", children: item.icon })
1965
+ }
1966
+ ),
1967
+ !isLast && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-0.5 h-12 ${colors.line}` })
1968
+ ] }),
1969
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 py-1", children: [
1970
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1971
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: `font-semibold text-sm ${colors.text}`, children: item.label }),
1972
+ item.timestamp && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-ink-faint", children: typeof item.timestamp === "string" ? item.timestamp : new Date(item.timestamp).toLocaleTimeString("en-GB", {
1973
+ hour12: false,
1974
+ hour: "2-digit",
1975
+ minute: "2-digit",
1976
+ second: "2-digit"
1977
+ }) })
1978
+ ] }),
1979
+ item.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-ink-faint mt-1", children: item.description })
1980
+ ] })
1981
+ ] }, item.id);
1982
+ }) });
1983
+ }
1984
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex gap-4 overflow-x-auto pb-2 ${className}`, children: items.map((item, index) => {
1985
+ const colors = statusColors[item.status];
1986
+ index === items.length - 1;
1987
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center flex-shrink-0 gap-2 min-w-fit", children: [
1988
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1989
+ index > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `h-0.5 w-6 ${statusColors[items[index - 1].status].line}` }),
1990
+ /* @__PURE__ */ jsxRuntime.jsx(
1991
+ "div",
1992
+ {
1993
+ className: `
1994
+ w-5 h-5 rounded-full border-2 flex items-center justify-center bg-white
1995
+ ${colors.dot}
1996
+ `,
1997
+ children: item.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white text-2xs", children: item.icon })
1998
+ }
1999
+ )
2000
+ ] }),
2001
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
2002
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-xs font-semibold ${colors.text}`, children: item.label }),
2003
+ item.timestamp && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xs text-ink-faint", children: typeof item.timestamp === "string" ? item.timestamp : new Date(item.timestamp).toLocaleTimeString("en-GB", {
2004
+ hour12: false,
2005
+ hour: "2-digit",
2006
+ minute: "2-digit"
2007
+ }) })
2008
+ ] })
2009
+ ] }, item.id);
2010
+ }) });
2011
+ }
2012
+ var sizeClasses11 = {
2013
+ xs: "text-xs px-1.5 py-0.5 min-w-5 h-5",
2014
+ sm: "text-xs px-2 py-1 min-w-6 h-6",
2015
+ md: "text-sm px-2.5 py-1.5 min-w-8 h-8"
2016
+ };
2017
+ var themeClasses5 = {
2018
+ gray: "bg-surface-gray border-outline text-ink",
2019
+ dark: "bg-ink text-white border-ink-faint",
2020
+ brand: "bg-brand border-brand text-white"
2021
+ };
2022
+ function Kbd({
2023
+ keys,
2024
+ size = "sm",
2025
+ theme = "gray",
2026
+ className = ""
2027
+ }) {
2028
+ const keyArray = Array.isArray(keys) ? keys : [keys];
2029
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center gap-1 ${className}`, children: keyArray.map((key, i) => /* @__PURE__ */ jsxRuntime.jsxs(React9__default.default.Fragment, { children: [
2030
+ /* @__PURE__ */ jsxRuntime.jsx(
2031
+ "kbd",
2032
+ {
2033
+ className: `
2034
+ inline-flex items-center justify-center rounded font-mono font-medium
2035
+ border border-b-2 shadow-sm
2036
+ ${sizeClasses11[size]}
2037
+ ${themeClasses5[theme]}
2038
+ `,
2039
+ children: key
2040
+ }
2041
+ ),
2042
+ i < keyArray.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ink-faint text-xs", children: "+" })
2043
+ ] }, key)) });
2044
+ }
1616
2045
  var statusConfig = {
1617
2046
  online: {
1618
2047
  label: "Online",
@@ -1687,7 +2116,7 @@ function StatusBadge({
1687
2116
  }
1688
2117
  );
1689
2118
  }
1690
- var sizeClasses10 = {
2119
+ var sizeClasses12 = {
1691
2120
  sm: "text-xs gap-1.5",
1692
2121
  md: "text-sm gap-2",
1693
2122
  lg: "text-base gap-2"
@@ -1706,7 +2135,7 @@ function ConnectionIndicator({
1706
2135
  className = ""
1707
2136
  }) {
1708
2137
  const status = connected ? "online" : "offline";
1709
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `inline-flex items-center ${sizeClasses10[size]} ${className}`, children: [
2138
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `inline-flex items-center ${sizeClasses12[size]} ${className}`, children: [
1710
2139
  /* @__PURE__ */ jsxRuntime.jsx(
1711
2140
  StatusBadge,
1712
2141
  {
@@ -1732,6 +2161,1004 @@ function ConnectionIcon({ connected, size = "md", className = "" }) {
1732
2161
  }
1733
2162
  );
1734
2163
  }
2164
+ var collectLeafNodes = (entries) => entries.flatMap(
2165
+ (entry) => entry.children.length === 0 ? [entry] : collectLeafNodes(entry.children)
2166
+ );
2167
+ var DiagnosticsTable = ({
2168
+ diagnostics,
2169
+ setSelectedRawName,
2170
+ variant
2171
+ }) => {
2172
+ const levelFilter = (level) => variant === "error" ? level >= 2 : level === 1;
2173
+ const filteredDiagnostics = collectLeafNodes(diagnostics).filter((d) => levelFilter(d.severity_level));
2174
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2175
+ /* @__PURE__ */ jsxRuntime.jsx(
2176
+ Alert,
2177
+ {
2178
+ theme: variant === "error" ? "danger" : "warning",
2179
+ title: variant === "error" ? "Errors" : "Warnings"
2180
+ }
2181
+ ),
2182
+ filteredDiagnostics.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto border border-gray-200 rounded", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-sm", children: [
2183
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-gray-50 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2184
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-2 text-left font-semibold text-gray-700", children: "Name" }),
2185
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-2 text-left font-semibold text-gray-700", children: "Message" })
2186
+ ] }) }),
2187
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: filteredDiagnostics.map((diag, index) => /* @__PURE__ */ jsxRuntime.jsxs(
2188
+ "tr",
2189
+ {
2190
+ className: "border-b border-gray-200 hover:bg-blue-50 cursor-pointer transition-colors",
2191
+ onClick: () => setSelectedRawName(diag.rawName),
2192
+ children: [
2193
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
2194
+ diag.icon,
2195
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
2196
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-gray-900", children: diag.name || "N/A" }),
2197
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: diag.path || "N/A" })
2198
+ ] })
2199
+ ] }) }),
2200
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2 text-gray-700", children: diag.message || "N/A" })
2201
+ ]
2202
+ },
2203
+ index
2204
+ )) })
2205
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx(
2206
+ EmptyState,
2207
+ {
2208
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { className: "w-12 h-12 text-green-500" }),
2209
+ title: variant === "error" ? "No Errors" : "No Warnings",
2210
+ description: "All diagnostics are healthy"
2211
+ }
2212
+ )
2213
+ ] });
2214
+ };
2215
+ var DiagnosticsTreeTable = ({
2216
+ diagnostics,
2217
+ bridgeConnected,
2218
+ selectedRawName,
2219
+ setSelectedRawName
2220
+ }) => {
2221
+ const renderTree = (entries, depth = 0) => {
2222
+ return /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-1", children: entries.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
2223
+ /* @__PURE__ */ jsxRuntime.jsxs(
2224
+ "div",
2225
+ {
2226
+ className: "flex items-start gap-2 p-2 hover:bg-gray-100 rounded cursor-pointer transition-colors",
2227
+ style: { paddingLeft: `${depth * 1.5}rem` },
2228
+ onClick: () => setSelectedRawName(entry.rawName),
2229
+ children: [
2230
+ entry.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 mt-0.5", children: entry.icon }),
2231
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
2232
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-gray-900 truncate", children: entry.name }),
2233
+ entry.message && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-600 truncate", children: entry.message })
2234
+ ] })
2235
+ ]
2236
+ }
2237
+ ),
2238
+ entry.children.length > 0 && renderTree(entry.children, depth + 1)
2239
+ ] }, entry.rawName)) });
2240
+ };
2241
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "bg-white", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4", children: [
2242
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Diagnostics Tree" }),
2243
+ diagnostics.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-auto", children: renderTree(diagnostics) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500 text-center py-8", children: "No diagnostics available" })
2244
+ ] }) });
2245
+ };
2246
+ var _client, _connecting, _messageReaders, _messageWriters, _channelsById, _channelsByName, _servicesById, _servicesByName, _publisherIdsWithCount, _subscriptionIdsWithCount, _callId, _paramId, _Impl_instances, getChannel_fn, getService_fn, getMessageReader_fn, getMessageWriter_fn;
2247
+ var Impl = class {
2248
+ constructor(url) {
2249
+ __privateAdd(this, _Impl_instances);
2250
+ this.emitter = new EventEmitter__default.default();
2251
+ __privateAdd(this, _client);
2252
+ __privateAdd(this, _connecting);
2253
+ // Message Readers / Writers
2254
+ __privateAdd(this, _messageReaders, /* @__PURE__ */ new Map());
2255
+ __privateAdd(this, _messageWriters, /* @__PURE__ */ new Map());
2256
+ // Channels
2257
+ __privateAdd(this, _channelsById, /* @__PURE__ */ new Map());
2258
+ __privateAdd(this, _channelsByName, /* @__PURE__ */ new Map());
2259
+ // Services
2260
+ __privateAdd(this, _servicesById, /* @__PURE__ */ new Map());
2261
+ __privateAdd(this, _servicesByName, /* @__PURE__ */ new Map());
2262
+ __privateAdd(this, _publisherIdsWithCount, /* @__PURE__ */ new Map());
2263
+ __privateAdd(this, _subscriptionIdsWithCount, /* @__PURE__ */ new Map());
2264
+ __privateAdd(this, _callId, 0);
2265
+ __privateAdd(this, _paramId, 0);
2266
+ __privateSet(this, _client, new wsProtocol.FoxgloveClient({
2267
+ // TODO: "foxglove.sdk.v1" was added here manually because Foxglove switched the foxglove-bridge
2268
+ // package for Jazzy from ros-foxglove-bridge repo (v0.8.5) which used "foxglove.websocket.v1"
2269
+ // to their new foxglove-sdk version (v3.2.x) which uses "foxglove.sdk.v1" around September 2025,
2270
+ // but the @foxglove/ws-protocol npm package has not yet been updated to include this new
2271
+ // subprotocol. Once that package is updated, or a new alternative is released, "foxglove.sdk.v1"
2272
+ // may be removed from here and adjusted accordingly. As far as I can tell, it seems that
2273
+ // the underlying protocols are compatible for our use case, and is effectively only a name change.
2274
+ ws: new WebSocket__default.default(url, ["foxglove.sdk.v1", wsProtocol.FoxgloveClient.SUPPORTED_SUBPROTOCOL])
2275
+ }));
2276
+ const open = new Promise((resolve) => {
2277
+ __privateGet(this, _client).on("open", resolve);
2278
+ });
2279
+ __privateGet(this, _client).on("close", (event) => {
2280
+ this.emitter.emit("close", event);
2281
+ });
2282
+ __privateGet(this, _client).on("error", (error) => {
2283
+ this.emitter.emit("error", error ?? new Error("WebSocket error"));
2284
+ });
2285
+ __privateGet(this, _client).on("advertise", (channels) => {
2286
+ for (const channel of channels) {
2287
+ __privateGet(this, _channelsById).set(channel.id, channel);
2288
+ __privateGet(this, _channelsByName).set(channel.topic, channel);
2289
+ }
2290
+ });
2291
+ __privateGet(this, _client).on("unadvertise", (channelIds) => {
2292
+ for (const channelId of channelIds) {
2293
+ const channel = __privateGet(this, _channelsById).get(channelId);
2294
+ if (channel) {
2295
+ __privateGet(this, _channelsById).delete(channel.id);
2296
+ __privateGet(this, _channelsByName).delete(channel.topic);
2297
+ }
2298
+ }
2299
+ });
2300
+ __privateGet(this, _client).on("advertiseServices", (services) => {
2301
+ for (const service of services) {
2302
+ __privateGet(this, _servicesById).set(service.id, service);
2303
+ __privateGet(this, _servicesByName).set(service.name, service);
2304
+ }
2305
+ });
2306
+ __privateGet(this, _client).on("unadvertiseServices", (serviceIds) => {
2307
+ for (const serviceId of serviceIds) {
2308
+ const service = __privateGet(this, _servicesById).get(serviceId);
2309
+ if (service) {
2310
+ __privateGet(this, _servicesById).delete(service.id);
2311
+ __privateGet(this, _servicesByName).delete(service.name);
2312
+ }
2313
+ }
2314
+ });
2315
+ __privateSet(this, _connecting, new Promise((resolve) => {
2316
+ Promise.all([open]).then(() => {
2317
+ this.emitter.emit("connection");
2318
+ resolve();
2319
+ });
2320
+ }));
2321
+ }
2322
+ close() {
2323
+ __privateGet(this, _client).close();
2324
+ }
2325
+ getTopics() {
2326
+ return {
2327
+ topics: [...__privateGet(this, _channelsByName).keys()],
2328
+ types: [...__privateGet(this, _channelsByName).values()].map((x) => x.schemaName)
2329
+ };
2330
+ }
2331
+ async getServices() {
2332
+ await __privateGet(this, _connecting);
2333
+ return new Promise((resolve) => {
2334
+ const listener = (event) => {
2335
+ __privateGet(this, _client).off("connectionGraphUpdate", listener);
2336
+ __privateGet(this, _client).unsubscribeConnectionGraph();
2337
+ resolve(event.advertisedServices.map((service) => service.name));
2338
+ };
2339
+ __privateGet(this, _client).on("connectionGraphUpdate", listener);
2340
+ __privateGet(this, _client).subscribeConnectionGraph();
2341
+ });
2342
+ }
2343
+ getTopicType(topic) {
2344
+ return __privateGet(this, _channelsByName).get(topic)?.schemaName;
2345
+ }
2346
+ getServiceType(service) {
2347
+ return __privateGet(this, _servicesByName).get(service)?.type;
2348
+ }
2349
+ async createPublisher(name, messageType) {
2350
+ await __privateGet(this, _connecting);
2351
+ const channel = __privateMethod(this, _Impl_instances, getChannel_fn).call(this, name);
2352
+ const publisherId = (() => {
2353
+ const idWithCount = __privateGet(this, _publisherIdsWithCount).get(name);
2354
+ if (idWithCount) {
2355
+ idWithCount.count++;
2356
+ return idWithCount.id;
2357
+ }
2358
+ const publisherId2 = __privateGet(this, _client).advertise({
2359
+ topic: name,
2360
+ encoding: "cdr",
2361
+ schemaName: messageType
2362
+ });
2363
+ __privateGet(this, _publisherIdsWithCount).set(name, { id: publisherId2, count: 1 });
2364
+ return publisherId2;
2365
+ })();
2366
+ const writer = __privateMethod(this, _Impl_instances, getMessageWriter_fn).call(this, await channel);
2367
+ return {
2368
+ publish: (message) => {
2369
+ __privateGet(this, _client).sendMessage(publisherId, writer.writeMessage(message));
2370
+ },
2371
+ unadvertise: () => {
2372
+ const idWithCount = __privateGet(this, _publisherIdsWithCount).get(name);
2373
+ if (idWithCount) {
2374
+ idWithCount.count--;
2375
+ if (idWithCount.count === 0) {
2376
+ __privateGet(this, _publisherIdsWithCount).delete(name);
2377
+ __privateGet(this, _client).unadvertise(publisherId);
2378
+ }
2379
+ }
2380
+ }
2381
+ };
2382
+ }
2383
+ async createSubscription(name, callback) {
2384
+ await __privateGet(this, _connecting);
2385
+ const channel = await __privateMethod(this, _Impl_instances, getChannel_fn).call(this, name);
2386
+ const subscriptionId = (() => {
2387
+ const idWithCount = __privateGet(this, _subscriptionIdsWithCount).get(name);
2388
+ if (idWithCount) {
2389
+ idWithCount.count++;
2390
+ return idWithCount.id;
2391
+ }
2392
+ const subscriptionId2 = __privateGet(this, _client).subscribe(channel.id);
2393
+ __privateGet(this, _subscriptionIdsWithCount).set(name, {
2394
+ id: subscriptionId2,
2395
+ count: 1
2396
+ });
2397
+ return subscriptionId2;
2398
+ })();
2399
+ const reader = __privateMethod(this, _Impl_instances, getMessageReader_fn).call(this, channel);
2400
+ const listener = (event) => {
2401
+ if (event.subscriptionId === subscriptionId) {
2402
+ callback(reader.readMessage(event.data));
2403
+ }
2404
+ };
2405
+ __privateGet(this, _client).on("message", listener);
2406
+ return {
2407
+ unsubscribe: () => {
2408
+ __privateGet(this, _client).off("message", listener);
2409
+ const idWithCount = __privateGet(this, _subscriptionIdsWithCount).get(name);
2410
+ if (idWithCount) {
2411
+ idWithCount.count--;
2412
+ if (idWithCount.count === 0) {
2413
+ __privateGet(this, _subscriptionIdsWithCount).delete(name);
2414
+ __privateGet(this, _client).unsubscribe(subscriptionId);
2415
+ }
2416
+ }
2417
+ }
2418
+ };
2419
+ }
2420
+ async sendServiceRequest(name, request) {
2421
+ await __privateGet(this, _connecting);
2422
+ const service = await __privateMethod(this, _Impl_instances, getService_fn).call(this, name);
2423
+ const writer = __privateMethod(this, _Impl_instances, getMessageWriter_fn).call(this, service);
2424
+ const reader = __privateMethod(this, _Impl_instances, getMessageReader_fn).call(this, service);
2425
+ const callId = __privateWrapper(this, _callId)._++;
2426
+ return new Promise((resolve) => {
2427
+ const listener = (event) => {
2428
+ if (event.serviceId === service.id && event.callId === callId) {
2429
+ __privateGet(this, _client).off("serviceCallResponse", listener);
2430
+ resolve(reader.readMessage(event.data));
2431
+ }
2432
+ };
2433
+ __privateGet(this, _client).on("serviceCallResponse", listener);
2434
+ __privateGet(this, _client).sendServiceCallRequest({
2435
+ serviceId: service.id,
2436
+ callId,
2437
+ encoding: "cdr",
2438
+ data: new DataView(writer.writeMessage(request).buffer)
2439
+ });
2440
+ });
2441
+ }
2442
+ async getParameter(name) {
2443
+ await __privateGet(this, _connecting);
2444
+ const paramId = (__privateWrapper(this, _paramId)._++).toString();
2445
+ return new Promise((resolve) => {
2446
+ const listener = (event) => {
2447
+ if (event.parameters[0]?.name === name && event.id === paramId) {
2448
+ __privateGet(this, _client).off("parameterValues", listener);
2449
+ resolve(event.parameters[0].value);
2450
+ }
2451
+ };
2452
+ __privateGet(this, _client).on("parameterValues", listener);
2453
+ __privateGet(this, _client).getParameters([name], paramId);
2454
+ });
2455
+ }
2456
+ async setParameter(name, value) {
2457
+ await __privateGet(this, _connecting);
2458
+ const paramId = (__privateWrapper(this, _paramId)._++).toString();
2459
+ return new Promise((resolve) => {
2460
+ const listener = (event) => {
2461
+ if (event.parameters[0]?.name === name && event.id === paramId) {
2462
+ __privateGet(this, _client).off("parameterValues", listener);
2463
+ resolve(event.parameters[0]);
2464
+ }
2465
+ };
2466
+ __privateGet(this, _client).on("parameterValues", listener);
2467
+ __privateGet(this, _client).setParameters([{ name, value }], paramId);
2468
+ });
2469
+ }
2470
+ };
2471
+ _client = new WeakMap();
2472
+ _connecting = new WeakMap();
2473
+ _messageReaders = new WeakMap();
2474
+ _messageWriters = new WeakMap();
2475
+ _channelsById = new WeakMap();
2476
+ _channelsByName = new WeakMap();
2477
+ _servicesById = new WeakMap();
2478
+ _servicesByName = new WeakMap();
2479
+ _publisherIdsWithCount = new WeakMap();
2480
+ _subscriptionIdsWithCount = new WeakMap();
2481
+ _callId = new WeakMap();
2482
+ _paramId = new WeakMap();
2483
+ _Impl_instances = new WeakSet();
2484
+ getChannel_fn = async function(name) {
2485
+ await __privateGet(this, _connecting);
2486
+ return __privateGet(this, _channelsByName).get(name) ?? await new Promise((resolve) => {
2487
+ const listener = (channels) => {
2488
+ const channel = channels.find((channel2) => channel2.topic === name);
2489
+ if (channel) {
2490
+ __privateGet(this, _client).off("advertise", listener);
2491
+ resolve(channel);
2492
+ }
2493
+ };
2494
+ __privateGet(this, _client).on("advertise", listener);
2495
+ });
2496
+ };
2497
+ getService_fn = async function(name) {
2498
+ await __privateGet(this, _connecting);
2499
+ return __privateGet(this, _servicesByName).get(name) ?? await new Promise((resolve) => {
2500
+ const listener = (services) => {
2501
+ const service = services.find((channel) => channel.name === name);
2502
+ if (service) {
2503
+ __privateGet(this, _client).off("advertiseServices", listener);
2504
+ resolve(service);
2505
+ }
2506
+ };
2507
+ __privateGet(this, _client).on("advertiseServices", listener);
2508
+ });
2509
+ };
2510
+ getMessageReader_fn = function(channelOrService) {
2511
+ const name = "schemaName" in channelOrService ? channelOrService.schemaName : channelOrService.type;
2512
+ const schemaEncoding = "schemaEncoding" in channelOrService ? channelOrService.schemaEncoding : void 0;
2513
+ const schema = "schema" in channelOrService ? channelOrService.schema : channelOrService.responseSchema;
2514
+ return __privateGet(this, _messageReaders).get(name) ?? (() => {
2515
+ const reader = new rosmsg2Serialization.MessageReader(
2516
+ schemaEncoding === "ros2idl" ? rosmsg.parseRos2idl(schema) : rosmsg.parse(schema, { ros2: true })
2517
+ );
2518
+ __privateGet(this, _messageReaders).set(name, reader);
2519
+ return reader;
2520
+ })();
2521
+ };
2522
+ getMessageWriter_fn = function(channelOrService) {
2523
+ const name = "schemaName" in channelOrService ? channelOrService.schemaName : channelOrService.type;
2524
+ const schemaEncoding = "schemaEncoding" in channelOrService ? channelOrService.schemaEncoding : void 0;
2525
+ const schema = "schema" in channelOrService ? channelOrService.schema : channelOrService.requestSchema;
2526
+ return __privateGet(this, _messageWriters).get(name) ?? (() => {
2527
+ const writer = new rosmsg2Serialization.MessageWriter(
2528
+ schemaEncoding === "ros2idl" ? rosmsg.parseRos2idl(schema) : rosmsg.parse(schema, { ros2: true })
2529
+ );
2530
+ __privateGet(this, _messageWriters).set(name, writer);
2531
+ return writer;
2532
+ })();
2533
+ };
2534
+
2535
+ // src/components/ROS2Diagnostics/roslib/Ros.ts
2536
+ var _rosImpl;
2537
+ var Ros = class {
2538
+ constructor(options) {
2539
+ this.options = options;
2540
+ __privateAdd(this, _rosImpl);
2541
+ if (options.url) {
2542
+ this.connect(options.url);
2543
+ }
2544
+ }
2545
+ /** @internal */
2546
+ get rosImpl() {
2547
+ return __privateGet(this, _rosImpl);
2548
+ }
2549
+ on(event, fn) {
2550
+ this.rosImpl?.emitter.on(event, fn);
2551
+ return this;
2552
+ }
2553
+ off(event, fn) {
2554
+ this.rosImpl?.emitter.off(event, fn);
2555
+ return this;
2556
+ }
2557
+ connect(url) {
2558
+ __privateSet(this, _rosImpl, new Impl(url));
2559
+ }
2560
+ close() {
2561
+ this.rosImpl?.close();
2562
+ __privateSet(this, _rosImpl, void 0);
2563
+ }
2564
+ getTopics(callback, failedCallback) {
2565
+ const topics = this.rosImpl?.getTopics();
2566
+ if (topics) {
2567
+ callback(topics);
2568
+ } else if (failedCallback) {
2569
+ failedCallback("Error: getTopics");
2570
+ }
2571
+ }
2572
+ getServices(callback, failedCallback) {
2573
+ this.rosImpl?.getServices().then(callback).catch(failedCallback);
2574
+ }
2575
+ getTopicType(topic, callback, failedCallback) {
2576
+ const topicType = this.rosImpl?.getTopicType(topic);
2577
+ if (topicType) {
2578
+ callback(topicType);
2579
+ } else if (failedCallback) {
2580
+ failedCallback("Error: getTopicType");
2581
+ }
2582
+ }
2583
+ getServiceType(service, callback, failedCallback) {
2584
+ const serviceType = this.rosImpl?.getServiceType(service);
2585
+ if (serviceType) {
2586
+ callback(serviceType);
2587
+ } else if (failedCallback) {
2588
+ failedCallback("Error: getServiceType");
2589
+ }
2590
+ }
2591
+ };
2592
+ _rosImpl = new WeakMap();
2593
+
2594
+ // src/components/ROS2Diagnostics/roslib/Topic.ts
2595
+ var _ros, _name, _messageType, _publisher, _subscriptions;
2596
+ var Topic = class {
2597
+ constructor(options) {
2598
+ this.options = options;
2599
+ __privateAdd(this, _ros);
2600
+ __privateAdd(this, _name);
2601
+ __privateAdd(this, _messageType);
2602
+ __privateAdd(this, _publisher);
2603
+ __privateAdd(this, _subscriptions, /* @__PURE__ */ new Map());
2604
+ __privateSet(this, _ros, options.ros);
2605
+ __privateSet(this, _name, options.name);
2606
+ __privateSet(this, _messageType, options.messageType);
2607
+ }
2608
+ get name() {
2609
+ return __privateGet(this, _name);
2610
+ }
2611
+ get messageType() {
2612
+ return __privateGet(this, _messageType);
2613
+ }
2614
+ publish(message) {
2615
+ if (!__privateGet(this, _publisher)) {
2616
+ this.advertise();
2617
+ }
2618
+ __privateGet(this, _publisher)?.then((publisher) => {
2619
+ publisher.publish(message);
2620
+ });
2621
+ }
2622
+ subscribe(callback) {
2623
+ __privateGet(this, _ros).rosImpl?.createSubscription(this.name, callback).then((subscription) => {
2624
+ __privateGet(this, _subscriptions).set(callback, subscription);
2625
+ });
2626
+ }
2627
+ unsubscribe(callback) {
2628
+ if (callback) {
2629
+ __privateGet(this, _subscriptions).get(callback)?.unsubscribe();
2630
+ __privateGet(this, _subscriptions).delete(callback);
2631
+ } else {
2632
+ for (const subscription of __privateGet(this, _subscriptions).values()) {
2633
+ subscription.unsubscribe();
2634
+ }
2635
+ __privateGet(this, _subscriptions).clear();
2636
+ }
2637
+ }
2638
+ advertise() {
2639
+ __privateSet(this, _publisher, __privateGet(this, _ros).rosImpl?.createPublisher(
2640
+ this.name,
2641
+ this.messageType
2642
+ ));
2643
+ }
2644
+ unadvertise() {
2645
+ __privateGet(this, _publisher)?.then((publisher) => {
2646
+ publisher.unadvertise();
2647
+ __privateSet(this, _publisher, void 0);
2648
+ });
2649
+ }
2650
+ };
2651
+ _ros = new WeakMap();
2652
+ _name = new WeakMap();
2653
+ _messageType = new WeakMap();
2654
+ _publisher = new WeakMap();
2655
+ _subscriptions = new WeakMap();
2656
+ var calculateOverallLevel = (diagnostics) => {
2657
+ let maxLevel = 0;
2658
+ const checkEntry = (entry) => {
2659
+ if (entry.severity_level > maxLevel) {
2660
+ maxLevel = entry.severity_level;
2661
+ }
2662
+ entry.children.forEach(checkEntry);
2663
+ };
2664
+ diagnostics.forEach(checkEntry);
2665
+ return maxLevel;
2666
+ };
2667
+ var buildDiagnosticsTree = (diagnostics) => {
2668
+ const root = [];
2669
+ diagnostics.forEach(({ name, message, level, hardware_id, values }) => {
2670
+ const parts = name.split("/");
2671
+ let currentLevel = root;
2672
+ parts.forEach((part, index) => {
2673
+ if (!part) return;
2674
+ let existingEntry = currentLevel.find((entry) => entry.name === part);
2675
+ if (!existingEntry) {
2676
+ const [baseName, suffix] = part.split(":");
2677
+ const path = parts.slice(0, index + 1).join("/").split(":")[0];
2678
+ existingEntry = {
2679
+ name: index === parts.length - 1 && suffix ? suffix : baseName,
2680
+ path,
2681
+ rawName: path,
2682
+ message: "",
2683
+ severity_level: -1,
2684
+ hardware_id: null,
2685
+ values: null,
2686
+ children: [],
2687
+ icon: null
2688
+ // Initialize icon as null
2689
+ };
2690
+ currentLevel.push(existingEntry);
2691
+ }
2692
+ if (index === parts.length - 1) {
2693
+ existingEntry.message = message || "";
2694
+ existingEntry.severity_level = level ?? -1;
2695
+ existingEntry.hardware_id = hardware_id || null;
2696
+ existingEntry.rawName = name;
2697
+ existingEntry.values = Array.isArray(values) ? values.reduce((acc, { key, value }) => {
2698
+ acc[key] = value;
2699
+ return acc;
2700
+ }, {}) : values && typeof values === "object" ? Object.fromEntries(
2701
+ Object.entries(values).map(([key, value]) => [key, value])
2702
+ ) : {};
2703
+ existingEntry.icon = level === 3 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.HelpCircle, { className: "w-4 h-4 text-blue-500" }) : level === 2 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-4 h-4 text-red-500" }) : level === 1 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "w-4 h-4 text-yellow-500" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { className: "w-4 h-4 text-green-500" });
2704
+ }
2705
+ currentLevel = existingEntry.children;
2706
+ });
2707
+ });
2708
+ return root;
2709
+ };
2710
+ var RosConnectionManager = ({
2711
+ namespace,
2712
+ url,
2713
+ onDiagnosticsUpdate,
2714
+ onConnectionStatusChange,
2715
+ onClearHistory
2716
+ }) => {
2717
+ const staleTimeoutId = React9.useRef(0);
2718
+ const retryTimeoutId = React9.useRef(0);
2719
+ React9.useEffect(() => {
2720
+ if (!url) {
2721
+ console.warn("WebSocket URL is not set correctly. Skipping WebSocket configuration.");
2722
+ return;
2723
+ }
2724
+ console.log(`Creating new connection to ${url} for namespace ${namespace}`);
2725
+ const ros = new Ros({ url });
2726
+ const diagnosticsTopic = new Topic({
2727
+ ros,
2728
+ name: `${namespace}/diagnostics_agg`,
2729
+ messageType: "diagnostic_msgs/DiagnosticArray"
2730
+ });
2731
+ const retryDelay = 3e3;
2732
+ const timeoutDuration = 5e3;
2733
+ let retryConnection = true;
2734
+ const connectToWebSocket = () => {
2735
+ clearTimeout(staleTimeoutId.current);
2736
+ clearTimeout(retryTimeoutId.current);
2737
+ ros.connect(url);
2738
+ ros.on("connection", () => {
2739
+ onClearHistory();
2740
+ console.log("Connected to Foxglove bridge at " + url);
2741
+ onConnectionStatusChange(true);
2742
+ diagnosticsTopic.subscribe((message) => {
2743
+ clearTimeout(staleTimeoutId.current);
2744
+ if (Array.isArray(message.status)) {
2745
+ const diagnosticsTree = buildDiagnosticsTree(
2746
+ message.status.map(({ name, message: msg, level, hardware_id, values }) => ({
2747
+ name,
2748
+ message: msg,
2749
+ level: level !== void 0 ? level : -1,
2750
+ hardware_id,
2751
+ values
2752
+ }))
2753
+ );
2754
+ const overallLevel = calculateOverallLevel(diagnosticsTree);
2755
+ let timestamp = Date.now();
2756
+ if (message.header && message.header.stamp) {
2757
+ const sec = message.header.stamp.sec || 0;
2758
+ const nanosec = message.header.stamp.nanosec || 0;
2759
+ timestamp = sec * 1e3 + Math.round(nanosec / 1e6);
2760
+ } else {
2761
+ console.log("No header.stamp found in message, using current time");
2762
+ }
2763
+ const diagStatus = {
2764
+ timestamp,
2765
+ level: overallLevel,
2766
+ diagnostics: diagnosticsTree
2767
+ };
2768
+ onDiagnosticsUpdate(diagStatus);
2769
+ } else {
2770
+ console.warn("Unexpected diagnostics data format:", message);
2771
+ }
2772
+ staleTimeoutId.current = setTimeout(() => {
2773
+ console.warn("No diagnostics message received for 5 seconds. Clearing stale diagnostics.");
2774
+ onClearHistory();
2775
+ }, timeoutDuration);
2776
+ });
2777
+ console.log(`Subscribed to topic: ${diagnosticsTopic.name}`);
2778
+ });
2779
+ ros.on("error", (error) => {
2780
+ console.error("Error connecting to Foxglove bridge:", error);
2781
+ ros.close();
2782
+ });
2783
+ ros.on("close", () => {
2784
+ onConnectionStatusChange(false);
2785
+ console.log("Connection to Foxglove bridge closed");
2786
+ onClearHistory();
2787
+ clearTimeout(staleTimeoutId.current);
2788
+ clearTimeout(retryTimeoutId.current);
2789
+ if (retryConnection) {
2790
+ console.log("Retrying WebSocket connection...");
2791
+ retryTimeoutId.current = setTimeout(connectToWebSocket, retryDelay);
2792
+ }
2793
+ });
2794
+ };
2795
+ connectToWebSocket();
2796
+ return () => {
2797
+ console.log(`Cleaning up connection for namespace ${namespace}`);
2798
+ diagnosticsTopic.unsubscribe();
2799
+ retryConnection = false;
2800
+ clearTimeout(staleTimeoutId.current);
2801
+ clearTimeout(retryTimeoutId.current);
2802
+ ros.close();
2803
+ };
2804
+ }, [namespace, url, onDiagnosticsUpdate, onConnectionStatusChange, onClearHistory]);
2805
+ return null;
2806
+ };
2807
+
2808
+ // src/components/ROS2Diagnostics/utils/namespaceUtils.ts
2809
+ var sanitizeNamespace = (namespace) => {
2810
+ let sanitizedNamespace = namespace.replace(/[^a-zA-Z0-9_/]/g, "").replace(/_+/g, "_").replace(/\/+/g, "/").replace(/^\d+/, "").replace(/\/\d+/g, "/").replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
2811
+ if (sanitizedNamespace) {
2812
+ sanitizedNamespace = "/" + sanitizedNamespace;
2813
+ }
2814
+ return sanitizedNamespace;
2815
+ };
2816
+ var sameNamespace = (ns1, ns2) => {
2817
+ if (ns1.replace(/^\/|\/$/g, "") !== ns2.replace(/^\/|\/$/g, "")) {
2818
+ return false;
2819
+ }
2820
+ return true;
2821
+ };
2822
+
2823
+ // src/components/ROS2Diagnostics/hooks/useNamespace.ts
2824
+ var NAMESPACE_STORAGE_KEY = "ros2_diag_namespace";
2825
+ var useNamespace = (initialNamespace = "/robot") => {
2826
+ const [namespace, setNamespace] = React9.useState("");
2827
+ const [invalidNamespaceMessage, setInvalidNamespaceMessage] = React9.useState(null);
2828
+ const [manualEntryRequired, setManualEntryRequired] = React9.useState(false);
2829
+ const setManualNamespace = React9.useCallback((ns) => {
2830
+ const ns_temp = sanitizeNamespace(ns);
2831
+ setNamespace(ns_temp);
2832
+ localStorage.setItem(NAMESPACE_STORAGE_KEY, ns_temp);
2833
+ }, []);
2834
+ React9.useEffect(() => {
2835
+ const savedNamespace = localStorage.getItem(NAMESPACE_STORAGE_KEY);
2836
+ if (savedNamespace) {
2837
+ console.log("Using namespace from storage:", savedNamespace);
2838
+ setNamespace(savedNamespace);
2839
+ return;
2840
+ }
2841
+ if (initialNamespace) {
2842
+ const sanitized = sanitizeNamespace(initialNamespace);
2843
+ if (!sameNamespace(initialNamespace, sanitized)) {
2844
+ const message = `Invalid namespace: "${initialNamespace}", using: "${sanitized}"`;
2845
+ console.warn(message);
2846
+ setInvalidNamespaceMessage(message);
2847
+ } else {
2848
+ setInvalidNamespaceMessage(null);
2849
+ }
2850
+ setNamespace(sanitized);
2851
+ } else {
2852
+ setManualEntryRequired(true);
2853
+ }
2854
+ }, [initialNamespace]);
2855
+ return { namespace, setManualNamespace, invalidNamespaceMessage, manualEntryRequired };
2856
+ };
2857
+ var useWebSocketUrl = (customUrl) => {
2858
+ const [url, setUrl] = React9.useState(customUrl || null);
2859
+ React9.useEffect(() => {
2860
+ if (customUrl) {
2861
+ setUrl(customUrl);
2862
+ return;
2863
+ }
2864
+ try {
2865
+ const host = window.location.hostname;
2866
+ if (host) {
2867
+ setUrl(`ws://${host}:8765`);
2868
+ } else {
2869
+ console.warn("Unable to determine the host IP address.");
2870
+ setUrl(`ws://localhost:8765`);
2871
+ }
2872
+ } catch (error) {
2873
+ console.error("Failed to determine WebSocket URL:", error);
2874
+ setUrl(`ws://localhost:8765`);
2875
+ }
2876
+ }, [customUrl]);
2877
+ return url;
2878
+ };
2879
+ var DiagnosticsCapture = ({ namespace }) => {
2880
+ const downloadDiagnostics = () => {
2881
+ console.log("Capture diagnostics for namespace:", namespace);
2882
+ };
2883
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(
2884
+ Button,
2885
+ {
2886
+ variant: "subtle",
2887
+ size: "sm",
2888
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "w-4 h-4" }),
2889
+ onClick: downloadDiagnostics,
2890
+ children: "Export Diagnostics"
2891
+ }
2892
+ ) });
2893
+ };
2894
+ var ManualNamespace = ({
2895
+ setManualNamespace,
2896
+ namespace
2897
+ }) => {
2898
+ const [value, setValue] = React9.useState(namespace);
2899
+ const [unsaved, setUnsaved] = React9.useState(false);
2900
+ const [invalidNamespaceMessage, setInvalidNamespaceMessage] = React9.useState("");
2901
+ const [isError, setIsError] = React9.useState(false);
2902
+ React9.useEffect(() => {
2903
+ const isSame = sameNamespace(namespace, sanitizeNamespace(value));
2904
+ setUnsaved(!isSame);
2905
+ }, [namespace, value]);
2906
+ React9.useEffect(() => {
2907
+ const sanitizedValue = sanitizeNamespace(value);
2908
+ const isSame = sameNamespace(value, sanitizedValue);
2909
+ setIsError(!isSame);
2910
+ setInvalidNamespaceMessage(!isSame ? `Invalid namespace. Legal namespace would be: ${sanitizedValue}` : "");
2911
+ }, [namespace, value]);
2912
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "bg-blue-50 border-l-4 border-blue-500", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4", children: [
2913
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Namespace Configuration" }),
2914
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => e.preventDefault(), children: [
2915
+ /* @__PURE__ */ jsxRuntime.jsx(
2916
+ FormControl,
2917
+ {
2918
+ label: "ROS 2 Namespace",
2919
+ error: isError ? invalidNamespaceMessage : void 0,
2920
+ description: "Enter the namespace for the diagnostics_agg topic",
2921
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2922
+ Input,
2923
+ {
2924
+ value,
2925
+ type: "text",
2926
+ placeholder: "/robot",
2927
+ onChange: (e) => setValue(e.target.value),
2928
+ "aria-label": "Manual Namespace Entry",
2929
+ className: isError ? "border-red-500" : ""
2930
+ }
2931
+ )
2932
+ }
2933
+ ),
2934
+ isError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 mt-3 text-red-700 text-sm", children: [
2935
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-4 h-4 mt-0.5 flex-shrink-0" }),
2936
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: invalidNamespaceMessage })
2937
+ ] }),
2938
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
2939
+ Button,
2940
+ {
2941
+ disabled: !unsaved,
2942
+ onClick: () => {
2943
+ const ns = sanitizeNamespace(value);
2944
+ setManualNamespace(ns);
2945
+ setValue(ns);
2946
+ },
2947
+ variant: "solid",
2948
+ theme: "brand",
2949
+ children: unsaved ? "Apply" : "Applied"
2950
+ }
2951
+ ) })
2952
+ ] })
2953
+ ] }) });
2954
+ };
2955
+ var HISTORY_SIZE = 50;
2956
+ var HistorySelection = ({
2957
+ diagHistory,
2958
+ setDiagStatusDisplay,
2959
+ isPaused,
2960
+ setIsPaused
2961
+ }) => {
2962
+ const [negIndex, setNegIndex] = React9.useState(-1);
2963
+ React9.useEffect(() => {
2964
+ if (!isPaused) {
2965
+ setNegIndex(-1);
2966
+ if (diagHistory.length > 0) {
2967
+ setDiagStatusDisplay(diagHistory[diagHistory.length - 1]);
2968
+ } else {
2969
+ setDiagStatusDisplay(null);
2970
+ }
2971
+ }
2972
+ }, [diagHistory, setDiagStatusDisplay, isPaused]);
2973
+ const getStatusColor = (level) => {
2974
+ if (level >= 2) return "bg-red-500";
2975
+ if (level === 1) return "bg-yellow-500";
2976
+ return "bg-green-500";
2977
+ };
2978
+ const isSelected = (index) => diagHistory.length + negIndex === index;
2979
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
2980
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2981
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Timeline:" }),
2982
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex gap-1 overflow-x-auto", children: [
2983
+ Array.from({ length: Math.max(0, HISTORY_SIZE - diagHistory.length) }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
2984
+ "div",
2985
+ {
2986
+ className: "w-2 h-2 rounded-full bg-gray-300 flex-shrink-0"
2987
+ },
2988
+ `blank-${index}`
2989
+ )),
2990
+ diagHistory.map((diagStatus, index) => /* @__PURE__ */ jsxRuntime.jsx(
2991
+ "button",
2992
+ {
2993
+ onClick: () => {
2994
+ setDiagStatusDisplay(diagStatus);
2995
+ setIsPaused(true);
2996
+ setNegIndex(index - diagHistory.length);
2997
+ },
2998
+ className: `w-2 h-2 rounded-full flex-shrink-0 transition-all ${isSelected(index) ? "w-4 h-4 ring-2 ring-blue-500" : getStatusColor(diagStatus.level)}`,
2999
+ title: new Date(diagStatus.timestamp).toLocaleTimeString(),
3000
+ "aria-label": `Diagnostics snapshot ${index + 1} at ${new Date(diagStatus.timestamp).toLocaleTimeString()}`
3001
+ },
3002
+ `history-${index}`
3003
+ ))
3004
+ ] })
3005
+ ] }),
3006
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs text-gray-600 px-2", children: [
3007
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3008
+ "Oldest: ",
3009
+ diagHistory.length > 0 ? new Date(diagHistory[0].timestamp).toLocaleTimeString() : "N/A"
3010
+ ] }),
3011
+ isPaused && diagHistory.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium text-blue-600", children: [
3012
+ "Selected: ",
3013
+ new Date(diagHistory[diagHistory.length + negIndex].timestamp).toLocaleTimeString()
3014
+ ] }),
3015
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3016
+ "Latest: ",
3017
+ diagHistory.length > 0 ? new Date(diagHistory[diagHistory.length - 1].timestamp).toLocaleTimeString() : "N/A"
3018
+ ] })
3019
+ ] })
3020
+ ] });
3021
+ };
3022
+ var HISTORY_SIZE2 = 30;
3023
+ var useDiagHistory = (isPaused) => {
3024
+ const [diagHistory, setDiagHistory] = React9.useState([]);
3025
+ const isPausedRef = React9.useRef(isPaused);
3026
+ React9.useEffect(() => {
3027
+ isPausedRef.current = isPaused;
3028
+ }, [isPaused]);
3029
+ const clearDiagHistory = React9.useCallback(() => {
3030
+ setDiagHistory([]);
3031
+ }, []);
3032
+ const updateDiagHistory = React9.useCallback((diagStatusLatest) => {
3033
+ if (!isPausedRef.current && diagStatusLatest && diagStatusLatest.diagnostics.length > 0) {
3034
+ setDiagHistory((prevItems) => {
3035
+ const updatedItems = [...prevItems, diagStatusLatest];
3036
+ if (updatedItems.length > HISTORY_SIZE2) {
3037
+ return updatedItems.slice(-HISTORY_SIZE2);
3038
+ }
3039
+ return updatedItems;
3040
+ });
3041
+ }
3042
+ }, [isPausedRef]);
3043
+ return { diagHistory, updateDiagHistory, clearDiagHistory };
3044
+ };
3045
+ var ROS2Diagnostics = ({
3046
+ foxgloveUrl,
3047
+ namespace: initialNamespace = "/robot",
3048
+ onDiagnosticUpdate
3049
+ }) => {
3050
+ const {
3051
+ namespace,
3052
+ setManualNamespace,
3053
+ invalidNamespaceMessage,
3054
+ manualEntryRequired
3055
+ } = useNamespace(initialNamespace);
3056
+ const wsUrl = useWebSocketUrl(foxgloveUrl);
3057
+ const [diagStatusDisplay, setDiagStatusDisplay] = React9.useState(null);
3058
+ const [bridgeConnected, setBridgeConnected] = React9.useState(false);
3059
+ const [selectedRawName, setSelectedRawName] = React9.useState(null);
3060
+ const [isPaused, setIsPaused] = React9.useState(false);
3061
+ const {
3062
+ diagHistory,
3063
+ updateDiagHistory,
3064
+ clearDiagHistory
3065
+ } = useDiagHistory(isPaused);
3066
+ const diagnostics = diagStatusDisplay?.diagnostics || [];
3067
+ const handleDiagnosticsUpdate = (diagStatus) => {
3068
+ setDiagStatusDisplay(diagStatus);
3069
+ updateDiagHistory(diagStatus);
3070
+ onDiagnosticUpdate?.(diagStatus);
3071
+ };
3072
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full gap-4 p-4 bg-white", children: [
3073
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3074
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-gray-900", children: "ROS 2 Diagnostics" }),
3075
+ /* @__PURE__ */ jsxRuntime.jsx(
3076
+ Button,
3077
+ {
3078
+ variant: isPaused ? "solid" : "subtle",
3079
+ theme: "brand",
3080
+ size: "sm",
3081
+ onClick: () => {
3082
+ if (isPaused) clearDiagHistory();
3083
+ setIsPaused(!isPaused);
3084
+ },
3085
+ icon: isPaused ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, { className: "w-4 h-4" }),
3086
+ children: isPaused ? "Resume" : "Pause"
3087
+ }
3088
+ )
3089
+ ] }),
3090
+ /* @__PURE__ */ jsxRuntime.jsx(
3091
+ HistorySelection,
3092
+ {
3093
+ diagHistory,
3094
+ setDiagStatusDisplay,
3095
+ isPaused,
3096
+ setIsPaused
3097
+ }
3098
+ ),
3099
+ invalidNamespaceMessage && /* @__PURE__ */ jsxRuntime.jsx(Alert, { theme: "danger", title: invalidNamespaceMessage }),
3100
+ manualEntryRequired && /* @__PURE__ */ jsxRuntime.jsx(
3101
+ ManualNamespace,
3102
+ {
3103
+ setManualNamespace,
3104
+ namespace
3105
+ }
3106
+ ),
3107
+ /* @__PURE__ */ jsxRuntime.jsx(DiagnosticsCapture, { namespace }),
3108
+ !invalidNamespaceMessage && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3109
+ /* @__PURE__ */ jsxRuntime.jsx(
3110
+ RosConnectionManager,
3111
+ {
3112
+ namespace,
3113
+ url: wsUrl,
3114
+ onDiagnosticsUpdate: handleDiagnosticsUpdate,
3115
+ onConnectionStatusChange: setBridgeConnected,
3116
+ onClearHistory: clearDiagHistory
3117
+ }
3118
+ ),
3119
+ !bridgeConnected && /* @__PURE__ */ jsxRuntime.jsxs(
3120
+ Alert,
3121
+ {
3122
+ theme: "warning",
3123
+ title: "Waiting for connection...",
3124
+ children: [
3125
+ "Attempting to connect to Foxglove bridge at ",
3126
+ wsUrl
3127
+ ]
3128
+ }
3129
+ ),
3130
+ diagnostics.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3131
+ DiagnosticsTable,
3132
+ {
3133
+ diagnostics,
3134
+ setSelectedRawName,
3135
+ variant: "error"
3136
+ }
3137
+ ),
3138
+ diagnostics.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3139
+ DiagnosticsTable,
3140
+ {
3141
+ diagnostics,
3142
+ setSelectedRawName,
3143
+ variant: "warning"
3144
+ }
3145
+ ),
3146
+ /* @__PURE__ */ jsxRuntime.jsx(
3147
+ DiagnosticsTreeTable,
3148
+ {
3149
+ diagnostics,
3150
+ bridgeConnected,
3151
+ selectedRawName,
3152
+ setSelectedRawName
3153
+ }
3154
+ ),
3155
+ diagnostics.length === 0 && bridgeConnected && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-gray-500", children: [
3156
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
3157
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2", children: "Waiting for diagnostic data..." })
3158
+ ] })
3159
+ ] })
3160
+ ] });
3161
+ };
1735
3162
  function useDisclosure(initial = false) {
1736
3163
  const [isOpen, setIsOpen] = React9.useState(initial);
1737
3164
  const open = React9.useCallback(() => setIsOpen(true), []);
@@ -1760,17 +3187,23 @@ exports.Breadcrumbs = Breadcrumbs;
1760
3187
  exports.Button = Button;
1761
3188
  exports.Card = Card;
1762
3189
  exports.Checkbox = Checkbox;
3190
+ exports.CodeBlock = CodeBlock;
1763
3191
  exports.ConnectionIcon = ConnectionIcon;
1764
3192
  exports.ConnectionIndicator = ConnectionIndicator;
1765
3193
  exports.DatePicker = DatePicker;
1766
3194
  exports.Dialog = Dialog;
1767
3195
  exports.Dropdown = Dropdown;
3196
+ exports.EmptyState = EmptyState;
1768
3197
  exports.FileUploader = FileUploader;
1769
3198
  exports.FormControl = FormControl;
1770
3199
  exports.Input = Input;
3200
+ exports.Kbd = Kbd;
1771
3201
  exports.Link = Link;
1772
3202
  exports.MultiSelect = MultiSelect;
3203
+ exports.NumberInput = NumberInput;
3204
+ exports.Pagination = Pagination;
1773
3205
  exports.Progress = Progress;
3206
+ exports.ROS2Diagnostics = ROS2Diagnostics;
1774
3207
  exports.Rating = Rating;
1775
3208
  exports.Select = Select;
1776
3209
  exports.Sidebar = Sidebar;
@@ -1778,9 +3211,11 @@ exports.Slider = Slider;
1778
3211
  exports.Spinner = Spinner;
1779
3212
  exports.StatusBadge = StatusBadge;
1780
3213
  exports.Switch = Switch;
3214
+ exports.Table = Table;
1781
3215
  exports.Tabs = Tabs;
1782
3216
  exports.Textarea = Textarea;
1783
3217
  exports.TimePicker = TimePicker;
3218
+ exports.Timeline = Timeline;
1784
3219
  exports.Toast = Toast;
1785
3220
  exports.ToastContainer = ToastContainer;
1786
3221
  exports.Tooltip = Tooltip;