Files
Aislo/B07_DesignDetail/openwebcad/src/helpers/selection-style.ts
T
eomsangdonandClaude Opus 5.5 17e6500cf6 fix(506-3 B07 · CAD 작은 흠): 저장 길 · 죽은 길 걷음 · CAD 되돌리기 · B09 인쇄 미리보기 · M02 도움말
- 1-A 도면 GET · 저장 PUT 한도 60 s · 저장 중 미리받기 쉼 · 끊긴 저장 = 목록 confirmed 로 판정
- 2 CAD 자동백업 열쇠 = b07:{프로젝트}:{도면} · 도각 편집 = b07-frame:{프로젝트} · [초기화] 같은 열쇠
- 3-1 [초기화] 물음 글 = 프로젝트 도각 그대로 · [도각 편집] → [기본 도각 복원] 안내
- 3-2 풀린 장 저장본 json · 수량표 찌꺼기 걷음(_write_manifest 한 곳)
- 5-1 확정 풀기 · 배치 · 축척 = mark_stage(save)
- 7-3 start_stage 가 DEV_UNLOCK 표식 남김
- 6-a navigateDrawing · onNavigate · hasPrev · hasNext 걷음 · 6-b cad-host-save 토글 걷음 · 4-2 B08 안 쓰는 글 11 걷음
- 6-f 특성 판 색 끌기 = 되돌리기 한 칸(paintNowCommitLater 한 벌) · 6-g LAYMRG 사본 길
- 4-1 B09 [인쇄 미리보기] = 저장 뒤 이동 · 단계 그대로 · 6-e M02 카드 도움말 DWG 한 줄
- 시험 test_506_3_* 둘 · cad/undo-506-3 · test_298_2_b07_buttons 열쇠 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 12:48:36 +09:00

128 lines
5.2 KiB
TypeScript

/**
* 고른 것의 선 특성(색 · 선 두께 · 선종류) 읽기 · 바꾸기 한 벌 (488 · 리본 특성 · 특성 판 · 특성 명령).
*
* 읽기 = 고른 것의 선 잎(폴리선 → 자식 선 · 글 뺌)이 모두 같으면 그 값 · 다르거나 목록 밖이면 빈 값 ·
* 고른 선이 없으면 그리기 기본값.
* 바꾸기 = id 그대로 사본으로 갈아 끼움(폴리선 자식까지) — 되돌리기 한 칸 · Ctrl+Z 한 번에 옛 값.
*/
import { type Entity, EntityName } from '../entities/Entity';
import type { PolyLineEntity } from '../entities/PolyLineEntity';
import {
getActiveLineColor,
getActiveLineDash,
getActiveLineWidth,
getEntities,
getSelectedEntities,
notifyWindow,
setEntities,
} from '../state';
import { HtmlEvent } from '../App.types';
import { cloneKeepingLook } from '../tools/move-tool.helpers';
export const LINE_TYPES: { value: string; label: string; dash: number[] | undefined }[] = [
{ value: 'solid', label: '실선', dash: undefined },
{ value: 'dashed', label: '파선', dash: [10, 5] },
{ value: 'dashdot', label: '1점쇄선', dash: [12, 4, 2, 4] },
{ value: 'dotted', label: '점선', dash: [2, 4] },
];
export const LINE_WIDTHS = [1, 2, 3, 4, 5];
/** 점선 → 목록 값 · `[]` · 없음 = 실선 · 목록 밖 = 빈 값 */
export const dashToLineType = (dash: number[] | undefined): string =>
dash?.length
? (LINE_TYPES.find((type) => JSON.stringify(type.dash) === JSON.stringify(dash))?.value ?? '')
: 'solid';
/** 선 메뉴가 보는 것 — 글 뺌(글 색은 문자 스타일 칸) */
export const styleTargets = (entities: Entity[]): Entity[] =>
entities.filter((entity) => entity.getType() !== EntityName.Text);
/** 그림에 쓰는 잎 — 폴리선은 자식 선이 제 색 · 두께로 그림 */
const leaves = (entity: Entity): Entity[] =>
entity.getType() === EntityName.PolyLine ? (entity as PolyLineEntity).getEntities() : [entity];
/** 모두 같으면 그 값 · 다르거나 없으면 `''`(빈 칸) */
export function commonValue(entities: Entity[], read: (entity: Entity) => string): string {
const values = new Set(entities.flatMap(leaves).map(read));
return values.size === 1 ? [...values][0] : '';
}
export interface LineStyleView {
color: string;
width: string;
lineType: string;
}
/** 리본 · 특성 판이 보일 값 — 고른 선 있으면 그 값(다르면 `''`) · 없으면 그리기 기본값 */
export function selectionLineStyle(): LineStyleView {
const targets = styleTargets(getSelectedEntities());
if (!targets.length) {
return {
color: getActiveLineColor(),
width: String(getActiveLineWidth()),
lineType: dashToLineType(getActiveLineDash()),
};
}
const width = commonValue(targets, (entity) => String(entity.lineWidth));
return {
color: commonValue(targets, (entity) => entity.lineColor.toLowerCase()),
width: LINE_WIDTHS.map(String).includes(width) ? width : '',
lineType: commonValue(targets, (entity) => dashToLineType(entity.lineDash)),
};
}
/** 사본에 바꿈 — 폴리선은 자식까지 */
function applyDeep(entity: Entity, change: (entity: Entity) => void): void {
change(entity);
if (entity.getType() === EntityName.PolyLine) {
for (const child of (entity as PolyLineEntity).getEntities()) change(child);
}
}
/**
* 바꾼 사본(id 그대로)을 제자리에 끼움 · `track` = 되돌리기 한 칸.
* 돌려준 사본은 이어서 칠할 수 있음(`repaint` · 색 끌기).
*/
export function restyle(targets: Entity[], change: (entity: Entity) => void, track = true): Entity[] {
if (!targets.length) return [];
const copies = targets.map((entity) => {
const copy = cloneKeepingLook(entity, true);
applyDeep(copy, change);
return copy;
});
const byId = new Map(copies.map((copy) => [copy.id, copy]));
setEntities(
getEntities().map((entity) => byId.get(entity.id) ?? entity),
track
);
notifyWindow(HtmlEvent.UPDATE_STATE);
return copies;
}
/** 이미 끼운 사본을 다시 칠함(화면만 · 되돌리기 없음) */
export function repaint(copies: Entity[], change: (entity: Entity) => void): void {
for (const copy of copies) applyDeep(copy, change);
setEntities([...getEntities()], false);
notifyWindow(HtmlEvent.UPDATE_STATE); // 견본 색도 따라감
}
/**
* 색 선택기는 드래그하는 내내 값 변경을 쏜다. 그 하나하나를 되돌리기·변경통지에 실으면
* 색 한 번 고르는 동안 수십 건이 나간다(2026-09-01 지적). 첫 변경에 사본을 끼우고 끄는 동안에는
* 그 사본만 칠하고, 멎은 뒤 한 번만 되돌리기·통지에 싣는다(488 — Ctrl+Z 한 번에 옛 색).
* 리본 특성 · 특성 판 색 칸이 같이 씀(506-3).
*/
const COLOR_COMMIT_DELAY_MS = 350;
let colorCommitTimer: ReturnType<typeof setTimeout> | undefined;
let painting: Entity[] | null = null;
export function paintNowCommitLater(targets: Entity[], change: (entity: Entity) => void): void {
if (painting) repaint(painting, change);
else painting = restyle(targets, change, false); // 화면만
clearTimeout(colorCommitTimer);
colorCommitTimer = setTimeout(() => {
painting = null;
setEntities([...getEntities()], true); // 멎었다 — 이번 한 번만 실어 보낸다
}, COLOR_COMMIT_DELAY_MS);
}