@camstack/ui-library 1.2.11 → 1.2.13
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/RecordingBandsEditor.d.ts +16 -0
- package/dist/composites/cap-settings/RecordingSettings.d.ts +5 -1
- package/dist/composites/cap-settings/index.d.ts +1 -1
- package/dist/composites/cap-settings/recording-config-form.d.ts +36 -20
- package/dist/generated/system-hooks.d.ts +28 -0
- package/dist/hooks/use-device-webrtc.d.ts +0 -7
- package/dist/index.cjs +418 -443
- package/dist/index.js +406 -445
- package/package.json +1 -1
- package/src/tailwind/camstack-theme.css +15 -0
- package/dist/composites/cap-settings/RecordingRulesEditor.d.ts +0 -7
- package/dist/composites/cap-settings/ScheduleBandsEditor.d.ts +0 -7
package/dist/index.cjs
CHANGED
|
@@ -12644,7 +12644,7 @@ var buttonVariants = cva("inline-flex items-center justify-center rounded-md fon
|
|
|
12644
12644
|
secondary: "bg-surface text-foreground hover:bg-surface-hover",
|
|
12645
12645
|
ghost: "hover:bg-surface-hover text-foreground-muted",
|
|
12646
12646
|
danger: "bg-danger text-white hover:bg-danger/90",
|
|
12647
|
-
outline: "border border-border bg-transparent hover:bg-surface-hover"
|
|
12647
|
+
outline: "border border-border bg-transparent text-foreground hover:bg-surface-hover"
|
|
12648
12648
|
},
|
|
12649
12649
|
size: {
|
|
12650
12650
|
sm: "h-7 px-2.5 text-xs gap-1.5",
|
|
@@ -12949,7 +12949,7 @@ function DialogTrigger({ children, ...props }) {
|
|
|
12949
12949
|
* `w-full` (which would overflow/clip a centered dialog); the per-variant
|
|
12950
12950
|
* `max-w-*` keeps small dialogs small while the cap prevents overflow.
|
|
12951
12951
|
*/
|
|
12952
|
-
var contentVariants = cva("fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 w-[calc(100vw-2rem)] bg-background-elevated border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm max-h-[calc(100dvh-2rem)] overflow-y-auto [&:not([open])]:hidden", {
|
|
12952
|
+
var contentVariants = cva("fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 w-[calc(100vw-2rem)] bg-background-elevated text-foreground border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm max-h-[calc(100dvh-2rem)] overflow-y-auto [&:not([open])]:hidden", {
|
|
12953
12953
|
variants: {
|
|
12954
12954
|
width: {
|
|
12955
12955
|
sm: "max-w-sm",
|
|
@@ -18162,6 +18162,12 @@ var useBackupListArchives = trpc.backup.listArchives.useQuery;
|
|
|
18162
18162
|
var useBackupUpsertDestinationPolicy = trpc.backup.upsertDestinationPolicy.useMutation;
|
|
18163
18163
|
/** Generated alias around `trpc.backup.previewSchedule.useQuery`. */
|
|
18164
18164
|
var useBackupPreviewSchedule = trpc.backup.previewSchedule.useQuery;
|
|
18165
|
+
/** Generated alias around `trpc.backup.listSchedules.useQuery`. */
|
|
18166
|
+
var useBackupListSchedules = trpc.backup.listSchedules.useQuery;
|
|
18167
|
+
/** Generated alias around `trpc.backup.upsertSchedule.useMutation`. */
|
|
18168
|
+
var useBackupUpsertSchedule = trpc.backup.upsertSchedule.useMutation;
|
|
18169
|
+
/** Generated alias around `trpc.backup.deleteSchedule.useMutation`. */
|
|
18170
|
+
var useBackupDeleteSchedule = trpc.backup.deleteSchedule.useMutation;
|
|
18165
18171
|
/** Generated alias around `trpc.battery.wakeForStream.useMutation`. */
|
|
18166
18172
|
var useBatteryWakeForStream = trpc.battery.wakeForStream.useMutation;
|
|
18167
18173
|
/** Generated alias around `trpc.battery.getStatus.useQuery`. */
|
|
@@ -18722,6 +18728,10 @@ var useLocalNetworkList = trpc.localNetwork.list.useQuery;
|
|
|
18722
18728
|
var useLocalNetworkGetPreferred = trpc.localNetwork.getPreferred.useQuery;
|
|
18723
18729
|
/** Generated alias around `trpc.localNetwork.getConnectionEndpoints.useQuery`. */
|
|
18724
18730
|
var useLocalNetworkGetConnectionEndpoints = trpc.localNetwork.getConnectionEndpoints.useQuery;
|
|
18731
|
+
/** Generated alias around `trpc.localNetwork.getNotificationEndpoint.useQuery`. */
|
|
18732
|
+
var useLocalNetworkGetNotificationEndpoint = trpc.localNetwork.getNotificationEndpoint.useQuery;
|
|
18733
|
+
/** Generated alias around `trpc.localNetwork.setNotificationEndpoint.useMutation`. */
|
|
18734
|
+
var useLocalNetworkSetNotificationEndpoint = trpc.localNetwork.setNotificationEndpoint.useMutation;
|
|
18725
18735
|
/** Generated alias around `trpc.localNetwork.getAllowedAddresses.useQuery`. */
|
|
18726
18736
|
var useLocalNetworkGetAllowedAddresses = trpc.localNetwork.getAllowedAddresses.useQuery;
|
|
18727
18737
|
/** Generated alias around `trpc.localNetwork.setAllowedAddresses.useMutation`. */
|
|
@@ -18994,6 +19004,12 @@ var usePipelineAnalyticsGetEventStoreFootprint = trpc.pipelineAnalytics.getEvent
|
|
|
18994
19004
|
var usePipelineAnalyticsPruneEvents = trpc.pipelineAnalytics.pruneEvents.useMutation;
|
|
18995
19005
|
/** Generated alias around `trpc.pipelineAnalytics.deleteDeviceEvents.useMutation`. */
|
|
18996
19006
|
var usePipelineAnalyticsDeleteDeviceEvents = trpc.pipelineAnalytics.deleteDeviceEvents.useMutation;
|
|
19007
|
+
/** Generated alias around `trpc.pipelineAnalytics.relocateMedia.useMutation`. */
|
|
19008
|
+
var usePipelineAnalyticsRelocateMedia = trpc.pipelineAnalytics.relocateMedia.useMutation;
|
|
19009
|
+
/** Generated alias around `trpc.pipelineAnalytics.getMediaRelocateStatus.useQuery`. */
|
|
19010
|
+
var usePipelineAnalyticsGetMediaRelocateStatus = trpc.pipelineAnalytics.getMediaRelocateStatus.useQuery;
|
|
19011
|
+
/** Generated alias around `trpc.pipelineAnalytics.cancelMediaRelocate.useMutation`. */
|
|
19012
|
+
var usePipelineAnalyticsCancelMediaRelocate = trpc.pipelineAnalytics.cancelMediaRelocate.useMutation;
|
|
18997
19013
|
/** Generated alias around `trpc.pipelineAnalytics.listOpsLog.useQuery`. */
|
|
18998
19014
|
var usePipelineAnalyticsListOpsLog = trpc.pipelineAnalytics.listOpsLog.useQuery;
|
|
18999
19015
|
/** Generated alias around `trpc.pipelineAnalytics.getEventMedia.useQuery`. */
|
|
@@ -19290,6 +19306,16 @@ var useRecordingPruneFootage = trpc.recording.pruneFootage.useMutation;
|
|
|
19290
19306
|
var useRecordingDeleteFootprint = trpc.recording.deleteFootprint.useMutation;
|
|
19291
19307
|
/** Generated alias around `trpc.recording.listOpsLog.useQuery`. */
|
|
19292
19308
|
var useRecordingListOpsLog = trpc.recording.listOpsLog.useQuery;
|
|
19309
|
+
/** Generated alias around `trpc.recording.renderGif.useMutation`. */
|
|
19310
|
+
var useRecordingRenderGif = trpc.recording.renderGif.useMutation;
|
|
19311
|
+
/** Generated alias around `trpc.recording.renderClip.useMutation`. */
|
|
19312
|
+
var useRecordingRenderClip = trpc.recording.renderClip.useMutation;
|
|
19313
|
+
/** Generated alias around `trpc.recording.relocateFootage.useMutation`. */
|
|
19314
|
+
var useRecordingRelocateFootage = trpc.recording.relocateFootage.useMutation;
|
|
19315
|
+
/** Generated alias around `trpc.recording.getRelocateStatus.useQuery`. */
|
|
19316
|
+
var useRecordingGetRelocateStatus = trpc.recording.getRelocateStatus.useQuery;
|
|
19317
|
+
/** Generated alias around `trpc.recording.cancelRelocate.useMutation`. */
|
|
19318
|
+
var useRecordingCancelRelocate = trpc.recording.cancelRelocate.useMutation;
|
|
19293
19319
|
/** Generated alias around `trpc.recording.getStatus.useQuery`. */
|
|
19294
19320
|
var useRecordingGetStatus = trpc.recording.getStatus.useQuery;
|
|
19295
19321
|
/** Generated alias around `trpc.recording.getDeviceSettingsContribution.useQuery`. */
|
|
@@ -19430,6 +19456,8 @@ var useStreamBrokerRetractCameraStream = trpc.streamBroker.retractCameraStream.u
|
|
|
19430
19456
|
var useStreamBrokerAssignProfile = trpc.streamBroker.assignProfile.useMutation;
|
|
19431
19457
|
/** Generated alias around `trpc.streamBroker.unassignProfile.useMutation`. */
|
|
19432
19458
|
var useStreamBrokerUnassignProfile = trpc.streamBroker.unassignProfile.useMutation;
|
|
19459
|
+
/** Generated alias around `trpc.streamBroker.renderPreBufferClip.useMutation`. */
|
|
19460
|
+
var useStreamBrokerRenderPreBufferClip = trpc.streamBroker.renderPreBufferClip.useMutation;
|
|
19433
19461
|
/** Generated alias around `trpc.streamBroker.listAllCameraStreams.useQuery`. */
|
|
19434
19462
|
var useStreamBrokerListAllCameraStreams = trpc.streamBroker.listAllCameraStreams.useQuery;
|
|
19435
19463
|
/** Generated alias around `trpc.streamBroker.listAllProfileSlots.useQuery`. */
|
|
@@ -34729,315 +34757,108 @@ function AutotrackSection({ deviceId }) {
|
|
|
34729
34757
|
}
|
|
34730
34758
|
//#endregion
|
|
34731
34759
|
//#region src/composites/cap-settings/recording-config-form.ts
|
|
34732
|
-
|
|
34760
|
+
/** Every weekday — the `days` of a band that covers the whole week. */
|
|
34761
|
+
var ALL_WEEKDAYS = [
|
|
34762
|
+
0,
|
|
34763
|
+
1,
|
|
34764
|
+
2,
|
|
34765
|
+
3,
|
|
34766
|
+
4,
|
|
34767
|
+
5,
|
|
34768
|
+
6
|
|
34769
|
+
];
|
|
34770
|
+
/** The full-day span of an always-on band (`HH:MM`, 23:59 = end of day). */
|
|
34771
|
+
var ALL_DAY_START = "00:00";
|
|
34772
|
+
var ALL_DAY_END = "23:59";
|
|
34773
|
+
/** A band covering every day, all day, in `mode` — what the Base tab authors. */
|
|
34774
|
+
function alwaysBand(mode, rest) {
|
|
34733
34775
|
return {
|
|
34734
|
-
|
|
34735
|
-
|
|
34736
|
-
|
|
34737
|
-
|
|
34738
|
-
|
|
34776
|
+
days: [...ALL_WEEKDAYS],
|
|
34777
|
+
start: ALL_DAY_START,
|
|
34778
|
+
end: ALL_DAY_END,
|
|
34779
|
+
mode,
|
|
34780
|
+
...rest
|
|
34739
34781
|
};
|
|
34740
34782
|
}
|
|
34741
|
-
|
|
34742
|
-
|
|
34783
|
+
/** A fresh row for the Advanced band table: every day, office hours, continuous. */
|
|
34784
|
+
function emptyBand() {
|
|
34785
|
+
return {
|
|
34786
|
+
days: [...ALL_WEEKDAYS],
|
|
34787
|
+
start: "09:00",
|
|
34788
|
+
end: "18:00",
|
|
34789
|
+
mode: "continuous"
|
|
34790
|
+
};
|
|
34743
34791
|
}
|
|
34744
|
-
function
|
|
34745
|
-
return
|
|
34746
|
-
...
|
|
34792
|
+
function withBandAt(bands, index, patch) {
|
|
34793
|
+
return bands.map((band, i) => i === index ? {
|
|
34794
|
+
...band,
|
|
34747
34795
|
...patch
|
|
34748
|
-
} :
|
|
34796
|
+
} : band);
|
|
34797
|
+
}
|
|
34798
|
+
function removeBandAt(bands, index) {
|
|
34799
|
+
return bands.filter((_, i) => i !== index);
|
|
34749
34800
|
}
|
|
34750
|
-
|
|
34751
|
-
|
|
34801
|
+
/** Does this band cover the whole week, all day? (the Base-tab shape) */
|
|
34802
|
+
function isAlwaysBand(band) {
|
|
34803
|
+
return (band.days.length === 0 || ALL_WEEKDAYS.every((d) => band.days.includes(d))) && band.start === "00:00" && band.end === "23:59";
|
|
34752
34804
|
}
|
|
34753
|
-
/**
|
|
34754
|
-
*
|
|
34755
|
-
|
|
34756
|
-
|
|
34757
|
-
|
|
34758
|
-
|
|
34805
|
+
/**
|
|
34806
|
+
* A config is "advanced" when its bands cannot be expressed by the Base tab —
|
|
34807
|
+
* i.e. more than one band, or a single band restricted in time or weekday. Such
|
|
34808
|
+
* a config opens directly in the Advanced band table so nothing is silently
|
|
34809
|
+
* flattened by the simpler form.
|
|
34810
|
+
*/
|
|
34811
|
+
function isAdvancedConfig(cfg) {
|
|
34812
|
+
if (cfg.bands.length > 1) return true;
|
|
34813
|
+
const [only] = cfg.bands;
|
|
34814
|
+
return only !== void 0 && !isAlwaysBand(only);
|
|
34759
34815
|
}
|
|
34760
|
-
/**
|
|
34761
|
-
*
|
|
34816
|
+
/**
|
|
34817
|
+
* Read a persisted config into the Basic form state. `mode` is DERIVED from the
|
|
34818
|
+
* bands by the same function the recorder stamps `config.mode` with; the
|
|
34819
|
+
* `events` parameters come from the first events band, so re-opening the form
|
|
34820
|
+
* shows what is actually recorded.
|
|
34821
|
+
*/
|
|
34762
34822
|
function formStateFromConfig(cfg) {
|
|
34823
|
+
const eventsBand = cfg.bands.find((band) => band.mode === "events");
|
|
34763
34824
|
return {
|
|
34764
|
-
mode:
|
|
34825
|
+
mode: (0, _camstack_types.deriveRecordingMode)(cfg),
|
|
34765
34826
|
profiles: cfg.profiles,
|
|
34766
34827
|
segmentSeconds: cfg.segmentSeconds,
|
|
34767
|
-
|
|
34768
|
-
|
|
34769
|
-
|
|
34770
|
-
postBufferSec: cfg.postBufferSec,
|
|
34828
|
+
triggers: eventsBand?.triggers ?? {},
|
|
34829
|
+
preBufferSec: eventsBand?.preBufferSec,
|
|
34830
|
+
postBufferSec: eventsBand?.postBufferSec,
|
|
34771
34831
|
retention: cfg.retention,
|
|
34772
34832
|
scrubThumbnails: cfg.scrubThumbnails
|
|
34773
34833
|
};
|
|
34774
34834
|
}
|
|
34775
|
-
/**
|
|
34776
|
-
*
|
|
34777
|
-
*
|
|
34835
|
+
/**
|
|
34836
|
+
* Serialize the Basic form state to the wire config: one always-on band of the
|
|
34837
|
+
* chosen mode, none when `off`. `enabled` follows the mode; `mode` itself is
|
|
34838
|
+
* NOT sent — the recorder stamps it as the derived summary of the bands.
|
|
34839
|
+
*/
|
|
34778
34840
|
function configFromFormState(state) {
|
|
34779
34841
|
return {
|
|
34780
34842
|
enabled: state.mode !== "off",
|
|
34781
|
-
|
|
34843
|
+
bands: bandsFromFormState(state),
|
|
34782
34844
|
profiles: state.profiles ? [...state.profiles] : void 0,
|
|
34783
34845
|
segmentSeconds: state.segmentSeconds,
|
|
34784
|
-
schedules: state.schedules.length > 0 ? [...state.schedules] : void 0,
|
|
34785
|
-
triggers: Object.keys(state.triggers).length > 0 ? state.triggers : void 0,
|
|
34786
|
-
preBufferSec: state.preBufferSec,
|
|
34787
|
-
postBufferSec: state.postBufferSec,
|
|
34788
34846
|
retention: state.retention,
|
|
34789
34847
|
scrubThumbnails: state.scrubThumbnails
|
|
34790
34848
|
};
|
|
34791
34849
|
}
|
|
34792
|
-
/**
|
|
34793
|
-
|
|
34794
|
-
|
|
34795
|
-
|
|
34796
|
-
|
|
34797
|
-
|
|
34798
|
-
|
|
34799
|
-
}
|
|
34800
|
-
|
|
34801
|
-
//#region src/composites/cap-settings/RecordingRulesEditor.tsx
|
|
34802
|
-
var DAYS = [
|
|
34803
|
-
{
|
|
34804
|
-
d: 0,
|
|
34805
|
-
label: "S"
|
|
34806
|
-
},
|
|
34807
|
-
{
|
|
34808
|
-
d: 1,
|
|
34809
|
-
label: "M"
|
|
34810
|
-
},
|
|
34811
|
-
{
|
|
34812
|
-
d: 2,
|
|
34813
|
-
label: "T"
|
|
34814
|
-
},
|
|
34815
|
-
{
|
|
34816
|
-
d: 3,
|
|
34817
|
-
label: "W"
|
|
34818
|
-
},
|
|
34819
|
-
{
|
|
34820
|
-
d: 4,
|
|
34821
|
-
label: "T"
|
|
34822
|
-
},
|
|
34823
|
-
{
|
|
34824
|
-
d: 5,
|
|
34825
|
-
label: "F"
|
|
34826
|
-
},
|
|
34827
|
-
{
|
|
34828
|
-
d: 6,
|
|
34829
|
-
label: "S"
|
|
34830
|
-
}
|
|
34831
|
-
];
|
|
34832
|
-
/** Converts a select string value to RecordingMode without a cast. */
|
|
34833
|
-
function toMode(v) {
|
|
34834
|
-
return v === "onMotion" || v === "onAudioThreshold" ? v : "continuous";
|
|
34835
|
-
}
|
|
34836
|
-
/**
|
|
34837
|
-
* The rule patch to apply when the mode changes, normalising the fields that
|
|
34838
|
-
* only apply to certain modes so a saved rule never carries stale values:
|
|
34839
|
-
* `continuous` drops the pre/post buffers + threshold; trigger modes drop the
|
|
34840
|
-
* threshold unless it's `onAudioThreshold`, which is seeded to -30 dBFS when
|
|
34841
|
-
* unset so the displayed default is the value actually persisted.
|
|
34842
|
-
*/
|
|
34843
|
-
function modePatch(rule, mode) {
|
|
34844
|
-
if (mode === "onAudioThreshold") return {
|
|
34845
|
-
mode,
|
|
34846
|
-
thresholdDbfs: rule.thresholdDbfs ?? -30
|
|
34847
|
-
};
|
|
34848
|
-
if (mode === "onMotion") return {
|
|
34849
|
-
mode,
|
|
34850
|
-
thresholdDbfs: void 0
|
|
34851
|
-
};
|
|
34852
|
-
return {
|
|
34853
|
-
mode,
|
|
34854
|
-
preBufferSec: 0,
|
|
34855
|
-
postBufferSec: 0,
|
|
34856
|
-
thresholdDbfs: void 0
|
|
34857
|
-
};
|
|
34858
|
-
}
|
|
34859
|
-
function TimeOfDayRow({ schedule, onChange }) {
|
|
34860
|
-
const toggleDay = (d) => {
|
|
34861
|
-
const cur = schedule.days ?? [
|
|
34862
|
-
0,
|
|
34863
|
-
1,
|
|
34864
|
-
2,
|
|
34865
|
-
3,
|
|
34866
|
-
4,
|
|
34867
|
-
5,
|
|
34868
|
-
6
|
|
34869
|
-
];
|
|
34870
|
-
const days = cur.includes(d) ? cur.filter((x) => x !== d) : [...cur, d].toSorted((a, b) => a - b);
|
|
34871
|
-
onChange({
|
|
34872
|
-
...schedule,
|
|
34873
|
-
days
|
|
34874
|
-
});
|
|
34875
|
-
};
|
|
34876
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34877
|
-
className: "flex flex-wrap items-center gap-2 text-xs",
|
|
34878
|
-
children: [
|
|
34879
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34880
|
-
type: "time",
|
|
34881
|
-
value: schedule.start,
|
|
34882
|
-
onChange: (e) => onChange({
|
|
34883
|
-
...schedule,
|
|
34884
|
-
start: e.target.value
|
|
34885
|
-
}),
|
|
34886
|
-
className: "rounded-md border border-border bg-background px-2 py-1 text-xs"
|
|
34887
|
-
}),
|
|
34888
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "–" }),
|
|
34889
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34890
|
-
type: "time",
|
|
34891
|
-
value: schedule.end,
|
|
34892
|
-
onChange: (e) => onChange({
|
|
34893
|
-
...schedule,
|
|
34894
|
-
end: e.target.value
|
|
34895
|
-
}),
|
|
34896
|
-
className: "rounded-md border border-border bg-background px-2 py-1 text-xs"
|
|
34897
|
-
}),
|
|
34898
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
34899
|
-
className: "flex gap-1",
|
|
34900
|
-
children: DAYS.map(({ d, label }) => {
|
|
34901
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34902
|
-
type: "button",
|
|
34903
|
-
onClick: () => toggleDay(d),
|
|
34904
|
-
className: `h-6 w-6 rounded text-[10px] ${!schedule.days || schedule.days.includes(d) ? "bg-primary text-primary-foreground" : "bg-surface-hover text-foreground-subtle"}`,
|
|
34905
|
-
children: label
|
|
34906
|
-
}, d);
|
|
34907
|
-
})
|
|
34908
|
-
})
|
|
34909
|
-
]
|
|
34910
|
-
});
|
|
34911
|
-
}
|
|
34912
|
-
function RecordingRulesEditor({ rules, onChange }) {
|
|
34913
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34914
|
-
className: "space-y-2",
|
|
34915
|
-
children: [
|
|
34916
|
-
rules.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
34917
|
-
className: "text-xs text-foreground-subtle",
|
|
34918
|
-
children: "No rules — recording is continuous (always) when enabled."
|
|
34919
|
-
}) : null,
|
|
34920
|
-
rules.map((rule, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34921
|
-
className: "space-y-2 rounded-lg border border-border bg-surface p-3",
|
|
34922
|
-
children: [
|
|
34923
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34924
|
-
className: "flex items-center justify-between gap-2",
|
|
34925
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
34926
|
-
className: "rounded-md border border-border bg-background px-2 py-1 text-xs",
|
|
34927
|
-
value: rule.schedule.kind,
|
|
34928
|
-
onChange: (e) => {
|
|
34929
|
-
onChange(withRuleAt(rules, i, { schedule: e.target.value === "timeOfDay" ? {
|
|
34930
|
-
kind: "timeOfDay",
|
|
34931
|
-
start: "00:00",
|
|
34932
|
-
end: "23:59"
|
|
34933
|
-
} : { kind: "always" } }));
|
|
34934
|
-
},
|
|
34935
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
34936
|
-
value: "always",
|
|
34937
|
-
children: "Always"
|
|
34938
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
34939
|
-
value: "timeOfDay",
|
|
34940
|
-
children: "Time of day"
|
|
34941
|
-
})]
|
|
34942
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34943
|
-
type: "button",
|
|
34944
|
-
onClick: () => onChange(removeRuleAt(rules, i)),
|
|
34945
|
-
className: "text-foreground-subtle hover:text-red-500",
|
|
34946
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-4 w-4" })
|
|
34947
|
-
})]
|
|
34948
|
-
}),
|
|
34949
|
-
rule.schedule.kind === "timeOfDay" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeOfDayRow, {
|
|
34950
|
-
schedule: rule.schedule,
|
|
34951
|
-
onChange: (schedule) => onChange(withRuleAt(rules, i, { schedule }))
|
|
34952
|
-
}) : null,
|
|
34953
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34954
|
-
className: "flex flex-wrap items-center gap-2 text-xs",
|
|
34955
|
-
children: [
|
|
34956
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
34957
|
-
className: "rounded-md border border-border bg-background px-2 py-1 text-xs",
|
|
34958
|
-
value: rule.mode,
|
|
34959
|
-
onChange: (e) => onChange(withRuleAt(rules, i, modePatch(rule, toMode(e.target.value)))),
|
|
34960
|
-
children: [
|
|
34961
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
34962
|
-
value: "continuous",
|
|
34963
|
-
children: "Continuous"
|
|
34964
|
-
}),
|
|
34965
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
34966
|
-
value: "onMotion",
|
|
34967
|
-
children: "On motion"
|
|
34968
|
-
}),
|
|
34969
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
34970
|
-
value: "onAudioThreshold",
|
|
34971
|
-
children: "On audio"
|
|
34972
|
-
})
|
|
34973
|
-
]
|
|
34974
|
-
}),
|
|
34975
|
-
isTriggerMode(rule.mode) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
34976
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
34977
|
-
className: "flex items-center gap-1",
|
|
34978
|
-
children: [
|
|
34979
|
-
"pre",
|
|
34980
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34981
|
-
type: "number",
|
|
34982
|
-
min: 0,
|
|
34983
|
-
value: rule.preBufferSec,
|
|
34984
|
-
onChange: (e) => onChange(withRuleAt(rules, i, { preBufferSec: Number(e.target.value) })),
|
|
34985
|
-
className: "w-14 rounded-md border border-border bg-background px-1 py-1 text-xs"
|
|
34986
|
-
}),
|
|
34987
|
-
"s"
|
|
34988
|
-
]
|
|
34989
|
-
}),
|
|
34990
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
34991
|
-
className: "flex items-center gap-1",
|
|
34992
|
-
children: [
|
|
34993
|
-
"post",
|
|
34994
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34995
|
-
type: "number",
|
|
34996
|
-
min: 0,
|
|
34997
|
-
value: rule.postBufferSec,
|
|
34998
|
-
onChange: (e) => onChange(withRuleAt(rules, i, { postBufferSec: Number(e.target.value) })),
|
|
34999
|
-
className: "w-14 rounded-md border border-border bg-background px-1 py-1 text-xs"
|
|
35000
|
-
}),
|
|
35001
|
-
"s"
|
|
35002
|
-
]
|
|
35003
|
-
}),
|
|
35004
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35005
|
-
className: "flex items-center gap-1",
|
|
35006
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35007
|
-
type: "checkbox",
|
|
35008
|
-
checked: rule.resetTimeoutOnNewEvent,
|
|
35009
|
-
onChange: (e) => onChange(withRuleAt(rules, i, { resetTimeoutOnNewEvent: e.target.checked }))
|
|
35010
|
-
}), "reset"]
|
|
35011
|
-
})
|
|
35012
|
-
] }) : null,
|
|
35013
|
-
rule.mode === "onAudioThreshold" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35014
|
-
className: "flex items-center gap-1",
|
|
35015
|
-
children: [
|
|
35016
|
-
"thr",
|
|
35017
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35018
|
-
type: "number",
|
|
35019
|
-
value: rule.thresholdDbfs ?? -30,
|
|
35020
|
-
onChange: (e) => onChange(withRuleAt(rules, i, { thresholdDbfs: Number(e.target.value) })),
|
|
35021
|
-
className: "w-16 rounded-md border border-border bg-background px-1 py-1 text-xs"
|
|
35022
|
-
}),
|
|
35023
|
-
"dBFS"
|
|
35024
|
-
]
|
|
35025
|
-
}) : null
|
|
35026
|
-
]
|
|
35027
|
-
})
|
|
35028
|
-
]
|
|
35029
|
-
}, i)),
|
|
35030
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
35031
|
-
type: "button",
|
|
35032
|
-
onClick: () => onChange([...rules, emptyRule()]),
|
|
35033
|
-
className: "flex items-center gap-1 rounded-md border border-dashed border-border px-3 py-1.5 text-xs text-foreground-subtle hover:border-primary hover:text-foreground",
|
|
35034
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3.5 w-3.5" }), " Add rule"]
|
|
35035
|
-
})
|
|
35036
|
-
]
|
|
35037
|
-
});
|
|
34850
|
+
/** The Base tab's bands: one always-on band of the chosen mode (none = off). */
|
|
34851
|
+
function bandsFromFormState(state) {
|
|
34852
|
+
if (state.mode === "off") return [];
|
|
34853
|
+
if (state.mode === "continuous") return [alwaysBand("continuous")];
|
|
34854
|
+
return [alwaysBand("events", {
|
|
34855
|
+
...Object.keys(state.triggers).length > 0 ? { triggers: state.triggers } : {},
|
|
34856
|
+
...state.preBufferSec !== void 0 ? { preBufferSec: state.preBufferSec } : {},
|
|
34857
|
+
...state.postBufferSec !== void 0 ? { postBufferSec: state.postBufferSec } : {}
|
|
34858
|
+
})];
|
|
35038
34859
|
}
|
|
35039
34860
|
//#endregion
|
|
35040
|
-
//#region src/composites/cap-settings/
|
|
34861
|
+
//#region src/composites/cap-settings/RecordingBandsEditor.tsx
|
|
35041
34862
|
/** Weekday chips in display order (Mon→Sun); value is `Date.getDay()` (0=Sun). */
|
|
35042
34863
|
var WEEKDAYS = [
|
|
35043
34864
|
{
|
|
@@ -35069,115 +34890,177 @@ var WEEKDAYS = [
|
|
|
35069
34890
|
label: "Sun"
|
|
35070
34891
|
}
|
|
35071
34892
|
];
|
|
35072
|
-
|
|
35073
|
-
|
|
35074
|
-
|
|
35075
|
-
|
|
35076
|
-
|
|
35077
|
-
|
|
35078
|
-
}
|
|
35079
|
-
|
|
35080
|
-
|
|
35081
|
-
|
|
35082
|
-
|
|
34893
|
+
var BAND_MODES = [{
|
|
34894
|
+
value: "continuous",
|
|
34895
|
+
label: "Continuous"
|
|
34896
|
+
}, {
|
|
34897
|
+
value: "events",
|
|
34898
|
+
label: "Events"
|
|
34899
|
+
}];
|
|
34900
|
+
/**
|
|
34901
|
+
* The band table — the ONLY authoring surface for recording intent since the
|
|
34902
|
+
* legacy mode/schedules/triggers/rules model was retired (2026-07-30).
|
|
34903
|
+
*
|
|
34904
|
+
* Each row is one `RecordingBand`: when it applies (weekdays + `HH:MM` span,
|
|
34905
|
+
* `end <= start` wraps past midnight) and how it records (continuous, or events
|
|
34906
|
+
* with its own triggers and pre/post buffers). No band covering *now* = nothing
|
|
34907
|
+
* is recorded then; "off" is the absence of a band, never a band value.
|
|
34908
|
+
*/
|
|
34909
|
+
function RecordingBandsEditor({ bands, onChange }) {
|
|
35083
34910
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35084
34911
|
className: "space-y-2",
|
|
35085
|
-
children: [
|
|
34912
|
+
children: [bands.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
35086
34913
|
className: "text-[11px] text-foreground-subtle",
|
|
35087
|
-
children: "
|
|
35088
|
-
}) :
|
|
34914
|
+
children: "No bands — nothing is recorded. Add one to start."
|
|
34915
|
+
}) : bands.map((band, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BandRow, {
|
|
35089
34916
|
band,
|
|
35090
|
-
onChange: (
|
|
35091
|
-
onRemove: () =>
|
|
34917
|
+
onChange: (patch) => onChange(withBandAt(bands, i, patch)),
|
|
34918
|
+
onRemove: () => onChange(removeBandAt(bands, i))
|
|
35092
34919
|
}, i)), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
35093
34920
|
type: "button",
|
|
35094
|
-
onClick:
|
|
34921
|
+
onClick: () => onChange([...bands, emptyBand()]),
|
|
35095
34922
|
className: "flex items-center gap-1 rounded-md bg-surface-hover px-2 py-1 text-[11px] text-foreground-subtle hover:text-foreground",
|
|
35096
34923
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3 w-3" }), " Add band"]
|
|
35097
34924
|
})]
|
|
35098
34925
|
});
|
|
35099
34926
|
}
|
|
34927
|
+
function numOrUndef(value) {
|
|
34928
|
+
return value === "" ? void 0 : Number(value);
|
|
34929
|
+
}
|
|
35100
34930
|
function BandRow({ band, onChange, onRemove }) {
|
|
35101
|
-
if (band.kind === "always") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35102
|
-
className: "flex items-center gap-2 text-xs",
|
|
35103
|
-
children: [
|
|
35104
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35105
|
-
className: "flex-1",
|
|
35106
|
-
children: "Always"
|
|
35107
|
-
}),
|
|
35108
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35109
|
-
type: "button",
|
|
35110
|
-
onClick: () => onChange(newBand()),
|
|
35111
|
-
className: "text-foreground-subtle hover:text-foreground underline",
|
|
35112
|
-
children: "Set band"
|
|
35113
|
-
}),
|
|
35114
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35115
|
-
type: "button",
|
|
35116
|
-
onClick: onRemove,
|
|
35117
|
-
"aria-label": "Remove band",
|
|
35118
|
-
className: "text-foreground-subtle hover:text-red-500",
|
|
35119
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Trash2, { className: "h-3.5 w-3.5" })
|
|
35120
|
-
})
|
|
35121
|
-
]
|
|
35122
|
-
});
|
|
35123
|
-
const days = band.days ?? [];
|
|
35124
34931
|
const toggleDay = (value) => {
|
|
35125
|
-
const
|
|
35126
|
-
onChange({
|
|
35127
|
-
...band,
|
|
35128
|
-
days: next.length > 0 ? next : void 0
|
|
35129
|
-
});
|
|
34932
|
+
const current = band.days.length === 0 ? WEEKDAYS.map((d) => d.value) : band.days;
|
|
34933
|
+
onChange({ days: current.includes(value) ? current.filter((d) => d !== value) : [...current, value] });
|
|
35130
34934
|
};
|
|
34935
|
+
const setTriggers = (patch) => onChange({ triggers: {
|
|
34936
|
+
...band.triggers,
|
|
34937
|
+
...patch
|
|
34938
|
+
} });
|
|
35131
34939
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35132
34940
|
className: "space-y-1.5 rounded-md border border-border p-2",
|
|
35133
|
-
children: [
|
|
35134
|
-
|
|
35135
|
-
|
|
35136
|
-
|
|
35137
|
-
|
|
35138
|
-
|
|
35139
|
-
|
|
35140
|
-
|
|
35141
|
-
start: e.target.value
|
|
34941
|
+
children: [
|
|
34942
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34943
|
+
className: "flex flex-wrap items-center gap-2 text-xs",
|
|
34944
|
+
children: [
|
|
34945
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34946
|
+
type: "time",
|
|
34947
|
+
"aria-label": "Band start",
|
|
34948
|
+
value: band.start,
|
|
34949
|
+
onChange: (e) => onChange({ start: e.target.value }),
|
|
34950
|
+
className: "rounded-md border border-border bg-background px-1 py-0.5"
|
|
35142
34951
|
}),
|
|
35143
|
-
|
|
35144
|
-
|
|
35145
|
-
|
|
35146
|
-
className: "text-foreground-subtle",
|
|
35147
|
-
children: "→"
|
|
35148
|
-
}),
|
|
35149
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35150
|
-
type: "time",
|
|
35151
|
-
value: band.end,
|
|
35152
|
-
onChange: (e) => onChange({
|
|
35153
|
-
...band,
|
|
35154
|
-
end: e.target.value
|
|
34952
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34953
|
+
className: "text-foreground-subtle",
|
|
34954
|
+
children: "→"
|
|
35155
34955
|
}),
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
|
|
35163
|
-
|
|
35164
|
-
|
|
35165
|
-
|
|
35166
|
-
|
|
35167
|
-
|
|
35168
|
-
|
|
35169
|
-
|
|
35170
|
-
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
|
|
34956
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
34957
|
+
type: "time",
|
|
34958
|
+
"aria-label": "Band end",
|
|
34959
|
+
value: band.end,
|
|
34960
|
+
onChange: (e) => onChange({ end: e.target.value }),
|
|
34961
|
+
className: "rounded-md border border-border bg-background px-1 py-0.5"
|
|
34962
|
+
}),
|
|
34963
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34964
|
+
className: "flex gap-1",
|
|
34965
|
+
children: BAND_MODES.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34966
|
+
type: "button",
|
|
34967
|
+
onClick: () => onChange({ mode: m.value }),
|
|
34968
|
+
className: `rounded px-2 py-0.5 ${band.mode === m.value ? "bg-primary text-primary-foreground" : "bg-surface-hover text-foreground-subtle"}`,
|
|
34969
|
+
children: m.label
|
|
34970
|
+
}, m.value))
|
|
34971
|
+
}),
|
|
34972
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "flex-1" }),
|
|
34973
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34974
|
+
type: "button",
|
|
34975
|
+
onClick: onRemove,
|
|
34976
|
+
"aria-label": "Remove band",
|
|
34977
|
+
className: "text-foreground-subtle hover:text-red-500",
|
|
34978
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Trash2, { className: "h-3.5 w-3.5" })
|
|
34979
|
+
})
|
|
34980
|
+
]
|
|
34981
|
+
}),
|
|
34982
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34983
|
+
className: "flex flex-wrap gap-1",
|
|
34984
|
+
children: [WEEKDAYS.map((d) => {
|
|
34985
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
34986
|
+
type: "button",
|
|
34987
|
+
onClick: () => toggleDay(d.value),
|
|
34988
|
+
className: `rounded px-1.5 py-0.5 text-[10px] ${band.days.length === 0 || band.days.includes(d.value) ? "bg-primary text-primary-foreground" : "bg-surface-hover text-foreground-subtle"}`,
|
|
34989
|
+
children: d.label
|
|
34990
|
+
}, d.value);
|
|
34991
|
+
}), band.days.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34992
|
+
className: "self-center text-[10px] text-foreground-subtle",
|
|
34993
|
+
children: "every day"
|
|
34994
|
+
}) : null]
|
|
34995
|
+
}),
|
|
34996
|
+
band.mode === "events" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
34997
|
+
className: "space-y-1.5 border-t border-border pt-1.5",
|
|
34998
|
+
children: [
|
|
34999
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35000
|
+
className: "flex items-center gap-2 text-xs",
|
|
35001
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35002
|
+
type: "checkbox",
|
|
35003
|
+
checked: band.triggers?.motion ?? false,
|
|
35004
|
+
onChange: (e) => setTriggers({ motion: e.target.checked ? true : void 0 })
|
|
35005
|
+
}), "Motion"]
|
|
35006
|
+
}),
|
|
35007
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35008
|
+
className: "flex items-center gap-2 text-xs",
|
|
35009
|
+
children: [
|
|
35010
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35011
|
+
type: "checkbox",
|
|
35012
|
+
checked: band.triggers?.audioThresholdDbfs != null,
|
|
35013
|
+
onChange: (e) => setTriggers({ audioThresholdDbfs: e.target.checked ? -30 : void 0 })
|
|
35014
|
+
}),
|
|
35015
|
+
"Audio above threshold",
|
|
35016
|
+
band.triggers?.audioThresholdDbfs != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35017
|
+
type: "number",
|
|
35018
|
+
"aria-label": "Audio threshold dBFS",
|
|
35019
|
+
value: band.triggers.audioThresholdDbfs,
|
|
35020
|
+
onChange: (e) => setTriggers({ audioThresholdDbfs: numOrUndef(e.target.value) }),
|
|
35021
|
+
className: "w-16 rounded-md border border-border bg-background px-1 py-0.5 text-xs"
|
|
35022
|
+
}) : null,
|
|
35023
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35024
|
+
className: "text-foreground-subtle",
|
|
35025
|
+
children: "dBFS"
|
|
35026
|
+
})
|
|
35027
|
+
]
|
|
35028
|
+
}),
|
|
35029
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35030
|
+
className: "flex flex-wrap items-center gap-3 text-xs",
|
|
35031
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35032
|
+
className: "flex items-center gap-1",
|
|
35033
|
+
children: [
|
|
35034
|
+
"Pre-buffer",
|
|
35035
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35036
|
+
type: "number",
|
|
35037
|
+
min: 0,
|
|
35038
|
+
value: band.preBufferSec ?? "",
|
|
35039
|
+
placeholder: "0",
|
|
35040
|
+
onChange: (e) => onChange({ preBufferSec: numOrUndef(e.target.value) }),
|
|
35041
|
+
className: "w-16 rounded-md border border-border bg-background px-1 py-1 text-xs"
|
|
35042
|
+
}),
|
|
35043
|
+
"s"
|
|
35044
|
+
]
|
|
35045
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35046
|
+
className: "flex items-center gap-1",
|
|
35047
|
+
children: [
|
|
35048
|
+
"Post-buffer",
|
|
35049
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35050
|
+
type: "number",
|
|
35051
|
+
min: 0,
|
|
35052
|
+
value: band.postBufferSec ?? "",
|
|
35053
|
+
placeholder: "0",
|
|
35054
|
+
onChange: (e) => onChange({ postBufferSec: numOrUndef(e.target.value) }),
|
|
35055
|
+
className: "w-16 rounded-md border border-border bg-background px-1 py-1 text-xs"
|
|
35056
|
+
}),
|
|
35057
|
+
"s"
|
|
35058
|
+
]
|
|
35059
|
+
})]
|
|
35060
|
+
})
|
|
35061
|
+
]
|
|
35062
|
+
}) : null
|
|
35063
|
+
]
|
|
35181
35064
|
});
|
|
35182
35065
|
}
|
|
35183
35066
|
//#endregion
|
|
@@ -35201,16 +35084,15 @@ var MODES = [
|
|
|
35201
35084
|
label: "Continuous"
|
|
35202
35085
|
}
|
|
35203
35086
|
];
|
|
35204
|
-
function RecordingSettings({ initial, saving, onSave }) {
|
|
35087
|
+
function RecordingSettings({ initial, saving, onSave, onRegenerateStrips, regeneratingStrips }) {
|
|
35205
35088
|
const [tab, setTab] = (0, react$1.useState)(isAdvancedConfig(initial) ? "advanced" : "base");
|
|
35206
35089
|
const [base, setBase] = (0, react$1.useState)(formStateFromConfig(initial));
|
|
35207
|
-
const [
|
|
35208
|
-
const [rules, setRules] = (0, react$1.useState)(initial.rules ? [...initial.rules] : []);
|
|
35090
|
+
const [bands, setBands] = (0, react$1.useState)([...initial.bands]);
|
|
35209
35091
|
const [common, setCommon] = (0, react$1.useState)({
|
|
35210
35092
|
profiles: initial.profiles,
|
|
35211
35093
|
segmentSeconds: initial.segmentSeconds,
|
|
35212
35094
|
retention: initial.retention,
|
|
35213
|
-
|
|
35095
|
+
stripsEnabled: initial.stripsEnabled
|
|
35214
35096
|
});
|
|
35215
35097
|
const ret = common.retention ?? {};
|
|
35216
35098
|
const setRet = (patch) => setCommon({
|
|
@@ -35221,10 +35103,6 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35221
35103
|
}
|
|
35222
35104
|
});
|
|
35223
35105
|
const numOrUndef = (v) => v === "" ? void 0 : Number(v);
|
|
35224
|
-
const parsePreset = (v) => {
|
|
35225
|
-
const parsed = _camstack_types.ScrubThumbnailPresetSchema.safeParse(v);
|
|
35226
|
-
return parsed.success ? parsed.data : _camstack_types.DEFAULT_SCRUB_THUMBNAIL_PRESET;
|
|
35227
|
-
};
|
|
35228
35106
|
const toggleProfile = (p) => {
|
|
35229
35107
|
const cur = common.profiles ?? [...PROFILES];
|
|
35230
35108
|
const next = cur.includes(p) ? cur.filter((x) => x !== p) : [...cur, p];
|
|
@@ -35241,24 +35119,31 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35241
35119
|
}
|
|
35242
35120
|
});
|
|
35243
35121
|
const switchTab = (next) => {
|
|
35244
|
-
if (next === "advanced"
|
|
35122
|
+
if (next === "advanced") setBands(bandsFromFormState(base));
|
|
35123
|
+
else setBase(formStateFromConfig({
|
|
35124
|
+
enabled: bands.length > 0,
|
|
35125
|
+
bands
|
|
35126
|
+
}));
|
|
35245
35127
|
setTab(next);
|
|
35246
35128
|
};
|
|
35247
35129
|
const handleSave = () => {
|
|
35248
|
-
|
|
35249
|
-
...base,
|
|
35250
|
-
profiles: common.profiles,
|
|
35251
|
-
segmentSeconds: common.segmentSeconds,
|
|
35252
|
-
retention: common.retention,
|
|
35253
|
-
scrubThumbnails: common.scrubThumbnails
|
|
35254
|
-
}));
|
|
35255
|
-
else onSave({
|
|
35256
|
-
enabled: advEnabled,
|
|
35257
|
-
rules,
|
|
35130
|
+
const shared = {
|
|
35258
35131
|
profiles: common.profiles ? [...common.profiles] : void 0,
|
|
35259
35132
|
segmentSeconds: common.segmentSeconds,
|
|
35260
35133
|
retention: common.retention,
|
|
35261
|
-
|
|
35134
|
+
...common.stripsEnabled === void 0 ? {} : { stripsEnabled: common.stripsEnabled }
|
|
35135
|
+
};
|
|
35136
|
+
if (tab === "base") onSave({
|
|
35137
|
+
...configFromFormState({
|
|
35138
|
+
...base,
|
|
35139
|
+
...shared
|
|
35140
|
+
}),
|
|
35141
|
+
...shared
|
|
35142
|
+
});
|
|
35143
|
+
else onSave({
|
|
35144
|
+
enabled: bands.length > 0,
|
|
35145
|
+
bands,
|
|
35146
|
+
...shared
|
|
35262
35147
|
});
|
|
35263
35148
|
};
|
|
35264
35149
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -35270,7 +35155,7 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35270
35155
|
type: "button",
|
|
35271
35156
|
onClick: () => switchTab(t),
|
|
35272
35157
|
className: `rounded px-2.5 py-1 ${tab === t ? "bg-primary text-primary-foreground" : "bg-surface-hover text-foreground-subtle"}`,
|
|
35273
|
-
children: t === "base" ? "Base" : "Advanced (
|
|
35158
|
+
children: t === "base" ? "Base" : "Advanced (bands)"
|
|
35274
35159
|
}, t))
|
|
35275
35160
|
}),
|
|
35276
35161
|
tab === "base" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -35291,16 +35176,17 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35291
35176
|
children: m.label
|
|
35292
35177
|
}, m.value))]
|
|
35293
35178
|
}),
|
|
35294
|
-
base.mode !== "off" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
35295
|
-
className: "
|
|
35296
|
-
children:
|
|
35297
|
-
|
|
35298
|
-
|
|
35299
|
-
|
|
35300
|
-
|
|
35301
|
-
|
|
35302
|
-
|
|
35303
|
-
|
|
35179
|
+
base.mode !== "off" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
35180
|
+
className: "text-[11px] text-foreground-subtle",
|
|
35181
|
+
children: [
|
|
35182
|
+
"Records 24/7. Use ",
|
|
35183
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35184
|
+
className: "font-medium",
|
|
35185
|
+
children: "Advanced (bands)"
|
|
35186
|
+
}),
|
|
35187
|
+
" to restrict hours or weekdays, or to mix continuous and event windows."
|
|
35188
|
+
]
|
|
35189
|
+
}) : null,
|
|
35304
35190
|
base.mode === "events" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35305
35191
|
className: "space-y-2",
|
|
35306
35192
|
children: [
|
|
@@ -35378,22 +35264,22 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35378
35264
|
]
|
|
35379
35265
|
}) : null
|
|
35380
35266
|
]
|
|
35381
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.
|
|
35267
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35382
35268
|
className: "space-y-4",
|
|
35383
|
-
children:
|
|
35384
|
-
|
|
35385
|
-
|
|
35386
|
-
|
|
35387
|
-
|
|
35388
|
-
|
|
35389
|
-
|
|
35390
|
-
|
|
35391
|
-
|
|
35392
|
-
|
|
35393
|
-
|
|
35394
|
-
|
|
35395
|
-
|
|
35396
|
-
|
|
35269
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
35270
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35271
|
+
className: "mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-foreground-subtle",
|
|
35272
|
+
children: "Recording bands"
|
|
35273
|
+
}),
|
|
35274
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecordingBandsEditor, {
|
|
35275
|
+
bands,
|
|
35276
|
+
onChange: setBands
|
|
35277
|
+
}),
|
|
35278
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
35279
|
+
className: "mt-1.5 text-[11px] text-foreground-subtle",
|
|
35280
|
+
children: "A band says WHEN to record and HOW. Nothing is recorded outside every band — an empty table means this camera is off."
|
|
35281
|
+
})
|
|
35282
|
+
] })
|
|
35397
35283
|
}),
|
|
35398
35284
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35399
35285
|
className: "flex flex-wrap items-center gap-4 text-xs",
|
|
@@ -35432,27 +35318,30 @@ function RecordingSettings({ initial, saving, onSave }) {
|
|
|
35432
35318
|
}),
|
|
35433
35319
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
35434
35320
|
className: "flex items-center gap-1",
|
|
35435
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
35436
|
-
|
|
35437
|
-
|
|
35438
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
35439
|
-
value: common.scrubThumbnails ?? _camstack_types.DEFAULT_SCRUB_THUMBNAIL_PRESET,
|
|
35321
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
35322
|
+
type: "checkbox",
|
|
35323
|
+
checked: common.stripsEnabled === true,
|
|
35440
35324
|
onChange: (e) => setCommon({
|
|
35441
35325
|
...common,
|
|
35442
|
-
|
|
35443
|
-
})
|
|
35444
|
-
|
|
35445
|
-
|
|
35446
|
-
|
|
35447
|
-
children: _camstack_types.SCRUB_THUMBNAIL_PRESET_LABELS[p]
|
|
35448
|
-
}, p))
|
|
35326
|
+
stripsEnabled: e.target.checked
|
|
35327
|
+
})
|
|
35328
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35329
|
+
className: "text-foreground-subtle",
|
|
35330
|
+
children: "Scrub thumbnail strips"
|
|
35449
35331
|
})]
|
|
35450
|
-
})
|
|
35332
|
+
}),
|
|
35333
|
+
onRegenerateStrips ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35334
|
+
type: "button",
|
|
35335
|
+
onClick: onRegenerateStrips,
|
|
35336
|
+
disabled: regeneratingStrips === true,
|
|
35337
|
+
className: "rounded-md border border-border bg-surface-hover px-2 py-1 text-xs text-foreground-subtle transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
35338
|
+
children: regeneratingStrips === true ? "Regenerating…" : "Regenerate strips"
|
|
35339
|
+
}) : null
|
|
35451
35340
|
]
|
|
35452
35341
|
}),
|
|
35453
35342
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
35454
35343
|
className: "text-[11px] text-foreground-subtle",
|
|
35455
|
-
children: "
|
|
35344
|
+
children: "OPT-IN: save every keyframe of the low recording as a JPEG strip for fluid fast scrubbing. Costs disk (a derived cache, reclaimed with the footage). Regenerate clears and rebuilds the recent days from the segments already on disk."
|
|
35456
35345
|
}),
|
|
35457
35346
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35458
35347
|
className: "mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-foreground-subtle",
|
|
@@ -36701,6 +36590,14 @@ function RecordingPanel({ deviceId }) {
|
|
|
36701
36590
|
const configQuery = useRecordingGetDeviceConfig({ deviceId });
|
|
36702
36591
|
const setConfig = useRecordingSetDeviceConfig();
|
|
36703
36592
|
const rescanStorage = useRecordingRescanStorage();
|
|
36593
|
+
const customAction = useAddonsCustom();
|
|
36594
|
+
const regenerateStrips = (0, react$1.useCallback)(() => {
|
|
36595
|
+
customAction.mutate({
|
|
36596
|
+
addonId: "recorder",
|
|
36597
|
+
action: "regenerateStrips",
|
|
36598
|
+
input: { deviceId }
|
|
36599
|
+
});
|
|
36600
|
+
}, [customAction, deviceId]);
|
|
36704
36601
|
const saveConfig = (config) => {
|
|
36705
36602
|
setConfig.mutate({
|
|
36706
36603
|
deviceId,
|
|
@@ -36742,7 +36639,9 @@ function RecordingPanel({ deviceId }) {
|
|
|
36742
36639
|
children: resolvedConfig ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecordingSettings, {
|
|
36743
36640
|
initial: resolvedConfig,
|
|
36744
36641
|
saving: setConfig.isPending,
|
|
36745
|
-
onSave: saveConfig
|
|
36642
|
+
onSave: saveConfig,
|
|
36643
|
+
onRegenerateStrips: regenerateStrips,
|
|
36644
|
+
regeneratingStrips: customAction.isPending
|
|
36746
36645
|
}, JSON.stringify(resolvedConfig)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
36747
36646
|
className: "px-1 py-2 text-xs text-foreground-subtle",
|
|
36748
36647
|
children: "Loading settings…"
|
|
@@ -40308,6 +40207,21 @@ function DetectionBoxes({ detections, frameWidth, frameHeight }) {
|
|
|
40308
40207
|
}
|
|
40309
40208
|
//#endregion
|
|
40310
40209
|
//#region src/composites/camera-stream-player.tsx
|
|
40210
|
+
/**
|
|
40211
|
+
* Silence (or restore) a live WebRTC stream FOR REAL.
|
|
40212
|
+
*
|
|
40213
|
+
* `video.muted` alone is not enough: iOS WebKit keeps playing a
|
|
40214
|
+
* `MediaStream`'s audio track through a muted element, which is why live audio
|
|
40215
|
+
* kept sounding under recorded playback on the viewer. Gating the audio TRACK
|
|
40216
|
+
* (`track.enabled`) is what actually stops it, so both are set together — and
|
|
40217
|
+
* re-applied at every stream swap, because a freshly-attached stream arrives
|
|
40218
|
+
* with its tracks enabled whatever the element says.
|
|
40219
|
+
*/
|
|
40220
|
+
function applyStreamMute(video, muted) {
|
|
40221
|
+
video.muted = muted;
|
|
40222
|
+
const stream = video.srcObject;
|
|
40223
|
+
if (stream instanceof MediaStream) for (const track of stream.getAudioTracks()) track.enabled = !muted;
|
|
40224
|
+
}
|
|
40311
40225
|
function isTouchDevice() {
|
|
40312
40226
|
if (typeof window === "undefined") return false;
|
|
40313
40227
|
return window.matchMedia("(hover: none) and (pointer: coarse)").matches || typeof navigator !== "undefined" && navigator.maxTouchPoints > 0 || "ontouchstart" in window;
|
|
@@ -40390,6 +40304,11 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40390
40304
|
const [state, setState] = (0, react$1.useState)("idle");
|
|
40391
40305
|
const [errorMessage, setErrorMessage] = (0, react$1.useState)("");
|
|
40392
40306
|
const [isMuted, setIsMuted] = (0, react$1.useState)(initialMuted);
|
|
40307
|
+
/** Read by the async stream-swap closures: a freshly-promoted stream must
|
|
40308
|
+
* arrive already respecting the CURRENT mute, not the one captured at
|
|
40309
|
+
* connect time. */
|
|
40310
|
+
const isMutedRef = (0, react$1.useRef)(isMuted);
|
|
40311
|
+
isMutedRef.current = isMuted;
|
|
40393
40312
|
const [isFullscreen, setIsFullscreen] = (0, react$1.useState)(false);
|
|
40394
40313
|
const [isPipSupported, setIsPipSupported] = (0, react$1.useState)(false);
|
|
40395
40314
|
const isTouch = isTouchDevice();
|
|
@@ -40519,6 +40438,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40519
40438
|
pcRef.current = pc;
|
|
40520
40439
|
if (videoRef.current) {
|
|
40521
40440
|
videoRef.current.srcObject = combinedStream;
|
|
40441
|
+
applyStreamMute(videoRef.current, isMutedRef.current);
|
|
40522
40442
|
videoRef.current.play().catch(() => {});
|
|
40523
40443
|
}
|
|
40524
40444
|
if (oldPc && oldPc !== pc) try {
|
|
@@ -40538,6 +40458,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40538
40458
|
}
|
|
40539
40459
|
if (videoRef.current) {
|
|
40540
40460
|
videoRef.current.srcObject = combinedStream;
|
|
40461
|
+
applyStreamMute(videoRef.current, isMutedRef.current);
|
|
40541
40462
|
videoRef.current.play().catch(() => {});
|
|
40542
40463
|
}
|
|
40543
40464
|
};
|
|
@@ -40624,6 +40545,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40624
40545
|
whepStream.addTrack(event.track);
|
|
40625
40546
|
if (videoRef.current) {
|
|
40626
40547
|
videoRef.current.srcObject = whepStream;
|
|
40548
|
+
applyStreamMute(videoRef.current, isMutedRef.current);
|
|
40627
40549
|
videoRef.current.play().catch(() => {});
|
|
40628
40550
|
}
|
|
40629
40551
|
};
|
|
@@ -40696,6 +40618,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40696
40618
|
pcRef.current = pc;
|
|
40697
40619
|
if (videoRef.current) {
|
|
40698
40620
|
videoRef.current.srcObject = combinedStream;
|
|
40621
|
+
applyStreamMute(videoRef.current, isMutedRef.current);
|
|
40699
40622
|
videoRef.current.play().catch(() => {});
|
|
40700
40623
|
}
|
|
40701
40624
|
if (oldPc && oldPc !== pc) try {
|
|
@@ -40716,6 +40639,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40716
40639
|
}
|
|
40717
40640
|
if (videoRef.current) {
|
|
40718
40641
|
videoRef.current.srcObject = combinedStream;
|
|
40642
|
+
applyStreamMute(videoRef.current, isMutedRef.current);
|
|
40719
40643
|
videoRef.current.play().catch(() => {});
|
|
40720
40644
|
}
|
|
40721
40645
|
};
|
|
@@ -41040,7 +40964,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
41040
40964
|
mountedRef.current = false;
|
|
41041
40965
|
cleanup();
|
|
41042
40966
|
};
|
|
41043
|
-
}, [
|
|
40967
|
+
}, []);
|
|
41044
40968
|
const hasSignaledRef = (0, react$1.useRef)(false);
|
|
41045
40969
|
(0, react$1.useEffect)(() => {
|
|
41046
40970
|
if (!autoPlay) return;
|
|
@@ -41078,6 +41002,23 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
41078
41002
|
}
|
|
41079
41003
|
connect();
|
|
41080
41004
|
};
|
|
41005
|
+
const prevServerUrlRef = (0, react$1.useRef)(serverUrl);
|
|
41006
|
+
const prevStreamKeyRef = (0, react$1.useRef)(streamKey);
|
|
41007
|
+
(0, react$1.useEffect)(() => {
|
|
41008
|
+
const serverChanged = prevServerUrlRef.current !== serverUrl;
|
|
41009
|
+
const keyChanged = prevStreamKeyRef.current !== streamKey;
|
|
41010
|
+
prevServerUrlRef.current = serverUrl;
|
|
41011
|
+
prevStreamKeyRef.current = streamKey;
|
|
41012
|
+
if (!serverChanged && !keyChanged) return;
|
|
41013
|
+
if (!mountedRef.current) return;
|
|
41014
|
+
reconnectAttemptsRef.current = 0;
|
|
41015
|
+
if (serverChanged) {
|
|
41016
|
+
cleanup();
|
|
41017
|
+
connect();
|
|
41018
|
+
return;
|
|
41019
|
+
}
|
|
41020
|
+
handleSeamlessReconnect();
|
|
41021
|
+
}, [serverUrl, streamKey]);
|
|
41081
41022
|
const prevReconnectSignalRef = (0, react$1.useRef)(reconnectSignal);
|
|
41082
41023
|
(0, react$1.useEffect)(() => {
|
|
41083
41024
|
if (prevReconnectSignalRef.current === reconnectSignal) return;
|
|
@@ -41087,8 +41028,9 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
41087
41028
|
}, [reconnectSignal]);
|
|
41088
41029
|
const toggleMute = () => {
|
|
41089
41030
|
if (videoRef.current) {
|
|
41090
|
-
|
|
41091
|
-
|
|
41031
|
+
const next = !videoRef.current.muted;
|
|
41032
|
+
applyStreamMute(videoRef.current, next);
|
|
41033
|
+
setIsMuted(next);
|
|
41092
41034
|
}
|
|
41093
41035
|
};
|
|
41094
41036
|
const toggleFullscreen = async () => {
|
|
@@ -41316,7 +41258,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
41316
41258
|
setIsMuted(initialMuted);
|
|
41317
41259
|
const video = videoRef.current;
|
|
41318
41260
|
if (!video) return;
|
|
41319
|
-
video
|
|
41261
|
+
applyStreamMute(video, initialMuted);
|
|
41320
41262
|
if (!initialMuted) video.play().catch(() => {});
|
|
41321
41263
|
}, [initialMuted]);
|
|
41322
41264
|
const showToolbarBriefly = () => {
|
|
@@ -42428,7 +42370,7 @@ function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIce
|
|
|
42428
42370
|
extraOverlay,
|
|
42429
42371
|
ptzOverlayVisible && ptzOverlay
|
|
42430
42372
|
] }) : void 0
|
|
42431
|
-
}
|
|
42373
|
+
})
|
|
42432
42374
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SnapshotPreview, {
|
|
42433
42375
|
src: snapshotSrc ?? null,
|
|
42434
42376
|
isLoading: snapshotLoading,
|
|
@@ -46809,6 +46751,15 @@ function useDeviceDetections(trpc, deviceId) {
|
|
|
46809
46751
|
* @param deviceId - numeric device ID (null = disabled)
|
|
46810
46752
|
* @param pollIntervalMs - how often to refresh profile slots (default: 5000)
|
|
46811
46753
|
*/
|
|
46754
|
+
/** TTL for the TURN/STUN credential cache. Short enough that rotated creds
|
|
46755
|
+
* are picked up promptly, long enough to cover a connect + several profile
|
|
46756
|
+
* switches without re-paying the fetch. */
|
|
46757
|
+
var TURN_CACHE_TTL_MS = 5 * 6e4;
|
|
46758
|
+
/** Keyed on the caller's trpc client object (stable per connected system —
|
|
46759
|
+
* NOT on `trpc.turnProvider`, which is a proxy minted fresh on every property
|
|
46760
|
+
* access), so two admin tabs pointed at different hubs never cross-serve
|
|
46761
|
+
* credentials. An unstable key degrades to a cache miss, never to wrong creds. */
|
|
46762
|
+
var turnServersCache = /* @__PURE__ */ new WeakMap();
|
|
46812
46763
|
function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
|
|
46813
46764
|
const [remoteStreams, setRemoteStreams] = (0, react$1.useState)([]);
|
|
46814
46765
|
(0, react$1.useEffect)(() => {
|
|
@@ -46878,18 +46829,28 @@ function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
|
|
|
46878
46829
|
}, [deviceId, remoteStreams]);
|
|
46879
46830
|
const getIceServers = (0, react$1.useCallback)(async () => {
|
|
46880
46831
|
if (!trpc.turnProvider) return void 0;
|
|
46832
|
+
const cached = turnServersCache.get(trpc);
|
|
46833
|
+
if (cached && Date.now() - cached.fetchedAt < TURN_CACHE_TTL_MS) return cached.servers;
|
|
46881
46834
|
try {
|
|
46882
|
-
|
|
46835
|
+
const mapped = (await trpc.turnProvider.getTurnServers.query()).map((s) => {
|
|
46883
46836
|
return {
|
|
46884
46837
|
urls: typeof s.urls === "string" ? s.urls : [...s.urls],
|
|
46885
46838
|
...s.username !== void 0 ? { username: s.username } : {},
|
|
46886
46839
|
...s.credential !== void 0 ? { credential: s.credential } : {}
|
|
46887
46840
|
};
|
|
46888
46841
|
});
|
|
46842
|
+
turnServersCache.set(trpc, {
|
|
46843
|
+
servers: mapped,
|
|
46844
|
+
fetchedAt: Date.now()
|
|
46845
|
+
});
|
|
46846
|
+
return mapped;
|
|
46889
46847
|
} catch {
|
|
46890
|
-
return;
|
|
46848
|
+
return cached?.servers;
|
|
46891
46849
|
}
|
|
46892
46850
|
}, [trpc]);
|
|
46851
|
+
(0, react$1.useEffect)(() => {
|
|
46852
|
+
getIceServers();
|
|
46853
|
+
}, [getIceServers]);
|
|
46893
46854
|
const createSession = (0, react$1.useCallback)(async (target, hints) => {
|
|
46894
46855
|
if (deviceId === null) throw new Error("No device selected");
|
|
46895
46856
|
const res = await trpc.webrtcSession.createSession.mutate({
|
|
@@ -47666,15 +47627,18 @@ exports.useAutomationControlEnable = useAutomationControlEnable;
|
|
|
47666
47627
|
exports.useAutomationControlGetStatus = useAutomationControlGetStatus;
|
|
47667
47628
|
exports.useAutomationControlTrigger = useAutomationControlTrigger;
|
|
47668
47629
|
exports.useBackupDelete = useBackupDelete;
|
|
47630
|
+
exports.useBackupDeleteSchedule = useBackupDeleteSchedule;
|
|
47669
47631
|
exports.useBackupGetEntries = useBackupGetEntries;
|
|
47670
47632
|
exports.useBackupList = useBackupList;
|
|
47671
47633
|
exports.useBackupListArchives = useBackupListArchives;
|
|
47672
47634
|
exports.useBackupListDestinations = useBackupListDestinations;
|
|
47673
47635
|
exports.useBackupListLocations = useBackupListLocations;
|
|
47636
|
+
exports.useBackupListSchedules = useBackupListSchedules;
|
|
47674
47637
|
exports.useBackupPreviewSchedule = useBackupPreviewSchedule;
|
|
47675
47638
|
exports.useBackupRestore = useBackupRestore;
|
|
47676
47639
|
exports.useBackupTrigger = useBackupTrigger;
|
|
47677
47640
|
exports.useBackupUpsertDestinationPolicy = useBackupUpsertDestinationPolicy;
|
|
47641
|
+
exports.useBackupUpsertSchedule = useBackupUpsertSchedule;
|
|
47678
47642
|
exports.useBatteryGetStatus = useBatteryGetStatus;
|
|
47679
47643
|
exports.useBatteryWakeForStream = useBatteryWakeForStream;
|
|
47680
47644
|
exports.useBinaryGetStatus = useBinaryGetStatus;
|
|
@@ -47984,10 +47948,12 @@ exports.useLlmTestProfile = useLlmTestProfile;
|
|
|
47984
47948
|
exports.useLlmUpsertProfile = useLlmUpsertProfile;
|
|
47985
47949
|
exports.useLocalNetworkGetAllowedAddresses = useLocalNetworkGetAllowedAddresses;
|
|
47986
47950
|
exports.useLocalNetworkGetConnectionEndpoints = useLocalNetworkGetConnectionEndpoints;
|
|
47951
|
+
exports.useLocalNetworkGetNotificationEndpoint = useLocalNetworkGetNotificationEndpoint;
|
|
47987
47952
|
exports.useLocalNetworkGetPreferred = useLocalNetworkGetPreferred;
|
|
47988
47953
|
exports.useLocalNetworkList = useLocalNetworkList;
|
|
47989
47954
|
exports.useLocalNetworkResetAllowlistToBestMatch = useLocalNetworkResetAllowlistToBestMatch;
|
|
47990
47955
|
exports.useLocalNetworkSetAllowedAddresses = useLocalNetworkSetAllowedAddresses;
|
|
47956
|
+
exports.useLocalNetworkSetNotificationEndpoint = useLocalNetworkSetNotificationEndpoint;
|
|
47991
47957
|
exports.useLockControlGetStatus = useLockControlGetStatus;
|
|
47992
47958
|
exports.useLockControlLock = useLockControlLock;
|
|
47993
47959
|
exports.useLockControlOpen = useLockControlOpen;
|
|
@@ -48107,6 +48073,7 @@ exports.usePetFeederSetFeedSound = usePetFeederSetFeedSound;
|
|
|
48107
48073
|
exports.usePetFeederSetIndicatorLight = usePetFeederSetIndicatorLight;
|
|
48108
48074
|
exports.usePetFeederSetVolume = usePetFeederSetVolume;
|
|
48109
48075
|
exports.usePipelineAnalyticsApplyDeviceSettingsPatch = usePipelineAnalyticsApplyDeviceSettingsPatch;
|
|
48076
|
+
exports.usePipelineAnalyticsCancelMediaRelocate = usePipelineAnalyticsCancelMediaRelocate;
|
|
48110
48077
|
exports.usePipelineAnalyticsClearTracks = usePipelineAnalyticsClearTracks;
|
|
48111
48078
|
exports.usePipelineAnalyticsDeleteDeviceEvents = usePipelineAnalyticsDeleteDeviceEvents;
|
|
48112
48079
|
exports.usePipelineAnalyticsDeleteTracks = usePipelineAnalyticsDeleteTracks;
|
|
@@ -48118,6 +48085,7 @@ exports.usePipelineAnalyticsGetEventDensity = usePipelineAnalyticsGetEventDensit
|
|
|
48118
48085
|
exports.usePipelineAnalyticsGetEventMedia = usePipelineAnalyticsGetEventMedia;
|
|
48119
48086
|
exports.usePipelineAnalyticsGetEventStoreFootprint = usePipelineAnalyticsGetEventStoreFootprint;
|
|
48120
48087
|
exports.usePipelineAnalyticsGetKeyEvents = usePipelineAnalyticsGetKeyEvents;
|
|
48088
|
+
exports.usePipelineAnalyticsGetMediaRelocateStatus = usePipelineAnalyticsGetMediaRelocateStatus;
|
|
48121
48089
|
exports.usePipelineAnalyticsGetMotionEvents = usePipelineAnalyticsGetMotionEvents;
|
|
48122
48090
|
exports.usePipelineAnalyticsGetObjectEvents = usePipelineAnalyticsGetObjectEvents;
|
|
48123
48091
|
exports.usePipelineAnalyticsGetSensorEvents = usePipelineAnalyticsGetSensorEvents;
|
|
@@ -48130,6 +48098,7 @@ exports.usePipelineAnalyticsListTracks = usePipelineAnalyticsListTracks;
|
|
|
48130
48098
|
exports.usePipelineAnalyticsPruneEvents = usePipelineAnalyticsPruneEvents;
|
|
48131
48099
|
exports.usePipelineAnalyticsPruneEventsBefore = usePipelineAnalyticsPruneEventsBefore;
|
|
48132
48100
|
exports.usePipelineAnalyticsPruneTracksBefore = usePipelineAnalyticsPruneTracksBefore;
|
|
48101
|
+
exports.usePipelineAnalyticsRelocateMedia = usePipelineAnalyticsRelocateMedia;
|
|
48133
48102
|
exports.usePipelineAnalyticsSearchObjectEvents = usePipelineAnalyticsSearchObjectEvents;
|
|
48134
48103
|
exports.usePipelineAnalyticsWipeAllAnalytics = usePipelineAnalyticsWipeAllAnalytics;
|
|
48135
48104
|
exports.usePipelineExecutorCacheFrameInPool = usePipelineExecutorCacheFrameInPool;
|
|
@@ -48267,6 +48236,7 @@ exports.usePtzStop = usePtzStop;
|
|
|
48267
48236
|
exports.useRebootReboot = useRebootReboot;
|
|
48268
48237
|
exports.useRecordedPlayback = useRecordedPlayback;
|
|
48269
48238
|
exports.useRecordingApplyDeviceSettingsPatch = useRecordingApplyDeviceSettingsPatch;
|
|
48239
|
+
exports.useRecordingCancelRelocate = useRecordingCancelRelocate;
|
|
48270
48240
|
exports.useRecordingDeleteFootprint = useRecordingDeleteFootprint;
|
|
48271
48241
|
exports.useRecordingExportCancelExport = useRecordingExportCancelExport;
|
|
48272
48242
|
exports.useRecordingExportCreateExport = useRecordingExportCreateExport;
|
|
@@ -48280,12 +48250,16 @@ exports.useRecordingGetDeviceConfig = useRecordingGetDeviceConfig;
|
|
|
48280
48250
|
exports.useRecordingGetDeviceLiveContribution = useRecordingGetDeviceLiveContribution;
|
|
48281
48251
|
exports.useRecordingGetDeviceSettingsContribution = useRecordingGetDeviceSettingsContribution;
|
|
48282
48252
|
exports.useRecordingGetPlaybackManifest = useRecordingGetPlaybackManifest;
|
|
48253
|
+
exports.useRecordingGetRelocateStatus = useRecordingGetRelocateStatus;
|
|
48283
48254
|
exports.useRecordingGetStatus = useRecordingGetStatus;
|
|
48284
48255
|
exports.useRecordingGetStorageUsage = useRecordingGetStorageUsage;
|
|
48285
48256
|
exports.useRecordingListOpsLog = useRecordingListOpsLog;
|
|
48286
48257
|
exports.useRecordingLocateSegment = useRecordingLocateSegment;
|
|
48287
48258
|
exports.useRecordingPruneFootage = useRecordingPruneFootage;
|
|
48288
48259
|
exports.useRecordingReadSegmentBytes = useRecordingReadSegmentBytes;
|
|
48260
|
+
exports.useRecordingRelocateFootage = useRecordingRelocateFootage;
|
|
48261
|
+
exports.useRecordingRenderClip = useRecordingRenderClip;
|
|
48262
|
+
exports.useRecordingRenderGif = useRecordingRenderGif;
|
|
48289
48263
|
exports.useRecordingRescanStorage = useRecordingRescanStorage;
|
|
48290
48264
|
exports.useRecordingSetDeviceConfig = useRecordingSetDeviceConfig;
|
|
48291
48265
|
exports.useRemoteComponent = useRemoteComponent;
|
|
@@ -48368,6 +48342,7 @@ exports.useStreamBrokerPullAudioChunks = useStreamBrokerPullAudioChunks;
|
|
|
48368
48342
|
exports.useStreamBrokerPullFrameHandles = useStreamBrokerPullFrameHandles;
|
|
48369
48343
|
exports.useStreamBrokerRegenerateRtspToken = useStreamBrokerRegenerateRtspToken;
|
|
48370
48344
|
exports.useStreamBrokerReleaseStreamWithCodec = useStreamBrokerReleaseStreamWithCodec;
|
|
48345
|
+
exports.useStreamBrokerRenderPreBufferClip = useStreamBrokerRenderPreBufferClip;
|
|
48371
48346
|
exports.useStreamBrokerRestartProfile = useStreamBrokerRestartProfile;
|
|
48372
48347
|
exports.useStreamBrokerRetractCameraStream = useStreamBrokerRetractCameraStream;
|
|
48373
48348
|
exports.useStreamBrokerSetPreBufferDuration = useStreamBrokerSetPreBufferDuration;
|