ai-generation-guard.mjs flagged REUSE-004 on 5 native <label> wrappers around Switch controls in Toolbar/OptionsPanel; swapped to @hm-design/ui's Label per AGENTS.md field-label rule. Remaining guard findings (hidden file input, dynamic panel geometry, per-entity CAD layer color) are documented exceptions with no matching HM component. Refs #1
169 lines
5.0 KiB
TypeScript
169 lines
5.0 KiB
TypeScript
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="w-full 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="w-full 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="w-full 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>
|
|
);
|
|
}
|