Files
Aislo/B07_DesignDetail/openwebcad/src/commands/registry.ts
T
eomsangdonandClaude Fable 5 17e14a25ed fix(B07): 편집이 사라지지 않게 하고 확정한 도면을 잠근다
사용자 편의성 점검 15건 중 반영 확정분 12건.

편집 손실 방지
- CAD에 미저장 표시(drawingDirty)를 두고, 도면을 바꾸기 전에 묻는다.
  전에는 그은 선이 경고 없이 사라졌다(실측: 캔버스 서명이 원본과 동일).
- 도면 전환을 겹쳐 눌러도 늦게 온 응답이 화면을 덮지 않게 가드를 뒀다.
- 도면층 삭제·객체 이동을 되돌리기에 실어 Ctrl+Z로 돌아오게 했다.

확정한 도면은 읽기 전용
- runCommand 한 곳에서 보는 명령만 통과시킨다(허용 명시 방식).
- 도면을 실을 때 앞 도면의 그리기 도구를 내린다 — 켜 둔 도구가 확정본에도
  계속 그렸다.
- 확정 버튼 자리를 [현재 도면 확정] / [수정]으로 가른다. 확정 해제는 [수정]
  한 곳뿐 — 되돌리기·색 고르기로 확정이 풀리던 경로를 없앴다.

그 밖
- 잠금 도면층(도각·등고선·계류)은 마우스가 스쳐도 하이라이트하지 않는다.
- 준비 중 도면 7종도 빈 도각으로 열린다(눌리지 않는 회색 버튼 제거).
- 도면층 행이 잘리지 않게 패널을 300px로 넓히고 이름을 줄여 담는다.
- 기본 그리기 색을 검정으로 — 흰 종이에 흰 선이라 안 보였다.
- 색 고르는 동안 변경 통지가 연발하지 않는다.
- 수량표는 앞 단계 산출물이라 도면에서 고치지 못하게 막고 안내한다.
- 자동백업 칸을 도면별로 나눈다 — 되살리면 다른 도면에 붙었다.
- 횡단 장 id를 시작 측점 기준으로 바꿔 구간이 달라져도 옛 확정이 안 붙는다.
- 회사 도각 저장 전에 좌표를 검사하고, 기본 도각으로 되돌리기를 연다.
- 횡단 파일이 없어도 나머지 도면 목록은 남는다.
- 불러오는 중·실패를 화면에 표시한다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 19:05:22 +09:00

135 lines
4.5 KiB
TypeScript

/**
* 명령 레지스트리 — 리본·명령행·단축키가 읽는 단일 소스.
* 새 명령은 commands.*.ts에 한 줄 추가하면 세 곳에 동시에 나타난다.
*/
import type { AnyToolMachine, CadCommand } from './command.types';
import type { Tool } from '../tools';
import {
DIMENSION_COMMANDS,
LEADER_COMMANDS,
MARKUP_COMMANDS,
TABLE_COMMANDS,
TEXT_COMMANDS,
} from './commands.annotate';
import { DRAW_COMMANDS } from './commands.draw';
import { FILE_COMMANDS } from './commands.file';
import { INSERT_COMMANDS } from './commands.insert';
import { LAYER_COMMANDS } from './commands.layer';
import {
CLIPBOARD_COMMANDS,
GROUP_COMMANDS,
INQUIRY_COMMANDS,
PROPERTY_COMMANDS,
} from './commands.utility';
import { ALIGN_COMMANDS, MODIFY_COMMANDS } from './commands.modify';
import { VIEW_COMMANDS } from './commands.view';
const ALL_COMMANDS: CadCommand[] = [
...DRAW_COMMANDS,
...MODIFY_COMMANDS,
...ALIGN_COMMANDS,
...TEXT_COMMANDS,
...DIMENSION_COMMANDS,
...LEADER_COMMANDS,
...TABLE_COMMANDS,
...MARKUP_COMMANDS,
...LAYER_COMMANDS,
...PROPERTY_COMMANDS,
...GROUP_COMMANDS,
...INQUIRY_COMMANDS,
...CLIPBOARD_COMMANDS,
...INSERT_COMMANDS,
...VIEW_COMMANDS,
...FILE_COMMANDS,
];
const COMMANDS_BY_ID = new Map<string, CadCommand>(
ALL_COMMANDS.map((command) => [command.id, command])
);
const COMMANDS_BY_ALIAS = new Map<string, CadCommand>();
for (const command of ALL_COMMANDS) {
for (const alias of command.aliases ?? []) {
if (!COMMANDS_BY_ALIAS.has(alias)) {
COMMANDS_BY_ALIAS.set(alias, command);
}
}
}
/**
* 확정한 도면에서도 쓸 수 있는 명령 — **보는 일만 하는 것들**이다
* (2026-09-01 사용자 확정: 확정하면 수정이 안 되고, 풀려면 [수정]을 누른다).
*
* 허용을 명시하는 쪽으로 짠다. 새 편집 명령이 늘어도 자동으로 막히고, 반대로 짜면
* 새 명령마다 여기 적어야 해 언젠가 빠뜨린다.
*/
const VIEW_ONLY_COMMAND_IDS: ReadonlySet<string> = new Set([
// 보기 — 확대·이동·다시그리기·선택
...VIEW_COMMANDS.map((command) => command.id),
// 조회 — 거리·면적·각도처럼 재기만 하는 것
...INQUIRY_COMMANDS.map((command) => command.id),
// 도면층 보이기·잠금 — 객체를 건드리지 않는 것만 (LAYMCH·LAYCUR·LAYMRG·LAYDEL 제외)
'LAYER',
'LAYCURSET',
'LAYOFF',
'LAYON',
'LAYFRZ',
'LAYTHW',
'LAYLCK',
'LAYULK',
'LAYISO',
'LAYUNISO',
'LAYERP',
'LAYERSTATE',
'LAYERSTATERESTORE',
'LAYWALK',
// 파일 — 내보내기와 클립보드 복사는 도면을 바꾸지 않는다
'EXPORT',
'EXPORTSVG',
'EXPORTPNG',
'COPYCLIP',
'COPYBASE',
]);
/** 확정한 도면에서 이 명령을 실행해도 되는가. */
export const isViewOnlyCommand = (command: CadCommand): boolean =>
VIEW_ONLY_COMMAND_IDS.has(command.id);
export const getAllCommands = (): CadCommand[] => ALL_COMMANDS;
export const getCommandById = (id: string): CadCommand | undefined => COMMANDS_BY_ID.get(id);
/** 활성 도구로 어떤 명령이 실행 중인지 되짚는다 (리본 강조·상태 표시용) */
export const getCommandByTool = (tool: Tool | null): CadCommand | undefined =>
tool ? ALL_COMMANDS.find((command) => command.tool === tool) : undefined;
/** 도구형 명령의 xstate 머신 표 — 도구 활성화 경로가 이 표만 본다 */
export const TOOL_STATE_MACHINES = Object.fromEntries(
ALL_COMMANDS.filter((command) => command.tool && command.machine).map((command) => [
command.tool,
command.machine,
])
) as Record<Tool, AnyToolMachine>;
/** 명령행 입력 해석 — 정확한 이름·별칭이 먼저, 없으면 접두사 후보의 첫 항목 */
export function resolveCommandInput(input: string): CadCommand | undefined {
const text = input.trim().toUpperCase();
if (!text) return undefined;
return COMMANDS_BY_ID.get(text) ?? COMMANDS_BY_ALIAS.get(text) ?? matchCommandPrefixes(text)[0];
}
/** 접두사가 같은 명령 후보 (커서 옆 자동완성 목록에 쓴다) */
export function matchCommandPrefixes(input: string): CadCommand[] {
const text = input.trim().toUpperCase();
if (!text) return [];
const byAlias = ALL_COMMANDS.filter((command) =>
(command.aliases ?? []).some((alias) => alias.startsWith(text))
);
const byId = ALL_COMMANDS.filter((command) => command.id.startsWith(text));
return [...new Set([...byAlias, ...byId])];
}
/** 명령행 자동완성에 보여줄 문자열 (예: "LINE (L) 선") */
export const describeCommand = (command: CadCommand): string =>
`${command.id}${command.aliases?.length ? ` (${command.aliases[0]})` : ''} ${command.label}`;