@camstack/ui-library 1.2.119 → 1.2.121

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
@@ -799,6 +799,42 @@ var Bus = createLucideIcon("bus", [
799
799
  * This source code is licensed under the ISC license.
800
800
  * See the LICENSE file in the root directory of this source tree.
801
801
  */
802
+ var Cable = createLucideIcon("cable", [
803
+ ["path", {
804
+ d: "M17 19a1 1 0 0 1-1-1v-2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2a1 1 0 0 1-1 1z",
805
+ key: "trhst0"
806
+ }],
807
+ ["path", {
808
+ d: "M17 21v-2",
809
+ key: "ds4u3f"
810
+ }],
811
+ ["path", {
812
+ d: "M19 14V6.5a1 1 0 0 0-7 0v11a1 1 0 0 1-7 0V10",
813
+ key: "1mo9zo"
814
+ }],
815
+ ["path", {
816
+ d: "M21 21v-2",
817
+ key: "eo0ou"
818
+ }],
819
+ ["path", {
820
+ d: "M3 5V3",
821
+ key: "1k5hjh"
822
+ }],
823
+ ["path", {
824
+ d: "M4 10a2 2 0 0 1-2-2V6a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2a2 2 0 0 1-2 2z",
825
+ key: "1dd30t"
826
+ }],
827
+ ["path", {
828
+ d: "M7 5V3",
829
+ key: "1t1388"
830
+ }]
831
+ ]);
832
+ /**
833
+ * @license lucide-react v0.576.0 - ISC
834
+ *
835
+ * This source code is licensed under the ISC license.
836
+ * See the LICENSE file in the root directory of this source tree.
837
+ */
802
838
  var Camera = createLucideIcon("camera", [["path", {
803
839
  d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",
804
840
  key: "18u6gg"
@@ -1854,6 +1890,34 @@ var Flame = createLucideIcon("flame", [["path", {
1854
1890
  * This source code is licensed under the ISC license.
1855
1891
  * See the LICENSE file in the root directory of this source tree.
1856
1892
  */
1893
+ var FlashlightOff = createLucideIcon("flashlight-off", [
1894
+ ["path", {
1895
+ d: "M11.652 6H18",
1896
+ key: "voqkpr"
1897
+ }],
1898
+ ["path", {
1899
+ d: "M12 13v1",
1900
+ key: "176q98"
1901
+ }],
1902
+ ["path", {
1903
+ d: "M16 16v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-8a4 4 0 0 0-.8-2.4l-.6-.8A3 3 0 0 1 6 7V6",
1904
+ key: "dzyf92"
1905
+ }],
1906
+ ["path", {
1907
+ d: "m2 2 20 20",
1908
+ key: "1ooewy"
1909
+ }],
1910
+ ["path", {
1911
+ d: "M7.649 2H17a1 1 0 0 1 1 1v4a3 3 0 0 1-.6 1.8l-.6.8a4 4 0 0 0-.55 1.007",
1912
+ key: "1hvcfn"
1913
+ }]
1914
+ ]);
1915
+ /**
1916
+ * @license lucide-react v0.576.0 - ISC
1917
+ *
1918
+ * This source code is licensed under the ISC license.
1919
+ * See the LICENSE file in the root directory of this source tree.
1920
+ */
1857
1921
  var Flashlight = createLucideIcon("flashlight", [
1858
1922
  ["path", {
1859
1923
  d: "M12 13v1",
@@ -2045,6 +2109,30 @@ var Hammer = createLucideIcon("hammer", [
2045
2109
  * This source code is licensed under the ISC license.
2046
2110
  * See the LICENSE file in the root directory of this source tree.
2047
2111
  */
2112
+ var Hand = createLucideIcon("hand", [
2113
+ ["path", {
2114
+ d: "M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2",
2115
+ key: "1fvzgz"
2116
+ }],
2117
+ ["path", {
2118
+ d: "M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2",
2119
+ key: "1kc0my"
2120
+ }],
2121
+ ["path", {
2122
+ d: "M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8",
2123
+ key: "10h0bg"
2124
+ }],
2125
+ ["path", {
2126
+ d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15",
2127
+ key: "1s1gnw"
2128
+ }]
2129
+ ]);
2130
+ /**
2131
+ * @license lucide-react v0.576.0 - ISC
2132
+ *
2133
+ * This source code is licensed under the ISC license.
2134
+ * See the LICENSE file in the root directory of this source tree.
2135
+ */
2048
2136
  var HardDrive = createLucideIcon("hard-drive", [
2049
2137
  ["path", {
2050
2138
  d: "M10 16h.01",
@@ -3161,6 +3249,44 @@ var Radar = createLucideIcon("radar", [
3161
3249
  * This source code is licensed under the ISC license.
3162
3250
  * See the LICENSE file in the root directory of this source tree.
3163
3251
  */
3252
+ var RadioTower = createLucideIcon("radio-tower", [
3253
+ ["path", {
3254
+ d: "M4.9 16.1C1 12.2 1 5.8 4.9 1.9",
3255
+ key: "s0qx1y"
3256
+ }],
3257
+ ["path", {
3258
+ d: "M7.8 4.7a6.14 6.14 0 0 0-.8 7.5",
3259
+ key: "1idnkw"
3260
+ }],
3261
+ ["circle", {
3262
+ cx: "12",
3263
+ cy: "9",
3264
+ r: "2",
3265
+ key: "1092wv"
3266
+ }],
3267
+ ["path", {
3268
+ d: "M16.2 4.8c2 2 2.26 5.11.8 7.47",
3269
+ key: "ojru2q"
3270
+ }],
3271
+ ["path", {
3272
+ d: "M19.1 1.9a9.96 9.96 0 0 1 0 14.1",
3273
+ key: "rhi7fg"
3274
+ }],
3275
+ ["path", {
3276
+ d: "M9.5 18h5",
3277
+ key: "mfy3pd"
3278
+ }],
3279
+ ["path", {
3280
+ d: "m8 22 4-11 4 11",
3281
+ key: "25yftu"
3282
+ }]
3283
+ ]);
3284
+ /**
3285
+ * @license lucide-react v0.576.0 - ISC
3286
+ *
3287
+ * This source code is licensed under the ISC license.
3288
+ * See the LICENSE file in the root directory of this source tree.
3289
+ */
3164
3290
  var Radio = createLucideIcon("radio", [
3165
3291
  ["path", {
3166
3292
  d: "M16.247 7.761a6 6 0 0 1 0 8.478",
@@ -4212,6 +4338,19 @@ var Type = createLucideIcon("type", [
4212
4338
  * This source code is licensed under the ISC license.
4213
4339
  * See the LICENSE file in the root directory of this source tree.
4214
4340
  */
4341
+ var Undo2 = createLucideIcon("undo-2", [["path", {
4342
+ d: "M9 14 4 9l5-5",
4343
+ key: "102s5s"
4344
+ }], ["path", {
4345
+ d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11",
4346
+ key: "f3b9sd"
4347
+ }]]);
4348
+ /**
4349
+ * @license lucide-react v0.576.0 - ISC
4350
+ *
4351
+ * This source code is licensed under the ISC license.
4352
+ * See the LICENSE file in the root directory of this source tree.
4353
+ */
4215
4354
  var Unlink2 = createLucideIcon("unlink-2", [["path", {
4216
4355
  d: "M15 7h2a5 5 0 0 1 0 10h-2m-6 0H7A5 5 0 0 1 7 7h2",
4217
4356
  key: "1re2ne"
@@ -4419,6 +4558,39 @@ var Waves = createLucideIcon("waves", [
4419
4558
  * This source code is licensed under the ISC license.
4420
4559
  * See the LICENSE file in the root directory of this source tree.
4421
4560
  */
4561
+ var WifiHigh = createLucideIcon("wifi-high", [
4562
+ ["path", {
4563
+ d: "M12 20h.01",
4564
+ key: "zekei9"
4565
+ }],
4566
+ ["path", {
4567
+ d: "M5 12.859a10 10 0 0 1 14 0",
4568
+ key: "1x1e6c"
4569
+ }],
4570
+ ["path", {
4571
+ d: "M8.5 16.429a5 5 0 0 1 7 0",
4572
+ key: "1bycff"
4573
+ }]
4574
+ ]);
4575
+ /**
4576
+ * @license lucide-react v0.576.0 - ISC
4577
+ *
4578
+ * This source code is licensed under the ISC license.
4579
+ * See the LICENSE file in the root directory of this source tree.
4580
+ */
4581
+ var WifiLow = createLucideIcon("wifi-low", [["path", {
4582
+ d: "M12 20h.01",
4583
+ key: "zekei9"
4584
+ }], ["path", {
4585
+ d: "M8.5 16.429a5 5 0 0 1 7 0",
4586
+ key: "1bycff"
4587
+ }]]);
4588
+ /**
4589
+ * @license lucide-react v0.576.0 - ISC
4590
+ *
4591
+ * This source code is licensed under the ISC license.
4592
+ * See the LICENSE file in the root directory of this source tree.
4593
+ */
4422
4594
  var WifiOff = createLucideIcon("wifi-off", [
4423
4595
  ["path", {
4424
4596
  d: "M12 20h.01",
@@ -4455,6 +4627,16 @@ var WifiOff = createLucideIcon("wifi-off", [
4455
4627
  * This source code is licensed under the ISC license.
4456
4628
  * See the LICENSE file in the root directory of this source tree.
4457
4629
  */
4630
+ var WifiZero = createLucideIcon("wifi-zero", [["path", {
4631
+ d: "M12 20h.01",
4632
+ key: "zekei9"
4633
+ }]]);
4634
+ /**
4635
+ * @license lucide-react v0.576.0 - ISC
4636
+ *
4637
+ * This source code is licensed under the ISC license.
4638
+ * See the LICENSE file in the root directory of this source tree.
4639
+ */
4458
4640
  var Wifi = createLucideIcon("wifi", [
4459
4641
  ["path", {
4460
4642
  d: "M12 20h.01",
@@ -5805,6 +5987,28 @@ var useMqttBrokerGetStatus = trpc.mqttBroker.getStatus.useQuery;
5805
5987
  var useNativeObjectDetectionSetEnabled = trpc.nativeObjectDetection.setEnabled.useMutation;
5806
5988
  /** Generated alias around `trpc.nativeObjectDetection.getStatus.useQuery`. */
5807
5989
  var useNativeObjectDetectionGetStatus = trpc.nativeObjectDetection.getStatus.useQuery;
5990
+ /** Generated alias around `trpc.navigation.move.useMutation`. */
5991
+ var useNavigationMove = trpc.navigation.move.useMutation;
5992
+ /** Generated alias around `trpc.navigation.stop.useMutation`. */
5993
+ var useNavigationStop = trpc.navigation.stop.useMutation;
5994
+ /** Generated alias around `trpc.navigation.goToPoint.useMutation`. */
5995
+ var useNavigationGoToPoint = trpc.navigation.goToPoint.useMutation;
5996
+ /** Generated alias around `trpc.navigation.listActions.useQuery`. */
5997
+ var useNavigationListActions = trpc.navigation.listActions.useQuery;
5998
+ /** Generated alias around `trpc.navigation.runAction.useMutation`. */
5999
+ var useNavigationRunAction = trpc.navigation.runAction.useMutation;
6000
+ /** Generated alias around `trpc.navigation.playSound.useMutation`. */
6001
+ var useNavigationPlaySound = trpc.navigation.playSound.useMutation;
6002
+ /** Generated alias around `trpc.navigation.setLightOn.useMutation`. */
6003
+ var useNavigationSetLightOn = trpc.navigation.setLightOn.useMutation;
6004
+ /** Generated alias around `trpc.navigation.setLightMode.useMutation`. */
6005
+ var useNavigationSetLightMode = trpc.navigation.setLightMode.useMutation;
6006
+ /** Generated alias around `trpc.navigation.setLightLevel.useMutation`. */
6007
+ var useNavigationSetLightLevel = trpc.navigation.setLightLevel.useMutation;
6008
+ /** Generated alias around `trpc.navigation.getFeatures.useQuery`. */
6009
+ var useNavigationGetFeatures = trpc.navigation.getFeatures.useQuery;
6010
+ /** Generated alias around `trpc.navigation.getStatus.useQuery`. */
6011
+ var useNavigationGetStatus = trpc.navigation.getStatus.useQuery;
5808
6012
  /** Generated alias around `trpc.networkAccess.start.useMutation`. */
5809
6013
  var useNetworkAccessStart = trpc.networkAccess.start.useMutation;
5810
6014
  /** Generated alias around `trpc.networkAccess.stop.useMutation`. */
@@ -5815,6 +6019,8 @@ var useNetworkAccessGetEndpoint = trpc.networkAccess.getEndpoint.useQuery;
5815
6019
  var useNetworkAccessGetStatus = trpc.networkAccess.getStatus.useQuery;
5816
6020
  /** Generated alias around `trpc.networkAccess.listEndpoints.useQuery`. */
5817
6021
  var useNetworkAccessListEndpoints = trpc.networkAccess.listEndpoints.useQuery;
6022
+ /** Generated alias around `trpc.networkLink.getStatus.useQuery`. */
6023
+ var useNetworkLinkGetStatus = trpc.networkLink.getStatus.useQuery;
5818
6024
  /** Generated alias around `trpc.networkQuality.getDeviceStats.useQuery`. */
5819
6025
  var useNetworkQualityGetDeviceStats = trpc.networkQuality.getDeviceStats.useQuery;
5820
6026
  /** Generated alias around `trpc.networkQuality.getAllStats.useQuery`. */
@@ -15870,6 +16076,373 @@ function ConsumablesPanel({ deviceId }) {
15870
16076
  });
15871
16077
  }
15872
16078
  //#endregion
16079
+ //#region src/hooks/use-navigation.ts
16080
+ /**
16081
+ * useNavigation — drive a `navigation` robot camera from the UI.
16082
+ *
16083
+ * The cap's `move` is MOMENTARY: fluid driving is the UI sending a nudge at
16084
+ * 1 Hz while a direction is held and ONE `stop` on release, exactly like the
16085
+ * robot app's joystick. This hook owns that cadence (`startDrive` /
16086
+ * `stopDrive`); the provider forwards each nudge to a single drive write.
16087
+ *
16088
+ * Everything else is a plain call plus the data the panel renders from:
16089
+ * `features` (which primitives are on), `actions` (the enabled dictionary)
16090
+ * and the live `status` slice off the device proxy.
16091
+ */
16092
+ /** The cadence the robot app drives at while a direction is held. */
16093
+ var NAVIGATION_DRIVE_INTERVAL_MS = 1e3;
16094
+ var DIRECTION_VECTORS$1 = {
16095
+ forward: {
16096
+ pan: 0,
16097
+ tilt: 1
16098
+ },
16099
+ left: {
16100
+ pan: -1,
16101
+ tilt: 0
16102
+ },
16103
+ right: {
16104
+ pan: 1,
16105
+ tilt: 0
16106
+ },
16107
+ spin: {
16108
+ pan: 0,
16109
+ tilt: -1
16110
+ }
16111
+ };
16112
+ function isAbsentProvider$1(err) {
16113
+ const message = err instanceof Error ? err.message : String(err);
16114
+ return /not available|no provider|NOT_FOUND|PRECONDITION/i.test(message);
16115
+ }
16116
+ function useNavigation(trpc, deviceId, hookOptions) {
16117
+ const enabled = hookOptions?.enabled ?? true;
16118
+ const nav = useDeviceProxy(trpc, enabled ? deviceId : null)?.navigation;
16119
+ const status = useDeviceState(trpc, enabled ? deviceId : null, (s) => s.navigation);
16120
+ const [features, setFeatures] = (0, react$1.useState)(null);
16121
+ const [actions, setActions] = (0, react$1.useState)([]);
16122
+ const [busy, setBusy] = (0, react$1.useState)(false);
16123
+ const [error, setError] = (0, react$1.useState)(null);
16124
+ const [driving, setDriving] = (0, react$1.useState)(null);
16125
+ const driveTimer = (0, react$1.useRef)(null);
16126
+ (0, react$1.useEffect)(() => {
16127
+ if (!enabled || !nav) return;
16128
+ let cancelled = false;
16129
+ (async () => {
16130
+ try {
16131
+ const [nextFeatures, nextActions] = await Promise.all([nav.getFeatures({}), nav.listActions({})]);
16132
+ if (cancelled) return;
16133
+ setFeatures(nextFeatures);
16134
+ setActions(nextActions);
16135
+ } catch (err) {
16136
+ if (cancelled || isAbsentProvider$1(err)) return;
16137
+ setError(err instanceof Error ? err.message : String(err));
16138
+ }
16139
+ })();
16140
+ return () => {
16141
+ cancelled = true;
16142
+ };
16143
+ }, [nav, enabled]);
16144
+ const wrap = (0, react$1.useCallback)(async (fn) => {
16145
+ if (!enabled || !nav) return;
16146
+ setError(null);
16147
+ setBusy(true);
16148
+ try {
16149
+ await fn();
16150
+ } catch (err) {
16151
+ setError(err instanceof Error ? err.message : String(err));
16152
+ } finally {
16153
+ setBusy(false);
16154
+ }
16155
+ }, [enabled, nav]);
16156
+ const clearTimer = (0, react$1.useCallback)(() => {
16157
+ if (driveTimer.current !== null) {
16158
+ clearInterval(driveTimer.current);
16159
+ driveTimer.current = null;
16160
+ }
16161
+ }, []);
16162
+ const stopDrive = (0, react$1.useCallback)(async () => {
16163
+ clearTimer();
16164
+ setDriving(null);
16165
+ if (!nav) return;
16166
+ await wrap(() => nav.stop({}));
16167
+ }, [
16168
+ clearTimer,
16169
+ nav,
16170
+ wrap
16171
+ ]);
16172
+ const startDrive = (0, react$1.useCallback)((direction) => {
16173
+ if (!nav) return;
16174
+ clearTimer();
16175
+ setDriving(direction);
16176
+ const vector = DIRECTION_VECTORS$1[direction];
16177
+ const nudge = () => {
16178
+ nav.move({
16179
+ pan: vector.pan,
16180
+ tilt: vector.tilt
16181
+ }).catch((err) => {
16182
+ setError(err instanceof Error ? err.message : String(err));
16183
+ });
16184
+ };
16185
+ nudge();
16186
+ driveTimer.current = setInterval(nudge, NAVIGATION_DRIVE_INTERVAL_MS);
16187
+ }, [clearTimer, nav]);
16188
+ (0, react$1.useEffect)(() => {
16189
+ return () => {
16190
+ if (driveTimer.current !== null) {
16191
+ clearInterval(driveTimer.current);
16192
+ driveTimer.current = null;
16193
+ nav?.stop({}).catch(() => void 0);
16194
+ }
16195
+ };
16196
+ }, [nav]);
16197
+ const runAction = (0, react$1.useCallback)((actionId) => wrap(async () => nav?.runAction({ actionId })), [nav, wrap]);
16198
+ const playSound = (0, react$1.useCallback)((soundId) => wrap(async () => nav?.playSound({ soundId })), [nav, wrap]);
16199
+ const setLightOn = (0, react$1.useCallback)((on) => wrap(async () => nav?.setLightOn({ on })), [nav, wrap]);
16200
+ const setLightMode = (0, react$1.useCallback)((mode, level) => wrap(async () => nav?.setLightMode(level === void 0 ? { mode } : {
16201
+ mode,
16202
+ level
16203
+ })), [nav, wrap]);
16204
+ const setLightLevel = (0, react$1.useCallback)((level) => wrap(async () => nav?.setLightLevel({ level })), [nav, wrap]);
16205
+ return {
16206
+ features,
16207
+ actions,
16208
+ status: status ?? null,
16209
+ startDrive,
16210
+ stopDrive,
16211
+ runAction,
16212
+ playSound,
16213
+ setLightOn,
16214
+ setLightMode,
16215
+ setLightLevel,
16216
+ busy,
16217
+ error,
16218
+ driving
16219
+ };
16220
+ }
16221
+ //#endregion
16222
+ //#region src/composites/cap-settings/NavigationPanel.tsx
16223
+ /** The cap's icon HINTS are lucide names; unknown hints fall back to a spark. */
16224
+ var ICONS = {
16225
+ play: Play,
16226
+ pause: Pause,
16227
+ hand: Hand,
16228
+ home: House,
16229
+ bell: Bell,
16230
+ crosshair: Crosshair,
16231
+ "paw-print": PawPrint,
16232
+ footprints: Footprints,
16233
+ droplets: Droplets,
16234
+ "trash-2": Trash2,
16235
+ flashlight: Flashlight,
16236
+ "flashlight-off": FlashlightOff,
16237
+ cat: Cat,
16238
+ dog: Dog,
16239
+ clock: Clock
16240
+ };
16241
+ function iconFor(hint) {
16242
+ return ICONS[hint] ?? Sparkles;
16243
+ }
16244
+ var DRIVE_BUTTONS = [
16245
+ {
16246
+ direction: "left",
16247
+ label: "Turn left",
16248
+ Icon: RotateCcw
16249
+ },
16250
+ {
16251
+ direction: "forward",
16252
+ label: "Forward",
16253
+ Icon: ArrowUp
16254
+ },
16255
+ {
16256
+ direction: "right",
16257
+ label: "Turn right",
16258
+ Icon: RotateCw
16259
+ },
16260
+ {
16261
+ direction: "spin",
16262
+ label: "Turn around",
16263
+ Icon: Undo2
16264
+ }
16265
+ ];
16266
+ var BUTTON = "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium transition-colors bg-surface-elevated/40 text-foreground-subtle hover:bg-surface-hover hover:text-foreground disabled:opacity-50";
16267
+ var BUTTON_ON = "bg-success/15 text-success hover:bg-success/25";
16268
+ function EntryButton({ entry, disabled, onRun }) {
16269
+ const Glyph = iconFor(entry.icon);
16270
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
16271
+ type: "button",
16272
+ className: BUTTON,
16273
+ disabled,
16274
+ onClick: () => onRun(entry),
16275
+ title: entry.label,
16276
+ "aria-label": entry.label,
16277
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: entry.label })]
16278
+ });
16279
+ }
16280
+ function NavigationPanel({ deviceId }) {
16281
+ const system = useSystem();
16282
+ const ready = Number.isFinite(deviceId);
16283
+ const nav = useNavigation(system.trpcClient, ready ? deviceId : 0, { enabled: ready });
16284
+ if (!ready) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
16285
+ className: "flex items-center justify-center h-full text-[11px] text-foreground-subtle italic",
16286
+ children: [
16287
+ "Device #",
16288
+ deviceId,
16289
+ " not loaded."
16290
+ ]
16291
+ });
16292
+ const features = nav.features;
16293
+ const actions = nav.actions.filter((entry) => entry.enabled && entry.kind === "action");
16294
+ const sounds = nav.actions.filter((entry) => entry.enabled && entry.kind === "sound");
16295
+ const status = nav.status;
16296
+ const manual = status?.lightMode === "manual";
16297
+ const run = (entry) => {
16298
+ if (entry.kind === "sound") {
16299
+ if (entry.soundId !== void 0) nav.playSound(entry.soundId);
16300
+ return;
16301
+ }
16302
+ nav.runAction(entry.id);
16303
+ };
16304
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(WidgetPanel, {
16305
+ title: "Navigation",
16306
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Navigation, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
16307
+ bodyClassName: "p-3 flex flex-col gap-3 overflow-y-auto",
16308
+ children: [
16309
+ status && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
16310
+ className: "flex flex-wrap items-center gap-1.5 text-[10px]",
16311
+ children: [
16312
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16313
+ className: "rounded-full bg-surface-elevated/60 px-2 py-0.5 text-foreground",
16314
+ children: status.mode
16315
+ }),
16316
+ status.following && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16317
+ className: "rounded-full bg-info/15 px-2 py-0.5 text-info",
16318
+ children: "following"
16319
+ }),
16320
+ status.flash && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16321
+ className: "rounded-full bg-warning/15 px-2 py-0.5 text-warning",
16322
+ children: "light on"
16323
+ })
16324
+ ]
16325
+ }),
16326
+ features?.move && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
16327
+ className: "flex flex-col gap-1.5",
16328
+ children: [
16329
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16330
+ className: "text-[10.5px] font-semibold text-foreground",
16331
+ children: "Drive (hold)"
16332
+ }),
16333
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
16334
+ className: "grid grid-cols-4 gap-1.5",
16335
+ children: DRIVE_BUTTONS.map(({ direction, label, Icon }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
16336
+ type: "button",
16337
+ className: cn(BUTTON, "justify-center select-none", nav.driving === direction && BUTTON_ON),
16338
+ "aria-label": label,
16339
+ title: label,
16340
+ onPointerDown: () => nav.startDrive(direction),
16341
+ onPointerUp: () => void nav.stopDrive(),
16342
+ onPointerLeave: () => {
16343
+ if (nav.driving === direction) nav.stopDrive();
16344
+ },
16345
+ onPointerCancel: () => void nav.stopDrive(),
16346
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "h-4 w-4" })
16347
+ }, direction))
16348
+ }),
16349
+ features.stop && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
16350
+ type: "button",
16351
+ className: cn(BUTTON, "justify-center"),
16352
+ onClick: () => void nav.stopDrive(),
16353
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Hand, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Stop" })]
16354
+ })
16355
+ ]
16356
+ }),
16357
+ features?.runAction && actions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
16358
+ className: "flex flex-col gap-1.5",
16359
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16360
+ className: "text-[10.5px] font-semibold text-foreground",
16361
+ children: "Actions"
16362
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
16363
+ className: "flex flex-wrap gap-1.5",
16364
+ children: actions.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryButton, {
16365
+ entry,
16366
+ disabled: nav.busy,
16367
+ onRun: run
16368
+ }, entry.id))
16369
+ })]
16370
+ }),
16371
+ features?.playSound && sounds.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
16372
+ className: "flex flex-col gap-1.5",
16373
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16374
+ className: "text-[10.5px] font-semibold text-foreground",
16375
+ children: "Sounds"
16376
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
16377
+ className: "flex flex-wrap gap-1.5",
16378
+ children: sounds.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryButton, {
16379
+ entry,
16380
+ disabled: nav.busy,
16381
+ onRun: run
16382
+ }, entry.id))
16383
+ })]
16384
+ }),
16385
+ features?.light && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
16386
+ className: "flex flex-col gap-1.5 border-t border-border/40 pt-2",
16387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
16388
+ className: "flex items-center justify-between",
16389
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16390
+ className: "text-[10.5px] font-semibold text-foreground",
16391
+ children: "Fill light"
16392
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
16393
+ type: "button",
16394
+ className: cn(BUTTON, status?.flash && BUTTON_ON),
16395
+ "aria-pressed": status?.flash === true,
16396
+ disabled: nav.busy,
16397
+ onClick: () => void nav.setLightOn(!(status?.flash === true)),
16398
+ children: [status?.flash ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Flashlight, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FlashlightOff, { className: "h-3.5 w-3.5" }), status?.flash ? "On" : "Off"]
16399
+ })]
16400
+ }), features.lightMode && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
16401
+ className: "flex items-center gap-2",
16402
+ children: [
16403
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
16404
+ "aria-label": "Light mode",
16405
+ className: "rounded-md bg-surface-elevated/40 px-2 py-1 text-[10px] text-foreground",
16406
+ value: status?.lightMode ?? "auto",
16407
+ disabled: nav.busy,
16408
+ onChange: (event) => {
16409
+ const mode = event.target.value === "manual" ? "manual" : "auto";
16410
+ nav.setLightMode(mode, mode === "manual" ? status?.lightLevel : void 0);
16411
+ },
16412
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
16413
+ value: "auto",
16414
+ children: "Auto"
16415
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
16416
+ value: "manual",
16417
+ children: "Manual"
16418
+ })]
16419
+ }),
16420
+ manual && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
16421
+ type: "range",
16422
+ "aria-label": "Light level",
16423
+ className: "flex-1",
16424
+ min: _camstack_types.NAVIGATION_LIGHT_LEVEL_MIN,
16425
+ max: _camstack_types.NAVIGATION_LIGHT_LEVEL_MAX,
16426
+ step: 1,
16427
+ value: status?.lightLevel ?? _camstack_types.NAVIGATION_LIGHT_LEVEL_MAX,
16428
+ disabled: nav.busy,
16429
+ onChange: (event) => void nav.setLightLevel(Number(event.target.value))
16430
+ }),
16431
+ manual && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16432
+ className: "text-[10px] text-foreground-subtle w-8 text-right",
16433
+ children: status?.lightLevel ?? _camstack_types.NAVIGATION_LIGHT_LEVEL_MAX
16434
+ })
16435
+ ]
16436
+ })]
16437
+ }),
16438
+ nav.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
16439
+ className: "text-[10px] text-danger",
16440
+ children: nav.error
16441
+ })
16442
+ ]
16443
+ });
16444
+ }
16445
+ //#endregion
15873
16446
  //#region src/lib/cap-error.ts
15874
16447
  /**
15875
16448
  * Shared capability-error utilities for the admin UI.
@@ -18695,6 +19268,7 @@ var HOST_WIDGETS = {
18695
19268
  "host/motion-zones-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./MotionZonesSettings-BP9p0pFB.cjs")).then((m) => ({ default: m.MotionZonesSettings }))),
18696
19269
  "host/privacy-mask-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-Oo7gyB4E.cjs")).then((m) => ({ default: m.PrivacyMaskSettings }))),
18697
19270
  "host/ptz-panel": PtzPanel,
19271
+ "host/navigation-panel": NavigationPanel,
18698
19272
  "host/consumables-panel": ConsumablesPanel,
18699
19273
  "host/ptz-autotrack": AutotrackSection,
18700
19274
  "host/recording-panel": RecordingPanel
@@ -24486,6 +25060,49 @@ function BatteryBadge({ status, variant = "compact", className }) {
24486
25060
  });
24487
25061
  }
24488
25062
  //#endregion
25063
+ //#region src/composites/network-link-badge.tsx
25064
+ /**
25065
+ * NetworkLinkBadge — compact link indicator for device rows and headers.
25066
+ *
25067
+ * A wire glyph for ethernet, a wifi glyph with the signal for wireless, an
25068
+ * antenna for cellular. Renders nothing for an `unknown` link — that is the
25069
+ * absence of a read, not a link — and shows no percentage when the signal is
25070
+ * `null` (a wire has none; a wireless link not yet read must not sit at 0 %).
25071
+ *
25072
+ * Self-contained on data: the caller passes the `network-link` slice from
25073
+ * `useDeviceNetworkLink` and drops the badge in unconditionally.
25074
+ */
25075
+ /** Monotonic — more signal, more arcs. */
25076
+ function pickWifiIcon(signalPercent) {
25077
+ if (signalPercent === null) return Wifi;
25078
+ if (signalPercent <= 20) return WifiZero;
25079
+ if (signalPercent <= 50) return WifiLow;
25080
+ if (signalPercent <= 80) return WifiHigh;
25081
+ return Wifi;
25082
+ }
25083
+ function signalColor(signalPercent) {
25084
+ if (signalPercent === null) return "text-foreground-subtle";
25085
+ if (signalPercent <= 20) return "text-rose-400";
25086
+ if (signalPercent <= 50) return "text-amber-400";
25087
+ return "text-foreground-subtle";
25088
+ }
25089
+ function NetworkLinkBadge({ status, className }) {
25090
+ if (!status || status.type === "unknown") return null;
25091
+ if (status.type === "ethernet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
25092
+ className: cn("inline-flex items-center gap-1 text-[10px] font-medium text-foreground-subtle", className),
25093
+ title: "Network: ethernet",
25094
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Cable, { className: "h-3.5 w-3.5" })
25095
+ });
25096
+ const Icon = status.type === "cellular" ? RadioTower : pickWifiIcon(status.signalPercent);
25097
+ const signal = status.signalPercent === null ? null : Math.round(status.signalPercent);
25098
+ const title = `Network: ${status.type}` + (status.ssid ? ` (${status.ssid})` : "") + (signal === null ? "" : ` · signal ${signal}%`);
25099
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
25100
+ className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(status.signalPercent), className),
25101
+ title,
25102
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "h-3.5 w-3.5" }), signal !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [signal, "%"] })]
25103
+ });
25104
+ }
25105
+ //#endregion
24489
25106
  //#region src/composites/breadcrumb.tsx
24490
25107
  /**
24491
25108
  * Breadcrumb navigation component.
@@ -42027,6 +42644,19 @@ function useDeviceBattery(trpc, deviceId) {
42027
42644
  return useDeviceState(trpc, deviceId, (s) => s.battery) ?? null;
42028
42645
  }
42029
42646
  //#endregion
42647
+ //#region src/hooks/use-device-network-link.ts
42648
+ /**
42649
+ * useDeviceNetworkLink — the device's own network link (medium + signal).
42650
+ *
42651
+ * Same shape as `useDeviceBattery`: a thin wrapper over `useDeviceState`,
42652
+ * fed by the kernel runtime-state mirror (one fetch on first subscribe, then
42653
+ * push updates on every `network-link` slice write). Returns `null` when no
42654
+ * provider has emitted yet — a device without the cap simply has no badge.
42655
+ */
42656
+ function useDeviceNetworkLink(trpc, deviceId) {
42657
+ return useDeviceState(trpc, deviceId, (s) => s.networkLink) ?? null;
42658
+ }
42659
+ //#endregion
42030
42660
  //#region src/hooks/use-device-snapshot.ts
42031
42661
  /**
42032
42662
  * useDeviceSnapshotImage — a device's snapshot as a plain image URL.
@@ -42200,17 +42830,23 @@ function DeviceItemPreview({ trpc, device, status, enabled, showStatusPills = fa
42200
42830
  const isBatteryOperated = (device.features ?? []).includes("battery-operated");
42201
42831
  const wantPills = showStatusPills && isBatteryOperated && !device.disabled;
42202
42832
  const batteryStatus = useDeviceBattery(trpc, wantPills ? device.id : null);
42203
- if (!enabled && !wantPills) return null;
42833
+ const wantLinkPill = showStatusPills && !device.disabled;
42834
+ const linkStatus = useDeviceNetworkLink(trpc, wantLinkPill ? device.id : null);
42835
+ if (!enabled && !wantPills && !(wantLinkPill && linkStatus)) return null;
42204
42836
  const offline = status === "offline" || status === "disabled";
42205
42837
  const placeholder = status === "offline" ? "Offline — no snapshot" : status === "disabled" ? "Disabled" : "No snapshot yet";
42206
42838
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
42207
42839
  className: "inline-flex items-center gap-1.5 flex-nowrap whitespace-nowrap",
42208
- children: [wantPills && batteryStatus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge, { status: batteryStatus }), enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverZoomImage, {
42209
- src: offline ? null : snapshot.src,
42210
- alt: device.name,
42211
- placeholder,
42212
- thumbClassName: "h-7 w-7 rounded"
42213
- })]
42840
+ children: [
42841
+ wantPills && batteryStatus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge, { status: batteryStatus }),
42842
+ wantLinkPill && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NetworkLinkBadge, { status: linkStatus }),
42843
+ enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverZoomImage, {
42844
+ src: offline ? null : snapshot.src,
42845
+ alt: device.name,
42846
+ placeholder,
42847
+ thumbClassName: "h-7 w-7 rounded"
42848
+ })
42849
+ ]
42214
42850
  });
42215
42851
  }
42216
42852
  //#endregion
@@ -49664,6 +50300,9 @@ exports.Mic = Mic;
49664
50300
  exports.MobileDrawer = MobileDrawer;
49665
50301
  exports.ModelPicker = ModelPicker;
49666
50302
  exports.MotionZonesSettings = MotionZonesSettings;
50303
+ exports.NAVIGATION_DRIVE_INTERVAL_MS = NAVIGATION_DRIVE_INTERVAL_MS;
50304
+ exports.NavigationPanel = NavigationPanel;
50305
+ exports.NetworkLinkBadge = NetworkLinkBadge;
49667
50306
  exports.NodeMultiSelectField = NodeMultiSelectField;
49668
50307
  exports.NodePicker = NodePicker;
49669
50308
  exports.NodeSelectField = NodeSelectField;
@@ -50181,6 +50820,7 @@ exports.useDeviceManagerTestField = useDeviceManagerTestField;
50181
50820
  exports.useDeviceManagerUpdateConfig = useDeviceManagerUpdateConfig;
50182
50821
  exports.useDeviceManagerUpdateDeviceField = useDeviceManagerUpdateDeviceField;
50183
50822
  exports.useDeviceManagerUpdateDeviceFieldsBatch = useDeviceManagerUpdateDeviceFieldsBatch;
50823
+ exports.useDeviceNetworkLink = useDeviceNetworkLink;
50184
50824
  exports.useDeviceOpsGetConfigEntries = useDeviceOpsGetConfigEntries;
50185
50825
  exports.useDeviceOpsGetRawState = useDeviceOpsGetRawState;
50186
50826
  exports.useDeviceOpsGetSettingsSchema = useDeviceOpsGetSettingsSchema;
@@ -50381,11 +51021,24 @@ exports.useMqttBrokerStopEmbeddedBroker = useMqttBrokerStopEmbeddedBroker;
50381
51021
  exports.useMqttBrokerTestConnection = useMqttBrokerTestConnection;
50382
51022
  exports.useNativeObjectDetectionGetStatus = useNativeObjectDetectionGetStatus;
50383
51023
  exports.useNativeObjectDetectionSetEnabled = useNativeObjectDetectionSetEnabled;
51024
+ exports.useNavigation = useNavigation;
51025
+ exports.useNavigationGetFeatures = useNavigationGetFeatures;
51026
+ exports.useNavigationGetStatus = useNavigationGetStatus;
51027
+ exports.useNavigationGoToPoint = useNavigationGoToPoint;
51028
+ exports.useNavigationListActions = useNavigationListActions;
51029
+ exports.useNavigationMove = useNavigationMove;
51030
+ exports.useNavigationPlaySound = useNavigationPlaySound;
51031
+ exports.useNavigationRunAction = useNavigationRunAction;
51032
+ exports.useNavigationSetLightLevel = useNavigationSetLightLevel;
51033
+ exports.useNavigationSetLightMode = useNavigationSetLightMode;
51034
+ exports.useNavigationSetLightOn = useNavigationSetLightOn;
51035
+ exports.useNavigationStop = useNavigationStop;
50384
51036
  exports.useNetworkAccessGetEndpoint = useNetworkAccessGetEndpoint;
50385
51037
  exports.useNetworkAccessGetStatus = useNetworkAccessGetStatus;
50386
51038
  exports.useNetworkAccessListEndpoints = useNetworkAccessListEndpoints;
50387
51039
  exports.useNetworkAccessStart = useNetworkAccessStart;
50388
51040
  exports.useNetworkAccessStop = useNetworkAccessStop;
51041
+ exports.useNetworkLinkGetStatus = useNetworkLinkGetStatus;
50389
51042
  exports.useNetworkQualityGetAllStats = useNetworkQualityGetAllStats;
50390
51043
  exports.useNetworkQualityGetDeviceStats = useNetworkQualityGetDeviceStats;
50391
51044
  exports.useNetworkQualityReportClientStats = useNetworkQualityReportClientStats;