@camstack/ui-library 1.2.327 → 1.2.328
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/composites/device-controls/sensor-hero-source.d.ts +24 -0
- package/dist/index.cjs +119 -15
- package/dist/index.js +120 -16
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SensorDescriptor } from "./sensor-value-atom";
|
|
2
|
+
export type SnapshotStatus = "pending" | "error" | "success";
|
|
3
|
+
export type SensorHeroResolution = {
|
|
4
|
+
readonly kind: "value";
|
|
5
|
+
readonly slice: unknown;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: "awaiting";
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: "quiet";
|
|
10
|
+
};
|
|
11
|
+
export interface SensorHeroSourceInput {
|
|
12
|
+
readonly addonId: string;
|
|
13
|
+
readonly role: string | null | undefined;
|
|
14
|
+
readonly descriptor: SensorDescriptor | undefined;
|
|
15
|
+
readonly capName: string | undefined;
|
|
16
|
+
readonly live: unknown;
|
|
17
|
+
readonly snapshotStatus: SnapshotStatus;
|
|
18
|
+
readonly snapshotCaps: unknown;
|
|
19
|
+
}
|
|
20
|
+
/** Cap a role's primary reading is stored on. Kept beside the hero, not a second renderer. */
|
|
21
|
+
export declare const ROLE_OUTPUT_CAP: Readonly<Record<string, string>>;
|
|
22
|
+
/** The descriptor can render this slice. `false` and `0` count; a missing field does not. */
|
|
23
|
+
export declare function sensorSliceReady(descriptor: SensorDescriptor, slice: unknown): boolean;
|
|
24
|
+
export declare function resolveSensorHeroReading(input: SensorHeroSourceInput): SensorHeroResolution;
|
package/dist/index.cjs
CHANGED
|
@@ -14428,7 +14428,7 @@ Button.displayName = "Button";
|
|
|
14428
14428
|
//#region src/composites/account-sign-in-field.tsx
|
|
14429
14429
|
var INPUT_CLASS$1 = "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none disabled:opacity-50 disabled:cursor-not-allowed";
|
|
14430
14430
|
var BARE_CODE = /^[A-Za-z0-9._~-]{8,}$/;
|
|
14431
|
-
function isRecord$
|
|
14431
|
+
function isRecord$4(value) {
|
|
14432
14432
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14433
14433
|
}
|
|
14434
14434
|
function isPresent(value) {
|
|
@@ -14457,7 +14457,7 @@ function choicesOf(value) {
|
|
|
14457
14457
|
if (!Array.isArray(value)) return [];
|
|
14458
14458
|
const out = [];
|
|
14459
14459
|
for (const row of value) {
|
|
14460
|
-
if (!isRecord$
|
|
14460
|
+
if (!isRecord$4(row) || typeof row["value"] !== "string" || typeof row["label"] !== "string") continue;
|
|
14461
14461
|
out.push({
|
|
14462
14462
|
value: row["value"],
|
|
14463
14463
|
label: row["label"],
|
|
@@ -14511,7 +14511,7 @@ function AccountSignInField({ field, values, disabled, onAction, onChangeMany })
|
|
|
14511
14511
|
setError(null);
|
|
14512
14512
|
try {
|
|
14513
14513
|
const result = await onAction(field.beginAction, field.key, {});
|
|
14514
|
-
if (!isRecord$
|
|
14514
|
+
if (!isRecord$4(result) || typeof result["authorizeUrl"] !== "string" || typeof result["attemptId"] !== "string") {
|
|
14515
14515
|
setError(t("uiLibrary.accountSignIn.noLink", "The sign-in page could not be opened. Try again."));
|
|
14516
14516
|
return;
|
|
14517
14517
|
}
|
|
@@ -14539,7 +14539,7 @@ function AccountSignInField({ field, values, disabled, onAction, onChangeMany })
|
|
|
14539
14539
|
attemptId,
|
|
14540
14540
|
callback
|
|
14541
14541
|
});
|
|
14542
|
-
if (!isRecord$
|
|
14542
|
+
if (!isRecord$4(result)) {
|
|
14543
14543
|
setError(t("uiLibrary.accountSignIn.rejected", "The sign-in was not accepted. Start again."));
|
|
14544
14544
|
return;
|
|
14545
14545
|
}
|
|
@@ -19906,11 +19906,11 @@ function useDeviceSnapshotImage(deviceId, options = {}) {
|
|
|
19906
19906
|
//#region src/composites/cap-settings/camera-grid-layout.ts
|
|
19907
19907
|
/** Smallest rectangle the editor will produce — ffmpeg refuses a zero-area crop. */
|
|
19908
19908
|
var MIN_EXTENT = .02;
|
|
19909
|
-
function isRecord$
|
|
19909
|
+
function isRecord$3(value) {
|
|
19910
19910
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19911
19911
|
}
|
|
19912
19912
|
function readRect(value) {
|
|
19913
|
-
if (!isRecord$
|
|
19913
|
+
if (!isRecord$3(value)) return null;
|
|
19914
19914
|
const { x, y, width, height } = value;
|
|
19915
19915
|
if (typeof x !== "number" || typeof y !== "number" || typeof width !== "number" || typeof height !== "number") return null;
|
|
19916
19916
|
if (x < 0 || y < 0 || width <= 0 || height <= 0) return null;
|
|
@@ -19923,7 +19923,7 @@ function readRect(value) {
|
|
|
19923
19923
|
};
|
|
19924
19924
|
}
|
|
19925
19925
|
function readCell(value) {
|
|
19926
|
-
if (!isRecord$
|
|
19926
|
+
if (!isRecord$3(value)) return null;
|
|
19927
19927
|
const deviceId = value["deviceId"];
|
|
19928
19928
|
if (typeof deviceId !== "number" || !Number.isInteger(deviceId)) return null;
|
|
19929
19929
|
const source = readRect(value["source"]);
|
|
@@ -19936,7 +19936,7 @@ function readCell(value) {
|
|
|
19936
19936
|
};
|
|
19937
19937
|
}
|
|
19938
19938
|
function readProfileOffer(value) {
|
|
19939
|
-
if (!isRecord$
|
|
19939
|
+
if (!isRecord$3(value)) return null;
|
|
19940
19940
|
const profile = value["profile"];
|
|
19941
19941
|
const offered = value["offered"];
|
|
19942
19942
|
const missing = value["missingSources"];
|
|
@@ -19965,7 +19965,7 @@ function readProfileOffer(value) {
|
|
|
19965
19965
|
*/
|
|
19966
19966
|
function parseGridView(raw) {
|
|
19967
19967
|
if (raw === null) return null;
|
|
19968
|
-
if (!isRecord$
|
|
19968
|
+
if (!isRecord$3(raw)) return "unreadable";
|
|
19969
19969
|
const { instanceId, deviceId, name, fps, cells, profiles } = raw;
|
|
19970
19970
|
if (typeof instanceId !== "string" || typeof deviceId !== "number" || typeof name !== "string" || typeof fps !== "number" || !Array.isArray(cells) || !Array.isArray(profiles)) return "unreadable";
|
|
19971
19971
|
const parsed = [];
|
|
@@ -34127,10 +34127,7 @@ function RunningBadge({ running, children, className }) {
|
|
|
34127
34127
|
function RunningStatusLine({ running, since, phase, className, now = Date.now() }) {
|
|
34128
34128
|
const t = useUiText();
|
|
34129
34129
|
const parts = runningStatusParts(running, since, phase, now);
|
|
34130
|
-
if (parts.key === "unknown"
|
|
34131
|
-
className: cn("text-[11px] text-foreground-subtle", className),
|
|
34132
|
-
children: t("uiLibrary.runningStatus.unknown", "Unknown")
|
|
34133
|
-
});
|
|
34130
|
+
if (parts.key === "unknown" || running !== true && running !== false) return null;
|
|
34134
34131
|
if (parts.key === "idle") {
|
|
34135
34132
|
const base = t("uiLibrary.runningStatus.idle", "Idle");
|
|
34136
34133
|
const text = parts.phase === void 0 ? base : t("uiLibrary.runningStatus.withPhase", "{{state}} · {{phase}}", {
|
|
@@ -47382,6 +47379,92 @@ function SensorInlineControl({ trpc, deviceId }) {
|
|
|
47382
47379
|
});
|
|
47383
47380
|
}
|
|
47384
47381
|
//#endregion
|
|
47382
|
+
//#region src/composites/device-controls/sensor-hero-source.ts
|
|
47383
|
+
/**
|
|
47384
|
+
* Where a sensor hero reads its number. The live proxy slice is the
|
|
47385
|
+
* usual source. A composed device publishes the same reading on the
|
|
47386
|
+
* snapshot before that proxy has a binding, and it has no running-state
|
|
47387
|
+
* to stand in for the reading. An answered snapshot with no primary
|
|
47388
|
+
* field is quiet — it must not say the value is still on its way.
|
|
47389
|
+
*/
|
|
47390
|
+
/** Cap a role's primary reading is stored on. Kept beside the hero, not a second renderer. */
|
|
47391
|
+
var ROLE_OUTPUT_CAP = {
|
|
47392
|
+
"temperature-sensor": "temperature-sensor",
|
|
47393
|
+
"humidity-sensor": "humidity-sensor",
|
|
47394
|
+
"ambient-light-sensor": "ambient-light-sensor",
|
|
47395
|
+
"pressure-sensor": "pressure-sensor",
|
|
47396
|
+
"numeric-sensor": "numeric-sensor",
|
|
47397
|
+
"wind-sensor": "numeric-sensor",
|
|
47398
|
+
"rain-sensor": "numeric-sensor",
|
|
47399
|
+
"uv-sensor": "numeric-sensor",
|
|
47400
|
+
"solar-radiation-sensor": "numeric-sensor",
|
|
47401
|
+
"soil-moisture-sensor": "numeric-sensor",
|
|
47402
|
+
"power-sensor": "power-meter",
|
|
47403
|
+
"energy-sensor": "power-meter",
|
|
47404
|
+
"voltage-sensor": "power-meter",
|
|
47405
|
+
"current-sensor": "power-meter",
|
|
47406
|
+
"battery-sensor": "numeric-sensor",
|
|
47407
|
+
"generic-sensor": "numeric-sensor",
|
|
47408
|
+
"motion-sensor": "motion",
|
|
47409
|
+
"pir-sensor": "motion",
|
|
47410
|
+
"contact-sensor": "contact",
|
|
47411
|
+
"smoke-sensor": "smoke",
|
|
47412
|
+
"leak-sensor": "flood",
|
|
47413
|
+
"co-sensor": "carbon-monoxide",
|
|
47414
|
+
"binary-sensor": "binary",
|
|
47415
|
+
presence: "presence",
|
|
47416
|
+
"enum-sensor": "enum-sensor",
|
|
47417
|
+
"datetime-sensor": "enum-sensor"
|
|
47418
|
+
};
|
|
47419
|
+
function isRecord$2(value) {
|
|
47420
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47421
|
+
}
|
|
47422
|
+
/** The descriptor can render this slice. `false` and `0` count; a missing field does not. */
|
|
47423
|
+
function sensorSliceReady(descriptor, slice) {
|
|
47424
|
+
if (!isRecord$2(slice)) return false;
|
|
47425
|
+
if (descriptor.kind === "presence") return typeof slice["state"] === "string";
|
|
47426
|
+
if (descriptor.kind === "string" || descriptor.kind === "datetime") return typeof slice["value"] === "string" && slice["value"] !== "";
|
|
47427
|
+
const value = slice[descriptor.field];
|
|
47428
|
+
if (descriptor.kind === "numeric") return typeof value === "number";
|
|
47429
|
+
return typeof value === "boolean";
|
|
47430
|
+
}
|
|
47431
|
+
function capSlice(caps, capName) {
|
|
47432
|
+
if (capName === void 0 || !isRecord$2(caps)) return void 0;
|
|
47433
|
+
return caps[capName];
|
|
47434
|
+
}
|
|
47435
|
+
/**
|
|
47436
|
+
* A battery group writes `battery.percentage`. The role's usual cap is
|
|
47437
|
+
* `numeric-sensor.value`. Hand the renderer a slice whose `value` is that
|
|
47438
|
+
* percentage so one numeric path draws both.
|
|
47439
|
+
*/
|
|
47440
|
+
function batteryPercentageSlice(caps) {
|
|
47441
|
+
const battery = capSlice(caps, "battery");
|
|
47442
|
+
if (!isRecord$2(battery) || typeof battery["percentage"] !== "number") return void 0;
|
|
47443
|
+
return {
|
|
47444
|
+
...battery,
|
|
47445
|
+
value: battery["percentage"]
|
|
47446
|
+
};
|
|
47447
|
+
}
|
|
47448
|
+
function snapshotSlice(input) {
|
|
47449
|
+
const direct = capSlice(input.snapshotCaps, input.capName);
|
|
47450
|
+
if (input.descriptor !== void 0 && sensorSliceReady(input.descriptor, direct)) return direct;
|
|
47451
|
+
if (input.role === "battery-sensor" && input.descriptor?.kind === "numeric") return batteryPercentageSlice(input.snapshotCaps);
|
|
47452
|
+
}
|
|
47453
|
+
function resolveSensorHeroReading(input) {
|
|
47454
|
+
const descriptor = input.descriptor;
|
|
47455
|
+
if (descriptor !== void 0 && sensorSliceReady(descriptor, input.live)) return {
|
|
47456
|
+
kind: "value",
|
|
47457
|
+
slice: input.live
|
|
47458
|
+
};
|
|
47459
|
+
const fromSnapshot = snapshotSlice(input);
|
|
47460
|
+
if (descriptor !== void 0 && sensorSliceReady(descriptor, fromSnapshot)) return {
|
|
47461
|
+
kind: "value",
|
|
47462
|
+
slice: fromSnapshot
|
|
47463
|
+
};
|
|
47464
|
+
if (input.addonId === _camstack_types.COMPOSER_ADDON_ID && input.snapshotStatus === "success") return { kind: "quiet" };
|
|
47465
|
+
return { kind: "awaiting" };
|
|
47466
|
+
}
|
|
47467
|
+
//#endregion
|
|
47385
47468
|
//#region src/composites/device-controls/sensor-hero-card.tsx
|
|
47386
47469
|
/**
|
|
47387
47470
|
* SensorHeroCard — large-format sensor value display for the device-detail
|
|
@@ -47448,7 +47531,28 @@ function SensorHeroInner({ trpc, device }) {
|
|
|
47448
47531
|
const occupancySlice = useDeviceStateSlice(dev !== null && !isBattery ? dev.state.binary : void 0);
|
|
47449
47532
|
const useBinaryBattery = isBattery && roleDescriptor?.kind === "numeric" && !hasBatteryNumericValue(numericSlice, roleDescriptor.field) && binaryBatterySlice !== void 0;
|
|
47450
47533
|
const descriptor = useBinaryBattery ? BINARY_BATTERY_DESCRIPTOR : roleDescriptor;
|
|
47451
|
-
const
|
|
47534
|
+
const liveSlice = useBinaryBattery ? binaryBatterySlice : numericSlice;
|
|
47535
|
+
const snapshot = useDeviceStateGetSnapshot({ deviceId: device.id }, { staleTime: 5e3 });
|
|
47536
|
+
const capName = useBinaryBattery ? "binary" : device.role != null ? ROLE_OUTPUT_CAP[device.role] : ROLE_OUTPUT_CAP["temperature-sensor"];
|
|
47537
|
+
const snapshotStatus = snapshot.status === "success" || snapshot.status === "error" ? snapshot.status : "pending";
|
|
47538
|
+
const resolution = resolveSensorHeroReading({
|
|
47539
|
+
addonId: device.addonId,
|
|
47540
|
+
role: device.role,
|
|
47541
|
+
descriptor,
|
|
47542
|
+
capName,
|
|
47543
|
+
live: liveSlice,
|
|
47544
|
+
snapshotStatus,
|
|
47545
|
+
snapshotCaps: snapshot.data
|
|
47546
|
+
});
|
|
47547
|
+
if (resolution.kind === "quiet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
47548
|
+
className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
|
|
47549
|
+
"data-testid": "sensor-hero-quiet",
|
|
47550
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
47551
|
+
className: "text-sm text-foreground-subtle",
|
|
47552
|
+
children: device.name
|
|
47553
|
+
})
|
|
47554
|
+
});
|
|
47555
|
+
const slice = resolution.kind === "value" ? resolution.slice : void 0;
|
|
47452
47556
|
if (descriptor === void 0 || slice === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47453
47557
|
className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
|
|
47454
47558
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -47467,7 +47571,7 @@ function SensorHeroInner({ trpc, device }) {
|
|
|
47467
47571
|
const rendered = resolveSensorDisplay({
|
|
47468
47572
|
display: device.display,
|
|
47469
47573
|
capName: device.role === "numeric-sensor" ? "numeric-sensor" : void 0,
|
|
47470
|
-
roleDefault: device.role != null ? roleDefaultsData?.defaults[device.role] : roleDefaultsData?.defaults["temperature-sensor"],
|
|
47574
|
+
roleDefault: device.role != null ? roleDefaultsData?.defaults?.[device.role] : roleDefaultsData?.defaults?.["temperature-sensor"],
|
|
47471
47575
|
quantity: descriptor.quantity ?? "device",
|
|
47472
47576
|
deviceQuantity: deviceQuantity === "device" ? void 0 : deviceQuantity
|
|
47473
47577
|
}).format(rawValue);
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as __mfHostReact from "react";
|
|
|
5
5
|
import React, { Fragment, Suspense, cloneElement, createContext, createElement, forwardRef, lazy, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import * as __mfHostReactJsxRuntime from "react/jsx-runtime";
|
|
7
7
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
import { ALL_CAPABILITY_DEFINITIONS, CAM_PROFILE_ORDER, CLIP_STILL_DISPOSITION_HEADER, CLIP_STILL_REASON_CODE_HEADER, CLIP_STILL_REASON_HEADER, CONTAINER_CHILD_PRIORITY, ConsumableLevelSchema, DEFAULT_DISPLAY_PREFERENCE, DEFAULT_EVENTS_BAND_BUFFER_SEC, DEVICE_HISTORY_ENERGY_KEY, DEVICE_HISTORY_QUERY_MANY_MAX_ITEMS, DEVICE_TYPE_CONTROL_KIND, DeviceExportStatusSchema, DeviceFeature, DeviceRole, DeviceType, EXTENSION_SLOTS, EXTENSION_SLOT_IDS, EventCategory, ExposedDeviceSchema, FIRST_LEVEL_MACRO_CLASSES, HISTORY_CATALOG, ICON_CATEGORIES, KNOWN_CAP_NAMES, MAX_SENSOR_TRIGGER_DEVICES, MODEL_PROVIDER_IDS, NAVIGATION_LIGHT_LEVEL_MAX, NAVIGATION_LIGHT_LEVEL_MIN, NODE_COLOR_IDS, NodeColorIdSchema, PALETTE_ENTRY_IDS, PET_FEEDER_MANUAL_FEED_MAX, PET_FEEDER_MANUAL_FEED_MIN, RECORDING_AUDIO_TRIGGER_OFFERED_DBFS, RECORDING_SOURCE_CAMSTACK_ADDON, REDACTED_SECRET, RecordingConfigSchema, RecordingStatusSchema, SECRET_CLEAR, SOURCE_CAP_ACTIVE_FIELD, STATIONARY_OVERLAY_STYLE, SUBJECT_PATH_DWELL_EPS, SUBJECT_PATH_FULL_FRAME_COVERAGE, SUBJECT_PATH_MAX_POINTS, TIMEZONES, USE_ROOT_MODEL_SETTING_KEY, assignNodeColorId, assignNodeColorId as assignNodeColorId$1, baseUnitOf, buildModelVariantGroups, buildSubjectPath, bundledCamstack, canConvertUnit, canonicalIconKey, clearModelBoundOverrides, clearModelBoundSetting, clusterStepSettingFieldsFor, collectModelBoundKeys, consumableFormOf, consumableKindOf, createDeviceProxy, createLazyTrpcSource, createSliceHandle, dehydrateSchema, deriveRecordingMode, describeModelVariant, displayUnitFor, effectiveStepSettings, findTimezone, formatParkedFor, formatReading, hasModelBoundOverrides, iconCategoryLabel, iconsInCategory, isDrawableSubjectPath, isKeychainStoredField, isModelScopedStepSettingKey, isReadingQuantity, isRootSuppliableStepId, isSecretClear, isSecretFieldView, isSecretMarker, modelBindingKey, normalizeUnit, paintHex, parseOccupancyScope, projectSubjectPath, publicEventCategories, resolveContainerPrimaryChild, resolveDeviceControlKind, resolveIconKey, resolveModelBoundSetting, resolveRecordingProfiles, resolveRootSupply, resolveVariantModelId, resolveViewableDeviceIds, searchIcons, selectAssignedProfileSlots, stepSettingSource, subjectPathSampleFromBox, subjectPathSvgPoints, tryConvertUnit, unitsForDimension, withFieldFrom, withoutField, writeModelBoundSetting } from "@camstack/types";
|
|
8
|
+
import { ALL_CAPABILITY_DEFINITIONS, CAM_PROFILE_ORDER, CLIP_STILL_DISPOSITION_HEADER, CLIP_STILL_REASON_CODE_HEADER, CLIP_STILL_REASON_HEADER, COMPOSER_ADDON_ID, CONTAINER_CHILD_PRIORITY, ConsumableLevelSchema, DEFAULT_DISPLAY_PREFERENCE, DEFAULT_EVENTS_BAND_BUFFER_SEC, DEVICE_HISTORY_ENERGY_KEY, DEVICE_HISTORY_QUERY_MANY_MAX_ITEMS, DEVICE_TYPE_CONTROL_KIND, DeviceExportStatusSchema, DeviceFeature, DeviceRole, DeviceType, EXTENSION_SLOTS, EXTENSION_SLOT_IDS, EventCategory, ExposedDeviceSchema, FIRST_LEVEL_MACRO_CLASSES, HISTORY_CATALOG, ICON_CATEGORIES, KNOWN_CAP_NAMES, MAX_SENSOR_TRIGGER_DEVICES, MODEL_PROVIDER_IDS, NAVIGATION_LIGHT_LEVEL_MAX, NAVIGATION_LIGHT_LEVEL_MIN, NODE_COLOR_IDS, NodeColorIdSchema, PALETTE_ENTRY_IDS, PET_FEEDER_MANUAL_FEED_MAX, PET_FEEDER_MANUAL_FEED_MIN, RECORDING_AUDIO_TRIGGER_OFFERED_DBFS, RECORDING_SOURCE_CAMSTACK_ADDON, REDACTED_SECRET, RecordingConfigSchema, RecordingStatusSchema, SECRET_CLEAR, SOURCE_CAP_ACTIVE_FIELD, STATIONARY_OVERLAY_STYLE, SUBJECT_PATH_DWELL_EPS, SUBJECT_PATH_FULL_FRAME_COVERAGE, SUBJECT_PATH_MAX_POINTS, TIMEZONES, USE_ROOT_MODEL_SETTING_KEY, assignNodeColorId, assignNodeColorId as assignNodeColorId$1, baseUnitOf, buildModelVariantGroups, buildSubjectPath, bundledCamstack, canConvertUnit, canonicalIconKey, clearModelBoundOverrides, clearModelBoundSetting, clusterStepSettingFieldsFor, collectModelBoundKeys, consumableFormOf, consumableKindOf, createDeviceProxy, createLazyTrpcSource, createSliceHandle, dehydrateSchema, deriveRecordingMode, describeModelVariant, displayUnitFor, effectiveStepSettings, findTimezone, formatParkedFor, formatReading, hasModelBoundOverrides, iconCategoryLabel, iconsInCategory, isDrawableSubjectPath, isKeychainStoredField, isModelScopedStepSettingKey, isReadingQuantity, isRootSuppliableStepId, isSecretClear, isSecretFieldView, isSecretMarker, modelBindingKey, normalizeUnit, paintHex, parseOccupancyScope, projectSubjectPath, publicEventCategories, resolveContainerPrimaryChild, resolveDeviceControlKind, resolveIconKey, resolveModelBoundSetting, resolveRecordingProfiles, resolveRootSupply, resolveVariantModelId, resolveViewableDeviceIds, searchIcons, selectAssignedProfileSlots, stepSettingSource, subjectPathSampleFromBox, subjectPathSvgPoints, tryConvertUnit, unitsForDimension, withFieldFrom, withoutField, writeModelBoundSetting } from "@camstack/types";
|
|
9
9
|
import * as __mfHostTrpcReactQuery from "@trpc/react-query";
|
|
10
10
|
import { createTRPCReact } from "@trpc/react-query";
|
|
11
11
|
import * as __mfHostReactDOM from "react-dom";
|
|
@@ -14404,7 +14404,7 @@ Button.displayName = "Button";
|
|
|
14404
14404
|
//#region src/composites/account-sign-in-field.tsx
|
|
14405
14405
|
var INPUT_CLASS$1 = "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none disabled:opacity-50 disabled:cursor-not-allowed";
|
|
14406
14406
|
var BARE_CODE = /^[A-Za-z0-9._~-]{8,}$/;
|
|
14407
|
-
function isRecord$
|
|
14407
|
+
function isRecord$4(value) {
|
|
14408
14408
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14409
14409
|
}
|
|
14410
14410
|
function isPresent(value) {
|
|
@@ -14433,7 +14433,7 @@ function choicesOf(value) {
|
|
|
14433
14433
|
if (!Array.isArray(value)) return [];
|
|
14434
14434
|
const out = [];
|
|
14435
14435
|
for (const row of value) {
|
|
14436
|
-
if (!isRecord$
|
|
14436
|
+
if (!isRecord$4(row) || typeof row["value"] !== "string" || typeof row["label"] !== "string") continue;
|
|
14437
14437
|
out.push({
|
|
14438
14438
|
value: row["value"],
|
|
14439
14439
|
label: row["label"],
|
|
@@ -14487,7 +14487,7 @@ function AccountSignInField({ field, values, disabled, onAction, onChangeMany })
|
|
|
14487
14487
|
setError(null);
|
|
14488
14488
|
try {
|
|
14489
14489
|
const result = await onAction(field.beginAction, field.key, {});
|
|
14490
|
-
if (!isRecord$
|
|
14490
|
+
if (!isRecord$4(result) || typeof result["authorizeUrl"] !== "string" || typeof result["attemptId"] !== "string") {
|
|
14491
14491
|
setError(t("uiLibrary.accountSignIn.noLink", "The sign-in page could not be opened. Try again."));
|
|
14492
14492
|
return;
|
|
14493
14493
|
}
|
|
@@ -14515,7 +14515,7 @@ function AccountSignInField({ field, values, disabled, onAction, onChangeMany })
|
|
|
14515
14515
|
attemptId,
|
|
14516
14516
|
callback
|
|
14517
14517
|
});
|
|
14518
|
-
if (!isRecord$
|
|
14518
|
+
if (!isRecord$4(result)) {
|
|
14519
14519
|
setError(t("uiLibrary.accountSignIn.rejected", "The sign-in was not accepted. Start again."));
|
|
14520
14520
|
return;
|
|
14521
14521
|
}
|
|
@@ -19882,11 +19882,11 @@ function useDeviceSnapshotImage(deviceId, options = {}) {
|
|
|
19882
19882
|
//#region src/composites/cap-settings/camera-grid-layout.ts
|
|
19883
19883
|
/** Smallest rectangle the editor will produce — ffmpeg refuses a zero-area crop. */
|
|
19884
19884
|
var MIN_EXTENT = .02;
|
|
19885
|
-
function isRecord$
|
|
19885
|
+
function isRecord$3(value) {
|
|
19886
19886
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19887
19887
|
}
|
|
19888
19888
|
function readRect(value) {
|
|
19889
|
-
if (!isRecord$
|
|
19889
|
+
if (!isRecord$3(value)) return null;
|
|
19890
19890
|
const { x, y, width, height } = value;
|
|
19891
19891
|
if (typeof x !== "number" || typeof y !== "number" || typeof width !== "number" || typeof height !== "number") return null;
|
|
19892
19892
|
if (x < 0 || y < 0 || width <= 0 || height <= 0) return null;
|
|
@@ -19899,7 +19899,7 @@ function readRect(value) {
|
|
|
19899
19899
|
};
|
|
19900
19900
|
}
|
|
19901
19901
|
function readCell(value) {
|
|
19902
|
-
if (!isRecord$
|
|
19902
|
+
if (!isRecord$3(value)) return null;
|
|
19903
19903
|
const deviceId = value["deviceId"];
|
|
19904
19904
|
if (typeof deviceId !== "number" || !Number.isInteger(deviceId)) return null;
|
|
19905
19905
|
const source = readRect(value["source"]);
|
|
@@ -19912,7 +19912,7 @@ function readCell(value) {
|
|
|
19912
19912
|
};
|
|
19913
19913
|
}
|
|
19914
19914
|
function readProfileOffer(value) {
|
|
19915
|
-
if (!isRecord$
|
|
19915
|
+
if (!isRecord$3(value)) return null;
|
|
19916
19916
|
const profile = value["profile"];
|
|
19917
19917
|
const offered = value["offered"];
|
|
19918
19918
|
const missing = value["missingSources"];
|
|
@@ -19941,7 +19941,7 @@ function readProfileOffer(value) {
|
|
|
19941
19941
|
*/
|
|
19942
19942
|
function parseGridView(raw) {
|
|
19943
19943
|
if (raw === null) return null;
|
|
19944
|
-
if (!isRecord$
|
|
19944
|
+
if (!isRecord$3(raw)) return "unreadable";
|
|
19945
19945
|
const { instanceId, deviceId, name, fps, cells, profiles } = raw;
|
|
19946
19946
|
if (typeof instanceId !== "string" || typeof deviceId !== "number" || typeof name !== "string" || typeof fps !== "number" || !Array.isArray(cells) || !Array.isArray(profiles)) return "unreadable";
|
|
19947
19947
|
const parsed = [];
|
|
@@ -34103,10 +34103,7 @@ function RunningBadge({ running, children, className }) {
|
|
|
34103
34103
|
function RunningStatusLine({ running, since, phase, className, now = Date.now() }) {
|
|
34104
34104
|
const t = useUiText();
|
|
34105
34105
|
const parts = runningStatusParts(running, since, phase, now);
|
|
34106
|
-
if (parts.key === "unknown"
|
|
34107
|
-
className: cn("text-[11px] text-foreground-subtle", className),
|
|
34108
|
-
children: t("uiLibrary.runningStatus.unknown", "Unknown")
|
|
34109
|
-
});
|
|
34106
|
+
if (parts.key === "unknown" || running !== true && running !== false) return null;
|
|
34110
34107
|
if (parts.key === "idle") {
|
|
34111
34108
|
const base = t("uiLibrary.runningStatus.idle", "Idle");
|
|
34112
34109
|
const text = parts.phase === void 0 ? base : t("uiLibrary.runningStatus.withPhase", "{{state}} · {{phase}}", {
|
|
@@ -47358,6 +47355,92 @@ function SensorInlineControl({ trpc, deviceId }) {
|
|
|
47358
47355
|
});
|
|
47359
47356
|
}
|
|
47360
47357
|
//#endregion
|
|
47358
|
+
//#region src/composites/device-controls/sensor-hero-source.ts
|
|
47359
|
+
/**
|
|
47360
|
+
* Where a sensor hero reads its number. The live proxy slice is the
|
|
47361
|
+
* usual source. A composed device publishes the same reading on the
|
|
47362
|
+
* snapshot before that proxy has a binding, and it has no running-state
|
|
47363
|
+
* to stand in for the reading. An answered snapshot with no primary
|
|
47364
|
+
* field is quiet — it must not say the value is still on its way.
|
|
47365
|
+
*/
|
|
47366
|
+
/** Cap a role's primary reading is stored on. Kept beside the hero, not a second renderer. */
|
|
47367
|
+
var ROLE_OUTPUT_CAP = {
|
|
47368
|
+
"temperature-sensor": "temperature-sensor",
|
|
47369
|
+
"humidity-sensor": "humidity-sensor",
|
|
47370
|
+
"ambient-light-sensor": "ambient-light-sensor",
|
|
47371
|
+
"pressure-sensor": "pressure-sensor",
|
|
47372
|
+
"numeric-sensor": "numeric-sensor",
|
|
47373
|
+
"wind-sensor": "numeric-sensor",
|
|
47374
|
+
"rain-sensor": "numeric-sensor",
|
|
47375
|
+
"uv-sensor": "numeric-sensor",
|
|
47376
|
+
"solar-radiation-sensor": "numeric-sensor",
|
|
47377
|
+
"soil-moisture-sensor": "numeric-sensor",
|
|
47378
|
+
"power-sensor": "power-meter",
|
|
47379
|
+
"energy-sensor": "power-meter",
|
|
47380
|
+
"voltage-sensor": "power-meter",
|
|
47381
|
+
"current-sensor": "power-meter",
|
|
47382
|
+
"battery-sensor": "numeric-sensor",
|
|
47383
|
+
"generic-sensor": "numeric-sensor",
|
|
47384
|
+
"motion-sensor": "motion",
|
|
47385
|
+
"pir-sensor": "motion",
|
|
47386
|
+
"contact-sensor": "contact",
|
|
47387
|
+
"smoke-sensor": "smoke",
|
|
47388
|
+
"leak-sensor": "flood",
|
|
47389
|
+
"co-sensor": "carbon-monoxide",
|
|
47390
|
+
"binary-sensor": "binary",
|
|
47391
|
+
presence: "presence",
|
|
47392
|
+
"enum-sensor": "enum-sensor",
|
|
47393
|
+
"datetime-sensor": "enum-sensor"
|
|
47394
|
+
};
|
|
47395
|
+
function isRecord$2(value) {
|
|
47396
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47397
|
+
}
|
|
47398
|
+
/** The descriptor can render this slice. `false` and `0` count; a missing field does not. */
|
|
47399
|
+
function sensorSliceReady(descriptor, slice) {
|
|
47400
|
+
if (!isRecord$2(slice)) return false;
|
|
47401
|
+
if (descriptor.kind === "presence") return typeof slice["state"] === "string";
|
|
47402
|
+
if (descriptor.kind === "string" || descriptor.kind === "datetime") return typeof slice["value"] === "string" && slice["value"] !== "";
|
|
47403
|
+
const value = slice[descriptor.field];
|
|
47404
|
+
if (descriptor.kind === "numeric") return typeof value === "number";
|
|
47405
|
+
return typeof value === "boolean";
|
|
47406
|
+
}
|
|
47407
|
+
function capSlice(caps, capName) {
|
|
47408
|
+
if (capName === void 0 || !isRecord$2(caps)) return void 0;
|
|
47409
|
+
return caps[capName];
|
|
47410
|
+
}
|
|
47411
|
+
/**
|
|
47412
|
+
* A battery group writes `battery.percentage`. The role's usual cap is
|
|
47413
|
+
* `numeric-sensor.value`. Hand the renderer a slice whose `value` is that
|
|
47414
|
+
* percentage so one numeric path draws both.
|
|
47415
|
+
*/
|
|
47416
|
+
function batteryPercentageSlice(caps) {
|
|
47417
|
+
const battery = capSlice(caps, "battery");
|
|
47418
|
+
if (!isRecord$2(battery) || typeof battery["percentage"] !== "number") return void 0;
|
|
47419
|
+
return {
|
|
47420
|
+
...battery,
|
|
47421
|
+
value: battery["percentage"]
|
|
47422
|
+
};
|
|
47423
|
+
}
|
|
47424
|
+
function snapshotSlice(input) {
|
|
47425
|
+
const direct = capSlice(input.snapshotCaps, input.capName);
|
|
47426
|
+
if (input.descriptor !== void 0 && sensorSliceReady(input.descriptor, direct)) return direct;
|
|
47427
|
+
if (input.role === "battery-sensor" && input.descriptor?.kind === "numeric") return batteryPercentageSlice(input.snapshotCaps);
|
|
47428
|
+
}
|
|
47429
|
+
function resolveSensorHeroReading(input) {
|
|
47430
|
+
const descriptor = input.descriptor;
|
|
47431
|
+
if (descriptor !== void 0 && sensorSliceReady(descriptor, input.live)) return {
|
|
47432
|
+
kind: "value",
|
|
47433
|
+
slice: input.live
|
|
47434
|
+
};
|
|
47435
|
+
const fromSnapshot = snapshotSlice(input);
|
|
47436
|
+
if (descriptor !== void 0 && sensorSliceReady(descriptor, fromSnapshot)) return {
|
|
47437
|
+
kind: "value",
|
|
47438
|
+
slice: fromSnapshot
|
|
47439
|
+
};
|
|
47440
|
+
if (input.addonId === COMPOSER_ADDON_ID && input.snapshotStatus === "success") return { kind: "quiet" };
|
|
47441
|
+
return { kind: "awaiting" };
|
|
47442
|
+
}
|
|
47443
|
+
//#endregion
|
|
47361
47444
|
//#region src/composites/device-controls/sensor-hero-card.tsx
|
|
47362
47445
|
/**
|
|
47363
47446
|
* SensorHeroCard — large-format sensor value display for the device-detail
|
|
@@ -47424,7 +47507,28 @@ function SensorHeroInner({ trpc, device }) {
|
|
|
47424
47507
|
const occupancySlice = useDeviceStateSlice(dev !== null && !isBattery ? dev.state.binary : void 0);
|
|
47425
47508
|
const useBinaryBattery = isBattery && roleDescriptor?.kind === "numeric" && !hasBatteryNumericValue(numericSlice, roleDescriptor.field) && binaryBatterySlice !== void 0;
|
|
47426
47509
|
const descriptor = useBinaryBattery ? BINARY_BATTERY_DESCRIPTOR : roleDescriptor;
|
|
47427
|
-
const
|
|
47510
|
+
const liveSlice = useBinaryBattery ? binaryBatterySlice : numericSlice;
|
|
47511
|
+
const snapshot = useDeviceStateGetSnapshot({ deviceId: device.id }, { staleTime: 5e3 });
|
|
47512
|
+
const capName = useBinaryBattery ? "binary" : device.role != null ? ROLE_OUTPUT_CAP[device.role] : ROLE_OUTPUT_CAP["temperature-sensor"];
|
|
47513
|
+
const snapshotStatus = snapshot.status === "success" || snapshot.status === "error" ? snapshot.status : "pending";
|
|
47514
|
+
const resolution = resolveSensorHeroReading({
|
|
47515
|
+
addonId: device.addonId,
|
|
47516
|
+
role: device.role,
|
|
47517
|
+
descriptor,
|
|
47518
|
+
capName,
|
|
47519
|
+
live: liveSlice,
|
|
47520
|
+
snapshotStatus,
|
|
47521
|
+
snapshotCaps: snapshot.data
|
|
47522
|
+
});
|
|
47523
|
+
if (resolution.kind === "quiet") return /* @__PURE__ */ jsx("div", {
|
|
47524
|
+
className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
|
|
47525
|
+
"data-testid": "sensor-hero-quiet",
|
|
47526
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
47527
|
+
className: "text-sm text-foreground-subtle",
|
|
47528
|
+
children: device.name
|
|
47529
|
+
})
|
|
47530
|
+
});
|
|
47531
|
+
const slice = resolution.kind === "value" ? resolution.slice : void 0;
|
|
47428
47532
|
if (descriptor === void 0 || slice === void 0) return /* @__PURE__ */ jsxs("div", {
|
|
47429
47533
|
className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
|
|
47430
47534
|
children: [/* @__PURE__ */ jsx("span", {
|
|
@@ -47443,7 +47547,7 @@ function SensorHeroInner({ trpc, device }) {
|
|
|
47443
47547
|
const rendered = resolveSensorDisplay({
|
|
47444
47548
|
display: device.display,
|
|
47445
47549
|
capName: device.role === "numeric-sensor" ? "numeric-sensor" : void 0,
|
|
47446
|
-
roleDefault: device.role != null ? roleDefaultsData?.defaults[device.role] : roleDefaultsData?.defaults["temperature-sensor"],
|
|
47550
|
+
roleDefault: device.role != null ? roleDefaultsData?.defaults?.[device.role] : roleDefaultsData?.defaults?.["temperature-sensor"],
|
|
47447
47551
|
quantity: descriptor.quantity ?? "device",
|
|
47448
47552
|
deviceQuantity: deviceQuantity === "device" ? void 0 : deviceQuantity
|
|
47449
47553
|
}).format(rawValue);
|