/** 도면층 명령 (조사표 3절 도면층 패널) */ import { toast } from 'react-toastify'; import type { Layer } from '../../App.types'; import { openInspector } from '../../components/ui-state'; import type { Entity } from '../../entities/Entity'; import { popLayerHistory, pushLayerHistory, restoreLayerState, saveLayerState, } from '../../helpers/layer-history'; import { deleteEntities, getActiveLayerId, getEntities, getLayers, setActiveLayerId, setEntities, setLayers, setSelectedEntityIds, } from '../../state'; import { Tool } from '../../tools'; import { createSequenceTool } from '../factories/sequence-tool'; /** 도면층을 바꾸기 전에 직전 상태를 기록한다 (LAYERP가 되돌린다) */ function updateLayers(mutate: (layers: Layer[]) => Layer[]): void { const current = getLayers(); pushLayerHistory(current); setLayers(mutate(current.map((layer) => ({ ...layer })))); } const layerNameOf = (layerId: string): string => getLayers().find((layer) => layer.id === layerId)?.name ?? layerId; /** LAYER — 도면층 특성 관리자(좌측 팔레트)를 연다 */ export function openLayerManager(): string { openInspector('layers'); return '도면층 관리자'; } export const layerCurrentToolStateMachine = createSequenceTool({ tool: Tool.LAYER_CURRENT, helpers: false, steps: [{ kind: 'entity', instructions: '현재 도면층으로 지정할 객체를 선택하십시오.' }], commit: (input) => { const layerId = input.entity(0).layerId; setActiveLayerId(layerId); toast.success(`현재 도면층: ${layerNameOf(layerId)}`); }, }); export const layerOffToolStateMachine = createSequenceTool({ tool: Tool.LAYOFF, helpers: false, steps: [{ kind: 'entity', instructions: '끌 도면층의 객체를 선택하십시오.' }], commit: (input) => { const layerId = input.entity(0).layerId; updateLayers((layers) => layers.map((layer) => (layer.id === layerId ? { ...layer, isVisible: false } : layer)) ); toast.info(`도면층 끄기: ${layerNameOf(layerId)}`); }, }); /** LAYON — 모든 도면층 켜기 */ export function turnAllLayersOn(): string { updateLayers((layers) => layers.map((layer) => ({ ...layer, isVisible: true }))); toast.success('모든 도면층을 켰습니다.'); return '모든 도면층 켜기'; } export const layerFreezeToolStateMachine = createSequenceTool({ tool: Tool.LAYFRZ, helpers: false, steps: [{ kind: 'entity', instructions: '동결할 도면층의 객체를 선택하십시오.' }], commit: (input) => { const layerId = input.entity(0).layerId; if (layerId === getActiveLayerId()) { toast.warn('현재 도면층은 동결할 수 없습니다.'); return; } updateLayers((layers) => layers.map((layer) => (layer.id === layerId ? { ...layer, isFrozen: true } : layer)) ); toast.info(`도면층 동결: ${layerNameOf(layerId)}`); }, }); /** LAYTHW — 모든 도면층 동결 해제 */ export function thawAllLayers(): string { updateLayers((layers) => layers.map((layer) => ({ ...layer, isFrozen: false }))); toast.success('모든 도면층을 동결 해제했습니다.'); return '동결 해제'; } export const layerLockToolStateMachine = createSequenceTool({ tool: Tool.LAYLCK, helpers: false, steps: [{ kind: 'entity', instructions: '잠글 도면층의 객체를 선택하십시오.' }], commit: (input) => { const layerId = input.entity(0).layerId; updateLayers((layers) => layers.map((layer) => (layer.id === layerId ? { ...layer, isLocked: true } : layer)) ); toast.info(`도면층 잠금: ${layerNameOf(layerId)}`); }, }); export const layerUnlockToolStateMachine = createSequenceTool({ tool: Tool.LAYULK, helpers: false, steps: [{ kind: 'entity', instructions: '잠금 해제할 도면층의 객체를 선택하십시오.' }], commit: (input) => { const layerId = input.entity(0).layerId; updateLayers((layers) => layers.map((layer) => (layer.id === layerId ? { ...layer, isLocked: false } : layer)) ); toast.success(`도면층 잠금 해제: ${layerNameOf(layerId)}`); }, }); export const layerIsolateToolStateMachine = createSequenceTool({ tool: Tool.LAYISO, helpers: false, steps: [{ kind: 'entity', instructions: '남길 도면층의 객체를 선택하십시오.' }], commit: (input) => { const layerId = input.entity(0).layerId; updateLayers((layers) => layers.map((layer) => ({ ...layer, isVisible: layer.id === layerId })) ); toast.info(`도면층 분리: ${layerNameOf(layerId)}`); }, }); /** LAYUNISO — 도면층 분리 해제 */ export function unisolateLayers(): string { updateLayers((layers) => layers.map((layer) => ({ ...layer, isVisible: true }))); toast.success('도면층 분리를 해제했습니다.'); return '도면층 분리 해제'; } /** LAYERP — 직전 도면층 상태로 되돌리기 */ export function restorePreviousLayers(): string { const previous = popLayerHistory(); if (!previous) { toast.info('되돌릴 도면층 상태가 없습니다.'); return '되돌릴 상태 없음'; } setLayers(previous); toast.success('직전 도면층 상태로 되돌렸습니다.'); return '직전 도면층 상태'; } export const layerStateSaveToolStateMachine = createSequenceTool({ tool: Tool.LAYERSTATE, helpers: false, steps: [{ kind: 'text', instructions: '저장할 도면층 상태 이름을 입력하십시오.' }], commit: (input) => { saveLayerState(input.text(0), getLayers()); toast.success(`도면층 상태 저장: ${input.text(0)}`); }, }); export const layerStateRestoreToolStateMachine = createSequenceTool({ tool: Tool.LAYERSTATE_RESTORE, helpers: false, steps: [{ kind: 'text', instructions: '복원할 도면층 상태 이름을 입력하십시오.' }], commit: (input) => { const restored = restoreLayerState(input.text(0)); if (!restored) { toast.warn('그 이름으로 저장한 도면층 상태가 없습니다.'); return; } pushLayerHistory(getLayers()); setLayers(restored); toast.success(`도면층 상태 복원: ${input.text(0)}`); }, }); export const layerMatchToolStateMachine = createSequenceTool({ tool: Tool.LAYMCH, helpers: false, steps: [ { kind: 'selection', instructions: '옮길 객체를 선택한 뒤 ENTER.' }, { kind: 'entity', instructions: '대상 도면층의 객체를 선택하십시오.' }, ], commit: (input) => { const layerId = input.entity(1).layerId; for (const entity of input.entities(0)) { entity.layerId = layerId; } setEntities([...getEntities()], true); setSelectedEntityIds([]); toast.success(`${input.entities(0).length}개 객체를 ${layerNameOf(layerId)}(으)로 옮겼습니다.`); }, }); export const layerToCurrentToolStateMachine = createSequenceTool({ tool: Tool.LAYCUR, helpers: false, steps: [{ kind: 'selection', instructions: '현재 도면층으로 옮길 객체를 선택한 뒤 ENTER.' }], commit: (input) => { const layerId = getActiveLayerId(); for (const entity of input.entities(0)) { entity.layerId = layerId; } setEntities([...getEntities()], true); setSelectedEntityIds([]); toast.success(`${input.entities(0).length}개 객체를 현재 도면층으로 옮겼습니다.`); }, }); export const layerMergeToolStateMachine = createSequenceTool({ tool: Tool.LAYMRG, helpers: false, steps: [ { kind: 'entity', instructions: '병합할(사라질) 도면층의 객체를 선택하십시오.' }, { kind: 'entity', instructions: '대상 도면층의 객체를 선택하십시오.' }, ], commit: (input) => { const sourceId = input.entity(0).layerId; const targetId = input.entity(1).layerId; if (sourceId === targetId) { toast.warn('같은 도면층입니다.'); return; } for (const entity of getEntities()) { if (entity.layerId === sourceId) entity.layerId = targetId; } setEntities([...getEntities()], true); updateLayers((layers) => layers.filter((layer) => layer.id !== sourceId)); if (getActiveLayerId() === sourceId) setActiveLayerId(targetId); toast.success(`도면층을 ${layerNameOf(targetId)}(으)로 병합했습니다.`); }, }); export const layerDeleteToolStateMachine = createSequenceTool({ tool: Tool.LAYDEL, helpers: false, steps: [{ kind: 'entity', instructions: '삭제할 도면층의 객체를 선택하십시오.' }], commit: (input) => { const layerId = input.entity(0).layerId; if (getLayers().length <= 1) { toast.warn('마지막 도면층은 삭제할 수 없습니다.'); return; } const doomed: Entity[] = getEntities().filter((entity) => entity.layerId === layerId); deleteEntities(doomed, true); updateLayers((layers) => layers.filter((layer) => layer.id !== layerId)); if (getActiveLayerId() === layerId) setActiveLayerId(getLayers()[0].id); toast.success(`도면층과 객체 ${doomed.length}개를 삭제했습니다.`); }, }); /** LAYWALK — 부를 때마다 다음 도면층 하나만 보여 준다 */ let walkIndex = -1; export function walkLayers(): string { const layers = getLayers(); if (!layers.length) return '도면층 없음'; walkIndex = (walkIndex + 1) % layers.length; const target = layers[walkIndex]; updateLayers((all) => all.map((layer) => ({ ...layer, isVisible: layer.id === target.id }))); toast.info(`도면층 탐색: ${target.name} (${walkIndex + 1}/${layers.length})`); return `도면층 탐색 ${target.name}`; }