@camstack/addon-agent-ui 1.2.351 → 1.2.352

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/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.352] - 2026-11-09
11
+
12
+ ### Changed
13
+
14
+ - Rebuilt against updated dependencies.
15
+
10
16
  ## [1.2.351] - 2026-11-09
11
17
 
12
18
  ### Changed
@@ -367,14 +367,14 @@
367
367
  "server/server/packages/addon-agent-ui/src/updates-panel.tsx": "ec6b5b3701322bbb68dbe581703409cd373418cf9227ce829ea0945e70e60d54",
368
368
  "server/server/packages/addon-agent-ui/src/use-pairing-status.ts": "46193a1e83a64250dfff75d417448d74ba0159465e710861b996e903b03e427a",
369
369
  "server/server/packages/sdk/dist/index.js": "89409bfb9632c46ed63ee4882c70b7994c1ff90c67d27a5d4b86b0990c41a131",
370
- "server/server/packages/types/dist/client.mjs": "88a71d1f1ab574370c175dae7e21b124bc4f45e6b100176e54c7c0ee3819e005",
370
+ "server/server/packages/types/dist/client.mjs": "ab4b63f8faa3c0cef26052a407d63509d0104362261c2b9631b2ea211d618c93",
371
371
  "server/server/packages/types/dist/composition-CXCX78IR.mjs": "3c34431544a6b88404f5f7c855c84ebbf2e5736ad300d4d4dfa2417759f30d46",
372
372
  "server/server/packages/types/dist/convert-BAzH15HU.mjs": "baa969d99051a3b05b2620e8b8e776a6f5539ac74ee9f9dbee1cc6b6a3a892a0",
373
373
  "server/server/packages/types/dist/device-type-CbDhVwFP.mjs": "94089ac4e225fbd1a8109ff7d5db680acff824c0745d60a1ccf52b4e2717451d",
374
374
  "server/server/packages/types/dist/event-category-78tcBemo.mjs": "767f1bb576027d9ee79ec036367001330203d84842c2c2111fa465fa434045e2",
375
- "server/server/packages/types/dist/index.mjs": "8ae165be10eef4d5ffc045bd507593595607fd673ea000a8f1609e44b1f96a98",
375
+ "server/server/packages/types/dist/index.mjs": "c81ce138f6e772774cbf59eca26247d111c9dd956e9295c83accf7bdc5be1087",
376
376
  "server/server/packages/types/dist/node-color.mjs": "942e663074f0a4ed2f8643848ec139eba4b5fd16d0d01c8a23e08a4819a8843b",
377
- "server/server/packages/types/dist/sleep-CS5Xp1iQ.mjs": "8f49002aa65d022d22d0104c15f3b691013ce9b20be91323a81ba058b0827e52",
377
+ "server/server/packages/types/dist/sleep-CkinsFFb.mjs": "ad13e74a69ba822ea871380a133cb56ecbcfcd844c2276a8fe5783c0ed113de3",
378
378
  "server/server/packages/ui-library/src/composites/adaptive-downlink.ts": "6e9698e97721461b72a3750f930dd7214d049be865491bfb5a64b87c032b0e22",
379
379
  "server/server/packages/ui-library/src/composites/camera-stream-player.tsx": "c04809229b4e091fed09e2bcfe643d6b36cb3a39e294229b49f8a095e4e6ebd3",
380
380
  "server/server/packages/ui-library/src/composites/cap-settings/CameraGridLayoutEditor.tsx": "fae93899c4903183e1b78db39e58bd52a3dd4dc2c11ce85d64e6d2a2bab7516d",
@@ -486,7 +486,7 @@
486
486
  "server/server/packages/ui-library/src/contexts/vod-playback.tsx": "c07bc3ecc786182ef2f27d13bec3f520288e0b47367edd7ddf1c2e7b4a2bbaee",
487
487
  "server/server/packages/ui-library/src/contexts/widget-registry.tsx": "71a2815c645abe96821a9e9e5668e321187527d569414f5ca197d615856cc17a",
488
488
  "server/server/packages/ui-library/src/contexts/zone-editing.tsx": "275f680304ab09dd747fd9e859dc5aa0535df33eb2d33eb669a6de66bdd848d0",
489
- "server/server/packages/ui-library/src/generated/system-hooks.ts": "80e1096642baea14bd28d7fc8a0311e9d2cd71d8a25a16c1255b4dfbe5c7de78",
489
+ "server/server/packages/ui-library/src/generated/system-hooks.ts": "f1242a45e5897fbc5318df28fe19dd1c0a72b6ac59b3a07362869cd5851f8eb8",
490
490
  "server/server/packages/ui-library/src/hooks/turn-server-cache.ts": "fab22f069c3761e0d268449e056d9c499b6ea63b879684a40023735b34bad061",
491
491
  "server/server/packages/ui-library/src/hooks/use-device-capability.ts": "b74ede60be8039c45f6c712a407ee838ab208610435870ddabaa8598cb7cd7f3",
492
492
  "server/server/packages/ui-library/src/hooks/use-device-detections.ts": "61cc54cf2e7f53864e5aa533c05679e4c204c4004a0296f63ab169b003888611",
package/dist/addon.js CHANGED
@@ -5880,7 +5880,7 @@ var ZodIssueCode = {
5880
5880
  var ZodFirstPartyTypeKind;
5881
5881
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5882
5882
  //#endregion
5883
- //#region ../types/dist/sleep-CS5Xp1iQ.mjs
5883
+ //#region ../types/dist/sleep-CkinsFFb.mjs
5884
5884
  /**
5885
5885
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5886
5886
  * window to float samples (D455).
@@ -8011,16 +8011,6 @@ var BaseAddon = class {
8011
8011
  return null;
8012
8012
  }
8013
8013
  /**
8014
- * The form schema of ONE integration instance, or `null` to use the scoped form.
8015
- *
8016
- * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
8017
- * needs its connection fields, the default integration the addon's own sections. It is the
8018
- * SHAPE of the form only — the values live on the instance record.
8019
- */
8020
- async integrationFormSchema(_instance) {
8021
- return null;
8022
- }
8023
- /**
8024
8014
  * D986 scoped settings: the form shape of the CLUSTER-wide settings one capability this addon
8025
8015
  * provides owns, or `null` when it has none. `cap` is a capability name the addon's manifest
8026
8016
  * declares. Stored as `addon-settings` rows `<addonId>.<field>`. The keys of this schema, of
@@ -8048,8 +8038,7 @@ var BaseAddon = class {
8048
8038
  }
8049
8039
  /**
8050
8040
  * The host's read of the scoped declarations: the schema one capability declares for `scope`,
8051
- * or `null`. Beside {@link getIntegrationFormSchema}, the same shape: the hook is protected, the
8052
- * host asks this method.
8041
+ * or `null`. The hook is protected, the host asks this method.
8053
8042
  */
8054
8043
  async getScopedSettingsSchema(scope, cap) {
8055
8044
  switch (scope) {
@@ -8217,22 +8206,6 @@ var BaseAddon = class {
8217
8206
  return Object.fromEntries([...collectHydratedFieldValues(form)].filter(([, value]) => value !== void 0));
8218
8207
  }
8219
8208
  /**
8220
- * What the create wizard and the integration edit page render for one instance: the addon's
8221
- * per-instance schema when it declares one, else its global schema (what they always showed).
8222
- */
8223
- async getIntegrationFormSchema(instance, nodeId) {
8224
- const scoped = await this.integrationFormSchema(instance);
8225
- if (scoped === null) return this.scopedForm(nodeId);
8226
- return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
8227
- }
8228
- /** The form of the scoped schemas this addon declared, hydrated from its store. */
8229
- async scopedForm(nodeId) {
8230
- await this.loadScopedSchemas();
8231
- const schema = this.scopedFormSchema();
8232
- if (!schema) return { sections: [] };
8233
- return hydrateSchema(schema, await this.readRawProjectedStore(nodeId), { inlineSecretsAsViews: true });
8234
- }
8235
- /**
8236
8209
  * The raw addon store PROJECTED onto the target node's bare per-node keys:
8237
8210
  * every `perNode: true` field carries THAT node's scoped value on its bare
8238
8211
  * key (absent scoped key ⇒ key absent, so the schema `default` wins — no
@@ -8419,12 +8392,6 @@ var BaseAddon = class {
8419
8392
  this._nodeKeysByCap = /* @__PURE__ */ new Map();
8420
8393
  this._addonSecretTree = null;
8421
8394
  }
8422
- /** The form of an addon's scoped schemas: their sections, each shown once. */
8423
- scopedFormSchema(cap) {
8424
- const byId = /* @__PURE__ */ new Map();
8425
- for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
8426
- return byId.size === 0 ? null : { sections: [...byId.values()] };
8427
- }
8428
8395
  /** Every loaded scoped schema of `cap`, or of every loaded capability. */
8429
8396
  loadedScopedSchemas(cap) {
8430
8397
  if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
@@ -9014,6 +8981,10 @@ function normalizeAddonInitResult(result) {
9014
8981
  if (Array.isArray(result)) return { providers: result };
9015
8982
  return result;
9016
8983
  }
8984
+ object({
8985
+ code: string().regex(/^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/),
8986
+ params: record(string(), union([string(), number$1()])).optional()
8987
+ });
9017
8988
  object({
9018
8989
  field: string().min(1),
9019
8990
  on: literal("advance")
@@ -9090,12 +9061,6 @@ function systemMethod(input, output, options) {
9090
9061
  systemOnly: true
9091
9062
  };
9092
9063
  }
9093
- /**
9094
- * A method a SOURCE of a collection cap may legitimately not serve — OPTIONAL
9095
- * on `InferProvider`. The `providerOptional: true` literal is what
9096
- * `InferProvider` keys on; see {@link CapabilityMethodSchema.providerOptional}
9097
- * for when this is the honest answer and when it is a soft stub.
9098
- */
9099
9064
  function optionalMethod(input, output, options) {
9100
9065
  return {
9101
9066
  ...method(input, output, options),
@@ -16318,10 +16283,6 @@ Object.freeze({
16318
16283
  "uploadDataset": 72e5
16319
16284
  }
16320
16285
  });
16321
- object({
16322
- code: string().regex(/^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/),
16323
- params: record(string(), union([string(), number$1()])).optional()
16324
- });
16325
16286
  //#endregion
16326
16287
  //#region ../types/dist/composition-CXCX78IR.mjs
16327
16288
  /**
@@ -18033,25 +17994,6 @@ var RequiredAddonUpdateSchema = object({
18033
17994
  installed: string().nullable()
18034
17995
  });
18035
17996
  /**
18036
- * Which integration instance a settings form is for. An addon that serves several instances
18037
- * (`instanceMode: multiple`) can show a different form per instance — an NVR integration
18038
- * needs its connection, the default integration needs the addon's own sections.
18039
- *
18040
- * - `integrationId` is `null` in the create wizard: nothing exists yet, the operator is
18041
- * setting up a NEW instance.
18042
- * - `mode` is the wizard mode of the integration type (`getAvailableTypes`).
18043
- * - `candidateStableId` is the discovery candidate a wizard was seeded from, if any.
18044
- */
18045
- var IntegrationInstanceRefSchema = object({
18046
- integrationId: string().nullable(),
18047
- mode: _enum([
18048
- "standalone",
18049
- "account",
18050
- "broker"
18051
- ]),
18052
- candidateStableId: string().optional()
18053
- });
18054
- /**
18055
17997
  * notification-output — canonical, capability-gated notification delivery.
18056
17998
  *
18057
17999
  * Apprise-derived model (see
@@ -44137,10 +44079,6 @@ method(object({
44137
44079
  scope: ScopedSettingsScopeSchema,
44138
44080
  nodeId: string().optional()
44139
44081
  }), record(string(), unknown()).nullable()), method(object({
44140
- addonId: string(),
44141
- nodeId: string().optional(),
44142
- instance: IntegrationInstanceRefSchema
44143
- }), SettingsSchemaWithValuesSchema.nullable()), method(object({
44144
44082
  addonId: string(),
44145
44083
  nodeId: string().optional()
44146
44084
  }), object({ keys: array(string()) }).nullable(), { auth: "admin" }), method(object({
@@ -54507,6 +54445,12 @@ var IntegrationWithStateSchema = object({
54507
54445
  name: string(),
54508
54446
  /** A projection of the instance body's switch (`setInstanceEnabled` is the only writer). */
54509
54447
  enabled: boolean(),
54448
+ /**
54449
+ * True when the addon that holds the switch did not answer (down, failed, or keeps no record for
54450
+ * this row): `enabled` is then the stored registry flag, a stale value, not the state. A reader
54451
+ * must not draw it as on or off (D315).
54452
+ */
54453
+ switchUnknown: literal(true).optional(),
54510
54454
  info: record(string(), unknown()).readonly(),
54511
54455
  /** The discovery candidate this integration was set up from (D984). */
54512
54456
  originStableId: string().optional(),
@@ -54520,6 +54464,12 @@ var IntegrationLiteSchema = object({
54520
54464
  name: string(),
54521
54465
  /** A projection of the instance body's switch (`setInstanceEnabled` is the only writer). */
54522
54466
  enabled: boolean(),
54467
+ /**
54468
+ * True when the addon that holds the switch did not answer (down, failed, or keeps no record for
54469
+ * this row): `enabled` is then the stored registry flag, a stale value, not the state. A reader
54470
+ * must not draw it as on or off (D315).
54471
+ */
54472
+ switchUnknown: literal(true).optional(),
54523
54473
  info: record(string(), unknown()).readonly(),
54524
54474
  /** The discovery candidate this integration was set up from (D984). */
54525
54475
  originStableId: string().optional(),
@@ -54618,9 +54568,9 @@ var UpdateIntegrationInputSchema = object({
54618
54568
  id: string(),
54619
54569
  name: string().optional(),
54620
54570
  /**
54621
- * Accepted for a FIXED builtin only (`info.fixed`: alarm panel, camera grid, core blocks,
54622
- * terminal), which keeps no instance record: the registry flag is its one store. Any other
54623
- * integration is switched through `integration-instances.setInstanceEnabled`.
54571
+ * Honoured ONLY for a fixed builtin (`info.fixed`), whose switch is the hub's own registry flag
54572
+ * and works while its addon is down. Any other row is refused (a typed `BAD_REQUEST`): its
54573
+ * switch is `integration-instances.setInstanceEnabled`.
54624
54574
  */
54625
54575
  enabled: boolean().optional(),
54626
54576
  info: record(string(), unknown()).optional()
@@ -54654,6 +54604,12 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
54654
54604
  * the form as a `SecretFieldView`, never text (D789). A patch value omitted, `''`
54655
54605
  * or the redaction token KEEPS the stored secret; `{ $secretClear: true }`
54656
54606
  * clears it.
54607
+ *
54608
+ * Who may call (epic #440): every method but `describe` and `instanceFormSchema` is open to any
54609
+ * session (`caller: 'scoped'`) and default-denies. An admin passes everywhere. A non-admin passes
54610
+ * only on a call pinned to one addon whose `describe().userScoped` is true, and that provider
54611
+ * filters every row on the `caller` the router injected (a notification target is somebody's
54612
+ * phone: a user sees and changes only their own). An addon that says nothing keeps the admin door.
54657
54613
  */
54658
54614
  var ConfigSchemaPassthrough$2 = custom();
54659
54615
  var IntegrationInstanceIdSchema = string().min(1);
@@ -54767,7 +54723,15 @@ var IntegrationInstanceDescriptionSchema = object({
54767
54723
  */
54768
54724
  mintsId: boolean().optional(),
54769
54725
  /** The links to other instances the provider's kinds declare; absent means none. */
54770
- links: array(IntegrationInstanceLinkSchema).optional()
54726
+ links: array(IntegrationInstanceLinkSchema).optional(),
54727
+ /**
54728
+ * The rows belong to individual users (a notification target is somebody's phone). The router
54729
+ * then opens `listInstances`, `getInstance`, `createInstance`, `updateInstance`,
54730
+ * `setInstanceEnabled`, `deleteInstance` and `testInstance` to a NON-admin session on a call
54731
+ * pinned to this addon, and hands the provider the `caller` so it keeps a user to their own rows.
54732
+ * Absent means admin only, as it always was.
54733
+ */
54734
+ userScoped: boolean().optional()
54771
54735
  });
54772
54736
  var IntegrationInstanceStatusSchema = object({
54773
54737
  state: _enum([
@@ -54817,7 +54781,13 @@ method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }), method
54817
54781
  ]).optional(),
54818
54782
  /** The discovery candidate the wizard was seeded from (`stableId`), if any. */
54819
54783
  candidateStableId: string().min(1).optional()
54820
- }), ConfigSchemaPassthrough$2, { auth: "admin" }), method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }), method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }), method(object({
54784
+ }), ConfigSchemaPassthrough$2, { auth: "admin" }), method(looseObject({}), array(IntegrationInstanceSchema).readonly(), {
54785
+ auth: "protected",
54786
+ caller: "scoped"
54787
+ }), method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), {
54788
+ auth: "protected",
54789
+ caller: "scoped"
54790
+ }), method(object({
54821
54791
  id: IntegrationInstanceIdSchema.optional(),
54822
54792
  name: string().min(1),
54823
54793
  kind: string().min(1).optional(),
@@ -54839,19 +54809,23 @@ method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }), method
54839
54809
  seed: object({ info: record(string(), unknown()) }).optional()
54840
54810
  }), object({ id: string() }), {
54841
54811
  kind: "mutation",
54842
- auth: "admin"
54812
+ auth: "protected",
54813
+ caller: "scoped"
54843
54814
  }), method(IntegrationInstancePatchSchema, object({ ok: literal(true) }), {
54844
54815
  kind: "mutation",
54845
- auth: "admin"
54816
+ auth: "protected",
54817
+ caller: "scoped"
54846
54818
  }), method(object({
54847
54819
  id: IntegrationInstanceIdSchema,
54848
54820
  enabled: boolean()
54849
54821
  }), object({ ok: literal(true) }), {
54850
54822
  kind: "mutation",
54851
- auth: "admin"
54823
+ auth: "protected",
54824
+ caller: "scoped"
54852
54825
  }), method(object({ id: IntegrationInstanceIdSchema }), object({ ok: literal(true) }), {
54853
54826
  kind: "mutation",
54854
- auth: "admin"
54827
+ auth: "protected",
54828
+ caller: "scoped"
54855
54829
  }), optionalMethod(object({
54856
54830
  id: IntegrationInstanceIdSchema.nullable(),
54857
54831
  values: record(string(), unknown()),
@@ -54859,7 +54833,8 @@ method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }), method
54859
54833
  kind: string().min(1).optional()
54860
54834
  }), TestConnectionResultSchema, {
54861
54835
  kind: "mutation",
54862
- auth: "admin"
54836
+ auth: "protected",
54837
+ caller: "scoped"
54863
54838
  });
54864
54839
  /**
54865
54840
  * Two-way audio / talk-back cap. Clients open a WebRTC audio-only
@@ -72707,6 +72682,11 @@ var SendEmailResultSchema = object({
72707
72682
  var SmtpStatusSchema = object({
72708
72683
  /** True iff the addon has successfully verified the relay. */
72709
72684
  ready: boolean(),
72685
+ /**
72686
+ * The tri-state behind `ready` (D315): true when the last send worked, false when it failed,
72687
+ * ABSENT when the relay has not sent yet (unknown, never "not verified").
72688
+ */
72689
+ verified: boolean().optional(),
72710
72690
  /** Operator-visible host string (no credentials). */
72711
72691
  host: string(),
72712
72692
  /** Last error message reported by the relay, when not ready. */
@@ -72718,10 +72698,6 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
72718
72698
  kind: "mutation",
72719
72699
  auth: "admin",
72720
72700
  access: "create"
72721
- }), method(RelayPinSchema, SmtpStatusSchema, {
72722
- kind: "mutation",
72723
- auth: "admin",
72724
- access: "view"
72725
72701
  }), method(RelayPinSchema, SmtpStatusSchema, { auth: "admin" });
72726
72702
  /**
72727
72703
  * A single device snapshot returned as base64 JPEG/PNG.
@@ -79390,13 +79366,6 @@ Object.freeze({
79390
79366
  access: "view",
79391
79367
  role: "admin"
79392
79368
  },
79393
- "addonSettings.getIntegrationFormSchema": {
79394
- capName: "addon-settings",
79395
- capScope: "system",
79396
- addonId: null,
79397
- access: "view",
79398
- role: "admin"
79399
- },
79400
79369
  "addonSettings.getScopedSettings": {
79401
79370
  capName: "addon-settings",
79402
79371
  capScope: "system",
@@ -87419,13 +87388,6 @@ Object.freeze({
87419
87388
  access: "create",
87420
87389
  role: "admin"
87421
87390
  },
87422
- "smtpProvider.verify": {
87423
- capName: "smtp-provider",
87424
- capScope: "system",
87425
- addonId: null,
87426
- access: "view",
87427
- role: "admin"
87428
- },
87429
87391
  "snapshot.applyDeviceSettingsPatch": {
87430
87392
  capName: "snapshot",
87431
87393
  capScope: "device",
@@ -93801,7 +93763,7 @@ var AgentUIAddon = class extends BaseAddon {
93801
93763
  capability: adminUiCapability,
93802
93764
  provider: {
93803
93765
  getStaticDir: async () => ({ staticDir: path.resolve(__dirname) }),
93804
- getVersion: async () => ({ version: "1.2.351" })
93766
+ getVersion: async () => ({ version: "1.2.352" })
93805
93767
  }
93806
93768
  }];
93807
93769
  }
@@ -1 +1 @@
1
- import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{T as a,_ as o,c as s,d as c,l,p as u,t as d}from"./index-833yqb2D.js";import{MaskShapeCanvas as f}from"./MaskShapeCanvas-BTvl6Rin.js";var p=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`}]]),m=n(i(),1),h=n(e(),1),g=110,_=`motion-zones`,v=0,y=[1,2,3],b=1,x=`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`,S=`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 C(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 w(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 T(e,t){return Math.ceil(e/t)}function E(e,t,n){return Math.min(n-1,Math.floor((e+.5)/t*n))}function D(e,t,n,r){let i=T(t,r),a=T(n,r),o=Array.from({length:i*a},()=>!1);for(let r=0;r<n;r+=1){let s=E(r,n,a);for(let n=0;n<t;n+=1)e[r*t+n]===!0&&(o[s*i+E(n,t,i)]=!0)}return o}function O(e,t,n,r){let i=T(t,r),a=T(n,r),o=Array.from({length:t*n},()=>!1);for(let r=0;r<n;r+=1){let s=E(r,n,a);for(let n=0;n<t;n+=1)o[r*t+n]=e[s*i+E(n,t,i)]===!0}return o}function k({deviceId:e}){let n=t(),i=c(o().trpcClient,e),[a,E]=(0,m.useState)(null),[k,A]=(0,m.useState)(!1),[j,M]=(0,m.useState)(null),[N,P]=(0,m.useState)(null),[F,I]=(0,m.useState)(b),[L,R]=(0,m.useState)(!1),[z,B]=(0,m.useState)(!1),V=(0,m.useRef)(!1);(0,m.useEffect)(()=>{if(!i)return;let e=!1;return V.current=!1,E(null),A(!1),M(null),P(null),I(b),B(!1),(async()=>{try{let t=await i.motionZones?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(E(t),V.current)return;let n=await i.motionZones?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);V.current=!0;let r=n.regions.find(e=>e.shape.kind===`grid`),a=C(r?r.shape.cells:[],t.grid.width,t.grid.height);P(a),M(D(a,t.grid.width,t.grid.height,b))}catch(t){if(e)return;l(t)?A(!0):console.error(`Motion Zones load failed`,t)}})(),()=>{e=!0}},[i]);let H=a?a.grid.width:0,U=a?a.grid.height:0,W=T(H,F),G=T(U,F),K=(0,m.useMemo)(()=>j&&a?O(j,H,U,F):null,[j,a,H,U,F]),q=(0,m.useMemo)(()=>K!==null&&N!==null&&!w(K,N),[K,N]),J=(0,m.useMemo)(()=>K?K.reduce((e,t)=>t?e+1:e,0):0,[K]),Y=H*U,X=W*G,Z=(0,m.useCallback)(e=>{X>0&&M(Array.from({length:X},()=>e))},[X]),ee=(0,m.useCallback)(()=>{M(e=>e&&e.map(e=>!e))},[]),te=(0,m.useCallback)(()=>{N&&a&&M(D(N,H,U,F))},[N,a,H,U,F]),ne=(0,m.useCallback)(e=>{e===F||!a||M(t=>{if(!t)return I(e),t;let n=D(O(t,H,U,F),H,U,e);return I(e),n})},[F,a,H,U]),Q=(0,m.useMemo)(()=>a&&j?[{id:v,shape:{kind:`grid`,gridWidth:W,gridHeight:G,cells:[...j]}}]:[],[a,j,W,G]),$=(0,m.useCallback)((e,t)=>{t.kind===`grid`&&M(t.cells)},[]),re=(0,m.useCallback)(async()=>{if(!(!i||!j||!a)){R(!0);try{let e=O(j,a.grid.width,a.grid.height,F),t={kind:`grid`,gridWidth:a.grid.width,gridHeight:a.grid.height,cells:e};await i.motionZones?.setZone({patch:{regions:[{id:v,enabled:!0,shape:t}]}});let n=await i.motionZones?.getStatus({});if(n){let e=n.regions.find(e=>e.shape.kind===`grid`),t=C(e?e.shape.cells:[],a.grid.width,a.grid.height);P(t),M(D(t,a.grid.width,a.grid.height,F))}}catch(e){console.error(`Motion Zones save failed`,e)}finally{R(!1)}}},[i,j,a,F]);d((0,m.useMemo)(()=>z&&!k&&a&&j?{id:_,order:g,node:(0,h.jsx)(f,{transparent:!0,items:Q,supportedShapes:[`grid`],grid:{width:W,height:G},selectedId:v,onSelect:()=>{},onShapeChange:$,onDrawComplete:()=>{},drawingKind:null})}:null,[z,k,a,j,Q,$,W,G]));let ie=!k&&a!==null&&j!==null;return i?(0,h.jsx)(u,{title:n(`uiLibrary.motionZones.title`,`Motion Zones`),icon:(0,h.jsx)(p,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,h.jsx)(`div`,{className:`flex flex-col gap-3`,children:k?(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:n(`uiLibrary.motionZones.unsupported`,`This camera doesn't expose an on-board motion zones grid.`)}):ie?(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:(0,h.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,h.jsx)(`strong`,{className:`text-foreground`},`e`),(0,h.jsx)(`strong`,{className:`text-foreground`},`s`),(0,h.jsx)(`strong`,{className:`text-foreground`},`c`)],values:{width:a.grid.width,height:a.grid.height}})}),(0,h.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,h.jsx)(`button`,{type:`button`,onClick:()=>B(e=>!e),disabled:L,"aria-pressed":z,className:z?S:x,children:z?n(`uiLibrary.motionZones.doneEditing`,`Done editing`):n(`uiLibrary.motionZones.editGrid`,`Edit grid`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>Z(!0),disabled:L,className:x,children:n(`uiLibrary.motionZones.allOn`,`All on`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>Z(!1),disabled:L,className:x,children:n(`uiLibrary.motionZones.allOff`,`All off`)}),(0,h.jsx)(`button`,{type:`button`,onClick:ee,disabled:L,className:x,children:n(`uiLibrary.motionZones.invert`,`Invert`)}),(0,h.jsxs)(`div`,{className:`flex items-center gap-1 ml-1`,role:`group`,"aria-label":n(`uiLibrary.motionZones.cellSize`,`Cell size`),children:[(0,h.jsx)(`span`,{className:`${s} mr-0.5`,children:n(`uiLibrary.motionZones.cellSize`,`Cell size`)}),y.map(e=>(0,h.jsxs)(`button`,{type:`button`,onClick:()=>ne(e),disabled:L,"aria-pressed":F===e,title:e===1?n(`uiLibrary.motionZones.cameraGrid`,`Camera grid {{width}}×{{height}} (finest)`,{width:H,height:U}):n(`uiLibrary.motionZones.paintingGrid`,`{{width}}×{{height}} painting grid · cells ×{{factor}} bigger`,{width:T(H,e),height:T(U,e),factor:e}),className:F===e?S:x,children:[`×`,e]},e))]}),(0,h.jsxs)(`span`,{className:`${s} ml-1 tabular-nums`,children:[n(`uiLibrary.motionZones.cells`,`{{active}} / {{total}} cells · {{width}}×{{height}}`,{active:J,total:Y,width:a.grid.width,height:a.grid.height}),F===1?``:n(`uiLibrary.motionZones.paintSuffix`,` · paint {{width}}×{{height}}`,{width:W,height:G})]}),(0,h.jsx)(`span`,{className:`flex-1`}),(0,h.jsx)(`button`,{type:`button`,onClick:te,disabled:L||!q,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,h.jsx)(`button`,{type:`button`,onClick:()=>void re(),disabled:L||!q,className:S,children:L?n(`uiLibrary.motionZones.saving`,`Saving…`):n(`uiLibrary.motionZones.save`,`Save`)})]})]}):(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:n(`uiLibrary.motionZones.loading`,`Loading the camera's grid…`)})})}):null}export{k as MotionZonesSettings};
1
+ import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{T as a,_ as o,c as s,d as c,l,p as u,t as d}from"./index-BePyQ_Ek.js";import{MaskShapeCanvas as f}from"./MaskShapeCanvas-BTvl6Rin.js";var p=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`}]]),m=n(i(),1),h=n(e(),1),g=110,_=`motion-zones`,v=0,y=[1,2,3],b=1,x=`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`,S=`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 C(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 w(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 T(e,t){return Math.ceil(e/t)}function E(e,t,n){return Math.min(n-1,Math.floor((e+.5)/t*n))}function D(e,t,n,r){let i=T(t,r),a=T(n,r),o=Array.from({length:i*a},()=>!1);for(let r=0;r<n;r+=1){let s=E(r,n,a);for(let n=0;n<t;n+=1)e[r*t+n]===!0&&(o[s*i+E(n,t,i)]=!0)}return o}function O(e,t,n,r){let i=T(t,r),a=T(n,r),o=Array.from({length:t*n},()=>!1);for(let r=0;r<n;r+=1){let s=E(r,n,a);for(let n=0;n<t;n+=1)o[r*t+n]=e[s*i+E(n,t,i)]===!0}return o}function k({deviceId:e}){let n=t(),i=c(o().trpcClient,e),[a,E]=(0,m.useState)(null),[k,A]=(0,m.useState)(!1),[j,M]=(0,m.useState)(null),[N,P]=(0,m.useState)(null),[F,I]=(0,m.useState)(b),[L,R]=(0,m.useState)(!1),[z,B]=(0,m.useState)(!1),V=(0,m.useRef)(!1);(0,m.useEffect)(()=>{if(!i)return;let e=!1;return V.current=!1,E(null),A(!1),M(null),P(null),I(b),B(!1),(async()=>{try{let t=await i.motionZones?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(E(t),V.current)return;let n=await i.motionZones?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);V.current=!0;let r=n.regions.find(e=>e.shape.kind===`grid`),a=C(r?r.shape.cells:[],t.grid.width,t.grid.height);P(a),M(D(a,t.grid.width,t.grid.height,b))}catch(t){if(e)return;l(t)?A(!0):console.error(`Motion Zones load failed`,t)}})(),()=>{e=!0}},[i]);let H=a?a.grid.width:0,U=a?a.grid.height:0,W=T(H,F),G=T(U,F),K=(0,m.useMemo)(()=>j&&a?O(j,H,U,F):null,[j,a,H,U,F]),q=(0,m.useMemo)(()=>K!==null&&N!==null&&!w(K,N),[K,N]),J=(0,m.useMemo)(()=>K?K.reduce((e,t)=>t?e+1:e,0):0,[K]),Y=H*U,X=W*G,Z=(0,m.useCallback)(e=>{X>0&&M(Array.from({length:X},()=>e))},[X]),ee=(0,m.useCallback)(()=>{M(e=>e&&e.map(e=>!e))},[]),te=(0,m.useCallback)(()=>{N&&a&&M(D(N,H,U,F))},[N,a,H,U,F]),ne=(0,m.useCallback)(e=>{e===F||!a||M(t=>{if(!t)return I(e),t;let n=D(O(t,H,U,F),H,U,e);return I(e),n})},[F,a,H,U]),Q=(0,m.useMemo)(()=>a&&j?[{id:v,shape:{kind:`grid`,gridWidth:W,gridHeight:G,cells:[...j]}}]:[],[a,j,W,G]),$=(0,m.useCallback)((e,t)=>{t.kind===`grid`&&M(t.cells)},[]),re=(0,m.useCallback)(async()=>{if(!(!i||!j||!a)){R(!0);try{let e=O(j,a.grid.width,a.grid.height,F),t={kind:`grid`,gridWidth:a.grid.width,gridHeight:a.grid.height,cells:e};await i.motionZones?.setZone({patch:{regions:[{id:v,enabled:!0,shape:t}]}});let n=await i.motionZones?.getStatus({});if(n){let e=n.regions.find(e=>e.shape.kind===`grid`),t=C(e?e.shape.cells:[],a.grid.width,a.grid.height);P(t),M(D(t,a.grid.width,a.grid.height,F))}}catch(e){console.error(`Motion Zones save failed`,e)}finally{R(!1)}}},[i,j,a,F]);d((0,m.useMemo)(()=>z&&!k&&a&&j?{id:_,order:g,node:(0,h.jsx)(f,{transparent:!0,items:Q,supportedShapes:[`grid`],grid:{width:W,height:G},selectedId:v,onSelect:()=>{},onShapeChange:$,onDrawComplete:()=>{},drawingKind:null})}:null,[z,k,a,j,Q,$,W,G]));let ie=!k&&a!==null&&j!==null;return i?(0,h.jsx)(u,{title:n(`uiLibrary.motionZones.title`,`Motion Zones`),icon:(0,h.jsx)(p,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,h.jsx)(`div`,{className:`flex flex-col gap-3`,children:k?(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:n(`uiLibrary.motionZones.unsupported`,`This camera doesn't expose an on-board motion zones grid.`)}):ie?(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:(0,h.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,h.jsx)(`strong`,{className:`text-foreground`},`e`),(0,h.jsx)(`strong`,{className:`text-foreground`},`s`),(0,h.jsx)(`strong`,{className:`text-foreground`},`c`)],values:{width:a.grid.width,height:a.grid.height}})}),(0,h.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,h.jsx)(`button`,{type:`button`,onClick:()=>B(e=>!e),disabled:L,"aria-pressed":z,className:z?S:x,children:z?n(`uiLibrary.motionZones.doneEditing`,`Done editing`):n(`uiLibrary.motionZones.editGrid`,`Edit grid`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>Z(!0),disabled:L,className:x,children:n(`uiLibrary.motionZones.allOn`,`All on`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>Z(!1),disabled:L,className:x,children:n(`uiLibrary.motionZones.allOff`,`All off`)}),(0,h.jsx)(`button`,{type:`button`,onClick:ee,disabled:L,className:x,children:n(`uiLibrary.motionZones.invert`,`Invert`)}),(0,h.jsxs)(`div`,{className:`flex items-center gap-1 ml-1`,role:`group`,"aria-label":n(`uiLibrary.motionZones.cellSize`,`Cell size`),children:[(0,h.jsx)(`span`,{className:`${s} mr-0.5`,children:n(`uiLibrary.motionZones.cellSize`,`Cell size`)}),y.map(e=>(0,h.jsxs)(`button`,{type:`button`,onClick:()=>ne(e),disabled:L,"aria-pressed":F===e,title:e===1?n(`uiLibrary.motionZones.cameraGrid`,`Camera grid {{width}}×{{height}} (finest)`,{width:H,height:U}):n(`uiLibrary.motionZones.paintingGrid`,`{{width}}×{{height}} painting grid · cells ×{{factor}} bigger`,{width:T(H,e),height:T(U,e),factor:e}),className:F===e?S:x,children:[`×`,e]},e))]}),(0,h.jsxs)(`span`,{className:`${s} ml-1 tabular-nums`,children:[n(`uiLibrary.motionZones.cells`,`{{active}} / {{total}} cells · {{width}}×{{height}}`,{active:J,total:Y,width:a.grid.width,height:a.grid.height}),F===1?``:n(`uiLibrary.motionZones.paintSuffix`,` · paint {{width}}×{{height}}`,{width:W,height:G})]}),(0,h.jsx)(`span`,{className:`flex-1`}),(0,h.jsx)(`button`,{type:`button`,onClick:te,disabled:L||!q,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,h.jsx)(`button`,{type:`button`,onClick:()=>void re(),disabled:L||!q,className:S,children:L?n(`uiLibrary.motionZones.saving`,`Saving…`):n(`uiLibrary.motionZones.save`,`Save`)})]})]}):(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:n(`uiLibrary.motionZones.loading`,`Loading the camera's grid…`)})})}):null}export{k 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 ee,T as a,_ as te,c as o,d as ne,l as s,p as c,t as re,v as l,x as u,y as d}from"./index-833yqb2D.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=ne(te().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;s(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??[];re((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)(c,{title:n(`uiLibrary.privacyMaskSettings.title`,`Privacy Mask`),icon:(0,h.jsx)(ee,{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:d;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)(l,{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)(u,{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 ee,T as a,_ as te,c as o,d as ne,l as s,p as c,t as re,v as l,x as u,y as d}from"./index-BePyQ_Ek.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=ne(te().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;s(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??[];re((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)(c,{title:n(`uiLibrary.privacyMaskSettings.title`,`Privacy Mask`),icon:(0,h.jsx)(ee,{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:d;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)(l,{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)(u,{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{S as a,T as o,_ as s,a as c,b as l,c as u,d,f,g as p,h as m,i as h,l as g,m as _,n as v,o as y,r as b,s as x,u as S,v as C,w}from"./index-833yqb2D.js";import{MaskShapeCanvas as T}from"./MaskShapeCanvas-BTvl6Rin.js";var E=o(`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`}]]),D=n(i(),1),O=n(e(),1),k=_(`/addon/pipeline-analytics/event-media`);function A(e){return`${k}/m/${e}`}function j(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 M(e){return e.states.reduce((e,t)=>e+t.references.length,0)}var N=`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 P({monitor:e,disabled:n,onDelete:r}){let i=t(),a=j(e),o=M(e),s=m();return a.length===0?(0,O.jsx)(`p`,{className:`${u} 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,O.jsx)(`div`,{className:`flex flex-col gap-2`,children:a.map(e=>(0,O.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,O.jsxs)(`p`,{className:`text-[10px] font-semibold uppercase tracking-wide text-foreground-subtle`,children:[x(e.condition,i),` · `,e.tiles.length]}),(0,O.jsx)(`ul`,{className:`flex flex-wrap gap-1.5`,children:e.tiles.map(t=>{let a=`${t.stateId}:${String(t.index)}`,c=o===1;return(0,O.jsxs)(`li`,{className:`flex w-24 flex-col gap-1 rounded-md border border-border p-1`,children:[(0,O.jsx)(F,{reference:t.reference}),(0,O.jsx)(`span`,{className:`${u} tabular-nums`,children:I(t.reference.capturedAt)}),(0,O.jsxs)(`button`,{type:`button`,className:N,disabled:n,"aria-label":i(`uiLibrary.sceneReferenceGallery.deleteLabel`,`Delete the {{variant}} photograph from {{date}}`,{variant:x(e.condition,i).toLowerCase(),date:I(t.reference.capturedAt)}),onClick:()=>{(async()=>{c&&!await s({title:i(`uiLibrary.sceneReferenceGallery.deleteTitle`,`Delete the only photograph?`),message: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.`),confirmLabel:i(`uiLibrary.sceneReferenceGallery.delete`,`Delete`),variant:`danger`})||r({stateId:t.stateId,index:t.index})})()},children:[(0,O.jsx)(C,{className:`mr-1 inline h-3 w-3`}),i(`uiLibrary.sceneReferenceGallery.delete`,`Delete`)]})]},a)})})]},e.condition))})}function F({reference:e}){let n=t(),[r,i]=(0,D.useState)(!1),o=e.thumbnailMediaId;return o===void 0||r?(0,O.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,O.jsx)(a,{className:`h-4 w-4`}),(0,O.jsx)(`span`,{className:`text-[9px]`,children:n(`uiLibrary.sceneReferenceGallery.noPicture`,`no picture`)})]}):(0,O.jsx)(`img`,{src:A(o),alt:``,loading:`lazy`,className:`h-16 w-full rounded object-cover`,onError:()=>i(!0)})}function I(e){let t=new Date(e);return`${t.toLocaleDateString()} ${t.toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`})}`}var L=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground focus:border-primary focus:outline-none disabled:opacity-50`,R=`text-[10px] font-medium text-foreground-subtle`,z=`flex flex-col gap-2 rounded-md border border-border bg-surface/40 p-2`,B=`text-[10px] font-semibold uppercase tracking-wide text-foreground-subtle`,V=5,H=3600;function U(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 W(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 G(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 K({monitor:e,disabled:n,onPatch:i}){let a=t(),[o,s]=(0,D.useState)(()=>G(e)),[c,l]=(0,D.useState)(!1);(0,D.useEffect)(()=>{c||s(G(e))},[e,c]);let d=e=>{l(!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},m=e.confirm?.enabled===!0,h=e.check.mode===`similarity`?e.check.threshold:null;return(0,O.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,O.jsxs)(`div`,{className:z,children:[(0,O.jsx)(`p`,{className:B,children:a(`uiLibrary.sceneSettingsForm.whatItWatches`,`What it watches`)}),(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.name`,`Name`)}),(0,O.jsx)(`input`,{type:`text`,value:o.label,disabled:n,className:L,onFocus:()=>l(!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}):l(!1)}})]}),(0,O.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`input`,{type:`checkbox`,checked:e.enabled,disabled:n,onChange:e=>d({enabled:e.target.checked})}),(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.watching`,`Watching`)})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.whenToLook`,`When to look`)}),(0,O.jsxs)(p,{value:e.triggerMode,disabled:n,className:L,onChange:e=>{let t=e.target.value;(t===`periodic`||t===`on-motion`||t===`both`)&&d({triggerMode:t})},children:[(0,O.jsx)(`option`,{value:`both`,children:a(`uiLibrary.sceneSettingsForm.triggerBoth`,`On the clock and after motion settles`)}),(0,O.jsx)(`option`,{value:`periodic`,children:a(`uiLibrary.sceneSettingsForm.triggerPeriodic`,`On the clock only`)}),(0,O.jsx)(`option`,{value:`on-motion`,children:a(`uiLibrary.sceneSettingsForm.triggerOnMotion`,`After motion settles only`)})]})]}),(0,O.jsx)(`p`,{className:u,children:U(e.triggerMode,a)}),(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.checkEvery`,`Check every (seconds)`)}),(0,O.jsx)(`input`,{type:`number`,min:V,max:H,value:o.checkIntervalSec,disabled:n,className:L,onFocus:()=>l(!0),onChange:e=>s(t=>({...t,checkIntervalSec:Number(e.target.value)||0})),onBlur:()=>{let t=q(o.checkIntervalSec,V,H);t===e.checkIntervalSec?l(!1):d({checkIntervalSec:t})}})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`div`,{className:z,children:[(0,O.jsx)(`p`,{className:B,children:a(`uiLibrary.sceneSettingsForm.howSure`,`How sure it has to be`)}),e.check.mode===`similarity`&&(0,O.jsxs)(O.Fragment,{children:[(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:(0,O.jsx)(r,{uiKey:`uiLibrary.sceneSettingsForm.matchThreshold`,fallback:`Match threshold — <0>{{value}}</0>`,components:[(0,O.jsx)(`span`,{className:`tabular-nums`},`value`)],values:{value:o.threshold.toFixed(2)}})}),(0,O.jsx)(`input`,{type:`range`,min:.3,max:.99,step:.01,value:o.threshold,disabled:n,onFocus:()=>l(!0),onChange:e=>s(t=>({...t,threshold:Number(e.target.value)})),onMouseUp:()=>{o.threshold===h?l(!1):d({check:f({threshold:o.threshold})})},onBlur:()=>{o.threshold===h?l(!1):d({check:f({threshold:o.threshold})})}})]}),(0,O.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,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.agreeingChecks`,`Agreeing checks before it changes its mind`)}),(0,O.jsx)(`input`,{type:`number`,min:1,max:10,value:o.hysteresisCount,disabled:n,className:L,onFocus:()=>l(!0),onChange:e=>s(t=>({...t,hysteresisCount:Number(e.target.value)||1})),onBlur:()=>{let t=q(o.hysteresisCount,1,10);t===e.check.hysteresisCount?l(!1):d({check:f({hysteresisCount:t})})}})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.quietFor`,`Quiet for (seconds) before a check counts`)}),(0,O.jsx)(`input`,{type:`number`,min:0,max:3600,value:o.quietSeconds,disabled:n,className:L,onFocus:()=>l(!0),onChange:e=>s(t=>({...t,quietSeconds:Number(e.target.value)||0})),onBlur:()=>{let t=q(o.quietSeconds,0,3600);t===e.quietSeconds?l(!1):d({quietSeconds:t})}})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.minGap`,`Minimum gap between counted checks (seconds)`)}),(0,O.jsx)(`input`,{type:`number`,min:0,max:3600,value:o.minObservationSpacingSec,disabled:n,className:L,onFocus:()=>l(!0),onChange:e=>s(t=>({...t,minObservationSpacingSec:Number(e.target.value)||0})),onBlur:()=>{let t=q(o.minObservationSpacingSec,0,3600);t===e.minObservationSpacingSec?l(!1):d({minObservationSpacingSec:t})}})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`div`,{className:z,children:[(0,O.jsx)(`p`,{className:B,children:a(`uiLibrary.sceneSettingsForm.lighting`,`Lighting`)}),(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.noReference`,`When a light has no reference of its own`)}),(0,O.jsxs)(p,{value:e.onUncoveredCondition,disabled:n,className:L,onChange:e=>{let t=e.target.value;(t===`skip`||t===`judge-anyway`)&&d({onUncoveredCondition:t})},children:[(0,O.jsx)(`option`,{value:`skip`,children:a(`uiLibrary.sceneSettingsForm.uncoveredSkip`,`Skip the check (recommended)`)}),(0,O.jsx)(`option`,{value:`judge-anyway`,children:a(`uiLibrary.sceneSettingsForm.uncoveredJudge`,`Judge against the other light anyway`)})]})]}),(0,O.jsx)(`p`,{className:u,children:W(e.onUncoveredCondition,a)})]}),(0,O.jsxs)(`div`,{className:z,children:[(0,O.jsx)(`p`,{className:B,children:a(`uiLibrary.sceneSettingsForm.whatYouGetTold`,`What you get told`)}),(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.notificationsFollow`,`Notifications follow`)}),(0,O.jsxs)(p,{value:e.emit,disabled:n,className:L,onChange:e=>{let t=e.target.value;(t===`latched`||t===`live`)&&d({emit:t})},children:[(0,O.jsx)(`option`,{value:`latched`,children:a(`uiLibrary.sceneSettingsForm.emitLatched`,`The flag — told once, still visible at breakfast`)}),(0,O.jsx)(`option`,{value:`live`,children:a(`uiLibrary.sceneSettingsForm.emitLive`,`The live state — follows the region as it changes`)})]})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`input`,{type:`checkbox`,checked:e.autoRestore,disabled:n,onChange:e=>d({autoRestore:e.target.checked})}),(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.autoRestore`,`Clear the flag by itself when things look normal again`)})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`div`,{className:z,children:[(0,O.jsx)(`p`,{className:B,children:a(`uiLibrary.sceneSettingsForm.aiDoubleCheck`,`AI double-check`)}),(0,O.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`input`,{type:`checkbox`,checked:m,disabled:n||o.confirmPrompt.trim().length===0,onChange:e=>d(e.target.checked?{confirm:{...J(o.confirmPrompt.trim()),enabled:!0}}:{confirm:null})}),(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.askVision`,`Ask a vision model before changing the answer`)})]}),(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.question`,`The question to ask`)}),(0,O.jsx)(`input`,{type:`text`,value:o.confirmPrompt,placeholder:a(`uiLibrary.sceneSettingsForm.questionPlaceholder`,`e.g. Is the wheelie bin still there?`),disabled:n,className:L,onFocus:()=>l(!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:{...J(t),enabled:m}}):l(!1)}})]}),(0,O.jsx)(`p`,{className:u,children:(0,O.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,O.jsx)(`strong`,{},`only`)]})})]})]})}function q(e,t,n){return Math.min(n,Math.max(t,e))}function J(e){return{enabled:!1,prompt:e,timeoutMs:8e3,maxImagePx:448,onTimeout:`hold`}}var 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`,X=`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`,Z=`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`,Q={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`},ee=.8,te=3;function $({deviceId:e}){let n=t(),r=d(s().trpcClient,e),i=f(r?.state.sceneMonitor),a=S(e,{width:640}),[o,p]=(0,D.useState)(null),[m,_]=(0,D.useState)(!1),[k,A]=(0,D.useState)(null),[j,M]=(0,D.useState)(null),[N,F]=(0,D.useState)(null),[I,L]=(0,D.useState)(null),[R,z]=(0,D.useState)(!1),[B,V]=(0,D.useState)(null),[H,U]=(0,D.useState)(``),W=(0,D.useRef)(!0);(0,D.useEffect)(()=>(W.current=!0,()=>{W.current=!1}),[]);let G=(0,D.useCallback)(async()=>{if(r)try{let e=await r.sceneMonitor?.listScenes({});if(!W.current)return;e&&p(e)}catch(e){if(!W.current)return;g(e)?_(!0):M(e instanceof Error?e.message:String(e))}},[r]);(0,D.useEffect)(()=>{p(null),_(!1),M(null),G()},[G]);let q=i?.monitors??o?.monitors??[],J=Date.now(),$=(0,D.useCallback)(async(e,t)=>{A(e),M(null);try{await t(),await G()}catch(e){W.current&&M(e instanceof Error?e.message:String(e))}finally{W.current&&A(null)}},[G]),ne=(0,D.useMemo)(()=>{let e=q.map(e=>({id:e.id,shape:e.roi,label:e.label,enabled:e.enabled}));return B===null?e:[...e,{id:`__draft__`,shape:B,label:n(`uiLibrary.sceneMonitor.newSceneLabel`,`New scene`)}]},[q,B,n]),re=(0,D.useCallback)((e,t)=>{if(t.kind===`rect`){if(e===`__draft__`){V(t);return}$(`roi-${String(e)}`,async()=>r?.sceneMonitor?.updateScene({monitorId:String(e),patch:{roi:t}}))}},[r,$]),ie=(0,D.useCallback)(e=>{e.kind===`rect`&&V(e)},[]),ae=(0,D.useCallback)(async()=>{B===null||H.trim().length===0||(await $(`create`,async()=>{await r?.sceneMonitor?.createScene({label:H.trim(),roi:B,check:{mode:`similarity`,threshold:ee,hysteresisCount:te}})}),V(null),U(``),z(!1))},[r,B,H,$]);return r?(0,O.jsx)(`div`,{className:`flex flex-col gap-3`,children:m?(0,O.jsx)(`p`,{className:`${u} leading-relaxed`,children:n(`uiLibrary.sceneMonitor.unsupported`,`Scene monitoring is not available for this camera.`)}):(0,O.jsxs)(O.Fragment,{children:[(0,O.jsx)(`p`,{className:`${u} 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,O.jsx)(`div`,{className:`relative overflow-hidden rounded-md border border-border`,children:(0,O.jsx)(T,{items:ne,supportedShapes:[`rect`],selectedId:N??(B===null?null:`__draft__`),onSelect:e=>F(e===null?null:String(e)),onShapeChange:re,onDrawComplete:ie,drawingKind:R&&B===null?`rect`:null,backdrop:a.src===null?null:(0,O.jsx)(`img`,{src:a.src,alt:``,className:`h-full w-full object-contain`,draggable:!1})})}),(0,O.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,O.jsx)(`button`,{type:`button`,className:R?X:Y,"aria-pressed":R,onClick:()=>{z(e=>!e),V(null)},disabled:k!==null,children:R?n(`uiLibrary.sceneMonitor.cancel`,`Cancel`):n(`uiLibrary.sceneMonitor.newScene`,`+ New scene`)}),(0,O.jsx)(`button`,{type:`button`,className:Y,onClick:a.refresh,disabled:k!==null,children:n(`uiLibrary.sceneMonitor.refresh`,`Refresh frame`)})]}),R&&(0,O.jsxs)(`div`,{className:`flex flex-col gap-2 rounded-md border border-border bg-surface p-2`,children:[(0,O.jsx)(`p`,{className:`text-[10px] text-foreground-subtle`,children:B===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.`)}),B!==null&&c(B)&&(0,O.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,O.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`input`,{type:`text`,value:H,onChange:e=>U(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,O.jsx)(`button`,{type:`button`,className:X,onClick:()=>void ae(),disabled:B===null||H.trim().length===0||k!==null,children:n(`uiLibrary.sceneMonitor.create`,`Create`)})]})]}),j!==null&&(0,O.jsx)(`p`,{className:`text-[11px] leading-relaxed text-danger`,children:j}),q.length===0?(0,O.jsx)(`p`,{className:`${u} leading-relaxed`,children:n(`uiLibrary.sceneMonitor.empty`,`No scenes on this camera yet.`)}):(0,O.jsx)(`ul`,{className:`flex flex-col gap-2`,children:q.map(e=>{let t=b(e,n);return(0,O.jsxs)(`li`,{className:`flex flex-col gap-1.5 rounded-md border p-2 ${N===e.id?`border-primary/50`:`border-border`}`,children:[(0,O.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`button`,{type:`button`,className:`flex-1 text-left text-[12px] font-medium text-foreground`,onClick:()=>F(e.id),children:e.label}),(0,O.jsx)(`span`,{className:`rounded border px-1.5 py-0.5 text-[10px] font-medium ${Q[t.tone]}`,children:t.text})]}),(0,O.jsx)(`p`,{className:`${u} leading-relaxed`,children:y(e,J,n)}),(0,O.jsx)(`p`,{className:`${u} tabular-nums`,children:h(e,n)}),(0,O.jsx)(P,{monitor:e,disabled:k!==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,O.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[v.map(t=>{let i=t===`ir`;return(0,O.jsxs)(`button`,{type:`button`,className:Y,disabled:k!==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:x(t,n).toLowerCase()}),onClick:()=>void $(`cap-${e.id}-${t}`,async()=>r.sceneMonitor?.captureReference({monitorId:e.id,condition:t})),children:[(0,O.jsx)(w,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.capture`,`Capture {{variant}}`,{variant:x(t,n).toLowerCase()}),i&&(0,O.jsx)(`span`,{className:`ml-1 text-foreground-subtle/70`,children:n(`uiLibrary.sceneMonitor.optional`,`(optional)`)})]},t)}),(0,O.jsxs)(`button`,{type:`button`,className:X,disabled:k!==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,O.jsx)(l,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.reset`,`Reset`)]}),(0,O.jsx)(`button`,{type:`button`,className:Y,disabled:k!==null,onClick:()=>void $(`check-${e.id}`,async()=>r.sceneMonitor?.recheckNow({monitorId:e.id})),children:n(`uiLibrary.sceneMonitor.checkNow`,`Check now`)}),(0,O.jsxs)(`button`,{type:`button`,className:I===e.id?X:Y,"aria-pressed":I===e.id,"aria-label":n(`uiLibrary.sceneMonitor.settingsFor`,`Settings for {{label}}`,{label:e.label}),disabled:k!==null,onClick:()=>L(t=>t===e.id?null:e.id),children:[(0,O.jsx)(E,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.settings`,`Settings`)]}),(0,O.jsx)(`button`,{type:`button`,className:Z,disabled:k!==null,onClick:()=>void $(`del-${e.id}`,async()=>r.sceneMonitor?.deleteScene({monitorId:e.id})),children:(0,O.jsx)(C,{className:`h-3 w-3`})})]}),I===e.id&&(0,O.jsx)(K,{monitor:e,disabled:k!==null,onPatch:t=>void $(`tune-${e.id}`,async()=>r.sceneMonitor?.updateScene({monitorId:e.id,patch:t}))})]},e.id)})})]})}):null}export{$ as SceneMonitorEditor};
1
+ import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{S as a,T as o,_ as s,a as c,b as l,c as u,d,f,g as p,h as m,i as h,l as g,m as _,n as v,o as y,r as b,s as x,u as S,v as C,w}from"./index-BePyQ_Ek.js";import{MaskShapeCanvas as T}from"./MaskShapeCanvas-BTvl6Rin.js";var E=o(`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`}]]),D=n(i(),1),O=n(e(),1),k=_(`/addon/pipeline-analytics/event-media`);function A(e){return`${k}/m/${e}`}function j(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 M(e){return e.states.reduce((e,t)=>e+t.references.length,0)}var N=`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 P({monitor:e,disabled:n,onDelete:r}){let i=t(),a=j(e),o=M(e),s=m();return a.length===0?(0,O.jsx)(`p`,{className:`${u} 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,O.jsx)(`div`,{className:`flex flex-col gap-2`,children:a.map(e=>(0,O.jsxs)(`div`,{className:`flex flex-col gap-1`,children:[(0,O.jsxs)(`p`,{className:`text-[10px] font-semibold uppercase tracking-wide text-foreground-subtle`,children:[x(e.condition,i),` · `,e.tiles.length]}),(0,O.jsx)(`ul`,{className:`flex flex-wrap gap-1.5`,children:e.tiles.map(t=>{let a=`${t.stateId}:${String(t.index)}`,c=o===1;return(0,O.jsxs)(`li`,{className:`flex w-24 flex-col gap-1 rounded-md border border-border p-1`,children:[(0,O.jsx)(F,{reference:t.reference}),(0,O.jsx)(`span`,{className:`${u} tabular-nums`,children:I(t.reference.capturedAt)}),(0,O.jsxs)(`button`,{type:`button`,className:N,disabled:n,"aria-label":i(`uiLibrary.sceneReferenceGallery.deleteLabel`,`Delete the {{variant}} photograph from {{date}}`,{variant:x(e.condition,i).toLowerCase(),date:I(t.reference.capturedAt)}),onClick:()=>{(async()=>{c&&!await s({title:i(`uiLibrary.sceneReferenceGallery.deleteTitle`,`Delete the only photograph?`),message: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.`),confirmLabel:i(`uiLibrary.sceneReferenceGallery.delete`,`Delete`),variant:`danger`})||r({stateId:t.stateId,index:t.index})})()},children:[(0,O.jsx)(C,{className:`mr-1 inline h-3 w-3`}),i(`uiLibrary.sceneReferenceGallery.delete`,`Delete`)]})]},a)})})]},e.condition))})}function F({reference:e}){let n=t(),[r,i]=(0,D.useState)(!1),o=e.thumbnailMediaId;return o===void 0||r?(0,O.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,O.jsx)(a,{className:`h-4 w-4`}),(0,O.jsx)(`span`,{className:`text-[9px]`,children:n(`uiLibrary.sceneReferenceGallery.noPicture`,`no picture`)})]}):(0,O.jsx)(`img`,{src:A(o),alt:``,loading:`lazy`,className:`h-16 w-full rounded object-cover`,onError:()=>i(!0)})}function I(e){let t=new Date(e);return`${t.toLocaleDateString()} ${t.toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`})}`}var L=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground focus:border-primary focus:outline-none disabled:opacity-50`,R=`text-[10px] font-medium text-foreground-subtle`,z=`flex flex-col gap-2 rounded-md border border-border bg-surface/40 p-2`,B=`text-[10px] font-semibold uppercase tracking-wide text-foreground-subtle`,V=5,H=3600;function U(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 W(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 G(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 K({monitor:e,disabled:n,onPatch:i}){let a=t(),[o,s]=(0,D.useState)(()=>G(e)),[c,l]=(0,D.useState)(!1);(0,D.useEffect)(()=>{c||s(G(e))},[e,c]);let d=e=>{l(!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},m=e.confirm?.enabled===!0,h=e.check.mode===`similarity`?e.check.threshold:null;return(0,O.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,O.jsxs)(`div`,{className:z,children:[(0,O.jsx)(`p`,{className:B,children:a(`uiLibrary.sceneSettingsForm.whatItWatches`,`What it watches`)}),(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.name`,`Name`)}),(0,O.jsx)(`input`,{type:`text`,value:o.label,disabled:n,className:L,onFocus:()=>l(!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}):l(!1)}})]}),(0,O.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`input`,{type:`checkbox`,checked:e.enabled,disabled:n,onChange:e=>d({enabled:e.target.checked})}),(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.watching`,`Watching`)})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.whenToLook`,`When to look`)}),(0,O.jsxs)(p,{value:e.triggerMode,disabled:n,className:L,onChange:e=>{let t=e.target.value;(t===`periodic`||t===`on-motion`||t===`both`)&&d({triggerMode:t})},children:[(0,O.jsx)(`option`,{value:`both`,children:a(`uiLibrary.sceneSettingsForm.triggerBoth`,`On the clock and after motion settles`)}),(0,O.jsx)(`option`,{value:`periodic`,children:a(`uiLibrary.sceneSettingsForm.triggerPeriodic`,`On the clock only`)}),(0,O.jsx)(`option`,{value:`on-motion`,children:a(`uiLibrary.sceneSettingsForm.triggerOnMotion`,`After motion settles only`)})]})]}),(0,O.jsx)(`p`,{className:u,children:U(e.triggerMode,a)}),(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.checkEvery`,`Check every (seconds)`)}),(0,O.jsx)(`input`,{type:`number`,min:V,max:H,value:o.checkIntervalSec,disabled:n,className:L,onFocus:()=>l(!0),onChange:e=>s(t=>({...t,checkIntervalSec:Number(e.target.value)||0})),onBlur:()=>{let t=q(o.checkIntervalSec,V,H);t===e.checkIntervalSec?l(!1):d({checkIntervalSec:t})}})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`div`,{className:z,children:[(0,O.jsx)(`p`,{className:B,children:a(`uiLibrary.sceneSettingsForm.howSure`,`How sure it has to be`)}),e.check.mode===`similarity`&&(0,O.jsxs)(O.Fragment,{children:[(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:(0,O.jsx)(r,{uiKey:`uiLibrary.sceneSettingsForm.matchThreshold`,fallback:`Match threshold — <0>{{value}}</0>`,components:[(0,O.jsx)(`span`,{className:`tabular-nums`},`value`)],values:{value:o.threshold.toFixed(2)}})}),(0,O.jsx)(`input`,{type:`range`,min:.3,max:.99,step:.01,value:o.threshold,disabled:n,onFocus:()=>l(!0),onChange:e=>s(t=>({...t,threshold:Number(e.target.value)})),onMouseUp:()=>{o.threshold===h?l(!1):d({check:f({threshold:o.threshold})})},onBlur:()=>{o.threshold===h?l(!1):d({check:f({threshold:o.threshold})})}})]}),(0,O.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,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.agreeingChecks`,`Agreeing checks before it changes its mind`)}),(0,O.jsx)(`input`,{type:`number`,min:1,max:10,value:o.hysteresisCount,disabled:n,className:L,onFocus:()=>l(!0),onChange:e=>s(t=>({...t,hysteresisCount:Number(e.target.value)||1})),onBlur:()=>{let t=q(o.hysteresisCount,1,10);t===e.check.hysteresisCount?l(!1):d({check:f({hysteresisCount:t})})}})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.quietFor`,`Quiet for (seconds) before a check counts`)}),(0,O.jsx)(`input`,{type:`number`,min:0,max:3600,value:o.quietSeconds,disabled:n,className:L,onFocus:()=>l(!0),onChange:e=>s(t=>({...t,quietSeconds:Number(e.target.value)||0})),onBlur:()=>{let t=q(o.quietSeconds,0,3600);t===e.quietSeconds?l(!1):d({quietSeconds:t})}})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.minGap`,`Minimum gap between counted checks (seconds)`)}),(0,O.jsx)(`input`,{type:`number`,min:0,max:3600,value:o.minObservationSpacingSec,disabled:n,className:L,onFocus:()=>l(!0),onChange:e=>s(t=>({...t,minObservationSpacingSec:Number(e.target.value)||0})),onBlur:()=>{let t=q(o.minObservationSpacingSec,0,3600);t===e.minObservationSpacingSec?l(!1):d({minObservationSpacingSec:t})}})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`div`,{className:z,children:[(0,O.jsx)(`p`,{className:B,children:a(`uiLibrary.sceneSettingsForm.lighting`,`Lighting`)}),(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.noReference`,`When a light has no reference of its own`)}),(0,O.jsxs)(p,{value:e.onUncoveredCondition,disabled:n,className:L,onChange:e=>{let t=e.target.value;(t===`skip`||t===`judge-anyway`)&&d({onUncoveredCondition:t})},children:[(0,O.jsx)(`option`,{value:`skip`,children:a(`uiLibrary.sceneSettingsForm.uncoveredSkip`,`Skip the check (recommended)`)}),(0,O.jsx)(`option`,{value:`judge-anyway`,children:a(`uiLibrary.sceneSettingsForm.uncoveredJudge`,`Judge against the other light anyway`)})]})]}),(0,O.jsx)(`p`,{className:u,children:W(e.onUncoveredCondition,a)})]}),(0,O.jsxs)(`div`,{className:z,children:[(0,O.jsx)(`p`,{className:B,children:a(`uiLibrary.sceneSettingsForm.whatYouGetTold`,`What you get told`)}),(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.notificationsFollow`,`Notifications follow`)}),(0,O.jsxs)(p,{value:e.emit,disabled:n,className:L,onChange:e=>{let t=e.target.value;(t===`latched`||t===`live`)&&d({emit:t})},children:[(0,O.jsx)(`option`,{value:`latched`,children:a(`uiLibrary.sceneSettingsForm.emitLatched`,`The flag — told once, still visible at breakfast`)}),(0,O.jsx)(`option`,{value:`live`,children:a(`uiLibrary.sceneSettingsForm.emitLive`,`The live state — follows the region as it changes`)})]})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`input`,{type:`checkbox`,checked:e.autoRestore,disabled:n,onChange:e=>d({autoRestore:e.target.checked})}),(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.autoRestore`,`Clear the flag by itself when things look normal again`)})]}),(0,O.jsx)(`p`,{className:u,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,O.jsxs)(`div`,{className:z,children:[(0,O.jsx)(`p`,{className:B,children:a(`uiLibrary.sceneSettingsForm.aiDoubleCheck`,`AI double-check`)}),(0,O.jsxs)(`label`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`input`,{type:`checkbox`,checked:m,disabled:n||o.confirmPrompt.trim().length===0,onChange:e=>d(e.target.checked?{confirm:{...J(o.confirmPrompt.trim()),enabled:!0}}:{confirm:null})}),(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.askVision`,`Ask a vision model before changing the answer`)})]}),(0,O.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,O.jsx)(`span`,{className:R,children:a(`uiLibrary.sceneSettingsForm.question`,`The question to ask`)}),(0,O.jsx)(`input`,{type:`text`,value:o.confirmPrompt,placeholder:a(`uiLibrary.sceneSettingsForm.questionPlaceholder`,`e.g. Is the wheelie bin still there?`),disabled:n,className:L,onFocus:()=>l(!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:{...J(t),enabled:m}}):l(!1)}})]}),(0,O.jsx)(`p`,{className:u,children:(0,O.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,O.jsx)(`strong`,{},`only`)]})})]})]})}function q(e,t,n){return Math.min(n,Math.max(t,e))}function J(e){return{enabled:!1,prompt:e,timeoutMs:8e3,maxImagePx:448,onTimeout:`hold`}}var 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`,X=`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`,Z=`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`,Q={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`},ee=.8,te=3;function $({deviceId:e}){let n=t(),r=d(s().trpcClient,e),i=f(r?.state.sceneMonitor),a=S(e,{width:640}),[o,p]=(0,D.useState)(null),[m,_]=(0,D.useState)(!1),[k,A]=(0,D.useState)(null),[j,M]=(0,D.useState)(null),[N,F]=(0,D.useState)(null),[I,L]=(0,D.useState)(null),[R,z]=(0,D.useState)(!1),[B,V]=(0,D.useState)(null),[H,U]=(0,D.useState)(``),W=(0,D.useRef)(!0);(0,D.useEffect)(()=>(W.current=!0,()=>{W.current=!1}),[]);let G=(0,D.useCallback)(async()=>{if(r)try{let e=await r.sceneMonitor?.listScenes({});if(!W.current)return;e&&p(e)}catch(e){if(!W.current)return;g(e)?_(!0):M(e instanceof Error?e.message:String(e))}},[r]);(0,D.useEffect)(()=>{p(null),_(!1),M(null),G()},[G]);let q=i?.monitors??o?.monitors??[],J=Date.now(),$=(0,D.useCallback)(async(e,t)=>{A(e),M(null);try{await t(),await G()}catch(e){W.current&&M(e instanceof Error?e.message:String(e))}finally{W.current&&A(null)}},[G]),ne=(0,D.useMemo)(()=>{let e=q.map(e=>({id:e.id,shape:e.roi,label:e.label,enabled:e.enabled}));return B===null?e:[...e,{id:`__draft__`,shape:B,label:n(`uiLibrary.sceneMonitor.newSceneLabel`,`New scene`)}]},[q,B,n]),re=(0,D.useCallback)((e,t)=>{if(t.kind===`rect`){if(e===`__draft__`){V(t);return}$(`roi-${String(e)}`,async()=>r?.sceneMonitor?.updateScene({monitorId:String(e),patch:{roi:t}}))}},[r,$]),ie=(0,D.useCallback)(e=>{e.kind===`rect`&&V(e)},[]),ae=(0,D.useCallback)(async()=>{B===null||H.trim().length===0||(await $(`create`,async()=>{await r?.sceneMonitor?.createScene({label:H.trim(),roi:B,check:{mode:`similarity`,threshold:ee,hysteresisCount:te}})}),V(null),U(``),z(!1))},[r,B,H,$]);return r?(0,O.jsx)(`div`,{className:`flex flex-col gap-3`,children:m?(0,O.jsx)(`p`,{className:`${u} leading-relaxed`,children:n(`uiLibrary.sceneMonitor.unsupported`,`Scene monitoring is not available for this camera.`)}):(0,O.jsxs)(O.Fragment,{children:[(0,O.jsx)(`p`,{className:`${u} 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,O.jsx)(`div`,{className:`relative overflow-hidden rounded-md border border-border`,children:(0,O.jsx)(T,{items:ne,supportedShapes:[`rect`],selectedId:N??(B===null?null:`__draft__`),onSelect:e=>F(e===null?null:String(e)),onShapeChange:re,onDrawComplete:ie,drawingKind:R&&B===null?`rect`:null,backdrop:a.src===null?null:(0,O.jsx)(`img`,{src:a.src,alt:``,className:`h-full w-full object-contain`,draggable:!1})})}),(0,O.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,O.jsx)(`button`,{type:`button`,className:R?X:Y,"aria-pressed":R,onClick:()=>{z(e=>!e),V(null)},disabled:k!==null,children:R?n(`uiLibrary.sceneMonitor.cancel`,`Cancel`):n(`uiLibrary.sceneMonitor.newScene`,`+ New scene`)}),(0,O.jsx)(`button`,{type:`button`,className:Y,onClick:a.refresh,disabled:k!==null,children:n(`uiLibrary.sceneMonitor.refresh`,`Refresh frame`)})]}),R&&(0,O.jsxs)(`div`,{className:`flex flex-col gap-2 rounded-md border border-border bg-surface p-2`,children:[(0,O.jsx)(`p`,{className:`text-[10px] text-foreground-subtle`,children:B===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.`)}),B!==null&&c(B)&&(0,O.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,O.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`input`,{type:`text`,value:H,onChange:e=>U(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,O.jsx)(`button`,{type:`button`,className:X,onClick:()=>void ae(),disabled:B===null||H.trim().length===0||k!==null,children:n(`uiLibrary.sceneMonitor.create`,`Create`)})]})]}),j!==null&&(0,O.jsx)(`p`,{className:`text-[11px] leading-relaxed text-danger`,children:j}),q.length===0?(0,O.jsx)(`p`,{className:`${u} leading-relaxed`,children:n(`uiLibrary.sceneMonitor.empty`,`No scenes on this camera yet.`)}):(0,O.jsx)(`ul`,{className:`flex flex-col gap-2`,children:q.map(e=>{let t=b(e,n);return(0,O.jsxs)(`li`,{className:`flex flex-col gap-1.5 rounded-md border p-2 ${N===e.id?`border-primary/50`:`border-border`}`,children:[(0,O.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`button`,{type:`button`,className:`flex-1 text-left text-[12px] font-medium text-foreground`,onClick:()=>F(e.id),children:e.label}),(0,O.jsx)(`span`,{className:`rounded border px-1.5 py-0.5 text-[10px] font-medium ${Q[t.tone]}`,children:t.text})]}),(0,O.jsx)(`p`,{className:`${u} leading-relaxed`,children:y(e,J,n)}),(0,O.jsx)(`p`,{className:`${u} tabular-nums`,children:h(e,n)}),(0,O.jsx)(P,{monitor:e,disabled:k!==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,O.jsxs)(`div`,{className:`flex flex-wrap items-center gap-1.5`,children:[v.map(t=>{let i=t===`ir`;return(0,O.jsxs)(`button`,{type:`button`,className:Y,disabled:k!==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:x(t,n).toLowerCase()}),onClick:()=>void $(`cap-${e.id}-${t}`,async()=>r.sceneMonitor?.captureReference({monitorId:e.id,condition:t})),children:[(0,O.jsx)(w,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.capture`,`Capture {{variant}}`,{variant:x(t,n).toLowerCase()}),i&&(0,O.jsx)(`span`,{className:`ml-1 text-foreground-subtle/70`,children:n(`uiLibrary.sceneMonitor.optional`,`(optional)`)})]},t)}),(0,O.jsxs)(`button`,{type:`button`,className:X,disabled:k!==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,O.jsx)(l,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.reset`,`Reset`)]}),(0,O.jsx)(`button`,{type:`button`,className:Y,disabled:k!==null,onClick:()=>void $(`check-${e.id}`,async()=>r.sceneMonitor?.recheckNow({monitorId:e.id})),children:n(`uiLibrary.sceneMonitor.checkNow`,`Check now`)}),(0,O.jsxs)(`button`,{type:`button`,className:I===e.id?X:Y,"aria-pressed":I===e.id,"aria-label":n(`uiLibrary.sceneMonitor.settingsFor`,`Settings for {{label}}`,{label:e.label}),disabled:k!==null,onClick:()=>L(t=>t===e.id?null:e.id),children:[(0,O.jsx)(E,{className:`mr-1 inline h-3 w-3`}),n(`uiLibrary.sceneMonitor.settings`,`Settings`)]}),(0,O.jsx)(`button`,{type:`button`,className:Z,disabled:k!==null,onClick:()=>void $(`del-${e.id}`,async()=>r.sceneMonitor?.deleteScene({monitorId:e.id})),children:(0,O.jsx)(C,{className:`h-3 w-3`})})]}),I===e.id&&(0,O.jsx)(K,{monitor:e,disabled:k!==null,onPatch:t=>void $(`tune-${e.id}`,async()=>r.sceneMonitor?.updateScene({monitorId:e.id,patch:t}))})]},e.id)})})]})}):null}export{$ as SceneMonitorEditor};