feat: Entity 선택기능, Property Inspector 패널, Layer 관리 패널, 드래그/접기/크기조절 UI 구현

This commit is contained in:
minsung
2026-07-31 13:38:14 +09:00
parent f7564d4abf
commit 199317d575
7 changed files with 846 additions and 27 deletions
+60 -26
View File
@@ -26,6 +26,9 @@ import { parseDxfBuffer } from './viewer2d/parseDxf';
import { is2D, isDxf, isDwg } from './viewer2d/ext2d';
import { listCadSpaces, pickDefaultSpace } from './viewer2d/cadSpaces';
import { setDefaultFontUrl } from './viewer2d/slugText';
import { renderPropertyInspector } from './propertyInspector';
import { LayerPanelManager } from './layerPanel';
import { makePanelDraggableAndCollapsible } from './panelUtils';
// parseDxfBuffer 내부:
// dxf-parser → HatchHandler → dxfAdapter
@@ -38,21 +41,58 @@ setDefaultFontUrl('/fonts/NanumGothic-Regular.ttf');
// ── ③ Viewer2D 마운트 ───────────────────────────────────────────
const app = document.getElementById('app')!;
const statusEl = document.getElementById('status')!;
const layerList = document.getElementById('layerList')!;
const layersPanel = document.getElementById('layers')!;
const spacesBar = document.getElementById('spaces')!;
// Layer panel elements
const layersPanel = document.getElementById('layers')!;
const layerList = document.getElementById('layerList')!;
const layerCount = document.getElementById('layerCount')!;
const layerSearch = document.getElementById('layerSearch') as HTMLInputElement;
const layerShowAll = document.getElementById('layerShowAll')!;
const layerHideAll = document.getElementById('layerHideAll')!;
const closeLayersBtn = document.getElementById('closeLayersBtn')!;
const collapseLayersBtn = document.getElementById('collapseLayersBtn')!;
// Properties panel elements
const propsPanel = document.getElementById('properties')!;
const propBody = document.getElementById('propBody')!;
const propsBtn = document.getElementById('propsBtn')!;
const zoomSelBtn = document.getElementById('zoomSelBtn')!;
const closePropsBtn = document.getElementById('closePropsBtn')!;
const collapsePropsBtn = document.getElementById('collapsePropsBtn')!;
function setStatus(msg: string, err = false) {
statusEl.textContent = msg;
statusEl.classList.toggle('err', err);
}
// Make floating UI panels draggable & collapsible
makePanelDraggableAndCollapsible(propsPanel, { collapseBtnEl: collapsePropsBtn });
makePanelDraggableAndCollapsible(layersPanel, { collapseBtnEl: collapseLayersBtn });
// Initial Property Inspector rendering (empty state)
renderPropertyInspector(propBody, null, { zoomBtnEl: zoomSelBtn });
const viewer = new Viewer2D(app);
// Layer Panel Manager
const layerManager = new LayerPanelManager(viewer, layerList, {
countEl: layerCount,
searchInputEl: layerSearch,
showAllBtnEl: layerShowAll,
hideAllBtnEl: layerHideAll,
});
viewer.onSelect((entity: unknown) => {
if (!entity) {
setStatus('deselected');
renderPropertyInspector(propBody, null, { zoomBtnEl: zoomSelBtn });
return;
}
// Show properties panel automatically on selection
propsPanel.style.display = 'flex';
renderPropertyInspector(propBody, entity, { zoomBtnEl: zoomSelBtn });
const e = entity as { type?: string; layer?: string; handle?: { value?: string } };
setStatus(`select ${e.type ?? '?'} · layer=${e.layer ?? '-'} · h=${e.handle?.value ?? '-'}`);
});
@@ -139,29 +179,7 @@ async function loadFile(file: File) {
// ── ⑤ 호스트 UI 글루 ────────────────────────────────────────────
function fillLayers() {
layerList.innerHTML = '';
for (const L of viewer.getLayerInfo()) {
const label = document.createElement('label');
label.dataset.name = L.name;
const sw = document.createElement('span');
sw.className = 'swatch';
sw.style.background = L.colorHex || '#888';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = L.visible;
cb.addEventListener('change', () => {
const next = new Set<string>();
layerList.querySelectorAll('label').forEach((row) => {
const input = row.querySelector('input') as HTMLInputElement;
if (!input.checked) next.add(row.dataset.name!);
});
viewer.setHiddenLayers(next);
});
const text = document.createElement('span');
text.textContent = `${L.name} (${L.count})`;
label.append(sw, cb, text);
layerList.append(label);
}
layerManager.update();
}
async function afterLoad(name: string, result: CadParseResult) {
@@ -212,7 +230,22 @@ document.getElementById('theme')!.addEventListener('click', () => {
fillLayers();
});
document.getElementById('layersBtn')!.addEventListener('click', () => {
layersPanel.style.display = layersPanel.style.display === 'block' ? 'none' : 'block';
layersPanel.style.display = layersPanel.style.display === 'flex' ? 'none' : 'flex';
});
closeLayersBtn.addEventListener('click', () => {
layersPanel.style.display = 'none';
});
propsBtn.addEventListener('click', () => {
propsPanel.style.display = propsPanel.style.display === 'none' ? 'flex' : 'none';
});
zoomSelBtn.addEventListener('click', () => {
viewer.zoomToSelection();
});
closePropsBtn.addEventListener('click', () => {
propsPanel.style.display = 'none';
});
window.addEventListener('keydown', (e) => {
@@ -237,3 +270,4 @@ if (model) {
window.addEventListener('resize', () => viewer.resize());