diff --git a/server/src/plugins/service-admin-http/routes-admin.ts b/server/src/plugins/service-admin-http/routes-admin.ts index 0365de6..525813a 100644 --- a/server/src/plugins/service-admin-http/routes-admin.ts +++ b/server/src/plugins/service-admin-http/routes-admin.ts @@ -31,6 +31,7 @@ import { renderCameraLabels, renderKioskLabels, renderDisplayLayouts, + renderDefaultLayoutSelect, } from "../../web-templates/admin-pages.js"; import { discover as onvifDiscover } from "../../shared/onvif.js"; import { generateBundle } from "../../shared/bundle.js"; @@ -986,7 +987,8 @@ export function registerAdminRoutes(app: H3, deps: AdminDeps): void { const attachedIds = new Set(attached.map((l) => l.id)); const available = deps.repo.listLayouts().filter((l) => !attachedIds.has(l.id)); return htmlFragment( - renderDisplayLayouts(displayId, display?.default_layout_id ?? null, attached, available), + renderDisplayLayouts(displayId, display?.default_layout_id ?? null, attached, available) + + renderDefaultLayoutSelect(display?.default_layout_id ?? null, attached, true), ); }; diff --git a/server/src/web-templates/admin-pages.tsx b/server/src/web-templates/admin-pages.tsx index 71112ab..2603ba0 100644 --- a/server/src/web-templates/admin-pages.tsx +++ b/server/src/web-templates/admin-pages.tsx @@ -2193,6 +2193,12 @@ interface DisplayEditPageProps { * Render the attached + available layouts region for a display. Returned * standalone so htmx attach/detach can swap just this fragment via * hx-target="#display-layouts-" hx-swap="innerHTML". + * + * Both lists render as flat tables with the same columns. Available rows + * leave Priority + Default blank and show an Attach button. The + * default-layout ` - - {available.map((l) => ( - - ))} - - - - ) : ( +

Available

+ {available.length === 0 ? (

{attached.length === 0 ? No layouts exist yet. Create one. : "All existing layouts are already attached."}

+ ) : ( +
+ + + + + + + + + + + {available.map((l) => ( + + + + + + + ))} + +
NamePriorityDefault
{l.name} + +
+
)} ); } +/** + * Render the "Default Layout" select for a display. Wrapped in an out-of-band + * htmx swap so attach/detach responses can refresh it without the rest of the + * page. The id matches the in-page select so swap-by-id works. + */ +export function renderDefaultLayoutSelect( + defaultLayoutId: number | null, + attached: LayoutType[], + oob: boolean = false, +): string { + const oobAttr = oob ? { "hx-swap-oob": "outerHTML" } : {}; + return ( + + ); +} + export function DisplayEditPage(props: DisplayEditPageProps) { const d = props.display; return ( @@ -2345,14 +2395,7 @@ export function DisplayEditPage(props: DisplayEditPageProps) {
- + {renderDefaultLayoutSelect(d.default_layout_id, props.attachedLayouts)}
Layout shown on idle revert. Only layouts attached below are eligible.
diff --git a/server/src/web-templates/layout.tsx b/server/src/web-templates/layout.tsx index 1270014..67a96b1 100644 --- a/server/src/web-templates/layout.tsx +++ b/server/src/web-templates/layout.tsx @@ -211,6 +211,8 @@ const baseStyles = { }, ".btn-primary": { backgroundColor: "#2563eb", color: "#fff" }, ".btn-primary:hover": { backgroundColor: "#1d4ed8" }, + ".btn-success": { backgroundColor: "#16a34a", color: "#fff" }, + ".btn-success:hover": { backgroundColor: "#15803d" }, ".btn-danger": { backgroundColor: "#dc2626", color: "#fff" }, ".btn-danger:hover": { backgroundColor: "#b91c1c" }, ".btn-ghost": { backgroundColor: "transparent", color: "#666", border: "1px solid #d0d0d0" },