- 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>
128 lines
5.2 KiB
TypeScript
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);
|
|
}
|