Files
dwg-dxf-viewer-hm/src/components/StageOverlays.tsx
T
minsungandClaude Opus 5.5 aaa0972bc1 feat(ui): rebuild the viewer UI with HMDesign components
New React UI around the unchanged engine (#1). GNB_ONLY page:

- AppHeader: GNB + GNBBrand; sample drawings in GNBNav (NavigationMenu)
  and GNBMobileMenu (same data, real ?model= links); file open and
  about/notice in GNBUtilities.
- DwgViewerCanvas: useRef/useEffect wrapper. new Viewer2D(host) on
  mount, option props diffed and pushed through the engine setters,
  onSelect/onPointerWorld/onViewChange forwarded to React. Viewer2D has
  no dispose(), so teardown stops the rAF loop / WebGL context from
  outside (also needed for StrictMode double mount).
- ViewerToolbar: Model/Layout ToggleGroup (sample's visibility rule),
  Fit, 2.5D and canvas-theme Toggles, window Toggles.
- FloatingPanel: HM Card in a fixed frame; drag/collapse via the ported
  makePanelDraggableAndCollapsible, CSS resize.
- LayersPanel (FilterBar search, Switch per layer, show/hide all via
  LayerPanelManager), PropertiesPanel (formatEntityProperties → Badge +
  Table, zoom to selection), OptionsPanel (Switch/Slider/Input, IndexedDB
  through optionsStore, reset).
- Load feedback per HMDesign: Progress for the measurable part
  (download bytes, stage steps) and LoadingScreen for the blocking
  WASM parse / Viewer2D.load(), painted before each blocking stage.
- StatusBar with status line, zoom %, live CAD coordinates in the
  bottom-right corner; AboutDialog keeps the acadrust MPL-2.0 notice,
  THIRD-PARTY-NOTICES and the local patch reachable.
- Drag & drop, ?model= auto-load, F shortcut, ResizeObserver resync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:49:33 +09:00

80 lines
3.1 KiB
TypeScript
Raw 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.
import { FileUpIcon, FolderOpenIcon } from "lucide-react"
import { Button, Card, CardContent, CardHeader, CardTitle, LoadingScreen, Progress } from "@hm-design/ui"
import { SAMPLE_DRAWINGS, type SampleDrawing } from "../lib/samples"
/** Shown on the empty canvas: the page's single brand action (open a file) plus the bundled samples. */
export function EmptyState({ onOpenFile, onSample }: { onOpenFile: () => void; onSample: (s: SampleDrawing) => void }) {
return (
<div className="viewer-stage-center">
<Card className="viewer-empty-card">
<CardHeader>
<CardTitle>도면을 열어 주세요</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<p className="text-muted-foreground">
DWG · DXF 파일을 선택하거나 이 화면에 끌어다 놓습니다. 주소에 <code>?model=경로</code>를 붙이면 자동으로 엽니다.
</p>
<Button type="button" size="lg" onClick={onOpenFile}>
<FolderOpenIcon aria-hidden="true" />
파일 열기
</Button>
<div className="grid gap-2">
<p className="viewer-section-title">샘플 도면</p>
<div className="flex flex-wrap gap-2">
{SAMPLE_DRAWINGS.map((s) => (
<Button key={s.id} type="button" variant="primary" appearance="outline" size="sm" title={s.hint} onClick={() => onSample(s)}>
{s.label}
</Button>
))}
</div>
</div>
</CardContent>
</Card>
</div>
)
}
export type LoadState = {
/** Text for the LoadingScreen label and the status line. */
label: string
/** 0–100 when measurable, null when not (no Content-Length). */
value: number | null
/** A stage is running (LoadingScreen shown); false only for the brief "done" frame of the Progress bar. */
busy: boolean
}
/**
* Load feedback, following HMDesign's split (loading-screen.md §2):
* - Progress (top edge of the canvas) — the measurable part: download bytes, then stage steps.
* - LoadingScreen (centre) — the unmeasurable part. WASM parsing and Viewer2D.load() block the main
* thread; LoadingScreen's spinner is a CSS opacity keyframe animation the compositor can keep running.
*/
export function LoadingOverlay({ state }: { state: LoadState }) {
return (
<>
{state.value != null ? (
<div className="viewer-stage-progress">
<Progress value={state.value} aria-label="도면 불러오기 진행률" />
</div>
) : null}
{state.busy ? (
<div className="viewer-stage-center viewer-stage-busy">
<LoadingScreen className="viewer-loading">{state.label}</LoadingScreen>
</div>
) : null}
</>
)
}
/** Drop target hint while a file is dragged over the window. */
export function DropOverlay() {
return (
<div className="viewer-stage-center viewer-drop" aria-hidden="true">
<div className="viewer-drop-box">
<FileUpIcon className="size-8" />
<span>여기에 놓아 도면 열기 (.dwg / .dxf)</span>
</div>
</div>
)
}