@camstack/ui-library 1.2.298 → 1.2.300
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{MotionZonesSettings-Bv8XzCa3.js → MotionZonesSettings-C3Tj12yc.js} +1 -1
- package/dist/{PrivacyMaskSettings-BlkbESYj.js → PrivacyMaskSettings-C9wfHkiB.js} +1 -1
- package/dist/{SceneMonitorEditor-i3-nXAEU.js → SceneMonitorEditor-30H29vN4.js} +1 -1
- package/dist/composites/addon-global-settings-form.d.ts +1 -1
- package/dist/composites/config-form-field.d.ts +3 -1
- package/dist/composites/device-controls/ac-panel.d.ts +14 -1
- package/dist/composites/device-controls/climate-panel.d.ts +4 -1
- package/dist/composites/device-controls/dishwasher-control.d.ts +17 -0
- package/dist/composites/device-controls/heating-valve-readout.d.ts +4 -0
- package/dist/composites/device-controls/types.d.ts +1 -1
- package/dist/composites/index.d.ts +2 -0
- package/dist/composites/record-entries/entry-json-schema-form.d.ts +11 -0
- package/dist/composites/record-entries/index.d.ts +4 -0
- package/dist/composites/record-entries/record-entries-hub.d.ts +9 -0
- package/dist/composites/record-entries/record-entries-panel.d.ts +6 -0
- package/dist/composites/record-entries/record-entry-secrets.d.ts +9 -0
- package/dist/composites/storage-records/index.d.ts +2 -0
- package/dist/composites/storage-records/storage-records-hub.d.ts +4 -0
- package/dist/composites/storage-records/storage-records-panel.d.ts +6 -0
- package/dist/generated/system-hooks.d.ts +20 -0
- package/dist/{icons-CLC60C-_.cjs → icons-B1AFXUaZ.cjs} +2 -78
- package/dist/{icons-BXXScOuy.js → icons-BT8c2hxp.js} +1 -77
- package/dist/index.cjs +1656 -186
- package/dist/index.js +1638 -193
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2185,6 +2185,54 @@ var Hash = require_createLucideIcon.createLucideIcon("hash", [
|
|
|
2185
2185
|
* This source code is licensed under the ISC license.
|
|
2186
2186
|
* See the LICENSE file in the root directory of this source tree.
|
|
2187
2187
|
*/
|
|
2188
|
+
var Heater = require_createLucideIcon.createLucideIcon("heater", [
|
|
2189
|
+
["path", {
|
|
2190
|
+
d: "M11 8c2-3-2-3 0-6",
|
|
2191
|
+
key: "1ldv5m"
|
|
2192
|
+
}],
|
|
2193
|
+
["path", {
|
|
2194
|
+
d: "M15.5 8c2-3-2-3 0-6",
|
|
2195
|
+
key: "1otqoz"
|
|
2196
|
+
}],
|
|
2197
|
+
["path", {
|
|
2198
|
+
d: "M6 10h.01",
|
|
2199
|
+
key: "1lbq93"
|
|
2200
|
+
}],
|
|
2201
|
+
["path", {
|
|
2202
|
+
d: "M6 14h.01",
|
|
2203
|
+
key: "zudwn7"
|
|
2204
|
+
}],
|
|
2205
|
+
["path", {
|
|
2206
|
+
d: "M10 16v-4",
|
|
2207
|
+
key: "1c25yv"
|
|
2208
|
+
}],
|
|
2209
|
+
["path", {
|
|
2210
|
+
d: "M14 16v-4",
|
|
2211
|
+
key: "1dkbt8"
|
|
2212
|
+
}],
|
|
2213
|
+
["path", {
|
|
2214
|
+
d: "M18 16v-4",
|
|
2215
|
+
key: "1yg9me"
|
|
2216
|
+
}],
|
|
2217
|
+
["path", {
|
|
2218
|
+
d: "M20 6a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3",
|
|
2219
|
+
key: "1ubg90"
|
|
2220
|
+
}],
|
|
2221
|
+
["path", {
|
|
2222
|
+
d: "M5 20v2",
|
|
2223
|
+
key: "1abpe8"
|
|
2224
|
+
}],
|
|
2225
|
+
["path", {
|
|
2226
|
+
d: "M19 20v2",
|
|
2227
|
+
key: "kqn6ft"
|
|
2228
|
+
}]
|
|
2229
|
+
]);
|
|
2230
|
+
/**
|
|
2231
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2232
|
+
*
|
|
2233
|
+
* This source code is licensed under the ISC license.
|
|
2234
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2235
|
+
*/
|
|
2188
2236
|
var House = require_createLucideIcon.createLucideIcon("house", [["path", {
|
|
2189
2237
|
d: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",
|
|
2190
2238
|
key: "5wwlr5"
|
|
@@ -4173,6 +4221,34 @@ var Thermometer = require_createLucideIcon.createLucideIcon("thermometer", [["pa
|
|
|
4173
4221
|
* This source code is licensed under the ISC license.
|
|
4174
4222
|
* See the LICENSE file in the root directory of this source tree.
|
|
4175
4223
|
*/
|
|
4224
|
+
var Timer = require_createLucideIcon.createLucideIcon("timer", [
|
|
4225
|
+
["line", {
|
|
4226
|
+
x1: "10",
|
|
4227
|
+
x2: "14",
|
|
4228
|
+
y1: "2",
|
|
4229
|
+
y2: "2",
|
|
4230
|
+
key: "14vaq8"
|
|
4231
|
+
}],
|
|
4232
|
+
["line", {
|
|
4233
|
+
x1: "12",
|
|
4234
|
+
x2: "15",
|
|
4235
|
+
y1: "14",
|
|
4236
|
+
y2: "11",
|
|
4237
|
+
key: "17fdiu"
|
|
4238
|
+
}],
|
|
4239
|
+
["circle", {
|
|
4240
|
+
cx: "12",
|
|
4241
|
+
cy: "14",
|
|
4242
|
+
r: "8",
|
|
4243
|
+
key: "1e1u0o"
|
|
4244
|
+
}]
|
|
4245
|
+
]);
|
|
4246
|
+
/**
|
|
4247
|
+
* @license lucide-react v0.576.0 - ISC
|
|
4248
|
+
*
|
|
4249
|
+
* This source code is licensed under the ISC license.
|
|
4250
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
4251
|
+
*/
|
|
4176
4252
|
var ToggleLeft = require_createLucideIcon.createLucideIcon("toggle-left", [["circle", {
|
|
4177
4253
|
cx: "9",
|
|
4178
4254
|
cy: "12",
|
|
@@ -5275,6 +5351,11 @@ var DEVICE_TYPE_META = {
|
|
|
5275
5351
|
icon: Bone,
|
|
5276
5352
|
labelKey: "uiLibrary.deviceMeta.type.petFeeder",
|
|
5277
5353
|
labelFallback: "Pet feeder"
|
|
5354
|
+
},
|
|
5355
|
+
[_camstack_types.DeviceType.Dishwasher]: {
|
|
5356
|
+
icon: Waves,
|
|
5357
|
+
labelKey: "uiLibrary.deviceMeta.type.dishwasher",
|
|
5358
|
+
labelFallback: "Dishwasher"
|
|
5278
5359
|
}
|
|
5279
5360
|
};
|
|
5280
5361
|
var DEVICE_ROLE_META = {
|
|
@@ -5756,7 +5837,7 @@ function lucideExportName(key) {
|
|
|
5756
5837
|
function loadIconGlyphs() {
|
|
5757
5838
|
if (fullTable !== void 0) return Promise.resolve();
|
|
5758
5839
|
if (failure !== void 0) return Promise.reject(failure);
|
|
5759
|
-
pending ??= Promise.resolve().then(() => require("./icons-
|
|
5840
|
+
pending ??= Promise.resolve().then(() => require("./icons-B1AFXUaZ.cjs")).then((module) => {
|
|
5760
5841
|
fullTable = module;
|
|
5761
5842
|
publish();
|
|
5762
5843
|
}, (error) => {
|
|
@@ -10931,6 +11012,24 @@ var useDeviceStateListClaims = trpc.deviceState.listClaims.useQuery;
|
|
|
10931
11012
|
var useDeviceStateGetNativeCapSlice = trpc.deviceState.getNativeCapSlice.useQuery;
|
|
10932
11013
|
/** Generated alias around `trpc.deviceStatus.getStatus.useQuery`. */
|
|
10933
11014
|
var useDeviceStatusGetStatus = trpc.deviceStatus.getStatus.useQuery;
|
|
11015
|
+
/** Generated alias around `trpc.dishwasher.start.useMutation`. */
|
|
11016
|
+
var useDishwasherStart = trpc.dishwasher.start.useMutation;
|
|
11017
|
+
/** Generated alias around `trpc.dishwasher.pause.useMutation`. */
|
|
11018
|
+
var useDishwasherPause = trpc.dishwasher.pause.useMutation;
|
|
11019
|
+
/** Generated alias around `trpc.dishwasher.stop.useMutation`. */
|
|
11020
|
+
var useDishwasherStop = trpc.dishwasher.stop.useMutation;
|
|
11021
|
+
/** Generated alias around `trpc.dishwasher.setProgram.useMutation`. */
|
|
11022
|
+
var useDishwasherSetProgram = trpc.dishwasher.setProgram.useMutation;
|
|
11023
|
+
/** Generated alias around `trpc.dishwasher.setPower.useMutation`. */
|
|
11024
|
+
var useDishwasherSetPower = trpc.dishwasher.setPower.useMutation;
|
|
11025
|
+
/** Generated alias around `trpc.dishwasher.setHalfLoad.useMutation`. */
|
|
11026
|
+
var useDishwasherSetHalfLoad = trpc.dishwasher.setHalfLoad.useMutation;
|
|
11027
|
+
/** Generated alias around `trpc.dishwasher.setHygienePlus.useMutation`. */
|
|
11028
|
+
var useDishwasherSetHygienePlus = trpc.dishwasher.setHygienePlus.useMutation;
|
|
11029
|
+
/** Generated alias around `trpc.dishwasher.setOption.useMutation`. */
|
|
11030
|
+
var useDishwasherSetOption = trpc.dishwasher.setOption.useMutation;
|
|
11031
|
+
/** Generated alias around `trpc.dishwasher.getStatus.useQuery`. */
|
|
11032
|
+
var useDishwasherGetStatus = trpc.dishwasher.getStatus.useQuery;
|
|
10934
11033
|
/** Generated alias around `trpc.doorbell.getStatus.useQuery`. */
|
|
10935
11034
|
var useDoorbellGetStatus = trpc.doorbell.getStatus.useQuery;
|
|
10936
11035
|
/** Generated alias around `trpc.enumSensor.getStatus.useQuery`. */
|
|
@@ -10985,6 +11084,8 @@ var useFeatureProbeGetStatus = trpc.featureProbe.getStatus.useQuery;
|
|
|
10985
11084
|
var useFloodGetStatus = trpc.flood.getStatus.useQuery;
|
|
10986
11085
|
/** Generated alias around `trpc.gas.getStatus.useQuery`. */
|
|
10987
11086
|
var useGasGetStatus = trpc.gas.getStatus.useQuery;
|
|
11087
|
+
/** Generated alias around `trpc.heatingValve.getStatus.useQuery`. */
|
|
11088
|
+
var useHeatingValveGetStatus = trpc.heatingValve.getStatus.useQuery;
|
|
10988
11089
|
/** Generated alias around `trpc.humidifier.setOn.useMutation`. */
|
|
10989
11090
|
var useHumidifierSetOn = trpc.humidifier.setOn.useMutation;
|
|
10990
11091
|
/** Generated alias around `trpc.humidifier.setTargetHumidity.useMutation`. */
|
|
@@ -14135,7 +14236,7 @@ function ConfigNotificationTargetMultiSelectFieldView({ field, value, onChange,
|
|
|
14135
14236
|
const kindsQuery = useNotificationOutputListTargetKinds({});
|
|
14136
14237
|
const targets = (0, react$1.useMemo)(() => toTargetOptions(targetsQuery.data), [targetsQuery.data]);
|
|
14137
14238
|
const kindIconUrls = (0, react$1.useMemo)(() => buildKindIconMap(kindsQuery.data), [kindsQuery.data]);
|
|
14138
|
-
const selected = Array.isArray(value) ? value.filter((v) => typeof v === "string") : typeof value === "string" && value.length > 0 ? [value] : [];
|
|
14239
|
+
const selected = Array.isArray(value) ? value.filter((v) => typeof v === "string") : typeof value === "string" && value.length > 0 ? value.includes(",") ? value.split(",").map((id) => id.trim()).filter((id) => id.length > 0) : [value] : [];
|
|
14139
14240
|
const pending = targetsQuery.isPending || kindsQuery.isPending;
|
|
14140
14241
|
const error = targetsQuery.isError ? targetsQuery.error : kindsQuery.isError ? kindsQuery.error : null;
|
|
14141
14242
|
if (pending) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
|
|
@@ -19498,11 +19599,11 @@ function useDeviceSnapshotImage(deviceId, options = {}) {
|
|
|
19498
19599
|
//#region src/composites/cap-settings/camera-grid-layout.ts
|
|
19499
19600
|
/** Smallest rectangle the editor will produce — ffmpeg refuses a zero-area crop. */
|
|
19500
19601
|
var MIN_EXTENT = .02;
|
|
19501
|
-
function isRecord(value) {
|
|
19602
|
+
function isRecord$2(value) {
|
|
19502
19603
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19503
19604
|
}
|
|
19504
19605
|
function readRect(value) {
|
|
19505
|
-
if (!isRecord(value)) return null;
|
|
19606
|
+
if (!isRecord$2(value)) return null;
|
|
19506
19607
|
const { x, y, width, height } = value;
|
|
19507
19608
|
if (typeof x !== "number" || typeof y !== "number" || typeof width !== "number" || typeof height !== "number") return null;
|
|
19508
19609
|
if (x < 0 || y < 0 || width <= 0 || height <= 0) return null;
|
|
@@ -19515,7 +19616,7 @@ function readRect(value) {
|
|
|
19515
19616
|
};
|
|
19516
19617
|
}
|
|
19517
19618
|
function readCell(value) {
|
|
19518
|
-
if (!isRecord(value)) return null;
|
|
19619
|
+
if (!isRecord$2(value)) return null;
|
|
19519
19620
|
const deviceId = value["deviceId"];
|
|
19520
19621
|
if (typeof deviceId !== "number" || !Number.isInteger(deviceId)) return null;
|
|
19521
19622
|
const source = readRect(value["source"]);
|
|
@@ -19528,7 +19629,7 @@ function readCell(value) {
|
|
|
19528
19629
|
};
|
|
19529
19630
|
}
|
|
19530
19631
|
function readProfileOffer(value) {
|
|
19531
|
-
if (!isRecord(value)) return null;
|
|
19632
|
+
if (!isRecord$2(value)) return null;
|
|
19532
19633
|
const profile = value["profile"];
|
|
19533
19634
|
const offered = value["offered"];
|
|
19534
19635
|
const missing = value["missingSources"];
|
|
@@ -19557,7 +19658,7 @@ function readProfileOffer(value) {
|
|
|
19557
19658
|
*/
|
|
19558
19659
|
function parseGridView(raw) {
|
|
19559
19660
|
if (raw === null) return null;
|
|
19560
|
-
if (!isRecord(raw)) return "unreadable";
|
|
19661
|
+
if (!isRecord$2(raw)) return "unreadable";
|
|
19561
19662
|
const { instanceId, deviceId, name, fps, cells, profiles } = raw;
|
|
19562
19663
|
if (typeof instanceId !== "string" || typeof deviceId !== "number" || typeof name !== "string" || typeof fps !== "number" || !Array.isArray(cells) || !Array.isArray(profiles)) return "unreadable";
|
|
19563
19664
|
const parsed = [];
|
|
@@ -25714,7 +25815,7 @@ function mergeAvailabilitySpans(ranges) {
|
|
|
25714
25815
|
* significant digits (binary prefixes, matching recording sizes elsewhere).
|
|
25715
25816
|
* KB and up render one decimal under 100, none above; bytes never get a decimal.
|
|
25716
25817
|
*/
|
|
25717
|
-
function formatBytes(bytes) {
|
|
25818
|
+
function formatBytes$1(bytes) {
|
|
25718
25819
|
if (!Number.isFinite(bytes) || bytes < 0) return "—";
|
|
25719
25820
|
if (bytes < 1024) return `${Math.round(bytes)} B`;
|
|
25720
25821
|
const units = [
|
|
@@ -26247,7 +26348,7 @@ function RecordingPanel({ deviceId }) {
|
|
|
26247
26348
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetMetricCard, {
|
|
26248
26349
|
label: t("uiLibrary.recordingPanel.storage", "Storage"),
|
|
26249
26350
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HardDrive, { className: "h-3.5 w-3.5" }),
|
|
26250
|
-
children: status ? formatBytes(status.storageBytes) : "—"
|
|
26351
|
+
children: status ? formatBytes$1(status.storageBytes) : "—"
|
|
26251
26352
|
}),
|
|
26252
26353
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetMetricCard, {
|
|
26253
26354
|
label: t("uiLibrary.recordingPanel.recorded24h", "Recorded (24h)"),
|
|
@@ -28815,7 +28916,7 @@ function AddonActionSelectField({ field, value, values, onChange, disabled, onAc
|
|
|
28815
28916
|
})()
|
|
28816
28917
|
});
|
|
28817
28918
|
}
|
|
28818
|
-
function AddonActionButtonField({ field, values, disabled, onAction }) {
|
|
28919
|
+
function AddonActionButtonField({ field, values, disabled, onAction, onChange, onChangeMany }) {
|
|
28819
28920
|
const t = useUiText();
|
|
28820
28921
|
const [loading, setLoading] = (0, react$1.useState)(false);
|
|
28821
28922
|
const [status, setStatus] = (0, react$1.useState)(null);
|
|
@@ -28847,6 +28948,17 @@ function AddonActionButtonField({ field, values, disabled, onAction }) {
|
|
|
28847
28948
|
try {
|
|
28848
28949
|
const result = await onAction(field.action, field.key, params);
|
|
28849
28950
|
const resultRecord = typeof result === "object" && result !== null ? result : null;
|
|
28951
|
+
if (resultRecord !== null && field.assignResult && (onChange || onChangeMany)) {
|
|
28952
|
+
const patch = {};
|
|
28953
|
+
for (const [formKey, resultKey] of Object.entries(field.assignResult)) {
|
|
28954
|
+
const assigned = resultRecord[resultKey];
|
|
28955
|
+
if (typeof assigned === "string") patch[formKey] = assigned;
|
|
28956
|
+
}
|
|
28957
|
+
if (Object.keys(patch).length > 0) {
|
|
28958
|
+
if (onChangeMany) onChangeMany(patch);
|
|
28959
|
+
else if (onChange) for (const [formKey, assigned] of Object.entries(patch)) onChange(formKey, assigned);
|
|
28960
|
+
}
|
|
28961
|
+
}
|
|
28850
28962
|
setStatus({
|
|
28851
28963
|
kind: "ok",
|
|
28852
28964
|
text: field.successMessage ? field.successMessage.replace(/\{(\w+)\}/g, (_, k) => {
|
|
@@ -29195,7 +29307,7 @@ function isFieldVisible(field, values, allFields) {
|
|
|
29195
29307
|
if (condition.notIncludes !== void 0) return !Array.isArray(condVal) || !condVal.includes(condition.notIncludes);
|
|
29196
29308
|
return true;
|
|
29197
29309
|
}
|
|
29198
|
-
function FormField({ field, values, onChange, disabled, translationFn, onTestField, externalProbe, allFields, onAction }) {
|
|
29310
|
+
function FormField({ field, values, onChange, onChangeMany, disabled, translationFn, onTestField, externalProbe, allFields, onAction }) {
|
|
29199
29311
|
if (hasMultiple(field)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MultipleFieldWrapper, {
|
|
29200
29312
|
field,
|
|
29201
29313
|
values,
|
|
@@ -29431,7 +29543,9 @@ function FormField({ field, values, onChange, disabled, translationFn, onTestFie
|
|
|
29431
29543
|
field,
|
|
29432
29544
|
values,
|
|
29433
29545
|
disabled,
|
|
29434
|
-
onAction
|
|
29546
|
+
onAction,
|
|
29547
|
+
onChange,
|
|
29548
|
+
onChangeMany
|
|
29435
29549
|
});
|
|
29436
29550
|
case "device-action-button": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceActionButtonField, {
|
|
29437
29551
|
field,
|
|
@@ -29608,17 +29722,7 @@ function applyValuesToSchema(schema, nextValues) {
|
|
|
29608
29722
|
};
|
|
29609
29723
|
}
|
|
29610
29724
|
function stripValues(schema) {
|
|
29611
|
-
return
|
|
29612
|
-
...schema,
|
|
29613
|
-
sections: schema.sections.map((s) => ({
|
|
29614
|
-
...s,
|
|
29615
|
-
fields: s.fields.map((f) => {
|
|
29616
|
-
if (!isValueBearingField(f)) return f;
|
|
29617
|
-
const { value: _drop, ...rest } = f;
|
|
29618
|
-
return rest;
|
|
29619
|
-
})
|
|
29620
|
-
}))
|
|
29621
|
-
};
|
|
29725
|
+
return (0, _camstack_types.dehydrateSchema)(schema);
|
|
29622
29726
|
}
|
|
29623
29727
|
function AddonGlobalSettingsForm({ trpc, addonId, nodeId, title, disabled, onAfterChange, refreshToken, sectionIds, bare = false, collapsible, defaultOpen, mode, onOverrideChange }) {
|
|
29624
29728
|
const t = useUiText();
|
|
@@ -42924,6 +43028,19 @@ function FanPanel({ trpc, deviceId, features, layout, optimistic }) {
|
|
|
42924
43028
|
}
|
|
42925
43029
|
//#endregion
|
|
42926
43030
|
//#region src/composites/device-controls/climate-panel.tsx
|
|
43031
|
+
/** Step size for target ± buttons; defaults to 1 °C when unset or invalid. */
|
|
43032
|
+
function resolveClimateTempStep(tempStep) {
|
|
43033
|
+
if (typeof tempStep === "number" && Number.isFinite(tempStep) && tempStep > 0) return tempStep;
|
|
43034
|
+
return 1;
|
|
43035
|
+
}
|
|
43036
|
+
function nextClimateTarget(current, deltaSteps, display) {
|
|
43037
|
+
let next = current + deltaSteps * resolveClimateTempStep(display.tempStep);
|
|
43038
|
+
const min = display.minTemp;
|
|
43039
|
+
const max = display.maxTemp;
|
|
43040
|
+
if (typeof min === "number" && Number.isFinite(min)) next = Math.max(next, min);
|
|
43041
|
+
if (typeof max === "number" && Number.isFinite(max)) next = Math.min(next, max);
|
|
43042
|
+
return next;
|
|
43043
|
+
}
|
|
42927
43044
|
/**
|
|
42928
43045
|
* Exhaustive colour map — one entry per HvacMode. Build will fail if a
|
|
42929
43046
|
* mode is added to the enum and not reflected here (Record<HvacMode, ...>
|
|
@@ -43004,7 +43121,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
43004
43121
|
onClick: (e) => {
|
|
43005
43122
|
e.stopPropagation();
|
|
43006
43123
|
if (!busy && display.target !== null) {
|
|
43007
|
-
const next = display.target -
|
|
43124
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
43008
43125
|
applyOptimistic({ target: next });
|
|
43009
43126
|
cap.setTarget({ target: next });
|
|
43010
43127
|
}
|
|
@@ -43018,7 +43135,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
43018
43135
|
onClick: (e) => {
|
|
43019
43136
|
e.stopPropagation();
|
|
43020
43137
|
if (!busy && display.target !== null) {
|
|
43021
|
-
const next = display.target
|
|
43138
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
43022
43139
|
applyOptimistic({ target: next });
|
|
43023
43140
|
cap.setTarget({ target: next });
|
|
43024
43141
|
}
|
|
@@ -43073,7 +43190,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
43073
43190
|
onClick: (e) => {
|
|
43074
43191
|
e.stopPropagation();
|
|
43075
43192
|
if (!busy && display.target !== null) {
|
|
43076
|
-
const next = display.target -
|
|
43193
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
43077
43194
|
applyOptimistic({ target: next });
|
|
43078
43195
|
cap.setTarget({ target: next });
|
|
43079
43196
|
}
|
|
@@ -43088,7 +43205,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
43088
43205
|
onClick: (e) => {
|
|
43089
43206
|
e.stopPropagation();
|
|
43090
43207
|
if (!busy && display.target !== null) {
|
|
43091
|
-
const next = display.target
|
|
43208
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
43092
43209
|
applyOptimistic({ target: next });
|
|
43093
43210
|
cap.setTarget({ target: next });
|
|
43094
43211
|
}
|
|
@@ -43746,7 +43863,8 @@ var CONTROL_CAP_NAMES = [
|
|
|
43746
43863
|
"climate-control",
|
|
43747
43864
|
"media-player",
|
|
43748
43865
|
"control",
|
|
43749
|
-
"pet-feeder"
|
|
43866
|
+
"pet-feeder",
|
|
43867
|
+
"dishwasher"
|
|
43750
43868
|
];
|
|
43751
43869
|
//#endregion
|
|
43752
43870
|
//#region src/composites/device-controls/popover-row-action.tsx
|
|
@@ -48557,7 +48675,405 @@ function PetFeederHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
48557
48675
|
});
|
|
48558
48676
|
}
|
|
48559
48677
|
//#endregion
|
|
48678
|
+
//#region src/composites/device-controls/dishwasher-control.tsx
|
|
48679
|
+
/**
|
|
48680
|
+
* Dishwasher controls for a `DeviceType.Dishwasher` device.
|
|
48681
|
+
*
|
|
48682
|
+
* Reads the `dishwasher` cap status slice and renders inline + hero surfaces
|
|
48683
|
+
* mirroring vacuum-control / pet-feeder patterns.
|
|
48684
|
+
*/
|
|
48685
|
+
function dishwasherStateMeta(state, t = defaultUiText) {
|
|
48686
|
+
switch (state) {
|
|
48687
|
+
case "running": return {
|
|
48688
|
+
label: t("uiLibrary.dishwasher.running", "Running"),
|
|
48689
|
+
tone: "text-primary"
|
|
48690
|
+
};
|
|
48691
|
+
case "paused": return {
|
|
48692
|
+
label: t("uiLibrary.dishwasher.paused", "Paused"),
|
|
48693
|
+
tone: "text-warning"
|
|
48694
|
+
};
|
|
48695
|
+
case "finished": return {
|
|
48696
|
+
label: t("uiLibrary.dishwasher.finished", "Finished"),
|
|
48697
|
+
tone: "text-success"
|
|
48698
|
+
};
|
|
48699
|
+
case "error": return {
|
|
48700
|
+
label: t("uiLibrary.dishwasher.error", "Error"),
|
|
48701
|
+
tone: "text-destructive"
|
|
48702
|
+
};
|
|
48703
|
+
case "unavailable": return {
|
|
48704
|
+
label: t("uiLibrary.dishwasher.unavailable", "Unavailable"),
|
|
48705
|
+
tone: "text-foreground-subtle"
|
|
48706
|
+
};
|
|
48707
|
+
case "idle": return {
|
|
48708
|
+
label: t("uiLibrary.dishwasher.idle", "Idle"),
|
|
48709
|
+
tone: "text-foreground-subtle"
|
|
48710
|
+
};
|
|
48711
|
+
case void 0: return {
|
|
48712
|
+
label: t("uiLibrary.dishwasher.unknown", "Unknown"),
|
|
48713
|
+
tone: "text-foreground-subtle"
|
|
48714
|
+
};
|
|
48715
|
+
}
|
|
48716
|
+
}
|
|
48717
|
+
function consumableTone(level) {
|
|
48718
|
+
switch (level) {
|
|
48719
|
+
case "ok": return "text-success";
|
|
48720
|
+
case "low": return "text-warning";
|
|
48721
|
+
case "empty": return "text-destructive";
|
|
48722
|
+
case null: return "text-foreground-subtle";
|
|
48723
|
+
}
|
|
48724
|
+
}
|
|
48725
|
+
function useDishwasher(trpc, deviceId) {
|
|
48726
|
+
return useDeviceCapability({
|
|
48727
|
+
trpc,
|
|
48728
|
+
deviceId,
|
|
48729
|
+
cap: (d) => d.dishwasher,
|
|
48730
|
+
slice: (d) => d.state.dishwasher
|
|
48731
|
+
});
|
|
48732
|
+
}
|
|
48733
|
+
function DishwasherIconButton({ label, busy, active, onClick, children }) {
|
|
48734
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
48735
|
+
type: "button",
|
|
48736
|
+
"aria-label": label,
|
|
48737
|
+
title: label,
|
|
48738
|
+
disabled: busy,
|
|
48739
|
+
onClick: (e) => {
|
|
48740
|
+
e.stopPropagation();
|
|
48741
|
+
onClick();
|
|
48742
|
+
},
|
|
48743
|
+
className: cn("inline-flex h-5 w-5 items-center justify-center rounded text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors", active && "bg-primary text-primary-foreground", busy && "opacity-40 cursor-not-allowed"),
|
|
48744
|
+
children
|
|
48745
|
+
});
|
|
48746
|
+
}
|
|
48747
|
+
function ConsumableBadge({ label, level }) {
|
|
48748
|
+
if (level === null) return null;
|
|
48749
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48750
|
+
className: cn("text-xs font-medium capitalize", consumableTone(level)),
|
|
48751
|
+
children: [
|
|
48752
|
+
label,
|
|
48753
|
+
": ",
|
|
48754
|
+
level
|
|
48755
|
+
]
|
|
48756
|
+
});
|
|
48757
|
+
}
|
|
48758
|
+
function DishwasherGraphic({ state, progressPercent }) {
|
|
48759
|
+
const running = state === "running";
|
|
48760
|
+
const pct = progressPercent != null ? Math.max(0, Math.min(100, progressPercent)) : running ? 40 : 0;
|
|
48761
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48762
|
+
"aria-hidden": "true",
|
|
48763
|
+
className: "relative w-28 h-36 rounded border-2 border-border bg-gradient-to-b from-surface-hover to-surface overflow-hidden flex-shrink-0",
|
|
48764
|
+
style: { boxShadow: "inset 0 0 0 2px rgba(255,255,255,.06)" },
|
|
48765
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48766
|
+
className: cn("absolute inset-x-0 bottom-0 bg-primary/25 transition-[height] duration-500", running && "animate-pulse"),
|
|
48767
|
+
style: { height: `${pct}%` }
|
|
48768
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48769
|
+
className: "absolute inset-0 flex items-center justify-center text-foreground-subtle",
|
|
48770
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Waves, { className: "h-10 w-10 opacity-70" })
|
|
48771
|
+
})]
|
|
48772
|
+
});
|
|
48773
|
+
}
|
|
48774
|
+
function DishwasherPanel({ trpc, deviceId, layout, optimistic }) {
|
|
48775
|
+
const t = useUiText();
|
|
48776
|
+
const { cap, slice, available } = useDishwasher(trpc, deviceId);
|
|
48777
|
+
const { display, applyOptimistic } = useOptimisticSlice(slice, { enabled: optimistic ?? true });
|
|
48778
|
+
if (cap === void 0) return null;
|
|
48779
|
+
if (display === void 0) return null;
|
|
48780
|
+
const busy = !available;
|
|
48781
|
+
const meta = dishwasherStateMeta(display.state, t);
|
|
48782
|
+
const running = display.state === "running";
|
|
48783
|
+
const paused = display.state === "paused";
|
|
48784
|
+
const press = (fn, patch) => {
|
|
48785
|
+
if (busy) return;
|
|
48786
|
+
applyOptimistic(patch);
|
|
48787
|
+
fn();
|
|
48788
|
+
};
|
|
48789
|
+
if (layout === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48790
|
+
className: "inline-flex items-center gap-1",
|
|
48791
|
+
children: [
|
|
48792
|
+
display.doorOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48793
|
+
className: "inline-flex text-warning",
|
|
48794
|
+
title: t("uiLibrary.dishwasher.doorOpen", "Door open"),
|
|
48795
|
+
"aria-label": t("uiLibrary.dishwasher.doorOpen", "Door open"),
|
|
48796
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" })
|
|
48797
|
+
}),
|
|
48798
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48799
|
+
className: cn("text-xs font-medium tabular-nums", meta.tone),
|
|
48800
|
+
children: meta.label
|
|
48801
|
+
}),
|
|
48802
|
+
display.remainingMinutes != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48803
|
+
className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums",
|
|
48804
|
+
children: [
|
|
48805
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }),
|
|
48806
|
+
display.remainingMinutes,
|
|
48807
|
+
"m"
|
|
48808
|
+
]
|
|
48809
|
+
}),
|
|
48810
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48811
|
+
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
48812
|
+
busy: busy || !display.remoteStartAllowed,
|
|
48813
|
+
active: running,
|
|
48814
|
+
onClick: () => press(() => cap.start({}), {
|
|
48815
|
+
state: "running",
|
|
48816
|
+
remoteStartAllowed: false
|
|
48817
|
+
}),
|
|
48818
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-3 w-3" })
|
|
48819
|
+
}),
|
|
48820
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48821
|
+
label: t("uiLibrary.dishwasher.pause", "Pause"),
|
|
48822
|
+
busy: busy || !running,
|
|
48823
|
+
active: paused,
|
|
48824
|
+
onClick: () => press(() => cap.pause({}), { state: "paused" }),
|
|
48825
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-3 w-3" })
|
|
48826
|
+
})
|
|
48827
|
+
]
|
|
48828
|
+
});
|
|
48829
|
+
const programs = display.availablePrograms;
|
|
48830
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48831
|
+
className: "flex flex-col gap-3 rounded-lg border border-border bg-surface p-4",
|
|
48832
|
+
children: [
|
|
48833
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48834
|
+
className: "flex items-start gap-4",
|
|
48835
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherGraphic, {
|
|
48836
|
+
state: display.state,
|
|
48837
|
+
progressPercent: display.progressPercent
|
|
48838
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48839
|
+
className: "flex min-w-0 flex-1 flex-col gap-2",
|
|
48840
|
+
children: [
|
|
48841
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48842
|
+
className: "flex flex-wrap items-center gap-2",
|
|
48843
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48844
|
+
className: cn("text-3xl font-semibold", meta.tone),
|
|
48845
|
+
children: meta.label
|
|
48846
|
+
}), !display.powerOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48847
|
+
className: "text-xs font-medium text-foreground-subtle",
|
|
48848
|
+
children: t("uiLibrary.dishwasher.powerOff", "Power off")
|
|
48849
|
+
})]
|
|
48850
|
+
}),
|
|
48851
|
+
display.program != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48852
|
+
className: "text-sm text-foreground-subtle",
|
|
48853
|
+
children: display.program
|
|
48854
|
+
}),
|
|
48855
|
+
display.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48856
|
+
className: "flex items-center gap-2",
|
|
48857
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48858
|
+
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
48859
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48860
|
+
className: "h-full bg-primary transition-[width] duration-500",
|
|
48861
|
+
style: { width: `${Math.max(0, Math.min(100, display.progressPercent))}%` }
|
|
48862
|
+
})
|
|
48863
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48864
|
+
className: "tabular-nums text-xs text-foreground-subtle",
|
|
48865
|
+
children: [Math.round(display.progressPercent), "%"]
|
|
48866
|
+
})]
|
|
48867
|
+
}),
|
|
48868
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48869
|
+
className: "flex flex-wrap gap-3",
|
|
48870
|
+
children: [
|
|
48871
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableBadge, {
|
|
48872
|
+
label: t("uiLibrary.dishwasher.salt", "Salt"),
|
|
48873
|
+
level: display.saltLevel
|
|
48874
|
+
}),
|
|
48875
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableBadge, {
|
|
48876
|
+
label: t("uiLibrary.dishwasher.rinseAid", "Rinse aid"),
|
|
48877
|
+
level: display.rinseAidLevel
|
|
48878
|
+
}),
|
|
48879
|
+
display.doorOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48880
|
+
className: "inline-flex items-center gap-1 text-xs text-warning",
|
|
48881
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.doorOpen", "Door open")]
|
|
48882
|
+
})
|
|
48883
|
+
]
|
|
48884
|
+
}),
|
|
48885
|
+
display.errorCode != null && display.errorCode !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48886
|
+
className: "inline-flex items-center gap-1 text-xs text-destructive",
|
|
48887
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: display.errorLabel ?? t("uiLibrary.dishwasher.errorCode", "Error {{code}}", { code: display.errorCode }) })]
|
|
48888
|
+
}),
|
|
48889
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48890
|
+
className: "flex flex-wrap items-center gap-2",
|
|
48891
|
+
children: [
|
|
48892
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48893
|
+
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
48894
|
+
busy: busy || !display.remoteStartAllowed,
|
|
48895
|
+
active: running,
|
|
48896
|
+
onClick: () => press(() => cap.start({}), {
|
|
48897
|
+
state: "running",
|
|
48898
|
+
remoteStartAllowed: false
|
|
48899
|
+
}),
|
|
48900
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-4 w-4" })
|
|
48901
|
+
}),
|
|
48902
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48903
|
+
label: t("uiLibrary.dishwasher.pause", "Pause"),
|
|
48904
|
+
busy: busy || !running,
|
|
48905
|
+
active: paused,
|
|
48906
|
+
onClick: () => press(() => cap.pause({}), { state: "paused" }),
|
|
48907
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-4 w-4" })
|
|
48908
|
+
}),
|
|
48909
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48910
|
+
label: t("uiLibrary.dishwasher.stop", "Stop"),
|
|
48911
|
+
busy,
|
|
48912
|
+
active: false,
|
|
48913
|
+
onClick: () => press(() => cap.stop({}), {
|
|
48914
|
+
state: "idle",
|
|
48915
|
+
remainingMinutes: null
|
|
48916
|
+
}),
|
|
48917
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Square, { className: "h-3.5 w-3.5" })
|
|
48918
|
+
}),
|
|
48919
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48920
|
+
label: t("uiLibrary.dishwasher.power", "Power"),
|
|
48921
|
+
busy,
|
|
48922
|
+
active: display.powerOn,
|
|
48923
|
+
onClick: () => {
|
|
48924
|
+
const next = !display.powerOn;
|
|
48925
|
+
press(() => cap.setPower({ on: next }), { powerOn: next });
|
|
48926
|
+
},
|
|
48927
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Power, { className: "h-4 w-4" })
|
|
48928
|
+
})
|
|
48929
|
+
]
|
|
48930
|
+
})
|
|
48931
|
+
]
|
|
48932
|
+
})]
|
|
48933
|
+
}),
|
|
48934
|
+
programs.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
48935
|
+
className: "flex items-center gap-2 text-xs text-foreground-subtle",
|
|
48936
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48937
|
+
className: "w-16 shrink-0",
|
|
48938
|
+
children: t("uiLibrary.dishwasher.program", "Program")
|
|
48939
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
48940
|
+
"aria-label": t("uiLibrary.dishwasher.program", "Program"),
|
|
48941
|
+
value: display.program ?? "",
|
|
48942
|
+
disabled: busy,
|
|
48943
|
+
onChange: (e) => {
|
|
48944
|
+
const program = e.target.value;
|
|
48945
|
+
if (!program) return;
|
|
48946
|
+
applyOptimistic({ program });
|
|
48947
|
+
cap.setProgram({ program });
|
|
48948
|
+
},
|
|
48949
|
+
onClick: (e) => {
|
|
48950
|
+
e.stopPropagation();
|
|
48951
|
+
},
|
|
48952
|
+
className: "flex-1 rounded border border-border bg-surface px-2 py-1 text-foreground disabled:opacity-50",
|
|
48953
|
+
children: [display.program == null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
48954
|
+
value: "",
|
|
48955
|
+
children: "—"
|
|
48956
|
+
}), programs.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
48957
|
+
value: p,
|
|
48958
|
+
children: p
|
|
48959
|
+
}, p))]
|
|
48960
|
+
})]
|
|
48961
|
+
}),
|
|
48962
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48963
|
+
className: "flex flex-wrap gap-4 text-xs",
|
|
48964
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
48965
|
+
label: t("uiLibrary.dishwasher.halfLoad", "Half load"),
|
|
48966
|
+
checked: display.halfLoad,
|
|
48967
|
+
busy,
|
|
48968
|
+
onChange: (on) => {
|
|
48969
|
+
applyOptimistic({ halfLoad: on });
|
|
48970
|
+
cap.setHalfLoad({ on });
|
|
48971
|
+
}
|
|
48972
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
48973
|
+
label: t("uiLibrary.dishwasher.hygienePlus", "Hygiene plus"),
|
|
48974
|
+
checked: display.hygienePlus,
|
|
48975
|
+
busy,
|
|
48976
|
+
onChange: (on) => {
|
|
48977
|
+
applyOptimistic({ hygienePlus: on });
|
|
48978
|
+
cap.setHygienePlus({ on });
|
|
48979
|
+
}
|
|
48980
|
+
})]
|
|
48981
|
+
})
|
|
48982
|
+
]
|
|
48983
|
+
});
|
|
48984
|
+
}
|
|
48985
|
+
function ToggleRow({ label, checked, busy, onChange }) {
|
|
48986
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
48987
|
+
className: "inline-flex items-center gap-2 cursor-pointer",
|
|
48988
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
48989
|
+
type: "checkbox",
|
|
48990
|
+
checked,
|
|
48991
|
+
disabled: busy,
|
|
48992
|
+
onChange: (e) => {
|
|
48993
|
+
onChange(e.target.checked);
|
|
48994
|
+
},
|
|
48995
|
+
onClick: (e) => {
|
|
48996
|
+
e.stopPropagation();
|
|
48997
|
+
},
|
|
48998
|
+
className: "rounded border-border"
|
|
48999
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
|
|
49000
|
+
});
|
|
49001
|
+
}
|
|
49002
|
+
function DishwasherInlineControl(props) {
|
|
49003
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherPanel, {
|
|
49004
|
+
...props,
|
|
49005
|
+
layout: "compact"
|
|
49006
|
+
});
|
|
49007
|
+
}
|
|
49008
|
+
function DishwasherHeroCard(props) {
|
|
49009
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherPanel, {
|
|
49010
|
+
...props,
|
|
49011
|
+
layout: "full"
|
|
49012
|
+
});
|
|
49013
|
+
}
|
|
49014
|
+
//#endregion
|
|
49015
|
+
//#region src/composites/device-controls/heating-valve-readout.tsx
|
|
49016
|
+
function formatHeatingValvePosition(position) {
|
|
49017
|
+
return position !== null ? `${position}%` : "—";
|
|
49018
|
+
}
|
|
49019
|
+
function HeatingValveReadout({ trpc, deviceId, layout }) {
|
|
49020
|
+
const t = useUiText();
|
|
49021
|
+
const { cap, slice } = useDeviceCapability({
|
|
49022
|
+
trpc,
|
|
49023
|
+
deviceId,
|
|
49024
|
+
cap: (d) => d.heatingValve,
|
|
49025
|
+
slice: (d) => d.state.heatingValve
|
|
49026
|
+
});
|
|
49027
|
+
if (cap === void 0 || slice === void 0) return null;
|
|
49028
|
+
const label = t("uiLibrary.heatingValveReadout.valve", "Valve");
|
|
49029
|
+
const value = formatHeatingValvePosition(slice.position);
|
|
49030
|
+
if (layout === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49031
|
+
className: "inline-flex items-center text-xs tabular-nums text-foreground-subtle",
|
|
49032
|
+
"aria-label": `${label} ${value}`,
|
|
49033
|
+
children: value
|
|
49034
|
+
});
|
|
49035
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49036
|
+
className: "flex items-center gap-2 text-xs tabular-nums text-foreground-subtle",
|
|
49037
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49038
|
+
className: "font-medium text-foreground",
|
|
49039
|
+
children: value
|
|
49040
|
+
})]
|
|
49041
|
+
});
|
|
49042
|
+
}
|
|
49043
|
+
//#endregion
|
|
48560
49044
|
//#region src/composites/device-controls/ac-panel.tsx
|
|
49045
|
+
/** Modes that mean the device can cool, dry, or move air — an air conditioner. */
|
|
49046
|
+
var COOLING_MODES = new Set([
|
|
49047
|
+
"cool",
|
|
49048
|
+
"heat_cool",
|
|
49049
|
+
"dry",
|
|
49050
|
+
"fan_only"
|
|
49051
|
+
]);
|
|
49052
|
+
/** A heater offers heat (and maybe auto/off) and none of the cooling modes. */
|
|
49053
|
+
function climateOffersCooling(modes) {
|
|
49054
|
+
return modes.some((mode) => COOLING_MODES.has(mode));
|
|
49055
|
+
}
|
|
49056
|
+
/** Setpoint track bounds. Absent when the integration did not report both ends. */
|
|
49057
|
+
function climateSetpointBounds(status) {
|
|
49058
|
+
const min = status.minTemp;
|
|
49059
|
+
const max = status.maxTemp;
|
|
49060
|
+
if (typeof min !== "number" || typeof max !== "number") return null;
|
|
49061
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) return null;
|
|
49062
|
+
return {
|
|
49063
|
+
min,
|
|
49064
|
+
max
|
|
49065
|
+
};
|
|
49066
|
+
}
|
|
49067
|
+
/** Map a 0–1 drag fraction onto the setpoint, snapped to `tempStep`. */
|
|
49068
|
+
function climateTargetFromFraction(fraction, bounds, tempStep) {
|
|
49069
|
+
const step = resolveClimateTempStep(tempStep);
|
|
49070
|
+
const clamped = Math.min(1, Math.max(0, fraction));
|
|
49071
|
+
const raw = bounds.min + clamped * (bounds.max - bounds.min);
|
|
49072
|
+
const steps = Math.round((raw - bounds.min) / step);
|
|
49073
|
+
const snapped = bounds.min + steps * step;
|
|
49074
|
+
const rounded = Math.round(snapped * 1e3) / 1e3;
|
|
49075
|
+
return Math.min(bounds.max, Math.max(bounds.min, rounded));
|
|
49076
|
+
}
|
|
48561
49077
|
/** AC-appropriate short mode labels. Exhaustive over HvacMode. */
|
|
48562
49078
|
function modeLabel(t, mode) {
|
|
48563
49079
|
switch (mode) {
|
|
@@ -48591,9 +49107,6 @@ var MODE_ACCENT = {
|
|
|
48591
49107
|
dry: "#22d3ee",
|
|
48592
49108
|
off: "#a1a1aa"
|
|
48593
49109
|
};
|
|
48594
|
-
/** Practical AC setpoint range (Celsius) for the target range track. */
|
|
48595
|
-
var TARGET_MIN = 16;
|
|
48596
|
-
var TARGET_MAX = 30;
|
|
48597
49110
|
/** Title-case a snake/lower token for a selector option ('medium_low' → 'Medium
|
|
48598
49111
|
* low', 'turbo' → 'Turbo'). Shared by the fan + preset selectors. */
|
|
48599
49112
|
function fanLabel(token) {
|
|
@@ -48680,7 +49193,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48680
49193
|
onClick: (e) => {
|
|
48681
49194
|
e.stopPropagation();
|
|
48682
49195
|
if (!busy && display.target !== null) {
|
|
48683
|
-
const next = display.target -
|
|
49196
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
48684
49197
|
applyOptimistic({ target: next });
|
|
48685
49198
|
cap.setTarget({ target: next });
|
|
48686
49199
|
}
|
|
@@ -48694,7 +49207,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48694
49207
|
onClick: (e) => {
|
|
48695
49208
|
e.stopPropagation();
|
|
48696
49209
|
if (!busy && display.target !== null) {
|
|
48697
|
-
const next = display.target
|
|
49210
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
48698
49211
|
applyOptimistic({ target: next });
|
|
48699
49212
|
cap.setTarget({ target: next });
|
|
48700
49213
|
}
|
|
@@ -48724,6 +49237,8 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48724
49237
|
});
|
|
48725
49238
|
const accent = MODE_ACCENT[display.mode];
|
|
48726
49239
|
const on = display.mode !== "off";
|
|
49240
|
+
const heatingOnly = !climateOffersCooling(display.availableModes);
|
|
49241
|
+
const setpointBounds = climateSetpointBounds(display);
|
|
48727
49242
|
const CurrentPresetIcon = presetIcon(display.preset);
|
|
48728
49243
|
const boost = display.preset === "turbo";
|
|
48729
49244
|
const period = Math.max(.55, airflowPeriodSec(display.fanMode) * (boost ? .6 : 1));
|
|
@@ -48740,11 +49255,12 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48740
49255
|
children: [
|
|
48741
49256
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48742
49257
|
className: "pointer-events-none absolute inset-0 opacity-60",
|
|
48743
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AirflowViz, {
|
|
49258
|
+
children: heatingOnly && !on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadiatorFins, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AirflowViz, {
|
|
48744
49259
|
mode: display.mode,
|
|
48745
49260
|
accent,
|
|
48746
49261
|
periodSec: period,
|
|
48747
|
-
swaying
|
|
49262
|
+
swaying,
|
|
49263
|
+
forceRising: heatingOnly
|
|
48748
49264
|
})
|
|
48749
49265
|
}),
|
|
48750
49266
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-b from-surface/50 via-transparent to-surface/50" }),
|
|
@@ -48762,6 +49278,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48762
49278
|
children: [
|
|
48763
49279
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeIcon, {
|
|
48764
49280
|
mode: display.mode,
|
|
49281
|
+
heatingOnly,
|
|
48765
49282
|
className: "h-3.5 w-3.5"
|
|
48766
49283
|
}),
|
|
48767
49284
|
modeLabel(t, display.mode),
|
|
@@ -48788,7 +49305,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48788
49305
|
disabled: busy,
|
|
48789
49306
|
onClick: () => {
|
|
48790
49307
|
if (busy || display.target === null) return;
|
|
48791
|
-
const next = display.target -
|
|
49308
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
48792
49309
|
applyOptimistic({ target: next });
|
|
48793
49310
|
cap.setTarget({ target: next });
|
|
48794
49311
|
}
|
|
@@ -48810,19 +49327,29 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48810
49327
|
disabled: busy,
|
|
48811
49328
|
onClick: () => {
|
|
48812
49329
|
if (busy || display.target === null) return;
|
|
48813
|
-
const next = display.target
|
|
49330
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
48814
49331
|
applyOptimistic({ target: next });
|
|
48815
49332
|
cap.setTarget({ target: next });
|
|
48816
49333
|
}
|
|
48817
49334
|
})
|
|
48818
49335
|
]
|
|
48819
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48820
|
-
className: "relative z-10 w-
|
|
49336
|
+
}), setpointBounds !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49337
|
+
className: "relative z-10 w-full px-1",
|
|
48821
49338
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RangeTrack, {
|
|
48822
49339
|
value: display.target,
|
|
48823
|
-
min:
|
|
48824
|
-
max:
|
|
48825
|
-
|
|
49340
|
+
min: setpointBounds.min,
|
|
49341
|
+
max: setpointBounds.max,
|
|
49342
|
+
step: display.tempStep,
|
|
49343
|
+
accent,
|
|
49344
|
+
disabled: busy,
|
|
49345
|
+
label: t("uiLibrary.acPanel.setpoint", "Setpoint"),
|
|
49346
|
+
onPreview: (next) => {
|
|
49347
|
+
applyOptimistic({ target: next });
|
|
49348
|
+
},
|
|
49349
|
+
onCommit: (next) => {
|
|
49350
|
+
applyOptimistic({ target: next });
|
|
49351
|
+
cap.setTarget({ target: next });
|
|
49352
|
+
}
|
|
48826
49353
|
})
|
|
48827
49354
|
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48828
49355
|
className: "relative z-10 flex items-baseline gap-0.5",
|
|
@@ -48846,6 +49373,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48846
49373
|
className: "flex",
|
|
48847
49374
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeIcon, {
|
|
48848
49375
|
mode: display.mode,
|
|
49376
|
+
heatingOnly,
|
|
48849
49377
|
className: "h-4 w-4"
|
|
48850
49378
|
})
|
|
48851
49379
|
}),
|
|
@@ -48929,9 +49457,8 @@ function Field({ label, children }) {
|
|
|
48929
49457
|
}), children]
|
|
48930
49458
|
});
|
|
48931
49459
|
}
|
|
48932
|
-
function ModeIcon({ mode, className }) {
|
|
48933
|
-
|
|
48934
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
49460
|
+
function ModeIcon({ mode, heatingOnly = false, className }) {
|
|
49461
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(heatingOnly && mode === "heat" ? Heater : MODE_ICON[mode], {
|
|
48935
49462
|
className,
|
|
48936
49463
|
"aria-hidden": true
|
|
48937
49464
|
});
|
|
@@ -48981,11 +49508,89 @@ function RoundStep({ label, symbol, disabled, onClick }) {
|
|
|
48981
49508
|
children: symbol
|
|
48982
49509
|
});
|
|
48983
49510
|
}
|
|
48984
|
-
/**
|
|
48985
|
-
function RangeTrack({ value, min, max, accent }) {
|
|
48986
|
-
const
|
|
49511
|
+
/** Setpoint track. Drag previews; the command is sent when the pointer is released. */
|
|
49512
|
+
function RangeTrack({ value, min, max, step, accent, disabled, label, onPreview, onCommit }) {
|
|
49513
|
+
const trackRef = (0, react$1.useRef)(null);
|
|
49514
|
+
const draggingRef = (0, react$1.useRef)(false);
|
|
49515
|
+
const [draft, setDraft] = (0, react$1.useState)(null);
|
|
49516
|
+
const shown = draft ?? value;
|
|
49517
|
+
const pct = `${Math.min(1, Math.max(0, (shown - min) / (max - min))) * 100}%`;
|
|
49518
|
+
const tempAt = (clientX) => {
|
|
49519
|
+
const el = trackRef.current;
|
|
49520
|
+
if (el === null) return value;
|
|
49521
|
+
const rect = el.getBoundingClientRect();
|
|
49522
|
+
if (rect.width <= 0) return value;
|
|
49523
|
+
return climateTargetFromFraction((clientX - rect.left) / rect.width, {
|
|
49524
|
+
min,
|
|
49525
|
+
max
|
|
49526
|
+
}, step);
|
|
49527
|
+
};
|
|
49528
|
+
const onPointerDown = (event) => {
|
|
49529
|
+
if (disabled) return;
|
|
49530
|
+
event.stopPropagation();
|
|
49531
|
+
event.preventDefault();
|
|
49532
|
+
draggingRef.current = true;
|
|
49533
|
+
if (typeof event.currentTarget.setPointerCapture === "function") event.currentTarget.setPointerCapture(event.pointerId);
|
|
49534
|
+
const next = tempAt(event.clientX);
|
|
49535
|
+
setDraft(next);
|
|
49536
|
+
onPreview(next);
|
|
49537
|
+
};
|
|
49538
|
+
const onPointerMove = (event) => {
|
|
49539
|
+
if (disabled || !draggingRef.current) return;
|
|
49540
|
+
event.stopPropagation();
|
|
49541
|
+
const next = tempAt(event.clientX);
|
|
49542
|
+
setDraft(next);
|
|
49543
|
+
onPreview(next);
|
|
49544
|
+
};
|
|
49545
|
+
const endDrag = (event) => {
|
|
49546
|
+
if (!draggingRef.current) return;
|
|
49547
|
+
event.stopPropagation();
|
|
49548
|
+
draggingRef.current = false;
|
|
49549
|
+
const next = tempAt(event.clientX);
|
|
49550
|
+
setDraft(null);
|
|
49551
|
+
if (event.currentTarget.hasPointerCapture?.(event.pointerId) === true) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
49552
|
+
onCommit(next);
|
|
49553
|
+
};
|
|
49554
|
+
const onKeyDown = (event) => {
|
|
49555
|
+
if (disabled) return;
|
|
49556
|
+
const current = draft ?? value;
|
|
49557
|
+
if (event.key === "ArrowRight" || event.key === "ArrowUp") {
|
|
49558
|
+
event.preventDefault();
|
|
49559
|
+
onCommit(nextClimateTarget(current, 1, {
|
|
49560
|
+
tempStep: step,
|
|
49561
|
+
minTemp: min,
|
|
49562
|
+
maxTemp: max
|
|
49563
|
+
}));
|
|
49564
|
+
} else if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
|
|
49565
|
+
event.preventDefault();
|
|
49566
|
+
onCommit(nextClimateTarget(current, -1, {
|
|
49567
|
+
tempStep: step,
|
|
49568
|
+
minTemp: min,
|
|
49569
|
+
maxTemp: max
|
|
49570
|
+
}));
|
|
49571
|
+
} else if (event.key === "Home") {
|
|
49572
|
+
event.preventDefault();
|
|
49573
|
+
onCommit(min);
|
|
49574
|
+
} else if (event.key === "End") {
|
|
49575
|
+
event.preventDefault();
|
|
49576
|
+
onCommit(max);
|
|
49577
|
+
}
|
|
49578
|
+
};
|
|
48987
49579
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48988
|
-
|
|
49580
|
+
ref: trackRef,
|
|
49581
|
+
role: "slider",
|
|
49582
|
+
"aria-label": label,
|
|
49583
|
+
"aria-valuemin": min,
|
|
49584
|
+
"aria-valuemax": max,
|
|
49585
|
+
"aria-valuenow": shown,
|
|
49586
|
+
"aria-disabled": disabled,
|
|
49587
|
+
tabIndex: disabled ? -1 : 0,
|
|
49588
|
+
onPointerDown,
|
|
49589
|
+
onPointerMove,
|
|
49590
|
+
onPointerUp: endDrag,
|
|
49591
|
+
onPointerCancel: endDrag,
|
|
49592
|
+
onKeyDown,
|
|
49593
|
+
className: cn("mt-1 w-full touch-none select-none py-2", disabled ? "cursor-not-allowed opacity-40" : "cursor-ew-resize"),
|
|
48989
49594
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48990
49595
|
className: "relative h-1.5 w-full rounded-full bg-surface-subtle",
|
|
48991
49596
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -48995,7 +49600,7 @@ function RangeTrack({ value, min, max, accent }) {
|
|
|
48995
49600
|
background: accent
|
|
48996
49601
|
}
|
|
48997
49602
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48998
|
-
className: "absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-surface shadow",
|
|
49603
|
+
className: "absolute top-1/2 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-surface shadow",
|
|
48999
49604
|
style: {
|
|
49000
49605
|
left: pct,
|
|
49001
49606
|
background: accent
|
|
@@ -49012,7 +49617,7 @@ function RangeTrack({ value, min, max, accent }) {
|
|
|
49012
49617
|
* vent; every active mode renders drifting particles that FALL (cool/dry/auto)
|
|
49013
49618
|
* or RISE (heat), with a horizontal sweep overlaid when a swing axis is active.
|
|
49014
49619
|
*/
|
|
49015
|
-
function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
49620
|
+
function AirflowViz({ mode, accent, periodSec, swaying, forceRising = false }) {
|
|
49016
49621
|
const columns = [
|
|
49017
49622
|
0,
|
|
49018
49623
|
1,
|
|
@@ -49021,7 +49626,7 @@ function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
|
49021
49626
|
4,
|
|
49022
49627
|
5
|
|
49023
49628
|
];
|
|
49024
|
-
const rising = mode === "heat";
|
|
49629
|
+
const rising = forceRising || mode === "heat";
|
|
49025
49630
|
const isDroplet = mode === "dry";
|
|
49026
49631
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49027
49632
|
"aria-hidden": true,
|
|
@@ -49037,6 +49642,7 @@ function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
|
49037
49642
|
size: 56
|
|
49038
49643
|
})
|
|
49039
49644
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49645
|
+
"data-testid": forceRising ? "climate-heat-rise" : void 0,
|
|
49040
49646
|
className: cn("acv-sway-wrap absolute inset-0", swaying && "acv-sway"),
|
|
49041
49647
|
style: swaying ? { animation: "acv-sway 2.6s ease-in-out infinite" } : void 0,
|
|
49042
49648
|
children: columns.map((i) => {
|
|
@@ -49059,6 +49665,23 @@ function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
|
49059
49665
|
})
|
|
49060
49666
|
});
|
|
49061
49667
|
}
|
|
49668
|
+
/** Still radiator fins — the off state of a heating-only climate. */
|
|
49669
|
+
function RadiatorFins() {
|
|
49670
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49671
|
+
"data-testid": "climate-radiator",
|
|
49672
|
+
"aria-hidden": true,
|
|
49673
|
+
className: "flex h-full items-end justify-center gap-1.5 px-8 pb-3 opacity-40",
|
|
49674
|
+
children: [
|
|
49675
|
+
0,
|
|
49676
|
+
1,
|
|
49677
|
+
2,
|
|
49678
|
+
3,
|
|
49679
|
+
4,
|
|
49680
|
+
5,
|
|
49681
|
+
6
|
|
49682
|
+
].map((i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "h-10 w-1.5 rounded-t-full bg-foreground-subtle" }, i))
|
|
49683
|
+
});
|
|
49684
|
+
}
|
|
49062
49685
|
/** Big spinning fan for `fan_only` mode. */
|
|
49063
49686
|
function FanBlades({ color, periodSec, size }) {
|
|
49064
49687
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
@@ -49225,16 +49848,33 @@ var MODE_ARC_COLOR = {
|
|
|
49225
49848
|
*/
|
|
49226
49849
|
function ThermostatHeroCard({ trpc, deviceId, features: _features, optimistic }) {
|
|
49227
49850
|
const t = useUiText();
|
|
49228
|
-
const
|
|
49851
|
+
const climate = useDeviceCapability({
|
|
49229
49852
|
trpc,
|
|
49230
49853
|
deviceId,
|
|
49231
49854
|
cap: (d) => d.climateControl,
|
|
49232
49855
|
slice: (d) => d.state.climateControl
|
|
49233
49856
|
});
|
|
49234
|
-
const
|
|
49235
|
-
|
|
49236
|
-
|
|
49237
|
-
|
|
49857
|
+
const valve = useDeviceCapability({
|
|
49858
|
+
trpc,
|
|
49859
|
+
deviceId,
|
|
49860
|
+
cap: (d) => d.heatingValve,
|
|
49861
|
+
slice: (d) => d.state.heatingValve
|
|
49862
|
+
});
|
|
49863
|
+
const { display, applyOptimistic } = useOptimisticSlice(climate.slice, { enabled: optimistic ?? true });
|
|
49864
|
+
const hasClimate = climate.cap !== void 0 && display !== void 0;
|
|
49865
|
+
const hasValve = valve.cap !== void 0;
|
|
49866
|
+
if (!hasClimate && !hasValve) return null;
|
|
49867
|
+
if (!hasClimate && hasValve) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49868
|
+
className: "flex flex-col items-stretch gap-3 rounded-lg border border-border bg-surface p-4",
|
|
49869
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatingValveReadout, {
|
|
49870
|
+
trpc,
|
|
49871
|
+
deviceId,
|
|
49872
|
+
layout: "full"
|
|
49873
|
+
})
|
|
49874
|
+
});
|
|
49875
|
+
if (climate.cap === void 0 || display === void 0) return null;
|
|
49876
|
+
const cap = climate.cap;
|
|
49877
|
+
const busy = !climate.available;
|
|
49238
49878
|
const color = MODE_ARC_COLOR[display.mode];
|
|
49239
49879
|
const isRange = display.mode === "heat_cool" || display.targetLow !== null && display.targetHigh !== null;
|
|
49240
49880
|
const min = DEFAULT_TEMP_MIN;
|
|
@@ -49245,132 +49885,140 @@ function ThermostatHeroCard({ trpc, deviceId, features: _features, optimistic })
|
|
|
49245
49885
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49246
49886
|
"data-mode": display.mode,
|
|
49247
49887
|
className: cn("flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-4 transition-colors", MODE_COLOR[display.mode]),
|
|
49248
|
-
children: [
|
|
49249
|
-
|
|
49250
|
-
|
|
49251
|
-
|
|
49252
|
-
|
|
49253
|
-
|
|
49254
|
-
|
|
49255
|
-
|
|
49256
|
-
|
|
49257
|
-
|
|
49258
|
-
|
|
49259
|
-
|
|
49260
|
-
|
|
49261
|
-
|
|
49262
|
-
|
|
49263
|
-
|
|
49264
|
-
|
|
49265
|
-
|
|
49266
|
-
|
|
49267
|
-
color
|
|
49268
|
-
}),
|
|
49269
|
-
isRange ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49270
|
-
"data-testid": "arc-knob",
|
|
49271
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49272
|
-
fraction: fractionLow,
|
|
49273
|
-
color,
|
|
49274
|
-
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49275
|
-
onDragFraction: (f) => {
|
|
49276
|
-
if (busy || display.targetHigh === null) return;
|
|
49277
|
-
const next = Math.min(fractionToTemp(f, min, max), display.targetHigh);
|
|
49278
|
-
applyOptimistic({ targetLow: next });
|
|
49279
|
-
cap.setTargetRange({
|
|
49280
|
-
targetLow: next,
|
|
49281
|
-
targetHigh: display.targetHigh
|
|
49282
|
-
});
|
|
49283
|
-
}
|
|
49284
|
-
})
|
|
49285
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49286
|
-
"data-testid": "arc-knob",
|
|
49287
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49288
|
-
fraction: fractionHigh,
|
|
49289
|
-
color,
|
|
49290
|
-
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49291
|
-
onDragFraction: (f) => {
|
|
49292
|
-
if (busy || display.targetLow === null) return;
|
|
49293
|
-
const next = Math.max(fractionToTemp(f, min, max), display.targetLow);
|
|
49294
|
-
applyOptimistic({ targetHigh: next });
|
|
49295
|
-
cap.setTargetRange({
|
|
49296
|
-
targetLow: display.targetLow,
|
|
49297
|
-
targetHigh: next
|
|
49298
|
-
});
|
|
49299
|
-
}
|
|
49300
|
-
})
|
|
49301
|
-
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49302
|
-
"data-testid": "arc-knob",
|
|
49303
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49888
|
+
children: [
|
|
49889
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49890
|
+
className: "flex flex-col items-center gap-3",
|
|
49891
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49892
|
+
className: "relative",
|
|
49893
|
+
style: {
|
|
49894
|
+
width: GAUGE_SIZE,
|
|
49895
|
+
height: GAUGE_SIZE
|
|
49896
|
+
},
|
|
49897
|
+
children: [
|
|
49898
|
+
isRange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeRing, {
|
|
49899
|
+
mode: "band",
|
|
49900
|
+
band: {
|
|
49901
|
+
from: fractionLow,
|
|
49902
|
+
to: fractionHigh
|
|
49903
|
+
},
|
|
49904
|
+
color
|
|
49905
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeRing, {
|
|
49906
|
+
mode: "fill",
|
|
49304
49907
|
fraction: singleFraction,
|
|
49305
|
-
color
|
|
49306
|
-
|
|
49307
|
-
|
|
49308
|
-
|
|
49309
|
-
|
|
49310
|
-
|
|
49311
|
-
|
|
49312
|
-
|
|
49313
|
-
|
|
49314
|
-
|
|
49315
|
-
|
|
49316
|
-
|
|
49317
|
-
|
|
49318
|
-
|
|
49319
|
-
|
|
49320
|
-
|
|
49321
|
-
|
|
49322
|
-
className: "text-lg text-foreground-subtle font-normal ml-0.5",
|
|
49323
|
-
children: "°"
|
|
49324
|
-
})]
|
|
49325
|
-
}),
|
|
49326
|
-
!isRange && display.target !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49327
|
-
className: "text-xs font-semibold",
|
|
49328
|
-
style: { color },
|
|
49329
|
-
children: t("uiLibrary.thermostatHeroCard.setTo", "set {{value}}°", { value: display.target })
|
|
49330
|
-
}),
|
|
49331
|
-
isRange && display.targetLow !== null && display.targetHigh !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49332
|
-
className: "text-xs font-semibold",
|
|
49333
|
-
style: { color },
|
|
49334
|
-
children: [
|
|
49335
|
-
display.targetLow,
|
|
49336
|
-
"° – ",
|
|
49337
|
-
display.targetHigh,
|
|
49338
|
-
"°"
|
|
49339
|
-
]
|
|
49908
|
+
color
|
|
49909
|
+
}),
|
|
49910
|
+
isRange ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49911
|
+
"data-testid": "arc-knob",
|
|
49912
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49913
|
+
fraction: fractionLow,
|
|
49914
|
+
color,
|
|
49915
|
+
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49916
|
+
onDragFraction: (f) => {
|
|
49917
|
+
if (busy || display.targetHigh === null) return;
|
|
49918
|
+
const next = Math.min(fractionToTemp(f, min, max), display.targetHigh);
|
|
49919
|
+
applyOptimistic({ targetLow: next });
|
|
49920
|
+
cap.setTargetRange({
|
|
49921
|
+
targetLow: next,
|
|
49922
|
+
targetHigh: display.targetHigh
|
|
49923
|
+
});
|
|
49924
|
+
}
|
|
49340
49925
|
})
|
|
49341
|
-
|
|
49926
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49927
|
+
"data-testid": "arc-knob",
|
|
49928
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49929
|
+
fraction: fractionHigh,
|
|
49930
|
+
color,
|
|
49931
|
+
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49932
|
+
onDragFraction: (f) => {
|
|
49933
|
+
if (busy || display.targetLow === null) return;
|
|
49934
|
+
const next = Math.max(fractionToTemp(f, min, max), display.targetLow);
|
|
49935
|
+
applyOptimistic({ targetHigh: next });
|
|
49936
|
+
cap.setTargetRange({
|
|
49937
|
+
targetLow: display.targetLow,
|
|
49938
|
+
targetHigh: next
|
|
49939
|
+
});
|
|
49940
|
+
}
|
|
49941
|
+
})
|
|
49942
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49943
|
+
"data-testid": "arc-knob",
|
|
49944
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49945
|
+
fraction: singleFraction,
|
|
49946
|
+
color,
|
|
49947
|
+
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49948
|
+
onDragFraction: (f) => {
|
|
49949
|
+
if (busy) return;
|
|
49950
|
+
const next = fractionToTemp(f, min, max);
|
|
49951
|
+
applyOptimistic({ target: next });
|
|
49952
|
+
cap.setTarget({ target: next });
|
|
49953
|
+
}
|
|
49954
|
+
})
|
|
49955
|
+
}),
|
|
49956
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49957
|
+
className: "absolute inset-0 flex flex-col items-center justify-center gap-0.5 pointer-events-none",
|
|
49958
|
+
children: [
|
|
49959
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49960
|
+
"data-testid": "current-temp",
|
|
49961
|
+
className: "text-4xl font-bold tabular-nums text-foreground leading-none",
|
|
49962
|
+
children: [display.currentTemp !== null ? display.currentTemp : "—", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49963
|
+
className: "text-lg text-foreground-subtle font-normal ml-0.5",
|
|
49964
|
+
children: "°"
|
|
49965
|
+
})]
|
|
49966
|
+
}),
|
|
49967
|
+
!isRange && display.target !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49968
|
+
className: "text-xs font-semibold",
|
|
49969
|
+
style: { color },
|
|
49970
|
+
children: t("uiLibrary.thermostatHeroCard.setTo", "set {{value}}°", { value: display.target })
|
|
49971
|
+
}),
|
|
49972
|
+
isRange && display.targetLow !== null && display.targetHigh !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49973
|
+
className: "text-xs font-semibold",
|
|
49974
|
+
style: { color },
|
|
49975
|
+
children: [
|
|
49976
|
+
display.targetLow,
|
|
49977
|
+
"° – ",
|
|
49978
|
+
display.targetHigh,
|
|
49979
|
+
"°"
|
|
49980
|
+
]
|
|
49981
|
+
})
|
|
49982
|
+
]
|
|
49983
|
+
})
|
|
49984
|
+
]
|
|
49985
|
+
}), !isRange && display.target !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49986
|
+
className: "w-[200px]",
|
|
49987
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stepper, {
|
|
49988
|
+
value: `${display.target}°`,
|
|
49989
|
+
onDelta: (d) => {
|
|
49990
|
+
if (busy || display.target === null) return;
|
|
49991
|
+
const next = display.target + d;
|
|
49992
|
+
applyOptimistic({ target: next });
|
|
49993
|
+
cap.setTarget({ target: next });
|
|
49994
|
+
}
|
|
49342
49995
|
})
|
|
49343
|
-
]
|
|
49344
|
-
}),
|
|
49345
|
-
|
|
49346
|
-
|
|
49347
|
-
|
|
49348
|
-
|
|
49349
|
-
|
|
49350
|
-
|
|
49351
|
-
|
|
49352
|
-
|
|
49996
|
+
})]
|
|
49997
|
+
}),
|
|
49998
|
+
hasValve ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatingValveReadout, {
|
|
49999
|
+
trpc,
|
|
50000
|
+
deviceId,
|
|
50001
|
+
layout: "full"
|
|
50002
|
+
}) : null,
|
|
50003
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
50004
|
+
"aria-label": t("uiLibrary.thermostatHeroCard.mode", "Mode"),
|
|
50005
|
+
value: display.mode,
|
|
50006
|
+
disabled: busy,
|
|
50007
|
+
onChange: (e) => {
|
|
50008
|
+
e.stopPropagation();
|
|
50009
|
+
const mode = e.target.value;
|
|
50010
|
+
if (!busy && mode) {
|
|
50011
|
+
applyOptimistic({ mode: display.availableModes.find((m) => m === mode) ?? display.mode });
|
|
50012
|
+
cap.setMode({ mode: display.availableModes.find((m) => m === mode) ?? display.mode });
|
|
49353
50013
|
}
|
|
49354
|
-
}
|
|
49355
|
-
|
|
49356
|
-
|
|
49357
|
-
|
|
49358
|
-
|
|
49359
|
-
|
|
49360
|
-
|
|
49361
|
-
|
|
49362
|
-
const mode = e.target.value;
|
|
49363
|
-
if (!busy && mode) {
|
|
49364
|
-
applyOptimistic({ mode: display.availableModes.find((m) => m === mode) ?? display.mode });
|
|
49365
|
-
cap.setMode({ mode: display.availableModes.find((m) => m === mode) ?? display.mode });
|
|
49366
|
-
}
|
|
49367
|
-
},
|
|
49368
|
-
className: cn("w-full rounded border border-border bg-surface px-3 py-1.5 text-sm", busy && "opacity-40 cursor-not-allowed"),
|
|
49369
|
-
children: display.availableModes.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
49370
|
-
value: m,
|
|
49371
|
-
children: m
|
|
49372
|
-
}, m))
|
|
49373
|
-
})]
|
|
50014
|
+
},
|
|
50015
|
+
className: cn("w-full rounded border border-border bg-surface px-3 py-1.5 text-sm", busy && "opacity-40 cursor-not-allowed"),
|
|
50016
|
+
children: display.availableModes.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
50017
|
+
value: m,
|
|
50018
|
+
children: m
|
|
50019
|
+
}, m))
|
|
50020
|
+
})
|
|
50021
|
+
]
|
|
49374
50022
|
});
|
|
49375
50023
|
}
|
|
49376
50024
|
//#endregion
|
|
@@ -50878,14 +51526,32 @@ function useClimateCapGuard(trpc, deviceId) {
|
|
|
50878
51526
|
});
|
|
50879
51527
|
return cap !== void 0;
|
|
50880
51528
|
}
|
|
50881
|
-
function
|
|
50882
|
-
|
|
50883
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClimatePanel, {
|
|
51529
|
+
function useHeatingValveCapGuard(trpc, deviceId) {
|
|
51530
|
+
const { cap } = useDeviceCapability({
|
|
50884
51531
|
trpc,
|
|
50885
51532
|
deviceId,
|
|
50886
|
-
|
|
50887
|
-
|
|
50888
|
-
|
|
51533
|
+
cap: (d) => d.heatingValve,
|
|
51534
|
+
slice: (d) => d.state.heatingValve
|
|
51535
|
+
});
|
|
51536
|
+
return cap !== void 0;
|
|
51537
|
+
}
|
|
51538
|
+
function ThermostatInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
51539
|
+
const hasClimate = useClimateCapGuard(trpc, deviceId);
|
|
51540
|
+
const hasValve = useHeatingValveCapGuard(trpc, deviceId);
|
|
51541
|
+
if (!hasClimate && !hasValve) return null;
|
|
51542
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
51543
|
+
className: "inline-flex items-center gap-2",
|
|
51544
|
+
children: [hasClimate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClimatePanel, {
|
|
51545
|
+
trpc,
|
|
51546
|
+
deviceId,
|
|
51547
|
+
features,
|
|
51548
|
+
layout: "compact",
|
|
51549
|
+
optimistic
|
|
51550
|
+
}) : null, hasValve ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatingValveReadout, {
|
|
51551
|
+
trpc,
|
|
51552
|
+
deviceId,
|
|
51553
|
+
layout: "compact"
|
|
51554
|
+
}) : null]
|
|
50889
51555
|
});
|
|
50890
51556
|
}
|
|
50891
51557
|
function AcInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
@@ -51050,6 +51716,10 @@ var CONTROL_KIND_COMPONENTS = {
|
|
|
51050
51716
|
InlineControl: PetFeederInlineControl,
|
|
51051
51717
|
HeroCard: PetFeederHeroCard
|
|
51052
51718
|
},
|
|
51719
|
+
dishwasher: {
|
|
51720
|
+
InlineControl: DishwasherInlineControl,
|
|
51721
|
+
HeroCard: DishwasherHeroCard
|
|
51722
|
+
},
|
|
51053
51723
|
script: {
|
|
51054
51724
|
InlineControl: ScriptInlineControl,
|
|
51055
51725
|
HeroCard: ScriptHeroCard
|
|
@@ -51126,7 +51796,8 @@ var CONTROL_FILLS = {
|
|
|
51126
51796
|
[_camstack_types.DeviceType.Weather]: false,
|
|
51127
51797
|
[_camstack_types.DeviceType.Container]: false,
|
|
51128
51798
|
[_camstack_types.DeviceType.Image]: false,
|
|
51129
|
-
[_camstack_types.DeviceType.PetFeeder]: false
|
|
51799
|
+
[_camstack_types.DeviceType.PetFeeder]: false,
|
|
51800
|
+
[_camstack_types.DeviceType.Dishwasher]: false
|
|
51130
51801
|
};
|
|
51131
51802
|
var DEVICE_TYPE_VALUES = new Set(Object.values(_camstack_types.DeviceType));
|
|
51132
51803
|
/**
|
|
@@ -58683,6 +59354,780 @@ function EntryIcon({ url }) {
|
|
|
58683
59354
|
});
|
|
58684
59355
|
}
|
|
58685
59356
|
//#endregion
|
|
59357
|
+
//#region src/composites/record-entries/record-entry-secrets.ts
|
|
59358
|
+
/** Whether a record entry field still holds the redaction token from a read. */
|
|
59359
|
+
function isRedactedSecret(value) {
|
|
59360
|
+
return value === _camstack_types.REDACTED_SECRET;
|
|
59361
|
+
}
|
|
59362
|
+
/** Text shown in a password box for a redacted stored secret (empty — placeholder carries the hint). */
|
|
59363
|
+
function secretDisplayValue(value) {
|
|
59364
|
+
if (isRedactedSecret(value)) return "";
|
|
59365
|
+
return typeof value === "string" ? value : "";
|
|
59366
|
+
}
|
|
59367
|
+
/** Merge an edited password box back into the body sent to `putEntry`. */
|
|
59368
|
+
function mergeSecretForSubmit(draft, original) {
|
|
59369
|
+
if (!isRedactedSecret(original)) return draft;
|
|
59370
|
+
if (draft.length === 0) return _camstack_types.REDACTED_SECRET;
|
|
59371
|
+
return draft;
|
|
59372
|
+
}
|
|
59373
|
+
/** Is this JSON-schema leaf (or path) a credential field? */
|
|
59374
|
+
function isSecretField(path, secretPaths, schemaNode) {
|
|
59375
|
+
if (schemaNode["secret"] === true) return true;
|
|
59376
|
+
return secretPaths.includes(path);
|
|
59377
|
+
}
|
|
59378
|
+
//#endregion
|
|
59379
|
+
//#region src/composites/record-entries/entry-json-schema-form.tsx
|
|
59380
|
+
function isRecord$1(value) {
|
|
59381
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
59382
|
+
}
|
|
59383
|
+
function labelFor(key, node) {
|
|
59384
|
+
const desc = node["description"];
|
|
59385
|
+
if (typeof desc === "string" && desc.length > 0) return desc;
|
|
59386
|
+
return key.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase());
|
|
59387
|
+
}
|
|
59388
|
+
function readString(value) {
|
|
59389
|
+
return typeof value === "string" ? value : "";
|
|
59390
|
+
}
|
|
59391
|
+
function readNumber(value) {
|
|
59392
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
59393
|
+
}
|
|
59394
|
+
function readBool(value) {
|
|
59395
|
+
return value === true;
|
|
59396
|
+
}
|
|
59397
|
+
function objectFromJson(text) {
|
|
59398
|
+
try {
|
|
59399
|
+
const parsed = JSON.parse(text);
|
|
59400
|
+
return isRecord$1(parsed) ? parsed : null;
|
|
59401
|
+
} catch {
|
|
59402
|
+
return null;
|
|
59403
|
+
}
|
|
59404
|
+
}
|
|
59405
|
+
function RecordEntrySecretField({ label, required, value, disabled, onPatch }) {
|
|
59406
|
+
const [shown, setShown] = (0, react$1.useState)(false);
|
|
59407
|
+
const stored = isRedactedSecret(value);
|
|
59408
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormField$1, {
|
|
59409
|
+
label,
|
|
59410
|
+
required,
|
|
59411
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PasswordInput, {
|
|
59412
|
+
value: secretDisplayValue(value),
|
|
59413
|
+
onValueChange: (text) => onPatch(mergeSecretForSubmit(text, value)),
|
|
59414
|
+
shown,
|
|
59415
|
+
onToggleShown: () => setShown((s) => !s),
|
|
59416
|
+
disabled,
|
|
59417
|
+
placeholder: stored ? SECRET_MASK_PLACEHOLDER : `Enter ${label.toLowerCase()}`
|
|
59418
|
+
})
|
|
59419
|
+
});
|
|
59420
|
+
}
|
|
59421
|
+
function SchemaField(props) {
|
|
59422
|
+
const { path, name, node, value, secretPaths, required, disabled, onPatch } = props;
|
|
59423
|
+
const label = labelFor(name, node);
|
|
59424
|
+
if (isSecretField(path, secretPaths, node)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecordEntrySecretField, {
|
|
59425
|
+
label,
|
|
59426
|
+
required,
|
|
59427
|
+
value,
|
|
59428
|
+
disabled,
|
|
59429
|
+
onPatch: (next) => onPatch(path, next)
|
|
59430
|
+
});
|
|
59431
|
+
const enumValues = node["enum"];
|
|
59432
|
+
if (Array.isArray(enumValues) && enumValues.every((v) => typeof v === "string")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormField$1, {
|
|
59433
|
+
label,
|
|
59434
|
+
required,
|
|
59435
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, {
|
|
59436
|
+
value: readString(value),
|
|
59437
|
+
onChange: (e) => onPatch(path, e.target.value),
|
|
59438
|
+
disabled,
|
|
59439
|
+
options: enumValues.map((opt) => ({
|
|
59440
|
+
value: opt,
|
|
59441
|
+
label: opt
|
|
59442
|
+
}))
|
|
59443
|
+
})
|
|
59444
|
+
});
|
|
59445
|
+
const type = node["type"];
|
|
59446
|
+
if (type === "boolean") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormField$1, {
|
|
59447
|
+
label,
|
|
59448
|
+
required,
|
|
59449
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Switch, {
|
|
59450
|
+
checked: readBool(value),
|
|
59451
|
+
onCheckedChange: (v) => onPatch(path, v),
|
|
59452
|
+
disabled
|
|
59453
|
+
})
|
|
59454
|
+
});
|
|
59455
|
+
if (type === "integer" || type === "number") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormField$1, {
|
|
59456
|
+
label,
|
|
59457
|
+
required,
|
|
59458
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
|
|
59459
|
+
type: "number",
|
|
59460
|
+
value: readNumber(value) ?? "",
|
|
59461
|
+
onChange: (e) => {
|
|
59462
|
+
const raw = e.target.value;
|
|
59463
|
+
onPatch(path, raw === "" ? void 0 : Number(raw));
|
|
59464
|
+
},
|
|
59465
|
+
disabled
|
|
59466
|
+
})
|
|
59467
|
+
});
|
|
59468
|
+
if (type === "object") {
|
|
59469
|
+
const properties = node["properties"];
|
|
59470
|
+
if (isRecord$1(properties)) {
|
|
59471
|
+
const childValue = isRecord$1(value) ? value : {};
|
|
59472
|
+
const childRequired = Array.isArray(node["required"]) ? node["required"].filter((k) => typeof k === "string") : [];
|
|
59473
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
59474
|
+
className: "space-y-3 rounded-lg border border-border-subtle p-3",
|
|
59475
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", {
|
|
59476
|
+
className: "px-1 text-xs font-medium text-foreground-muted",
|
|
59477
|
+
children: label
|
|
59478
|
+
}), Object.entries(properties).map(([key, childNode]) => isRecord$1(childNode) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SchemaField, {
|
|
59479
|
+
path: `${path}.${key}`,
|
|
59480
|
+
name: key,
|
|
59481
|
+
node: childNode,
|
|
59482
|
+
value: childValue[key],
|
|
59483
|
+
secretPaths,
|
|
59484
|
+
required: childRequired.includes(key),
|
|
59485
|
+
disabled,
|
|
59486
|
+
onPatch
|
|
59487
|
+
}, key) : null)]
|
|
59488
|
+
});
|
|
59489
|
+
}
|
|
59490
|
+
const jsonText = isRecord$1(value) ? JSON.stringify(value, null, 2) : "{}";
|
|
59491
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormField$1, {
|
|
59492
|
+
label,
|
|
59493
|
+
required,
|
|
59494
|
+
description: "JSON object",
|
|
59495
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
59496
|
+
className: cn("min-h-[8rem] w-full resize-y rounded-md border border-border bg-surface px-2.5 py-2 font-mono text-xs text-foreground", "placeholder:text-foreground-subtle focus:outline-none focus:ring-1 focus:ring-inset focus:ring-primary", "disabled:cursor-not-allowed disabled:opacity-50"),
|
|
59497
|
+
rows: 6,
|
|
59498
|
+
defaultValue: jsonText,
|
|
59499
|
+
disabled,
|
|
59500
|
+
onBlur: (e) => {
|
|
59501
|
+
const parsed = objectFromJson(e.target.value);
|
|
59502
|
+
if (parsed !== null) onPatch(path, parsed);
|
|
59503
|
+
}
|
|
59504
|
+
})
|
|
59505
|
+
});
|
|
59506
|
+
}
|
|
59507
|
+
if (type === "string" || type === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormField$1, {
|
|
59508
|
+
label,
|
|
59509
|
+
required,
|
|
59510
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
|
|
59511
|
+
value: readString(value),
|
|
59512
|
+
onChange: (e) => onPatch(path, e.target.value),
|
|
59513
|
+
disabled
|
|
59514
|
+
})
|
|
59515
|
+
});
|
|
59516
|
+
return null;
|
|
59517
|
+
}
|
|
59518
|
+
function setAtPath(root, path, next) {
|
|
59519
|
+
const parts = path.split(".");
|
|
59520
|
+
if (parts.length === 1) return {
|
|
59521
|
+
...root,
|
|
59522
|
+
[parts[0]]: next
|
|
59523
|
+
};
|
|
59524
|
+
const [head, ...rest] = parts;
|
|
59525
|
+
const key = head;
|
|
59526
|
+
const child = isRecord$1(root[key]) ? root[key] : {};
|
|
59527
|
+
return {
|
|
59528
|
+
...root,
|
|
59529
|
+
[key]: setAtPath(child, rest.join("."), next)
|
|
59530
|
+
};
|
|
59531
|
+
}
|
|
59532
|
+
/** Renders an entry body from the engine's JSON Schema descriptor. */
|
|
59533
|
+
function EntryJsonSchemaForm({ schema, secretPaths, value, onChange, disabled }) {
|
|
59534
|
+
const properties = schema["properties"];
|
|
59535
|
+
if (!isRecord$1(properties)) return null;
|
|
59536
|
+
const required = Array.isArray(schema["required"]) ? schema["required"].filter((k) => typeof k === "string") : [];
|
|
59537
|
+
const onPatch = (path, next) => {
|
|
59538
|
+
onChange(setAtPath(value, path, next));
|
|
59539
|
+
};
|
|
59540
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
59541
|
+
className: "space-y-3",
|
|
59542
|
+
children: Object.entries(properties).map(([key, node]) => isRecord$1(node) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SchemaField, {
|
|
59543
|
+
path: key,
|
|
59544
|
+
name: key,
|
|
59545
|
+
node,
|
|
59546
|
+
value: value[key],
|
|
59547
|
+
secretPaths,
|
|
59548
|
+
required: required.includes(key),
|
|
59549
|
+
disabled,
|
|
59550
|
+
onPatch
|
|
59551
|
+
}, key) : null)
|
|
59552
|
+
});
|
|
59553
|
+
}
|
|
59554
|
+
//#endregion
|
|
59555
|
+
//#region src/composites/record-entries/record-entries-panel.tsx
|
|
59556
|
+
function isRecord(value) {
|
|
59557
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
59558
|
+
}
|
|
59559
|
+
function defaultBody(schema) {
|
|
59560
|
+
const properties = schema["properties"];
|
|
59561
|
+
if (!isRecord(properties)) return {};
|
|
59562
|
+
const out = {};
|
|
59563
|
+
for (const [key, node] of Object.entries(properties)) {
|
|
59564
|
+
if (!isRecord(node)) continue;
|
|
59565
|
+
if (node["default"] !== void 0) {
|
|
59566
|
+
out[key] = node["default"];
|
|
59567
|
+
continue;
|
|
59568
|
+
}
|
|
59569
|
+
if (node["type"] === "boolean") out[key] = false;
|
|
59570
|
+
else if (node["type"] === "object") out[key] = {};
|
|
59571
|
+
}
|
|
59572
|
+
return out;
|
|
59573
|
+
}
|
|
59574
|
+
function RecordEntriesPanel({ kind }) {
|
|
59575
|
+
const t = useUiText();
|
|
59576
|
+
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
59577
|
+
const confirm = useConfirm();
|
|
59578
|
+
const formUrl = useItemFormUrl({
|
|
59579
|
+
newKey: "entryNew",
|
|
59580
|
+
editKey: "entryEdit"
|
|
59581
|
+
});
|
|
59582
|
+
const listQuery = useRecordEntriesListEntries({
|
|
59583
|
+
owner: kind.owner,
|
|
59584
|
+
kind: kind.kind
|
|
59585
|
+
}, { staleTime: 15e3 });
|
|
59586
|
+
const putMutation = useRecordEntriesPutEntry({ onSuccess: async () => {
|
|
59587
|
+
await queryClient.invalidateQueries({ queryKey: [["recordEntries"]] });
|
|
59588
|
+
formUrl.close();
|
|
59589
|
+
} });
|
|
59590
|
+
const deleteMutation = useRecordEntriesDeleteEntry({ onSuccess: async () => {
|
|
59591
|
+
await queryClient.invalidateQueries({ queryKey: [["recordEntries"]] });
|
|
59592
|
+
} });
|
|
59593
|
+
const entries = listQuery.data?.entries ?? [];
|
|
59594
|
+
const editing = (0, react$1.useMemo)(() => {
|
|
59595
|
+
const st = formUrl.state;
|
|
59596
|
+
if (st.mode !== "edit") return null;
|
|
59597
|
+
return entries.find((e) => e.id === st.id) ?? null;
|
|
59598
|
+
}, [entries, formUrl.state]);
|
|
59599
|
+
const [draft, setDraft] = (0, react$1.useState)({});
|
|
59600
|
+
const [saveError, setSaveError] = (0, react$1.useState)(null);
|
|
59601
|
+
const openNew = (0, react$1.useCallback)(() => {
|
|
59602
|
+
setDraft(defaultBody(kind.fields));
|
|
59603
|
+
setSaveError(null);
|
|
59604
|
+
formUrl.openNew();
|
|
59605
|
+
}, [formUrl, kind.fields]);
|
|
59606
|
+
const openEdit = (0, react$1.useCallback)((entry) => {
|
|
59607
|
+
setDraft({ ...entry.data });
|
|
59608
|
+
setSaveError(null);
|
|
59609
|
+
formUrl.openEdit(entry.id);
|
|
59610
|
+
}, [formUrl]);
|
|
59611
|
+
const onDelete = (0, react$1.useCallback)(async (entry) => {
|
|
59612
|
+
if (!await confirm({
|
|
59613
|
+
title: t("uiLibrary.recordEntries.deleteTitle", "Delete entry"),
|
|
59614
|
+
message: t("uiLibrary.recordEntries.deleteDescription", "Remove \"{{name}}\" permanently?", { name: entry.name }),
|
|
59615
|
+
confirmLabel: t("uiLibrary.recordEntries.deleteConfirm", "Delete"),
|
|
59616
|
+
variant: "danger"
|
|
59617
|
+
})) return;
|
|
59618
|
+
await deleteMutation.mutateAsync({
|
|
59619
|
+
owner: kind.owner,
|
|
59620
|
+
kind: kind.kind,
|
|
59621
|
+
id: entry.id
|
|
59622
|
+
});
|
|
59623
|
+
}, [
|
|
59624
|
+
confirm,
|
|
59625
|
+
deleteMutation,
|
|
59626
|
+
kind.kind,
|
|
59627
|
+
kind.owner,
|
|
59628
|
+
t
|
|
59629
|
+
]);
|
|
59630
|
+
const columns = (0, react$1.useMemo)(() => {
|
|
59631
|
+
return [
|
|
59632
|
+
{
|
|
59633
|
+
key: "name",
|
|
59634
|
+
header: t("uiLibrary.recordEntries.colName", "Name"),
|
|
59635
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
59636
|
+
type: "button",
|
|
59637
|
+
variant: "ghost",
|
|
59638
|
+
size: "sm",
|
|
59639
|
+
className: "h-auto max-w-full justify-start px-0 py-0 font-medium hover:underline",
|
|
59640
|
+
onClick: () => openEdit(row),
|
|
59641
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
59642
|
+
className: "truncate",
|
|
59643
|
+
children: row.name
|
|
59644
|
+
})
|
|
59645
|
+
})
|
|
59646
|
+
},
|
|
59647
|
+
{
|
|
59648
|
+
key: "updated",
|
|
59649
|
+
header: t("uiLibrary.recordEntries.colUpdated", "Updated"),
|
|
59650
|
+
nowrap: true,
|
|
59651
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
59652
|
+
className: "text-[11px] text-foreground-subtle",
|
|
59653
|
+
children: new Date(row.updatedAt).toLocaleString()
|
|
59654
|
+
})
|
|
59655
|
+
},
|
|
59656
|
+
{
|
|
59657
|
+
key: "actions",
|
|
59658
|
+
header: "",
|
|
59659
|
+
align: "right",
|
|
59660
|
+
nowrap: true,
|
|
59661
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
59662
|
+
className: "flex justify-end gap-1",
|
|
59663
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
59664
|
+
size: "sm",
|
|
59665
|
+
variant: "ghost",
|
|
59666
|
+
type: "button",
|
|
59667
|
+
onClick: () => openEdit(row),
|
|
59668
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pencil, { className: "h-3.5 w-3.5" })
|
|
59669
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
59670
|
+
size: "sm",
|
|
59671
|
+
variant: "ghost",
|
|
59672
|
+
type: "button",
|
|
59673
|
+
onClick: () => void onDelete(row),
|
|
59674
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Trash2, { className: "h-3.5 w-3.5 text-danger" })
|
|
59675
|
+
})]
|
|
59676
|
+
})
|
|
59677
|
+
}
|
|
59678
|
+
];
|
|
59679
|
+
}, [
|
|
59680
|
+
onDelete,
|
|
59681
|
+
openEdit,
|
|
59682
|
+
t
|
|
59683
|
+
]);
|
|
59684
|
+
const dialogOpen = formUrl.state.mode !== "closed";
|
|
59685
|
+
const dialogTitle = formUrl.state.mode === "new" ? t("uiLibrary.recordEntries.addTitle", "Add entry") : t("uiLibrary.recordEntries.editTitle", "Edit entry");
|
|
59686
|
+
const onSave = async () => {
|
|
59687
|
+
setSaveError(null);
|
|
59688
|
+
try {
|
|
59689
|
+
if (formUrl.state.mode === "new") await putMutation.mutateAsync({
|
|
59690
|
+
owner: kind.owner,
|
|
59691
|
+
kind: kind.kind,
|
|
59692
|
+
data: draft
|
|
59693
|
+
});
|
|
59694
|
+
else if (formUrl.state.mode === "edit" && editing !== null) await putMutation.mutateAsync({
|
|
59695
|
+
owner: kind.owner,
|
|
59696
|
+
kind: kind.kind,
|
|
59697
|
+
id: editing.id,
|
|
59698
|
+
data: draft,
|
|
59699
|
+
ifRev: editing.rev
|
|
59700
|
+
});
|
|
59701
|
+
else {
|
|
59702
|
+
setSaveError(t("uiLibrary.recordEntries.saveUnavailable", "Nothing to save"));
|
|
59703
|
+
return;
|
|
59704
|
+
}
|
|
59705
|
+
} catch (err) {
|
|
59706
|
+
setSaveError(err instanceof Error ? err.message : t("uiLibrary.recordEntries.saveFailed", "Save failed"));
|
|
59707
|
+
}
|
|
59708
|
+
};
|
|
59709
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
59710
|
+
className: "space-y-3",
|
|
59711
|
+
children: [
|
|
59712
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
59713
|
+
className: "flex justify-end",
|
|
59714
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
|
|
59715
|
+
type: "button",
|
|
59716
|
+
size: "sm",
|
|
59717
|
+
onClick: openNew,
|
|
59718
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "mr-1 h-3.5 w-3.5" }), t("uiLibrary.recordEntries.add", "Add")]
|
|
59719
|
+
})
|
|
59720
|
+
}),
|
|
59721
|
+
listQuery.isError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBox, { message: listQuery.error instanceof Error ? listQuery.error.message : t("uiLibrary.recordEntries.loadFailed", "Could not load entries") }),
|
|
59722
|
+
listQuery.isPending ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
59723
|
+
className: "flex justify-center py-8 text-foreground-subtle",
|
|
59724
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-5 w-5 animate-spin" })
|
|
59725
|
+
}) : entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {
|
|
59726
|
+
title: t("uiLibrary.recordEntries.emptyTitle", "No entries yet"),
|
|
59727
|
+
description: t("uiLibrary.recordEntries.emptyDescription", "Add the first row for this kind.")
|
|
59728
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, {
|
|
59729
|
+
columns,
|
|
59730
|
+
rows: entries,
|
|
59731
|
+
rowKey: (r) => r.id,
|
|
59732
|
+
maxBodyHeight: TABLE_SCROLL_MAX_H,
|
|
59733
|
+
emptyMessage: t("uiLibrary.recordEntries.emptyTitle", "No entries yet")
|
|
59734
|
+
}),
|
|
59735
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ItemFormDialog, {
|
|
59736
|
+
open: dialogOpen,
|
|
59737
|
+
onClose: formUrl.close,
|
|
59738
|
+
title: dialogTitle,
|
|
59739
|
+
error: saveError,
|
|
59740
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
59741
|
+
type: "button",
|
|
59742
|
+
size: "sm",
|
|
59743
|
+
disabled: putMutation.isPending,
|
|
59744
|
+
onClick: () => void onSave(),
|
|
59745
|
+
children: putMutation.isPending ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }) : t("uiLibrary.recordEntries.save", "Save")
|
|
59746
|
+
}),
|
|
59747
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryJsonSchemaForm, {
|
|
59748
|
+
schema: kind.fields,
|
|
59749
|
+
secretPaths: kind.secretPaths,
|
|
59750
|
+
value: draft,
|
|
59751
|
+
onChange: setDraft,
|
|
59752
|
+
disabled: putMutation.isPending
|
|
59753
|
+
})
|
|
59754
|
+
})
|
|
59755
|
+
]
|
|
59756
|
+
});
|
|
59757
|
+
}
|
|
59758
|
+
//#endregion
|
|
59759
|
+
//#region src/composites/record-entries/record-entries-hub.tsx
|
|
59760
|
+
var RECORD_ENTRY_OWNER_PARAM = "entryOwner";
|
|
59761
|
+
var RECORD_ENTRY_KIND_PARAM = "entryKind";
|
|
59762
|
+
function kindKey$1(k) {
|
|
59763
|
+
return `${k.owner}/${k.kind}`;
|
|
59764
|
+
}
|
|
59765
|
+
function RecordEntriesHub({ filter }) {
|
|
59766
|
+
const t = useUiText();
|
|
59767
|
+
const kindsQuery = useRecordEntriesListEntryKinds({}, { staleTime: 3e4 });
|
|
59768
|
+
const [ownerParam, setOwner] = useUrlQueryParam(RECORD_ENTRY_OWNER_PARAM);
|
|
59769
|
+
const [kindParam, setKind] = useUrlQueryParam(RECORD_ENTRY_KIND_PARAM);
|
|
59770
|
+
const kinds = (0, react$1.useMemo)(() => {
|
|
59771
|
+
return (kindsQuery.data?.kinds ?? []).filter((k) => {
|
|
59772
|
+
if (filter?.owner !== void 0 && k.owner !== filter.owner) return false;
|
|
59773
|
+
if (filter?.kind !== void 0 && k.kind !== filter.kind) return false;
|
|
59774
|
+
return true;
|
|
59775
|
+
});
|
|
59776
|
+
}, [
|
|
59777
|
+
filter?.kind,
|
|
59778
|
+
filter?.owner,
|
|
59779
|
+
kindsQuery.data?.kinds
|
|
59780
|
+
]);
|
|
59781
|
+
const active = (0, react$1.useMemo)(() => {
|
|
59782
|
+
if (kinds.length === 0) return null;
|
|
59783
|
+
return kinds.find((k) => k.owner === ownerParam && k.kind === kindParam) ?? kinds[0] ?? null;
|
|
59784
|
+
}, [
|
|
59785
|
+
kindParam,
|
|
59786
|
+
kinds,
|
|
59787
|
+
ownerParam
|
|
59788
|
+
]);
|
|
59789
|
+
const selectKind = (k) => {
|
|
59790
|
+
setOwner(k.owner);
|
|
59791
|
+
setKind(k.kind);
|
|
59792
|
+
};
|
|
59793
|
+
if (kindsQuery.isError) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBox, { message: kindsQuery.error instanceof Error ? kindsQuery.error.message : t("uiLibrary.recordEntries.kindsFailed", "Could not load entry kinds") });
|
|
59794
|
+
if (kindsQuery.isPending) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
59795
|
+
className: "flex justify-center py-10 text-foreground-subtle",
|
|
59796
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-5 w-5 animate-spin" })
|
|
59797
|
+
});
|
|
59798
|
+
if (kinds.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {
|
|
59799
|
+
title: t("uiLibrary.recordEntries.noKindsTitle", "No entry kinds"),
|
|
59800
|
+
description: t("uiLibrary.recordEntries.noKindsDescription", "No addon has declared an entry record kind yet.")
|
|
59801
|
+
});
|
|
59802
|
+
const activeKey = active === null ? "" : kindKey$1(active);
|
|
59803
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
59804
|
+
className: "space-y-4",
|
|
59805
|
+
children: [kinds.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tabs, {
|
|
59806
|
+
appearance: "pills",
|
|
59807
|
+
value: activeKey,
|
|
59808
|
+
onValueChange: (key) => {
|
|
59809
|
+
const found = kinds.find((k) => kindKey$1(k) === key);
|
|
59810
|
+
if (found !== void 0) selectKind(found);
|
|
59811
|
+
},
|
|
59812
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabsList, {
|
|
59813
|
+
className: "flex flex-wrap gap-1",
|
|
59814
|
+
children: kinds.map((k) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TabsTrigger, {
|
|
59815
|
+
value: kindKey$1(k),
|
|
59816
|
+
children: [k.title, k.rows > 0 ? ` (${k.rows})` : ""]
|
|
59817
|
+
}, kindKey$1(k)))
|
|
59818
|
+
})
|
|
59819
|
+
}), active !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [kinds.length === 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
59820
|
+
className: "mb-2 text-sm font-semibold text-foreground",
|
|
59821
|
+
children: active.title
|
|
59822
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecordEntriesPanel, { kind: active })] })]
|
|
59823
|
+
});
|
|
59824
|
+
}
|
|
59825
|
+
//#endregion
|
|
59826
|
+
//#region src/composites/storage-records/storage-records-panel.tsx
|
|
59827
|
+
function StorageRecordsPanel({ kind }) {
|
|
59828
|
+
const t = useUiText();
|
|
59829
|
+
const [inspect, setInspect] = (0, react$1.useState)(null);
|
|
59830
|
+
const recordsQuery = trpc.storageAdmin.readRecords.useQuery({
|
|
59831
|
+
owner: kind.owner,
|
|
59832
|
+
kind: kind.kind,
|
|
59833
|
+
limit: 100,
|
|
59834
|
+
desc: true
|
|
59835
|
+
}, { staleTime: 15e3 });
|
|
59836
|
+
const records = recordsQuery.data?.records ?? [];
|
|
59837
|
+
const columns = (0, react$1.useMemo)(() => {
|
|
59838
|
+
return [
|
|
59839
|
+
{
|
|
59840
|
+
key: "id",
|
|
59841
|
+
header: t("uiLibrary.storageRecords.colId", "Id"),
|
|
59842
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
59843
|
+
type: "button",
|
|
59844
|
+
variant: "ghost",
|
|
59845
|
+
size: "sm",
|
|
59846
|
+
className: "h-auto max-w-full justify-start px-0 py-0 font-mono text-xs hover:underline",
|
|
59847
|
+
onClick: () => setInspect(row),
|
|
59848
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
59849
|
+
className: "truncate",
|
|
59850
|
+
children: row.id
|
|
59851
|
+
})
|
|
59852
|
+
})
|
|
59853
|
+
},
|
|
59854
|
+
{
|
|
59855
|
+
key: "deviceId",
|
|
59856
|
+
header: t("uiLibrary.storageRecords.colDevice", "Device"),
|
|
59857
|
+
nowrap: true,
|
|
59858
|
+
render: (row) => row.deviceId === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
59859
|
+
className: "text-foreground-subtle",
|
|
59860
|
+
children: "—"
|
|
59861
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
59862
|
+
className: "font-mono text-xs",
|
|
59863
|
+
children: row.deviceId
|
|
59864
|
+
})
|
|
59865
|
+
},
|
|
59866
|
+
{
|
|
59867
|
+
key: "at",
|
|
59868
|
+
header: t("uiLibrary.storageRecords.colAt", "At"),
|
|
59869
|
+
nowrap: true,
|
|
59870
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
59871
|
+
className: "text-[11px] text-foreground-subtle",
|
|
59872
|
+
children: row.at === null ? "—" : new Date(row.at).toLocaleString()
|
|
59873
|
+
})
|
|
59874
|
+
},
|
|
59875
|
+
{
|
|
59876
|
+
key: "updatedAt",
|
|
59877
|
+
header: t("uiLibrary.storageRecords.colUpdated", "Updated"),
|
|
59878
|
+
nowrap: true,
|
|
59879
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
59880
|
+
className: "text-[11px] text-foreground-subtle",
|
|
59881
|
+
children: new Date(row.updatedAt).toLocaleString()
|
|
59882
|
+
})
|
|
59883
|
+
}
|
|
59884
|
+
];
|
|
59885
|
+
}, [t]);
|
|
59886
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
59887
|
+
className: "space-y-3",
|
|
59888
|
+
children: [
|
|
59889
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
59890
|
+
className: "flex flex-wrap items-center gap-2",
|
|
59891
|
+
children: [
|
|
59892
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
59893
|
+
className: "text-sm font-semibold text-foreground",
|
|
59894
|
+
children: `${kind.owner}/${kind.kind}`
|
|
59895
|
+
}),
|
|
59896
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
59897
|
+
variant: "neutral",
|
|
59898
|
+
style: "outline",
|
|
59899
|
+
children: kind.database
|
|
59900
|
+
}),
|
|
59901
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
59902
|
+
variant: "neutral",
|
|
59903
|
+
style: "outline",
|
|
59904
|
+
children: kind.class
|
|
59905
|
+
}),
|
|
59906
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
59907
|
+
variant: kind.state === "declared" ? "default" : "neutral",
|
|
59908
|
+
children: kind.state
|
|
59909
|
+
}),
|
|
59910
|
+
kind.entry ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
59911
|
+
variant: "info",
|
|
59912
|
+
children: t("uiLibrary.storageRecords.entryKind", "Entry")
|
|
59913
|
+
}) : null
|
|
59914
|
+
]
|
|
59915
|
+
}),
|
|
59916
|
+
recordsQuery.isError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBox, { message: recordsQuery.error instanceof Error ? recordsQuery.error.message : t("uiLibrary.storageRecords.loadFailed", "Could not load records") }) : recordsQuery.isPending ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
59917
|
+
className: "flex justify-center py-8 text-foreground-subtle",
|
|
59918
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-5 w-5 animate-spin" })
|
|
59919
|
+
}) : records.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {
|
|
59920
|
+
title: t("uiLibrary.storageRecords.emptyTitle", "No rows"),
|
|
59921
|
+
description: t("uiLibrary.storageRecords.emptyDescription", "This kind has no stored records yet.")
|
|
59922
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, {
|
|
59923
|
+
columns,
|
|
59924
|
+
rows: records,
|
|
59925
|
+
rowKey: (r) => r.id,
|
|
59926
|
+
maxBodyHeight: TABLE_SCROLL_MAX_H,
|
|
59927
|
+
emptyMessage: t("uiLibrary.storageRecords.emptyTitle", "No rows")
|
|
59928
|
+
}),
|
|
59929
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
|
|
59930
|
+
open: inspect !== null,
|
|
59931
|
+
onOpenChange: (open) => !open && setInspect(null),
|
|
59932
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogContent, {
|
|
59933
|
+
width: "lg",
|
|
59934
|
+
placement: "sheet-on-mobile",
|
|
59935
|
+
scroll: "body",
|
|
59936
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogTitle, { children: t("uiLibrary.storageRecords.inspectTitle", "Record") }), inspect !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogDescription, {
|
|
59937
|
+
className: "font-mono",
|
|
59938
|
+
children: inspect.id
|
|
59939
|
+
}) : null] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogBody, { children: inspect !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodeBlock, {
|
|
59940
|
+
maxHeight: 480,
|
|
59941
|
+
children: JSON.stringify(inspect, null, 2)
|
|
59942
|
+
}) : null })]
|
|
59943
|
+
})
|
|
59944
|
+
})
|
|
59945
|
+
]
|
|
59946
|
+
});
|
|
59947
|
+
}
|
|
59948
|
+
//#endregion
|
|
59949
|
+
//#region src/composites/storage-records/storage-records-hub.tsx
|
|
59950
|
+
var STORAGE_RECORDS_OWNER_PARAM = "recordsOwner";
|
|
59951
|
+
var STORAGE_RECORDS_KIND_PARAM = "recordsKind";
|
|
59952
|
+
function kindKey(k) {
|
|
59953
|
+
return `${k.owner}/${k.kind}`;
|
|
59954
|
+
}
|
|
59955
|
+
function formatBytes(n) {
|
|
59956
|
+
if (n === null) return "—";
|
|
59957
|
+
if (n < 1024) return `${n} B`;
|
|
59958
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KiB`;
|
|
59959
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MiB`;
|
|
59960
|
+
}
|
|
59961
|
+
function StorageRecordsHub() {
|
|
59962
|
+
const t = useUiText();
|
|
59963
|
+
const kindsQuery = trpc.storageAdmin.listKinds.useQuery({}, { staleTime: 3e4 });
|
|
59964
|
+
const [ownerParam, setOwner] = useUrlQueryParam(STORAGE_RECORDS_OWNER_PARAM);
|
|
59965
|
+
const [kindParam, setKind] = useUrlQueryParam(STORAGE_RECORDS_KIND_PARAM);
|
|
59966
|
+
const kinds = kindsQuery.data?.kinds ?? [];
|
|
59967
|
+
const filter = useDataTableFilter(kinds, {
|
|
59968
|
+
searchText: (row) => [
|
|
59969
|
+
row.owner,
|
|
59970
|
+
row.kind,
|
|
59971
|
+
row.class,
|
|
59972
|
+
row.database,
|
|
59973
|
+
row.state
|
|
59974
|
+
],
|
|
59975
|
+
facetGroups: [{
|
|
59976
|
+
id: "owner",
|
|
59977
|
+
value: (row) => row.owner
|
|
59978
|
+
}, {
|
|
59979
|
+
id: "database",
|
|
59980
|
+
value: (row) => row.database
|
|
59981
|
+
}]
|
|
59982
|
+
});
|
|
59983
|
+
const active = (0, react$1.useMemo)(() => {
|
|
59984
|
+
if (kinds.length === 0) return null;
|
|
59985
|
+
return kinds.find((k) => k.owner === ownerParam && k.kind === kindParam) ?? null;
|
|
59986
|
+
}, [
|
|
59987
|
+
kindParam,
|
|
59988
|
+
kinds,
|
|
59989
|
+
ownerParam
|
|
59990
|
+
]);
|
|
59991
|
+
const selectKind = (0, react$1.useCallback)((k) => {
|
|
59992
|
+
setOwner(k.owner);
|
|
59993
|
+
setKind(k.kind);
|
|
59994
|
+
}, [setKind, setOwner]);
|
|
59995
|
+
const columns = (0, react$1.useMemo)(() => {
|
|
59996
|
+
return [
|
|
59997
|
+
{
|
|
59998
|
+
key: "kind",
|
|
59999
|
+
header: t("uiLibrary.storageRecords.colKind", "Kind"),
|
|
60000
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
60001
|
+
className: "font-mono text-xs",
|
|
60002
|
+
children: [
|
|
60003
|
+
row.owner,
|
|
60004
|
+
"/",
|
|
60005
|
+
row.kind
|
|
60006
|
+
]
|
|
60007
|
+
}),
|
|
60008
|
+
sortValue: (row) => kindKey(row)
|
|
60009
|
+
},
|
|
60010
|
+
{
|
|
60011
|
+
key: "database",
|
|
60012
|
+
header: t("uiLibrary.storageRecords.colDatabase", "DB"),
|
|
60013
|
+
nowrap: true,
|
|
60014
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
60015
|
+
variant: "neutral",
|
|
60016
|
+
style: "outline",
|
|
60017
|
+
children: row.database
|
|
60018
|
+
}),
|
|
60019
|
+
sortValue: (row) => row.database
|
|
60020
|
+
},
|
|
60021
|
+
{
|
|
60022
|
+
key: "class",
|
|
60023
|
+
header: t("uiLibrary.storageRecords.colClass", "Class"),
|
|
60024
|
+
nowrap: true,
|
|
60025
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
60026
|
+
className: "text-xs text-foreground-muted",
|
|
60027
|
+
children: row.class
|
|
60028
|
+
}),
|
|
60029
|
+
sortValue: (row) => row.class
|
|
60030
|
+
},
|
|
60031
|
+
{
|
|
60032
|
+
key: "state",
|
|
60033
|
+
header: t("uiLibrary.storageRecords.colState", "State"),
|
|
60034
|
+
nowrap: true,
|
|
60035
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
60036
|
+
variant: row.state === "declared" ? "default" : "neutral",
|
|
60037
|
+
children: row.state
|
|
60038
|
+
}),
|
|
60039
|
+
sortValue: (row) => row.state
|
|
60040
|
+
},
|
|
60041
|
+
{
|
|
60042
|
+
key: "rows",
|
|
60043
|
+
header: t("uiLibrary.storageRecords.colRows", "Rows"),
|
|
60044
|
+
align: "right",
|
|
60045
|
+
nowrap: true,
|
|
60046
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
60047
|
+
className: "font-mono text-xs tabular-nums",
|
|
60048
|
+
children: row.rows === null ? "—" : row.rows
|
|
60049
|
+
}),
|
|
60050
|
+
sortValue: (row) => row.rows ?? -1
|
|
60051
|
+
},
|
|
60052
|
+
{
|
|
60053
|
+
key: "bytes",
|
|
60054
|
+
header: t("uiLibrary.storageRecords.colSize", "Est. size"),
|
|
60055
|
+
align: "right",
|
|
60056
|
+
nowrap: true,
|
|
60057
|
+
render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
60058
|
+
className: "text-xs text-foreground-subtle",
|
|
60059
|
+
children: formatBytes(row.bytesEstimate)
|
|
60060
|
+
}),
|
|
60061
|
+
sortValue: (row) => row.bytesEstimate ?? -1
|
|
60062
|
+
},
|
|
60063
|
+
{
|
|
60064
|
+
key: "inspect",
|
|
60065
|
+
header: "",
|
|
60066
|
+
align: "right",
|
|
60067
|
+
nowrap: true,
|
|
60068
|
+
render: (row) => {
|
|
60069
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
60070
|
+
type: "button",
|
|
60071
|
+
size: "sm",
|
|
60072
|
+
variant: active !== null && kindKey(active) === kindKey(row) ? "primary" : "outline",
|
|
60073
|
+
onClick: () => selectKind(row),
|
|
60074
|
+
children: t("uiLibrary.storageRecords.viewRows", "View rows")
|
|
60075
|
+
});
|
|
60076
|
+
},
|
|
60077
|
+
notSortableReason: t("uiLibrary.storageRecords.actionColumn", "Actions")
|
|
60078
|
+
}
|
|
60079
|
+
];
|
|
60080
|
+
}, [
|
|
60081
|
+
active,
|
|
60082
|
+
selectKind,
|
|
60083
|
+
t
|
|
60084
|
+
]);
|
|
60085
|
+
if (kindsQuery.isError) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBox, { message: kindsQuery.error instanceof Error ? kindsQuery.error.message : t("uiLibrary.storageRecords.kindsFailed", "Could not load record kinds") });
|
|
60086
|
+
if (kindsQuery.isPending) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
60087
|
+
className: "flex justify-center py-10 text-foreground-subtle",
|
|
60088
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-5 w-5 animate-spin" })
|
|
60089
|
+
});
|
|
60090
|
+
if (kinds.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {
|
|
60091
|
+
title: t("uiLibrary.storageRecords.noKindsTitle", "No record kinds"),
|
|
60092
|
+
description: t("uiLibrary.storageRecords.noKindsDescription", "No addon has declared a record kind on this hub yet.")
|
|
60093
|
+
});
|
|
60094
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
60095
|
+
className: "space-y-4",
|
|
60096
|
+
children: [
|
|
60097
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableFilterBar, {
|
|
60098
|
+
state: filter.state,
|
|
60099
|
+
facets: filter.facets,
|
|
60100
|
+
groups: filter.groups.map((g) => ({
|
|
60101
|
+
...g,
|
|
60102
|
+
label: g.id === "owner" ? t("uiLibrary.storageRecords.facetOwner", "Owner") : t("uiLibrary.storageRecords.facetDatabase", "Database")
|
|
60103
|
+
})),
|
|
60104
|
+
labels: {
|
|
60105
|
+
searchPlaceholder: t("uiLibrary.storageRecords.search", "Search kinds…"),
|
|
60106
|
+
searchLabel: t("uiLibrary.storageRecords.searchLabel", "Search record kinds"),
|
|
60107
|
+
facetsLabel: t("uiLibrary.storageRecords.facetsLabel", "Filter record kinds"),
|
|
60108
|
+
clearLabel: t("uiLibrary.storageRecords.clearFilters", "Clear filters")
|
|
60109
|
+
},
|
|
60110
|
+
onSearch: filter.setSearch,
|
|
60111
|
+
onToggleFacet: filter.toggleFacet,
|
|
60112
|
+
onToggleGroupFacet: filter.toggleGroupFacet,
|
|
60113
|
+
onClear: filter.clear
|
|
60114
|
+
}),
|
|
60115
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, {
|
|
60116
|
+
columns,
|
|
60117
|
+
rows: filter.rows,
|
|
60118
|
+
rowKey: (k) => kindKey(k),
|
|
60119
|
+
maxBodyHeight: TABLE_SCROLL_MAX_H,
|
|
60120
|
+
defaultSort: {
|
|
60121
|
+
columnKey: "kind",
|
|
60122
|
+
dir: "asc"
|
|
60123
|
+
},
|
|
60124
|
+
emptyMessage: t("uiLibrary.storageRecords.noMatch", "No kinds match the filter")
|
|
60125
|
+
}),
|
|
60126
|
+
active !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StorageRecordsPanel, { kind: active }) : null
|
|
60127
|
+
]
|
|
60128
|
+
});
|
|
60129
|
+
}
|
|
60130
|
+
//#endregion
|
|
58686
60131
|
//#region src/composites/response-log.tsx
|
|
58687
60132
|
/**
|
|
58688
60133
|
* Expandable log box showing the raw JSON response from a pipeline run.
|
|
@@ -63152,6 +64597,7 @@ exports.EllipsisVertical = EllipsisVertical;
|
|
|
63152
64597
|
exports.EmptyState = EmptyState;
|
|
63153
64598
|
exports.EndpointResultsCard = EndpointResultsCard;
|
|
63154
64599
|
exports.EndpointTestCard = EndpointTestCard;
|
|
64600
|
+
exports.EntryJsonSchemaForm = EntryJsonSchemaForm;
|
|
63155
64601
|
exports.ErrorBox = ErrorBox;
|
|
63156
64602
|
exports.EventKindGlyph = EventKindGlyph;
|
|
63157
64603
|
exports.EventStream = EventStream;
|
|
@@ -63199,6 +64645,7 @@ exports.Hammer = Hammer;
|
|
|
63199
64645
|
exports.Hand = Hand;
|
|
63200
64646
|
exports.HardDrive = HardDrive;
|
|
63201
64647
|
exports.Hash = Hash;
|
|
64648
|
+
exports.Heater = Heater;
|
|
63202
64649
|
exports.HlsVideo = HlsVideo;
|
|
63203
64650
|
exports.House = House;
|
|
63204
64651
|
exports.HoverZoomImage = HoverZoomImage;
|
|
@@ -63339,6 +64786,8 @@ exports.QrCode = QrCode;
|
|
|
63339
64786
|
exports.QrCode$1 = QrCode$1;
|
|
63340
64787
|
exports.RECONNECT_POLICY = RECONNECT_POLICY;
|
|
63341
64788
|
exports.RECORDED_PLAYBACK_MODES = RECORDED_PLAYBACK_MODES;
|
|
64789
|
+
exports.RECORD_ENTRY_KIND_PARAM = RECORD_ENTRY_KIND_PARAM;
|
|
64790
|
+
exports.RECORD_ENTRY_OWNER_PARAM = RECORD_ENTRY_OWNER_PARAM;
|
|
63342
64791
|
exports.RIGHT = RIGHT;
|
|
63343
64792
|
exports.ROI_AREA_WARN_FRACTION = ROI_AREA_WARN_FRACTION;
|
|
63344
64793
|
exports.ROLE_DESCRIPTOR = ROLE_DESCRIPTOR;
|
|
@@ -63347,6 +64796,8 @@ exports.Radar = Radar;
|
|
|
63347
64796
|
exports.RadialGauge = RadialGauge;
|
|
63348
64797
|
exports.Radio = Radio;
|
|
63349
64798
|
exports.RadioTower = RadioTower;
|
|
64799
|
+
exports.RecordEntriesHub = RecordEntriesHub;
|
|
64800
|
+
exports.RecordEntriesPanel = RecordEntriesPanel;
|
|
63350
64801
|
exports.RecordedPlaybackProvider = RecordedPlaybackProvider;
|
|
63351
64802
|
exports.RecordingPanel = RecordingPanel;
|
|
63352
64803
|
exports.RefreshCw = RefreshCw;
|
|
@@ -63379,6 +64830,8 @@ exports.SPLIT_PANEL_SIDE = SPLIT_PANEL_SIDE;
|
|
|
63379
64830
|
exports.STACK_GAP = STACK_GAP;
|
|
63380
64831
|
exports.STATE_COLOR = STATE_COLOR;
|
|
63381
64832
|
exports.STEP_DOCK_WIDTH_PX = STEP_DOCK_WIDTH_PX;
|
|
64833
|
+
exports.STORAGE_RECORDS_KIND_PARAM = STORAGE_RECORDS_KIND_PARAM;
|
|
64834
|
+
exports.STORAGE_RECORDS_OWNER_PARAM = STORAGE_RECORDS_OWNER_PARAM;
|
|
63382
64835
|
exports.Sailboat = Sailboat;
|
|
63383
64836
|
exports.Save = Save;
|
|
63384
64837
|
exports.Scan = Scan;
|
|
@@ -63437,6 +64890,8 @@ exports.StepInspectorPanel = StepInspectorPanel;
|
|
|
63437
64890
|
exports.StepModelRow = StepModelRow;
|
|
63438
64891
|
exports.StepTimings = StepTimings;
|
|
63439
64892
|
exports.Stepper = Stepper;
|
|
64893
|
+
exports.StorageRecordsHub = StorageRecordsHub;
|
|
64894
|
+
exports.StorageRecordsPanel = StorageRecordsPanel;
|
|
63440
64895
|
exports.StreamBrokerSelector = StreamBrokerSelector;
|
|
63441
64896
|
exports.StreamDebugOverlay = StreamDebugOverlay;
|
|
63442
64897
|
exports.StreamPanel = StreamPanel;
|
|
@@ -63466,6 +64921,7 @@ exports.ThemeProvider = require_theme_index.ThemeProvider$1;
|
|
|
63466
64921
|
exports.Thermometer = Thermometer;
|
|
63467
64922
|
exports.ThermostatHeroCard = ThermostatHeroCard;
|
|
63468
64923
|
exports.ThermostatInlineControl = ThermostatInlineControl;
|
|
64924
|
+
exports.Timer = Timer;
|
|
63469
64925
|
exports.TimezoneSelector = TimezoneSelector;
|
|
63470
64926
|
exports.ToggleLeft = ToggleLeft;
|
|
63471
64927
|
exports.ToggleRight = ToggleRight;
|
|
@@ -63629,6 +65085,8 @@ exports.isDataTableFilterActive = isDataTableFilterActive;
|
|
|
63629
65085
|
exports.isFieldVisible = isFieldVisible;
|
|
63630
65086
|
exports.isGroupExpanded = isGroupExpanded;
|
|
63631
65087
|
exports.isKeychainFieldValue = isKeychainFieldValue;
|
|
65088
|
+
exports.isRedactedSecret = isRedactedSecret;
|
|
65089
|
+
exports.isSecretField = isSecretField;
|
|
63632
65090
|
exports.isValueField = isValueField;
|
|
63633
65091
|
exports.lawnMowerActivityMeta = lawnMowerActivityMeta;
|
|
63634
65092
|
exports.lightColors = require_theme_to_root_css.lightColors;
|
|
@@ -63637,6 +65095,7 @@ exports.loadRemoteBundle = loadRemoteBundle;
|
|
|
63637
65095
|
exports.lucideExportName = lucideExportName;
|
|
63638
65096
|
exports.makeScrubBridge = makeScrubBridge;
|
|
63639
65097
|
exports.mapRootDetectorPlacements = mapRootDetectorPlacements;
|
|
65098
|
+
exports.mergeSecretForSubmit = mergeSecretForSubmit;
|
|
63640
65099
|
exports.metadataEntries = metadataEntries;
|
|
63641
65100
|
exports.metadataString = metadataString;
|
|
63642
65101
|
exports.mirror = mirror;
|
|
@@ -63709,6 +65168,7 @@ exports.routerBasename = routerBasename;
|
|
|
63709
65168
|
exports.sameLocationSuggestions = sameLocationSuggestions;
|
|
63710
65169
|
exports.scrubReducer = scrubReducer;
|
|
63711
65170
|
exports.searchSteps = searchSteps;
|
|
65171
|
+
exports.secretDisplayValue = secretDisplayValue;
|
|
63712
65172
|
exports.secretSubmitValue = secretSubmitValue;
|
|
63713
65173
|
exports.selectPickerGroup = selectPickerGroup;
|
|
63714
65174
|
exports.selectedDeviceOptions = selectedDeviceOptions;
|
|
@@ -64142,6 +65602,15 @@ exports.useDeviceStateSlice$1 = useDeviceStateSlice;
|
|
|
64142
65602
|
exports.useDeviceStatusGetStatus = useDeviceStatusGetStatus;
|
|
64143
65603
|
exports.useDeviceWebrtc = useDeviceWebrtc;
|
|
64144
65604
|
exports.useDevices = useDevices;
|
|
65605
|
+
exports.useDishwasherGetStatus = useDishwasherGetStatus;
|
|
65606
|
+
exports.useDishwasherPause = useDishwasherPause;
|
|
65607
|
+
exports.useDishwasherSetHalfLoad = useDishwasherSetHalfLoad;
|
|
65608
|
+
exports.useDishwasherSetHygienePlus = useDishwasherSetHygienePlus;
|
|
65609
|
+
exports.useDishwasherSetOption = useDishwasherSetOption;
|
|
65610
|
+
exports.useDishwasherSetPower = useDishwasherSetPower;
|
|
65611
|
+
exports.useDishwasherSetProgram = useDishwasherSetProgram;
|
|
65612
|
+
exports.useDishwasherStart = useDishwasherStart;
|
|
65613
|
+
exports.useDishwasherStop = useDishwasherStop;
|
|
64145
65614
|
exports.useDisplayIconResolver = useDisplayIconResolver;
|
|
64146
65615
|
exports.useDoorbellEvents = useDoorbellEvents;
|
|
64147
65616
|
exports.useDoorbellGetStatus = useDoorbellGetStatus;
|
|
@@ -64174,6 +65643,7 @@ exports.useFanControlSetPreset = useFanControlSetPreset;
|
|
|
64174
65643
|
exports.useFeatureProbeGetStatus = useFeatureProbeGetStatus;
|
|
64175
65644
|
exports.useFloodGetStatus = useFloodGetStatus;
|
|
64176
65645
|
exports.useGasGetStatus = useGasGetStatus;
|
|
65646
|
+
exports.useHeatingValveGetStatus = useHeatingValveGetStatus;
|
|
64177
65647
|
exports.useHumidifierGetStatus = useHumidifierGetStatus;
|
|
64178
65648
|
exports.useHumidifierSetMode = useHumidifierSetMode;
|
|
64179
65649
|
exports.useHumidifierSetOn = useHumidifierSetOn;
|