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>
This commit is contained in:
2026-10-21 12:48:36 +09:00
co-authored by Claude Opus 5.5
parent aaadbd295e
commit 17e6500cf6
27 changed files with 639 additions and 112 deletions
@@ -51,9 +51,6 @@ export interface DesignMeta {
confirmed: boolean;
/** 수량 산출표 값 (횡단도만). 미산정 항목은 null. */
quantityTable: Record<string, number | null> | null;
/** 이전/다음 도면 존재 여부 (경계에서 버튼 비활성화) */
hasPrev: boolean;
hasNext: boolean;
}
export interface StateMetaData {
@@ -6,6 +6,7 @@ import {
commonValue,
LINE_TYPES,
LINE_WIDTHS,
paintNowCommitLater,
restyle,
selectionLineStyle,
styleTargets,
@@ -47,11 +48,13 @@ export const PropertiesEditor: FC<PropertiesEditorProps> = ({ compact = false })
type="color"
value={view.color || '#000000'}
data-mixed={view.color ? undefined : 'true'}
onChange={(event) =>
applyToLines((entity) => {
entity.lineColor = event.target.value;
})
}
onChange={(event) => {
// 끄는 동안 = 화면만 · 멎은 뒤 되돌리기 한 칸(리본 색 칸과 같은 길)
const color = event.target.value;
paintNowCommitLater(styleTargets(getSelectedEntities()), (entity) => {
entity.lineColor = color;
});
}}
/>
</label>
@@ -7,7 +7,7 @@ import type { TextEntity } from '../entities/TextEntity';
import {
LINE_TYPES,
LINE_WIDTHS,
repaint,
paintNowCommitLater,
restyle,
selectionLineStyle,
styleTargets,
@@ -15,7 +15,6 @@ import {
import {
getActiveLayerId,
getActiveTextStyle,
getEntities,
getLayers,
getSelectedEntities,
setActiveLayerId,
@@ -23,29 +22,10 @@ import {
setActiveLineDash,
setActiveLineWidth,
setActiveTextStyle,
setEntities,
} from '../state';
const FONT_FAMILIES = ['Noto Sans KR', 'Malgun Gothic', 'Pretendard', 'Arial', 'monospace'];
/**
* 색 선택기는 드래그하는 내내 값 변경을 쏜다. 그 하나하나를 되돌리기·변경통지에 실으면
* 색 한 번 고르는 동안 수십 건이 나간다(2026-09-01 지적). 첫 변경에 사본을 끼우고 끄는 동안에는
* 그 사본만 칠하고, 멎은 뒤 한 번만 되돌리기·통지에 싣는다(488 — Ctrl+Z 한 번에 옛 색).
*/
const COLOR_COMMIT_DELAY_MS = 350;
let colorCommitTimer: ReturnType<typeof setTimeout> | undefined;
let painting: Entity[] | null = null;
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);
}
/** 고른 선 있으면 그것만 바꿈(488 — 그리기 기본값 그대로) · 없으면 그리기 기본값만 */
const selectedLines = () => styleTargets(getSelectedEntities());
@@ -106,3 +106,22 @@ export function repaint(copies: Entity[], change: (entity: Entity) => void): voi
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);
}
@@ -40,8 +40,6 @@ const meta = (confirmed: boolean): DesignMeta => ({
info: '',
confirmed,
quantityTable: null,
hasPrev: false,
hasNext: false,
});
describe('setActiveLayerId', () => {
-2
View File
@@ -523,8 +523,6 @@ export const setDesignMeta = (newMeta: DesignMeta | null) => {
export const isHostSave = (): boolean => hostSave;
export const setHostSave = (enabled: boolean) => {
hostSave = enabled;
// 제목 줄이 없어지면 그 높이만큼 위로 당긴다(App.css `.cad-host-save`).
document.documentElement.classList.toggle('cad-host-save', enabled);
};
export const isBare = (): boolean => bare;
export const setBare = (enabled: boolean) => {
@@ -16,7 +16,6 @@ import {
getEntities,
getLayers,
setActiveLayerId,
setEntities,
setLayers,
setSelectedEntityIds,
} from '../../state';
@@ -223,10 +222,13 @@ export const layerMergeToolStateMachine = createSequenceTool({
toast.warn('같은 레이어입니다.');
return;
}
for (const entity of getEntities()) {
if (entity.layerId === sourceId) entity.layerId = targetId;
}
setEntities([...getEntities()], true);
// 사본으로 갈아 끼움(제자리 고침 = 되돌리기 칸이 같은 참조 → Ctrl+Z 해도 그대로) · 층 목록 = LAYERP
restyle(
getEntities().filter((entity) => entity.layerId === sourceId),
(entity) => {
entity.layerId = targetId;
}
);
updateLayers((layers) => layers.filter((layer) => layer.id !== sourceId));
if (getActiveLayerId() === sourceId) setActiveLayerId(targetId);
toast.success(`레이어를 ${layerNameOf(targetId)}(으)로 병합했습니다.`);