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>
This commit is contained in:
1 parent
40befda905
commit
aaa0972bc1
18 files changed
+2163
No files matched your search
@@ -0,0 +1,78 @@
|
||||
import type { RefObject } from "react"
|
||||
import { ClipboardListIcon, MousePointerClickIcon, ZoomInIcon } from "lucide-react"
|
||||
import { Badge, Button, ScrollArea, Table, TableBody, TableCell, TableHead, TableRow } from "@hm-design/ui"
|
||||
import { formatEntityProperties } from "../engine/propertyInspector"
|
||||
import { FloatingPanel } from "./FloatingPanel"
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
anchorRef: RefObject<HTMLElement | null>
|
||||
/** Entity from Viewer2D.onSelect (null = nothing selected). */
|
||||
entity: unknown
|
||||
onZoomToSelection: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties window. Formatting (labels, units, per-type geometry) is the ported formatEntityProperties
|
||||
* (src/engine/propertyInspector.ts, unchanged); only the presentation is HM (Badge + Table).
|
||||
*/
|
||||
export function PropertiesPanel({ open, onClose, anchorRef, entity, onZoomToSelection }: Props) {
|
||||
const prop = entity ? formatEntityProperties(entity) : null
|
||||
|
||||
return (
|
||||
<FloatingPanel
|
||||
title="속성"
|
||||
icon={<ClipboardListIcon className="size-4" />}
|
||||
actions={
|
||||
prop ? (
|
||||
<Button type="button" variant="primary" appearance="outline" size="sm" onClick={onZoomToSelection}>
|
||||
<ZoomInIcon aria-hidden="true" />
|
||||
선택 확대
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
slot={0}
|
||||
anchorRef={anchorRef}
|
||||
>
|
||||
{!prop ? (
|
||||
<div className="viewer-empty-note">
|
||||
<MousePointerClickIcon aria-hidden="true" className="mx-auto mb-2 size-6" />
|
||||
도면에서 객체(엔티티)를 클릭하면
|
||||
<br />
|
||||
상세 속성이 표시됩니다
|
||||
</div>
|
||||
) : (
|
||||
<ScrollArea className="viewer-window-scroll">
|
||||
<div className="viewer-window-section grid gap-4">
|
||||
<Badge className="viewer-prop-id">{prop.id}</Badge>
|
||||
<PropertyTable caption="일반 정보 (General)" rows={prop.general} />
|
||||
{prop.geometry.length > 0 ? <PropertyTable caption="기하 속성 (Geometry)" rows={prop.geometry} /> : null}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</FloatingPanel>
|
||||
)
|
||||
}
|
||||
|
||||
function PropertyTable({ caption, rows }: { caption: string; rows: [string, string][] }) {
|
||||
return (
|
||||
<section className="grid gap-1.5">
|
||||
<h3 className="viewer-section-title">{caption}</h3>
|
||||
<Table aria-label={caption} className="viewer-prop-table">
|
||||
<TableBody>
|
||||
{rows.map(([k, v], i) => (
|
||||
<TableRow key={`${k}-${i}`}>
|
||||
<TableHead scope="row" className="viewer-prop-key">
|
||||
{k}
|
||||
</TableHead>
|
||||
<TableCell className="viewer-prop-value">{v}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user