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:
@@ -0,0 +1,44 @@
|
||||
/* =============================================================================
|
||||
* B07_DesignDetail_UI_GuideToggle.ts
|
||||
* 횡단 장 안내선 보이기 / 숨기기 단추(PLAN 180-3) — 좌측 「횡단도」 묶음 배치 칸 줄에 붙음.
|
||||
*
|
||||
* 끄는 것 = 화면 전용 안내선 둘(붉은 넘침 칸 틀 + 파란 점선 작도 영역) · 인쇄되는 칸선은 그대로
|
||||
* 상태 = 이 화면을 쓰는 동안만(저장 안 함) · 장을 바꾸거나 화면을 다시 세워도 이어짐(모듈 값)
|
||||
* 숨김이면 「n 칸 넘침」 토스트도 안 띄움 · CAD 에는 `cad.setGuideVisible` 로 알림
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton } from "@ui/ui_template_elements";
|
||||
|
||||
/** 안내선 보임 — 모듈 값(화면 다시 세워도 유지 · 새로 고침하면 보임으로) */
|
||||
let shown = true;
|
||||
|
||||
export const guideShown = (): boolean => shown;
|
||||
|
||||
const labelOf = (visible: boolean): string => (visible ? "안내선 숨기기" : "안내선 보이기");
|
||||
|
||||
/**
|
||||
* 단추 하나 — 누르면 보임 ↔ 숨김 · `apply(보임)` 로 CAD 에 알림.
|
||||
* 장이 아닌 도면이면 꺼짐 — 부르는 쪽이 `button.disabled` 로 정함.
|
||||
*/
|
||||
export function buildGuideToggle(apply: (visible: boolean) => void): HTMLButtonElement {
|
||||
const button = createButton({
|
||||
label: labelOf(shown),
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => {
|
||||
shown = !shown;
|
||||
paint();
|
||||
apply(shown);
|
||||
},
|
||||
});
|
||||
const paint = (): void => {
|
||||
const label = button.children[button.children.length - 1];
|
||||
if (label) label.textContent = labelOf(shown);
|
||||
button.setAttribute("aria-pressed", String(!shown));
|
||||
button.title = shown
|
||||
? "점선 작도 영역 · 붉은 넘침 틀을 화면에서 숨김(인쇄 칸선은 그대로)"
|
||||
: "숨긴 점선 작도 영역 · 붉은 넘침 틀을 다시 보임";
|
||||
};
|
||||
paint();
|
||||
return button;
|
||||
}
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
type QuantityTable,
|
||||
} from "./B07_DesignDetail_Api_Fetch";
|
||||
import { appendStructureEntities } from "./B07_DesignDetail_UI_Cad_Structures";
|
||||
import { buildGuideToggle, guideShown } from "./B07_DesignDetail_UI_GuideToggle";
|
||||
// CAD iframe 부모 흐름 · 도각 편집은 M02 와 한 벌로 공용에 둔다(PLAN 10-3).
|
||||
import { createCadHost, type CadSaveResult } from "@ui/cad_host/cad_host";
|
||||
import { createFrameTemplateEditor } from "@ui/cad_host/cad_host_frame_edit";
|
||||
@@ -125,6 +126,10 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
onGuideChange: (area) => frameEditor.guideChanged(area),
|
||||
});
|
||||
|
||||
// 180-3 안내선 보이기 / 숨기기 — 숨김은 화면을 다시 세워도 이어 줌(`…_UI_GuideToggle` 모듈 값)
|
||||
if (!guideShown()) cad.setGuideVisible(false);
|
||||
const guideToggle = buildGuideToggle((visible) => cad.setGuideVisible(visible));
|
||||
|
||||
let currentDrawing: DesignDrawingItem | undefined;
|
||||
let currentIndex = -1;
|
||||
let currentConfirmed = false;
|
||||
@@ -154,10 +159,16 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
/** 장 배치 칸 자리(153-1) — 횡단도 묶음 맨 아래 · 정보 칸을 다시 그려도 남음. */
|
||||
const crossLayoutSlot = document.createElement("div");
|
||||
crossLayoutSlot.className = "b07-cross-layout-slot";
|
||||
// 배치 칸을 못 받아도 안내선 단추는 남음 · 받으면 같은 줄 끝에 붙음
|
||||
const guideOnly = document.createElement("div");
|
||||
guideOnly.className = "b07-cross-layout";
|
||||
guideOnly.append(guideToggle);
|
||||
crossLayoutSlot.append(guideOnly);
|
||||
let crossLayoutRequest: Promise<CrossLayout> | undefined;
|
||||
|
||||
/** 장 배치 칸(140-3) — 적용 = 서버 쓰기 → 목록 다시 · 같은 장 번호를 다시 엶. 장 아니면 기본값 · [이 장] 꺼짐. */
|
||||
const fillCrossLayout = async (drawing: DesignDrawingItem) => {
|
||||
guideToggle.disabled = !isCrossSheet(drawing); // 장의 안내선만
|
||||
if (!projectId) return;
|
||||
let layout: CrossLayout;
|
||||
try {
|
||||
@@ -185,15 +196,15 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
};
|
||||
crossLayoutSlot.replaceChildren(
|
||||
buildCrossLayoutControls(
|
||||
(number && layout.sheets[String(number)]) || layout.default,
|
||||
number
|
||||
? (grid) => void save({ ...layout, sheets: { ...layout.sheets, [String(number)]: grid } })
|
||||
: null,
|
||||
(grid) => void save({ default: grid, sheets: {} }),
|
||||
),
|
||||
const controls = buildCrossLayoutControls(
|
||||
(number && layout.sheets[String(number)]) || layout.default,
|
||||
number
|
||||
? (grid) => void save({ ...layout, sheets: { ...layout.sheets, [String(number)]: grid } })
|
||||
: null,
|
||||
(grid) => void save({ default: grid, sheets: {} }),
|
||||
);
|
||||
controls.append(guideToggle);
|
||||
crossLayoutSlot.replaceChildren(controls);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -335,7 +346,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
const guide = response.guide ?? null;
|
||||
const meta = buildMeta(drawing, response, index);
|
||||
cad.load(stash ?? response.drawing, meta, false, {}, { guide });
|
||||
if (guide?.marks?.length) {
|
||||
if (guideShown() && guide?.marks?.length) {
|
||||
showToast(`${guide.marks.length} 칸 넘침 — 열 · 행을 줄일 것`, "warning");
|
||||
}
|
||||
if (response.drawing.stale_layout) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/* 180-3 B07 안내선 보이기 / 숨기기 단추 헬퍼 — `…_UI_GuideToggle.ts` 를 Node 로 돌려 JSON 한 벌로.
|
||||
* 공용 단추(`ui_template_elements`)는 진짜 · 화면 그릇만 흉내(helper_153 꼴).
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const cache = new Map();
|
||||
|
||||
class FakeNode {
|
||||
constructor(tag) {
|
||||
this.tagName = tag.toUpperCase();
|
||||
this.children = [];
|
||||
this.dataset = {};
|
||||
this.attrs = {};
|
||||
this.listeners = {};
|
||||
this._text = "";
|
||||
this._classes = new Set();
|
||||
this.disabled = false;
|
||||
this.title = "";
|
||||
const self = this;
|
||||
this.classList = {
|
||||
add: (...names) => names.forEach((n) => self._classes.add(n)),
|
||||
contains: (name) => self._classes.has(name),
|
||||
};
|
||||
}
|
||||
set className(value) {
|
||||
this._classes = new Set(String(value).split(/\s+/).filter(Boolean));
|
||||
}
|
||||
get className() {
|
||||
return [...this._classes].join(" ");
|
||||
}
|
||||
set textContent(value) {
|
||||
this._text = String(value);
|
||||
this.children = [];
|
||||
}
|
||||
get textContent() {
|
||||
return this._text + this.children.map((c) => c.textContent).join("");
|
||||
}
|
||||
setAttribute(key, value) {
|
||||
this.attrs[key] = String(value);
|
||||
}
|
||||
append(...kids) {
|
||||
kids.forEach((k) => this.children.push(k));
|
||||
}
|
||||
addEventListener(type, handler) {
|
||||
(this.listeners[type] ||= []).push(handler);
|
||||
}
|
||||
click() {
|
||||
if (!this.disabled) (this.listeners.click || []).forEach((handler) => handler({}));
|
||||
}
|
||||
}
|
||||
globalThis.document = { createElement: (tag) => new FakeNode(tag) };
|
||||
|
||||
function loadTs(file) {
|
||||
const full = path.resolve(file);
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (request === "@ui/ui_template_elements")
|
||||
return loadTs(path.join(ROOT, "ui_template", "ui_template_elements.ts"));
|
||||
if (request.startsWith(".")) {
|
||||
const target = path.join(path.dirname(full), `${request}.ts`);
|
||||
return fs.existsSync(target) ? loadTs(target) : {};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
cache.set(full, box.exports);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const { buildGuideToggle, guideShown } = loadTs(
|
||||
path.join(ROOT, "B07_DesignDetail", "B07_DesignDetail_UI_GuideToggle.ts"),
|
||||
);
|
||||
|
||||
const sent = [];
|
||||
const button = buildGuideToggle((visible) => sent.push(visible));
|
||||
const view = () => ({
|
||||
text: button.textContent,
|
||||
cls: button.className,
|
||||
pressed: button.attrs["aria-pressed"],
|
||||
shown: guideShown(),
|
||||
});
|
||||
const out = { first: view() };
|
||||
button.click();
|
||||
out.hidden = view();
|
||||
button.click();
|
||||
out.again = view();
|
||||
button.click();
|
||||
out.last = view();
|
||||
// 화면을 다시 세워 새 단추를 만들어도 숨김 유지(모듈 값)
|
||||
const rebuilt = buildGuideToggle((visible) => sent.push(visible));
|
||||
out.rebuilt = { text: rebuilt.textContent, pressed: rebuilt.attrs["aria-pressed"] };
|
||||
out.sent = sent;
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -0,0 +1,76 @@
|
||||
"""180-3 B07 횡단 장 안내선 보이기 / 숨기기 단추.
|
||||
|
||||
- 끄는 것 = 점선 작도 영역 + 붉은 넘침 틀(화면 전용) · 인쇄 칸선 · 도면 개체는 그대로
|
||||
- 단추 = 배치 칸 줄 · ghost sm · 글 「안내선 숨기기」 ↔ 「안내선 보이기」 · 상태는 모듈 값(저장 안 함)
|
||||
- 숨김이면 넘침 토스트도 안 띄움 · 메시지 한 줄(`aislo:b08:guide-visible`) · cad_host `setGuideVisible`
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = Path(HERE).parents[1]
|
||||
B07 = ROOT / "B07_DesignDetail"
|
||||
|
||||
|
||||
def _read(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_단추_글_누름_상태_이어짐():
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_180_3_b07_guide_toggle.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
out = json.loads(proc.stdout)
|
||||
assert out["first"] == {
|
||||
"text": "안내선 숨기기",
|
||||
"cls": "ui-btn ui-btn--ghost ui-btn--sm",
|
||||
"pressed": "false",
|
||||
"shown": True,
|
||||
}
|
||||
assert out["hidden"]["text"] == "안내선 보이기" and out["hidden"]["pressed"] == "true"
|
||||
assert out["hidden"]["shown"] is False
|
||||
assert out["again"]["text"] == "안내선 숨기기" and out["again"]["shown"] is True
|
||||
assert out["last"]["shown"] is False
|
||||
assert out["sent"] == [False, True, False] # CAD 에 알린 값
|
||||
# 화면을 다시 세워 새 단추를 만들어도 숨김 유지
|
||||
assert out["rebuilt"] == {"text": "안내선 보이기", "pressed": "true"}
|
||||
|
||||
|
||||
def test_메시지_짝_호스트_CAD():
|
||||
host = _read(ROOT / "ui_template" / "cad_host" / "cad_host.ts")
|
||||
bridge = _read(B07 / "openwebcad" / "src" / "integration" / "aislo-drawing-bridge.ts")
|
||||
guide = _read(B07 / "openwebcad" / "src" / "integration" / "guide-area.ts")
|
||||
name = "aislo:b08:guide-visible"
|
||||
assert f'"{name}"' in host and f"'{name}'" in bridge # 짝 메시지 이름 같음
|
||||
assert "setGuideVisible: (visible) => postAfterLoad(" in host
|
||||
assert "setGuideVisible: (visible: boolean) => void;" in host
|
||||
assert "return setGuideVisible(guideMessage.visible);" in bridge
|
||||
assert "if (!guide || !guideVisible) return;" in guide # 그리기 거름
|
||||
assert "!guideVisible) return;" in guide # 끌기 집기도 거름
|
||||
# 싣기(setGuide)가 보임 값을 건드리지 않음
|
||||
body = guide[
|
||||
guide.index("export function setGuide(") : guide.index("export function setGuideArea")
|
||||
]
|
||||
assert "guideVisible" not in body
|
||||
|
||||
|
||||
def test_페이지_잇기_토스트_거름_장만():
|
||||
page = _read(B07 / "B07_DesignDetail_UI_Page.ts")
|
||||
assert "if (guideShown() && guide?.marks?.length) {" in page # 숨김 = 넘침 토스트 없음
|
||||
assert "cad.setGuideVisible(visible)" in page
|
||||
assert "if (!guideShown()) cad.setGuideVisible(false);" in page # 다시 세운 화면에 이어 줌
|
||||
assert "controls.append(guideToggle);" in page # 배치 칸 줄 끝
|
||||
assert "guideToggle.disabled = !isCrossSheet(drawing);" in page # 장만
|
||||
# 단추는 인쇄 칸선(도각 층) · 도면 개체를 건드리지 않음 — 파이썬 쪽 변경 없음
|
||||
assert not re.search(
|
||||
r"guide.?visible", _read(B07 / "B07_DesignDetail_Engine_Cad_Sheet.py"), re.I
|
||||
)
|
||||
@@ -36,6 +36,7 @@ const CAD_PLACE_CANCELLED_MESSAGE = "aislo:b08:place-cancelled";
|
||||
/** 작도 영역 가이드(169-1 · 169-3) — 짝 = CAD `integration/guide-area.ts` */
|
||||
const CAD_GUIDE_AREA_MESSAGE = "aislo:b08:guide-area";
|
||||
const CAD_GUIDE_CHANGED_MESSAGE = "aislo:b08:guide-changed";
|
||||
const CAD_GUIDE_VISIBLE_MESSAGE = "aislo:b08:guide-visible";
|
||||
|
||||
/** 작도 영역 [왼쪽 x, 아래 y, 오른쪽 x, 위 y] — 도면 좌표 mm */
|
||||
export type CadGuideArea = [number, number, number, number];
|
||||
@@ -128,6 +129,8 @@ export interface CadHost<D extends CadHostDrawing, M, Q> {
|
||||
updateTable: (link: string, cells: unknown[][], preview?: boolean) => Promise<number>;
|
||||
/** 가이드 사각형만 갈기(숫자 칸 고침 · 169-3) — 도면은 다시 안 실음. */
|
||||
setGuideArea: (area: CadGuideArea) => void;
|
||||
/** 안내선(점선 작도 영역 + 붉은 넘침 틀) 보이기 / 숨기기(180-3) — 싣기를 거쳐도 이어짐 · 도면에 안 섞임. */
|
||||
setGuideVisible: (visible: boolean) => void;
|
||||
/** 메시지 듣기를 멈춘다 — 페이지를 떠날 때. */
|
||||
destroy: () => void;
|
||||
}
|
||||
@@ -360,6 +363,7 @@ export function createCadHost<D extends CadHostDrawing, M = unknown, Q = unknown
|
||||
placeTable,
|
||||
updateTable,
|
||||
setGuideArea: (area) => postAfterLoad({ type: CAD_GUIDE_AREA_MESSAGE, area }),
|
||||
setGuideVisible: (visible) => postAfterLoad({ type: CAD_GUIDE_VISIBLE_MESSAGE, visible }),
|
||||
destroy: () => {
|
||||
stopLoadWatchdog();
|
||||
window.removeEventListener("message", onMessage);
|
||||
|
||||
Reference in New Issue
Block a user