HMDesign 적용 작업 기록

dwg-dxf-viewer-hm · 2026-10-12 · Gitea 이슈 #2 hmdesign 적용 / #1 디자인팀 디자인 시스템 적용

개요

기존 vanilla TS+Vite+Three.js DWG/DXF 뷰어(dwg-dxf-viewer-sample)를 건드리지 않고, 이 저장소(dwg-dxf-viewer-hm)에 React 19 + HMDesign 디자인 시스템 기반으로 UI/UX를 새로 구성했다. 뷰어 렌더링 엔진(Viewer2D.js + 파서들)은 로직을 그대로 포팅하고, 그 위의 모든 UI(툴바·레이어/속성/옵션 패널·좌표 표시·라이선스 팝업·로딩바)는 HMDesign 컴포넌트로 새로 디자인했다. 기존 UI의 색/아이콘/레이아웃은 가져오지 않았고, 기능만 1:1 유지하는 것을 목표로 했다.

작업 흐름

Gitea 퍼블리시 — dwg-dxf-viewer-hm을 public repo로 생성, 로컬 git init 후 최초 push.
이슈 #1 생성 — "디자인팀 디자인 시스템 적용": 앞으로 구현되는 UI/UX 작업을 커밋으로 연결.
포팅 계획 확정 — 현재 디렉토리 재사용 / HMDesign은 git submodule+npm workspace로 연결 / Measure 기능 비노출 / 로딩 progress bar는 HM 추천 방식으로 신규 연결, 3가지+1가지 질문에 대한 사용자 답변을 바탕으로 계획 수립 후 승인.
1차 구현 커밋 — submodule 추가, 워크스페이스 연결, src/viewer2d/* 포팅, React 컴포넌트 전체 신규 작성.
Guard 수정 커밋 — ai-generation-guard.mjs REUSE-004 지적(네이티브 <label>)을 HM Label로 교체.
버그 수정 커밋 — 샘플 로드 후 캔버스에 아무것도 안 보이는 문제 원인 확정 및 수정 (아래 "발견된 버그" 참고).
이슈 #2 생성 — "hmdesign 적용", 이 리포트 작성 및 커밋.

아키텍처: HMDesign 연결

@hm-design/ui는 npm 레지스트리에 배포되지 않고 HMDesign 저장소 자신의 npm workspace (packages/*) 안에서만 resolve된다. exports는 전부 dist/*를 가리키므로 반드시 빌드가 먼저 끝나 있어야 한다.

git submodule add https://gitea.hmac.kr/b25027/HMDesign.git HMDesign
git submodule update --init --recursive
cd HMDesign && npm ci && npm run build:packages && cd ..   # HMDesign 자체 devDependency로 dist/ 생성
npm install        # 루트 workspaces(["HMDesign/packages/ui"])가 symlink 연결
npm run dev

HMDesign 내부 빌드는 HMDesign 자신의 node_modules(Storybook/Tailwind/TS7 등)로 수행하고, 외부 앱(이 저장소)은 빌드된 결과물만 workspace symlink로 소비한다 — 두 트리의 의존성 버전이 섞이지 않는다. @hm-design/center-graphic은 라이선스 미확정으로 사용하지 않았다.

Vite 설정에서 optimizeDeps.entries: ['index.html']을 명시해야 한다 — 지정하지 않으면 Vite의 의존성 스캐너가 submodule(HMDesign) 안의 다른 예제용 *.html까지 크롤링하다 해석할 수 없는 @/... alias에서 죽는다.

구현 내용

디렉토리 구조

src/
├── main.tsx, App.tsx, index.css
├── viewer2d/            ← 기존 엔진 전체, 로직 불변, import 경로만 조정
├── lib/
│   ├── optionsStore.ts  ← IndexedDB 영속, 그대로 포팅
│   └── propertyFormat.ts← 속성 포맷터만 추출 포팅 (DOM 렌더는 React로 대체)
├── components/          ← Toolbar / LayerPanel / PropertiesPanel / OptionsPanel /
│                           DraggablePanel / CoordReadout / LicenseNotice / LoadProgress
└── hooks/
    ├── useDwgViewer.ts      ← Viewer2D 인스턴스 소유 + 파일 로드 파이프라인 + 옵션 동기화
    └── useDraggablePanel.ts ← panelUtils.ts 드래그/collapse 로직의 React 훅 포팅

UI → HMDesign 컴포넌트 매핑 (요약)

원본 UIHMDesign 매핑
Open file / 샘플 3버튼Button (+숨김 <input type=file>, HM에 대응 컴포넌트 없음)
Model/Layout 탭Tabs
2.5D·테마 토글Switch + Label
레이어/옵션/속성 패널 열기Toggle
레이어 검색/전체켜기끄기FilterBar / FilterBarSearch / FilterBarActions
레이어 행(색상/개수/가시성)커스텀 — color swatch(동적 color), Badge, lucide Eye/EyeOff
패널 자체(드래그/접기/리사이즈)DraggablePanel 커스텀(HM Card 기반 + 포인터 드래그 훅, resize는 네이티브 CSS)
슬라이더+숫자입력 3종Slider + Input
라이선스/크레딧Dialog (THIRD-PARTY-NOTICES / MPL-2.0 / patch 링크 3종 유지)
상단 로딩바Progress (원본은 미연결 죽은 코드였음 — HM 추천 방식으로 신규 실연결)

Navigation(GNB/LNB/Dashboard)은 채용하지 않았다 — 멀티페이지 앱이 아닌 전체화면 캔버스형 단일 도구이기 때문.

발견된 버그 및 수정

증상: 샘플 DWG/DXF 3개 버튼 모두 "로드완료" 상태 텍스트는 뜨지만 캔버스에 아무것도 렌더되지 않음.

원인 확정: Viewer2D(포팅된 vanilla 엔진)는 dispose/teardown API가 없고 생성자에서 window에 영구 리스너를 걸고 자체 requestAnimationFrame 루프를 시작한다. React <StrictMode>의 개발 모드 mount→unmount→remount 더블 호출이 Viewer2D를 같은 container에 두 번 생성했고, 첫 인스턴스를 정리할 방법이 없어 캔버스가 중첩/렌더가 꼬였다.

수정 완료·미검증: main.tsx에서 <StrictMode> 제거, useDwgViewer 마운트 effect에서 construction 전에 container.replaceChildren()으로 방어적 클리어 추가. 코드 수정은 했으나 이 세션엔 브라우저 도구가 없어 실제 화면에서 재확인은 못 함 — 사용자 브라우저에서 재검증 필요.

검증 상태

다음 할 일