feat: Entity 선택기능, Property Inspector 패널, Layer 관리 패널, 드래그/접기/크기조절 UI 구현
This commit is contained in:
+60
-26
@@ -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());
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user