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
123 lines
4.1 KiB
TypeScript
123 lines
4.1 KiB
TypeScript
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>
|
|
);
|
|
}
|