feat: React19+HMDesign shell with ported DWG/DXF viewer engine
Scaffolds the new app (Vite+React19+Tailwind v4) on HMDesign via git submodule + npm workspace, ports src/viewer2d/* and optionsStore.ts unchanged, and rebuilds the vanilla UI (toolbar/layers/properties/ options panels, coord readout, license dialog, load progress) as HM Design System components. Panel drag/collapse ported from panelUtils.ts as a React hook; Measure UI intentionally left unwired per original app parity. Typecheck and dev-server module serving verified; in-browser rendering not yet verified in this session (no browser tool available) — needs manual check. Refs #1
This commit is contained in:
1 parent
1fd0b00df3
commit
b114581c07
54 files changed
+16494
No files matched your search
@@ -0,0 +1,122 @@
|
||||
import { useRef } from 'react';
|
||||
import { Button, Switch, Tabs, TabsList, TabsTrigger, Badge, Toggle } from '@hm-design/ui';
|
||||
import { FolderOpen, Scan, Layers, SlidersHorizontal, Info as InfoIcon } from 'lucide-react';
|
||||
import type { CadSpace } from '../viewer2d/cadSpaces';
|
||||
import { LicenseNotice } from './LicenseNotice';
|
||||
|
||||
export interface ToolbarProps {
|
||||
status: string;
|
||||
statusError: boolean;
|
||||
spaces: CadSpace[];
|
||||
activeSpace: string | null;
|
||||
onSelectSpace: (handleHex: string | null) => void;
|
||||
onOpenFile: (file: File) => void;
|
||||
onLoadSample: (url: string, label: string) => void;
|
||||
onFit: () => void;
|
||||
is25D: boolean;
|
||||
onToggle25D: (on: boolean) => void;
|
||||
canvasDark: boolean;
|
||||
onToggleTheme: () => void;
|
||||
layersOpen: boolean;
|
||||
onToggleLayers: () => void;
|
||||
optionsOpen: boolean;
|
||||
onToggleOptions: () => void;
|
||||
propsOpen: boolean;
|
||||
onToggleProps: () => void;
|
||||
}
|
||||
|
||||
export function Toolbar({
|
||||
status,
|
||||
statusError,
|
||||
spaces,
|
||||
activeSpace,
|
||||
onSelectSpace,
|
||||
onOpenFile,
|
||||
onLoadSample,
|
||||
onFit,
|
||||
is25D,
|
||||
onToggle25D,
|
||||
canvasDark,
|
||||
onToggleTheme,
|
||||
layersOpen,
|
||||
onToggleLayers,
|
||||
optionsOpen,
|
||||
onToggleOptions,
|
||||
propsOpen,
|
||||
onToggleProps,
|
||||
}: ToolbarProps) {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const showSpaceTabs = spaces.length > 1 || (spaces[0]?.entityCount ?? 0) > 0;
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none fixed inset-x-0 top-0 z-20 flex items-center gap-2 border-b bg-card/90 px-3 py-2 backdrop-blur [&>*]:pointer-events-auto">
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".dwg,.dxf"
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) onOpenFile(file);
|
||||
e.currentTarget.value = '';
|
||||
}}
|
||||
/>
|
||||
<Button type="button" variant="outline" size="sm" onClick={() => fileInputRef.current?.click()}>
|
||||
<FolderOpen /> 파일 열기
|
||||
</Button>
|
||||
<Button type="button" variant="secondary" size="sm" title="acadrust-dwg WASM" onClick={() => onLoadSample('/samples/BasicSample.dwg', 'BasicSample.dwg')}>
|
||||
Sample DWG
|
||||
</Button>
|
||||
<Button type="button" variant="secondary" size="sm" onClick={() => onLoadSample('/samples/civil.dwg', 'C0060202-001-평면및종단면도.dwg')}>
|
||||
Sample Civil
|
||||
</Button>
|
||||
<Button type="button" variant="secondary" size="sm" title="dxf-parser" onClick={() => onLoadSample('/samples/simple.dxf', 'simple.dxf')}>
|
||||
Sample DXF
|
||||
</Button>
|
||||
|
||||
{showSpaceTabs && (
|
||||
<Tabs value={activeSpace ?? undefined} onValueChange={(v) => onSelectSpace(v)}>
|
||||
<TabsList>
|
||||
{spaces.map((s) => (
|
||||
<TabsTrigger key={s.handleHex} value={s.handleHex} title={`${s.name} · ${s.handleHex}`}>
|
||||
{s.label}
|
||||
{s.entityCount > 0 ? ` (${s.entityCount})` : ''}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
<Button type="button" variant="ghost" size="sm" onClick={onFit} title="Fit (F)">
|
||||
<Scan /> Fit
|
||||
</Button>
|
||||
|
||||
<label className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Switch checked={is25D} onCheckedChange={onToggle25D} />
|
||||
2.5D
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Switch checked={canvasDark} onCheckedChange={onToggleTheme} />
|
||||
캔버스 테마
|
||||
</label>
|
||||
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<Toggle pressed={layersOpen} onPressedChange={onToggleLayers} size="sm" aria-label="레이어 패널" title="Layers">
|
||||
<Layers />
|
||||
</Toggle>
|
||||
<Toggle pressed={optionsOpen} onPressedChange={onToggleOptions} size="sm" aria-label="옵션 패널" title="Options">
|
||||
<SlidersHorizontal />
|
||||
</Toggle>
|
||||
<Toggle pressed={propsOpen} onPressedChange={onToggleProps} size="sm" aria-label="속성 패널" title="Properties">
|
||||
<InfoIcon />
|
||||
</Toggle>
|
||||
<LicenseNotice />
|
||||
</div>
|
||||
|
||||
<Badge variant={statusError ? 'destructive' : 'secondary'} className="max-w-[320px] truncate">
|
||||
{status}
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user