v0.7.2: fixes uit de code-review van v0.7.1: elektraleidingen nemen niet langer het valse plafond van de buurruimte door de wand heen over (gebied zonder functie = geen vals plafond) en meten begin/eind 50 mm de ruimte in, zodat de zigzag bij een overgang vals plafond/dak verdwijnt (Amstel 249,9 → 246,8 m, 16 van 68 leidingen strakker); elektra en schoorsteen snijden mee met de doorsnede (geen zwevende buizen); PDF en de plaatfase van de IFC bouwen geen buizen meer; schoorsteen in 2D en 3D op de laag Gevelbekleding; Esc sluit elke dialoog via Annuleren en sneltoetsen gaan niet meer naar de editor erachter; toets 3 sluit het rapport; menu en popovers sluiten elkaar (één uitklapper tegelijk); paneelkop ge-escaped; na slepen/pannen buiten de plattegrond wordt de volgende klik niet meer ingeslikt en blijft de cursor van het gereedschap; "Alles" in Lagen zet ook beplating binnen/buiten terug; laag uit met een selectie erin werkt het paneel bij; opgeruimd: gedeelde werkbalk (UI.editorBar) voor woning, wand en vloer, één knopbouwer (UI.btnOf), dichte paneelsecties pas opgebouwd bij openen, dode knopacties weg, binnenwand verwijderen op één plek, doorgestuurde acties uit één lijst, render-export "alleen afgewerkt" volledig in HouseModel (finishedOnly), structuurboom springt alleen bij een andere selectie
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ab0dca9bb5
commit
89cadbebf7
+17
-10
@@ -9,11 +9,9 @@
|
||||
// zodat wire() alleen de eigen knoppen koppelt.
|
||||
|
||||
window.Inspector = (() => {
|
||||
const icon = (n) => `<i class="bi bi-${n}" aria-hidden="true"></i>`;
|
||||
const big = (label, iconName, attrs, cls) =>
|
||||
`<button type="button" class="ins-btn ins-big${cls ? ' ' + cls : ''}" ${attrs || ''}>${icon(iconName)}<span>${label}</span></button>`;
|
||||
const small = (label, iconName, attrs, cls) =>
|
||||
`<button type="button" class="ins-btn${cls ? ' ' + cls : ''}" ${attrs || ''}>${icon(iconName)}<span>${label}</span></button>`;
|
||||
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>`;
|
||||
|
||||
@@ -32,9 +30,9 @@ window.Inspector = (() => {
|
||||
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 || ''}>${c.label}</button>`).join(icon('chevron-right'))}${icon('chevron-right')}</div>` : ''}
|
||||
<h2>${view.icon ? icon(view.icon) : ''}<span>${view.title}</span></h2>
|
||||
${view.sub ? `<div class="ins-sub">${view.sub}</div>` : ''}
|
||||
${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>`;
|
||||
@@ -45,16 +43,25 @@ window.Inspector = (() => {
|
||||
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">${groupsHtml(sec.groups ? sec.groups() : [], sec.label)}</div>`;
|
||||
'<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();
|
||||
});
|
||||
if (sec.wire) sec.wire(node);
|
||||
});
|
||||
el.scrollTop = keepScroll;
|
||||
}
|
||||
|
||||
+3
-2
@@ -23,13 +23,15 @@ window.Menubar = (() => {
|
||||
function close() {
|
||||
if (openIdx < 0) return false;
|
||||
openIdx = -1;
|
||||
UI.release(close);
|
||||
root.querySelectorAll('.mb-menu').forEach(m => m.classList.remove('open'));
|
||||
root.querySelectorAll('.mb-drop').forEach(d => { d.innerHTML = ''; });
|
||||
return true;
|
||||
}
|
||||
|
||||
function openMenu(i) {
|
||||
close();
|
||||
if (openIdx < 0) UI.claim(close);
|
||||
else { root.querySelectorAll('.mb-menu').forEach(m => m.classList.remove('open')); root.querySelectorAll('.mb-drop').forEach(d => { d.innerHTML = ''; }); }
|
||||
openIdx = i;
|
||||
const wrap = root.querySelectorAll('.mb-menu')[i];
|
||||
wrap.classList.add('open');
|
||||
@@ -54,7 +56,6 @@ window.Menubar = (() => {
|
||||
const top = e.target.closest('[data-menu]');
|
||||
if (top && openIdx >= 0 && Number(top.dataset.menu) !== openIdx) openMenu(Number(top.dataset.menu));
|
||||
});
|
||||
document.addEventListener('click', close);
|
||||
}
|
||||
|
||||
return { init, close };
|
||||
|
||||
+38
-13
@@ -80,8 +80,10 @@ window.UI = (() => {
|
||||
const info = (text) => `<div class="ui-info">${icon('info-circle')}<span>${text}</span></div>`;
|
||||
const warn = (text) => `<div class="ui-warn">${icon('exclamation-triangle')}<span>${text}</span></div>`;
|
||||
const grid = (...cols) => `<div class="ui-grid">${cols.map(c => `<div>${c}</div>`).join('')}</div>`;
|
||||
const button = (label, iconName, attrs, cls) =>
|
||||
`<button type="button" class="ui-btn${cls ? ' ' + cls : ''}" ${attrs || ''}>${iconName ? icon(iconName) : ''}<span>${label}</span></button>`;
|
||||
// Eén knopbouwer; base = de knopsoort (ui-btn, tb-btn, ins-btn …), cls = extra klassen.
|
||||
const btnOf = (base) => (label, iconName, attrs, cls) =>
|
||||
`<button type="button" class="${base}${cls ? ' ' + cls : ''}" ${attrs || ''}>${iconName ? icon(iconName) : ''}${label ? `<span>${label}</span>` : ''}</button>`;
|
||||
const button = btnOf('ui-btn');
|
||||
const actions = (...btns) => `<div class="ui-actions">${btns.join('')}</div>`;
|
||||
|
||||
const activeTabs = {};
|
||||
@@ -142,27 +144,50 @@ window.UI = (() => {
|
||||
el.querySelectorAll('[data-view]').forEach(b => b.classList.toggle('active', b.dataset.view === mode));
|
||||
}
|
||||
|
||||
const tb = (label, iconName, attrs, cls) =>
|
||||
`<button type="button" class="tb-btn${cls ? ' ' + cls : ''}" ${attrs || ''}>${iconName ? icon(iconName) : ''}${label ? `<span>${label}</span>` : ''}</button>`;
|
||||
const tb = btnOf('tb-btn');
|
||||
const tbSep = () => '<span class="tb-sep"></span>';
|
||||
const viewSeg = (mode) => `<div class="tb-seg" role="group" aria-label="Weergave">${VIEWS.map(v =>
|
||||
tb('', v.icon, `data-view="${v.key}" title="${v.label} (${v.key1})"`, v.key === mode ? 'active' : '')).join('')}</div>`;
|
||||
|
||||
// Uitklappaneel onder een knop; sluit bij klik buiten, Esc of een andere popover.
|
||||
// Werkbalk van een editor: [left] … [Lagen, Omgeving] [after] | [2D + 3D, 2D, 3D]. Gedeeld door woning, wand en vloer.
|
||||
// o: left/after (html), onResize, layers (opties voor ViewLayers.createMenu), onSky, beforeView(mode).
|
||||
function editorBar(toolbar, root, o) {
|
||||
toolbar.innerHTML = `${o.left || ''}<div class="tb-spacer"></div><div class="tb-group tb-view"></div>${o.after || ''}${tbSep()}${viewSeg(viewOf(root))}`;
|
||||
const group = toolbar.querySelector('.tb-view');
|
||||
if (o.layers) ViewLayers.createMenu(group, o.layers);
|
||||
if (o.onSky) Skybox.createToggle(group, o.onSky);
|
||||
const bar = {
|
||||
setView(mode) {
|
||||
if (o.beforeView) o.beforeView(mode);
|
||||
setView(root, mode);
|
||||
requestAnimationFrame(o.onResize);
|
||||
},
|
||||
viewState: () => ({ view: viewOf(root) })
|
||||
};
|
||||
toolbar.querySelectorAll('[data-view]').forEach(b => b.addEventListener('click', () => bar.setView(b.dataset.view)));
|
||||
return bar;
|
||||
}
|
||||
|
||||
// Eén uitklapper tegelijk (popover of menu): wie opent, sluit de vorige. claim bij openen, release bij sluiten.
|
||||
let openPop = null;
|
||||
function claim(close) { if (openPop && openPop !== close) openPop(); openPop = close; }
|
||||
function release(close) { if (openPop === close) openPop = null; }
|
||||
const closePopovers = () => { if (openPop) { openPop(); return true; } return false; };
|
||||
document.addEventListener('click', () => closePopovers());
|
||||
|
||||
// Uitklappaneel onder een knop; sluit bij klik buiten, Esc of een andere uitklapper.
|
||||
function popover(btn, panel) {
|
||||
const close = () => { panel.classList.remove('open'); btn.classList.remove('open'); if (openPop === close) openPop = null; };
|
||||
const close = () => { panel.classList.remove('open'); btn.classList.remove('open'); release(close); };
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const was = panel.classList.contains('open');
|
||||
if (openPop) openPop();
|
||||
if (!was) { panel.classList.add('open'); btn.classList.add('open'); openPop = close; }
|
||||
if (panel.classList.contains('open')) { close(); return; }
|
||||
claim(close);
|
||||
panel.classList.add('open');
|
||||
btn.classList.add('open');
|
||||
});
|
||||
panel.addEventListener('click', (e) => e.stopPropagation());
|
||||
return close;
|
||||
}
|
||||
const closePopovers = () => { if (openPop) { openPop(); return true; } return false; };
|
||||
document.addEventListener('click', () => closePopovers());
|
||||
|
||||
// Kaarten verdelen over kolommen van min. minW breed; telkens in de kortste kolom. Opnieuw aanroepen bij een andere breedte.
|
||||
function masonry(el, minW, gap) {
|
||||
@@ -178,6 +203,6 @@ window.UI = (() => {
|
||||
cards.forEach(card => cols.reduce((a, b) => b.offsetHeight < a.offsetHeight ? b : a).appendChild(card));
|
||||
}
|
||||
|
||||
return { esc, icon, hint, num, range, onRange, setRange, sel, toggle, color, onColor, text, onText, section, info, warn, grid, button, actions, tabs, wireTabs, specCard, onNum, onSel, onToggle,
|
||||
VIEWS, viewOf, setView, tb, tbSep, viewSeg, popover, closePopovers, masonry };
|
||||
return { esc, icon, hint, num, range, onRange, setRange, sel, toggle, color, onColor, text, onText, section, info, warn, grid, button, actions, tabs, wireTabs, specCard, onNum, onSel, onToggle, btnOf,
|
||||
VIEWS, viewOf, setView, tb, tbSep, viewSeg, editorBar, popover, claim, release, closePopovers, masonry };
|
||||
})();
|
||||
|
||||
+10
-3
@@ -20,7 +20,7 @@ window.ViewLayers = (() => {
|
||||
{ key: 'lampen', label: 'Verlichting', icon: 'lightbulb', phase: 6, hint: 'Gevellampen, spots en binnenverlichting' },
|
||||
{ key: 'inrichting', label: 'Inrichting', icon: 'lamp', phase: 6, hint: 'Sanitair, keuken, kasten, bed, zithoek' },
|
||||
{ key: 'ruimtes', label: 'Ruimtes', icon: 'bounding-box', phase: 6, hint: 'Ruimtes met functie, vloer en wandpanelen' },
|
||||
{ key: 'dak', label: 'Dak', icon: 'house', hint: 'Het hele dak met dakrand, overkapping, palen en schoorsteen; uit om van boven in de woning te kijken' },
|
||||
{ key: 'dak', label: 'Dak', icon: 'house', hint: 'Het hele dak met dakrand, overkapping en palen; uit om van boven in de woning te kijken' },
|
||||
{ key: 'maten', label: 'Maten', icon: 'rulers', hint: 'Maatvoering in de plattegrond' }
|
||||
];
|
||||
const KEYS = LIST.map(l => l.key);
|
||||
@@ -98,10 +98,17 @@ window.ViewLayers = (() => {
|
||||
emit();
|
||||
}
|
||||
});
|
||||
// Alles = ook de editor-eigen schakelaars weer aan.
|
||||
const all = panel.querySelector('[data-all]');
|
||||
if (all) all.addEventListener('click', () => { cur = ViewLayers.all(); emit(); });
|
||||
if (all) all.addEventListener('click', () => {
|
||||
const extraOff = extras.some(x => !extra[x.key]);
|
||||
extras.forEach(x => { extra[x.key] = true; });
|
||||
cur = ViewLayers.all();
|
||||
emit();
|
||||
if (extraOff && o.onExtra) o.onExtra(Object.assign({}, extra));
|
||||
});
|
||||
sync();
|
||||
return { el: wrap, set: (l) => { cur = normalize(l); sync(); }, toggle: (key) => { cur = Object.assign({}, cur, { [key]: !cur[key] }); emit(); } };
|
||||
return { el: wrap, set: (l) => { cur = normalize(l); sync(); } };
|
||||
}
|
||||
|
||||
return { LIST, KEYS, PHASES, GROUPS, all, normalize, shows, upTo, phaseOf, hideByMaterial, createMenu };
|
||||
|
||||
Reference in New Issue
Block a user