From 84316b575f62c2fc10292ab0089ee00a278b6ada Mon Sep 17 00:00:00 2001 From: jessikitty Date: Mon, 3 Aug 2026 14:27:49 +1000 Subject: [PATCH] Add Layouts modal (save/load/delete named scene+playlist profiles) --- public/admin/layout.html | 88 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/public/admin/layout.html b/public/admin/layout.html index 4096c06..ca20efb 100644 --- a/public/admin/layout.html +++ b/public/admin/layout.html @@ -30,17 +30,23 @@ document.head.insertAdjacentHTML('beforeend', ``); await requireAuth(); @@ -53,6 +59,7 @@ app.innerHTML = `${nav('layout')} + @@ -69,11 +76,23 @@ app.innerHTML = `${nav('layout')}
-
+ +`; @@ -311,6 +330,57 @@ setList.querySelectorAll('[data-fp]').forEach(b => b.onclick = () => { buildAll(); panel(); }); +// ---------- layouts (save / load / delete) ---------- +const layoutsModal = document.getElementById('layoutsModal'); +const layoutList = document.getElementById('layoutList'); +async function refreshLayouts() { + let data; + try { data = await api('/api/layouts'); } + catch { layoutList.innerHTML = 'Could not load layouts.'; return; } + const { layouts, activeSlug } = data; + if (!layouts.length) { layoutList.innerHTML = 'No saved layouts yet. Name one above and Save current.'; return; } + layoutList.innerHTML = layouts.map(l => `
+ ${l.name}${l.slug === activeSlug ? ' ● active' : ''} + + +
`).join(''); + layoutList.querySelectorAll('.layoutRow').forEach(row => { + const slug = row.dataset.slug; + row.querySelector('[data-act="load"]').onclick = async () => { + if (!confirm('Load this layout? The current live scene will be replaced (save it first if unsaved).')) return; + try { + const r = await api('/api/layouts/' + slug + '/load', 'POST'); + scene = r.scene; + scene.anchors ||= []; scene.buildings ||= []; scene.spawns ||= []; scene.paths ||= []; + scene.table = Object.assign({ width: 120, depth: 100, offsetX: 0, offsetZ: 0, show: true }, scene.table || {}); + sel = null; gizmo.detach(); buildAll(); panel(); + status.textContent = 'Loaded layout'; + refreshLayouts(); + } catch (e) { alert('Load failed: ' + e.message); } + }; + row.querySelector('[data-act="del"]').onclick = async () => { + if (!confirm('Delete this saved layout? (Does not affect the live scene.)')) return; + try { await api('/api/layouts/' + slug, 'DELETE'); refreshLayouts(); } + catch (e) { alert('Delete failed: ' + e.message); } + }; + }); +} +document.getElementById('layoutsBtn').onclick = () => { layoutsModal.classList.add('open'); refreshLayouts(); }; +layoutsModal.querySelector('.close').onclick = () => layoutsModal.classList.remove('open'); +layoutsModal.onclick = e => { if (e.target === layoutsModal) layoutsModal.classList.remove('open'); }; +document.getElementById('layoutSave').onclick = async () => { + const name = document.getElementById('layoutName').value.trim(); + if (!name) return alert('Give the layout a name first.'); + try { + // persist current edits into the live scene, then snapshot as a layout + scene = await api('/api/scene', 'PUT', scene); + await api('/api/layouts', 'POST', { name }); + document.getElementById('layoutName').value = ''; + status.textContent = 'Saved layout "' + name + '"'; + refreshLayouts(); + } catch (e) { alert('Save failed: ' + e.message); } +}; + // ---------- property panel ---------- const side = document.getElementById('side'); function field(label, val, oncommit, opts) {