@camstack/ui-library 0.1.51 → 0.1.53
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/composites/cap-settings/AutotrackSection.d.ts +5 -0
- package/dist/composites/cap-settings/MotionGridCanvas.d.ts +9 -0
- package/dist/composites/cap-settings/MotionZonesSettings.d.ts +2 -0
- package/dist/composites/cap-settings/PtzPanel.d.ts +2 -0
- package/dist/composites/cap-settings/index.d.ts +14 -0
- package/dist/composites/index.d.ts +2 -0
- package/dist/composites/state-values-stream.d.ts +14 -18
- package/dist/contexts/widget-registry.d.ts +67 -6
- package/dist/generated/system-hooks.d.ts +66 -30
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/use-device-autotrack.d.ts +10 -0
- package/dist/hooks/use-ptz.d.ts +29 -1
- package/dist/index.cjs +2140 -1162
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2099 -1148
- package/dist/index.js.map +1 -1
- package/dist/widgets/host-widgets.d.ts +9 -0
- package/package.json +9 -1
package/dist/index.js
CHANGED
|
@@ -13,6 +13,8 @@ import * as __mfHostTrpcClient from "@trpc/client";
|
|
|
13
13
|
import { createTRPCClient, createWSClient, httpLink, splitLink, wsLink } from "@trpc/client";
|
|
14
14
|
import * as __mfHostTrpcReactQuery from "@trpc/react-query";
|
|
15
15
|
import { createTRPCReact } from "@trpc/react-query";
|
|
16
|
+
import * as __mfHostReactKonva from "react-konva";
|
|
17
|
+
import * as __mfHostKonva from "konva";
|
|
16
18
|
import { ALL_CAPABILITY_DEFINITIONS, DeviceExportStatusSchema, EventCategory, ExposedDeviceSchema, KNOWN_CAP_NAMES, createDeviceProxy } from "@camstack/types";
|
|
17
19
|
import { createSystem } from "@camstack/sdk";
|
|
18
20
|
import { z } from "zod";
|
|
@@ -6851,6 +6853,8 @@ function populateShareCache() {
|
|
|
6851
6853
|
cache.share["@tanstack/react-query"] ??= __mfHostTanstackQuery;
|
|
6852
6854
|
cache.share["@trpc/client"] ??= __mfHostTrpcClient;
|
|
6853
6855
|
cache.share["@trpc/react-query"] ??= __mfHostTrpcReactQuery;
|
|
6856
|
+
cache.share["react-konva"] ??= __mfHostReactKonva;
|
|
6857
|
+
cache.share["konva"] ??= __mfHostKonva;
|
|
6854
6858
|
cache.share["@camstack/ui-library"] ??= readHostGlobal("__camstackUiLibrary");
|
|
6855
6859
|
cache.share["@camstack/sdk"] ??= readHostGlobal("__camstackSdk");
|
|
6856
6860
|
cache.share["@camstack/types"] ??= readHostGlobal("__camstackTypes");
|
|
@@ -6869,6 +6873,8 @@ function ensureMfHostInit() {
|
|
|
6869
6873
|
"@tanstack/react-query": npmShared(() => __mfHostTanstackQuery),
|
|
6870
6874
|
"@trpc/client": npmShared(() => __mfHostTrpcClient),
|
|
6871
6875
|
"@trpc/react-query": npmShared(() => __mfHostTrpcReactQuery),
|
|
6876
|
+
"react-konva": npmShared(() => __mfHostReactKonva),
|
|
6877
|
+
"konva": npmShared(() => __mfHostKonva),
|
|
6872
6878
|
"@camstack/ui-library": npmShared(() => readHostGlobal("__camstackUiLibrary")),
|
|
6873
6879
|
"@camstack/sdk": npmShared(() => readHostGlobal("__camstackSdk")),
|
|
6874
6880
|
"@camstack/types": npmShared(() => readHostGlobal("__camstackTypes"))
|
|
@@ -15782,6 +15788,8 @@ var useAddonsGetLastRestart = trpc.addons.getLastRestart.useQuery;
|
|
|
15782
15788
|
var useAddonsListFrameworkPackages = trpc.addons.listFrameworkPackages.useQuery;
|
|
15783
15789
|
/** Generated alias around `trpc.addons.listCapabilityProviders.useQuery`. */
|
|
15784
15790
|
var useAddonsListCapabilityProviders = trpc.addons.listCapabilityProviders.useQuery;
|
|
15791
|
+
/** Generated alias around `trpc.addons.setCapabilityProviderEnabled.useMutation`. */
|
|
15792
|
+
var useAddonsSetCapabilityProviderEnabled = trpc.addons.setCapabilityProviderEnabled.useMutation;
|
|
15785
15793
|
/** Generated alias around `trpc.addons.updateFrameworkPackage.useMutation`. */
|
|
15786
15794
|
var useAddonsUpdateFrameworkPackage = trpc.addons.updateFrameworkPackage.useMutation;
|
|
15787
15795
|
/** Generated alias around `trpc.addons.getVersions.useQuery`. */
|
|
@@ -15872,10 +15880,6 @@ var useAudioCodecListActiveSessions = trpc.audioCodec.listActiveSessions.useQuer
|
|
|
15872
15880
|
var useAudioMetricsGetCurrentSnapshot = trpc.audioMetrics.getCurrentSnapshot.useQuery;
|
|
15873
15881
|
/** Generated alias around `trpc.audioMetrics.getHistory.useQuery`. */
|
|
15874
15882
|
var useAudioMetricsGetHistory = trpc.audioMetrics.getHistory.useQuery;
|
|
15875
|
-
/** Generated alias around `trpc.authentication.listProviders.useQuery`. */
|
|
15876
|
-
var useAuthenticationListProviders = trpc.authentication.listProviders.useQuery;
|
|
15877
|
-
/** Generated alias around `trpc.authentication.setProviderEnabled.useMutation`. */
|
|
15878
|
-
var useAuthenticationSetProviderEnabled = trpc.authentication.setProviderEnabled.useMutation;
|
|
15879
15883
|
/** Generated alias around `trpc.backup.listDestinations.useQuery`. */
|
|
15880
15884
|
var useBackupListDestinations = trpc.backup.listDestinations.useQuery;
|
|
15881
15885
|
/** Generated alias around `trpc.backup.trigger.useMutation`. */
|
|
@@ -15926,6 +15930,12 @@ var useDecoderPushPacket = trpc.decoder.pushPacket.useQuery;
|
|
|
15926
15930
|
var useDecoderOpenStream = trpc.decoder.openStream.useQuery;
|
|
15927
15931
|
/** Generated alias around `trpc.decoder.pullFrames.useQuery`. */
|
|
15928
15932
|
var useDecoderPullFrames = trpc.decoder.pullFrames.useQuery;
|
|
15933
|
+
/** Generated alias around `trpc.decoder.pullHandles.useQuery`. */
|
|
15934
|
+
var useDecoderPullHandles = trpc.decoder.pullHandles.useQuery;
|
|
15935
|
+
/** Generated alias around `trpc.decoder.getFrame.useQuery`. */
|
|
15936
|
+
var useDecoderGetFrame = trpc.decoder.getFrame.useQuery;
|
|
15937
|
+
/** Generated alias around `trpc.decoder.getShmStats.useQuery`. */
|
|
15938
|
+
var useDecoderGetShmStats = trpc.decoder.getShmStats.useQuery;
|
|
15929
15939
|
/** Generated alias around `trpc.decoder.updateConfig.useQuery`. */
|
|
15930
15940
|
var useDecoderUpdateConfig = trpc.decoder.updateConfig.useQuery;
|
|
15931
15941
|
/** Generated alias around `trpc.decoder.getStats.useQuery`. */
|
|
@@ -16168,18 +16178,6 @@ var useMeshNetworkLogout = trpc.meshNetwork.logout.useMutation;
|
|
|
16168
16178
|
var useMeshNetworkListPeers = trpc.meshNetwork.listPeers.useQuery;
|
|
16169
16179
|
/** Generated alias around `trpc.meshNetwork.testConnection.useMutation`. */
|
|
16170
16180
|
var useMeshNetworkTestConnection = trpc.meshNetwork.testConnection.useMutation;
|
|
16171
|
-
/** Generated alias around `trpc.meshOrchestrator.listProviders.useQuery`. */
|
|
16172
|
-
var useMeshOrchestratorListProviders = trpc.meshOrchestrator.listProviders.useQuery;
|
|
16173
|
-
/** Generated alias around `trpc.meshOrchestrator.joinProvider.useMutation`. */
|
|
16174
|
-
var useMeshOrchestratorJoinProvider = trpc.meshOrchestrator.joinProvider.useMutation;
|
|
16175
|
-
/** Generated alias around `trpc.meshOrchestrator.leaveProvider.useMutation`. */
|
|
16176
|
-
var useMeshOrchestratorLeaveProvider = trpc.meshOrchestrator.leaveProvider.useMutation;
|
|
16177
|
-
/** Generated alias around `trpc.meshOrchestrator.startLoginProvider.useMutation`. */
|
|
16178
|
-
var useMeshOrchestratorStartLoginProvider = trpc.meshOrchestrator.startLoginProvider.useMutation;
|
|
16179
|
-
/** Generated alias around `trpc.meshOrchestrator.logoutProvider.useMutation`. */
|
|
16180
|
-
var useMeshOrchestratorLogoutProvider = trpc.meshOrchestrator.logoutProvider.useMutation;
|
|
16181
|
-
/** Generated alias around `trpc.meshOrchestrator.listProviderPeers.useQuery`. */
|
|
16182
|
-
var useMeshOrchestratorListProviderPeers = trpc.meshOrchestrator.listProviderPeers.useQuery;
|
|
16183
16181
|
/** Generated alias around `trpc.metricsProvider.collectSnapshot.useQuery`. */
|
|
16184
16182
|
var useMetricsProviderCollectSnapshot = trpc.metricsProvider.collectSnapshot.useQuery;
|
|
16185
16183
|
/** Generated alias around `trpc.metricsProvider.getCached.useQuery`. */
|
|
@@ -16222,6 +16220,12 @@ var useMotionDetectionApplyDeviceSettingsPatch = trpc.motionDetection.applyDevic
|
|
|
16222
16220
|
var useMotionTriggerSetMotionTrigger = trpc.motionTrigger.setMotionTrigger.useMutation;
|
|
16223
16221
|
/** Generated alias around `trpc.motionTrigger.getStatus.useQuery`. */
|
|
16224
16222
|
var useMotionTriggerGetStatus = trpc.motionTrigger.getStatus.useQuery;
|
|
16223
|
+
/** Generated alias around `trpc.motionZones.getOptions.useQuery`. */
|
|
16224
|
+
var useMotionZonesGetOptions = trpc.motionZones.getOptions.useQuery;
|
|
16225
|
+
/** Generated alias around `trpc.motionZones.setZone.useMutation`. */
|
|
16226
|
+
var useMotionZonesSetZone = trpc.motionZones.setZone.useMutation;
|
|
16227
|
+
/** Generated alias around `trpc.motionZones.getStatus.useQuery`. */
|
|
16228
|
+
var useMotionZonesGetStatus = trpc.motionZones.getStatus.useQuery;
|
|
16225
16229
|
/** Generated alias around `trpc.mqttBroker.listBrokers.useQuery`. */
|
|
16226
16230
|
var useMqttBrokerListBrokers = trpc.mqttBroker.listBrokers.useQuery;
|
|
16227
16231
|
/** Generated alias around `trpc.mqttBroker.getBrokerConfig.useQuery`. */
|
|
@@ -16240,6 +16244,16 @@ var useMqttBrokerStopEmbeddedBroker = trpc.mqttBroker.stopEmbeddedBroker.useMuta
|
|
|
16240
16244
|
var useMqttBrokerGetStatus = trpc.mqttBroker.getStatus.useQuery;
|
|
16241
16245
|
/** Generated alias around `trpc.nativeObjectDetection.getStatus.useQuery`. */
|
|
16242
16246
|
var useNativeObjectDetectionGetStatus = trpc.nativeObjectDetection.getStatus.useQuery;
|
|
16247
|
+
/** Generated alias around `trpc.networkAccess.start.useMutation`. */
|
|
16248
|
+
var useNetworkAccessStart = trpc.networkAccess.start.useMutation;
|
|
16249
|
+
/** Generated alias around `trpc.networkAccess.stop.useMutation`. */
|
|
16250
|
+
var useNetworkAccessStop = trpc.networkAccess.stop.useMutation;
|
|
16251
|
+
/** Generated alias around `trpc.networkAccess.getEndpoint.useQuery`. */
|
|
16252
|
+
var useNetworkAccessGetEndpoint = trpc.networkAccess.getEndpoint.useQuery;
|
|
16253
|
+
/** Generated alias around `trpc.networkAccess.getStatus.useQuery`. */
|
|
16254
|
+
var useNetworkAccessGetStatus = trpc.networkAccess.getStatus.useQuery;
|
|
16255
|
+
/** Generated alias around `trpc.networkAccess.listEndpoints.useQuery`. */
|
|
16256
|
+
var useNetworkAccessListEndpoints = trpc.networkAccess.listEndpoints.useQuery;
|
|
16243
16257
|
/** Generated alias around `trpc.networkQuality.getDeviceStats.useQuery`. */
|
|
16244
16258
|
var useNetworkQualityGetDeviceStats = trpc.networkQuality.getDeviceStats.useQuery;
|
|
16245
16259
|
/** Generated alias around `trpc.networkQuality.getAllStats.useQuery`. */
|
|
@@ -16264,6 +16278,8 @@ var useNodesShutdownNode = trpc.nodes.shutdownNode.useMutation;
|
|
|
16264
16278
|
var useNodesRenameNode = trpc.nodes.renameNode.useMutation;
|
|
16265
16279
|
/** Generated alias around `trpc.nodes.clusterAddonStatus.useQuery`. */
|
|
16266
16280
|
var useNodesClusterAddonStatus = trpc.nodes.clusterAddonStatus.useQuery;
|
|
16281
|
+
/** Generated alias around `trpc.nodes.getCapUsageGraph.useQuery`. */
|
|
16282
|
+
var useNodesGetCapUsageGraph = trpc.nodes.getCapUsageGraph.useQuery;
|
|
16267
16283
|
/** Generated alias around `trpc.nodes.getNodeAddons.useQuery`. */
|
|
16268
16284
|
var useNodesGetNodeAddons = trpc.nodes.getNodeAddons.useQuery;
|
|
16269
16285
|
/** Generated alias around `trpc.nodes.setProcessLogLevel.useMutation`. */
|
|
@@ -16482,10 +16498,18 @@ var usePtzStop = trpc.ptz.stop.useMutation;
|
|
|
16482
16498
|
var usePtzGetPresets = trpc.ptz.getPresets.useQuery;
|
|
16483
16499
|
/** Generated alias around `trpc.ptz.goToPreset.useMutation`. */
|
|
16484
16500
|
var usePtzGoToPreset = trpc.ptz.goToPreset.useMutation;
|
|
16501
|
+
/** Generated alias around `trpc.ptz.savePreset.useMutation`. */
|
|
16502
|
+
var usePtzSavePreset = trpc.ptz.savePreset.useMutation;
|
|
16503
|
+
/** Generated alias around `trpc.ptz.deletePreset.useMutation`. */
|
|
16504
|
+
var usePtzDeletePreset = trpc.ptz.deletePreset.useMutation;
|
|
16505
|
+
/** Generated alias around `trpc.ptz.getOptions.useQuery`. */
|
|
16506
|
+
var usePtzGetOptions = trpc.ptz.getOptions.useQuery;
|
|
16485
16507
|
/** Generated alias around `trpc.ptz.goHome.useMutation`. */
|
|
16486
16508
|
var usePtzGoHome = trpc.ptz.goHome.useMutation;
|
|
16487
16509
|
/** Generated alias around `trpc.ptz.getPosition.useQuery`. */
|
|
16488
16510
|
var usePtzGetPosition = trpc.ptz.getPosition.useQuery;
|
|
16511
|
+
/** Generated alias around `trpc.ptz.setAutofocus.useMutation`. */
|
|
16512
|
+
var usePtzSetAutofocus = trpc.ptz.setAutofocus.useMutation;
|
|
16489
16513
|
/** Generated alias around `trpc.ptz.getStatus.useQuery`. */
|
|
16490
16514
|
var usePtzGetStatus = trpc.ptz.getStatus.useQuery;
|
|
16491
16515
|
/** Generated alias around `trpc.ptzAutotrack.getStatus.useQuery`. */
|
|
@@ -16540,12 +16564,6 @@ var useRecordingEngineGetRetentionConfig = trpc.recordingEngine.getRetentionConf
|
|
|
16540
16564
|
var useRecordingEngineUpdateRetentionConfig = trpc.recordingEngine.updateRetentionConfig.useMutation;
|
|
16541
16565
|
/** Generated alias around `trpc.recordingEngine.getMotionStats.useQuery`. */
|
|
16542
16566
|
var useRecordingEngineGetMotionStats = trpc.recordingEngine.getMotionStats.useQuery;
|
|
16543
|
-
/** Generated alias around `trpc.remoteAccess.listProviders.useQuery`. */
|
|
16544
|
-
var useRemoteAccessListProviders = trpc.remoteAccess.listProviders.useQuery;
|
|
16545
|
-
/** Generated alias around `trpc.remoteAccess.startProvider.useMutation`. */
|
|
16546
|
-
var useRemoteAccessStartProvider = trpc.remoteAccess.startProvider.useMutation;
|
|
16547
|
-
/** Generated alias around `trpc.remoteAccess.stopProvider.useMutation`. */
|
|
16548
|
-
var useRemoteAccessStopProvider = trpc.remoteAccess.stopProvider.useMutation;
|
|
16549
16567
|
/** Generated alias around `trpc.settingsStore.get.useQuery`. */
|
|
16550
16568
|
var useSettingsStoreGet = trpc.settingsStore.get.useQuery;
|
|
16551
16569
|
/** Generated alias around `trpc.settingsStore.set.useMutation`. */
|
|
@@ -16648,8 +16666,18 @@ var useStreamBrokerGetStreamUrl = trpc.streamBroker.getStreamUrl.useQuery;
|
|
|
16648
16666
|
var useStreamBrokerGetStreamWithCodec = trpc.streamBroker.getStreamWithCodec.useMutation;
|
|
16649
16667
|
/** Generated alias around `trpc.streamBroker.releaseStreamWithCodec.useMutation`. */
|
|
16650
16668
|
var useStreamBrokerReleaseStreamWithCodec = trpc.streamBroker.releaseStreamWithCodec.useMutation;
|
|
16651
|
-
/** Generated alias around `trpc.streamBroker.
|
|
16652
|
-
var
|
|
16669
|
+
/** Generated alias around `trpc.streamBroker.subscribeAudioChunks.useMutation`. */
|
|
16670
|
+
var useStreamBrokerSubscribeAudioChunks = trpc.streamBroker.subscribeAudioChunks.useMutation;
|
|
16671
|
+
/** Generated alias around `trpc.streamBroker.pullAudioChunks.useQuery`. */
|
|
16672
|
+
var useStreamBrokerPullAudioChunks = trpc.streamBroker.pullAudioChunks.useQuery;
|
|
16673
|
+
/** Generated alias around `trpc.streamBroker.unsubscribeAudioChunks.useMutation`. */
|
|
16674
|
+
var useStreamBrokerUnsubscribeAudioChunks = trpc.streamBroker.unsubscribeAudioChunks.useMutation;
|
|
16675
|
+
/** Generated alias around `trpc.streamBroker.subscribeFrames.useMutation`. */
|
|
16676
|
+
var useStreamBrokerSubscribeFrames = trpc.streamBroker.subscribeFrames.useMutation;
|
|
16677
|
+
/** Generated alias around `trpc.streamBroker.pullFrameHandles.useQuery`. */
|
|
16678
|
+
var useStreamBrokerPullFrameHandles = trpc.streamBroker.pullFrameHandles.useQuery;
|
|
16679
|
+
/** Generated alias around `trpc.streamBroker.unsubscribeFrames.useMutation`. */
|
|
16680
|
+
var useStreamBrokerUnsubscribeFrames = trpc.streamBroker.unsubscribeFrames.useMutation;
|
|
16653
16681
|
/** Generated alias around `trpc.streamBroker.setPreBufferDuration.useMutation`. */
|
|
16654
16682
|
var useStreamBrokerSetPreBufferDuration = trpc.streamBroker.setPreBufferDuration.useMutation;
|
|
16655
16683
|
/** Generated alias around `trpc.streamBroker.getPreBufferInfo.useQuery`. */
|
|
@@ -16672,6 +16700,14 @@ var useStreamBrokerGetDeviceSettingsContribution = trpc.streamBroker.getDeviceSe
|
|
|
16672
16700
|
var useStreamBrokerGetDeviceLiveContribution = trpc.streamBroker.getDeviceLiveContribution.useQuery;
|
|
16673
16701
|
/** Generated alias around `trpc.streamBroker.applyDeviceSettingsPatch.useMutation`. */
|
|
16674
16702
|
var useStreamBrokerApplyDeviceSettingsPatch = trpc.streamBroker.applyDeviceSettingsPatch.useMutation;
|
|
16703
|
+
/** Generated alias around `trpc.streamParams.getOptions.useQuery`. */
|
|
16704
|
+
var useStreamParamsGetOptions = trpc.streamParams.getOptions.useQuery;
|
|
16705
|
+
/** Generated alias around `trpc.streamParams.setProfile.useMutation`. */
|
|
16706
|
+
var useStreamParamsSetProfile = trpc.streamParams.setProfile.useMutation;
|
|
16707
|
+
/** Generated alias around `trpc.streamParams.getConfigSchema.useQuery`. */
|
|
16708
|
+
var useStreamParamsGetConfigSchema = trpc.streamParams.getConfigSchema.useQuery;
|
|
16709
|
+
/** Generated alias around `trpc.streamParams.getStatus.useQuery`. */
|
|
16710
|
+
var useStreamParamsGetStatus = trpc.streamParams.getStatus.useQuery;
|
|
16675
16711
|
/** Generated alias around `trpc.switch.setState.useMutation`. */
|
|
16676
16712
|
var useSwitchSetState = trpc.switch.setState.useMutation;
|
|
16677
16713
|
/** Generated alias around `trpc.switch.getStatus.useQuery`. */
|
|
@@ -16692,12 +16728,6 @@ var useSystemSetRetentionConfig = trpc.system.setRetentionConfig.useMutation;
|
|
|
16692
16728
|
var useSystemForceRetentionCleanup = trpc.system.forceRetentionCleanup.useMutation;
|
|
16693
16729
|
/** Generated alias around `trpc.toast.onToast.useSubscription`. */
|
|
16694
16730
|
var useToastOnToast = trpc.toast.onToast.useSubscription;
|
|
16695
|
-
/** Generated alias around `trpc.turnOrchestrator.listProviders.useQuery`. */
|
|
16696
|
-
var useTurnOrchestratorListProviders = trpc.turnOrchestrator.listProviders.useQuery;
|
|
16697
|
-
/** Generated alias around `trpc.turnOrchestrator.getAllServers.useQuery`. */
|
|
16698
|
-
var useTurnOrchestratorGetAllServers = trpc.turnOrchestrator.getAllServers.useQuery;
|
|
16699
|
-
/** Generated alias around `trpc.turnOrchestrator.setProviderEnabled.useMutation`. */
|
|
16700
|
-
var useTurnOrchestratorSetProviderEnabled = trpc.turnOrchestrator.setProviderEnabled.useMutation;
|
|
16701
16731
|
/** Generated alias around `trpc.turnProvider.getTurnServers.useQuery`. */
|
|
16702
16732
|
var useTurnProviderGetTurnServers = trpc.turnProvider.getTurnServers.useQuery;
|
|
16703
16733
|
/** Generated alias around `trpc.userManagement.listUsers.useQuery`. */
|
|
@@ -16740,6 +16770,18 @@ var useUserManagementDisableTotp = trpc.userManagement.disableTotp.useMutation;
|
|
|
16740
16770
|
var useUserManagementGetTotpStatus = trpc.userManagement.getTotpStatus.useQuery;
|
|
16741
16771
|
/** Generated alias around `trpc.userManagement.verifyTotp.useMutation`. */
|
|
16742
16772
|
var useUserManagementVerifyTotp = trpc.userManagement.verifyTotp.useMutation;
|
|
16773
|
+
/** Generated alias around `trpc.userManagement.oauthIssueCode.useMutation`. */
|
|
16774
|
+
var useUserManagementOauthIssueCode = trpc.userManagement.oauthIssueCode.useMutation;
|
|
16775
|
+
/** Generated alias around `trpc.userManagement.oauthExchangeCode.useMutation`. */
|
|
16776
|
+
var useUserManagementOauthExchangeCode = trpc.userManagement.oauthExchangeCode.useMutation;
|
|
16777
|
+
/** Generated alias around `trpc.userManagement.oauthRefresh.useMutation`. */
|
|
16778
|
+
var useUserManagementOauthRefresh = trpc.userManagement.oauthRefresh.useMutation;
|
|
16779
|
+
/** Generated alias around `trpc.userManagement.oauthVerifyAccessToken.useQuery`. */
|
|
16780
|
+
var useUserManagementOauthVerifyAccessToken = trpc.userManagement.oauthVerifyAccessToken.useQuery;
|
|
16781
|
+
/** Generated alias around `trpc.userManagement.listOauthSessions.useQuery`. */
|
|
16782
|
+
var useUserManagementListOauthSessions = trpc.userManagement.listOauthSessions.useQuery;
|
|
16783
|
+
/** Generated alias around `trpc.userManagement.revokeOauthSession.useMutation`. */
|
|
16784
|
+
var useUserManagementRevokeOauthSession = trpc.userManagement.revokeOauthSession.useMutation;
|
|
16743
16785
|
/** Generated alias around `trpc.webrtcSession.listStreams.useQuery`. */
|
|
16744
16786
|
var useWebrtcSessionListStreams = trpc.webrtcSession.listStreams.useQuery;
|
|
16745
16787
|
/** Generated alias around `trpc.webrtcSession.createSession.useMutation`. */
|
|
@@ -16783,8 +16825,16 @@ var useZonesUpdateZone = trpc.zones.updateZone.useMutation;
|
|
|
16783
16825
|
* `vite.widgets.config.ts`). At runtime we fetch the public list of
|
|
16784
16826
|
* addon-contributed widgets via `useAddonWidgetsListWidgets()`, register
|
|
16785
16827
|
* each remote (`registerRemotes`) once, then resolve a widget by
|
|
16786
|
-
* loading the exposed
|
|
16787
|
-
*
|
|
16828
|
+
* loading the exposed module (`loadRemote`) whose default export is a
|
|
16829
|
+
* `Record<componentKey, ComponentType>` map.
|
|
16830
|
+
*
|
|
16831
|
+
* Unified UI-contribution model (Task 10) — a widget declaration IS a
|
|
16832
|
+
* `UiContribution` with `kind:'remote'`. Both the device-detail path
|
|
16833
|
+
* (`ContributionRenderer`) and the dashboard render widgets through the
|
|
16834
|
+
* same `loadRemoteBundle` MF path. `loadRemoteBundle` is exported and
|
|
16835
|
+
* generic over `(remoteName, exposedModule, entryUrl)` so any
|
|
16836
|
+
* `UiContributionRemote` descriptor resolves without an aggregator-keyed
|
|
16837
|
+
* lookup.
|
|
16788
16838
|
*
|
|
16789
16839
|
* Why MF instead of raw ESM:
|
|
16790
16840
|
* - automatic dedup of shared deps (react/react-dom/@tanstack/etc.)
|
|
@@ -16794,9 +16844,7 @@ var useZonesUpdateZone = trpc.zones.updateZone.useMutation;
|
|
|
16794
16844
|
* wins by default).
|
|
16795
16845
|
* - cross-bundle context invariant preserved automatically: every
|
|
16796
16846
|
* remote's `@camstack/ui-library` import resolves to the host's
|
|
16797
|
-
* instance, so `createContext()` references match across bundles
|
|
16798
|
-
* (the duplicate-Context bug that motivated `createSharedContext`
|
|
16799
|
-
* falls out for free).
|
|
16847
|
+
* instance, so `createContext()` references match across bundles.
|
|
16800
16848
|
*
|
|
16801
16849
|
* Live-update — listens to `addon.widget-ready` so newly-loaded addons
|
|
16802
16850
|
* surface their widgets without a manual page reload (the aggregator
|
|
@@ -16807,11 +16855,11 @@ var useZonesUpdateZone = trpc.zones.updateZone.useMutation;
|
|
|
16807
16855
|
*/
|
|
16808
16856
|
var WidgetRegistryContext = createSharedContext("camstack:widget-registry", null);
|
|
16809
16857
|
/**
|
|
16810
|
-
* Process-global cache for resolved
|
|
16811
|
-
*
|
|
16812
|
-
* default
|
|
16813
|
-
* provider remount — avoids a full re-fetch + re-init of the
|
|
16814
|
-
* when admin-ui's tree cycles.
|
|
16858
|
+
* Process-global cache for resolved remote bundles, keyed by
|
|
16859
|
+
* `<remoteName>::<exposedModule>`. Each cached value is the exposed
|
|
16860
|
+
* module's default-record map (`Record<componentKey, Component>`).
|
|
16861
|
+
* Survives provider remount — avoids a full re-fetch + re-init of the
|
|
16862
|
+
* MF remote when admin-ui's tree cycles.
|
|
16815
16863
|
*/
|
|
16816
16864
|
var bundleModuleCache = /* @__PURE__ */ new Map();
|
|
16817
16865
|
var bundleInflight = /* @__PURE__ */ new Map();
|
|
@@ -16823,6 +16871,10 @@ var bundleInflight = /* @__PURE__ */ new Map();
|
|
|
16823
16871
|
* update.
|
|
16824
16872
|
*/
|
|
16825
16873
|
var registeredRemotes = /* @__PURE__ */ new Set();
|
|
16874
|
+
/** Cache key for `bundleModuleCache` — one entry per `(remote, module)`. */
|
|
16875
|
+
function bundleCacheKey(remoteName, exposedModule) {
|
|
16876
|
+
return `${remoteName}::${exposedModule}`;
|
|
16877
|
+
}
|
|
16826
16878
|
function isRemoteWidgetsModule(value) {
|
|
16827
16879
|
if (!value || typeof value !== "object") return false;
|
|
16828
16880
|
const def = value.default;
|
|
@@ -16831,8 +16883,7 @@ function isRemoteWidgetsModule(value) {
|
|
|
16831
16883
|
/**
|
|
16832
16884
|
* Diagnostic-only string for the "Got: …" tail in the not-a-record
|
|
16833
16885
|
* error. Returns a JSON-friendly snapshot of the module's keys + proto
|
|
16834
|
-
* name without exposing the value itself
|
|
16835
|
-
* thing the module factory produced, may be huge or contain PII).
|
|
16886
|
+
* name without exposing the value itself.
|
|
16836
16887
|
*/
|
|
16837
16888
|
function describeRemoteShape(mod) {
|
|
16838
16889
|
if (!mod || typeof mod !== "object") return typeof mod;
|
|
@@ -16846,13 +16897,19 @@ function describeRemoteShape(mod) {
|
|
|
16846
16897
|
};
|
|
16847
16898
|
}
|
|
16848
16899
|
/**
|
|
16849
|
-
* Register the MF remote (idempotent) and load its
|
|
16850
|
-
* Returns the resolved `Record<
|
|
16900
|
+
* Register the MF remote (idempotent) and load one of its exposed
|
|
16901
|
+
* modules. Returns the resolved `Record<componentKey, Component>` map
|
|
16902
|
+
* (the exposed module's `default` export).
|
|
16903
|
+
*
|
|
16904
|
+
* Exported so the unified `ContributionRenderer` `kind:'remote'` path
|
|
16905
|
+
* can load an arbitrary `{ remoteName, exposedModule }` descriptor —
|
|
16906
|
+
* the same path the `WidgetRegistry` uses internally for widgets.
|
|
16851
16907
|
*/
|
|
16852
|
-
async function loadRemoteBundle(remoteName, entryUrl) {
|
|
16853
|
-
const
|
|
16908
|
+
async function loadRemoteBundle(remoteName, exposedModule, entryUrl) {
|
|
16909
|
+
const cacheKey = bundleCacheKey(remoteName, exposedModule);
|
|
16910
|
+
const cached = bundleModuleCache.get(cacheKey);
|
|
16854
16911
|
if (cached) return cached;
|
|
16855
|
-
const inflight = bundleInflight.get(
|
|
16912
|
+
const inflight = bundleInflight.get(cacheKey);
|
|
16856
16913
|
if (inflight) return inflight;
|
|
16857
16914
|
if (!registeredRemotes.has(remoteName)) {
|
|
16858
16915
|
ensureMfHostInit();
|
|
@@ -16863,22 +16920,26 @@ async function loadRemoteBundle(remoteName, entryUrl) {
|
|
|
16863
16920
|
}], { force: false });
|
|
16864
16921
|
registeredRemotes.add(remoteName);
|
|
16865
16922
|
}
|
|
16866
|
-
const promise = loadRemote(`${remoteName}
|
|
16923
|
+
const promise = loadRemote(`${remoteName}/${exposedModule.startsWith("./") ? exposedModule.slice(2) : exposedModule}`).then((mod) => {
|
|
16867
16924
|
if (!isRemoteWidgetsModule(mod)) {
|
|
16868
16925
|
const shape = describeRemoteShape(mod);
|
|
16869
|
-
throw new Error(`
|
|
16926
|
+
throw new Error(`Remote ${remoteName} (${entryUrl}) does not expose a default record on '${exposedModule}'. Got: ${JSON.stringify(shape)}`);
|
|
16870
16927
|
}
|
|
16871
16928
|
const map = mod.default;
|
|
16872
|
-
bundleModuleCache.set(
|
|
16873
|
-
bundleInflight.delete(
|
|
16929
|
+
bundleModuleCache.set(cacheKey, map);
|
|
16930
|
+
bundleInflight.delete(cacheKey);
|
|
16874
16931
|
return map;
|
|
16875
16932
|
}).catch((err) => {
|
|
16876
|
-
bundleInflight.delete(
|
|
16933
|
+
bundleInflight.delete(cacheKey);
|
|
16877
16934
|
throw err;
|
|
16878
16935
|
});
|
|
16879
|
-
bundleInflight.set(
|
|
16936
|
+
bundleInflight.set(cacheKey, promise);
|
|
16880
16937
|
return promise;
|
|
16881
16938
|
}
|
|
16939
|
+
/** Synchronous peek at the resolved-bundle cache — `undefined` if not yet loaded. */
|
|
16940
|
+
function peekBundle(remoteName, exposedModule) {
|
|
16941
|
+
return bundleModuleCache.get(bundleCacheKey(remoteName, exposedModule));
|
|
16942
|
+
}
|
|
16882
16943
|
var BOOT_WINDOW_MS = 3e4;
|
|
16883
16944
|
var POLL_INTERVAL_MS = 2e3;
|
|
16884
16945
|
function WidgetRegistryProvider({ children }) {
|
|
@@ -16898,21 +16959,24 @@ function WidgetRegistryProvider({ children }) {
|
|
|
16898
16959
|
queryClient.invalidateQueries({ queryKey: [["addonWidgets", "listWidgets"]] });
|
|
16899
16960
|
});
|
|
16900
16961
|
const [resolvedTick, setResolvedTick] = useState(0);
|
|
16962
|
+
const widgets = useMemo(() => rawWidgets ?? [], [rawWidgets]);
|
|
16901
16963
|
useEffect(() => {
|
|
16902
|
-
if (
|
|
16964
|
+
if (widgets.length === 0) return;
|
|
16903
16965
|
let cancelled = false;
|
|
16904
|
-
const
|
|
16905
|
-
for (const w of
|
|
16906
|
-
|
|
16907
|
-
|
|
16908
|
-
|
|
16966
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16967
|
+
for (const w of widgets) {
|
|
16968
|
+
const key = bundleCacheKey(w.remote.remoteName, w.remote.exposedModule);
|
|
16969
|
+
if (seen.has(key)) continue;
|
|
16970
|
+
seen.add(key);
|
|
16971
|
+
if (bundleModuleCache.has(key)) continue;
|
|
16909
16972
|
const entryUrl = w.bundleUrl;
|
|
16910
|
-
loadRemoteBundle(w.remoteName, entryUrl).then(() => {
|
|
16973
|
+
loadRemoteBundle(w.remote.remoteName, w.remote.exposedModule, entryUrl).then(() => {
|
|
16911
16974
|
if (!cancelled) setResolvedTick((t) => t + 1);
|
|
16912
16975
|
}).catch((err) => {
|
|
16913
16976
|
const reason = err instanceof Error ? err.message : String(err);
|
|
16914
16977
|
(typeof globalThis !== "undefined" ? globalThis.console : void 0)?.error?.("[WidgetRegistry] Failed to load widget remote", {
|
|
16915
|
-
remoteName: w.remoteName,
|
|
16978
|
+
remoteName: w.remote.remoteName,
|
|
16979
|
+
exposedModule: w.remote.exposedModule,
|
|
16916
16980
|
entryUrl,
|
|
16917
16981
|
reason
|
|
16918
16982
|
});
|
|
@@ -16921,19 +16985,26 @@ function WidgetRegistryProvider({ children }) {
|
|
|
16921
16985
|
return () => {
|
|
16922
16986
|
cancelled = true;
|
|
16923
16987
|
};
|
|
16924
|
-
}, [
|
|
16988
|
+
}, [widgets]);
|
|
16925
16989
|
const registry = useMemo(() => {
|
|
16926
|
-
const widgets = rawWidgets ?? [];
|
|
16927
16990
|
const byId = /* @__PURE__ */ new Map();
|
|
16928
|
-
|
|
16991
|
+
const entryUrlByRemote = /* @__PURE__ */ new Map();
|
|
16992
|
+
for (const w of widgets) {
|
|
16993
|
+
byId.set(`${w.addonId}/${w.stableId}`, w);
|
|
16994
|
+
entryUrlByRemote.set(w.remote.remoteName, w.bundleUrl);
|
|
16995
|
+
}
|
|
16929
16996
|
const toMetadata = (widgetId, entry) => ({
|
|
16930
16997
|
widgetId,
|
|
16931
16998
|
addonId: entry.addonId,
|
|
16932
16999
|
stableId: entry.stableId,
|
|
17000
|
+
tab: entry.tab,
|
|
17001
|
+
subTab: entry.subTab,
|
|
16933
17002
|
label: entry.label,
|
|
17003
|
+
order: entry.order,
|
|
17004
|
+
kind: entry.kind,
|
|
17005
|
+
remote: entry.remote,
|
|
16934
17006
|
description: entry.description,
|
|
16935
17007
|
icon: entry.icon,
|
|
16936
|
-
remoteName: entry.remoteName,
|
|
16937
17008
|
bundleUrl: entry.bundleUrl,
|
|
16938
17009
|
hosts: entry.hosts,
|
|
16939
17010
|
requires: entry.requires,
|
|
@@ -16946,9 +17017,9 @@ function WidgetRegistryProvider({ children }) {
|
|
|
16946
17017
|
resolve: (widgetId) => {
|
|
16947
17018
|
const entry = byId.get(widgetId);
|
|
16948
17019
|
if (!entry) return void 0;
|
|
16949
|
-
const bundle =
|
|
17020
|
+
const bundle = peekBundle(entry.remote.remoteName, entry.remote.exposedModule);
|
|
16950
17021
|
if (!bundle) return null;
|
|
16951
|
-
const Component = bundle[entry.stableId];
|
|
17022
|
+
const Component = bundle[entry.remote.componentKey ?? entry.stableId];
|
|
16952
17023
|
if (!Component) return void 0;
|
|
16953
17024
|
return Component;
|
|
16954
17025
|
},
|
|
@@ -16961,15 +17032,23 @@ function WidgetRegistryProvider({ children }) {
|
|
|
16961
17032
|
const out = [];
|
|
16962
17033
|
for (const [widgetId, entry] of byId) out.push(toMetadata(widgetId, entry));
|
|
16963
17034
|
return out;
|
|
16964
|
-
}
|
|
17035
|
+
},
|
|
17036
|
+
entryUrlFor: (remoteName) => entryUrlByRemote.get(remoteName)
|
|
16965
17037
|
};
|
|
16966
|
-
}, [
|
|
17038
|
+
}, [widgets, resolvedTick]);
|
|
16967
17039
|
return /* @__PURE__ */ jsx(WidgetRegistryContext.Provider, {
|
|
16968
17040
|
value: registry,
|
|
16969
17041
|
children
|
|
16970
17042
|
});
|
|
16971
17043
|
}
|
|
16972
|
-
/**
|
|
17044
|
+
/**
|
|
17045
|
+
* Returns the registered widget component, `null` while loading, or
|
|
17046
|
+
* `undefined` if unknown.
|
|
17047
|
+
*
|
|
17048
|
+
* @deprecated Unused by all render paths since the unified
|
|
17049
|
+
* UI-contribution model (Task 10). Use `useRemoteComponent` /
|
|
17050
|
+
* `<WidgetSlot>` instead. Kept for external addons that may import it.
|
|
17051
|
+
*/
|
|
16973
17052
|
function useWidget(widgetId) {
|
|
16974
17053
|
return useWidgetRegistry().resolve(widgetId);
|
|
16975
17054
|
}
|
|
@@ -16983,6 +17062,68 @@ function useWidgetMetadata(widgetId) {
|
|
|
16983
17062
|
function useAllWidgets() {
|
|
16984
17063
|
return useWidgetRegistry().listAll();
|
|
16985
17064
|
}
|
|
17065
|
+
/**
|
|
17066
|
+
* Resolve a `kind:'remote'` `UiContributionRemote` descriptor to a
|
|
17067
|
+
* React component. Drives the unified `ContributionRenderer`
|
|
17068
|
+
* `kind:'remote'` path — both device-detail contributions and dashboard
|
|
17069
|
+
* widgets resolve through the same MF `loadRemoteBundle` loader.
|
|
17070
|
+
*
|
|
17071
|
+
* Returns:
|
|
17072
|
+
* - `undefined` — the remote couldn't be resolved (no entry URL known,
|
|
17073
|
+
* or the remote exposed no component for `componentKey`),
|
|
17074
|
+
* - `null` — the bundle is still loading,
|
|
17075
|
+
* - the component otherwise.
|
|
17076
|
+
*
|
|
17077
|
+
* `entryUrl` may be passed explicitly; when omitted it's resolved from
|
|
17078
|
+
* the registry's aggregator-stamped `bundleUrl` for `remote.remoteName`.
|
|
17079
|
+
*/
|
|
17080
|
+
function useRemoteComponent(remote, entryUrl) {
|
|
17081
|
+
const reg = useOptionalWidgetRegistry();
|
|
17082
|
+
const resolvedEntryUrl = entryUrl ?? reg?.entryUrlFor(remote.remoteName);
|
|
17083
|
+
const [tick, setTick] = useState(0);
|
|
17084
|
+
const [loadFailed, setLoadFailed] = useState(false);
|
|
17085
|
+
useEffect(() => {
|
|
17086
|
+
if (!resolvedEntryUrl) return;
|
|
17087
|
+
setLoadFailed(false);
|
|
17088
|
+
if (peekBundle(remote.remoteName, remote.exposedModule)) return;
|
|
17089
|
+
let cancelled = false;
|
|
17090
|
+
loadRemoteBundle(remote.remoteName, remote.exposedModule, resolvedEntryUrl).then(() => {
|
|
17091
|
+
if (!cancelled) setTick((t) => t + 1);
|
|
17092
|
+
}).catch((err) => {
|
|
17093
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
17094
|
+
(typeof globalThis !== "undefined" ? globalThis.console : void 0)?.error?.("[WidgetRegistry] Failed to load remote component", {
|
|
17095
|
+
remoteName: remote.remoteName,
|
|
17096
|
+
exposedModule: remote.exposedModule,
|
|
17097
|
+
entryUrl: resolvedEntryUrl,
|
|
17098
|
+
reason
|
|
17099
|
+
});
|
|
17100
|
+
if (!cancelled) setLoadFailed(true);
|
|
17101
|
+
});
|
|
17102
|
+
return () => {
|
|
17103
|
+
cancelled = true;
|
|
17104
|
+
};
|
|
17105
|
+
}, [
|
|
17106
|
+
remote.remoteName,
|
|
17107
|
+
remote.exposedModule,
|
|
17108
|
+
resolvedEntryUrl
|
|
17109
|
+
]);
|
|
17110
|
+
return useMemo(() => {
|
|
17111
|
+
if (!resolvedEntryUrl) return void 0;
|
|
17112
|
+
if (loadFailed) return void 0;
|
|
17113
|
+
const bundle = peekBundle(remote.remoteName, remote.exposedModule);
|
|
17114
|
+
if (!bundle) return null;
|
|
17115
|
+
const componentKey = remote.componentKey;
|
|
17116
|
+
if (componentKey === void 0) return Object.values(bundle)[0] ?? void 0;
|
|
17117
|
+
return bundle[componentKey] ?? void 0;
|
|
17118
|
+
}, [
|
|
17119
|
+
remote.remoteName,
|
|
17120
|
+
remote.exposedModule,
|
|
17121
|
+
remote.componentKey,
|
|
17122
|
+
resolvedEntryUrl,
|
|
17123
|
+
tick,
|
|
17124
|
+
loadFailed
|
|
17125
|
+
]);
|
|
17126
|
+
}
|
|
16986
17127
|
/** Read the registry instance — throws when no provider is mounted. */
|
|
16987
17128
|
function useWidgetRegistry() {
|
|
16988
17129
|
const ctx = useOptionalWidgetRegistry();
|
|
@@ -16997,163 +17138,1429 @@ function useContextSafe(ctx) {
|
|
|
16997
17138
|
return useContext(ctx);
|
|
16998
17139
|
}
|
|
16999
17140
|
//#endregion
|
|
17000
|
-
//#region src/
|
|
17141
|
+
//#region src/hooks/use-ptz.ts
|
|
17001
17142
|
/**
|
|
17002
|
-
*
|
|
17003
|
-
* widget. Consumers reference a widget by its public id
|
|
17004
|
-
* (`<addonId>/<stableId>`), the slot looks the component up in the
|
|
17005
|
-
* shared `WidgetRegistry`, validates host context against the widget's
|
|
17006
|
-
* `requires` metadata, and renders one of:
|
|
17007
|
-
* - skeleton placeholder while the bundle is still loading,
|
|
17008
|
-
* - inline error fallback when the widget id is unknown OR the host
|
|
17009
|
-
* didn't supply a required context (`deviceContext` /
|
|
17010
|
-
* `integrationContext`),
|
|
17011
|
-
* - the resolved component otherwise.
|
|
17143
|
+
* usePTZ — PTZ control hook for device-scoped pan / tilt / zoom.
|
|
17012
17144
|
*
|
|
17013
|
-
*
|
|
17014
|
-
*
|
|
17015
|
-
*
|
|
17145
|
+
* Wraps the `ptz` capability methods through the canonical
|
|
17146
|
+
* `useDeviceProxy` surface — every call goes through
|
|
17147
|
+
* `dev.ptz?.<method>(...)` with deviceId/nodeId auto-injected. The
|
|
17148
|
+
* hook stays bare-bones around state (`busy`, `error`, `presets`)
|
|
17149
|
+
* so the operator-facing component (PTZOverlay) stays trivial.
|
|
17150
|
+
*
|
|
17151
|
+
* Returns:
|
|
17152
|
+
* - `move(direction)`: discrete one-shot move (cap.move + cap.stop).
|
|
17153
|
+
* Use for short pulse moves triggered by tapping a d-pad button.
|
|
17154
|
+
* - `startContinuous(direction)` / `stopContinuous()`: gesture-driven
|
|
17155
|
+
* continuous motion (`cap.continuousMove` + `cap.stop`). Use for
|
|
17156
|
+
* long-press handlers on touch / mouse-down handlers on desktop.
|
|
17157
|
+
* - `zoom('in' | 'out')`: discrete zoom step.
|
|
17158
|
+
* - `goHome()`: jump to preset 0.
|
|
17159
|
+
* - `presets` + `goToPreset(presetId)`: list and jump to named presets.
|
|
17160
|
+
*
|
|
17161
|
+
* Parametrised by the same `UseDeviceProxyTrpc` shape every other
|
|
17162
|
+
* device hook uses — works under admin-ui (BackendClient.trpc) and
|
|
17163
|
+
* addon pages (AddonPageProps.trpc) alike.
|
|
17016
17164
|
*/
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
}
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
}
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
|
|
17039
|
-
|
|
17165
|
+
var DIRECTION_VECTORS = {
|
|
17166
|
+
"up": {
|
|
17167
|
+
pan: 0,
|
|
17168
|
+
tilt: 1
|
|
17169
|
+
},
|
|
17170
|
+
"down": {
|
|
17171
|
+
pan: 0,
|
|
17172
|
+
tilt: -1
|
|
17173
|
+
},
|
|
17174
|
+
"left": {
|
|
17175
|
+
pan: -1,
|
|
17176
|
+
tilt: 0
|
|
17177
|
+
},
|
|
17178
|
+
"right": {
|
|
17179
|
+
pan: 1,
|
|
17180
|
+
tilt: 0
|
|
17181
|
+
},
|
|
17182
|
+
"up-left": {
|
|
17183
|
+
pan: -1,
|
|
17184
|
+
tilt: 1
|
|
17185
|
+
},
|
|
17186
|
+
"up-right": {
|
|
17187
|
+
pan: 1,
|
|
17188
|
+
tilt: 1
|
|
17189
|
+
},
|
|
17190
|
+
"down-left": {
|
|
17191
|
+
pan: -1,
|
|
17192
|
+
tilt: -1
|
|
17193
|
+
},
|
|
17194
|
+
"down-right": {
|
|
17195
|
+
pan: 1,
|
|
17196
|
+
tilt: -1
|
|
17040
17197
|
}
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
|
|
17051
|
-
|
|
17052
|
-
|
|
17053
|
-
|
|
17054
|
-
|
|
17055
|
-
|
|
17056
|
-
|
|
17057
|
-
|
|
17058
|
-
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
17062
|
-
|
|
17063
|
-
|
|
17064
|
-
|
|
17065
|
-
|
|
17066
|
-
|
|
17067
|
-
}
|
|
17068
|
-
|
|
17069
|
-
|
|
17070
|
-
|
|
17071
|
-
|
|
17072
|
-
|
|
17073
|
-
function FieldWrapper({ label, description, required, span, children, translationFn }) {
|
|
17074
|
-
const colSpanClass = span === 2 ? "col-span-2" : span === 3 ? "col-span-3" : span === 4 ? "col-span-4" : "col-span-1";
|
|
17075
|
-
const resolvedLabel = resolveLabel(label, translationFn);
|
|
17076
|
-
const resolvedDescription = resolveLabel(description, translationFn);
|
|
17077
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
17078
|
-
className: colSpanClass,
|
|
17079
|
-
children: [
|
|
17080
|
-
resolvedLabel !== void 0 && resolvedLabel !== "" && /* @__PURE__ */ jsxs("label", {
|
|
17081
|
-
className: LABEL_CLASS,
|
|
17082
|
-
children: [resolvedLabel, required && /* @__PURE__ */ jsx("span", {
|
|
17083
|
-
className: "text-danger ml-0.5",
|
|
17084
|
-
children: "*"
|
|
17085
|
-
})]
|
|
17086
|
-
}),
|
|
17087
|
-
children,
|
|
17088
|
-
resolvedDescription && /* @__PURE__ */ jsx("p", {
|
|
17089
|
-
className: DESC_CLASS,
|
|
17090
|
-
children: resolvedDescription
|
|
17091
|
-
})
|
|
17092
|
-
]
|
|
17093
|
-
});
|
|
17094
|
-
}
|
|
17095
|
-
function TextField({ field, value, onChange, disabled, translationFn }) {
|
|
17096
|
-
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
17097
|
-
label: field.label,
|
|
17098
|
-
description: field.description,
|
|
17099
|
-
required: field.required,
|
|
17100
|
-
span: field.span,
|
|
17101
|
-
translationFn,
|
|
17102
|
-
children: /* @__PURE__ */ jsx("input", {
|
|
17103
|
-
type: field.inputType ?? "text",
|
|
17104
|
-
className: INPUT_CLASS,
|
|
17105
|
-
value: value === void 0 || value === null ? "" : String(value),
|
|
17106
|
-
placeholder: field.placeholder,
|
|
17107
|
-
maxLength: field.maxLength,
|
|
17108
|
-
pattern: field.pattern,
|
|
17109
|
-
disabled: disabled || field.disabled,
|
|
17110
|
-
onChange: (e) => onChange(e.target.value)
|
|
17111
|
-
})
|
|
17112
|
-
});
|
|
17113
|
-
}
|
|
17114
|
-
function NumberField({ field, value, onChange, disabled, translationFn }) {
|
|
17115
|
-
const [local, setLocal] = useState(value === void 0 || value === null ? "" : String(value));
|
|
17116
|
-
const focusedRef = useRef(false);
|
|
17198
|
+
};
|
|
17199
|
+
function usePTZ(trpc, deviceId, hookOptions) {
|
|
17200
|
+
const defaultSpeed = hookOptions?.defaultSpeed ?? .5;
|
|
17201
|
+
const pulseMs = hookOptions?.pulseMs ?? 250;
|
|
17202
|
+
const enabled = hookOptions?.enabled ?? true;
|
|
17203
|
+
const ptz = useDeviceProxy(trpc, enabled ? deviceId : null)?.ptz;
|
|
17204
|
+
const [presets, setPresets] = useState([]);
|
|
17205
|
+
const [options, setOptions] = useState(null);
|
|
17206
|
+
const [busy, setBusy] = useState(false);
|
|
17207
|
+
const [error, setError] = useState(null);
|
|
17208
|
+
const isAbsentProvider = (err) => {
|
|
17209
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17210
|
+
return msg.includes("provider not available") || msg.includes("no 'ptz' binding");
|
|
17211
|
+
};
|
|
17212
|
+
const refreshPresets = useCallback(async () => {
|
|
17213
|
+
if (!enabled || !ptz) return;
|
|
17214
|
+
try {
|
|
17215
|
+
setPresets(await ptz.getPresets({}));
|
|
17216
|
+
} catch (err) {
|
|
17217
|
+
if (isAbsentProvider(err)) return;
|
|
17218
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
17219
|
+
}
|
|
17220
|
+
}, [ptz, enabled]);
|
|
17221
|
+
const refreshOptions = useCallback(async () => {
|
|
17222
|
+
if (!enabled || !ptz) return;
|
|
17223
|
+
try {
|
|
17224
|
+
setOptions(await ptz.getOptions({}));
|
|
17225
|
+
} catch (err) {
|
|
17226
|
+
if (isAbsentProvider(err)) return;
|
|
17227
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
17228
|
+
}
|
|
17229
|
+
}, [ptz, enabled]);
|
|
17117
17230
|
useEffect(() => {
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
}, [
|
|
17121
|
-
const
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17231
|
+
refreshPresets();
|
|
17232
|
+
refreshOptions();
|
|
17233
|
+
}, [refreshPresets, refreshOptions]);
|
|
17234
|
+
const wrap = useCallback(async (fn) => {
|
|
17235
|
+
if (!enabled || !ptz) return void 0;
|
|
17236
|
+
setError(null);
|
|
17237
|
+
setBusy(true);
|
|
17238
|
+
try {
|
|
17239
|
+
return await fn();
|
|
17240
|
+
} catch (err) {
|
|
17241
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
17125
17242
|
return;
|
|
17243
|
+
} finally {
|
|
17244
|
+
setBusy(false);
|
|
17126
17245
|
}
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
17246
|
+
}, [enabled, ptz]);
|
|
17247
|
+
return {
|
|
17248
|
+
move: useCallback(async (direction, speed) => {
|
|
17249
|
+
if (!ptz) return;
|
|
17250
|
+
const v = DIRECTION_VECTORS[direction];
|
|
17251
|
+
const s = speed ?? defaultSpeed;
|
|
17252
|
+
await wrap(async () => {
|
|
17253
|
+
await ptz.move({
|
|
17254
|
+
pan: v.pan,
|
|
17255
|
+
tilt: v.tilt,
|
|
17256
|
+
speed: s
|
|
17257
|
+
});
|
|
17258
|
+
await new Promise((r) => setTimeout(r, pulseMs));
|
|
17259
|
+
await ptz.stop({});
|
|
17260
|
+
});
|
|
17261
|
+
}, [
|
|
17262
|
+
ptz,
|
|
17263
|
+
defaultSpeed,
|
|
17264
|
+
pulseMs,
|
|
17265
|
+
wrap
|
|
17266
|
+
]),
|
|
17267
|
+
startContinuous: useCallback(async (direction, speed) => {
|
|
17268
|
+
if (!ptz) return;
|
|
17269
|
+
const v = DIRECTION_VECTORS[direction];
|
|
17270
|
+
const s = speed ?? defaultSpeed;
|
|
17271
|
+
await wrap(() => ptz.continuousMove({
|
|
17272
|
+
pan: v.pan,
|
|
17273
|
+
tilt: v.tilt,
|
|
17274
|
+
speed: s
|
|
17275
|
+
}));
|
|
17276
|
+
}, [
|
|
17277
|
+
ptz,
|
|
17278
|
+
defaultSpeed,
|
|
17279
|
+
wrap
|
|
17280
|
+
]),
|
|
17281
|
+
stopContinuous: useCallback(async () => {
|
|
17282
|
+
if (!ptz) return;
|
|
17283
|
+
await wrap(() => ptz.stop({}));
|
|
17284
|
+
}, [ptz, wrap]),
|
|
17285
|
+
zoom: useCallback(async (direction, speed) => {
|
|
17286
|
+
if (!ptz) return;
|
|
17287
|
+
const z = direction === "in" ? 1 : -1;
|
|
17288
|
+
const s = speed ?? defaultSpeed;
|
|
17289
|
+
await wrap(async () => {
|
|
17290
|
+
await ptz.move({
|
|
17291
|
+
zoom: z,
|
|
17292
|
+
speed: s
|
|
17293
|
+
});
|
|
17294
|
+
await new Promise((r) => setTimeout(r, pulseMs));
|
|
17295
|
+
await ptz.stop({});
|
|
17296
|
+
});
|
|
17297
|
+
}, [
|
|
17298
|
+
ptz,
|
|
17299
|
+
defaultSpeed,
|
|
17300
|
+
pulseMs,
|
|
17301
|
+
wrap
|
|
17302
|
+
]),
|
|
17303
|
+
goHome: useCallback(async () => {
|
|
17304
|
+
if (!ptz) return;
|
|
17305
|
+
await wrap(() => ptz.goHome({}));
|
|
17306
|
+
}, [ptz, wrap]),
|
|
17307
|
+
goToPreset: useCallback(async (presetId) => {
|
|
17308
|
+
if (!ptz) return;
|
|
17309
|
+
await wrap(() => ptz.goToPreset({ presetId }));
|
|
17310
|
+
}, [ptz, wrap]),
|
|
17311
|
+
savePreset: useCallback(async (name) => {
|
|
17312
|
+
if (!ptz) return void 0;
|
|
17313
|
+
return wrap(async () => {
|
|
17314
|
+
const usedIds = new Set(presets.map((p) => Number(p.id)).filter((n) => Number.isInteger(n) && n >= 0));
|
|
17315
|
+
let nextId = 0;
|
|
17316
|
+
while (usedIds.has(nextId)) nextId += 1;
|
|
17317
|
+
const presetId = String(nextId);
|
|
17318
|
+
await ptz.savePreset({
|
|
17319
|
+
presetId,
|
|
17320
|
+
name
|
|
17321
|
+
});
|
|
17322
|
+
await refreshPresets();
|
|
17323
|
+
return presetId;
|
|
17324
|
+
});
|
|
17325
|
+
}, [
|
|
17326
|
+
ptz,
|
|
17327
|
+
presets,
|
|
17328
|
+
refreshPresets,
|
|
17329
|
+
wrap
|
|
17330
|
+
]),
|
|
17331
|
+
deletePreset: useCallback(async (presetId) => {
|
|
17332
|
+
if (!ptz) return;
|
|
17333
|
+
await wrap(async () => {
|
|
17334
|
+
await ptz.deletePreset({ presetId });
|
|
17335
|
+
await refreshPresets();
|
|
17336
|
+
});
|
|
17337
|
+
}, [
|
|
17338
|
+
ptz,
|
|
17339
|
+
refreshPresets,
|
|
17340
|
+
wrap
|
|
17341
|
+
]),
|
|
17342
|
+
presets,
|
|
17343
|
+
refreshPresets,
|
|
17344
|
+
options,
|
|
17345
|
+
busy,
|
|
17346
|
+
error
|
|
17347
|
+
};
|
|
17348
|
+
}
|
|
17349
|
+
//#endregion
|
|
17350
|
+
//#region src/composites/ptz-overlay.tsx
|
|
17351
|
+
/**
|
|
17352
|
+
* PTZOverlay — pan / tilt / zoom controls.
|
|
17353
|
+
*
|
|
17354
|
+
* Two visual variants driven by `mode`:
|
|
17355
|
+
* - `'overlay'` (default): translucent dark pill positioned bottom-
|
|
17356
|
+
* right of the camera viewport. Used as `extraOverlay` on the
|
|
17357
|
+
* `CameraStreamPlayer` so operators can drive PTZ without leaving
|
|
17358
|
+
* the live view. Opaque background + subtle ring keeps the d-pad
|
|
17359
|
+
* legible against any frame.
|
|
17360
|
+
* - `'panel'`: full-bleed inside a host container (e.g. the floating
|
|
17361
|
+
* PTZ panel in DeviceDetail). No absolute positioning, no inner
|
|
17362
|
+
* wrapper card — the host's chrome is the only frame. Inherits the
|
|
17363
|
+
* surrounding `bg-surface` so the dark theme reads consistently
|
|
17364
|
+
* instead of the previous always-dark-bubble look.
|
|
17365
|
+
*
|
|
17366
|
+
* Interaction model is identical across modes: short tap fires a
|
|
17367
|
+
* discrete pulse (`move`); long press starts continuous motion until
|
|
17368
|
+
* release (`startContinuous` + `stopContinuous` on pointer up).
|
|
17369
|
+
*/
|
|
17370
|
+
function DPadButton({ direction, icon: Icon, disabled, className, variant, onMove, onStart, onStop }) {
|
|
17371
|
+
const [pressedAt, setPressedAt] = useState(null);
|
|
17372
|
+
const [continuous, setContinuous] = useState(false);
|
|
17373
|
+
const handlePointerDown = useCallback((e) => {
|
|
17374
|
+
if (disabled) return;
|
|
17375
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
17376
|
+
setPressedAt(Date.now());
|
|
17377
|
+
setContinuous(false);
|
|
17378
|
+
const timer = setTimeout(() => {
|
|
17379
|
+
setContinuous(true);
|
|
17380
|
+
onStart(direction);
|
|
17381
|
+
}, 250);
|
|
17382
|
+
e.currentTarget.dataset.timer = String(timer);
|
|
17383
|
+
}, [
|
|
17384
|
+
direction,
|
|
17385
|
+
disabled,
|
|
17386
|
+
onStart
|
|
17387
|
+
]);
|
|
17388
|
+
const handlePointerUp = useCallback((e) => {
|
|
17389
|
+
const timerId = Number(e.currentTarget.dataset.timer);
|
|
17390
|
+
if (timerId) clearTimeout(timerId);
|
|
17391
|
+
e.currentTarget.dataset.timer = "";
|
|
17392
|
+
if (continuous) onStop();
|
|
17393
|
+
else if (pressedAt !== null) onMove(direction);
|
|
17394
|
+
setPressedAt(null);
|
|
17395
|
+
setContinuous(false);
|
|
17396
|
+
}, [
|
|
17397
|
+
continuous,
|
|
17398
|
+
direction,
|
|
17399
|
+
onMove,
|
|
17400
|
+
onStop,
|
|
17401
|
+
pressedAt
|
|
17402
|
+
]);
|
|
17403
|
+
const handlePointerCancel = useCallback((e) => {
|
|
17404
|
+
const timerId = Number(e.currentTarget.dataset.timer);
|
|
17405
|
+
if (timerId) clearTimeout(timerId);
|
|
17406
|
+
e.currentTarget.dataset.timer = "";
|
|
17407
|
+
if (continuous) onStop();
|
|
17408
|
+
setPressedAt(null);
|
|
17409
|
+
setContinuous(false);
|
|
17410
|
+
}, [continuous, onStop]);
|
|
17411
|
+
const sizeClass = variant === "panel" ? "h-9 w-9" : "h-7 w-7";
|
|
17412
|
+
const iconSizeClass = variant === "panel" ? "h-4 w-4" : "h-3.5 w-3.5";
|
|
17413
|
+
return /* @__PURE__ */ jsx("button", {
|
|
17414
|
+
type: "button",
|
|
17415
|
+
disabled,
|
|
17416
|
+
onPointerDown: handlePointerDown,
|
|
17417
|
+
onPointerUp: handlePointerUp,
|
|
17418
|
+
onPointerCancel: handlePointerCancel,
|
|
17419
|
+
className: cn("flex items-center justify-center rounded transition-colors disabled:opacity-40", variant === "panel" ? "text-foreground hover:bg-surface-hover active:bg-surface-hover/80" : "text-white hover:bg-white/15 active:bg-white/25", sizeClass, className),
|
|
17420
|
+
title: direction,
|
|
17421
|
+
children: /* @__PURE__ */ jsx(Icon, { className: iconSizeClass })
|
|
17422
|
+
});
|
|
17423
|
+
}
|
|
17424
|
+
function PTZOverlay({ controls, mode = "overlay", showPresets, showZoom = true, showHome = true, className }) {
|
|
17425
|
+
const { move, startContinuous, stopContinuous, zoom, goHome, goToPreset, savePreset, deletePreset, presets, options, busy, error } = controls;
|
|
17426
|
+
const confirm = useConfirm();
|
|
17427
|
+
const [presetsOpen, setPresetsOpen] = useState(false);
|
|
17428
|
+
const [presetName, setPresetName] = useState("");
|
|
17429
|
+
const presetsVisible = (showPresets ?? presets.length > 0) && presets.length > 0;
|
|
17430
|
+
const isPanel = mode === "panel";
|
|
17431
|
+
const presetManagementVisible = isPanel && (options?.supportsPresets ?? false);
|
|
17432
|
+
const maxPresetsReached = options?.maxPresets !== void 0 && presets.length >= options.maxPresets;
|
|
17433
|
+
const handleSavePreset = useCallback(async () => {
|
|
17434
|
+
const name = presetName.trim();
|
|
17435
|
+
if (!name || busy || maxPresetsReached) return;
|
|
17436
|
+
if (await savePreset(name) !== void 0) setPresetName("");
|
|
17437
|
+
}, [
|
|
17438
|
+
presetName,
|
|
17439
|
+
busy,
|
|
17440
|
+
maxPresetsReached,
|
|
17441
|
+
savePreset
|
|
17442
|
+
]);
|
|
17443
|
+
const handleDeletePreset = useCallback(async (presetId, label) => {
|
|
17444
|
+
if (!await confirm({
|
|
17445
|
+
title: "Delete preset",
|
|
17446
|
+
message: `Delete PTZ preset "${label}"? This removes it from the camera.`,
|
|
17447
|
+
confirmLabel: "Delete",
|
|
17448
|
+
variant: "danger"
|
|
17449
|
+
})) return;
|
|
17450
|
+
await deletePreset(presetId);
|
|
17451
|
+
}, [confirm, deletePreset]);
|
|
17452
|
+
const containerClass = isPanel ? "flex flex-col items-stretch gap-2 w-full h-full p-3" : "pointer-events-auto absolute top-3 right-3 z-10 flex flex-col items-end gap-2";
|
|
17453
|
+
const rowClass = isPanel ? cn("flex items-center gap-3 rounded-lg p-2", busy && "ring-1 ring-primary/40") : cn("flex items-center gap-2 rounded-lg border border-white/30 bg-zinc-900/90 backdrop-blur-md p-2 shadow-lg shadow-black/40", busy && "ring-1 ring-primary/40");
|
|
17454
|
+
const sideButtonSize = isPanel ? "h-9 w-9" : "h-7 w-7";
|
|
17455
|
+
const sideIconSize = isPanel ? "h-4 w-4" : "h-3.5 w-3.5";
|
|
17456
|
+
const sideButtonHover = isPanel ? "text-foreground hover:bg-surface-hover" : "text-white hover:bg-white/15";
|
|
17457
|
+
const sepClass = isPanel ? "h-12 w-px bg-border mx-1" : "h-12 w-px bg-white/15 mx-1";
|
|
17458
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
17459
|
+
className: cn(containerClass, className),
|
|
17460
|
+
children: [
|
|
17461
|
+
error && /* @__PURE__ */ jsxs("div", {
|
|
17462
|
+
className: "rounded bg-danger/90 px-2 py-1 text-[10px] font-medium text-white shadow-lg max-w-[200px] self-center",
|
|
17463
|
+
children: ["PTZ: ", error]
|
|
17464
|
+
}),
|
|
17465
|
+
/* @__PURE__ */ jsxs("div", {
|
|
17466
|
+
className: cn(rowClass, isPanel && "justify-center"),
|
|
17467
|
+
children: [
|
|
17468
|
+
/* @__PURE__ */ jsxs("div", {
|
|
17469
|
+
className: "grid grid-cols-3 gap-0.5",
|
|
17470
|
+
children: [
|
|
17471
|
+
/* @__PURE__ */ jsx("span", {
|
|
17472
|
+
"aria-hidden": true,
|
|
17473
|
+
className: isPanel ? "h-9 w-9" : "h-7 w-7"
|
|
17474
|
+
}),
|
|
17475
|
+
/* @__PURE__ */ jsx(DPadButton, {
|
|
17476
|
+
direction: "up",
|
|
17477
|
+
icon: ArrowUp,
|
|
17478
|
+
variant: mode,
|
|
17479
|
+
onMove: move,
|
|
17480
|
+
onStart: startContinuous,
|
|
17481
|
+
onStop: stopContinuous
|
|
17482
|
+
}),
|
|
17483
|
+
/* @__PURE__ */ jsx("span", {
|
|
17484
|
+
"aria-hidden": true,
|
|
17485
|
+
className: isPanel ? "h-9 w-9" : "h-7 w-7"
|
|
17486
|
+
}),
|
|
17487
|
+
/* @__PURE__ */ jsx(DPadButton, {
|
|
17488
|
+
direction: "left",
|
|
17489
|
+
icon: ArrowLeft,
|
|
17490
|
+
variant: mode,
|
|
17491
|
+
onMove: move,
|
|
17492
|
+
onStart: startContinuous,
|
|
17493
|
+
onStop: stopContinuous
|
|
17494
|
+
}),
|
|
17495
|
+
/* @__PURE__ */ jsx("span", {
|
|
17496
|
+
"aria-hidden": true,
|
|
17497
|
+
className: isPanel ? "h-9 w-9" : "h-7 w-7"
|
|
17498
|
+
}),
|
|
17499
|
+
/* @__PURE__ */ jsx(DPadButton, {
|
|
17500
|
+
direction: "right",
|
|
17501
|
+
icon: ArrowRight,
|
|
17502
|
+
variant: mode,
|
|
17503
|
+
onMove: move,
|
|
17504
|
+
onStart: startContinuous,
|
|
17505
|
+
onStop: stopContinuous
|
|
17506
|
+
}),
|
|
17507
|
+
/* @__PURE__ */ jsx("span", {
|
|
17508
|
+
"aria-hidden": true,
|
|
17509
|
+
className: isPanel ? "h-9 w-9" : "h-7 w-7"
|
|
17510
|
+
}),
|
|
17511
|
+
/* @__PURE__ */ jsx(DPadButton, {
|
|
17512
|
+
direction: "down",
|
|
17513
|
+
icon: ArrowDown,
|
|
17514
|
+
variant: mode,
|
|
17515
|
+
onMove: move,
|
|
17516
|
+
onStart: startContinuous,
|
|
17517
|
+
onStop: stopContinuous
|
|
17518
|
+
}),
|
|
17519
|
+
/* @__PURE__ */ jsx("span", {
|
|
17520
|
+
"aria-hidden": true,
|
|
17521
|
+
className: isPanel ? "h-9 w-9" : "h-7 w-7"
|
|
17522
|
+
})
|
|
17523
|
+
]
|
|
17524
|
+
}),
|
|
17525
|
+
(showZoom || showHome) && /* @__PURE__ */ jsx("div", { className: sepClass }),
|
|
17526
|
+
/* @__PURE__ */ jsxs("div", {
|
|
17527
|
+
className: "flex flex-col gap-0.5",
|
|
17528
|
+
children: [showZoom && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
17529
|
+
type: "button",
|
|
17530
|
+
onClick: () => zoom("in"),
|
|
17531
|
+
disabled: busy,
|
|
17532
|
+
title: "Zoom in",
|
|
17533
|
+
className: cn("flex items-center justify-center rounded disabled:opacity-40", sideButtonSize, sideButtonHover),
|
|
17534
|
+
children: /* @__PURE__ */ jsx(ZoomIn, { className: sideIconSize })
|
|
17535
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
17536
|
+
type: "button",
|
|
17537
|
+
onClick: () => zoom("out"),
|
|
17538
|
+
disabled: busy,
|
|
17539
|
+
title: "Zoom out",
|
|
17540
|
+
className: cn("flex items-center justify-center rounded disabled:opacity-40", sideButtonSize, sideButtonHover),
|
|
17541
|
+
children: /* @__PURE__ */ jsx(ZoomOut, { className: sideIconSize })
|
|
17542
|
+
})] }), showHome && /* @__PURE__ */ jsx("button", {
|
|
17543
|
+
type: "button",
|
|
17544
|
+
onClick: () => goHome(),
|
|
17545
|
+
disabled: busy,
|
|
17546
|
+
title: "Go home",
|
|
17547
|
+
className: cn("flex items-center justify-center rounded disabled:opacity-40", sideButtonSize, sideButtonHover),
|
|
17548
|
+
children: /* @__PURE__ */ jsx(House, { className: sideIconSize })
|
|
17549
|
+
})]
|
|
17550
|
+
}),
|
|
17551
|
+
presetsVisible && /* @__PURE__ */ jsxs("div", {
|
|
17552
|
+
className: "relative",
|
|
17553
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
17554
|
+
type: "button",
|
|
17555
|
+
onClick: () => setPresetsOpen((v) => !v),
|
|
17556
|
+
disabled: busy,
|
|
17557
|
+
className: cn("flex items-center gap-1 rounded px-2 text-[10px] disabled:opacity-40", isPanel ? "h-9 text-foreground hover:bg-surface-hover" : "h-7 text-white hover:bg-white/15"),
|
|
17558
|
+
title: "Presets",
|
|
17559
|
+
children: ["Presets", /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-3 w-3 transition-transform", presetsOpen && "rotate-180") })]
|
|
17560
|
+
}), presetsOpen && /* @__PURE__ */ jsx("div", {
|
|
17561
|
+
className: cn("absolute right-0 top-full mt-1 min-w-[140px] rounded-lg shadow-xl py-1 z-20", isPanel ? "bg-surface border border-border" : "border border-white/30 bg-zinc-900/95 backdrop-blur-md"),
|
|
17562
|
+
children: presets.map((p) => /* @__PURE__ */ jsxs("div", {
|
|
17563
|
+
className: cn("group flex items-center", isPanel ? "hover:bg-surface-hover" : "hover:bg-white/15"),
|
|
17564
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
17565
|
+
type: "button",
|
|
17566
|
+
onClick: () => {
|
|
17567
|
+
goToPreset(p.id);
|
|
17568
|
+
setPresetsOpen(false);
|
|
17569
|
+
},
|
|
17570
|
+
disabled: busy,
|
|
17571
|
+
className: cn("flex-1 px-3 py-1.5 text-left text-[10px] disabled:opacity-40", isPanel ? "text-foreground" : "text-white"),
|
|
17572
|
+
children: p.name || p.id
|
|
17573
|
+
}), presetManagementVisible && /* @__PURE__ */ jsx("button", {
|
|
17574
|
+
type: "button",
|
|
17575
|
+
onClick: () => void handleDeletePreset(p.id, p.name || p.id),
|
|
17576
|
+
disabled: busy,
|
|
17577
|
+
title: `Delete preset ${p.name || p.id}`,
|
|
17578
|
+
className: cn("mr-1 flex h-5 w-5 shrink-0 items-center justify-center rounded", "text-foreground-subtle hover:bg-danger/15 hover:text-danger disabled:opacity-40"),
|
|
17579
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
|
|
17580
|
+
})]
|
|
17581
|
+
}, p.id))
|
|
17582
|
+
})]
|
|
17583
|
+
})
|
|
17584
|
+
]
|
|
17585
|
+
}),
|
|
17586
|
+
presetManagementVisible && /* @__PURE__ */ jsxs("div", {
|
|
17587
|
+
className: "flex flex-col gap-1 rounded-lg border border-border p-2",
|
|
17588
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
17589
|
+
className: "flex items-center gap-2",
|
|
17590
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
17591
|
+
type: "text",
|
|
17592
|
+
value: presetName,
|
|
17593
|
+
onChange: (e) => setPresetName(e.target.value),
|
|
17594
|
+
onKeyDown: (e) => {
|
|
17595
|
+
if (e.key === "Enter") handleSavePreset();
|
|
17596
|
+
},
|
|
17597
|
+
disabled: busy || maxPresetsReached,
|
|
17598
|
+
placeholder: "New preset name",
|
|
17599
|
+
maxLength: 64,
|
|
17600
|
+
className: cn("min-w-0 flex-1 rounded border border-border bg-surface px-2 py-1 text-[11px] text-foreground", "placeholder:text-foreground-subtle focus:outline-none focus:ring-1 focus:ring-primary/50", "disabled:opacity-40")
|
|
17601
|
+
}), /* @__PURE__ */ jsxs("button", {
|
|
17602
|
+
type: "button",
|
|
17603
|
+
onClick: () => void handleSavePreset(),
|
|
17604
|
+
disabled: busy || maxPresetsReached || presetName.trim().length === 0,
|
|
17605
|
+
title: maxPresetsReached ? `Camera preset limit reached (${options?.maxPresets})` : "Save current position as a new preset",
|
|
17606
|
+
className: cn("flex h-7 shrink-0 items-center gap-1 rounded px-2 text-[10px] font-medium", "bg-primary/15 text-primary hover:bg-primary/25 disabled:opacity-40"),
|
|
17607
|
+
children: [/* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }), "Save current position"]
|
|
17608
|
+
})]
|
|
17609
|
+
}), maxPresetsReached && /* @__PURE__ */ jsxs("span", {
|
|
17610
|
+
className: "text-[10px] text-foreground-subtle",
|
|
17611
|
+
children: [
|
|
17612
|
+
"Camera preset limit reached (",
|
|
17613
|
+
options?.maxPresets,
|
|
17614
|
+
"). Delete a preset to add a new one."
|
|
17615
|
+
]
|
|
17616
|
+
})]
|
|
17617
|
+
})
|
|
17618
|
+
]
|
|
17619
|
+
});
|
|
17620
|
+
}
|
|
17621
|
+
//#endregion
|
|
17622
|
+
//#region src/composites/cap-settings/PtzPanel.tsx
|
|
17623
|
+
/**
|
|
17624
|
+
* PtzPanel — live pan/tilt/zoom controls for the `ptz` capability. A
|
|
17625
|
+
* cap-settings component (ui-library), mounted as a cap-contributed
|
|
17626
|
+
* top-level device-detail tab via the cap-UI contribution mechanism
|
|
17627
|
+
* (the `ptz` cap declares `ui: { tab: 'ptz', kind: 'static', ... }`).
|
|
17628
|
+
*
|
|
17629
|
+
* Consolidates the former admin-ui `PTZPanelContent`: `usePTZ` drives
|
|
17630
|
+
* the controls, `PTZOverlay` renders them (`mode='panel'`), and an
|
|
17631
|
+
* Autofocus toggle is shown only when `getOptions().hasAutofocus`.
|
|
17632
|
+
* Autotrack is its own cap-UI contribution (`ptz-autotrack`), mounted
|
|
17633
|
+
* independently by the contribution mechanism — not nested here.
|
|
17634
|
+
*/
|
|
17635
|
+
/** Autofocus toggle — only mounted when `hasAutofocus`. Reads the cap's
|
|
17636
|
+
* `getStatus().autofocus` and drives `setAutofocus`. */
|
|
17637
|
+
function AutofocusToggle({ deviceId }) {
|
|
17638
|
+
const dev = useDeviceProxy(useSystem().trpcClient, deviceId);
|
|
17639
|
+
const [enabled, setEnabled] = useState(null);
|
|
17640
|
+
const [busy, setBusy] = useState(false);
|
|
17641
|
+
useEffect(() => {
|
|
17642
|
+
if (!dev) return void 0;
|
|
17643
|
+
let cancelled = false;
|
|
17644
|
+
(async () => {
|
|
17645
|
+
try {
|
|
17646
|
+
const status = await dev.ptz?.getStatus({});
|
|
17647
|
+
if (cancelled || !status) return;
|
|
17648
|
+
setEnabled(status.autofocus);
|
|
17649
|
+
} catch {}
|
|
17650
|
+
})();
|
|
17651
|
+
return () => {
|
|
17652
|
+
cancelled = true;
|
|
17653
|
+
};
|
|
17654
|
+
}, [dev]);
|
|
17655
|
+
const toggle = useCallback(async () => {
|
|
17656
|
+
if (!dev?.ptz || enabled === null) return;
|
|
17657
|
+
setBusy(true);
|
|
17658
|
+
try {
|
|
17659
|
+
const next = !enabled;
|
|
17660
|
+
await dev.ptz.setAutofocus({ enabled: next });
|
|
17661
|
+
setEnabled(next);
|
|
17662
|
+
} catch (err) {
|
|
17663
|
+
console.error("ptz.setAutofocus failed", err);
|
|
17664
|
+
} finally {
|
|
17665
|
+
setBusy(false);
|
|
17666
|
+
}
|
|
17667
|
+
}, [dev, enabled]);
|
|
17668
|
+
if (enabled === null) return null;
|
|
17669
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
17670
|
+
className: "flex items-center justify-between border-t border-border/40 mt-2 pt-2",
|
|
17671
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
17672
|
+
className: "text-[10.5px] font-semibold text-foreground",
|
|
17673
|
+
children: "Autofocus"
|
|
17674
|
+
}), /* @__PURE__ */ jsxs("button", {
|
|
17675
|
+
type: "button",
|
|
17676
|
+
onClick: () => void toggle(),
|
|
17677
|
+
disabled: busy,
|
|
17678
|
+
"aria-pressed": enabled,
|
|
17679
|
+
className: `flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium transition-colors ${enabled ? "bg-success/15 text-success hover:bg-success/25" : "bg-surface-elevated/40 text-foreground-subtle hover:bg-surface-hover hover:text-foreground"} disabled:opacity-50`,
|
|
17680
|
+
children: [/* @__PURE__ */ jsx("span", { className: `h-1.5 w-1.5 rounded-full ${enabled ? "bg-success" : "bg-foreground-subtle/40"}` }), enabled ? "On" : "Off"]
|
|
17681
|
+
})]
|
|
17682
|
+
});
|
|
17683
|
+
}
|
|
17684
|
+
function PtzPanel({ deviceId }) {
|
|
17685
|
+
const system = useSystem();
|
|
17686
|
+
const ready = Number.isFinite(deviceId);
|
|
17687
|
+
const controls = usePTZ(system.trpcClient, ready ? deviceId : 0, { enabled: ready });
|
|
17688
|
+
if (!ready) return /* @__PURE__ */ jsxs("div", {
|
|
17689
|
+
className: "flex items-center justify-center h-full text-[11px] text-foreground-subtle italic",
|
|
17690
|
+
children: [
|
|
17691
|
+
"Device #",
|
|
17692
|
+
deviceId,
|
|
17693
|
+
" not loaded."
|
|
17694
|
+
]
|
|
17695
|
+
});
|
|
17696
|
+
const hasAutofocus = controls.options?.hasAutofocus === true;
|
|
17697
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
17698
|
+
className: "flex flex-col h-full overflow-y-auto",
|
|
17699
|
+
children: [
|
|
17700
|
+
/* @__PURE__ */ jsx("h3", {
|
|
17701
|
+
className: "text-[11px] font-semibold text-foreground-subtle uppercase tracking-wider mb-2",
|
|
17702
|
+
children: "PTZ"
|
|
17703
|
+
}),
|
|
17704
|
+
/* @__PURE__ */ jsx(PTZOverlay, {
|
|
17705
|
+
controls,
|
|
17706
|
+
mode: "panel"
|
|
17707
|
+
}),
|
|
17708
|
+
hasAutofocus && /* @__PURE__ */ jsx(AutofocusToggle, { deviceId })
|
|
17709
|
+
]
|
|
17710
|
+
});
|
|
17711
|
+
}
|
|
17712
|
+
//#endregion
|
|
17713
|
+
//#region src/hooks/use-device-autotrack.ts
|
|
17714
|
+
/**
|
|
17715
|
+
* `useDeviceAutotrack` — typed wrapper around the `ptz-autotrack` cap.
|
|
17716
|
+
*
|
|
17717
|
+
* Surface:
|
|
17718
|
+
* - `status` — current `{enabled, lastChangedAt, currentSettings}`,
|
|
17719
|
+
* polled at 5s intervals while the hook is mounted.
|
|
17720
|
+
* - `setEnabled(on)` — flip the on/off state.
|
|
17721
|
+
* - `setSettings(partial)` — patch one or more settings keys.
|
|
17722
|
+
* - `isPending` — true while a mutation is in flight.
|
|
17723
|
+
*
|
|
17724
|
+
* Returns `null` for `status` when the cap isn't available (device
|
|
17725
|
+
* doesn't support autotrack) — callers gate render on this OR on the
|
|
17726
|
+
* device's `PtzAutotrack` feature flag.
|
|
17727
|
+
*/
|
|
17728
|
+
function useDeviceAutotrack(deviceId) {
|
|
17729
|
+
const queryClient = useQueryClient();
|
|
17730
|
+
const statusQuery = usePtzAutotrackGetStatus({ deviceId: deviceId ?? 0 }, {
|
|
17731
|
+
enabled: deviceId !== null && Number.isFinite(deviceId),
|
|
17732
|
+
refetchInterval: 5e3,
|
|
17733
|
+
retry: 1
|
|
17734
|
+
});
|
|
17735
|
+
const setEnabledMutation = usePtzAutotrackSetEnabled({ onSuccess: () => {
|
|
17736
|
+
queryClient.invalidateQueries({ queryKey: [["ptzAutotrack"]] });
|
|
17737
|
+
} });
|
|
17738
|
+
const setSettingsMutation = usePtzAutotrackSetSettings({ onSuccess: () => {
|
|
17739
|
+
queryClient.invalidateQueries({ queryKey: [["ptzAutotrack"]] });
|
|
17740
|
+
} });
|
|
17741
|
+
const setEnabled = useCallback(async (on) => {
|
|
17742
|
+
if (deviceId === null) return;
|
|
17743
|
+
await setEnabledMutation.mutateAsync({
|
|
17744
|
+
deviceId,
|
|
17745
|
+
enabled: on
|
|
17746
|
+
});
|
|
17747
|
+
}, [deviceId, setEnabledMutation]);
|
|
17748
|
+
const setSettings = useCallback(async (patch) => {
|
|
17749
|
+
if (deviceId === null) return;
|
|
17750
|
+
await setSettingsMutation.mutateAsync({
|
|
17751
|
+
deviceId,
|
|
17752
|
+
settings: patch
|
|
17753
|
+
});
|
|
17754
|
+
}, [deviceId, setSettingsMutation]);
|
|
17755
|
+
const errorMsg = (() => {
|
|
17756
|
+
if (statusQuery.error) return statusQuery.error.message;
|
|
17757
|
+
if (setEnabledMutation.error) return setEnabledMutation.error.message;
|
|
17758
|
+
if (setSettingsMutation.error) return setSettingsMutation.error.message;
|
|
17759
|
+
return null;
|
|
17760
|
+
})();
|
|
17761
|
+
return {
|
|
17762
|
+
status: statusQuery.data ?? null,
|
|
17763
|
+
isLoading: statusQuery.isLoading,
|
|
17764
|
+
isPending: setEnabledMutation.isPending || setSettingsMutation.isPending,
|
|
17765
|
+
error: errorMsg,
|
|
17766
|
+
setEnabled,
|
|
17767
|
+
setSettings
|
|
17768
|
+
};
|
|
17769
|
+
}
|
|
17770
|
+
//#endregion
|
|
17771
|
+
//#region src/composites/cap-settings/AutotrackSection.tsx
|
|
17772
|
+
/**
|
|
17773
|
+
* Autotrack settings card — rendered inside the PTZ panel when the
|
|
17774
|
+
* device has `DeviceFeature.PtzAutotrack`. Three knobs (cross-vendor
|
|
17775
|
+
* schema): target type, stop delay, disappear delay, plus an enable /
|
|
17776
|
+
* disable toggle that drives the `ptz-autotrack` cap directly.
|
|
17777
|
+
*
|
|
17778
|
+
* Save semantics:
|
|
17779
|
+
* - Toggle (enable/disable) calls `setEnabled` immediately.
|
|
17780
|
+
* - Form fields debounce-save on blur — no explicit Save button.
|
|
17781
|
+
*
|
|
17782
|
+
* Vendor capability hints:
|
|
17783
|
+
* - The cap's status carries `currentSettings` from the camera's
|
|
17784
|
+
* own GET; rendered on every poll so the operator sees what's
|
|
17785
|
+
* really applied vs what they typed.
|
|
17786
|
+
* - When the firmware ignored a setting, the field stays editable.
|
|
17787
|
+
*/
|
|
17788
|
+
function AutotrackSection({ deviceId }) {
|
|
17789
|
+
const { status, isLoading, isPending, error, setEnabled, setSettings } = useDeviceAutotrack(Number.isFinite(deviceId) ? deviceId : null);
|
|
17790
|
+
const [draft, setDraft] = useState({
|
|
17791
|
+
targetType: "",
|
|
17792
|
+
stopDelaySeconds: 30,
|
|
17793
|
+
disappearDelaySeconds: 15
|
|
17794
|
+
});
|
|
17795
|
+
const [editing, setEditing] = useState(false);
|
|
17796
|
+
useEffect(() => {
|
|
17797
|
+
if (editing) return;
|
|
17798
|
+
const s = status?.currentSettings;
|
|
17799
|
+
if (!s) return;
|
|
17800
|
+
setDraft({
|
|
17801
|
+
targetType: s.targetType,
|
|
17802
|
+
stopDelaySeconds: s.stopDelaySeconds,
|
|
17803
|
+
disappearDelaySeconds: s.disappearDelaySeconds
|
|
17804
|
+
});
|
|
17805
|
+
}, [status, editing]);
|
|
17806
|
+
if (isLoading && !status) return /* @__PURE__ */ jsxs("div", {
|
|
17807
|
+
className: "flex items-center gap-2 px-3 py-2 text-[10.5px] text-foreground-subtle",
|
|
17808
|
+
children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }), "Loading autotrack…"]
|
|
17809
|
+
});
|
|
17810
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
17811
|
+
className: "border-t border-border/40 mt-2 pt-2 px-1 space-y-2",
|
|
17812
|
+
children: [
|
|
17813
|
+
/* @__PURE__ */ jsxs("div", {
|
|
17814
|
+
className: "flex items-center justify-between px-2",
|
|
17815
|
+
children: [/* @__PURE__ */ jsxs("h3", {
|
|
17816
|
+
className: "flex items-center gap-1.5 text-[11px] font-semibold text-foreground-subtle uppercase tracking-wider",
|
|
17817
|
+
children: [/* @__PURE__ */ jsx(Crosshair, { className: "h-3 w-3 text-primary" }), "Autotrack"]
|
|
17818
|
+
}), /* @__PURE__ */ jsxs("button", {
|
|
17819
|
+
type: "button",
|
|
17820
|
+
onClick: () => {
|
|
17821
|
+
setEnabled(!(status?.enabled ?? false));
|
|
17822
|
+
},
|
|
17823
|
+
disabled: isPending,
|
|
17824
|
+
className: `flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium transition-colors ${status?.enabled ? "bg-success/15 text-success hover:bg-success/25" : "bg-surface-elevated/40 text-foreground-subtle hover:bg-surface-hover hover:text-foreground"} disabled:opacity-50`,
|
|
17825
|
+
"aria-pressed": status?.enabled ?? false,
|
|
17826
|
+
children: [/* @__PURE__ */ jsx("span", { className: `h-1.5 w-1.5 rounded-full ${status?.enabled ? "bg-success" : "bg-foreground-subtle/40"}` }), status?.enabled ? "Enabled" : "Disabled"]
|
|
17827
|
+
})]
|
|
17828
|
+
}),
|
|
17829
|
+
error && /* @__PURE__ */ jsxs("div", {
|
|
17830
|
+
className: "flex items-start gap-1.5 rounded-md border border-danger/30 bg-danger/5 px-2 py-1.5 text-[10px] text-danger",
|
|
17831
|
+
children: [/* @__PURE__ */ jsx(CircleAlert, { className: "h-3 w-3 flex-shrink-0 mt-0.5" }), /* @__PURE__ */ jsx("span", {
|
|
17832
|
+
className: "leading-snug break-words",
|
|
17833
|
+
children: error
|
|
17834
|
+
})]
|
|
17835
|
+
}),
|
|
17836
|
+
/* @__PURE__ */ jsxs("div", {
|
|
17837
|
+
className: "grid grid-cols-2 gap-2 px-2",
|
|
17838
|
+
children: [
|
|
17839
|
+
(status?.supportedTargetTypes?.length ?? 0) > 0 && /* @__PURE__ */ jsxs("label", {
|
|
17840
|
+
className: "col-span-2 flex flex-col gap-1",
|
|
17841
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
17842
|
+
className: "text-[10px] font-medium text-foreground-subtle",
|
|
17843
|
+
children: "Target type"
|
|
17844
|
+
}), /* @__PURE__ */ jsx("select", {
|
|
17845
|
+
value: draft.targetType,
|
|
17846
|
+
onFocus: () => setEditing(true),
|
|
17847
|
+
onChange: (e) => setDraft((d) => ({
|
|
17848
|
+
...d,
|
|
17849
|
+
targetType: e.target.value
|
|
17850
|
+
})),
|
|
17851
|
+
onBlur: () => {
|
|
17852
|
+
setEditing(false);
|
|
17853
|
+
const current = status?.currentSettings?.targetType ?? "";
|
|
17854
|
+
if (draft.targetType !== current) setSettings({ targetType: draft.targetType });
|
|
17855
|
+
},
|
|
17856
|
+
disabled: isPending,
|
|
17857
|
+
className: "rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground focus:border-primary focus:outline-none disabled:opacity-50",
|
|
17858
|
+
children: (status?.supportedTargetTypes ?? []).map((opt) => /* @__PURE__ */ jsx("option", {
|
|
17859
|
+
value: opt.value,
|
|
17860
|
+
children: opt.label
|
|
17861
|
+
}, opt.value))
|
|
17862
|
+
})]
|
|
17863
|
+
}),
|
|
17864
|
+
/* @__PURE__ */ jsxs("label", {
|
|
17865
|
+
className: "flex flex-col gap-1",
|
|
17866
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
17867
|
+
className: "text-[10px] font-medium text-foreground-subtle",
|
|
17868
|
+
children: "Stop delay (s)"
|
|
17869
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
17870
|
+
type: "number",
|
|
17871
|
+
min: 0,
|
|
17872
|
+
max: 300,
|
|
17873
|
+
value: draft.stopDelaySeconds,
|
|
17874
|
+
onFocus: () => setEditing(true),
|
|
17875
|
+
onChange: (e) => setDraft((d) => ({
|
|
17876
|
+
...d,
|
|
17877
|
+
stopDelaySeconds: Number(e.target.value) || 0
|
|
17878
|
+
})),
|
|
17879
|
+
onBlur: () => {
|
|
17880
|
+
setEditing(false);
|
|
17881
|
+
const current = status?.currentSettings?.stopDelaySeconds ?? 30;
|
|
17882
|
+
if (draft.stopDelaySeconds !== current) setSettings({ stopDelaySeconds: draft.stopDelaySeconds });
|
|
17883
|
+
},
|
|
17884
|
+
disabled: isPending,
|
|
17885
|
+
className: "rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground focus:border-primary focus:outline-none disabled:opacity-50"
|
|
17886
|
+
})]
|
|
17887
|
+
}),
|
|
17888
|
+
/* @__PURE__ */ jsxs("label", {
|
|
17889
|
+
className: "flex flex-col gap-1",
|
|
17890
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
17891
|
+
className: "text-[10px] font-medium text-foreground-subtle",
|
|
17892
|
+
children: "Disappear delay (s)"
|
|
17893
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
17894
|
+
type: "number",
|
|
17895
|
+
min: 0,
|
|
17896
|
+
max: 300,
|
|
17897
|
+
value: draft.disappearDelaySeconds,
|
|
17898
|
+
onFocus: () => setEditing(true),
|
|
17899
|
+
onChange: (e) => setDraft((d) => ({
|
|
17900
|
+
...d,
|
|
17901
|
+
disappearDelaySeconds: Number(e.target.value) || 0
|
|
17902
|
+
})),
|
|
17903
|
+
onBlur: () => {
|
|
17904
|
+
setEditing(false);
|
|
17905
|
+
const current = status?.currentSettings?.disappearDelaySeconds ?? 15;
|
|
17906
|
+
if (draft.disappearDelaySeconds !== current) setSettings({ disappearDelaySeconds: draft.disappearDelaySeconds });
|
|
17907
|
+
},
|
|
17908
|
+
disabled: isPending,
|
|
17909
|
+
className: "rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground focus:border-primary focus:outline-none disabled:opacity-50"
|
|
17910
|
+
})]
|
|
17911
|
+
})
|
|
17912
|
+
]
|
|
17913
|
+
}),
|
|
17914
|
+
/* @__PURE__ */ jsx("p", {
|
|
17915
|
+
className: "px-2 text-[9.5px] text-foreground-subtle leading-snug italic",
|
|
17916
|
+
children: "Settings save on blur. Vendor support varies — Reolink honours all three; Hikvision honours target type + stop delay only (disappear delay is accepted but ignored at firmware push)."
|
|
17917
|
+
})
|
|
17918
|
+
]
|
|
17919
|
+
});
|
|
17920
|
+
}
|
|
17921
|
+
//#endregion
|
|
17922
|
+
//#region src/contexts/player-overlays.tsx
|
|
17923
|
+
/**
|
|
17924
|
+
* Player overlay registry — pluggable layers + toolbar buttons for
|
|
17925
|
+
* the device-detail live-frame `StreamPanel`.
|
|
17926
|
+
*
|
|
17927
|
+
* Why a registry: previously `StreamPanelContent` hardcoded each
|
|
17928
|
+
* overlay (PTZ, intercom toggle, zone editor) and threaded the
|
|
17929
|
+
* editing state by hand. Adding a new feature (audio waveform,
|
|
17930
|
+
* detection bbox tracker, recording controls, …) meant another
|
|
17931
|
+
* round of plumbing through the host file and StreamPanel's prop
|
|
17932
|
+
* surface. The registry lets a sibling component (Detection tab,
|
|
17933
|
+
* Live Stats tab, an addon-page extension) declare its own layer
|
|
17934
|
+
* imperatively via a hook, and the host renders whatever's
|
|
17935
|
+
* registered.
|
|
17936
|
+
*
|
|
17937
|
+
* Two registries live side-by-side:
|
|
17938
|
+
*
|
|
17939
|
+
* 1. **layers** — absolute-positioned React nodes drawn over the
|
|
17940
|
+
* video frame (zones polygon canvas, motion bbox overlay,
|
|
17941
|
+
* audio waveform). Rendered ordered by `order` (lower first
|
|
17942
|
+
* → bottom; higher → top); the last-registered layer wins
|
|
17943
|
+
* ties.
|
|
17944
|
+
*
|
|
17945
|
+
* 2. **toolbar buttons** — controls surfaced in the player's
|
|
17946
|
+
* always-visible toolbar cluster (next to Intercom + Play/Stop).
|
|
17947
|
+
* Each carries an icon, label, controlled `active` flag, and
|
|
17948
|
+
* a click handler. The host (StreamPanel) renders them
|
|
17949
|
+
* uniformly; tone variants stay simple (`'default' | 'primary'`).
|
|
17950
|
+
*
|
|
17951
|
+
* Lifecycle: hooks register their layer / button on mount and
|
|
17952
|
+
* unregister on unmount, so swapping tabs (e.g. leaving Detection)
|
|
17953
|
+
* automatically tears down the registration. Re-registering with
|
|
17954
|
+
* the same `id` replaces the prior entry — meaning a single hook
|
|
17955
|
+
* call can update its overlay/button props on every render without
|
|
17956
|
+
* leaking entries.
|
|
17957
|
+
*
|
|
17958
|
+
* Provider scope: typically wraps the whole device-detail subtree
|
|
17959
|
+
* so the StreamPanel + every tab share the same registry. One
|
|
17960
|
+
* provider per `deviceId`; switching device IDs unmounts the
|
|
17961
|
+
* provider naturally.
|
|
17962
|
+
*/
|
|
17963
|
+
var PlayerOverlaysStateContext = createSharedContext("camstack:player-overlays-state", null);
|
|
17964
|
+
var PlayerOverlaysActionsContext = createSharedContext("camstack:player-overlays-actions", null);
|
|
17965
|
+
function PlayerOverlaysProvider({ children }) {
|
|
17966
|
+
const [layers, setLayers] = useState(() => /* @__PURE__ */ new Map());
|
|
17967
|
+
const [buttons, setButtons] = useState(() => /* @__PURE__ */ new Map());
|
|
17968
|
+
const setLayer = useCallback((layer) => {
|
|
17969
|
+
setLayers((prev) => {
|
|
17970
|
+
const next = new Map(prev);
|
|
17971
|
+
next.set(layer.id, layer);
|
|
17972
|
+
return next;
|
|
17973
|
+
});
|
|
17974
|
+
}, []);
|
|
17975
|
+
const removeLayer = useCallback((id) => {
|
|
17976
|
+
setLayers((prev) => {
|
|
17977
|
+
if (!prev.has(id)) return prev;
|
|
17978
|
+
const next = new Map(prev);
|
|
17979
|
+
next.delete(id);
|
|
17980
|
+
return next;
|
|
17981
|
+
});
|
|
17982
|
+
}, []);
|
|
17983
|
+
const setButton = useCallback((button) => {
|
|
17984
|
+
setButtons((prev) => {
|
|
17985
|
+
const next = new Map(prev);
|
|
17986
|
+
next.set(button.id, button);
|
|
17987
|
+
return next;
|
|
17988
|
+
});
|
|
17989
|
+
}, []);
|
|
17990
|
+
const removeButton = useCallback((id) => {
|
|
17991
|
+
setButtons((prev) => {
|
|
17992
|
+
if (!prev.has(id)) return prev;
|
|
17993
|
+
const next = new Map(prev);
|
|
17994
|
+
next.delete(id);
|
|
17995
|
+
return next;
|
|
17996
|
+
});
|
|
17997
|
+
}, []);
|
|
17998
|
+
const stateValue = useMemo(() => ({
|
|
17999
|
+
layers,
|
|
18000
|
+
buttons
|
|
18001
|
+
}), [layers, buttons]);
|
|
18002
|
+
const actionsValue = useMemo(() => ({
|
|
18003
|
+
setLayer,
|
|
18004
|
+
removeLayer,
|
|
18005
|
+
setButton,
|
|
18006
|
+
removeButton
|
|
18007
|
+
}), [
|
|
18008
|
+
setLayer,
|
|
18009
|
+
removeLayer,
|
|
18010
|
+
setButton,
|
|
18011
|
+
removeButton
|
|
18012
|
+
]);
|
|
18013
|
+
return /* @__PURE__ */ jsx(PlayerOverlaysStateContext.Provider, {
|
|
18014
|
+
value: stateValue,
|
|
18015
|
+
children: /* @__PURE__ */ jsx(PlayerOverlaysActionsContext.Provider, {
|
|
18016
|
+
value: actionsValue,
|
|
18017
|
+
children
|
|
18018
|
+
})
|
|
18019
|
+
});
|
|
18020
|
+
}
|
|
18021
|
+
/** Snapshot of registered layers ordered by `order` (asc, ties resolved
|
|
18022
|
+
* by insertion order of the underlying Map). Returns `[]` outside a
|
|
18023
|
+
* provider. */
|
|
18024
|
+
function usePlayerOverlayLayers() {
|
|
18025
|
+
const state = useContext(PlayerOverlaysStateContext);
|
|
18026
|
+
return useMemo(() => {
|
|
18027
|
+
if (!state) return [];
|
|
18028
|
+
return [...state.layers.values()].sort((a, b) => a.order - b.order);
|
|
18029
|
+
}, [state]);
|
|
18030
|
+
}
|
|
18031
|
+
/** Snapshot of registered toolbar buttons, ordered by `order` (asc). */
|
|
18032
|
+
function usePlayerToolbarButtons() {
|
|
18033
|
+
const state = useContext(PlayerOverlaysStateContext);
|
|
18034
|
+
return useMemo(() => {
|
|
18035
|
+
if (!state) return [];
|
|
18036
|
+
return [...state.buttons.values()].sort((a, b) => a.order - b.order);
|
|
18037
|
+
}, [state]);
|
|
18038
|
+
}
|
|
18039
|
+
/**
|
|
18040
|
+
* Register an overlay layer for the lifetime of the calling component.
|
|
18041
|
+
* Re-registers on every render with the latest spec; auto-unregisters
|
|
18042
|
+
* on unmount. Pass `null` to skip registration when the layer is
|
|
18043
|
+
* conditionally enabled (the hook still runs every render — keeps
|
|
18044
|
+
* react-hook order stable across spec === null toggles).
|
|
18045
|
+
*
|
|
18046
|
+
* Callers that build the spec inline should memoise it (`useMemo`)
|
|
18047
|
+
* to avoid re-registering on every parent render — context-write
|
|
18048
|
+
* effects depend on referential equality of the spec object.
|
|
18049
|
+
*/
|
|
18050
|
+
function usePlayerOverlayLayer(spec) {
|
|
18051
|
+
const actions = useContext(PlayerOverlaysActionsContext);
|
|
18052
|
+
useEffect(() => {
|
|
18053
|
+
if (!actions || !spec) return void 0;
|
|
18054
|
+
actions.setLayer(spec);
|
|
18055
|
+
return () => actions.removeLayer(spec.id);
|
|
18056
|
+
}, [actions, spec]);
|
|
18057
|
+
}
|
|
18058
|
+
/** Same shape as `usePlayerOverlayLayer`, scoped to toolbar buttons. */
|
|
18059
|
+
function usePlayerToolbarButton(spec) {
|
|
18060
|
+
const actions = useContext(PlayerOverlaysActionsContext);
|
|
18061
|
+
useEffect(() => {
|
|
18062
|
+
if (!actions || !spec) return void 0;
|
|
18063
|
+
actions.setButton(spec);
|
|
18064
|
+
return () => actions.removeButton(spec.id);
|
|
18065
|
+
}, [actions, spec]);
|
|
18066
|
+
}
|
|
18067
|
+
//#endregion
|
|
18068
|
+
//#region src/composites/cap-settings/MotionGridCanvas.tsx
|
|
18069
|
+
/**
|
|
18070
|
+
* MotionGridCanvas — the on-frame motion-zone grid editor.
|
|
18071
|
+
*
|
|
18072
|
+
* This component is *only* the editable lattice painted over the live
|
|
18073
|
+
* frame: a plain CSS-grid of `gridWidth × gridHeight` cells. It carries
|
|
18074
|
+
* NO controls — enabled / sensitivity / save / quick-actions all live
|
|
18075
|
+
* in the management bar inside the settings section (`MotionZonesTab`),
|
|
18076
|
+
* so the live frame stays unobstructed.
|
|
18077
|
+
*
|
|
18078
|
+
* Interaction:
|
|
18079
|
+
* - Click a cell → toggle it.
|
|
18080
|
+
* - Press + drag → paint every touched cell to whatever the
|
|
18081
|
+
* first-touched cell flipped TO (the standard grid-mask gesture).
|
|
18082
|
+
*
|
|
18083
|
+
* Purely controlled — every change is forwarded through `onCellsChange`;
|
|
18084
|
+
* the owning `MotionZonesTab` keeps the single source of truth.
|
|
18085
|
+
*/
|
|
18086
|
+
function MotionGridCanvas({ options, cells, onCellsChange }) {
|
|
18087
|
+
const { gridWidth, gridHeight } = options;
|
|
18088
|
+
const total = gridWidth * gridHeight;
|
|
18089
|
+
const paintingRef = useRef(false);
|
|
18090
|
+
const paintValueRef = useRef(true);
|
|
18091
|
+
const paintedRef = useRef(/* @__PURE__ */ new Set());
|
|
18092
|
+
const [, forceTick] = useState(0);
|
|
18093
|
+
const applyCell = useCallback((index, value) => {
|
|
18094
|
+
if (index < 0 || index >= total) return;
|
|
18095
|
+
if (cells[index] === value) return;
|
|
18096
|
+
const next = [...cells];
|
|
18097
|
+
while (next.length < total) next.push(false);
|
|
18098
|
+
next.length = total;
|
|
18099
|
+
next[index] = value;
|
|
18100
|
+
onCellsChange(next);
|
|
18101
|
+
}, [
|
|
18102
|
+
cells,
|
|
18103
|
+
total,
|
|
18104
|
+
onCellsChange
|
|
18105
|
+
]);
|
|
18106
|
+
const handlePointerDown = useCallback((index) => (e) => {
|
|
18107
|
+
e.preventDefault();
|
|
18108
|
+
const nextValue = !cells[index];
|
|
18109
|
+
paintingRef.current = true;
|
|
18110
|
+
paintValueRef.current = nextValue;
|
|
18111
|
+
paintedRef.current = new Set([index]);
|
|
18112
|
+
applyCell(index, nextValue);
|
|
18113
|
+
forceTick((t) => t + 1);
|
|
18114
|
+
}, [cells, applyCell]);
|
|
18115
|
+
const handlePointerEnter = useCallback((index) => () => {
|
|
18116
|
+
if (!paintingRef.current) return;
|
|
18117
|
+
if (paintedRef.current.has(index)) return;
|
|
18118
|
+
paintedRef.current.add(index);
|
|
18119
|
+
applyCell(index, paintValueRef.current);
|
|
18120
|
+
}, [applyCell]);
|
|
18121
|
+
const endPaint = useCallback(() => {
|
|
18122
|
+
if (!paintingRef.current) return;
|
|
18123
|
+
paintingRef.current = false;
|
|
18124
|
+
paintedRef.current = /* @__PURE__ */ new Set();
|
|
18125
|
+
}, []);
|
|
18126
|
+
return /* @__PURE__ */ jsx("div", {
|
|
18127
|
+
className: "absolute inset-0 select-none grid",
|
|
18128
|
+
style: {
|
|
18129
|
+
gridTemplateColumns: `repeat(${String(gridWidth)}, 1fr)`,
|
|
18130
|
+
gridTemplateRows: `repeat(${String(gridHeight)}, 1fr)`
|
|
18131
|
+
},
|
|
18132
|
+
onPointerUp: endPaint,
|
|
18133
|
+
onPointerLeave: endPaint,
|
|
18134
|
+
children: Array.from({ length: total }, (_, i) => {
|
|
18135
|
+
const active = cells[i] === true;
|
|
18136
|
+
return /* @__PURE__ */ jsx("button", {
|
|
18137
|
+
type: "button",
|
|
18138
|
+
"aria-label": `motion cell ${String(i)}`,
|
|
18139
|
+
"aria-pressed": active,
|
|
18140
|
+
onPointerDown: handlePointerDown(i),
|
|
18141
|
+
onPointerEnter: handlePointerEnter(i),
|
|
18142
|
+
className: active ? "border border-primary/70 bg-primary/55 hover:bg-primary/65 transition-colors" : "border border-white/25 bg-white/10 hover:bg-white/20 transition-colors"
|
|
18143
|
+
}, i);
|
|
18144
|
+
})
|
|
18145
|
+
});
|
|
18146
|
+
}
|
|
18147
|
+
//#endregion
|
|
18148
|
+
//#region src/composites/cap-settings/MotionZonesSettings.tsx
|
|
18149
|
+
/**
|
|
18150
|
+
* MotionZonesSettings — management surface for the on-camera
|
|
18151
|
+
* `motion-zones` capability. A cap-settings component (ui-library),
|
|
18152
|
+
* mounted into the device-detail Config tab via the cap-UI
|
|
18153
|
+
* contribution mechanism (the `motion-zones` cap declares a `ui` block).
|
|
18154
|
+
*
|
|
18155
|
+
* Split of concerns (mirrors the analytics zones drawer):
|
|
18156
|
+
* - The editable GRID is painted over the live frame by
|
|
18157
|
+
* `MotionGridCanvas`, registered as a player-overlay layer only
|
|
18158
|
+
* while the operator has toggled "Edit grid" on (OFF by default).
|
|
18159
|
+
* - Every CONTROL lives here, in the settings section, NOT on the
|
|
18160
|
+
* frame: a quick-action bar (All on / All off / Invert), Save /
|
|
18161
|
+
* Revert, and the cell-count status.
|
|
18162
|
+
* - Detection enable + sensitivity are NOT edited here — they are
|
|
18163
|
+
* the camera's on-board motion master switch, owned by the
|
|
18164
|
+
* driver's "On-camera motion" settings section. The grid editor
|
|
18165
|
+
* only paints the mask (`setZone` with a `cells`-only patch;
|
|
18166
|
+
* enable / sensitivity are left untouched camera-side).
|
|
18167
|
+
*
|
|
18168
|
+
* The grid editor draws nothing on cameras without the `motion-zones`
|
|
18169
|
+
* cap — the fetch self-gates on a failed `getOptions`.
|
|
18170
|
+
*/
|
|
18171
|
+
/** "Camera doesn't expose the cap" — swallow so the editor stays
|
|
18172
|
+
* hidden on unsupported devices (mirrors `usePTZ`'s gate). */
|
|
18173
|
+
function isAbsentProvider(err) {
|
|
18174
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
18175
|
+
return msg.includes("provider not available") || msg.includes("motion-zones");
|
|
18176
|
+
}
|
|
18177
|
+
/** Coerce an arbitrary `cells` array to exactly `gridWidth*gridHeight`. */
|
|
18178
|
+
function normaliseCells(cells, options) {
|
|
18179
|
+
const total = options.gridWidth * options.gridHeight;
|
|
18180
|
+
const next = new Array(total);
|
|
18181
|
+
for (let i = 0; i < total; i += 1) next[i] = cells[i] === true;
|
|
18182
|
+
return next;
|
|
18183
|
+
}
|
|
18184
|
+
function cellsEqual(a, b) {
|
|
18185
|
+
if (a.length !== b.length) return false;
|
|
18186
|
+
for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;
|
|
18187
|
+
return true;
|
|
18188
|
+
}
|
|
18189
|
+
function MotionZonesSettings({ deviceId }) {
|
|
18190
|
+
const dev = useDeviceProxy(useSystem().trpcClient, deviceId);
|
|
18191
|
+
const [options, setOptions] = useState(null);
|
|
18192
|
+
const [unsupported, setUnsupported] = useState(false);
|
|
18193
|
+
const [cells, setCells] = useState(null);
|
|
18194
|
+
const [committed, setCommitted] = useState(null);
|
|
18195
|
+
const [saving, setSaving] = useState(false);
|
|
18196
|
+
const [editingGrid, setEditingGrid] = useState(false);
|
|
18197
|
+
const seededRef = useRef(false);
|
|
18198
|
+
useEffect(() => {
|
|
18199
|
+
if (!dev) return void 0;
|
|
18200
|
+
let cancelled = false;
|
|
18201
|
+
(async () => {
|
|
18202
|
+
try {
|
|
18203
|
+
const opts = await dev.motionZones?.getOptions({});
|
|
18204
|
+
if (cancelled || !opts) return;
|
|
18205
|
+
setOptions(opts);
|
|
18206
|
+
if (seededRef.current) return;
|
|
18207
|
+
const status = await dev.motionZones?.getStatus({});
|
|
18208
|
+
if (cancelled || !status) return;
|
|
18209
|
+
seededRef.current = true;
|
|
18210
|
+
const norm = normaliseCells(status.cells, opts);
|
|
18211
|
+
setCommitted(norm);
|
|
18212
|
+
setCells(norm);
|
|
18213
|
+
} catch (err) {
|
|
18214
|
+
if (cancelled) return;
|
|
18215
|
+
if (isAbsentProvider(err)) setUnsupported(true);
|
|
18216
|
+
else console.error("motion-zones load failed", err);
|
|
18217
|
+
}
|
|
18218
|
+
})();
|
|
18219
|
+
return () => {
|
|
18220
|
+
cancelled = true;
|
|
18221
|
+
};
|
|
18222
|
+
}, [dev]);
|
|
18223
|
+
const dirty = useMemo(() => cells !== null && committed !== null && !cellsEqual(cells, committed), [cells, committed]);
|
|
18224
|
+
const activeCount = useMemo(() => cells ? cells.reduce((n, c) => c ? n + 1 : n, 0) : 0, [cells]);
|
|
18225
|
+
const total = options ? options.gridWidth * options.gridHeight : 0;
|
|
18226
|
+
const setAll = useCallback((value) => {
|
|
18227
|
+
if (total > 0) setCells(new Array(total).fill(value));
|
|
18228
|
+
}, [total]);
|
|
18229
|
+
const invert = useCallback(() => {
|
|
18230
|
+
setCells((prev) => prev ? prev.map((c) => !c) : prev);
|
|
18231
|
+
}, []);
|
|
18232
|
+
const revert = useCallback(() => {
|
|
18233
|
+
if (committed) setCells([...committed]);
|
|
18234
|
+
}, [committed]);
|
|
18235
|
+
const save = useCallback(async () => {
|
|
18236
|
+
if (!cells || !dev?.motionZones || !options) return;
|
|
18237
|
+
setSaving(true);
|
|
18238
|
+
try {
|
|
18239
|
+
await dev.motionZones.setZone({ patch: { cells: [...cells] } });
|
|
18240
|
+
const fresh = await dev.motionZones.getStatus({});
|
|
18241
|
+
if (fresh) {
|
|
18242
|
+
const norm = normaliseCells(fresh.cells, options);
|
|
18243
|
+
setCommitted(norm);
|
|
18244
|
+
setCells(norm);
|
|
18245
|
+
}
|
|
18246
|
+
} catch (err) {
|
|
18247
|
+
console.error("motion-zones.setZone failed", err);
|
|
18248
|
+
} finally {
|
|
18249
|
+
setSaving(false);
|
|
18250
|
+
}
|
|
18251
|
+
}, [
|
|
18252
|
+
cells,
|
|
18253
|
+
dev,
|
|
18254
|
+
options
|
|
18255
|
+
]);
|
|
18256
|
+
usePlayerOverlayLayer(useMemo(() => editingGrid && !unsupported && options && cells ? {
|
|
18257
|
+
id: "motion-zones",
|
|
18258
|
+
order: 110,
|
|
18259
|
+
node: /* @__PURE__ */ jsx(MotionGridCanvas, {
|
|
18260
|
+
options,
|
|
18261
|
+
cells,
|
|
18262
|
+
onCellsChange: setCells
|
|
18263
|
+
})
|
|
18264
|
+
} : null, [
|
|
18265
|
+
editingGrid,
|
|
18266
|
+
unsupported,
|
|
18267
|
+
options,
|
|
18268
|
+
cells
|
|
18269
|
+
]));
|
|
18270
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
18271
|
+
className: "flex flex-col gap-3",
|
|
18272
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
18273
|
+
className: "text-[11px] font-semibold text-foreground-subtle uppercase tracking-wider",
|
|
18274
|
+
children: "Motion Zones"
|
|
18275
|
+
}), unsupported ? /* @__PURE__ */ jsx("p", {
|
|
18276
|
+
className: `${TEXT_HINT} leading-relaxed`,
|
|
18277
|
+
children: "This camera doesn't expose an on-board motion-detection grid."
|
|
18278
|
+
}) : !(!unsupported && options !== null && cells !== null) ? /* @__PURE__ */ jsx("p", {
|
|
18279
|
+
className: `${TEXT_HINT} leading-relaxed`,
|
|
18280
|
+
children: "Loading the camera's motion grid…"
|
|
18281
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("p", {
|
|
18282
|
+
className: `${TEXT_HINT} leading-relaxed`,
|
|
18283
|
+
children: [
|
|
18284
|
+
"Toggle",
|
|
18285
|
+
" ",
|
|
18286
|
+
/* @__PURE__ */ jsx("strong", {
|
|
18287
|
+
className: "text-foreground",
|
|
18288
|
+
children: "Edit grid"
|
|
18289
|
+
}),
|
|
18290
|
+
" to paint the region the camera watches for motion directly on the live frame, then ",
|
|
18291
|
+
/* @__PURE__ */ jsx("strong", {
|
|
18292
|
+
className: "text-foreground",
|
|
18293
|
+
children: "Save"
|
|
18294
|
+
}),
|
|
18295
|
+
" to push the mask to the camera. Detection on/off and sensitivity are set in the",
|
|
18296
|
+
" ",
|
|
18297
|
+
/* @__PURE__ */ jsx("strong", {
|
|
18298
|
+
className: "text-foreground",
|
|
18299
|
+
children: "On-camera motion"
|
|
18300
|
+
}),
|
|
18301
|
+
" section."
|
|
18302
|
+
]
|
|
18303
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
18304
|
+
className: "flex items-center gap-2 flex-wrap",
|
|
18305
|
+
children: [
|
|
18306
|
+
/* @__PURE__ */ jsx("button", {
|
|
18307
|
+
type: "button",
|
|
18308
|
+
onClick: () => setEditingGrid((v) => !v),
|
|
18309
|
+
disabled: saving,
|
|
18310
|
+
"aria-pressed": editingGrid,
|
|
18311
|
+
className: editingGrid ? "rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors" : "rounded-md border border-border bg-surface px-2.5 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors",
|
|
18312
|
+
children: editingGrid ? "Done editing" : "Edit grid"
|
|
18313
|
+
}),
|
|
18314
|
+
/* @__PURE__ */ jsx("button", {
|
|
18315
|
+
type: "button",
|
|
18316
|
+
onClick: () => setAll(true),
|
|
18317
|
+
disabled: saving,
|
|
18318
|
+
className: "rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors",
|
|
18319
|
+
children: "All on"
|
|
18320
|
+
}),
|
|
18321
|
+
/* @__PURE__ */ jsx("button", {
|
|
18322
|
+
type: "button",
|
|
18323
|
+
onClick: () => setAll(false),
|
|
18324
|
+
disabled: saving,
|
|
18325
|
+
className: "rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors",
|
|
18326
|
+
children: "All off"
|
|
18327
|
+
}),
|
|
18328
|
+
/* @__PURE__ */ jsx("button", {
|
|
18329
|
+
type: "button",
|
|
18330
|
+
onClick: invert,
|
|
18331
|
+
disabled: saving,
|
|
18332
|
+
className: "rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors",
|
|
18333
|
+
children: "Invert"
|
|
18334
|
+
}),
|
|
18335
|
+
/* @__PURE__ */ jsxs("span", {
|
|
18336
|
+
className: `${TEXT_HINT} ml-1 tabular-nums`,
|
|
18337
|
+
children: [
|
|
18338
|
+
activeCount,
|
|
18339
|
+
" / ",
|
|
18340
|
+
total,
|
|
18341
|
+
" cells · ",
|
|
18342
|
+
options.gridWidth,
|
|
18343
|
+
"×",
|
|
18344
|
+
options.gridHeight
|
|
18345
|
+
]
|
|
18346
|
+
}),
|
|
18347
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
18348
|
+
/* @__PURE__ */ jsx("button", {
|
|
18349
|
+
type: "button",
|
|
18350
|
+
onClick: revert,
|
|
18351
|
+
disabled: saving || !dirty,
|
|
18352
|
+
className: "rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors",
|
|
18353
|
+
children: "Revert"
|
|
18354
|
+
}),
|
|
18355
|
+
/* @__PURE__ */ jsx("button", {
|
|
18356
|
+
type: "button",
|
|
18357
|
+
onClick: () => void save(),
|
|
18358
|
+
disabled: saving || !dirty,
|
|
18359
|
+
className: "rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors",
|
|
18360
|
+
children: saving ? "Saving…" : "Save"
|
|
18361
|
+
})
|
|
18362
|
+
]
|
|
18363
|
+
})] })]
|
|
18364
|
+
});
|
|
18365
|
+
}
|
|
18366
|
+
//#endregion
|
|
18367
|
+
//#region src/widgets/host-widgets.ts
|
|
18368
|
+
/** Host widgets — ui-library React components embeddable as a
|
|
18369
|
+
* `type:'widget'` ConfigField. `WidgetSlot` resolves these BEFORE the
|
|
18370
|
+
* Module-Federation `WidgetRegistry`. ids are namespaced `host/<name>`. */
|
|
18371
|
+
var HOST_WIDGETS = {
|
|
18372
|
+
"host/motion-zones-grid": MotionZonesSettings,
|
|
18373
|
+
"host/ptz-panel": PtzPanel,
|
|
18374
|
+
"host/ptz-autotrack": AutotrackSection
|
|
18375
|
+
};
|
|
18376
|
+
//#endregion
|
|
18377
|
+
//#region src/composites/widget-slot.tsx
|
|
18378
|
+
/**
|
|
18379
|
+
* <WidgetSlot> — single host-side mount point for any addon-contributed
|
|
18380
|
+
* widget. Consumers reference a widget by its public id
|
|
18381
|
+
* (`<addonId>/<stableId>`), the slot looks the widget's metadata up in
|
|
18382
|
+
* the shared `WidgetRegistry`, resolves the component through the
|
|
18383
|
+
* unified `useRemoteComponent` Module-Federation loader (the SAME path
|
|
18384
|
+
* `ContributionRenderer`'s `kind:'remote'` branch uses), validates host
|
|
18385
|
+
* context against the widget's `requires` metadata, and renders one of:
|
|
18386
|
+
* - skeleton placeholder while the bundle is still loading,
|
|
18387
|
+
* - inline error fallback when the widget id is unknown OR the host
|
|
18388
|
+
* didn't supply a required context (`deviceContext` /
|
|
18389
|
+
* `integrationContext`),
|
|
18390
|
+
* - the resolved component otherwise.
|
|
18391
|
+
*
|
|
18392
|
+
* The slot is intentionally STUPID — no styling beyond the skeleton/
|
|
18393
|
+
* error fallback. Layout (card vs inline, sizing) is the host's
|
|
18394
|
+
* responsibility.
|
|
18395
|
+
*/
|
|
18396
|
+
function WidgetSlot(props) {
|
|
18397
|
+
const { widgetId, deviceId } = props;
|
|
18398
|
+
const HostComponent = HOST_WIDGETS[widgetId];
|
|
18399
|
+
if (HostComponent !== void 0) {
|
|
18400
|
+
if (deviceId === void 0) return /* @__PURE__ */ jsx(WidgetMissingError, {
|
|
18401
|
+
widgetId,
|
|
18402
|
+
reason: "missing-device-context"
|
|
18403
|
+
});
|
|
18404
|
+
return /* @__PURE__ */ jsx(HostComponent, { deviceId });
|
|
18405
|
+
}
|
|
18406
|
+
return /* @__PURE__ */ jsx(RemoteWidgetSlot, { ...props });
|
|
18407
|
+
}
|
|
18408
|
+
/**
|
|
18409
|
+
* Module-Federation remote-widget path — looks the widget's metadata up
|
|
18410
|
+
* in the shared `WidgetRegistry` and resolves it through `useRemoteComponent`.
|
|
18411
|
+
*/
|
|
18412
|
+
function RemoteWidgetSlot(props) {
|
|
18413
|
+
const { widgetId } = props;
|
|
18414
|
+
const metadata = useWidgetMetadata(widgetId);
|
|
18415
|
+
if (metadata === void 0) return /* @__PURE__ */ jsx(WidgetMissingError, {
|
|
18416
|
+
widgetId,
|
|
18417
|
+
reason: "unknown"
|
|
18418
|
+
});
|
|
18419
|
+
return /* @__PURE__ */ jsx(ResolvedWidgetSlot, {
|
|
18420
|
+
...props,
|
|
18421
|
+
metadata
|
|
18422
|
+
});
|
|
18423
|
+
}
|
|
18424
|
+
/**
|
|
18425
|
+
* Inner host — resolves the widget component through the unified
|
|
18426
|
+
* `useRemoteComponent` MF loader (shared with `ContributionRenderer`'s
|
|
18427
|
+
* `kind:'remote'` branch). Mounted only once a `metadata` descriptor is
|
|
18428
|
+
* known, so the hook always receives a valid `remote` descriptor.
|
|
18429
|
+
*/
|
|
18430
|
+
function ResolvedWidgetSlot(props) {
|
|
18431
|
+
const { widgetId, host = "device-tab", config, deviceId, integrationId, instanceId, size, columns, rows, metadata } = props;
|
|
18432
|
+
const Component = useRemoteComponent(metadata.remote, metadata.bundleUrl);
|
|
18433
|
+
const resolvedInstanceId = useMemo(() => instanceId ?? widgetId, [instanceId, widgetId]);
|
|
18434
|
+
if (Component === null) return /* @__PURE__ */ jsx(WidgetSkeleton, {});
|
|
18435
|
+
if (Component === void 0) return /* @__PURE__ */ jsx(WidgetMissingError, {
|
|
18436
|
+
widgetId,
|
|
18437
|
+
reason: "missing-export"
|
|
18438
|
+
});
|
|
18439
|
+
if (metadata.requires.deviceContext && deviceId === void 0) return /* @__PURE__ */ jsx(WidgetMissingError, {
|
|
18440
|
+
widgetId,
|
|
18441
|
+
reason: "missing-device-context"
|
|
18442
|
+
});
|
|
18443
|
+
if (metadata.requires.integrationContext && integrationId === void 0) return /* @__PURE__ */ jsx(WidgetMissingError, {
|
|
18444
|
+
widgetId,
|
|
18445
|
+
reason: "missing-integration-context"
|
|
18446
|
+
});
|
|
18447
|
+
return /* @__PURE__ */ jsx(Component, {
|
|
18448
|
+
instanceId: resolvedInstanceId,
|
|
18449
|
+
host,
|
|
18450
|
+
config,
|
|
18451
|
+
deviceId,
|
|
18452
|
+
integrationId,
|
|
18453
|
+
size,
|
|
18454
|
+
columns,
|
|
18455
|
+
rows
|
|
18456
|
+
});
|
|
18457
|
+
}
|
|
18458
|
+
function WidgetSkeleton() {
|
|
18459
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
18460
|
+
className: "rounded-lg border border-border bg-surface/40 p-4 animate-pulse",
|
|
18461
|
+
children: [
|
|
18462
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-24 bg-foreground-subtle/20 rounded mb-2" }),
|
|
18463
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-full bg-foreground-subtle/10 rounded mb-1" }),
|
|
18464
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-3/4 bg-foreground-subtle/10 rounded" })
|
|
18465
|
+
]
|
|
18466
|
+
});
|
|
18467
|
+
}
|
|
18468
|
+
function WidgetMissingError({ widgetId, reason }) {
|
|
18469
|
+
return /* @__PURE__ */ jsx("div", {
|
|
18470
|
+
className: "rounded-lg border border-warning/30 bg-warning/10 px-3 py-2 text-xs text-warning",
|
|
18471
|
+
children: reason === "unknown" ? `Widget "${widgetId}" not registered` : reason === "missing-export" ? `Widget "${widgetId}" bundle does not export this stableId` : reason === "missing-device-context" ? `Widget "${widgetId}" requires a deviceId` : `Widget "${widgetId}" requires an integrationId`
|
|
18472
|
+
});
|
|
18473
|
+
}
|
|
18474
|
+
//#endregion
|
|
18475
|
+
//#region src/composites/config-form-field.tsx
|
|
18476
|
+
var INPUT_CLASS = "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none disabled:opacity-50 disabled:cursor-not-allowed";
|
|
18477
|
+
var LABEL_CLASS = "block text-[11px] font-medium text-foreground mb-1";
|
|
18478
|
+
var DESC_CLASS = "text-[10px] text-foreground-subtle mt-0.5";
|
|
18479
|
+
function FieldWrapper({ label, description, required, span, children, translationFn }) {
|
|
18480
|
+
const colSpanClass = span === 2 ? "col-span-2" : span === 3 ? "col-span-3" : span === 4 ? "col-span-4" : "col-span-1";
|
|
18481
|
+
const resolvedLabel = resolveLabel(label, translationFn);
|
|
18482
|
+
const resolvedDescription = resolveLabel(description, translationFn);
|
|
18483
|
+
const hasLabel = resolvedLabel !== void 0 && resolvedLabel !== "";
|
|
18484
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
18485
|
+
className: `${colSpanClass} min-w-0`,
|
|
18486
|
+
children: [
|
|
18487
|
+
hasLabel && /* @__PURE__ */ jsxs("label", {
|
|
18488
|
+
className: LABEL_CLASS,
|
|
18489
|
+
children: [resolvedLabel, required && /* @__PURE__ */ jsx("span", {
|
|
18490
|
+
className: "text-danger ml-0.5",
|
|
18491
|
+
children: "*"
|
|
18492
|
+
})]
|
|
18493
|
+
}),
|
|
18494
|
+
children,
|
|
18495
|
+
resolvedDescription && /* @__PURE__ */ jsx("p", {
|
|
18496
|
+
className: DESC_CLASS,
|
|
18497
|
+
children: resolvedDescription
|
|
18498
|
+
})
|
|
18499
|
+
]
|
|
18500
|
+
});
|
|
18501
|
+
}
|
|
18502
|
+
function TextField({ field, value, onChange, disabled, translationFn }) {
|
|
18503
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
18504
|
+
label: field.label,
|
|
18505
|
+
description: field.description,
|
|
18506
|
+
required: field.required,
|
|
18507
|
+
span: field.span,
|
|
18508
|
+
translationFn,
|
|
18509
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
18510
|
+
type: field.inputType ?? "text",
|
|
18511
|
+
className: INPUT_CLASS,
|
|
18512
|
+
value: value === void 0 || value === null ? "" : String(value),
|
|
18513
|
+
placeholder: field.placeholder,
|
|
18514
|
+
maxLength: field.maxLength,
|
|
18515
|
+
pattern: field.pattern,
|
|
18516
|
+
disabled: disabled || field.disabled,
|
|
18517
|
+
onChange: (e) => onChange(e.target.value)
|
|
18518
|
+
})
|
|
18519
|
+
});
|
|
18520
|
+
}
|
|
18521
|
+
function NumberField({ field, value, onChange, disabled, translationFn }) {
|
|
18522
|
+
const [local, setLocal] = useState(value === void 0 || value === null ? "" : String(value));
|
|
18523
|
+
const focusedRef = useRef(false);
|
|
18524
|
+
useEffect(() => {
|
|
18525
|
+
if (focusedRef.current) return;
|
|
18526
|
+
setLocal(value === void 0 || value === null ? "" : String(value));
|
|
18527
|
+
}, [value]);
|
|
18528
|
+
const handleChange = (raw) => {
|
|
18529
|
+
setLocal(raw);
|
|
18530
|
+
if (raw === "" || raw === "-") {
|
|
18531
|
+
onChange(void 0);
|
|
18532
|
+
return;
|
|
18533
|
+
}
|
|
18534
|
+
const parsed = Number(raw);
|
|
18535
|
+
if (Number.isNaN(parsed)) return;
|
|
18536
|
+
onChange(parsed);
|
|
18537
|
+
};
|
|
18538
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
18539
|
+
label: field.label,
|
|
18540
|
+
description: field.description,
|
|
18541
|
+
required: field.required,
|
|
18542
|
+
span: field.span,
|
|
18543
|
+
translationFn,
|
|
18544
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
18545
|
+
className: "flex items-center gap-1",
|
|
18546
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
18547
|
+
type: "number",
|
|
18548
|
+
className: INPUT_CLASS,
|
|
18549
|
+
value: local,
|
|
18550
|
+
placeholder: field.placeholder,
|
|
18551
|
+
min: field.min,
|
|
18552
|
+
max: field.max,
|
|
18553
|
+
step: field.step,
|
|
18554
|
+
disabled: disabled || field.disabled,
|
|
18555
|
+
onFocus: () => {
|
|
18556
|
+
focusedRef.current = true;
|
|
18557
|
+
},
|
|
18558
|
+
onBlur: () => {
|
|
18559
|
+
focusedRef.current = false;
|
|
18560
|
+
},
|
|
18561
|
+
onChange: (e) => handleChange(e.target.value)
|
|
18562
|
+
}), field.unit && /* @__PURE__ */ jsx("span", {
|
|
18563
|
+
className: "text-xs text-foreground-subtle whitespace-nowrap",
|
|
17157
18564
|
children: field.unit
|
|
17158
18565
|
})]
|
|
17159
18566
|
})
|
|
@@ -18262,18 +19669,12 @@ function AddonActionButtonField({ field, values, disabled, onAction }) {
|
|
|
18262
19669
|
}
|
|
18263
19670
|
function WidgetField({ field }) {
|
|
18264
19671
|
const deviceId = useDeviceId();
|
|
18265
|
-
return /* @__PURE__ */ jsx(
|
|
18266
|
-
|
|
18267
|
-
|
|
18268
|
-
|
|
18269
|
-
|
|
18270
|
-
|
|
18271
|
-
widgetId: field.widgetId,
|
|
18272
|
-
host: "device-tab",
|
|
18273
|
-
config: field.widgetConfig,
|
|
18274
|
-
deviceId: deviceId ?? void 0,
|
|
18275
|
-
instanceId: field.key
|
|
18276
|
-
})
|
|
19672
|
+
return /* @__PURE__ */ jsx(WidgetSlot, {
|
|
19673
|
+
widgetId: field.widgetId,
|
|
19674
|
+
host: "device-tab",
|
|
19675
|
+
config: field.widgetConfig,
|
|
19676
|
+
deviceId: deviceId ?? void 0,
|
|
19677
|
+
instanceId: field.key
|
|
18277
19678
|
});
|
|
18278
19679
|
}
|
|
18279
19680
|
function formatReadonlyValue(value, unit) {
|
|
@@ -18764,7 +20165,7 @@ function ConfigFormBuilder({ schema, values, onChange, disabled, translationFn,
|
|
|
18764
20165
|
* + patch loop. `onAfterChange` fires once the patch is applied so
|
|
18765
20166
|
* consumers can re-read any derived state.
|
|
18766
20167
|
*/
|
|
18767
|
-
var Chevron = ({ open }) => /* @__PURE__ */ jsx("svg", {
|
|
20168
|
+
var Chevron$1 = ({ open }) => /* @__PURE__ */ jsx("svg", {
|
|
18768
20169
|
className: `h-3 w-3 transition-transform ${open ? "rotate-90" : ""}`,
|
|
18769
20170
|
viewBox: "0 0 24 24",
|
|
18770
20171
|
fill: "none",
|
|
@@ -18929,7 +20330,7 @@ function AddonGlobalSettingsForm({ trpc, addonId, nodeId, title, disabled, onAft
|
|
|
18929
20330
|
className: "w-full px-4 py-2 flex items-center gap-2 hover:bg-surface-hover text-left",
|
|
18930
20331
|
"aria-expanded": open,
|
|
18931
20332
|
children: [
|
|
18932
|
-
/* @__PURE__ */ jsx(Chevron, { open }),
|
|
20333
|
+
/* @__PURE__ */ jsx(Chevron$1, { open }),
|
|
18933
20334
|
/* @__PURE__ */ jsx("h3", {
|
|
18934
20335
|
className: "text-xs font-semibold text-foreground uppercase tracking-wide flex-1",
|
|
18935
20336
|
children: title ?? addonId
|
|
@@ -19500,183 +20901,37 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
19500
20901
|
className: "h-3.5 w-3.5",
|
|
19501
20902
|
viewBox: "0 0 24 24",
|
|
19502
20903
|
fill: "none",
|
|
19503
|
-
stroke: "currentColor",
|
|
19504
|
-
strokeWidth: "2",
|
|
19505
|
-
strokeLinecap: "round",
|
|
19506
|
-
children: /* @__PURE__ */ jsx("path", { d: "M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3" })
|
|
19507
|
-
})
|
|
19508
|
-
})]
|
|
19509
|
-
})]
|
|
19510
|
-
})
|
|
19511
|
-
]
|
|
19512
|
-
});
|
|
19513
|
-
}
|
|
19514
|
-
function ToolbarButton$1({ onClick, title, children }) {
|
|
19515
|
-
return /* @__PURE__ */ jsx("button", {
|
|
19516
|
-
onClick,
|
|
19517
|
-
title,
|
|
19518
|
-
className: "rounded-full p-1.5 text-white/80 hover:text-white hover:bg-white/20 transition-colors",
|
|
19519
|
-
children
|
|
19520
|
-
});
|
|
19521
|
-
}
|
|
19522
|
-
async function waitForIceGathering(pc) {
|
|
19523
|
-
if (pc.iceGatheringState === "complete") return;
|
|
19524
|
-
return new Promise((resolve) => {
|
|
19525
|
-
const handler = () => {
|
|
19526
|
-
if (pc.iceGatheringState === "complete") {
|
|
19527
|
-
pc.removeEventListener("icegatheringstatechange", handler);
|
|
19528
|
-
resolve();
|
|
19529
|
-
}
|
|
19530
|
-
};
|
|
19531
|
-
pc.addEventListener("icegatheringstatechange", handler);
|
|
19532
|
-
setTimeout(resolve, 5e3);
|
|
19533
|
-
});
|
|
19534
|
-
}
|
|
19535
|
-
//#endregion
|
|
19536
|
-
//#region src/contexts/player-overlays.tsx
|
|
19537
|
-
/**
|
|
19538
|
-
* Player overlay registry — pluggable layers + toolbar buttons for
|
|
19539
|
-
* the device-detail live-frame `StreamPanel`.
|
|
19540
|
-
*
|
|
19541
|
-
* Why a registry: previously `StreamPanelContent` hardcoded each
|
|
19542
|
-
* overlay (PTZ, intercom toggle, zone editor) and threaded the
|
|
19543
|
-
* editing state by hand. Adding a new feature (audio waveform,
|
|
19544
|
-
* detection bbox tracker, recording controls, …) meant another
|
|
19545
|
-
* round of plumbing through the host file and StreamPanel's prop
|
|
19546
|
-
* surface. The registry lets a sibling component (Detection tab,
|
|
19547
|
-
* Live Stats tab, an addon-page extension) declare its own layer
|
|
19548
|
-
* imperatively via a hook, and the host renders whatever's
|
|
19549
|
-
* registered.
|
|
19550
|
-
*
|
|
19551
|
-
* Two registries live side-by-side:
|
|
19552
|
-
*
|
|
19553
|
-
* 1. **layers** — absolute-positioned React nodes drawn over the
|
|
19554
|
-
* video frame (zones polygon canvas, motion bbox overlay,
|
|
19555
|
-
* audio waveform). Rendered ordered by `order` (lower first
|
|
19556
|
-
* → bottom; higher → top); the last-registered layer wins
|
|
19557
|
-
* ties.
|
|
19558
|
-
*
|
|
19559
|
-
* 2. **toolbar buttons** — controls surfaced in the player's
|
|
19560
|
-
* always-visible toolbar cluster (next to Intercom + Play/Stop).
|
|
19561
|
-
* Each carries an icon, label, controlled `active` flag, and
|
|
19562
|
-
* a click handler. The host (StreamPanel) renders them
|
|
19563
|
-
* uniformly; tone variants stay simple (`'default' | 'primary'`).
|
|
19564
|
-
*
|
|
19565
|
-
* Lifecycle: hooks register their layer / button on mount and
|
|
19566
|
-
* unregister on unmount, so swapping tabs (e.g. leaving Detection)
|
|
19567
|
-
* automatically tears down the registration. Re-registering with
|
|
19568
|
-
* the same `id` replaces the prior entry — meaning a single hook
|
|
19569
|
-
* call can update its overlay/button props on every render without
|
|
19570
|
-
* leaking entries.
|
|
19571
|
-
*
|
|
19572
|
-
* Provider scope: typically wraps the whole device-detail subtree
|
|
19573
|
-
* so the StreamPanel + every tab share the same registry. One
|
|
19574
|
-
* provider per `deviceId`; switching device IDs unmounts the
|
|
19575
|
-
* provider naturally.
|
|
19576
|
-
*/
|
|
19577
|
-
var PlayerOverlaysStateContext = createSharedContext("camstack:player-overlays-state", null);
|
|
19578
|
-
var PlayerOverlaysActionsContext = createSharedContext("camstack:player-overlays-actions", null);
|
|
19579
|
-
function PlayerOverlaysProvider({ children }) {
|
|
19580
|
-
const [layers, setLayers] = useState(() => /* @__PURE__ */ new Map());
|
|
19581
|
-
const [buttons, setButtons] = useState(() => /* @__PURE__ */ new Map());
|
|
19582
|
-
const setLayer = useCallback((layer) => {
|
|
19583
|
-
setLayers((prev) => {
|
|
19584
|
-
const next = new Map(prev);
|
|
19585
|
-
next.set(layer.id, layer);
|
|
19586
|
-
return next;
|
|
19587
|
-
});
|
|
19588
|
-
}, []);
|
|
19589
|
-
const removeLayer = useCallback((id) => {
|
|
19590
|
-
setLayers((prev) => {
|
|
19591
|
-
if (!prev.has(id)) return prev;
|
|
19592
|
-
const next = new Map(prev);
|
|
19593
|
-
next.delete(id);
|
|
19594
|
-
return next;
|
|
19595
|
-
});
|
|
19596
|
-
}, []);
|
|
19597
|
-
const setButton = useCallback((button) => {
|
|
19598
|
-
setButtons((prev) => {
|
|
19599
|
-
const next = new Map(prev);
|
|
19600
|
-
next.set(button.id, button);
|
|
19601
|
-
return next;
|
|
19602
|
-
});
|
|
19603
|
-
}, []);
|
|
19604
|
-
const removeButton = useCallback((id) => {
|
|
19605
|
-
setButtons((prev) => {
|
|
19606
|
-
if (!prev.has(id)) return prev;
|
|
19607
|
-
const next = new Map(prev);
|
|
19608
|
-
next.delete(id);
|
|
19609
|
-
return next;
|
|
19610
|
-
});
|
|
19611
|
-
}, []);
|
|
19612
|
-
const stateValue = useMemo(() => ({
|
|
19613
|
-
layers,
|
|
19614
|
-
buttons
|
|
19615
|
-
}), [layers, buttons]);
|
|
19616
|
-
const actionsValue = useMemo(() => ({
|
|
19617
|
-
setLayer,
|
|
19618
|
-
removeLayer,
|
|
19619
|
-
setButton,
|
|
19620
|
-
removeButton
|
|
19621
|
-
}), [
|
|
19622
|
-
setLayer,
|
|
19623
|
-
removeLayer,
|
|
19624
|
-
setButton,
|
|
19625
|
-
removeButton
|
|
19626
|
-
]);
|
|
19627
|
-
return /* @__PURE__ */ jsx(PlayerOverlaysStateContext.Provider, {
|
|
19628
|
-
value: stateValue,
|
|
19629
|
-
children: /* @__PURE__ */ jsx(PlayerOverlaysActionsContext.Provider, {
|
|
19630
|
-
value: actionsValue,
|
|
19631
|
-
children
|
|
19632
|
-
})
|
|
20904
|
+
stroke: "currentColor",
|
|
20905
|
+
strokeWidth: "2",
|
|
20906
|
+
strokeLinecap: "round",
|
|
20907
|
+
children: /* @__PURE__ */ jsx("path", { d: "M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3" })
|
|
20908
|
+
})
|
|
20909
|
+
})]
|
|
20910
|
+
})]
|
|
20911
|
+
})
|
|
20912
|
+
]
|
|
19633
20913
|
});
|
|
19634
20914
|
}
|
|
19635
|
-
|
|
19636
|
-
|
|
19637
|
-
|
|
19638
|
-
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19642
|
-
return [...state.layers.values()].sort((a, b) => a.order - b.order);
|
|
19643
|
-
}, [state]);
|
|
19644
|
-
}
|
|
19645
|
-
/** Snapshot of registered toolbar buttons, ordered by `order` (asc). */
|
|
19646
|
-
function usePlayerToolbarButtons() {
|
|
19647
|
-
const state = useContext(PlayerOverlaysStateContext);
|
|
19648
|
-
return useMemo(() => {
|
|
19649
|
-
if (!state) return [];
|
|
19650
|
-
return [...state.buttons.values()].sort((a, b) => a.order - b.order);
|
|
19651
|
-
}, [state]);
|
|
19652
|
-
}
|
|
19653
|
-
/**
|
|
19654
|
-
* Register an overlay layer for the lifetime of the calling component.
|
|
19655
|
-
* Re-registers on every render with the latest spec; auto-unregisters
|
|
19656
|
-
* on unmount. Pass `null` to skip registration when the layer is
|
|
19657
|
-
* conditionally enabled (the hook still runs every render — keeps
|
|
19658
|
-
* react-hook order stable across spec === null toggles).
|
|
19659
|
-
*
|
|
19660
|
-
* Callers that build the spec inline should memoise it (`useMemo`)
|
|
19661
|
-
* to avoid re-registering on every parent render — context-write
|
|
19662
|
-
* effects depend on referential equality of the spec object.
|
|
19663
|
-
*/
|
|
19664
|
-
function usePlayerOverlayLayer(spec) {
|
|
19665
|
-
const actions = useContext(PlayerOverlaysActionsContext);
|
|
19666
|
-
useEffect(() => {
|
|
19667
|
-
if (!actions || !spec) return void 0;
|
|
19668
|
-
actions.setLayer(spec);
|
|
19669
|
-
return () => actions.removeLayer(spec.id);
|
|
19670
|
-
}, [actions, spec]);
|
|
20915
|
+
function ToolbarButton$1({ onClick, title, children }) {
|
|
20916
|
+
return /* @__PURE__ */ jsx("button", {
|
|
20917
|
+
onClick,
|
|
20918
|
+
title,
|
|
20919
|
+
className: "rounded-full p-1.5 text-white/80 hover:text-white hover:bg-white/20 transition-colors",
|
|
20920
|
+
children
|
|
20921
|
+
});
|
|
19671
20922
|
}
|
|
19672
|
-
|
|
19673
|
-
|
|
19674
|
-
|
|
19675
|
-
|
|
19676
|
-
|
|
19677
|
-
|
|
19678
|
-
|
|
19679
|
-
|
|
20923
|
+
async function waitForIceGathering(pc) {
|
|
20924
|
+
if (pc.iceGatheringState === "complete") return;
|
|
20925
|
+
return new Promise((resolve) => {
|
|
20926
|
+
const handler = () => {
|
|
20927
|
+
if (pc.iceGatheringState === "complete") {
|
|
20928
|
+
pc.removeEventListener("icegatheringstatechange", handler);
|
|
20929
|
+
resolve();
|
|
20930
|
+
}
|
|
20931
|
+
};
|
|
20932
|
+
pc.addEventListener("icegatheringstatechange", handler);
|
|
20933
|
+
setTimeout(resolve, 5e3);
|
|
20934
|
+
});
|
|
19680
20935
|
}
|
|
19681
20936
|
//#endregion
|
|
19682
20937
|
//#region src/composites/stream-panel.tsx
|
|
@@ -21484,12 +22739,11 @@ function CopyButton({ value, label, className, disabled }) {
|
|
|
21484
22739
|
* Alexa / HAP / MQTT specifics here. Mount it next to an export addon's
|
|
21485
22740
|
* standard settings form (e.g. in the addon-settings modal).
|
|
21486
22741
|
*
|
|
21487
|
-
* Routing note: `device-export` is a collection cap
|
|
21488
|
-
*
|
|
21489
|
-
*
|
|
21490
|
-
* multiple device-export addons are
|
|
21491
|
-
*
|
|
21492
|
-
* separate follow-up) — identical behaviour to the pages it replaces.
|
|
22742
|
+
* Routing note: `device-export` is a collection cap. Export addons are
|
|
22743
|
+
* all `hub-only`, so the panel queries `nodeId: 'hub'`. It also passes
|
|
22744
|
+
* `addonId` so the codegen'd cap-router resolves THIS addon's provider
|
|
22745
|
+
* within the local collection — when multiple device-export addons are
|
|
22746
|
+
* co-installed each panel shows its own provider, not provider[0].
|
|
21493
22747
|
*/
|
|
21494
22748
|
var ExposedDeviceArraySchema = z.array(ExposedDeviceSchema);
|
|
21495
22749
|
var STATUS_POLL_INTERVAL_MS = 1e4;
|
|
@@ -21580,11 +22834,17 @@ function SetupSection({ setup }) {
|
|
|
21580
22834
|
*/
|
|
21581
22835
|
function DeviceExportPanel({ addonId, onOpenDevice }) {
|
|
21582
22836
|
const queryClient = useQueryClient();
|
|
21583
|
-
const statusQuery = useDeviceExportGetStatus({
|
|
22837
|
+
const statusQuery = useDeviceExportGetStatus({
|
|
22838
|
+
nodeId: "hub",
|
|
22839
|
+
addonId
|
|
22840
|
+
}, {
|
|
21584
22841
|
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
21585
22842
|
retry: false
|
|
21586
22843
|
});
|
|
21587
|
-
const exposedQuery = useDeviceExportListExposedDevices({
|
|
22844
|
+
const exposedQuery = useDeviceExportListExposedDevices({
|
|
22845
|
+
nodeId: "hub",
|
|
22846
|
+
addonId
|
|
22847
|
+
}, {
|
|
21588
22848
|
refetchInterval: STATUS_POLL_INTERVAL_MS,
|
|
21589
22849
|
retry: false
|
|
21590
22850
|
});
|
|
@@ -21656,13 +22916,18 @@ function DeviceExportPanel({ addonId, onOpenDevice }) {
|
|
|
21656
22916
|
disabled: unexposeMutation.isPending,
|
|
21657
22917
|
onClick: () => unexposeMutation.mutate({
|
|
21658
22918
|
deviceId: row.deviceId,
|
|
21659
|
-
nodeId: "hub"
|
|
22919
|
+
nodeId: "hub",
|
|
22920
|
+
addonId
|
|
21660
22921
|
}),
|
|
21661
22922
|
children: [/* @__PURE__ */ jsx(Unlink2, { className: "h-3.5 w-3.5 mr-1" }), "Unexpose"]
|
|
21662
22923
|
})]
|
|
21663
22924
|
})
|
|
21664
22925
|
}
|
|
21665
|
-
], [
|
|
22926
|
+
], [
|
|
22927
|
+
onOpenDevice,
|
|
22928
|
+
unexposeMutation,
|
|
22929
|
+
addonId
|
|
22930
|
+
]);
|
|
21666
22931
|
return /* @__PURE__ */ jsxs("div", {
|
|
21667
22932
|
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
21668
22933
|
children: [
|
|
@@ -22152,7 +23417,7 @@ var LEVEL_OPTIONS = [
|
|
|
22152
23417
|
"error"
|
|
22153
23418
|
];
|
|
22154
23419
|
/** Max live entries kept in the ring buffer. */
|
|
22155
|
-
var MAX_LIVE_ENTRIES$
|
|
23420
|
+
var MAX_LIVE_ENTRIES$1 = 500;
|
|
22156
23421
|
function passesLevel(logLevel, filterLevel) {
|
|
22157
23422
|
if (!filterLevel) return true;
|
|
22158
23423
|
return (LEVEL_SEVERITY[logLevel] ?? 0) >= (LEVEL_SEVERITY[filterLevel] ?? 0);
|
|
@@ -22181,7 +23446,7 @@ function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: pro
|
|
|
22181
23446
|
integrationId,
|
|
22182
23447
|
requestId
|
|
22183
23448
|
]);
|
|
22184
|
-
const fallbackBuffer = useLiveBuffer(MAX_LIVE_ENTRIES$
|
|
23449
|
+
const fallbackBuffer = useLiveBuffer(MAX_LIVE_ENTRIES$1);
|
|
22185
23450
|
const buffer = externalBuffer ?? fallbackBuffer;
|
|
22186
23451
|
const liveLogs = buffer.entries;
|
|
22187
23452
|
const [clearedAt, setClearedAt] = useState(0);
|
|
@@ -22508,7 +23773,7 @@ function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: pro
|
|
|
22508
23773
|
}),
|
|
22509
23774
|
/* @__PURE__ */ jsx("td", {
|
|
22510
23775
|
className: "px-1 py-1 align-top w-5",
|
|
22511
|
-
children: /* @__PURE__ */ jsx(RowCopyButton$
|
|
23776
|
+
children: /* @__PURE__ */ jsx(RowCopyButton$1, {
|
|
22512
23777
|
copied: copiedRowKey === key,
|
|
22513
23778
|
onCopy: () => copyOne(log, key)
|
|
22514
23779
|
})
|
|
@@ -22547,7 +23812,7 @@ function ScopeBadge$2({ label, value }) {
|
|
|
22547
23812
|
* "did it work" affordance — same UX language the toolbar Copy
|
|
22548
23813
|
* button already uses for its `Copied!` label.
|
|
22549
23814
|
*/
|
|
22550
|
-
function RowCopyButton$
|
|
23815
|
+
function RowCopyButton$1({ copied, onCopy }) {
|
|
22551
23816
|
return /* @__PURE__ */ jsx("button", {
|
|
22552
23817
|
type: "button",
|
|
22553
23818
|
title: "Copy this row",
|
|
@@ -22872,7 +24137,7 @@ function hasContent(entry) {
|
|
|
22872
24137
|
if (cat === EventCategory.DetectionResult || cat === EventCategory.PipelineAudioInferenceResult) return summarizeEvent(cat, entry.data) !== null;
|
|
22873
24138
|
return true;
|
|
22874
24139
|
}
|
|
22875
|
-
var MAX_LIVE_ENTRIES
|
|
24140
|
+
var MAX_LIVE_ENTRIES = 300;
|
|
22876
24141
|
function EventStream({ agentId, addonId, deviceId, defaultCategories, categories: legacyCategories, category: propsCategory, maxHeight = "max-h-96", limit = 50, showCategoryFilter = true, showFilters: _showFilters = false, liveBuffer: externalBuffer, onClose, className }) {
|
|
22877
24142
|
const defaultsArray = useMemo(() => defaultCategories ?? legacyCategories ?? DEFAULT_EVENT_CATEGORIES, [defaultCategories, legacyCategories]);
|
|
22878
24143
|
const defaultsKey = useMemo(() => [...defaultsArray].sort().join(","), [defaultsArray]);
|
|
@@ -22884,7 +24149,7 @@ function EventStream({ agentId, addonId, deviceId, defaultCategories, categories
|
|
|
22884
24149
|
setActiveCategories(new Set(defaultsArray));
|
|
22885
24150
|
}
|
|
22886
24151
|
}, [defaultsKey, defaultsArray]);
|
|
22887
|
-
const fallbackBuffer = useLiveBuffer(MAX_LIVE_ENTRIES
|
|
24152
|
+
const fallbackBuffer = useLiveBuffer(MAX_LIVE_ENTRIES);
|
|
22888
24153
|
const buffer = externalBuffer ?? fallbackBuffer;
|
|
22889
24154
|
const liveEvents = buffer.entries;
|
|
22890
24155
|
const [autoScroll, setAutoScroll] = useState(true);
|
|
@@ -23208,7 +24473,7 @@ function EventStream({ agentId, addonId, deviceId, defaultCategories, categories
|
|
|
23208
24473
|
className: "text-foreground-subtle whitespace-nowrap w-[70px] shrink-0 pt-0.5",
|
|
23209
24474
|
children: new Date(event.timestamp).toLocaleTimeString()
|
|
23210
24475
|
}),
|
|
23211
|
-
/* @__PURE__ */ jsx(RowCopyButton
|
|
24476
|
+
/* @__PURE__ */ jsx(RowCopyButton, {
|
|
23212
24477
|
copied: copiedRowId === event.id,
|
|
23213
24478
|
onCopy: () => copyOne(event)
|
|
23214
24479
|
}),
|
|
@@ -23398,7 +24663,7 @@ function ScopeBadge$1({ label, value }) {
|
|
|
23398
24663
|
* click doesn't toggle the row's expand state. Flashes a Check
|
|
23399
24664
|
* for ~1s after copy.
|
|
23400
24665
|
*/
|
|
23401
|
-
function RowCopyButton
|
|
24666
|
+
function RowCopyButton({ copied, onCopy }) {
|
|
23402
24667
|
return /* @__PURE__ */ jsx("button", {
|
|
23403
24668
|
type: "button",
|
|
23404
24669
|
title: "Copy this event",
|
|
@@ -23665,162 +24930,137 @@ function StatusBadge$1({ status }) {
|
|
|
23665
24930
|
//#endregion
|
|
23666
24931
|
//#region src/composites/state-values-stream.tsx
|
|
23667
24932
|
/**
|
|
23668
|
-
* StateValuesStream —
|
|
24933
|
+
* StateValuesStream — live current-state tree for ui-library.
|
|
24934
|
+
*
|
|
24935
|
+
* Renders a single device's CURRENT runtime-state as a scrollable,
|
|
24936
|
+
* collapsible hierarchical tree (NOT a chronological change log).
|
|
23669
24937
|
*
|
|
23670
|
-
*
|
|
23671
|
-
*
|
|
23672
|
-
*
|
|
24938
|
+
* - Seed: on mount, `trpc.deviceState.getAllSnapshots` returns the
|
|
24939
|
+
* whole-system snapshot; we pick out this device's entry and build
|
|
24940
|
+
* a `Map<capName, slice>` of its current state.
|
|
24941
|
+
* - Realtime: an `EventCategory.DeviceStateChanged` tRPC subscription
|
|
24942
|
+
* scoped to the device patches each cap's slice into the map in
|
|
24943
|
+
* place — the matching tree node re-renders, no rows are appended.
|
|
24944
|
+
* - Tree: top level = cap names (sorted, expanded by default); each
|
|
24945
|
+
* cap node expands to its slice keys; nested objects/arrays expand
|
|
24946
|
+
* recursively, deep nesting collapsed by default. A per-cap
|
|
24947
|
+
* "updated Ns ago" indicator surfaces realtime activity.
|
|
24948
|
+
* - Filter/search: cap-name multiselect popover + free-text search,
|
|
24949
|
+
* both applied to the tree (filter which caps show; search matches
|
|
24950
|
+
* keys/values anywhere in the slice).
|
|
23673
24951
|
*
|
|
23674
|
-
*
|
|
24952
|
+
* The live subscription is mounted only while this component renders,
|
|
23675
24953
|
* so the parent can mount/unmount it on tab switches and the tRPC
|
|
23676
24954
|
* subscription is torn down automatically when hidden.
|
|
23677
24955
|
*/
|
|
23678
|
-
|
|
23679
|
-
|
|
23680
|
-
|
|
23681
|
-
if (e.category !== EventCategory.DeviceStateChanged) return false;
|
|
23682
|
-
const data = e.data;
|
|
23683
|
-
if (!data || typeof data !== "object") return false;
|
|
23684
|
-
if (data.deviceId !== deviceId) return false;
|
|
23685
|
-
if (typeof data.capName !== "string") return false;
|
|
23686
|
-
if (!data.slice || typeof data.slice !== "object") return false;
|
|
23687
|
-
return true;
|
|
23688
|
-
}
|
|
23689
|
-
function toEntry(raw) {
|
|
24956
|
+
/** Narrow an untrusted live-stream event to a `DeviceStateChanged`
|
|
24957
|
+
* for THIS device, returning the cap name + slice it carries. */
|
|
24958
|
+
function readStateChange(raw, deviceId) {
|
|
23690
24959
|
if (!raw || typeof raw !== "object") return null;
|
|
23691
24960
|
const e = raw;
|
|
23692
|
-
|
|
23693
|
-
const timestamp = typeof e.timestamp === "string" ? e.timestamp : null;
|
|
24961
|
+
if (e.category !== EventCategory.DeviceStateChanged) return null;
|
|
23694
24962
|
const data = e.data;
|
|
23695
|
-
if (!
|
|
23696
|
-
if (
|
|
24963
|
+
if (!data || typeof data !== "object") return null;
|
|
24964
|
+
if (data.deviceId !== deviceId) return null;
|
|
24965
|
+
if (typeof data.capName !== "string") return null;
|
|
24966
|
+
if (!data.slice || typeof data.slice !== "object") return null;
|
|
23697
24967
|
return {
|
|
23698
|
-
id,
|
|
23699
|
-
timestamp,
|
|
23700
24968
|
capName: data.capName,
|
|
23701
24969
|
slice: data.slice
|
|
23702
24970
|
};
|
|
23703
24971
|
}
|
|
23704
|
-
|
|
24972
|
+
function isSystemSnapshot(raw) {
|
|
24973
|
+
return !!raw && typeof raw === "object" && !Array.isArray(raw);
|
|
24974
|
+
}
|
|
23705
24975
|
/**
|
|
23706
24976
|
* Every device-scoped cap name, sorted alphabetically — same shape
|
|
23707
24977
|
* as `ALL_EVENT_CATEGORIES` in event-stream.tsx so the filter
|
|
23708
24978
|
* surface is symmetric across the three live-streams (Logs, Events,
|
|
23709
|
-
* State). Computed once at module load
|
|
23710
|
-
* full list rather than only the caps that have already emitted at
|
|
23711
|
-
* least one slice change in the current session.
|
|
24979
|
+
* State). Computed once at module load.
|
|
23712
24980
|
*/
|
|
23713
24981
|
var ALL_DEVICE_CAP_NAMES = Object.freeze([...new Set(ALL_CAPABILITY_DEFINITIONS.filter((c) => c.scope === "device").map((c) => c.name))].sort());
|
|
23714
|
-
|
|
24982
|
+
/** Depth at which tree nodes start collapsed. Cap nodes (depth 0) and
|
|
24983
|
+
* their direct slice keys (depth 1) render expanded; anything nested
|
|
24984
|
+
* deeper opens on demand. */
|
|
24985
|
+
var DEFAULT_EXPAND_DEPTH = 1;
|
|
24986
|
+
function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", onClose, className }) {
|
|
23715
24987
|
const [activeCaps, setActiveCaps] = useState(useMemo(() => new Set(defaultCaps ?? []), [defaultCaps]));
|
|
23716
24988
|
const [searchText, setSearchText] = useState("");
|
|
23717
|
-
const [autoScroll, setAutoScroll] = useState(true);
|
|
23718
|
-
const [expandedRows, setExpandedRows] = useState(/* @__PURE__ */ new Set());
|
|
23719
|
-
const [clearedAt, setClearedAt] = useState(0);
|
|
23720
24989
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
23721
24990
|
const [filterSearch, setFilterSearch] = useState("");
|
|
23722
24991
|
const filterRootRef = useRef(null);
|
|
23723
|
-
const
|
|
23724
|
-
const
|
|
23725
|
-
const
|
|
23726
|
-
const scrollRef = useRef(null);
|
|
24992
|
+
const [capStates, setCapStates] = useState(/* @__PURE__ */ new Map());
|
|
24993
|
+
const [collapseOverrides, setCollapseOverrides] = useState(/* @__PURE__ */ new Map());
|
|
24994
|
+
const [, setNowTick] = useState(0);
|
|
23727
24995
|
const prevDeviceRef = useRef(deviceId);
|
|
23728
24996
|
useEffect(() => {
|
|
23729
24997
|
if (prevDeviceRef.current !== deviceId) {
|
|
23730
24998
|
prevDeviceRef.current = deviceId;
|
|
23731
|
-
|
|
23732
|
-
|
|
23733
|
-
setClearedAt(0);
|
|
24999
|
+
setCapStates(/* @__PURE__ */ new Map());
|
|
25000
|
+
setCollapseOverrides(/* @__PURE__ */ new Map());
|
|
23734
25001
|
}
|
|
23735
|
-
}, [deviceId
|
|
23736
|
-
const
|
|
23737
|
-
|
|
23738
|
-
|
|
23739
|
-
|
|
23740
|
-
|
|
23741
|
-
|
|
23742
|
-
|
|
25002
|
+
}, [deviceId]);
|
|
25003
|
+
const { data: snapshot, isLoading } = trpc.deviceState.getAllSnapshots.useQuery({}, { staleTime: 3e4 });
|
|
25004
|
+
const seededRef = useRef(false);
|
|
25005
|
+
useEffect(() => {
|
|
25006
|
+
if (seededRef.current) return;
|
|
25007
|
+
if (!isSystemSnapshot(snapshot)) return;
|
|
25008
|
+
const deviceSlices = snapshot[String(deviceId)];
|
|
25009
|
+
seededRef.current = true;
|
|
25010
|
+
if (!deviceSlices) return;
|
|
25011
|
+
const now = Date.now();
|
|
25012
|
+
setCapStates((prev) => {
|
|
25013
|
+
const next = new Map(prev);
|
|
25014
|
+
for (const [capName, slice] of Object.entries(deviceSlices)) {
|
|
25015
|
+
if (next.has(capName)) continue;
|
|
25016
|
+
next.set(capName, {
|
|
25017
|
+
slice,
|
|
25018
|
+
updatedAt: now
|
|
25019
|
+
});
|
|
25020
|
+
}
|
|
25021
|
+
return next;
|
|
25022
|
+
});
|
|
25023
|
+
}, [snapshot, deviceId]);
|
|
23743
25024
|
useEffect(() => {
|
|
23744
|
-
|
|
23745
|
-
}, [
|
|
25025
|
+
seededRef.current = false;
|
|
25026
|
+
}, [deviceId]);
|
|
23746
25027
|
trpc.systemEvents.subscribe.useSubscription({
|
|
23747
25028
|
deviceId,
|
|
23748
25029
|
category: EventCategory.DeviceStateChanged
|
|
23749
25030
|
}, { onData: (raw) => {
|
|
23750
|
-
|
|
23751
|
-
|
|
23752
|
-
|
|
23753
|
-
|
|
23754
|
-
|
|
23755
|
-
|
|
25031
|
+
const change = readStateChange(raw, deviceId);
|
|
25032
|
+
if (!change) return;
|
|
25033
|
+
setCapStates((prev) => {
|
|
25034
|
+
const next = new Map(prev);
|
|
25035
|
+
next.set(change.capName, {
|
|
25036
|
+
slice: change.slice,
|
|
25037
|
+
updatedAt: Date.now()
|
|
25038
|
+
});
|
|
25039
|
+
return next;
|
|
25040
|
+
});
|
|
23756
25041
|
} });
|
|
23757
|
-
const prevLiveCountRef = useRef(liveEvents.length);
|
|
23758
25042
|
useEffect(() => {
|
|
23759
|
-
const
|
|
23760
|
-
|
|
23761
|
-
|
|
23762
|
-
|
|
23763
|
-
|
|
23764
|
-
const
|
|
23765
|
-
|
|
23766
|
-
|
|
23767
|
-
|
|
23768
|
-
else if (!autoScroll && el.scrollTop > 0) {
|
|
23769
|
-
const firstRow = el.firstElementChild?.firstElementChild;
|
|
23770
|
-
const rowHeight = firstRow instanceof HTMLElement ? firstRow.offsetHeight : 28;
|
|
23771
|
-
const newCount = liveEvents.length - prevCount;
|
|
23772
|
-
el.scrollTop += rowHeight * newCount;
|
|
23773
|
-
}
|
|
23774
|
-
}, [liveEvents.length, autoScroll]);
|
|
23775
|
-
const allEntries = useMemo(() => {
|
|
23776
|
-
const byId = /* @__PURE__ */ new Map();
|
|
23777
|
-
for (const e of initialEvents ?? []) {
|
|
23778
|
-
const entry = toEntry(e);
|
|
23779
|
-
if (entry) byId.set(entry.id, entry);
|
|
23780
|
-
}
|
|
23781
|
-
for (const e of liveEvents) byId.set(e.id, e);
|
|
23782
|
-
let list = [...byId.values()];
|
|
23783
|
-
if (clearedAt > 0) list = list.filter((e) => new Date(e.timestamp).getTime() > clearedAt);
|
|
23784
|
-
if (activeCaps.size > 0) list = list.filter((e) => activeCaps.has(e.capName));
|
|
25043
|
+
const id = setInterval(() => setNowTick((t) => t + 1), 5e3);
|
|
25044
|
+
return () => clearInterval(id);
|
|
25045
|
+
}, []);
|
|
25046
|
+
const visibleCaps = useMemo(() => {
|
|
25047
|
+
const caps = new Set(ALL_DEVICE_CAP_NAMES);
|
|
25048
|
+
for (const capName of capStates.keys()) caps.add(capName);
|
|
25049
|
+
return [...caps].sort();
|
|
25050
|
+
}, [capStates]);
|
|
25051
|
+
const filteredCapNames = useMemo(() => {
|
|
23785
25052
|
const needle = searchText.trim().toLowerCase();
|
|
23786
|
-
|
|
23787
|
-
if (
|
|
23788
|
-
|
|
23789
|
-
|
|
23790
|
-
|
|
23791
|
-
try {
|
|
23792
|
-
if (Object.entries(e.slice).map(([k, v]) => `${k}=${formatValue(v)}`).join(" · ").toLowerCase().includes(needle)) return true;
|
|
23793
|
-
} catch {}
|
|
23794
|
-
try {
|
|
23795
|
-
if (JSON.stringify(e.slice).toLowerCase().includes(needle)) return true;
|
|
23796
|
-
} catch {}
|
|
23797
|
-
return false;
|
|
25053
|
+
return [...capStates.keys()].sort().filter((capName) => {
|
|
25054
|
+
if (activeCaps.size > 0 && !activeCaps.has(capName)) return false;
|
|
25055
|
+
if (!needle) return true;
|
|
25056
|
+
if (capName.toLowerCase().includes(needle)) return true;
|
|
25057
|
+
return sliceMatchesSearch(capStates.get(capName)?.slice ?? {}, needle);
|
|
23798
25058
|
});
|
|
23799
|
-
return list.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
23800
25059
|
}, [
|
|
23801
|
-
|
|
23802
|
-
liveEvents,
|
|
25060
|
+
capStates,
|
|
23803
25061
|
activeCaps,
|
|
23804
|
-
searchText
|
|
23805
|
-
clearedAt
|
|
25062
|
+
searchText
|
|
23806
25063
|
]);
|
|
23807
|
-
const visibleCaps = useMemo(() => {
|
|
23808
|
-
const caps = new Set(ALL_DEVICE_CAP_NAMES);
|
|
23809
|
-
for (const e of initialEvents ?? []) {
|
|
23810
|
-
const entry = toEntry(e);
|
|
23811
|
-
if (entry) caps.add(entry.capName);
|
|
23812
|
-
}
|
|
23813
|
-
for (const e of liveEvents) caps.add(e.capName);
|
|
23814
|
-
return [...caps].sort();
|
|
23815
|
-
}, [initialEvents, liveEvents]);
|
|
23816
|
-
const toggleRow = useCallback((id) => {
|
|
23817
|
-
setExpandedRows((prev) => {
|
|
23818
|
-
const next = new Set(prev);
|
|
23819
|
-
if (next.has(id)) next.delete(id);
|
|
23820
|
-
else next.add(id);
|
|
23821
|
-
return next;
|
|
23822
|
-
});
|
|
23823
|
-
}, []);
|
|
23824
25064
|
const toggleCap = useCallback((cap) => {
|
|
23825
25065
|
setActiveCaps((prev) => {
|
|
23826
25066
|
const next = new Set(prev);
|
|
@@ -23833,34 +25073,36 @@ function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", limi
|
|
|
23833
25073
|
setActiveCaps(new Set(defaultCaps ?? []));
|
|
23834
25074
|
setSearchText("");
|
|
23835
25075
|
}, [defaultCaps]);
|
|
23836
|
-
const handleClear = useCallback(() => {
|
|
23837
|
-
setClearedAt(Date.now());
|
|
23838
|
-
buffer.reset();
|
|
23839
|
-
}, [buffer]);
|
|
23840
|
-
const [copiedAll, setCopiedAll] = useState(false);
|
|
23841
|
-
const [copiedRowId, setCopiedRowId] = useState(null);
|
|
23842
|
-
const formatEntryForCopy = useCallback((entry) => {
|
|
23843
|
-
return `${new Date(entry.timestamp).toISOString()} [${entry.capName}] slice=${JSON.stringify(entry.slice)}`;
|
|
23844
|
-
}, []);
|
|
23845
|
-
const handleCopyAll = useCallback(() => {
|
|
23846
|
-
const lines = allEntries.map(formatEntryForCopy);
|
|
23847
|
-
navigator.clipboard.writeText(lines.join("\n")).then(() => {
|
|
23848
|
-
setCopiedAll(true);
|
|
23849
|
-
setTimeout(() => setCopiedAll(false), 1500);
|
|
23850
|
-
});
|
|
23851
|
-
}, [allEntries, formatEntryForCopy]);
|
|
23852
|
-
const copyOne = useCallback((entry) => {
|
|
23853
|
-
navigator.clipboard.writeText(formatEntryForCopy(entry)).then(() => {
|
|
23854
|
-
setCopiedRowId(entry.id);
|
|
23855
|
-
setTimeout(() => setCopiedRowId((prev) => prev === entry.id ? null : prev), 1200);
|
|
23856
|
-
});
|
|
23857
|
-
}, [formatEntryForCopy]);
|
|
23858
25076
|
const handleSelectAllCaps = useCallback(() => {
|
|
23859
25077
|
setActiveCaps(new Set(visibleCaps));
|
|
23860
25078
|
}, [visibleCaps]);
|
|
23861
25079
|
const handleSelectNoCaps = useCallback(() => {
|
|
23862
25080
|
setActiveCaps(/* @__PURE__ */ new Set());
|
|
23863
25081
|
}, []);
|
|
25082
|
+
const toggleNode = useCallback((path, currentlyOpen) => {
|
|
25083
|
+
setCollapseOverrides((prev) => {
|
|
25084
|
+
const next = new Map(prev);
|
|
25085
|
+
next.set(path, !currentlyOpen);
|
|
25086
|
+
return next;
|
|
25087
|
+
});
|
|
25088
|
+
}, []);
|
|
25089
|
+
const isPathOpen = useCallback((path, depth) => {
|
|
25090
|
+
const override = collapseOverrides.get(path);
|
|
25091
|
+
if (override !== void 0) return override;
|
|
25092
|
+
return depth < DEFAULT_EXPAND_DEPTH;
|
|
25093
|
+
}, [collapseOverrides]);
|
|
25094
|
+
const [copiedAll, setCopiedAll] = useState(false);
|
|
25095
|
+
const handleCopyAll = useCallback(() => {
|
|
25096
|
+
const out = {};
|
|
25097
|
+
for (const capName of filteredCapNames) {
|
|
25098
|
+
const state = capStates.get(capName);
|
|
25099
|
+
if (state) out[capName] = state.slice;
|
|
25100
|
+
}
|
|
25101
|
+
navigator.clipboard.writeText(JSON.stringify(out, null, 2)).then(() => {
|
|
25102
|
+
setCopiedAll(true);
|
|
25103
|
+
setTimeout(() => setCopiedAll(false), 1500);
|
|
25104
|
+
});
|
|
25105
|
+
}, [filteredCapNames, capStates]);
|
|
23864
25106
|
const filteredPopoverCaps = useMemo(() => {
|
|
23865
25107
|
const needle = filterSearch.trim().toLowerCase();
|
|
23866
25108
|
if (!needle) return visibleCaps;
|
|
@@ -23879,6 +25121,7 @@ function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", limi
|
|
|
23879
25121
|
document.addEventListener("mousedown", onDocClick);
|
|
23880
25122
|
return () => document.removeEventListener("mousedown", onDocClick);
|
|
23881
25123
|
}, [filterOpen]);
|
|
25124
|
+
const searchNeedle = searchText.trim().toLowerCase();
|
|
23882
25125
|
return /* @__PURE__ */ jsxs("div", {
|
|
23883
25126
|
className: cn("h-full min-h-0 flex flex-col", className),
|
|
23884
25127
|
onClick: (e) => e.stopPropagation(),
|
|
@@ -23895,18 +25138,6 @@ function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", limi
|
|
|
23895
25138
|
label: "device",
|
|
23896
25139
|
value: `#${deviceId}`
|
|
23897
25140
|
}),
|
|
23898
|
-
/* @__PURE__ */ jsxs("button", {
|
|
23899
|
-
type: "button",
|
|
23900
|
-
onClick: () => {
|
|
23901
|
-
setAutoScroll((a) => !a);
|
|
23902
|
-
if (!autoScroll && scrollRef.current) scrollRef.current.scrollTo({
|
|
23903
|
-
top: 0,
|
|
23904
|
-
behavior: "smooth"
|
|
23905
|
-
});
|
|
23906
|
-
},
|
|
23907
|
-
className: cn("inline-flex items-center gap-1 text-[9px] px-1.5 py-0.5 rounded border transition-colors", autoScroll ? "border-border text-foreground-subtle hover:text-foreground" : "border-amber-500/30 bg-amber-500/10 text-amber-400"),
|
|
23908
|
-
children: [/* @__PURE__ */ jsx(ArrowUpToLine, { className: "h-2.5 w-2.5" }), autoScroll ? "Auto" : "Paused"]
|
|
23909
|
-
}),
|
|
23910
25141
|
/* @__PURE__ */ jsxs("div", {
|
|
23911
25142
|
className: "relative",
|
|
23912
25143
|
ref: filterRootRef,
|
|
@@ -23914,7 +25145,7 @@ function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", limi
|
|
|
23914
25145
|
type: "button",
|
|
23915
25146
|
onClick: () => setFilterOpen((v) => !v),
|
|
23916
25147
|
className: cn("inline-flex items-center gap-1 text-[9px] px-1.5 py-0.5 rounded border transition-colors", !allSelected ? "border-violet-500/30 bg-violet-500/10 text-violet-400" : "border-border text-foreground-subtle hover:text-foreground"),
|
|
23917
|
-
title: "Pick caps to
|
|
25148
|
+
title: "Pick caps to show",
|
|
23918
25149
|
children: [
|
|
23919
25150
|
/* @__PURE__ */ jsx(Funnel, { className: "h-2.5 w-2.5" }),
|
|
23920
25151
|
"Filter (",
|
|
@@ -23939,19 +25170,12 @@ function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", limi
|
|
|
23939
25170
|
title: "Reset filters",
|
|
23940
25171
|
children: [/* @__PURE__ */ jsx(RotateCcw, { className: "h-2.5 w-2.5" }), "Reset"]
|
|
23941
25172
|
}),
|
|
23942
|
-
/* @__PURE__ */ jsxs("button", {
|
|
23943
|
-
type: "button",
|
|
23944
|
-
onClick: handleClear,
|
|
23945
|
-
className: "inline-flex items-center gap-1 text-[9px] px-1.5 py-0.5 rounded border border-border text-foreground-subtle hover:text-foreground transition-colors",
|
|
23946
|
-
title: "Clear visible entries",
|
|
23947
|
-
children: [/* @__PURE__ */ jsx(Trash2, { className: "h-2.5 w-2.5" }), "Clear"]
|
|
23948
|
-
}),
|
|
23949
25173
|
/* @__PURE__ */ jsxs("button", {
|
|
23950
25174
|
type: "button",
|
|
23951
25175
|
onClick: handleCopyAll,
|
|
23952
|
-
disabled:
|
|
23953
|
-
className: cn("inline-flex items-center gap-1 text-[9px] px-1.5 py-0.5 rounded border transition-colors", copiedAll ? "border-emerald-500/30 bg-emerald-500/10 text-emerald-400" : "border-border text-foreground-subtle hover:text-foreground",
|
|
23954
|
-
title: "Copy
|
|
25176
|
+
disabled: filteredCapNames.length === 0,
|
|
25177
|
+
className: cn("inline-flex items-center gap-1 text-[9px] px-1.5 py-0.5 rounded border transition-colors", copiedAll ? "border-emerald-500/30 bg-emerald-500/10 text-emerald-400" : "border-border text-foreground-subtle hover:text-foreground", filteredCapNames.length === 0 && "opacity-40 cursor-not-allowed"),
|
|
25178
|
+
title: "Copy visible state as JSON",
|
|
23955
25179
|
children: [/* @__PURE__ */ jsx(Copy, { className: "h-2.5 w-2.5" }), copiedAll ? "Copied!" : "Copy"]
|
|
23956
25180
|
}),
|
|
23957
25181
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -23970,13 +25194,9 @@ function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", limi
|
|
|
23970
25194
|
children: /* @__PURE__ */ jsx(X, { className: "h-2.5 w-2.5" })
|
|
23971
25195
|
})]
|
|
23972
25196
|
}),
|
|
23973
|
-
|
|
23974
|
-
className: "text-[9px] text-emerald-400 font-medium",
|
|
23975
|
-
children: [
|
|
23976
|
-
"+",
|
|
23977
|
-
liveEvents.length,
|
|
23978
|
-
" live"
|
|
23979
|
-
]
|
|
25197
|
+
/* @__PURE__ */ jsxs("span", {
|
|
25198
|
+
className: "text-[9px] text-emerald-400 font-medium inline-flex items-center gap-1",
|
|
25199
|
+
children: [/* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" }), "live"]
|
|
23980
25200
|
})
|
|
23981
25201
|
]
|
|
23982
25202
|
}), onClose && /* @__PURE__ */ jsx("button", {
|
|
@@ -23986,90 +25206,172 @@ function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", limi
|
|
|
23986
25206
|
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
23987
25207
|
})]
|
|
23988
25208
|
}), /* @__PURE__ */ jsxs("div", {
|
|
23989
|
-
|
|
23990
|
-
className: cn("flex-1 min-h-0 overflow-auto text-[10px]", maxHeight),
|
|
25209
|
+
className: cn("flex-1 min-h-0 overflow-auto text-[10px] font-mono", maxHeight),
|
|
23991
25210
|
children: [
|
|
23992
|
-
isLoading && /* @__PURE__ */ jsxs("div", {
|
|
23993
|
-
className: "flex items-center justify-center gap-2 py-6 text-foreground-subtle",
|
|
23994
|
-
children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), "Loading state
|
|
25211
|
+
isLoading && capStates.size === 0 && /* @__PURE__ */ jsxs("div", {
|
|
25212
|
+
className: "flex items-center justify-center gap-2 py-6 text-foreground-subtle font-sans",
|
|
25213
|
+
children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), "Loading device state..."]
|
|
23995
25214
|
}),
|
|
23996
|
-
!isLoading &&
|
|
23997
|
-
className: "py-4 text-center text-foreground-subtle",
|
|
23998
|
-
children: "No state
|
|
25215
|
+
!isLoading && capStates.size === 0 && /* @__PURE__ */ jsx("div", {
|
|
25216
|
+
className: "py-4 text-center text-foreground-subtle font-sans",
|
|
25217
|
+
children: "No runtime state"
|
|
23999
25218
|
}),
|
|
24000
|
-
|
|
24001
|
-
className: "
|
|
24002
|
-
children:
|
|
24003
|
-
|
|
24004
|
-
|
|
24005
|
-
|
|
24006
|
-
|
|
24007
|
-
|
|
24008
|
-
|
|
24009
|
-
|
|
24010
|
-
|
|
24011
|
-
|
|
24012
|
-
|
|
24013
|
-
|
|
24014
|
-
|
|
24015
|
-
|
|
24016
|
-
|
|
24017
|
-
}),
|
|
24018
|
-
/* @__PURE__ */ jsxs("div", {
|
|
24019
|
-
className: "flex-1 min-w-0",
|
|
24020
|
-
children: [
|
|
24021
|
-
/* @__PURE__ */ jsxs("div", {
|
|
24022
|
-
className: "flex items-center gap-1.5",
|
|
24023
|
-
children: [
|
|
24024
|
-
expanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 text-foreground-subtle shrink-0" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 text-foreground-subtle shrink-0" }),
|
|
24025
|
-
/* @__PURE__ */ jsx("span", {
|
|
24026
|
-
className: "inline-flex items-center gap-1 rounded-full border border-violet-500/30 bg-violet-500/15 text-violet-400 px-2 py-0.5 text-[9px] font-medium",
|
|
24027
|
-
children: entry.capName
|
|
24028
|
-
}),
|
|
24029
|
-
fields.length > 3 && /* @__PURE__ */ jsxs("span", {
|
|
24030
|
-
className: "text-[9px] text-foreground-subtle",
|
|
24031
|
-
children: [
|
|
24032
|
-
"+",
|
|
24033
|
-
fields.length - 3,
|
|
24034
|
-
" more"
|
|
24035
|
-
]
|
|
24036
|
-
})
|
|
24037
|
-
]
|
|
24038
|
-
}),
|
|
24039
|
-
summary && /* @__PURE__ */ jsx("p", {
|
|
24040
|
-
className: "text-foreground-subtle text-[10px] mt-0.5 ml-5 truncate",
|
|
24041
|
-
children: summary
|
|
24042
|
-
}),
|
|
24043
|
-
expanded && /* @__PURE__ */ jsx("div", {
|
|
24044
|
-
className: "mt-1 ml-5 text-[9px] bg-surface rounded px-2 py-1.5 space-y-0.5 font-mono overflow-x-auto",
|
|
24045
|
-
children: fields.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
24046
|
-
className: "text-foreground-subtle italic",
|
|
24047
|
-
children: "empty slice"
|
|
24048
|
-
}) : fields.map(([k, v]) => /* @__PURE__ */ jsxs("div", { children: [
|
|
24049
|
-
/* @__PURE__ */ jsx("span", {
|
|
24050
|
-
className: "text-primary/70",
|
|
24051
|
-
children: k
|
|
24052
|
-
}),
|
|
24053
|
-
/* @__PURE__ */ jsx("span", {
|
|
24054
|
-
className: "text-foreground-subtle",
|
|
24055
|
-
children: ": "
|
|
24056
|
-
}),
|
|
24057
|
-
/* @__PURE__ */ jsx("span", {
|
|
24058
|
-
className: "text-foreground break-all",
|
|
24059
|
-
children: formatValue(v)
|
|
24060
|
-
})
|
|
24061
|
-
] }, k))
|
|
24062
|
-
})
|
|
24063
|
-
]
|
|
24064
|
-
})
|
|
24065
|
-
]
|
|
24066
|
-
}, entry.id);
|
|
25219
|
+
!isLoading && capStates.size > 0 && filteredCapNames.length === 0 && /* @__PURE__ */ jsx("div", {
|
|
25220
|
+
className: "py-4 text-center text-foreground-subtle font-sans",
|
|
25221
|
+
children: "No caps match the filter"
|
|
25222
|
+
}),
|
|
25223
|
+
filteredCapNames.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
25224
|
+
className: "py-1",
|
|
25225
|
+
children: filteredCapNames.map((capName) => {
|
|
25226
|
+
const state = capStates.get(capName);
|
|
25227
|
+
if (!state) return null;
|
|
25228
|
+
return /* @__PURE__ */ jsx(CapNode, {
|
|
25229
|
+
capName,
|
|
25230
|
+
slice: state.slice,
|
|
25231
|
+
updatedAt: state.updatedAt,
|
|
25232
|
+
searchNeedle,
|
|
25233
|
+
isPathOpen,
|
|
25234
|
+
onToggle: toggleNode
|
|
25235
|
+
}, capName);
|
|
24067
25236
|
})
|
|
24068
25237
|
})
|
|
24069
25238
|
]
|
|
24070
25239
|
})]
|
|
24071
25240
|
});
|
|
24072
25241
|
}
|
|
25242
|
+
/** Top-level tree node: one capability. Header carries the cap-name
|
|
25243
|
+
* pill + an "updated Ns ago" indicator; the body expands to the
|
|
25244
|
+
* slice's keys. */
|
|
25245
|
+
function CapNode({ capName, slice, updatedAt, searchNeedle, isPathOpen, onToggle }) {
|
|
25246
|
+
const path = capName;
|
|
25247
|
+
const open = isPathOpen(path, 0);
|
|
25248
|
+
const entries = Object.entries(slice);
|
|
25249
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
25250
|
+
className: "flex items-center gap-1.5 px-3 py-1 hover:bg-surface-hover/30 cursor-pointer select-none",
|
|
25251
|
+
onClick: () => onToggle(path, open),
|
|
25252
|
+
children: [
|
|
25253
|
+
/* @__PURE__ */ jsx(Chevron, {
|
|
25254
|
+
open,
|
|
25255
|
+
hasChildren: entries.length > 0
|
|
25256
|
+
}),
|
|
25257
|
+
/* @__PURE__ */ jsx("span", {
|
|
25258
|
+
className: "inline-flex items-center gap-1 rounded-full border border-violet-500/30 bg-violet-500/15 text-violet-400 px-2 py-0.5 text-[9px] font-medium font-sans",
|
|
25259
|
+
children: capName
|
|
25260
|
+
}),
|
|
25261
|
+
/* @__PURE__ */ jsxs("span", {
|
|
25262
|
+
className: "text-[9px] text-foreground-subtle font-sans",
|
|
25263
|
+
children: [
|
|
25264
|
+
entries.length,
|
|
25265
|
+
" ",
|
|
25266
|
+
entries.length === 1 ? "key" : "keys"
|
|
25267
|
+
]
|
|
25268
|
+
}),
|
|
25269
|
+
/* @__PURE__ */ jsxs("span", {
|
|
25270
|
+
className: "ml-auto text-[9px] text-foreground-subtle/70 font-sans tabular-nums",
|
|
25271
|
+
children: ["updated ", formatAge(updatedAt)]
|
|
25272
|
+
})
|
|
25273
|
+
]
|
|
25274
|
+
}), open && /* @__PURE__ */ jsx("div", { children: entries.length === 0 ? /* @__PURE__ */ jsx(LeafEmpty, {
|
|
25275
|
+
depth: 1,
|
|
25276
|
+
label: "empty slice"
|
|
25277
|
+
}) : entries.map(([k, v]) => /* @__PURE__ */ jsx(TreeNode, {
|
|
25278
|
+
path: `${path}.${k}`,
|
|
25279
|
+
nodeKey: k,
|
|
25280
|
+
value: v,
|
|
25281
|
+
depth: 1,
|
|
25282
|
+
searchNeedle,
|
|
25283
|
+
isPathOpen,
|
|
25284
|
+
onToggle
|
|
25285
|
+
}, k)) })] });
|
|
25286
|
+
}
|
|
25287
|
+
/** Recursive tree node. A leaf renders `key: value`; an object or
|
|
25288
|
+
* array renders a collapsible branch whose children recurse one
|
|
25289
|
+
* level deeper. */
|
|
25290
|
+
function TreeNode({ path, nodeKey, value, depth, searchNeedle, isPathOpen, onToggle }) {
|
|
25291
|
+
const branch = asBranch(value);
|
|
25292
|
+
const indentStyle = { paddingLeft: `${12 + depth * 14}px` };
|
|
25293
|
+
if (!branch) return /* @__PURE__ */ jsxs("div", {
|
|
25294
|
+
className: "flex items-start gap-1 px-3 py-0.5",
|
|
25295
|
+
style: indentStyle,
|
|
25296
|
+
children: [
|
|
25297
|
+
/* @__PURE__ */ jsx("span", {
|
|
25298
|
+
className: cn("shrink-0", highlightCls(nodeKey, searchNeedle, "text-primary/70")),
|
|
25299
|
+
children: nodeKey
|
|
25300
|
+
}),
|
|
25301
|
+
/* @__PURE__ */ jsx("span", {
|
|
25302
|
+
className: "text-foreground-subtle",
|
|
25303
|
+
children: ":"
|
|
25304
|
+
}),
|
|
25305
|
+
/* @__PURE__ */ jsx("span", {
|
|
25306
|
+
className: cn("break-all", highlightCls(formatValue(value), searchNeedle, valueCls(value))),
|
|
25307
|
+
children: formatValue(value)
|
|
25308
|
+
})
|
|
25309
|
+
]
|
|
25310
|
+
});
|
|
25311
|
+
const open = isPathOpen(path, depth);
|
|
25312
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
25313
|
+
className: "flex items-center gap-1 px-3 py-0.5 hover:bg-surface-hover/30 cursor-pointer select-none",
|
|
25314
|
+
style: indentStyle,
|
|
25315
|
+
onClick: () => onToggle(path, open),
|
|
25316
|
+
children: [
|
|
25317
|
+
/* @__PURE__ */ jsx(Chevron, {
|
|
25318
|
+
open,
|
|
25319
|
+
hasChildren: branch.entries.length > 0
|
|
25320
|
+
}),
|
|
25321
|
+
/* @__PURE__ */ jsx("span", {
|
|
25322
|
+
className: highlightCls(nodeKey, searchNeedle, "text-primary/70"),
|
|
25323
|
+
children: nodeKey
|
|
25324
|
+
}),
|
|
25325
|
+
/* @__PURE__ */ jsx("span", {
|
|
25326
|
+
className: "text-foreground-subtle/70 text-[9px]",
|
|
25327
|
+
children: branch.summary
|
|
25328
|
+
})
|
|
25329
|
+
]
|
|
25330
|
+
}), open && (branch.entries.length === 0 ? /* @__PURE__ */ jsx(LeafEmpty, {
|
|
25331
|
+
depth: depth + 1,
|
|
25332
|
+
label: branch.kind === "array" ? "empty array" : "empty object"
|
|
25333
|
+
}) : branch.entries.map(([k, v]) => /* @__PURE__ */ jsx(TreeNode, {
|
|
25334
|
+
path: `${path}.${k}`,
|
|
25335
|
+
nodeKey: k,
|
|
25336
|
+
value: v,
|
|
25337
|
+
depth: depth + 1,
|
|
25338
|
+
searchNeedle,
|
|
25339
|
+
isPathOpen,
|
|
25340
|
+
onToggle
|
|
25341
|
+
}, k)))] });
|
|
25342
|
+
}
|
|
25343
|
+
/** Placeholder row for an object/array/slice with no entries. */
|
|
25344
|
+
function LeafEmpty({ depth, label }) {
|
|
25345
|
+
return /* @__PURE__ */ jsx("div", {
|
|
25346
|
+
className: "px-3 py-0.5 text-foreground-subtle/60 italic",
|
|
25347
|
+
style: { paddingLeft: `${12 + depth * 14}px` },
|
|
25348
|
+
children: label
|
|
25349
|
+
});
|
|
25350
|
+
}
|
|
25351
|
+
/** Expand/collapse chevron. Renders a fixed-width spacer when the node
|
|
25352
|
+
* has no children so leaf and branch keys stay column-aligned. */
|
|
25353
|
+
function Chevron({ open, hasChildren }) {
|
|
25354
|
+
if (!hasChildren) return /* @__PURE__ */ jsx("span", { className: "inline-block w-3 shrink-0" });
|
|
25355
|
+
return open ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 text-foreground-subtle shrink-0" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 text-foreground-subtle shrink-0" });
|
|
25356
|
+
}
|
|
25357
|
+
/** Classify a value as an expandable branch (plain object or array)
|
|
25358
|
+
* or `null` for a leaf. Arrays expose index → element entries. */
|
|
25359
|
+
function asBranch(value) {
|
|
25360
|
+
if (Array.isArray(value)) return {
|
|
25361
|
+
kind: "array",
|
|
25362
|
+
entries: value.map((v, i) => [String(i), v]),
|
|
25363
|
+
summary: `[${value.length}]`
|
|
25364
|
+
};
|
|
25365
|
+
if (value && typeof value === "object") {
|
|
25366
|
+
const entries = Object.entries(value);
|
|
25367
|
+
return {
|
|
25368
|
+
kind: "object",
|
|
25369
|
+
entries,
|
|
25370
|
+
summary: `{${entries.length}}`
|
|
25371
|
+
};
|
|
25372
|
+
}
|
|
25373
|
+
return null;
|
|
25374
|
+
}
|
|
24073
25375
|
function formatValue(v) {
|
|
24074
25376
|
if (v === null) return "null";
|
|
24075
25377
|
if (v === void 0) return "undefined";
|
|
@@ -24081,9 +25383,46 @@ function formatValue(v) {
|
|
|
24081
25383
|
return String(v);
|
|
24082
25384
|
}
|
|
24083
25385
|
}
|
|
25386
|
+
/** Tailwind colour for a leaf value by JS type. */
|
|
25387
|
+
function valueCls(v) {
|
|
25388
|
+
if (v === null || v === void 0) return "text-foreground-subtle/60";
|
|
25389
|
+
if (typeof v === "number") return "text-amber-400";
|
|
25390
|
+
if (typeof v === "boolean") return v ? "text-emerald-400" : "text-rose-400";
|
|
25391
|
+
return "text-foreground";
|
|
25392
|
+
}
|
|
25393
|
+
/** Append a highlight background when `text` matches the live search
|
|
25394
|
+
* needle, so search hits are visible inside the tree. */
|
|
25395
|
+
function highlightCls(text, needle, base) {
|
|
25396
|
+
if (needle && text.toLowerCase().includes(needle)) return cn(base, "bg-violet-500/20 rounded px-0.5");
|
|
25397
|
+
return base;
|
|
25398
|
+
}
|
|
25399
|
+
/** Deep search a slice — true when `needle` appears in any key or any
|
|
25400
|
+
* stringified leaf value anywhere in the tree. */
|
|
25401
|
+
function sliceMatchesSearch(value, needle) {
|
|
25402
|
+
if (Array.isArray(value)) return value.some((v) => sliceMatchesSearch(v, needle));
|
|
25403
|
+
if (value && typeof value === "object") {
|
|
25404
|
+
for (const [k, v] of Object.entries(value)) {
|
|
25405
|
+
if (k.toLowerCase().includes(needle)) return true;
|
|
25406
|
+
if (sliceMatchesSearch(v, needle)) return true;
|
|
25407
|
+
}
|
|
25408
|
+
return false;
|
|
25409
|
+
}
|
|
25410
|
+
return formatValue(value).toLowerCase().includes(needle);
|
|
25411
|
+
}
|
|
25412
|
+
/** Human "Ns ago" / "Nm ago" age label for the per-cap freshness
|
|
25413
|
+
* indicator. */
|
|
25414
|
+
function formatAge(updatedAt) {
|
|
25415
|
+
const deltaMs = Date.now() - updatedAt;
|
|
25416
|
+
if (deltaMs < 5e3) return "just now";
|
|
25417
|
+
const secs = Math.floor(deltaMs / 1e3);
|
|
25418
|
+
if (secs < 60) return `${secs}s ago`;
|
|
25419
|
+
const mins = Math.floor(secs / 60);
|
|
25420
|
+
if (mins < 60) return `${mins}m ago`;
|
|
25421
|
+
return `${Math.floor(mins / 60)}h ago`;
|
|
25422
|
+
}
|
|
24084
25423
|
function ScopeBadge({ label, value }) {
|
|
24085
25424
|
return /* @__PURE__ */ jsxs("span", {
|
|
24086
|
-
className: "text-[9px] px-1.5 py-0.5 rounded-full bg-primary/10 text-primary font-medium",
|
|
25425
|
+
className: "text-[9px] px-1.5 py-0.5 rounded-full bg-primary/10 text-primary font-medium font-sans",
|
|
24087
25426
|
children: [
|
|
24088
25427
|
label,
|
|
24089
25428
|
": ",
|
|
@@ -24092,16 +25431,13 @@ function ScopeBadge({ label, value }) {
|
|
|
24092
25431
|
});
|
|
24093
25432
|
}
|
|
24094
25433
|
/**
|
|
24095
|
-
* Cap-name multiselect popover — same shape as
|
|
24096
|
-
*
|
|
24097
|
-
*
|
|
24098
|
-
* Caps don't have icons / styled badges like events do — we render
|
|
24099
|
-
* a flat name + violet-pill bracket so the look stays consistent
|
|
24100
|
-
* with the rest of the StateValuesStream rows.
|
|
25434
|
+
* Cap-name multiselect popover — same shape as `CategoryFilterPopover`
|
|
25435
|
+
* in event-stream.tsx (search box at top, Select All / None / Defaults
|
|
25436
|
+
* buttons, scrollable checkbox list).
|
|
24101
25437
|
*/
|
|
24102
25438
|
function CapFilterPopover({ search, onSearchChange, caps, active, onToggle, onSelectAll, onSelectNone, onResetDefaults }) {
|
|
24103
25439
|
return /* @__PURE__ */ jsxs("div", {
|
|
24104
|
-
className: "absolute z-20 top-full mt-1 left-0 w-72 max-h-80 rounded-md border border-border bg-surface shadow-lg flex flex-col",
|
|
25440
|
+
className: "absolute z-20 top-full mt-1 left-0 w-72 max-h-80 rounded-md border border-border bg-surface shadow-lg flex flex-col font-sans",
|
|
24105
25441
|
onClick: (e) => e.stopPropagation(),
|
|
24106
25442
|
children: [
|
|
24107
25443
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -24172,24 +25508,6 @@ function CapFilterPopover({ search, onSearchChange, caps, active, onToggle, onSe
|
|
|
24172
25508
|
]
|
|
24173
25509
|
});
|
|
24174
25510
|
}
|
|
24175
|
-
/**
|
|
24176
|
-
* Per-row Copy icon — same shape used in log-stream / event-stream
|
|
24177
|
-
* so the three live-streams' rows are visually symmetric. Stops
|
|
24178
|
-
* propagation so the click doesn't toggle the row's expand state.
|
|
24179
|
-
* Flashes a Check for ~1s on success.
|
|
24180
|
-
*/
|
|
24181
|
-
function RowCopyButton({ copied, onCopy }) {
|
|
24182
|
-
return /* @__PURE__ */ jsx("button", {
|
|
24183
|
-
type: "button",
|
|
24184
|
-
title: "Copy this state change",
|
|
24185
|
-
onClick: (e) => {
|
|
24186
|
-
e.stopPropagation();
|
|
24187
|
-
onCopy();
|
|
24188
|
-
},
|
|
24189
|
-
className: cn("rounded p-0.5 transition-colors mt-0.5", copied ? "text-emerald-400" : "text-foreground-subtle/60 hover:text-foreground hover:bg-surface-hover"),
|
|
24190
|
-
children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" })
|
|
24191
|
-
});
|
|
24192
|
-
}
|
|
24193
25511
|
//#endregion
|
|
24194
25512
|
//#region src/composites/device-activity-panel.tsx
|
|
24195
25513
|
/**
|
|
@@ -24268,303 +25586,99 @@ function DeviceActivityPanel({ deviceId, defaultEventCategories, defaultStateCap
|
|
|
24268
25586
|
showFilters: false,
|
|
24269
25587
|
liveBuffer: logsBuffer
|
|
24270
25588
|
}),
|
|
24271
|
-
tab === "events" && /* @__PURE__ */ jsx(EventStream, {
|
|
24272
|
-
deviceId,
|
|
24273
|
-
defaultCategories: defaultEventCategories,
|
|
24274
|
-
maxHeight,
|
|
24275
|
-
showCategoryFilter: true,
|
|
24276
|
-
liveBuffer: eventsBuffer
|
|
24277
|
-
}),
|
|
24278
|
-
tab === "state" && /* @__PURE__ */ jsx(StateValuesStream, {
|
|
24279
|
-
deviceId,
|
|
24280
|
-
defaultCaps: defaultStateCaps,
|
|
24281
|
-
maxHeight,
|
|
24282
|
-
liveBuffer: stateBuffer
|
|
24283
|
-
})
|
|
24284
|
-
]
|
|
24285
|
-
})]
|
|
24286
|
-
});
|
|
24287
|
-
}
|
|
24288
|
-
//#endregion
|
|
24289
|
-
//#region src/composites/confirm-action-button.tsx
|
|
24290
|
-
/**
|
|
24291
|
-
* ConfirmActionButton — destructive-action button with a modal confirm.
|
|
24292
|
-
*
|
|
24293
|
-
* Two-step UX for any operation operators shouldn't trigger by accident:
|
|
24294
|
-
* reboot device, restart addon, regenerate token, etc. The trigger is a
|
|
24295
|
-
* normal Button that opens a Dialog; confirming runs the async action,
|
|
24296
|
-
* the trigger spinner-disables itself for the duration, and the dialog
|
|
24297
|
-
* closes on success. Errors surface inline at the dialog's bottom.
|
|
24298
|
-
*
|
|
24299
|
-
* Generic on `TResult` so callers don't have to discard the return
|
|
24300
|
-
* value. Pass an `icon` to render it inside the trigger (e.g. RotateCw
|
|
24301
|
-
* for reboot, RefreshCw for restart).
|
|
24302
|
-
*/
|
|
24303
|
-
function ConfirmActionButton({ label, icon: Icon, title = "Confirm action", description, confirmLabel, triggerVariant = "outline", confirmVariant = "danger", size = "sm", disabled, className, action, onSuccess }) {
|
|
24304
|
-
const [open, setOpen] = useState(false);
|
|
24305
|
-
const [running, setRunning] = useState(false);
|
|
24306
|
-
const [error, setError] = useState(null);
|
|
24307
|
-
const handleConfirm = async () => {
|
|
24308
|
-
setError(null);
|
|
24309
|
-
setRunning(true);
|
|
24310
|
-
try {
|
|
24311
|
-
const result = await action();
|
|
24312
|
-
onSuccess?.(result);
|
|
24313
|
-
setOpen(false);
|
|
24314
|
-
} catch (err) {
|
|
24315
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
24316
|
-
} finally {
|
|
24317
|
-
setRunning(false);
|
|
24318
|
-
}
|
|
24319
|
-
};
|
|
24320
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
24321
|
-
type: "button",
|
|
24322
|
-
variant: triggerVariant,
|
|
24323
|
-
size,
|
|
24324
|
-
disabled,
|
|
24325
|
-
onClick: () => {
|
|
24326
|
-
setError(null);
|
|
24327
|
-
setOpen(true);
|
|
24328
|
-
},
|
|
24329
|
-
className,
|
|
24330
|
-
children: [Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }), label]
|
|
24331
|
-
}), /* @__PURE__ */ jsx(Dialog, {
|
|
24332
|
-
open,
|
|
24333
|
-
onOpenChange: (next) => !running && setOpen(next),
|
|
24334
|
-
children: /* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
24335
|
-
/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsxs(DialogTitle, {
|
|
24336
|
-
className: "flex items-center gap-2",
|
|
24337
|
-
children: [/* @__PURE__ */ jsx(TriangleAlert, { className: "h-4 w-4 text-amber-400" }), title]
|
|
24338
|
-
}), typeof description === "string" ? /* @__PURE__ */ jsx(DialogDescription, { children: description }) : description] }),
|
|
24339
|
-
error && /* @__PURE__ */ jsx("div", {
|
|
24340
|
-
className: cn("mt-2 px-3 py-2 rounded border border-danger/40 bg-danger/10", "text-xs text-danger"),
|
|
24341
|
-
children: error
|
|
24342
|
-
}),
|
|
24343
|
-
/* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
|
|
24344
|
-
type: "button",
|
|
24345
|
-
variant: "ghost",
|
|
24346
|
-
size,
|
|
24347
|
-
disabled: running,
|
|
24348
|
-
onClick: () => setOpen(false),
|
|
24349
|
-
children: "Cancel"
|
|
24350
|
-
}), /* @__PURE__ */ jsxs(Button, {
|
|
24351
|
-
type: "button",
|
|
24352
|
-
variant: confirmVariant,
|
|
24353
|
-
size,
|
|
24354
|
-
disabled: running,
|
|
24355
|
-
onClick: () => {
|
|
24356
|
-
handleConfirm();
|
|
24357
|
-
},
|
|
24358
|
-
children: [running && /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), confirmLabel ?? label]
|
|
24359
|
-
})] })
|
|
24360
|
-
] })
|
|
24361
|
-
})] });
|
|
24362
|
-
}
|
|
24363
|
-
//#endregion
|
|
24364
|
-
//#region src/composites/ptz-overlay.tsx
|
|
24365
|
-
/**
|
|
24366
|
-
* PTZOverlay — pan / tilt / zoom controls.
|
|
24367
|
-
*
|
|
24368
|
-
* Two visual variants driven by `mode`:
|
|
24369
|
-
* - `'overlay'` (default): translucent dark pill positioned bottom-
|
|
24370
|
-
* right of the camera viewport. Used as `extraOverlay` on the
|
|
24371
|
-
* `CameraStreamPlayer` so operators can drive PTZ without leaving
|
|
24372
|
-
* the live view. Opaque background + subtle ring keeps the d-pad
|
|
24373
|
-
* legible against any frame.
|
|
24374
|
-
* - `'panel'`: full-bleed inside a host container (e.g. the floating
|
|
24375
|
-
* PTZ panel in DeviceDetail). No absolute positioning, no inner
|
|
24376
|
-
* wrapper card — the host's chrome is the only frame. Inherits the
|
|
24377
|
-
* surrounding `bg-surface` so the dark theme reads consistently
|
|
24378
|
-
* instead of the previous always-dark-bubble look.
|
|
24379
|
-
*
|
|
24380
|
-
* Interaction model is identical across modes: short tap fires a
|
|
24381
|
-
* discrete pulse (`move`); long press starts continuous motion until
|
|
24382
|
-
* release (`startContinuous` + `stopContinuous` on pointer up).
|
|
24383
|
-
*/
|
|
24384
|
-
function DPadButton({ direction, icon: Icon, disabled, className, variant, onMove, onStart, onStop }) {
|
|
24385
|
-
const [pressedAt, setPressedAt] = useState(null);
|
|
24386
|
-
const [continuous, setContinuous] = useState(false);
|
|
24387
|
-
const handlePointerDown = useCallback((e) => {
|
|
24388
|
-
if (disabled) return;
|
|
24389
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
24390
|
-
setPressedAt(Date.now());
|
|
24391
|
-
setContinuous(false);
|
|
24392
|
-
const timer = setTimeout(() => {
|
|
24393
|
-
setContinuous(true);
|
|
24394
|
-
onStart(direction);
|
|
24395
|
-
}, 250);
|
|
24396
|
-
e.currentTarget.dataset.timer = String(timer);
|
|
24397
|
-
}, [
|
|
24398
|
-
direction,
|
|
24399
|
-
disabled,
|
|
24400
|
-
onStart
|
|
24401
|
-
]);
|
|
24402
|
-
const handlePointerUp = useCallback((e) => {
|
|
24403
|
-
const timerId = Number(e.currentTarget.dataset.timer);
|
|
24404
|
-
if (timerId) clearTimeout(timerId);
|
|
24405
|
-
e.currentTarget.dataset.timer = "";
|
|
24406
|
-
if (continuous) onStop();
|
|
24407
|
-
else if (pressedAt !== null) onMove(direction);
|
|
24408
|
-
setPressedAt(null);
|
|
24409
|
-
setContinuous(false);
|
|
24410
|
-
}, [
|
|
24411
|
-
continuous,
|
|
24412
|
-
direction,
|
|
24413
|
-
onMove,
|
|
24414
|
-
onStop,
|
|
24415
|
-
pressedAt
|
|
24416
|
-
]);
|
|
24417
|
-
const handlePointerCancel = useCallback((e) => {
|
|
24418
|
-
const timerId = Number(e.currentTarget.dataset.timer);
|
|
24419
|
-
if (timerId) clearTimeout(timerId);
|
|
24420
|
-
e.currentTarget.dataset.timer = "";
|
|
24421
|
-
if (continuous) onStop();
|
|
24422
|
-
setPressedAt(null);
|
|
24423
|
-
setContinuous(false);
|
|
24424
|
-
}, [continuous, onStop]);
|
|
24425
|
-
const sizeClass = variant === "panel" ? "h-9 w-9" : "h-7 w-7";
|
|
24426
|
-
const iconSizeClass = variant === "panel" ? "h-4 w-4" : "h-3.5 w-3.5";
|
|
24427
|
-
return /* @__PURE__ */ jsx("button", {
|
|
24428
|
-
type: "button",
|
|
24429
|
-
disabled,
|
|
24430
|
-
onPointerDown: handlePointerDown,
|
|
24431
|
-
onPointerUp: handlePointerUp,
|
|
24432
|
-
onPointerCancel: handlePointerCancel,
|
|
24433
|
-
className: cn("flex items-center justify-center rounded transition-colors disabled:opacity-40", variant === "panel" ? "text-foreground hover:bg-surface-hover active:bg-surface-hover/80" : "text-white hover:bg-white/15 active:bg-white/25", sizeClass, className),
|
|
24434
|
-
title: direction,
|
|
24435
|
-
children: /* @__PURE__ */ jsx(Icon, { className: iconSizeClass })
|
|
24436
|
-
});
|
|
24437
|
-
}
|
|
24438
|
-
function PTZOverlay({ controls, mode = "overlay", showPresets, showZoom = true, showHome = true, className }) {
|
|
24439
|
-
const { move, startContinuous, stopContinuous, zoom, goHome, goToPreset, presets, busy, error } = controls;
|
|
24440
|
-
const [presetsOpen, setPresetsOpen] = useState(false);
|
|
24441
|
-
const presetsVisible = (showPresets ?? presets.length > 0) && presets.length > 0;
|
|
24442
|
-
const isPanel = mode === "panel";
|
|
24443
|
-
const containerClass = isPanel ? "flex flex-col items-stretch gap-2 w-full h-full p-3" : "pointer-events-auto absolute top-3 right-3 z-10 flex flex-col items-end gap-2";
|
|
24444
|
-
const rowClass = isPanel ? cn("flex items-center gap-3 rounded-lg p-2", busy && "ring-1 ring-primary/40") : cn("flex items-center gap-2 rounded-lg border border-white/30 bg-zinc-900/90 backdrop-blur-md p-2 shadow-lg shadow-black/40", busy && "ring-1 ring-primary/40");
|
|
24445
|
-
const sideButtonSize = isPanel ? "h-9 w-9" : "h-7 w-7";
|
|
24446
|
-
const sideIconSize = isPanel ? "h-4 w-4" : "h-3.5 w-3.5";
|
|
24447
|
-
const sideButtonHover = isPanel ? "text-foreground hover:bg-surface-hover" : "text-white hover:bg-white/15";
|
|
24448
|
-
const sepClass = isPanel ? "h-12 w-px bg-border mx-1" : "h-12 w-px bg-white/15 mx-1";
|
|
24449
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
24450
|
-
className: cn(containerClass, className),
|
|
24451
|
-
children: [error && /* @__PURE__ */ jsxs("div", {
|
|
24452
|
-
className: "rounded bg-danger/90 px-2 py-1 text-[10px] font-medium text-white shadow-lg max-w-[200px] self-center",
|
|
24453
|
-
children: ["PTZ: ", error]
|
|
24454
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
24455
|
-
className: cn(rowClass, isPanel && "justify-center"),
|
|
24456
|
-
children: [
|
|
24457
|
-
/* @__PURE__ */ jsxs("div", {
|
|
24458
|
-
className: "grid grid-cols-3 gap-0.5",
|
|
24459
|
-
children: [
|
|
24460
|
-
/* @__PURE__ */ jsx("span", {
|
|
24461
|
-
"aria-hidden": true,
|
|
24462
|
-
className: isPanel ? "h-9 w-9" : "h-7 w-7"
|
|
24463
|
-
}),
|
|
24464
|
-
/* @__PURE__ */ jsx(DPadButton, {
|
|
24465
|
-
direction: "up",
|
|
24466
|
-
icon: ArrowUp,
|
|
24467
|
-
variant: mode,
|
|
24468
|
-
onMove: move,
|
|
24469
|
-
onStart: startContinuous,
|
|
24470
|
-
onStop: stopContinuous
|
|
24471
|
-
}),
|
|
24472
|
-
/* @__PURE__ */ jsx("span", {
|
|
24473
|
-
"aria-hidden": true,
|
|
24474
|
-
className: isPanel ? "h-9 w-9" : "h-7 w-7"
|
|
24475
|
-
}),
|
|
24476
|
-
/* @__PURE__ */ jsx(DPadButton, {
|
|
24477
|
-
direction: "left",
|
|
24478
|
-
icon: ArrowLeft,
|
|
24479
|
-
variant: mode,
|
|
24480
|
-
onMove: move,
|
|
24481
|
-
onStart: startContinuous,
|
|
24482
|
-
onStop: stopContinuous
|
|
24483
|
-
}),
|
|
24484
|
-
/* @__PURE__ */ jsx("span", {
|
|
24485
|
-
"aria-hidden": true,
|
|
24486
|
-
className: isPanel ? "h-9 w-9" : "h-7 w-7"
|
|
24487
|
-
}),
|
|
24488
|
-
/* @__PURE__ */ jsx(DPadButton, {
|
|
24489
|
-
direction: "right",
|
|
24490
|
-
icon: ArrowRight,
|
|
24491
|
-
variant: mode,
|
|
24492
|
-
onMove: move,
|
|
24493
|
-
onStart: startContinuous,
|
|
24494
|
-
onStop: stopContinuous
|
|
24495
|
-
}),
|
|
24496
|
-
/* @__PURE__ */ jsx("span", {
|
|
24497
|
-
"aria-hidden": true,
|
|
24498
|
-
className: isPanel ? "h-9 w-9" : "h-7 w-7"
|
|
24499
|
-
}),
|
|
24500
|
-
/* @__PURE__ */ jsx(DPadButton, {
|
|
24501
|
-
direction: "down",
|
|
24502
|
-
icon: ArrowDown,
|
|
24503
|
-
variant: mode,
|
|
24504
|
-
onMove: move,
|
|
24505
|
-
onStart: startContinuous,
|
|
24506
|
-
onStop: stopContinuous
|
|
24507
|
-
}),
|
|
24508
|
-
/* @__PURE__ */ jsx("span", {
|
|
24509
|
-
"aria-hidden": true,
|
|
24510
|
-
className: isPanel ? "h-9 w-9" : "h-7 w-7"
|
|
24511
|
-
})
|
|
24512
|
-
]
|
|
24513
|
-
}),
|
|
24514
|
-
(showZoom || showHome) && /* @__PURE__ */ jsx("div", { className: sepClass }),
|
|
24515
|
-
/* @__PURE__ */ jsxs("div", {
|
|
24516
|
-
className: "flex flex-col gap-0.5",
|
|
24517
|
-
children: [showZoom && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
24518
|
-
type: "button",
|
|
24519
|
-
onClick: () => zoom("in"),
|
|
24520
|
-
disabled: busy,
|
|
24521
|
-
title: "Zoom in",
|
|
24522
|
-
className: cn("flex items-center justify-center rounded disabled:opacity-40", sideButtonSize, sideButtonHover),
|
|
24523
|
-
children: /* @__PURE__ */ jsx(ZoomIn, { className: sideIconSize })
|
|
24524
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
24525
|
-
type: "button",
|
|
24526
|
-
onClick: () => zoom("out"),
|
|
24527
|
-
disabled: busy,
|
|
24528
|
-
title: "Zoom out",
|
|
24529
|
-
className: cn("flex items-center justify-center rounded disabled:opacity-40", sideButtonSize, sideButtonHover),
|
|
24530
|
-
children: /* @__PURE__ */ jsx(ZoomOut, { className: sideIconSize })
|
|
24531
|
-
})] }), showHome && /* @__PURE__ */ jsx("button", {
|
|
24532
|
-
type: "button",
|
|
24533
|
-
onClick: () => goHome(),
|
|
24534
|
-
disabled: busy,
|
|
24535
|
-
title: "Go home",
|
|
24536
|
-
className: cn("flex items-center justify-center rounded disabled:opacity-40", sideButtonSize, sideButtonHover),
|
|
24537
|
-
children: /* @__PURE__ */ jsx(House, { className: sideIconSize })
|
|
24538
|
-
})]
|
|
25589
|
+
tab === "events" && /* @__PURE__ */ jsx(EventStream, {
|
|
25590
|
+
deviceId,
|
|
25591
|
+
defaultCategories: defaultEventCategories,
|
|
25592
|
+
maxHeight,
|
|
25593
|
+
showCategoryFilter: true,
|
|
25594
|
+
liveBuffer: eventsBuffer
|
|
24539
25595
|
}),
|
|
24540
|
-
|
|
24541
|
-
|
|
24542
|
-
|
|
24543
|
-
|
|
24544
|
-
|
|
24545
|
-
disabled: busy,
|
|
24546
|
-
className: cn("flex items-center gap-1 rounded px-2 text-[10px] disabled:opacity-40", isPanel ? "h-9 text-foreground hover:bg-surface-hover" : "h-7 text-white hover:bg-white/15"),
|
|
24547
|
-
title: "Presets",
|
|
24548
|
-
children: ["Presets", /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-3 w-3 transition-transform", presetsOpen && "rotate-180") })]
|
|
24549
|
-
}), presetsOpen && /* @__PURE__ */ jsx("div", {
|
|
24550
|
-
className: cn("absolute right-0 min-w-[140px] rounded-lg shadow-xl py-1 z-20", isPanel ? "bottom-full mb-1 bg-surface border border-border" : "top-full mt-1 border border-white/30 bg-zinc-900/95 backdrop-blur-md"),
|
|
24551
|
-
children: presets.map((p) => /* @__PURE__ */ jsx("button", {
|
|
24552
|
-
type: "button",
|
|
24553
|
-
onClick: () => {
|
|
24554
|
-
goToPreset(p.id);
|
|
24555
|
-
setPresetsOpen(false);
|
|
24556
|
-
},
|
|
24557
|
-
disabled: busy,
|
|
24558
|
-
className: cn("block w-full px-3 py-1.5 text-left text-[10px] disabled:opacity-40", isPanel ? "text-foreground hover:bg-surface-hover" : "text-white hover:bg-white/15"),
|
|
24559
|
-
children: p.name || p.id
|
|
24560
|
-
}, p.id))
|
|
24561
|
-
})]
|
|
25596
|
+
tab === "state" && /* @__PURE__ */ jsx(StateValuesStream, {
|
|
25597
|
+
deviceId,
|
|
25598
|
+
defaultCaps: defaultStateCaps,
|
|
25599
|
+
maxHeight,
|
|
25600
|
+
liveBuffer: stateBuffer
|
|
24562
25601
|
})
|
|
24563
25602
|
]
|
|
24564
25603
|
})]
|
|
24565
25604
|
});
|
|
24566
25605
|
}
|
|
24567
25606
|
//#endregion
|
|
25607
|
+
//#region src/composites/confirm-action-button.tsx
|
|
25608
|
+
/**
|
|
25609
|
+
* ConfirmActionButton — destructive-action button with a modal confirm.
|
|
25610
|
+
*
|
|
25611
|
+
* Two-step UX for any operation operators shouldn't trigger by accident:
|
|
25612
|
+
* reboot device, restart addon, regenerate token, etc. The trigger is a
|
|
25613
|
+
* normal Button that opens a Dialog; confirming runs the async action,
|
|
25614
|
+
* the trigger spinner-disables itself for the duration, and the dialog
|
|
25615
|
+
* closes on success. Errors surface inline at the dialog's bottom.
|
|
25616
|
+
*
|
|
25617
|
+
* Generic on `TResult` so callers don't have to discard the return
|
|
25618
|
+
* value. Pass an `icon` to render it inside the trigger (e.g. RotateCw
|
|
25619
|
+
* for reboot, RefreshCw for restart).
|
|
25620
|
+
*/
|
|
25621
|
+
function ConfirmActionButton({ label, icon: Icon, title = "Confirm action", description, confirmLabel, triggerVariant = "outline", confirmVariant = "danger", size = "sm", disabled, className, action, onSuccess }) {
|
|
25622
|
+
const [open, setOpen] = useState(false);
|
|
25623
|
+
const [running, setRunning] = useState(false);
|
|
25624
|
+
const [error, setError] = useState(null);
|
|
25625
|
+
const handleConfirm = async () => {
|
|
25626
|
+
setError(null);
|
|
25627
|
+
setRunning(true);
|
|
25628
|
+
try {
|
|
25629
|
+
const result = await action();
|
|
25630
|
+
onSuccess?.(result);
|
|
25631
|
+
setOpen(false);
|
|
25632
|
+
} catch (err) {
|
|
25633
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
25634
|
+
} finally {
|
|
25635
|
+
setRunning(false);
|
|
25636
|
+
}
|
|
25637
|
+
};
|
|
25638
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
25639
|
+
type: "button",
|
|
25640
|
+
variant: triggerVariant,
|
|
25641
|
+
size,
|
|
25642
|
+
disabled,
|
|
25643
|
+
onClick: () => {
|
|
25644
|
+
setError(null);
|
|
25645
|
+
setOpen(true);
|
|
25646
|
+
},
|
|
25647
|
+
className,
|
|
25648
|
+
children: [Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }), label]
|
|
25649
|
+
}), /* @__PURE__ */ jsx(Dialog, {
|
|
25650
|
+
open,
|
|
25651
|
+
onOpenChange: (next) => !running && setOpen(next),
|
|
25652
|
+
children: /* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
25653
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsxs(DialogTitle, {
|
|
25654
|
+
className: "flex items-center gap-2",
|
|
25655
|
+
children: [/* @__PURE__ */ jsx(TriangleAlert, { className: "h-4 w-4 text-amber-400" }), title]
|
|
25656
|
+
}), typeof description === "string" ? /* @__PURE__ */ jsx(DialogDescription, { children: description }) : description] }),
|
|
25657
|
+
error && /* @__PURE__ */ jsx("div", {
|
|
25658
|
+
className: cn("mt-2 px-3 py-2 rounded border border-danger/40 bg-danger/10", "text-xs text-danger"),
|
|
25659
|
+
children: error
|
|
25660
|
+
}),
|
|
25661
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
|
|
25662
|
+
type: "button",
|
|
25663
|
+
variant: "ghost",
|
|
25664
|
+
size,
|
|
25665
|
+
disabled: running,
|
|
25666
|
+
onClick: () => setOpen(false),
|
|
25667
|
+
children: "Cancel"
|
|
25668
|
+
}), /* @__PURE__ */ jsxs(Button, {
|
|
25669
|
+
type: "button",
|
|
25670
|
+
variant: confirmVariant,
|
|
25671
|
+
size,
|
|
25672
|
+
disabled: running,
|
|
25673
|
+
onClick: () => {
|
|
25674
|
+
handleConfirm();
|
|
25675
|
+
},
|
|
25676
|
+
children: [running && /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), confirmLabel ?? label]
|
|
25677
|
+
})] })
|
|
25678
|
+
] })
|
|
25679
|
+
})] });
|
|
25680
|
+
}
|
|
25681
|
+
//#endregion
|
|
24568
25682
|
//#region src/composites/snapshot-button.tsx
|
|
24569
25683
|
/**
|
|
24570
25684
|
* SnapshotButton — operator-facing manual snapshot trigger.
|
|
@@ -25471,169 +26585,6 @@ function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
|
|
|
25471
26585
|
};
|
|
25472
26586
|
}
|
|
25473
26587
|
//#endregion
|
|
25474
|
-
//#region src/hooks/use-ptz.ts
|
|
25475
|
-
/**
|
|
25476
|
-
* usePTZ — PTZ control hook for device-scoped pan / tilt / zoom.
|
|
25477
|
-
*
|
|
25478
|
-
* Wraps the `ptz` capability methods through the canonical
|
|
25479
|
-
* `useDeviceProxy` surface — every call goes through
|
|
25480
|
-
* `dev.ptz?.<method>(...)` with deviceId/nodeId auto-injected. The
|
|
25481
|
-
* hook stays bare-bones around state (`busy`, `error`, `presets`)
|
|
25482
|
-
* so the operator-facing component (PTZOverlay) stays trivial.
|
|
25483
|
-
*
|
|
25484
|
-
* Returns:
|
|
25485
|
-
* - `move(direction)`: discrete one-shot move (cap.move + cap.stop).
|
|
25486
|
-
* Use for short pulse moves triggered by tapping a d-pad button.
|
|
25487
|
-
* - `startContinuous(direction)` / `stopContinuous()`: gesture-driven
|
|
25488
|
-
* continuous motion (`cap.continuousMove` + `cap.stop`). Use for
|
|
25489
|
-
* long-press handlers on touch / mouse-down handlers on desktop.
|
|
25490
|
-
* - `zoom('in' | 'out')`: discrete zoom step.
|
|
25491
|
-
* - `goHome()`: jump to preset 0.
|
|
25492
|
-
* - `presets` + `goToPreset(presetId)`: list and jump to named presets.
|
|
25493
|
-
*
|
|
25494
|
-
* Parametrised by the same `UseDeviceProxyTrpc` shape every other
|
|
25495
|
-
* device hook uses — works under admin-ui (BackendClient.trpc) and
|
|
25496
|
-
* addon pages (AddonPageProps.trpc) alike.
|
|
25497
|
-
*/
|
|
25498
|
-
var DIRECTION_VECTORS = {
|
|
25499
|
-
"up": {
|
|
25500
|
-
pan: 0,
|
|
25501
|
-
tilt: 1
|
|
25502
|
-
},
|
|
25503
|
-
"down": {
|
|
25504
|
-
pan: 0,
|
|
25505
|
-
tilt: -1
|
|
25506
|
-
},
|
|
25507
|
-
"left": {
|
|
25508
|
-
pan: -1,
|
|
25509
|
-
tilt: 0
|
|
25510
|
-
},
|
|
25511
|
-
"right": {
|
|
25512
|
-
pan: 1,
|
|
25513
|
-
tilt: 0
|
|
25514
|
-
},
|
|
25515
|
-
"up-left": {
|
|
25516
|
-
pan: -1,
|
|
25517
|
-
tilt: 1
|
|
25518
|
-
},
|
|
25519
|
-
"up-right": {
|
|
25520
|
-
pan: 1,
|
|
25521
|
-
tilt: 1
|
|
25522
|
-
},
|
|
25523
|
-
"down-left": {
|
|
25524
|
-
pan: -1,
|
|
25525
|
-
tilt: -1
|
|
25526
|
-
},
|
|
25527
|
-
"down-right": {
|
|
25528
|
-
pan: 1,
|
|
25529
|
-
tilt: -1
|
|
25530
|
-
}
|
|
25531
|
-
};
|
|
25532
|
-
function usePTZ(trpc, deviceId, options) {
|
|
25533
|
-
const defaultSpeed = options?.defaultSpeed ?? .5;
|
|
25534
|
-
const pulseMs = options?.pulseMs ?? 250;
|
|
25535
|
-
const enabled = options?.enabled ?? true;
|
|
25536
|
-
const ptz = useDeviceProxy(trpc, enabled ? deviceId : null)?.ptz;
|
|
25537
|
-
const [presets, setPresets] = useState([]);
|
|
25538
|
-
const [busy, setBusy] = useState(false);
|
|
25539
|
-
const [error, setError] = useState(null);
|
|
25540
|
-
const refreshPresets = useCallback(async () => {
|
|
25541
|
-
if (!enabled || !ptz) return;
|
|
25542
|
-
try {
|
|
25543
|
-
setPresets(await ptz.getPresets({}));
|
|
25544
|
-
} catch (err) {
|
|
25545
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
25546
|
-
if (msg.includes("provider not available") || msg.includes("no 'ptz' binding")) return;
|
|
25547
|
-
setError(msg);
|
|
25548
|
-
}
|
|
25549
|
-
}, [ptz, enabled]);
|
|
25550
|
-
useEffect(() => {
|
|
25551
|
-
refreshPresets();
|
|
25552
|
-
}, [refreshPresets]);
|
|
25553
|
-
const wrap = useCallback(async (fn) => {
|
|
25554
|
-
if (!enabled || !ptz) return void 0;
|
|
25555
|
-
setError(null);
|
|
25556
|
-
setBusy(true);
|
|
25557
|
-
try {
|
|
25558
|
-
return await fn();
|
|
25559
|
-
} catch (err) {
|
|
25560
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
25561
|
-
return;
|
|
25562
|
-
} finally {
|
|
25563
|
-
setBusy(false);
|
|
25564
|
-
}
|
|
25565
|
-
}, [enabled, ptz]);
|
|
25566
|
-
return {
|
|
25567
|
-
move: useCallback(async (direction, speed) => {
|
|
25568
|
-
if (!ptz) return;
|
|
25569
|
-
const v = DIRECTION_VECTORS[direction];
|
|
25570
|
-
const s = speed ?? defaultSpeed;
|
|
25571
|
-
await wrap(async () => {
|
|
25572
|
-
await ptz.move({
|
|
25573
|
-
pan: v.pan,
|
|
25574
|
-
tilt: v.tilt,
|
|
25575
|
-
speed: s
|
|
25576
|
-
});
|
|
25577
|
-
await new Promise((r) => setTimeout(r, pulseMs));
|
|
25578
|
-
await ptz.stop({});
|
|
25579
|
-
});
|
|
25580
|
-
}, [
|
|
25581
|
-
ptz,
|
|
25582
|
-
defaultSpeed,
|
|
25583
|
-
pulseMs,
|
|
25584
|
-
wrap
|
|
25585
|
-
]),
|
|
25586
|
-
startContinuous: useCallback(async (direction, speed) => {
|
|
25587
|
-
if (!ptz) return;
|
|
25588
|
-
const v = DIRECTION_VECTORS[direction];
|
|
25589
|
-
const s = speed ?? defaultSpeed;
|
|
25590
|
-
await wrap(() => ptz.continuousMove({
|
|
25591
|
-
pan: v.pan,
|
|
25592
|
-
tilt: v.tilt,
|
|
25593
|
-
speed: s
|
|
25594
|
-
}));
|
|
25595
|
-
}, [
|
|
25596
|
-
ptz,
|
|
25597
|
-
defaultSpeed,
|
|
25598
|
-
wrap
|
|
25599
|
-
]),
|
|
25600
|
-
stopContinuous: useCallback(async () => {
|
|
25601
|
-
if (!ptz) return;
|
|
25602
|
-
await wrap(() => ptz.stop({}));
|
|
25603
|
-
}, [ptz, wrap]),
|
|
25604
|
-
zoom: useCallback(async (direction, speed) => {
|
|
25605
|
-
if (!ptz) return;
|
|
25606
|
-
const z = direction === "in" ? 1 : -1;
|
|
25607
|
-
const s = speed ?? defaultSpeed;
|
|
25608
|
-
await wrap(async () => {
|
|
25609
|
-
await ptz.move({
|
|
25610
|
-
zoom: z,
|
|
25611
|
-
speed: s
|
|
25612
|
-
});
|
|
25613
|
-
await new Promise((r) => setTimeout(r, pulseMs));
|
|
25614
|
-
await ptz.stop({});
|
|
25615
|
-
});
|
|
25616
|
-
}, [
|
|
25617
|
-
ptz,
|
|
25618
|
-
defaultSpeed,
|
|
25619
|
-
pulseMs,
|
|
25620
|
-
wrap
|
|
25621
|
-
]),
|
|
25622
|
-
goHome: useCallback(async () => {
|
|
25623
|
-
if (!ptz) return;
|
|
25624
|
-
await wrap(() => ptz.goHome({}));
|
|
25625
|
-
}, [ptz, wrap]),
|
|
25626
|
-
goToPreset: useCallback(async (presetId) => {
|
|
25627
|
-
if (!ptz) return;
|
|
25628
|
-
await wrap(() => ptz.goToPreset({ presetId }));
|
|
25629
|
-
}, [ptz, wrap]),
|
|
25630
|
-
presets,
|
|
25631
|
-
refreshPresets,
|
|
25632
|
-
busy,
|
|
25633
|
-
error
|
|
25634
|
-
};
|
|
25635
|
-
}
|
|
25636
|
-
//#endregion
|
|
25637
26588
|
//#region src/hooks/use-doorbell-events.ts
|
|
25638
26589
|
/**
|
|
25639
26590
|
* useDoorbellEvents — subscribe to doorbell.onPressed across the cluster.
|
|
@@ -25855,6 +26806,6 @@ function useEventInvalidation(queryKey, categories) {
|
|
|
25855
26806
|
]);
|
|
25856
26807
|
}
|
|
25857
26808
|
//#endregion
|
|
25858
|
-
export { AddonGlobalSettingsForm, AgentStepEditor, AppShell, AudioClassificationList, AudioLevelWaveform, AudioWaveform, BTN_COMPACT, BTN_COMPACT_DANGER, BTN_COMPACT_PRIMARY, BTN_COMPACT_WARNING, Badge, BatteryBadge, BottomSheet, Breadcrumb, Button, CHIP_ACTIVE, CHIP_BASE, CHIP_INACTIVE, CLASS_COLORS, CameraStreamPlayer, Card, Checkbox, CodeBlock, CollapsibleCard, ConfigFormBuilder, FormField as ConfigFormField, ConfigSchemaField, ConfirmActionButton, ConfirmDialogProvider, CopyButton, CustomFieldRenderersProvider, DEFAULT_COLOR, DataTable, DetectionCanvas, DetectionOverlay, DetectionResultTree, DevShell, DeviceActivityPanel, DeviceCard, DeviceContextProvider, DeviceExportPanel, DeviceGrid, DeviceItem, DeviceList, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DiscoveryPanel, DoorbellRecentPanel, Dropdown, DropdownContent, DropdownItem, DropdownTrigger, EmptyState, ErrorBox, EventStream, FilterBar, FloatingEventStream, FloatingLogStream, FloatingPanel, FormField$1 as FormField, GRID_GAP, GRID_PAIRED, GRID_QUICK_STATS, INPUT_COMPACT, IconButton, ImageSelector, InferenceConfigSelector, Input, KebabMenu, KeyValueList, LIST_ROW, Label, LogStream, LoginForm, MobileDrawer, NodeMultiSelectField, NodePicker, NodeSelectField, PHASE_CONFIG, PTZOverlay, PageHeader, PhaseIcon, PipelineBuilder, PipelineRuntimeSelector, PipelineStep, PipelineTreeMatrix, PlayerOverlaysProvider, Popover, PopoverContent, PopoverTrigger, ProviderBadge, QrCode, ResponseLog, SECTION_BODY, SECTION_CARD, SECTION_HEADER, SPLIT_PANEL_OUTER, SPLIT_PANEL_SIDE, STACK_GAP, ScopePicker, ScrollArea, Select, SemanticBadge, Separator, Sidebar, SidebarItem, Skeleton, SlideOverPanel, SnapshotButton, StatCard, StateValuesStream, StatusBadge, StepTimings, StepTreeMaster, StreamBrokerSelector, StreamPanel, Switch, SystemProvider, TEXT_FIELD_LABEL, TEXT_HINT, TEXT_METRIC, TEXT_SECTION_LABEL, TEXT_VALUE, Tabs, TabsContent, TabsList, TabsTrigger, ThemeProvider, Tooltip, TooltipContent, TooltipTrigger, VersionBadge, WidgetRegistryProvider, WidgetSlot, ZoneEditingProvider, buildStepTreeFromSchema, cn, createSharedContext, createTheme, darkColors, defaultTheme, deriveDeviceKind, ensureMfHostInit, getClassColor, getPhaseVisual, isFieldVisible, lightColors, mirror, mountAddonPage, providerIcons, statusIcons, themeToCss, trpc, useAccessoriesGetStatus, useAddonPagesListPages, useAddonSettingsGetDeviceSettings, useAddonSettingsGetGlobalSettings, useAddonSettingsUpdateDeviceSettings, useAddonSettingsUpdateGlobalSettings, useAddonWidgetsListWidgets, useAddonsApplyAutoUpdateToAll, useAddonsCustom, useAddonsForceRefresh, useAddonsGetAddonAutoUpdate, useAddonsGetAutoUpdateSettings, useAddonsGetLastRestart, useAddonsGetLogs, useAddonsGetVersions, useAddonsInstallFromWorkspace, useAddonsInstallPackage, useAddonsIsWorkspaceAvailable, useAddonsList, useAddonsListCapabilityProviders, useAddonsListFrameworkPackages, useAddonsListPackages, useAddonsListUpdates, useAddonsListWorkspacePackages, useAddonsOnAddonLogs, useAddonsReloadPackages, useAddonsRestartAddon, useAddonsRestartServer, useAddonsRetryLoad, useAddonsRollbackPackage, useAddonsSearchAvailable, useAddonsSetAddonAutoUpdate, useAddonsSetAutoUpdateSettings, useAddonsUninstallPackage, useAddonsUpdateFrameworkPackage, useAddonsUpdatePackage, useAlertsDismiss, useAlertsEmit, useAlertsGetUnreadCount, useAlertsList, useAlertsMarkAllRead, useAlertsMarkRead, useAlertsUpdate, useAllWidgets, useAudioAnalysisApplyDeviceSettingsPatch, useAudioAnalysisGetDeviceLiveContribution, useAudioAnalysisGetDeviceSettingsContribution, useAudioAnalysisResolveDeviceSettings, useAudioAnalyzerAnalyseChunk, useAudioAnalyzerClassify, useAudioAnalyzerDispose, useAudioAnalyzerIsReady, useAudioAnalyzerReprobeAudioEngine, useAudioCodecCanHandle, useAudioCodecCloseSession, useAudioCodecCreateDecodeSession, useAudioCodecCreateEncodeSession, useAudioCodecFlushEncode, useAudioCodecListActiveSessions, useAudioCodecListSupportedCodecs, useAudioCodecPullEncoded, useAudioCodecPullPcm, useAudioCodecPushEncodedFrame, useAudioCodecPushPcm, useAudioMetricsGetCurrentSnapshot, useAudioMetricsGetHistory, useAuthenticationListProviders, useAuthenticationSetProviderEnabled, useBackupDelete, useBackupGetEntries, useBackupList, useBackupListArchives, useBackupListDestinations, useBackupListLocations, useBackupPreviewSchedule, useBackupRestore, useBackupTrigger, useBackupUpsertDestinationPolicy, useBatteryGetStatus, useBrightnessGetStatus, useBrightnessSetBrightness, useCameraCredentialsGetCredentials, useCameraCredentialsGetStatus, useCameraStreamsGetBrokerStreams, useCameraStreamsGetCameraStreams, useCameraStreamsGetRtspEntries, useClusterNodes, useConfirm, useCustomFieldRenderer, useDebouncedString, useDecoderCreateSession, useDecoderDestroySession, useDecoderGetInfo, useDecoderGetStats, useDecoderListActiveSessions, useDecoderOpenStream, useDecoderPullFrames, useDecoderPushPacket, useDecoderReprobeHwaccel, useDecoderSupportsCodec, useDecoderUpdateConfig, useDetectionPipelineApplyDeviceSettingsPatch, useDetectionPipelineGetDeviceLiveContribution, useDetectionPipelineGetDeviceSettingsContribution, useDevShell, useDevice, useDeviceBattery, useDeviceCapability, useDeviceDetections, useDeviceDiscoveryAdoptDevice, useDeviceDiscoveryGetStatus, useDeviceDiscoveryListDiscovered, useDeviceDiscoveryRefreshDiscovery, useDeviceDiscoveryReleaseDevice, useDeviceExportApplyDeviceSettingsPatch, useDeviceExportExposeDevice, useDeviceExportGetDeviceLiveContribution, useDeviceExportGetDeviceSettingsContribution, useDeviceExportGetStatus, useDeviceExportListExposedDevices, useDeviceExportListSupportedDeviceKinds, useDeviceExportUnexposeDevice, useDeviceId, useDeviceManagerAddLocation, useDeviceManagerAdoptDevice, useDeviceManagerAllocateDeviceId, useDeviceManagerCreateDevice, useDeviceManagerDisable, useDeviceManagerDiscoverDevices, useDeviceManagerEnable, useDeviceManagerGetAllBindings, useDeviceManagerGetBindings, useDeviceManagerGetChildren, useDeviceManagerGetConfigSchema, useDeviceManagerGetCreationSchema, useDeviceManagerGetDevice, useDeviceManagerGetDeviceAggregate, useDeviceManagerGetDeviceLiveInfoAggregate, useDeviceManagerGetDeviceSettingsAggregate, useDeviceManagerGetDeviceStatusAggregate, useDeviceManagerGetSettingsSchema, useDeviceManagerGetStreamProfileMap, useDeviceManagerGetStreamSources, useDeviceManagerListAll, useDeviceManagerListBindableCapsForDeviceType, useDeviceManagerListLocations, useDeviceManagerListPersistedByAddon, useDeviceManagerListWrappersForCap, useDeviceManagerLoadConfig, useDeviceManagerLoadMeta, useDeviceManagerLoadRuntimeState, useDeviceManagerPersistConfig, useDeviceManagerProbeStreams, useDeviceManagerRegisterDevice, useDeviceManagerRemove, useDeviceManagerRemoveDevice, useDeviceManagerRemoveLocation, useDeviceManagerSetDisabled, useDeviceManagerSetLocation, useDeviceManagerSetMetadata, useDeviceManagerSetName, useDeviceManagerSetStreamProfileMap, useDeviceManagerSetWrapperActive, useDeviceManagerTestCreationField, useDeviceManagerTestField, useDeviceManagerUpdateConfig, useDeviceManagerUpdateDeviceField, useDeviceManagerUpdateDeviceFieldsBatch, useDeviceOpsGetConfigEntries, useDeviceOpsGetSettingsSchema, useDeviceOpsGetStreamSources, useDeviceOpsRemoveDevice, useDeviceOpsSetConfig, useDeviceProviderAdoptDiscoveredDevice, useDeviceProviderCreateDevice, useDeviceProviderDiscoverDevices, useDeviceProviderGetChildCreationSchema, useDeviceProviderGetDevices, useDeviceProviderGetStatus, useDeviceProviderStart, useDeviceProviderStop, useDeviceProviderSupportsDiscovery, useDeviceProviderSupportsManualCreation, useDeviceProviderTestCreationField, useDeviceProxy, useDeviceSnapshot, useDeviceSnapshotImage, useDeviceState, useDeviceStateGetAllSnapshots, useDeviceStateGetCapSlice, useDeviceStateGetSnapshot, useDeviceStateSetCapSlice, useDeviceStateSlice, useDeviceStatusGetStatus, useDeviceWebrtc, useDevices, useDoorbellEvents, useDoorbellGetStatus, useEventInvalidation, useEventStreamLatest, useEventStreamMap, useEventsGetEventClipUrl, useEventsGetEventThumbnail, useEventsGetEvents, useFeatureProbeGetStatus, useIntegrationsCreate, useIntegrationsDelete, useIntegrationsGet, useIntegrationsGetAvailableTypes, useIntegrationsGetByAddonId, useIntegrationsGetSettings, useIntegrationsList, useIntegrationsSetSettings, useIntegrationsTestConnection, useIntegrationsUpdate, useIntercomEndTalkSession, useIntercomGetStatus, useIntercomHandleAnswer, useIntercomPushTalkPcm, useIntercomStartSession, useIntercomStartTalkSession, useIntercomStopSession, useIsMidWidth, useIsMobile, useLiveBuffer, useLiveEvent, useLocalNetworkGetAllowedAddresses, useLocalNetworkGetConnectionEndpoints, useLocalNetworkGetPreferred, useLocalNetworkList, useLocalNetworkResetAllowlistToBestMatch, useLocalNetworkSetAllowedAddresses, useMeshNetworkGetStatus, useMeshNetworkJoin, useMeshNetworkLeave, useMeshNetworkListPeers, useMeshNetworkLogout, useMeshNetworkStartLogin, useMeshNetworkTestConnection, useMeshOrchestratorJoinProvider, useMeshOrchestratorLeaveProvider, useMeshOrchestratorListProviderPeers, useMeshOrchestratorListProviders, useMeshOrchestratorLogoutProvider, useMeshOrchestratorStartLoginProvider, useMetricsProviderCollectSnapshot, useMetricsProviderGetAddonStats, useMetricsProviderGetCached, useMetricsProviderGetCpuTemperature, useMetricsProviderGetCurrent, useMetricsProviderGetDiskSpace, useMetricsProviderGetGpuInfo, useMetricsProviderGetProcessStats, useMetricsProviderKillProcess, useMetricsProviderListAddonInstances, useMetricsProviderListNodeProcesses, useMotionDetectionAnalyze, useMotionDetectionApplyDeviceSettingsPatch, useMotionDetectionGetDeviceLiveContribution, useMotionDetectionGetDeviceSettingsContribution, useMotionDetectionRemoveCamera, useMotionDetectionReset, useMotionGetStatus, useMotionIsDetected, useMotionTriggerGetStatus, useMotionTriggerSetMotionTrigger, useMqttBrokerAddBroker, useMqttBrokerGetBrokerConfig, useMqttBrokerGetStatus, useMqttBrokerListBrokers, useMqttBrokerRemoveBroker, useMqttBrokerStartEmbeddedBroker, useMqttBrokerStopEmbeddedBroker, useMqttBrokerTestConnection, useNativeObjectDetectionGetStatus, useNetworkQualityGetAllStats, useNetworkQualityGetDeviceStats, useNetworkQualityReportClientStats, useNodesClusterAddonStatus, useNodesDeployAddon, useNodesExecuteQuery, useNodesGetNodeAddons, useNodesRenameNode, useNodesRestartAddon, useNodesRestartNode, useNodesRestartProcess, useNodesSetProcessLogLevel, useNodesShutdownNode, useNodesTopology, useNodesUndeployAddon, useNotificationOutputSend, useNotificationOutputSendTest, useOptionalSystem, useOptionalWidgetRegistry, useOsdGetStatus, useOsdSetOverlay, usePTZ, usePipelineAnalyticsApplyDeviceSettingsPatch, usePipelineAnalyticsClearTracks, usePipelineAnalyticsGetActiveTracks, usePipelineAnalyticsGetAudioEvents, usePipelineAnalyticsGetDeviceLiveContribution, usePipelineAnalyticsGetDeviceSettingsContribution, usePipelineAnalyticsGetEventMedia, usePipelineAnalyticsGetMotionEvents, usePipelineAnalyticsGetObjectEvents, usePipelineAnalyticsGetTrack, usePipelineAnalyticsGetTrackMedia, usePipelineAnalyticsListTracks, usePipelineExecutorCacheFrameInPool, usePipelineExecutorDeleteModel, usePipelineExecutorDeleteTemplate, usePipelineExecutorDetect, usePipelineExecutorDownloadModel, usePipelineExecutorGetAddonModels, usePipelineExecutorGetAudioCapabilities, usePipelineExecutorGetAvailableEngines, usePipelineExecutorGetCapabilities, usePipelineExecutorGetDefaultSteps, usePipelineExecutorGetDetectionConfigSchema, usePipelineExecutorGetEffectiveTuning, usePipelineExecutorGetGlobalPipelineConfig, usePipelineExecutorGetGlobalSteps, usePipelineExecutorGetOrchestratorConfigSchema, usePipelineExecutorGetReferenceAudio, usePipelineExecutorGetReferenceAudioFiles, usePipelineExecutorGetReferenceImage, usePipelineExecutorGetSchema, usePipelineExecutorGetSelectedEngine, usePipelineExecutorGetVideoPipelineSteps, usePipelineExecutorInferCached, usePipelineExecutorKillEngine, usePipelineExecutorListLoadedEngines, usePipelineExecutorListReferenceImages, usePipelineExecutorListTemplates, usePipelineExecutorReprobeEngine, usePipelineExecutorRunAudioTest, usePipelineExecutorRunPipeline, usePipelineExecutorRunPipelineBatch, usePipelineExecutorSaveTemplate, usePipelineExecutorSetVideoPipelineSteps, usePipelineExecutorSpinEngine, usePipelineExecutorUncacheFrame, usePipelineExecutorUpdateTemplate, usePipelineOrchestratorApplyDeviceSettingsPatch, usePipelineOrchestratorAssignAudio, usePipelineOrchestratorAssignDecoder, usePipelineOrchestratorAssignPipeline, usePipelineOrchestratorDeleteTemplate, usePipelineOrchestratorGetAgentLoad, usePipelineOrchestratorGetAgentSettings, usePipelineOrchestratorGetAudioAssignment, usePipelineOrchestratorGetAudioAssignments, usePipelineOrchestratorGetAudioNodeLoad, usePipelineOrchestratorGetCameraMetrics, usePipelineOrchestratorGetCameraSettings, usePipelineOrchestratorGetCameraStepOverrides, usePipelineOrchestratorGetCapabilityBindings, usePipelineOrchestratorGetDecoderAssignment, usePipelineOrchestratorGetDecoderAssignments, usePipelineOrchestratorGetDeviceLiveContribution, usePipelineOrchestratorGetDeviceSettingsContribution, usePipelineOrchestratorGetGlobalMetrics, usePipelineOrchestratorGetPipelineAssignment, usePipelineOrchestratorGetPipelineAssignments, usePipelineOrchestratorListAgentSettings, usePipelineOrchestratorListTemplates, usePipelineOrchestratorRebalance, usePipelineOrchestratorRemoveAgentSettings, usePipelineOrchestratorResolvePipeline, usePipelineOrchestratorSaveTemplate, usePipelineOrchestratorSetAgentAddonDefaults, usePipelineOrchestratorSetCameraPipelineForAgent, usePipelineOrchestratorSetCameraStepOverride, usePipelineOrchestratorSetCameraStepToggle, usePipelineOrchestratorSetCapabilityBinding, usePipelineOrchestratorUnassignAudio, usePipelineOrchestratorUnassignDecoder, usePipelineOrchestratorUnassignPipeline, usePipelineOrchestratorUpdateTemplate, usePipelineRunnerAttachCamera, usePipelineRunnerDetachCamera, usePipelineRunnerGetAllCameraMetrics, usePipelineRunnerGetCameraMetrics, usePipelineRunnerGetLocalCameras, usePipelineRunnerGetLocalLoad, usePipelineRunnerGetLocalMetrics, usePipelineRunnerReportMotion, usePlatformProbeGetCapabilities, usePlatformProbeGetHardware, usePlatformProbeGetHardwareEncoders, usePlatformProbeRefreshHardwareEncoders, usePlatformProbeResolveHwAccel, usePlatformProbeResolveInferenceConfig, usePlayerOverlayLayer, usePlayerOverlayLayers, usePlayerToolbarButton, usePlayerToolbarButtons, usePtzAutotrackGetSettings, usePtzAutotrackGetStatus, usePtzAutotrackSetEnabled, usePtzAutotrackSetSettings, usePtzContinuousMove, usePtzGetPosition, usePtzGetPresets, usePtzGetStatus, usePtzGoHome, usePtzGoToPreset, usePtzMove, usePtzStop, useRebootReboot, useRecordingEngineDisable, useRecordingEngineEnable, useRecordingEngineEstimateGlobalStorage, useRecordingEngineEstimateStorage, useRecordingEngineGetAvailability, useRecordingEngineGetConfig, useRecordingEngineGetMotionStats, useRecordingEngineGetPlaylist, useRecordingEngineGetPolicy, useRecordingEngineGetPolicyStatus, useRecordingEngineGetRetentionConfig, useRecordingEngineGetSegments, useRecordingEngineGetStatus, useRecordingEngineGetStorageUsage, useRecordingEngineGetThumbnail, useRecordingEngineSetPolicy, useRecordingEngineUpdateConfig, useRecordingEngineUpdateRetentionConfig, useRecordingGetPlaybackUrl, useRecordingGetSegments, useRecordingGetThumbnailAt, useRemoteAccessListProviders, useRemoteAccessStartProvider, useRemoteAccessStopProvider, useSettingsStoreCount, useSettingsStoreDeclareCollection, useSettingsStoreDelete, useSettingsStoreGet, useSettingsStoreInsert, useSettingsStoreIsEmpty, useSettingsStoreQuery, useSettingsStoreSet, useSettingsStoreUpdate, useSnapshotApplyDeviceSettingsPatch, useSnapshotGetDeviceLiveContribution, useSnapshotGetDeviceSettingsContribution, useSnapshotGetSnapshot, useSnapshotGetStatus, useSnapshotInvalidateCache, useSnapshotProviderGetSnapshot, useSnapshotProviderSupportsDevice, useStorageAbortUpload, useStorageBeginDownload, useStorageBeginUpload, useStorageDelete, useStorageDeleteLocation, useStorageEndDownload, useStorageExists, useStorageFinalizeUpload, useStorageGetAvailableSpace, useStorageGetDefaultLocation, useStorageList, useStorageListLocations, useStorageListProviders, useStorageRead, useStorageReadChunk, useStorageResolve, useStorageTestConfig, useStorageTestLocation, useStorageUpsertLocation, useStorageWrite, useStorageWriteChunk, useStreamBrokerApplyDeviceSettingsPatch, useStreamBrokerAssignProfile, useStreamBrokerGetAllRtspEntries, useStreamBrokerGetBroker, useStreamBrokerGetBrokerStats, useStreamBrokerGetDeviceLiveContribution, useStreamBrokerGetDeviceSettingsContribution, useStreamBrokerGetPreBufferInfo, useStreamBrokerGetRtspEntry, useStreamBrokerGetRtspPort, useStreamBrokerGetStreamUrl, useStreamBrokerGetStreamWithCodec, useStreamBrokerIsRtspEnabled, useStreamBrokerKillClient, useStreamBrokerListAllCameraStreams, useStreamBrokerListAllProfileSlots, useStreamBrokerListClients, useStreamBrokerPublishCameraStream, useStreamBrokerRegenerateRtspToken, useStreamBrokerReleaseStreamWithCodec, useStreamBrokerRestartProfile, useStreamBrokerRetractCameraStream, useStreamBrokerSetPreBufferDuration, useStreamBrokerSetRtspEnabled, useStreamBrokerUnassignProfile, useSwitchGetStatus, useSwitchSetState, useSystem, useSystemFeatureFlags, useSystemForceRetentionCleanup, useSystemGetRetentionConfig, useSystemHealth, useSystemInfo, useSystemMutation, useSystemNetworkAddresses, useSystemQuery, useSystemSetRetentionConfig, useThemeMode, useToastOnToast, useTurnOrchestratorGetAllServers, useTurnOrchestratorListProviders, useTurnOrchestratorSetProviderEnabled, useTurnProviderGetTurnServers, useUserManagementConfirmTotp, useUserManagementCreateApiKey, useUserManagementCreateScopedToken, useUserManagementCreateUser, useUserManagementDeleteUser, useUserManagementDisableTotp, useUserManagementGetTotpStatus, useUserManagementListApiKeys, useUserManagementListScopedTokens, useUserManagementListUsers, useUserManagementResetPassword, useUserManagementRevokeApiKey, useUserManagementRevokeScopedToken, useUserManagementSetUserScopes, useUserManagementSetupTotp, useUserManagementUpdateUser, useUserManagementValidateApiKey, useUserManagementValidateCredentials, useUserManagementValidateScopedToken, useUserManagementVerifyTotp, useWebrtcSessionCloseSession, useWebrtcSessionCreateSession, useWebrtcSessionHandleAnswer, useWebrtcSessionHandleOffer, useWebrtcSessionHasAdaptiveBitrate, useWebrtcSessionListStreams, useWidget, useWidgetMetadata, useWidgetRegistry, useZoneAnalyticsGetCameraHistory, useZoneAnalyticsGetCurrentSnapshot, useZoneAnalyticsGetUnzonedHistory, useZoneAnalyticsGetZoneHistory, useZoneEditing, useZoneRulesListRules, useZoneRulesSetRules, useZonesAddZone, useZonesListZones, useZonesRemoveZone, useZonesUpdateZone, validateScopes };
|
|
26809
|
+
export { AddonGlobalSettingsForm, AgentStepEditor, AppShell, AudioClassificationList, AudioLevelWaveform, AudioWaveform, AutotrackSection, BTN_COMPACT, BTN_COMPACT_DANGER, BTN_COMPACT_PRIMARY, BTN_COMPACT_WARNING, Badge, BatteryBadge, BottomSheet, Breadcrumb, Button, CHIP_ACTIVE, CHIP_BASE, CHIP_INACTIVE, CLASS_COLORS, CameraStreamPlayer, Card, Checkbox, CodeBlock, CollapsibleCard, ConfigFormBuilder, FormField as ConfigFormField, ConfigSchemaField, ConfirmActionButton, ConfirmDialogProvider, CopyButton, CustomFieldRenderersProvider, DEFAULT_COLOR, DataTable, DetectionCanvas, DetectionOverlay, DetectionResultTree, DevShell, DeviceActivityPanel, DeviceCard, DeviceContextProvider, DeviceExportPanel, DeviceGrid, DeviceItem, DeviceList, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DiscoveryPanel, DoorbellRecentPanel, Dropdown, DropdownContent, DropdownItem, DropdownTrigger, EmptyState, ErrorBox, EventStream, FilterBar, FloatingEventStream, FloatingLogStream, FloatingPanel, FormField$1 as FormField, GRID_GAP, GRID_PAIRED, GRID_QUICK_STATS, HOST_WIDGETS, INPUT_COMPACT, IconButton, ImageSelector, InferenceConfigSelector, Input, KebabMenu, KeyValueList, LIST_ROW, Label, LogStream, LoginForm, MobileDrawer, MotionZonesSettings, NodeMultiSelectField, NodePicker, NodeSelectField, PHASE_CONFIG, PTZOverlay, PageHeader, PhaseIcon, PipelineBuilder, PipelineRuntimeSelector, PipelineStep, PipelineTreeMatrix, PlayerOverlaysProvider, Popover, PopoverContent, PopoverTrigger, ProviderBadge, PtzPanel, QrCode, ResponseLog, SECTION_BODY, SECTION_CARD, SECTION_HEADER, SPLIT_PANEL_OUTER, SPLIT_PANEL_SIDE, STACK_GAP, ScopePicker, ScrollArea, Select, SemanticBadge, Separator, Sidebar, SidebarItem, Skeleton, SlideOverPanel, SnapshotButton, StatCard, StateValuesStream, StatusBadge, StepTimings, StepTreeMaster, StreamBrokerSelector, StreamPanel, Switch, SystemProvider, TEXT_FIELD_LABEL, TEXT_HINT, TEXT_METRIC, TEXT_SECTION_LABEL, TEXT_VALUE, Tabs, TabsContent, TabsList, TabsTrigger, ThemeProvider, Tooltip, TooltipContent, TooltipTrigger, VersionBadge, WidgetRegistryProvider, WidgetSlot, ZoneEditingProvider, buildStepTreeFromSchema, cn, createSharedContext, createTheme, darkColors, defaultTheme, deriveDeviceKind, ensureMfHostInit, getClassColor, getPhaseVisual, isFieldVisible, lightColors, loadRemoteBundle, mirror, mountAddonPage, providerIcons, statusIcons, themeToCss, trpc, useAccessoriesGetStatus, useAddonPagesListPages, useAddonSettingsGetDeviceSettings, useAddonSettingsGetGlobalSettings, useAddonSettingsUpdateDeviceSettings, useAddonSettingsUpdateGlobalSettings, useAddonWidgetsListWidgets, useAddonsApplyAutoUpdateToAll, useAddonsCustom, useAddonsForceRefresh, useAddonsGetAddonAutoUpdate, useAddonsGetAutoUpdateSettings, useAddonsGetLastRestart, useAddonsGetLogs, useAddonsGetVersions, useAddonsInstallFromWorkspace, useAddonsInstallPackage, useAddonsIsWorkspaceAvailable, useAddonsList, useAddonsListCapabilityProviders, useAddonsListFrameworkPackages, useAddonsListPackages, useAddonsListUpdates, useAddonsListWorkspacePackages, useAddonsOnAddonLogs, useAddonsReloadPackages, useAddonsRestartAddon, useAddonsRestartServer, useAddonsRetryLoad, useAddonsRollbackPackage, useAddonsSearchAvailable, useAddonsSetAddonAutoUpdate, useAddonsSetAutoUpdateSettings, useAddonsSetCapabilityProviderEnabled, useAddonsUninstallPackage, useAddonsUpdateFrameworkPackage, useAddonsUpdatePackage, useAlertsDismiss, useAlertsEmit, useAlertsGetUnreadCount, useAlertsList, useAlertsMarkAllRead, useAlertsMarkRead, useAlertsUpdate, useAllWidgets, useAudioAnalysisApplyDeviceSettingsPatch, useAudioAnalysisGetDeviceLiveContribution, useAudioAnalysisGetDeviceSettingsContribution, useAudioAnalysisResolveDeviceSettings, useAudioAnalyzerAnalyseChunk, useAudioAnalyzerClassify, useAudioAnalyzerDispose, useAudioAnalyzerIsReady, useAudioAnalyzerReprobeAudioEngine, useAudioCodecCanHandle, useAudioCodecCloseSession, useAudioCodecCreateDecodeSession, useAudioCodecCreateEncodeSession, useAudioCodecFlushEncode, useAudioCodecListActiveSessions, useAudioCodecListSupportedCodecs, useAudioCodecPullEncoded, useAudioCodecPullPcm, useAudioCodecPushEncodedFrame, useAudioCodecPushPcm, useAudioMetricsGetCurrentSnapshot, useAudioMetricsGetHistory, useBackupDelete, useBackupGetEntries, useBackupList, useBackupListArchives, useBackupListDestinations, useBackupListLocations, useBackupPreviewSchedule, useBackupRestore, useBackupTrigger, useBackupUpsertDestinationPolicy, useBatteryGetStatus, useBrightnessGetStatus, useBrightnessSetBrightness, useCameraCredentialsGetCredentials, useCameraCredentialsGetStatus, useCameraStreamsGetBrokerStreams, useCameraStreamsGetCameraStreams, useCameraStreamsGetRtspEntries, useClusterNodes, useConfirm, useCustomFieldRenderer, useDebouncedString, useDecoderCreateSession, useDecoderDestroySession, useDecoderGetFrame, useDecoderGetInfo, useDecoderGetShmStats, useDecoderGetStats, useDecoderListActiveSessions, useDecoderOpenStream, useDecoderPullFrames, useDecoderPullHandles, useDecoderPushPacket, useDecoderReprobeHwaccel, useDecoderSupportsCodec, useDecoderUpdateConfig, useDetectionPipelineApplyDeviceSettingsPatch, useDetectionPipelineGetDeviceLiveContribution, useDetectionPipelineGetDeviceSettingsContribution, useDevShell, useDevice, useDeviceAutotrack, useDeviceBattery, useDeviceCapability, useDeviceDetections, useDeviceDiscoveryAdoptDevice, useDeviceDiscoveryGetStatus, useDeviceDiscoveryListDiscovered, useDeviceDiscoveryRefreshDiscovery, useDeviceDiscoveryReleaseDevice, useDeviceExportApplyDeviceSettingsPatch, useDeviceExportExposeDevice, useDeviceExportGetDeviceLiveContribution, useDeviceExportGetDeviceSettingsContribution, useDeviceExportGetStatus, useDeviceExportListExposedDevices, useDeviceExportListSupportedDeviceKinds, useDeviceExportUnexposeDevice, useDeviceId, useDeviceManagerAddLocation, useDeviceManagerAdoptDevice, useDeviceManagerAllocateDeviceId, useDeviceManagerCreateDevice, useDeviceManagerDisable, useDeviceManagerDiscoverDevices, useDeviceManagerEnable, useDeviceManagerGetAllBindings, useDeviceManagerGetBindings, useDeviceManagerGetChildren, useDeviceManagerGetConfigSchema, useDeviceManagerGetCreationSchema, useDeviceManagerGetDevice, useDeviceManagerGetDeviceAggregate, useDeviceManagerGetDeviceLiveInfoAggregate, useDeviceManagerGetDeviceSettingsAggregate, useDeviceManagerGetDeviceStatusAggregate, useDeviceManagerGetSettingsSchema, useDeviceManagerGetStreamProfileMap, useDeviceManagerGetStreamSources, useDeviceManagerListAll, useDeviceManagerListBindableCapsForDeviceType, useDeviceManagerListLocations, useDeviceManagerListPersistedByAddon, useDeviceManagerListWrappersForCap, useDeviceManagerLoadConfig, useDeviceManagerLoadMeta, useDeviceManagerLoadRuntimeState, useDeviceManagerPersistConfig, useDeviceManagerProbeStreams, useDeviceManagerRegisterDevice, useDeviceManagerRemove, useDeviceManagerRemoveDevice, useDeviceManagerRemoveLocation, useDeviceManagerSetDisabled, useDeviceManagerSetLocation, useDeviceManagerSetMetadata, useDeviceManagerSetName, useDeviceManagerSetStreamProfileMap, useDeviceManagerSetWrapperActive, useDeviceManagerTestCreationField, useDeviceManagerTestField, useDeviceManagerUpdateConfig, useDeviceManagerUpdateDeviceField, useDeviceManagerUpdateDeviceFieldsBatch, useDeviceOpsGetConfigEntries, useDeviceOpsGetSettingsSchema, useDeviceOpsGetStreamSources, useDeviceOpsRemoveDevice, useDeviceOpsSetConfig, useDeviceProviderAdoptDiscoveredDevice, useDeviceProviderCreateDevice, useDeviceProviderDiscoverDevices, useDeviceProviderGetChildCreationSchema, useDeviceProviderGetDevices, useDeviceProviderGetStatus, useDeviceProviderStart, useDeviceProviderStop, useDeviceProviderSupportsDiscovery, useDeviceProviderSupportsManualCreation, useDeviceProviderTestCreationField, useDeviceProxy, useDeviceSnapshot, useDeviceSnapshotImage, useDeviceState, useDeviceStateGetAllSnapshots, useDeviceStateGetCapSlice, useDeviceStateGetSnapshot, useDeviceStateSetCapSlice, useDeviceStateSlice, useDeviceStatusGetStatus, useDeviceWebrtc, useDevices, useDoorbellEvents, useDoorbellGetStatus, useEventInvalidation, useEventStreamLatest, useEventStreamMap, useEventsGetEventClipUrl, useEventsGetEventThumbnail, useEventsGetEvents, useFeatureProbeGetStatus, useIntegrationsCreate, useIntegrationsDelete, useIntegrationsGet, useIntegrationsGetAvailableTypes, useIntegrationsGetByAddonId, useIntegrationsGetSettings, useIntegrationsList, useIntegrationsSetSettings, useIntegrationsTestConnection, useIntegrationsUpdate, useIntercomEndTalkSession, useIntercomGetStatus, useIntercomHandleAnswer, useIntercomPushTalkPcm, useIntercomStartSession, useIntercomStartTalkSession, useIntercomStopSession, useIsMidWidth, useIsMobile, useLiveBuffer, useLiveEvent, useLocalNetworkGetAllowedAddresses, useLocalNetworkGetConnectionEndpoints, useLocalNetworkGetPreferred, useLocalNetworkList, useLocalNetworkResetAllowlistToBestMatch, useLocalNetworkSetAllowedAddresses, useMeshNetworkGetStatus, useMeshNetworkJoin, useMeshNetworkLeave, useMeshNetworkListPeers, useMeshNetworkLogout, useMeshNetworkStartLogin, useMeshNetworkTestConnection, useMetricsProviderCollectSnapshot, useMetricsProviderGetAddonStats, useMetricsProviderGetCached, useMetricsProviderGetCpuTemperature, useMetricsProviderGetCurrent, useMetricsProviderGetDiskSpace, useMetricsProviderGetGpuInfo, useMetricsProviderGetProcessStats, useMetricsProviderKillProcess, useMetricsProviderListAddonInstances, useMetricsProviderListNodeProcesses, useMotionDetectionAnalyze, useMotionDetectionApplyDeviceSettingsPatch, useMotionDetectionGetDeviceLiveContribution, useMotionDetectionGetDeviceSettingsContribution, useMotionDetectionRemoveCamera, useMotionDetectionReset, useMotionGetStatus, useMotionIsDetected, useMotionTriggerGetStatus, useMotionTriggerSetMotionTrigger, useMotionZonesGetOptions, useMotionZonesGetStatus, useMotionZonesSetZone, useMqttBrokerAddBroker, useMqttBrokerGetBrokerConfig, useMqttBrokerGetStatus, useMqttBrokerListBrokers, useMqttBrokerRemoveBroker, useMqttBrokerStartEmbeddedBroker, useMqttBrokerStopEmbeddedBroker, useMqttBrokerTestConnection, useNativeObjectDetectionGetStatus, useNetworkAccessGetEndpoint, useNetworkAccessGetStatus, useNetworkAccessListEndpoints, useNetworkAccessStart, useNetworkAccessStop, useNetworkQualityGetAllStats, useNetworkQualityGetDeviceStats, useNetworkQualityReportClientStats, useNodesClusterAddonStatus, useNodesDeployAddon, useNodesExecuteQuery, useNodesGetCapUsageGraph, useNodesGetNodeAddons, useNodesRenameNode, useNodesRestartAddon, useNodesRestartNode, useNodesRestartProcess, useNodesSetProcessLogLevel, useNodesShutdownNode, useNodesTopology, useNodesUndeployAddon, useNotificationOutputSend, useNotificationOutputSendTest, useOptionalSystem, useOptionalWidgetRegistry, useOsdGetStatus, useOsdSetOverlay, usePTZ, usePipelineAnalyticsApplyDeviceSettingsPatch, usePipelineAnalyticsClearTracks, usePipelineAnalyticsGetActiveTracks, usePipelineAnalyticsGetAudioEvents, usePipelineAnalyticsGetDeviceLiveContribution, usePipelineAnalyticsGetDeviceSettingsContribution, usePipelineAnalyticsGetEventMedia, usePipelineAnalyticsGetMotionEvents, usePipelineAnalyticsGetObjectEvents, usePipelineAnalyticsGetTrack, usePipelineAnalyticsGetTrackMedia, usePipelineAnalyticsListTracks, usePipelineExecutorCacheFrameInPool, usePipelineExecutorDeleteModel, usePipelineExecutorDeleteTemplate, usePipelineExecutorDetect, usePipelineExecutorDownloadModel, usePipelineExecutorGetAddonModels, usePipelineExecutorGetAudioCapabilities, usePipelineExecutorGetAvailableEngines, usePipelineExecutorGetCapabilities, usePipelineExecutorGetDefaultSteps, usePipelineExecutorGetDetectionConfigSchema, usePipelineExecutorGetEffectiveTuning, usePipelineExecutorGetGlobalPipelineConfig, usePipelineExecutorGetGlobalSteps, usePipelineExecutorGetOrchestratorConfigSchema, usePipelineExecutorGetReferenceAudio, usePipelineExecutorGetReferenceAudioFiles, usePipelineExecutorGetReferenceImage, usePipelineExecutorGetSchema, usePipelineExecutorGetSelectedEngine, usePipelineExecutorGetVideoPipelineSteps, usePipelineExecutorInferCached, usePipelineExecutorKillEngine, usePipelineExecutorListLoadedEngines, usePipelineExecutorListReferenceImages, usePipelineExecutorListTemplates, usePipelineExecutorReprobeEngine, usePipelineExecutorRunAudioTest, usePipelineExecutorRunPipeline, usePipelineExecutorRunPipelineBatch, usePipelineExecutorSaveTemplate, usePipelineExecutorSetVideoPipelineSteps, usePipelineExecutorSpinEngine, usePipelineExecutorUncacheFrame, usePipelineExecutorUpdateTemplate, usePipelineOrchestratorApplyDeviceSettingsPatch, usePipelineOrchestratorAssignAudio, usePipelineOrchestratorAssignDecoder, usePipelineOrchestratorAssignPipeline, usePipelineOrchestratorDeleteTemplate, usePipelineOrchestratorGetAgentLoad, usePipelineOrchestratorGetAgentSettings, usePipelineOrchestratorGetAudioAssignment, usePipelineOrchestratorGetAudioAssignments, usePipelineOrchestratorGetAudioNodeLoad, usePipelineOrchestratorGetCameraMetrics, usePipelineOrchestratorGetCameraSettings, usePipelineOrchestratorGetCameraStepOverrides, usePipelineOrchestratorGetCapabilityBindings, usePipelineOrchestratorGetDecoderAssignment, usePipelineOrchestratorGetDecoderAssignments, usePipelineOrchestratorGetDeviceLiveContribution, usePipelineOrchestratorGetDeviceSettingsContribution, usePipelineOrchestratorGetGlobalMetrics, usePipelineOrchestratorGetPipelineAssignment, usePipelineOrchestratorGetPipelineAssignments, usePipelineOrchestratorListAgentSettings, usePipelineOrchestratorListTemplates, usePipelineOrchestratorRebalance, usePipelineOrchestratorRemoveAgentSettings, usePipelineOrchestratorResolvePipeline, usePipelineOrchestratorSaveTemplate, usePipelineOrchestratorSetAgentAddonDefaults, usePipelineOrchestratorSetCameraPipelineForAgent, usePipelineOrchestratorSetCameraStepOverride, usePipelineOrchestratorSetCameraStepToggle, usePipelineOrchestratorSetCapabilityBinding, usePipelineOrchestratorUnassignAudio, usePipelineOrchestratorUnassignDecoder, usePipelineOrchestratorUnassignPipeline, usePipelineOrchestratorUpdateTemplate, usePipelineRunnerAttachCamera, usePipelineRunnerDetachCamera, usePipelineRunnerGetAllCameraMetrics, usePipelineRunnerGetCameraMetrics, usePipelineRunnerGetLocalCameras, usePipelineRunnerGetLocalLoad, usePipelineRunnerGetLocalMetrics, usePipelineRunnerReportMotion, usePlatformProbeGetCapabilities, usePlatformProbeGetHardware, usePlatformProbeGetHardwareEncoders, usePlatformProbeRefreshHardwareEncoders, usePlatformProbeResolveHwAccel, usePlatformProbeResolveInferenceConfig, usePlayerOverlayLayer, usePlayerOverlayLayers, usePlayerToolbarButton, usePlayerToolbarButtons, usePtzAutotrackGetSettings, usePtzAutotrackGetStatus, usePtzAutotrackSetEnabled, usePtzAutotrackSetSettings, usePtzContinuousMove, usePtzDeletePreset, usePtzGetOptions, usePtzGetPosition, usePtzGetPresets, usePtzGetStatus, usePtzGoHome, usePtzGoToPreset, usePtzMove, usePtzSavePreset, usePtzSetAutofocus, usePtzStop, useRebootReboot, useRecordingEngineDisable, useRecordingEngineEnable, useRecordingEngineEstimateGlobalStorage, useRecordingEngineEstimateStorage, useRecordingEngineGetAvailability, useRecordingEngineGetConfig, useRecordingEngineGetMotionStats, useRecordingEngineGetPlaylist, useRecordingEngineGetPolicy, useRecordingEngineGetPolicyStatus, useRecordingEngineGetRetentionConfig, useRecordingEngineGetSegments, useRecordingEngineGetStatus, useRecordingEngineGetStorageUsage, useRecordingEngineGetThumbnail, useRecordingEngineSetPolicy, useRecordingEngineUpdateConfig, useRecordingEngineUpdateRetentionConfig, useRecordingGetPlaybackUrl, useRecordingGetSegments, useRecordingGetThumbnailAt, useRemoteComponent, useSettingsStoreCount, useSettingsStoreDeclareCollection, useSettingsStoreDelete, useSettingsStoreGet, useSettingsStoreInsert, useSettingsStoreIsEmpty, useSettingsStoreQuery, useSettingsStoreSet, useSettingsStoreUpdate, useSnapshotApplyDeviceSettingsPatch, useSnapshotGetDeviceLiveContribution, useSnapshotGetDeviceSettingsContribution, useSnapshotGetSnapshot, useSnapshotGetStatus, useSnapshotInvalidateCache, useSnapshotProviderGetSnapshot, useSnapshotProviderSupportsDevice, useStorageAbortUpload, useStorageBeginDownload, useStorageBeginUpload, useStorageDelete, useStorageDeleteLocation, useStorageEndDownload, useStorageExists, useStorageFinalizeUpload, useStorageGetAvailableSpace, useStorageGetDefaultLocation, useStorageList, useStorageListLocations, useStorageListProviders, useStorageRead, useStorageReadChunk, useStorageResolve, useStorageTestConfig, useStorageTestLocation, useStorageUpsertLocation, useStorageWrite, useStorageWriteChunk, useStreamBrokerApplyDeviceSettingsPatch, useStreamBrokerAssignProfile, useStreamBrokerGetAllRtspEntries, useStreamBrokerGetBrokerStats, useStreamBrokerGetDeviceLiveContribution, useStreamBrokerGetDeviceSettingsContribution, useStreamBrokerGetPreBufferInfo, useStreamBrokerGetRtspEntry, useStreamBrokerGetRtspPort, useStreamBrokerGetStreamUrl, useStreamBrokerGetStreamWithCodec, useStreamBrokerIsRtspEnabled, useStreamBrokerKillClient, useStreamBrokerListAllCameraStreams, useStreamBrokerListAllProfileSlots, useStreamBrokerListClients, useStreamBrokerPublishCameraStream, useStreamBrokerPullAudioChunks, useStreamBrokerPullFrameHandles, useStreamBrokerRegenerateRtspToken, useStreamBrokerReleaseStreamWithCodec, useStreamBrokerRestartProfile, useStreamBrokerRetractCameraStream, useStreamBrokerSetPreBufferDuration, useStreamBrokerSetRtspEnabled, useStreamBrokerSubscribeAudioChunks, useStreamBrokerSubscribeFrames, useStreamBrokerUnassignProfile, useStreamBrokerUnsubscribeAudioChunks, useStreamBrokerUnsubscribeFrames, useStreamParamsGetConfigSchema, useStreamParamsGetOptions, useStreamParamsGetStatus, useStreamParamsSetProfile, useSwitchGetStatus, useSwitchSetState, useSystem, useSystemFeatureFlags, useSystemForceRetentionCleanup, useSystemGetRetentionConfig, useSystemHealth, useSystemInfo, useSystemMutation, useSystemNetworkAddresses, useSystemQuery, useSystemSetRetentionConfig, useThemeMode, useToastOnToast, useTurnProviderGetTurnServers, useUserManagementConfirmTotp, useUserManagementCreateApiKey, useUserManagementCreateScopedToken, useUserManagementCreateUser, useUserManagementDeleteUser, useUserManagementDisableTotp, useUserManagementGetTotpStatus, useUserManagementListApiKeys, useUserManagementListOauthSessions, useUserManagementListScopedTokens, useUserManagementListUsers, useUserManagementOauthExchangeCode, useUserManagementOauthIssueCode, useUserManagementOauthRefresh, useUserManagementOauthVerifyAccessToken, useUserManagementResetPassword, useUserManagementRevokeApiKey, useUserManagementRevokeOauthSession, useUserManagementRevokeScopedToken, useUserManagementSetUserScopes, useUserManagementSetupTotp, useUserManagementUpdateUser, useUserManagementValidateApiKey, useUserManagementValidateCredentials, useUserManagementValidateScopedToken, useUserManagementVerifyTotp, useWebrtcSessionCloseSession, useWebrtcSessionCreateSession, useWebrtcSessionHandleAnswer, useWebrtcSessionHandleOffer, useWebrtcSessionHasAdaptiveBitrate, useWebrtcSessionListStreams, useWidget, useWidgetMetadata, useWidgetRegistry, useZoneAnalyticsGetCameraHistory, useZoneAnalyticsGetCurrentSnapshot, useZoneAnalyticsGetUnzonedHistory, useZoneAnalyticsGetZoneHistory, useZoneEditing, useZoneRulesListRules, useZoneRulesSetRules, useZonesAddZone, useZonesListZones, useZonesRemoveZone, useZonesUpdateZone, validateScopes };
|
|
25859
26810
|
|
|
25860
26811
|
//# sourceMappingURL=index.js.map
|