Files

71 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*!
* Tectum
* Copyright © 2026 Machiel Heinen.
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
* Zie LICENTIE.md.
*/
// Eigenschappenpaneel rechts: eigenschappen van de selectie, of van de woning als er niets geselecteerd is.
// Een sectie = { key, label, icon, groups() → [{ label, html }], wire(el) }; elke sectie krijgt een eigen element,
// zodat wire() alleen de eigen knoppen koppelt.
window.Inspector = (() => {
const { icon, esc } = UI;
const big = UI.btnOf('ins-btn ins-big');
const small = UI.btnOf('ins-btn');
const kv = (label, value, unit) =>
`<div class="ins-kv"><span>${label}</span><b>${value}${unit ? `<small>${unit}</small>` : ''}</b></div>`;
const PREF_KEY = 'tectum.inspector.open';
let open;
try { open = new Set(JSON.parse(localStorage.getItem(PREF_KEY) || 'null') || ['gevel']); } catch (e) { open = new Set(['gevel']); }
const saveOpen = () => { try { localStorage.setItem(PREF_KEY, JSON.stringify([...open])); } catch (e) { } };
// Een groepskop gelijk aan de sectienaam (bijv. Elektra › Elektra) vervalt.
const groupsHtml = (groups, secLabel) => groups.filter(Boolean).map(g =>
`<div class="ins-group${g.cls ? ' ' + g.cls : ''}">${g.label && g.label.toLowerCase() !== String(secLabel || '').toLowerCase() ? `<h3>${g.label}</h3>` : ''}<div class="ins-group-body">${g.html}</div></div>`).join('');
// view = { crumbs: [{ label, attrs }], title, icon, sub, top: html, sections: [...], collapsible, onBack }
function render(el, view) {
const scrollKey = view.title + '|' + (view.sub || '');
const keepScroll = el._insKey === scrollKey ? el.scrollTop : 0;
el._insKey = scrollKey;
el.innerHTML = `<div class="ins-head">
${view.crumbs && view.crumbs.length ? `<div class="ins-crumbs">${view.crumbs.map(c => `<button type="button" class="ins-crumb" ${c.attrs || ''}>${esc(c.label)}</button>`).join(icon('chevron-right'))}${icon('chevron-right')}</div>` : ''}
<h2>${view.icon ? icon(view.icon) : ''}<span>${esc(view.title)}</span></h2>
${view.sub ? `<div class="ins-sub">${esc(view.sub)}</div>` : ''}
</div>
${view.top ? `<div class="ins-top">${view.top}</div>` : ''}
<div class="ins-sections"></div>`;
const host = el.querySelector('.ins-sections');
view.sections.filter(Boolean).forEach(sec => {
const node = document.createElement('section');
const collapsible = view.collapsible;
const isOpen = !collapsible || open.has(sec.key);
node.className = 'ins-section' + (collapsible ? ' ins-collapsible' : '') + (isOpen ? ' open' : '');
node.innerHTML = (collapsible ? `<button type="button" class="ins-section-head" aria-expanded="${isOpen}">${sec.icon ? icon(sec.icon) : ''}<span>${sec.label}</span>${icon('chevron-down')}</button>` : '') +
'<div class="ins-section-body"></div>';
host.appendChild(node);
// Een dichte sectie wordt pas opgebouwd (en gekoppeld) als hij opengaat.
let built = false;
const build = () => {
if (built) return;
built = true;
node.querySelector('.ins-section-body').innerHTML = groupsHtml(sec.groups ? sec.groups() : [], sec.label);
if (sec.wire) sec.wire(node);
};
if (isOpen) build();
if (collapsible) node.querySelector('.ins-section-head').addEventListener('click', () => {
const now = !node.classList.contains('open');
if (now) build();
node.classList.toggle('open', now);
node.querySelector('.ins-section-head').setAttribute('aria-expanded', now);
if (now) open.add(sec.key); else open.delete(sec.key);
saveOpen();
});
});
el.scrollTop = keepScroll;
}
return { big, small, kv, render };
})();