@camstack/ui-library 1.2.48 → 1.2.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{MotionZonesSettings-Bvavn6s9.js → MotionZonesSettings-DRrdkR4U.js} +1 -1
- package/dist/{PrivacyMaskSettings-CSxDpLOw.js → PrivacyMaskSettings-DsyAfD21.js} +1 -1
- package/dist/SceneMonitorEditor-BCn18zsr.js +411 -0
- package/dist/SceneMonitorEditor-BXwXLLch.cjs +411 -0
- package/dist/composites/cap-settings/SceneMonitorEditor.d.ts +2 -0
- package/dist/composites/cap-settings/index.d.ts +6 -5
- package/dist/composites/cap-settings/scene-monitor-copy.d.ts +37 -0
- package/dist/index.cjs +279 -250
- package/dist/index.js +279 -254
- package/dist/primitives/dialog.d.ts +6 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -13119,6 +13119,10 @@ var contentVariants = cva("fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-
|
|
|
13119
13119
|
* header, tab strip, footer — stays put at every height. The dialog
|
|
13120
13120
|
* keeps its own padding either way, so `sheet-on-mobile`'s
|
|
13121
13121
|
* safe-area bottom padding still applies to the footer.
|
|
13122
|
+
*
|
|
13123
|
+
* `body` also takes a DEFINITE height below `md` — see the
|
|
13124
|
+
* `scroll: 'body'` compound variants. That is not a style choice; the
|
|
13125
|
+
* flex chain does not resolve without it on iOS Safari.
|
|
13122
13126
|
*/
|
|
13123
13127
|
scroll: {
|
|
13124
13128
|
content: "overflow-y-auto",
|
|
@@ -13144,6 +13148,16 @@ var contentVariants = cva("fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-
|
|
|
13144
13148
|
placement: "sheet-on-mobile",
|
|
13145
13149
|
width: "lg",
|
|
13146
13150
|
class: "md:w-[calc(100vw-2rem)] md:max-w-lg"
|
|
13151
|
+
},
|
|
13152
|
+
{
|
|
13153
|
+
scroll: "body",
|
|
13154
|
+
placement: "sheet-on-mobile",
|
|
13155
|
+
class: "h-[92dvh] md:h-auto"
|
|
13156
|
+
},
|
|
13157
|
+
{
|
|
13158
|
+
scroll: "body",
|
|
13159
|
+
placement: "center",
|
|
13160
|
+
class: "h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] md:h-auto"
|
|
13147
13161
|
}
|
|
13148
13162
|
],
|
|
13149
13163
|
defaultVariants: {
|
|
@@ -13197,6 +13211,12 @@ function DialogHeader({ className, ...props }) {
|
|
|
13197
13211
|
* footer out of the dialog instead of scrolling — the exact failure this
|
|
13198
13212
|
* variant exists to remove. Anything sticky INSIDE a dialog body pins against
|
|
13199
13213
|
* this element, since it is the scroll container.
|
|
13214
|
+
*
|
|
13215
|
+
* It only bounds a scroll area if every ancestor up to the dialog has a
|
|
13216
|
+
* resolved height. `DialogContent` supplies the top of that chain (see the
|
|
13217
|
+
* `scroll: 'body'` compound variants); an intermediate wrapper between them —
|
|
13218
|
+
* a `<Tabs>` that owns the tab strip, say — must carry `flex-1 min-h-0` too,
|
|
13219
|
+
* or it collapses and takes the body with it.
|
|
13200
13220
|
*/
|
|
13201
13221
|
function DialogBody({ className, ...props }) {
|
|
13202
13222
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -35142,255 +35162,6 @@ function useContextSafe(ctx) {
|
|
|
35142
35162
|
return (0, react$1.useContext)(ctx);
|
|
35143
35163
|
}
|
|
35144
35164
|
//#endregion
|
|
35145
|
-
//#region src/composites/widget-panel.tsx
|
|
35146
|
-
function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, bodyClassName = "p-3", children }) {
|
|
35147
|
-
const [open, setOpen] = (0, react$1.useState)(defaultOpen);
|
|
35148
|
-
const showBody = !collapsible || open;
|
|
35149
|
-
const headerInner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
35150
|
-
className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-foreground",
|
|
35151
|
-
children: [icon, title]
|
|
35152
|
-
}), collapsible ? open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3.5 w-3.5 text-foreground-subtle" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3.5 w-3.5 text-foreground-subtle" }) : headerRight] });
|
|
35153
|
-
const headerClass = `px-4 py-2 flex items-center justify-between ${showBody ? "border-b border-border" : ""}`;
|
|
35154
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35155
|
-
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
35156
|
-
children: [collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35157
|
-
type: "button",
|
|
35158
|
-
onClick: () => setOpen((v) => !v),
|
|
35159
|
-
className: `w-full text-left ${headerClass}`,
|
|
35160
|
-
children: headerInner
|
|
35161
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35162
|
-
className: headerClass,
|
|
35163
|
-
children: headerInner
|
|
35164
|
-
}), showBody ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35165
|
-
className: bodyClassName,
|
|
35166
|
-
children
|
|
35167
|
-
}) : null]
|
|
35168
|
-
});
|
|
35169
|
-
}
|
|
35170
|
-
function WidgetMetricCard({ label, icon, children, footer }) {
|
|
35171
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35172
|
-
className: "rounded-md border border-border/60 bg-background/40 px-3 py-2",
|
|
35173
|
-
children: [
|
|
35174
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35175
|
-
className: "mb-1 flex items-center gap-1.5 text-[11px] uppercase tracking-wider text-foreground-subtle",
|
|
35176
|
-
children: [icon, label]
|
|
35177
|
-
}),
|
|
35178
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35179
|
-
className: "text-sm font-medium text-foreground",
|
|
35180
|
-
children
|
|
35181
|
-
}),
|
|
35182
|
-
footer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35183
|
-
className: "mt-0.5 text-[10px] text-foreground-subtle",
|
|
35184
|
-
children: footer
|
|
35185
|
-
}) : null
|
|
35186
|
-
]
|
|
35187
|
-
});
|
|
35188
|
-
}
|
|
35189
|
-
//#endregion
|
|
35190
|
-
//#region src/composites/cap-settings/PtzPanel.tsx
|
|
35191
|
-
/**
|
|
35192
|
-
* PtzPanel — live pan/tilt/zoom controls for the `ptz` capability. A
|
|
35193
|
-
* cap-settings component (ui-library), mounted as a cap-contributed
|
|
35194
|
-
* top-level device-detail tab via the cap-UI contribution mechanism
|
|
35195
|
-
* (the `ptz` cap declares `ui: { tab: 'ptz', kind: 'static', ... }`).
|
|
35196
|
-
*
|
|
35197
|
-
* Consolidates the former admin-ui `PTZPanelContent`: `usePTZ` drives
|
|
35198
|
-
* the controls, `PTZOverlay` renders them (`mode='panel'`), and an
|
|
35199
|
-
* Autofocus toggle is shown only when `getOptions().hasAutofocus`.
|
|
35200
|
-
* Autotrack is its own cap-UI contribution (`ptz-autotrack`), mounted
|
|
35201
|
-
* independently by the contribution mechanism — not nested here.
|
|
35202
|
-
*/
|
|
35203
|
-
/** Autofocus toggle — only mounted when `hasAutofocus`. Reads the cap's
|
|
35204
|
-
* `getStatus().autofocus` and drives `setAutofocus`. */
|
|
35205
|
-
function AutofocusToggle({ deviceId }) {
|
|
35206
|
-
const dev = useDeviceProxy(useSystem().trpcClient, deviceId);
|
|
35207
|
-
const [enabled, setEnabled] = (0, react$1.useState)(null);
|
|
35208
|
-
const [busy, setBusy] = (0, react$1.useState)(false);
|
|
35209
|
-
(0, react$1.useEffect)(() => {
|
|
35210
|
-
if (!dev) return void 0;
|
|
35211
|
-
let cancelled = false;
|
|
35212
|
-
(async () => {
|
|
35213
|
-
try {
|
|
35214
|
-
const status = await dev.ptz?.getStatus({});
|
|
35215
|
-
if (cancelled || !status) return;
|
|
35216
|
-
setEnabled(status.autofocus);
|
|
35217
|
-
} catch {}
|
|
35218
|
-
})();
|
|
35219
|
-
return () => {
|
|
35220
|
-
cancelled = true;
|
|
35221
|
-
};
|
|
35222
|
-
}, [dev]);
|
|
35223
|
-
const toggle = (0, react$1.useCallback)(async () => {
|
|
35224
|
-
if (!dev?.ptz || enabled === null) return;
|
|
35225
|
-
setBusy(true);
|
|
35226
|
-
try {
|
|
35227
|
-
const next = !enabled;
|
|
35228
|
-
await dev.ptz.setAutofocus({ enabled: next });
|
|
35229
|
-
setEnabled(next);
|
|
35230
|
-
} catch (err) {
|
|
35231
|
-
console.error("ptz.setAutofocus failed", err);
|
|
35232
|
-
} finally {
|
|
35233
|
-
setBusy(false);
|
|
35234
|
-
}
|
|
35235
|
-
}, [dev, enabled]);
|
|
35236
|
-
if (enabled === null) return null;
|
|
35237
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35238
|
-
className: "flex items-center justify-between border-t border-border/40 mt-2 pt-2",
|
|
35239
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35240
|
-
className: "text-[10.5px] font-semibold text-foreground",
|
|
35241
|
-
children: "Autofocus"
|
|
35242
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
35243
|
-
type: "button",
|
|
35244
|
-
onClick: () => void toggle(),
|
|
35245
|
-
disabled: busy,
|
|
35246
|
-
"aria-pressed": enabled,
|
|
35247
|
-
className: `flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium transition-colors ${enabled ? "bg-success/15 text-success hover:bg-success/25" : "bg-surface-elevated/40 text-foreground-subtle hover:bg-surface-hover hover:text-foreground"} disabled:opacity-50`,
|
|
35248
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `h-1.5 w-1.5 rounded-full ${enabled ? "bg-success" : "bg-foreground-subtle/40"}` }), enabled ? "On" : "Off"]
|
|
35249
|
-
})]
|
|
35250
|
-
});
|
|
35251
|
-
}
|
|
35252
|
-
function PtzPanel({ deviceId }) {
|
|
35253
|
-
const system = useSystem();
|
|
35254
|
-
const ready = Number.isFinite(deviceId);
|
|
35255
|
-
const controls = usePTZ(system.trpcClient, ready ? deviceId : 0, { enabled: ready });
|
|
35256
|
-
if (!ready) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35257
|
-
className: "flex items-center justify-center h-full text-[11px] text-foreground-subtle italic",
|
|
35258
|
-
children: [
|
|
35259
|
-
"Device #",
|
|
35260
|
-
deviceId,
|
|
35261
|
-
" not loaded."
|
|
35262
|
-
]
|
|
35263
|
-
});
|
|
35264
|
-
const hasAutofocus = controls.options?.hasAutofocus === true;
|
|
35265
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(WidgetPanel, {
|
|
35266
|
-
title: "PTZ",
|
|
35267
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Move, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
|
|
35268
|
-
bodyClassName: "p-3 flex flex-col overflow-y-auto",
|
|
35269
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PTZOverlay, {
|
|
35270
|
-
controls,
|
|
35271
|
-
mode: "panel"
|
|
35272
|
-
}), hasAutofocus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutofocusToggle, { deviceId })]
|
|
35273
|
-
});
|
|
35274
|
-
}
|
|
35275
|
-
//#endregion
|
|
35276
|
-
//#region src/composites/cap-settings/ConsumablesPanel.tsx
|
|
35277
|
-
/**
|
|
35278
|
-
* ConsumablesPanel — maintenance-items surface for the generic
|
|
35279
|
-
* `consumables` capability. A cap-settings component (ui-library),
|
|
35280
|
-
* mounted as a cap-contributed top-level device-detail tab via the
|
|
35281
|
-
* cap-UI contribution mechanism (the `consumables` cap declares
|
|
35282
|
-
* `ui: { kind:'widget', widgetId:'host/consumables-panel', tab:'consumables', topTab:true, ... }`).
|
|
35283
|
-
*
|
|
35284
|
-
* Reads the cap's pushed runtime-state slice via `useDeviceCapability`
|
|
35285
|
-
* and renders one row per item: a fill bar + percentage when `level` is
|
|
35286
|
-
* known, else an `ok`/`replace` status pill when `status` is known, else
|
|
35287
|
-
* a muted em-dash. A per-item "Replaced" button calls `cap.reset` when
|
|
35288
|
-
* the item is `resettable`. The cap is inert until a provider sets
|
|
35289
|
-
* items — an empty list renders a muted placeholder.
|
|
35290
|
-
*/
|
|
35291
|
-
/** Remaining-life % at or below this is treated as a warning. */
|
|
35292
|
-
var LOW_LEVEL_THRESHOLD = 15;
|
|
35293
|
-
function isLowOrReplace(item) {
|
|
35294
|
-
return item.status === "replace" || item.level !== null && item.level <= LOW_LEVEL_THRESHOLD;
|
|
35295
|
-
}
|
|
35296
|
-
/** Format a ms-epoch as a compact local date for the "replaced" hint. */
|
|
35297
|
-
function formatResetAt(ms) {
|
|
35298
|
-
try {
|
|
35299
|
-
return new Date(ms).toLocaleDateString(void 0, {
|
|
35300
|
-
year: "numeric",
|
|
35301
|
-
month: "short",
|
|
35302
|
-
day: "numeric"
|
|
35303
|
-
});
|
|
35304
|
-
} catch {
|
|
35305
|
-
return "";
|
|
35306
|
-
}
|
|
35307
|
-
}
|
|
35308
|
-
function ConsumableRow({ item, busy, canReset, onReset }) {
|
|
35309
|
-
const warn = isLowOrReplace(item);
|
|
35310
|
-
const barColor = warn ? "bg-warning" : "bg-success";
|
|
35311
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35312
|
-
className: `flex flex-col gap-1.5 rounded-md border p-3 ${warn ? "border-warning/40 bg-warning/5" : "border-border/40 bg-surface-elevated/30"}`,
|
|
35313
|
-
children: [
|
|
35314
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35315
|
-
className: "flex items-center justify-between gap-2",
|
|
35316
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35317
|
-
className: `text-xs font-semibold ${warn ? "text-warning" : "text-foreground"}`,
|
|
35318
|
-
children: item.label
|
|
35319
|
-
}), item.resettable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35320
|
-
type: "button",
|
|
35321
|
-
onClick: () => onReset(item.key),
|
|
35322
|
-
disabled: busy || !canReset,
|
|
35323
|
-
className: "rounded-md border border-border bg-surface px-2 py-0.5 text-[10px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors",
|
|
35324
|
-
children: "Replaced"
|
|
35325
|
-
})]
|
|
35326
|
-
}),
|
|
35327
|
-
item.level !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35328
|
-
className: "flex items-center gap-2",
|
|
35329
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35330
|
-
className: "h-1.5 flex-1 overflow-hidden rounded-full bg-surface-elevated/60",
|
|
35331
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35332
|
-
className: `h-full rounded-full ${barColor} transition-all`,
|
|
35333
|
-
style: { width: `${Math.max(0, Math.min(100, item.level))}%` }
|
|
35334
|
-
})
|
|
35335
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
35336
|
-
className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
|
|
35337
|
-
children: [Math.round(item.level), "%"]
|
|
35338
|
-
})]
|
|
35339
|
-
}) : item.status !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35340
|
-
className: `inline-flex w-fit items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium ${item.status === "replace" ? "bg-warning/15 text-warning" : "bg-success/15 text-success"}`,
|
|
35341
|
-
children: item.status === "replace" ? "Replace" : "OK"
|
|
35342
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35343
|
-
className: "text-[10px] text-foreground-subtle",
|
|
35344
|
-
children: "—"
|
|
35345
|
-
}),
|
|
35346
|
-
item.lastResetAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
35347
|
-
className: "text-[10px] text-foreground-subtle",
|
|
35348
|
-
children: ["Replaced ", formatResetAt(item.lastResetAt)]
|
|
35349
|
-
})
|
|
35350
|
-
]
|
|
35351
|
-
});
|
|
35352
|
-
}
|
|
35353
|
-
function ConsumablesPanel({ deviceId }) {
|
|
35354
|
-
const system = useSystem();
|
|
35355
|
-
const ready = Number.isFinite(deviceId);
|
|
35356
|
-
const { cap, slice, available } = useDeviceCapability({
|
|
35357
|
-
trpc: system.trpcClient,
|
|
35358
|
-
deviceId: ready ? deviceId : null,
|
|
35359
|
-
cap: (d) => d.consumables,
|
|
35360
|
-
slice: (d) => d.state.consumables
|
|
35361
|
-
});
|
|
35362
|
-
const [busyKey, setBusyKey] = (0, react$1.useState)(null);
|
|
35363
|
-
const onReset = (0, react$1.useCallback)(async (key) => {
|
|
35364
|
-
if (!cap) return;
|
|
35365
|
-
setBusyKey(key);
|
|
35366
|
-
try {
|
|
35367
|
-
await cap.reset({ key });
|
|
35368
|
-
} catch (err) {
|
|
35369
|
-
console.error("consumables.reset failed", err);
|
|
35370
|
-
} finally {
|
|
35371
|
-
setBusyKey(null);
|
|
35372
|
-
}
|
|
35373
|
-
}, [cap]);
|
|
35374
|
-
const items = slice?.items ?? [];
|
|
35375
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
|
|
35376
|
-
title: "Consumables",
|
|
35377
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Gauge, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
|
|
35378
|
-
bodyClassName: "p-3 overflow-y-auto",
|
|
35379
|
-
children: items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
35380
|
-
className: "text-[11px] text-foreground-subtle italic leading-relaxed",
|
|
35381
|
-
children: "No consumables reported."
|
|
35382
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35383
|
-
className: "flex flex-col gap-2",
|
|
35384
|
-
children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
|
|
35385
|
-
item,
|
|
35386
|
-
busy: busyKey === item.key,
|
|
35387
|
-
canReset: available,
|
|
35388
|
-
onReset: (key) => void onReset(key)
|
|
35389
|
-
}, item.key))
|
|
35390
|
-
})
|
|
35391
|
-
});
|
|
35392
|
-
}
|
|
35393
|
-
//#endregion
|
|
35394
35165
|
//#region src/hooks/use-device-autotrack.ts
|
|
35395
35166
|
/**
|
|
35396
35167
|
* `useDeviceAutotrack` — typed wrapper around the `ptz-autotrack` cap.
|
|
@@ -35449,6 +35220,51 @@ function useDeviceAutotrack(deviceId) {
|
|
|
35449
35220
|
};
|
|
35450
35221
|
}
|
|
35451
35222
|
//#endregion
|
|
35223
|
+
//#region src/composites/widget-panel.tsx
|
|
35224
|
+
function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, bodyClassName = "p-3", children }) {
|
|
35225
|
+
const [open, setOpen] = (0, react$1.useState)(defaultOpen);
|
|
35226
|
+
const showBody = !collapsible || open;
|
|
35227
|
+
const headerInner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
35228
|
+
className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-foreground",
|
|
35229
|
+
children: [icon, title]
|
|
35230
|
+
}), collapsible ? open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3.5 w-3.5 text-foreground-subtle" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3.5 w-3.5 text-foreground-subtle" }) : headerRight] });
|
|
35231
|
+
const headerClass = `px-4 py-2 flex items-center justify-between ${showBody ? "border-b border-border" : ""}`;
|
|
35232
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35233
|
+
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
35234
|
+
children: [collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35235
|
+
type: "button",
|
|
35236
|
+
onClick: () => setOpen((v) => !v),
|
|
35237
|
+
className: `w-full text-left ${headerClass}`,
|
|
35238
|
+
children: headerInner
|
|
35239
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35240
|
+
className: headerClass,
|
|
35241
|
+
children: headerInner
|
|
35242
|
+
}), showBody ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35243
|
+
className: bodyClassName,
|
|
35244
|
+
children
|
|
35245
|
+
}) : null]
|
|
35246
|
+
});
|
|
35247
|
+
}
|
|
35248
|
+
function WidgetMetricCard({ label, icon, children, footer }) {
|
|
35249
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35250
|
+
className: "rounded-md border border-border/60 bg-background/40 px-3 py-2",
|
|
35251
|
+
children: [
|
|
35252
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35253
|
+
className: "mb-1 flex items-center gap-1.5 text-[11px] uppercase tracking-wider text-foreground-subtle",
|
|
35254
|
+
children: [icon, label]
|
|
35255
|
+
}),
|
|
35256
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35257
|
+
className: "text-sm font-medium text-foreground",
|
|
35258
|
+
children
|
|
35259
|
+
}),
|
|
35260
|
+
footer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35261
|
+
className: "mt-0.5 text-[10px] text-foreground-subtle",
|
|
35262
|
+
children: footer
|
|
35263
|
+
}) : null
|
|
35264
|
+
]
|
|
35265
|
+
});
|
|
35266
|
+
}
|
|
35267
|
+
//#endregion
|
|
35452
35268
|
//#region src/composites/cap-settings/AutotrackSection.tsx
|
|
35453
35269
|
/**
|
|
35454
35270
|
* Autotrack settings card — rendered inside the PTZ panel when the
|
|
@@ -35601,6 +35417,210 @@ function AutotrackSection({ deviceId }) {
|
|
|
35601
35417
|
});
|
|
35602
35418
|
}
|
|
35603
35419
|
//#endregion
|
|
35420
|
+
//#region src/composites/cap-settings/ConsumablesPanel.tsx
|
|
35421
|
+
/**
|
|
35422
|
+
* ConsumablesPanel — maintenance-items surface for the generic
|
|
35423
|
+
* `consumables` capability. A cap-settings component (ui-library),
|
|
35424
|
+
* mounted as a cap-contributed top-level device-detail tab via the
|
|
35425
|
+
* cap-UI contribution mechanism (the `consumables` cap declares
|
|
35426
|
+
* `ui: { kind:'widget', widgetId:'host/consumables-panel', tab:'consumables', topTab:true, ... }`).
|
|
35427
|
+
*
|
|
35428
|
+
* Reads the cap's pushed runtime-state slice via `useDeviceCapability`
|
|
35429
|
+
* and renders one row per item: a fill bar + percentage when `level` is
|
|
35430
|
+
* known, else an `ok`/`replace` status pill when `status` is known, else
|
|
35431
|
+
* a muted em-dash. A per-item "Replaced" button calls `cap.reset` when
|
|
35432
|
+
* the item is `resettable`. The cap is inert until a provider sets
|
|
35433
|
+
* items — an empty list renders a muted placeholder.
|
|
35434
|
+
*/
|
|
35435
|
+
/** Remaining-life % at or below this is treated as a warning. */
|
|
35436
|
+
var LOW_LEVEL_THRESHOLD = 15;
|
|
35437
|
+
function isLowOrReplace(item) {
|
|
35438
|
+
return item.status === "replace" || item.level !== null && item.level <= LOW_LEVEL_THRESHOLD;
|
|
35439
|
+
}
|
|
35440
|
+
/** Format a ms-epoch as a compact local date for the "replaced" hint. */
|
|
35441
|
+
function formatResetAt(ms) {
|
|
35442
|
+
try {
|
|
35443
|
+
return new Date(ms).toLocaleDateString(void 0, {
|
|
35444
|
+
year: "numeric",
|
|
35445
|
+
month: "short",
|
|
35446
|
+
day: "numeric"
|
|
35447
|
+
});
|
|
35448
|
+
} catch {
|
|
35449
|
+
return "";
|
|
35450
|
+
}
|
|
35451
|
+
}
|
|
35452
|
+
function ConsumableRow({ item, busy, canReset, onReset }) {
|
|
35453
|
+
const warn = isLowOrReplace(item);
|
|
35454
|
+
const barColor = warn ? "bg-warning" : "bg-success";
|
|
35455
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35456
|
+
className: `flex flex-col gap-1.5 rounded-md border p-3 ${warn ? "border-warning/40 bg-warning/5" : "border-border/40 bg-surface-elevated/30"}`,
|
|
35457
|
+
children: [
|
|
35458
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35459
|
+
className: "flex items-center justify-between gap-2",
|
|
35460
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35461
|
+
className: `text-xs font-semibold ${warn ? "text-warning" : "text-foreground"}`,
|
|
35462
|
+
children: item.label
|
|
35463
|
+
}), item.resettable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35464
|
+
type: "button",
|
|
35465
|
+
onClick: () => onReset(item.key),
|
|
35466
|
+
disabled: busy || !canReset,
|
|
35467
|
+
className: "rounded-md border border-border bg-surface px-2 py-0.5 text-[10px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors",
|
|
35468
|
+
children: "Replaced"
|
|
35469
|
+
})]
|
|
35470
|
+
}),
|
|
35471
|
+
item.level !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35472
|
+
className: "flex items-center gap-2",
|
|
35473
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35474
|
+
className: "h-1.5 flex-1 overflow-hidden rounded-full bg-surface-elevated/60",
|
|
35475
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35476
|
+
className: `h-full rounded-full ${barColor} transition-all`,
|
|
35477
|
+
style: { width: `${Math.max(0, Math.min(100, item.level))}%` }
|
|
35478
|
+
})
|
|
35479
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
35480
|
+
className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
|
|
35481
|
+
children: [Math.round(item.level), "%"]
|
|
35482
|
+
})]
|
|
35483
|
+
}) : item.status !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35484
|
+
className: `inline-flex w-fit items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium ${item.status === "replace" ? "bg-warning/15 text-warning" : "bg-success/15 text-success"}`,
|
|
35485
|
+
children: item.status === "replace" ? "Replace" : "OK"
|
|
35486
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35487
|
+
className: "text-[10px] text-foreground-subtle",
|
|
35488
|
+
children: "—"
|
|
35489
|
+
}),
|
|
35490
|
+
item.lastResetAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
35491
|
+
className: "text-[10px] text-foreground-subtle",
|
|
35492
|
+
children: ["Replaced ", formatResetAt(item.lastResetAt)]
|
|
35493
|
+
})
|
|
35494
|
+
]
|
|
35495
|
+
});
|
|
35496
|
+
}
|
|
35497
|
+
function ConsumablesPanel({ deviceId }) {
|
|
35498
|
+
const system = useSystem();
|
|
35499
|
+
const ready = Number.isFinite(deviceId);
|
|
35500
|
+
const { cap, slice, available } = useDeviceCapability({
|
|
35501
|
+
trpc: system.trpcClient,
|
|
35502
|
+
deviceId: ready ? deviceId : null,
|
|
35503
|
+
cap: (d) => d.consumables,
|
|
35504
|
+
slice: (d) => d.state.consumables
|
|
35505
|
+
});
|
|
35506
|
+
const [busyKey, setBusyKey] = (0, react$1.useState)(null);
|
|
35507
|
+
const onReset = (0, react$1.useCallback)(async (key) => {
|
|
35508
|
+
if (!cap) return;
|
|
35509
|
+
setBusyKey(key);
|
|
35510
|
+
try {
|
|
35511
|
+
await cap.reset({ key });
|
|
35512
|
+
} catch (err) {
|
|
35513
|
+
console.error("consumables.reset failed", err);
|
|
35514
|
+
} finally {
|
|
35515
|
+
setBusyKey(null);
|
|
35516
|
+
}
|
|
35517
|
+
}, [cap]);
|
|
35518
|
+
const items = slice?.items ?? [];
|
|
35519
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
|
|
35520
|
+
title: "Consumables",
|
|
35521
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Gauge, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
|
|
35522
|
+
bodyClassName: "p-3 overflow-y-auto",
|
|
35523
|
+
children: items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
35524
|
+
className: "text-[11px] text-foreground-subtle italic leading-relaxed",
|
|
35525
|
+
children: "No consumables reported."
|
|
35526
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35527
|
+
className: "flex flex-col gap-2",
|
|
35528
|
+
children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
|
|
35529
|
+
item,
|
|
35530
|
+
busy: busyKey === item.key,
|
|
35531
|
+
canReset: available,
|
|
35532
|
+
onReset: (key) => void onReset(key)
|
|
35533
|
+
}, item.key))
|
|
35534
|
+
})
|
|
35535
|
+
});
|
|
35536
|
+
}
|
|
35537
|
+
//#endregion
|
|
35538
|
+
//#region src/composites/cap-settings/PtzPanel.tsx
|
|
35539
|
+
/**
|
|
35540
|
+
* PtzPanel — live pan/tilt/zoom controls for the `ptz` capability. A
|
|
35541
|
+
* cap-settings component (ui-library), mounted as a cap-contributed
|
|
35542
|
+
* top-level device-detail tab via the cap-UI contribution mechanism
|
|
35543
|
+
* (the `ptz` cap declares `ui: { tab: 'ptz', kind: 'static', ... }`).
|
|
35544
|
+
*
|
|
35545
|
+
* Consolidates the former admin-ui `PTZPanelContent`: `usePTZ` drives
|
|
35546
|
+
* the controls, `PTZOverlay` renders them (`mode='panel'`), and an
|
|
35547
|
+
* Autofocus toggle is shown only when `getOptions().hasAutofocus`.
|
|
35548
|
+
* Autotrack is its own cap-UI contribution (`ptz-autotrack`), mounted
|
|
35549
|
+
* independently by the contribution mechanism — not nested here.
|
|
35550
|
+
*/
|
|
35551
|
+
/** Autofocus toggle — only mounted when `hasAutofocus`. Reads the cap's
|
|
35552
|
+
* `getStatus().autofocus` and drives `setAutofocus`. */
|
|
35553
|
+
function AutofocusToggle({ deviceId }) {
|
|
35554
|
+
const dev = useDeviceProxy(useSystem().trpcClient, deviceId);
|
|
35555
|
+
const [enabled, setEnabled] = (0, react$1.useState)(null);
|
|
35556
|
+
const [busy, setBusy] = (0, react$1.useState)(false);
|
|
35557
|
+
(0, react$1.useEffect)(() => {
|
|
35558
|
+
if (!dev) return void 0;
|
|
35559
|
+
let cancelled = false;
|
|
35560
|
+
(async () => {
|
|
35561
|
+
try {
|
|
35562
|
+
const status = await dev.ptz?.getStatus({});
|
|
35563
|
+
if (cancelled || !status) return;
|
|
35564
|
+
setEnabled(status.autofocus);
|
|
35565
|
+
} catch {}
|
|
35566
|
+
})();
|
|
35567
|
+
return () => {
|
|
35568
|
+
cancelled = true;
|
|
35569
|
+
};
|
|
35570
|
+
}, [dev]);
|
|
35571
|
+
const toggle = (0, react$1.useCallback)(async () => {
|
|
35572
|
+
if (!dev?.ptz || enabled === null) return;
|
|
35573
|
+
setBusy(true);
|
|
35574
|
+
try {
|
|
35575
|
+
const next = !enabled;
|
|
35576
|
+
await dev.ptz.setAutofocus({ enabled: next });
|
|
35577
|
+
setEnabled(next);
|
|
35578
|
+
} catch (err) {
|
|
35579
|
+
console.error("ptz.setAutofocus failed", err);
|
|
35580
|
+
} finally {
|
|
35581
|
+
setBusy(false);
|
|
35582
|
+
}
|
|
35583
|
+
}, [dev, enabled]);
|
|
35584
|
+
if (enabled === null) return null;
|
|
35585
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35586
|
+
className: "flex items-center justify-between border-t border-border/40 mt-2 pt-2",
|
|
35587
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35588
|
+
className: "text-[10.5px] font-semibold text-foreground",
|
|
35589
|
+
children: "Autofocus"
|
|
35590
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
35591
|
+
type: "button",
|
|
35592
|
+
onClick: () => void toggle(),
|
|
35593
|
+
disabled: busy,
|
|
35594
|
+
"aria-pressed": enabled,
|
|
35595
|
+
className: `flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium transition-colors ${enabled ? "bg-success/15 text-success hover:bg-success/25" : "bg-surface-elevated/40 text-foreground-subtle hover:bg-surface-hover hover:text-foreground"} disabled:opacity-50`,
|
|
35596
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `h-1.5 w-1.5 rounded-full ${enabled ? "bg-success" : "bg-foreground-subtle/40"}` }), enabled ? "On" : "Off"]
|
|
35597
|
+
})]
|
|
35598
|
+
});
|
|
35599
|
+
}
|
|
35600
|
+
function PtzPanel({ deviceId }) {
|
|
35601
|
+
const system = useSystem();
|
|
35602
|
+
const ready = Number.isFinite(deviceId);
|
|
35603
|
+
const controls = usePTZ(system.trpcClient, ready ? deviceId : 0, { enabled: ready });
|
|
35604
|
+
if (!ready) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35605
|
+
className: "flex items-center justify-center h-full text-[11px] text-foreground-subtle italic",
|
|
35606
|
+
children: [
|
|
35607
|
+
"Device #",
|
|
35608
|
+
deviceId,
|
|
35609
|
+
" not loaded."
|
|
35610
|
+
]
|
|
35611
|
+
});
|
|
35612
|
+
const hasAutofocus = controls.options?.hasAutofocus === true;
|
|
35613
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(WidgetPanel, {
|
|
35614
|
+
title: "PTZ",
|
|
35615
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Move, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
|
|
35616
|
+
bodyClassName: "p-3 flex flex-col overflow-y-auto",
|
|
35617
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PTZOverlay, {
|
|
35618
|
+
controls,
|
|
35619
|
+
mode: "panel"
|
|
35620
|
+
}), hasAutofocus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutofocusToggle, { deviceId })]
|
|
35621
|
+
});
|
|
35622
|
+
}
|
|
35623
|
+
//#endregion
|
|
35604
35624
|
//#region src/composites/cap-settings/recording-config-form.ts
|
|
35605
35625
|
/** Every weekday — the `days` of a band that covers the whole week. */
|
|
35606
35626
|
var ALL_WEEKDAYS = [
|
|
@@ -37507,12 +37527,17 @@ function RecordingPanel({ deviceId }) {
|
|
|
37507
37527
|
})]
|
|
37508
37528
|
});
|
|
37509
37529
|
}
|
|
37530
|
+
//#endregion
|
|
37531
|
+
//#region src/widgets/host-widgets.ts
|
|
37532
|
+
var MotionZonesSettingsLazy = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./MotionZonesSettings-BP9p0pFB.cjs")).then((m) => ({ default: m.MotionZonesSettings })));
|
|
37533
|
+
var SceneMonitorEditorLazy = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./SceneMonitorEditor-BXwXLLch.cjs")).then((m) => ({ default: m.SceneMonitorEditor })));
|
|
37510
37534
|
/** Host widgets — ui-library React components embeddable as a
|
|
37511
37535
|
* `type:'widget'` ConfigField. `WidgetSlot` resolves these BEFORE the
|
|
37512
37536
|
* Module-Federation `WidgetRegistry`. ids are namespaced `host/<name>`. */
|
|
37513
37537
|
var HOST_WIDGETS = {
|
|
37514
|
-
"host/motion-zones-grid":
|
|
37538
|
+
"host/motion-zones-grid": MotionZonesSettingsLazy,
|
|
37515
37539
|
"host/privacy-mask-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-Oo7gyB4E.cjs")).then((m) => ({ default: m.PrivacyMaskSettings }))),
|
|
37540
|
+
"host/scene-monitor-editor": SceneMonitorEditorLazy,
|
|
37516
37541
|
"host/ptz-panel": PtzPanel,
|
|
37517
37542
|
"host/consumables-panel": ConsumablesPanel,
|
|
37518
37543
|
"host/ptz-autotrack": AutotrackSection,
|
|
@@ -48562,6 +48587,7 @@ exports.COMMIT_DEDUPE_TOLERANCE_MS = COMMIT_DEDUPE_TOLERANCE_MS;
|
|
|
48562
48587
|
exports.COMMIT_DEDUPE_WINDOW_MS = COMMIT_DEDUPE_WINDOW_MS;
|
|
48563
48588
|
exports.CONTROL_CAP_NAMES = CONTROL_CAP_NAMES;
|
|
48564
48589
|
exports.CONTROL_FILLS = CONTROL_FILLS;
|
|
48590
|
+
exports.Camera = Camera;
|
|
48565
48591
|
exports.CameraStreamPlayer = CameraStreamPlayer;
|
|
48566
48592
|
exports.Card = Card;
|
|
48567
48593
|
exports.Checkbox = Checkbox;
|
|
@@ -48713,6 +48739,7 @@ exports.RadialGauge = RadialGauge;
|
|
|
48713
48739
|
exports.RecordedPlaybackProvider = RecordedPlaybackProvider;
|
|
48714
48740
|
exports.RecordingPanel = RecordingPanel;
|
|
48715
48741
|
exports.ResponseLog = ResponseLog;
|
|
48742
|
+
exports.RotateCcw = RotateCcw;
|
|
48716
48743
|
exports.SECTION_BODY = SECTION_BODY;
|
|
48717
48744
|
exports.SECTION_CARD = SECTION_CARD;
|
|
48718
48745
|
exports.SECTION_HEADER = SECTION_HEADER;
|
|
@@ -49193,12 +49220,14 @@ exports.useDeviceProxy = useDeviceProxy;
|
|
|
49193
49220
|
exports.useDeviceProxy$1 = useDeviceProxy;
|
|
49194
49221
|
exports.useDeviceSnapshot = useDeviceSnapshot;
|
|
49195
49222
|
exports.useDeviceSnapshotImage = useDeviceSnapshotImage;
|
|
49223
|
+
exports.useDeviceSnapshotImage$1 = useDeviceSnapshotImage;
|
|
49196
49224
|
exports.useDeviceState = useDeviceState;
|
|
49197
49225
|
exports.useDeviceStateGetAllSnapshots = useDeviceStateGetAllSnapshots;
|
|
49198
49226
|
exports.useDeviceStateGetCapSlice = useDeviceStateGetCapSlice;
|
|
49199
49227
|
exports.useDeviceStateGetSnapshot = useDeviceStateGetSnapshot;
|
|
49200
49228
|
exports.useDeviceStateSetCapSlice = useDeviceStateSetCapSlice;
|
|
49201
49229
|
exports.useDeviceStateSlice = useDeviceStateSlice;
|
|
49230
|
+
exports.useDeviceStateSlice$1 = useDeviceStateSlice;
|
|
49202
49231
|
exports.useDeviceStatusGetStatus = useDeviceStatusGetStatus;
|
|
49203
49232
|
exports.useDeviceWebrtc = useDeviceWebrtc;
|
|
49204
49233
|
exports.useDevices = useDevices;
|