@hashsome/ui 0.1.0
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/LICENSE +674 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/entities/action-button.d.ts +17 -0
- package/dist/entities/action-button.js +56 -0
- package/dist/entities/action-button.js.map +1 -0
- package/dist/entities/artwork-ring.d.ts +17 -0
- package/dist/entities/artwork-ring.js +89 -0
- package/dist/entities/artwork-ring.js.map +1 -0
- package/dist/entities/climate-tile.d.ts +20 -0
- package/dist/entities/climate-tile.js +98 -0
- package/dist/entities/climate-tile.js.map +1 -0
- package/dist/entities/light-color.d.ts +52 -0
- package/dist/entities/light-color.js +148 -0
- package/dist/entities/light-color.js.map +1 -0
- package/dist/entities/light-tile.d.ts +24 -0
- package/dist/entities/light-tile.js +139 -0
- package/dist/entities/light-tile.js.map +1 -0
- package/dist/entities/media-browser.d.ts +15 -0
- package/dist/entities/media-browser.js +67 -0
- package/dist/entities/media-browser.js.map +1 -0
- package/dist/entities/media-controls.d.ts +20 -0
- package/dist/entities/media-controls.js +53 -0
- package/dist/entities/media-controls.js.map +1 -0
- package/dist/entities/media-player-bar.d.ts +14 -0
- package/dist/entities/media-player-bar.js +78 -0
- package/dist/entities/media-player-bar.js.map +1 -0
- package/dist/entities/media-player-body.d.ts +16 -0
- package/dist/entities/media-player-body.js +18 -0
- package/dist/entities/media-player-body.js.map +1 -0
- package/dist/entities/media-player-column.d.ts +18 -0
- package/dist/entities/media-player-column.js +34 -0
- package/dist/entities/media-player-column.js.map +1 -0
- package/dist/entities/media-player-page.d.ts +12 -0
- package/dist/entities/media-player-page.js +13 -0
- package/dist/entities/media-player-page.js.map +1 -0
- package/dist/entities/media-progress.d.ts +7 -0
- package/dist/entities/media-progress.js +30 -0
- package/dist/entities/media-progress.js.map +1 -0
- package/dist/entities/media-seek.d.ts +9 -0
- package/dist/entities/media-seek.js +19 -0
- package/dist/entities/media-seek.js.map +1 -0
- package/dist/entities/nav-dock.d.ts +13 -0
- package/dist/entities/nav-dock.js +22 -0
- package/dist/entities/nav-dock.js.map +1 -0
- package/dist/entities/nav-rail.d.ts +19 -0
- package/dist/entities/nav-rail.js +18 -0
- package/dist/entities/nav-rail.js.map +1 -0
- package/dist/entities/now-playing.d.ts +20 -0
- package/dist/entities/now-playing.js +34 -0
- package/dist/entities/now-playing.js.map +1 -0
- package/dist/entities/scene-button.d.ts +13 -0
- package/dist/entities/scene-button.js +6 -0
- package/dist/entities/scene-button.js.map +1 -0
- package/dist/entities/seek-line.d.ts +9 -0
- package/dist/entities/seek-line.js +71 -0
- package/dist/entities/seek-line.js.map +1 -0
- package/dist/entities/sensor-readout.d.ts +28 -0
- package/dist/entities/sensor-readout.js +95 -0
- package/dist/entities/sensor-readout.js.map +1 -0
- package/dist/entities/top-bar.d.ts +68 -0
- package/dist/entities/top-bar.js +248 -0
- package/dist/entities/top-bar.js.map +1 -0
- package/dist/entity-handle.d.ts +21 -0
- package/dist/entity-handle.js +16 -0
- package/dist/entity-handle.js.map +1 -0
- package/dist/gallery/fixtures.d.ts +4 -0
- package/dist/gallery/fixtures.js +119 -0
- package/dist/gallery/fixtures.js.map +1 -0
- package/dist/gallery/gallery.d.ts +9 -0
- package/dist/gallery/gallery.js +355 -0
- package/dist/gallery/gallery.js.map +1 -0
- package/dist/gallery/index.d.ts +2 -0
- package/dist/gallery/index.js +2 -0
- package/dist/gallery/index.js.map +1 -0
- package/dist/gallery/props-data.d.ts +10 -0
- package/dist/gallery/props-data.js +718 -0
- package/dist/gallery/props-data.js.map +1 -0
- package/dist/hooks.d.ts +19 -0
- package/dist/hooks.js +53 -0
- package/dist/hooks.js.map +1 -0
- package/dist/icon-data.d.ts +6 -0
- package/dist/icon-data.js +59193 -0
- package/dist/icon-data.js.map +1 -0
- package/dist/icon.d.ts +9 -0
- package/dist/icon.js +28 -0
- package/dist/icon.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/cover.d.ts +5 -0
- package/dist/layout/cover.js +8 -0
- package/dist/layout/cover.js.map +1 -0
- package/dist/layout/date-time-field.d.ts +15 -0
- package/dist/layout/date-time-field.js +108 -0
- package/dist/layout/date-time-field.js.map +1 -0
- package/dist/layout/detail-provider.d.ts +22 -0
- package/dist/layout/detail-provider.js +26 -0
- package/dist/layout/detail-provider.js.map +1 -0
- package/dist/layout/drawer-controls.d.ts +46 -0
- package/dist/layout/drawer-controls.js +93 -0
- package/dist/layout/drawer-controls.js.map +1 -0
- package/dist/layout/energy-chart.d.ts +37 -0
- package/dist/layout/energy-chart.js +85 -0
- package/dist/layout/energy-chart.js.map +1 -0
- package/dist/layout/energy-usage.d.ts +6 -0
- package/dist/layout/energy-usage.js +18 -0
- package/dist/layout/energy-usage.js.map +1 -0
- package/dist/layout/entity-drawer.d.ts +15 -0
- package/dist/layout/entity-drawer.js +77 -0
- package/dist/layout/entity-drawer.js.map +1 -0
- package/dist/layout/fade-scroll.d.ts +14 -0
- package/dist/layout/fade-scroll.js +59 -0
- package/dist/layout/fade-scroll.js.map +1 -0
- package/dist/layout/grid.d.ts +9 -0
- package/dist/layout/grid.js +6 -0
- package/dist/layout/grid.js.map +1 -0
- package/dist/layout/history-section.d.ts +19 -0
- package/dist/layout/history-section.js +39 -0
- package/dist/layout/history-section.js.map +1 -0
- package/dist/layout/hold-progress.d.ts +2 -0
- package/dist/layout/hold-progress.js +9 -0
- package/dist/layout/hold-progress.js.map +1 -0
- package/dist/layout/icon-button-styles.d.ts +9 -0
- package/dist/layout/icon-button-styles.js +28 -0
- package/dist/layout/icon-button-styles.js.map +1 -0
- package/dist/layout/page.d.ts +17 -0
- package/dist/layout/page.js +29 -0
- package/dist/layout/page.js.map +1 -0
- package/dist/layout/plain-button.d.ts +11 -0
- package/dist/layout/plain-button.js +17 -0
- package/dist/layout/plain-button.js.map +1 -0
- package/dist/layout/room-header.d.ts +12 -0
- package/dist/layout/room-header.js +11 -0
- package/dist/layout/room-header.js.map +1 -0
- package/dist/layout/round-button.d.ts +13 -0
- package/dist/layout/round-button.js +19 -0
- package/dist/layout/round-button.js.map +1 -0
- package/dist/layout/sensor-history.d.ts +15 -0
- package/dist/layout/sensor-history.js +91 -0
- package/dist/layout/sensor-history.js.map +1 -0
- package/dist/layout/sensor-stats.d.ts +21 -0
- package/dist/layout/sensor-stats.js +79 -0
- package/dist/layout/sensor-stats.js.map +1 -0
- package/dist/layout/series-chart.d.ts +25 -0
- package/dist/layout/series-chart.js +46 -0
- package/dist/layout/series-chart.js.map +1 -0
- package/dist/layout/stepper.d.ts +10 -0
- package/dist/layout/stepper.js +12 -0
- package/dist/layout/stepper.js.map +1 -0
- package/dist/layout/swatch-row.d.ts +19 -0
- package/dist/layout/swatch-row.js +20 -0
- package/dist/layout/swatch-row.js.map +1 -0
- package/dist/layout/tile.d.ts +67 -0
- package/dist/layout/tile.js +207 -0
- package/dist/layout/tile.js.map +1 -0
- package/dist/layout/use-drawer.d.ts +33 -0
- package/dist/layout/use-drawer.js +34 -0
- package/dist/layout/use-drawer.js.map +1 -0
- package/dist/layout/use-hold.d.ts +21 -0
- package/dist/layout/use-hold.js +61 -0
- package/dist/layout/use-hold.js.map +1 -0
- package/dist/provider.d.ts +37 -0
- package/dist/provider.js +95 -0
- package/dist/provider.js.map +1 -0
- package/dist/status.d.ts +8 -0
- package/dist/status.js +27 -0
- package/dist/status.js.map +1 -0
- package/dist/theme/density.d.ts +16 -0
- package/dist/theme/density.js +7 -0
- package/dist/theme/density.js.map +1 -0
- package/dist/theme/global-styles.d.ts +5 -0
- package/dist/theme/global-styles.js +44 -0
- package/dist/theme/global-styles.js.map +1 -0
- package/dist/theme/index.d.ts +19 -0
- package/dist/theme/index.js +106 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/overrides.d.ts +28 -0
- package/dist/theme/overrides.js +24 -0
- package/dist/theme/overrides.js.map +1 -0
- package/dist/theme/tokens.d.ts +78 -0
- package/dist/theme/tokens.js +1 -0
- package/dist/theme/tokens.js.map +1 -0
- package/dist/theme/use-typography.d.ts +4 -0
- package/dist/theme/use-typography.js +9 -0
- package/dist/theme/use-typography.js.map +1 -0
- package/dist/use-entity-history.d.ts +12 -0
- package/dist/use-entity-history.js +43 -0
- package/dist/use-entity-history.js.map +1 -0
- package/dist/use-media-browser.d.ts +31 -0
- package/dist/use-media-browser.js +87 -0
- package/dist/use-media-browser.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @jsxImportSource @emotion/react */
|
|
2
|
+
import type { EntityRef } from '@hashsome/core';
|
|
3
|
+
export interface MediaPlayerPageProps {
|
|
4
|
+
/** A media player, as a ref like `ma:living_room`. A ref, not a handle: the library below the player is read through the runtime. */
|
|
5
|
+
entity: EntityRef;
|
|
6
|
+
/** What to call the player: the drawer's title and the name shown when nothing is playing. Defaults to the player's own name. */
|
|
7
|
+
name?: string;
|
|
8
|
+
}
|
|
9
|
+
/** The player as a whole page: the player centered, and the player's library below it, so songs
|
|
10
|
+
* are picked right here instead of in a drawer. It is the same layout as the expanded drawer. It
|
|
11
|
+
* fills the height it is given and scrolls inside it. */
|
|
12
|
+
export declare function MediaPlayerPage({ entity, name }: MediaPlayerPageProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
2
|
+
import { Flex } from 'e-prim';
|
|
3
|
+
import { useEntityHandle } from '../hooks.js';
|
|
4
|
+
import { MediaBrowser } from './media-browser.js';
|
|
5
|
+
import { MediaPlayerBody } from './media-player-body.js';
|
|
6
|
+
/** The player as a whole page: the player centered, and the player's library below it, so songs
|
|
7
|
+
* are picked right here instead of in a drawer. It is the same layout as the expanded drawer. It
|
|
8
|
+
* fills the height it is given and scrolls inside it. */
|
|
9
|
+
export function MediaPlayerPage({ entity, name }) {
|
|
10
|
+
const handle = useEntityHandle('mediaPlayer', entity);
|
|
11
|
+
const browsable = handle.entity?.capabilities.browse === true;
|
|
12
|
+
return (_jsx(Flex, { direction: "column", background: "surface", radius: "card", p: 5, grow: 1, minHeight: 0, overflow: "auto", children: _jsx(MediaPlayerBody, { entity: entity, wide: true, ...(name !== undefined ? { name } : {}), ...(browsable ? { browser: _jsx(MediaBrowser, { entity: entity, layout: "theater" }) } : {}) }) }));
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-player-page.js","sourceRoot":"","sources":["../../src/entities/media-player-page.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAqB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAyB,CAAC;AAU1D;;yDAEyD;AACzD,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,IAAI,EAAwB;IACpE,MAAM,MAAM,GAAG,eAAe,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,MAAM,KAAK,IAAI,CAAC;IAE9D,OAAO,CACL,KAAC,IAAI,IACH,SAAS,EAAC,QAAQ,EAClB,UAAU,EAAC,SAAS,EACpB,MAAM,EAAC,MAAM,EACb,CAAC,EAAE,CAAC,EACJ,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAC,MAAM,YAEf,KAAC,eAAe,IACd,MAAM,EAAE,MAAM,EACd,IAAI,WACA,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KACpC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAC,YAAY,IAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAC,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GACrF,GACG,CACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MediaPlayerEntity } from '@hashsome/core';
|
|
2
|
+
/** `m:ss`, or `h:mm:ss` from an hour up. */
|
|
3
|
+
export declare function formatDuration(seconds: number): string;
|
|
4
|
+
/** Where playback is now. The player reports a position as of a moment; while it plays, this
|
|
5
|
+
* moves it forward from there on its own clock, so the bar advances without a message per second.
|
|
6
|
+
* `undefined` until the player reports a position. */
|
|
7
|
+
export declare function useMediaPosition(player: MediaPlayerEntity | undefined): number | undefined;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
/** `m:ss`, or `h:mm:ss` from an hour up. */
|
|
3
|
+
export function formatDuration(seconds) {
|
|
4
|
+
const total = Math.max(0, Math.floor(seconds));
|
|
5
|
+
const hours = Math.floor(total / 3600);
|
|
6
|
+
const minutes = Math.floor((total % 3600) / 60);
|
|
7
|
+
const rest = String(total % 60).padStart(2, '0');
|
|
8
|
+
return hours > 0 ? `${hours}:${String(minutes).padStart(2, '0')}:${rest}` : `${minutes}:${rest}`;
|
|
9
|
+
}
|
|
10
|
+
/** Where playback is now. The player reports a position as of a moment; while it plays, this
|
|
11
|
+
* moves it forward from there on its own clock, so the bar advances without a message per second.
|
|
12
|
+
* `undefined` until the player reports a position. */
|
|
13
|
+
export function useMediaPosition(player) {
|
|
14
|
+
const playing = player?.playback === 'playing';
|
|
15
|
+
const [now, setNow] = useState(() => Date.now());
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!playing) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const id = setInterval(() => setNow(Date.now()), 500);
|
|
21
|
+
return () => clearInterval(id);
|
|
22
|
+
}, [playing]);
|
|
23
|
+
if (player?.position === undefined) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const since = player.positionUpdatedAt ? Date.parse(player.positionUpdatedAt) : Number.NaN;
|
|
27
|
+
const elapsed = playing && Number.isFinite(since) ? Math.max(0, (now - since) / 1000) : 0;
|
|
28
|
+
const position = player.position + elapsed;
|
|
29
|
+
return player.duration === undefined ? position : Math.min(player.duration, position);
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-progress.js","sourceRoot":"","sources":["../../src/entities/media-progress.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE5C,4CAA4C;AAC5C,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACjD,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,IAAI,EAAE,CAAC;AACnG,CAAC;AAED;;sDAEsD;AACtD,MAAM,UAAU,gBAAgB,CAAC,MAAqC;IACpE,MAAM,OAAO,GAAG,MAAM,EAAE,QAAQ,KAAK,SAAS,CAAC;IAC/C,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACjD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QAED,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACtD,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAEd,IAAI,MAAM,EAAE,QAAQ,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;IAC3F,MAAM,OAAO,GAAG,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1F,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC3C,OAAO,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACxF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The spot being sought to while a drag is in progress, and for a moment after it ends: the
|
|
2
|
+
* player takes a beat to report the new position, and without this the bar would snap back to
|
|
3
|
+
* the old one first. `shown` is what to draw; `preview` follows a drag; `commit` ends it. */
|
|
4
|
+
export declare function useSeekHold(position: number | undefined, onSeek: ((position: number) => void) | undefined): {
|
|
5
|
+
shown: number | undefined;
|
|
6
|
+
preview: (next: number) => void;
|
|
7
|
+
commit: (next: number) => void;
|
|
8
|
+
};
|
|
9
|
+
export type SeekHold = ReturnType<typeof useSeekHold>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
/** The spot being sought to while a drag is in progress, and for a moment after it ends: the
|
|
3
|
+
* player takes a beat to report the new position, and without this the bar would snap back to
|
|
4
|
+
* the old one first. `shown` is what to draw; `preview` follows a drag; `commit` ends it. */
|
|
5
|
+
export function useSeekHold(position, onSeek) {
|
|
6
|
+
const [drag, setDrag] = useState(null);
|
|
7
|
+
const release = useRef(undefined);
|
|
8
|
+
useEffect(() => () => clearTimeout(release.current), []);
|
|
9
|
+
return {
|
|
10
|
+
shown: drag ?? position,
|
|
11
|
+
preview: (next) => setDrag(next),
|
|
12
|
+
commit: (next) => {
|
|
13
|
+
setDrag(next);
|
|
14
|
+
clearTimeout(release.current);
|
|
15
|
+
release.current = setTimeout(() => setDrag(null), 1500);
|
|
16
|
+
onSeek?.(next);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-seek.js","sourceRoot":"","sources":["../../src/entities/media-seek.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEpD;;6FAE6F;AAC7F,MAAM,UAAU,WAAW,CACzB,QAA4B,EAC5B,MAAgD;IAEhD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,MAAM,CAA4C,SAAS,CAAC,CAAC;IAC7E,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IAEzD,OAAO;QACL,KAAK,EAAE,IAAI,IAAI,QAAQ;QACvB,OAAO,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QACxC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;YACvB,OAAO,CAAC,IAAI,CAAC,CAAC;YACd,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC9B,OAAO,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YACxD,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NavItem } from './nav-rail.tsx';
|
|
2
|
+
export interface NavDockProps {
|
|
3
|
+
/** The pages to link to; `to` is relative to `base`. */
|
|
4
|
+
items: NavItem[];
|
|
5
|
+
/** Base path items are resolved against, e.g. `/home`. */
|
|
6
|
+
base: string;
|
|
7
|
+
/** Adds a light/dark toggle at the end of the dock. Off by default — meant for development or a project that deliberately exposes it, not every kiosk install. */
|
|
8
|
+
showThemeToggle?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Floating bottom pill for switching between a dashboard's pages. Must be rendered inside a
|
|
11
|
+
* router. `position: fixed`, floating over the page; it reserves the space it takes in the
|
|
12
|
+
* surrounding `Page`, which pads for it. */
|
|
13
|
+
export declare function NavDock({ items, base, showThemeToggle }: NavDockProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource @emotion/react */
|
|
3
|
+
import { Flex } from 'e-prim';
|
|
4
|
+
import { NavLink } from 'react-router';
|
|
5
|
+
import { Icon } from '../icon.js';
|
|
6
|
+
import { usePageInset } from '../layout/page.js';
|
|
7
|
+
import { useThemeToggle } from '../provider.js';
|
|
8
|
+
/** Floating bottom pill for switching between a dashboard's pages. Must be rendered inside a
|
|
9
|
+
* router. `position: fixed`, floating over the page; it reserves the space it takes in the
|
|
10
|
+
* surrounding `Page`, which pads for it. */
|
|
11
|
+
export function NavDock({ items, base, showThemeToggle }) {
|
|
12
|
+
usePageInset('bottom', 88);
|
|
13
|
+
return (_jsxs(Flex, { as: "nav", "aria-label": "Pages", align: "center", gap: 1.5, background: "rail", radius: "full", p: 1.5, shadow: "drawer", position: "fixed", css: ({ spacing }) => ({
|
|
14
|
+
bottom: spacing(5),
|
|
15
|
+
left: '50%',
|
|
16
|
+
transform: 'translateX(-50%)',
|
|
17
|
+
}), children: [items.map((item) => (_jsx(NavLink, { to: item.to ? `${base}/${item.to}` : base, end: true, "aria-label": item.label, title: item.label, children: ({ isActive }) => (_jsx(Flex, { as: "span", align: "center", justify: "center", width: 44, height: 44, radius: "chrome", cursor: "pointer", color: isActive ? 'accentText' : 'line', ...(isActive ? { background: 'accent' } : {}), children: _jsx(Icon, { name: item.icon, size: 22 }) })) }, item.to))), showThemeToggle ? _jsx(ThemeToggleButton, {}) : null] }));
|
|
18
|
+
}
|
|
19
|
+
function ThemeToggleButton() {
|
|
20
|
+
const { resolved, toggle } = useThemeToggle();
|
|
21
|
+
return (_jsx(Flex, { as: "button", type: "button", onClick: toggle, "aria-label": `Switch to ${resolved === 'dark' ? 'light' : 'dark'} theme`, title: `Switch to ${resolved === 'dark' ? 'light' : 'dark'} theme`, align: "center", justify: "center", width: 44, height: 44, radius: "chrome", cursor: "pointer", color: "line", css: { background: 'none' }, children: _jsx(Icon, { name: resolved === 'dark' ? 'lu:moon' : 'lu:sun', size: 22 }) }));
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-dock.js","sourceRoot":"","sources":["../../src/entities/nav-dock.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IconName } from '../icon-data.ts';
|
|
2
|
+
export interface NavItem {
|
|
3
|
+
/** Route path relative to `base`, e.g. `''` (the dashboard's home) or `'lights'`. */
|
|
4
|
+
to: string;
|
|
5
|
+
label: string;
|
|
6
|
+
icon: IconName;
|
|
7
|
+
}
|
|
8
|
+
export interface NavRailProps {
|
|
9
|
+
/** The pages to link to; `to` is relative to `base`. */
|
|
10
|
+
items: NavItem[];
|
|
11
|
+
/** Base path items are resolved against, e.g. `/home`. */
|
|
12
|
+
base: string;
|
|
13
|
+
/** Adds a light/dark toggle at the bottom of the rail. Off by default — meant for development or a project that deliberately exposes it, not every kiosk install. */
|
|
14
|
+
showThemeToggle?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Fixed left sidebar for switching between a dashboard's pages. Must be rendered inside a
|
|
17
|
+
* router. It is `position: fixed` and reserves its width in the surrounding `Page`, which pads
|
|
18
|
+
* for it so content never sits under it. */
|
|
19
|
+
export declare function NavRail({ items, base, showThemeToggle }: NavRailProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource @emotion/react */
|
|
3
|
+
import { Flex } from 'e-prim';
|
|
4
|
+
import { NavLink } from 'react-router';
|
|
5
|
+
import { Icon } from '../icon.js';
|
|
6
|
+
import { usePageInset } from '../layout/page.js';
|
|
7
|
+
import { useThemeToggle } from '../provider.js';
|
|
8
|
+
/** Fixed left sidebar for switching between a dashboard's pages. Must be rendered inside a
|
|
9
|
+
* router. It is `position: fixed` and reserves its width in the surrounding `Page`, which pads
|
|
10
|
+
* for it so content never sits under it. */
|
|
11
|
+
export function NavRail({ items, base, showThemeToggle }) {
|
|
12
|
+
usePageInset('left', 76);
|
|
13
|
+
return (_jsxs(Flex, { as: "nav", "aria-label": "Pages", direction: "column", align: "center", gap: 2.5, background: "rail", width: 76, py: 6, position: "fixed", zIndex: "nav", css: { top: 0, bottom: 0, left: 0 }, children: [items.map((item) => (_jsx(NavLink, { to: item.to ? `${base}/${item.to}` : base, end: true, "aria-label": item.label, title: item.label, children: ({ isActive }) => (_jsx(Flex, { as: "span", align: "center", justify: "center", width: 44, height: 44, radius: "chrome", cursor: "pointer", color: isActive ? 'accentText' : 'line', ...(isActive ? { background: 'accent' } : {}), children: _jsx(Icon, { name: item.icon, size: 22 }) })) }, item.to))), showThemeToggle ? _jsx(ThemeToggleButton, {}) : null] }));
|
|
14
|
+
}
|
|
15
|
+
function ThemeToggleButton() {
|
|
16
|
+
const { resolved, toggle } = useThemeToggle();
|
|
17
|
+
return (_jsx(Flex, { as: "button", type: "button", onClick: toggle, "aria-label": `Switch to ${resolved === 'dark' ? 'light' : 'dark'} theme`, title: `Switch to ${resolved === 'dark' ? 'light' : 'dark'} theme`, align: "center", justify: "center", width: 44, height: 44, radius: "chrome", cursor: "pointer", color: "line", css: { background: 'none', marginTop: 'auto' }, children: _jsx(Icon, { name: resolved === 'dark' ? 'lu:moon' : 'lu:sun', size: 22 }) }));
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-rail.js","sourceRoot":"","sources":["../../src/entities/nav-rail.tsx"],"names":[],"mappings":";AAAA,sCAAsC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAa,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAiB,CAAC;AAoBjD;;4CAE4C;AAC5C,MAAM,UAAU,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,EAAgB;IACpE,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEzB,OAAO,CACL,MAAC,IAAI,IACH,EAAE,EAAC,KAAK,gBACG,OAAO,EAClB,SAAS,EAAC,QAAQ,EAClB,KAAK,EAAC,QAAQ,EACd,GAAG,EAAE,GAAG,EACR,UAAU,EAAC,MAAM,EACjB,KAAK,EAAE,EAAE,EACT,EAAE,EAAE,CAAC,EACL,QAAQ,EAAC,OAAO,EAChB,MAAM,EAAC,KAAK,EACZ,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,aAElC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,KAAC,OAAO,IAEN,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EACzC,GAAG,sBACS,IAAI,CAAC,KAAK,EACtB,KAAK,EAAE,IAAI,CAAC,KAAK,YAEhB,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CACjB,KAAC,IAAI,IACH,EAAE,EAAC,MAAM,EACT,KAAK,EAAC,QAAQ,EACd,OAAO,EAAC,QAAQ,EAChB,KAAK,EAAE,EAAE,EACT,MAAM,EAAE,EAAE,EACV,MAAM,EAAC,QAAQ,EACf,MAAM,EAAC,SAAS,EAChB,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,KACnC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAE9C,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,GAAI,GAC9B,CACR,IApBI,IAAI,CAAC,EAAE,CAqBJ,CACX,CAAC,EACD,eAAe,CAAC,CAAC,CAAC,KAAC,iBAAiB,KAAG,CAAC,CAAC,CAAC,IAAI,IAC1C,CACR,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB;IACxB,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAC9C,OAAO,CACL,KAAC,IAAI,IACH,EAAE,EAAC,QAAQ,EACX,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,MAAM,gBACH,aAAa,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,QAAQ,EACvE,KAAK,EAAE,aAAa,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,QAAQ,EAClE,KAAK,EAAC,QAAQ,EACd,OAAO,EAAC,QAAQ,EAChB,KAAK,EAAE,EAAE,EACT,MAAM,EAAE,EAAE,EACV,MAAM,EAAC,QAAQ,EACf,MAAM,EAAC,SAAS,EAChB,KAAK,EAAC,MAAM,EACZ,GAAG,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAE9C,KAAC,IAAI,IAAC,IAAI,EAAE,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAI,GAC/D,CACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { EntityHandle } from '../entity-handle.ts';
|
|
3
|
+
/** The upright "now playing" panel the media column and page share: the artwork on a record, what is playing, how
|
|
4
|
+
* far along it is, the transport buttons and a volume bar that is always there. Not exported from
|
|
5
|
+
* the package; `MediaPlayerColumn` and `MediaPlayerPage` are its public forms. */
|
|
6
|
+
export declare function NowPlaying({ handle, fallback, name, extra, onOpenArtwork, size, shuffle: shufflePlacement, }: {
|
|
7
|
+
handle: EntityHandle<'mediaPlayer'>;
|
|
8
|
+
/** What to call the player before it reports a name. */
|
|
9
|
+
fallback: string;
|
|
10
|
+
/** Calls the player this instead of the name it reports. */
|
|
11
|
+
name?: string;
|
|
12
|
+
/** One more button, to the right of the transport (the browse button). Shuffle takes the slot on the left, unless it sits by the title. */
|
|
13
|
+
extra?: ReactNode;
|
|
14
|
+
/** Makes the artwork a button that calls this (opens the library). */
|
|
15
|
+
onOpenArtwork?: () => void;
|
|
16
|
+
/** The artwork ring's diameter in px, for a roomier player. Default 168. */
|
|
17
|
+
size?: number;
|
|
18
|
+
/** Where the shuffle button goes: `transport` is the left slot of the button row, `title` a small button beside the song title, which leaves previous, play and next centered on their own. Default `transport`. */
|
|
19
|
+
shuffle?: 'transport' | 'title';
|
|
20
|
+
}): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource @emotion/react */
|
|
3
|
+
import { Flex, Grid, Typography } from 'e-prim';
|
|
4
|
+
import { Icon } from '../icon.js';
|
|
5
|
+
import { ValueBar } from '../layout/drawer-controls.js';
|
|
6
|
+
import { PlainButton } from '../layout/plain-button.js';
|
|
7
|
+
import { IconButton } from '../layout/tile.js';
|
|
8
|
+
import { statusLabels } from '../status.js';
|
|
9
|
+
import { usePressFeedback, useVolumeControl } from './media-controls.js';
|
|
10
|
+
import { formatDuration, useMediaPosition } from './media-progress.js';
|
|
11
|
+
import { useSeekHold } from './media-seek.js';
|
|
12
|
+
import { ArtworkRing } from './artwork-ring.js';
|
|
13
|
+
/** The upright "now playing" panel the media column and page share: the artwork on a record, what is playing, how
|
|
14
|
+
* far along it is, the transport buttons and a volume bar that is always there. Not exported from
|
|
15
|
+
* the package; `MediaPlayerColumn` and `MediaPlayerPage` are its public forms. */
|
|
16
|
+
export function NowPlaying({ handle, fallback, name, extra, onOpenArtwork, size, shuffle: shufflePlacement = 'transport', }) {
|
|
17
|
+
const player = handle.entity;
|
|
18
|
+
const { status } = handle;
|
|
19
|
+
const ready = status === 'ready';
|
|
20
|
+
const caps = player?.capabilities;
|
|
21
|
+
const playing = player?.playback === 'playing';
|
|
22
|
+
const { feedbackFor, press } = usePressFeedback();
|
|
23
|
+
const volume = useVolumeControl(handle);
|
|
24
|
+
const position = useMediaPosition(player);
|
|
25
|
+
const duration = ready ? player?.duration : undefined;
|
|
26
|
+
// The ring and the time under the title share one position, so dragging the ring moves both.
|
|
27
|
+
const seek = useSeekHold(ready ? position : undefined, caps?.seek ? (next) => void handle.command('seek', { position: next }) : undefined);
|
|
28
|
+
return (_jsxs(Flex, { direction: "column", gap: 4, "data-status": status, children: [_jsx(ArtworkRing, { artworkUrl: player?.media?.artworkUrl, seek: seek, duration: duration, seekable: caps?.seek === true, onOpen: onOpenArtwork, ...(size !== undefined ? { size } : {}) }), _jsxs(Flex, { direction: "column", align: "center", gap: 0.5, color: ready ? 'text' : 'textMuted', children: [_jsxs(Flex, { align: "center", justify: "center", gap: 2, maxWidth: "100%", children: [_jsx(Typography, { as: "h2", variant: "title", noWrap: true, textOverflow: "ellipsis", overflow: "hidden", m: 0, minWidth: 0, children: ready
|
|
29
|
+
? (player?.media?.title ?? 'Nothing playing')
|
|
30
|
+
: statusLabels[status] }), caps?.shuffle && shufflePlacement === 'title' ? (_jsx(IconButton, { icon: "lu:shuffle", label: "Shuffle", size: 14, active: player?.shuffle === true, pressed: player?.shuffle === true, disabled: !ready, onClick: () => void handle.command('setShuffle', { shuffle: player?.shuffle !== true }) })) : null] }), ready && player?.media?.artist ? (_jsx(Typography, { as: "span", variant: "body", color: "textMuted", noWrap: true, textOverflow: "ellipsis", overflow: "hidden", children: [player.media.artist, player.media.album].filter(Boolean).join(' · ') })) : (_jsx(Typography, { as: "span", variant: "body", color: "textMuted", noWrap: true, textOverflow: "ellipsis", overflow: "hidden", children: name ?? player?.name ?? fallback }))] }), seek.shown !== undefined && duration ? (_jsxs(Typography, { as: "span", variant: "secondary", color: "textMuted", align: "center", css: { fontVariantNumeric: 'tabular-nums' }, children: [formatDuration(seek.shown), " / ", formatDuration(duration)] })) : null, _jsxs(Grid, { gap: 3, css: ({ density }) => ({
|
|
31
|
+
gridTemplateColumns: `repeat(5, ${density.iconCircle}px)`,
|
|
32
|
+
justifyContent: 'center',
|
|
33
|
+
}), children: [caps?.shuffle && shufflePlacement === 'transport' ? (_jsx(IconButton, { icon: "lu:shuffle", label: "Shuffle", glyph: 18, active: player?.shuffle === true, pressed: player?.shuffle === true, disabled: !ready, onClick: () => void handle.command('setShuffle', { shuffle: player?.shuffle !== true }) })) : (_jsx("span", {})), _jsx(IconButton, { icon: "lu:skip-back", label: "Previous", glyph: 18, disabled: !ready || caps?.previous === false, feedback: feedbackFor('previous'), onClick: () => press('previous', () => handle.command('previous')) }), _jsx(IconButton, { icon: playing ? 'lu:pause' : 'lu:play', label: playing ? 'Pause' : 'Play', glyph: 18, disabled: !ready, primary: true, onClick: () => void handle.command('togglePlay') }), _jsx(IconButton, { icon: "lu:skip-forward", label: "Next", glyph: 18, disabled: !ready || caps?.next === false, feedback: feedbackFor('next'), onClick: () => press('next', () => handle.command('next')) }), extra ?? _jsx("span", {})] }), caps?.volume !== false || caps.mute ? (_jsxs(Flex, { align: "center", gap: 2, children: [_jsx(PlainButton, { "aria-label": volume.muted ? 'Unmute' : 'Mute', "aria-pressed": volume.muted, title: volume.muted ? 'Unmute' : 'Mute', disabled: !ready || caps?.mute === false, onClick: volume.toggleMute, center: true, color: volume.muted ? 'accent' : 'textMuted', css: { flex: 'none' }, children: _jsx(Icon, { name: volume.muted ? 'lu:volume-x' : 'lu:volume-2', size: 16 }) }), _jsx(ValueBar, { label: "Volume level", value: volume.shown, min: 0, max: 100, keyStep: 5, height: 10, onDrag: volume.drag, onCommit: volume.commit }), _jsxs(Typography, { as: "span", variant: "secondary", color: "textMuted", minWidth: 32, align: "right", css: { flex: 'none', fontVariantNumeric: 'tabular-nums' }, children: [volume.shown, "%"] })] })) : null] }));
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"now-playing.js","sourceRoot":"","sources":["../../src/entities/now-playing.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { EntityRef } from '@hashsome/core';
|
|
2
|
+
import type { EntityHandle } from '../entity-handle.ts';
|
|
3
|
+
import type { IconName } from '../icon-data.ts';
|
|
4
|
+
export interface SceneButtonProps {
|
|
5
|
+
/** A scene (an `action` entity), as a ref like `ha:scene.movie_night` or a handle. */
|
|
6
|
+
entity: EntityRef | EntityHandle<'action'>;
|
|
7
|
+
/** Defaults to the scene's name. */
|
|
8
|
+
name?: string;
|
|
9
|
+
/** Icon id. Defaults to a palette. */
|
|
10
|
+
icon?: IconName;
|
|
11
|
+
}
|
|
12
|
+
/** Activates a scene. */
|
|
13
|
+
export declare function SceneButton({ entity, name, icon }: SceneButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ActionButton } from './action-button.js';
|
|
3
|
+
/** Activates a scene. */
|
|
4
|
+
export function SceneButton({ entity, name, icon }) {
|
|
5
|
+
return (_jsx(ActionButton, { entity: entity, ...(name ? { label: name } : {}), icon: icon ?? 'lu:palette' }));
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-button.js","sourceRoot":"","sources":["../../src/entities/scene-button.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAqB,CAAC;AAanD,yBAAyB;AACzB,MAAM,UAAU,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAoB;IAClE,OAAO,CACL,KAAC,YAAY,IAAC,MAAM,EAAE,MAAM,KAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,YAAY,GAAI,CAChG,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SeekHold } from './media-seek.ts';
|
|
2
|
+
/** The thin progress line along the bottom of the media bar. A tall invisible strip around it is
|
|
3
|
+
* the touch target; with `seekable` a press or drag on it seeks, and the arrow keys nudge it. */
|
|
4
|
+
export declare function SeekLine({ seek, duration, seekable, }: {
|
|
5
|
+
seek: SeekHold;
|
|
6
|
+
/** Seconds. */
|
|
7
|
+
duration: number;
|
|
8
|
+
seekable: boolean;
|
|
9
|
+
}): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource @emotion/react */
|
|
3
|
+
import { Box } from 'e-prim';
|
|
4
|
+
import { useRef } from 'react';
|
|
5
|
+
/** The thin progress line along the bottom of the media bar. A tall invisible strip around it is
|
|
6
|
+
* the touch target; with `seekable` a press or drag on it seeks, and the arrow keys nudge it. */
|
|
7
|
+
export function SeekLine({ seek, duration, seekable, }) {
|
|
8
|
+
const dragging = useRef(false);
|
|
9
|
+
const shown = Math.min(duration, Math.max(0, seek.shown ?? 0));
|
|
10
|
+
const fraction = duration > 0 ? shown / duration : 0;
|
|
11
|
+
const positionAt = (event) => {
|
|
12
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
13
|
+
const along = box.width > 0 ? (event.clientX - box.left) / box.width : 0;
|
|
14
|
+
return Math.round(Math.min(1, Math.max(0, along)) * duration);
|
|
15
|
+
};
|
|
16
|
+
const onKeyDown = (event) => {
|
|
17
|
+
if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
const step = event.key === 'ArrowRight' ? 5 : -5;
|
|
22
|
+
seek.commit(Math.min(duration, Math.max(0, Math.round(shown + step))));
|
|
23
|
+
};
|
|
24
|
+
return (_jsx(Box, { position: "absolute", height: 16, cursor: seekable ? 'pointer' : 'default', ...(seekable
|
|
25
|
+
? {
|
|
26
|
+
role: 'slider',
|
|
27
|
+
tabIndex: 0,
|
|
28
|
+
onKeyDown,
|
|
29
|
+
onPointerDown: (event) => {
|
|
30
|
+
dragging.current = true;
|
|
31
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
32
|
+
seek.preview(positionAt(event));
|
|
33
|
+
},
|
|
34
|
+
onPointerMove: (event) => {
|
|
35
|
+
if (dragging.current) {
|
|
36
|
+
seek.preview(positionAt(event));
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
onPointerUp: (event) => {
|
|
40
|
+
if (dragging.current) {
|
|
41
|
+
dragging.current = false;
|
|
42
|
+
seek.commit(positionAt(event));
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
: { role: 'progressbar' }), "aria-label": "Position", "aria-valuemin": 0, "aria-valuemax": Math.round(duration), "aria-valuenow": Math.round(shown), css: ({ palette }) => ({
|
|
47
|
+
left: 0,
|
|
48
|
+
right: 0,
|
|
49
|
+
bottom: 0,
|
|
50
|
+
touchAction: 'none',
|
|
51
|
+
'&::before, &::after': {
|
|
52
|
+
content: '""',
|
|
53
|
+
position: 'absolute',
|
|
54
|
+
left: 0,
|
|
55
|
+
bottom: 0,
|
|
56
|
+
height: 3,
|
|
57
|
+
transition: 'height 0.12s ease',
|
|
58
|
+
},
|
|
59
|
+
// The track only shows when there is something to grab.
|
|
60
|
+
'&::before': { right: 0, background: seekable ? palette.border : 'transparent' },
|
|
61
|
+
'&::after': { width: `${fraction * 100}%`, background: palette.accent },
|
|
62
|
+
...(seekable
|
|
63
|
+
? {
|
|
64
|
+
'&:hover::before, &:hover::after, &:focus-visible::before, &:focus-visible::after': {
|
|
65
|
+
height: 6,
|
|
66
|
+
},
|
|
67
|
+
outline: 'none',
|
|
68
|
+
}
|
|
69
|
+
: {}),
|
|
70
|
+
}) }));
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seek-line.js","sourceRoot":"","sources":["../../src/entities/seek-line.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EntityRef } from '@hashsome/core';
|
|
2
|
+
import type { IconName } from '../icon-data.ts';
|
|
3
|
+
import { type EntityHandle } from '../entity-handle.ts';
|
|
4
|
+
import { type SensorSample } from '../layout/sensor-history.tsx';
|
|
5
|
+
/** Bounds a reading is considered safe within; either end can be left open. */
|
|
6
|
+
export interface SafeRange {
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface SensorReadoutProps {
|
|
11
|
+
/** A sensor, as a ref like `ha:sensor.living_room_humidity` or a handle (a custom source). */
|
|
12
|
+
entity: EntityRef | EntityHandle<'sensor'>;
|
|
13
|
+
/** Icon id. Defaults by device class (temperature, humidity). */
|
|
14
|
+
icon?: IconName;
|
|
15
|
+
/** Drawer title. Defaults to the sensor's name. */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** The sensor's battery level, as a sensor ref like `ha:sensor.kitchen_t_h_sensor_battery` (a percentage). Shown in the drawer, in red when low. */
|
|
18
|
+
battery?: EntityRef;
|
|
19
|
+
/** Readings over time (oldest first). Adds a chart and min/max for today / this week / this month to the drawer, plus a custom date and time range when expanded. Left out, they come from the backend's own history when it keeps one (Home Assistant's long-term statistics). */
|
|
20
|
+
history?: SensorSample[];
|
|
21
|
+
/** Flags readings outside `{ min, max }` with a warning. Humidity defaults to 30–60 %; pass `false` to turn a default off. */
|
|
22
|
+
safeRange?: SafeRange | false;
|
|
23
|
+
/** Tapping a ready numeric reading opens a drawer (current value and safe-range verdict, plus the chart and min/max when `history` is given). Default `true`; `false` keeps it inline text, e.g. for a presence chip. */
|
|
24
|
+
drawer?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Inline value with icon and unit. Shows "Unavailable" / "Unknown" instead of a reading, and a
|
|
27
|
+
* warning when the value leaves its safe range. Tapping a numeric reading opens a drawer. */
|
|
28
|
+
export declare function SensorReadout({ entity, icon, name, battery, history, safeRange, drawer, }: SensorReadoutProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
|
+
import { Flex, Typography } from 'e-prim';
|
|
3
|
+
import { Icon } from '../icon.js';
|
|
4
|
+
import { fallbackName } from '../entity-handle.js';
|
|
5
|
+
import { useEntityHandle } from '../hooks.js';
|
|
6
|
+
import { SensorHistory } from '../layout/sensor-history.js';
|
|
7
|
+
import { PlainButton } from '../layout/plain-button.js';
|
|
8
|
+
import { DrawerTrigger } from '../layout/use-drawer.js';
|
|
9
|
+
import { capitalize, statusLabels } from '../status.js';
|
|
10
|
+
/** What a readout looks like in each state, from its data attributes (`data-range`, `data-status`):
|
|
11
|
+
* red outside its safe range, dimmed while it has no reading. */
|
|
12
|
+
const readoutStates = ({ palette }) => ({
|
|
13
|
+
"&[data-range='low'], &[data-range='high']": { color: palette.danger },
|
|
14
|
+
"&[data-status]:not([data-status='ready'])": { opacity: 0.6 },
|
|
15
|
+
});
|
|
16
|
+
const deviceClassIcons = {
|
|
17
|
+
temperature: 'lu:thermometer',
|
|
18
|
+
humidity: 'lu:droplet',
|
|
19
|
+
};
|
|
20
|
+
const DEFAULT_SAFE_RANGES = {
|
|
21
|
+
humidity: { min: 30, max: 60 },
|
|
22
|
+
};
|
|
23
|
+
function formatValue(raw) {
|
|
24
|
+
const value = Number(raw);
|
|
25
|
+
return raw.trim() !== '' && Number.isFinite(value) ? String(Math.round(value * 10) / 10) : raw;
|
|
26
|
+
}
|
|
27
|
+
function rangeLevel(raw, range) {
|
|
28
|
+
const value = Number(raw);
|
|
29
|
+
if (!range || raw === undefined || raw.trim() === '' || !Number.isFinite(value)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
if (range.min !== undefined && value < range.min) {
|
|
33
|
+
return 'low';
|
|
34
|
+
}
|
|
35
|
+
if (range.max !== undefined && value > range.max) {
|
|
36
|
+
return 'high';
|
|
37
|
+
}
|
|
38
|
+
return 'ok';
|
|
39
|
+
}
|
|
40
|
+
function describeRange(range, unit) {
|
|
41
|
+
const suffix = unit ? ` ${unit}` : '';
|
|
42
|
+
if (range.min !== undefined && range.max !== undefined) {
|
|
43
|
+
return `${range.min}–${range.max}${suffix}`;
|
|
44
|
+
}
|
|
45
|
+
return range.min !== undefined
|
|
46
|
+
? `at least ${range.min}${suffix}`
|
|
47
|
+
: `at most ${range.max}${suffix}`;
|
|
48
|
+
}
|
|
49
|
+
/** Inline value with icon and unit. Shows "Unavailable" / "Unknown" instead of a reading, and a
|
|
50
|
+
* warning when the value leaves its safe range. Tapping a numeric reading opens a drawer. */
|
|
51
|
+
export function SensorReadout({ entity, icon, name, battery, history, safeRange, drawer = true, }) {
|
|
52
|
+
const handle = useEntityHandle('sensor', entity);
|
|
53
|
+
const { status } = handle;
|
|
54
|
+
const sensor = handle.entity;
|
|
55
|
+
const ready = status === 'ready' && sensor !== undefined;
|
|
56
|
+
const deviceClass = sensor?.measurement;
|
|
57
|
+
const unit = sensor?.unit ?? '';
|
|
58
|
+
const resolvedIcon = icon ?? (deviceClass ? deviceClassIcons[deviceClass] : undefined) ?? 'lu:gauge';
|
|
59
|
+
const range = safeRange === false
|
|
60
|
+
? undefined
|
|
61
|
+
: (safeRange ?? (deviceClass ? DEFAULT_SAFE_RANGES[deviceClass] : undefined));
|
|
62
|
+
const level = ready ? rangeLevel(sensor.value, range) : undefined;
|
|
63
|
+
const label = name ?? sensor?.name ?? fallbackName(entity);
|
|
64
|
+
const valueText = ready ? `${formatValue(sensor.value)}${unit ? ` ${unit}` : ''}` : '';
|
|
65
|
+
const warning = level === 'low' || level === 'high';
|
|
66
|
+
const hint = warning && range
|
|
67
|
+
? `${label} is too ${level} (safe range ${describeRange(range, unit)})`
|
|
68
|
+
: undefined;
|
|
69
|
+
const content = (_jsxs(_Fragment, { children: [_jsx(Icon, { name: resolvedIcon, size: 16 }), _jsx(Typography, { as: "span", variant: "body", noWrap: true, children: ready ? valueText : statusLabels[status] }), warning ? _jsx(Icon, { name: "lu:triangle-alert", size: 16 }) : null] }));
|
|
70
|
+
const numeric = ready && sensor.numeric !== undefined;
|
|
71
|
+
if (drawer && numeric) {
|
|
72
|
+
return (_jsx(DrawerTrigger, { icon: resolvedIcon, label: label, kind: deviceClass ? capitalize(deviceClass) : 'Sensor', body: _jsx(SensorDetailBody, { valueText: valueText, level: level, range: range, unit: unit, ...(battery ? { battery } : {}), ...(typeof entity === 'string' ? { entity } : {}), ...(history ? { history } : {}) }), children: (open) => (_jsx(PlainButton, { inline: true, align: "center", gap: 1, css: readoutStates, "data-status": status, "data-range": level, "aria-label": `${label}: ${valueText}${hint ? `. ${hint}` : ''}`, title: hint, onClick: open, children: content })) }));
|
|
73
|
+
}
|
|
74
|
+
return (_jsx(Flex, { as: "span", inline: true, align: "center", gap: 1, css: readoutStates, "data-status": status, "data-range": level, title: hint, children: content }));
|
|
75
|
+
}
|
|
76
|
+
function SensorDetailBody({ valueText, level, range, unit, entity, battery, history, }) {
|
|
77
|
+
const warning = level === 'low' || level === 'high';
|
|
78
|
+
return (_jsxs(Flex, { direction: "column", gap: 2, children: [_jsxs(Flex, { direction: "column", background: "surfaceRaised", radius: "row", py: 3.5, px: 4, children: [_jsx(Typography, { as: "span", variant: "secondary", color: "textMuted", children: "Now" }), _jsx(Typography, { as: "span", variant: "stat", children: valueText }), range ? (_jsxs(Flex, { align: "center", gap: 1.5, css: ({ palette }) => ({ color: warning ? palette.danger : palette.textMuted }), children: [warning ? _jsx(Icon, { name: "lu:triangle-alert", size: 14 }) : null, _jsx(Typography, { as: "span", variant: "secondary", children: warning
|
|
79
|
+
? `Too ${level} · safe range ${describeRange(range, unit)}`
|
|
80
|
+
: `Within the safe range (${describeRange(range, unit)})` })] })) : null] }), battery ? _jsx(BatteryLevel, { entity: battery }) : null, _jsx(SensorHistory, { ...(history ? { samples: history } : entity ? { entity } : {}), unit: unit })] }));
|
|
81
|
+
}
|
|
82
|
+
/** Below this a battery is shown as low. */
|
|
83
|
+
const LOW_BATTERY = 20;
|
|
84
|
+
const batteryIcon = (percent) => percent <= LOW_BATTERY
|
|
85
|
+
? 'lu:battery-low'
|
|
86
|
+
: percent <= 60
|
|
87
|
+
? 'lu:battery-medium'
|
|
88
|
+
: 'lu:battery-full';
|
|
89
|
+
/** A sensor's battery, in its drawer: the level, red and with a warning once it is low. */
|
|
90
|
+
function BatteryLevel({ entity }) {
|
|
91
|
+
const handle = useEntityHandle('sensor', entity);
|
|
92
|
+
const level = handle.status === 'ready' ? handle.entity?.numeric : undefined;
|
|
93
|
+
const low = level !== undefined && level <= LOW_BATTERY;
|
|
94
|
+
return (_jsxs(Flex, { align: "center", gap: 2, background: "surfaceRaised", radius: "row", py: 2.5, px: 4, color: low ? 'danger' : 'textMuted', children: [_jsx(Icon, { name: level === undefined ? 'lu:battery' : batteryIcon(level), size: 16 }), _jsx(Typography, { as: "span", variant: "secondary", grow: 1, children: low ? 'Battery low' : 'Battery' }), _jsx(Typography, { as: "span", variant: "bodyStrong", color: low ? 'danger' : 'text', children: level === undefined ? '—' : `${Math.round(level)}%` })] }));
|
|
95
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sensor-readout.js","sourceRoot":"","sources":["../../src/entities/sensor-readout.tsx"],"names":[],"mappings":"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"}
|