Files
dwg-dxf-viewer-hm/src/components/Toolbar.tsx
T
minsung b114581c07 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
2026-10-12 17:42:58 +09:00

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>
);
}