@camstack/addon-agent-ui 1.2.242 → 1.2.243
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/addon.js +44 -22
- package/dist/assets/{MotionZonesSettings-ke3MFEXn.js → MotionZonesSettings-DpsgVYvm.js} +1 -1
- package/dist/assets/{PrivacyMaskSettings-EKG145Wm.js → PrivacyMaskSettings-DnEaRgrq.js} +1 -1
- package/dist/assets/{SceneMonitorEditor-cZdEyak0.js → SceneMonitorEditor-DG8m4MVp.js} +1 -1
- package/dist/assets/index-A78mhhqu.css +2 -0
- package/dist/assets/index-M3f9Qo3X.js +112 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/assets/index-B_NT7bSo.js +0 -112
- package/dist/assets/index-CfJM7zHb.css +0 -2
package/dist/addon.js
CHANGED
|
@@ -5406,7 +5406,7 @@ var ZodIssueCode = {
|
|
|
5406
5406
|
var ZodFirstPartyTypeKind;
|
|
5407
5407
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5408
5408
|
//#endregion
|
|
5409
|
-
//#region ../types/dist/sleep-
|
|
5409
|
+
//#region ../types/dist/sleep-bZIhUvjd.mjs
|
|
5410
5410
|
/**
|
|
5411
5411
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5412
5412
|
* window to float samples (D455).
|
|
@@ -5523,7 +5523,7 @@ function isKeychainStoredField(field) {
|
|
|
5523
5523
|
var SECRET_CLEAR = { $secretClear: true };
|
|
5524
5524
|
/** The `'***'` token D634 redaction writes for non-admins. */
|
|
5525
5525
|
var LEGACY_DEVICE_REDACTION = "***";
|
|
5526
|
-
function isRecord$
|
|
5526
|
+
function isRecord$5(value) {
|
|
5527
5527
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5528
5528
|
}
|
|
5529
5529
|
function makeSecretMarker(ref) {
|
|
@@ -5534,12 +5534,12 @@ function makeSecretMarker(ref) {
|
|
|
5534
5534
|
}
|
|
5535
5535
|
/** A stored marker: an object whose `$secret` is a non-empty string. */
|
|
5536
5536
|
function isSecretMarker(value) {
|
|
5537
|
-
if (!isRecord$
|
|
5537
|
+
if (!isRecord$5(value)) return false;
|
|
5538
5538
|
const ref = value["$secret"];
|
|
5539
5539
|
return typeof ref === "string" && ref.length > 0;
|
|
5540
5540
|
}
|
|
5541
5541
|
function isSecretClear(value) {
|
|
5542
|
-
return isRecord$
|
|
5542
|
+
return isRecord$5(value) && value["$secretClear"] === true;
|
|
5543
5543
|
}
|
|
5544
5544
|
/**
|
|
5545
5545
|
* The UI view of a stored secret field value: a marker becomes "set", inline
|
|
@@ -6149,7 +6149,7 @@ var KeychainWriteRefusedError = class extends Error {
|
|
|
6149
6149
|
}
|
|
6150
6150
|
};
|
|
6151
6151
|
var KEEP = { kind: "keep" };
|
|
6152
|
-
function isRecord$
|
|
6152
|
+
function isRecord$4(value) {
|
|
6153
6153
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6154
6154
|
}
|
|
6155
6155
|
function defaultMintId() {
|
|
@@ -6162,7 +6162,7 @@ function collectSecretMarkerRefs(value, out = []) {
|
|
|
6162
6162
|
return out;
|
|
6163
6163
|
}
|
|
6164
6164
|
if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
|
|
6165
|
-
else if (isRecord$
|
|
6165
|
+
else if (isRecord$4(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
|
|
6166
6166
|
return out;
|
|
6167
6167
|
}
|
|
6168
6168
|
async function storedValueEquals(secrets, marker, candidate) {
|
|
@@ -6217,7 +6217,7 @@ function rowId(row, idKey) {
|
|
|
6217
6217
|
return null;
|
|
6218
6218
|
}
|
|
6219
6219
|
async function writeArray(ctx, node, incoming, stored, fieldPath) {
|
|
6220
|
-
const storedRows = Array.isArray(stored) ? stored.filter(isRecord$
|
|
6220
|
+
const storedRows = Array.isArray(stored) ? stored.filter(isRecord$4) : [];
|
|
6221
6221
|
if (!Array.isArray(incoming)) {
|
|
6222
6222
|
ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
|
|
6223
6223
|
return {
|
|
@@ -6233,7 +6233,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
|
|
|
6233
6233
|
const kept = /* @__PURE__ */ new Set();
|
|
6234
6234
|
const rows = [];
|
|
6235
6235
|
for (const row of incoming) {
|
|
6236
|
-
if (!isRecord$
|
|
6236
|
+
if (!isRecord$4(row)) {
|
|
6237
6237
|
rows.push(row);
|
|
6238
6238
|
continue;
|
|
6239
6239
|
}
|
|
@@ -6258,7 +6258,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
|
|
|
6258
6258
|
case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
|
|
6259
6259
|
case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
|
|
6260
6260
|
case "object": {
|
|
6261
|
-
if (!isRecord$
|
|
6261
|
+
if (!isRecord$4(incoming)) {
|
|
6262
6262
|
if (incoming === void 0) return KEEP;
|
|
6263
6263
|
ctx.deletes.push(...collectSecretMarkerRefs(stored));
|
|
6264
6264
|
return {
|
|
@@ -6266,7 +6266,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
|
|
|
6266
6266
|
value: incoming
|
|
6267
6267
|
};
|
|
6268
6268
|
}
|
|
6269
|
-
const storedObj = isRecord$
|
|
6269
|
+
const storedObj = isRecord$4(stored) ? stored : {};
|
|
6270
6270
|
return {
|
|
6271
6271
|
kind: "value",
|
|
6272
6272
|
value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
|
|
@@ -6389,7 +6389,7 @@ async function resolveSecretMarkers(value, secrets, options = {}) {
|
|
|
6389
6389
|
value: items
|
|
6390
6390
|
};
|
|
6391
6391
|
}
|
|
6392
|
-
if (isRecord$
|
|
6392
|
+
if (isRecord$4(v)) {
|
|
6393
6393
|
const out = {};
|
|
6394
6394
|
for (const [k, child] of Object.entries(v)) {
|
|
6395
6395
|
const s = substitute(child);
|
|
@@ -6421,7 +6421,7 @@ var SecretOverwriteRefusedError = class extends Error {
|
|
|
6421
6421
|
this.name = "SecretOverwriteRefusedError";
|
|
6422
6422
|
}
|
|
6423
6423
|
};
|
|
6424
|
-
function isRecord$
|
|
6424
|
+
function isRecord$3(value) {
|
|
6425
6425
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6426
6426
|
}
|
|
6427
6427
|
function resolvedValue(pass, marker) {
|
|
@@ -6450,7 +6450,7 @@ function inArray(pass, incoming, stored) {
|
|
|
6450
6450
|
return incoming.map((item, index) => {
|
|
6451
6451
|
const twin = stored[index];
|
|
6452
6452
|
if (isSecretMarker(twin)) return typeof item === "string" && item === resolvedValue(pass, twin) ? twin : item;
|
|
6453
|
-
if (isRecord$
|
|
6453
|
+
if (isRecord$3(item) && isRecord$3(twin)) return inArrayRow(pass, item, twin);
|
|
6454
6454
|
if (Array.isArray(item) && Array.isArray(twin)) return inArray(pass, item, twin);
|
|
6455
6455
|
return item;
|
|
6456
6456
|
});
|
|
@@ -6461,7 +6461,7 @@ function inArrayRow(pass, incoming, stored) {
|
|
|
6461
6461
|
const twin = stored[key];
|
|
6462
6462
|
if (isSecretMarker(twin)) {
|
|
6463
6463
|
if (typeof value === "string" && value === resolvedValue(pass, twin)) out[key] = twin;
|
|
6464
|
-
} else if (isRecord$
|
|
6464
|
+
} else if (isRecord$3(value) && isRecord$3(twin)) out[key] = inArrayRow(pass, value, twin);
|
|
6465
6465
|
else if (Array.isArray(value) && Array.isArray(twin)) out[key] = inArray(pass, value, twin);
|
|
6466
6466
|
}
|
|
6467
6467
|
return out;
|
|
@@ -6469,7 +6469,7 @@ function inArrayRow(pass, incoming, stored) {
|
|
|
6469
6469
|
async function walk(pass, incoming, stored) {
|
|
6470
6470
|
if (isSecretMarker(stored)) return atMarker(pass, incoming, stored);
|
|
6471
6471
|
if (Array.isArray(incoming) && Array.isArray(stored)) return inArray(pass, incoming, stored);
|
|
6472
|
-
if (!isRecord$
|
|
6472
|
+
if (!isRecord$3(incoming) || !isRecord$3(stored)) return incoming;
|
|
6473
6473
|
const out = { ...incoming };
|
|
6474
6474
|
for (const [key, value] of Object.entries(incoming)) out[key] = await walk(pass, value, stored[key]);
|
|
6475
6475
|
return out;
|
|
@@ -6500,7 +6500,7 @@ async function restoreUnchangedSecrets(input) {
|
|
|
6500
6500
|
function isJsonEqual(a, b) {
|
|
6501
6501
|
if (Object.is(a, b)) return true;
|
|
6502
6502
|
if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((v, i) => isJsonEqual(v, b[i]));
|
|
6503
|
-
if (isRecord$
|
|
6503
|
+
if (isRecord$3(a) && isRecord$3(b)) {
|
|
6504
6504
|
const keys = Object.keys(a);
|
|
6505
6505
|
if (keys.length !== Object.keys(b).length) return false;
|
|
6506
6506
|
return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && isJsonEqual(a[k], b[k]));
|
|
@@ -6524,7 +6524,7 @@ function markerKeysOf(blob, owner) {
|
|
|
6524
6524
|
}
|
|
6525
6525
|
function nodeFromStored(value, owner) {
|
|
6526
6526
|
if (isSecretMarker(value)) return isSecretRefOwnedBy(value.$secret, owner) ? { kind: "leaf" } : null;
|
|
6527
|
-
if (!isRecord$
|
|
6527
|
+
if (!isRecord$3(value)) return null;
|
|
6528
6528
|
const children = /* @__PURE__ */ new Map();
|
|
6529
6529
|
for (const [key, child] of Object.entries(value)) {
|
|
6530
6530
|
const node = nodeFromStored(child, owner);
|
|
@@ -7312,7 +7312,7 @@ var BaseAddon = class {
|
|
|
7312
7312
|
if (!ctx) return;
|
|
7313
7313
|
const addonId = ctx.id;
|
|
7314
7314
|
setImmediate(() => {
|
|
7315
|
-
ctx.api.addons
|
|
7315
|
+
ctx.api.addons.restartAddon.mutate({ addonId }).then(() => {
|
|
7316
7316
|
ctx.logger.info("addon auto-restart triggered by restart-required setting change", { meta: { changedFields: patchKeys.filter((k) => restartKeys.has(k)) } });
|
|
7317
7317
|
}).catch((err) => {
|
|
7318
7318
|
ctx.logger.error("addon auto-restart failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
@@ -16340,13 +16340,23 @@ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: arr
|
|
|
16340
16340
|
}), RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
16341
16341
|
owner: string().min(1),
|
|
16342
16342
|
kind: RecordKindNameSchema,
|
|
16343
|
-
records: array(RecordInputSchema).
|
|
16343
|
+
records: array(RecordInputSchema).max(RECORD_BATCH_MAX),
|
|
16344
16344
|
/** The LAST batch: the copy is complete, the boot guard stops asking. */
|
|
16345
16345
|
complete: boolean().optional()
|
|
16346
|
-
}), object({ adopted: number().int() }), {
|
|
16346
|
+
}).refine((input) => input.records.length > 0 || input.complete === true, { message: "records must be non-empty unless complete is true" }), object({ adopted: number().int() }), {
|
|
16347
16347
|
kind: "mutation",
|
|
16348
16348
|
auth: "admin"
|
|
16349
|
-
})
|
|
16349
|
+
}), method(object({
|
|
16350
|
+
collection: string().min(1),
|
|
16351
|
+
cursor: string().optional(),
|
|
16352
|
+
limit: number().int().min(1).max(1e3).optional()
|
|
16353
|
+
}), object({
|
|
16354
|
+
rows: array(object({
|
|
16355
|
+
id: string(),
|
|
16356
|
+
data: record(string(), unknown())
|
|
16357
|
+
})),
|
|
16358
|
+
nextCursor: string().nullable()
|
|
16359
|
+
}), { auth: "admin" }), method(object({ collection: string().min(1) }), object({ adopted: boolean() }), { auth: "admin" });
|
|
16350
16360
|
/**
|
|
16351
16361
|
* record-entries - the ONE admin surface for every list of user-created entries
|
|
16352
16362
|
* (D881): notification targets, brokers, LLM profiles, anything an addon
|
|
@@ -68318,6 +68328,12 @@ Object.freeze({
|
|
|
68318
68328
|
addonId: null,
|
|
68319
68329
|
access: "delete"
|
|
68320
68330
|
},
|
|
68331
|
+
"storageAdmin.isAdoptedLegacyTable": {
|
|
68332
|
+
capName: "storage-admin",
|
|
68333
|
+
capScope: "system",
|
|
68334
|
+
addonId: null,
|
|
68335
|
+
access: "view"
|
|
68336
|
+
},
|
|
68321
68337
|
"storageAdmin.listKinds": {
|
|
68322
68338
|
capName: "storage-admin",
|
|
68323
68339
|
capScope: "system",
|
|
@@ -68330,6 +68346,12 @@ Object.freeze({
|
|
|
68330
68346
|
addonId: null,
|
|
68331
68347
|
access: "view"
|
|
68332
68348
|
},
|
|
68349
|
+
"storageAdmin.readLegacyCollection": {
|
|
68350
|
+
capName: "storage-admin",
|
|
68351
|
+
capScope: "system",
|
|
68352
|
+
addonId: null,
|
|
68353
|
+
access: "view"
|
|
68354
|
+
},
|
|
68333
68355
|
"storageAdmin.readRecords": {
|
|
68334
68356
|
capName: "storage-admin",
|
|
68335
68357
|
capScope: "system",
|
|
@@ -73558,7 +73580,7 @@ var AgentUIAddon = class extends BaseAddon {
|
|
|
73558
73580
|
capability: adminUiCapability,
|
|
73559
73581
|
provider: {
|
|
73560
73582
|
getStaticDir: async () => ({ staticDir: path.resolve(__dirname) }),
|
|
73561
|
-
getVersion: async () => ({ version: "1.2.
|
|
73583
|
+
getVersion: async () => ({ version: "1.2.243" })
|
|
73562
73584
|
}
|
|
73563
73585
|
}];
|
|
73564
73586
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{C as a,c as o,d as s,h as c,l,p as u,t as ee}from"./index-
|
|
1
|
+
import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{C as a,c as o,d as s,h as c,l,p as u,t as ee}from"./index-M3f9Qo3X.js";import{MaskShapeCanvas as d}from"./MaskShapeCanvas-BTvl6Rin.js";var f=a(`grid-2x2`,[[`path`,{d:`M12 3v18`,key:`108xh3`}],[`path`,{d:`M3 12h18`,key:`1i2n21`}],[`rect`,{x:`3`,y:`3`,width:`18`,height:`18`,rx:`2`,key:`h1oib`}]]),p=n(i(),1),m=n(e(),1),h=110,g=`motion-zones`,_=0,te=[1,2,3],v=1,y=`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`,b=`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`;function x(e,t,n){let r=t*n,i=Array.from({length:r});for(let t=0;t<r;t+=1)i[t]=e[t]===!0;return i}function S(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}function C(e,t){return Math.ceil(e/t)}function w(e,t,n){return Math.min(n-1,Math.floor((e+.5)/t*n))}function T(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:i*a},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)e[r*t+n]===!0&&(o[s*i+w(n,t,i)]=!0)}return o}function E(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:t*n},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)o[r*t+n]=e[s*i+w(n,t,i)]===!0}return o}function D({deviceId:e}){let n=t(),i=s(c().trpcClient,e),[a,w]=(0,p.useState)(null),[D,O]=(0,p.useState)(!1),[k,A]=(0,p.useState)(null),[j,M]=(0,p.useState)(null),[N,P]=(0,p.useState)(v),[F,I]=(0,p.useState)(!1),[L,R]=(0,p.useState)(!1),z=(0,p.useRef)(!1);(0,p.useEffect)(()=>{if(!i)return;let e=!1;return z.current=!1,w(null),O(!1),A(null),M(null),P(v),R(!1),(async()=>{try{let t=await i.motionZones?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(w(t),z.current)return;let n=await i.motionZones?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);z.current=!0;let r=n.regions.find(e=>e.shape.kind===`grid`),a=x(r?r.shape.cells:[],t.grid.width,t.grid.height);M(a),A(T(a,t.grid.width,t.grid.height,v))}catch(t){if(e)return;l(t)?O(!0):console.error(`Motion Zones load failed`,t)}})(),()=>{e=!0}},[i]);let B=a?a.grid.width:0,V=a?a.grid.height:0,H=C(B,N),U=C(V,N),W=(0,p.useMemo)(()=>k&&a?E(k,B,V,N):null,[k,a,B,V,N]),G=(0,p.useMemo)(()=>W!==null&&j!==null&&!S(W,j),[W,j]),K=(0,p.useMemo)(()=>W?W.reduce((e,t)=>t?e+1:e,0):0,[W]),q=B*V,J=H*U,Y=(0,p.useCallback)(e=>{J>0&&A(Array.from({length:J},()=>e))},[J]),X=(0,p.useCallback)(()=>{A(e=>e&&e.map(e=>!e))},[]),Z=(0,p.useCallback)(()=>{j&&a&&A(T(j,B,V,N))},[j,a,B,V,N]),ne=(0,p.useCallback)(e=>{e===N||!a||A(t=>{if(!t)return P(e),t;let n=T(E(t,B,V,N),B,V,e);return P(e),n})},[N,a,B,V]),Q=(0,p.useMemo)(()=>a&&k?[{id:_,shape:{kind:`grid`,gridWidth:H,gridHeight:U,cells:[...k]}}]:[],[a,k,H,U]),$=(0,p.useCallback)((e,t)=>{t.kind===`grid`&&A(t.cells)},[]),re=(0,p.useCallback)(async()=>{if(!(!i||!k||!a)){I(!0);try{let e=E(k,a.grid.width,a.grid.height,N),t={kind:`grid`,gridWidth:a.grid.width,gridHeight:a.grid.height,cells:e};await i.motionZones?.setZone({patch:{regions:[{id:_,enabled:!0,shape:t}]}});let n=await i.motionZones?.getStatus({});if(n){let e=n.regions.find(e=>e.shape.kind===`grid`),t=x(e?e.shape.cells:[],a.grid.width,a.grid.height);M(t),A(T(t,a.grid.width,a.grid.height,N))}}catch(e){console.error(`Motion Zones save failed`,e)}finally{I(!1)}}},[i,k,a,N]);ee((0,p.useMemo)(()=>L&&!D&&a&&k?{id:g,order:h,node:(0,m.jsx)(d,{transparent:!0,items:Q,supportedShapes:[`grid`],grid:{width:H,height:U},selectedId:_,onSelect:()=>{},onShapeChange:$,onDrawComplete:()=>{},drawingKind:null})}:null,[L,D,a,k,Q,$,H,U]));let ie=!D&&a!==null&&k!==null;return i?(0,m.jsx)(u,{title:n(`uiLibrary.motionZones.title`,`Motion Zones`),icon:(0,m.jsx)(f,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,m.jsx)(`div`,{className:`flex flex-col gap-3`,children:D?(0,m.jsx)(`p`,{className:`${o} leading-relaxed`,children:n(`uiLibrary.motionZones.unsupported`,`This camera doesn't expose an on-board motion zones grid.`)}):ie?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`p`,{className:`${o} leading-relaxed`,children:(0,m.jsx)(r,{uiKey:`uiLibrary.motionZones.intro`,fallback:`Toggle <0>Edit grid</0> to paint the region directly on the live frame, then <1>Save</1> to push the mask to the camera. Pick a bigger <2>Cell size</2> for quicker, broad-stroke painting — it's resampled to the camera's native {{width}}×{{height}} grid on save (×1 is the finest).`,components:[(0,m.jsx)(`strong`,{className:`text-foreground`},`e`),(0,m.jsx)(`strong`,{className:`text-foreground`},`s`),(0,m.jsx)(`strong`,{className:`text-foreground`},`c`)],values:{width:a.grid.width,height:a.grid.height}})}),(0,m.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,m.jsx)(`button`,{type:`button`,onClick:()=>R(e=>!e),disabled:F,"aria-pressed":L,className:L?b:y,children:L?n(`uiLibrary.motionZones.doneEditing`,`Done editing`):n(`uiLibrary.motionZones.editGrid`,`Edit grid`)}),(0,m.jsx)(`button`,{type:`button`,onClick:()=>Y(!0),disabled:F,className:y,children:n(`uiLibrary.motionZones.allOn`,`All on`)}),(0,m.jsx)(`button`,{type:`button`,onClick:()=>Y(!1),disabled:F,className:y,children:n(`uiLibrary.motionZones.allOff`,`All off`)}),(0,m.jsx)(`button`,{type:`button`,onClick:X,disabled:F,className:y,children:n(`uiLibrary.motionZones.invert`,`Invert`)}),(0,m.jsxs)(`div`,{className:`flex items-center gap-1 ml-1`,role:`group`,"aria-label":n(`uiLibrary.motionZones.cellSize`,`Cell size`),children:[(0,m.jsx)(`span`,{className:`${o} mr-0.5`,children:n(`uiLibrary.motionZones.cellSize`,`Cell size`)}),te.map(e=>(0,m.jsxs)(`button`,{type:`button`,onClick:()=>ne(e),disabled:F,"aria-pressed":N===e,title:e===1?n(`uiLibrary.motionZones.cameraGrid`,`Camera grid {{width}}×{{height}} (finest)`,{width:B,height:V}):n(`uiLibrary.motionZones.paintingGrid`,`{{width}}×{{height}} painting grid · cells ×{{factor}} bigger`,{width:C(B,e),height:C(V,e),factor:e}),className:N===e?b:y,children:[`×`,e]},e))]}),(0,m.jsxs)(`span`,{className:`${o} ml-1 tabular-nums`,children:[n(`uiLibrary.motionZones.cells`,`{{active}} / {{total}} cells · {{width}}×{{height}}`,{active:K,total:q,width:a.grid.width,height:a.grid.height}),N===1?``:n(`uiLibrary.motionZones.paintSuffix`,` · paint {{width}}×{{height}}`,{width:H,height:U})]}),(0,m.jsx)(`span`,{className:`flex-1`}),(0,m.jsx)(`button`,{type:`button`,onClick:Z,disabled:F||!G,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:n(`uiLibrary.motionZones.revert`,`Revert`)}),(0,m.jsx)(`button`,{type:`button`,onClick:()=>void re(),disabled:F||!G,className:b,children:F?n(`uiLibrary.motionZones.saving`,`Saving…`):n(`uiLibrary.motionZones.save`,`Save`)})]})]}):(0,m.jsx)(`p`,{className:`${o} leading-relaxed`,children:n(`uiLibrary.motionZones.loading`,`Loading the camera's grid…`)})})}):null}export{D as MotionZonesSettings};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{C as a,_ as ee,c as o,d as te,g as ne,h as s,l as c,p as re,t as l,x as u,y as d}from"./index-B_NT7bSo.js";import{MaskShapeCanvas as ie}from"./MaskShapeCanvas-BTvl6Rin.js";var ae=a(`hexagon`,[[`path`,{d:`M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z`,key:`yt0hxn`}]]),oe=a(`mic-off`,[[`path`,{d:`M12 19v3`,key:`npa21l`}],[`path`,{d:`M15 9.34V5a3 3 0 0 0-5.68-1.33`,key:`1gzdoj`}],[`path`,{d:`M16.95 16.95A7 7 0 0 1 5 12v-2`,key:`cqa7eg`}],[`path`,{d:`M18.89 13.23A7 7 0 0 0 19 12v-2`,key:`16hl24`}],[`path`,{d:`m2 2 20 20`,key:`1ooewy`}],[`path`,{d:`M9 9v3a3 3 0 0 0 5.12 2.12`,key:`r2i35w`}]]),f=n(i(),1),p=`Microphone`,m=`The camera encodes no audio at all — live view, recordings, exports and the audio analyzer are all silent.`;function se(e){let t=e.supportsAudioMute===!0,n=t?e.audioEnabled:null,r=e.pendingOn??null;return{label:p,supported:t,on:n===null?null:r??n,available:t&&n!==null,costWhenOff:m}}function ce(e){return!e.available||e.on===null?null:{enabled:!e.on}}var h=n(e(),1),le=120,ue=`privacy-mask`,g=`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`,_=`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`;function v(e){return e.kind===`rect`||e.kind===`polygon`?e:null}function de(e){let t=new Set(e.map(e=>e.id)),n=0;for(;t.has(n);)n+=1;return n}function fe(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(JSON.stringify(e[n])!==JSON.stringify(t[n]))return!1;return!0}function y({deviceId:e}){let n=t(),i=te(s().trpcClient,e),[a,p]=(0,f.useState)(null),[m,y]=(0,f.useState)(!1),[b,x]=(0,f.useState)(!1),[S,C]=(0,f.useState)(null),[w,T]=(0,f.useState)(null),[E,D]=(0,f.useState)(!1),[O,k]=(0,f.useState)(!1),[A,j]=(0,f.useState)(null),[M,N]=(0,f.useState)(null),[pe,P]=(0,f.useState)(null),[F,I]=(0,f.useState)(null),[L,R]=(0,f.useState)(null),z=(0,f.useRef)(!1);(0,f.useEffect)(()=>{if(!i)return;let e=!1;return z.current=!1,p(null),y(!1),x(!1),C(null),T(null),k(!1),j(null),N(null),P(null),I(null),R(null),(async()=>{try{let t=await i.privacyMask?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(p(t),z.current)return;let n=await i.privacyMask?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);if(n.regions===null){x(!0),P(n.audioEnabled);return}z.current=!0;let r={enabled:n.enabled,regions:n.regions};T(r),C(r),P(n.audioEnabled)}catch(t){if(e)return;c(t)?y(!0):console.error(`Privacy Mask load failed`,t)}})(),()=>{e=!0}},[i]);let B=(0,f.useMemo)(()=>S!==null&&w!==null&&(S.enabled!==w.enabled||!fe(S.regions,w.regions)),[S,w]),V=S?S.regions.length:0,H=a?a.maxRegions:0,U=(0,f.useRef)(0);(0,f.useEffect)(()=>{U.current=H},[H]);let W=H>0&&V>=H,me=(0,f.useCallback)(()=>{C(e=>e&&{...e,enabled:!e.enabled})},[]),G=(0,f.useCallback)(e=>{j(typeof e==`number`?e:null)},[]),K=(0,f.useMemo)(()=>S?S.regions.map(e=>({id:e.id,shape:e.shape,enabled:e.enabled,label:n(`uiLibrary.privacyMaskSettings.zoneN`,`Zone {{id}}`,{id:e.id})})):[],[S,n]),q=(0,f.useCallback)((e,t)=>{let n=v(t);n&&C(t=>t&&{...t,regions:t.regions.map(t=>t.id===e?{...t,shape:n}:t)})},[]),J=(0,f.useCallback)(e=>{let t=v(e);t&&(N(null),C(e=>{if(!e)return e;let n=U.current;if(n>0&&e.regions.length>=n)return e;let r=de(e.regions),i={id:r,enabled:!0,shape:t};return j(r),{...e,regions:[...e.regions,i]}}))},[]),Y=(0,f.useCallback)(e=>{k(!0),j(null),N(e)},[]),he=(0,f.useCallback)(e=>{k(!0),N(null),j(e)},[]),ge=(0,f.useCallback)(e=>{j(t=>t===e?null:t),C(t=>t&&{...t,regions:t.regions.filter(t=>t.id!==e)})},[]),_e=(0,f.useCallback)(()=>{N(null),j(null),w&&C({enabled:w.enabled,regions:w.regions})},[w]),X=(0,f.useCallback)(async()=>{if(!(!i||!S)){D(!0);try{await i.privacyMask?.setMask({patch:{enabled:S.enabled,regions:[...S.regions]}});let e=await i.privacyMask?.getStatus({});if(e&&e.regions!==null){let t={enabled:e.enabled,regions:e.regions};T(t),C(t)}}catch(e){console.error(`Privacy Mask save failed`,e)}finally{D(!1)}}},[i,S]),Z=se({supportsAudioMute:a===null?null:a.supportsAudioMute,audioEnabled:pe,pendingOn:F}),ve=(0,f.useCallback)(async()=>{let e=ce(Z);if(!(!i||e===null)){I(e.enabled),R(null);try{await i.privacyMask?.setAudioEnabled(e);let t=await i.privacyMask?.getStatus({});t&&P(t.audioEnabled)}catch(e){console.error(`Privacy microphone write failed — reverting`,e),R(e instanceof Error?e.message:String(e))}finally{I(null)}}},[i,Z]),ye=(0,f.useCallback)(()=>{k(e=>(e&&(N(null),j(null)),!e))},[]),Q=a?.supportedShapes??[];l((0,f.useMemo)(()=>O&&!m&&a&&S?{id:ue,order:le,node:(0,h.jsx)(ie,{transparent:!0,items:K,supportedShapes:Q,polygonVertices:a.polygonVertices,selectedId:A,onSelect:G,onShapeChange:q,onDrawComplete:J,drawingKind:M})}:null,[O,m,a,S,K,Q,A,G,q,J,M]));let be=a?.supportedShapes.includes(`rect`)??!1,xe=a?.supportedShapes.includes(`polygon`)??!1,$=a!==null&&(a.maxRegions<=0||a.supportedShapes.length===0),Se=!m&&!$&&!b&&a!==null&&S!==null;return i?(0,h.jsx)(re,{title:n(`uiLibrary.privacyMaskSettings.title`,`Privacy Mask`),icon:(0,h.jsx)(u,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,h.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[m||$?(0,h.jsx)(`p`,{className:`${o} leading-relaxed`,children:n(`uiLibrary.privacyMaskSettings.unsupported`,`This camera doesn't support an on-board privacy mask.`)}):b?(0,h.jsx)(`p`,{className:`${o} leading-relaxed`,children:n(`uiLibrary.privacyMaskSettings.unreadable`,`This camera hasn't answered, so what it currently masks is unknown — the editor stays closed rather than showing an empty frame you could save over. A battery camera answers once it wakes; otherwise check that it is reachable.`)}):Se?(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(`p`,{className:`${o} leading-relaxed`,children:(0,h.jsx)(r,{uiKey:`uiLibrary.privacyMaskSettings.help`,fallback:`Toggle <0>Edit mask</0> to draw blanked-out zones on the live frame, then <1>Save</1> to push them to the camera. Drag a rectangle to move, its corner to resize; drag polygon vertices, click an edge midpoint to add one, or right-click a vertex to remove it.`,components:[(0,h.jsx)(`strong`,{className:`text-foreground`},`edit`),(0,h.jsx)(`strong`,{className:`text-foreground`},`save`)]})}),(0,h.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,h.jsx)(`button`,{type:`button`,onClick:ye,disabled:E,"aria-pressed":O,className:O?_:g,children:O?n(`uiLibrary.privacyMaskSettings.doneEditing`,`Done editing`):n(`uiLibrary.privacyMaskSettings.editMask`,`Edit mask`)}),(0,h.jsx)(`button`,{type:`button`,onClick:me,disabled:E,"aria-pressed":S.enabled,className:S.enabled?_:g,children:S.enabled?n(`uiLibrary.privacyMaskSettings.maskOn`,`Mask on`):n(`uiLibrary.privacyMaskSettings.maskOff`,`Mask off`)}),be&&(0,h.jsx)(`button`,{type:`button`,onClick:()=>Y(`rect`),disabled:E||W,"aria-pressed":M===`rect`,className:M===`rect`?_:g,title:W?n(`uiLibrary.privacyMaskSettings.maxZones`,`Maximum zones reached`):n(`uiLibrary.privacyMaskSettings.addRect`,`Add a rectangle zone`),children:n(`uiLibrary.privacyMaskSettings.rectButton`,`+ Rect`)}),xe&&(0,h.jsx)(`button`,{type:`button`,onClick:()=>Y(`polygon`),disabled:E||W,"aria-pressed":M===`polygon`,className:M===`polygon`?_:g,title:W?n(`uiLibrary.privacyMaskSettings.maxZones`,`Maximum zones reached`):n(`uiLibrary.privacyMaskSettings.addPolygon`,`Add a polygon zone`),children:n(`uiLibrary.privacyMaskSettings.polygonButton`,`+ Polygon`)}),(0,h.jsx)(`span`,{className:`${o} ml-1 tabular-nums`,children:n(`uiLibrary.privacyMaskSettings.zoneCount`,`{{active}} / {{max}} zones`,{active:V,max:H})}),(0,h.jsx)(`span`,{className:`flex-1`}),(0,h.jsx)(`button`,{type:`button`,onClick:_e,disabled:E||!B,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:n(`uiLibrary.privacyMaskSettings.revert`,`Revert`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>void X(),disabled:E||!B,className:_,children:E?n(`uiLibrary.privacyMaskSettings.saving`,`Saving…`):n(`uiLibrary.privacyMaskSettings.save`,`Save`)})]}),V>0?(0,h.jsx)(`div`,{className:`flex flex-col gap-1`,children:S.regions.map(e=>{let t=A===e.id,r=e.shape.kind===`polygon`?ae:ee;return(0,h.jsxs)(`div`,{className:`flex items-center gap-2 rounded-md border px-2 py-1 transition-colors ${t?`border-primary/50 bg-primary/10`:`border-border bg-surface`}`,children:[(0,h.jsxs)(`button`,{type:`button`,onClick:()=>he(e.id),disabled:E,className:`flex flex-1 items-center gap-2 text-left text-[11px] font-medium text-foreground-subtle hover:text-foreground disabled:opacity-40 transition-colors`,children:[(0,h.jsx)(r,{className:`h-3.5 w-3.5 shrink-0`}),(0,h.jsx)(`span`,{children:n(`uiLibrary.privacyMaskSettings.zoneN`,`Zone {{id}}`,{id:e.id})}),(0,h.jsx)(`span`,{className:`text-foreground-faint capitalize`,children:e.shape.kind})]}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>ge(e.id),disabled:E,"aria-label":n(`uiLibrary.privacyMaskSettings.deleteZoneN`,`Delete zone {{id}}`,{id:e.id}),title:n(`uiLibrary.privacyMaskSettings.deleteZone`,`Delete zone`),className:`inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-surface text-foreground-subtle hover:border-red-400/40 hover:bg-red-500/10 hover:text-red-400 disabled:opacity-40 transition-colors`,children:(0,h.jsx)(ne,{className:`h-3.5 w-3.5`})})]},e.id)})}):null]}):(0,h.jsx)(`p`,{className:`${o} leading-relaxed`,children:n(`uiLibrary.privacyMaskSettings.loading`,`Loading the camera's privacy mask…`)}),Z.supported&&(0,h.jsxs)(`div`,{className:`flex items-start gap-3 rounded-md border border-border bg-surface px-2 py-2`,children:[(0,h.jsx)(`button`,{type:`button`,role:`switch`,"aria-label":Z.label,"aria-checked":Z.on===!0,disabled:!Z.available||F!==null,onClick:()=>void ve(),title:Z.on===null?n(`uiLibrary.privacyMaskSettings.micTitleUnknown`,`Microphone — state unknown; the camera did not answer.`):Z.on?n(`uiLibrary.privacyMaskSettings.micTitleOn`,`The camera is capturing sound. Turning it off: {{cost}}`,{cost:Z.costWhenOff}):n(`uiLibrary.privacyMaskSettings.micTitleOff`,`The camera is silent. {{cost}}`,{cost:Z.costWhenOff}),className:Z.on===!1||Z.on===null?g:_,children:(0,h.jsxs)(`span`,{className:`inline-flex items-center gap-1.5`,children:[Z.on===!1?(0,h.jsx)(oe,{className:`h-3.5 w-3.5`}):(0,h.jsx)(d,{className:`h-3.5 w-3.5`}),Z.on===null?n(`uiLibrary.privacyMaskSettings.micUnknown`,`Microphone unknown`):Z.on?n(`uiLibrary.privacyMaskSettings.micOn`,`Microphone on`):n(`uiLibrary.privacyMaskSettings.micOff`,`Microphone off`)]})}),(0,h.jsx)(`p`,{className:`text-[10px] text-foreground-subtle flex-1 leading-relaxed`,children:L===null?Z.on===null?n(`uiLibrary.privacyMaskSettings.micNotReported`,`The camera did not report its microphone, so this state is unknown.`):Z.costWhenOff:n(`uiLibrary.privacyMaskSettings.writeFailed`,`Write failed: {{reason}}`,{reason:L})})]})]})}):null}export{y as PrivacyMaskSettings};
|
|
1
|
+
import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{C as a,_ as ee,c as o,d as te,g as ne,h as s,l as c,p as re,t as l,x as u,y as d}from"./index-M3f9Qo3X.js";import{MaskShapeCanvas as ie}from"./MaskShapeCanvas-BTvl6Rin.js";var ae=a(`hexagon`,[[`path`,{d:`M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z`,key:`yt0hxn`}]]),oe=a(`mic-off`,[[`path`,{d:`M12 19v3`,key:`npa21l`}],[`path`,{d:`M15 9.34V5a3 3 0 0 0-5.68-1.33`,key:`1gzdoj`}],[`path`,{d:`M16.95 16.95A7 7 0 0 1 5 12v-2`,key:`cqa7eg`}],[`path`,{d:`M18.89 13.23A7 7 0 0 0 19 12v-2`,key:`16hl24`}],[`path`,{d:`m2 2 20 20`,key:`1ooewy`}],[`path`,{d:`M9 9v3a3 3 0 0 0 5.12 2.12`,key:`r2i35w`}]]),f=n(i(),1),p=`Microphone`,m=`The camera encodes no audio at all — live view, recordings, exports and the audio analyzer are all silent.`;function se(e){let t=e.supportsAudioMute===!0,n=t?e.audioEnabled:null,r=e.pendingOn??null;return{label:p,supported:t,on:n===null?null:r??n,available:t&&n!==null,costWhenOff:m}}function ce(e){return!e.available||e.on===null?null:{enabled:!e.on}}var h=n(e(),1),le=120,ue=`privacy-mask`,g=`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`,_=`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`;function v(e){return e.kind===`rect`||e.kind===`polygon`?e:null}function de(e){let t=new Set(e.map(e=>e.id)),n=0;for(;t.has(n);)n+=1;return n}function fe(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(JSON.stringify(e[n])!==JSON.stringify(t[n]))return!1;return!0}function y({deviceId:e}){let n=t(),i=te(s().trpcClient,e),[a,p]=(0,f.useState)(null),[m,y]=(0,f.useState)(!1),[b,x]=(0,f.useState)(!1),[S,C]=(0,f.useState)(null),[w,T]=(0,f.useState)(null),[E,D]=(0,f.useState)(!1),[O,k]=(0,f.useState)(!1),[A,j]=(0,f.useState)(null),[M,N]=(0,f.useState)(null),[pe,P]=(0,f.useState)(null),[F,I]=(0,f.useState)(null),[L,R]=(0,f.useState)(null),z=(0,f.useRef)(!1);(0,f.useEffect)(()=>{if(!i)return;let e=!1;return z.current=!1,p(null),y(!1),x(!1),C(null),T(null),k(!1),j(null),N(null),P(null),I(null),R(null),(async()=>{try{let t=await i.privacyMask?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(p(t),z.current)return;let n=await i.privacyMask?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);if(n.regions===null){x(!0),P(n.audioEnabled);return}z.current=!0;let r={enabled:n.enabled,regions:n.regions};T(r),C(r),P(n.audioEnabled)}catch(t){if(e)return;c(t)?y(!0):console.error(`Privacy Mask load failed`,t)}})(),()=>{e=!0}},[i]);let B=(0,f.useMemo)(()=>S!==null&&w!==null&&(S.enabled!==w.enabled||!fe(S.regions,w.regions)),[S,w]),V=S?S.regions.length:0,H=a?a.maxRegions:0,U=(0,f.useRef)(0);(0,f.useEffect)(()=>{U.current=H},[H]);let W=H>0&&V>=H,me=(0,f.useCallback)(()=>{C(e=>e&&{...e,enabled:!e.enabled})},[]),G=(0,f.useCallback)(e=>{j(typeof e==`number`?e:null)},[]),K=(0,f.useMemo)(()=>S?S.regions.map(e=>({id:e.id,shape:e.shape,enabled:e.enabled,label:n(`uiLibrary.privacyMaskSettings.zoneN`,`Zone {{id}}`,{id:e.id})})):[],[S,n]),q=(0,f.useCallback)((e,t)=>{let n=v(t);n&&C(t=>t&&{...t,regions:t.regions.map(t=>t.id===e?{...t,shape:n}:t)})},[]),J=(0,f.useCallback)(e=>{let t=v(e);t&&(N(null),C(e=>{if(!e)return e;let n=U.current;if(n>0&&e.regions.length>=n)return e;let r=de(e.regions),i={id:r,enabled:!0,shape:t};return j(r),{...e,regions:[...e.regions,i]}}))},[]),Y=(0,f.useCallback)(e=>{k(!0),j(null),N(e)},[]),he=(0,f.useCallback)(e=>{k(!0),N(null),j(e)},[]),ge=(0,f.useCallback)(e=>{j(t=>t===e?null:t),C(t=>t&&{...t,regions:t.regions.filter(t=>t.id!==e)})},[]),_e=(0,f.useCallback)(()=>{N(null),j(null),w&&C({enabled:w.enabled,regions:w.regions})},[w]),X=(0,f.useCallback)(async()=>{if(!(!i||!S)){D(!0);try{await i.privacyMask?.setMask({patch:{enabled:S.enabled,regions:[...S.regions]}});let e=await i.privacyMask?.getStatus({});if(e&&e.regions!==null){let t={enabled:e.enabled,regions:e.regions};T(t),C(t)}}catch(e){console.error(`Privacy Mask save failed`,e)}finally{D(!1)}}},[i,S]),Z=se({supportsAudioMute:a===null?null:a.supportsAudioMute,audioEnabled:pe,pendingOn:F}),ve=(0,f.useCallback)(async()=>{let e=ce(Z);if(!(!i||e===null)){I(e.enabled),R(null);try{await i.privacyMask?.setAudioEnabled(e);let t=await i.privacyMask?.getStatus({});t&&P(t.audioEnabled)}catch(e){console.error(`Privacy microphone write failed — reverting`,e),R(e instanceof Error?e.message:String(e))}finally{I(null)}}},[i,Z]),ye=(0,f.useCallback)(()=>{k(e=>(e&&(N(null),j(null)),!e))},[]),Q=a?.supportedShapes??[];l((0,f.useMemo)(()=>O&&!m&&a&&S?{id:ue,order:le,node:(0,h.jsx)(ie,{transparent:!0,items:K,supportedShapes:Q,polygonVertices:a.polygonVertices,selectedId:A,onSelect:G,onShapeChange:q,onDrawComplete:J,drawingKind:M})}:null,[O,m,a,S,K,Q,A,G,q,J,M]));let be=a?.supportedShapes.includes(`rect`)??!1,xe=a?.supportedShapes.includes(`polygon`)??!1,$=a!==null&&(a.maxRegions<=0||a.supportedShapes.length===0),Se=!m&&!$&&!b&&a!==null&&S!==null;return i?(0,h.jsx)(re,{title:n(`uiLibrary.privacyMaskSettings.title`,`Privacy Mask`),icon:(0,h.jsx)(u,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,h.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[m||$?(0,h.jsx)(`p`,{className:`${o} leading-relaxed`,children:n(`uiLibrary.privacyMaskSettings.unsupported`,`This camera doesn't support an on-board privacy mask.`)}):b?(0,h.jsx)(`p`,{className:`${o} leading-relaxed`,children:n(`uiLibrary.privacyMaskSettings.unreadable`,`This camera hasn't answered, so what it currently masks is unknown — the editor stays closed rather than showing an empty frame you could save over. A battery camera answers once it wakes; otherwise check that it is reachable.`)}):Se?(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(`p`,{className:`${o} leading-relaxed`,children:(0,h.jsx)(r,{uiKey:`uiLibrary.privacyMaskSettings.help`,fallback:`Toggle <0>Edit mask</0> to draw blanked-out zones on the live frame, then <1>Save</1> to push them to the camera. Drag a rectangle to move, its corner to resize; drag polygon vertices, click an edge midpoint to add one, or right-click a vertex to remove it.`,components:[(0,h.jsx)(`strong`,{className:`text-foreground`},`edit`),(0,h.jsx)(`strong`,{className:`text-foreground`},`save`)]})}),(0,h.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,h.jsx)(`button`,{type:`button`,onClick:ye,disabled:E,"aria-pressed":O,className:O?_:g,children:O?n(`uiLibrary.privacyMaskSettings.doneEditing`,`Done editing`):n(`uiLibrary.privacyMaskSettings.editMask`,`Edit mask`)}),(0,h.jsx)(`button`,{type:`button`,onClick:me,disabled:E,"aria-pressed":S.enabled,className:S.enabled?_:g,children:S.enabled?n(`uiLibrary.privacyMaskSettings.maskOn`,`Mask on`):n(`uiLibrary.privacyMaskSettings.maskOff`,`Mask off`)}),be&&(0,h.jsx)(`button`,{type:`button`,onClick:()=>Y(`rect`),disabled:E||W,"aria-pressed":M===`rect`,className:M===`rect`?_:g,title:W?n(`uiLibrary.privacyMaskSettings.maxZones`,`Maximum zones reached`):n(`uiLibrary.privacyMaskSettings.addRect`,`Add a rectangle zone`),children:n(`uiLibrary.privacyMaskSettings.rectButton`,`+ Rect`)}),xe&&(0,h.jsx)(`button`,{type:`button`,onClick:()=>Y(`polygon`),disabled:E||W,"aria-pressed":M===`polygon`,className:M===`polygon`?_:g,title:W?n(`uiLibrary.privacyMaskSettings.maxZones`,`Maximum zones reached`):n(`uiLibrary.privacyMaskSettings.addPolygon`,`Add a polygon zone`),children:n(`uiLibrary.privacyMaskSettings.polygonButton`,`+ Polygon`)}),(0,h.jsx)(`span`,{className:`${o} ml-1 tabular-nums`,children:n(`uiLibrary.privacyMaskSettings.zoneCount`,`{{active}} / {{max}} zones`,{active:V,max:H})}),(0,h.jsx)(`span`,{className:`flex-1`}),(0,h.jsx)(`button`,{type:`button`,onClick:_e,disabled:E||!B,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:n(`uiLibrary.privacyMaskSettings.revert`,`Revert`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>void X(),disabled:E||!B,className:_,children:E?n(`uiLibrary.privacyMaskSettings.saving`,`Saving…`):n(`uiLibrary.privacyMaskSettings.save`,`Save`)})]}),V>0?(0,h.jsx)(`div`,{className:`flex flex-col gap-1`,children:S.regions.map(e=>{let t=A===e.id,r=e.shape.kind===`polygon`?ae:ee;return(0,h.jsxs)(`div`,{className:`flex items-center gap-2 rounded-md border px-2 py-1 transition-colors ${t?`border-primary/50 bg-primary/10`:`border-border bg-surface`}`,children:[(0,h.jsxs)(`button`,{type:`button`,onClick:()=>he(e.id),disabled:E,className:`flex flex-1 items-center gap-2 text-left text-[11px] font-medium text-foreground-subtle hover:text-foreground disabled:opacity-40 transition-colors`,children:[(0,h.jsx)(r,{className:`h-3.5 w-3.5 shrink-0`}),(0,h.jsx)(`span`,{children:n(`uiLibrary.privacyMaskSettings.zoneN`,`Zone {{id}}`,{id:e.id})}),(0,h.jsx)(`span`,{className:`text-foreground-faint capitalize`,children:e.shape.kind})]}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>ge(e.id),disabled:E,"aria-label":n(`uiLibrary.privacyMaskSettings.deleteZoneN`,`Delete zone {{id}}`,{id:e.id}),title:n(`uiLibrary.privacyMaskSettings.deleteZone`,`Delete zone`),className:`inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-surface text-foreground-subtle hover:border-red-400/40 hover:bg-red-500/10 hover:text-red-400 disabled:opacity-40 transition-colors`,children:(0,h.jsx)(ne,{className:`h-3.5 w-3.5`})})]},e.id)})}):null]}):(0,h.jsx)(`p`,{className:`${o} leading-relaxed`,children:n(`uiLibrary.privacyMaskSettings.loading`,`Loading the camera's privacy mask…`)}),Z.supported&&(0,h.jsxs)(`div`,{className:`flex items-start gap-3 rounded-md border border-border bg-surface px-2 py-2`,children:[(0,h.jsx)(`button`,{type:`button`,role:`switch`,"aria-label":Z.label,"aria-checked":Z.on===!0,disabled:!Z.available||F!==null,onClick:()=>void ve(),title:Z.on===null?n(`uiLibrary.privacyMaskSettings.micTitleUnknown`,`Microphone — state unknown; the camera did not answer.`):Z.on?n(`uiLibrary.privacyMaskSettings.micTitleOn`,`The camera is capturing sound. Turning it off: {{cost}}`,{cost:Z.costWhenOff}):n(`uiLibrary.privacyMaskSettings.micTitleOff`,`The camera is silent. {{cost}}`,{cost:Z.costWhenOff}),className:Z.on===!1||Z.on===null?g:_,children:(0,h.jsxs)(`span`,{className:`inline-flex items-center gap-1.5`,children:[Z.on===!1?(0,h.jsx)(oe,{className:`h-3.5 w-3.5`}):(0,h.jsx)(d,{className:`h-3.5 w-3.5`}),Z.on===null?n(`uiLibrary.privacyMaskSettings.micUnknown`,`Microphone unknown`):Z.on?n(`uiLibrary.privacyMaskSettings.micOn`,`Microphone on`):n(`uiLibrary.privacyMaskSettings.micOff`,`Microphone off`)]})}),(0,h.jsx)(`p`,{className:`text-[10px] text-foreground-subtle flex-1 leading-relaxed`,children:L===null?Z.on===null?n(`uiLibrary.privacyMaskSettings.micNotReported`,`The camera did not report its microphone, so this state is unknown.`):Z.costWhenOff:n(`uiLibrary.privacyMaskSettings.writeFailed`,`Write failed: {{reason}}`,{reason:L})})]})]})}):null}export{y as PrivacyMaskSettings};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{C as a,S as o,a as s,b as c,c as l,d as u,f as d,g as f,h as p,i as m,l as h,m as g,n as _,o as v,r as ee,s as y,u as b,v as x}from"./index-B_NT7bSo.js";import{MaskShapeCanvas as S}from"./MaskShapeCanvas-BTvl6Rin.js";var C=a(`settings-2`,[[`path`,{d:`M14 17H5`,key:`gfn3mx`}],[`path`,{d:`M19 7h-9`,key:`6i9tg`}],[`circle`,{cx:`17`,cy:`17`,r:`3`,key:`18b49y`}],[`circle`,{cx:`7`,cy:`7`,r:`3`,key:`dfmy0x`}]]),w=n(i(),1),T=n(e(),1),E=g(`/addon/pipeline-analytics/event-media`);function D(e){return`${E}/m/${e}`}function O(e){let t=new Map;for(let n of e.states)n.references.forEach((e,r)=>{let i=t.get(e.condition)??[];i.push({stateId:n.id,index:r,reference:e}),t.set(e.condition,i)});return[...t.entries()].map(([e,t])=>({condition:e,tiles:[...t].sort((e,t)=>t.reference.capturedAt-e.reference.capturedAt)})).sort((e,t)=>e.condition.localeCompare(t.condition))}function k(e){return e.states.reduce((e,t)=>e+t.references.length,0)}var A=`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`;function j({monitor:e,disabled:n,onDelete:r}){let i=t(),a=O(e),o=k(e),[s,c]=(0,w.useState)(null);return a.length===0?(0,T.jsx)(`p`,{className:`${l} leading-relaxed`,children:i(`uiLibrary.sceneReferenceGallery.empty`,`No photographs captured yet. Press “Capture daylight” to take the one this scene will judge everything against.`)}):(0,T.jsx)(`div`,{className:`flex flex-col gap-2`,children:a.map(e=>(0,T.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,T.jsxs)(`p`,{className:`text-[10px] font-semibold uppercase tracking-wide text-foreground-subtle`,children:[y(e.condition,i),` · `,e.tiles.length]}),(0,T.jsx)(`ul`,{className:`flex flex-wrap gap-1.5`,children:e.tiles.map(t=>{let a=`${t.stateId}:${String(t.index)}`,u=o===1,d=s===a;return(0,T.jsxs)(`li`,{className:`flex w-24 flex-col gap-1 rounded-md border border-border p-1`,children:[(0,T.jsx)(M,{reference:t.reference}),(0,T.jsx)(`span`,{className:`${l} tabular-nums`,children:N(t.reference.capturedAt)}),(0,T.jsxs)(`button`,{type:`button`,className:A,disabled:n,"aria-label":i(`uiLibrary.sceneReferenceGallery.deleteLabel`,`Delete the {{variant}} photograph from {{date}}`,{variant:y(e.condition,i).toLowerCase(),date:N(t.reference.capturedAt)}),onClick:()=>{if(u&&!d){c(a);return}c(null),r({stateId:t.stateId,index:t.index})},children:[(0,T.jsx)(f,{className:`mr-1 inline h-3 w-3`}),d?i(`uiLibrary.sceneReferenceGallery.confirm`,`Confirm`):i(`uiLibrary.sceneReferenceGallery.delete`,`Delete`)]}),d&&(0,T.jsx)(`p`,{className:`text-[10px] leading-snug text-warning`,children:i(`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.`)})]},a)})})]},e.condition))})}function M({reference:e}){let n=t(),[r,i]=(0,w.useState)(!1),a=e.thumbnailMediaId;return a===void 0||r?(0,T.jsxs)(`div`,{className:`flex h-16 w-full flex-col items-center justify-center gap-0.5 rounded bg-surface text-foreground-subtle`,title:n(`uiLibrary.sceneReferenceGallery.missingPicture`,`The picture for this reference is no longer stored. The reference itself is intact and still being scored against.`),children:[(0,T.jsx)(c,{className:`h-4 w-4`}),(0,T.jsx)(`span`,{className:`text-[9px]`,children:n(`uiLibrary.sceneReferenceGallery.noPicture`,`no picture`)})]}):(0,T.jsx)(`img`,{src:D(a),alt:``,loading:`lazy`,className:`h-16 w-full rounded object-cover`,onError:()=>i(!0)})}function N(e){let t=new Date(e);return`${t.toLocaleDateString()} ${t.toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`})}`}var P=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground focus:border-primary focus:outline-none disabled:opacity-50`,F=`text-[10px] font-medium text-foreground-subtle`,I=`flex flex-col gap-2 rounded-md border border-border bg-surface/40 p-2`,L=`text-[10px] font-semibold uppercase tracking-wide text-foreground-subtle`,R=5,z=3600;function B(e,t){switch(e){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.`);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.`);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.`)}}function V(e,t){switch(e){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.`);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.`)}}function H(e){return{label:e.label,checkIntervalSec:e.checkIntervalSec??60,threshold:e.check.mode===`similarity`?e.check.threshold:.8,hysteresisCount:e.check.hysteresisCount,quietSeconds:e.quietSeconds,minObservationSpacingSec:e.minObservationSpacingSec,confirmPrompt:e.confirm?.prompt??``}}function U({monitor:e,disabled:n,onPatch:i}){let a=t(),[o,s]=(0,w.useState)(()=>H(e)),[c,u]=(0,w.useState)(!1);(0,w.useEffect)(()=>{c||s(H(e))},[e,c]);let d=e=>{u(!1),i(e)},f=t=>e.check.mode===`similarity`?{mode:`similarity`,threshold:t.threshold??e.check.threshold,hysteresisCount:t.hysteresisCount??e.check.hysteresisCount}:{...e.check,hysteresisCount:t.hysteresisCount??e.check.hysteresisCount},p=e.confirm?.enabled===!0,m=e.check.mode===`similarity`?e.check.threshold:null;return(0,T.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,T.jsxs)(`div`,{className:I,children:[(0,T.jsx)(`p`,{className:L,children:a(`uiLibrary.sceneSettingsForm.whatItWatches`,`What it watches`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.name`,`Name`)}),(0,T.jsx)(`input`,{type:`text`,value:o.label,disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,label:e.target.value})),onBlur:()=>{let t=o.label.trim();t.length>0&&t!==e.label?d({label:t}):u(!1)}})]}),(0,T.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,T.jsx)(`input`,{type:`checkbox`,checked:e.enabled,disabled:n,onChange:e=>d({enabled:e.target.checked})}),(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.watching`,`Watching`)})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.whenToLook`,`When to look`)}),(0,T.jsxs)(`select`,{value:e.triggerMode,disabled:n,className:P,onChange:e=>{let t=e.target.value;(t===`periodic`||t===`on-motion`||t===`both`)&&d({triggerMode:t})},children:[(0,T.jsx)(`option`,{value:`both`,children:a(`uiLibrary.sceneSettingsForm.triggerBoth`,`On the clock and after motion settles`)}),(0,T.jsx)(`option`,{value:`periodic`,children:a(`uiLibrary.sceneSettingsForm.triggerPeriodic`,`On the clock only`)}),(0,T.jsx)(`option`,{value:`on-motion`,children:a(`uiLibrary.sceneSettingsForm.triggerOnMotion`,`After motion settles only`)})]})]}),(0,T.jsx)(`p`,{className:l,children:B(e.triggerMode,a)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.checkEvery`,`Check every (seconds)`)}),(0,T.jsx)(`input`,{type:`number`,min:R,max:z,value:o.checkIntervalSec,disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,checkIntervalSec:Number(e.target.value)||0})),onBlur:()=>{let t=W(o.checkIntervalSec,R,z);t===e.checkIntervalSec?u(!1):d({checkIntervalSec:t})}})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)})]}),(0,T.jsxs)(`div`,{className:I,children:[(0,T.jsx)(`p`,{className:L,children:a(`uiLibrary.sceneSettingsForm.howSure`,`How sure it has to be`)}),e.check.mode===`similarity`&&(0,T.jsxs)(T.Fragment,{children:[(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:(0,T.jsx)(r,{uiKey:`uiLibrary.sceneSettingsForm.matchThreshold`,fallback:`Match threshold — <0>{{value}}</0>`,components:[(0,T.jsx)(`span`,{className:`tabular-nums`},`value`)],values:{value:o.threshold.toFixed(2)}})}),(0,T.jsx)(`input`,{type:`range`,min:.3,max:.99,step:.01,value:o.threshold,disabled:n,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,threshold:Number(e.target.value)})),onMouseUp:()=>{o.threshold===m?u(!1):d({check:f({threshold:o.threshold})})},onBlur:()=>{o.threshold===m?u(!1):d({check:f({threshold:o.threshold})})}})]}),(0,T.jsx)(`p`,{className:`text-[10px] text-foreground-subtle`,children:a(`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.`)})]}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.agreeingChecks`,`Agreeing checks before it changes its mind`)}),(0,T.jsx)(`input`,{type:`number`,min:1,max:10,value:o.hysteresisCount,disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,hysteresisCount:Number(e.target.value)||1})),onBlur:()=>{let t=W(o.hysteresisCount,1,10);t===e.check.hysteresisCount?u(!1):d({check:f({hysteresisCount:t})})}})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.quietFor`,`Quiet for (seconds) before a check counts`)}),(0,T.jsx)(`input`,{type:`number`,min:0,max:3600,value:o.quietSeconds,disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,quietSeconds:Number(e.target.value)||0})),onBlur:()=>{let t=W(o.quietSeconds,0,3600);t===e.quietSeconds?u(!1):d({quietSeconds:t})}})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.minGap`,`Minimum gap between counted checks (seconds)`)}),(0,T.jsx)(`input`,{type:`number`,min:0,max:3600,value:o.minObservationSpacingSec,disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,minObservationSpacingSec:Number(e.target.value)||0})),onBlur:()=>{let t=W(o.minObservationSpacingSec,0,3600);t===e.minObservationSpacingSec?u(!1):d({minObservationSpacingSec:t})}})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)})]}),(0,T.jsxs)(`div`,{className:I,children:[(0,T.jsx)(`p`,{className:L,children:a(`uiLibrary.sceneSettingsForm.lighting`,`Lighting`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.noReference`,`When a light has no reference of its own`)}),(0,T.jsxs)(`select`,{value:e.onUncoveredCondition,disabled:n,className:P,onChange:e=>{let t=e.target.value;(t===`skip`||t===`judge-anyway`)&&d({onUncoveredCondition:t})},children:[(0,T.jsx)(`option`,{value:`skip`,children:a(`uiLibrary.sceneSettingsForm.uncoveredSkip`,`Skip the check (recommended)`)}),(0,T.jsx)(`option`,{value:`judge-anyway`,children:a(`uiLibrary.sceneSettingsForm.uncoveredJudge`,`Judge against the other light anyway`)})]})]}),(0,T.jsx)(`p`,{className:l,children:V(e.onUncoveredCondition,a)})]}),(0,T.jsxs)(`div`,{className:I,children:[(0,T.jsx)(`p`,{className:L,children:a(`uiLibrary.sceneSettingsForm.whatYouGetTold`,`What you get told`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.notificationsFollow`,`Notifications follow`)}),(0,T.jsxs)(`select`,{value:e.emit,disabled:n,className:P,onChange:e=>{let t=e.target.value;(t===`latched`||t===`live`)&&d({emit:t})},children:[(0,T.jsx)(`option`,{value:`latched`,children:a(`uiLibrary.sceneSettingsForm.emitLatched`,`The flag — told once, still visible at breakfast`)}),(0,T.jsx)(`option`,{value:`live`,children:a(`uiLibrary.sceneSettingsForm.emitLive`,`The live state — follows the region as it changes`)})]})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)}),(0,T.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,T.jsx)(`input`,{type:`checkbox`,checked:e.autoRestore,disabled:n,onChange:e=>d({autoRestore:e.target.checked})}),(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.autoRestore`,`Clear the flag by itself when things look normal again`)})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)})]}),(0,T.jsxs)(`div`,{className:I,children:[(0,T.jsx)(`p`,{className:L,children:a(`uiLibrary.sceneSettingsForm.aiDoubleCheck`,`AI double-check`)}),(0,T.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,T.jsx)(`input`,{type:`checkbox`,checked:p,disabled:n||o.confirmPrompt.trim().length===0,onChange:e=>d(e.target.checked?{confirm:{...G(o.confirmPrompt.trim()),enabled:!0}}:{confirm:null})}),(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.askVision`,`Ask a vision model before changing the answer`)})]}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.question`,`The question to ask`)}),(0,T.jsx)(`input`,{type:`text`,value:o.confirmPrompt,placeholder:a(`uiLibrary.sceneSettingsForm.questionPlaceholder`,`e.g. Is the wheelie bin still there?`),disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,confirmPrompt:e.target.value})),onBlur:()=>{let t=o.confirmPrompt.trim();t.length>0&&t!==e.confirm?.prompt?d({confirm:{...G(t),enabled:p}}):u(!1)}})]}),(0,T.jsx)(`p`,{className:l,children:(0,T.jsx)(r,{uiKey:`uiLibrary.sceneSettingsForm.confirmHint`,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.`,components:[(0,T.jsx)(`strong`,{},`only`)]})})]})]})}function W(e,t,n){return Math.min(n,Math.max(t,e))}function G(e){return{enabled:!1,prompt:e,timeoutMs:8e3,maxImagePx:448,onTimeout:`hold`}}var K=`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`,q=`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`,J=`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`,Y={alarm:`border-danger/50 bg-danger/15 text-danger`,ok:`border-success/50 bg-success/15 text-success`,unknown:`border-border bg-surface text-foreground-subtle`},X=.8,Z=3;function Q({deviceId:e}){let n=t(),r=u(p().trpcClient,e),i=d(r?.state.sceneMonitor),a=b(e,{width:640}),[c,g]=(0,w.useState)(null),[E,D]=(0,w.useState)(!1),[O,k]=(0,w.useState)(null),[A,M]=(0,w.useState)(null),[N,P]=(0,w.useState)(null),[F,I]=(0,w.useState)(null),[L,R]=(0,w.useState)(!1),[z,B]=(0,w.useState)(null),[V,H]=(0,w.useState)(``),W=(0,w.useRef)(!0);(0,w.useEffect)(()=>(W.current=!0,()=>{W.current=!1}),[]);let G=(0,w.useCallback)(async()=>{if(r)try{let e=await r.sceneMonitor?.listScenes({});if(!W.current)return;e&&g(e)}catch(e){if(!W.current)return;h(e)?D(!0):M(e instanceof Error?e.message:String(e))}},[r]);(0,w.useEffect)(()=>{g(null),D(!1),M(null),G()},[G]);let Q=i?.monitors??c?.monitors??[],te=Date.now(),$=(0,w.useCallback)(async(e,t)=>{k(e),M(null);try{await t(),await G()}catch(e){W.current&&M(e instanceof Error?e.message:String(e))}finally{W.current&&k(null)}},[G]),ne=(0,w.useMemo)(()=>{let e=Q.map(e=>({id:e.id,shape:e.roi,label:e.label,enabled:e.enabled}));return z===null?e:[...e,{id:`__draft__`,shape:z,label:n(`uiLibrary.sceneMonitor.newSceneLabel`,`New scene`)}]},[Q,z,n]),re=(0,w.useCallback)((e,t)=>{if(t.kind===`rect`){if(e===`__draft__`){B(t);return}$(`roi-${String(e)}`,async()=>r?.sceneMonitor?.updateScene({monitorId:String(e),patch:{roi:t}}))}},[r,$]),ie=(0,w.useCallback)(e=>{e.kind===`rect`&&B(e)},[]),ae=(0,w.useCallback)(async()=>{z===null||V.trim().length===0||(await $(`create`,async()=>{await r?.sceneMonitor?.createScene({label:V.trim(),roi:z,check:{mode:`similarity`,threshold:X,hysteresisCount:Z}})}),B(null),H(``),R(!1))},[r,z,V,$]);return r?(0,T.jsx)(`div`,{className:`flex flex-col gap-3`,children:E?(0,T.jsx)(`p`,{className:`${l} leading-relaxed`,children:n(`uiLibrary.sceneMonitor.unsupported`,`Scene monitoring is not available for this camera.`)}):(0,T.jsxs)(T.Fragment,{children:[(0,T.jsx)(`p`,{className:`${l} leading-relaxed`,children:n(`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.`)}),(0,T.jsx)(`div`,{className:`relative overflow-hidden rounded-md border border-border`,children:(0,T.jsx)(S,{items:ne,supportedShapes:[`rect`],selectedId:N??(z===null?null:`__draft__`),onSelect:e=>P(e===null?null:String(e)),onShapeChange:re,onDrawComplete:ie,drawingKind:L&&z===null?`rect`:null,backdrop:a.src===null?null:(0,T.jsx)(`img`,{src:a.src,alt:``,className:`h-full w-full object-contain`,draggable:!1})})}),(0,T.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,T.jsx)(`button`,{type:`button`,className:L?q:K,"aria-pressed":L,onClick:()=>{R(e=>!e),B(null)},disabled:O!==null,children:L?n(`uiLibrary.sceneMonitor.cancel`,`Cancel`):n(`uiLibrary.sceneMonitor.newScene`,`+ New scene`)}),(0,T.jsx)(`button`,{type:`button`,className:K,onClick:a.refresh,disabled:O!==null,children:n(`uiLibrary.sceneMonitor.refresh`,`Refresh frame`)})]}),L&&(0,T.jsxs)(`div`,{className:`flex flex-col gap-2 rounded-md border border-border bg-surface p-2`,children:[(0,T.jsx)(`p`,{className:`text-[10px] text-foreground-subtle`,children:z===null?n(`uiLibrary.sceneMonitor.draftHint`,`Click on the frame to drop a box, then drag its corner to fit it around the thing you want watched.`):n(`uiLibrary.sceneMonitor.nameHint`,`Name it, then create. You capture what “normal” looks like next.`)}),z!==null&&s(z)&&(0,T.jsx)(`p`,{className:`text-[11px] leading-relaxed text-warning`,children:n(`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.`)}),(0,T.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,T.jsx)(`input`,{type:`text`,value:V,onChange:e=>H(e.target.value),placeholder:n(`uiLibrary.sceneMonitor.placeholder`,`e.g. Immondizia prelevata`),className:`flex-1 rounded-md border border-border bg-surface px-2 py-1 text-[12px] text-foreground`}),(0,T.jsx)(`button`,{type:`button`,className:q,onClick:()=>void ae(),disabled:z===null||V.trim().length===0||O!==null,children:n(`uiLibrary.sceneMonitor.create`,`Create`)})]})]}),A!==null&&(0,T.jsx)(`p`,{className:`text-[11px] leading-relaxed text-danger`,children:A}),Q.length===0?(0,T.jsx)(`p`,{className:`${l} leading-relaxed`,children:n(`uiLibrary.sceneMonitor.empty`,`No scenes on this camera yet.`)}):(0,T.jsx)(`ul`,{className:`flex flex-col gap-2`,children:Q.map(e=>{let t=ee(e,n);return(0,T.jsxs)(`li`,{className:`flex flex-col gap-1.5 rounded-md border p-2 ${N===e.id?`border-primary/50`:`border-border`}`,children:[(0,T.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,T.jsx)(`button`,{type:`button`,className:`flex-1 text-left text-[12px] font-medium text-foreground`,onClick:()=>P(e.id),children:e.label}),(0,T.jsx)(`span`,{className:`rounded border px-1.5 py-0.5 text-[10px] font-medium ${Y[t.tone]}`,children:t.text})]}),(0,T.jsx)(`p`,{className:`${l} leading-relaxed`,children:v(e,te,n)}),(0,T.jsx)(`p`,{className:`${l} tabular-nums`,children:m(e,n)}),(0,T.jsx)(j,{monitor:e,disabled:O!==null,onDelete:({stateId:t,index:n})=>void $(`refdel-${e.id}-${t}-${String(n)}`,async()=>r.sceneMonitor?.deleteReference({monitorId:e.id,stateId:t,index:n}))}),(0,T.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[_.map(t=>{let i=t===`ir`;return(0,T.jsxs)(`button`,{type:`button`,className:K,disabled:O!==null,title:i?n(`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.`):n(`uiLibrary.sceneMonitor.captureTitle`,`Take a fresh photo now and store it as the {{variant}} reference`,{variant:y(t,n).toLowerCase()}),onClick:()=>void $(`cap-${e.id}-${t}`,async()=>r.sceneMonitor?.captureReference({monitorId:e.id,condition:t})),children:[(0,T.jsx)(o,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.capture`,`Capture {{variant}}`,{variant:y(t,n).toLowerCase()}),i&&(0,T.jsx)(`span`,{className:`ml-1 text-foreground-subtle/70`,children:n(`uiLibrary.sceneMonitor.optional`,`(optional)`)})]},t)}),(0,T.jsxs)(`button`,{type:`button`,className:q,disabled:O!==null||e.states.length===0,title:n(`uiLibrary.sceneMonitor.resetTitle`,`Clear the flag and re-photograph what normal looks like right now`),onClick:()=>void $(`reset-${e.id}`,async()=>r.sceneMonitor?.resetScene({monitorId:e.id})),children:[(0,T.jsx)(x,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.reset`,`Reset`)]}),(0,T.jsx)(`button`,{type:`button`,className:K,disabled:O!==null,onClick:()=>void $(`check-${e.id}`,async()=>r.sceneMonitor?.recheckNow({monitorId:e.id})),children:n(`uiLibrary.sceneMonitor.checkNow`,`Check now`)}),(0,T.jsxs)(`button`,{type:`button`,className:F===e.id?q:K,"aria-pressed":F===e.id,"aria-label":n(`uiLibrary.sceneMonitor.settingsFor`,`Settings for {{label}}`,{label:e.label}),disabled:O!==null,onClick:()=>I(t=>t===e.id?null:e.id),children:[(0,T.jsx)(C,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.settings`,`Settings`)]}),(0,T.jsx)(`button`,{type:`button`,className:J,disabled:O!==null,onClick:()=>void $(`del-${e.id}`,async()=>r.sceneMonitor?.deleteScene({monitorId:e.id})),children:(0,T.jsx)(f,{className:`h-3 w-3`})})]}),F===e.id&&(0,T.jsx)(U,{monitor:e,disabled:O!==null,onPatch:t=>void $(`tune-${e.id}`,async()=>r.sceneMonitor?.updateScene({monitorId:e.id,patch:t}))})]},e.id)})})]})}):null}export{Q as SceneMonitorEditor};
|
|
1
|
+
import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{C as a,S as o,a as s,b as c,c as l,d as u,f as d,g as f,h as p,i as m,l as h,m as g,n as _,o as v,r as ee,s as y,u as b,v as x}from"./index-M3f9Qo3X.js";import{MaskShapeCanvas as S}from"./MaskShapeCanvas-BTvl6Rin.js";var C=a(`settings-2`,[[`path`,{d:`M14 17H5`,key:`gfn3mx`}],[`path`,{d:`M19 7h-9`,key:`6i9tg`}],[`circle`,{cx:`17`,cy:`17`,r:`3`,key:`18b49y`}],[`circle`,{cx:`7`,cy:`7`,r:`3`,key:`dfmy0x`}]]),w=n(i(),1),T=n(e(),1),E=g(`/addon/pipeline-analytics/event-media`);function D(e){return`${E}/m/${e}`}function O(e){let t=new Map;for(let n of e.states)n.references.forEach((e,r)=>{let i=t.get(e.condition)??[];i.push({stateId:n.id,index:r,reference:e}),t.set(e.condition,i)});return[...t.entries()].map(([e,t])=>({condition:e,tiles:[...t].sort((e,t)=>t.reference.capturedAt-e.reference.capturedAt)})).sort((e,t)=>e.condition.localeCompare(t.condition))}function k(e){return e.states.reduce((e,t)=>e+t.references.length,0)}var A=`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`;function j({monitor:e,disabled:n,onDelete:r}){let i=t(),a=O(e),o=k(e),[s,c]=(0,w.useState)(null);return a.length===0?(0,T.jsx)(`p`,{className:`${l} leading-relaxed`,children:i(`uiLibrary.sceneReferenceGallery.empty`,`No photographs captured yet. Press “Capture daylight” to take the one this scene will judge everything against.`)}):(0,T.jsx)(`div`,{className:`flex flex-col gap-2`,children:a.map(e=>(0,T.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,T.jsxs)(`p`,{className:`text-[10px] font-semibold uppercase tracking-wide text-foreground-subtle`,children:[y(e.condition,i),` · `,e.tiles.length]}),(0,T.jsx)(`ul`,{className:`flex flex-wrap gap-1.5`,children:e.tiles.map(t=>{let a=`${t.stateId}:${String(t.index)}`,u=o===1,d=s===a;return(0,T.jsxs)(`li`,{className:`flex w-24 flex-col gap-1 rounded-md border border-border p-1`,children:[(0,T.jsx)(M,{reference:t.reference}),(0,T.jsx)(`span`,{className:`${l} tabular-nums`,children:N(t.reference.capturedAt)}),(0,T.jsxs)(`button`,{type:`button`,className:A,disabled:n,"aria-label":i(`uiLibrary.sceneReferenceGallery.deleteLabel`,`Delete the {{variant}} photograph from {{date}}`,{variant:y(e.condition,i).toLowerCase(),date:N(t.reference.capturedAt)}),onClick:()=>{if(u&&!d){c(a);return}c(null),r({stateId:t.stateId,index:t.index})},children:[(0,T.jsx)(f,{className:`mr-1 inline h-3 w-3`}),d?i(`uiLibrary.sceneReferenceGallery.confirm`,`Confirm`):i(`uiLibrary.sceneReferenceGallery.delete`,`Delete`)]}),d&&(0,T.jsx)(`p`,{className:`text-[10px] leading-snug text-warning`,children:i(`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.`)})]},a)})})]},e.condition))})}function M({reference:e}){let n=t(),[r,i]=(0,w.useState)(!1),a=e.thumbnailMediaId;return a===void 0||r?(0,T.jsxs)(`div`,{className:`flex h-16 w-full flex-col items-center justify-center gap-0.5 rounded bg-surface text-foreground-subtle`,title:n(`uiLibrary.sceneReferenceGallery.missingPicture`,`The picture for this reference is no longer stored. The reference itself is intact and still being scored against.`),children:[(0,T.jsx)(c,{className:`h-4 w-4`}),(0,T.jsx)(`span`,{className:`text-[9px]`,children:n(`uiLibrary.sceneReferenceGallery.noPicture`,`no picture`)})]}):(0,T.jsx)(`img`,{src:D(a),alt:``,loading:`lazy`,className:`h-16 w-full rounded object-cover`,onError:()=>i(!0)})}function N(e){let t=new Date(e);return`${t.toLocaleDateString()} ${t.toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`})}`}var P=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground focus:border-primary focus:outline-none disabled:opacity-50`,F=`text-[10px] font-medium text-foreground-subtle`,I=`flex flex-col gap-2 rounded-md border border-border bg-surface/40 p-2`,L=`text-[10px] font-semibold uppercase tracking-wide text-foreground-subtle`,R=5,z=3600;function B(e,t){switch(e){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.`);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.`);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.`)}}function V(e,t){switch(e){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.`);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.`)}}function H(e){return{label:e.label,checkIntervalSec:e.checkIntervalSec??60,threshold:e.check.mode===`similarity`?e.check.threshold:.8,hysteresisCount:e.check.hysteresisCount,quietSeconds:e.quietSeconds,minObservationSpacingSec:e.minObservationSpacingSec,confirmPrompt:e.confirm?.prompt??``}}function U({monitor:e,disabled:n,onPatch:i}){let a=t(),[o,s]=(0,w.useState)(()=>H(e)),[c,u]=(0,w.useState)(!1);(0,w.useEffect)(()=>{c||s(H(e))},[e,c]);let d=e=>{u(!1),i(e)},f=t=>e.check.mode===`similarity`?{mode:`similarity`,threshold:t.threshold??e.check.threshold,hysteresisCount:t.hysteresisCount??e.check.hysteresisCount}:{...e.check,hysteresisCount:t.hysteresisCount??e.check.hysteresisCount},p=e.confirm?.enabled===!0,m=e.check.mode===`similarity`?e.check.threshold:null;return(0,T.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,T.jsxs)(`div`,{className:I,children:[(0,T.jsx)(`p`,{className:L,children:a(`uiLibrary.sceneSettingsForm.whatItWatches`,`What it watches`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.name`,`Name`)}),(0,T.jsx)(`input`,{type:`text`,value:o.label,disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,label:e.target.value})),onBlur:()=>{let t=o.label.trim();t.length>0&&t!==e.label?d({label:t}):u(!1)}})]}),(0,T.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,T.jsx)(`input`,{type:`checkbox`,checked:e.enabled,disabled:n,onChange:e=>d({enabled:e.target.checked})}),(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.watching`,`Watching`)})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.whenToLook`,`When to look`)}),(0,T.jsxs)(`select`,{value:e.triggerMode,disabled:n,className:P,onChange:e=>{let t=e.target.value;(t===`periodic`||t===`on-motion`||t===`both`)&&d({triggerMode:t})},children:[(0,T.jsx)(`option`,{value:`both`,children:a(`uiLibrary.sceneSettingsForm.triggerBoth`,`On the clock and after motion settles`)}),(0,T.jsx)(`option`,{value:`periodic`,children:a(`uiLibrary.sceneSettingsForm.triggerPeriodic`,`On the clock only`)}),(0,T.jsx)(`option`,{value:`on-motion`,children:a(`uiLibrary.sceneSettingsForm.triggerOnMotion`,`After motion settles only`)})]})]}),(0,T.jsx)(`p`,{className:l,children:B(e.triggerMode,a)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.checkEvery`,`Check every (seconds)`)}),(0,T.jsx)(`input`,{type:`number`,min:R,max:z,value:o.checkIntervalSec,disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,checkIntervalSec:Number(e.target.value)||0})),onBlur:()=>{let t=W(o.checkIntervalSec,R,z);t===e.checkIntervalSec?u(!1):d({checkIntervalSec:t})}})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)})]}),(0,T.jsxs)(`div`,{className:I,children:[(0,T.jsx)(`p`,{className:L,children:a(`uiLibrary.sceneSettingsForm.howSure`,`How sure it has to be`)}),e.check.mode===`similarity`&&(0,T.jsxs)(T.Fragment,{children:[(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:(0,T.jsx)(r,{uiKey:`uiLibrary.sceneSettingsForm.matchThreshold`,fallback:`Match threshold — <0>{{value}}</0>`,components:[(0,T.jsx)(`span`,{className:`tabular-nums`},`value`)],values:{value:o.threshold.toFixed(2)}})}),(0,T.jsx)(`input`,{type:`range`,min:.3,max:.99,step:.01,value:o.threshold,disabled:n,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,threshold:Number(e.target.value)})),onMouseUp:()=>{o.threshold===m?u(!1):d({check:f({threshold:o.threshold})})},onBlur:()=>{o.threshold===m?u(!1):d({check:f({threshold:o.threshold})})}})]}),(0,T.jsx)(`p`,{className:`text-[10px] text-foreground-subtle`,children:a(`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.`)})]}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.agreeingChecks`,`Agreeing checks before it changes its mind`)}),(0,T.jsx)(`input`,{type:`number`,min:1,max:10,value:o.hysteresisCount,disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,hysteresisCount:Number(e.target.value)||1})),onBlur:()=>{let t=W(o.hysteresisCount,1,10);t===e.check.hysteresisCount?u(!1):d({check:f({hysteresisCount:t})})}})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.quietFor`,`Quiet for (seconds) before a check counts`)}),(0,T.jsx)(`input`,{type:`number`,min:0,max:3600,value:o.quietSeconds,disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,quietSeconds:Number(e.target.value)||0})),onBlur:()=>{let t=W(o.quietSeconds,0,3600);t===e.quietSeconds?u(!1):d({quietSeconds:t})}})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.minGap`,`Minimum gap between counted checks (seconds)`)}),(0,T.jsx)(`input`,{type:`number`,min:0,max:3600,value:o.minObservationSpacingSec,disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,minObservationSpacingSec:Number(e.target.value)||0})),onBlur:()=>{let t=W(o.minObservationSpacingSec,0,3600);t===e.minObservationSpacingSec?u(!1):d({minObservationSpacingSec:t})}})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)})]}),(0,T.jsxs)(`div`,{className:I,children:[(0,T.jsx)(`p`,{className:L,children:a(`uiLibrary.sceneSettingsForm.lighting`,`Lighting`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.noReference`,`When a light has no reference of its own`)}),(0,T.jsxs)(`select`,{value:e.onUncoveredCondition,disabled:n,className:P,onChange:e=>{let t=e.target.value;(t===`skip`||t===`judge-anyway`)&&d({onUncoveredCondition:t})},children:[(0,T.jsx)(`option`,{value:`skip`,children:a(`uiLibrary.sceneSettingsForm.uncoveredSkip`,`Skip the check (recommended)`)}),(0,T.jsx)(`option`,{value:`judge-anyway`,children:a(`uiLibrary.sceneSettingsForm.uncoveredJudge`,`Judge against the other light anyway`)})]})]}),(0,T.jsx)(`p`,{className:l,children:V(e.onUncoveredCondition,a)})]}),(0,T.jsxs)(`div`,{className:I,children:[(0,T.jsx)(`p`,{className:L,children:a(`uiLibrary.sceneSettingsForm.whatYouGetTold`,`What you get told`)}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.notificationsFollow`,`Notifications follow`)}),(0,T.jsxs)(`select`,{value:e.emit,disabled:n,className:P,onChange:e=>{let t=e.target.value;(t===`latched`||t===`live`)&&d({emit:t})},children:[(0,T.jsx)(`option`,{value:`latched`,children:a(`uiLibrary.sceneSettingsForm.emitLatched`,`The flag — told once, still visible at breakfast`)}),(0,T.jsx)(`option`,{value:`live`,children:a(`uiLibrary.sceneSettingsForm.emitLive`,`The live state — follows the region as it changes`)})]})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)}),(0,T.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,T.jsx)(`input`,{type:`checkbox`,checked:e.autoRestore,disabled:n,onChange:e=>d({autoRestore:e.target.checked})}),(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.autoRestore`,`Clear the flag by itself when things look normal again`)})]}),(0,T.jsx)(`p`,{className:l,children:a(`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.`)})]}),(0,T.jsxs)(`div`,{className:I,children:[(0,T.jsx)(`p`,{className:L,children:a(`uiLibrary.sceneSettingsForm.aiDoubleCheck`,`AI double-check`)}),(0,T.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,T.jsx)(`input`,{type:`checkbox`,checked:p,disabled:n||o.confirmPrompt.trim().length===0,onChange:e=>d(e.target.checked?{confirm:{...G(o.confirmPrompt.trim()),enabled:!0}}:{confirm:null})}),(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.askVision`,`Ask a vision model before changing the answer`)})]}),(0,T.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,T.jsx)(`span`,{className:F,children:a(`uiLibrary.sceneSettingsForm.question`,`The question to ask`)}),(0,T.jsx)(`input`,{type:`text`,value:o.confirmPrompt,placeholder:a(`uiLibrary.sceneSettingsForm.questionPlaceholder`,`e.g. Is the wheelie bin still there?`),disabled:n,className:P,onFocus:()=>u(!0),onChange:e=>s(t=>({...t,confirmPrompt:e.target.value})),onBlur:()=>{let t=o.confirmPrompt.trim();t.length>0&&t!==e.confirm?.prompt?d({confirm:{...G(t),enabled:p}}):u(!1)}})]}),(0,T.jsx)(`p`,{className:l,children:(0,T.jsx)(r,{uiKey:`uiLibrary.sceneSettingsForm.confirmHint`,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.`,components:[(0,T.jsx)(`strong`,{},`only`)]})})]})]})}function W(e,t,n){return Math.min(n,Math.max(t,e))}function G(e){return{enabled:!1,prompt:e,timeoutMs:8e3,maxImagePx:448,onTimeout:`hold`}}var K=`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`,q=`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`,J=`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`,Y={alarm:`border-danger/50 bg-danger/15 text-danger`,ok:`border-success/50 bg-success/15 text-success`,unknown:`border-border bg-surface text-foreground-subtle`},X=.8,Z=3;function Q({deviceId:e}){let n=t(),r=u(p().trpcClient,e),i=d(r?.state.sceneMonitor),a=b(e,{width:640}),[c,g]=(0,w.useState)(null),[E,D]=(0,w.useState)(!1),[O,k]=(0,w.useState)(null),[A,M]=(0,w.useState)(null),[N,P]=(0,w.useState)(null),[F,I]=(0,w.useState)(null),[L,R]=(0,w.useState)(!1),[z,B]=(0,w.useState)(null),[V,H]=(0,w.useState)(``),W=(0,w.useRef)(!0);(0,w.useEffect)(()=>(W.current=!0,()=>{W.current=!1}),[]);let G=(0,w.useCallback)(async()=>{if(r)try{let e=await r.sceneMonitor?.listScenes({});if(!W.current)return;e&&g(e)}catch(e){if(!W.current)return;h(e)?D(!0):M(e instanceof Error?e.message:String(e))}},[r]);(0,w.useEffect)(()=>{g(null),D(!1),M(null),G()},[G]);let Q=i?.monitors??c?.monitors??[],te=Date.now(),$=(0,w.useCallback)(async(e,t)=>{k(e),M(null);try{await t(),await G()}catch(e){W.current&&M(e instanceof Error?e.message:String(e))}finally{W.current&&k(null)}},[G]),ne=(0,w.useMemo)(()=>{let e=Q.map(e=>({id:e.id,shape:e.roi,label:e.label,enabled:e.enabled}));return z===null?e:[...e,{id:`__draft__`,shape:z,label:n(`uiLibrary.sceneMonitor.newSceneLabel`,`New scene`)}]},[Q,z,n]),re=(0,w.useCallback)((e,t)=>{if(t.kind===`rect`){if(e===`__draft__`){B(t);return}$(`roi-${String(e)}`,async()=>r?.sceneMonitor?.updateScene({monitorId:String(e),patch:{roi:t}}))}},[r,$]),ie=(0,w.useCallback)(e=>{e.kind===`rect`&&B(e)},[]),ae=(0,w.useCallback)(async()=>{z===null||V.trim().length===0||(await $(`create`,async()=>{await r?.sceneMonitor?.createScene({label:V.trim(),roi:z,check:{mode:`similarity`,threshold:X,hysteresisCount:Z}})}),B(null),H(``),R(!1))},[r,z,V,$]);return r?(0,T.jsx)(`div`,{className:`flex flex-col gap-3`,children:E?(0,T.jsx)(`p`,{className:`${l} leading-relaxed`,children:n(`uiLibrary.sceneMonitor.unsupported`,`Scene monitoring is not available for this camera.`)}):(0,T.jsxs)(T.Fragment,{children:[(0,T.jsx)(`p`,{className:`${l} leading-relaxed`,children:n(`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.`)}),(0,T.jsx)(`div`,{className:`relative overflow-hidden rounded-md border border-border`,children:(0,T.jsx)(S,{items:ne,supportedShapes:[`rect`],selectedId:N??(z===null?null:`__draft__`),onSelect:e=>P(e===null?null:String(e)),onShapeChange:re,onDrawComplete:ie,drawingKind:L&&z===null?`rect`:null,backdrop:a.src===null?null:(0,T.jsx)(`img`,{src:a.src,alt:``,className:`h-full w-full object-contain`,draggable:!1})})}),(0,T.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,T.jsx)(`button`,{type:`button`,className:L?q:K,"aria-pressed":L,onClick:()=>{R(e=>!e),B(null)},disabled:O!==null,children:L?n(`uiLibrary.sceneMonitor.cancel`,`Cancel`):n(`uiLibrary.sceneMonitor.newScene`,`+ New scene`)}),(0,T.jsx)(`button`,{type:`button`,className:K,onClick:a.refresh,disabled:O!==null,children:n(`uiLibrary.sceneMonitor.refresh`,`Refresh frame`)})]}),L&&(0,T.jsxs)(`div`,{className:`flex flex-col gap-2 rounded-md border border-border bg-surface p-2`,children:[(0,T.jsx)(`p`,{className:`text-[10px] text-foreground-subtle`,children:z===null?n(`uiLibrary.sceneMonitor.draftHint`,`Click on the frame to drop a box, then drag its corner to fit it around the thing you want watched.`):n(`uiLibrary.sceneMonitor.nameHint`,`Name it, then create. You capture what “normal” looks like next.`)}),z!==null&&s(z)&&(0,T.jsx)(`p`,{className:`text-[11px] leading-relaxed text-warning`,children:n(`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.`)}),(0,T.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,T.jsx)(`input`,{type:`text`,value:V,onChange:e=>H(e.target.value),placeholder:n(`uiLibrary.sceneMonitor.placeholder`,`e.g. Immondizia prelevata`),className:`flex-1 rounded-md border border-border bg-surface px-2 py-1 text-[12px] text-foreground`}),(0,T.jsx)(`button`,{type:`button`,className:q,onClick:()=>void ae(),disabled:z===null||V.trim().length===0||O!==null,children:n(`uiLibrary.sceneMonitor.create`,`Create`)})]})]}),A!==null&&(0,T.jsx)(`p`,{className:`text-[11px] leading-relaxed text-danger`,children:A}),Q.length===0?(0,T.jsx)(`p`,{className:`${l} leading-relaxed`,children:n(`uiLibrary.sceneMonitor.empty`,`No scenes on this camera yet.`)}):(0,T.jsx)(`ul`,{className:`flex flex-col gap-2`,children:Q.map(e=>{let t=ee(e,n);return(0,T.jsxs)(`li`,{className:`flex flex-col gap-1.5 rounded-md border p-2 ${N===e.id?`border-primary/50`:`border-border`}`,children:[(0,T.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,T.jsx)(`button`,{type:`button`,className:`flex-1 text-left text-[12px] font-medium text-foreground`,onClick:()=>P(e.id),children:e.label}),(0,T.jsx)(`span`,{className:`rounded border px-1.5 py-0.5 text-[10px] font-medium ${Y[t.tone]}`,children:t.text})]}),(0,T.jsx)(`p`,{className:`${l} leading-relaxed`,children:v(e,te,n)}),(0,T.jsx)(`p`,{className:`${l} tabular-nums`,children:m(e,n)}),(0,T.jsx)(j,{monitor:e,disabled:O!==null,onDelete:({stateId:t,index:n})=>void $(`refdel-${e.id}-${t}-${String(n)}`,async()=>r.sceneMonitor?.deleteReference({monitorId:e.id,stateId:t,index:n}))}),(0,T.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[_.map(t=>{let i=t===`ir`;return(0,T.jsxs)(`button`,{type:`button`,className:K,disabled:O!==null,title:i?n(`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.`):n(`uiLibrary.sceneMonitor.captureTitle`,`Take a fresh photo now and store it as the {{variant}} reference`,{variant:y(t,n).toLowerCase()}),onClick:()=>void $(`cap-${e.id}-${t}`,async()=>r.sceneMonitor?.captureReference({monitorId:e.id,condition:t})),children:[(0,T.jsx)(o,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.capture`,`Capture {{variant}}`,{variant:y(t,n).toLowerCase()}),i&&(0,T.jsx)(`span`,{className:`ml-1 text-foreground-subtle/70`,children:n(`uiLibrary.sceneMonitor.optional`,`(optional)`)})]},t)}),(0,T.jsxs)(`button`,{type:`button`,className:q,disabled:O!==null||e.states.length===0,title:n(`uiLibrary.sceneMonitor.resetTitle`,`Clear the flag and re-photograph what normal looks like right now`),onClick:()=>void $(`reset-${e.id}`,async()=>r.sceneMonitor?.resetScene({monitorId:e.id})),children:[(0,T.jsx)(x,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.reset`,`Reset`)]}),(0,T.jsx)(`button`,{type:`button`,className:K,disabled:O!==null,onClick:()=>void $(`check-${e.id}`,async()=>r.sceneMonitor?.recheckNow({monitorId:e.id})),children:n(`uiLibrary.sceneMonitor.checkNow`,`Check now`)}),(0,T.jsxs)(`button`,{type:`button`,className:F===e.id?q:K,"aria-pressed":F===e.id,"aria-label":n(`uiLibrary.sceneMonitor.settingsFor`,`Settings for {{label}}`,{label:e.label}),disabled:O!==null,onClick:()=>I(t=>t===e.id?null:e.id),children:[(0,T.jsx)(C,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.settings`,`Settings`)]}),(0,T.jsx)(`button`,{type:`button`,className:J,disabled:O!==null,onClick:()=>void $(`del-${e.id}`,async()=>r.sceneMonitor?.deleteScene({monitorId:e.id})),children:(0,T.jsx)(f,{className:`h-3 w-3`})})]}),F===e.id&&(0,T.jsx)(U,{monitor:e,disabled:O!==null,onPatch:t=>void $(`tune-${e.id}`,async()=>r.sceneMonitor?.updateScene({monitorId:e.id,patch:t}))})]},e.id)})})]})}):null}export{Q as SceneMonitorEditor};
|