@camstack/ui-library 1.2.49 → 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 +259 -250
- package/dist/index.js +259 -254
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -35162,255 +35162,6 @@ function useContextSafe(ctx) {
|
|
|
35162
35162
|
return (0, react$1.useContext)(ctx);
|
|
35163
35163
|
}
|
|
35164
35164
|
//#endregion
|
|
35165
|
-
//#region src/composites/widget-panel.tsx
|
|
35166
|
-
function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, bodyClassName = "p-3", children }) {
|
|
35167
|
-
const [open, setOpen] = (0, react$1.useState)(defaultOpen);
|
|
35168
|
-
const showBody = !collapsible || open;
|
|
35169
|
-
const headerInner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
35170
|
-
className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-foreground",
|
|
35171
|
-
children: [icon, title]
|
|
35172
|
-
}), 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] });
|
|
35173
|
-
const headerClass = `px-4 py-2 flex items-center justify-between ${showBody ? "border-b border-border" : ""}`;
|
|
35174
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35175
|
-
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
35176
|
-
children: [collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35177
|
-
type: "button",
|
|
35178
|
-
onClick: () => setOpen((v) => !v),
|
|
35179
|
-
className: `w-full text-left ${headerClass}`,
|
|
35180
|
-
children: headerInner
|
|
35181
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35182
|
-
className: headerClass,
|
|
35183
|
-
children: headerInner
|
|
35184
|
-
}), showBody ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35185
|
-
className: bodyClassName,
|
|
35186
|
-
children
|
|
35187
|
-
}) : null]
|
|
35188
|
-
});
|
|
35189
|
-
}
|
|
35190
|
-
function WidgetMetricCard({ label, icon, children, footer }) {
|
|
35191
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35192
|
-
className: "rounded-md border border-border/60 bg-background/40 px-3 py-2",
|
|
35193
|
-
children: [
|
|
35194
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35195
|
-
className: "mb-1 flex items-center gap-1.5 text-[11px] uppercase tracking-wider text-foreground-subtle",
|
|
35196
|
-
children: [icon, label]
|
|
35197
|
-
}),
|
|
35198
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35199
|
-
className: "text-sm font-medium text-foreground",
|
|
35200
|
-
children
|
|
35201
|
-
}),
|
|
35202
|
-
footer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35203
|
-
className: "mt-0.5 text-[10px] text-foreground-subtle",
|
|
35204
|
-
children: footer
|
|
35205
|
-
}) : null
|
|
35206
|
-
]
|
|
35207
|
-
});
|
|
35208
|
-
}
|
|
35209
|
-
//#endregion
|
|
35210
|
-
//#region src/composites/cap-settings/PtzPanel.tsx
|
|
35211
|
-
/**
|
|
35212
|
-
* PtzPanel — live pan/tilt/zoom controls for the `ptz` capability. A
|
|
35213
|
-
* cap-settings component (ui-library), mounted as a cap-contributed
|
|
35214
|
-
* top-level device-detail tab via the cap-UI contribution mechanism
|
|
35215
|
-
* (the `ptz` cap declares `ui: { tab: 'ptz', kind: 'static', ... }`).
|
|
35216
|
-
*
|
|
35217
|
-
* Consolidates the former admin-ui `PTZPanelContent`: `usePTZ` drives
|
|
35218
|
-
* the controls, `PTZOverlay` renders them (`mode='panel'`), and an
|
|
35219
|
-
* Autofocus toggle is shown only when `getOptions().hasAutofocus`.
|
|
35220
|
-
* Autotrack is its own cap-UI contribution (`ptz-autotrack`), mounted
|
|
35221
|
-
* independently by the contribution mechanism — not nested here.
|
|
35222
|
-
*/
|
|
35223
|
-
/** Autofocus toggle — only mounted when `hasAutofocus`. Reads the cap's
|
|
35224
|
-
* `getStatus().autofocus` and drives `setAutofocus`. */
|
|
35225
|
-
function AutofocusToggle({ deviceId }) {
|
|
35226
|
-
const dev = useDeviceProxy(useSystem().trpcClient, deviceId);
|
|
35227
|
-
const [enabled, setEnabled] = (0, react$1.useState)(null);
|
|
35228
|
-
const [busy, setBusy] = (0, react$1.useState)(false);
|
|
35229
|
-
(0, react$1.useEffect)(() => {
|
|
35230
|
-
if (!dev) return void 0;
|
|
35231
|
-
let cancelled = false;
|
|
35232
|
-
(async () => {
|
|
35233
|
-
try {
|
|
35234
|
-
const status = await dev.ptz?.getStatus({});
|
|
35235
|
-
if (cancelled || !status) return;
|
|
35236
|
-
setEnabled(status.autofocus);
|
|
35237
|
-
} catch {}
|
|
35238
|
-
})();
|
|
35239
|
-
return () => {
|
|
35240
|
-
cancelled = true;
|
|
35241
|
-
};
|
|
35242
|
-
}, [dev]);
|
|
35243
|
-
const toggle = (0, react$1.useCallback)(async () => {
|
|
35244
|
-
if (!dev?.ptz || enabled === null) return;
|
|
35245
|
-
setBusy(true);
|
|
35246
|
-
try {
|
|
35247
|
-
const next = !enabled;
|
|
35248
|
-
await dev.ptz.setAutofocus({ enabled: next });
|
|
35249
|
-
setEnabled(next);
|
|
35250
|
-
} catch (err) {
|
|
35251
|
-
console.error("ptz.setAutofocus failed", err);
|
|
35252
|
-
} finally {
|
|
35253
|
-
setBusy(false);
|
|
35254
|
-
}
|
|
35255
|
-
}, [dev, enabled]);
|
|
35256
|
-
if (enabled === null) return null;
|
|
35257
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35258
|
-
className: "flex items-center justify-between border-t border-border/40 mt-2 pt-2",
|
|
35259
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35260
|
-
className: "text-[10.5px] font-semibold text-foreground",
|
|
35261
|
-
children: "Autofocus"
|
|
35262
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
35263
|
-
type: "button",
|
|
35264
|
-
onClick: () => void toggle(),
|
|
35265
|
-
disabled: busy,
|
|
35266
|
-
"aria-pressed": enabled,
|
|
35267
|
-
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`,
|
|
35268
|
-
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"]
|
|
35269
|
-
})]
|
|
35270
|
-
});
|
|
35271
|
-
}
|
|
35272
|
-
function PtzPanel({ deviceId }) {
|
|
35273
|
-
const system = useSystem();
|
|
35274
|
-
const ready = Number.isFinite(deviceId);
|
|
35275
|
-
const controls = usePTZ(system.trpcClient, ready ? deviceId : 0, { enabled: ready });
|
|
35276
|
-
if (!ready) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35277
|
-
className: "flex items-center justify-center h-full text-[11px] text-foreground-subtle italic",
|
|
35278
|
-
children: [
|
|
35279
|
-
"Device #",
|
|
35280
|
-
deviceId,
|
|
35281
|
-
" not loaded."
|
|
35282
|
-
]
|
|
35283
|
-
});
|
|
35284
|
-
const hasAutofocus = controls.options?.hasAutofocus === true;
|
|
35285
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(WidgetPanel, {
|
|
35286
|
-
title: "PTZ",
|
|
35287
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Move, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
|
|
35288
|
-
bodyClassName: "p-3 flex flex-col overflow-y-auto",
|
|
35289
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PTZOverlay, {
|
|
35290
|
-
controls,
|
|
35291
|
-
mode: "panel"
|
|
35292
|
-
}), hasAutofocus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutofocusToggle, { deviceId })]
|
|
35293
|
-
});
|
|
35294
|
-
}
|
|
35295
|
-
//#endregion
|
|
35296
|
-
//#region src/composites/cap-settings/ConsumablesPanel.tsx
|
|
35297
|
-
/**
|
|
35298
|
-
* ConsumablesPanel — maintenance-items surface for the generic
|
|
35299
|
-
* `consumables` capability. A cap-settings component (ui-library),
|
|
35300
|
-
* mounted as a cap-contributed top-level device-detail tab via the
|
|
35301
|
-
* cap-UI contribution mechanism (the `consumables` cap declares
|
|
35302
|
-
* `ui: { kind:'widget', widgetId:'host/consumables-panel', tab:'consumables', topTab:true, ... }`).
|
|
35303
|
-
*
|
|
35304
|
-
* Reads the cap's pushed runtime-state slice via `useDeviceCapability`
|
|
35305
|
-
* and renders one row per item: a fill bar + percentage when `level` is
|
|
35306
|
-
* known, else an `ok`/`replace` status pill when `status` is known, else
|
|
35307
|
-
* a muted em-dash. A per-item "Replaced" button calls `cap.reset` when
|
|
35308
|
-
* the item is `resettable`. The cap is inert until a provider sets
|
|
35309
|
-
* items — an empty list renders a muted placeholder.
|
|
35310
|
-
*/
|
|
35311
|
-
/** Remaining-life % at or below this is treated as a warning. */
|
|
35312
|
-
var LOW_LEVEL_THRESHOLD = 15;
|
|
35313
|
-
function isLowOrReplace(item) {
|
|
35314
|
-
return item.status === "replace" || item.level !== null && item.level <= LOW_LEVEL_THRESHOLD;
|
|
35315
|
-
}
|
|
35316
|
-
/** Format a ms-epoch as a compact local date for the "replaced" hint. */
|
|
35317
|
-
function formatResetAt(ms) {
|
|
35318
|
-
try {
|
|
35319
|
-
return new Date(ms).toLocaleDateString(void 0, {
|
|
35320
|
-
year: "numeric",
|
|
35321
|
-
month: "short",
|
|
35322
|
-
day: "numeric"
|
|
35323
|
-
});
|
|
35324
|
-
} catch {
|
|
35325
|
-
return "";
|
|
35326
|
-
}
|
|
35327
|
-
}
|
|
35328
|
-
function ConsumableRow({ item, busy, canReset, onReset }) {
|
|
35329
|
-
const warn = isLowOrReplace(item);
|
|
35330
|
-
const barColor = warn ? "bg-warning" : "bg-success";
|
|
35331
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35332
|
-
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"}`,
|
|
35333
|
-
children: [
|
|
35334
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35335
|
-
className: "flex items-center justify-between gap-2",
|
|
35336
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35337
|
-
className: `text-xs font-semibold ${warn ? "text-warning" : "text-foreground"}`,
|
|
35338
|
-
children: item.label
|
|
35339
|
-
}), item.resettable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
35340
|
-
type: "button",
|
|
35341
|
-
onClick: () => onReset(item.key),
|
|
35342
|
-
disabled: busy || !canReset,
|
|
35343
|
-
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",
|
|
35344
|
-
children: "Replaced"
|
|
35345
|
-
})]
|
|
35346
|
-
}),
|
|
35347
|
-
item.level !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
35348
|
-
className: "flex items-center gap-2",
|
|
35349
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35350
|
-
className: "h-1.5 flex-1 overflow-hidden rounded-full bg-surface-elevated/60",
|
|
35351
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35352
|
-
className: `h-full rounded-full ${barColor} transition-all`,
|
|
35353
|
-
style: { width: `${Math.max(0, Math.min(100, item.level))}%` }
|
|
35354
|
-
})
|
|
35355
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
35356
|
-
className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
|
|
35357
|
-
children: [Math.round(item.level), "%"]
|
|
35358
|
-
})]
|
|
35359
|
-
}) : item.status !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35360
|
-
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"}`,
|
|
35361
|
-
children: item.status === "replace" ? "Replace" : "OK"
|
|
35362
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
35363
|
-
className: "text-[10px] text-foreground-subtle",
|
|
35364
|
-
children: "—"
|
|
35365
|
-
}),
|
|
35366
|
-
item.lastResetAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
35367
|
-
className: "text-[10px] text-foreground-subtle",
|
|
35368
|
-
children: ["Replaced ", formatResetAt(item.lastResetAt)]
|
|
35369
|
-
})
|
|
35370
|
-
]
|
|
35371
|
-
});
|
|
35372
|
-
}
|
|
35373
|
-
function ConsumablesPanel({ deviceId }) {
|
|
35374
|
-
const system = useSystem();
|
|
35375
|
-
const ready = Number.isFinite(deviceId);
|
|
35376
|
-
const { cap, slice, available } = useDeviceCapability({
|
|
35377
|
-
trpc: system.trpcClient,
|
|
35378
|
-
deviceId: ready ? deviceId : null,
|
|
35379
|
-
cap: (d) => d.consumables,
|
|
35380
|
-
slice: (d) => d.state.consumables
|
|
35381
|
-
});
|
|
35382
|
-
const [busyKey, setBusyKey] = (0, react$1.useState)(null);
|
|
35383
|
-
const onReset = (0, react$1.useCallback)(async (key) => {
|
|
35384
|
-
if (!cap) return;
|
|
35385
|
-
setBusyKey(key);
|
|
35386
|
-
try {
|
|
35387
|
-
await cap.reset({ key });
|
|
35388
|
-
} catch (err) {
|
|
35389
|
-
console.error("consumables.reset failed", err);
|
|
35390
|
-
} finally {
|
|
35391
|
-
setBusyKey(null);
|
|
35392
|
-
}
|
|
35393
|
-
}, [cap]);
|
|
35394
|
-
const items = slice?.items ?? [];
|
|
35395
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
|
|
35396
|
-
title: "Consumables",
|
|
35397
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Gauge, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
|
|
35398
|
-
bodyClassName: "p-3 overflow-y-auto",
|
|
35399
|
-
children: items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
35400
|
-
className: "text-[11px] text-foreground-subtle italic leading-relaxed",
|
|
35401
|
-
children: "No consumables reported."
|
|
35402
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
35403
|
-
className: "flex flex-col gap-2",
|
|
35404
|
-
children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
|
|
35405
|
-
item,
|
|
35406
|
-
busy: busyKey === item.key,
|
|
35407
|
-
canReset: available,
|
|
35408
|
-
onReset: (key) => void onReset(key)
|
|
35409
|
-
}, item.key))
|
|
35410
|
-
})
|
|
35411
|
-
});
|
|
35412
|
-
}
|
|
35413
|
-
//#endregion
|
|
35414
35165
|
//#region src/hooks/use-device-autotrack.ts
|
|
35415
35166
|
/**
|
|
35416
35167
|
* `useDeviceAutotrack` — typed wrapper around the `ptz-autotrack` cap.
|
|
@@ -35469,6 +35220,51 @@ function useDeviceAutotrack(deviceId) {
|
|
|
35469
35220
|
};
|
|
35470
35221
|
}
|
|
35471
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
|
|
35472
35268
|
//#region src/composites/cap-settings/AutotrackSection.tsx
|
|
35473
35269
|
/**
|
|
35474
35270
|
* Autotrack settings card — rendered inside the PTZ panel when the
|
|
@@ -35621,6 +35417,210 @@ function AutotrackSection({ deviceId }) {
|
|
|
35621
35417
|
});
|
|
35622
35418
|
}
|
|
35623
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
|
|
35624
35624
|
//#region src/composites/cap-settings/recording-config-form.ts
|
|
35625
35625
|
/** Every weekday — the `days` of a band that covers the whole week. */
|
|
35626
35626
|
var ALL_WEEKDAYS = [
|
|
@@ -37527,12 +37527,17 @@ function RecordingPanel({ deviceId }) {
|
|
|
37527
37527
|
})]
|
|
37528
37528
|
});
|
|
37529
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 })));
|
|
37530
37534
|
/** Host widgets — ui-library React components embeddable as a
|
|
37531
37535
|
* `type:'widget'` ConfigField. `WidgetSlot` resolves these BEFORE the
|
|
37532
37536
|
* Module-Federation `WidgetRegistry`. ids are namespaced `host/<name>`. */
|
|
37533
37537
|
var HOST_WIDGETS = {
|
|
37534
|
-
"host/motion-zones-grid":
|
|
37538
|
+
"host/motion-zones-grid": MotionZonesSettingsLazy,
|
|
37535
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,
|
|
37536
37541
|
"host/ptz-panel": PtzPanel,
|
|
37537
37542
|
"host/consumables-panel": ConsumablesPanel,
|
|
37538
37543
|
"host/ptz-autotrack": AutotrackSection,
|
|
@@ -48582,6 +48587,7 @@ exports.COMMIT_DEDUPE_TOLERANCE_MS = COMMIT_DEDUPE_TOLERANCE_MS;
|
|
|
48582
48587
|
exports.COMMIT_DEDUPE_WINDOW_MS = COMMIT_DEDUPE_WINDOW_MS;
|
|
48583
48588
|
exports.CONTROL_CAP_NAMES = CONTROL_CAP_NAMES;
|
|
48584
48589
|
exports.CONTROL_FILLS = CONTROL_FILLS;
|
|
48590
|
+
exports.Camera = Camera;
|
|
48585
48591
|
exports.CameraStreamPlayer = CameraStreamPlayer;
|
|
48586
48592
|
exports.Card = Card;
|
|
48587
48593
|
exports.Checkbox = Checkbox;
|
|
@@ -48733,6 +48739,7 @@ exports.RadialGauge = RadialGauge;
|
|
|
48733
48739
|
exports.RecordedPlaybackProvider = RecordedPlaybackProvider;
|
|
48734
48740
|
exports.RecordingPanel = RecordingPanel;
|
|
48735
48741
|
exports.ResponseLog = ResponseLog;
|
|
48742
|
+
exports.RotateCcw = RotateCcw;
|
|
48736
48743
|
exports.SECTION_BODY = SECTION_BODY;
|
|
48737
48744
|
exports.SECTION_CARD = SECTION_CARD;
|
|
48738
48745
|
exports.SECTION_HEADER = SECTION_HEADER;
|
|
@@ -49213,12 +49220,14 @@ exports.useDeviceProxy = useDeviceProxy;
|
|
|
49213
49220
|
exports.useDeviceProxy$1 = useDeviceProxy;
|
|
49214
49221
|
exports.useDeviceSnapshot = useDeviceSnapshot;
|
|
49215
49222
|
exports.useDeviceSnapshotImage = useDeviceSnapshotImage;
|
|
49223
|
+
exports.useDeviceSnapshotImage$1 = useDeviceSnapshotImage;
|
|
49216
49224
|
exports.useDeviceState = useDeviceState;
|
|
49217
49225
|
exports.useDeviceStateGetAllSnapshots = useDeviceStateGetAllSnapshots;
|
|
49218
49226
|
exports.useDeviceStateGetCapSlice = useDeviceStateGetCapSlice;
|
|
49219
49227
|
exports.useDeviceStateGetSnapshot = useDeviceStateGetSnapshot;
|
|
49220
49228
|
exports.useDeviceStateSetCapSlice = useDeviceStateSetCapSlice;
|
|
49221
49229
|
exports.useDeviceStateSlice = useDeviceStateSlice;
|
|
49230
|
+
exports.useDeviceStateSlice$1 = useDeviceStateSlice;
|
|
49222
49231
|
exports.useDeviceStatusGetStatus = useDeviceStatusGetStatus;
|
|
49223
49232
|
exports.useDeviceWebrtc = useDeviceWebrtc;
|
|
49224
49233
|
exports.useDevices = useDevices;
|