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 +1440 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +126 -1
- package/dist/index.d.ts +126 -1
- package/dist/index.js +1431 -6
- package/dist/index.js.map +1 -1
- package/package.json +9 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 ${
|
|
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;
|