@camstack/ui-library 1.2.290 → 1.2.292
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/{MaskShapeCanvas-DI4BY7W2.js → MaskShapeCanvas-C9cJlxvG.js} +3 -1
- package/dist/{MaskShapeCanvas-BByN3jvt.cjs → MaskShapeCanvas-NCNwu9_5.cjs} +3 -1
- package/dist/{MotionZonesSettings-CiYjd-yl.js → MotionZonesSettings-Bv8XzCa3.js} +45 -48
- package/dist/{MotionZonesSettings-BK9R9NIr.cjs → MotionZonesSettings-CPRH536-.cjs} +44 -47
- package/dist/{PrivacyMaskSettings-DuXUCUev.cjs → PrivacyMaskSettings-BUWvVF_n.cjs} +33 -42
- package/dist/{PrivacyMaskSettings-BmRdUrPp.js → PrivacyMaskSettings-BlkbESYj.js} +34 -43
- package/dist/{SceneMonitorEditor-COgAjtq5.cjs → SceneMonitorEditor-D_jkzQ2o.cjs} +189 -86
- package/dist/{SceneMonitorEditor-J4Ix_0tu.js → SceneMonitorEditor-i3-nXAEU.js} +190 -87
- package/dist/composites/addon-global-settings-form.d.ts +0 -12
- package/dist/composites/cap-settings/AutotrackSection.d.ts +0 -16
- package/dist/composites/cap-settings/CameraGridLayoutEditor.d.ts +0 -21
- package/dist/composites/cap-settings/ConsumablesPanel.d.ts +0 -18
- package/dist/composites/cap-settings/MaskShapeCanvas.d.ts +0 -102
- package/dist/composites/cap-settings/MotionZonesSettings.d.ts +0 -38
- package/dist/composites/cap-settings/SceneReferenceGallery.d.ts +0 -38
- package/dist/composites/cap-settings/scene-monitor-copy.d.ts +12 -6
- package/dist/composites/clips/clip-source-state.d.ts +3 -2
- package/dist/composites/clips/clip-thumbnail-fetch.d.ts +2 -1
- package/dist/composites/clips/clip-tile-media.d.ts +3 -2
- package/dist/composites/clips/clip-view-state.d.ts +2 -1
- package/dist/composites/confirm-dialog.d.ts +0 -62
- package/dist/composites/data-table.d.ts +2 -11
- package/dist/composites/detection-canvas.d.ts +0 -9
- package/dist/composites/detection-result-tree.d.ts +0 -12
- package/dist/composites/device-controls/container-primary-hero.d.ts +0 -17
- package/dist/composites/device-controls/humidifier-control.d.ts +0 -17
- package/dist/composites/device-controls/lawn-mower-control.d.ts +2 -1
- package/dist/composites/device-controls/pet-feeder-panel.d.ts +0 -18
- package/dist/composites/device-controls/script-automation-controls.d.ts +2 -1
- package/dist/composites/device-controls/sensor-hero-card.d.ts +0 -18
- package/dist/composites/device-controls/update-control.d.ts +0 -13
- package/dist/composites/device-controls/vacuum-control.d.ts +2 -1
- package/dist/composites/device-controls/valve-control.d.ts +2 -1
- package/dist/composites/device-controls/weather-control.d.ts +2 -1
- package/dist/composites/device-item/actions.d.ts +0 -22
- package/dist/composites/device-item/helpers.d.ts +2 -1
- package/dist/composites/device-item/index.d.ts +0 -9
- package/dist/composites/device-list/batch-actions-bar.d.ts +0 -11
- package/dist/composites/device-list/filter-chips.d.ts +0 -13
- package/dist/composites/device-list/page-size-select.d.ts +2 -1
- package/dist/composites/device-list/url-state.d.ts +2 -3
- package/dist/composites/device-meta.d.ts +14 -7
- package/dist/composites/device-picker/DevicePicker.d.ts +9 -1
- package/dist/composites/device-picker/device-filter-bar.d.ts +2 -1
- package/dist/composites/device-picker/device-parent.d.ts +2 -1
- package/dist/composites/device-picker/device-selection-rows.d.ts +6 -1
- package/dist/composites/device-selector-picker.d.ts +25 -1
- package/dist/composites/device-step-matrix.d.ts +0 -26
- package/dist/composites/error-box.d.ts +0 -15
- package/dist/composites/event-stream.d.ts +2 -1
- package/dist/composites/extensions/extensions-view-state.d.ts +2 -1
- package/dist/composites/hls-quality.d.ts +3 -2
- package/dist/composites/index.d.ts +0 -2
- package/dist/composites/notification-targets-field.d.ts +2 -2
- package/dist/composites/option-select/OptionSelect.d.ts +6 -0
- package/dist/composites/pipeline-matrix-shared.d.ts +2 -1
- package/dist/composites/provider-entries/AddProviderEntryDialog.d.ts +1 -1
- package/dist/composites/provider-entries/ProviderEntryPills.d.ts +1 -1
- package/dist/composites/ptz-overlay.d.ts +0 -19
- package/dist/composites/qr-code.d.ts +0 -14
- package/dist/composites/scope-picker.d.ts +2 -1
- package/dist/composites/step-inspector/layered-settings-form.d.ts +0 -11
- package/dist/composites/step-inspector/step-inspector-panel.d.ts +0 -6
- package/dist/composites/stream-broker-selector.d.ts +0 -21
- package/dist/composites/training/TrainingBaseModelPicker.d.ts +0 -10
- package/dist/composites/training/retrain-review-badge.d.ts +2 -1
- package/dist/composites/webrtc-diagnostics/endpoint-test.d.ts +2 -1
- package/dist/composites/webrtc-diagnostics/nat-mapping.d.ts +2 -1
- package/dist/composites/webrtc-diagnostics/session-run.d.ts +2 -1
- package/dist/composites/webrtc-diagnostics/session-verdict.d.ts +2 -1
- package/dist/contexts/ui-text.d.ts +49 -0
- package/dist/generated/system-hooks.d.ts +4 -0
- package/dist/i18n/addon-i18n.d.ts +46 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/use-addon-t.d.ts +57 -0
- package/dist/index.cjs +3309 -2368
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3297 -2368
- package/dist/lib/format-last-seen.d.ts +15 -1
- package/dist/lib/phase-config.d.ts +9 -3
- package/dist/lib/pipeline-mirror.d.ts +2 -1
- package/package.json +1 -1
- package/dist/composites/data-table/data-table-header.d.ts +0 -11
- package/dist/composites/data-table/data-table-pagination.d.ts +0 -10
- package/dist/composites/data-table/data-table-row.d.ts +0 -11
- package/dist/composites/data-table/data-table.d.ts +0 -3
- package/dist/composites/data-table/index.d.ts +0 -2
- package/dist/composites/data-table/types.d.ts +0 -34
- package/dist/composites/data-table-layout.d.ts +0 -31
|
@@ -1,9 +1,47 @@
|
|
|
1
1
|
const require_settings_2 = require("./settings-2-CR4WtEw4.cjs");
|
|
2
2
|
const require_index = require("./index.cjs");
|
|
3
|
-
const require_MaskShapeCanvas = require("./MaskShapeCanvas-
|
|
3
|
+
const require_MaskShapeCanvas = require("./MaskShapeCanvas-NCNwu9_5.cjs");
|
|
4
4
|
let react = require("react");
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
6
|
//#region src/composites/cap-settings/SceneReferenceGallery.tsx
|
|
7
|
+
/**
|
|
8
|
+
* SceneReferenceGallery — the photographs a scene is actually judging against,
|
|
9
|
+
* grouped by lighting variant, each individually deletable.
|
|
10
|
+
*
|
|
11
|
+
* ## Why it has to exist
|
|
12
|
+
*
|
|
13
|
+
* A scene's whole answer is "does this region still look like the photograph".
|
|
14
|
+
* Until now the operator could see a COUNT of references and never the
|
|
15
|
+
* references themselves, which makes the two failure modes indistinguishable
|
|
16
|
+
* from the outside: a blurry capture taken while a van was in the way poisons a
|
|
17
|
+
* condition forever, and from a cosine score there is no way to tell which one
|
|
18
|
+
* it was. `deleteReference` shipped with the cap for exactly this and had no
|
|
19
|
+
* caller.
|
|
20
|
+
*
|
|
21
|
+
* ## The image
|
|
22
|
+
*
|
|
23
|
+
* A bare `<img>` against the `event-media` data plane
|
|
24
|
+
* (`/addon/pipeline-analytics/event-media/m/<mediaId>` — D482), which is
|
|
25
|
+
* `access: 'authenticated'` — that accepts the `camstack_session` cookie, and a
|
|
26
|
+
* cookie is the only credential an `<img>` can send. Same mechanism, and the
|
|
27
|
+
* same reasoning, as `use-device-snapshot`; no bearer-token blob fetch is
|
|
28
|
+
* needed and no second copy of the auth plumbing is introduced here.
|
|
29
|
+
*
|
|
30
|
+
* `SceneReference.thumbnailMediaId` finally IS a media row id (D482). It was
|
|
31
|
+
* named `…MediaId` while holding a composite MediaStore key
|
|
32
|
+
* (`scene:<ownerId>:thumbnailSmall:<ts>`), which this component read
|
|
33
|
+
* POSITIONALLY for its owner id — a name that described the right design
|
|
34
|
+
* beside a value that did not, which is the shape of leftover this repo has
|
|
35
|
+
* been bitten by three times. The tile now addresses the row.
|
|
36
|
+
*
|
|
37
|
+
* **A missing blob is normal, not exceptional.** Reference thumbnails written
|
|
38
|
+
* before the `ownerKind: 'scene'` fix carried an `ownerId` no event row backs,
|
|
39
|
+
* which made them eligible for the orphan sweep — so on an existing install
|
|
40
|
+
* some of these ids resolve to nothing. That must degrade to a dated
|
|
41
|
+
* placeholder: the reference is still real, still being scored against, and
|
|
42
|
+
* still deletable. A gallery that blanks or throws over a missing picture would
|
|
43
|
+
* take the delete button away exactly when it is needed.
|
|
44
|
+
*/
|
|
7
45
|
/** The hub-proxied prefix for the pipeline-analytics event-media data plane. */
|
|
8
46
|
var SCENE_MEDIA_BASE = require_index.hubPath$1("/addon/pipeline-analytics/event-media");
|
|
9
47
|
/**
|
|
@@ -51,12 +89,13 @@ function totalReferences(monitor) {
|
|
|
51
89
|
}
|
|
52
90
|
var BTN_TINY = "rounded border border-danger/50 bg-danger/10 px-1 py-0.5 text-[10px] font-medium text-danger hover:bg-danger/20 disabled:opacity-40 transition-colors";
|
|
53
91
|
function SceneReferenceGallery({ monitor, disabled, onDelete }) {
|
|
92
|
+
const t = require_index.useUiText();
|
|
54
93
|
const groups = groupReferences(monitor);
|
|
55
94
|
const total = totalReferences(monitor);
|
|
56
95
|
const [confirming, setConfirming] = (0, react.useState)(null);
|
|
57
96
|
if (groups.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
58
97
|
className: `${require_index.TEXT_HINT} leading-relaxed`,
|
|
59
|
-
children: "No photographs captured yet. Press “Capture daylight” to take the one this scene will judge everything against."
|
|
98
|
+
children: t("uiLibrary.sceneReferenceGallery.empty", "No photographs captured yet. Press “Capture daylight” to take the one this scene will judge everything against.")
|
|
60
99
|
});
|
|
61
100
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
62
101
|
className: "flex flex-col gap-2",
|
|
@@ -65,7 +104,7 @@ function SceneReferenceGallery({ monitor, disabled, onDelete }) {
|
|
|
65
104
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
66
105
|
className: "text-[10px] font-semibold uppercase tracking-wide text-foreground-subtle",
|
|
67
106
|
children: [
|
|
68
|
-
require_index.variantLabel$1(group.condition),
|
|
107
|
+
require_index.variantLabel$1(group.condition, t),
|
|
69
108
|
" · ",
|
|
70
109
|
group.tiles.length
|
|
71
110
|
]
|
|
@@ -87,7 +126,10 @@ function SceneReferenceGallery({ monitor, disabled, onDelete }) {
|
|
|
87
126
|
type: "button",
|
|
88
127
|
className: BTN_TINY,
|
|
89
128
|
disabled,
|
|
90
|
-
"aria-label":
|
|
129
|
+
"aria-label": t("uiLibrary.sceneReferenceGallery.deleteLabel", "Delete the {{variant}} photograph from {{date}}", {
|
|
130
|
+
variant: require_index.variantLabel$1(group.condition, t).toLowerCase(),
|
|
131
|
+
date: formatCapturedAt(tile.reference.capturedAt)
|
|
132
|
+
}),
|
|
91
133
|
onClick: () => {
|
|
92
134
|
if (isLastOverall && !pending) {
|
|
93
135
|
setConfirming(key);
|
|
@@ -99,11 +141,11 @@ function SceneReferenceGallery({ monitor, disabled, onDelete }) {
|
|
|
99
141
|
index: tile.index
|
|
100
142
|
});
|
|
101
143
|
},
|
|
102
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.Trash2, { className: "mr-1 inline h-3 w-3" }), pending ? "Confirm" : "Delete"]
|
|
144
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.Trash2, { className: "mr-1 inline h-3 w-3" }), pending ? t("uiLibrary.sceneReferenceGallery.confirm", "Confirm") : t("uiLibrary.sceneReferenceGallery.delete", "Delete")]
|
|
103
145
|
}),
|
|
104
146
|
pending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
105
147
|
className: "text-[10px] leading-snug text-warning",
|
|
106
|
-
children: "This is the only photograph this scene has. Delete it and the scene no longer knows what normal looks like — it stops judging until you capture again."
|
|
148
|
+
children: t("uiLibrary.sceneReferenceGallery.lastWarning", "This is the only photograph this scene has. Delete it and the scene no longer knows what normal looks like — it stops judging until you capture again.")
|
|
107
149
|
})
|
|
108
150
|
]
|
|
109
151
|
}, key);
|
|
@@ -113,14 +155,15 @@ function SceneReferenceGallery({ monitor, disabled, onDelete }) {
|
|
|
113
155
|
});
|
|
114
156
|
}
|
|
115
157
|
function ReferenceThumb({ reference }) {
|
|
158
|
+
const t = require_index.useUiText();
|
|
116
159
|
const [failed, setFailed] = (0, react.useState)(false);
|
|
117
160
|
const mediaId = reference.thumbnailMediaId;
|
|
118
161
|
if (mediaId === void 0 || failed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
119
162
|
className: "flex h-16 w-full flex-col items-center justify-center gap-0.5 rounded bg-surface text-foreground-subtle",
|
|
120
|
-
title: "The picture for this reference is no longer stored. The reference itself is intact and still being scored against.",
|
|
163
|
+
title: t("uiLibrary.sceneReferenceGallery.missingPicture", "The picture for this reference is no longer stored. The reference itself is intact and still being scored against."),
|
|
121
164
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.ImageOff, { className: "h-4 w-4" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
122
165
|
className: "text-[9px]",
|
|
123
|
-
children: "no picture"
|
|
166
|
+
children: t("uiLibrary.sceneReferenceGallery.noPicture", "no picture")
|
|
124
167
|
})]
|
|
125
168
|
});
|
|
126
169
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
@@ -147,15 +190,19 @@ var GROUP_TITLE = "text-[10px] font-semibold uppercase tracking-wide text-foregr
|
|
|
147
190
|
/** The two engine-visible interval floors, in the units the operator thinks in. */
|
|
148
191
|
var MIN_INTERVAL_SEC = 5;
|
|
149
192
|
var MAX_INTERVAL_SEC = 3600;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
193
|
+
function triggerHint(mode, t) {
|
|
194
|
+
switch (mode) {
|
|
195
|
+
case "periodic": return t("uiLibrary.sceneSettingsForm.triggerHintPeriodic", "Only on the clock. Predictable, and it notices a change even on a camera nothing ever moves in front of.");
|
|
196
|
+
case "on-motion": return t("uiLibrary.sceneSettingsForm.triggerHintOnMotion", "Only after the camera has been still again for the quiet window below. Cheapest, but a change nobody walks past is never seen.");
|
|
197
|
+
case "both": return t("uiLibrary.sceneSettingsForm.triggerHintBoth", "Both — the clock is the safety net that lets the motion path miss. This is the right answer for almost every scene.");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function uncoveredHint(policy, t) {
|
|
201
|
+
switch (policy) {
|
|
202
|
+
case "skip": return t("uiLibrary.sceneSettingsForm.uncoveredHintSkip", "Sit that light out. No verdict, no alarm, no flag touched — it picks itself back up when a light it knows returns. This is why the night capture is optional.");
|
|
203
|
+
case "judge-anyway": return t("uiLibrary.sceneSettingsForm.uncoveredHintJudge", "Compare against the references you do have. Only correct when the two lights genuinely look alike (a floodlit driveway, an indoor lamp) — against a real infrared frame it will report a change that did not happen.");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
159
206
|
function draftOf(monitor) {
|
|
160
207
|
return {
|
|
161
208
|
label: monitor.label,
|
|
@@ -168,6 +215,7 @@ function draftOf(monitor) {
|
|
|
168
215
|
};
|
|
169
216
|
}
|
|
170
217
|
function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
218
|
+
const t = require_index.useUiText();
|
|
171
219
|
const [draft, setDraft] = (0, react.useState)(() => draftOf(monitor));
|
|
172
220
|
const [editing, setEditing] = (0, react.useState)(false);
|
|
173
221
|
(0, react.useEffect)(() => {
|
|
@@ -196,13 +244,13 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
196
244
|
children: [
|
|
197
245
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
198
246
|
className: GROUP_TITLE,
|
|
199
|
-
children: "What it watches"
|
|
247
|
+
children: t("uiLibrary.sceneSettingsForm.whatItWatches", "What it watches")
|
|
200
248
|
}),
|
|
201
249
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
202
250
|
className: "flex flex-col gap-1",
|
|
203
251
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
204
252
|
className: LABEL,
|
|
205
|
-
children: "Name"
|
|
253
|
+
children: t("uiLibrary.sceneSettingsForm.name", "Name")
|
|
206
254
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
207
255
|
type: "text",
|
|
208
256
|
value: draft.label,
|
|
@@ -229,18 +277,18 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
229
277
|
onChange: (e) => commit({ enabled: e.target.checked })
|
|
230
278
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
231
279
|
className: LABEL,
|
|
232
|
-
children: "Watching"
|
|
280
|
+
children: t("uiLibrary.sceneSettingsForm.watching", "Watching")
|
|
233
281
|
})]
|
|
234
282
|
}),
|
|
235
283
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
236
284
|
className: require_index.TEXT_HINT,
|
|
237
|
-
children: "Turned off, the scene keeps its region, its references and its flag — it simply stops checking. Nothing is lost by pausing one for a while."
|
|
285
|
+
children: t("uiLibrary.sceneSettingsForm.watchingHint", "Turned off, the scene keeps its region, its references and its flag — it simply stops checking. Nothing is lost by pausing one for a while.")
|
|
238
286
|
}),
|
|
239
287
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
240
288
|
className: "flex flex-col gap-1",
|
|
241
289
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
242
290
|
className: LABEL,
|
|
243
|
-
children: "When to look"
|
|
291
|
+
children: t("uiLibrary.sceneSettingsForm.whenToLook", "When to look")
|
|
244
292
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
245
293
|
value: monitor.triggerMode,
|
|
246
294
|
disabled,
|
|
@@ -252,28 +300,28 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
252
300
|
children: [
|
|
253
301
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
254
302
|
value: "both",
|
|
255
|
-
children: "On the clock and after motion settles"
|
|
303
|
+
children: t("uiLibrary.sceneSettingsForm.triggerBoth", "On the clock and after motion settles")
|
|
256
304
|
}),
|
|
257
305
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
258
306
|
value: "periodic",
|
|
259
|
-
children: "On the clock only"
|
|
307
|
+
children: t("uiLibrary.sceneSettingsForm.triggerPeriodic", "On the clock only")
|
|
260
308
|
}),
|
|
261
309
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
262
310
|
value: "on-motion",
|
|
263
|
-
children: "After motion settles only"
|
|
311
|
+
children: t("uiLibrary.sceneSettingsForm.triggerOnMotion", "After motion settles only")
|
|
264
312
|
})
|
|
265
313
|
]
|
|
266
314
|
})]
|
|
267
315
|
}),
|
|
268
316
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
269
317
|
className: require_index.TEXT_HINT,
|
|
270
|
-
children:
|
|
318
|
+
children: triggerHint(monitor.triggerMode, t)
|
|
271
319
|
}),
|
|
272
320
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
273
321
|
className: "flex flex-col gap-1",
|
|
274
322
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
275
323
|
className: LABEL,
|
|
276
|
-
children: "Check every (seconds)"
|
|
324
|
+
children: t("uiLibrary.sceneSettingsForm.checkEvery", "Check every (seconds)")
|
|
277
325
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
278
326
|
type: "number",
|
|
279
327
|
min: MIN_INTERVAL_SEC,
|
|
@@ -295,7 +343,7 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
295
343
|
}),
|
|
296
344
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
297
345
|
className: require_index.TEXT_HINT,
|
|
298
|
-
children: "60 s suits “is the bin still out”. Every check costs one photograph and one encode, so going below about 30 s buys latency you almost never need and multiplies the cost by the number of scenes you own."
|
|
346
|
+
children: t("uiLibrary.sceneSettingsForm.intervalHint", "60 s suits “is the bin still out”. Every check costs one photograph and one encode, so going below about 30 s buys latency you almost never need and multiplies the cost by the number of scenes you own.")
|
|
299
347
|
})
|
|
300
348
|
]
|
|
301
349
|
}),
|
|
@@ -304,16 +352,18 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
304
352
|
children: [
|
|
305
353
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
306
354
|
className: GROUP_TITLE,
|
|
307
|
-
children: "How sure it has to be"
|
|
355
|
+
children: t("uiLibrary.sceneSettingsForm.howSure", "How sure it has to be")
|
|
308
356
|
}),
|
|
309
357
|
monitor.check.mode === "similarity" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
310
358
|
className: "flex flex-col gap-1",
|
|
311
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
359
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
312
360
|
className: LABEL,
|
|
313
|
-
children:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
361
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.UiTrans, {
|
|
362
|
+
uiKey: "uiLibrary.sceneSettingsForm.matchThreshold",
|
|
363
|
+
fallback: "Match threshold — <0>{{value}}</0>",
|
|
364
|
+
components: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "tabular-nums" }, "value")],
|
|
365
|
+
values: { value: draft.threshold.toFixed(2) }
|
|
366
|
+
})
|
|
317
367
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
318
368
|
type: "range",
|
|
319
369
|
min: .3,
|
|
@@ -337,13 +387,13 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
337
387
|
})]
|
|
338
388
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
339
389
|
className: "text-[10px] text-foreground-subtle",
|
|
340
|
-
children: "How closely the region still has to resemble the photograph to count as unchanged. Higher means fussier: at 0.95 a passing cloud can read as a change; at 0.60 the bin can be taken and the scene will shrug. 0.80 is a sensible start — if it cries wolf, lower it; if it misses the thing you care about, raise it."
|
|
390
|
+
children: t("uiLibrary.sceneSettingsForm.thresholdHint", "How closely the region still has to resemble the photograph to count as unchanged. Higher means fussier: at 0.95 a passing cloud can read as a change; at 0.60 the bin can be taken and the scene will shrug. 0.80 is a sensible start — if it cries wolf, lower it; if it misses the thing you care about, raise it.")
|
|
341
391
|
})] }),
|
|
342
392
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
343
393
|
className: "flex flex-col gap-1",
|
|
344
394
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
345
395
|
className: LABEL,
|
|
346
|
-
children: "Agreeing checks before it changes its mind"
|
|
396
|
+
children: t("uiLibrary.sceneSettingsForm.agreeingChecks", "Agreeing checks before it changes its mind")
|
|
347
397
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
348
398
|
type: "number",
|
|
349
399
|
min: 1,
|
|
@@ -365,13 +415,13 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
365
415
|
}),
|
|
366
416
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
367
417
|
className: require_index.TEXT_HINT,
|
|
368
|
-
children: "One bad photograph must never be able to flip a scene. 3 agreeing checks at 60 s is about four minutes of latency and is what makes the answer trustworthy — leaving a state costs exactly the same as entering one."
|
|
418
|
+
children: t("uiLibrary.sceneSettingsForm.agreeingHint", "One bad photograph must never be able to flip a scene. 3 agreeing checks at 60 s is about four minutes of latency and is what makes the answer trustworthy — leaving a state costs exactly the same as entering one.")
|
|
369
419
|
}),
|
|
370
420
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
371
421
|
className: "flex flex-col gap-1",
|
|
372
422
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
373
423
|
className: LABEL,
|
|
374
|
-
children: "Quiet for (seconds) before a check counts"
|
|
424
|
+
children: t("uiLibrary.sceneSettingsForm.quietFor", "Quiet for (seconds) before a check counts")
|
|
375
425
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
376
426
|
type: "number",
|
|
377
427
|
min: 0,
|
|
@@ -393,13 +443,13 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
393
443
|
}),
|
|
394
444
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
395
445
|
className: require_index.TEXT_HINT,
|
|
396
|
-
children: "A check taken while something is moving is a photograph of the van parked in front of the bin, so it is discarded rather than merely delayed. 0 disables the gate."
|
|
446
|
+
children: t("uiLibrary.sceneSettingsForm.quietHint", "A check taken while something is moving is a photograph of the van parked in front of the bin, so it is discarded rather than merely delayed. 0 disables the gate.")
|
|
397
447
|
}),
|
|
398
448
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
399
449
|
className: "flex flex-col gap-1",
|
|
400
450
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
401
451
|
className: LABEL,
|
|
402
|
-
children: "Minimum gap between counted checks (seconds)"
|
|
452
|
+
children: t("uiLibrary.sceneSettingsForm.minGap", "Minimum gap between counted checks (seconds)")
|
|
403
453
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
404
454
|
type: "number",
|
|
405
455
|
min: 0,
|
|
@@ -421,7 +471,7 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
421
471
|
}),
|
|
422
472
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
423
473
|
className: require_index.TEXT_HINT,
|
|
424
|
-
children: "Spreads the agreeing checks over real time instead of letting three adjacent polls inside one long occlusion be mistaken for three independent opinions."
|
|
474
|
+
children: t("uiLibrary.sceneSettingsForm.minGapHint", "Spreads the agreeing checks over real time instead of letting three adjacent polls inside one long occlusion be mistaken for three independent opinions.")
|
|
425
475
|
})
|
|
426
476
|
]
|
|
427
477
|
}),
|
|
@@ -430,13 +480,13 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
430
480
|
children: [
|
|
431
481
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
432
482
|
className: GROUP_TITLE,
|
|
433
|
-
children: "Lighting"
|
|
483
|
+
children: t("uiLibrary.sceneSettingsForm.lighting", "Lighting")
|
|
434
484
|
}),
|
|
435
485
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
436
486
|
className: "flex flex-col gap-1",
|
|
437
487
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
438
488
|
className: LABEL,
|
|
439
|
-
children: "When a light has no reference of its own"
|
|
489
|
+
children: t("uiLibrary.sceneSettingsForm.noReference", "When a light has no reference of its own")
|
|
440
490
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
441
491
|
value: monitor.onUncoveredCondition,
|
|
442
492
|
disabled,
|
|
@@ -447,16 +497,16 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
447
497
|
},
|
|
448
498
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
449
499
|
value: "skip",
|
|
450
|
-
children: "Skip the check (recommended)"
|
|
500
|
+
children: t("uiLibrary.sceneSettingsForm.uncoveredSkip", "Skip the check (recommended)")
|
|
451
501
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
452
502
|
value: "judge-anyway",
|
|
453
|
-
children: "Judge against the other light anyway"
|
|
503
|
+
children: t("uiLibrary.sceneSettingsForm.uncoveredJudge", "Judge against the other light anyway")
|
|
454
504
|
})]
|
|
455
505
|
})]
|
|
456
506
|
}),
|
|
457
507
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
458
508
|
className: require_index.TEXT_HINT,
|
|
459
|
-
children:
|
|
509
|
+
children: uncoveredHint(monitor.onUncoveredCondition, t)
|
|
460
510
|
})
|
|
461
511
|
]
|
|
462
512
|
}),
|
|
@@ -465,13 +515,13 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
465
515
|
children: [
|
|
466
516
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
467
517
|
className: GROUP_TITLE,
|
|
468
|
-
children: "What you get told"
|
|
518
|
+
children: t("uiLibrary.sceneSettingsForm.whatYouGetTold", "What you get told")
|
|
469
519
|
}),
|
|
470
520
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
471
521
|
className: "flex flex-col gap-1",
|
|
472
522
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
473
523
|
className: LABEL,
|
|
474
|
-
children: "Notifications follow"
|
|
524
|
+
children: t("uiLibrary.sceneSettingsForm.notificationsFollow", "Notifications follow")
|
|
475
525
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
476
526
|
value: monitor.emit,
|
|
477
527
|
disabled,
|
|
@@ -482,16 +532,16 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
482
532
|
},
|
|
483
533
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
484
534
|
value: "latched",
|
|
485
|
-
children: "The flag — told once, still visible at breakfast"
|
|
535
|
+
children: t("uiLibrary.sceneSettingsForm.emitLatched", "The flag — told once, still visible at breakfast")
|
|
486
536
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
487
537
|
value: "live",
|
|
488
|
-
children: "The live state — follows the region as it changes"
|
|
538
|
+
children: t("uiLibrary.sceneSettingsForm.emitLive", "The live state — follows the region as it changes")
|
|
489
539
|
})]
|
|
490
540
|
})]
|
|
491
541
|
}),
|
|
492
542
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
493
543
|
className: require_index.TEXT_HINT,
|
|
494
|
-
children: "“Is the bin gone” is a flag: you want to be told once and still see it later. “Is the gate open” is a live state."
|
|
544
|
+
children: t("uiLibrary.sceneSettingsForm.emitHint", "“Is the bin gone” is a flag: you want to be told once and still see it later. “Is the gate open” is a live state.")
|
|
495
545
|
}),
|
|
496
546
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
497
547
|
className: "flex items-center gap-2",
|
|
@@ -502,12 +552,12 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
502
552
|
onChange: (e) => commit({ autoRestore: e.target.checked })
|
|
503
553
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
504
554
|
className: LABEL,
|
|
505
|
-
children: "Clear the flag by itself when things look normal again"
|
|
555
|
+
children: t("uiLibrary.sceneSettingsForm.autoRestore", "Clear the flag by itself when things look normal again")
|
|
506
556
|
})]
|
|
507
557
|
}),
|
|
508
558
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
509
559
|
className: require_index.TEXT_HINT,
|
|
510
|
-
children: "Off by default, so an alarm never silently clears itself. The “back to normal” moment is recorded either way, so an automation can still react to it."
|
|
560
|
+
children: t("uiLibrary.sceneSettingsForm.autoRestoreHint", "Off by default, so an alarm never silently clears itself. The “back to normal” moment is recorded either way, so an automation can still react to it.")
|
|
511
561
|
})
|
|
512
562
|
]
|
|
513
563
|
}),
|
|
@@ -516,7 +566,7 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
516
566
|
children: [
|
|
517
567
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
518
568
|
className: GROUP_TITLE,
|
|
519
|
-
children: "AI double-check"
|
|
569
|
+
children: t("uiLibrary.sceneSettingsForm.aiDoubleCheck", "AI double-check")
|
|
520
570
|
}),
|
|
521
571
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
522
572
|
className: "flex items-center gap-2",
|
|
@@ -530,18 +580,18 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
530
580
|
} } : { confirm: null })
|
|
531
581
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
532
582
|
className: LABEL,
|
|
533
|
-
children: "Ask a vision model before changing the answer"
|
|
583
|
+
children: t("uiLibrary.sceneSettingsForm.askVision", "Ask a vision model before changing the answer")
|
|
534
584
|
})]
|
|
535
585
|
}),
|
|
536
586
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
537
587
|
className: "flex flex-col gap-1",
|
|
538
588
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
539
589
|
className: LABEL,
|
|
540
|
-
children: "The question to ask"
|
|
590
|
+
children: t("uiLibrary.sceneSettingsForm.question", "The question to ask")
|
|
541
591
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
542
592
|
type: "text",
|
|
543
593
|
value: draft.confirmPrompt,
|
|
544
|
-
placeholder: "e.g. Is the wheelie bin still there?",
|
|
594
|
+
placeholder: t("uiLibrary.sceneSettingsForm.questionPlaceholder", "e.g. Is the wheelie bin still there?"),
|
|
545
595
|
disabled,
|
|
546
596
|
className: FIELD,
|
|
547
597
|
onFocus: () => setEditing(true),
|
|
@@ -559,13 +609,13 @@ function SceneSettingsForm({ monitor, disabled, onPatch }) {
|
|
|
559
609
|
}
|
|
560
610
|
})]
|
|
561
611
|
}),
|
|
562
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
612
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
563
613
|
className: require_index.TEXT_HINT,
|
|
564
|
-
children:
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
614
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.UiTrans, {
|
|
615
|
+
uiKey: "uiLibrary.sceneSettingsForm.confirmHint",
|
|
616
|
+
fallback: "The model is asked <0>only</0> when the region has already changed enough to flip the answer — roughly twice a week for a bin, not once a minute. It reasons about the object rather than the pixels, so rain, shadows and a repositioned bin stop counting as the thing being taken. If it cannot be reached, the change is held rather than announced.",
|
|
617
|
+
components: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {}, "only")]
|
|
618
|
+
})
|
|
569
619
|
})
|
|
570
620
|
]
|
|
571
621
|
})
|
|
@@ -588,6 +638,55 @@ function defaultConfirm(prompt) {
|
|
|
588
638
|
}
|
|
589
639
|
//#endregion
|
|
590
640
|
//#region src/composites/cap-settings/SceneMonitorEditor.tsx
|
|
641
|
+
/**
|
|
642
|
+
* SceneMonitorEditor — the per-camera half of the Scenes surface.
|
|
643
|
+
*
|
|
644
|
+
* **Imported directly, not resolved as a widget.** It briefly lived behind the
|
|
645
|
+
* host widget `host/scene-monitor-editor`, mounted as a "Scenes" tab on one
|
|
646
|
+
* camera's detail page. That put a standing question about the property ("is
|
|
647
|
+
* the bin still out") one camera deep, where the operator's actual question —
|
|
648
|
+
* "which of my scenes have tripped" — could not be asked at all. The overview
|
|
649
|
+
* is now the top-level `/scenes` page; this component is what that page opens
|
|
650
|
+
* once a camera is chosen, in a dialog. The widget id and its `deviceConfig`
|
|
651
|
+
* declaration are both gone, in the same commit, because
|
|
652
|
+
* `scripts/check-host-widget-resolves.ts` checks both directions.
|
|
653
|
+
*
|
|
654
|
+
* It renders BARE — no `WidgetPanel` — because its only caller supplies a
|
|
655
|
+
* dialog header, and two stacked titles read as a layout bug.
|
|
656
|
+
*
|
|
657
|
+
* ## The flow, in the order the operator meets it
|
|
658
|
+
*
|
|
659
|
+
* 1. **Draw a region on the still frame.** A rect over a SNAPSHOT, not the live
|
|
660
|
+
* player: a scene is defined against a photograph, and drawing on a moving
|
|
661
|
+
* picture makes it impossible to see whether the box actually contains the
|
|
662
|
+
* bin. The editor starts in draw mode and warns above ~40 % of the frame,
|
|
663
|
+
* because a full-frame scene is dominated by weather, sky and parked cars —
|
|
664
|
+
* it will not work, and it will look broken rather than misconfigured.
|
|
665
|
+
* 2. **Capture the initial state, per lighting variant.** Two buttons, Daylight
|
|
666
|
+
* and Night (IR), each forcing a fresh snapshot. The variant is DETECTED
|
|
667
|
+
* from the frame and shown rather than asked; the buttons exist so the
|
|
668
|
+
* operator can say "capture this as the night reference" while standing
|
|
669
|
+
* outside at 22:00 — which is exactly why **Night (IR) is labelled
|
|
670
|
+
* optional**. Almost nobody does that, and a scene captured only in daylight
|
|
671
|
+
* is a configuration, not a defect: under the default
|
|
672
|
+
* `onUncoveredCondition: 'skip'` the infrared hours are simply sat out.
|
|
673
|
+
* 3. **Read the coverage line.** "Daylight ✓ · Night (IR) ✗ — optional; checks
|
|
674
|
+
* in night (ir) are skipped". This feature's failure mode is silence that
|
|
675
|
+
* reads as "nothing has happened", and that one line is the difference
|
|
676
|
+
* between a five-minute fix and a session of guessing — but it must say what
|
|
677
|
+
* HAPPENS, not complain. It used to end "reports 'can't tell'", which made a
|
|
678
|
+
* correctly configured scene look broken every night.
|
|
679
|
+
* 4. **Reset.** Clears the flag AND re-captures the baseline for the current
|
|
680
|
+
* light, because the bin never goes back in exactly the same spot.
|
|
681
|
+
* 5. **Settings.** Every knob the model has actually carried since the provider
|
|
682
|
+
* shipped — how often it looks, how closely it has to match, how many
|
|
683
|
+
* agreeing checks a change costs, what a missing light means, which boolean
|
|
684
|
+
* drives notifications, and whether a vision model adjudicates a flip. None
|
|
685
|
+
* of it was reachable before; see `SceneSettingsForm`.
|
|
686
|
+
*
|
|
687
|
+
* Live state arrives through the cap's `status.kind: 'push'` runtime slice, so
|
|
688
|
+
* the card updates itself without polling.
|
|
689
|
+
*/
|
|
591
690
|
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";
|
|
592
691
|
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";
|
|
593
692
|
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";
|
|
@@ -601,6 +700,7 @@ var TONE_CLASS = {
|
|
|
601
700
|
var DEFAULT_THRESHOLD = .8;
|
|
602
701
|
var DEFAULT_HYSTERESIS = 3;
|
|
603
702
|
function SceneMonitorEditor({ deviceId }) {
|
|
703
|
+
const t = require_index.useUiText();
|
|
604
704
|
const dev = require_index.useDeviceProxy$1(require_index.useSystem$1().trpcClient, deviceId);
|
|
605
705
|
const pushed = require_index.useDeviceStateSlice$1(dev?.state.sceneMonitor);
|
|
606
706
|
const snapshot = require_index.useDeviceSnapshotImage$1(deviceId, { width: 640 });
|
|
@@ -664,9 +764,13 @@ function SceneMonitorEditor({ deviceId }) {
|
|
|
664
764
|
return draftRoi !== null ? [...existing, {
|
|
665
765
|
id: "__draft__",
|
|
666
766
|
shape: draftRoi,
|
|
667
|
-
label: "New scene"
|
|
767
|
+
label: t("uiLibrary.sceneMonitor.newSceneLabel", "New scene")
|
|
668
768
|
}] : existing;
|
|
669
|
-
}, [
|
|
769
|
+
}, [
|
|
770
|
+
monitors,
|
|
771
|
+
draftRoi,
|
|
772
|
+
t
|
|
773
|
+
]);
|
|
670
774
|
const onShapeChange = (0, react.useCallback)((id, shape) => {
|
|
671
775
|
if (shape.kind !== "rect") return;
|
|
672
776
|
if (id === "__draft__") {
|
|
@@ -709,11 +813,11 @@ function SceneMonitorEditor({ deviceId }) {
|
|
|
709
813
|
className: "flex flex-col gap-3",
|
|
710
814
|
children: unsupported ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
711
815
|
className: `${require_index.TEXT_HINT} leading-relaxed`,
|
|
712
|
-
children: "Scene monitoring is not available for this camera."
|
|
816
|
+
children: t("uiLibrary.sceneMonitor.unsupported", "Scene monitoring is not available for this camera.")
|
|
713
817
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
714
818
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
715
819
|
className: `${require_index.TEXT_HINT} leading-relaxed`,
|
|
716
|
-
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. Daylight is enough to start: the night reference is optional, and without it the scene simply sits the infrared hours out instead of reporting a problem."
|
|
820
|
+
children: t("uiLibrary.sceneMonitor.intro", "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. Daylight is enough to start: the night reference is optional, and without it the scene simply sits the infrared hours out instead of reporting a problem.")
|
|
717
821
|
}),
|
|
718
822
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
719
823
|
className: "relative overflow-hidden rounded-md border border-border",
|
|
@@ -744,13 +848,13 @@ function SceneMonitorEditor({ deviceId }) {
|
|
|
744
848
|
setDraftRoi(null);
|
|
745
849
|
},
|
|
746
850
|
disabled: busy !== null,
|
|
747
|
-
children: drafting ? "Cancel" : "+ New scene"
|
|
851
|
+
children: drafting ? t("uiLibrary.sceneMonitor.cancel", "Cancel") : t("uiLibrary.sceneMonitor.newScene", "+ New scene")
|
|
748
852
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
749
853
|
type: "button",
|
|
750
854
|
className: BTN_NEUTRAL,
|
|
751
855
|
onClick: snapshot.refresh,
|
|
752
856
|
disabled: busy !== null,
|
|
753
|
-
children: "Refresh frame"
|
|
857
|
+
children: t("uiLibrary.sceneMonitor.refresh", "Refresh frame")
|
|
754
858
|
})]
|
|
755
859
|
}),
|
|
756
860
|
drafting && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -758,11 +862,11 @@ function SceneMonitorEditor({ deviceId }) {
|
|
|
758
862
|
children: [
|
|
759
863
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
760
864
|
className: "text-[10px] text-foreground-subtle",
|
|
761
|
-
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."
|
|
865
|
+
children: draftRoi === null ? t("uiLibrary.sceneMonitor.draftHint", "Click on the frame to drop a box, then drag its corner to fit it around the thing you want watched.") : t("uiLibrary.sceneMonitor.nameHint", "Name it, then create. You capture what “normal” looks like next.")
|
|
762
866
|
}),
|
|
763
867
|
draftRoi !== null && require_index.roiTooLarge$1(draftRoi) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
764
868
|
className: "text-[11px] leading-relaxed text-warning",
|
|
765
|
-
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."
|
|
869
|
+
children: t("uiLibrary.sceneMonitor.tooLarge", "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.")
|
|
766
870
|
}),
|
|
767
871
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
768
872
|
className: "flex items-center gap-2",
|
|
@@ -770,14 +874,14 @@ function SceneMonitorEditor({ deviceId }) {
|
|
|
770
874
|
type: "text",
|
|
771
875
|
value: draftLabel,
|
|
772
876
|
onChange: (e) => setDraftLabel(e.target.value),
|
|
773
|
-
placeholder: "e.g. Immondizia prelevata",
|
|
877
|
+
placeholder: t("uiLibrary.sceneMonitor.placeholder", "e.g. Immondizia prelevata"),
|
|
774
878
|
className: "flex-1 rounded-md border border-border bg-surface px-2 py-1 text-[12px] text-foreground"
|
|
775
879
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
776
880
|
type: "button",
|
|
777
881
|
className: BTN_PRIMARY,
|
|
778
882
|
onClick: () => void createScene(),
|
|
779
883
|
disabled: draftRoi === null || draftLabel.trim().length === 0 || busy !== null,
|
|
780
|
-
children: "Create"
|
|
884
|
+
children: t("uiLibrary.sceneMonitor.create", "Create")
|
|
781
885
|
})]
|
|
782
886
|
})
|
|
783
887
|
]
|
|
@@ -788,11 +892,11 @@ function SceneMonitorEditor({ deviceId }) {
|
|
|
788
892
|
}),
|
|
789
893
|
monitors.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
790
894
|
className: `${require_index.TEXT_HINT} leading-relaxed`,
|
|
791
|
-
children: "No scenes on this camera yet."
|
|
895
|
+
children: t("uiLibrary.sceneMonitor.empty", "No scenes on this camera yet.")
|
|
792
896
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
793
897
|
className: "flex flex-col gap-2",
|
|
794
898
|
children: monitors.map((m) => {
|
|
795
|
-
const badge = require_index.badgeFor$1(m);
|
|
899
|
+
const badge = require_index.badgeFor$1(m, t);
|
|
796
900
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
797
901
|
className: `flex flex-col gap-1.5 rounded-md border p-2 ${selectedId === m.id ? "border-primary/50" : "border-border"}`,
|
|
798
902
|
children: [
|
|
@@ -810,11 +914,11 @@ function SceneMonitorEditor({ deviceId }) {
|
|
|
810
914
|
}),
|
|
811
915
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
812
916
|
className: `${require_index.TEXT_HINT} leading-relaxed`,
|
|
813
|
-
children: require_index.statusLine$1(m, now)
|
|
917
|
+
children: require_index.statusLine$1(m, now, t)
|
|
814
918
|
}),
|
|
815
919
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
816
920
|
className: `${require_index.TEXT_HINT} tabular-nums`,
|
|
817
|
-
children: require_index.coverageLine$1(m)
|
|
921
|
+
children: require_index.coverageLine$1(m, t)
|
|
818
922
|
}),
|
|
819
923
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SceneReferenceGallery, {
|
|
820
924
|
monitor: m,
|
|
@@ -834,18 +938,17 @@ function SceneMonitorEditor({ deviceId }) {
|
|
|
834
938
|
type: "button",
|
|
835
939
|
className: BTN_NEUTRAL,
|
|
836
940
|
disabled: busy !== null,
|
|
837
|
-
title: optional ? "Optional. Without a night reference the checks in infrared are simply skipped — the scene keeps working in daylight and nothing is reported as broken." :
|
|
941
|
+
title: optional ? t("uiLibrary.sceneMonitor.optionalTitle", "Optional. Without a night reference the checks in infrared are simply skipped — the scene keeps working in daylight and nothing is reported as broken.") : t("uiLibrary.sceneMonitor.captureTitle", "Take a fresh photo now and store it as the {{variant}} reference", { variant: require_index.variantLabel$1(variant, t).toLowerCase() }),
|
|
838
942
|
onClick: () => void run(`cap-${m.id}-${variant}`, async () => dev.sceneMonitor?.captureReference({
|
|
839
943
|
monitorId: m.id,
|
|
840
944
|
condition: variant
|
|
841
945
|
})),
|
|
842
946
|
children: [
|
|
843
947
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.Camera, { className: "mr-1 inline h-3 w-3" }),
|
|
844
|
-
"Capture ",
|
|
845
|
-
require_index.variantLabel$1(variant).toLowerCase(),
|
|
948
|
+
t("uiLibrary.sceneMonitor.capture", "Capture {{variant}}", { variant: require_index.variantLabel$1(variant, t).toLowerCase() }),
|
|
846
949
|
optional && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
847
950
|
className: "ml-1 text-foreground-subtle/70",
|
|
848
|
-
children: "(optional)"
|
|
951
|
+
children: t("uiLibrary.sceneMonitor.optional", "(optional)")
|
|
849
952
|
})
|
|
850
953
|
]
|
|
851
954
|
}, variant);
|
|
@@ -854,25 +957,25 @@ function SceneMonitorEditor({ deviceId }) {
|
|
|
854
957
|
type: "button",
|
|
855
958
|
className: BTN_PRIMARY,
|
|
856
959
|
disabled: busy !== null || m.states.length === 0,
|
|
857
|
-
title: "Clear the flag and re-photograph what normal looks like right now",
|
|
960
|
+
title: t("uiLibrary.sceneMonitor.resetTitle", "Clear the flag and re-photograph what normal looks like right now"),
|
|
858
961
|
onClick: () => void run(`reset-${m.id}`, async () => dev.sceneMonitor?.resetScene({ monitorId: m.id })),
|
|
859
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.RotateCcw, { className: "mr-1 inline h-3 w-3" }), "Reset"]
|
|
962
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.RotateCcw, { className: "mr-1 inline h-3 w-3" }), t("uiLibrary.sceneMonitor.reset", "Reset")]
|
|
860
963
|
}),
|
|
861
964
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
862
965
|
type: "button",
|
|
863
966
|
className: BTN_NEUTRAL,
|
|
864
967
|
disabled: busy !== null,
|
|
865
968
|
onClick: () => void run(`check-${m.id}`, async () => dev.sceneMonitor?.recheckNow({ monitorId: m.id })),
|
|
866
|
-
children: "Check now"
|
|
969
|
+
children: t("uiLibrary.sceneMonitor.checkNow", "Check now")
|
|
867
970
|
}),
|
|
868
971
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
869
972
|
type: "button",
|
|
870
973
|
className: tuningId === m.id ? BTN_PRIMARY : BTN_NEUTRAL,
|
|
871
974
|
"aria-pressed": tuningId === m.id,
|
|
872
|
-
"aria-label":
|
|
975
|
+
"aria-label": t("uiLibrary.sceneMonitor.settingsFor", "Settings for {{label}}", { label: m.label }),
|
|
873
976
|
disabled: busy !== null,
|
|
874
977
|
onClick: () => setTuningId((id) => id === m.id ? null : m.id),
|
|
875
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_settings_2.Settings2, { className: "mr-1 inline h-3 w-3" }), "Settings"]
|
|
978
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_settings_2.Settings2, { className: "mr-1 inline h-3 w-3" }), t("uiLibrary.sceneMonitor.settings", "Settings")]
|
|
876
979
|
}),
|
|
877
980
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
878
981
|
type: "button",
|