The second of the web front-end's three pages. Lights get a toggle, five brightness steps and six colours; shutters get four presets, up/stop/down and a window that reflects the position Home Assistant reports. Its own violet hue rather than the player's teal, so the two pages read as siblings rather than one bleeding into the other - the same reason room.css has its own tokens. The backend keeps Home Assistant's URL and token and relays the calls, so this client learns only which entities exist, exactly as the browser does. A 404 from GET /api/ha means Home Assistant is not configured, which is a normal answer: the tab simply does not appear. Polled, not subscribed, and only while the page is on screen. HA's own auth-and- subscribe websocket is more machinery than a room panel needs, and a lamp's state is not worth a request every 2.5 seconds when nobody is looking at it - this device has a music player to stay out of the way of. A tap patches the entity locally and records when; a poll that was already in flight when the patch landed is dropped for that entity rather than putting the lamp back to "off" until the next one catches up. That reconciliation is the one non-obvious thing in useHomeAssistant.ts and it is carried over for the same reason. Nothing on a shutter card animates locally. A real cover reports its own movement and position, and a client-side guess at where it will end up is precisely what would fight with that - the design mockup animated it because it had no backend to ask. Two additions beyond a straight port, both because a kiosk has no pointer: CTRL+TAB cycles the pages. The web's tab rail is pointer-only, which sits badly with a front-end whose README claims every screen is reachable from the keyboard. Plain TAB was already the group cycle, and CTRL+TAB is the idiom for the outer one everywhere else. --page opens directly on a page, which a kiosk may well want and which is the only way in with neither keyboard nor pointer. Verified against the real Home Assistant: the shutter reported open and the lamp off, and both drew that way. Nothing was switched - those entities are hardware. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
152 lines
4.5 KiB
Plaintext
152 lines
4.5 KiB
Plaintext
// The shapes that cross between Rust and the UI.
|
||
//
|
||
// Everything here is already *presentation*: labels are formatted, durations are
|
||
// strings, covers are decoded images. Nothing in the .slint files formats a number or
|
||
// picks a word, so the German copy and the duration format live in one place (Rust)
|
||
// rather than being split across two languages.
|
||
//
|
||
// `nav` is each item's position in the single flat selection order that runs songs ->
|
||
// categories -> albums, so one pair of arrow keys walks the whole page. It is assigned
|
||
// in Rust, where the three lists are built together.
|
||
|
||
export struct AlbumTile {
|
||
id: string,
|
||
title: string,
|
||
artist: string,
|
||
// "12 Songs · 45:03 · 🧸" - already assembled, including the figurine marker.
|
||
meta: string,
|
||
cover: image,
|
||
is-book: bool,
|
||
locked: bool,
|
||
// This album is the one playing; drawn with the accent ring rather than the
|
||
// selection ring, and outranked by it when they coincide.
|
||
current: bool,
|
||
nav: int,
|
||
}
|
||
|
||
// The grid is handed to the UI pre-chunked into rows, because a `ListView` virtualizes
|
||
// its model and a flat list of 343 cards has no rows to skip. This is the direct
|
||
// counterpart of the web app's `content-visibility: auto` on `.grid > .card`, which
|
||
// exists for exactly the same measurement: 340 live cards saturated the Pi's renderer.
|
||
export struct GridRow {
|
||
tiles: [AlbumTile],
|
||
}
|
||
|
||
export struct CategoryTile {
|
||
key: string,
|
||
// "7 Hörbücher"
|
||
count-label: string,
|
||
// Up to four covers in a 2x2, or one filling the tile when the category holds a
|
||
// single album. Four fields rather than an array: the count is fixed and small, and
|
||
// this keeps the tile a plain struct.
|
||
cover1: image,
|
||
cover2: image,
|
||
cover3: image,
|
||
cover4: image,
|
||
cover-count: int,
|
||
is-book: bool,
|
||
nav: int,
|
||
}
|
||
|
||
export struct CategoryRow {
|
||
tiles: [CategoryTile],
|
||
}
|
||
|
||
// One of the three shelves on the root screen.
|
||
export struct Shelf {
|
||
label: string,
|
||
icon: string,
|
||
tiles: [CategoryTile],
|
||
empty-label: string,
|
||
}
|
||
|
||
export struct TrackRow {
|
||
title: string,
|
||
subtitle: string,
|
||
duration: string,
|
||
cover: image,
|
||
is-book: bool,
|
||
current: bool,
|
||
locked: bool,
|
||
album-id: string,
|
||
index: int,
|
||
nav: int,
|
||
}
|
||
|
||
// What the player bar and the full-screen view both say. Shared so the two cannot
|
||
// drift apart on a copy change.
|
||
export struct NowPlaying {
|
||
title: string,
|
||
subtitle: string,
|
||
// "Song 3 von 8" / "Kapitel 3 von 8"
|
||
counter: string,
|
||
cover: image,
|
||
hero-cover: image,
|
||
is-book: bool,
|
||
has-album: bool,
|
||
// "−4:12", the time left in this track.
|
||
clock-label: string,
|
||
// "Noch 38:20 im Album" - empty for a podcast, where it means nothing.
|
||
remaining-label: string,
|
||
}
|
||
|
||
// ------------------------------------------------------------- Mein Zimmer --
|
||
|
||
// One device card on the room page. Lights and shutters share a struct rather than
|
||
// having one each, because the grid renders them in the order Home Assistant's config
|
||
// lists them and two models would lose that order. `kind` is what the view branches on.
|
||
export struct RoomCard {
|
||
entity-id: string,
|
||
name: string,
|
||
// "light" | "shutter"
|
||
kind: string,
|
||
on: bool,
|
||
|
||
// --- light ---
|
||
is-color: bool,
|
||
// 0..5, matching the five brightness buttons.
|
||
level: int,
|
||
tint: color,
|
||
// Which of the six swatches the light is currently set to, or -1.
|
||
swatch: int,
|
||
|
||
// --- shutter ---
|
||
// Percent *closed*: 0 is fully open. Home Assistant reports the opposite; the
|
||
// inversion happens once, in Rust.
|
||
closed-percent: float,
|
||
// "Fährt runter …", or the preset name the position matches.
|
||
status: string,
|
||
// "" | "up" | "down"
|
||
moving: string,
|
||
supports-position: bool,
|
||
// Which of the four presets the position matches, or -1.
|
||
preset: int,
|
||
}
|
||
|
||
// The room grid, pre-chunked into rows for the same reason the album grid is: the
|
||
// view lays rows out, and how many fit is a function of the window width, which Rust
|
||
// is the one that knows.
|
||
export struct RoomRow {
|
||
cards: [RoomCard],
|
||
}
|
||
|
||
export struct SceneTile {
|
||
entity-id: string,
|
||
name: string,
|
||
active: bool,
|
||
}
|
||
|
||
// One of the six colours a colour-capable light can be set to.
|
||
export struct Swatch {
|
||
name: string,
|
||
tint: color,
|
||
}
|
||
|
||
// One entry in the vertical tab rail. Built in Rust like every other string on
|
||
// screen, so the page order, the icons and the German labels live in one place.
|
||
export struct Tab {
|
||
id: string,
|
||
icon: string,
|
||
label: string,
|
||
}
|