Add "Import set…" footprint picker to layout editor

This commit is contained in:
2026-08-03 14:15:48 +10:00
parent d1eb123974
commit 34f49d6abf
+40 -1
View File
@@ -14,6 +14,7 @@ import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { TransformControls } from 'three/addons/controls/TransformControls.js'; import { TransformControls } from 'three/addons/controls/TransformControls.js';
import { anchorWorldMatrix } from '/js/ar/pose.js'; import { anchorWorldMatrix } from '/js/ar/pose.js';
import { SET_FOOTPRINTS, footprintLabel, footprintToBuilding } from '/js/set-footprints.js';
import { api, requireAuth, styles, nav } from './admin.js'; import { api, requireAuth, styles, nav } from './admin.js';
document.head.insertAdjacentHTML('beforeend', `<style>${styles} document.head.insertAdjacentHTML('beforeend', `<style>${styles}
@@ -29,6 +30,17 @@ document.head.insertAdjacentHTML('beforeend', `<style>${styles}
.badge { font-size:.7rem; opacity:.6; } .badge { font-size:.7rem; opacity:.6; }
#legend { position:absolute; left:10px; bottom:10px; font-size:.72rem; background:rgba(6,8,15,.6); #legend { position:absolute; left:10px; bottom:10px; font-size:.72rem; background:rgba(6,8,15,.6);
padding:6px 10px; border-radius:8px; line-height:1.5; pointer-events:none; } padding:6px 10px; border-radius:8px; line-height:1.5; pointer-events:none; }
#setModal { position:fixed; inset:0; background:rgba(4,6,12,.7); display:none; z-index:50;
align-items:center; justify-content:center; }
#setModal.open { display:flex; }
#setCard { background:#111730; border:1px solid #2a3a6e; border-radius:10px; padding:16px;
width:min(420px,92vw); max-height:80vh; display:flex; flex-direction:column; }
#setCard h2 { margin:0 0 4px; font-size:1rem; color:#8fb8ff; }
#setCard .hint { font-size:.72rem; opacity:.6; margin:0 0 10px; }
#setList { overflow:auto; display:flex; flex-direction:column; gap:4px; }
#setList button { text-align:left; background:#141a33; }
#setList button:hover { background:#1c2650; }
#setCard .close { margin-top:12px; align-self:flex-end; }
</style>`); </style>`);
await requireAuth(); await requireAuth();
@@ -37,6 +49,7 @@ app.innerHTML = `${nav('layout')}
<div class="tools"> <div class="tools">
<button data-add="anchor">+ Anchor</button> <button data-add="anchor">+ Anchor</button>
<button data-add="building">+ Building</button> <button data-add="building">+ Building</button>
<button id="importSet">Import set…</button>
<button data-add="spawn">+ Spawn</button> <button data-add="spawn">+ Spawn</button>
<button data-add="path">+ Path</button> <button data-add="path">+ Path</button>
<button id="tableBtn">Table…</button> <button id="tableBtn">Table…</button>
@@ -55,7 +68,14 @@ app.innerHTML = `${nav('layout')}
purple = ghost paths: drag the spheres, cone shows walk direction</div> purple = ghost paths: drag the spheres, cone shows walk direction</div>
</div> </div>
<div id="side"></div> <div id="side"></div>
</div>`; </div>
<div id="setModal"><div id="setCard">
<h2>Import Hidden Side set</h2>
<p class="hint">Adds a to-scale footprint (a building box) at the view centre. Drag it into place,
then set its height/yaw in the panel. Footprints in cm; not affiliated with the LEGO Group.</p>
<div id="setList"></div>
<button class="close">Close</button>
</div></div>`;
let scene = await api('/api/scene'); let scene = await api('/api/scene');
scene.anchors ||= []; scene.buildings ||= []; scene.spawns ||= []; scene.paths ||= []; scene.anchors ||= []; scene.buildings ||= []; scene.spawns ||= []; scene.paths ||= [];
@@ -274,6 +294,23 @@ document.querySelectorAll('[data-add]').forEach(b => b.onclick = () => {
buildAll(); panel(); buildAll(); panel();
}); });
// ---------- import set footprint ----------
const setModal = document.getElementById('setModal');
const setList = document.getElementById('setList');
setList.innerHTML = SET_FOOTPRINTS
.map((fp, i) => `<button data-fp="${i}">${footprintLabel(fp)}</button>`).join('');
document.getElementById('importSet').onclick = () => setModal.classList.add('open');
setModal.querySelector('.close').onclick = () => setModal.classList.remove('open');
setModal.onclick = e => { if (e.target === setModal) setModal.classList.remove('open'); };
setList.querySelectorAll('[data-fp]').forEach(b => b.onclick = () => {
const fp = SET_FOOTPRINTS[+b.dataset.fp];
const t = orbit.target;
scene.buildings.push(footprintToBuilding(fp, [t.x, 0, t.z]));
sel = { kind: 'building', index: scene.buildings.length - 1 };
setModal.classList.remove('open');
buildAll(); panel();
});
// ---------- property panel ---------- // ---------- property panel ----------
const side = document.getElementById('side'); const side = document.getElementById('side');
function field(label, val, oncommit, opts) { function field(label, val, oncommit, opts) {
@@ -340,6 +377,8 @@ function panel(rebuild = true) {
h += field('height', o.size[1], v => o.size[1] = v); h += field('height', o.size[1], v => o.size[1] = v);
h += field('depth', o.size[2], v => o.size[2] = v); h += field('depth', o.size[2], v => o.size[2] = v);
h += field('yaw °', o.yawDeg || 0, v => o.yawDeg = v, { step: 1 }); h += field('yaw °', o.yawDeg || 0, v => o.yawDeg = v, { step: 1 });
if (o.setNumber) h += `<p class="badge">Imported footprint · Hidden Side set <b>${o.setNumber}</b>.
Size is the published build; adjust height/yaw freely.</p>`;
} else if (sel.kind === 'pathpoint') { } else if (sel.kind === 'pathpoint') {
h = `<h2>path ${sel.index} · point ${sel.sub + 1}/${o.points.length}</h2>`; h = `<h2>path ${sel.index} · point ${sel.sub + 1}/${o.points.length}</h2>`;
h += textField('path id', o.id, v => o.id = v); h += textField('path id', o.id, v => o.id = v);