/**
* 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);
}