@camstack/ui-library 1.2.316 → 1.2.317
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/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 +4 -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 +587 -470
- package/dist/index.js +589 -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`. */
|
|
@@ -19149,20 +19133,30 @@ function useDeviceAutotrack(deviceId) {
|
|
|
19149
19133
|
}
|
|
19150
19134
|
//#endregion
|
|
19151
19135
|
//#region src/composites/widget-panel.tsx
|
|
19152
|
-
function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, bodyClassName = "p-3", children }) {
|
|
19153
|
-
const [
|
|
19136
|
+
function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, open: openProp, onOpenChange, bodyClassName = "p-3", children }) {
|
|
19137
|
+
const [openState, setOpenState] = (0, react$1.useState)(defaultOpen);
|
|
19138
|
+
const open = openProp ?? openState;
|
|
19139
|
+
const toggle = () => {
|
|
19140
|
+
const next = !open;
|
|
19141
|
+
if (openProp === void 0) setOpenState(next);
|
|
19142
|
+
onOpenChange?.(next);
|
|
19143
|
+
};
|
|
19154
19144
|
const showBody = !collapsible || open;
|
|
19155
19145
|
const headerInner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
19156
19146
|
className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-foreground",
|
|
19157
19147
|
children: [icon, title]
|
|
19158
|
-
}), collapsible ?
|
|
19148
|
+
}), collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
19149
|
+
className: "flex min-w-0 items-center gap-2",
|
|
19150
|
+
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" })]
|
|
19151
|
+
}) : headerRight] });
|
|
19159
19152
|
const headerClass = `px-4 py-2 flex items-center justify-between ${showBody ? "border-b border-border" : ""}`;
|
|
19160
19153
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
19161
19154
|
className: "rounded-lg border border-border bg-surface overflow-hidden",
|
|
19162
19155
|
children: [collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
19163
19156
|
type: "button",
|
|
19164
|
-
|
|
19165
|
-
|
|
19157
|
+
"aria-expanded": open,
|
|
19158
|
+
onClick: toggle,
|
|
19159
|
+
className: `w-full gap-2 text-left ${headerClass}`,
|
|
19166
19160
|
children: headerInner
|
|
19167
19161
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
19168
19162
|
className: headerClass,
|
|
@@ -20523,6 +20517,60 @@ function useDeviceCapability(options) {
|
|
|
20523
20517
|
};
|
|
20524
20518
|
}
|
|
20525
20519
|
//#endregion
|
|
20520
|
+
//#region src/hooks/use-persisted-disclosure.ts
|
|
20521
|
+
/**
|
|
20522
|
+
* usePersistedDisclosure — an open / closed choice the viewer made, remembered
|
|
20523
|
+
* per viewer under the caller's key (one key per section and device).
|
|
20524
|
+
*
|
|
20525
|
+
* `null` means the viewer never chose. The caller then shows its own default,
|
|
20526
|
+
* which may follow the data (the Consumables section opens itself while an
|
|
20527
|
+
* item needs attention) — the default is never written, so it can change with
|
|
20528
|
+
* the data until the viewer picks.
|
|
20529
|
+
*
|
|
20530
|
+
* Storage is a convenience, never a requirement: every read and write is
|
|
20531
|
+
* guarded, and a blocked / private-mode storage means the choice lasts until
|
|
20532
|
+
* the page is left. A stored value this hook did not write is ignored.
|
|
20533
|
+
*/
|
|
20534
|
+
var OPEN = "open";
|
|
20535
|
+
var CLOSED = "closed";
|
|
20536
|
+
function readStored$1(key) {
|
|
20537
|
+
if (key === null || typeof window === "undefined") return null;
|
|
20538
|
+
try {
|
|
20539
|
+
const raw = window.localStorage.getItem(key);
|
|
20540
|
+
if (raw === OPEN) return true;
|
|
20541
|
+
if (raw === CLOSED) return false;
|
|
20542
|
+
return null;
|
|
20543
|
+
} catch {
|
|
20544
|
+
return null;
|
|
20545
|
+
}
|
|
20546
|
+
}
|
|
20547
|
+
function writeStored$1(key, open) {
|
|
20548
|
+
if (key === null || typeof window === "undefined") return;
|
|
20549
|
+
try {
|
|
20550
|
+
window.localStorage.setItem(key, open ? OPEN : CLOSED);
|
|
20551
|
+
} catch {}
|
|
20552
|
+
}
|
|
20553
|
+
/** @returns `[choice, setChoice]` — `choice` is `null` until the viewer picks. */
|
|
20554
|
+
function usePersistedDisclosure(storageKey) {
|
|
20555
|
+
const [choice, setChoice] = (0, react$1.useState)(() => ({
|
|
20556
|
+
key: storageKey,
|
|
20557
|
+
open: readStored$1(storageKey)
|
|
20558
|
+
}));
|
|
20559
|
+
const current = choice.key === storageKey ? choice : {
|
|
20560
|
+
key: storageKey,
|
|
20561
|
+
open: readStored$1(storageKey)
|
|
20562
|
+
};
|
|
20563
|
+
if (current !== choice) setChoice(current);
|
|
20564
|
+
const update = (0, react$1.useCallback)((open) => {
|
|
20565
|
+
setChoice({
|
|
20566
|
+
key: storageKey,
|
|
20567
|
+
open
|
|
20568
|
+
});
|
|
20569
|
+
writeStored$1(storageKey, open);
|
|
20570
|
+
}, [storageKey]);
|
|
20571
|
+
return [current.open, update];
|
|
20572
|
+
}
|
|
20573
|
+
//#endregion
|
|
20526
20574
|
//#region src/composites/consumable-level-badge.tsx
|
|
20527
20575
|
var LOOKS$1 = {
|
|
20528
20576
|
ok: {
|
|
@@ -20543,6 +20591,12 @@ var LOOKS$1 = {
|
|
|
20543
20591
|
labelKey: "uiLibrary.consumableLevel.empty",
|
|
20544
20592
|
labelFallback: "Empty"
|
|
20545
20593
|
},
|
|
20594
|
+
full: {
|
|
20595
|
+
variant: "warning",
|
|
20596
|
+
barClass: "bg-warning",
|
|
20597
|
+
labelKey: "uiLibrary.consumableLevel.full",
|
|
20598
|
+
labelFallback: "Full"
|
|
20599
|
+
},
|
|
20546
20600
|
unknown: {
|
|
20547
20601
|
variant: "neutral",
|
|
20548
20602
|
barClass: "bg-foreground-subtle",
|
|
@@ -20566,39 +20620,57 @@ function consumableLevelLabel(level, t) {
|
|
|
20566
20620
|
const look = lookOf(level);
|
|
20567
20621
|
return t(look.labelKey, look.labelFallback);
|
|
20568
20622
|
}
|
|
20569
|
-
/** A remaining-life % at or below this is `low`. */
|
|
20623
|
+
/** A remaining-life (or supply fill) % at or below this is `low`. */
|
|
20570
20624
|
var CONSUMABLE_LOW_PERCENT = 15;
|
|
20571
|
-
/**
|
|
20572
|
-
|
|
20573
|
-
|
|
20574
|
-
|
|
20575
|
-
|
|
20576
|
-
|
|
20577
|
-
|
|
20625
|
+
/** A waste item: full is bad, low or empty is what it should be. */
|
|
20626
|
+
function wasteLevel(item) {
|
|
20627
|
+
if (item.status === "full" || item.status === "replace") return "full";
|
|
20628
|
+
if (item.level !== null) return item.level >= 85 ? "full" : "ok";
|
|
20629
|
+
return item.status === null ? null : "ok";
|
|
20630
|
+
}
|
|
20631
|
+
/** A wear part or a supply: low / empty is bad, a full supply is fine. */
|
|
20632
|
+
function drawnLevel(item) {
|
|
20578
20633
|
if (item.level !== null) {
|
|
20579
|
-
if (item.level <= 0) return "empty";
|
|
20580
|
-
if (item.level <= 15 || item.status === "replace") return "low";
|
|
20634
|
+
if (item.level <= 0 || item.status === "empty") return "empty";
|
|
20635
|
+
if (item.level <= 15 || item.status === "replace" || item.status === "low") return "low";
|
|
20581
20636
|
return "ok";
|
|
20582
20637
|
}
|
|
20583
20638
|
switch (item.status) {
|
|
20584
|
-
case "replace":
|
|
20585
|
-
case "
|
|
20639
|
+
case "replace":
|
|
20640
|
+
case "low": return "low";
|
|
20641
|
+
case "empty": return "empty";
|
|
20642
|
+
case "ok":
|
|
20643
|
+
case "full": return "ok";
|
|
20586
20644
|
case null: return null;
|
|
20587
20645
|
}
|
|
20588
20646
|
}
|
|
20589
20647
|
/**
|
|
20648
|
+
* A `consumables` cap item as a level, by its kind (D915):
|
|
20649
|
+
* - a `waste` item (a dust bin) is `full` when the provider says so or its
|
|
20650
|
+
* fill is at or above {@link CONSUMABLE_FULL_PERCENT}, else ok;
|
|
20651
|
+
* - a `wear` part or a `supply` reads a known % (0 = empty, at or below
|
|
20652
|
+
* {@link CONSUMABLE_LOW_PERCENT} = low), never better than `low` when the
|
|
20653
|
+
* provider says `replace` or `low`; without a % its discrete status.
|
|
20654
|
+
* Nothing known is unknown (`null`).
|
|
20655
|
+
*/
|
|
20656
|
+
function consumableItemLevel(item) {
|
|
20657
|
+
return (0, _camstack_types.consumableKindOf)(item) === "waste" ? wasteLevel(item) : drawnLevel(item);
|
|
20658
|
+
}
|
|
20659
|
+
/** The supply vocabulary a level FIELD of a cap is typed by: `ok | low | empty`. */
|
|
20660
|
+
var SUPPLY_LEVELS = _camstack_types.ConsumableLevelSchema.exclude(["full"]).options;
|
|
20661
|
+
/**
|
|
20590
20662
|
* Whether a cap state field IS a consumable level: an enum whose members are
|
|
20591
|
-
* exactly the
|
|
20592
|
-
* cap that uses the
|
|
20663
|
+
* exactly the supply levels (`ok | low | empty`). Derived from the schema, so a
|
|
20664
|
+
* new cap that uses the vocabulary is recognised without naming it anywhere.
|
|
20593
20665
|
*/
|
|
20594
20666
|
function isConsumableLevelField(field) {
|
|
20595
20667
|
if (field.kind !== "enum" || field.enumValues === void 0) return false;
|
|
20596
|
-
const members =
|
|
20668
|
+
const members = SUPPLY_LEVELS;
|
|
20597
20669
|
return field.enumValues.length === members.length && members.every((m) => field.enumValues?.includes(m));
|
|
20598
20670
|
}
|
|
20599
20671
|
/** A value read from a slice as a level; anything that is not a member is unknown. */
|
|
20600
20672
|
function parseConsumableLevel(value) {
|
|
20601
|
-
const parsed = _camstack_types.
|
|
20673
|
+
const parsed = _camstack_types.ConsumableLevelSchema.safeParse(value);
|
|
20602
20674
|
return parsed.success ? parsed.data : null;
|
|
20603
20675
|
}
|
|
20604
20676
|
function ConsumableLevelBadge({ level, name, label, size = "md", className }) {
|
|
@@ -20613,26 +20685,50 @@ function ConsumableLevelBadge({ level, name, label, size = "md", className }) {
|
|
|
20613
20685
|
});
|
|
20614
20686
|
}
|
|
20615
20687
|
//#endregion
|
|
20616
|
-
//#region src/
|
|
20617
|
-
|
|
20618
|
-
|
|
20619
|
-
|
|
20620
|
-
|
|
20621
|
-
|
|
20622
|
-
|
|
20623
|
-
|
|
20624
|
-
|
|
20625
|
-
|
|
20626
|
-
|
|
20627
|
-
|
|
20628
|
-
|
|
20629
|
-
|
|
20630
|
-
|
|
20631
|
-
|
|
20632
|
-
|
|
20633
|
-
|
|
20634
|
-
|
|
20635
|
-
|
|
20688
|
+
//#region src/primitives/skeleton.tsx
|
|
20689
|
+
var Skeleton = (0, react$1.forwardRef)(({ className, ...props }, ref) => {
|
|
20690
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20691
|
+
ref,
|
|
20692
|
+
className: cn("animate-pulse bg-surface-hover rounded-md h-4 w-full", className),
|
|
20693
|
+
...props
|
|
20694
|
+
});
|
|
20695
|
+
});
|
|
20696
|
+
Skeleton.displayName = "Skeleton";
|
|
20697
|
+
//#endregion
|
|
20698
|
+
//#region src/primitives/button.tsx
|
|
20699
|
+
var buttonVariants = cva("inline-flex items-center justify-center rounded-md font-medium transition-colors cursor-pointer disabled:opacity-50 disabled:pointer-events-none", {
|
|
20700
|
+
variants: {
|
|
20701
|
+
variant: {
|
|
20702
|
+
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
20703
|
+
secondary: "bg-surface text-foreground hover:bg-surface-hover",
|
|
20704
|
+
ghost: "hover:bg-surface-hover text-foreground-muted",
|
|
20705
|
+
danger: "bg-danger text-white hover:bg-danger/90",
|
|
20706
|
+
outline: "border border-border bg-transparent text-foreground hover:bg-surface-hover"
|
|
20707
|
+
},
|
|
20708
|
+
size: {
|
|
20709
|
+
sm: "h-7 px-2.5 text-xs gap-1.5",
|
|
20710
|
+
md: "h-8 px-3 text-sm gap-2",
|
|
20711
|
+
lg: "h-9 px-4 text-sm gap-2"
|
|
20712
|
+
}
|
|
20713
|
+
},
|
|
20714
|
+
defaultVariants: {
|
|
20715
|
+
variant: "primary",
|
|
20716
|
+
size: "sm"
|
|
20717
|
+
}
|
|
20718
|
+
});
|
|
20719
|
+
var Button = (0, react$1.forwardRef)(({ className, variant, size, ...props }, ref) => {
|
|
20720
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
20721
|
+
ref,
|
|
20722
|
+
className: cn(buttonVariants({
|
|
20723
|
+
variant,
|
|
20724
|
+
size
|
|
20725
|
+
}), className),
|
|
20726
|
+
...props
|
|
20727
|
+
});
|
|
20728
|
+
});
|
|
20729
|
+
Button.displayName = "Button";
|
|
20730
|
+
//#endregion
|
|
20731
|
+
//#region src/composites/cap-settings/consumable-rows.tsx
|
|
20636
20732
|
/** `value` null = unknown (D393) — renders an em-dash, never a fake `0`. */
|
|
20637
20733
|
function formatRemaining(t, r) {
|
|
20638
20734
|
if (r.value === null) return "—";
|
|
@@ -20664,55 +20760,278 @@ function formatResetAt(ms) {
|
|
|
20664
20760
|
return "";
|
|
20665
20761
|
}
|
|
20666
20762
|
}
|
|
20667
|
-
function
|
|
20763
|
+
function needsAttention(level) {
|
|
20764
|
+
return level === "low" || level === "empty" || level === "full";
|
|
20765
|
+
}
|
|
20766
|
+
function clampPercent(value) {
|
|
20767
|
+
return Math.max(0, Math.min(100, value));
|
|
20768
|
+
}
|
|
20769
|
+
function RowFrame({ item, warn, children }) {
|
|
20770
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20771
|
+
"data-form": (0, _camstack_types.consumableFormOf)(item),
|
|
20772
|
+
className: `flex gap-2.5 rounded-md border p-3 ${warn ? "border-warning/40 bg-warning/5" : "border-border-subtle"}`,
|
|
20773
|
+
children
|
|
20774
|
+
});
|
|
20775
|
+
}
|
|
20776
|
+
function RowLabel({ item, warn }) {
|
|
20777
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
20778
|
+
className: `truncate text-xs font-semibold ${warn ? "text-warning" : "text-foreground"}`,
|
|
20779
|
+
children: item.label
|
|
20780
|
+
});
|
|
20781
|
+
}
|
|
20782
|
+
/** A wear part: bar + %, or its verdict; remaining time; the reset. */
|
|
20783
|
+
function PartRow({ item, busy, canReset, onReset }) {
|
|
20668
20784
|
const t = useUiText();
|
|
20669
20785
|
const level = consumableItemLevel(item);
|
|
20670
|
-
const warn = level
|
|
20671
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
20672
|
-
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20677
|
-
|
|
20678
|
-
|
|
20679
|
-
|
|
20786
|
+
const warn = needsAttention(level);
|
|
20787
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowFrame, {
|
|
20788
|
+
item,
|
|
20789
|
+
warn,
|
|
20790
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20791
|
+
className: "flex min-w-0 flex-1 flex-col gap-1.5",
|
|
20792
|
+
children: [
|
|
20793
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowLabel, {
|
|
20794
|
+
item,
|
|
20795
|
+
warn
|
|
20796
|
+
}),
|
|
20797
|
+
item.level !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20798
|
+
className: "flex items-center gap-2",
|
|
20799
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20800
|
+
className: "h-1.5 flex-1 overflow-hidden rounded-full bg-surface-hover",
|
|
20801
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20802
|
+
className: `h-full rounded-full ${consumableLevelBarClass(level)} transition-all`,
|
|
20803
|
+
style: { width: `${clampPercent(item.level)}%` }
|
|
20804
|
+
})
|
|
20805
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
20806
|
+
className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
|
|
20807
|
+
children: [Math.round(item.level), "%"]
|
|
20808
|
+
})]
|
|
20809
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
|
|
20810
|
+
level,
|
|
20811
|
+
size: "sm",
|
|
20812
|
+
className: "w-fit",
|
|
20813
|
+
label: item.status === "replace" ? t("uiLibrary.consumablesPanel.replace", "Replace") : void 0
|
|
20814
|
+
}),
|
|
20815
|
+
item.remaining && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
20816
|
+
className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
|
|
20817
|
+
children: formatRemaining(t, item.remaining)
|
|
20818
|
+
}),
|
|
20819
|
+
item.lastResetAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
20820
|
+
className: "text-[10px] text-foreground-subtle",
|
|
20821
|
+
children: t("uiLibrary.consumablesPanel.replacedOn", "Replaced {{date}}", { date: formatResetAt(item.lastResetAt) })
|
|
20822
|
+
}),
|
|
20823
|
+
item.resettable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
20680
20824
|
type: "button",
|
|
20825
|
+
variant: "outline",
|
|
20826
|
+
size: "sm",
|
|
20827
|
+
className: "w-fit",
|
|
20681
20828
|
onClick: () => onReset(item.key),
|
|
20682
20829
|
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
20830
|
children: t("uiLibrary.consumablesPanel.replaced", "Replaced")
|
|
20685
|
-
})
|
|
20686
|
-
|
|
20687
|
-
|
|
20831
|
+
})
|
|
20832
|
+
]
|
|
20833
|
+
})
|
|
20834
|
+
});
|
|
20835
|
+
}
|
|
20836
|
+
/** A tank or a bin: a vertical gauge filled from the bottom, then the % or the verdict. */
|
|
20837
|
+
function GaugeRow({ item }) {
|
|
20838
|
+
const level = consumableItemLevel(item);
|
|
20839
|
+
const warn = needsAttention(level);
|
|
20840
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(RowFrame, {
|
|
20841
|
+
item,
|
|
20842
|
+
warn,
|
|
20843
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20844
|
+
className: "relative h-10 w-3 shrink-0 overflow-hidden rounded-sm border border-border bg-surface-hover",
|
|
20845
|
+
"data-testid": "consumable-gauge",
|
|
20846
|
+
children: item.level !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20847
|
+
className: `absolute inset-x-0 bottom-0 ${consumableLevelBarClass(level)} transition-all`,
|
|
20848
|
+
style: { height: `${clampPercent(item.level)}%` }
|
|
20849
|
+
})
|
|
20850
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20851
|
+
className: "flex min-w-0 flex-1 flex-col justify-center gap-1",
|
|
20852
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowLabel, {
|
|
20853
|
+
item,
|
|
20854
|
+
warn
|
|
20855
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20688
20856
|
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", {
|
|
20857
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
|
|
20858
|
+
level,
|
|
20859
|
+
size: "sm",
|
|
20860
|
+
className: "w-fit"
|
|
20861
|
+
}), item.level !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
20696
20862
|
className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
|
|
20697
20863
|
children: [Math.round(item.level), "%"]
|
|
20698
20864
|
})]
|
|
20699
|
-
})
|
|
20700
|
-
|
|
20701
|
-
|
|
20702
|
-
|
|
20703
|
-
|
|
20704
|
-
|
|
20705
|
-
|
|
20706
|
-
|
|
20707
|
-
|
|
20708
|
-
|
|
20709
|
-
|
|
20710
|
-
|
|
20711
|
-
|
|
20712
|
-
|
|
20713
|
-
|
|
20865
|
+
})]
|
|
20866
|
+
})]
|
|
20867
|
+
});
|
|
20868
|
+
}
|
|
20869
|
+
/** How many of the three steps a product's level fills. */
|
|
20870
|
+
var PRODUCT_STEPS = {
|
|
20871
|
+
full: 3,
|
|
20872
|
+
ok: 3,
|
|
20873
|
+
low: 1,
|
|
20874
|
+
empty: 0,
|
|
20875
|
+
unknown: 0
|
|
20876
|
+
};
|
|
20877
|
+
var STEP_INDEXES = [
|
|
20878
|
+
0,
|
|
20879
|
+
1,
|
|
20880
|
+
2
|
|
20881
|
+
];
|
|
20882
|
+
/** The refill wording a product's level reads as. */
|
|
20883
|
+
function productLabel(level, t) {
|
|
20884
|
+
if (level === "low") return t("uiLibrary.consumablesPanel.refillSoon", "Refill soon");
|
|
20885
|
+
if (level === "empty") return t("uiLibrary.consumablesPanel.refillNow", "Refill now");
|
|
20886
|
+
}
|
|
20887
|
+
/** A product bought and refilled: package icon, three steps, "refill". */
|
|
20888
|
+
function ProductRow({ item }) {
|
|
20889
|
+
const t = useUiText();
|
|
20890
|
+
const level = consumableItemLevel(item);
|
|
20891
|
+
const warn = needsAttention(level);
|
|
20892
|
+
const filled = PRODUCT_STEPS[level ?? "unknown"];
|
|
20893
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(RowFrame, {
|
|
20894
|
+
item,
|
|
20895
|
+
warn,
|
|
20896
|
+
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", {
|
|
20897
|
+
className: "flex min-w-0 flex-1 flex-col gap-1.5",
|
|
20898
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowLabel, {
|
|
20899
|
+
item,
|
|
20900
|
+
warn
|
|
20901
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20902
|
+
className: "flex flex-wrap items-center gap-2",
|
|
20903
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20904
|
+
className: "flex gap-0.5",
|
|
20905
|
+
"data-testid": "consumable-steps",
|
|
20906
|
+
"data-filled": filled,
|
|
20907
|
+
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))
|
|
20908
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
|
|
20909
|
+
level,
|
|
20910
|
+
size: "sm",
|
|
20911
|
+
className: "whitespace-nowrap",
|
|
20912
|
+
label: productLabel(level, t)
|
|
20913
|
+
})]
|
|
20914
|
+
})]
|
|
20915
|
+
})]
|
|
20714
20916
|
});
|
|
20715
20917
|
}
|
|
20918
|
+
/** The row for an item, chosen by its form. */
|
|
20919
|
+
function ConsumableRow(props) {
|
|
20920
|
+
switch ((0, _camstack_types.consumableFormOf)(props.item)) {
|
|
20921
|
+
case "part": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PartRow, { ...props });
|
|
20922
|
+
case "tank":
|
|
20923
|
+
case "bin": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeRow, { item: props.item });
|
|
20924
|
+
case "product": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProductRow, { item: props.item });
|
|
20925
|
+
}
|
|
20926
|
+
}
|
|
20927
|
+
//#endregion
|
|
20928
|
+
//#region src/composites/cap-settings/consumables-summary.ts
|
|
20929
|
+
var SEVERITY = {
|
|
20930
|
+
empty: 3,
|
|
20931
|
+
full: 2,
|
|
20932
|
+
low: 1
|
|
20933
|
+
};
|
|
20934
|
+
function isAttention(level) {
|
|
20935
|
+
return level === "low" || level === "empty" || level === "full";
|
|
20936
|
+
}
|
|
20937
|
+
function summarizeConsumables(items) {
|
|
20938
|
+
if (items.length === 0) return {
|
|
20939
|
+
kind: "none",
|
|
20940
|
+
needsAttention: false
|
|
20941
|
+
};
|
|
20942
|
+
const attention = [];
|
|
20943
|
+
let ok = 0;
|
|
20944
|
+
let unknown = 0;
|
|
20945
|
+
for (const item of items) {
|
|
20946
|
+
const level = consumableItemLevel(item);
|
|
20947
|
+
if (isAttention(level)) attention.push({
|
|
20948
|
+
label: item.label,
|
|
20949
|
+
level,
|
|
20950
|
+
replace: item.status === "replace"
|
|
20951
|
+
});
|
|
20952
|
+
else if (level === "ok") ok += 1;
|
|
20953
|
+
else unknown += 1;
|
|
20954
|
+
}
|
|
20955
|
+
const worst = [...attention].sort((a, b) => SEVERITY[b.level] - SEVERITY[a.level])[0];
|
|
20956
|
+
if (worst !== void 0) return {
|
|
20957
|
+
kind: "attention",
|
|
20958
|
+
needsAttention: true,
|
|
20959
|
+
count: attention.length,
|
|
20960
|
+
worst
|
|
20961
|
+
};
|
|
20962
|
+
return unknown === 0 ? {
|
|
20963
|
+
kind: "ok",
|
|
20964
|
+
needsAttention: false,
|
|
20965
|
+
count: ok
|
|
20966
|
+
} : {
|
|
20967
|
+
kind: "partial",
|
|
20968
|
+
needsAttention: false,
|
|
20969
|
+
ok,
|
|
20970
|
+
unknown
|
|
20971
|
+
};
|
|
20972
|
+
}
|
|
20973
|
+
//#endregion
|
|
20974
|
+
//#region src/composites/cap-settings/ConsumablesPanel.tsx
|
|
20975
|
+
/**
|
|
20976
|
+
* ConsumablesPanel — the ONE consumables surface of every device (D915): wear
|
|
20977
|
+
* parts, tanks (a vacuum's clean and dirty water), products (detergent, a
|
|
20978
|
+
* dishwasher's salt and rinse aid) and bins (a dust bin), for whichever device
|
|
20979
|
+
* is bound to the `consumables` capability. A cap-settings component
|
|
20980
|
+
* (ui-library), shown directly under the device page's hero card through the
|
|
20981
|
+
* cap-UI contribution mechanism (the cap declares
|
|
20982
|
+
* `ui: { kind:'widget', widgetId:'host/consumables-panel', placement:'hero', ... }`)
|
|
20983
|
+
* — never drawn by a device type's hero card itself.
|
|
20984
|
+
*
|
|
20985
|
+
* Reads the cap's pushed runtime-state slice via `useDeviceCapability` and
|
|
20986
|
+
* renders one row per item, drawn by its form (`consumable-rows.tsx`). A
|
|
20987
|
+
* slice that has not landed is a loading line, never "none reported" (D315).
|
|
20988
|
+
*
|
|
20989
|
+
* The section COLLAPSES smartly: it starts open while any item needs
|
|
20990
|
+
* attention (low, empty, a full bin or waste tank, a part to replace) and
|
|
20991
|
+
* closed while every item is ok or unknown; closed, its header carries a
|
|
20992
|
+
* one-line summary (`consumables-summary.ts`). The operator's own toggle wins
|
|
20993
|
+
* and is remembered per device and per viewer.
|
|
20994
|
+
*/
|
|
20995
|
+
/** Where one viewer's open / closed choice for one device's section is kept. */
|
|
20996
|
+
function consumablesOpenStorageKey(deviceId) {
|
|
20997
|
+
return `camstack.device.${String(deviceId)}.consumables.open`;
|
|
20998
|
+
}
|
|
20999
|
+
/** The one line a collapsed section says. */
|
|
21000
|
+
function SummaryLine({ summary, t }) {
|
|
21001
|
+
switch (summary.kind) {
|
|
21002
|
+
case "none": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
21003
|
+
variant: "neutral",
|
|
21004
|
+
size: "sm",
|
|
21005
|
+
children: t("uiLibrary.consumablesPanel.noneReported", "None reported")
|
|
21006
|
+
});
|
|
21007
|
+
case "ok": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
21008
|
+
variant: "success",
|
|
21009
|
+
size: "sm",
|
|
21010
|
+
children: t("uiLibrary.consumablesPanel.allOk", "All OK")
|
|
21011
|
+
});
|
|
21012
|
+
case "partial": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
21013
|
+
variant: "neutral",
|
|
21014
|
+
size: "sm",
|
|
21015
|
+
children: t("uiLibrary.consumablesPanel.okAndUnknown", "{{ok}} OK · {{unknown}} unknown", {
|
|
21016
|
+
ok: summary.ok,
|
|
21017
|
+
unknown: summary.unknown
|
|
21018
|
+
})
|
|
21019
|
+
});
|
|
21020
|
+
case "attention": return summary.count === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableLevelBadge, {
|
|
21021
|
+
level: summary.worst.level,
|
|
21022
|
+
name: summary.worst.label,
|
|
21023
|
+
size: "sm",
|
|
21024
|
+
label: summary.worst.replace ? t("uiLibrary.consumablesPanel.replace", "Replace") : void 0
|
|
21025
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
21026
|
+
variant: consumableLevelVariant(summary.worst.level),
|
|
21027
|
+
size: "sm",
|
|
21028
|
+
children: t("uiLibrary.consumablesPanel.needAttention", {
|
|
21029
|
+
one: "{{count}} needs attention",
|
|
21030
|
+
other: "{{count}} need attention"
|
|
21031
|
+
}, { count: summary.count })
|
|
21032
|
+
});
|
|
21033
|
+
}
|
|
21034
|
+
}
|
|
20716
21035
|
function ConsumablesPanel({ deviceId }) {
|
|
20717
21036
|
const t = useUiText();
|
|
20718
21037
|
const system = useSystem();
|
|
@@ -20724,6 +21043,7 @@ function ConsumablesPanel({ deviceId }) {
|
|
|
20724
21043
|
slice: (d) => d.state.consumables
|
|
20725
21044
|
});
|
|
20726
21045
|
const [busyKey, setBusyKey] = (0, react$1.useState)(null);
|
|
21046
|
+
const [choice, setChoice] = usePersistedDisclosure(ready ? consumablesOpenStorageKey(deviceId) : null);
|
|
20727
21047
|
const onReset = (0, react$1.useCallback)(async (key) => {
|
|
20728
21048
|
if (!cap) return;
|
|
20729
21049
|
setBusyKey(key);
|
|
@@ -20735,17 +21055,30 @@ function ConsumablesPanel({ deviceId }) {
|
|
|
20735
21055
|
setBusyKey(null);
|
|
20736
21056
|
}
|
|
20737
21057
|
}, [cap]);
|
|
20738
|
-
const
|
|
21058
|
+
const summary = slice === void 0 ? null : summarizeConsumables(slice.items);
|
|
21059
|
+
const open = choice ?? summary?.needsAttention ?? false;
|
|
21060
|
+
const loading = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
21061
|
+
className: open ? "h-10 w-full" : "h-4 w-16",
|
|
21062
|
+
"data-testid": "consumables-loading",
|
|
21063
|
+
"aria-label": t("uiLibrary.consumablesPanel.loading", "Reading consumables…")
|
|
21064
|
+
});
|
|
20739
21065
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
|
|
20740
21066
|
title: t("uiLibrary.consumablesPanel.title", "Consumables"),
|
|
20741
21067
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Gauge, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
|
|
20742
21068
|
bodyClassName: "p-3 overflow-y-auto",
|
|
20743
|
-
|
|
21069
|
+
collapsible: true,
|
|
21070
|
+
open,
|
|
21071
|
+
onOpenChange: setChoice,
|
|
21072
|
+
headerRight: open ? void 0 : summary === null ? loading : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SummaryLine, {
|
|
21073
|
+
summary,
|
|
21074
|
+
t
|
|
21075
|
+
}),
|
|
21076
|
+
children: slice === void 0 ? loading : slice.items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
20744
21077
|
className: "text-[11px] text-foreground-subtle italic leading-relaxed",
|
|
20745
21078
|
children: t("uiLibrary.consumablesPanel.empty", "No consumables reported.")
|
|
20746
21079
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
20747
|
-
className: "
|
|
20748
|
-
children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
|
|
21080
|
+
className: "grid grid-cols-1 gap-2 sm:grid-cols-2",
|
|
21081
|
+
children: slice.items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
|
|
20749
21082
|
item,
|
|
20750
21083
|
busy: busyKey === item.key,
|
|
20751
21084
|
canReset: available,
|
|
@@ -20785,49 +21118,6 @@ function useCapabilityPresence(capabilityName) {
|
|
|
20785
21118
|
return classifyCapability(query.data);
|
|
20786
21119
|
}
|
|
20787
21120
|
//#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
21121
|
//#region src/composites/cap-settings/recording-timeline-model.ts
|
|
20832
21122
|
/** Tie-break precedence for a bucket's dominant kind (first wins on equal count). */
|
|
20833
21123
|
var KIND_PRECEDENCE = [
|
|
@@ -47102,147 +47392,6 @@ function BatteryBadge$2({ level }) {
|
|
|
47102
47392
|
});
|
|
47103
47393
|
}
|
|
47104
47394
|
/**
|
|
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
47395
|
* VacuumInlineControl — compact, state-aware vacuum controls for a
|
|
47247
47396
|
* device-list row. Start / Pause / Return-to-base + battery badge.
|
|
47248
47397
|
*/
|
|
@@ -47324,114 +47473,105 @@ function VacuumHeroCard({ trpc, deviceId }) {
|
|
|
47324
47473
|
};
|
|
47325
47474
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47326
47475
|
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
|
-
]
|
|
47476
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47477
|
+
className: "flex items-center gap-6",
|
|
47478
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumGraphic, {
|
|
47479
|
+
state,
|
|
47480
|
+
batteryLevel: slice?.batteryLevel ?? null
|
|
47481
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47482
|
+
className: "flex flex-col gap-3 flex-1",
|
|
47483
|
+
children: [
|
|
47484
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47485
|
+
className: "flex items-center justify-between",
|
|
47486
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
47487
|
+
className: cn("text-3xl font-semibold", meta.tone),
|
|
47488
|
+
children: meta.label
|
|
47489
|
+
}), slice?.batteryLevel != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge$2, { level: slice.batteryLevel })]
|
|
47490
|
+
}),
|
|
47491
|
+
slice?.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47492
|
+
className: "flex items-center gap-2",
|
|
47493
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
47494
|
+
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
47495
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
47496
|
+
className: "h-full bg-primary transition-[width] duration-500",
|
|
47497
|
+
style: { width: `${Math.max(0, Math.min(100, slice.progressPercent))}%` }
|
|
47498
|
+
})
|
|
47499
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
47500
|
+
className: "tabular-nums text-xs text-foreground-subtle",
|
|
47501
|
+
children: [Math.round(slice.progressPercent), "%"]
|
|
47502
|
+
})]
|
|
47503
|
+
}),
|
|
47504
|
+
slice?.errorCode != null && slice.errorCode !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47505
|
+
className: "inline-flex items-center gap-1 text-xs text-destructive",
|
|
47506
|
+
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 }) })]
|
|
47507
|
+
}),
|
|
47508
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47509
|
+
className: "flex items-center gap-2",
|
|
47510
|
+
children: [
|
|
47511
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
47512
|
+
label: t("uiLibrary.vacuum.start", "Start"),
|
|
47513
|
+
busy,
|
|
47514
|
+
active: state === "cleaning",
|
|
47515
|
+
onClick: () => press(cap.start),
|
|
47516
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-5 w-5" })
|
|
47517
|
+
}),
|
|
47518
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
47519
|
+
label: t("uiLibrary.vacuum.pause", "Pause"),
|
|
47520
|
+
busy,
|
|
47521
|
+
active: state === "paused",
|
|
47522
|
+
onClick: () => press(cap.pause),
|
|
47523
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-5 w-5" })
|
|
47524
|
+
}),
|
|
47525
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
47526
|
+
label: t("uiLibrary.vacuum.stop", "Stop"),
|
|
47527
|
+
busy,
|
|
47528
|
+
active: false,
|
|
47529
|
+
onClick: () => press(cap.stop),
|
|
47530
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Square, { className: "h-4 w-4" })
|
|
47531
|
+
}),
|
|
47532
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
47533
|
+
label: t("uiLibrary.vacuum.returnToBase", "Return to base"),
|
|
47534
|
+
busy,
|
|
47535
|
+
active: state === "returning" || state === "docked",
|
|
47536
|
+
onClick: () => press(cap.returnToBase),
|
|
47537
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(House, { className: "h-5 w-5" })
|
|
47538
|
+
}),
|
|
47539
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
|
|
47540
|
+
label: t("uiLibrary.vacuum.locate", "Locate"),
|
|
47541
|
+
busy,
|
|
47542
|
+
active: false,
|
|
47543
|
+
onClick: () => press(cap.locate),
|
|
47544
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Megaphone, { className: "h-5 w-5" })
|
|
47545
|
+
})
|
|
47546
|
+
]
|
|
47547
|
+
})
|
|
47548
|
+
]
|
|
47549
|
+
})]
|
|
47550
|
+
}), fanSpeeds.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
47551
|
+
className: "flex items-center gap-2 text-xs text-foreground-subtle",
|
|
47552
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
47553
|
+
className: "w-16 shrink-0",
|
|
47554
|
+
children: t("uiLibrary.vacuum.fanSpeed", "Fan speed")
|
|
47555
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
47556
|
+
"aria-label": t("uiLibrary.vacuum.fanSpeed", "Fan speed"),
|
|
47557
|
+
value: slice?.fanSpeed ?? "",
|
|
47558
|
+
disabled: busy,
|
|
47559
|
+
onChange: (e) => {
|
|
47560
|
+
onFanSpeed(e.target.value);
|
|
47561
|
+
},
|
|
47562
|
+
onClick: (e) => {
|
|
47563
|
+
e.stopPropagation();
|
|
47564
|
+
},
|
|
47565
|
+
className: "flex-1 rounded border border-border bg-surface px-2 py-1 text-foreground disabled:opacity-50",
|
|
47566
|
+
children: [slice?.fanSpeed == null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
47567
|
+
value: "",
|
|
47568
|
+
children: "—"
|
|
47569
|
+
}), fanSpeeds.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
47570
|
+
value: s,
|
|
47571
|
+
children: s
|
|
47572
|
+
}, s))]
|
|
47573
|
+
})]
|
|
47574
|
+
})]
|
|
47435
47575
|
});
|
|
47436
47576
|
}
|
|
47437
47577
|
//#endregion
|
|
@@ -50359,6 +50499,9 @@ function PetFeederHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
50359
50499
|
* Reads the `dishwasher` cap status slice and renders inline + hero surfaces
|
|
50360
50500
|
* mirroring vacuum-control / pet-feeder patterns.
|
|
50361
50501
|
*
|
|
50502
|
+
* Salt and rinse aid are not here: they are items of the device's `consumables`
|
|
50503
|
+
* cap, shown by the page's Consumables section like every device's (D915).
|
|
50504
|
+
*
|
|
50362
50505
|
* UNKNOWN IS NOT OFF. The slice carries `null` for everything the appliance has not
|
|
50363
50506
|
* reported, and this view renders that as "—" / a disabled control, never as an unchecked
|
|
50364
50507
|
* box or a zero. It also does not echo a command before the appliance confirms it: the
|
|
@@ -50492,60 +50635,44 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
|
|
|
50492
50635
|
if (busy) return;
|
|
50493
50636
|
fn();
|
|
50494
50637
|
};
|
|
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
|
-
}
|
|
50638
|
+
if (layout === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50639
|
+
className: "inline-flex items-center gap-1.5 max-w-full",
|
|
50640
|
+
children: [
|
|
50641
|
+
display.doorOpen === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50642
|
+
className: "inline-flex text-warning shrink-0",
|
|
50643
|
+
title: t("uiLibrary.dishwasher.doorOpen", "Door open"),
|
|
50644
|
+
"aria-label": t("uiLibrary.dishwasher.doorOpen", "Door open"),
|
|
50645
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" })
|
|
50646
|
+
}),
|
|
50647
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50648
|
+
className: cn("text-xs font-medium tabular-nums shrink-0", meta.tone),
|
|
50649
|
+
children: meta.label
|
|
50650
|
+
}),
|
|
50651
|
+
display.program != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50652
|
+
className: "text-xs text-foreground-subtle truncate max-w-[7rem]",
|
|
50653
|
+
title: display.program,
|
|
50654
|
+
children: featureLabel(display.program)
|
|
50655
|
+
}),
|
|
50656
|
+
display.remainingSeconds != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
50657
|
+
className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums shrink-0",
|
|
50658
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }), formatDuration(display.remainingSeconds, t)]
|
|
50659
|
+
}),
|
|
50660
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
50661
|
+
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
50662
|
+
busy: busy || !startable,
|
|
50663
|
+
active: running,
|
|
50664
|
+
onClick: () => press(() => cap.start({})),
|
|
50665
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-3 w-3" })
|
|
50666
|
+
}),
|
|
50667
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
50668
|
+
label: paused ? t("uiLibrary.dishwasher.resume", "Resume") : t("uiLibrary.dishwasher.pause", "Pause"),
|
|
50669
|
+
busy: busy || !(running || paused),
|
|
50670
|
+
active: paused,
|
|
50671
|
+
onClick: () => press(() => paused ? cap.resume({}) : cap.pause({})),
|
|
50672
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-3 w-3" })
|
|
50673
|
+
})
|
|
50674
|
+
]
|
|
50675
|
+
});
|
|
50549
50676
|
const programs = display.availablePrograms;
|
|
50550
50677
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50551
50678
|
className: "flex flex-col gap-3 rounded-lg border border-border bg-surface p-4",
|
|
@@ -50596,25 +50723,14 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
|
|
|
50596
50723
|
}),
|
|
50597
50724
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50598
50725
|
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
|
-
]
|
|
50726
|
+
children: [display.doorOpen === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
50727
|
+
className: "inline-flex items-center gap-1 text-xs text-warning",
|
|
50728
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.doorOpen", "Door open")]
|
|
50729
|
+
}), display.activeEvents.map((event) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50730
|
+
className: "text-xs text-warning",
|
|
50731
|
+
title: event,
|
|
50732
|
+
children: featureLabel(event)
|
|
50733
|
+
}, event))]
|
|
50618
50734
|
}),
|
|
50619
50735
|
display.operationState === "Error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50620
50736
|
className: "inline-flex items-center gap-1 text-xs text-danger",
|
|
@@ -68253,7 +68369,6 @@ exports.FilterBar = FilterBar;
|
|
|
68253
68369
|
exports.Flame = Flame;
|
|
68254
68370
|
exports.Flashlight = Flashlight;
|
|
68255
68371
|
exports.FlashlightOff = FlashlightOff;
|
|
68256
|
-
exports.FlaskConical = FlaskConical;
|
|
68257
68372
|
exports.FloatingEventStream = FloatingEventStream;
|
|
68258
68373
|
exports.FloatingLogStream = FloatingLogStream;
|
|
68259
68374
|
exports.FloatingPanel = FloatingPanel;
|
|
@@ -68945,7 +69060,6 @@ exports.summarizeHubCandidates = summarizeHubCandidates;
|
|
|
68945
69060
|
exports.swatchFillClass = swatchFillClass;
|
|
68946
69061
|
exports.swatchLineClass = swatchLineClass;
|
|
68947
69062
|
exports.tabForSelector = tabForSelector;
|
|
68948
|
-
exports.tankAlert = tankAlert;
|
|
68949
69063
|
exports.themeToCss = require_theme_to_root_css.themeToCss;
|
|
68950
69064
|
exports.themeToRootCss = require_theme_to_root_css.themeToRootCss;
|
|
68951
69065
|
exports.timeInState = timeInState;
|
|
@@ -69211,9 +69325,11 @@ exports.useDeviceExtensionListSlots = useDeviceExtensionListSlots;
|
|
|
69211
69325
|
exports.useDeviceExtensionListSources = useDeviceExtensionListSources;
|
|
69212
69326
|
exports.useDeviceExtensionSetSlot = useDeviceExtensionSetSlot;
|
|
69213
69327
|
exports.useDeviceHistoryDeclareSeries = useDeviceHistoryDeclareSeries;
|
|
69328
|
+
exports.useDeviceHistoryDeleteOrphanSeries = useDeviceHistoryDeleteOrphanSeries;
|
|
69214
69329
|
exports.useDeviceHistoryGetDeviceEnabled = useDeviceHistoryGetDeviceEnabled;
|
|
69215
69330
|
exports.useDeviceHistoryGetStatus = useDeviceHistoryGetStatus;
|
|
69216
69331
|
exports.useDeviceHistoryListNodeSeries = useDeviceHistoryListNodeSeries;
|
|
69332
|
+
exports.useDeviceHistoryListOrphanSeries = useDeviceHistoryListOrphanSeries;
|
|
69217
69333
|
exports.useDeviceHistoryListSeries = useDeviceHistoryListSeries;
|
|
69218
69334
|
exports.useDeviceHistoryQuery = useDeviceHistoryQuery;
|
|
69219
69335
|
exports.useDeviceHistoryQueryMany = useDeviceHistoryQueryMany;
|
|
@@ -69666,6 +69782,7 @@ exports.useOsdManagerSetSlotCameraEnabled = useOsdManagerSetSlotCameraEnabled;
|
|
|
69666
69782
|
exports.useOsdManagerUpsertTemplate = useOsdManagerUpsertTemplate;
|
|
69667
69783
|
exports.useOsdSetOverlay = useOsdSetOverlay;
|
|
69668
69784
|
exports.usePTZ = usePTZ;
|
|
69785
|
+
exports.usePersistedDisclosure = usePersistedDisclosure;
|
|
69669
69786
|
exports.usePersistedPageSize = usePersistedPageSize;
|
|
69670
69787
|
exports.usePetFeederCallPet = usePetFeederCallPet;
|
|
69671
69788
|
exports.usePetFeederCancelFeed = usePetFeederCancelFeed;
|