/** * Component-editor for sonic-diamond. * * Modes: * - Matrix (default): grid of all valid preset combinations. Click → Single. * - Single: one component instance with dropdown/checkbox controls and * a checker-pattern preview frame. Diamond has no per-variant tuning, * so this mode here only demonstrates the prop-switching flow. * * Mode is driven by the URL hash: * '' → Matrix * 'shape=...&color=...&chipped=...' → Single * * "Copy snapshot" works in either mode and produces JS source ready to * paste between `@editor:anchors-*` and `@editor:variants-*` markers. */ await customElements.whenDefined('sonic-diamond'); const Comp = customElements.get('sonic-diamond'); const { presetProps, chippedShapes } = Comp; document.getElementById('meta').textContent = `${Object.keys(Comp.variants).length} variants · baseSize ${Comp.baseSize.w}×${Comp.baseSize.h}mm · origin (${Comp.origin.x}, ${Comp.origin.y})`; initMatrix(); initSingle(); window.addEventListener('hashchange', route); route(); document.getElementById('btn-copy').addEventListener('click', async () => { const text = serializeSnapshot(Comp.anchorsDefault, Comp.variants); await navigator.clipboard.writeText(text); showToast('Snapshot copied'); }); // ---- Routing ---- function route() { const params = new URLSearchParams(location.hash.slice(1)); const matrixView = document.getElementById('matrix-view'); const singleView = document.getElementById('single-view'); if (params.has('shape')) { matrixView.hidden = true; singleView.hidden = false; applySingle(params); } else { matrixView.hidden = false; singleView.hidden = true; } } function setHashParams(updates) { const params = new URLSearchParams(location.hash.slice(1)); for (const [k, v] of Object.entries(updates)) params.set(k, String(v)); location.hash = params.toString(); } // ---- Matrix mode ---- function initMatrix() { const root = document.getElementById('matrix-view'); renderGrid(root, 'Full', false); renderGrid(root, 'Chipped', true); } function renderGrid(root, title, chipped) { const shapes = chipped ? presetProps.shape.filter(s => chippedShapes.has(s)) : presetProps.shape; const colors = presetProps.color; const section = document.createElement('section'); const h2 = document.createElement('h2'); h2.textContent = `${title} — ${shapes.length} × ${colors.length}`; section.appendChild(h2); const grid = document.createElement('div'); grid.className = 'grid'; grid.style.gridTemplateColumns = `repeat(${colors.length}, 1fr)`; for (const shape of shapes) { for (const color of colors) { grid.appendChild(makeCell(shape, color, chipped)); } } section.appendChild(grid); root.appendChild(section); } function makeCell(shape, color, chipped) { const cell = document.createElement('div'); cell.className = 'cell'; cell.title = 'Open in single mode'; cell.addEventListener('click', () => { location.hash = `shape=${shape}&color=${color}&chipped=${chipped}`; }); const preview = document.createElement('div'); preview.className = 'preview'; const inst = document.createElement('sonic-diamond'); inst.setAttribute('shape', shape); inst.setAttribute('color', color); inst.setAttribute('chipped', String(chipped)); preview.appendChild(inst); const label = document.createElement('div'); label.className = 'label'; label.innerHTML = `
${shape} · ${color}
`; const key = document.createElement('div'); key.className = 'key'; key.textContent = inst._variantKey(); cell.appendChild(preview); cell.appendChild(label); cell.appendChild(key); return cell; } // ---- Single mode ---- let singleInstance = null; function initSingle() { fillSelect('ctrl-shape', presetProps.shape); fillSelect('ctrl-color', presetProps.color); document.getElementById('ctrl-shape').addEventListener('change', e => setHashParams({ shape: e.target.value }) ); document.getElementById('ctrl-color').addEventListener('change', e => setHashParams({ color: e.target.value }) ); document.getElementById('ctrl-chipped').addEventListener('change', e => setHashParams({ chipped: e.target.checked }) ); document.getElementById('btn-back').addEventListener('click', () => { location.hash = ''; }); } function applySingle(params) { if (!singleInstance) { singleInstance = document.createElement('sonic-diamond'); document.getElementById('single-frame').appendChild(singleInstance); } const shape = params.get('shape') ?? presetProps.shape[0]; const color = params.get('color') ?? presetProps.color[0]; const chipped = params.get('chipped') === 'true'; singleInstance.setAttribute('shape', shape); singleInstance.setAttribute('color', color); singleInstance.setAttribute('chipped', String(chipped)); document.getElementById('ctrl-shape').value = shape; document.getElementById('ctrl-color').value = color; const cb = document.getElementById('ctrl-chipped'); const cbLabel = document.getElementById('ctrl-chipped-label'); const supportsChipped = chippedShapes.has(shape); cb.checked = chipped && supportsChipped; cb.disabled = !supportsChipped; cbLabel.textContent = supportsChipped ? 'Chipped' : 'Chipped (n/a for this shape)'; const key = singleInstance._variantKey(); document.getElementById('info-variant').textContent = key; document.getElementById('info-basesize').textContent = `${Comp.baseSize.w}×${Comp.baseSize.h}mm`; document.getElementById('info-origin').textContent = `(${Comp.origin.x}, ${Comp.origin.y})`; document.getElementById('info-img').textContent = Comp.variants[key]?.img ?? '—'; } function fillSelect(id, options) { const sel = document.getElementById(id); sel.innerHTML = ''; for (const opt of options) { const o = document.createElement('option'); o.value = opt; o.textContent = opt; sel.appendChild(o); } } // ---- Snapshot serialization ---- function serializeSnapshot(anchorsDefault, variants) { const lines = []; lines.push(' // @editor:anchors-start'); lines.push(` static anchorsDefault = ${formatAnchors(anchorsDefault, ' ')};`); lines.push(' // @editor:anchors-end'); lines.push(''); lines.push(' // @editor:variants-start'); lines.push(' static variants = {'); for (const [key, v] of Object.entries(variants)) { const a = formatAnchorsInline(v.anchors || {}); lines.push(` ${key}: { img: ${JSON.stringify(v.img)}, dx: ${v.dx}, dy: ${v.dy}, scale: ${v.scale}, anchors: ${a} },`); } lines.push(' };'); lines.push(' // @editor:variants-end'); return lines.join('\n'); } function formatAnchors(anchors, indent) { const keys = Object.keys(anchors); if (keys.length === 0) return '{}'; const inner = keys.map(k => `${indent} ${k}: { x: ${anchors[k].x}, y: ${anchors[k].y} }`).join(',\n'); return `{\n${inner},\n${indent}}`; } function formatAnchorsInline(anchors) { const keys = Object.keys(anchors); if (keys.length === 0) return '{}'; const parts = keys.map(k => `${k}: { x: ${anchors[k].x}, y: ${anchors[k].y} }`); return `{ ${parts.join(', ')} }`; } function showToast(msg) { const toast = document.getElementById('toast'); toast.textContent = msg; toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 1500); }