파일마다 포맷 폭이 달라(≈80 대 100) 한 줄만 고쳐도 포맷터가 무관한 줄을 대량 재포맷했음. 사용자 지시로 전체를 한 번에 맞춤. 코드 동작 변경 없음 — 포맷만. - 프론트엔드 `.ts/.css/.html` → 저장소 prettier (`.prettierrc`, printWidth 100) - `B07_DesignDetail/openwebcad/**` → 자체 biome (tab 들여쓰기·single quote·lineWidth 100). `biome format` 만 사용 — `biome lint --write` 는 포맷 아닌 코드 수정까지 하므로 제외 - 파이썬 → `ruff format` (엔진 코드는 이미 정합, resources·scratch 스크립트 24개만 변경) 두 포맷터가 서로 되돌리지 않도록 `.prettierignore` 신규 — openwebcad 와 빌드·산출물 폴더를 prettier 대상에서 뺌. `.prettierrc` 에 `endOfLine: "auto"` 추가 — 기본값 `lf` 가 `core.autocrlf=true` 로 받은 CRLF 파일을 매번 전부 다시 써서 `--list-different` 가 실제 포맷 차이를 가리고 있었음. 검증: `tsc --noEmit` 통과(루트·openwebcad 둘 다), pytest 349 passed / 17 skipped / 0 failed, CAD vitest 87건 중 81 passed / 6 failed(laptop-sub 기준선과 동일, 회귀 없음). 포맷터 재실행 시 prettier·biome 모두 변경 0건. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
148 lines
3.8 KiB
TypeScript
148 lines
3.8 KiB
TypeScript
/** 특성 팔레트 본문 — 선택 객체의 값을 읽고 바로 고친다 (PROPERTIES) */
|
||
import type { FC } from 'react';
|
||
import type { Entity } from '../entities/Entity';
|
||
import { polylineLength, sampleEntityPoints } from '../helpers/geometry/sample-entity';
|
||
import { getEntities, getLayers, getSelectedEntities, setEntities } from '../state';
|
||
import { dashToLineType, LINE_TYPES, LINE_WIDTHS } from './RibbonWidgets';
|
||
|
||
interface PropertiesEditorProps {
|
||
compact?: boolean;
|
||
}
|
||
|
||
function applyToSelection(mutate: (entity: Entity) => void): void {
|
||
const selected = getSelectedEntities();
|
||
if (!selected.length) return;
|
||
for (const entity of selected) mutate(entity);
|
||
setEntities([...getEntities()], true);
|
||
}
|
||
|
||
/** 여러 객체가 값이 다르면 '*가지각색' 대신 첫 객체 값을 보여 준다 (AutoCAD와 같은 관행) */
|
||
export const PropertiesEditor: FC<PropertiesEditorProps> = ({ compact = false }) => {
|
||
const selected = getSelectedEntities();
|
||
const layers = getLayers();
|
||
const first = selected[0];
|
||
|
||
if (!first) {
|
||
return <p className="cad-properties__empty">선택된 객체가 없습니다.</p>;
|
||
}
|
||
|
||
const points = sampleEntityPoints(first);
|
||
const box = first.getBoundingBox();
|
||
|
||
return (
|
||
<div className="cad-properties-editor" data-compact={compact}>
|
||
<div className="cad-properties-editor__title">
|
||
{selected.length === 1 ? first.getType() : `여러 객체 (${selected.length})`}
|
||
</div>
|
||
|
||
<label>
|
||
<span>색상</span>
|
||
<input
|
||
type="color"
|
||
value={first.lineColor}
|
||
onChange={(event) =>
|
||
applyToSelection((entity) => {
|
||
entity.lineColor = event.target.value;
|
||
})
|
||
}
|
||
/>
|
||
</label>
|
||
|
||
<label>
|
||
<span>선가중치</span>
|
||
<select
|
||
value={first.lineWidth}
|
||
onChange={(event) =>
|
||
applyToSelection((entity) => {
|
||
entity.lineWidth = Number(event.target.value);
|
||
})
|
||
}
|
||
>
|
||
{LINE_WIDTHS.map((width) => (
|
||
<option key={width} value={width}>
|
||
{width}px
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
|
||
<label>
|
||
<span>선종류</span>
|
||
<select
|
||
value={dashToLineType(first.lineDash)}
|
||
onChange={(event) => {
|
||
const dash = LINE_TYPES.find((type) => type.value === event.target.value)?.dash;
|
||
applyToSelection((entity) => {
|
||
entity.lineDash = dash ? [...dash] : undefined;
|
||
});
|
||
}}
|
||
>
|
||
{LINE_TYPES.map((type) => (
|
||
<option key={type.value} value={type.value}>
|
||
{type.label}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
|
||
<label>
|
||
<span>도면층</span>
|
||
<select
|
||
value={first.layerId}
|
||
onChange={(event) =>
|
||
applyToSelection((entity) => {
|
||
entity.layerId = event.target.value;
|
||
})
|
||
}
|
||
>
|
||
{layers.map((layer) => (
|
||
<option key={layer.id} value={layer.id}>
|
||
{layer.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
|
||
<label>
|
||
<span>투명도</span>
|
||
<input
|
||
type="number"
|
||
min={0}
|
||
max={90}
|
||
step={5}
|
||
value={Math.round((1 - (first.opacity ?? 1)) * 100)}
|
||
onChange={(event) => {
|
||
const percent = Math.min(90, Math.max(0, Number(event.target.value)));
|
||
applyToSelection((entity) => {
|
||
entity.opacity = 1 - percent / 100;
|
||
});
|
||
}}
|
||
/>
|
||
</label>
|
||
|
||
{!compact && (
|
||
<dl className="cad-properties-editor__readout">
|
||
<div>
|
||
<dt>길이</dt>
|
||
<dd>{polylineLength(points).toFixed(3)}</dd>
|
||
</div>
|
||
<div>
|
||
<dt>크기</dt>
|
||
<dd>
|
||
{(box.xmax - box.xmin).toFixed(3)} × {(box.ymax - box.ymin).toFixed(3)}
|
||
</dd>
|
||
</div>
|
||
<div>
|
||
<dt>시작점</dt>
|
||
<dd>{points[0] ? `${points[0].x.toFixed(2)}, ${points[0].y.toFixed(2)}` : '-'}</dd>
|
||
</div>
|
||
<div>
|
||
<dt>그룹</dt>
|
||
<dd>{first.groupId ? '있음' : '없음'}</dd>
|
||
</div>
|
||
</dl>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|