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,168 @@
|
||||
import { Button, Input, Label, Slider, Switch } from '@hm-design/ui';
|
||||
import { RotateCcw } from 'lucide-react';
|
||||
import { DraggablePanel } from './DraggablePanel';
|
||||
import { SlidersHorizontal } from 'lucide-react';
|
||||
import type { PersistedOptions } from '../hooks/useDwgViewer';
|
||||
|
||||
function SliderNumberRow({
|
||||
label,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
unit,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
unit?: string;
|
||||
onChange: (v: number) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs text-muted-foreground">{label}</Label>
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
type="number"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
size="small"
|
||||
className="w-20"
|
||||
onChange={(e) => {
|
||||
const v = Number(e.target.value);
|
||||
if (Number.isFinite(v)) onChange(v);
|
||||
}}
|
||||
/>
|
||||
{unit && <span className="text-xs text-muted-foreground">{unit}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<Slider
|
||||
value={[value]}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
aria-label={label}
|
||||
onValueChange={([v]) => onChange(v)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface OptionsPanelProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
options: PersistedOptions;
|
||||
is25D: boolean;
|
||||
lineweightClasses: number[];
|
||||
onSetGrid: (on: boolean) => void;
|
||||
onSetZoomSpeed: (v: number) => void;
|
||||
onSetZScale: (v: number) => void;
|
||||
onSetLineweightOn: (on: boolean | null) => void;
|
||||
onSetLineweightScale: (v: number | null) => void;
|
||||
onSet25D: (on: boolean) => void;
|
||||
onReset: () => void;
|
||||
}
|
||||
|
||||
export function OptionsPanel({
|
||||
open,
|
||||
onClose,
|
||||
options,
|
||||
is25D,
|
||||
lineweightClasses,
|
||||
onSetGrid,
|
||||
onSetZoomSpeed,
|
||||
onSetZScale,
|
||||
onSetLineweightOn,
|
||||
onSetLineweightScale,
|
||||
onSet25D,
|
||||
onReset,
|
||||
}: OptionsPanelProps) {
|
||||
const lwScale = options.lineweightScale ?? 11;
|
||||
const lwPreview =
|
||||
lineweightClasses.length > 0
|
||||
? lineweightClasses.map((mm) => `${mm.toFixed(2)}mm → ${(mm * lwScale).toFixed(1)}px`).join(' · ')
|
||||
: `이 도면에는 굵은 선이 없습니다 (0.50mm 였다면 ${(0.5 * lwScale).toFixed(1)}px)`;
|
||||
|
||||
return (
|
||||
<DraggablePanel
|
||||
title="옵션"
|
||||
icon={<SlidersHorizontal className="size-4" />}
|
||||
initial={{ left: window.innerWidth - 676, top: 56 }}
|
||||
width={340}
|
||||
height={520}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
headerActions={
|
||||
<Button type="button" variant="ghost" size="icon-xs" title="기본값으로" aria-label="기본값으로" onClick={onReset}>
|
||||
<RotateCcw />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="space-y-5">
|
||||
<section className="space-y-3">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">선가중치</h4>
|
||||
<label className="flex items-center justify-between text-sm">
|
||||
표시 사용
|
||||
<Switch
|
||||
checked={options.lineweightOn ?? false}
|
||||
onCheckedChange={(on) => onSetLineweightOn(on)}
|
||||
/>
|
||||
</label>
|
||||
<SliderNumberRow
|
||||
label="표시 배율 (px/mm)"
|
||||
value={lwScale}
|
||||
min={2}
|
||||
max={60}
|
||||
step={0.5}
|
||||
onChange={onSetLineweightScale}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{lwPreview}</p>
|
||||
<p className="text-xs text-muted-foreground">AutoCAD의 "선가중치 설정 → 표시 배율"과 동일합니다.</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-3 border-t pt-4">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">표시</h4>
|
||||
<label className="flex items-center justify-between text-sm">
|
||||
그리드
|
||||
<Switch checked={options.grid} onCheckedChange={onSetGrid} />
|
||||
</label>
|
||||
<SliderNumberRow
|
||||
label="줌 속도"
|
||||
value={options.zoomSpeed}
|
||||
min={0.2}
|
||||
max={3}
|
||||
step={0.1}
|
||||
onChange={onSetZoomSpeed}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="space-y-3 border-t pt-4">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">2.5D</h4>
|
||||
<label className="flex items-center justify-between text-sm">
|
||||
2.5D 보기
|
||||
<Switch checked={is25D} onCheckedChange={onSet25D} />
|
||||
</label>
|
||||
<div className={is25D ? '' : 'pointer-events-none opacity-50'}>
|
||||
<SliderNumberRow
|
||||
label="Z 높이 배율"
|
||||
value={options.zScale}
|
||||
min={1}
|
||||
max={50}
|
||||
step={0.5}
|
||||
onChange={onSetZScale}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
좌클릭 드래그로 회전, 중간/우클릭 드래그로 이동합니다.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</DraggablePanel>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user