@camstack/ui-library 1.2.51 → 1.2.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,281 +0,0 @@
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
- //#region src/composites/cap-settings/SceneMonitorEditor.tsx
6
- 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";
7
- 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";
8
- 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";
9
- var TONE_CLASS = {
10
- alarm: "border-danger/50 bg-danger/15 text-danger",
11
- ok: "border-success/50 bg-success/15 text-success",
12
- unknown: "border-border bg-surface text-foreground-subtle"
13
- };
14
- /** A scene without at least one reference cannot judge anything, so the
15
- * similarity threshold only becomes meaningful after the first capture. */
16
- var DEFAULT_THRESHOLD = .8;
17
- var DEFAULT_HYSTERESIS = 3;
18
- function SceneMonitorEditor({ deviceId }) {
19
- const dev = require_index.useDeviceProxy$1(require_index.useSystem$1().trpcClient, deviceId);
20
- const pushed = require_index.useDeviceStateSlice$1(dev?.state.sceneMonitor);
21
- const snapshot = require_index.useDeviceSnapshotImage$1(deviceId, { width: 640 });
22
- const [fetched, setFetched] = (0, react.useState)(null);
23
- const [unsupported, setUnsupported] = (0, react.useState)(false);
24
- const [busy, setBusy] = (0, react.useState)(null);
25
- const [error, setError] = (0, react.useState)(null);
26
- const [selectedId, setSelectedId] = (0, react.useState)(null);
27
- const [drafting, setDrafting] = (0, react.useState)(false);
28
- const [draftRoi, setDraftRoi] = (0, react.useState)(null);
29
- const [draftLabel, setDraftLabel] = (0, react.useState)("");
30
- const mounted = (0, react.useRef)(true);
31
- (0, react.useEffect)(() => {
32
- mounted.current = true;
33
- return () => {
34
- mounted.current = false;
35
- };
36
- }, []);
37
- const reload = (0, react.useCallback)(async () => {
38
- if (!dev) return;
39
- try {
40
- const status = await dev.sceneMonitor?.listScenes({});
41
- if (!mounted.current) return;
42
- if (status) setFetched(status);
43
- } catch (err) {
44
- if (!mounted.current) return;
45
- if (require_index.isAbsentProvider$1(err)) setUnsupported(true);
46
- else setError(err instanceof Error ? err.message : String(err));
47
- }
48
- }, [dev]);
49
- (0, react.useEffect)(() => {
50
- setFetched(null);
51
- setUnsupported(false);
52
- setError(null);
53
- reload();
54
- }, [reload]);
55
- const monitors = pushed?.monitors ?? fetched?.monitors ?? [];
56
- const now = Date.now();
57
- const run = (0, react.useCallback)(async (key, fn) => {
58
- setBusy(key);
59
- setError(null);
60
- try {
61
- await fn();
62
- await reload();
63
- } catch (err) {
64
- if (mounted.current) setError(err instanceof Error ? err.message : String(err));
65
- } finally {
66
- if (mounted.current) setBusy(null);
67
- }
68
- }, [reload]);
69
- const items = (0, react.useMemo)(() => {
70
- const existing = monitors.map((m) => ({
71
- id: m.id,
72
- shape: m.roi,
73
- label: m.label,
74
- enabled: m.enabled
75
- }));
76
- return draftRoi !== null ? [...existing, {
77
- id: "__draft__",
78
- shape: draftRoi,
79
- label: "New scene"
80
- }] : existing;
81
- }, [monitors, draftRoi]);
82
- const onShapeChange = (0, react.useCallback)((id, shape) => {
83
- if (shape.kind !== "rect") return;
84
- if (id === "__draft__") {
85
- setDraftRoi(shape);
86
- return;
87
- }
88
- run(`roi-${String(id)}`, async () => dev?.sceneMonitor?.updateScene({
89
- monitorId: String(id),
90
- patch: { roi: shape }
91
- }));
92
- }, [dev, run]);
93
- const onDrawComplete = (0, react.useCallback)((shape) => {
94
- if (shape.kind !== "rect") return;
95
- setDraftRoi(shape);
96
- }, []);
97
- const createScene = (0, react.useCallback)(async () => {
98
- if (draftRoi === null || draftLabel.trim().length === 0) return;
99
- await run("create", async () => {
100
- await dev?.sceneMonitor?.createScene({
101
- label: draftLabel.trim(),
102
- roi: draftRoi,
103
- check: {
104
- mode: "similarity",
105
- threshold: DEFAULT_THRESHOLD,
106
- hysteresisCount: DEFAULT_HYSTERESIS
107
- }
108
- });
109
- });
110
- setDraftRoi(null);
111
- setDraftLabel("");
112
- setDrafting(false);
113
- }, [
114
- dev,
115
- draftRoi,
116
- draftLabel,
117
- run
118
- ]);
119
- if (!dev) return null;
120
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
121
- className: "flex flex-col gap-3",
122
- children: unsupported ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
123
- className: `${require_index.TEXT_HINT} leading-relaxed`,
124
- children: "Scene monitoring is not available for this camera."
125
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
126
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
127
- className: `${require_index.TEXT_HINT} leading-relaxed`,
128
- 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."
129
- }),
130
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
131
- className: "relative overflow-hidden rounded-md border border-border",
132
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_MaskShapeCanvas.MaskShapeCanvas, {
133
- items,
134
- supportedShapes: ["rect"],
135
- selectedId: selectedId ?? (draftRoi !== null ? "__draft__" : null),
136
- onSelect: (id) => setSelectedId(id === null ? null : String(id)),
137
- onShapeChange,
138
- onDrawComplete,
139
- drawingKind: drafting && draftRoi === null ? "rect" : null,
140
- backdrop: snapshot.src !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
141
- src: snapshot.src,
142
- alt: "",
143
- className: "h-full w-full object-contain",
144
- draggable: false
145
- }) : null
146
- })
147
- }),
148
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
149
- className: "flex flex-wrap items-center gap-2",
150
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
151
- type: "button",
152
- className: drafting ? BTN_PRIMARY : BTN_NEUTRAL,
153
- "aria-pressed": drafting,
154
- onClick: () => {
155
- setDrafting((v) => !v);
156
- setDraftRoi(null);
157
- },
158
- disabled: busy !== null,
159
- children: drafting ? "Cancel" : "+ New scene"
160
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
161
- type: "button",
162
- className: BTN_NEUTRAL,
163
- onClick: snapshot.refresh,
164
- disabled: busy !== null,
165
- children: "Refresh frame"
166
- })]
167
- }),
168
- drafting && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
169
- className: "flex flex-col gap-2 rounded-md border border-border bg-surface p-2",
170
- children: [
171
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
172
- className: "text-[10px] text-foreground-subtle",
173
- 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."
174
- }),
175
- draftRoi !== null && require_index.roiTooLarge$1(draftRoi) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
176
- className: "text-[11px] leading-relaxed text-warning",
177
- 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."
178
- }),
179
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
180
- className: "flex items-center gap-2",
181
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
182
- type: "text",
183
- value: draftLabel,
184
- onChange: (e) => setDraftLabel(e.target.value),
185
- placeholder: "e.g. Immondizia prelevata",
186
- className: "flex-1 rounded-md border border-border bg-surface px-2 py-1 text-[12px] text-foreground"
187
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
188
- type: "button",
189
- className: BTN_PRIMARY,
190
- onClick: () => void createScene(),
191
- disabled: draftRoi === null || draftLabel.trim().length === 0 || busy !== null,
192
- children: "Create"
193
- })]
194
- })
195
- ]
196
- }),
197
- error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
198
- className: "text-[11px] leading-relaxed text-danger",
199
- children: error
200
- }),
201
- monitors.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
202
- className: `${require_index.TEXT_HINT} leading-relaxed`,
203
- children: "No scenes on this camera yet."
204
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
205
- className: "flex flex-col gap-2",
206
- children: monitors.map((m) => {
207
- const badge = require_index.badgeFor$1(m);
208
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
209
- className: `flex flex-col gap-1.5 rounded-md border p-2 ${selectedId === m.id ? "border-primary/50" : "border-border"}`,
210
- children: [
211
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
212
- className: "flex items-center gap-2",
213
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
214
- type: "button",
215
- className: "flex-1 text-left text-[12px] font-medium text-foreground",
216
- onClick: () => setSelectedId(m.id),
217
- children: m.label
218
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
219
- className: `rounded border px-1.5 py-0.5 text-[10px] font-medium ${TONE_CLASS[badge.tone]}`,
220
- children: badge.text
221
- })]
222
- }),
223
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
224
- className: `${require_index.TEXT_HINT} leading-relaxed`,
225
- children: require_index.statusLine$1(m, now)
226
- }),
227
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
228
- className: `${require_index.TEXT_HINT} tabular-nums`,
229
- children: require_index.coverageLine$1(m)
230
- }),
231
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
232
- className: "flex flex-wrap items-center gap-1.5",
233
- children: [
234
- require_index.SCENE_CAPTURE_VARIANTS$1.map((variant) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
235
- type: "button",
236
- className: BTN_NEUTRAL,
237
- disabled: busy !== null,
238
- title: `Take a fresh photo now and store it as the ${require_index.variantLabel$1(variant).toLowerCase()} reference`,
239
- onClick: () => void run(`cap-${m.id}-${variant}`, async () => dev.sceneMonitor?.captureReference({
240
- monitorId: m.id,
241
- condition: variant
242
- })),
243
- children: [
244
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.Camera, { className: "mr-1 inline h-3 w-3" }),
245
- "Capture ",
246
- require_index.variantLabel$1(variant).toLowerCase()
247
- ]
248
- }, variant)),
249
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
250
- type: "button",
251
- className: BTN_PRIMARY,
252
- disabled: busy !== null || m.states.length === 0,
253
- title: "Clear the flag and re-photograph what normal looks like right now",
254
- onClick: () => void run(`reset-${m.id}`, async () => dev.sceneMonitor?.resetScene({ monitorId: m.id })),
255
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.RotateCcw, { className: "mr-1 inline h-3 w-3" }), "Reset"]
256
- }),
257
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
258
- type: "button",
259
- className: BTN_NEUTRAL,
260
- disabled: busy !== null,
261
- onClick: () => void run(`check-${m.id}`, async () => dev.sceneMonitor?.recheckNow({ monitorId: m.id })),
262
- children: "Check now"
263
- }),
264
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
265
- type: "button",
266
- className: BTN_DANGER,
267
- disabled: busy !== null,
268
- onClick: () => void run(`del-${m.id}`, async () => dev.sceneMonitor?.deleteScene({ monitorId: m.id })),
269
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.Trash2, { className: "h-3 w-3" })
270
- })
271
- ]
272
- })
273
- ]
274
- }, m.id);
275
- })
276
- })
277
- ] })
278
- });
279
- }
280
- //#endregion
281
- exports.SceneMonitorEditor = SceneMonitorEditor;
@@ -1,281 +0,0 @@
1
- import { SCENE_CAPTURE_VARIANTS, TEXT_HINT, badgeFor, coverageLine, isAbsentProvider, o as Camera, r as RotateCcw, roiTooLarge, statusLine, t as Trash2, useDeviceProxy, useDeviceSnapshotImage, useDeviceStateSlice, useSystem, variantLabel } from "./index.js";
2
- import { MaskShapeCanvas } from "./MaskShapeCanvas-DI4BY7W2.js";
3
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
- //#region src/composites/cap-settings/SceneMonitorEditor.tsx
6
- 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";
7
- 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";
8
- 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";
9
- var TONE_CLASS = {
10
- alarm: "border-danger/50 bg-danger/15 text-danger",
11
- ok: "border-success/50 bg-success/15 text-success",
12
- unknown: "border-border bg-surface text-foreground-subtle"
13
- };
14
- /** A scene without at least one reference cannot judge anything, so the
15
- * similarity threshold only becomes meaningful after the first capture. */
16
- var DEFAULT_THRESHOLD = .8;
17
- var DEFAULT_HYSTERESIS = 3;
18
- function SceneMonitorEditor({ deviceId }) {
19
- const dev = useDeviceProxy(useSystem().trpcClient, deviceId);
20
- const pushed = useDeviceStateSlice(dev?.state.sceneMonitor);
21
- const snapshot = useDeviceSnapshotImage(deviceId, { width: 640 });
22
- const [fetched, setFetched] = useState(null);
23
- const [unsupported, setUnsupported] = useState(false);
24
- const [busy, setBusy] = useState(null);
25
- const [error, setError] = useState(null);
26
- const [selectedId, setSelectedId] = useState(null);
27
- const [drafting, setDrafting] = useState(false);
28
- const [draftRoi, setDraftRoi] = useState(null);
29
- const [draftLabel, setDraftLabel] = useState("");
30
- const mounted = useRef(true);
31
- useEffect(() => {
32
- mounted.current = true;
33
- return () => {
34
- mounted.current = false;
35
- };
36
- }, []);
37
- const reload = useCallback(async () => {
38
- if (!dev) return;
39
- try {
40
- const status = await dev.sceneMonitor?.listScenes({});
41
- if (!mounted.current) return;
42
- if (status) setFetched(status);
43
- } catch (err) {
44
- if (!mounted.current) return;
45
- if (isAbsentProvider(err)) setUnsupported(true);
46
- else setError(err instanceof Error ? err.message : String(err));
47
- }
48
- }, [dev]);
49
- useEffect(() => {
50
- setFetched(null);
51
- setUnsupported(false);
52
- setError(null);
53
- reload();
54
- }, [reload]);
55
- const monitors = pushed?.monitors ?? fetched?.monitors ?? [];
56
- const now = Date.now();
57
- const run = useCallback(async (key, fn) => {
58
- setBusy(key);
59
- setError(null);
60
- try {
61
- await fn();
62
- await reload();
63
- } catch (err) {
64
- if (mounted.current) setError(err instanceof Error ? err.message : String(err));
65
- } finally {
66
- if (mounted.current) setBusy(null);
67
- }
68
- }, [reload]);
69
- const items = useMemo(() => {
70
- const existing = monitors.map((m) => ({
71
- id: m.id,
72
- shape: m.roi,
73
- label: m.label,
74
- enabled: m.enabled
75
- }));
76
- return draftRoi !== null ? [...existing, {
77
- id: "__draft__",
78
- shape: draftRoi,
79
- label: "New scene"
80
- }] : existing;
81
- }, [monitors, draftRoi]);
82
- const onShapeChange = useCallback((id, shape) => {
83
- if (shape.kind !== "rect") return;
84
- if (id === "__draft__") {
85
- setDraftRoi(shape);
86
- return;
87
- }
88
- run(`roi-${String(id)}`, async () => dev?.sceneMonitor?.updateScene({
89
- monitorId: String(id),
90
- patch: { roi: shape }
91
- }));
92
- }, [dev, run]);
93
- const onDrawComplete = useCallback((shape) => {
94
- if (shape.kind !== "rect") return;
95
- setDraftRoi(shape);
96
- }, []);
97
- const createScene = useCallback(async () => {
98
- if (draftRoi === null || draftLabel.trim().length === 0) return;
99
- await run("create", async () => {
100
- await dev?.sceneMonitor?.createScene({
101
- label: draftLabel.trim(),
102
- roi: draftRoi,
103
- check: {
104
- mode: "similarity",
105
- threshold: DEFAULT_THRESHOLD,
106
- hysteresisCount: DEFAULT_HYSTERESIS
107
- }
108
- });
109
- });
110
- setDraftRoi(null);
111
- setDraftLabel("");
112
- setDrafting(false);
113
- }, [
114
- dev,
115
- draftRoi,
116
- draftLabel,
117
- run
118
- ]);
119
- if (!dev) return null;
120
- return /* @__PURE__ */ jsx("div", {
121
- className: "flex flex-col gap-3",
122
- children: unsupported ? /* @__PURE__ */ jsx("p", {
123
- className: `${TEXT_HINT} leading-relaxed`,
124
- children: "Scene monitoring is not available for this camera."
125
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
126
- /* @__PURE__ */ jsx("p", {
127
- className: `${TEXT_HINT} leading-relaxed`,
128
- 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."
129
- }),
130
- /* @__PURE__ */ jsx("div", {
131
- className: "relative overflow-hidden rounded-md border border-border",
132
- children: /* @__PURE__ */ jsx(MaskShapeCanvas, {
133
- items,
134
- supportedShapes: ["rect"],
135
- selectedId: selectedId ?? (draftRoi !== null ? "__draft__" : null),
136
- onSelect: (id) => setSelectedId(id === null ? null : String(id)),
137
- onShapeChange,
138
- onDrawComplete,
139
- drawingKind: drafting && draftRoi === null ? "rect" : null,
140
- backdrop: snapshot.src !== null ? /* @__PURE__ */ jsx("img", {
141
- src: snapshot.src,
142
- alt: "",
143
- className: "h-full w-full object-contain",
144
- draggable: false
145
- }) : null
146
- })
147
- }),
148
- /* @__PURE__ */ jsxs("div", {
149
- className: "flex flex-wrap items-center gap-2",
150
- children: [/* @__PURE__ */ jsx("button", {
151
- type: "button",
152
- className: drafting ? BTN_PRIMARY : BTN_NEUTRAL,
153
- "aria-pressed": drafting,
154
- onClick: () => {
155
- setDrafting((v) => !v);
156
- setDraftRoi(null);
157
- },
158
- disabled: busy !== null,
159
- children: drafting ? "Cancel" : "+ New scene"
160
- }), /* @__PURE__ */ jsx("button", {
161
- type: "button",
162
- className: BTN_NEUTRAL,
163
- onClick: snapshot.refresh,
164
- disabled: busy !== null,
165
- children: "Refresh frame"
166
- })]
167
- }),
168
- drafting && /* @__PURE__ */ jsxs("div", {
169
- className: "flex flex-col gap-2 rounded-md border border-border bg-surface p-2",
170
- children: [
171
- /* @__PURE__ */ jsx("p", {
172
- className: "text-[10px] text-foreground-subtle",
173
- 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."
174
- }),
175
- draftRoi !== null && roiTooLarge(draftRoi) && /* @__PURE__ */ jsx("p", {
176
- className: "text-[11px] leading-relaxed text-warning",
177
- 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."
178
- }),
179
- /* @__PURE__ */ jsxs("div", {
180
- className: "flex items-center gap-2",
181
- children: [/* @__PURE__ */ jsx("input", {
182
- type: "text",
183
- value: draftLabel,
184
- onChange: (e) => setDraftLabel(e.target.value),
185
- placeholder: "e.g. Immondizia prelevata",
186
- className: "flex-1 rounded-md border border-border bg-surface px-2 py-1 text-[12px] text-foreground"
187
- }), /* @__PURE__ */ jsx("button", {
188
- type: "button",
189
- className: BTN_PRIMARY,
190
- onClick: () => void createScene(),
191
- disabled: draftRoi === null || draftLabel.trim().length === 0 || busy !== null,
192
- children: "Create"
193
- })]
194
- })
195
- ]
196
- }),
197
- error !== null && /* @__PURE__ */ jsx("p", {
198
- className: "text-[11px] leading-relaxed text-danger",
199
- children: error
200
- }),
201
- monitors.length === 0 ? /* @__PURE__ */ jsx("p", {
202
- className: `${TEXT_HINT} leading-relaxed`,
203
- children: "No scenes on this camera yet."
204
- }) : /* @__PURE__ */ jsx("ul", {
205
- className: "flex flex-col gap-2",
206
- children: monitors.map((m) => {
207
- const badge = badgeFor(m);
208
- return /* @__PURE__ */ jsxs("li", {
209
- className: `flex flex-col gap-1.5 rounded-md border p-2 ${selectedId === m.id ? "border-primary/50" : "border-border"}`,
210
- children: [
211
- /* @__PURE__ */ jsxs("div", {
212
- className: "flex items-center gap-2",
213
- children: [/* @__PURE__ */ jsx("button", {
214
- type: "button",
215
- className: "flex-1 text-left text-[12px] font-medium text-foreground",
216
- onClick: () => setSelectedId(m.id),
217
- children: m.label
218
- }), /* @__PURE__ */ jsx("span", {
219
- className: `rounded border px-1.5 py-0.5 text-[10px] font-medium ${TONE_CLASS[badge.tone]}`,
220
- children: badge.text
221
- })]
222
- }),
223
- /* @__PURE__ */ jsx("p", {
224
- className: `${TEXT_HINT} leading-relaxed`,
225
- children: statusLine(m, now)
226
- }),
227
- /* @__PURE__ */ jsx("p", {
228
- className: `${TEXT_HINT} tabular-nums`,
229
- children: coverageLine(m)
230
- }),
231
- /* @__PURE__ */ jsxs("div", {
232
- className: "flex flex-wrap items-center gap-1.5",
233
- children: [
234
- SCENE_CAPTURE_VARIANTS.map((variant) => /* @__PURE__ */ jsxs("button", {
235
- type: "button",
236
- className: BTN_NEUTRAL,
237
- disabled: busy !== null,
238
- title: `Take a fresh photo now and store it as the ${variantLabel(variant).toLowerCase()} reference`,
239
- onClick: () => void run(`cap-${m.id}-${variant}`, async () => dev.sceneMonitor?.captureReference({
240
- monitorId: m.id,
241
- condition: variant
242
- })),
243
- children: [
244
- /* @__PURE__ */ jsx(Camera, { className: "mr-1 inline h-3 w-3" }),
245
- "Capture ",
246
- variantLabel(variant).toLowerCase()
247
- ]
248
- }, variant)),
249
- /* @__PURE__ */ jsxs("button", {
250
- type: "button",
251
- className: BTN_PRIMARY,
252
- disabled: busy !== null || m.states.length === 0,
253
- title: "Clear the flag and re-photograph what normal looks like right now",
254
- onClick: () => void run(`reset-${m.id}`, async () => dev.sceneMonitor?.resetScene({ monitorId: m.id })),
255
- children: [/* @__PURE__ */ jsx(RotateCcw, { className: "mr-1 inline h-3 w-3" }), "Reset"]
256
- }),
257
- /* @__PURE__ */ jsx("button", {
258
- type: "button",
259
- className: BTN_NEUTRAL,
260
- disabled: busy !== null,
261
- onClick: () => void run(`check-${m.id}`, async () => dev.sceneMonitor?.recheckNow({ monitorId: m.id })),
262
- children: "Check now"
263
- }),
264
- /* @__PURE__ */ jsx("button", {
265
- type: "button",
266
- className: BTN_DANGER,
267
- disabled: busy !== null,
268
- onClick: () => void run(`del-${m.id}`, async () => dev.sceneMonitor?.deleteScene({ monitorId: m.id })),
269
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
270
- })
271
- ]
272
- })
273
- ]
274
- }, m.id);
275
- })
276
- })
277
- ] })
278
- });
279
- }
280
- //#endregion
281
- export { SceneMonitorEditor };