feat(b07): 횡단 장 안내선 보이기 / 숨기기 단추 — 점선 작도 영역 + 붉은 넘침 틀 · 숨김이면 넘침 토스트 없음(180-3)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
2026-10-10 11:56:37 +09:00
co-authored by Claude Sonnet 5.5
parent be57e54fff
commit 1ceee943bc
8 changed files with 303 additions and 15 deletions
@@ -35,7 +35,7 @@ import { runCommandInput } from '../commands/run-command.ts';
import { setRecoveryScope } from '../helpers/autosave.ts';
import { registerBoxResizeDrag } from './box-resize-drag.ts';
import { handleTableMessage } from './aislo-table-bridge.ts';
import { registerGuideDrag, setGuide, setGuideArea } from './guide-area.ts';
import { registerGuideDrag, setGuide, setGuideArea, setGuideVisible } from './guide-area.ts';
export const AISLO_DRAWING_LOAD_MESSAGE = 'aislo:b08:load-drawing';
export const AISLO_DRAWING_READY_MESSAGE = 'aislo:b08:drawing-ready';
@@ -50,6 +50,7 @@ export const AISLO_DRAWING_HOST_SAVE_MESSAGE = 'aislo:b08:host-save';
/** 작도 영역 가이드(169-1 · 169-3) — 부모 숫자 칸 → CAD 갈기 · CAD 끌기 → 부모 숫자 칸 */
export const AISLO_GUIDE_AREA_MESSAGE = 'aislo:b08:guide-area';
export const AISLO_GUIDE_CHANGED_MESSAGE = 'aislo:b08:guide-changed';
export const AISLO_GUIDE_VISIBLE_MESSAGE = 'aislo:b08:guide-visible';
interface DrawingLoadMessage {
type: typeof AISLO_DRAWING_LOAD_MESSAGE;
@@ -201,8 +202,10 @@ export function registerAisloDrawingBridge() {
window.addEventListener('message', async (event: MessageEvent<unknown>) => {
if (event.origin !== window.location.origin || event.source !== window.parent) return;
if (await handleTableMessage(event.data, notifyParent)) return; // 표 놓기 · 갈기(M02 41장)
const guideMessage = event.data as { type?: string; area?: unknown };
const guideMessage = event.data as { type?: string; area?: unknown; visible?: unknown };
if (guideMessage?.type === AISLO_GUIDE_AREA_MESSAGE) return setGuideArea(guideMessage.area);
if (guideMessage?.type === AISLO_GUIDE_VISIBLE_MESSAGE)
return setGuideVisible(guideMessage.visible);
const message = event.data as Partial<DrawingSaveRequestMessage>;
if (message.type === AISLO_DRAWING_SAVE_REQUEST_MESSAGE) {
try {
@@ -1,13 +1,17 @@
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import {
GUIDE_MIN_MM,
type GuideArea,
dragGuideArea,
drawGuide,
getGuide,
isGuideVisible,
pickGuideHandle,
setGuide,
setGuideArea,
setGuideVisible,
} from './guide-area.ts';
import type { ScreenCanvasDrawController } from '../drawControllers/screenCanvas.drawController.ts';
const AREA: GuideArea = [42, 47, 812, 567];
@@ -40,4 +44,39 @@ describe('작도 영역 가이드(169-1 · 169-3)', () => {
expect(dragGuideArea(AREA, 's', { x: 0, y: 9999 })).toEqual([42, 567 - GUIDE_MIN_MM, 812, 567]);
expect(dragGuideArea(AREA, 'e', { x: -100, y: 0 })).toEqual([42, 47, 42 + GUIDE_MIN_MM, 567]);
});
it('안내선 보이기 / 숨기기(180-3) — 숨김이면 점선 영역 · 붉은 틀 안 그림 · 싣기를 거쳐도 이어짐', () => {
const lines = vi.fn();
const controller = {
setOpacity: vi.fn(),
setLineStyles: vi.fn(),
worldToTarget: (point: unknown) => point,
drawLineScreen: lines,
fillRectScreen: vi.fn(),
} as unknown as ScreenCanvasDrawController;
const guide = { area: AREA, marks: [[0, 0, 5, 5] as GuideArea], editable: false };
setGuide(guide);
expect(isGuideVisible()).toBe(true); // 기본 = 보임
drawGuide(controller);
expect(lines).toHaveBeenCalledTimes(8); // 점선 영역 4 변 + 붉은 틀 4 변
setGuideVisible(false);
lines.mockClear();
drawGuide(controller);
expect(lines).not.toHaveBeenCalled();
setGuide(guide); // 장을 다시 실어도 숨김 유지
setGuideArea([0, 0, 100, 50]);
drawGuide(controller);
expect(isGuideVisible()).toBe(false);
expect(lines).not.toHaveBeenCalled();
expect(getGuide()?.marks).toHaveLength(1); // 값은 그대로 — 그리기만 거름
setGuideVisible(true);
drawGuide(controller);
expect(lines).toHaveBeenCalledTimes(8);
setGuideVisible('x'); // 어긋난 값 = 보임
expect(isGuideVisible()).toBe(true);
setGuide(null);
});
});
@@ -7,7 +7,8 @@ import { getCanvas, getScreenCanvasDrawController } from '../state.ts';
*
* 도면 개체가 아니라 그리기 겹판(기준점 십자와 같은 자리)이라 저장 · DXF · 인쇄에 안 섞인다.
* `editable` 이면 모서리 · 변을 끌어 영역을 고치고, 고친 값은 부모(숫자 칸)에 알린다.
* 부모 짝 = `ui_template/cad_host/cad_host.ts` `guide` 곁값 · `setGuideArea` · `onGuideChange`.
* 부모 짝 = `ui_template/cad_host/cad_host.ts` `guide` 곁값 · `setGuideArea` · `onGuideChange` ·
* `setGuideVisible`(180-3 — 점선 영역 + 붉은 넘침 틀 보이기 / 숨기기 · 도각 칸선은 도면 개체라 그대로).
*/
/** [왼쪽 x, 아래 y, 오른쪽 x, 위 y] — 도면 좌표 mm */
@@ -32,6 +33,8 @@ const GRAB_PIXELS = 9;
export const GUIDE_MIN_MM = 10;
let guide: Guide | null = null;
/** 안내선(점선 영역 + 붉은 틀) 보임 — 싣기 때 안 지움 · 숨김이면 그리지도 · 집지도 않음 */
let guideVisible = true;
const isArea = (value: unknown): value is GuideArea =>
Array.isArray(value) &&
@@ -42,6 +45,13 @@ const isArea = (value: unknown): value is GuideArea =>
export const getGuide = (): Guide | null => guide;
export const isGuideVisible = (): boolean => guideVisible;
/** 부모 단추(180-3) — `true` 만 보임 · 그 밖 값은 보임으로 */
export function setGuideVisible(visible: unknown): void {
guideVisible = visible !== false;
}
/** 싣기 때 — 어긋난 값이면 가이드 없음 */
export function setGuide(next: unknown): void {
const candidate = next as Partial<Guide> | null | undefined;
@@ -103,7 +113,7 @@ function strokeRect(drawController: ScreenCanvasDrawController, area: GuideArea)
/** 그리기 겹판 — 엔티티 장면 위에 매 장 그림(`helpers/draw.ts`) */
export function drawGuide(drawController: ScreenCanvasDrawController): void {
if (!guide) return;
if (!guide || !guideVisible) return;
drawController.setOpacity(0.9);
drawController.setLineStyles(false, false, GUIDE_COLOR, 1, [8, 5]);
strokeRect(drawController, guide.area);
@@ -142,7 +152,7 @@ export function registerGuideDrag(notify: (area: GuideArea) => void): void {
window.addEventListener(
'mousedown',
(event: MouseEvent) => {
if (event.button !== 0 || event.target !== canvas || !guide?.editable) return;
if (event.button !== 0 || event.target !== canvas || !guide?.editable || !guideVisible) return;
const world = worldAt(event);
if (!world) return;
const tolerance = GRAB_PIXELS / getScreenCanvasDrawController().getScreenScale();