@camstack/ui-library 1.2.376 → 1.2.378
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/CHANGELOG.md +16 -0
- package/dist/{MotionZonesSettings-Zcfa5c9l.js → MotionZonesSettings-BvpjmW6y.js} +1 -1
- package/dist/{PrivacyMaskSettings-B_X42yzN.js → PrivacyMaskSettings-D7o4tx8e.js} +1 -1
- package/dist/{SceneMonitorEditor-C8YXVzYI.js → SceneMonitorEditor-BwelmHlF.js} +1 -1
- package/dist/composites/cap-settings/PowerMeterLine.d.ts +10 -0
- package/dist/composites/cap-settings/index.d.ts +1 -0
- package/dist/composites/device-controls/appliance-control.d.ts +0 -2
- package/dist/composites/device-controls/media-player-hero-card.d.ts +15 -10
- package/dist/composites/device-controls/media-player-hero-parts.d.ts +18 -0
- package/dist/composites/device-controls/media-player-panel.d.ts +1 -1
- package/dist/composites/device-controls/media-player-view.d.ts +66 -0
- package/dist/composites/index.d.ts +1 -0
- package/dist/generated/system-hooks.d.ts +2 -0
- package/dist/{icons-DXwPEKdJ.cjs → icons-BWpVdTJu.cjs} +3 -83
- package/dist/{icons-e-xohmFb.js → icons-cIFSolkx.js} +1 -81
- package/dist/index.cjs +724 -160
- package/dist/index.js +725 -166
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3471,6 +3471,58 @@ var RefreshCw = require_createLucideIcon.createLucideIcon("refresh-cw", [
|
|
|
3471
3471
|
* This source code is licensed under the ISC license.
|
|
3472
3472
|
* See the LICENSE file in the root directory of this source tree.
|
|
3473
3473
|
*/
|
|
3474
|
+
var Repeat1 = require_createLucideIcon.createLucideIcon("repeat-1", [
|
|
3475
|
+
["path", {
|
|
3476
|
+
d: "m17 2 4 4-4 4",
|
|
3477
|
+
key: "nntrym"
|
|
3478
|
+
}],
|
|
3479
|
+
["path", {
|
|
3480
|
+
d: "M3 11v-1a4 4 0 0 1 4-4h14",
|
|
3481
|
+
key: "84bu3i"
|
|
3482
|
+
}],
|
|
3483
|
+
["path", {
|
|
3484
|
+
d: "m7 22-4-4 4-4",
|
|
3485
|
+
key: "1wqhfi"
|
|
3486
|
+
}],
|
|
3487
|
+
["path", {
|
|
3488
|
+
d: "M21 13v1a4 4 0 0 1-4 4H3",
|
|
3489
|
+
key: "1rx37r"
|
|
3490
|
+
}],
|
|
3491
|
+
["path", {
|
|
3492
|
+
d: "M11 10h1v4",
|
|
3493
|
+
key: "70cz1p"
|
|
3494
|
+
}]
|
|
3495
|
+
]);
|
|
3496
|
+
/**
|
|
3497
|
+
* @license lucide-react v0.576.0 - ISC
|
|
3498
|
+
*
|
|
3499
|
+
* This source code is licensed under the ISC license.
|
|
3500
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
3501
|
+
*/
|
|
3502
|
+
var Repeat = require_createLucideIcon.createLucideIcon("repeat", [
|
|
3503
|
+
["path", {
|
|
3504
|
+
d: "m17 2 4 4-4 4",
|
|
3505
|
+
key: "nntrym"
|
|
3506
|
+
}],
|
|
3507
|
+
["path", {
|
|
3508
|
+
d: "M3 11v-1a4 4 0 0 1 4-4h14",
|
|
3509
|
+
key: "84bu3i"
|
|
3510
|
+
}],
|
|
3511
|
+
["path", {
|
|
3512
|
+
d: "m7 22-4-4 4-4",
|
|
3513
|
+
key: "1wqhfi"
|
|
3514
|
+
}],
|
|
3515
|
+
["path", {
|
|
3516
|
+
d: "M21 13v1a4 4 0 0 1-4 4H3",
|
|
3517
|
+
key: "1rx37r"
|
|
3518
|
+
}]
|
|
3519
|
+
]);
|
|
3520
|
+
/**
|
|
3521
|
+
* @license lucide-react v0.576.0 - ISC
|
|
3522
|
+
*
|
|
3523
|
+
* This source code is licensed under the ISC license.
|
|
3524
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
3525
|
+
*/
|
|
3474
3526
|
var RotateCcw = require_createLucideIcon.createLucideIcon("rotate-ccw", [["path", {
|
|
3475
3527
|
d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",
|
|
3476
3528
|
key: "1357e3"
|
|
@@ -3846,6 +3898,34 @@ var Ship = require_createLucideIcon.createLucideIcon("ship", [
|
|
|
3846
3898
|
* This source code is licensed under the ISC license.
|
|
3847
3899
|
* See the LICENSE file in the root directory of this source tree.
|
|
3848
3900
|
*/
|
|
3901
|
+
var Shuffle = require_createLucideIcon.createLucideIcon("shuffle", [
|
|
3902
|
+
["path", {
|
|
3903
|
+
d: "m18 14 4 4-4 4",
|
|
3904
|
+
key: "10pe0f"
|
|
3905
|
+
}],
|
|
3906
|
+
["path", {
|
|
3907
|
+
d: "m18 2 4 4-4 4",
|
|
3908
|
+
key: "pucp1d"
|
|
3909
|
+
}],
|
|
3910
|
+
["path", {
|
|
3911
|
+
d: "M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22",
|
|
3912
|
+
key: "1ailkh"
|
|
3913
|
+
}],
|
|
3914
|
+
["path", {
|
|
3915
|
+
d: "M2 6h1.972a4 4 0 0 1 3.6 2.2",
|
|
3916
|
+
key: "km57vx"
|
|
3917
|
+
}],
|
|
3918
|
+
["path", {
|
|
3919
|
+
d: "M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45",
|
|
3920
|
+
key: "os18l9"
|
|
3921
|
+
}]
|
|
3922
|
+
]);
|
|
3923
|
+
/**
|
|
3924
|
+
* @license lucide-react v0.576.0 - ISC
|
|
3925
|
+
*
|
|
3926
|
+
* This source code is licensed under the ISC license.
|
|
3927
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
3928
|
+
*/
|
|
3849
3929
|
var Siren = require_createLucideIcon.createLucideIcon("siren", [
|
|
3850
3930
|
["path", {
|
|
3851
3931
|
d: "M7 18v-6a5 5 0 1 1 10 0v6",
|
|
@@ -5994,7 +6074,7 @@ function lucideExportName(key) {
|
|
|
5994
6074
|
function loadIconGlyphs() {
|
|
5995
6075
|
if (fullTable !== void 0) return Promise.resolve();
|
|
5996
6076
|
if (failure$1 !== void 0) return Promise.reject(failure$1);
|
|
5997
|
-
pending ??= Promise.resolve().then(() => require("./icons-
|
|
6077
|
+
pending ??= Promise.resolve().then(() => require("./icons-BWpVdTJu.cjs")).then((module) => {
|
|
5998
6078
|
fullTable = module;
|
|
5999
6079
|
publish();
|
|
6000
6080
|
}, (error) => {
|
|
@@ -11135,6 +11215,8 @@ var useCoreBlocksGetCustomization = /*#__PURE__*/ alias(() => trpc.coreBlocks.ge
|
|
|
11135
11215
|
var useCoreBlocksListComposableCaps = /*#__PURE__*/ alias(() => trpc.coreBlocks.listComposableCaps.useQuery);
|
|
11136
11216
|
/** Generated alias around `trpc.coreBlocks.validateComposition.useMutation`. */
|
|
11137
11217
|
var useCoreBlocksValidateComposition = /*#__PURE__*/ alias(() => trpc.coreBlocks.validateComposition.useMutation);
|
|
11218
|
+
/** Generated alias around `trpc.coreBlocks.proposeRunningThreshold.useQuery`. */
|
|
11219
|
+
var useCoreBlocksProposeRunningThreshold = /*#__PURE__*/ alias(() => trpc.coreBlocks.proposeRunningThreshold.useQuery);
|
|
11138
11220
|
/** Generated alias around `trpc.coreBlocks.reportAutomationRun.useMutation`. */
|
|
11139
11221
|
var useCoreBlocksReportAutomationRun = /*#__PURE__*/ alias(() => trpc.coreBlocks.reportAutomationRun.useMutation);
|
|
11140
11222
|
/** Generated alias around `trpc.coreBlocks.getAutomationRunState.useQuery`. */
|
|
@@ -27761,6 +27843,92 @@ function NavigationPanel({ deviceId }) {
|
|
|
27761
27843
|
});
|
|
27762
27844
|
}
|
|
27763
27845
|
//#endregion
|
|
27846
|
+
//#region src/composites/cap-settings/PowerMeterLine.tsx
|
|
27847
|
+
/**
|
|
27848
|
+
* PowerMeterLine — the ONE place a device's electrical draw is shown (D1004):
|
|
27849
|
+
* a slim line directly under the device page's hero card, for every device
|
|
27850
|
+
* bound to the `power-meter` capability, whatever its type. The cap declares
|
|
27851
|
+
* `ui: { kind:'widget', widgetId:'host/power-meter-line', placement:'hero' }`;
|
|
27852
|
+
* no device-controls hero card draws watts itself
|
|
27853
|
+
* (`scripts/check-no-hero-self-drawn-power.ts`).
|
|
27854
|
+
*
|
|
27855
|
+
* A reading the device never reported is not drawn as a number: no slice, or
|
|
27856
|
+
* a slice with neither power nor energy, renders nothing; a missing field
|
|
27857
|
+
* (power but no energy, or the reverse) is simply absent, never `0` or `NaN`.
|
|
27858
|
+
*/
|
|
27859
|
+
/** A finite number, or null: the one gate between a slice field and the screen. */
|
|
27860
|
+
function known(value) {
|
|
27861
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
27862
|
+
}
|
|
27863
|
+
/** Whole watts below a kilowatt, one decimal above. */
|
|
27864
|
+
function formatPowerWatts(watts) {
|
|
27865
|
+
return Math.abs(watts) >= 1e3 ? {
|
|
27866
|
+
value: (watts / 1e3).toFixed(1),
|
|
27867
|
+
unit: "kW"
|
|
27868
|
+
} : {
|
|
27869
|
+
value: String(Math.round(watts)),
|
|
27870
|
+
unit: "W"
|
|
27871
|
+
};
|
|
27872
|
+
}
|
|
27873
|
+
/** Two decimals under 100 kWh, whole kWh above. */
|
|
27874
|
+
function formatEnergyKwh(kwh) {
|
|
27875
|
+
return Math.abs(kwh) >= 100 ? String(Math.round(kwh)) : kwh.toFixed(2);
|
|
27876
|
+
}
|
|
27877
|
+
function PowerMeterLine({ deviceId }) {
|
|
27878
|
+
const t = useUiText();
|
|
27879
|
+
const { slice } = useDeviceCapability({
|
|
27880
|
+
trpc: useSystem().trpcClient,
|
|
27881
|
+
deviceId,
|
|
27882
|
+
cap: (d) => d.powerMeter,
|
|
27883
|
+
slice: (d) => d.state.powerMeter
|
|
27884
|
+
});
|
|
27885
|
+
const power = known(slice?.power);
|
|
27886
|
+
const energy = known(slice?.energy);
|
|
27887
|
+
if (power === null && energy === null) return null;
|
|
27888
|
+
const drawing = power !== null && power > 0;
|
|
27889
|
+
const shown = power === null ? null : formatPowerWatts(power);
|
|
27890
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
27891
|
+
"data-testid": "power-meter-line",
|
|
27892
|
+
className: "flex items-center gap-3 rounded-lg border border-border bg-surface px-3 py-2",
|
|
27893
|
+
children: [
|
|
27894
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
27895
|
+
className: "relative flex h-5 w-5 shrink-0 items-center justify-center",
|
|
27896
|
+
children: [drawing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
27897
|
+
"aria-hidden": "true",
|
|
27898
|
+
"data-testid": "power-meter-pulse",
|
|
27899
|
+
className: "absolute inset-0 rounded-full bg-primary/20 motion-safe:animate-pulse"
|
|
27900
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Zap, {
|
|
27901
|
+
className: cn("relative h-3.5 w-3.5", drawing ? "text-primary" : "text-foreground-subtle"),
|
|
27902
|
+
"aria-hidden": true
|
|
27903
|
+
})]
|
|
27904
|
+
}),
|
|
27905
|
+
shown !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
27906
|
+
"data-testid": "power-meter-power",
|
|
27907
|
+
className: "flex items-baseline gap-1 tabular-nums",
|
|
27908
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
27909
|
+
className: "text-lg font-semibold text-foreground",
|
|
27910
|
+
children: shown.value
|
|
27911
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
27912
|
+
className: "text-xs text-foreground-muted",
|
|
27913
|
+
children: shown.unit
|
|
27914
|
+
})]
|
|
27915
|
+
}),
|
|
27916
|
+
energy !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
27917
|
+
"data-testid": "power-meter-energy",
|
|
27918
|
+
className: "ml-auto flex items-baseline gap-1 tabular-nums",
|
|
27919
|
+
title: t("uiLibrary.powerMeterLine.energy", "Energy"),
|
|
27920
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
27921
|
+
className: "text-sm text-foreground-muted",
|
|
27922
|
+
children: formatEnergyKwh(energy)
|
|
27923
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
27924
|
+
className: "text-xs text-foreground-subtle",
|
|
27925
|
+
children: "kWh"
|
|
27926
|
+
})]
|
|
27927
|
+
})
|
|
27928
|
+
]
|
|
27929
|
+
});
|
|
27930
|
+
}
|
|
27931
|
+
//#endregion
|
|
27764
27932
|
//#region src/lib/cap-error.ts
|
|
27765
27933
|
/**
|
|
27766
27934
|
* Shared capability-error utilities for the admin UI.
|
|
@@ -30739,6 +30907,7 @@ var HOST_WIDGETS = {
|
|
|
30739
30907
|
"host/ptz-panel": PtzPanel,
|
|
30740
30908
|
"host/navigation-panel": NavigationPanel,
|
|
30741
30909
|
"host/consumables-panel": ConsumablesPanel,
|
|
30910
|
+
"host/power-meter-line": PowerMeterLine,
|
|
30742
30911
|
"host/ptz-autotrack": AutotrackSection,
|
|
30743
30912
|
"host/recording-panel": RecordingPanel,
|
|
30744
30913
|
"host/camera-grid-layout": CameraGridLayoutEditor,
|
|
@@ -37075,8 +37244,8 @@ function pickWifiIcon(signalPercent) {
|
|
|
37075
37244
|
}
|
|
37076
37245
|
function signalColor(signalPercent) {
|
|
37077
37246
|
if (signalPercent === null) return "text-foreground-subtle";
|
|
37078
|
-
if (signalPercent <= 20) return "text-
|
|
37079
|
-
if (signalPercent <= 50) return "text-
|
|
37247
|
+
if (signalPercent <= 20) return "text-danger";
|
|
37248
|
+
if (signalPercent <= 50) return "text-warning";
|
|
37080
37249
|
return "text-foreground-subtle";
|
|
37081
37250
|
}
|
|
37082
37251
|
function NetworkLinkBadge({ status, className }) {
|
|
@@ -37087,15 +37256,16 @@ function NetworkLinkBadge({ status, className }) {
|
|
|
37087
37256
|
title: t("uiLibrary.networkLinkBadge.ethernet", "Network: ethernet"),
|
|
37088
37257
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Cable, { className: "h-3.5 w-3.5" })
|
|
37089
37258
|
});
|
|
37090
|
-
const
|
|
37091
|
-
const
|
|
37259
|
+
const reading = typeof status.signal === "number" && Number.isFinite(status.signal) ? status.signal : null;
|
|
37260
|
+
const Icon = status.type === "cellular" ? RadioTower : pickWifiIcon(reading);
|
|
37261
|
+
const signal = reading === null ? null : Math.round(reading);
|
|
37092
37262
|
const networkText = status.ssid ? t("uiLibrary.networkLinkBadge.typeSsid", "Network: {{type}} ({{ssid}})", {
|
|
37093
37263
|
type: status.type,
|
|
37094
37264
|
ssid: status.ssid
|
|
37095
37265
|
}) : t("uiLibrary.networkLinkBadge.type", "Network: {{type}}", { type: status.type });
|
|
37096
37266
|
const title = signal === null ? networkText : `${networkText} · ${t("uiLibrary.networkLinkBadge.signal", "signal {{percent}}%", { percent: signal })}`;
|
|
37097
37267
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
37098
|
-
className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(
|
|
37268
|
+
className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(reading), className),
|
|
37099
37269
|
title,
|
|
37100
37270
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "h-3.5 w-3.5" }), signal !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [signal, "%"] })]
|
|
37101
37271
|
});
|
|
@@ -48782,29 +48952,179 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
48782
48952
|
});
|
|
48783
48953
|
}
|
|
48784
48954
|
//#endregion
|
|
48955
|
+
//#region src/composites/device-controls/media-player-view.ts
|
|
48956
|
+
/**
|
|
48957
|
+
* The state → view mapping of the media-player hero card, kept pure so it can
|
|
48958
|
+
* be specified without rendering.
|
|
48959
|
+
*
|
|
48960
|
+
* Two questions are answered separately. POWER is whether the device is on
|
|
48961
|
+
* (`off` / `standby` / on); RUNNING is what a powered device is doing
|
|
48962
|
+
* (`playing` / `paused` / `buffering` / `idle`). A status that never arrived
|
|
48963
|
+
* is `unknown` for both, and an unknown state is never drawn as off: it is
|
|
48964
|
+
* its own look. A value the driver only guesses (infrared has no feedback)
|
|
48965
|
+
* carries an `assumed` flag so the card can draw it as a guess.
|
|
48966
|
+
*
|
|
48967
|
+
* The controls are derived from what the device exposes — the status fields
|
|
48968
|
+
* it fills and the `DeviceFeature` flags it declares — never from a vendor.
|
|
48969
|
+
*/
|
|
48970
|
+
var NO_TRANSPORT = {
|
|
48971
|
+
playPause: false,
|
|
48972
|
+
previous: false,
|
|
48973
|
+
next: false,
|
|
48974
|
+
stop: false
|
|
48975
|
+
};
|
|
48976
|
+
/** Whether the status carries any activity the device actually reported. */
|
|
48977
|
+
function runningOf(state) {
|
|
48978
|
+
switch (state) {
|
|
48979
|
+
case "playing":
|
|
48980
|
+
case "paused":
|
|
48981
|
+
case "buffering":
|
|
48982
|
+
case "idle": return state;
|
|
48983
|
+
case void 0: return "unknown";
|
|
48984
|
+
default: return "none";
|
|
48985
|
+
}
|
|
48986
|
+
}
|
|
48987
|
+
function artworkOf(status) {
|
|
48988
|
+
const url = status.currentMedia?.imageUrl?.trim() ?? "";
|
|
48989
|
+
return url === "" ? null : url;
|
|
48990
|
+
}
|
|
48991
|
+
function powerOf(state) {
|
|
48992
|
+
if (state === void 0) return "unknown";
|
|
48993
|
+
if (state === "off") return "off";
|
|
48994
|
+
if (state === "standby") return "standby";
|
|
48995
|
+
return "on";
|
|
48996
|
+
}
|
|
48997
|
+
/**
|
|
48998
|
+
* A slice from before `assumedFields` existed (stateSource assumed, no list)
|
|
48999
|
+
* means the whole state is a guess; with a list, only the named fields are.
|
|
49000
|
+
*/
|
|
49001
|
+
function isAssumed(status, field) {
|
|
49002
|
+
if (status.stateSource !== "assumed") return false;
|
|
49003
|
+
const listed = status.assumedFields ?? [];
|
|
49004
|
+
return listed.length === 0 || listed.includes(field);
|
|
49005
|
+
}
|
|
49006
|
+
var UNKNOWN_VIEW = {
|
|
49007
|
+
power: "unknown",
|
|
49008
|
+
runningReading: "unknown",
|
|
49009
|
+
running: "unknown",
|
|
49010
|
+
stateAssumed: false,
|
|
49011
|
+
canSwitchPower: false,
|
|
49012
|
+
transport: NO_TRANSPORT,
|
|
49013
|
+
seek: false,
|
|
49014
|
+
volume: "none",
|
|
49015
|
+
volumeAssumed: false,
|
|
49016
|
+
volumeKnown: false,
|
|
49017
|
+
muteKnown: false,
|
|
49018
|
+
artworkUrl: null,
|
|
49019
|
+
mute: false,
|
|
49020
|
+
muteAssumed: false,
|
|
49021
|
+
source: null,
|
|
49022
|
+
sourceAssumed: false,
|
|
49023
|
+
shuffle: false,
|
|
49024
|
+
repeat: false
|
|
49025
|
+
};
|
|
49026
|
+
function readingOf(running) {
|
|
49027
|
+
if (running === true) return "running";
|
|
49028
|
+
if (running === false) return "idle";
|
|
49029
|
+
return "unknown";
|
|
49030
|
+
}
|
|
49031
|
+
function deriveMediaPlayerView(status, features, extras = {}) {
|
|
49032
|
+
const runningReading = readingOf(extras.running);
|
|
49033
|
+
const view = deriveFromStatus(status, features);
|
|
49034
|
+
const guessed = status === void 0 || status.state === void 0 || view.stateAssumed;
|
|
49035
|
+
if (runningReading === "unknown" || !guessed) return {
|
|
49036
|
+
...view,
|
|
49037
|
+
runningReading
|
|
49038
|
+
};
|
|
49039
|
+
if (runningReading === "idle") return {
|
|
49040
|
+
...UNKNOWN_VIEW,
|
|
49041
|
+
power: "off",
|
|
49042
|
+
running: "none",
|
|
49043
|
+
canSwitchPower: view.canSwitchPower,
|
|
49044
|
+
runningReading
|
|
49045
|
+
};
|
|
49046
|
+
return {
|
|
49047
|
+
...status === void 0 ? view : deriveFromStatus(status.state === void 0 || status.state === "off" || status.state === "standby" ? {
|
|
49048
|
+
...status,
|
|
49049
|
+
state: "on"
|
|
49050
|
+
} : status, features),
|
|
49051
|
+
power: "on",
|
|
49052
|
+
stateAssumed: false,
|
|
49053
|
+
runningReading
|
|
49054
|
+
};
|
|
49055
|
+
}
|
|
49056
|
+
function deriveFromStatus(status, features) {
|
|
49057
|
+
const has = (f) => features?.includes(f) ?? false;
|
|
49058
|
+
const canSwitchPower = has(_camstack_types.DeviceFeature.MediaPlayerPower);
|
|
49059
|
+
if (status === void 0 || status.state === void 0) return {
|
|
49060
|
+
...UNKNOWN_VIEW,
|
|
49061
|
+
canSwitchPower
|
|
49062
|
+
};
|
|
49063
|
+
const power = powerOf(status.state);
|
|
49064
|
+
const running = runningOf(status.state);
|
|
49065
|
+
const base = {
|
|
49066
|
+
...UNKNOWN_VIEW,
|
|
49067
|
+
power,
|
|
49068
|
+
running,
|
|
49069
|
+
stateAssumed: isAssumed(status, "state"),
|
|
49070
|
+
canSwitchPower
|
|
49071
|
+
};
|
|
49072
|
+
if (power !== "on") return base;
|
|
49073
|
+
const hasTransportFeature = has(_camstack_types.DeviceFeature.MediaPlayerNext) || has(_camstack_types.DeviceFeature.MediaPlayerPrevious) || has(_camstack_types.DeviceFeature.MediaPlayerStop) || has(_camstack_types.DeviceFeature.MediaPlayerSeek);
|
|
49074
|
+
const volume = has(_camstack_types.DeviceFeature.MediaPlayerVolume) ? "slider" : has(_camstack_types.DeviceFeature.MediaPlayerVolumeStep) ? "steps" : "none";
|
|
49075
|
+
const sourceName = typeof status.source === "string" ? status.source.trim() : "";
|
|
49076
|
+
const options = status.availableSources ?? [];
|
|
49077
|
+
const hasSource = sourceName !== "" || options.length > 0;
|
|
49078
|
+
return {
|
|
49079
|
+
...base,
|
|
49080
|
+
transport: {
|
|
49081
|
+
playPause: running !== "none" || hasTransportFeature,
|
|
49082
|
+
previous: has(_camstack_types.DeviceFeature.MediaPlayerPrevious),
|
|
49083
|
+
next: has(_camstack_types.DeviceFeature.MediaPlayerNext),
|
|
49084
|
+
stop: has(_camstack_types.DeviceFeature.MediaPlayerStop)
|
|
49085
|
+
},
|
|
49086
|
+
seek: has(_camstack_types.DeviceFeature.MediaPlayerSeek) && status.duration !== null && status.duration > 0,
|
|
49087
|
+
volume,
|
|
49088
|
+
volumeAssumed: isAssumed(status, "volumeLevel"),
|
|
49089
|
+
mute: has(_camstack_types.DeviceFeature.MediaPlayerMute) || volume === "slider" && status.isMuted !== null,
|
|
49090
|
+
volumeKnown: status.volumeLevel !== null,
|
|
49091
|
+
muteKnown: status.isMuted !== null,
|
|
49092
|
+
artworkUrl: artworkOf(status),
|
|
49093
|
+
muteAssumed: isAssumed(status, "isMuted"),
|
|
49094
|
+
source: hasSource ? {
|
|
49095
|
+
current: sourceName,
|
|
49096
|
+
options,
|
|
49097
|
+
selectable: has(_camstack_types.DeviceFeature.MediaPlayerSelectSource) && options.length > 1
|
|
49098
|
+
} : null,
|
|
49099
|
+
sourceAssumed: isAssumed(status, "source"),
|
|
49100
|
+
shuffle: has(_camstack_types.DeviceFeature.MediaPlayerShuffle) && status.shuffle !== null,
|
|
49101
|
+
repeat: has(_camstack_types.DeviceFeature.MediaPlayerRepeat) && status.repeat !== null
|
|
49102
|
+
};
|
|
49103
|
+
}
|
|
49104
|
+
//#endregion
|
|
48785
49105
|
//#region src/composites/device-controls/media-player-panel.tsx
|
|
48786
49106
|
/**
|
|
48787
49107
|
* Exhaustive colour map — one entry per MediaPlayerState.
|
|
48788
49108
|
* Build fails if a state is added and not reflected here.
|
|
48789
49109
|
*/
|
|
48790
49110
|
var STATE_COLOR = {
|
|
48791
|
-
playing: "border-
|
|
48792
|
-
paused: "border-
|
|
48793
|
-
buffering: "border-
|
|
48794
|
-
on: "border-
|
|
48795
|
-
idle: "border-
|
|
48796
|
-
off: "border-
|
|
48797
|
-
standby: "border-
|
|
49111
|
+
playing: "border-success/50 bg-success/10",
|
|
49112
|
+
paused: "border-warning/50 bg-warning/10",
|
|
49113
|
+
buffering: "border-info/50 bg-info/10",
|
|
49114
|
+
on: "border-foreground-subtle/40 bg-surface-hover",
|
|
49115
|
+
idle: "border-foreground-subtle/40 bg-surface-hover",
|
|
49116
|
+
off: "border-border bg-surface",
|
|
49117
|
+
standby: "border-border bg-surface"
|
|
48798
49118
|
};
|
|
48799
49119
|
/** Dot indicator colour (small circle) per state. */
|
|
48800
49120
|
var STATE_DOT_COLOR = {
|
|
48801
|
-
playing: "bg-
|
|
48802
|
-
paused: "bg-
|
|
48803
|
-
buffering: "bg-
|
|
48804
|
-
on: "bg-
|
|
48805
|
-
idle: "bg-
|
|
48806
|
-
off: "bg-
|
|
48807
|
-
standby: "bg-
|
|
49121
|
+
playing: "bg-success",
|
|
49122
|
+
paused: "bg-warning",
|
|
49123
|
+
buffering: "bg-info",
|
|
49124
|
+
on: "bg-foreground-subtle",
|
|
49125
|
+
idle: "bg-foreground-subtle",
|
|
49126
|
+
off: "bg-border",
|
|
49127
|
+
standby: "bg-border"
|
|
48808
49128
|
};
|
|
48809
49129
|
/** Exhaustive tuple of valid repeat values — single source of truth. */
|
|
48810
49130
|
var REPEAT_VALUES = [
|
|
@@ -48828,7 +49148,7 @@ function toggleAction$1(state) {
|
|
|
48828
49148
|
if (state === "playing" || state === "buffering") return "pause";
|
|
48829
49149
|
return "play";
|
|
48830
49150
|
}
|
|
48831
|
-
function MediaPlayerPanel({ trpc, deviceId, features
|
|
49151
|
+
function MediaPlayerPanel({ trpc, deviceId, features, layout, optimistic }) {
|
|
48832
49152
|
const t = useUiText();
|
|
48833
49153
|
const { cap, slice, available } = useDeviceCapability({
|
|
48834
49154
|
trpc,
|
|
@@ -48838,6 +49158,7 @@ function MediaPlayerPanel({ trpc, deviceId, features: _features, layout, optimis
|
|
|
48838
49158
|
});
|
|
48839
49159
|
const { display, applyOptimistic } = useOptimisticSlice(slice, { enabled: optimistic ?? true });
|
|
48840
49160
|
if (display === void 0) return null;
|
|
49161
|
+
const declared = deriveMediaPlayerView(display, features, {});
|
|
48841
49162
|
const busy = !available;
|
|
48842
49163
|
const action = toggleAction$1(display.state);
|
|
48843
49164
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -48892,7 +49213,7 @@ function MediaPlayerPanel({ trpc, deviceId, features: _features, layout, optimis
|
|
|
48892
49213
|
children: "⏭"
|
|
48893
49214
|
})]
|
|
48894
49215
|
}),
|
|
48895
|
-
display.volumeLevel !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
49216
|
+
(declared.volume === "slider" || display.volumeLevel !== null) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
48896
49217
|
className: "flex items-center gap-2 text-xs text-foreground-subtle",
|
|
48897
49218
|
children: [
|
|
48898
49219
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("uiLibrary.mediaPlayerPanel.vol", "Vol") }),
|
|
@@ -48902,7 +49223,7 @@ function MediaPlayerPanel({ trpc, deviceId, features: _features, layout, optimis
|
|
|
48902
49223
|
min: 0,
|
|
48903
49224
|
max: 100,
|
|
48904
49225
|
step: 1,
|
|
48905
|
-
value: display.volumeLevel,
|
|
49226
|
+
value: display.volumeLevel ?? 0,
|
|
48906
49227
|
disabled: busy,
|
|
48907
49228
|
onChange: (e) => {
|
|
48908
49229
|
const volumeLevel = Number(e.target.value);
|
|
@@ -48910,25 +49231,25 @@ function MediaPlayerPanel({ trpc, deviceId, features: _features, layout, optimis
|
|
|
48910
49231
|
if (!busy) cap?.setVolume({ volumeLevel });
|
|
48911
49232
|
}
|
|
48912
49233
|
}),
|
|
48913
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
49234
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48914
49235
|
className: "tabular-nums w-8 text-right",
|
|
48915
|
-
children:
|
|
49236
|
+
children: display.volumeLevel === null ? "—" : `${display.volumeLevel}%`
|
|
48916
49237
|
})
|
|
48917
49238
|
]
|
|
48918
49239
|
}),
|
|
48919
|
-
display.isMuted !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
49240
|
+
(declared.mute || display.isMuted !== null) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
48920
49241
|
type: "button",
|
|
48921
49242
|
"aria-label": display.isMuted ? t("uiLibrary.mediaPlayerPanel.unmute", "Unmute") : t("uiLibrary.mediaPlayerPanel.mute", "Mute"),
|
|
48922
49243
|
disabled: busy,
|
|
48923
49244
|
onClick: (e) => {
|
|
48924
49245
|
e.stopPropagation();
|
|
48925
|
-
if (!busy
|
|
48926
|
-
const muted = !display.isMuted;
|
|
49246
|
+
if (!busy) {
|
|
49247
|
+
const muted = display.isMuted === null ? true : !display.isMuted;
|
|
48927
49248
|
applyOptimistic({ isMuted: muted });
|
|
48928
49249
|
cap?.setMute({ muted });
|
|
48929
49250
|
}
|
|
48930
49251
|
},
|
|
48931
|
-
className: cn("inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs transition-colors", display.isMuted ? "bg-
|
|
49252
|
+
className: cn("inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs transition-colors", display.isMuted ? "bg-warning-fill text-warning-on" : "text-foreground-subtle hover:text-foreground hover:bg-surface-hover", busy && "opacity-40 cursor-not-allowed"),
|
|
48932
49253
|
children: display.isMuted ? "🔇" : "🔊"
|
|
48933
49254
|
}),
|
|
48934
49255
|
display.availableSources.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -48967,7 +49288,7 @@ function MediaPlayerPanel({ trpc, deviceId, features: _features, layout, optimis
|
|
|
48967
49288
|
cap?.setShuffle({ shuffle });
|
|
48968
49289
|
}
|
|
48969
49290
|
},
|
|
48970
|
-
className: cn("inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs transition-colors", display.shuffle ? "bg-
|
|
49291
|
+
className: cn("inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs transition-colors", display.shuffle ? "bg-success-fill text-success-on" : "text-foreground-subtle hover:text-foreground hover:bg-surface-hover", busy && "opacity-40 cursor-not-allowed"),
|
|
48971
49292
|
children: ["⇌ ", t("uiLibrary.mediaPlayerPanel.shuffle", "Shuffle")]
|
|
48972
49293
|
}),
|
|
48973
49294
|
display.repeat !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -55611,7 +55932,7 @@ function SwingToggle({ axis, on, busy, onToggle }) {
|
|
|
55611
55932
|
* "Run N min".
|
|
55612
55933
|
* - HeaterHeroCard (device-detail hero): the glowing heat backdrop (the same
|
|
55613
55934
|
* rising streaks a heating air conditioner draws) while it is on, the
|
|
55614
|
-
* on/off switch
|
|
55935
|
+
* on/off switch and the timed
|
|
55615
55936
|
* run (number, unit, Run, countdown, Cancel).
|
|
55616
55937
|
*
|
|
55617
55938
|
* Every reading may be unknown (`null`) and is drawn as a dash, never as zero.
|
|
@@ -55633,12 +55954,6 @@ function useHeater({ trpc, deviceId, optimistic }) {
|
|
|
55633
55954
|
cap: (d) => d.runningState,
|
|
55634
55955
|
slice: (d) => d.state.runningState
|
|
55635
55956
|
});
|
|
55636
|
-
const power = useDeviceCapability({
|
|
55637
|
-
trpc,
|
|
55638
|
-
deviceId,
|
|
55639
|
-
cap: (d) => d.powerMeter,
|
|
55640
|
-
slice: (d) => d.state.powerMeter
|
|
55641
|
-
});
|
|
55642
55957
|
const timer = useDeviceCapability({
|
|
55643
55958
|
trpc,
|
|
55644
55959
|
deviceId,
|
|
@@ -55652,7 +55967,6 @@ function useHeater({ trpc, deviceId, optimistic }) {
|
|
|
55652
55967
|
available,
|
|
55653
55968
|
applyOptimistic,
|
|
55654
55969
|
running: running.slice,
|
|
55655
|
-
power: power.slice,
|
|
55656
55970
|
timer
|
|
55657
55971
|
};
|
|
55658
55972
|
}
|
|
@@ -55695,11 +56009,10 @@ function HeaterInlineControl(props) {
|
|
|
55695
56009
|
}
|
|
55696
56010
|
function HeaterHeroCard(props) {
|
|
55697
56011
|
const t = useUiText();
|
|
55698
|
-
const { cap, display, available, applyOptimistic, running,
|
|
56012
|
+
const { cap, display, available, applyOptimistic, running, timer } = useHeater(props);
|
|
55699
56013
|
if (cap === void 0 || display === void 0) return null;
|
|
55700
56014
|
const on = display.on === true;
|
|
55701
56015
|
const heating = running?.running ?? on;
|
|
55702
|
-
const watts = power?.power ?? null;
|
|
55703
56016
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
55704
56017
|
className: "relative flex flex-col items-stretch gap-3 overflow-hidden rounded-lg border border-border bg-surface p-4",
|
|
55705
56018
|
children: [
|
|
@@ -55717,21 +56030,13 @@ function HeaterHeroCard(props) {
|
|
|
55717
56030
|
})
|
|
55718
56031
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
55719
56032
|
className: "relative z-10 flex flex-col items-center gap-1",
|
|
55720
|
-
children: [
|
|
55721
|
-
|
|
55722
|
-
|
|
55723
|
-
|
|
55724
|
-
|
|
55725
|
-
|
|
55726
|
-
|
|
55727
|
-
children: heating ? t("uiLibrary.heater.heating", "Heating") : t("uiLibrary.heater.off", "Off")
|
|
55728
|
-
}),
|
|
55729
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
55730
|
-
className: "text-xs tabular-nums text-foreground-muted",
|
|
55731
|
-
"data-testid": "heater-watts",
|
|
55732
|
-
children: watts === null ? "—" : `${Math.round(watts)} W`
|
|
55733
|
-
})
|
|
55734
|
-
]
|
|
56033
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Flame, {
|
|
56034
|
+
className: cn("h-8 w-8", heating ? "text-warning" : "text-foreground-subtle"),
|
|
56035
|
+
"aria-hidden": true
|
|
56036
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
56037
|
+
className: "text-sm font-medium text-foreground",
|
|
56038
|
+
children: heating ? t("uiLibrary.heater.heating", "Heating") : t("uiLibrary.heater.off", "Off")
|
|
56039
|
+
})]
|
|
55735
56040
|
})]
|
|
55736
56041
|
}),
|
|
55737
56042
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -56176,17 +56481,16 @@ function DishwasherHeroCard(props) {
|
|
|
56176
56481
|
* Appliance card for a `DeviceType.Washer` / `DeviceType.Dryer` device.
|
|
56177
56482
|
*
|
|
56178
56483
|
* These appliances have no program surface: what is worth showing is whether
|
|
56179
|
-
* the cycle is running (the `running-state` cap)
|
|
56180
|
-
*
|
|
56181
|
-
*
|
|
56484
|
+
* the cycle is running (the `running-state` cap) and for how long. The draw
|
|
56485
|
+
* is NOT drawn here: the `power-meter` cap's hero widget shows it under every
|
|
56486
|
+
* hero card (D1004).
|
|
56182
56487
|
* - ApplianceInlineControl (device-list row): the running badge round the
|
|
56183
|
-
* drum icon
|
|
56488
|
+
* drum icon.
|
|
56184
56489
|
* - ApplianceHeroCard (device-detail hero): the drum (turning only while a
|
|
56185
|
-
* cycle runs), the state, the phase
|
|
56490
|
+
* cycle runs), the state, the phase and the dwell.
|
|
56186
56491
|
*
|
|
56187
56492
|
* UNKNOWN IS NOT OFF (D393). `running: null` is drawn as "Unknown" and the
|
|
56188
|
-
* drum stays still and unbadged
|
|
56189
|
-
* "0 W". A slice that has not landed hides its row instead of faking a value.
|
|
56493
|
+
* drum stays still and unbadged.
|
|
56190
56494
|
*/
|
|
56191
56495
|
var KEYFRAMES = `
|
|
56192
56496
|
@keyframes apl-tumble { to { transform: rotate(360deg) } }
|
|
@@ -56195,10 +56499,6 @@ var KEYFRAMES = `
|
|
|
56195
56499
|
.apl-drum-run { animation: none !important; }
|
|
56196
56500
|
}
|
|
56197
56501
|
`;
|
|
56198
|
-
/** Whole watts, or a dash when the appliance has not reported a reading. */
|
|
56199
|
-
function formatWatts(power) {
|
|
56200
|
-
return power === void 0 || !Number.isFinite(power) ? "—" : `${Math.round(power)} W`;
|
|
56201
|
-
}
|
|
56202
56502
|
/** The state to a person: running / idle, or why there is none (unknown). */
|
|
56203
56503
|
function applianceStateLabel(running, t) {
|
|
56204
56504
|
if (running === true) return {
|
|
@@ -56221,10 +56521,7 @@ function dwellText(running, since, now, t) {
|
|
|
56221
56521
|
return "minutes" in duration ? t("uiLibrary.appliance.forMinutes", "for {{count}} min", { count: duration.minutes }) : t("uiLibrary.appliance.forHours", "for {{count}} h", { count: duration.hours });
|
|
56222
56522
|
}
|
|
56223
56523
|
function useApplianceState(props) {
|
|
56224
|
-
return {
|
|
56225
|
-
running: useDeviceState(props.trpc, props.deviceId, (s) => s.runningState),
|
|
56226
|
-
power: useDeviceState(props.trpc, props.deviceId, (s) => s.powerMeter)
|
|
56227
|
-
};
|
|
56524
|
+
return { running: useDeviceState(props.trpc, props.deviceId, (s) => s.runningState) };
|
|
56228
56525
|
}
|
|
56229
56526
|
/** The drum: a ring with a few items in it, turning only while a cycle runs. */
|
|
56230
56527
|
function Drum({ running }) {
|
|
@@ -56246,7 +56543,7 @@ function Drum({ running }) {
|
|
|
56246
56543
|
}
|
|
56247
56544
|
function ApplianceHeroCard(props) {
|
|
56248
56545
|
const t = useUiText();
|
|
56249
|
-
const { running
|
|
56546
|
+
const { running } = useApplianceState(props);
|
|
56250
56547
|
const state = running?.running ?? null;
|
|
56251
56548
|
const meta = applianceStateLabel(state, t);
|
|
56252
56549
|
const phase = running?.phase?.trim() ?? "";
|
|
@@ -56258,50 +56555,35 @@ function ApplianceHeroCard(props) {
|
|
|
56258
56555
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Drum, { running: state }),
|
|
56259
56556
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
56260
56557
|
className: "flex min-w-0 flex-1 flex-col gap-2",
|
|
56261
|
-
children: [
|
|
56262
|
-
|
|
56263
|
-
|
|
56264
|
-
|
|
56265
|
-
|
|
56266
|
-
|
|
56267
|
-
|
|
56268
|
-
|
|
56269
|
-
|
|
56270
|
-
|
|
56271
|
-
|
|
56272
|
-
|
|
56273
|
-
phase
|
|
56274
|
-
|
|
56275
|
-
children: phase
|
|
56276
|
-
}),
|
|
56277
|
-
power !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
56278
|
-
"data-testid": "appliance-power",
|
|
56279
|
-
className: "inline-flex items-center gap-1 text-sm tabular-nums text-foreground-muted",
|
|
56280
|
-
title: power.power === void 0 ? t("uiLibrary.appliance.powerUnknown", "Power unknown") : void 0,
|
|
56281
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Zap, {
|
|
56282
|
-
className: "h-3.5 w-3.5",
|
|
56283
|
-
"aria-hidden": true
|
|
56284
|
-
}), formatWatts(power.power)]
|
|
56285
|
-
})
|
|
56286
|
-
]
|
|
56558
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
56559
|
+
className: "flex flex-wrap items-baseline gap-x-2",
|
|
56560
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
56561
|
+
"data-testid": "appliance-state",
|
|
56562
|
+
className: cn("text-3xl font-semibold", meta.tone),
|
|
56563
|
+
children: meta.label
|
|
56564
|
+
}), dwell !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
56565
|
+
className: "text-sm tabular-nums text-foreground-subtle",
|
|
56566
|
+
children: dwell
|
|
56567
|
+
})]
|
|
56568
|
+
}), phase !== "" && state !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
56569
|
+
className: "text-sm text-foreground-subtle",
|
|
56570
|
+
children: phase
|
|
56571
|
+
})]
|
|
56287
56572
|
})
|
|
56288
56573
|
]
|
|
56289
56574
|
});
|
|
56290
56575
|
}
|
|
56291
56576
|
function ApplianceInlineControl(props) {
|
|
56292
|
-
const { running
|
|
56293
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
56577
|
+
const { running } = useApplianceState(props);
|
|
56578
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
56294
56579
|
className: "inline-flex items-center gap-1.5 max-w-full",
|
|
56295
|
-
children:
|
|
56580
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RunningBadge, {
|
|
56296
56581
|
running: running?.running ?? null,
|
|
56297
56582
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WashingMachine, {
|
|
56298
56583
|
className: "h-4 w-4 text-foreground-muted",
|
|
56299
56584
|
"aria-hidden": true
|
|
56300
56585
|
})
|
|
56301
|
-
})
|
|
56302
|
-
className: "text-xs tabular-nums text-foreground-subtle",
|
|
56303
|
-
children: formatWatts(power.power)
|
|
56304
|
-
})]
|
|
56586
|
+
})
|
|
56305
56587
|
});
|
|
56306
56588
|
}
|
|
56307
56589
|
//#endregion
|
|
@@ -57266,6 +57548,106 @@ function SwitchHeroCard({ trpc, deviceId, optimistic }) {
|
|
|
57266
57548
|
});
|
|
57267
57549
|
}
|
|
57268
57550
|
//#endregion
|
|
57551
|
+
//#region src/composites/device-controls/media-player-hero-parts.tsx
|
|
57552
|
+
/**
|
|
57553
|
+
* The animated parts of the media-player hero: the power orb and the running
|
|
57554
|
+
* indicator. Motion is transform / opacity only, plays only while the state
|
|
57555
|
+
* says so, and stops under `prefers-reduced-motion` (the state is still told
|
|
57556
|
+
* by colour, icon and text, never by motion alone).
|
|
57557
|
+
*/
|
|
57558
|
+
var MEDIA_HERO_KEYFRAMES = `
|
|
57559
|
+
@keyframes mph-eq { 0%, 100% { transform: scaleY(.3) } 50% { transform: scaleY(1) } }
|
|
57560
|
+
@keyframes mph-halo { 0% { transform: scale(.92); opacity: .55 } 100% { transform: scale(1.4); opacity: 0 } }
|
|
57561
|
+
.mph-eq { transform-origin: bottom; animation: mph-eq 1s ease-in-out infinite; }
|
|
57562
|
+
.mph-eq:nth-child(2) { animation-duration: .8s; animation-delay: -.3s; }
|
|
57563
|
+
.mph-eq:nth-child(3) { animation-duration: 1.2s; animation-delay: -.6s; }
|
|
57564
|
+
.mph-eq:nth-child(4) { animation-duration: .9s; animation-delay: -.1s; }
|
|
57565
|
+
.mph-halo { animation: mph-halo 2.8s ease-out infinite; }
|
|
57566
|
+
@keyframes mph-in { from { opacity: 0 } to { opacity: 1 } }
|
|
57567
|
+
@keyframes mph-out { from { opacity: 1 } to { opacity: 0 } }
|
|
57568
|
+
.mph-fade-in { animation: mph-in .6s ease-out both; }
|
|
57569
|
+
.mph-fade-out { animation: mph-out .6s ease-out both; }
|
|
57570
|
+
@media (prefers-reduced-motion: reduce) {
|
|
57571
|
+
.mph-eq, .mph-halo, .mph-fade-in, .mph-fade-out { animation: none !important; }
|
|
57572
|
+
.mph-fade-out { opacity: 0; }
|
|
57573
|
+
.mph-eq:nth-child(odd) { transform: scaleY(.6); }
|
|
57574
|
+
.mph-halo { opacity: 0; }
|
|
57575
|
+
}
|
|
57576
|
+
`;
|
|
57577
|
+
var ORB_TONE = {
|
|
57578
|
+
on: "border-success bg-success/15 text-success shadow-lg shadow-success/30",
|
|
57579
|
+
standby: "border-warning bg-warning/10 text-warning",
|
|
57580
|
+
off: "border-border bg-surface-hover text-foreground-subtle",
|
|
57581
|
+
unknown: "border-dashed border-border bg-transparent text-foreground-subtle"
|
|
57582
|
+
};
|
|
57583
|
+
/** The device's power: a glowing ring when on, amber in standby, flat when off, dashed when unknown. */
|
|
57584
|
+
function PowerOrb({ power, onPress, label, busy }) {
|
|
57585
|
+
const body = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: power === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CircleQuestionMark, {
|
|
57586
|
+
className: "h-7 w-7",
|
|
57587
|
+
"aria-hidden": "true"
|
|
57588
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Power, {
|
|
57589
|
+
className: "h-7 w-7",
|
|
57590
|
+
"aria-hidden": "true"
|
|
57591
|
+
}) });
|
|
57592
|
+
const classes = cn("relative z-10 inline-flex h-16 w-16 items-center justify-center rounded-full border-2", "transition-all duration-500 motion-reduce:transition-none", ORB_TONE[power]);
|
|
57593
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57594
|
+
className: "relative flex h-20 w-20 items-center justify-center",
|
|
57595
|
+
"data-power": power,
|
|
57596
|
+
children: [power === "on" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
57597
|
+
"aria-hidden": "true",
|
|
57598
|
+
"data-testid": "media-power-halo",
|
|
57599
|
+
className: "mph-halo absolute h-16 w-16 rounded-full bg-success/25"
|
|
57600
|
+
}), onPress === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
57601
|
+
className: classes,
|
|
57602
|
+
"data-testid": "media-power",
|
|
57603
|
+
role: "img",
|
|
57604
|
+
"aria-label": label,
|
|
57605
|
+
children: body
|
|
57606
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
57607
|
+
type: "button",
|
|
57608
|
+
"aria-label": label,
|
|
57609
|
+
title: label,
|
|
57610
|
+
disabled: busy,
|
|
57611
|
+
"data-testid": "media-power",
|
|
57612
|
+
onClick: (e) => {
|
|
57613
|
+
e.stopPropagation();
|
|
57614
|
+
if (!busy) onPress();
|
|
57615
|
+
},
|
|
57616
|
+
className: cn(classes, "hover:brightness-110 active:scale-95", busy && "cursor-not-allowed opacity-40"),
|
|
57617
|
+
children: body
|
|
57618
|
+
})]
|
|
57619
|
+
});
|
|
57620
|
+
}
|
|
57621
|
+
var EQ_BARS = [
|
|
57622
|
+
0,
|
|
57623
|
+
1,
|
|
57624
|
+
2,
|
|
57625
|
+
3
|
|
57626
|
+
];
|
|
57627
|
+
/** Equalizer while playing, a spinner while buffering, a pause glyph when paused; nothing otherwise. */
|
|
57628
|
+
function RunningIndicator({ running }) {
|
|
57629
|
+
if (running === "playing") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
57630
|
+
className: "inline-flex h-4 items-end gap-0.5 text-primary",
|
|
57631
|
+
"data-testid": "media-running",
|
|
57632
|
+
"data-running": "playing",
|
|
57633
|
+
"aria-hidden": "true",
|
|
57634
|
+
children: EQ_BARS.map((bar) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "mph-eq h-4 w-1 rounded-full bg-primary" }, bar))
|
|
57635
|
+
});
|
|
57636
|
+
if (running === "buffering") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, {
|
|
57637
|
+
className: "h-4 w-4 animate-spin text-foreground-muted motion-reduce:animate-none",
|
|
57638
|
+
"data-testid": "media-running",
|
|
57639
|
+
"data-running": "buffering",
|
|
57640
|
+
"aria-hidden": "true"
|
|
57641
|
+
});
|
|
57642
|
+
if (running === "paused") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, {
|
|
57643
|
+
className: "h-4 w-4 text-foreground-muted",
|
|
57644
|
+
"data-testid": "media-running",
|
|
57645
|
+
"data-running": "paused",
|
|
57646
|
+
"aria-hidden": "true"
|
|
57647
|
+
});
|
|
57648
|
+
return null;
|
|
57649
|
+
}
|
|
57650
|
+
//#endregion
|
|
57269
57651
|
//#region src/composites/device-controls/media-player-hero-card.tsx
|
|
57270
57652
|
/** Which states map to play() vs pause() on the central transport button. */
|
|
57271
57653
|
function toggleAction(state) {
|
|
@@ -57282,12 +57664,50 @@ function formatClock(ms) {
|
|
|
57282
57664
|
const total = Math.floor(ms / 1e3);
|
|
57283
57665
|
return `${Math.floor(total / 60)}:${(total % 60).toString().padStart(2, "0")}`;
|
|
57284
57666
|
}
|
|
57285
|
-
|
|
57667
|
+
var NEXT_REPEAT = {
|
|
57668
|
+
off: "all",
|
|
57669
|
+
all: "one",
|
|
57670
|
+
one: "off"
|
|
57671
|
+
};
|
|
57672
|
+
/** The artwork of what is playing; a change cross-fades from the previous cover. */
|
|
57673
|
+
function Artwork({ url }) {
|
|
57674
|
+
const [shown, setShown] = (0, react$1.useState)(url);
|
|
57675
|
+
const [previous, setPrevious] = (0, react$1.useState)(null);
|
|
57676
|
+
const [failed, setFailed] = (0, react$1.useState)(null);
|
|
57677
|
+
const last = (0, react$1.useRef)(url);
|
|
57678
|
+
(0, react$1.useEffect)(() => {
|
|
57679
|
+
if (last.current === url) return;
|
|
57680
|
+
setPrevious(last.current);
|
|
57681
|
+
setShown(url);
|
|
57682
|
+
last.current = url;
|
|
57683
|
+
const timer = setTimeout(() => setPrevious(null), 700);
|
|
57684
|
+
return () => clearTimeout(timer);
|
|
57685
|
+
}, [url]);
|
|
57686
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57687
|
+
"data-testid": "media-artwork",
|
|
57688
|
+
className: "relative h-40 w-40 overflow-hidden rounded-lg border border-border bg-surface-hover",
|
|
57689
|
+
children: [previous !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
57690
|
+
src: previous,
|
|
57691
|
+
alt: "",
|
|
57692
|
+
className: "mph-fade-out absolute inset-0 h-full w-full object-cover"
|
|
57693
|
+
}), shown !== null && failed !== shown ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
57694
|
+
src: shown,
|
|
57695
|
+
alt: "",
|
|
57696
|
+
"data-testid": "media-artwork-img",
|
|
57697
|
+
className: "mph-fade-in absolute inset-0 h-full w-full object-cover",
|
|
57698
|
+
onError: () => setFailed(shown)
|
|
57699
|
+
}, shown) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Music, {
|
|
57700
|
+
className: "absolute inset-0 m-auto h-8 w-8 text-foreground-subtle",
|
|
57701
|
+
"aria-hidden": "true"
|
|
57702
|
+
})]
|
|
57703
|
+
});
|
|
57704
|
+
}
|
|
57705
|
+
function NowPlaying({ title, subtitle, artworkUrl }) {
|
|
57286
57706
|
const t = useUiText();
|
|
57287
57707
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57288
|
-
className: "flex flex-col items-center gap-
|
|
57708
|
+
className: "flex flex-col items-center gap-2 text-center",
|
|
57289
57709
|
children: [
|
|
57290
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Music, {
|
|
57710
|
+
artworkUrl !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Artwork, { url: artworkUrl }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Music, {
|
|
57291
57711
|
className: "h-6 w-6 text-foreground-subtle",
|
|
57292
57712
|
"aria-hidden": "true"
|
|
57293
57713
|
}),
|
|
@@ -57311,18 +57731,23 @@ var STATE_LABEL$1 = {
|
|
|
57311
57731
|
buffering: "Buffering",
|
|
57312
57732
|
standby: "Standby"
|
|
57313
57733
|
};
|
|
57314
|
-
function StateLine({ state, assumed, source }) {
|
|
57734
|
+
function StateLine({ state, assumed, source, running }) {
|
|
57315
57735
|
const t = useUiText();
|
|
57316
57736
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57317
57737
|
className: "flex flex-wrap items-center justify-center gap-2 text-xs text-foreground-muted",
|
|
57318
57738
|
"data-testid": "media-state",
|
|
57319
57739
|
children: [
|
|
57320
|
-
|
|
57740
|
+
running,
|
|
57741
|
+
state === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
57742
|
+
className: "font-medium text-foreground-subtle",
|
|
57743
|
+
"data-testid": "media-state-unknown",
|
|
57744
|
+
children: t("uiLibrary.mediaPlayerHero.state.unknown", "State unknown")
|
|
57745
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
57321
57746
|
className: "font-medium text-foreground",
|
|
57322
57747
|
children: t(`uiLibrary.mediaPlayerHero.state.${state}`, STATE_LABEL$1[state])
|
|
57323
57748
|
}),
|
|
57324
57749
|
assumed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
57325
|
-
className: "rounded border border-border px-1.5 py-0.5 text-[11px] text-foreground-subtle",
|
|
57750
|
+
className: "rounded border border-dashed border-border px-1.5 py-0.5 text-[11px] text-foreground-subtle",
|
|
57326
57751
|
title: t("uiLibrary.mediaPlayerHero.assumedHint", "No feedback from the device: this is what was last sent."),
|
|
57327
57752
|
"data-testid": "media-assumed",
|
|
57328
57753
|
children: t("uiLibrary.mediaPlayerHero.assumed", "assumed")
|
|
@@ -57334,33 +57759,40 @@ function StateLine({ state, assumed, source }) {
|
|
|
57334
57759
|
]
|
|
57335
57760
|
});
|
|
57336
57761
|
}
|
|
57337
|
-
function TransportButton({ label, busy, onPress, children, primary }) {
|
|
57762
|
+
function TransportButton({ label, busy, onPress, children, primary, active, testId }) {
|
|
57338
57763
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
57339
57764
|
type: "button",
|
|
57340
57765
|
"aria-label": label,
|
|
57766
|
+
"aria-pressed": active,
|
|
57341
57767
|
title: label,
|
|
57342
57768
|
disabled: busy,
|
|
57769
|
+
"data-testid": testId,
|
|
57343
57770
|
onClick: (e) => {
|
|
57344
57771
|
e.stopPropagation();
|
|
57345
57772
|
if (!busy) onPress();
|
|
57346
57773
|
},
|
|
57347
|
-
className: cn("inline-flex items-center justify-center rounded-full transition-colors", primary ? "h-12 w-12 bg-primary text-primary-foreground hover:bg-primary/90 shadow-md" : "h-10 w-10 text-foreground-muted hover:bg-surface-hover", busy && "opacity-40 cursor-not-allowed"),
|
|
57774
|
+
className: cn("inline-flex items-center justify-center rounded-full transition-colors", primary ? "h-12 w-12 bg-primary text-primary-foreground hover:bg-primary/90 shadow-md" : "h-10 w-10 text-foreground-muted hover:bg-surface-hover", active === true && "bg-primary/15 text-primary", busy && "opacity-40 cursor-not-allowed"),
|
|
57348
57775
|
children
|
|
57349
57776
|
});
|
|
57350
57777
|
}
|
|
57351
57778
|
/**
|
|
57352
|
-
* MediaPlayerHeroCard —
|
|
57779
|
+
* MediaPlayerHeroCard — the state of a MediaPlayer device and every control it
|
|
57780
|
+
* exposes, drawn from `deriveMediaPlayerView` (the state → view mapping and
|
|
57781
|
+
* its spec live in `media-player-view.ts`).
|
|
57353
57782
|
*
|
|
57354
|
-
*
|
|
57355
|
-
*
|
|
57356
|
-
*
|
|
57357
|
-
*
|
|
57358
|
-
*
|
|
57359
|
-
*
|
|
57360
|
-
*
|
|
57361
|
-
*
|
|
57783
|
+
* - Power orb: glowing when on, amber in standby, flat when off, dashed with a
|
|
57784
|
+
* question mark while the state is unknown (never drawn as off). It is the
|
|
57785
|
+
* power switch when the device declares `MediaPlayerPower`.
|
|
57786
|
+
* - Running line: equalizer while playing, spinner while buffering, pause
|
|
57787
|
+
* glyph when paused; a value the driver only guesses carries an "assumed" mark.
|
|
57788
|
+
* - Only while the device is on: now-playing, seek, transport (previous /
|
|
57789
|
+
* play-pause / stop / next), volume (slider with mute, or steps when the
|
|
57790
|
+
* device has no level reading), the source (a picker when it can switch),
|
|
57791
|
+
* shuffle and repeat. Each appears only when the status fills it or a
|
|
57792
|
+
* `DeviceFeature` declares it — no per-vendor list.
|
|
57362
57793
|
*
|
|
57363
57794
|
* Cap-gated on `media-player` — returns null when the device lacks the cap.
|
|
57795
|
+
* Motion respects `prefers-reduced-motion`.
|
|
57364
57796
|
*/
|
|
57365
57797
|
function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
57366
57798
|
const t = useUiText();
|
|
@@ -57371,15 +57803,24 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
57371
57803
|
slice: (d) => d.state.mediaPlayer
|
|
57372
57804
|
});
|
|
57373
57805
|
const { display, applyOptimistic } = useOptimisticSlice(slice, { enabled: optimistic ?? true });
|
|
57806
|
+
const runningSlice = useDeviceState(trpc, deviceId, (st) => st.runningState);
|
|
57374
57807
|
if (cap === void 0) return null;
|
|
57375
|
-
|
|
57808
|
+
const view = deriveMediaPlayerView(display, features, { running: runningSlice?.running });
|
|
57376
57809
|
const busy = !available;
|
|
57377
|
-
const
|
|
57378
|
-
const
|
|
57379
|
-
const
|
|
57380
|
-
const
|
|
57381
|
-
|
|
57382
|
-
|
|
57810
|
+
const status = display;
|
|
57811
|
+
const powered = status !== void 0 && view.power === "on";
|
|
57812
|
+
const action = toggleAction(status?.state ?? "off");
|
|
57813
|
+
const switchPower = () => {
|
|
57814
|
+
if (busy) return;
|
|
57815
|
+
if (view.power === "on") {
|
|
57816
|
+
applyOptimistic({ state: "off" });
|
|
57817
|
+
cap.turnOff({});
|
|
57818
|
+
} else {
|
|
57819
|
+
applyOptimistic({ state: "on" });
|
|
57820
|
+
cap.turnOn({});
|
|
57821
|
+
}
|
|
57822
|
+
};
|
|
57823
|
+
const powerLabel = powerLabelOf(view.power, t);
|
|
57383
57824
|
const handleToggle = () => {
|
|
57384
57825
|
if (busy) return;
|
|
57385
57826
|
if (action === "play") {
|
|
@@ -57390,25 +57831,60 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
57390
57831
|
cap.pause({});
|
|
57391
57832
|
}
|
|
57392
57833
|
};
|
|
57393
|
-
const title =
|
|
57394
|
-
const subtitle =
|
|
57395
|
-
const hasNowPlaying = title !== null || subtitle !== null;
|
|
57396
|
-
const
|
|
57397
|
-
const
|
|
57398
|
-
const
|
|
57834
|
+
const title = status?.currentMedia?.title ?? null;
|
|
57835
|
+
const subtitle = status?.currentMedia?.artist ?? status?.currentMedia?.album ?? null;
|
|
57836
|
+
const hasNowPlaying = powered && (title !== null || subtitle !== null || view.artworkUrl !== null);
|
|
57837
|
+
const assumedAny = view.stateAssumed || view.volumeAssumed || view.muteAssumed || view.sourceAssumed;
|
|
57838
|
+
const sourceText = powered && view.source !== null && !view.source.selectable ? view.source.current : "";
|
|
57839
|
+
const volumeLevel = status?.volumeLevel ?? null;
|
|
57399
57840
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57400
|
-
className: "flex flex-col items-stretch gap-4 rounded-lg border
|
|
57841
|
+
className: cn("flex flex-col items-stretch gap-4 rounded-lg border bg-surface p-6", "transition-colors duration-500 motion-reduce:transition-none", view.power === "on" ? "border-success/40" : "border-border"),
|
|
57842
|
+
"data-testid": "media-hero",
|
|
57843
|
+
"data-power": view.power,
|
|
57401
57844
|
children: [
|
|
57402
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
57403
|
-
|
|
57404
|
-
|
|
57405
|
-
|
|
57845
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: MEDIA_HERO_KEYFRAMES }),
|
|
57846
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57847
|
+
className: "flex flex-col items-center gap-2",
|
|
57848
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PowerOrb, {
|
|
57849
|
+
power: view.power,
|
|
57850
|
+
label: powerLabel,
|
|
57851
|
+
busy,
|
|
57852
|
+
onPress: view.canSwitchPower ? switchPower : void 0
|
|
57853
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StateLine, {
|
|
57854
|
+
state: status?.state,
|
|
57855
|
+
assumed: assumedAny,
|
|
57856
|
+
source: sourceText,
|
|
57857
|
+
running: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RunningIndicator, { running: view.running })
|
|
57858
|
+
})]
|
|
57406
57859
|
}),
|
|
57407
57860
|
hasNowPlaying && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NowPlaying, {
|
|
57408
57861
|
title,
|
|
57409
|
-
subtitle
|
|
57862
|
+
subtitle,
|
|
57863
|
+
artworkUrl: view.artworkUrl
|
|
57864
|
+
}),
|
|
57865
|
+
powered && view.source !== null && view.source.selectable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57866
|
+
"data-testid": "media-source-select",
|
|
57867
|
+
className: "flex items-center gap-2",
|
|
57868
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
57869
|
+
className: "text-xs text-foreground-subtle",
|
|
57870
|
+
children: t("uiLibrary.mediaPlayerHero.source", "Source")
|
|
57871
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, {
|
|
57872
|
+
"aria-label": t("uiLibrary.mediaPlayerHero.source", "Source"),
|
|
57873
|
+
value: view.source.current,
|
|
57874
|
+
disabled: busy,
|
|
57875
|
+
wrapperClassName: "relative min-w-0 flex-1",
|
|
57876
|
+
options: view.source.options.map((o) => ({
|
|
57877
|
+
value: o,
|
|
57878
|
+
label: o
|
|
57879
|
+
})),
|
|
57880
|
+
onChange: (e) => {
|
|
57881
|
+
if (busy) return;
|
|
57882
|
+
applyOptimistic({ source: e.target.value });
|
|
57883
|
+
cap.selectSource({ source: e.target.value });
|
|
57884
|
+
}
|
|
57885
|
+
})]
|
|
57410
57886
|
}),
|
|
57411
|
-
|
|
57887
|
+
powered && view.seek && status !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57412
57888
|
"data-testid": "media-seek",
|
|
57413
57889
|
className: "flex flex-col gap-1",
|
|
57414
57890
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -57422,10 +57898,10 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
57422
57898
|
"aria-label": t("uiLibrary.mediaPlayerHero.seek", "Seek"),
|
|
57423
57899
|
type: "range",
|
|
57424
57900
|
min: 0,
|
|
57425
|
-
max:
|
|
57901
|
+
max: status.duration ?? 0,
|
|
57426
57902
|
step: 1e3,
|
|
57427
57903
|
className: "absolute inset-0 h-4 w-full cursor-pointer opacity-0",
|
|
57428
|
-
value:
|
|
57904
|
+
value: status.position ?? 0,
|
|
57429
57905
|
disabled: busy,
|
|
57430
57906
|
onChange: (e) => {
|
|
57431
57907
|
if (busy) return;
|
|
@@ -57436,18 +57912,19 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
57436
57912
|
}),
|
|
57437
57913
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
57438
57914
|
className: "pointer-events-none",
|
|
57439
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GripTrack, { fraction:
|
|
57915
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GripTrack, { fraction: progressFraction(status.position, status.duration) })
|
|
57440
57916
|
})
|
|
57441
57917
|
]
|
|
57442
57918
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57443
57919
|
className: "flex justify-between text-[11px] tabular-nums text-foreground-subtle",
|
|
57444
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatClock(
|
|
57920
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatClock(status.position) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatClock(status.duration) })]
|
|
57445
57921
|
})]
|
|
57446
57922
|
}),
|
|
57447
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57923
|
+
powered && view.transport.playPause && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57448
57924
|
className: "flex items-center justify-center gap-4",
|
|
57925
|
+
"data-testid": "media-transport",
|
|
57449
57926
|
children: [
|
|
57450
|
-
|
|
57927
|
+
view.transport.previous && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransportButton, {
|
|
57451
57928
|
label: t("uiLibrary.mediaPlayerHero.previous", "Previous"),
|
|
57452
57929
|
busy,
|
|
57453
57930
|
onPress: () => void cap.previous({}),
|
|
@@ -57469,7 +57946,16 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
57469
57946
|
"aria-hidden": "true"
|
|
57470
57947
|
})
|
|
57471
57948
|
}),
|
|
57472
|
-
|
|
57949
|
+
view.transport.stop && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransportButton, {
|
|
57950
|
+
label: t("uiLibrary.mediaPlayerHero.stop", "Stop"),
|
|
57951
|
+
busy,
|
|
57952
|
+
onPress: () => void cap.stop({}),
|
|
57953
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Square, {
|
|
57954
|
+
className: "h-4 w-4",
|
|
57955
|
+
"aria-hidden": "true"
|
|
57956
|
+
})
|
|
57957
|
+
}),
|
|
57958
|
+
view.transport.next && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransportButton, {
|
|
57473
57959
|
label: t("uiLibrary.mediaPlayerHero.next", "Next"),
|
|
57474
57960
|
busy,
|
|
57475
57961
|
onPress: () => void cap.next({}),
|
|
@@ -57480,24 +57966,24 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
57480
57966
|
})
|
|
57481
57967
|
]
|
|
57482
57968
|
}),
|
|
57483
|
-
|
|
57969
|
+
powered && view.volume === "slider" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57484
57970
|
"data-testid": "media-volume",
|
|
57485
57971
|
className: "flex items-center gap-2",
|
|
57486
57972
|
children: [
|
|
57487
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
57973
|
+
view.mute && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
57488
57974
|
type: "button",
|
|
57489
|
-
"aria-label":
|
|
57490
|
-
title:
|
|
57975
|
+
"aria-label": status?.isMuted === true ? t("uiLibrary.mediaPlayerHero.unmute", "Unmute") : t("uiLibrary.mediaPlayerHero.mute", "Mute"),
|
|
57976
|
+
title: status?.isMuted === true ? t("uiLibrary.mediaPlayerHero.unmute", "Unmute") : t("uiLibrary.mediaPlayerHero.mute", "Mute"),
|
|
57491
57977
|
disabled: busy,
|
|
57492
57978
|
onClick: (e) => {
|
|
57493
57979
|
e.stopPropagation();
|
|
57494
|
-
if (busy
|
|
57495
|
-
const muted = !
|
|
57980
|
+
if (busy) return;
|
|
57981
|
+
const muted = status?.isMuted === null || status === void 0 ? true : !status.isMuted;
|
|
57496
57982
|
applyOptimistic({ isMuted: muted });
|
|
57497
57983
|
cap.setMute({ muted });
|
|
57498
57984
|
},
|
|
57499
57985
|
className: cn("inline-flex h-7 w-7 items-center justify-center rounded text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors", busy && "opacity-40 cursor-not-allowed"),
|
|
57500
|
-
children:
|
|
57986
|
+
children: status?.isMuted === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VolumeX, {
|
|
57501
57987
|
className: "h-4 w-4",
|
|
57502
57988
|
"aria-hidden": "true"
|
|
57503
57989
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Volume2, {
|
|
@@ -57519,7 +58005,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
57519
58005
|
max: 100,
|
|
57520
58006
|
step: 1,
|
|
57521
58007
|
className: "absolute inset-0 h-4 w-full cursor-pointer opacity-0",
|
|
57522
|
-
value: volumeLevel,
|
|
58008
|
+
value: volumeLevel ?? 0,
|
|
57523
58009
|
disabled: busy,
|
|
57524
58010
|
onChange: (e) => {
|
|
57525
58011
|
if (busy) return;
|
|
@@ -57530,19 +58016,92 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
57530
58016
|
}),
|
|
57531
58017
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
57532
58018
|
className: "pointer-events-none",
|
|
57533
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
58019
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
58020
|
+
className: cn(volumeLevel === null && "opacity-40"),
|
|
58021
|
+
"data-testid": "media-volume-track",
|
|
58022
|
+
"data-known": volumeLevel !== null,
|
|
58023
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GripTrack, { fraction: status?.isMuted === true || volumeLevel === null ? 0 : volumeLevel / 100 })
|
|
58024
|
+
})
|
|
57534
58025
|
})
|
|
57535
58026
|
]
|
|
57536
58027
|
}),
|
|
57537
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
58028
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
57538
58029
|
className: "w-9 text-right text-[11px] tabular-nums text-foreground-subtle",
|
|
57539
|
-
children:
|
|
58030
|
+
children: volumeLevel === null ? "—" : `${volumeLevel}%`
|
|
57540
58031
|
})
|
|
57541
58032
|
]
|
|
58033
|
+
}),
|
|
58034
|
+
powered && view.volume === "steps" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58035
|
+
"data-testid": "media-volume-steps",
|
|
58036
|
+
className: "flex items-center justify-center gap-3 text-xs text-foreground-subtle",
|
|
58037
|
+
children: [
|
|
58038
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransportButton, {
|
|
58039
|
+
label: t("uiLibrary.mediaPlayerHero.volumeDown", "Volume down"),
|
|
58040
|
+
busy,
|
|
58041
|
+
onPress: () => void cap.volumeDown({ steps: 1 }),
|
|
58042
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Minus, {
|
|
58043
|
+
className: "h-4 w-4",
|
|
58044
|
+
"aria-hidden": "true"
|
|
58045
|
+
})
|
|
58046
|
+
}),
|
|
58047
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Volume2, {
|
|
58048
|
+
className: "h-4 w-4",
|
|
58049
|
+
"aria-hidden": "true"
|
|
58050
|
+
}),
|
|
58051
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransportButton, {
|
|
58052
|
+
label: t("uiLibrary.mediaPlayerHero.volumeUp", "Volume up"),
|
|
58053
|
+
busy,
|
|
58054
|
+
onPress: () => void cap.volumeUp({ steps: 1 }),
|
|
58055
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, {
|
|
58056
|
+
className: "h-4 w-4",
|
|
58057
|
+
"aria-hidden": "true"
|
|
58058
|
+
})
|
|
58059
|
+
})
|
|
58060
|
+
]
|
|
58061
|
+
}),
|
|
58062
|
+
powered && status !== void 0 && (view.shuffle || view.repeat) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58063
|
+
className: "flex items-center justify-center gap-2",
|
|
58064
|
+
"data-testid": "media-modes",
|
|
58065
|
+
children: [view.shuffle && status.shuffle !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransportButton, {
|
|
58066
|
+
label: t("uiLibrary.mediaPlayerHero.shuffle", "Shuffle"),
|
|
58067
|
+
busy,
|
|
58068
|
+
active: status.shuffle,
|
|
58069
|
+
onPress: () => {
|
|
58070
|
+
const shuffle = !status.shuffle;
|
|
58071
|
+
applyOptimistic({ shuffle });
|
|
58072
|
+
cap.setShuffle({ shuffle });
|
|
58073
|
+
},
|
|
58074
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Shuffle, {
|
|
58075
|
+
className: "h-4 w-4",
|
|
58076
|
+
"aria-hidden": "true"
|
|
58077
|
+
})
|
|
58078
|
+
}), view.repeat && status.repeat !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransportButton, {
|
|
58079
|
+
label: t("uiLibrary.mediaPlayerHero.repeat", "Repeat"),
|
|
58080
|
+
busy,
|
|
58081
|
+
active: status.repeat !== "off",
|
|
58082
|
+
onPress: () => {
|
|
58083
|
+
if (status.repeat === null) return;
|
|
58084
|
+
const repeat = NEXT_REPEAT[status.repeat];
|
|
58085
|
+
applyOptimistic({ repeat });
|
|
58086
|
+
cap.setRepeat({ repeat });
|
|
58087
|
+
},
|
|
58088
|
+
children: status.repeat === "one" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Repeat1, {
|
|
58089
|
+
className: "h-4 w-4",
|
|
58090
|
+
"aria-hidden": "true"
|
|
58091
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Repeat, {
|
|
58092
|
+
className: "h-4 w-4",
|
|
58093
|
+
"aria-hidden": "true"
|
|
58094
|
+
})
|
|
58095
|
+
})]
|
|
57542
58096
|
})
|
|
57543
58097
|
]
|
|
57544
58098
|
});
|
|
57545
58099
|
}
|
|
58100
|
+
function powerLabelOf(power, t) {
|
|
58101
|
+
if (power === "on") return t("uiLibrary.mediaPlayerHero.turnOff", "Turn off");
|
|
58102
|
+
if (power === "unknown") return t("uiLibrary.mediaPlayerHero.powerUnknown", "Power state unknown");
|
|
58103
|
+
return t("uiLibrary.mediaPlayerHero.turnOn", "Turn on");
|
|
58104
|
+
}
|
|
57546
58105
|
//#endregion
|
|
57547
58106
|
//#region src/composites/device-controls/alarm-hero-card.tsx
|
|
57548
58107
|
var ALARM_STATE_CONFIG = {
|
|
@@ -73116,7 +73675,7 @@ function TimelineSlider({ markers, bands = [], selectedId = null, step = 5, aria
|
|
|
73116
73675
|
}
|
|
73117
73676
|
//#endregion
|
|
73118
73677
|
//#region src/ui-library-version.ts
|
|
73119
|
-
var uiLibraryVersion = "1.2.
|
|
73678
|
+
var uiLibraryVersion = "1.2.378";
|
|
73120
73679
|
//#endregion
|
|
73121
73680
|
//#region src/hooks/use-account-unit-preferences.ts
|
|
73122
73681
|
/**
|
|
@@ -74481,6 +75040,7 @@ exports.PopoverContent = PopoverContent;
|
|
|
74481
75040
|
exports.PopoverRowAction = PopoverRowAction;
|
|
74482
75041
|
exports.PopoverTrigger = PopoverTrigger;
|
|
74483
75042
|
exports.Power = Power;
|
|
75043
|
+
exports.PowerMeterLine = PowerMeterLine;
|
|
74484
75044
|
exports.PowerOff = PowerOff;
|
|
74485
75045
|
exports.PrimaryChildPicker = PrimaryChildPicker;
|
|
74486
75046
|
exports.PrivacyMaskSettings = PrivacyMaskSettings;
|
|
@@ -74507,6 +75067,8 @@ exports.RecordEntriesPanel = RecordEntriesPanel;
|
|
|
74507
75067
|
exports.RecordedPlaybackProvider = RecordedPlaybackProvider;
|
|
74508
75068
|
exports.RecordingPanel = RecordingPanel;
|
|
74509
75069
|
exports.RefreshCw = RefreshCw;
|
|
75070
|
+
exports.Repeat = Repeat;
|
|
75071
|
+
exports.Repeat1 = Repeat1;
|
|
74510
75072
|
exports.ResponseLog = ResponseLog;
|
|
74511
75073
|
exports.RetrainReviewBadge = RetrainReviewBadge;
|
|
74512
75074
|
exports.RoleDisplayDefaultsFromServer = RoleDisplayDefaultsFromServer;
|
|
@@ -74590,6 +75152,7 @@ exports.ShieldAlert = ShieldAlert;
|
|
|
74590
75152
|
exports.ShieldCheck = ShieldCheck;
|
|
74591
75153
|
exports.ShieldOff = ShieldOff;
|
|
74592
75154
|
exports.Ship = Ship;
|
|
75155
|
+
exports.Shuffle = Shuffle;
|
|
74593
75156
|
exports.Sidebar = Sidebar;
|
|
74594
75157
|
exports.SidebarItem = SidebarItem;
|
|
74595
75158
|
exports.Siren = Siren;
|
|
@@ -75273,6 +75836,7 @@ exports.useCoreBlocksList = useCoreBlocksList;
|
|
|
75273
75836
|
exports.useCoreBlocksListComposableCaps = useCoreBlocksListComposableCaps;
|
|
75274
75837
|
exports.useCoreBlocksListCompositions = useCoreBlocksListCompositions;
|
|
75275
75838
|
exports.useCoreBlocksMigrateRawSecrets = useCoreBlocksMigrateRawSecrets;
|
|
75839
|
+
exports.useCoreBlocksProposeRunningThreshold = useCoreBlocksProposeRunningThreshold;
|
|
75276
75840
|
exports.useCoreBlocksReportAutomationRun = useCoreBlocksReportAutomationRun;
|
|
75277
75841
|
exports.useCoreBlocksRestart = useCoreBlocksRestart;
|
|
75278
75842
|
exports.useCoreBlocksSetEnabled = useCoreBlocksSetEnabled;
|