@camstack/ui-library 1.2.120 → 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.
@@ -0,0 +1,2 @@
1
+ import { CapSettingsComponentProps } from './index';
2
+ export declare function NavigationPanel({ deviceId }: CapSettingsComponentProps): import("react").JSX.Element;
@@ -4,6 +4,7 @@ export type { MaskShapeCanvasProps, MaskShapeItem } from './MaskShapeCanvas';
4
4
  export { MaskShapeCanvas } from './MaskShapeCanvas';
5
5
  export { MotionZonesSettings } from './MotionZonesSettings';
6
6
  export { PrivacyMaskSettings } from './PrivacyMaskSettings';
7
+ export { NavigationPanel } from './NavigationPanel';
7
8
  export { PtzPanel } from './PtzPanel';
8
9
  export { RecordingBandsEditor } from './RecordingBandsEditor';
9
10
  export { RecordingPanel } from './RecordingPanel';
@@ -21,6 +21,7 @@ export { AutotrackSection } from './cap-settings/AutotrackSection';
21
21
  export { ConsumablesPanel } from './cap-settings/ConsumablesPanel';
22
22
  export type { CapSettingsComponentProps } from './cap-settings/index';
23
23
  export type { MaskShapeCanvasProps, MaskShapeItem } from './cap-settings/MaskShapeCanvas';
24
+ export { NavigationPanel } from './cap-settings/NavigationPanel';
24
25
  export { PtzPanel } from './cap-settings/PtzPanel';
25
26
  export { RecordingPanel } from './cap-settings/RecordingPanel';
26
27
  export type { SceneCaptureVariant } from './cap-settings/scene-monitor-copy';
@@ -935,6 +935,28 @@ export declare const useMqttBrokerGetStatus: typeof trpc.mqttBroker.getStatus.us
935
935
  export declare const useNativeObjectDetectionSetEnabled: typeof trpc.nativeObjectDetection.setEnabled.useMutation;
936
936
  /** Generated alias around `trpc.nativeObjectDetection.getStatus.useQuery`. */
937
937
  export declare const useNativeObjectDetectionGetStatus: typeof trpc.nativeObjectDetection.getStatus.useQuery;
938
+ /** Generated alias around `trpc.navigation.move.useMutation`. */
939
+ export declare const useNavigationMove: typeof trpc.navigation.move.useMutation;
940
+ /** Generated alias around `trpc.navigation.stop.useMutation`. */
941
+ export declare const useNavigationStop: typeof trpc.navigation.stop.useMutation;
942
+ /** Generated alias around `trpc.navigation.goToPoint.useMutation`. */
943
+ export declare const useNavigationGoToPoint: typeof trpc.navigation.goToPoint.useMutation;
944
+ /** Generated alias around `trpc.navigation.listActions.useQuery`. */
945
+ export declare const useNavigationListActions: typeof trpc.navigation.listActions.useQuery;
946
+ /** Generated alias around `trpc.navigation.runAction.useMutation`. */
947
+ export declare const useNavigationRunAction: typeof trpc.navigation.runAction.useMutation;
948
+ /** Generated alias around `trpc.navigation.playSound.useMutation`. */
949
+ export declare const useNavigationPlaySound: typeof trpc.navigation.playSound.useMutation;
950
+ /** Generated alias around `trpc.navigation.setLightOn.useMutation`. */
951
+ export declare const useNavigationSetLightOn: typeof trpc.navigation.setLightOn.useMutation;
952
+ /** Generated alias around `trpc.navigation.setLightMode.useMutation`. */
953
+ export declare const useNavigationSetLightMode: typeof trpc.navigation.setLightMode.useMutation;
954
+ /** Generated alias around `trpc.navigation.setLightLevel.useMutation`. */
955
+ export declare const useNavigationSetLightLevel: typeof trpc.navigation.setLightLevel.useMutation;
956
+ /** Generated alias around `trpc.navigation.getFeatures.useQuery`. */
957
+ export declare const useNavigationGetFeatures: typeof trpc.navigation.getFeatures.useQuery;
958
+ /** Generated alias around `trpc.navigation.getStatus.useQuery`. */
959
+ export declare const useNavigationGetStatus: typeof trpc.navigation.getStatus.useQuery;
938
960
  /** Generated alias around `trpc.networkAccess.start.useMutation`. */
939
961
  export declare const useNetworkAccessStart: typeof trpc.networkAccess.start.useMutation;
940
962
  /** Generated alias around `trpc.networkAccess.stop.useMutation`. */
@@ -945,6 +967,8 @@ export declare const useNetworkAccessGetEndpoint: typeof trpc.networkAccess.getE
945
967
  export declare const useNetworkAccessGetStatus: typeof trpc.networkAccess.getStatus.useQuery;
946
968
  /** Generated alias around `trpc.networkAccess.listEndpoints.useQuery`. */
947
969
  export declare const useNetworkAccessListEndpoints: typeof trpc.networkAccess.listEndpoints.useQuery;
970
+ /** Generated alias around `trpc.networkLink.getStatus.useQuery`. */
971
+ export declare const useNetworkLinkGetStatus: typeof trpc.networkLink.getStatus.useQuery;
948
972
  /** Generated alias around `trpc.networkQuality.getDeviceStats.useQuery`. */
949
973
  export declare const useNetworkQualityGetDeviceStats: typeof trpc.networkQuality.getDeviceStats.useQuery;
950
974
  /** Generated alias around `trpc.networkQuality.getAllStats.useQuery`. */
@@ -4,6 +4,8 @@ export { useDebouncedString } from './use-debounced-string';
4
4
  export type { UseDeviceAutotrack } from './use-device-autotrack';
5
5
  export { useDeviceAutotrack } from './use-device-autotrack';
6
6
  export type { UseDeviceBatteryTrpc } from './use-device-battery';
7
+ export type { NavigationDirection, UseNavigationResult } from './use-navigation';
8
+ export { NAVIGATION_DRIVE_INTERVAL_MS, useNavigation } from './use-navigation';
7
9
  export { useDeviceBattery } from './use-device-battery';
8
10
  export type { NetworkLinkStatus } from './use-device-network-link';
9
11
  export { useDeviceNetworkLink } from './use-device-network-link';
@@ -0,0 +1,26 @@
1
+ import { NavigationActionEntry, NavigationActionId, NavigationFeatures, NavigationLightMode, NavigationStatus } from '@camstack/types';
2
+ import { UseDeviceProxyTrpc } from './use-device-proxy';
3
+ export type NavigationDirection = 'forward' | 'left' | 'right' | 'spin';
4
+ /** The cadence the robot app drives at while a direction is held. */
5
+ export declare const NAVIGATION_DRIVE_INTERVAL_MS = 1000;
6
+ export interface UseNavigationOptions {
7
+ readonly enabled?: boolean;
8
+ }
9
+ export interface UseNavigationResult {
10
+ readonly features: NavigationFeatures | null;
11
+ readonly actions: readonly NavigationActionEntry[];
12
+ readonly status: NavigationStatus | null;
13
+ /** Start nudging in a direction at 1 Hz until `stopDrive`. */
14
+ readonly startDrive: (direction: NavigationDirection) => void;
15
+ readonly stopDrive: () => Promise<void>;
16
+ readonly runAction: (actionId: NavigationActionId) => Promise<void>;
17
+ readonly playSound: (soundId: number) => Promise<void>;
18
+ readonly setLightOn: (on: boolean) => Promise<void>;
19
+ readonly setLightMode: (mode: NavigationLightMode, level?: number) => Promise<void>;
20
+ readonly setLightLevel: (level: number) => Promise<void>;
21
+ readonly busy: boolean;
22
+ readonly error: string | null;
23
+ /** True while a direction is held. */
24
+ readonly driving: NavigationDirection | null;
25
+ }
26
+ export declare function useNavigation(trpc: UseDeviceProxyTrpc, deviceId: number, hookOptions?: UseNavigationOptions): UseNavigationResult;
package/dist/index.cjs CHANGED
@@ -1890,6 +1890,34 @@ var Flame = createLucideIcon("flame", [["path", {
1890
1890
  * This source code is licensed under the ISC license.
1891
1891
  * See the LICENSE file in the root directory of this source tree.
1892
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
+ */
1893
1921
  var Flashlight = createLucideIcon("flashlight", [
1894
1922
  ["path", {
1895
1923
  d: "M12 13v1",
@@ -2081,6 +2109,30 @@ var Hammer = createLucideIcon("hammer", [
2081
2109
  * This source code is licensed under the ISC license.
2082
2110
  * See the LICENSE file in the root directory of this source tree.
2083
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
+ */
2084
2136
  var HardDrive = createLucideIcon("hard-drive", [
2085
2137
  ["path", {
2086
2138
  d: "M10 16h.01",
@@ -4286,6 +4338,19 @@ var Type = createLucideIcon("type", [
4286
4338
  * This source code is licensed under the ISC license.
4287
4339
  * See the LICENSE file in the root directory of this source tree.
4288
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
+ */
4289
4354
  var Unlink2 = createLucideIcon("unlink-2", [["path", {
4290
4355
  d: "M15 7h2a5 5 0 0 1 0 10h-2m-6 0H7A5 5 0 0 1 7 7h2",
4291
4356
  key: "1re2ne"
@@ -5922,6 +5987,28 @@ var useMqttBrokerGetStatus = trpc.mqttBroker.getStatus.useQuery;
5922
5987
  var useNativeObjectDetectionSetEnabled = trpc.nativeObjectDetection.setEnabled.useMutation;
5923
5988
  /** Generated alias around `trpc.nativeObjectDetection.getStatus.useQuery`. */
5924
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;
5925
6012
  /** Generated alias around `trpc.networkAccess.start.useMutation`. */
5926
6013
  var useNetworkAccessStart = trpc.networkAccess.start.useMutation;
5927
6014
  /** Generated alias around `trpc.networkAccess.stop.useMutation`. */
@@ -5932,6 +6019,8 @@ var useNetworkAccessGetEndpoint = trpc.networkAccess.getEndpoint.useQuery;
5932
6019
  var useNetworkAccessGetStatus = trpc.networkAccess.getStatus.useQuery;
5933
6020
  /** Generated alias around `trpc.networkAccess.listEndpoints.useQuery`. */
5934
6021
  var useNetworkAccessListEndpoints = trpc.networkAccess.listEndpoints.useQuery;
6022
+ /** Generated alias around `trpc.networkLink.getStatus.useQuery`. */
6023
+ var useNetworkLinkGetStatus = trpc.networkLink.getStatus.useQuery;
5935
6024
  /** Generated alias around `trpc.networkQuality.getDeviceStats.useQuery`. */
5936
6025
  var useNetworkQualityGetDeviceStats = trpc.networkQuality.getDeviceStats.useQuery;
5937
6026
  /** Generated alias around `trpc.networkQuality.getAllStats.useQuery`. */
@@ -15987,6 +16076,373 @@ function ConsumablesPanel({ deviceId }) {
15987
16076
  });
15988
16077
  }
15989
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
15990
16446
  //#region src/lib/cap-error.ts
15991
16447
  /**
15992
16448
  * Shared capability-error utilities for the admin UI.
@@ -18812,6 +19268,7 @@ var HOST_WIDGETS = {
18812
19268
  "host/motion-zones-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./MotionZonesSettings-BP9p0pFB.cjs")).then((m) => ({ default: m.MotionZonesSettings }))),
18813
19269
  "host/privacy-mask-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-Oo7gyB4E.cjs")).then((m) => ({ default: m.PrivacyMaskSettings }))),
18814
19270
  "host/ptz-panel": PtzPanel,
19271
+ "host/navigation-panel": NavigationPanel,
18815
19272
  "host/consumables-panel": ConsumablesPanel,
18816
19273
  "host/ptz-autotrack": AutotrackSection,
18817
19274
  "host/recording-panel": RecordingPanel
@@ -49843,6 +50300,8 @@ exports.Mic = Mic;
49843
50300
  exports.MobileDrawer = MobileDrawer;
49844
50301
  exports.ModelPicker = ModelPicker;
49845
50302
  exports.MotionZonesSettings = MotionZonesSettings;
50303
+ exports.NAVIGATION_DRIVE_INTERVAL_MS = NAVIGATION_DRIVE_INTERVAL_MS;
50304
+ exports.NavigationPanel = NavigationPanel;
49846
50305
  exports.NetworkLinkBadge = NetworkLinkBadge;
49847
50306
  exports.NodeMultiSelectField = NodeMultiSelectField;
49848
50307
  exports.NodePicker = NodePicker;
@@ -50562,11 +51021,24 @@ exports.useMqttBrokerStopEmbeddedBroker = useMqttBrokerStopEmbeddedBroker;
50562
51021
  exports.useMqttBrokerTestConnection = useMqttBrokerTestConnection;
50563
51022
  exports.useNativeObjectDetectionGetStatus = useNativeObjectDetectionGetStatus;
50564
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;
50565
51036
  exports.useNetworkAccessGetEndpoint = useNetworkAccessGetEndpoint;
50566
51037
  exports.useNetworkAccessGetStatus = useNetworkAccessGetStatus;
50567
51038
  exports.useNetworkAccessListEndpoints = useNetworkAccessListEndpoints;
50568
51039
  exports.useNetworkAccessStart = useNetworkAccessStart;
50569
51040
  exports.useNetworkAccessStop = useNetworkAccessStop;
51041
+ exports.useNetworkLinkGetStatus = useNetworkLinkGetStatus;
50570
51042
  exports.useNetworkQualityGetAllStats = useNetworkQualityGetAllStats;
50571
51043
  exports.useNetworkQualityGetDeviceStats = useNetworkQualityGetDeviceStats;
50572
51044
  exports.useNetworkQualityReportClientStats = useNetworkQualityReportClientStats;