@camstack/ui-library 1.2.48 → 1.2.50
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/{MotionZonesSettings-Bvavn6s9.js → MotionZonesSettings-DRrdkR4U.js} +1 -1
- package/dist/{PrivacyMaskSettings-CSxDpLOw.js → PrivacyMaskSettings-DsyAfD21.js} +1 -1
- package/dist/SceneMonitorEditor-BCn18zsr.js +411 -0
- package/dist/SceneMonitorEditor-BXwXLLch.cjs +411 -0
- package/dist/composites/cap-settings/SceneMonitorEditor.d.ts +2 -0
- package/dist/composites/cap-settings/index.d.ts +6 -5
- package/dist/composites/cap-settings/scene-monitor-copy.d.ts +37 -0
- package/dist/index.cjs +279 -250
- package/dist/index.js +279 -254
- package/dist/primitives/dialog.d.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
const require_index = require("./index.cjs");
|
|
2
|
+
const require_MaskShapeCanvas = require("./MaskShapeCanvas-BByN3jvt.cjs");
|
|
3
|
+
let react = require("react");
|
|
4
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
/**
|
|
6
|
+
* @license lucide-react v0.576.0 - ISC
|
|
7
|
+
*
|
|
8
|
+
* This source code is licensed under the ISC license.
|
|
9
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
10
|
+
*/
|
|
11
|
+
var ScanEye = require_index.createLucideIcon("scan-eye", [
|
|
12
|
+
["path", {
|
|
13
|
+
d: "M3 7V5a2 2 0 0 1 2-2h2",
|
|
14
|
+
key: "aa7l1z"
|
|
15
|
+
}],
|
|
16
|
+
["path", {
|
|
17
|
+
d: "M17 3h2a2 2 0 0 1 2 2v2",
|
|
18
|
+
key: "4qcy5o"
|
|
19
|
+
}],
|
|
20
|
+
["path", {
|
|
21
|
+
d: "M21 17v2a2 2 0 0 1-2 2h-2",
|
|
22
|
+
key: "6vwrx8"
|
|
23
|
+
}],
|
|
24
|
+
["path", {
|
|
25
|
+
d: "M7 21H5a2 2 0 0 1-2-2v-2",
|
|
26
|
+
key: "ioqczr"
|
|
27
|
+
}],
|
|
28
|
+
["circle", {
|
|
29
|
+
cx: "12",
|
|
30
|
+
cy: "12",
|
|
31
|
+
r: "1",
|
|
32
|
+
key: "41hilf"
|
|
33
|
+
}],
|
|
34
|
+
["path", {
|
|
35
|
+
d: "M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0",
|
|
36
|
+
key: "11ak4c"
|
|
37
|
+
}]
|
|
38
|
+
]);
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/composites/cap-settings/scene-monitor-copy.ts
|
|
41
|
+
/** The lighting variants an operator is asked to capture. The resolver can also
|
|
42
|
+
* report `night`, `dawn` and `dusk`; those are shown when present but never
|
|
43
|
+
* demanded, because most installs only ever see day and IR. */
|
|
44
|
+
var SCENE_CAPTURE_VARIANTS = ["day", "ir"];
|
|
45
|
+
var VARIANT_LABEL = {
|
|
46
|
+
day: "Daylight",
|
|
47
|
+
ir: "Night (IR)",
|
|
48
|
+
night: "Night",
|
|
49
|
+
dawn: "Dawn",
|
|
50
|
+
dusk: "Dusk"
|
|
51
|
+
};
|
|
52
|
+
function variantLabel(condition) {
|
|
53
|
+
return VARIANT_LABEL[condition] ?? condition;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* "day ✓ · ir ✗ — this scene reports UNKNOWN at night."
|
|
57
|
+
*
|
|
58
|
+
* Converts the silent cross-condition fallback this engine used to have into an
|
|
59
|
+
* operator-visible fact. Without it, a scene with only a daylight reference
|
|
60
|
+
* simply stops answering after sunset and nothing anywhere says why.
|
|
61
|
+
*/
|
|
62
|
+
function coverageLine(monitor) {
|
|
63
|
+
const covered = new Set(monitor.coveredConditions);
|
|
64
|
+
const parts = SCENE_CAPTURE_VARIANTS.map((v) => `${variantLabel(v)} ${covered.has(v) ? "✓" : "✗"}`);
|
|
65
|
+
const extra = [...covered].filter((c) => !SCENE_CAPTURE_VARIANTS.some((v) => v === c));
|
|
66
|
+
for (const c of extra) parts.push(`${variantLabel(c)} ✓`);
|
|
67
|
+
const missing = SCENE_CAPTURE_VARIANTS.filter((v) => !covered.has(v));
|
|
68
|
+
if (missing.length === 0) return parts.join(" · ");
|
|
69
|
+
return `${parts.join(" · ")} — reports “can’t tell” in ${missing.map((v) => variantLabel(v).toLowerCase()).join(" and ")}`;
|
|
70
|
+
}
|
|
71
|
+
var UNAVAILABLE_SENTENCE = {
|
|
72
|
+
"no-reference-for-condition": "can’t tell — nothing was captured in this light yet. Use “Capture now” when the camera is in it.",
|
|
73
|
+
"view-shifted": "can’t tell — the camera view has moved since the reference was captured. Re-capture it.",
|
|
74
|
+
"no-vision-profile": "can’t tell — the vision model is unreachable, so the change is on hold.",
|
|
75
|
+
"encoder-model-changed": "can’t tell — the embedding model changed, so the stored references are unreadable. Re-capture them.",
|
|
76
|
+
"no-snapshot": "can’t tell — this camera did not return a frame."
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The one line under a scene's name. It answers, in order: can it judge, what
|
|
80
|
+
* does it say, and how long has it said it.
|
|
81
|
+
*/
|
|
82
|
+
function statusLine(monitor, now) {
|
|
83
|
+
if (monitor.states.length === 0) return "Not armed yet — capture the initial state to start watching.";
|
|
84
|
+
if (monitor.verdict === "unknown") return monitor.unavailable !== null ? UNAVAILABLE_SENTENCE[monitor.unavailable] : "can’t tell right now.";
|
|
85
|
+
const since = monitor.verdict === "diverged" ? monitor.divergedAt : monitor.armedAt;
|
|
86
|
+
const held = since !== null ? ` for ${humanDuration(Math.max(0, now - since))}` : "";
|
|
87
|
+
if (monitor.latched) return monitor.verdict === "diverged" ? `CHANGED${held} — press Reset when you have dealt with it.` : `Back to normal, but still flagged as changed${held}. Press Reset to clear it.`;
|
|
88
|
+
return monitor.verdict === "diverged" ? `CHANGED${held}` : `Normal${held}`;
|
|
89
|
+
}
|
|
90
|
+
/** The badge word. `latched` wins over `verdict`, because the latch is the
|
|
91
|
+
* thing the operator asked to be told and it outlives the live state. */
|
|
92
|
+
function badgeFor(monitor) {
|
|
93
|
+
if (monitor.states.length === 0) return {
|
|
94
|
+
text: "Not armed",
|
|
95
|
+
tone: "unknown"
|
|
96
|
+
};
|
|
97
|
+
if (monitor.latched) return {
|
|
98
|
+
text: "Changed",
|
|
99
|
+
tone: "alarm"
|
|
100
|
+
};
|
|
101
|
+
if (monitor.verdict === "unknown") return {
|
|
102
|
+
text: "Can’t tell",
|
|
103
|
+
tone: "unknown"
|
|
104
|
+
};
|
|
105
|
+
return monitor.verdict === "diverged" ? {
|
|
106
|
+
text: "Changed",
|
|
107
|
+
tone: "alarm"
|
|
108
|
+
} : {
|
|
109
|
+
text: "Normal",
|
|
110
|
+
tone: "ok"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** A rect covering most of the frame is dominated by weather, sky and parked
|
|
114
|
+
* cars: the bin is 2 % of the pixels and CLIP will not notice it leaving. The
|
|
115
|
+
* failure is silent INSENSITIVITY, which is the worst kind — hence a warning
|
|
116
|
+
* rather than a refusal. */
|
|
117
|
+
var ROI_AREA_WARN_FRACTION = .4;
|
|
118
|
+
function roiTooLarge(roi) {
|
|
119
|
+
return roi.width * roi.height > ROI_AREA_WARN_FRACTION;
|
|
120
|
+
}
|
|
121
|
+
function humanDuration(ms) {
|
|
122
|
+
const s = Math.floor(ms / 1e3);
|
|
123
|
+
if (s < 60) return `${s}s`;
|
|
124
|
+
const m = Math.floor(s / 60);
|
|
125
|
+
if (m < 60) return `${m}m`;
|
|
126
|
+
const h = Math.floor(m / 60);
|
|
127
|
+
if (h < 48) return `${h}h`;
|
|
128
|
+
return `${Math.floor(h / 24)}d`;
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/composites/cap-settings/SceneMonitorEditor.tsx
|
|
132
|
+
var BTN_NEUTRAL = "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";
|
|
133
|
+
var BTN_PRIMARY = "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";
|
|
134
|
+
var BTN_DANGER = "rounded-md border border-danger/50 bg-danger/10 px-2 py-1 text-[11px] font-medium text-danger hover:bg-danger/20 disabled:opacity-40 transition-colors";
|
|
135
|
+
var TONE_CLASS = {
|
|
136
|
+
alarm: "border-danger/50 bg-danger/15 text-danger",
|
|
137
|
+
ok: "border-success/50 bg-success/15 text-success",
|
|
138
|
+
unknown: "border-border bg-surface text-foreground-subtle"
|
|
139
|
+
};
|
|
140
|
+
/** A scene without at least one reference cannot judge anything, so the
|
|
141
|
+
* similarity threshold only becomes meaningful after the first capture. */
|
|
142
|
+
var DEFAULT_THRESHOLD = .8;
|
|
143
|
+
var DEFAULT_HYSTERESIS = 3;
|
|
144
|
+
function SceneMonitorEditor({ deviceId }) {
|
|
145
|
+
const dev = require_index.useDeviceProxy$1(require_index.useSystem$1().trpcClient, deviceId);
|
|
146
|
+
const pushed = require_index.useDeviceStateSlice$1(dev?.state.sceneMonitor);
|
|
147
|
+
const snapshot = require_index.useDeviceSnapshotImage$1(deviceId, { width: 640 });
|
|
148
|
+
const [fetched, setFetched] = (0, react.useState)(null);
|
|
149
|
+
const [unsupported, setUnsupported] = (0, react.useState)(false);
|
|
150
|
+
const [busy, setBusy] = (0, react.useState)(null);
|
|
151
|
+
const [error, setError] = (0, react.useState)(null);
|
|
152
|
+
const [selectedId, setSelectedId] = (0, react.useState)(null);
|
|
153
|
+
const [drafting, setDrafting] = (0, react.useState)(false);
|
|
154
|
+
const [draftRoi, setDraftRoi] = (0, react.useState)(null);
|
|
155
|
+
const [draftLabel, setDraftLabel] = (0, react.useState)("");
|
|
156
|
+
const mounted = (0, react.useRef)(true);
|
|
157
|
+
(0, react.useEffect)(() => {
|
|
158
|
+
mounted.current = true;
|
|
159
|
+
return () => {
|
|
160
|
+
mounted.current = false;
|
|
161
|
+
};
|
|
162
|
+
}, []);
|
|
163
|
+
const reload = (0, react.useCallback)(async () => {
|
|
164
|
+
if (!dev) return;
|
|
165
|
+
try {
|
|
166
|
+
const status = await dev.sceneMonitor?.listScenes({});
|
|
167
|
+
if (!mounted.current) return;
|
|
168
|
+
if (status) setFetched(status);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (!mounted.current) return;
|
|
171
|
+
if (require_index.isAbsentProvider$1(err)) setUnsupported(true);
|
|
172
|
+
else setError(err instanceof Error ? err.message : String(err));
|
|
173
|
+
}
|
|
174
|
+
}, [dev]);
|
|
175
|
+
(0, react.useEffect)(() => {
|
|
176
|
+
setFetched(null);
|
|
177
|
+
setUnsupported(false);
|
|
178
|
+
setError(null);
|
|
179
|
+
reload();
|
|
180
|
+
}, [reload]);
|
|
181
|
+
const monitors = pushed?.monitors ?? fetched?.monitors ?? [];
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
const run = (0, react.useCallback)(async (key, fn) => {
|
|
184
|
+
setBusy(key);
|
|
185
|
+
setError(null);
|
|
186
|
+
try {
|
|
187
|
+
await fn();
|
|
188
|
+
await reload();
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (mounted.current) setError(err instanceof Error ? err.message : String(err));
|
|
191
|
+
} finally {
|
|
192
|
+
if (mounted.current) setBusy(null);
|
|
193
|
+
}
|
|
194
|
+
}, [reload]);
|
|
195
|
+
const items = (0, react.useMemo)(() => {
|
|
196
|
+
const existing = monitors.map((m) => ({
|
|
197
|
+
id: m.id,
|
|
198
|
+
shape: m.roi,
|
|
199
|
+
label: m.label,
|
|
200
|
+
enabled: m.enabled
|
|
201
|
+
}));
|
|
202
|
+
return draftRoi !== null ? [...existing, {
|
|
203
|
+
id: "__draft__",
|
|
204
|
+
shape: draftRoi,
|
|
205
|
+
label: "New scene"
|
|
206
|
+
}] : existing;
|
|
207
|
+
}, [monitors, draftRoi]);
|
|
208
|
+
const onShapeChange = (0, react.useCallback)((id, shape) => {
|
|
209
|
+
if (shape.kind !== "rect") return;
|
|
210
|
+
if (id === "__draft__") {
|
|
211
|
+
setDraftRoi(shape);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
run(`roi-${String(id)}`, async () => dev?.sceneMonitor?.updateScene({
|
|
215
|
+
monitorId: String(id),
|
|
216
|
+
patch: { roi: shape }
|
|
217
|
+
}));
|
|
218
|
+
}, [dev, run]);
|
|
219
|
+
const onDrawComplete = (0, react.useCallback)((shape) => {
|
|
220
|
+
if (shape.kind !== "rect") return;
|
|
221
|
+
setDraftRoi(shape);
|
|
222
|
+
}, []);
|
|
223
|
+
const createScene = (0, react.useCallback)(async () => {
|
|
224
|
+
if (draftRoi === null || draftLabel.trim().length === 0) return;
|
|
225
|
+
await run("create", async () => {
|
|
226
|
+
await dev?.sceneMonitor?.createScene({
|
|
227
|
+
label: draftLabel.trim(),
|
|
228
|
+
roi: draftRoi,
|
|
229
|
+
check: {
|
|
230
|
+
mode: "similarity",
|
|
231
|
+
threshold: DEFAULT_THRESHOLD,
|
|
232
|
+
hysteresisCount: DEFAULT_HYSTERESIS
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
setDraftRoi(null);
|
|
237
|
+
setDraftLabel("");
|
|
238
|
+
setDrafting(false);
|
|
239
|
+
}, [
|
|
240
|
+
dev,
|
|
241
|
+
draftRoi,
|
|
242
|
+
draftLabel,
|
|
243
|
+
run
|
|
244
|
+
]);
|
|
245
|
+
if (!dev) return null;
|
|
246
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.WidgetPanel, {
|
|
247
|
+
title: "Scenes",
|
|
248
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScanEye, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
|
|
249
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
250
|
+
className: "flex flex-col gap-3",
|
|
251
|
+
children: unsupported ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
252
|
+
className: `${require_index.TEXT_HINT} leading-relaxed`,
|
|
253
|
+
children: "Scene monitoring is not available for this camera."
|
|
254
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
255
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
256
|
+
className: `${require_index.TEXT_HINT} leading-relaxed`,
|
|
257
|
+
children: "A scene watches one region of this camera and tells you when it stops looking like the picture you captured. Draw a box around the thing you care about — a wheelie bin, a gate, a parking space — then capture what “normal” looks like, in daylight and again at night."
|
|
258
|
+
}),
|
|
259
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
260
|
+
className: "relative overflow-hidden rounded-md border border-border",
|
|
261
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_MaskShapeCanvas.MaskShapeCanvas, {
|
|
262
|
+
items,
|
|
263
|
+
supportedShapes: ["rect"],
|
|
264
|
+
selectedId: selectedId ?? (draftRoi !== null ? "__draft__" : null),
|
|
265
|
+
onSelect: (id) => setSelectedId(id === null ? null : String(id)),
|
|
266
|
+
onShapeChange,
|
|
267
|
+
onDrawComplete,
|
|
268
|
+
drawingKind: drafting && draftRoi === null ? "rect" : null,
|
|
269
|
+
backdrop: snapshot.src !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
270
|
+
src: snapshot.src,
|
|
271
|
+
alt: "",
|
|
272
|
+
className: "h-full w-full object-contain",
|
|
273
|
+
draggable: false
|
|
274
|
+
}) : null
|
|
275
|
+
})
|
|
276
|
+
}),
|
|
277
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
278
|
+
className: "flex flex-wrap items-center gap-2",
|
|
279
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
280
|
+
type: "button",
|
|
281
|
+
className: drafting ? BTN_PRIMARY : BTN_NEUTRAL,
|
|
282
|
+
"aria-pressed": drafting,
|
|
283
|
+
onClick: () => {
|
|
284
|
+
setDrafting((v) => !v);
|
|
285
|
+
setDraftRoi(null);
|
|
286
|
+
},
|
|
287
|
+
disabled: busy !== null,
|
|
288
|
+
children: drafting ? "Cancel" : "+ New scene"
|
|
289
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
290
|
+
type: "button",
|
|
291
|
+
className: BTN_NEUTRAL,
|
|
292
|
+
onClick: snapshot.refresh,
|
|
293
|
+
disabled: busy !== null,
|
|
294
|
+
children: "Refresh frame"
|
|
295
|
+
})]
|
|
296
|
+
}),
|
|
297
|
+
drafting && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
298
|
+
className: "flex flex-col gap-2 rounded-md border border-border bg-surface p-2",
|
|
299
|
+
children: [
|
|
300
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
301
|
+
className: "text-[10px] text-foreground-subtle",
|
|
302
|
+
children: draftRoi === null ? "Click on the frame to drop a box, then drag its corner to fit it around the thing you want watched." : "Name it, then create. You capture what “normal” looks like next."
|
|
303
|
+
}),
|
|
304
|
+
draftRoi !== null && roiTooLarge(draftRoi) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
305
|
+
className: "text-[11px] leading-relaxed text-warning",
|
|
306
|
+
children: "That box covers most of the frame. A scene that big is dominated by the weather, the sky and passing cars — it will quietly stop noticing the thing you actually care about. Draw it tighter."
|
|
307
|
+
}),
|
|
308
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
309
|
+
className: "flex items-center gap-2",
|
|
310
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
311
|
+
type: "text",
|
|
312
|
+
value: draftLabel,
|
|
313
|
+
onChange: (e) => setDraftLabel(e.target.value),
|
|
314
|
+
placeholder: "e.g. Immondizia prelevata",
|
|
315
|
+
className: "flex-1 rounded-md border border-border bg-surface px-2 py-1 text-[12px] text-foreground"
|
|
316
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
317
|
+
type: "button",
|
|
318
|
+
className: BTN_PRIMARY,
|
|
319
|
+
onClick: () => void createScene(),
|
|
320
|
+
disabled: draftRoi === null || draftLabel.trim().length === 0 || busy !== null,
|
|
321
|
+
children: "Create"
|
|
322
|
+
})]
|
|
323
|
+
})
|
|
324
|
+
]
|
|
325
|
+
}),
|
|
326
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
327
|
+
className: "text-[11px] leading-relaxed text-danger",
|
|
328
|
+
children: error
|
|
329
|
+
}),
|
|
330
|
+
monitors.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
331
|
+
className: `${require_index.TEXT_HINT} leading-relaxed`,
|
|
332
|
+
children: "No scenes on this camera yet."
|
|
333
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
334
|
+
className: "flex flex-col gap-2",
|
|
335
|
+
children: monitors.map((m) => {
|
|
336
|
+
const badge = badgeFor(m);
|
|
337
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
338
|
+
className: `flex flex-col gap-1.5 rounded-md border p-2 ${selectedId === m.id ? "border-primary/50" : "border-border"}`,
|
|
339
|
+
children: [
|
|
340
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
341
|
+
className: "flex items-center gap-2",
|
|
342
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
343
|
+
type: "button",
|
|
344
|
+
className: "flex-1 text-left text-[12px] font-medium text-foreground",
|
|
345
|
+
onClick: () => setSelectedId(m.id),
|
|
346
|
+
children: m.label
|
|
347
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
348
|
+
className: `rounded border px-1.5 py-0.5 text-[10px] font-medium ${TONE_CLASS[badge.tone]}`,
|
|
349
|
+
children: badge.text
|
|
350
|
+
})]
|
|
351
|
+
}),
|
|
352
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
353
|
+
className: `${require_index.TEXT_HINT} leading-relaxed`,
|
|
354
|
+
children: statusLine(m, now)
|
|
355
|
+
}),
|
|
356
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
357
|
+
className: `${require_index.TEXT_HINT} tabular-nums`,
|
|
358
|
+
children: coverageLine(m)
|
|
359
|
+
}),
|
|
360
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
361
|
+
className: "flex flex-wrap items-center gap-1.5",
|
|
362
|
+
children: [
|
|
363
|
+
SCENE_CAPTURE_VARIANTS.map((variant) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
364
|
+
type: "button",
|
|
365
|
+
className: BTN_NEUTRAL,
|
|
366
|
+
disabled: busy !== null,
|
|
367
|
+
title: `Take a fresh photo now and store it as the ${variantLabel(variant).toLowerCase()} reference`,
|
|
368
|
+
onClick: () => void run(`cap-${m.id}-${variant}`, async () => dev.sceneMonitor?.captureReference({
|
|
369
|
+
monitorId: m.id,
|
|
370
|
+
condition: variant
|
|
371
|
+
})),
|
|
372
|
+
children: [
|
|
373
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.Camera, { className: "mr-1 inline h-3 w-3" }),
|
|
374
|
+
"Capture ",
|
|
375
|
+
variantLabel(variant).toLowerCase()
|
|
376
|
+
]
|
|
377
|
+
}, variant)),
|
|
378
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
379
|
+
type: "button",
|
|
380
|
+
className: BTN_PRIMARY,
|
|
381
|
+
disabled: busy !== null || m.states.length === 0,
|
|
382
|
+
title: "Clear the flag and re-photograph what normal looks like right now",
|
|
383
|
+
onClick: () => void run(`reset-${m.id}`, async () => dev.sceneMonitor?.resetScene({ monitorId: m.id })),
|
|
384
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.RotateCcw, { className: "mr-1 inline h-3 w-3" }), "Reset"]
|
|
385
|
+
}),
|
|
386
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
387
|
+
type: "button",
|
|
388
|
+
className: BTN_NEUTRAL,
|
|
389
|
+
disabled: busy !== null,
|
|
390
|
+
onClick: () => void run(`check-${m.id}`, async () => dev.sceneMonitor?.recheckNow({ monitorId: m.id })),
|
|
391
|
+
children: "Check now"
|
|
392
|
+
}),
|
|
393
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
394
|
+
type: "button",
|
|
395
|
+
className: BTN_DANGER,
|
|
396
|
+
disabled: busy !== null,
|
|
397
|
+
onClick: () => void run(`del-${m.id}`, async () => dev.sceneMonitor?.deleteScene({ monitorId: m.id })),
|
|
398
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.Trash2, { className: "h-3 w-3" })
|
|
399
|
+
})
|
|
400
|
+
]
|
|
401
|
+
})
|
|
402
|
+
]
|
|
403
|
+
}, m.id);
|
|
404
|
+
})
|
|
405
|
+
})
|
|
406
|
+
] })
|
|
407
|
+
})
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
//#endregion
|
|
411
|
+
exports.SceneMonitorEditor = SceneMonitorEditor;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
export { PtzPanel } from './PtzPanel';
|
|
2
|
-
export { ConsumablesPanel } from './ConsumablesPanel';
|
|
3
1
|
export { AutotrackSection } from './AutotrackSection';
|
|
2
|
+
export { ConsumablesPanel } from './ConsumablesPanel';
|
|
3
|
+
export type { MaskShapeCanvasProps, MaskShapeItem } from './MaskShapeCanvas';
|
|
4
|
+
export { MaskShapeCanvas } from './MaskShapeCanvas';
|
|
4
5
|
export { MotionZonesSettings } from './MotionZonesSettings';
|
|
5
6
|
export { PrivacyMaskSettings } from './PrivacyMaskSettings';
|
|
7
|
+
export { PtzPanel } from './PtzPanel';
|
|
8
|
+
export { RecordingBandsEditor } from './RecordingBandsEditor';
|
|
6
9
|
export { RecordingPanel } from './RecordingPanel';
|
|
7
10
|
export { RecordingSettings } from './RecordingSettings';
|
|
8
|
-
export {
|
|
9
|
-
export { MaskShapeCanvas } from './MaskShapeCanvas';
|
|
10
|
-
export type { MaskShapeItem, MaskShapeCanvasProps } from './MaskShapeCanvas';
|
|
11
|
+
export { SceneMonitorEditor } from './SceneMonitorEditor';
|
|
11
12
|
/**
|
|
12
13
|
* Props every cap-settings component receives.
|
|
13
14
|
*
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SceneMonitor } from '@camstack/types';
|
|
2
|
+
/** The lighting variants an operator is asked to capture. The resolver can also
|
|
3
|
+
* report `night`, `dawn` and `dusk`; those are shown when present but never
|
|
4
|
+
* demanded, because most installs only ever see day and IR. */
|
|
5
|
+
export declare const SCENE_CAPTURE_VARIANTS: readonly ["day", "ir"];
|
|
6
|
+
export type SceneCaptureVariant = (typeof SCENE_CAPTURE_VARIANTS)[number];
|
|
7
|
+
export declare const VARIANT_LABEL: Readonly<Record<string, string>>;
|
|
8
|
+
export declare function variantLabel(condition: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* "day ✓ · ir ✗ — this scene reports UNKNOWN at night."
|
|
11
|
+
*
|
|
12
|
+
* Converts the silent cross-condition fallback this engine used to have into an
|
|
13
|
+
* operator-visible fact. Without it, a scene with only a daylight reference
|
|
14
|
+
* simply stops answering after sunset and nothing anywhere says why.
|
|
15
|
+
*/
|
|
16
|
+
export declare function coverageLine(monitor: SceneMonitor): string;
|
|
17
|
+
/**
|
|
18
|
+
* The one line under a scene's name. It answers, in order: can it judge, what
|
|
19
|
+
* does it say, and how long has it said it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function statusLine(monitor: SceneMonitor, now: number): string;
|
|
22
|
+
/** The badge word. `latched` wins over `verdict`, because the latch is the
|
|
23
|
+
* thing the operator asked to be told and it outlives the live state. */
|
|
24
|
+
export declare function badgeFor(monitor: SceneMonitor): {
|
|
25
|
+
text: string;
|
|
26
|
+
tone: 'alarm' | 'ok' | 'unknown';
|
|
27
|
+
};
|
|
28
|
+
/** A rect covering most of the frame is dominated by weather, sky and parked
|
|
29
|
+
* cars: the bin is 2 % of the pixels and CLIP will not notice it leaving. The
|
|
30
|
+
* failure is silent INSENSITIVITY, which is the worst kind — hence a warning
|
|
31
|
+
* rather than a refusal. */
|
|
32
|
+
export declare const ROI_AREA_WARN_FRACTION = 0.4;
|
|
33
|
+
export declare function roiTooLarge(roi: {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
}): boolean;
|
|
37
|
+
export declare function humanDuration(ms: number): string;
|