@camstack/ui-library 1.2.316 → 1.2.318
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/{PrivacyMaskSettings-CtetT6Jl.js → PrivacyMaskSettings-C9wfHkiB.js} +1 -1
- package/dist/{SceneMonitorEditor-BQhVO2F2.js → SceneMonitorEditor-CrxB3ZPL.js} +1 -1
- package/dist/composites/addon-page-props.d.ts +9 -1
- package/dist/composites/cap-settings/ConsumablesPanel.d.ts +2 -0
- package/dist/composites/cap-settings/consumable-rows.d.ts +21 -0
- package/dist/composites/cap-settings/consumables-summary.d.ts +41 -0
- package/dist/composites/consumable-level-badge.d.ts +25 -18
- package/dist/composites/device-controls/dishwasher-control.d.ts +3 -0
- package/dist/composites/device-controls/index.d.ts +1 -1
- package/dist/composites/device-controls/vacuum-control.d.ts +1 -13
- package/dist/composites/widget-panel.d.ts +9 -4
- package/dist/generated/system-hooks.d.ts +6 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/use-persisted-disclosure.d.ts +3 -0
- package/dist/{icons-D178phML.js → icons-aFMzvquu.js} +21 -1
- package/dist/{icons-BTux33dH.cjs → icons-pqV1UOV_.cjs} +21 -1
- package/dist/index.cjs +590 -470
- package/dist/index.js +591 -473
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1899,26 +1899,6 @@ var Flashlight = require_createLucideIcon.createLucideIcon("flashlight", [
|
|
|
1899
1899
|
* This source code is licensed under the ISC license.
|
|
1900
1900
|
* See the LICENSE file in the root directory of this source tree.
|
|
1901
1901
|
*/
|
|
1902
|
-
var FlaskConical = require_createLucideIcon.createLucideIcon("flask-conical", [
|
|
1903
|
-
["path", {
|
|
1904
|
-
d: "M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2",
|
|
1905
|
-
key: "18mbvz"
|
|
1906
|
-
}],
|
|
1907
|
-
["path", {
|
|
1908
|
-
d: "M6.453 15h11.094",
|
|
1909
|
-
key: "3shlmq"
|
|
1910
|
-
}],
|
|
1911
|
-
["path", {
|
|
1912
|
-
d: "M8.5 2h7",
|
|
1913
|
-
key: "csnxdl"
|
|
1914
|
-
}]
|
|
1915
|
-
]);
|
|
1916
|
-
/**
|
|
1917
|
-
* @license lucide-react v0.576.0 - ISC
|
|
1918
|
-
*
|
|
1919
|
-
* This source code is licensed under the ISC license.
|
|
1920
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
1921
|
-
*/
|
|
1922
1902
|
var Footprints = require_createLucideIcon.createLucideIcon("footprints", [
|
|
1923
1903
|
["path", {
|
|
1924
1904
|
d: "M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z",
|
|
@@ -5870,7 +5850,7 @@ function lucideExportName(key) {
|
|
|
5870
5850
|
function loadIconGlyphs() {
|
|
5871
5851
|
if (fullTable !== void 0) return Promise.resolve();
|
|
5872
5852
|
if (failure$1 !== void 0) return Promise.reject(failure$1);
|
|
5873
|
-
pending ??= Promise.resolve().then(() => require("./icons-
|
|
5853
|
+
pending ??= Promise.resolve().then(() => require("./icons-pqV1UOV_.cjs")).then((module) => {
|
|
5874
5854
|
fullTable = module;
|
|
5875
5855
|
publish();
|
|
5876
5856
|
}, (error) => {
|
|
@@ -10801,6 +10781,10 @@ var useDeviceHistoryGetDeviceEnabled = trpc.deviceHistory.getDeviceEnabled.useQu
|
|
|
10801
10781
|
var useDeviceHistorySetDeviceEnabled = trpc.deviceHistory.setDeviceEnabled.useMutation;
|
|
10802
10782
|
/** Generated alias around `trpc.deviceHistory.getStatus.useQuery`. */
|
|
10803
10783
|
var useDeviceHistoryGetStatus = trpc.deviceHistory.getStatus.useQuery;
|
|
10784
|
+
/** Generated alias around `trpc.deviceHistory.listOrphanSeries.useQuery`. */
|
|
10785
|
+
var useDeviceHistoryListOrphanSeries = trpc.deviceHistory.listOrphanSeries.useQuery;
|
|
10786
|
+
/** Generated alias around `trpc.deviceHistory.deleteOrphanSeries.useMutation`. */
|
|
10787
|
+
var useDeviceHistoryDeleteOrphanSeries = trpc.deviceHistory.deleteOrphanSeries.useMutation;
|
|
10804
10788
|
/** Generated alias around `trpc.deviceManager.allocateDeviceId.useMutation`. */
|
|
10805
10789
|
var useDeviceManagerAllocateDeviceId = trpc.deviceManager.allocateDeviceId.useMutation;
|
|
10806
10790
|
/** Generated alias around `trpc.deviceManager.migrateDevice.useMutation`. */
|
|
@@ -11953,6 +11937,8 @@ var usePipelineOrchestratorSetAgentReachableHost = trpc.pipelineOrchestrator.set
|
|
|
11953
11937
|
var usePipelineOrchestratorSetAgentInferenceDevices = trpc.pipelineOrchestrator.setAgentInferenceDevices.useMutation;
|
|
11954
11938
|
/** Generated alias around `trpc.pipelineOrchestrator.getNodeInferenceDevices.useQuery`. */
|
|
11955
11939
|
var usePipelineOrchestratorGetNodeInferenceDevices = trpc.pipelineOrchestrator.getNodeInferenceDevices.useQuery;
|
|
11940
|
+
/** Generated alias around `trpc.pipelineOrchestrator.forgetInferenceDevice.useMutation`. */
|
|
11941
|
+
var usePipelineOrchestratorForgetInferenceDevice = trpc.pipelineOrchestrator.forgetInferenceDevice.useMutation;
|
|
11956
11942
|
/** Generated alias around `trpc.pipelineOrchestrator.resetNodePipelineDefaults.useMutation`. */
|
|
11957
11943
|
var usePipelineOrchestratorResetNodePipelineDefaults = trpc.pipelineOrchestrator.resetNodePipelineDefaults.useMutation;
|
|
11958
11944
|
/** Generated alias around `trpc.pipelineOrchestrator.planAutoTune.useQuery`. */
|
|
@@ -19149,20 +19135,30 @@ function useDeviceAutotrack(deviceId) {
|
|
|
19149
19135
|
}
|
|
19150
19136
|
//#endregion
|
|
19151
19137
|
//#region src/composites/widget-panel.tsx
|
|
19152
|
-
function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, bodyClassName = "p-3", children }) {
|
|
19153
|
-
const [
|
|
19138
|
+
function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, open: openProp, onOpenChange, bodyClassName = "p-3", children }) {
|
|
19139
|
+
const [openState, setOpenState] = (0, react$1.useState)(defaultOpen);
|
|
19140
|
+
const open = openProp ?? openState;
|
|
19141
|
+
const toggle = () => {
|
|
19142
|
+
const next = !open;
|
|
19143
|
+
if (openProp === void 0) setOpenState(next);
|
|
19144
|
+
onOpenChange?.(next);
|
|
19145
|
+
};
|
|
19154
19146
|
const showBody = !collapsible || open;
|
|
19155
19147
|
const headerInner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
19156
19148
|
className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-foreground",
|
|
19157
19149
|
children: [icon, title]
|
|
19158
|
-
}), collapsible ?
|
|
19150
|
+
}), collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
19151
|
+
className: "flex min-w-0 items-center gap-2",
|
|
19152
|
+
children: [headerRight, open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3.5 w-3.5 shrink-0 text-foreground-subtle" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-foreground-subtle" })]
|
|
19153
|
+
}) : headerRight] });
|
|
19159
19154
|
const headerClass = `px-4 py-2 flex items-center justify-between ${showBody ? "border-b border-border" : ""}`;
|
|
19160
19155
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19161
19156
|
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
19162
19157
|
children: [collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
19163
19158
|
type: "button",
|
|
19164
|
-
|
|
19165
|
-
|
|
19159
|
+
"aria-expanded": open,
|
|
19160
|
+
onClick: toggle,
|
|
19161
|
+
className: `w-full gap-2 text-left ${headerClass}`,
|
|
19166
19162
|
children: headerInner
|
|
19167
19163
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19168
19164
|
className: headerClass,
|
|
@@ -20523,6 +20519,60 @@ function useDeviceCapability(options) {
|
|
|
20523
20519
|
};
|
|
20524
20520
|
}
|
|
20525
20521
|
//#endregion
|
|
20522
|
+
//#region src/hooks/use-persisted-disclosure.ts
|
|
20523
|
+
/**
|
|
20524
|
+
* usePersistedDisclosure — an open / closed choice the viewer made, remembered
|
|
20525
|
+
* per viewer under the caller's key (one key per section and device).
|
|
20526
|
+
*
|
|
20527
|
+
* `null` means the viewer never chose. The caller then shows its own default,
|
|
20528
|
+
* which may follow the data (the Consumables section opens itself while an
|
|
20529
|
+
* item needs attention) — the default is never written, so it can change with
|
|
20530
|
+
* the data until the viewer picks.
|
|
20531
|
+
*
|
|
20532
|
+
* Storage is a convenience, never a requirement: every read and write is
|
|
20533
|
+
* guarded, and a blocked / private-mode storage means the choice lasts until
|
|
20534
|
+
* the page is left. A stored value this hook did not write is ignored.
|
|
20535
|
+
*/
|
|
20536
|
+
var OPEN = "open";
|
|
20537
|
+
var CLOSED = "closed";
|
|
20538
|
+
function readStored$1(key) {
|
|
20539
|
+
if (key === null || typeof window === "undefined") return null;
|
|
20540
|
+
try {
|
|
20541
|
+
const raw = window.localStorage.getItem(key);
|
|
20542
|
+
if (raw === OPEN) return true;
|
|
20543
|
+
if (raw === CLOSED) return false;
|
|
20544
|
+
return null;
|
|
20545
|
+
} catch {
|
|
20546
|
+
return null;
|
|
20547
|
+
}
|
|
20548
|
+
}
|
|
20549
|
+
function writeStored$1(key, open) {
|
|
20550
|
+
if (key === null || typeof window === "undefined") return;
|
|
20551
|
+
try {
|
|
20552
|
+
window.localStorage.setItem(key, open ? OPEN : CLOSED);
|
|
20553
|
+
} catch {}
|
|
20554
|
+
}
|
|
20555
|
+
/** @returns `[choice, setChoice]` — `choice` is `null` until the viewer picks. */
|
|
20556
|
+
function usePersistedDisclosure(storageKey) {
|
|
20557
|
+
const [choice, setChoice] = (0, react$1.useState)(() => ({
|
|
20558
|
+
key: storageKey,
|
|
20559
|
+
open: readStored$1(storageKey)
|
|
20560
|
+
}));
|
|
20561
|
+
const current = choice.key === storageKey ? choice : {
|
|
20562
|
+
key: storageKey,
|
|
20563
|
+
open: readStored$1(storageKey)
|
|
20564
|
+
};
|
|
20565
|
+
if (current !== choice) setChoice(current);
|
|
20566
|
+
const update = (0, react$1.useCallback)((open) => {
|
|
20567
|
+
setChoice({
|
|
20568
|
+
key: storageKey,
|
|
20569
|
+
open
|
|
20570
|
+
});
|
|
20571
|
+
writeStored$1(storageKey, open);
|
|
20572
|
+
}, [storageKey]);
|
|
20573
|
+
return [current.open, update];
|
|
20574
|
+
}
|
|
20575
|
+
//#endregion
|
|
20526
20576
|
//#region src/composites/consumable-level-badge.tsx
|
|
20527
20577
|
var LOOKS$1 = {
|
|
20528
20578
|
ok: {
|
|
@@ -20543,6 +20593,12 @@ var LOOKS$1 = {
|
|
|
20543
20593
|
labelKey: "uiLibrary.consumableLevel.empty",
|
|
20544
20594
|
labelFallback: "Empty"
|
|
20545
20595
|
},
|
|
20596
|
+
full: {
|
|
20597
|
+
variant: "warning",
|
|
20598
|
+
barClass: "bg-warning",
|
|
20599
|
+
labelKey: "uiLibrary.consumableLevel.full",
|
|
20600
|
+
labelFallback: "Full"
|
|
20601
|
+
},
|
|
20546
20602
|
unknown: {
|
|
20547
20603
|
variant: "neutral",
|
|
20548
20604
|
barClass: "bg-foreground-subtle",
|
|
@@ -20566,39 +20622,57 @@ function consumableLevelLabel(level, t) {
|
|
|
20566
20622
|
const look = lookOf(level);
|
|
20567
20623
|
return t(look.labelKey, look.labelFallback);
|
|
20568
20624
|
}
|
|
20569
|
-
/** A remaining-life % at or below this is `low`. */
|
|
20625
|
+
/** A remaining-life (or supply fill) % at or below this is `low`. */
|
|
20570
20626
|
var CONSUMABLE_LOW_PERCENT = 15;
|
|
20571
|
-
/**
|
|
20572
|
-
|
|
20573
|
-
|
|
20574
|
-
|
|
20575
|
-
|
|
20576
|
-
|
|
20577
|
-
|
|
20627
|
+
/** A waste item: full is bad, low or empty is what it should be. */
|
|
20628
|
+
function wasteLevel(item) {
|
|
20629
|
+
if (item.status === "full" || item.status === "replace") return "full";
|
|
20630
|
+
if (item.level !== null) return item.level >= 85 ? "full" : "ok";
|
|
20631
|
+
return item.status === null ? null : "ok";
|
|
20632
|
+
}
|
|
20633
|
+
/** A wear part or a supply: low / empty is bad, a full supply is fine. */
|
|
20634
|
+
function drawnLevel(item) {
|
|
20578
20635
|
if (item.level !== null) {
|
|
20579
|
-
if (item.level <= 0) return "empty";
|
|
20580
|
-
if (item.level <= 15 || item.status === "replace") return "low";
|
|
20636
|
+
if (item.level <= 0 || item.status === "empty") return "empty";
|
|
20637
|
+
if (item.level <= 15 || item.status === "replace" || item.status === "low") return "low";
|
|
20581
20638
|
return "ok";
|
|
20582
20639
|
}
|
|
20583
20640
|
switch (item.status) {
|
|
20584
|
-
case "replace":
|
|
20585
|
-
case "
|
|
20641
|
+
case "replace":
|
|
20642
|
+
case "low": return "low";
|
|
20643
|
+
case "empty": return "empty";
|
|
20644
|
+
case "ok":
|
|
20645
|
+
case "full": return "ok";
|
|
20586
20646
|
case null: return null;
|
|
20587
20647
|
}
|
|
20588
20648
|
}
|
|
20589
20649
|
/**
|
|
20650
|
+
* A `consumables` cap item as a level, by its kind (D915):
|
|
20651
|
+
* - a `waste` item (a dust bin) is `full` when the provider says so or its
|
|
20652
|
+
* fill is at or above {@link CONSUMABLE_FULL_PERCENT}, else ok;
|
|
20653
|
+
* - a `wear` part or a `supply` reads a known % (0 = empty, at or below
|
|
20654
|
+
* {@link CONSUMABLE_LOW_PERCENT} = low), never better than `low` when the
|
|
20655
|
+
* provider says `replace` or `low`; without a % its discrete status.
|
|
20656
|
+
* Nothing known is unknown (`null`).
|
|
20657
|
+
*/
|
|
20658
|
+
function consumableItemLevel(item) {
|
|
20659
|
+
return (0, _camstack_types.consumableKindOf)(item) === "waste" ? wasteLevel(item) : drawnLevel(item);
|
|
20660
|
+
}
|
|
20661
|
+
/** The supply vocabulary a level FIELD of a cap is typed by: `ok | low | empty`. */
|
|
20662
|
+
var SUPPLY_LEVELS = _camstack_types.ConsumableLevelSchema.exclude(["full"]).options;
|
|
20663
|
+
/**
|
|
20590
20664
|
* Whether a cap state field IS a consumable level: an enum whose members are
|
|
20591
|
-
* exactly the
|
|
20592
|
-
* cap that uses the
|
|
20665
|
+
* exactly the supply levels (`ok | low | empty`). Derived from the schema, so a
|
|
20666
|
+
* new cap that uses the vocabulary is recognised without naming it anywhere.
|
|
20593
20667
|
*/
|
|
20594
20668
|
function isConsumableLevelField(field) {
|
|
20595
20669
|
if (field.kind !== "enum" || field.enumValues === void 0) return false;
|
|
20596
|
-
const members =
|
|
20670
|
+
const members = SUPPLY_LEVELS;
|
|
20597
20671
|
return field.enumValues.length === members.length && members.every((m) => field.enumValues?.includes(m));
|
|
20598
20672
|
}
|
|
20599
20673
|
/** A value read from a slice as a level; anything that is not a member is unknown. */
|
|
20600
20674
|
function parseConsumableLevel(value) {
|
|
20601
|
-
const parsed = _camstack_types.
|
|
20675
|
+
const parsed = _camstack_types.ConsumableLevelSchema.safeParse(value);
|
|
20602
20676
|
return parsed.success ? parsed.data : null;
|
|
20603
20677
|
}
|
|
20604
20678
|
function ConsumableLevelBadge({ level, name, label, size = "md", className }) {
|
|
@@ -20613,26 +20687,50 @@ function ConsumableLevelBadge({ level, name, label, size = "md", className }) {
|
|
|
20613
20687
|
});
|
|
20614
20688
|
}
|
|
20615
20689
|
//#endregion
|
|
20616
|
-
//#region src/
|
|
20617
|
-
|
|
20618
|
-
|
|
20619
|
-
|
|
20620
|
-
|
|
20621
|
-
|
|
20622
|
-
|
|
20623
|
-
|
|
20624
|
-
|
|
20625
|
-
|
|
20626
|
-
|
|
20627
|
-
|
|
20628
|
-
|
|
20629
|
-
|
|
20630
|
-
|
|
20631
|
-
|
|
20632
|
-
|
|
20633
|
-
|
|
20634
|
-
|
|
20635
|
-
|
|
20690
|
+
//#region src/primitives/skeleton.tsx
|
|
20691
|
+
var Skeleton = (0, react$1.forwardRef)(({ className, ...props }, ref) => {
|
|
20692
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20693
|
+
ref,
|
|
20694
|
+
className: cn("animate-pulse bg-surface-hover rounded-md h-4 w-full", className),
|
|
20695
|
+
...props
|
|
20696
|
+
});
|
|
20697
|
+
});
|
|
20698
|
+
Skeleton.displayName = "Skeleton";
|
|
20699
|
+
//#endregion
|
|
20700
|
+
//#region src/primitives/button.tsx
|
|
20701
|
+
var buttonVariants = cva("inline-flex items-center justify-center rounded-md font-medium transition-colors cursor-pointer disabled:opacity-50 disabled:pointer-events-none", {
|
|
20702
|
+
variants: {
|
|
20703
|
+
variant: {
|
|
20704
|
+
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
20705
|
+
secondary: "bg-surface text-foreground hover:bg-surface-hover",
|
|
20706
|
+
ghost: "hover:bg-surface-hover text-foreground-muted",
|
|
20707
|
+
danger: "bg-danger text-white hover:bg-danger/90",
|
|
20708
|
+
outline: "border border-border bg-transparent text-foreground hover:bg-surface-hover"
|
|
20709
|
+
},
|
|
20710
|
+
size: {
|
|
20711
|
+
sm: "h-7 px-2.5 text-xs gap-1.5",
|
|
20712
|
+
md: "h-8 px-3 text-sm gap-2",
|
|
20713
|
+
lg: "h-9 px-4 text-sm gap-2"
|
|
20714
|
+
}
|
|
20715
|
+
},
|
|
20716
|
+
defaultVariants: {
|
|
20717
|
+
variant: "primary",
|
|
20718
|
+
size: "sm"
|
|
20719
|
+
}
|
|
20720
|
+
});
|
|
20721
|
+
var Button = (0, react$1.forwardRef)(({ className, variant, size, ...props }, ref) => {
|
|
20722
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
20723
|
+
ref,
|
|
20724
|
+
className: cn(buttonVariants({
|
|
20725
|
+
variant,
|
|
20726
|
+
size
|
|
20727
|
+
}), className),
|
|
20728
|
+
...props
|
|
20729
|
+
});
|
|
20730
|
+
});
|
|
20731
|
+
Button.displayName = "Button";
|
|
20732
|
+
//#endregion
|
|
20733
|
+
//#region src/composites/cap-settings/consumable-rows.tsx
|
|
20636
20734
|
/** `value` null = unknown (D393) — renders an em-dash, never a fake `0`. */
|
|
20637
20735
|
function formatRemaining(t, r) {
|
|
20638
20736
|
if (r.value === null) return "—";
|
|
@@ -20664,55 +20762,278 @@ function formatResetAt(ms) {
|
|
|
20664
20762
|
return "";
|
|
20665
20763
|
}
|
|
20666
20764
|
}
|
|
20667
|
-
function
|
|
20765
|
+
function needsAttention(level) {
|
|
20766
|
+
return level === "low" || level === "empty" || level === "full";
|
|
20767
|
+
}
|
|
20768
|
+
function clampPercent(value) {
|
|
20769
|
+
return Math.max(0, Math.min(100, value));
|
|
20770
|
+
}
|
|
20771
|
+
function RowFrame({ item, warn, children }) {
|
|
20772
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20773
|
+
"data-form": (0, _camstack_types.consumableFormOf)(item),
|
|
20774
|
+
className: `flex gap-2.5 rounded-md border p-3 ${warn ? "border-warning/40 bg-warning/5" : "border-border-subtle"}`,
|
|
20775
|
+
children
|
|
20776
|
+
});
|
|
20777
|
+
}
|
|
20778
|
+
function RowLabel({ item, warn }) {
|
|
20779
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
20780
|
+
className: `truncate text-xs font-semibold ${warn ? "text-warning" : "text-foreground"}`,
|
|
20781
|
+
children: item.label
|
|
20782
|
+
});
|
|
20783
|
+
}
|
|
20784
|
+
/** A wear part: bar + %, or its verdict; remaining time; the reset. */
|
|
20785
|
+
function PartRow({ item, busy, canReset, onReset }) {
|
|
20668
20786
|
const t = useUiText();
|
|
20669
20787
|
const level = consumableItemLevel(item);
|
|
20670
|
-
const warn = level
|
|
20671
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
20672
|
-
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20677
|
-
|
|
20678
|
-
|
|
20679
|
-
|
|
20788
|
+
const warn = needsAttention(level);
|
|
20789
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowFrame, {
|
|
20790
|
+
item,
|
|
20791
|
+
warn,
|
|
20792
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20793
|
+
className: "flex min-w-0 flex-1 flex-col gap-1.5",
|
|
20794
|
+
children: [
|
|
20795
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowLabel, {
|
|
20796
|
+
item,
|
|
20797
|
+
warn
|
|
20798
|
+
}),
|
|
20799
|
+
item.level !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20800
|
+
className: "flex items-center gap-2",
|
|
20801
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20802
|
+
className: "h-1.5 flex-1 overflow-hidden rounded-full bg-surface-hover",
|
|
20803
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20804
|
+
className: `h-full rounded-full ${consumableLevelBarClass(level)} transition-all`,
|
|
20805
|
+
style: { width: `${clampPercent(item.level)}%` }
|
|
20806
|
+
})
|
|
20807
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
20808
|
+
className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
|
|
20809
|
+
children: [Math.round(item.level), "%"]
|
|
20810
|
+
})]
|
|
20811
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
|
|
20812
|
+
level,
|
|
20813
|
+
size: "sm",
|
|
20814
|
+
className: "w-fit",
|
|
20815
|
+
label: item.status === "replace" ? t("uiLibrary.consumablesPanel.replace", "Replace") : void 0
|
|
20816
|
+
}),
|
|
20817
|
+
item.remaining && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
20818
|
+
className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
|
|
20819
|
+
children: formatRemaining(t, item.remaining)
|
|
20820
|
+
}),
|
|
20821
|
+
item.lastResetAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
20822
|
+
className: "text-[10px] text-foreground-subtle",
|
|
20823
|
+
children: t("uiLibrary.consumablesPanel.replacedOn", "Replaced {{date}}", { date: formatResetAt(item.lastResetAt) })
|
|
20824
|
+
}),
|
|
20825
|
+
item.resettable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
20680
20826
|
type: "button",
|
|
20827
|
+
variant: "outline",
|
|
20828
|
+
size: "sm",
|
|
20829
|
+
className: "w-fit",
|
|
20681
20830
|
onClick: () => onReset(item.key),
|
|
20682
20831
|
disabled: busy || !canReset,
|
|
20683
|
-
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",
|
|
20684
20832
|
children: t("uiLibrary.consumablesPanel.replaced", "Replaced")
|
|
20685
|
-
})
|
|
20686
|
-
|
|
20687
|
-
|
|
20833
|
+
})
|
|
20834
|
+
]
|
|
20835
|
+
})
|
|
20836
|
+
});
|
|
20837
|
+
}
|
|
20838
|
+
/** A tank or a bin: a vertical gauge filled from the bottom, then the % or the verdict. */
|
|
20839
|
+
function GaugeRow({ item }) {
|
|
20840
|
+
const level = consumableItemLevel(item);
|
|
20841
|
+
const warn = needsAttention(level);
|
|
20842
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(RowFrame, {
|
|
20843
|
+
item,
|
|
20844
|
+
warn,
|
|
20845
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20846
|
+
className: "relative h-10 w-3 shrink-0 overflow-hidden rounded-sm border border-border bg-surface-hover",
|
|
20847
|
+
"data-testid": "consumable-gauge",
|
|
20848
|
+
children: item.level !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20849
|
+
className: `absolute inset-x-0 bottom-0 ${consumableLevelBarClass(level)} transition-all`,
|
|
20850
|
+
style: { height: `${clampPercent(item.level)}%` }
|
|
20851
|
+
})
|
|
20852
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20853
|
+
className: "flex min-w-0 flex-1 flex-col justify-center gap-1",
|
|
20854
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowLabel, {
|
|
20855
|
+
item,
|
|
20856
|
+
warn
|
|
20857
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20688
20858
|
className: "flex items-center gap-2",
|
|
20689
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
20690
|
-
|
|
20691
|
-
|
|
20692
|
-
|
|
20693
|
-
|
|
20694
|
-
})
|
|
20695
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
20859
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
|
|
20860
|
+
level,
|
|
20861
|
+
size: "sm",
|
|
20862
|
+
className: "w-fit"
|
|
20863
|
+
}), item.level !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
20696
20864
|
className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
|
|
20697
20865
|
children: [Math.round(item.level), "%"]
|
|
20698
20866
|
})]
|
|
20699
|
-
})
|
|
20700
|
-
|
|
20701
|
-
|
|
20702
|
-
|
|
20703
|
-
|
|
20704
|
-
|
|
20705
|
-
|
|
20706
|
-
|
|
20707
|
-
|
|
20708
|
-
|
|
20709
|
-
|
|
20710
|
-
|
|
20711
|
-
|
|
20712
|
-
|
|
20713
|
-
|
|
20867
|
+
})]
|
|
20868
|
+
})]
|
|
20869
|
+
});
|
|
20870
|
+
}
|
|
20871
|
+
/** How many of the three steps a product's level fills. */
|
|
20872
|
+
var PRODUCT_STEPS = {
|
|
20873
|
+
full: 3,
|
|
20874
|
+
ok: 3,
|
|
20875
|
+
low: 1,
|
|
20876
|
+
empty: 0,
|
|
20877
|
+
unknown: 0
|
|
20878
|
+
};
|
|
20879
|
+
var STEP_INDEXES = [
|
|
20880
|
+
0,
|
|
20881
|
+
1,
|
|
20882
|
+
2
|
|
20883
|
+
];
|
|
20884
|
+
/** The refill wording a product's level reads as. */
|
|
20885
|
+
function productLabel(level, t) {
|
|
20886
|
+
if (level === "low") return t("uiLibrary.consumablesPanel.refillSoon", "Refill soon");
|
|
20887
|
+
if (level === "empty") return t("uiLibrary.consumablesPanel.refillNow", "Refill now");
|
|
20888
|
+
}
|
|
20889
|
+
/** A product bought and refilled: package icon, three steps, "refill". */
|
|
20890
|
+
function ProductRow({ item }) {
|
|
20891
|
+
const t = useUiText();
|
|
20892
|
+
const level = consumableItemLevel(item);
|
|
20893
|
+
const warn = needsAttention(level);
|
|
20894
|
+
const filled = PRODUCT_STEPS[level ?? "unknown"];
|
|
20895
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(RowFrame, {
|
|
20896
|
+
item,
|
|
20897
|
+
warn,
|
|
20898
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Package, { className: `mt-0.5 h-4 w-4 shrink-0 ${warn ? "text-warning" : "text-foreground-subtle"}` }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20899
|
+
className: "flex min-w-0 flex-1 flex-col gap-1.5",
|
|
20900
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowLabel, {
|
|
20901
|
+
item,
|
|
20902
|
+
warn
|
|
20903
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20904
|
+
className: "flex flex-wrap items-center gap-2",
|
|
20905
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20906
|
+
className: "flex gap-0.5",
|
|
20907
|
+
"data-testid": "consumable-steps",
|
|
20908
|
+
"data-filled": filled,
|
|
20909
|
+
children: STEP_INDEXES.map((i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `h-1.5 w-3 rounded-full ${i < filled ? consumableLevelBarClass(level) : "bg-surface-hover"}` }, i))
|
|
20910
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
|
|
20911
|
+
level,
|
|
20912
|
+
size: "sm",
|
|
20913
|
+
className: "whitespace-nowrap",
|
|
20914
|
+
label: productLabel(level, t)
|
|
20915
|
+
})]
|
|
20916
|
+
})]
|
|
20917
|
+
})]
|
|
20714
20918
|
});
|
|
20715
20919
|
}
|
|
20920
|
+
/** The row for an item, chosen by its form. */
|
|
20921
|
+
function ConsumableRow(props) {
|
|
20922
|
+
switch ((0, _camstack_types.consumableFormOf)(props.item)) {
|
|
20923
|
+
case "part": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PartRow, { ...props });
|
|
20924
|
+
case "tank":
|
|
20925
|
+
case "bin": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeRow, { item: props.item });
|
|
20926
|
+
case "product": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProductRow, { item: props.item });
|
|
20927
|
+
}
|
|
20928
|
+
}
|
|
20929
|
+
//#endregion
|
|
20930
|
+
//#region src/composites/cap-settings/consumables-summary.ts
|
|
20931
|
+
var SEVERITY = {
|
|
20932
|
+
empty: 3,
|
|
20933
|
+
full: 2,
|
|
20934
|
+
low: 1
|
|
20935
|
+
};
|
|
20936
|
+
function isAttention(level) {
|
|
20937
|
+
return level === "low" || level === "empty" || level === "full";
|
|
20938
|
+
}
|
|
20939
|
+
function summarizeConsumables(items) {
|
|
20940
|
+
if (items.length === 0) return {
|
|
20941
|
+
kind: "none",
|
|
20942
|
+
needsAttention: false
|
|
20943
|
+
};
|
|
20944
|
+
const attention = [];
|
|
20945
|
+
let ok = 0;
|
|
20946
|
+
let unknown = 0;
|
|
20947
|
+
for (const item of items) {
|
|
20948
|
+
const level = consumableItemLevel(item);
|
|
20949
|
+
if (isAttention(level)) attention.push({
|
|
20950
|
+
label: item.label,
|
|
20951
|
+
level,
|
|
20952
|
+
replace: item.status === "replace"
|
|
20953
|
+
});
|
|
20954
|
+
else if (level === "ok") ok += 1;
|
|
20955
|
+
else unknown += 1;
|
|
20956
|
+
}
|
|
20957
|
+
const worst = [...attention].sort((a, b) => SEVERITY[b.level] - SEVERITY[a.level])[0];
|
|
20958
|
+
if (worst !== void 0) return {
|
|
20959
|
+
kind: "attention",
|
|
20960
|
+
needsAttention: true,
|
|
20961
|
+
count: attention.length,
|
|
20962
|
+
worst
|
|
20963
|
+
};
|
|
20964
|
+
return unknown === 0 ? {
|
|
20965
|
+
kind: "ok",
|
|
20966
|
+
needsAttention: false,
|
|
20967
|
+
count: ok
|
|
20968
|
+
} : {
|
|
20969
|
+
kind: "partial",
|
|
20970
|
+
needsAttention: false,
|
|
20971
|
+
ok,
|
|
20972
|
+
unknown
|
|
20973
|
+
};
|
|
20974
|
+
}
|
|
20975
|
+
//#endregion
|
|
20976
|
+
//#region src/composites/cap-settings/ConsumablesPanel.tsx
|
|
20977
|
+
/**
|
|
20978
|
+
* ConsumablesPanel — the ONE consumables surface of every device (D915): wear
|
|
20979
|
+
* parts, tanks (a vacuum's clean and dirty water), products (detergent, a
|
|
20980
|
+
* dishwasher's salt and rinse aid) and bins (a dust bin), for whichever device
|
|
20981
|
+
* is bound to the `consumables` capability. A cap-settings component
|
|
20982
|
+
* (ui-library), shown directly under the device page's hero card through the
|
|
20983
|
+
* cap-UI contribution mechanism (the cap declares
|
|
20984
|
+
* `ui: { kind:'widget', widgetId:'host/consumables-panel', placement:'hero', ... }`)
|
|
20985
|
+
* — never drawn by a device type's hero card itself.
|
|
20986
|
+
*
|
|
20987
|
+
* Reads the cap's pushed runtime-state slice via `useDeviceCapability` and
|
|
20988
|
+
* renders one row per item, drawn by its form (`consumable-rows.tsx`). A
|
|
20989
|
+
* slice that has not landed is a loading line, never "none reported" (D315).
|
|
20990
|
+
*
|
|
20991
|
+
* The section COLLAPSES smartly: it starts open while any item needs
|
|
20992
|
+
* attention (low, empty, a full bin or waste tank, a part to replace) and
|
|
20993
|
+
* closed while every item is ok or unknown; closed, its header carries a
|
|
20994
|
+
* one-line summary (`consumables-summary.ts`). The operator's own toggle wins
|
|
20995
|
+
* and is remembered per device and per viewer.
|
|
20996
|
+
*/
|
|
20997
|
+
/** Where one viewer's open / closed choice for one device's section is kept. */
|
|
20998
|
+
function consumablesOpenStorageKey(deviceId) {
|
|
20999
|
+
return `camstack.device.${String(deviceId)}.consumables.open`;
|
|
21000
|
+
}
|
|
21001
|
+
/** The one line a collapsed section says. */
|
|
21002
|
+
function SummaryLine({ summary, t }) {
|
|
21003
|
+
switch (summary.kind) {
|
|
21004
|
+
case "none": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
21005
|
+
variant: "neutral",
|
|
21006
|
+
size: "sm",
|
|
21007
|
+
children: t("uiLibrary.consumablesPanel.noneReported", "None reported")
|
|
21008
|
+
});
|
|
21009
|
+
case "ok": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
21010
|
+
variant: "success",
|
|
21011
|
+
size: "sm",
|
|
21012
|
+
children: t("uiLibrary.consumablesPanel.allOk", "All OK")
|
|
21013
|
+
});
|
|
21014
|
+
case "partial": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
21015
|
+
variant: "neutral",
|
|
21016
|
+
size: "sm",
|
|
21017
|
+
children: t("uiLibrary.consumablesPanel.okAndUnknown", "{{ok}} OK · {{unknown}} unknown", {
|
|
21018
|
+
ok: summary.ok,
|
|
21019
|
+
unknown: summary.unknown
|
|
21020
|
+
})
|
|
21021
|
+
});
|
|
21022
|
+
case "attention": return summary.count === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
|
|
21023
|
+
level: summary.worst.level,
|
|
21024
|
+
name: summary.worst.label,
|
|
21025
|
+
size: "sm",
|
|
21026
|
+
label: summary.worst.replace ? t("uiLibrary.consumablesPanel.replace", "Replace") : void 0
|
|
21027
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
21028
|
+
variant: consumableLevelVariant(summary.worst.level),
|
|
21029
|
+
size: "sm",
|
|
21030
|
+
children: t("uiLibrary.consumablesPanel.needAttention", {
|
|
21031
|
+
one: "{{count}} needs attention",
|
|
21032
|
+
other: "{{count}} need attention"
|
|
21033
|
+
}, { count: summary.count })
|
|
21034
|
+
});
|
|
21035
|
+
}
|
|
21036
|
+
}
|
|
20716
21037
|
function ConsumablesPanel({ deviceId }) {
|
|
20717
21038
|
const t = useUiText();
|
|
20718
21039
|
const system = useSystem();
|
|
@@ -20724,6 +21045,7 @@ function ConsumablesPanel({ deviceId }) {
|
|
|
20724
21045
|
slice: (d) => d.state.consumables
|
|
20725
21046
|
});
|
|
20726
21047
|
const [busyKey, setBusyKey] = (0, react$1.useState)(null);
|
|
21048
|
+
const [choice, setChoice] = usePersistedDisclosure(ready ? consumablesOpenStorageKey(deviceId) : null);
|
|
20727
21049
|
const onReset = (0, react$1.useCallback)(async (key) => {
|
|
20728
21050
|
if (!cap) return;
|
|
20729
21051
|
setBusyKey(key);
|
|
@@ -20735,17 +21057,30 @@ function ConsumablesPanel({ deviceId }) {
|
|
|
20735
21057
|
setBusyKey(null);
|
|
20736
21058
|
}
|
|
20737
21059
|
}, [cap]);
|
|
20738
|
-
const
|
|
21060
|
+
const summary = slice === void 0 ? null : summarizeConsumables(slice.items);
|
|
21061
|
+
const open = choice ?? summary?.needsAttention ?? false;
|
|
21062
|
+
const loading = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
21063
|
+
className: open ? "h-10 w-full" : "h-4 w-16",
|
|
21064
|
+
"data-testid": "consumables-loading",
|
|
21065
|
+
"aria-label": t("uiLibrary.consumablesPanel.loading", "Reading consumables…")
|
|
21066
|
+
});
|
|
20739
21067
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
|
|
20740
21068
|
title: t("uiLibrary.consumablesPanel.title", "Consumables"),
|
|
20741
21069
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Gauge, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
|
|
20742
21070
|
bodyClassName: "p-3 overflow-y-auto",
|
|
20743
|
-
|
|
21071
|
+
collapsible: true,
|
|
21072
|
+
open,
|
|
21073
|
+
onOpenChange: setChoice,
|
|
21074
|
+
headerRight: open ? void 0 : summary === null ? loading : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SummaryLine, {
|
|
21075
|
+
summary,
|
|
21076
|
+
t
|
|
21077
|
+
}),
|
|
21078
|
+
children: slice === void 0 ? loading : slice.items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
20744
21079
|
className: "text-[11px] text-foreground-subtle italic leading-relaxed",
|
|
20745
21080
|
children: t("uiLibrary.consumablesPanel.empty", "No consumables reported.")
|
|
20746
21081
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20747
|
-
className: "
|
|
20748
|
-
children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
|
|
21082
|
+
className: "grid grid-cols-1 gap-2 sm:grid-cols-2",
|
|
21083
|
+
children: slice.items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
|
|
20749
21084
|
item,
|
|
20750
21085
|
busy: busyKey === item.key,
|
|
20751
21086
|
canReset: available,
|
|
@@ -20785,49 +21120,6 @@ function useCapabilityPresence(capabilityName) {
|
|
|
20785
21120
|
return classifyCapability(query.data);
|
|
20786
21121
|
}
|
|
20787
21122
|
//#endregion
|
|
20788
|
-
//#region src/primitives/button.tsx
|
|
20789
|
-
var buttonVariants = cva("inline-flex items-center justify-center rounded-md font-medium transition-colors cursor-pointer disabled:opacity-50 disabled:pointer-events-none", {
|
|
20790
|
-
variants: {
|
|
20791
|
-
variant: {
|
|
20792
|
-
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
20793
|
-
secondary: "bg-surface text-foreground hover:bg-surface-hover",
|
|
20794
|
-
ghost: "hover:bg-surface-hover text-foreground-muted",
|
|
20795
|
-
danger: "bg-danger text-white hover:bg-danger/90",
|
|
20796
|
-
outline: "border border-border bg-transparent text-foreground hover:bg-surface-hover"
|
|
20797
|
-
},
|
|
20798
|
-
size: {
|
|
20799
|
-
sm: "h-7 px-2.5 text-xs gap-1.5",
|
|
20800
|
-
md: "h-8 px-3 text-sm gap-2",
|
|
20801
|
-
lg: "h-9 px-4 text-sm gap-2"
|
|
20802
|
-
}
|
|
20803
|
-
},
|
|
20804
|
-
defaultVariants: {
|
|
20805
|
-
variant: "primary",
|
|
20806
|
-
size: "sm"
|
|
20807
|
-
}
|
|
20808
|
-
});
|
|
20809
|
-
var Button = (0, react$1.forwardRef)(({ className, variant, size, ...props }, ref) => {
|
|
20810
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
20811
|
-
ref,
|
|
20812
|
-
className: cn(buttonVariants({
|
|
20813
|
-
variant,
|
|
20814
|
-
size
|
|
20815
|
-
}), className),
|
|
20816
|
-
...props
|
|
20817
|
-
});
|
|
20818
|
-
});
|
|
20819
|
-
Button.displayName = "Button";
|
|
20820
|
-
//#endregion
|
|
20821
|
-
//#region src/primitives/skeleton.tsx
|
|
20822
|
-
var Skeleton = (0, react$1.forwardRef)(({ className, ...props }, ref) => {
|
|
20823
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20824
|
-
ref,
|
|
20825
|
-
className: cn("animate-pulse bg-surface-hover rounded-md h-4 w-full", className),
|
|
20826
|
-
...props
|
|
20827
|
-
});
|
|
20828
|
-
});
|
|
20829
|
-
Skeleton.displayName = "Skeleton";
|
|
20830
|
-
//#endregion
|
|
20831
21123
|
//#region src/composites/cap-settings/recording-timeline-model.ts
|
|
20832
21124
|
/** Tie-break precedence for a bucket's dominant kind (first wins on equal count). */
|
|
20833
21125
|
var KIND_PRECEDENCE = [
|
|
@@ -47102,147 +47394,6 @@ function BatteryBadge$2({ level }) {
|
|
|
47102
47394
|
});
|
|
47103
47395
|
}
|
|
47104
47396
|
/**
|
|
47105
|
-
* Pure helper — partitions a tank kind into "supply" (low is bad) vs
|
|
47106
|
-
* "waste" (full is bad) via an exhaustive switch. A future TankKind that
|
|
47107
|
-
* forgets to classify itself fails the build (the `never` default arm),
|
|
47108
|
-
* rather than silently defaulting to the waste branch.
|
|
47109
|
-
*/
|
|
47110
|
-
function tankIsSupply(kind) {
|
|
47111
|
-
switch (kind) {
|
|
47112
|
-
case "cleanWater": return true;
|
|
47113
|
-
case "detergent": return true;
|
|
47114
|
-
case "dirtyWater": return false;
|
|
47115
|
-
case "dustBin": return false;
|
|
47116
|
-
default: return kind;
|
|
47117
|
-
}
|
|
47118
|
-
}
|
|
47119
|
-
/**
|
|
47120
|
-
* Pure helper — classifies a tank as `ok` or `warning`.
|
|
47121
|
-
*
|
|
47122
|
-
* Clean-water + detergent are "supply" tanks: low is bad. Dirty-water +
|
|
47123
|
-
* dust-bin are "waste" tanks: full is bad. Prefers the numeric `level`
|
|
47124
|
-
* (thresholds 15 / 85), falling back to the discrete `status` flag. Both
|
|
47125
|
-
* unknown → `ok`. Exhaustive over TankKind.
|
|
47126
|
-
*/
|
|
47127
|
-
function tankAlert(kind, tank) {
|
|
47128
|
-
const isSupply = tankIsSupply(kind);
|
|
47129
|
-
if (tank.level !== null) {
|
|
47130
|
-
if (isSupply) return tank.level <= 15 ? "warning" : "ok";
|
|
47131
|
-
return tank.level >= 85 ? "warning" : "ok";
|
|
47132
|
-
}
|
|
47133
|
-
if (tank.status !== null) {
|
|
47134
|
-
if (isSupply) return tank.status === "low" ? "warning" : "ok";
|
|
47135
|
-
return tank.status === "full" ? "warning" : "ok";
|
|
47136
|
-
}
|
|
47137
|
-
return "ok";
|
|
47138
|
-
}
|
|
47139
|
-
function tankMeta(t, kind) {
|
|
47140
|
-
switch (kind) {
|
|
47141
|
-
case "cleanWater": return {
|
|
47142
|
-
label: t("uiLibrary.vacuum.cleanWater", "Clean water"),
|
|
47143
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Droplet, { className: "h-3.5 w-3.5" })
|
|
47144
|
-
};
|
|
47145
|
-
case "dirtyWater": return {
|
|
47146
|
-
label: t("uiLibrary.vacuum.dirtyWater", "Dirty water"),
|
|
47147
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Droplets, { className: "h-3.5 w-3.5" })
|
|
47148
|
-
};
|
|
47149
|
-
case "detergent": return {
|
|
47150
|
-
label: t("uiLibrary.vacuum.detergent", "Detergent"),
|
|
47151
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FlaskConical, { className: "h-3.5 w-3.5" })
|
|
47152
|
-
};
|
|
47153
|
-
case "dustBin": return {
|
|
47154
|
-
label: t("uiLibrary.vacuum.dustBin", "Dust bin"),
|
|
47155
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Trash2, { className: "h-3.5 w-3.5" })
|
|
47156
|
-
};
|
|
47157
|
-
default: return kind;
|
|
47158
|
-
}
|
|
47159
|
-
}
|
|
47160
|
-
/**
|
|
47161
|
-
* TankIndicator — one tank row: label + glyph, then a fill bar (numeric
|
|
47162
|
-
* level) or a status pill (discrete), tinted by `tankAlert`. Renders
|
|
47163
|
-
* nothing only when the tank is absent (null). A PRESENT tank whose
|
|
47164
|
-
* reading is unknown (both fields null) still renders a neutral row with
|
|
47165
|
-
* an em-dash placeholder, so the user always sees which tanks the robot
|
|
47166
|
-
* has (Dreame reports presence but no fill telemetry).
|
|
47167
|
-
*/
|
|
47168
|
-
function TankIndicator({ kind, tank }) {
|
|
47169
|
-
const t = useUiText();
|
|
47170
|
-
if (tank === null) return null;
|
|
47171
|
-
const meta = tankMeta(t, kind);
|
|
47172
|
-
const warning = tankAlert(kind, tank) === "warning";
|
|
47173
|
-
if (tank.level !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47174
|
-
className: "flex items-center gap-2 text-xs",
|
|
47175
|
-
children: [
|
|
47176
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
47177
|
-
className: cn("inline-flex items-center gap-1 w-24 shrink-0", warning ? "text-warning" : "text-foreground-subtle"),
|
|
47178
|
-
children: [meta.icon, meta.label]
|
|
47179
|
-
}),
|
|
47180
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
47181
|
-
className: "relative h-1.5 flex-1 overflow-hidden rounded-full bg-surface-hover",
|
|
47182
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
47183
|
-
className: cn("absolute inset-y-0 left-0 rounded-full transition-all", warning ? "bg-warning" : "bg-primary"),
|
|
47184
|
-
style: { width: `${tank.level}%` }
|
|
47185
|
-
})
|
|
47186
|
-
}),
|
|
47187
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
47188
|
-
className: cn("tabular-nums w-9 text-right", warning ? "text-warning" : "text-foreground-subtle"),
|
|
47189
|
-
children: [tank.level, "%"]
|
|
47190
|
-
})
|
|
47191
|
-
]
|
|
47192
|
-
});
|
|
47193
|
-
if (tank.status !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47194
|
-
className: "flex items-center gap-2 text-xs",
|
|
47195
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
47196
|
-
className: cn("inline-flex items-center gap-1 w-24 shrink-0", warning ? "text-warning" : "text-foreground-subtle"),
|
|
47197
|
-
children: [meta.icon, meta.label]
|
|
47198
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
|
|
47199
|
-
level: warning ? "low" : "ok",
|
|
47200
|
-
label: tankStatusLabel(t, tank.status),
|
|
47201
|
-
size: "sm"
|
|
47202
|
-
})]
|
|
47203
|
-
});
|
|
47204
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47205
|
-
className: "flex items-center gap-2 text-xs",
|
|
47206
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
47207
|
-
className: "inline-flex items-center gap-1 w-24 shrink-0 text-foreground-subtle",
|
|
47208
|
-
children: [meta.icon, meta.label]
|
|
47209
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
|
|
47210
|
-
level: null,
|
|
47211
|
-
size: "sm"
|
|
47212
|
-
})]
|
|
47213
|
-
});
|
|
47214
|
-
}
|
|
47215
|
-
/** A tank's discrete reading in words; the tone is `tankAlert`'s (a full waste tank warns, a full supply tank does not). */
|
|
47216
|
-
function tankStatusLabel(t, status) {
|
|
47217
|
-
switch (status) {
|
|
47218
|
-
case "ok": return consumableLevelLabel("ok", t);
|
|
47219
|
-
case "low": return consumableLevelLabel("low", t);
|
|
47220
|
-
case "full": return t("uiLibrary.vacuum.tankFull", "Full");
|
|
47221
|
-
}
|
|
47222
|
-
}
|
|
47223
|
-
/**
|
|
47224
|
-
* TanksRow — compact 2-column grid of the present tanks for the vacuum
|
|
47225
|
-
* hero. Absent tanks (null) are skipped; every PRESENT tank is shown, even
|
|
47226
|
-
* when its reading is unknown (`{level:null,status:null}`) — it renders a
|
|
47227
|
-
* neutral em-dash row. The row renders iff the robot has at least one tank.
|
|
47228
|
-
*/
|
|
47229
|
-
function TanksRow({ cleanWater, dirtyWater, detergent, dustBin }) {
|
|
47230
|
-
const tanks = [
|
|
47231
|
-
["cleanWater", cleanWater],
|
|
47232
|
-
["dirtyWater", dirtyWater],
|
|
47233
|
-
["detergent", detergent],
|
|
47234
|
-
["dustBin", dustBin]
|
|
47235
|
-
];
|
|
47236
|
-
if (!tanks.some(([, t]) => t !== null)) return null;
|
|
47237
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
47238
|
-
className: "grid grid-cols-1 gap-x-6 gap-y-2 rounded-md border border-border bg-surface-hover/40 p-3 sm:grid-cols-2",
|
|
47239
|
-
children: tanks.map(([kind, tank]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TankIndicator, {
|
|
47240
|
-
kind,
|
|
47241
|
-
tank
|
|
47242
|
-
}, kind))
|
|
47243
|
-
});
|
|
47244
|
-
}
|
|
47245
|
-
/**
|
|
47246
47397
|
* VacuumInlineControl — compact, state-aware vacuum controls for a
|
|
47247
47398
|
* device-list row. Start / Pause / Return-to-base + battery badge.
|
|
47248
47399
|
*/
|
|
@@ -47324,114 +47475,105 @@ function VacuumHeroCard({ trpc, deviceId }) {
|
|
|
47324
47475
|
};
|
|
47325
47476
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47326
47477
|
className: "flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-4",
|
|
47327
|
-
children: [
|
|
47328
|
-
|
|
47329
|
-
|
|
47330
|
-
|
|
47331
|
-
|
|
47332
|
-
|
|
47333
|
-
|
|
47334
|
-
|
|
47335
|
-
|
|
47336
|
-
|
|
47337
|
-
|
|
47338
|
-
|
|
47339
|
-
|
|
47340
|
-
|
|
47341
|
-
|
|
47342
|
-
|
|
47343
|
-
|
|
47344
|
-
|
|
47345
|
-
|
|
47346
|
-
|
|
47347
|
-
|
|
47348
|
-
|
|
47349
|
-
|
|
47350
|
-
|
|
47351
|
-
|
|
47352
|
-
|
|
47353
|
-
|
|
47354
|
-
|
|
47355
|
-
|
|
47356
|
-
|
|
47357
|
-
|
|
47358
|
-
|
|
47359
|
-
|
|
47360
|
-
|
|
47361
|
-
|
|
47362
|
-
|
|
47363
|
-
|
|
47364
|
-
|
|
47365
|
-
|
|
47366
|
-
|
|
47367
|
-
|
|
47368
|
-
|
|
47369
|
-
|
|
47370
|
-
|
|
47371
|
-
|
|
47372
|
-
|
|
47373
|
-
|
|
47374
|
-
|
|
47375
|
-
|
|
47376
|
-
|
|
47377
|
-
|
|
47378
|
-
|
|
47379
|
-
|
|
47380
|
-
|
|
47381
|
-
|
|
47382
|
-
|
|
47383
|
-
|
|
47384
|
-
|
|
47385
|
-
|
|
47386
|
-
|
|
47387
|
-
|
|
47388
|
-
|
|
47389
|
-
|
|
47390
|
-
|
|
47391
|
-
|
|
47392
|
-
|
|
47393
|
-
|
|
47394
|
-
|
|
47395
|
-
|
|
47396
|
-
|
|
47397
|
-
|
|
47398
|
-
|
|
47399
|
-
|
|
47400
|
-
|
|
47401
|
-
|
|
47402
|
-
|
|
47403
|
-
|
|
47404
|
-
className: "
|
|
47405
|
-
children:
|
|
47406
|
-
|
|
47407
|
-
|
|
47408
|
-
|
|
47409
|
-
|
|
47410
|
-
|
|
47411
|
-
|
|
47412
|
-
|
|
47413
|
-
|
|
47414
|
-
|
|
47415
|
-
|
|
47416
|
-
|
|
47417
|
-
|
|
47418
|
-
|
|
47419
|
-
children:
|
|
47420
|
-
|
|
47421
|
-
|
|
47422
|
-
|
|
47423
|
-
|
|
47424
|
-
|
|
47425
|
-
|
|
47426
|
-
})]
|
|
47427
|
-
}),
|
|
47428
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TanksRow, {
|
|
47429
|
-
cleanWater: slice?.cleanWater ?? null,
|
|
47430
|
-
dirtyWater: slice?.dirtyWater ?? null,
|
|
47431
|
-
detergent: slice?.detergent ?? null,
|
|
47432
|
-
dustBin: slice?.dustBin ?? null
|
|
47433
|
-
})
|
|
47434
|
-
]
|
|
47478
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47479
|
+
className: "flex items-center gap-6",
|
|
47480
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumGraphic, {
|
|
47481
|
+
state,
|
|
47482
|
+
batteryLevel: slice?.batteryLevel ?? null
|
|
47483
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47484
|
+
className: "flex flex-col gap-3 flex-1",
|
|
47485
|
+
children: [
|
|
47486
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47487
|
+
className: "flex items-center justify-between",
|
|
47488
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
47489
|
+
className: cn("text-3xl font-semibold", meta.tone),
|
|
47490
|
+
children: meta.label
|
|
47491
|
+
}), slice?.batteryLevel != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge$2, { level: slice.batteryLevel })]
|
|
47492
|
+
}),
|
|
47493
|
+
slice?.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47494
|
+
className: "flex items-center gap-2",
|
|
47495
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
47496
|
+
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
47497
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
47498
|
+
className: "h-full bg-primary transition-[width] duration-500",
|
|
47499
|
+
style: { width: `${Math.max(0, Math.min(100, slice.progressPercent))}%` }
|
|
47500
|
+
})
|
|
47501
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
47502
|
+
className: "tabular-nums text-xs text-foreground-subtle",
|
|
47503
|
+
children: [Math.round(slice.progressPercent), "%"]
|
|
47504
|
+
})]
|
|
47505
|
+
}),
|
|
47506
|
+
slice?.errorCode != null && slice.errorCode !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47507
|
+
className: "inline-flex items-center gap-1 text-xs text-destructive",
|
|
47508
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: slice.errorLabel ?? t("uiLibrary.vacuum.errorCode", "Error {{code}}", { code: slice.errorCode }) })]
|
|
47509
|
+
}),
|
|
47510
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47511
|
+
className: "flex items-center gap-2",
|
|
47512
|
+
children: [
|
|
47513
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
47514
|
+
label: t("uiLibrary.vacuum.start", "Start"),
|
|
47515
|
+
busy,
|
|
47516
|
+
active: state === "cleaning",
|
|
47517
|
+
onClick: () => press(cap.start),
|
|
47518
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-5 w-5" })
|
|
47519
|
+
}),
|
|
47520
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
47521
|
+
label: t("uiLibrary.vacuum.pause", "Pause"),
|
|
47522
|
+
busy,
|
|
47523
|
+
active: state === "paused",
|
|
47524
|
+
onClick: () => press(cap.pause),
|
|
47525
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-5 w-5" })
|
|
47526
|
+
}),
|
|
47527
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
47528
|
+
label: t("uiLibrary.vacuum.stop", "Stop"),
|
|
47529
|
+
busy,
|
|
47530
|
+
active: false,
|
|
47531
|
+
onClick: () => press(cap.stop),
|
|
47532
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Square, { className: "h-4 w-4" })
|
|
47533
|
+
}),
|
|
47534
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
47535
|
+
label: t("uiLibrary.vacuum.returnToBase", "Return to base"),
|
|
47536
|
+
busy,
|
|
47537
|
+
active: state === "returning" || state === "docked",
|
|
47538
|
+
onClick: () => press(cap.returnToBase),
|
|
47539
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(House, { className: "h-5 w-5" })
|
|
47540
|
+
}),
|
|
47541
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
47542
|
+
label: t("uiLibrary.vacuum.locate", "Locate"),
|
|
47543
|
+
busy,
|
|
47544
|
+
active: false,
|
|
47545
|
+
onClick: () => press(cap.locate),
|
|
47546
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Megaphone, { className: "h-5 w-5" })
|
|
47547
|
+
})
|
|
47548
|
+
]
|
|
47549
|
+
})
|
|
47550
|
+
]
|
|
47551
|
+
})]
|
|
47552
|
+
}), fanSpeeds.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
47553
|
+
className: "flex items-center gap-2 text-xs text-foreground-subtle",
|
|
47554
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
47555
|
+
className: "w-16 shrink-0",
|
|
47556
|
+
children: t("uiLibrary.vacuum.fanSpeed", "Fan speed")
|
|
47557
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
47558
|
+
"aria-label": t("uiLibrary.vacuum.fanSpeed", "Fan speed"),
|
|
47559
|
+
value: slice?.fanSpeed ?? "",
|
|
47560
|
+
disabled: busy,
|
|
47561
|
+
onChange: (e) => {
|
|
47562
|
+
onFanSpeed(e.target.value);
|
|
47563
|
+
},
|
|
47564
|
+
onClick: (e) => {
|
|
47565
|
+
e.stopPropagation();
|
|
47566
|
+
},
|
|
47567
|
+
className: "flex-1 rounded border border-border bg-surface px-2 py-1 text-foreground disabled:opacity-50",
|
|
47568
|
+
children: [slice?.fanSpeed == null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
47569
|
+
value: "",
|
|
47570
|
+
children: "—"
|
|
47571
|
+
}), fanSpeeds.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
47572
|
+
value: s,
|
|
47573
|
+
children: s
|
|
47574
|
+
}, s))]
|
|
47575
|
+
})]
|
|
47576
|
+
})]
|
|
47435
47577
|
});
|
|
47436
47578
|
}
|
|
47437
47579
|
//#endregion
|
|
@@ -50359,6 +50501,9 @@ function PetFeederHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
50359
50501
|
* Reads the `dishwasher` cap status slice and renders inline + hero surfaces
|
|
50360
50502
|
* mirroring vacuum-control / pet-feeder patterns.
|
|
50361
50503
|
*
|
|
50504
|
+
* Salt and rinse aid are not here: they are items of the device's `consumables`
|
|
50505
|
+
* cap, shown by the page's Consumables section like every device's (D915).
|
|
50506
|
+
*
|
|
50362
50507
|
* UNKNOWN IS NOT OFF. The slice carries `null` for everything the appliance has not
|
|
50363
50508
|
* reported, and this view renders that as "—" / a disabled control, never as an unchecked
|
|
50364
50509
|
* box or a zero. It also does not echo a command before the appliance confirms it: the
|
|
@@ -50492,60 +50637,44 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
|
|
|
50492
50637
|
if (busy) return;
|
|
50493
50638
|
fn();
|
|
50494
50639
|
};
|
|
50495
|
-
if (layout === "compact") {
|
|
50496
|
-
|
|
50497
|
-
|
|
50498
|
-
|
|
50499
|
-
|
|
50500
|
-
|
|
50501
|
-
|
|
50502
|
-
|
|
50503
|
-
|
|
50504
|
-
|
|
50505
|
-
|
|
50506
|
-
|
|
50507
|
-
|
|
50508
|
-
|
|
50509
|
-
|
|
50510
|
-
|
|
50511
|
-
display.program
|
|
50512
|
-
|
|
50513
|
-
|
|
50514
|
-
|
|
50515
|
-
}),
|
|
50516
|
-
|
|
50517
|
-
|
|
50518
|
-
|
|
50519
|
-
|
|
50520
|
-
|
|
50521
|
-
|
|
50522
|
-
|
|
50523
|
-
|
|
50524
|
-
|
|
50525
|
-
|
|
50526
|
-
|
|
50527
|
-
|
|
50528
|
-
|
|
50529
|
-
|
|
50530
|
-
|
|
50531
|
-
|
|
50532
|
-
|
|
50533
|
-
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
50534
|
-
busy: busy || !startable,
|
|
50535
|
-
active: running,
|
|
50536
|
-
onClick: () => press(() => cap.start({})),
|
|
50537
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-3 w-3" })
|
|
50538
|
-
}),
|
|
50539
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
50540
|
-
label: paused ? t("uiLibrary.dishwasher.resume", "Resume") : t("uiLibrary.dishwasher.pause", "Pause"),
|
|
50541
|
-
busy: busy || !(running || paused),
|
|
50542
|
-
active: paused,
|
|
50543
|
-
onClick: () => press(() => paused ? cap.resume({}) : cap.pause({})),
|
|
50544
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-3 w-3" })
|
|
50545
|
-
})
|
|
50546
|
-
]
|
|
50547
|
-
});
|
|
50548
|
-
}
|
|
50640
|
+
if (layout === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50641
|
+
className: "inline-flex items-center gap-1.5 max-w-full",
|
|
50642
|
+
children: [
|
|
50643
|
+
display.doorOpen === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50644
|
+
className: "inline-flex text-warning shrink-0",
|
|
50645
|
+
title: t("uiLibrary.dishwasher.doorOpen", "Door open"),
|
|
50646
|
+
"aria-label": t("uiLibrary.dishwasher.doorOpen", "Door open"),
|
|
50647
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" })
|
|
50648
|
+
}),
|
|
50649
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50650
|
+
className: cn("text-xs font-medium tabular-nums shrink-0", meta.tone),
|
|
50651
|
+
children: meta.label
|
|
50652
|
+
}),
|
|
50653
|
+
display.program != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50654
|
+
className: "text-xs text-foreground-subtle truncate max-w-[7rem]",
|
|
50655
|
+
title: display.program,
|
|
50656
|
+
children: featureLabel(display.program)
|
|
50657
|
+
}),
|
|
50658
|
+
display.remainingSeconds != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
50659
|
+
className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums shrink-0",
|
|
50660
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }), formatDuration(display.remainingSeconds, t)]
|
|
50661
|
+
}),
|
|
50662
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
50663
|
+
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
50664
|
+
busy: busy || !startable,
|
|
50665
|
+
active: running,
|
|
50666
|
+
onClick: () => press(() => cap.start({})),
|
|
50667
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-3 w-3" })
|
|
50668
|
+
}),
|
|
50669
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
50670
|
+
label: paused ? t("uiLibrary.dishwasher.resume", "Resume") : t("uiLibrary.dishwasher.pause", "Pause"),
|
|
50671
|
+
busy: busy || !(running || paused),
|
|
50672
|
+
active: paused,
|
|
50673
|
+
onClick: () => press(() => paused ? cap.resume({}) : cap.pause({})),
|
|
50674
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-3 w-3" })
|
|
50675
|
+
})
|
|
50676
|
+
]
|
|
50677
|
+
});
|
|
50549
50678
|
const programs = display.availablePrograms;
|
|
50550
50679
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50551
50680
|
className: "flex flex-col gap-3 rounded-lg border border-border bg-surface p-4",
|
|
@@ -50596,25 +50725,14 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
|
|
|
50596
50725
|
}),
|
|
50597
50726
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50598
50727
|
className: "flex flex-wrap items-center gap-x-3 gap-y-1.5",
|
|
50599
|
-
children: [
|
|
50600
|
-
|
|
50601
|
-
|
|
50602
|
-
|
|
50603
|
-
|
|
50604
|
-
|
|
50605
|
-
|
|
50606
|
-
|
|
50607
|
-
}),
|
|
50608
|
-
display.doorOpen === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
50609
|
-
className: "inline-flex items-center gap-1 text-xs text-warning",
|
|
50610
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.doorOpen", "Door open")]
|
|
50611
|
-
}),
|
|
50612
|
-
display.activeEvents.map((event) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50613
|
-
className: "text-xs text-warning",
|
|
50614
|
-
title: event,
|
|
50615
|
-
children: featureLabel(event)
|
|
50616
|
-
}, event))
|
|
50617
|
-
]
|
|
50728
|
+
children: [display.doorOpen === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
50729
|
+
className: "inline-flex items-center gap-1 text-xs text-warning",
|
|
50730
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.doorOpen", "Door open")]
|
|
50731
|
+
}), display.activeEvents.map((event) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50732
|
+
className: "text-xs text-warning",
|
|
50733
|
+
title: event,
|
|
50734
|
+
children: featureLabel(event)
|
|
50735
|
+
}, event))]
|
|
50618
50736
|
}),
|
|
50619
50737
|
display.operationState === "Error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50620
50738
|
className: "inline-flex items-center gap-1 text-xs text-danger",
|
|
@@ -68253,7 +68371,6 @@ exports.FilterBar = FilterBar;
|
|
|
68253
68371
|
exports.Flame = Flame;
|
|
68254
68372
|
exports.Flashlight = Flashlight;
|
|
68255
68373
|
exports.FlashlightOff = FlashlightOff;
|
|
68256
|
-
exports.FlaskConical = FlaskConical;
|
|
68257
68374
|
exports.FloatingEventStream = FloatingEventStream;
|
|
68258
68375
|
exports.FloatingLogStream = FloatingLogStream;
|
|
68259
68376
|
exports.FloatingPanel = FloatingPanel;
|
|
@@ -68945,7 +69062,6 @@ exports.summarizeHubCandidates = summarizeHubCandidates;
|
|
|
68945
69062
|
exports.swatchFillClass = swatchFillClass;
|
|
68946
69063
|
exports.swatchLineClass = swatchLineClass;
|
|
68947
69064
|
exports.tabForSelector = tabForSelector;
|
|
68948
|
-
exports.tankAlert = tankAlert;
|
|
68949
69065
|
exports.themeToCss = require_theme_to_root_css.themeToCss;
|
|
68950
69066
|
exports.themeToRootCss = require_theme_to_root_css.themeToRootCss;
|
|
68951
69067
|
exports.timeInState = timeInState;
|
|
@@ -69211,9 +69327,11 @@ exports.useDeviceExtensionListSlots = useDeviceExtensionListSlots;
|
|
|
69211
69327
|
exports.useDeviceExtensionListSources = useDeviceExtensionListSources;
|
|
69212
69328
|
exports.useDeviceExtensionSetSlot = useDeviceExtensionSetSlot;
|
|
69213
69329
|
exports.useDeviceHistoryDeclareSeries = useDeviceHistoryDeclareSeries;
|
|
69330
|
+
exports.useDeviceHistoryDeleteOrphanSeries = useDeviceHistoryDeleteOrphanSeries;
|
|
69214
69331
|
exports.useDeviceHistoryGetDeviceEnabled = useDeviceHistoryGetDeviceEnabled;
|
|
69215
69332
|
exports.useDeviceHistoryGetStatus = useDeviceHistoryGetStatus;
|
|
69216
69333
|
exports.useDeviceHistoryListNodeSeries = useDeviceHistoryListNodeSeries;
|
|
69334
|
+
exports.useDeviceHistoryListOrphanSeries = useDeviceHistoryListOrphanSeries;
|
|
69217
69335
|
exports.useDeviceHistoryListSeries = useDeviceHistoryListSeries;
|
|
69218
69336
|
exports.useDeviceHistoryQuery = useDeviceHistoryQuery;
|
|
69219
69337
|
exports.useDeviceHistoryQueryMany = useDeviceHistoryQueryMany;
|
|
@@ -69666,6 +69784,7 @@ exports.useOsdManagerSetSlotCameraEnabled = useOsdManagerSetSlotCameraEnabled;
|
|
|
69666
69784
|
exports.useOsdManagerUpsertTemplate = useOsdManagerUpsertTemplate;
|
|
69667
69785
|
exports.useOsdSetOverlay = useOsdSetOverlay;
|
|
69668
69786
|
exports.usePTZ = usePTZ;
|
|
69787
|
+
exports.usePersistedDisclosure = usePersistedDisclosure;
|
|
69669
69788
|
exports.usePersistedPageSize = usePersistedPageSize;
|
|
69670
69789
|
exports.usePetFeederCallPet = usePetFeederCallPet;
|
|
69671
69790
|
exports.usePetFeederCancelFeed = usePetFeederCancelFeed;
|
|
@@ -69808,6 +69927,7 @@ exports.usePipelineOrchestratorApplyDeviceSettingsPatch = usePipelineOrchestrato
|
|
|
69808
69927
|
exports.usePipelineOrchestratorAssignAudio = usePipelineOrchestratorAssignAudio;
|
|
69809
69928
|
exports.usePipelineOrchestratorAssignPipeline = usePipelineOrchestratorAssignPipeline;
|
|
69810
69929
|
exports.usePipelineOrchestratorDeleteNodeBenchmarkResult = usePipelineOrchestratorDeleteNodeBenchmarkResult;
|
|
69930
|
+
exports.usePipelineOrchestratorForgetInferenceDevice = usePipelineOrchestratorForgetInferenceDevice;
|
|
69811
69931
|
exports.usePipelineOrchestratorGetAgentLoad = usePipelineOrchestratorGetAgentLoad;
|
|
69812
69932
|
exports.usePipelineOrchestratorGetAgentSettings = usePipelineOrchestratorGetAgentSettings;
|
|
69813
69933
|
exports.usePipelineOrchestratorGetAudioAssignment = usePipelineOrchestratorGetAudioAssignment;
|