@camstack/addon-pipeline 1.2.58 → 1.2.59

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.
@@ -21718,21 +21718,32 @@ function clampPlaybackRate(rate) {
21718
21718
  if (!Number.isFinite(rate) || rate <= 0) return 0;
21719
21719
  return Math.min(4, Math.max(.25, rate));
21720
21720
  }
21721
- var ServerMessageSchema = require_dist.discriminatedUnion("t", [require_dist.object({
21722
- t: require_dist.literal("state"),
21723
- state: require_dist._enum([
21724
- "live",
21725
- "loading",
21726
- "playing",
21727
- "paused",
21728
- "gap",
21729
- "ended",
21730
- "error"
21731
- ])
21732
- }), require_dist.object({
21733
- t: require_dist.literal("position"),
21734
- epochMs: require_dist.number()
21735
- })]);
21721
+ var ServerMessageSchema = require_dist.discriminatedUnion("t", [
21722
+ require_dist.object({
21723
+ t: require_dist.literal("state"),
21724
+ state: require_dist._enum([
21725
+ "live",
21726
+ "loading",
21727
+ "playing",
21728
+ "paused",
21729
+ "gap",
21730
+ "ended",
21731
+ "error"
21732
+ ])
21733
+ }),
21734
+ require_dist.object({
21735
+ t: require_dist.literal("position"),
21736
+ epochMs: require_dist.number()
21737
+ }),
21738
+ require_dist.object({
21739
+ t: require_dist.literal("liveProfile"),
21740
+ profile: require_dist._enum([
21741
+ "high",
21742
+ "mid",
21743
+ "low"
21744
+ ])
21745
+ })
21746
+ ]);
21736
21747
  function parseControlMessage(raw) {
21737
21748
  try {
21738
21749
  const parsed = ControlMessageSchema.safeParse(JSON.parse(raw));
@@ -22071,7 +22082,7 @@ var SEEK_DISCONTINUITY_MS = 250;
22071
22082
  * sustainable there — cross-fragment moves remain paced by the demux + the
22072
22083
  * one-in-flight guard, so this cannot spawn parallel demuxes.
22073
22084
  */
22074
- var SCRUB_COALESCE_MS = 60;
22085
+ var SCRUB_COALESCE_MS = 33;
22075
22086
  /**
22076
22087
  * How much forward slack two fragments may have and still count as ADJACENT
22077
22088
  * for sweep continuity. Two real cases live inside it: the ±1 ms rounding of a
@@ -23608,6 +23619,12 @@ var TimelineSession = class {
23608
23619
  mode = "live";
23609
23620
  feeder = null;
23610
23621
  profile = "mid";
23622
+ /** Transport rate belongs to the session, not to one profile-specific feeder.
23623
+ * Scrub pins `low`; release restores the selected playback profile and may
23624
+ * therefore rebuild the feeder. Carrying the rate keeps a paused scrub
23625
+ * paused across that required quality transition. */
23626
+ rate = 1;
23627
+ liveProfileReported = false;
23611
23628
  /**
23612
23629
  * The last position the viewer was SHOWN (every `_emitPosition`). Handed to
23613
23630
  * `seek` as `travelFromMs` so the seek travel animation works across feeder
@@ -23631,6 +23648,7 @@ var TimelineSession = class {
23631
23648
  return this.mode;
23632
23649
  }
23633
23650
  async handleControl(msg) {
23651
+ this.emitLiveProfile();
23634
23652
  switch (msg.t) {
23635
23653
  case "playRecorded": {
23636
23654
  const profileChanged = msg.profile !== this.profile;
@@ -23659,6 +23677,7 @@ var TimelineSession = class {
23659
23677
  if (this.mode === "recorded") this.feeder?.scrubAck();
23660
23678
  break;
23661
23679
  case "setRate":
23680
+ this.rate = msg.rate;
23662
23681
  this.feeder?.setRate(msg.rate);
23663
23682
  break;
23664
23683
  case "stepFrame":
@@ -23683,6 +23702,7 @@ var TimelineSession = class {
23683
23702
  this.deps.session.setLiveFeedGate(true);
23684
23703
  this.mode = "recorded";
23685
23704
  const feeder = this.deps.makeFeeder(this.profile);
23705
+ if (this.rate !== 1) feeder.setRate(this.rate);
23686
23706
  this.feeder = feeder;
23687
23707
  return feeder;
23688
23708
  }
@@ -23710,6 +23730,14 @@ var TimelineSession = class {
23710
23730
  epochMs
23711
23731
  }));
23712
23732
  }
23733
+ emitLiveProfile() {
23734
+ if (this.liveProfileReported || this.deps.initialLiveProfile === void 0) return;
23735
+ this.liveProfileReported = true;
23736
+ this.deps.session.sendControl(serializeServerMessage({
23737
+ t: "liveProfile",
23738
+ profile: this.deps.initialLiveProfile
23739
+ }));
23740
+ }
23713
23741
  dispose() {
23714
23742
  this.feeder?.dispose();
23715
23743
  this.feeder = null;
@@ -31369,8 +31397,8 @@ var ADAPTIVE_CONFIG = {
31369
31397
  analyzeWindowMs: 15e3,
31370
31398
  minFailPercent: .05,
31371
31399
  waitAfterResetMs: 8e3,
31372
- upgradeHealthyMs: 6e4,
31373
- minTimeBetweenChangesMs: 6e4,
31400
+ upgradeHealthyMs: 2e4,
31401
+ minTimeBetweenChangesMs: 2e4,
31374
31402
  maxUpgradeBackoffMs: 9e5
31375
31403
  };
31376
31404
  /**
@@ -31538,8 +31566,7 @@ function scoreBroker(tier, broker, hints) {
31538
31566
  const fallback = tier ? LABEL_DEFAULTS[tier] : void 0;
31539
31567
  const bitrateKbps = stats.bitrateKbps > 0 ? stats.bitrateKbps : fallback?.bitrateKbps ?? 2e3;
31540
31568
  const srcPixels = fallback?.pixels ?? 1920 * 1080;
31541
- const dpr = hints.devicePixelRatio ?? 1;
31542
- const targetPixels = (hints.viewportWidth ?? 1920) * dpr * ((hints.viewportHeight ?? 1080) * dpr);
31569
+ const targetPixels = (hints.viewportWidth ?? 1920) * (hints.viewportHeight ?? 1080);
31543
31570
  const pixelDistance = Math.abs(srcPixels - targetPixels) / 1e6;
31544
31571
  let bandwidthPenalty = 0;
31545
31572
  if (hints.downlinkMbps && hints.downlinkMbps > 0 && bitrateKbps > 0) {
@@ -32284,6 +32311,7 @@ var BrokerWebrtcServer = class {
32284
32311
  }
32285
32312
  });
32286
32313
  const scrubCache = new ScrubFragmentCache();
32314
+ const initialLiveProfile = this.profileTierResolver?.(brokerId) ?? null;
32287
32315
  const readGopAccessor = this.readRecordedGopBytes;
32288
32316
  const readGop = readGopAccessor ? async (args) => {
32289
32317
  const res = await readGopAccessor({
@@ -32303,6 +32331,7 @@ var BrokerWebrtcServer = class {
32303
32331
  timeline = new TimelineSession({
32304
32332
  session,
32305
32333
  deviceId,
32334
+ ...initialLiveProfile !== null ? { initialLiveProfile } : {},
32306
32335
  warm: readGop ? (profile, epochMs) => {
32307
32336
  warmFragment({
32308
32337
  deviceId,
@@ -32531,9 +32560,9 @@ var BrokerWebrtcServer = class {
32531
32560
  * server.
32532
32561
  *
32533
32562
  * The client-loss % is turned into a monotonic {@link LossSample} LOCALLY:
32534
- * a per-controller counter `clientLossN` advances each tick that carries a
32535
- * positive loss %, so consecutive samples have a clean delta whose ratio
32536
- * equals `pct/100` exactly what the controller's `LossTracker` expects.
32563
+ * per-controller cumulative sequence/loss counters advance for every
32564
+ * reported interval (including 0% loss), so a perfectly healthy LAN is real
32565
+ * evidence rather than silence, exactly like RTCP's cumulative fields.
32537
32566
  * Both paths feed the same controller; the tracker naturally honours
32538
32567
  * whichever produces the higher loss ratio across its window. The async
32539
32568
  * poll is guarded against overlapping ticks via `tickInFlight`.
@@ -32553,7 +32582,8 @@ var BrokerWebrtcServer = class {
32553
32582
  });
32554
32583
  entry.session.onReceiverReport((s) => controller.onLoss(s, Date.now()));
32555
32584
  entry.adaptiveController = controller;
32556
- let clientLossN = 0;
32585
+ let clientHighestSequence = 0;
32586
+ let clientPacketsLost = 0;
32557
32587
  let tickInFlight = false;
32558
32588
  const CLIENT_LOSS_SPAN = 1e3;
32559
32589
  entry.adaptiveTimer = setInterval(() => {
@@ -32567,11 +32597,12 @@ var BrokerWebrtcServer = class {
32567
32597
  }
32568
32598
  const pct = this.clientLossPct ? await this.clientLossPct(deviceId) : null;
32569
32599
  const now = Date.now();
32570
- if (pct !== null && pct !== void 0 && pct > 0) {
32571
- clientLossN += 1;
32600
+ if (pct !== null && pct !== void 0) {
32601
+ clientHighestSequence += CLIENT_LOSS_SPAN;
32602
+ clientPacketsLost += Math.round(Math.max(0, Math.min(100, pct)) / 100 * CLIENT_LOSS_SPAN);
32572
32603
  controller.onLoss({
32573
- packetsLost: Math.round(pct / 100 * CLIENT_LOSS_SPAN * clientLossN),
32574
- highestSequence: CLIENT_LOSS_SPAN * clientLossN
32604
+ packetsLost: clientPacketsLost,
32605
+ highestSequence: clientHighestSequence
32575
32606
  }, now);
32576
32607
  }
32577
32608
  controller.tick(now);
@@ -21712,21 +21712,32 @@ function clampPlaybackRate(rate) {
21712
21712
  if (!Number.isFinite(rate) || rate <= 0) return 0;
21713
21713
  return Math.min(4, Math.max(.25, rate));
21714
21714
  }
21715
- var ServerMessageSchema = discriminatedUnion("t", [object({
21716
- t: literal("state"),
21717
- state: _enum([
21718
- "live",
21719
- "loading",
21720
- "playing",
21721
- "paused",
21722
- "gap",
21723
- "ended",
21724
- "error"
21725
- ])
21726
- }), object({
21727
- t: literal("position"),
21728
- epochMs: number()
21729
- })]);
21715
+ var ServerMessageSchema = discriminatedUnion("t", [
21716
+ object({
21717
+ t: literal("state"),
21718
+ state: _enum([
21719
+ "live",
21720
+ "loading",
21721
+ "playing",
21722
+ "paused",
21723
+ "gap",
21724
+ "ended",
21725
+ "error"
21726
+ ])
21727
+ }),
21728
+ object({
21729
+ t: literal("position"),
21730
+ epochMs: number()
21731
+ }),
21732
+ object({
21733
+ t: literal("liveProfile"),
21734
+ profile: _enum([
21735
+ "high",
21736
+ "mid",
21737
+ "low"
21738
+ ])
21739
+ })
21740
+ ]);
21730
21741
  function parseControlMessage(raw) {
21731
21742
  try {
21732
21743
  const parsed = ControlMessageSchema.safeParse(JSON.parse(raw));
@@ -22065,7 +22076,7 @@ var SEEK_DISCONTINUITY_MS = 250;
22065
22076
  * sustainable there — cross-fragment moves remain paced by the demux + the
22066
22077
  * one-in-flight guard, so this cannot spawn parallel demuxes.
22067
22078
  */
22068
- var SCRUB_COALESCE_MS = 60;
22079
+ var SCRUB_COALESCE_MS = 33;
22069
22080
  /**
22070
22081
  * How much forward slack two fragments may have and still count as ADJACENT
22071
22082
  * for sweep continuity. Two real cases live inside it: the ±1 ms rounding of a
@@ -23602,6 +23613,12 @@ var TimelineSession = class {
23602
23613
  mode = "live";
23603
23614
  feeder = null;
23604
23615
  profile = "mid";
23616
+ /** Transport rate belongs to the session, not to one profile-specific feeder.
23617
+ * Scrub pins `low`; release restores the selected playback profile and may
23618
+ * therefore rebuild the feeder. Carrying the rate keeps a paused scrub
23619
+ * paused across that required quality transition. */
23620
+ rate = 1;
23621
+ liveProfileReported = false;
23605
23622
  /**
23606
23623
  * The last position the viewer was SHOWN (every `_emitPosition`). Handed to
23607
23624
  * `seek` as `travelFromMs` so the seek travel animation works across feeder
@@ -23625,6 +23642,7 @@ var TimelineSession = class {
23625
23642
  return this.mode;
23626
23643
  }
23627
23644
  async handleControl(msg) {
23645
+ this.emitLiveProfile();
23628
23646
  switch (msg.t) {
23629
23647
  case "playRecorded": {
23630
23648
  const profileChanged = msg.profile !== this.profile;
@@ -23653,6 +23671,7 @@ var TimelineSession = class {
23653
23671
  if (this.mode === "recorded") this.feeder?.scrubAck();
23654
23672
  break;
23655
23673
  case "setRate":
23674
+ this.rate = msg.rate;
23656
23675
  this.feeder?.setRate(msg.rate);
23657
23676
  break;
23658
23677
  case "stepFrame":
@@ -23677,6 +23696,7 @@ var TimelineSession = class {
23677
23696
  this.deps.session.setLiveFeedGate(true);
23678
23697
  this.mode = "recorded";
23679
23698
  const feeder = this.deps.makeFeeder(this.profile);
23699
+ if (this.rate !== 1) feeder.setRate(this.rate);
23680
23700
  this.feeder = feeder;
23681
23701
  return feeder;
23682
23702
  }
@@ -23704,6 +23724,14 @@ var TimelineSession = class {
23704
23724
  epochMs
23705
23725
  }));
23706
23726
  }
23727
+ emitLiveProfile() {
23728
+ if (this.liveProfileReported || this.deps.initialLiveProfile === void 0) return;
23729
+ this.liveProfileReported = true;
23730
+ this.deps.session.sendControl(serializeServerMessage({
23731
+ t: "liveProfile",
23732
+ profile: this.deps.initialLiveProfile
23733
+ }));
23734
+ }
23707
23735
  dispose() {
23708
23736
  this.feeder?.dispose();
23709
23737
  this.feeder = null;
@@ -31360,8 +31388,8 @@ var ADAPTIVE_CONFIG = {
31360
31388
  analyzeWindowMs: 15e3,
31361
31389
  minFailPercent: .05,
31362
31390
  waitAfterResetMs: 8e3,
31363
- upgradeHealthyMs: 6e4,
31364
- minTimeBetweenChangesMs: 6e4,
31391
+ upgradeHealthyMs: 2e4,
31392
+ minTimeBetweenChangesMs: 2e4,
31365
31393
  maxUpgradeBackoffMs: 9e5
31366
31394
  };
31367
31395
  /**
@@ -31529,8 +31557,7 @@ function scoreBroker(tier, broker, hints) {
31529
31557
  const fallback = tier ? LABEL_DEFAULTS[tier] : void 0;
31530
31558
  const bitrateKbps = stats.bitrateKbps > 0 ? stats.bitrateKbps : fallback?.bitrateKbps ?? 2e3;
31531
31559
  const srcPixels = fallback?.pixels ?? 1920 * 1080;
31532
- const dpr = hints.devicePixelRatio ?? 1;
31533
- const targetPixels = (hints.viewportWidth ?? 1920) * dpr * ((hints.viewportHeight ?? 1080) * dpr);
31560
+ const targetPixels = (hints.viewportWidth ?? 1920) * (hints.viewportHeight ?? 1080);
31534
31561
  const pixelDistance = Math.abs(srcPixels - targetPixels) / 1e6;
31535
31562
  let bandwidthPenalty = 0;
31536
31563
  if (hints.downlinkMbps && hints.downlinkMbps > 0 && bitrateKbps > 0) {
@@ -32275,6 +32302,7 @@ var BrokerWebrtcServer = class {
32275
32302
  }
32276
32303
  });
32277
32304
  const scrubCache = new ScrubFragmentCache();
32305
+ const initialLiveProfile = this.profileTierResolver?.(brokerId) ?? null;
32278
32306
  const readGopAccessor = this.readRecordedGopBytes;
32279
32307
  const readGop = readGopAccessor ? async (args) => {
32280
32308
  const res = await readGopAccessor({
@@ -32294,6 +32322,7 @@ var BrokerWebrtcServer = class {
32294
32322
  timeline = new TimelineSession({
32295
32323
  session,
32296
32324
  deviceId,
32325
+ ...initialLiveProfile !== null ? { initialLiveProfile } : {},
32297
32326
  warm: readGop ? (profile, epochMs) => {
32298
32327
  warmFragment({
32299
32328
  deviceId,
@@ -32522,9 +32551,9 @@ var BrokerWebrtcServer = class {
32522
32551
  * server.
32523
32552
  *
32524
32553
  * The client-loss % is turned into a monotonic {@link LossSample} LOCALLY:
32525
- * a per-controller counter `clientLossN` advances each tick that carries a
32526
- * positive loss %, so consecutive samples have a clean delta whose ratio
32527
- * equals `pct/100` exactly what the controller's `LossTracker` expects.
32554
+ * per-controller cumulative sequence/loss counters advance for every
32555
+ * reported interval (including 0% loss), so a perfectly healthy LAN is real
32556
+ * evidence rather than silence, exactly like RTCP's cumulative fields.
32528
32557
  * Both paths feed the same controller; the tracker naturally honours
32529
32558
  * whichever produces the higher loss ratio across its window. The async
32530
32559
  * poll is guarded against overlapping ticks via `tickInFlight`.
@@ -32544,7 +32573,8 @@ var BrokerWebrtcServer = class {
32544
32573
  });
32545
32574
  entry.session.onReceiverReport((s) => controller.onLoss(s, Date.now()));
32546
32575
  entry.adaptiveController = controller;
32547
- let clientLossN = 0;
32576
+ let clientHighestSequence = 0;
32577
+ let clientPacketsLost = 0;
32548
32578
  let tickInFlight = false;
32549
32579
  const CLIENT_LOSS_SPAN = 1e3;
32550
32580
  entry.adaptiveTimer = setInterval(() => {
@@ -32558,11 +32588,12 @@ var BrokerWebrtcServer = class {
32558
32588
  }
32559
32589
  const pct = this.clientLossPct ? await this.clientLossPct(deviceId) : null;
32560
32590
  const now = Date.now();
32561
- if (pct !== null && pct !== void 0 && pct > 0) {
32562
- clientLossN += 1;
32591
+ if (pct !== null && pct !== void 0) {
32592
+ clientHighestSequence += CLIENT_LOSS_SPAN;
32593
+ clientPacketsLost += Math.round(Math.max(0, Math.min(100, pct)) / 100 * CLIENT_LOSS_SPAN);
32563
32594
  controller.onLoss({
32564
- packetsLost: Math.round(pct / 100 * CLIENT_LOSS_SPAN * clientLossN),
32565
- highestSequence: CLIENT_LOSS_SPAN * clientLossN
32595
+ packetsLost: clientPacketsLost,
32596
+ highestSequence: clientHighestSequence
32566
32597
  }, now);
32567
32598
  }
32568
32599
  controller.tick(now);
@@ -1,4 +1,4 @@
1
- import{d as e,f as t,g as n,h as r,m as i,p as a}from"./index-hICPL_hd.js";var o=n(t()),s=n(e(),1),c=Math.PI/180;function l(){return typeof window<`u`&&({}.toString.call(window)===`[object Window]`||{}.toString.call(window)===`[object global]`)}var u=typeof global<`u`?global:typeof window<`u`?window:typeof WorkerGlobalScope<`u`?self:{},d={_global:u,version:`10.3.0`,isBrowser:l(),isUnminified:/param/.test(function(e){}.toString()),dblClickWindow:400,getAngle(e){return d.angleDeg?e*c:e},enableTrace:!1,pointerEventsEnabled:!0,autoDrawEnabled:!0,hitOnDragEnabled:!1,capturePointerEventsEnabled:!1,_mouseListenClick:!1,_touchListenClick:!1,_pointerListenClick:!1,_mouseInDblClickWindow:!1,_touchInDblClickWindow:!1,_pointerInDblClickWindow:!1,_mouseDblClickPointerId:null,_touchDblClickPointerId:null,_pointerDblClickPointerId:null,_renderBackend:`web`,legacyTextRendering:!1,pixelRatio:typeof window<`u`&&window.devicePixelRatio||1,dragDistance:3,angleDeg:!0,showWarnings:!0,dragButtons:[0,1],isDragging(){return d.DD.isDragging},isTransforming(){return d.Transformer?.isTransforming()??!1},isDragReady(){return!!d.DD.node},releaseCanvasOnDestroy:!0,document:u.document,_injectGlobal(e){u.Konva!==void 0&&console.error(`Several Konva instances detected. It is not recommended to use multiple Konva instances in the same environment.`),u.Konva=e}},f=e=>{d[e.prototype.getClassName()]=e};d._injectGlobal(d);var p=`Konva.js unsupported environment.
1
+ import{d as e,f as t,g as n,h as r,m as i,p as a}from"./index-Dcfe-5Ig.js";var o=n(t()),s=n(e(),1),c=Math.PI/180;function l(){return typeof window<`u`&&({}.toString.call(window)===`[object Window]`||{}.toString.call(window)===`[object global]`)}var u=typeof global<`u`?global:typeof window<`u`?window:typeof WorkerGlobalScope<`u`?self:{},d={_global:u,version:`10.3.0`,isBrowser:l(),isUnminified:/param/.test(function(e){}.toString()),dblClickWindow:400,getAngle(e){return d.angleDeg?e*c:e},enableTrace:!1,pointerEventsEnabled:!0,autoDrawEnabled:!0,hitOnDragEnabled:!1,capturePointerEventsEnabled:!1,_mouseListenClick:!1,_touchListenClick:!1,_pointerListenClick:!1,_mouseInDblClickWindow:!1,_touchInDblClickWindow:!1,_pointerInDblClickWindow:!1,_mouseDblClickPointerId:null,_touchDblClickPointerId:null,_pointerDblClickPointerId:null,_renderBackend:`web`,legacyTextRendering:!1,pixelRatio:typeof window<`u`&&window.devicePixelRatio||1,dragDistance:3,angleDeg:!0,showWarnings:!0,dragButtons:[0,1],isDragging(){return d.DD.isDragging},isTransforming(){return d.Transformer?.isTransforming()??!1},isDragReady(){return!!d.DD.node},releaseCanvasOnDestroy:!0,document:u.document,_injectGlobal(e){u.Konva!==void 0&&console.error(`Several Konva instances detected. It is not recommended to use multiple Konva instances in the same environment.`),u.Konva=e}},f=e=>{d[e.prototype.getClassName()]=e};d._injectGlobal(d);var p=`Konva.js unsupported environment.
2
2
 
3
3
  Looks like you are trying to use Konva.js in Node.js environment. because "document" object is undefined.
4
4
 
@@ -1 +1 @@
1
- import{a as e,c as t,d as n,f as r,g as i,l as a,o,r as s,s as c,u as l}from"./index-hICPL_hd.js";import{MaskShapeCanvas as u}from"./MaskShapeCanvas-DI4BY7W2-Bxm34WFr.js";var d=i(r(),1),f=i(n(),1),p=o(`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=110,h=`motion-zones`,g=0,_=[1,2,3],v=1,y=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,b=`rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors`;function x(e,t,n){let r=t*n,i=Array.from({length:r});for(let t=0;t<r;t+=1)i[t]=e[t]===!0;return i}function S(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}function C(e,t){return Math.ceil(e/t)}function w(e,t,n){return Math.min(n-1,Math.floor((e+.5)/t*n))}function T(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:i*a},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)e[r*t+n]===!0&&(o[s*i+w(n,t,i)]=!0)}return o}function E(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:t*n},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)o[r*t+n]=e[s*i+w(n,t,i)]===!0}return o}function D({deviceId:n}){let r=t(l().trpcClient,n),[i,o]=(0,d.useState)(null),[w,D]=(0,d.useState)(!1),[O,k]=(0,d.useState)(null),[A,j]=(0,d.useState)(null),[M,N]=(0,d.useState)(v),[P,F]=(0,d.useState)(!1),[I,L]=(0,d.useState)(!1),R=(0,d.useRef)(!1);(0,d.useEffect)(()=>{if(!r)return;let e=!1;return R.current=!1,o(null),D(!1),k(null),j(null),N(v),L(!1),(async()=>{try{let t=await r.motionZones?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(o(t),R.current)return;let n=await r.motionZones?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);R.current=!0;let i=n.regions.find(e=>e.shape.kind===`grid`),a=x(i?i.shape.cells:[],t.grid.width,t.grid.height);j(a),k(T(a,t.grid.width,t.grid.height,v))}catch(t){if(e)return;c(t)?D(!0):console.error(`Motion Zones load failed`,t)}})(),()=>{e=!0}},[r]);let z=i?i.grid.width:0,B=i?i.grid.height:0,V=C(z,M),H=C(B,M),U=(0,d.useMemo)(()=>O&&i?E(O,z,B,M):null,[O,i,z,B,M]),W=(0,d.useMemo)(()=>U!==null&&A!==null&&!S(U,A),[U,A]),G=(0,d.useMemo)(()=>U?U.reduce((e,t)=>t?e+1:e,0):0,[U]),K=z*B,q=V*H,J=(0,d.useCallback)(e=>{q>0&&k(Array.from({length:q},()=>e))},[q]),Y=(0,d.useCallback)(()=>{k(e=>e&&e.map(e=>!e))},[]),X=(0,d.useCallback)(()=>{A&&i&&k(T(A,z,B,M))},[A,i,z,B,M]),Z=(0,d.useCallback)(e=>{e===M||!i||k(t=>{if(!t)return N(e),t;let n=T(E(t,z,B,M),z,B,e);return N(e),n})},[M,i,z,B]),Q=(0,d.useMemo)(()=>i&&O?[{id:g,shape:{kind:`grid`,gridWidth:V,gridHeight:H,cells:[...O]}}]:[],[i,O,V,H]),$=(0,d.useCallback)((e,t)=>{t.kind===`grid`&&k(t.cells)},[]),ee=(0,d.useCallback)(async()=>{if(!(!r||!O||!i)){F(!0);try{let e=E(O,i.grid.width,i.grid.height,M),t={kind:`grid`,gridWidth:i.grid.width,gridHeight:i.grid.height,cells:e};await r.motionZones?.setZone({patch:{regions:[{id:g,enabled:!0,shape:t}]}});let n=await r.motionZones?.getStatus({});if(n){let e=n.regions.find(e=>e.shape.kind===`grid`),t=x(e?e.shape.cells:[],i.grid.width,i.grid.height);j(t),k(T(t,i.grid.width,i.grid.height,M))}}catch(e){console.error(`Motion Zones save failed`,e)}finally{F(!1)}}},[r,O,i,M]);a((0,d.useMemo)(()=>I&&!w&&i&&O?{id:h,order:m,node:(0,f.jsx)(u,{transparent:!0,items:Q,supportedShapes:[`grid`],grid:{width:V,height:H},selectedId:g,onSelect:()=>{},onShapeChange:$,onDrawComplete:()=>{},drawingKind:null})}:null,[I,w,i,O,Q,$,V,H]));let te=!w&&i!==null&&O!==null;return r?(0,f.jsx)(e,{title:`Motion Zones`,icon:(0,f.jsx)(p,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,f.jsx)(`div`,{className:`flex flex-col gap-3`,children:w?(0,f.jsx)(`p`,{className:`${s} leading-relaxed`,children:`This camera doesn't expose an on-board motion zones grid.`}):te?(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`p`,{className:`${s} leading-relaxed`,children:[`Toggle `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Edit grid`}),` to paint the region directly on the live frame, then `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Save`}),` to push the mask to the camera. Pick a bigger`,` `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Cell size`}),` for quicker, broad-stroke painting — it's resampled to the camera's native `,i.grid.width,`×`,i.grid.height,` grid on save (×1 is the finest).`]}),(0,f.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,f.jsx)(`button`,{type:`button`,onClick:()=>L(e=>!e),disabled:P,"aria-pressed":I,className:I?b:y,children:I?`Done editing`:`Edit grid`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>J(!0),disabled:P,className:y,children:`All on`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>J(!1),disabled:P,className:y,children:`All off`}),(0,f.jsx)(`button`,{type:`button`,onClick:Y,disabled:P,className:y,children:`Invert`}),(0,f.jsxs)(`div`,{className:`flex items-center gap-1 ml-1`,role:`group`,"aria-label":`Cell size`,children:[(0,f.jsx)(`span`,{className:`${s} mr-0.5`,children:`Cell size`}),_.map(e=>(0,f.jsxs)(`button`,{type:`button`,onClick:()=>Z(e),disabled:P,"aria-pressed":M===e,title:e===1?`Camera grid ${z}×${B} (finest)`:`${C(z,e)}×${C(B,e)} painting grid · cells ×${e} bigger`,className:M===e?b:y,children:[`×`,e]},e))]}),(0,f.jsxs)(`span`,{className:`${s} ml-1 tabular-nums`,children:[G,` / `,K,` cells · `,i.grid.width,`×`,i.grid.height,M===1?``:` · paint ${V}×${H}`]}),(0,f.jsx)(`span`,{className:`flex-1`}),(0,f.jsx)(`button`,{type:`button`,onClick:X,disabled:P||!W,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:`Revert`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>void ee(),disabled:P||!W,className:b,children:P?`Saving…`:`Save`})]})]}):(0,f.jsx)(`p`,{className:`${s} leading-relaxed`,children:`Loading the camera's grid…`})})}):null}export{D as MotionZonesSettings};
1
+ import{a as e,c as t,d as n,f as r,g as i,l as a,o,r as s,s as c,u as l}from"./index-Dcfe-5Ig.js";import{MaskShapeCanvas as u}from"./MaskShapeCanvas-DI4BY7W2-BWAtF7b-.js";var d=i(r(),1),f=i(n(),1),p=o(`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=110,h=`motion-zones`,g=0,_=[1,2,3],v=1,y=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,b=`rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors`;function x(e,t,n){let r=t*n,i=Array.from({length:r});for(let t=0;t<r;t+=1)i[t]=e[t]===!0;return i}function S(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}function C(e,t){return Math.ceil(e/t)}function w(e,t,n){return Math.min(n-1,Math.floor((e+.5)/t*n))}function T(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:i*a},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)e[r*t+n]===!0&&(o[s*i+w(n,t,i)]=!0)}return o}function E(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:t*n},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)o[r*t+n]=e[s*i+w(n,t,i)]===!0}return o}function D({deviceId:n}){let r=t(l().trpcClient,n),[i,o]=(0,d.useState)(null),[w,D]=(0,d.useState)(!1),[O,k]=(0,d.useState)(null),[A,j]=(0,d.useState)(null),[M,N]=(0,d.useState)(v),[P,F]=(0,d.useState)(!1),[I,L]=(0,d.useState)(!1),R=(0,d.useRef)(!1);(0,d.useEffect)(()=>{if(!r)return;let e=!1;return R.current=!1,o(null),D(!1),k(null),j(null),N(v),L(!1),(async()=>{try{let t=await r.motionZones?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(o(t),R.current)return;let n=await r.motionZones?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);R.current=!0;let i=n.regions.find(e=>e.shape.kind===`grid`),a=x(i?i.shape.cells:[],t.grid.width,t.grid.height);j(a),k(T(a,t.grid.width,t.grid.height,v))}catch(t){if(e)return;c(t)?D(!0):console.error(`Motion Zones load failed`,t)}})(),()=>{e=!0}},[r]);let z=i?i.grid.width:0,B=i?i.grid.height:0,V=C(z,M),H=C(B,M),U=(0,d.useMemo)(()=>O&&i?E(O,z,B,M):null,[O,i,z,B,M]),W=(0,d.useMemo)(()=>U!==null&&A!==null&&!S(U,A),[U,A]),G=(0,d.useMemo)(()=>U?U.reduce((e,t)=>t?e+1:e,0):0,[U]),K=z*B,q=V*H,J=(0,d.useCallback)(e=>{q>0&&k(Array.from({length:q},()=>e))},[q]),Y=(0,d.useCallback)(()=>{k(e=>e&&e.map(e=>!e))},[]),X=(0,d.useCallback)(()=>{A&&i&&k(T(A,z,B,M))},[A,i,z,B,M]),Z=(0,d.useCallback)(e=>{e===M||!i||k(t=>{if(!t)return N(e),t;let n=T(E(t,z,B,M),z,B,e);return N(e),n})},[M,i,z,B]),Q=(0,d.useMemo)(()=>i&&O?[{id:g,shape:{kind:`grid`,gridWidth:V,gridHeight:H,cells:[...O]}}]:[],[i,O,V,H]),$=(0,d.useCallback)((e,t)=>{t.kind===`grid`&&k(t.cells)},[]),ee=(0,d.useCallback)(async()=>{if(!(!r||!O||!i)){F(!0);try{let e=E(O,i.grid.width,i.grid.height,M),t={kind:`grid`,gridWidth:i.grid.width,gridHeight:i.grid.height,cells:e};await r.motionZones?.setZone({patch:{regions:[{id:g,enabled:!0,shape:t}]}});let n=await r.motionZones?.getStatus({});if(n){let e=n.regions.find(e=>e.shape.kind===`grid`),t=x(e?e.shape.cells:[],i.grid.width,i.grid.height);j(t),k(T(t,i.grid.width,i.grid.height,M))}}catch(e){console.error(`Motion Zones save failed`,e)}finally{F(!1)}}},[r,O,i,M]);a((0,d.useMemo)(()=>I&&!w&&i&&O?{id:h,order:m,node:(0,f.jsx)(u,{transparent:!0,items:Q,supportedShapes:[`grid`],grid:{width:V,height:H},selectedId:g,onSelect:()=>{},onShapeChange:$,onDrawComplete:()=>{},drawingKind:null})}:null,[I,w,i,O,Q,$,V,H]));let te=!w&&i!==null&&O!==null;return r?(0,f.jsx)(e,{title:`Motion Zones`,icon:(0,f.jsx)(p,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,f.jsx)(`div`,{className:`flex flex-col gap-3`,children:w?(0,f.jsx)(`p`,{className:`${s} leading-relaxed`,children:`This camera doesn't expose an on-board motion zones grid.`}):te?(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`p`,{className:`${s} leading-relaxed`,children:[`Toggle `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Edit grid`}),` to paint the region directly on the live frame, then `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Save`}),` to push the mask to the camera. Pick a bigger`,` `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Cell size`}),` for quicker, broad-stroke painting — it's resampled to the camera's native `,i.grid.width,`×`,i.grid.height,` grid on save (×1 is the finest).`]}),(0,f.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,f.jsx)(`button`,{type:`button`,onClick:()=>L(e=>!e),disabled:P,"aria-pressed":I,className:I?b:y,children:I?`Done editing`:`Edit grid`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>J(!0),disabled:P,className:y,children:`All on`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>J(!1),disabled:P,className:y,children:`All off`}),(0,f.jsx)(`button`,{type:`button`,onClick:Y,disabled:P,className:y,children:`Invert`}),(0,f.jsxs)(`div`,{className:`flex items-center gap-1 ml-1`,role:`group`,"aria-label":`Cell size`,children:[(0,f.jsx)(`span`,{className:`${s} mr-0.5`,children:`Cell size`}),_.map(e=>(0,f.jsxs)(`button`,{type:`button`,onClick:()=>Z(e),disabled:P,"aria-pressed":M===e,title:e===1?`Camera grid ${z}×${B} (finest)`:`${C(z,e)}×${C(B,e)} painting grid · cells ×${e} bigger`,className:M===e?b:y,children:[`×`,e]},e))]}),(0,f.jsxs)(`span`,{className:`${s} ml-1 tabular-nums`,children:[G,` / `,K,` cells · `,i.grid.width,`×`,i.grid.height,M===1?``:` · paint ${V}×${H}`]}),(0,f.jsx)(`span`,{className:`flex-1`}),(0,f.jsx)(`button`,{type:`button`,onClick:X,disabled:P||!W,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:`Revert`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>void ee(),disabled:P||!W,className:b,children:P?`Saving…`:`Save`})]})]}):(0,f.jsx)(`p`,{className:`${s} leading-relaxed`,children:`Loading the camera's grid…`})})}):null}export{D as MotionZonesSettings};
@@ -1 +1 @@
1
- import{a as e,c as t,d as n,f as r,g as i,i as a,l as o,n as s,o as c,r as l,s as u,t as d,u as ee}from"./index-hICPL_hd.js";import{MaskShapeCanvas as te}from"./MaskShapeCanvas-DI4BY7W2-Bxm34WFr.js";var f=i(r(),1),p=i(n(),1),m=c(`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`}]]),h=120,g=`privacy-mask`,_=`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`,v=`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 y(e){return e.kind===`rect`||e.kind===`polygon`?e:null}function b(e){let t=new Set(e.map(e=>e.id)),n=0;for(;t.has(n);)n+=1;return n}function x(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 S({deviceId:n}){let r=t(ee().trpcClient,n),[i,c]=(0,f.useState)(null),[S,C]=(0,f.useState)(!1),[w,T]=(0,f.useState)(null),[E,D]=(0,f.useState)(null),[O,k]=(0,f.useState)(!1),[A,j]=(0,f.useState)(!1),[M,N]=(0,f.useState)(null),[P,F]=(0,f.useState)(null),I=(0,f.useRef)(!1);(0,f.useEffect)(()=>{if(!r)return;let e=!1;return I.current=!1,c(null),C(!1),T(null),D(null),j(!1),N(null),F(null),(async()=>{try{let t=await r.privacyMask?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(c(t),I.current)return;let n=await r.privacyMask?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);I.current=!0;let i={enabled:n.enabled,regions:n.regions};D(i),T(i)}catch(t){if(e)return;u(t)?C(!0):console.error(`Privacy Mask load failed`,t)}})(),()=>{e=!0}},[r]);let L=(0,f.useMemo)(()=>w!==null&&E!==null&&(w.enabled!==E.enabled||!x(w.regions,E.regions)),[w,E]),R=w?w.regions.length:0,z=i?i.maxRegions:0,B=(0,f.useRef)(0);(0,f.useEffect)(()=>{B.current=z},[z]);let V=z>0&&R>=z,H=(0,f.useCallback)(()=>{T(e=>e&&{...e,enabled:!e.enabled})},[]),U=(0,f.useCallback)(e=>{N(typeof e==`number`?e:null)},[]),W=(0,f.useMemo)(()=>w?w.regions.map(e=>({id:e.id,shape:e.shape,enabled:e.enabled,label:`Zone ${String(e.id)}`})):[],[w]),G=(0,f.useCallback)((e,t)=>{let n=y(t);n&&T(t=>t&&{...t,regions:t.regions.map(t=>t.id===e?{...t,shape:n}:t)})},[]),K=(0,f.useCallback)(e=>{let t=y(e);t&&(F(null),T(e=>{if(!e)return e;let n=B.current;if(n>0&&e.regions.length>=n)return e;let r=b(e.regions),i={id:r,enabled:!0,shape:t};return N(r),{...e,regions:[...e.regions,i]}}))},[]),q=(0,f.useCallback)(e=>{j(!0),N(null),F(e)},[]),J=(0,f.useCallback)(e=>{j(!0),F(null),N(e)},[]),Y=(0,f.useCallback)(e=>{N(t=>t===e?null:t),T(t=>t&&{...t,regions:t.regions.filter(t=>t.id!==e)})},[]),X=(0,f.useCallback)(()=>{F(null),N(null),E&&T({enabled:E.enabled,regions:E.regions})},[E]),Z=(0,f.useCallback)(async()=>{if(!(!r||!w)){k(!0);try{await r.privacyMask?.setMask({patch:{enabled:w.enabled,regions:[...w.regions]}});let e=await r.privacyMask?.getStatus({});if(e){let t={enabled:e.enabled,regions:e.regions};D(t),T(t)}}catch(e){console.error(`Privacy Mask save failed`,e)}finally{k(!1)}}},[r,w]),ne=(0,f.useCallback)(()=>{j(e=>(e&&(F(null),N(null)),!e))},[]),Q=i?.supportedShapes??[];o((0,f.useMemo)(()=>A&&!S&&i&&w?{id:g,order:h,node:(0,p.jsx)(te,{transparent:!0,items:W,supportedShapes:Q,polygonVertices:i.polygonVertices,selectedId:M,onSelect:U,onShapeChange:G,onDrawComplete:K,drawingKind:P})}:null,[A,S,i,w,W,Q,M,U,G,K,P]));let re=i?.supportedShapes.includes(`rect`)??!1,ie=i?.supportedShapes.includes(`polygon`)??!1,$=i!==null&&(i.maxRegions<=0||i.supportedShapes.length===0),ae=!S&&!$&&i!==null&&w!==null;return r?(0,p.jsx)(e,{title:`Privacy Mask`,icon:(0,p.jsx)(d,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,p.jsx)(`div`,{className:`flex flex-col gap-3`,children:S||$?(0,p.jsx)(`p`,{className:`${l} leading-relaxed`,children:`This camera doesn't support an on-board privacy mask.`}):ae?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`p`,{className:`${l} leading-relaxed`,children:[`Toggle `,(0,p.jsx)(`strong`,{className:`text-foreground`,children:`Edit mask`}),` to draw blanked-out zones on the live frame, then `,(0,p.jsx)(`strong`,{className:`text-foreground`,children:`Save`}),` 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.`]}),(0,p.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,p.jsx)(`button`,{type:`button`,onClick:ne,disabled:O,"aria-pressed":A,className:A?v:_,children:A?`Done editing`:`Edit mask`}),(0,p.jsx)(`button`,{type:`button`,onClick:H,disabled:O,"aria-pressed":w.enabled,className:w.enabled?v:_,children:w.enabled?`Mask on`:`Mask off`}),re&&(0,p.jsx)(`button`,{type:`button`,onClick:()=>q(`rect`),disabled:O||V,"aria-pressed":P===`rect`,className:P===`rect`?v:_,title:V?`Maximum zones reached`:`Add a rectangle zone`,children:`+ Rect`}),ie&&(0,p.jsx)(`button`,{type:`button`,onClick:()=>q(`polygon`),disabled:O||V,"aria-pressed":P===`polygon`,className:P===`polygon`?v:_,title:V?`Maximum zones reached`:`Add a polygon zone`,children:`+ Polygon`}),(0,p.jsxs)(`span`,{className:`${l} ml-1 tabular-nums`,children:[R,` / `,z,` zones`]}),(0,p.jsx)(`span`,{className:`flex-1`}),(0,p.jsx)(`button`,{type:`button`,onClick:X,disabled:O||!L,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:`Revert`}),(0,p.jsx)(`button`,{type:`button`,onClick:()=>void Z(),disabled:O||!L,className:v,children:O?`Saving…`:`Save`})]}),R>0?(0,p.jsx)(`div`,{className:`flex flex-col gap-1`,children:w.regions.map(e=>{let t=M===e.id,n=e.shape.kind===`polygon`?m:s;return(0,p.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,p.jsxs)(`button`,{type:`button`,onClick:()=>J(e.id),disabled:O,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,p.jsx)(n,{className:`h-3.5 w-3.5 shrink-0`}),(0,p.jsxs)(`span`,{children:[`Zone `,e.id]}),(0,p.jsx)(`span`,{className:`text-foreground-faint capitalize`,children:e.shape.kind})]}),(0,p.jsx)(`button`,{type:`button`,onClick:()=>Y(e.id),disabled:O,"aria-label":`Delete zone ${String(e.id)}`,title:`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,p.jsx)(a,{className:`h-3.5 w-3.5`})})]},e.id)})}):null]}):(0,p.jsx)(`p`,{className:`${l} leading-relaxed`,children:`Loading the camera's privacy mask…`})})}):null}export{S as PrivacyMaskSettings};
1
+ import{a as e,c as t,d as n,f as r,g as i,i as a,l as o,n as s,o as c,r as l,s as u,t as d,u as ee}from"./index-Dcfe-5Ig.js";import{MaskShapeCanvas as te}from"./MaskShapeCanvas-DI4BY7W2-BWAtF7b-.js";var f=i(r(),1),p=i(n(),1),m=c(`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`}]]),h=120,g=`privacy-mask`,_=`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`,v=`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 y(e){return e.kind===`rect`||e.kind===`polygon`?e:null}function b(e){let t=new Set(e.map(e=>e.id)),n=0;for(;t.has(n);)n+=1;return n}function x(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 S({deviceId:n}){let r=t(ee().trpcClient,n),[i,c]=(0,f.useState)(null),[S,C]=(0,f.useState)(!1),[w,T]=(0,f.useState)(null),[E,D]=(0,f.useState)(null),[O,k]=(0,f.useState)(!1),[A,j]=(0,f.useState)(!1),[M,N]=(0,f.useState)(null),[P,F]=(0,f.useState)(null),I=(0,f.useRef)(!1);(0,f.useEffect)(()=>{if(!r)return;let e=!1;return I.current=!1,c(null),C(!1),T(null),D(null),j(!1),N(null),F(null),(async()=>{try{let t=await r.privacyMask?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(c(t),I.current)return;let n=await r.privacyMask?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);I.current=!0;let i={enabled:n.enabled,regions:n.regions};D(i),T(i)}catch(t){if(e)return;u(t)?C(!0):console.error(`Privacy Mask load failed`,t)}})(),()=>{e=!0}},[r]);let L=(0,f.useMemo)(()=>w!==null&&E!==null&&(w.enabled!==E.enabled||!x(w.regions,E.regions)),[w,E]),R=w?w.regions.length:0,z=i?i.maxRegions:0,B=(0,f.useRef)(0);(0,f.useEffect)(()=>{B.current=z},[z]);let V=z>0&&R>=z,H=(0,f.useCallback)(()=>{T(e=>e&&{...e,enabled:!e.enabled})},[]),U=(0,f.useCallback)(e=>{N(typeof e==`number`?e:null)},[]),W=(0,f.useMemo)(()=>w?w.regions.map(e=>({id:e.id,shape:e.shape,enabled:e.enabled,label:`Zone ${String(e.id)}`})):[],[w]),G=(0,f.useCallback)((e,t)=>{let n=y(t);n&&T(t=>t&&{...t,regions:t.regions.map(t=>t.id===e?{...t,shape:n}:t)})},[]),K=(0,f.useCallback)(e=>{let t=y(e);t&&(F(null),T(e=>{if(!e)return e;let n=B.current;if(n>0&&e.regions.length>=n)return e;let r=b(e.regions),i={id:r,enabled:!0,shape:t};return N(r),{...e,regions:[...e.regions,i]}}))},[]),q=(0,f.useCallback)(e=>{j(!0),N(null),F(e)},[]),J=(0,f.useCallback)(e=>{j(!0),F(null),N(e)},[]),Y=(0,f.useCallback)(e=>{N(t=>t===e?null:t),T(t=>t&&{...t,regions:t.regions.filter(t=>t.id!==e)})},[]),X=(0,f.useCallback)(()=>{F(null),N(null),E&&T({enabled:E.enabled,regions:E.regions})},[E]),Z=(0,f.useCallback)(async()=>{if(!(!r||!w)){k(!0);try{await r.privacyMask?.setMask({patch:{enabled:w.enabled,regions:[...w.regions]}});let e=await r.privacyMask?.getStatus({});if(e){let t={enabled:e.enabled,regions:e.regions};D(t),T(t)}}catch(e){console.error(`Privacy Mask save failed`,e)}finally{k(!1)}}},[r,w]),ne=(0,f.useCallback)(()=>{j(e=>(e&&(F(null),N(null)),!e))},[]),Q=i?.supportedShapes??[];o((0,f.useMemo)(()=>A&&!S&&i&&w?{id:g,order:h,node:(0,p.jsx)(te,{transparent:!0,items:W,supportedShapes:Q,polygonVertices:i.polygonVertices,selectedId:M,onSelect:U,onShapeChange:G,onDrawComplete:K,drawingKind:P})}:null,[A,S,i,w,W,Q,M,U,G,K,P]));let re=i?.supportedShapes.includes(`rect`)??!1,ie=i?.supportedShapes.includes(`polygon`)??!1,$=i!==null&&(i.maxRegions<=0||i.supportedShapes.length===0),ae=!S&&!$&&i!==null&&w!==null;return r?(0,p.jsx)(e,{title:`Privacy Mask`,icon:(0,p.jsx)(d,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,p.jsx)(`div`,{className:`flex flex-col gap-3`,children:S||$?(0,p.jsx)(`p`,{className:`${l} leading-relaxed`,children:`This camera doesn't support an on-board privacy mask.`}):ae?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`p`,{className:`${l} leading-relaxed`,children:[`Toggle `,(0,p.jsx)(`strong`,{className:`text-foreground`,children:`Edit mask`}),` to draw blanked-out zones on the live frame, then `,(0,p.jsx)(`strong`,{className:`text-foreground`,children:`Save`}),` 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.`]}),(0,p.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,p.jsx)(`button`,{type:`button`,onClick:ne,disabled:O,"aria-pressed":A,className:A?v:_,children:A?`Done editing`:`Edit mask`}),(0,p.jsx)(`button`,{type:`button`,onClick:H,disabled:O,"aria-pressed":w.enabled,className:w.enabled?v:_,children:w.enabled?`Mask on`:`Mask off`}),re&&(0,p.jsx)(`button`,{type:`button`,onClick:()=>q(`rect`),disabled:O||V,"aria-pressed":P===`rect`,className:P===`rect`?v:_,title:V?`Maximum zones reached`:`Add a rectangle zone`,children:`+ Rect`}),ie&&(0,p.jsx)(`button`,{type:`button`,onClick:()=>q(`polygon`),disabled:O||V,"aria-pressed":P===`polygon`,className:P===`polygon`?v:_,title:V?`Maximum zones reached`:`Add a polygon zone`,children:`+ Polygon`}),(0,p.jsxs)(`span`,{className:`${l} ml-1 tabular-nums`,children:[R,` / `,z,` zones`]}),(0,p.jsx)(`span`,{className:`flex-1`}),(0,p.jsx)(`button`,{type:`button`,onClick:X,disabled:O||!L,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:`Revert`}),(0,p.jsx)(`button`,{type:`button`,onClick:()=>void Z(),disabled:O||!L,className:v,children:O?`Saving…`:`Save`})]}),R>0?(0,p.jsx)(`div`,{className:`flex flex-col gap-1`,children:w.regions.map(e=>{let t=M===e.id,n=e.shape.kind===`polygon`?m:s;return(0,p.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,p.jsxs)(`button`,{type:`button`,onClick:()=>J(e.id),disabled:O,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,p.jsx)(n,{className:`h-3.5 w-3.5 shrink-0`}),(0,p.jsxs)(`span`,{children:[`Zone `,e.id]}),(0,p.jsx)(`span`,{className:`text-foreground-faint capitalize`,children:e.shape.kind})]}),(0,p.jsx)(`button`,{type:`button`,onClick:()=>Y(e.id),disabled:O,"aria-label":`Delete zone ${String(e.id)}`,title:`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,p.jsx)(a,{className:`h-3.5 w-3.5`})})]},e.id)})}):null]}):(0,p.jsx)(`p`,{className:`${l} leading-relaxed`,children:`Loading the camera's privacy mask…`})})}):null}export{S as PrivacyMaskSettings};