fix(B07): 도각을 배경으로 굳혀 새 선이 잠금 도면층에 떨어지지 않게 한다
- state.setActiveLayerId(): 잠금 도면층(도각·원지반) 요청은 잠금 아닌 첫 층으로 돌린다. 브리지·자동백업 복구·JSON 가져오기·도면층 패널·리본이 모두 이 함수를 지나므로 가드를 한 곳에 뒀다. 잠금 아닌 층이 없으면 요청값 유지. - RibbonWidgets: 「현재 도면층」 목록에서 잠금층을 뺀다(현재 층이 잠금이면 유지). - Cover 엔진: 표지에 잠금 아닌 「주기」 도면층을 실어 덧그릴 자리를 만든다. 표지는 엔티티가 전부 도각(잠금)이라 가드만으로는 그릴 곳이 없었다. - state.resetUndoBaseline(): 도면 적재를 되돌리기 바닥으로 세운다. 첫 Ctrl+Z가 도면을 싣기 전 빈 상태로 되돌아가 화면이 백지가 되던 결함을 닫는다. - debug-hook: 검증용 snap() 창구 추가. state.test.ts 3건 신규. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,9 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Template import (
|
||||
)
|
||||
|
||||
COVER_TEMPLATE = "00_template_cover"
|
||||
# 표지는 엔티티가 전부 도각(잠금)이라 그대로 두면 덧그릴 자리가 없다 — 잠금 아닌
|
||||
# 도면층을 하나 실어 사용자가 적을 자리를 준다(2026-09-01).
|
||||
NOTE_LAYER_ID = "b08-cover-note"
|
||||
|
||||
|
||||
def build_cover_drawing(drawing_id: str, fields: dict[str, str] | None = None) -> dict[str, Any]:
|
||||
@@ -52,5 +55,8 @@ def build_cover_drawing(drawing_id: str, fields: dict[str, str] | None = None) -
|
||||
return {
|
||||
"format": DRAWING_FORMAT,
|
||||
"entities": entities,
|
||||
"layers": [_layer(FRAME_LAYER_ID, "도각", locked=True)],
|
||||
"layers": [
|
||||
_layer(NOTE_LAYER_ID, "주기"),
|
||||
_layer(FRAME_LAYER_ID, "도각", locked=True),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -117,11 +117,15 @@ export const LayersWidget: FC = () => {
|
||||
<label className="cad-prop cad-prop--wide" title="현재 도면층">
|
||||
<span>현재 도면층</span>
|
||||
<select value={activeLayerId} onChange={(event) => setActiveLayerId(event.target.value)}>
|
||||
{layers.map((layer) => (
|
||||
<option key={layer.id} value={layer.id}>
|
||||
{layer.name}
|
||||
</option>
|
||||
))}
|
||||
{/* 잠금 도면층(배경)은 고를 수 없으니 보여 주지 않는다. 현재 층이 잠금이면
|
||||
(전부 잠금인 도면) 표시가 비지 않게 그 항목만 남긴다. */}
|
||||
{layers
|
||||
.filter((layer) => !layer.isLocked || layer.id === activeLayerId)
|
||||
.map((layer) => (
|
||||
<option key={layer.id} value={layer.id}>
|
||||
{layer.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { toast } from 'react-toastify';
|
||||
import { HtmlEvent } from '../App.types';
|
||||
import type { Entity } from '../entities/Entity';
|
||||
import { getEntities, setActiveLayerId, setEntities, setLayers } from '../state';
|
||||
import { getEntities, resetUndoBaseline, setActiveLayerId, setEntities, setLayers } from '../state';
|
||||
import { exportEntitiesAndLayersToJsonString } from './import-export-handlers/export-entities-to-json';
|
||||
import { getEntitiesAndLayersFromJsonString } from './import-export-handlers/import-entities-from-json';
|
||||
|
||||
@@ -64,6 +64,7 @@ export async function restoreRecovery(): Promise<void> {
|
||||
setEntities(drawing.entities, true);
|
||||
setLayers(drawing.layers);
|
||||
setActiveLayerId(drawing.layers[0].id);
|
||||
resetUndoBaseline();
|
||||
toast.success(`백업을 되살렸습니다 (${new Date(file.savedAt).toLocaleString()}).`);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 화면 검증용 상태 조회 창구 — 브라우저 콘솔·자동화 스크립트가
|
||||
* window.__aisloCad로 현재 객체·도면층·선택을 읽는다. 앱 동작에는 관여하지 않는다.
|
||||
*/
|
||||
import { getEntities, getLayers, getSelectedEntityIds } from '../state';
|
||||
import { getEntities, getLayers, getSelectedEntityIds, getSnapPoint } from '../state';
|
||||
import { isEntityHidden } from './visibility';
|
||||
|
||||
export function registerCadDebugHook(): void {
|
||||
@@ -28,5 +28,7 @@ export function registerCadDebugHook(): void {
|
||||
}),
|
||||
layers: () => getLayers(),
|
||||
selection: () => getSelectedEntityIds(),
|
||||
// 마우스 위치의 객체 스냅점 — 잠금 도면층(도각)이 스냅에 끼는지 검증할 때 읽는다.
|
||||
snap: () => getSnapPoint(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
getEntities,
|
||||
getLayers,
|
||||
getScreenCanvasDrawController,
|
||||
resetUndoBaseline,
|
||||
setActiveLayerId,
|
||||
setDesignMeta,
|
||||
setEntities,
|
||||
@@ -123,6 +124,8 @@ export function registerAisloDrawingBridge() {
|
||||
setEntities(drawing.entities, false);
|
||||
setLayers(drawing.layers);
|
||||
setActiveLayerId(drawing.layers[0].id);
|
||||
// 실은 도면이 되돌리기의 바닥 — 첫 Ctrl+Z가 백지로 가지 않게 한다.
|
||||
resetUndoBaseline();
|
||||
// 설계 컨텍스트(제목·측점정보·확정상태·수량표)를 수량 패널에 반영
|
||||
setDesignMeta(event.data.meta ?? null);
|
||||
getScreenCanvasDrawController().zoomToFitScreen();
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import type { Entity } from './entities/Entity';
|
||||
import {
|
||||
getActiveLayerId,
|
||||
getEntities,
|
||||
resetUndoBaseline,
|
||||
setActiveLayerId,
|
||||
setEntities,
|
||||
setLayers,
|
||||
undo,
|
||||
} from './state';
|
||||
|
||||
const layer = (id: string, isLocked: boolean) => ({
|
||||
id,
|
||||
name: id,
|
||||
isVisible: true,
|
||||
isLocked,
|
||||
});
|
||||
|
||||
describe('setActiveLayerId', () => {
|
||||
it('잠금 도면층(배경)은 현재 도면층이 되지 않고 잠금 아닌 첫 층으로 간다', () => {
|
||||
setLayers([layer('도각', true), layer('계획선', false)]);
|
||||
setActiveLayerId('도각');
|
||||
expect(getActiveLayerId()).toBe('계획선');
|
||||
});
|
||||
|
||||
it('잠금 아닌 층이 하나도 없으면 요청값을 그대로 둔다', () => {
|
||||
setLayers([layer('도각', true)]);
|
||||
setActiveLayerId('도각');
|
||||
expect(getActiveLayerId()).toBe('도각');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetUndoBaseline', () => {
|
||||
// 상태 변경 알림만 window로 나간다 — 노드 환경이라 받는 시늉만 시킨다.
|
||||
beforeAll(() => {
|
||||
(globalThis as { window?: unknown }).window = { dispatchEvent: () => true };
|
||||
});
|
||||
|
||||
it('기준선을 세운 뒤의 되돌리기는 실은 도면까지만 물러난다', () => {
|
||||
const loaded = [{ id: 'a' } as unknown as Entity];
|
||||
setEntities(loaded, false);
|
||||
resetUndoBaseline();
|
||||
setEntities([...loaded, { id: 'b' } as unknown as Entity], true);
|
||||
undo();
|
||||
expect(getEntities()).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -415,6 +415,12 @@ export const setLayers = (newLayers: Layer[], triggerReact = true) => {
|
||||
}
|
||||
};
|
||||
export const setActiveLayerId = (newActiveLayerId: string, triggerReact = true) => {
|
||||
// 잠금 도면층(도각·원지반 등 배경)은 현재 도면층이 될 수 없다 — 새 객체가 배경으로
|
||||
// 떨어지면 그린 즉시 집지도 지우지도 못한다. 호출부(브리지·복구·가져오기·도면층
|
||||
// 패널·리본)마다 거르지 않고 여기 한 곳에서 잠금 아닌 첫 층으로 돌린다.
|
||||
if (layersById.get(newActiveLayerId)?.isLocked) {
|
||||
newActiveLayerId = layers.find((layer) => !layer.isLocked)?.id ?? newActiveLayerId;
|
||||
}
|
||||
activeLayerId = newActiveLayerId;
|
||||
|
||||
if (triggerReact) {
|
||||
@@ -510,6 +516,15 @@ function updateStates(undoState: UndoState) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 지금 객체 목록을 되돌리기 스택의 바닥으로 삼는다. 서버 도면을 실은 직후에 부른다 —
|
||||
* 안 부르면 첫 Ctrl+Z가 도면을 싣기 전의 빈 상태로 되돌아가 화면이 백지가 된다.
|
||||
*/
|
||||
export function resetUndoBaseline() {
|
||||
undoStack.clear(StateVariable.entities);
|
||||
undoStack.push({ variable: StateVariable.entities, value: entities });
|
||||
}
|
||||
|
||||
export function undo() {
|
||||
const undoState = undoStack.undo();
|
||||
if (!undoState) return;
|
||||
|
||||
Reference in New Issue
Block a user