mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 19:06:34 +00:00
feat(display): persist + surface active layout
Kiosk's layout.changed events now bump displays.active_layout_id on the server side. Display edit page and kiosk edit page render the currently- active layout, and the "Switch Layout" dropdowns pre-select it (with "(active)" suffix) instead of defaulting to first-in-list. Stops the operator from accidentally re-switching to the layout already showing. Migration is idempotent + tail-positioned so existing DBs pick up the column without breaking PRAGMA user_version semantics.
This commit is contained in:
parent
d51e01ff0e
commit
7df048c195
5 changed files with 41 additions and 2 deletions
|
|
@ -511,6 +511,20 @@ function registerKioskRoutes(
|
||||||
forwarded_to_nodered: false,
|
forwarded_to_nodered: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Side-effect: persist active layout per display so the admin UI can
|
||||||
|
// surface "currently showing X" without having to query event_log.
|
||||||
|
if (body.topic === "layout.changed") {
|
||||||
|
const displayId = Number(body.payload?.["display_id"]);
|
||||||
|
const layoutId = Number(body.payload?.["layout_id"]);
|
||||||
|
if (Number.isInteger(displayId) && Number.isInteger(layoutId)) {
|
||||||
|
try {
|
||||||
|
repo.updateDisplay(displayId, { active_layout_id: layoutId } as any);
|
||||||
|
} catch {
|
||||||
|
// Display might not exist; layout.changed is best-effort telemetry.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Best-effort forward to Node-RED. Topics that have a dedicated trigger
|
// Best-effort forward to Node-RED. Topics that have a dedicated trigger
|
||||||
// node (bf-trigger-layout-changed etc.) expect a FLAT payload matching
|
// node (bf-trigger-layout-changed etc.) expect a FLAT payload matching
|
||||||
// what the admin-side emit produces — splat body.payload up to the top
|
// what the admin-side emit produces — splat body.payload up to the top
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,7 @@ export function rowToDisplay(r: Row): Display {
|
||||||
state_check_enabled: b(r["state_check_enabled"]),
|
state_check_enabled: b(r["state_check_enabled"]),
|
||||||
state_check_interval_seconds: n(r["state_check_interval_seconds"]),
|
state_check_interval_seconds: n(r["state_check_interval_seconds"]),
|
||||||
is_enabled: b(r["is_enabled"]),
|
is_enabled: b(r["is_enabled"]),
|
||||||
|
active_layout_id: nn(r["active_layout_id"]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -867,6 +867,13 @@ export const MIGRATIONS: readonly MigrationEntry[] = [
|
||||||
addColumnIfNotExists(db, "displays", "actual_power_state_at", "TEXT");
|
addColumnIfNotExists(db, "displays", "actual_power_state_at", "TEXT");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Kiosk reports active layout per display via layout.changed events.
|
||||||
|
// Persist on the display row so the admin UI can highlight which layout
|
||||||
|
// is currently rendering instead of defaulting to first-in-list.
|
||||||
|
(db: DatabaseSync) => {
|
||||||
|
addColumnIfNotExists(db, "displays", "active_layout_id", "INTEGER REFERENCES layouts(id) ON DELETE SET NULL");
|
||||||
|
},
|
||||||
|
|
||||||
// Backfill hwmon/telemetry columns. They were originally added inline to
|
// Backfill hwmon/telemetry columns. They were originally added inline to
|
||||||
// an earlier migration entry; existing deploys had already passed that
|
// an earlier migration entry; existing deploys had already passed that
|
||||||
// index via PRAGMA user_version, so the new columns silently never landed.
|
// index via PRAGMA user_version, so the new columns silently never landed.
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ export interface Display {
|
||||||
state_check_enabled: boolean;
|
state_check_enabled: boolean;
|
||||||
state_check_interval_seconds: number;
|
state_check_interval_seconds: number;
|
||||||
is_enabled: boolean;
|
is_enabled: boolean;
|
||||||
|
active_layout_id: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Camera {
|
export interface Camera {
|
||||||
|
|
|
||||||
|
|
@ -1596,7 +1596,10 @@ export function KioskEditPage(props: KioskEditProps) {
|
||||||
{layouts.length > 0 ? (
|
{layouts.length > 0 ? (
|
||||||
<select name="layout_id" class="form-input">
|
<select name="layout_id" class="form-input">
|
||||||
{layouts.map((l) => (
|
{layouts.map((l) => (
|
||||||
<option value={String(l.id)}>{l.name}</option>
|
<option
|
||||||
|
value={String(l.id)}
|
||||||
|
selected={l.id === display.active_layout_id}
|
||||||
|
>{l.name}{l.id === display.active_layout_id ? " (active)" : ""}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -2538,6 +2541,16 @@ export function DisplayEditPage(props: DisplayEditPageProps) {
|
||||||
{d.kiosk_id && (
|
{d.kiosk_id && (
|
||||||
<div>Kiosk: <a href={`/admin/kiosks/${d.kiosk_id}`}>{props.kioskName ?? `#${String(d.kiosk_id)}`}</a></div>
|
<div>Kiosk: <a href={`/admin/kiosks/${d.kiosk_id}`}>{props.kioskName ?? `#${String(d.kiosk_id)}`}</a></div>
|
||||||
)}
|
)}
|
||||||
|
{(() => {
|
||||||
|
const active = props.attachedLayouts.find((l) => l.id === d.active_layout_id);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Active layout: {active
|
||||||
|
? <strong>{active.name}</strong>
|
||||||
|
: <span style="color:#999">(unknown — kiosk hasn't reported)</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
{props.attachedLayouts.length > 0 && d.kiosk_id ? (
|
{props.attachedLayouts.length > 0 && d.kiosk_id ? (
|
||||||
<div style="margin-bottom:1rem; padding:0.75rem; background:#f9fafb; border-radius:4px">
|
<div style="margin-bottom:1rem; padding:0.75rem; background:#f9fafb; border-radius:4px">
|
||||||
|
|
@ -2553,7 +2566,10 @@ export function DisplayEditPage(props: DisplayEditPageProps) {
|
||||||
>
|
>
|
||||||
<select name="layout_id" class="form-input" style="flex:1">
|
<select name="layout_id" class="form-input" style="flex:1">
|
||||||
{props.attachedLayouts.map((l) => (
|
{props.attachedLayouts.map((l) => (
|
||||||
<option value={String(l.id)}>{l.name}</option>
|
<option
|
||||||
|
value={String(l.id)}
|
||||||
|
selected={l.id === d.active_layout_id}
|
||||||
|
>{l.name}{l.id === d.active_layout_id ? " (active)" : ""}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue