mywhy-ui 0.2.0 → 0.3.1

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,34 @@
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 eventemitter3 = 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 WebSocket__default = /*#__PURE__*/_interopDefault(WebSocket);
9
16
 
10
- // src/components/Spinner/Spinner.tsx
17
+ var __typeError = (msg) => {
18
+ throw TypeError(msg);
19
+ };
20
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
21
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
22
+ 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);
23
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
24
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
25
+ var __privateWrapper = (obj, member, setter, getter) => ({
26
+ set _(value) {
27
+ __privateSet(obj, member, value);
28
+ },
29
+ get _() {
30
+ return __privateGet(obj, member, getter);
31
+ }
32
+ });
11
33
  var sizeClasses = {
12
34
  xs: "w-3 h-3",
13
35
  sm: "w-4 h-4",
@@ -2138,6 +2160,1004 @@ function ConnectionIcon({ connected, size = "md", className = "" }) {
2138
2160
  }
2139
2161
  );
2140
2162
  }
2163
+ var collectLeafNodes = (entries) => entries.flatMap(
2164
+ (entry) => entry.children.length === 0 ? [entry] : collectLeafNodes(entry.children)
2165
+ );
2166
+ var DiagnosticsTable = ({
2167
+ diagnostics,
2168
+ setSelectedRawName,
2169
+ variant
2170
+ }) => {
2171
+ const levelFilter = (level) => variant === "error" ? level >= 2 : level === 1;
2172
+ const filteredDiagnostics = collectLeafNodes(diagnostics).filter((d) => levelFilter(d.severity_level));
2173
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2174
+ /* @__PURE__ */ jsxRuntime.jsx(
2175
+ Alert,
2176
+ {
2177
+ theme: variant === "error" ? "danger" : "warning",
2178
+ title: variant === "error" ? "Errors" : "Warnings"
2179
+ }
2180
+ ),
2181
+ 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: [
2182
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-gray-50 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2183
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-2 text-left font-semibold text-gray-700", children: "Name" }),
2184
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-2 text-left font-semibold text-gray-700", children: "Message" })
2185
+ ] }) }),
2186
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: filteredDiagnostics.map((diag, index) => /* @__PURE__ */ jsxRuntime.jsxs(
2187
+ "tr",
2188
+ {
2189
+ className: "border-b border-gray-200 hover:bg-blue-50 cursor-pointer transition-colors",
2190
+ onClick: () => setSelectedRawName(diag.rawName),
2191
+ children: [
2192
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
2193
+ diag.icon,
2194
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
2195
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-gray-900", children: diag.name || "N/A" }),
2196
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: diag.path || "N/A" })
2197
+ ] })
2198
+ ] }) }),
2199
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2 text-gray-700", children: diag.message || "N/A" })
2200
+ ]
2201
+ },
2202
+ index
2203
+ )) })
2204
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx(
2205
+ EmptyState,
2206
+ {
2207
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { className: "w-12 h-12 text-green-500" }),
2208
+ title: variant === "error" ? "No Errors" : "No Warnings",
2209
+ description: "All diagnostics are healthy"
2210
+ }
2211
+ )
2212
+ ] });
2213
+ };
2214
+ var DiagnosticsTreeTable = ({
2215
+ diagnostics,
2216
+ bridgeConnected,
2217
+ selectedRawName,
2218
+ setSelectedRawName
2219
+ }) => {
2220
+ const renderTree = (entries, depth = 0) => {
2221
+ return /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-1", children: entries.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
2222
+ /* @__PURE__ */ jsxRuntime.jsxs(
2223
+ "div",
2224
+ {
2225
+ className: "flex items-start gap-2 p-2 hover:bg-gray-100 rounded cursor-pointer transition-colors",
2226
+ style: { paddingLeft: `${depth * 1.5}rem` },
2227
+ onClick: () => setSelectedRawName(entry.rawName),
2228
+ children: [
2229
+ entry.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 mt-0.5", children: entry.icon }),
2230
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
2231
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-gray-900 truncate", children: entry.name }),
2232
+ entry.message && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-600 truncate", children: entry.message })
2233
+ ] })
2234
+ ]
2235
+ }
2236
+ ),
2237
+ entry.children.length > 0 && renderTree(entry.children, depth + 1)
2238
+ ] }, entry.rawName)) });
2239
+ };
2240
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "bg-white", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4", children: [
2241
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Diagnostics Tree" }),
2242
+ 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" })
2243
+ ] }) });
2244
+ };
2245
+ var _client, _connecting, _messageReaders, _messageWriters, _channelsById, _channelsByName, _servicesById, _servicesByName, _publisherIdsWithCount, _subscriptionIdsWithCount, _callId, _paramId, _Impl_instances, getChannel_fn, getService_fn, getMessageReader_fn, getMessageWriter_fn;
2246
+ var Impl = class {
2247
+ constructor(url) {
2248
+ __privateAdd(this, _Impl_instances);
2249
+ this.emitter = new eventemitter3.EventEmitter();
2250
+ __privateAdd(this, _client);
2251
+ __privateAdd(this, _connecting);
2252
+ // Message Readers / Writers
2253
+ __privateAdd(this, _messageReaders, /* @__PURE__ */ new Map());
2254
+ __privateAdd(this, _messageWriters, /* @__PURE__ */ new Map());
2255
+ // Channels
2256
+ __privateAdd(this, _channelsById, /* @__PURE__ */ new Map());
2257
+ __privateAdd(this, _channelsByName, /* @__PURE__ */ new Map());
2258
+ // Services
2259
+ __privateAdd(this, _servicesById, /* @__PURE__ */ new Map());
2260
+ __privateAdd(this, _servicesByName, /* @__PURE__ */ new Map());
2261
+ __privateAdd(this, _publisherIdsWithCount, /* @__PURE__ */ new Map());
2262
+ __privateAdd(this, _subscriptionIdsWithCount, /* @__PURE__ */ new Map());
2263
+ __privateAdd(this, _callId, 0);
2264
+ __privateAdd(this, _paramId, 0);
2265
+ __privateSet(this, _client, new wsProtocol.FoxgloveClient({
2266
+ // TODO: "foxglove.sdk.v1" was added here manually because Foxglove switched the foxglove-bridge
2267
+ // package for Jazzy from ros-foxglove-bridge repo (v0.8.5) which used "foxglove.websocket.v1"
2268
+ // to their new foxglove-sdk version (v3.2.x) which uses "foxglove.sdk.v1" around September 2025,
2269
+ // but the @foxglove/ws-protocol npm package has not yet been updated to include this new
2270
+ // subprotocol. Once that package is updated, or a new alternative is released, "foxglove.sdk.v1"
2271
+ // may be removed from here and adjusted accordingly. As far as I can tell, it seems that
2272
+ // the underlying protocols are compatible for our use case, and is effectively only a name change.
2273
+ ws: new WebSocket__default.default(url, ["foxglove.sdk.v1", wsProtocol.FoxgloveClient.SUPPORTED_SUBPROTOCOL])
2274
+ }));
2275
+ const open = new Promise((resolve) => {
2276
+ __privateGet(this, _client).on("open", resolve);
2277
+ });
2278
+ __privateGet(this, _client).on("close", (event) => {
2279
+ this.emitter.emit("close", event);
2280
+ });
2281
+ __privateGet(this, _client).on("error", (error) => {
2282
+ this.emitter.emit("error", error ?? new Error("WebSocket error"));
2283
+ });
2284
+ __privateGet(this, _client).on("advertise", (channels) => {
2285
+ for (const channel of channels) {
2286
+ __privateGet(this, _channelsById).set(channel.id, channel);
2287
+ __privateGet(this, _channelsByName).set(channel.topic, channel);
2288
+ }
2289
+ });
2290
+ __privateGet(this, _client).on("unadvertise", (channelIds) => {
2291
+ for (const channelId of channelIds) {
2292
+ const channel = __privateGet(this, _channelsById).get(channelId);
2293
+ if (channel) {
2294
+ __privateGet(this, _channelsById).delete(channel.id);
2295
+ __privateGet(this, _channelsByName).delete(channel.topic);
2296
+ }
2297
+ }
2298
+ });
2299
+ __privateGet(this, _client).on("advertiseServices", (services) => {
2300
+ for (const service of services) {
2301
+ __privateGet(this, _servicesById).set(service.id, service);
2302
+ __privateGet(this, _servicesByName).set(service.name, service);
2303
+ }
2304
+ });
2305
+ __privateGet(this, _client).on("unadvertiseServices", (serviceIds) => {
2306
+ for (const serviceId of serviceIds) {
2307
+ const service = __privateGet(this, _servicesById).get(serviceId);
2308
+ if (service) {
2309
+ __privateGet(this, _servicesById).delete(service.id);
2310
+ __privateGet(this, _servicesByName).delete(service.name);
2311
+ }
2312
+ }
2313
+ });
2314
+ __privateSet(this, _connecting, new Promise((resolve) => {
2315
+ Promise.all([open]).then(() => {
2316
+ this.emitter.emit("connection");
2317
+ resolve();
2318
+ });
2319
+ }));
2320
+ }
2321
+ close() {
2322
+ __privateGet(this, _client).close();
2323
+ }
2324
+ getTopics() {
2325
+ return {
2326
+ topics: [...__privateGet(this, _channelsByName).keys()],
2327
+ types: [...__privateGet(this, _channelsByName).values()].map((x) => x.schemaName)
2328
+ };
2329
+ }
2330
+ async getServices() {
2331
+ await __privateGet(this, _connecting);
2332
+ return new Promise((resolve) => {
2333
+ const listener = (event) => {
2334
+ __privateGet(this, _client).off("connectionGraphUpdate", listener);
2335
+ __privateGet(this, _client).unsubscribeConnectionGraph();
2336
+ resolve(event.advertisedServices.map((service) => service.name));
2337
+ };
2338
+ __privateGet(this, _client).on("connectionGraphUpdate", listener);
2339
+ __privateGet(this, _client).subscribeConnectionGraph();
2340
+ });
2341
+ }
2342
+ getTopicType(topic) {
2343
+ return __privateGet(this, _channelsByName).get(topic)?.schemaName;
2344
+ }
2345
+ getServiceType(service) {
2346
+ return __privateGet(this, _servicesByName).get(service)?.type;
2347
+ }
2348
+ async createPublisher(name, messageType) {
2349
+ await __privateGet(this, _connecting);
2350
+ const channel = __privateMethod(this, _Impl_instances, getChannel_fn).call(this, name);
2351
+ const publisherId = (() => {
2352
+ const idWithCount = __privateGet(this, _publisherIdsWithCount).get(name);
2353
+ if (idWithCount) {
2354
+ idWithCount.count++;
2355
+ return idWithCount.id;
2356
+ }
2357
+ const publisherId2 = __privateGet(this, _client).advertise({
2358
+ topic: name,
2359
+ encoding: "cdr",
2360
+ schemaName: messageType
2361
+ });
2362
+ __privateGet(this, _publisherIdsWithCount).set(name, { id: publisherId2, count: 1 });
2363
+ return publisherId2;
2364
+ })();
2365
+ const writer = __privateMethod(this, _Impl_instances, getMessageWriter_fn).call(this, await channel);
2366
+ return {
2367
+ publish: (message) => {
2368
+ __privateGet(this, _client).sendMessage(publisherId, writer.writeMessage(message));
2369
+ },
2370
+ unadvertise: () => {
2371
+ const idWithCount = __privateGet(this, _publisherIdsWithCount).get(name);
2372
+ if (idWithCount) {
2373
+ idWithCount.count--;
2374
+ if (idWithCount.count === 0) {
2375
+ __privateGet(this, _publisherIdsWithCount).delete(name);
2376
+ __privateGet(this, _client).unadvertise(publisherId);
2377
+ }
2378
+ }
2379
+ }
2380
+ };
2381
+ }
2382
+ async createSubscription(name, callback) {
2383
+ await __privateGet(this, _connecting);
2384
+ const channel = await __privateMethod(this, _Impl_instances, getChannel_fn).call(this, name);
2385
+ const subscriptionId = (() => {
2386
+ const idWithCount = __privateGet(this, _subscriptionIdsWithCount).get(name);
2387
+ if (idWithCount) {
2388
+ idWithCount.count++;
2389
+ return idWithCount.id;
2390
+ }
2391
+ const subscriptionId2 = __privateGet(this, _client).subscribe(channel.id);
2392
+ __privateGet(this, _subscriptionIdsWithCount).set(name, {
2393
+ id: subscriptionId2,
2394
+ count: 1
2395
+ });
2396
+ return subscriptionId2;
2397
+ })();
2398
+ const reader = __privateMethod(this, _Impl_instances, getMessageReader_fn).call(this, channel);
2399
+ const listener = (event) => {
2400
+ if (event.subscriptionId === subscriptionId) {
2401
+ callback(reader.readMessage(event.data));
2402
+ }
2403
+ };
2404
+ __privateGet(this, _client).on("message", listener);
2405
+ return {
2406
+ unsubscribe: () => {
2407
+ __privateGet(this, _client).off("message", listener);
2408
+ const idWithCount = __privateGet(this, _subscriptionIdsWithCount).get(name);
2409
+ if (idWithCount) {
2410
+ idWithCount.count--;
2411
+ if (idWithCount.count === 0) {
2412
+ __privateGet(this, _subscriptionIdsWithCount).delete(name);
2413
+ __privateGet(this, _client).unsubscribe(subscriptionId);
2414
+ }
2415
+ }
2416
+ }
2417
+ };
2418
+ }
2419
+ async sendServiceRequest(name, request) {
2420
+ await __privateGet(this, _connecting);
2421
+ const service = await __privateMethod(this, _Impl_instances, getService_fn).call(this, name);
2422
+ const writer = __privateMethod(this, _Impl_instances, getMessageWriter_fn).call(this, service);
2423
+ const reader = __privateMethod(this, _Impl_instances, getMessageReader_fn).call(this, service);
2424
+ const callId = __privateWrapper(this, _callId)._++;
2425
+ return new Promise((resolve) => {
2426
+ const listener = (event) => {
2427
+ if (event.serviceId === service.id && event.callId === callId) {
2428
+ __privateGet(this, _client).off("serviceCallResponse", listener);
2429
+ resolve(reader.readMessage(event.data));
2430
+ }
2431
+ };
2432
+ __privateGet(this, _client).on("serviceCallResponse", listener);
2433
+ __privateGet(this, _client).sendServiceCallRequest({
2434
+ serviceId: service.id,
2435
+ callId,
2436
+ encoding: "cdr",
2437
+ data: new DataView(writer.writeMessage(request).buffer)
2438
+ });
2439
+ });
2440
+ }
2441
+ async getParameter(name) {
2442
+ await __privateGet(this, _connecting);
2443
+ const paramId = (__privateWrapper(this, _paramId)._++).toString();
2444
+ return new Promise((resolve) => {
2445
+ const listener = (event) => {
2446
+ if (event.parameters[0]?.name === name && event.id === paramId) {
2447
+ __privateGet(this, _client).off("parameterValues", listener);
2448
+ resolve(event.parameters[0].value);
2449
+ }
2450
+ };
2451
+ __privateGet(this, _client).on("parameterValues", listener);
2452
+ __privateGet(this, _client).getParameters([name], paramId);
2453
+ });
2454
+ }
2455
+ async setParameter(name, value) {
2456
+ await __privateGet(this, _connecting);
2457
+ const paramId = (__privateWrapper(this, _paramId)._++).toString();
2458
+ return new Promise((resolve) => {
2459
+ const listener = (event) => {
2460
+ if (event.parameters[0]?.name === name && event.id === paramId) {
2461
+ __privateGet(this, _client).off("parameterValues", listener);
2462
+ resolve(event.parameters[0]);
2463
+ }
2464
+ };
2465
+ __privateGet(this, _client).on("parameterValues", listener);
2466
+ __privateGet(this, _client).setParameters([{ name, value }], paramId);
2467
+ });
2468
+ }
2469
+ };
2470
+ _client = new WeakMap();
2471
+ _connecting = new WeakMap();
2472
+ _messageReaders = new WeakMap();
2473
+ _messageWriters = new WeakMap();
2474
+ _channelsById = new WeakMap();
2475
+ _channelsByName = new WeakMap();
2476
+ _servicesById = new WeakMap();
2477
+ _servicesByName = new WeakMap();
2478
+ _publisherIdsWithCount = new WeakMap();
2479
+ _subscriptionIdsWithCount = new WeakMap();
2480
+ _callId = new WeakMap();
2481
+ _paramId = new WeakMap();
2482
+ _Impl_instances = new WeakSet();
2483
+ getChannel_fn = async function(name) {
2484
+ await __privateGet(this, _connecting);
2485
+ return __privateGet(this, _channelsByName).get(name) ?? await new Promise((resolve) => {
2486
+ const listener = (channels) => {
2487
+ const channel = channels.find((channel2) => channel2.topic === name);
2488
+ if (channel) {
2489
+ __privateGet(this, _client).off("advertise", listener);
2490
+ resolve(channel);
2491
+ }
2492
+ };
2493
+ __privateGet(this, _client).on("advertise", listener);
2494
+ });
2495
+ };
2496
+ getService_fn = async function(name) {
2497
+ await __privateGet(this, _connecting);
2498
+ return __privateGet(this, _servicesByName).get(name) ?? await new Promise((resolve) => {
2499
+ const listener = (services) => {
2500
+ const service = services.find((channel) => channel.name === name);
2501
+ if (service) {
2502
+ __privateGet(this, _client).off("advertiseServices", listener);
2503
+ resolve(service);
2504
+ }
2505
+ };
2506
+ __privateGet(this, _client).on("advertiseServices", listener);
2507
+ });
2508
+ };
2509
+ getMessageReader_fn = function(channelOrService) {
2510
+ const name = "schemaName" in channelOrService ? channelOrService.schemaName : channelOrService.type;
2511
+ const schemaEncoding = "schemaEncoding" in channelOrService ? channelOrService.schemaEncoding : void 0;
2512
+ const schema = "schema" in channelOrService ? channelOrService.schema : channelOrService.responseSchema;
2513
+ return __privateGet(this, _messageReaders).get(name) ?? (() => {
2514
+ const reader = new rosmsg2Serialization.MessageReader(
2515
+ schemaEncoding === "ros2idl" ? rosmsg.parseRos2idl(schema) : rosmsg.parse(schema, { ros2: true })
2516
+ );
2517
+ __privateGet(this, _messageReaders).set(name, reader);
2518
+ return reader;
2519
+ })();
2520
+ };
2521
+ getMessageWriter_fn = function(channelOrService) {
2522
+ const name = "schemaName" in channelOrService ? channelOrService.schemaName : channelOrService.type;
2523
+ const schemaEncoding = "schemaEncoding" in channelOrService ? channelOrService.schemaEncoding : void 0;
2524
+ const schema = "schema" in channelOrService ? channelOrService.schema : channelOrService.requestSchema;
2525
+ return __privateGet(this, _messageWriters).get(name) ?? (() => {
2526
+ const writer = new rosmsg2Serialization.MessageWriter(
2527
+ schemaEncoding === "ros2idl" ? rosmsg.parseRos2idl(schema) : rosmsg.parse(schema, { ros2: true })
2528
+ );
2529
+ __privateGet(this, _messageWriters).set(name, writer);
2530
+ return writer;
2531
+ })();
2532
+ };
2533
+
2534
+ // src/components/ROS2Diagnostics/roslib/Ros.ts
2535
+ var _rosImpl;
2536
+ var Ros = class {
2537
+ constructor(options) {
2538
+ this.options = options;
2539
+ __privateAdd(this, _rosImpl);
2540
+ if (options.url) {
2541
+ this.connect(options.url);
2542
+ }
2543
+ }
2544
+ /** @internal */
2545
+ get rosImpl() {
2546
+ return __privateGet(this, _rosImpl);
2547
+ }
2548
+ on(event, fn) {
2549
+ this.rosImpl?.emitter.on(event, fn);
2550
+ return this;
2551
+ }
2552
+ off(event, fn) {
2553
+ this.rosImpl?.emitter.off(event, fn);
2554
+ return this;
2555
+ }
2556
+ connect(url) {
2557
+ __privateSet(this, _rosImpl, new Impl(url));
2558
+ }
2559
+ close() {
2560
+ this.rosImpl?.close();
2561
+ __privateSet(this, _rosImpl, void 0);
2562
+ }
2563
+ getTopics(callback, failedCallback) {
2564
+ const topics = this.rosImpl?.getTopics();
2565
+ if (topics) {
2566
+ callback(topics);
2567
+ } else if (failedCallback) {
2568
+ failedCallback("Error: getTopics");
2569
+ }
2570
+ }
2571
+ getServices(callback, failedCallback) {
2572
+ this.rosImpl?.getServices().then(callback).catch(failedCallback);
2573
+ }
2574
+ getTopicType(topic, callback, failedCallback) {
2575
+ const topicType = this.rosImpl?.getTopicType(topic);
2576
+ if (topicType) {
2577
+ callback(topicType);
2578
+ } else if (failedCallback) {
2579
+ failedCallback("Error: getTopicType");
2580
+ }
2581
+ }
2582
+ getServiceType(service, callback, failedCallback) {
2583
+ const serviceType = this.rosImpl?.getServiceType(service);
2584
+ if (serviceType) {
2585
+ callback(serviceType);
2586
+ } else if (failedCallback) {
2587
+ failedCallback("Error: getServiceType");
2588
+ }
2589
+ }
2590
+ };
2591
+ _rosImpl = new WeakMap();
2592
+
2593
+ // src/components/ROS2Diagnostics/roslib/Topic.ts
2594
+ var _ros, _name, _messageType, _publisher, _subscriptions;
2595
+ var Topic = class {
2596
+ constructor(options) {
2597
+ this.options = options;
2598
+ __privateAdd(this, _ros);
2599
+ __privateAdd(this, _name);
2600
+ __privateAdd(this, _messageType);
2601
+ __privateAdd(this, _publisher);
2602
+ __privateAdd(this, _subscriptions, /* @__PURE__ */ new Map());
2603
+ __privateSet(this, _ros, options.ros);
2604
+ __privateSet(this, _name, options.name);
2605
+ __privateSet(this, _messageType, options.messageType);
2606
+ }
2607
+ get name() {
2608
+ return __privateGet(this, _name);
2609
+ }
2610
+ get messageType() {
2611
+ return __privateGet(this, _messageType);
2612
+ }
2613
+ publish(message) {
2614
+ if (!__privateGet(this, _publisher)) {
2615
+ this.advertise();
2616
+ }
2617
+ __privateGet(this, _publisher)?.then((publisher) => {
2618
+ publisher.publish(message);
2619
+ });
2620
+ }
2621
+ subscribe(callback) {
2622
+ __privateGet(this, _ros).rosImpl?.createSubscription(this.name, callback).then((subscription) => {
2623
+ __privateGet(this, _subscriptions).set(callback, subscription);
2624
+ });
2625
+ }
2626
+ unsubscribe(callback) {
2627
+ if (callback) {
2628
+ __privateGet(this, _subscriptions).get(callback)?.unsubscribe();
2629
+ __privateGet(this, _subscriptions).delete(callback);
2630
+ } else {
2631
+ for (const subscription of __privateGet(this, _subscriptions).values()) {
2632
+ subscription.unsubscribe();
2633
+ }
2634
+ __privateGet(this, _subscriptions).clear();
2635
+ }
2636
+ }
2637
+ advertise() {
2638
+ __privateSet(this, _publisher, __privateGet(this, _ros).rosImpl?.createPublisher(
2639
+ this.name,
2640
+ this.messageType
2641
+ ));
2642
+ }
2643
+ unadvertise() {
2644
+ __privateGet(this, _publisher)?.then((publisher) => {
2645
+ publisher.unadvertise();
2646
+ __privateSet(this, _publisher, void 0);
2647
+ });
2648
+ }
2649
+ };
2650
+ _ros = new WeakMap();
2651
+ _name = new WeakMap();
2652
+ _messageType = new WeakMap();
2653
+ _publisher = new WeakMap();
2654
+ _subscriptions = new WeakMap();
2655
+ var calculateOverallLevel = (diagnostics) => {
2656
+ let maxLevel = 0;
2657
+ const checkEntry = (entry) => {
2658
+ if (entry.severity_level > maxLevel) {
2659
+ maxLevel = entry.severity_level;
2660
+ }
2661
+ entry.children.forEach(checkEntry);
2662
+ };
2663
+ diagnostics.forEach(checkEntry);
2664
+ return maxLevel;
2665
+ };
2666
+ var buildDiagnosticsTree = (diagnostics) => {
2667
+ const root = [];
2668
+ diagnostics.forEach(({ name, message, level, hardware_id, values }) => {
2669
+ const parts = name.split("/");
2670
+ let currentLevel = root;
2671
+ parts.forEach((part, index) => {
2672
+ if (!part) return;
2673
+ let existingEntry = currentLevel.find((entry) => entry.name === part);
2674
+ if (!existingEntry) {
2675
+ const [baseName, suffix] = part.split(":");
2676
+ const path = parts.slice(0, index + 1).join("/").split(":")[0];
2677
+ existingEntry = {
2678
+ name: index === parts.length - 1 && suffix ? suffix : baseName,
2679
+ path,
2680
+ rawName: path,
2681
+ message: "",
2682
+ severity_level: -1,
2683
+ hardware_id: null,
2684
+ values: null,
2685
+ children: [],
2686
+ icon: null
2687
+ // Initialize icon as null
2688
+ };
2689
+ currentLevel.push(existingEntry);
2690
+ }
2691
+ if (index === parts.length - 1) {
2692
+ existingEntry.message = message || "";
2693
+ existingEntry.severity_level = level ?? -1;
2694
+ existingEntry.hardware_id = hardware_id || null;
2695
+ existingEntry.rawName = name;
2696
+ existingEntry.values = Array.isArray(values) ? values.reduce((acc, { key, value }) => {
2697
+ acc[key] = value;
2698
+ return acc;
2699
+ }, {}) : values && typeof values === "object" ? Object.fromEntries(
2700
+ Object.entries(values).map(([key, value]) => [key, value])
2701
+ ) : {};
2702
+ 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" });
2703
+ }
2704
+ currentLevel = existingEntry.children;
2705
+ });
2706
+ });
2707
+ return root;
2708
+ };
2709
+ var RosConnectionManager = ({
2710
+ namespace,
2711
+ url,
2712
+ onDiagnosticsUpdate,
2713
+ onConnectionStatusChange,
2714
+ onClearHistory
2715
+ }) => {
2716
+ const staleTimeoutId = React9.useRef(0);
2717
+ const retryTimeoutId = React9.useRef(0);
2718
+ React9.useEffect(() => {
2719
+ if (!url) {
2720
+ console.warn("WebSocket URL is not set correctly. Skipping WebSocket configuration.");
2721
+ return;
2722
+ }
2723
+ console.log(`Creating new connection to ${url} for namespace ${namespace}`);
2724
+ const ros = new Ros({ url });
2725
+ const diagnosticsTopic = new Topic({
2726
+ ros,
2727
+ name: `${namespace}/diagnostics_agg`,
2728
+ messageType: "diagnostic_msgs/DiagnosticArray"
2729
+ });
2730
+ const retryDelay = 3e3;
2731
+ const timeoutDuration = 5e3;
2732
+ let retryConnection = true;
2733
+ const connectToWebSocket = () => {
2734
+ clearTimeout(staleTimeoutId.current);
2735
+ clearTimeout(retryTimeoutId.current);
2736
+ ros.connect(url);
2737
+ ros.on("connection", () => {
2738
+ onClearHistory();
2739
+ console.log("Connected to Foxglove bridge at " + url);
2740
+ onConnectionStatusChange(true);
2741
+ diagnosticsTopic.subscribe((message) => {
2742
+ clearTimeout(staleTimeoutId.current);
2743
+ if (Array.isArray(message.status)) {
2744
+ const diagnosticsTree = buildDiagnosticsTree(
2745
+ message.status.map(({ name, message: msg, level, hardware_id, values }) => ({
2746
+ name,
2747
+ message: msg,
2748
+ level: level !== void 0 ? level : -1,
2749
+ hardware_id,
2750
+ values
2751
+ }))
2752
+ );
2753
+ const overallLevel = calculateOverallLevel(diagnosticsTree);
2754
+ let timestamp = Date.now();
2755
+ if (message.header && message.header.stamp) {
2756
+ const sec = message.header.stamp.sec || 0;
2757
+ const nanosec = message.header.stamp.nanosec || 0;
2758
+ timestamp = sec * 1e3 + Math.round(nanosec / 1e6);
2759
+ } else {
2760
+ console.log("No header.stamp found in message, using current time");
2761
+ }
2762
+ const diagStatus = {
2763
+ timestamp,
2764
+ level: overallLevel,
2765
+ diagnostics: diagnosticsTree
2766
+ };
2767
+ onDiagnosticsUpdate(diagStatus);
2768
+ } else {
2769
+ console.warn("Unexpected diagnostics data format:", message);
2770
+ }
2771
+ staleTimeoutId.current = setTimeout(() => {
2772
+ console.warn("No diagnostics message received for 5 seconds. Clearing stale diagnostics.");
2773
+ onClearHistory();
2774
+ }, timeoutDuration);
2775
+ });
2776
+ console.log(`Subscribed to topic: ${diagnosticsTopic.name}`);
2777
+ });
2778
+ ros.on("error", (error) => {
2779
+ console.error("Error connecting to Foxglove bridge:", error);
2780
+ ros.close();
2781
+ });
2782
+ ros.on("close", () => {
2783
+ onConnectionStatusChange(false);
2784
+ console.log("Connection to Foxglove bridge closed");
2785
+ onClearHistory();
2786
+ clearTimeout(staleTimeoutId.current);
2787
+ clearTimeout(retryTimeoutId.current);
2788
+ if (retryConnection) {
2789
+ console.log("Retrying WebSocket connection...");
2790
+ retryTimeoutId.current = setTimeout(connectToWebSocket, retryDelay);
2791
+ }
2792
+ });
2793
+ };
2794
+ connectToWebSocket();
2795
+ return () => {
2796
+ console.log(`Cleaning up connection for namespace ${namespace}`);
2797
+ diagnosticsTopic.unsubscribe();
2798
+ retryConnection = false;
2799
+ clearTimeout(staleTimeoutId.current);
2800
+ clearTimeout(retryTimeoutId.current);
2801
+ ros.close();
2802
+ };
2803
+ }, [namespace, url, onDiagnosticsUpdate, onConnectionStatusChange, onClearHistory]);
2804
+ return null;
2805
+ };
2806
+
2807
+ // src/components/ROS2Diagnostics/utils/namespaceUtils.ts
2808
+ var sanitizeNamespace = (namespace) => {
2809
+ let sanitizedNamespace = namespace.replace(/[^a-zA-Z0-9_/]/g, "").replace(/_+/g, "_").replace(/\/+/g, "/").replace(/^\d+/, "").replace(/\/\d+/g, "/").replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
2810
+ if (sanitizedNamespace) {
2811
+ sanitizedNamespace = "/" + sanitizedNamespace;
2812
+ }
2813
+ return sanitizedNamespace;
2814
+ };
2815
+ var sameNamespace = (ns1, ns2) => {
2816
+ if (ns1.replace(/^\/|\/$/g, "") !== ns2.replace(/^\/|\/$/g, "")) {
2817
+ return false;
2818
+ }
2819
+ return true;
2820
+ };
2821
+
2822
+ // src/components/ROS2Diagnostics/hooks/useNamespace.ts
2823
+ var NAMESPACE_STORAGE_KEY = "ros2_diag_namespace";
2824
+ var useNamespace = (initialNamespace = "/robot") => {
2825
+ const [namespace, setNamespace] = React9.useState("");
2826
+ const [invalidNamespaceMessage, setInvalidNamespaceMessage] = React9.useState(null);
2827
+ const [manualEntryRequired, setManualEntryRequired] = React9.useState(false);
2828
+ const setManualNamespace = React9.useCallback((ns) => {
2829
+ const ns_temp = sanitizeNamespace(ns);
2830
+ setNamespace(ns_temp);
2831
+ localStorage.setItem(NAMESPACE_STORAGE_KEY, ns_temp);
2832
+ }, []);
2833
+ React9.useEffect(() => {
2834
+ const savedNamespace = localStorage.getItem(NAMESPACE_STORAGE_KEY);
2835
+ if (savedNamespace) {
2836
+ console.log("Using namespace from storage:", savedNamespace);
2837
+ setNamespace(savedNamespace);
2838
+ return;
2839
+ }
2840
+ if (initialNamespace) {
2841
+ const sanitized = sanitizeNamespace(initialNamespace);
2842
+ if (!sameNamespace(initialNamespace, sanitized)) {
2843
+ const message = `Invalid namespace: "${initialNamespace}", using: "${sanitized}"`;
2844
+ console.warn(message);
2845
+ setInvalidNamespaceMessage(message);
2846
+ } else {
2847
+ setInvalidNamespaceMessage(null);
2848
+ }
2849
+ setNamespace(sanitized);
2850
+ } else {
2851
+ setManualEntryRequired(true);
2852
+ }
2853
+ }, [initialNamespace]);
2854
+ return { namespace, setManualNamespace, invalidNamespaceMessage, manualEntryRequired };
2855
+ };
2856
+ var useWebSocketUrl = (customUrl) => {
2857
+ const [url, setUrl] = React9.useState(customUrl || null);
2858
+ React9.useEffect(() => {
2859
+ if (customUrl) {
2860
+ setUrl(customUrl);
2861
+ return;
2862
+ }
2863
+ try {
2864
+ const host = window.location.hostname;
2865
+ if (host) {
2866
+ setUrl(`ws://${host}:8765`);
2867
+ } else {
2868
+ console.warn("Unable to determine the host IP address.");
2869
+ setUrl(`ws://localhost:8765`);
2870
+ }
2871
+ } catch (error) {
2872
+ console.error("Failed to determine WebSocket URL:", error);
2873
+ setUrl(`ws://localhost:8765`);
2874
+ }
2875
+ }, [customUrl]);
2876
+ return url;
2877
+ };
2878
+ var DiagnosticsCapture = ({ namespace }) => {
2879
+ const downloadDiagnostics = () => {
2880
+ console.log("Capture diagnostics for namespace:", namespace);
2881
+ };
2882
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(
2883
+ Button,
2884
+ {
2885
+ variant: "subtle",
2886
+ size: "sm",
2887
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "w-4 h-4" }),
2888
+ onClick: downloadDiagnostics,
2889
+ children: "Export Diagnostics"
2890
+ }
2891
+ ) });
2892
+ };
2893
+ var ManualNamespace = ({
2894
+ setManualNamespace,
2895
+ namespace
2896
+ }) => {
2897
+ const [value, setValue] = React9.useState(namespace);
2898
+ const [unsaved, setUnsaved] = React9.useState(false);
2899
+ const [invalidNamespaceMessage, setInvalidNamespaceMessage] = React9.useState("");
2900
+ const [isError, setIsError] = React9.useState(false);
2901
+ React9.useEffect(() => {
2902
+ const isSame = sameNamespace(namespace, sanitizeNamespace(value));
2903
+ setUnsaved(!isSame);
2904
+ }, [namespace, value]);
2905
+ React9.useEffect(() => {
2906
+ const sanitizedValue = sanitizeNamespace(value);
2907
+ const isSame = sameNamespace(value, sanitizedValue);
2908
+ setIsError(!isSame);
2909
+ setInvalidNamespaceMessage(!isSame ? `Invalid namespace. Legal namespace would be: ${sanitizedValue}` : "");
2910
+ }, [namespace, value]);
2911
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "bg-blue-50 border-l-4 border-blue-500", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4", children: [
2912
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Namespace Configuration" }),
2913
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => e.preventDefault(), children: [
2914
+ /* @__PURE__ */ jsxRuntime.jsx(
2915
+ FormControl,
2916
+ {
2917
+ label: "ROS 2 Namespace",
2918
+ error: isError ? invalidNamespaceMessage : void 0,
2919
+ description: "Enter the namespace for the diagnostics_agg topic",
2920
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2921
+ Input,
2922
+ {
2923
+ value,
2924
+ type: "text",
2925
+ placeholder: "/robot",
2926
+ onChange: (e) => setValue(e.target.value),
2927
+ "aria-label": "Manual Namespace Entry",
2928
+ className: isError ? "border-red-500" : ""
2929
+ }
2930
+ )
2931
+ }
2932
+ ),
2933
+ isError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 mt-3 text-red-700 text-sm", children: [
2934
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-4 h-4 mt-0.5 flex-shrink-0" }),
2935
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: invalidNamespaceMessage })
2936
+ ] }),
2937
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
2938
+ Button,
2939
+ {
2940
+ disabled: !unsaved,
2941
+ onClick: () => {
2942
+ const ns = sanitizeNamespace(value);
2943
+ setManualNamespace(ns);
2944
+ setValue(ns);
2945
+ },
2946
+ variant: "solid",
2947
+ theme: "brand",
2948
+ children: unsaved ? "Apply" : "Applied"
2949
+ }
2950
+ ) })
2951
+ ] })
2952
+ ] }) });
2953
+ };
2954
+ var HISTORY_SIZE = 50;
2955
+ var HistorySelection = ({
2956
+ diagHistory,
2957
+ setDiagStatusDisplay,
2958
+ isPaused,
2959
+ setIsPaused
2960
+ }) => {
2961
+ const [negIndex, setNegIndex] = React9.useState(-1);
2962
+ React9.useEffect(() => {
2963
+ if (!isPaused) {
2964
+ setNegIndex(-1);
2965
+ if (diagHistory.length > 0) {
2966
+ setDiagStatusDisplay(diagHistory[diagHistory.length - 1]);
2967
+ } else {
2968
+ setDiagStatusDisplay(null);
2969
+ }
2970
+ }
2971
+ }, [diagHistory, setDiagStatusDisplay, isPaused]);
2972
+ const getStatusColor = (level) => {
2973
+ if (level >= 2) return "bg-red-500";
2974
+ if (level === 1) return "bg-yellow-500";
2975
+ return "bg-green-500";
2976
+ };
2977
+ const isSelected = (index) => diagHistory.length + negIndex === index;
2978
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
2979
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2980
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Timeline:" }),
2981
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex gap-1 overflow-x-auto", children: [
2982
+ Array.from({ length: Math.max(0, HISTORY_SIZE - diagHistory.length) }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
2983
+ "div",
2984
+ {
2985
+ className: "w-2 h-2 rounded-full bg-gray-300 flex-shrink-0"
2986
+ },
2987
+ `blank-${index}`
2988
+ )),
2989
+ diagHistory.map((diagStatus, index) => /* @__PURE__ */ jsxRuntime.jsx(
2990
+ "button",
2991
+ {
2992
+ onClick: () => {
2993
+ setDiagStatusDisplay(diagStatus);
2994
+ setIsPaused(true);
2995
+ setNegIndex(index - diagHistory.length);
2996
+ },
2997
+ 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)}`,
2998
+ title: new Date(diagStatus.timestamp).toLocaleTimeString(),
2999
+ "aria-label": `Diagnostics snapshot ${index + 1} at ${new Date(diagStatus.timestamp).toLocaleTimeString()}`
3000
+ },
3001
+ `history-${index}`
3002
+ ))
3003
+ ] })
3004
+ ] }),
3005
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs text-gray-600 px-2", children: [
3006
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3007
+ "Oldest: ",
3008
+ diagHistory.length > 0 ? new Date(diagHistory[0].timestamp).toLocaleTimeString() : "N/A"
3009
+ ] }),
3010
+ isPaused && diagHistory.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium text-blue-600", children: [
3011
+ "Selected: ",
3012
+ new Date(diagHistory[diagHistory.length + negIndex].timestamp).toLocaleTimeString()
3013
+ ] }),
3014
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3015
+ "Latest: ",
3016
+ diagHistory.length > 0 ? new Date(diagHistory[diagHistory.length - 1].timestamp).toLocaleTimeString() : "N/A"
3017
+ ] })
3018
+ ] })
3019
+ ] });
3020
+ };
3021
+ var HISTORY_SIZE2 = 30;
3022
+ var useDiagHistory = (isPaused) => {
3023
+ const [diagHistory, setDiagHistory] = React9.useState([]);
3024
+ const isPausedRef = React9.useRef(isPaused);
3025
+ React9.useEffect(() => {
3026
+ isPausedRef.current = isPaused;
3027
+ }, [isPaused]);
3028
+ const clearDiagHistory = React9.useCallback(() => {
3029
+ setDiagHistory([]);
3030
+ }, []);
3031
+ const updateDiagHistory = React9.useCallback((diagStatusLatest) => {
3032
+ if (!isPausedRef.current && diagStatusLatest && diagStatusLatest.diagnostics.length > 0) {
3033
+ setDiagHistory((prevItems) => {
3034
+ const updatedItems = [...prevItems, diagStatusLatest];
3035
+ if (updatedItems.length > HISTORY_SIZE2) {
3036
+ return updatedItems.slice(-HISTORY_SIZE2);
3037
+ }
3038
+ return updatedItems;
3039
+ });
3040
+ }
3041
+ }, [isPausedRef]);
3042
+ return { diagHistory, updateDiagHistory, clearDiagHistory };
3043
+ };
3044
+ var ROS2Diagnostics = ({
3045
+ foxgloveUrl,
3046
+ namespace: initialNamespace = "/robot",
3047
+ onDiagnosticUpdate
3048
+ }) => {
3049
+ const {
3050
+ namespace,
3051
+ setManualNamespace,
3052
+ invalidNamespaceMessage,
3053
+ manualEntryRequired
3054
+ } = useNamespace(initialNamespace);
3055
+ const wsUrl = useWebSocketUrl(foxgloveUrl);
3056
+ const [diagStatusDisplay, setDiagStatusDisplay] = React9.useState(null);
3057
+ const [bridgeConnected, setBridgeConnected] = React9.useState(false);
3058
+ const [selectedRawName, setSelectedRawName] = React9.useState(null);
3059
+ const [isPaused, setIsPaused] = React9.useState(false);
3060
+ const {
3061
+ diagHistory,
3062
+ updateDiagHistory,
3063
+ clearDiagHistory
3064
+ } = useDiagHistory(isPaused);
3065
+ const diagnostics = diagStatusDisplay?.diagnostics || [];
3066
+ const handleDiagnosticsUpdate = (diagStatus) => {
3067
+ setDiagStatusDisplay(diagStatus);
3068
+ updateDiagHistory(diagStatus);
3069
+ onDiagnosticUpdate?.(diagStatus);
3070
+ };
3071
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full gap-4 p-4 bg-white", children: [
3072
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3073
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-gray-900", children: "ROS 2 Diagnostics" }),
3074
+ /* @__PURE__ */ jsxRuntime.jsx(
3075
+ Button,
3076
+ {
3077
+ variant: isPaused ? "solid" : "subtle",
3078
+ theme: "brand",
3079
+ size: "sm",
3080
+ onClick: () => {
3081
+ if (isPaused) clearDiagHistory();
3082
+ setIsPaused(!isPaused);
3083
+ },
3084
+ icon: isPaused ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, { className: "w-4 h-4" }),
3085
+ children: isPaused ? "Resume" : "Pause"
3086
+ }
3087
+ )
3088
+ ] }),
3089
+ /* @__PURE__ */ jsxRuntime.jsx(
3090
+ HistorySelection,
3091
+ {
3092
+ diagHistory,
3093
+ setDiagStatusDisplay,
3094
+ isPaused,
3095
+ setIsPaused
3096
+ }
3097
+ ),
3098
+ invalidNamespaceMessage && /* @__PURE__ */ jsxRuntime.jsx(Alert, { theme: "danger", title: invalidNamespaceMessage }),
3099
+ manualEntryRequired && /* @__PURE__ */ jsxRuntime.jsx(
3100
+ ManualNamespace,
3101
+ {
3102
+ setManualNamespace,
3103
+ namespace
3104
+ }
3105
+ ),
3106
+ /* @__PURE__ */ jsxRuntime.jsx(DiagnosticsCapture, { namespace }),
3107
+ !invalidNamespaceMessage && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3108
+ /* @__PURE__ */ jsxRuntime.jsx(
3109
+ RosConnectionManager,
3110
+ {
3111
+ namespace,
3112
+ url: wsUrl,
3113
+ onDiagnosticsUpdate: handleDiagnosticsUpdate,
3114
+ onConnectionStatusChange: setBridgeConnected,
3115
+ onClearHistory: clearDiagHistory
3116
+ }
3117
+ ),
3118
+ !bridgeConnected && /* @__PURE__ */ jsxRuntime.jsxs(
3119
+ Alert,
3120
+ {
3121
+ theme: "warning",
3122
+ title: "Waiting for connection...",
3123
+ children: [
3124
+ "Attempting to connect to Foxglove bridge at ",
3125
+ wsUrl
3126
+ ]
3127
+ }
3128
+ ),
3129
+ diagnostics.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3130
+ DiagnosticsTable,
3131
+ {
3132
+ diagnostics,
3133
+ setSelectedRawName,
3134
+ variant: "error"
3135
+ }
3136
+ ),
3137
+ diagnostics.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3138
+ DiagnosticsTable,
3139
+ {
3140
+ diagnostics,
3141
+ setSelectedRawName,
3142
+ variant: "warning"
3143
+ }
3144
+ ),
3145
+ /* @__PURE__ */ jsxRuntime.jsx(
3146
+ DiagnosticsTreeTable,
3147
+ {
3148
+ diagnostics,
3149
+ bridgeConnected,
3150
+ selectedRawName,
3151
+ setSelectedRawName
3152
+ }
3153
+ ),
3154
+ diagnostics.length === 0 && bridgeConnected && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-gray-500", children: [
3155
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
3156
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2", children: "Waiting for diagnostic data..." })
3157
+ ] })
3158
+ ] })
3159
+ ] });
3160
+ };
2141
3161
  function useDisclosure(initial = false) {
2142
3162
  const [isOpen, setIsOpen] = React9.useState(initial);
2143
3163
  const open = React9.useCallback(() => setIsOpen(true), []);
@@ -2182,6 +3202,7 @@ exports.MultiSelect = MultiSelect;
2182
3202
  exports.NumberInput = NumberInput;
2183
3203
  exports.Pagination = Pagination;
2184
3204
  exports.Progress = Progress;
3205
+ exports.ROS2Diagnostics = ROS2Diagnostics;
2185
3206
  exports.Rating = Rating;
2186
3207
  exports.Select = Select;
2187
3208
  exports.Sidebar = Sidebar;