From c0eb280c32e92daf4fc9916565c24445471c6c4e Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 25 Sep 2026 12:01:51 +0900 Subject: [PATCH] =?UTF-8?q?fix(M02):=20=EB=8F=84=EB=A9=B4=20=EC=96=91?= =?UTF-8?q?=EC=8B=9D=20CAD=20=EB=A8=B8=EB=A6=AC=20=EA=B8=80=EC=9D=84=20?= =?UTF-8?q?=E3=80=8C=EB=8F=84=EB=A9=B4=20=EC=96=91=EC=8B=9D=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=E3=80=8D=EC=9C=BC=EB=A1=9C=20=C2=B7=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EB=8B=A8=EC=B6=94=EB=8A=94=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=A8=B8=EB=A6=AC=20=ED=95=9C=20=EA=B3=B3=20=C2=B7?= =?UTF-8?q?=20=EC=9E=90=EB=8F=99=EB=B0=B1=EC=97=85=20=EC=B9=B8=EC=97=90=20?= =?UTF-8?q?=EC=B8=B5(=EA=B3=BC=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8)?= =?UTF-8?q?=EC=9D=84=20=EB=B6=99=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01PxvYb5ufV1kWdBvZbpDfu6 --- .../src/components/QuickAccessBar.tsx | 16 ++++- .../src/integration/aislo-drawing-bridge.ts | 4 ++ B07_DesignDetail/openwebcad/src/state.ts | 7 ++ .../M02_MasterTemplete_Drawing.ts | 64 ++++++++++++------- .../M02_MasterTemplete_UI_Main.ts | 2 + ui_template/cad_host/cad_host.ts | 2 + 6 files changed, 68 insertions(+), 27 deletions(-) diff --git a/B07_DesignDetail/openwebcad/src/components/QuickAccessBar.tsx b/B07_DesignDetail/openwebcad/src/components/QuickAccessBar.tsx index 3050cc48..46476910 100644 --- a/B07_DesignDetail/openwebcad/src/components/QuickAccessBar.tsx +++ b/B07_DesignDetail/openwebcad/src/components/QuickAccessBar.tsx @@ -1,14 +1,23 @@ /** 제목표시줄 + 빠른 실행 도구막대 (AutoCAD 상단 막대) */ -import type { FC } from 'react'; +import { type FC, useEffect, useState } from 'react'; +import { HtmlEvent } from '../App.types'; import { getCommandById } from '../commands/registry'; import { runCommand } from '../commands/run-command'; import { QUICK_ACCESS_COMMANDS } from '../ribbon/ribbon.config'; +import { getHostTitle } from '../state'; -export const QuickAccessBar: FC = () => ( +export const QuickAccessBar: FC = () => { + const [hostTitle, setTitle] = useState(getHostTitle()); + useEffect(() => { + const refresh = () => setTitle(getHostTitle()); + window.addEventListener(HtmlEvent.UPDATE_STATE, refresh); + return () => window.removeEventListener(HtmlEvent.UPDATE_STATE, refresh); + }, []); + return (
Aislo CAD - B07 상세 설계 + {hostTitle}
{QUICK_ACCESS_COMMANDS.map((id) => { @@ -32,3 +41,4 @@ export const QuickAccessBar: FC = () => (
); +}; diff --git a/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts b/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts index 3ffebc53..755abd0f 100644 --- a/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts +++ b/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts @@ -21,6 +21,7 @@ import { setEntities, setFrameEditMode, setHostReadOnly, + setHostTitle, setLayers, } from '../state.ts'; import { toast } from 'react-toastify'; @@ -50,6 +51,8 @@ interface DrawingLoadMessage { recoveryScope?: string; /** 보기 전용으로 싣는가 — 확정본처럼 그리기·수정을 막는다. */ readOnly?: boolean; + /** 제목표시줄 부제 — 없으면 「B07 상세 설계」. */ + hostTitle?: string; } interface DrawingSaveRequestMessage { @@ -193,6 +196,7 @@ export function registerAisloDrawingBridge() { setDesignMeta(event.data.meta ?? null); setFrameEditMode(event.data.frameEdit === true, event.data.frameFields ?? {}); setHostReadOnly(event.data.readOnly === true); + setHostTitle(event.data.hostTitle ?? 'B07 상세 설계'); if (event.data.frameEdit) applyFramePreview(); // 앞 도면에서 켜 둔 그리기 도구를 내린다. 안 내리면 **확정한 도면 위에도** // 그 도구가 계속 그린다 — 읽기 전용은 새 명령만 막기 때문이다(2026-09-01 실측: diff --git a/B07_DesignDetail/openwebcad/src/state.ts b/B07_DesignDetail/openwebcad/src/state.ts index aa51cd94..785555dd 100644 --- a/B07_DesignDetail/openwebcad/src/state.ts +++ b/B07_DesignDetail/openwebcad/src/state.ts @@ -192,6 +192,7 @@ let frameEditMode = false; let frameFields: Record = {}; /** 부모가 보기 전용으로 실었는가 — M02 에서 고칠 권한이 없는 양식을 볼 때. */ let hostReadOnly = false; +let hostTitle = 'B07 상세 설계'; /** * 실은 뒤로 실제 편집이 있었는가. 도면을 바꾸기 전에 부모가 물어보는 근거다 — @@ -496,6 +497,12 @@ export const setDesignMeta = (newMeta: DesignMeta | null) => { }; /** 도각 편집 모드 켜고 끄기 — 자리표 패널의 표시 여부를 가른다 (2026-09-06 사용자 지시). */ /** 부모가 실은 도면을 보기 전용으로 둔다 — 확정본과 같은 막힘(도면을 실을 때마다 새로 정함). */ +/** 제목표시줄 부제 — 부모가 도면을 실을 때 정함(없으면 B07 기본 글). */ +export const getHostTitle = (): string => hostTitle; +export const setHostTitle = (title: string) => { + hostTitle = title; + notifyWindow(HtmlEvent.UPDATE_STATE); +}; export const setHostReadOnly = (readOnly: boolean) => { hostReadOnly = readOnly; notifyWindow(HtmlEvent.UPDATE_STATE); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts b/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts index 5c7d42ea..b3bbbecb 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts @@ -24,12 +24,42 @@ export interface DrawingTemplateDoc extends CadHostDrawing { } export interface DrawingTemplateOptions { - /** [저장] 을 누르면 편집본을 넘긴다. 없으면 [저장] 단추를 두지 않는다. */ + /** 쓰지 않음 — 저장은 페이지 머리 [저장] 한 곳(`getDoc()` 으로 편집본을 가져감 · 판 · 409 흐름). */ onSave?: (doc: DrawingTemplateDoc) => void | Promise; /** 보기 전용 — CAD 그리기·수정 · 작도 영역 · 불러오기가 막힌다. */ readOnly?: boolean; /** 양식 이름 — CAD 자동백업 칸을 양식마다 나눈다(B07 도면 백업과도 안 겹침). */ name?: string; + /** 양식 층 — 같은 이름도 층마다 백업 칸이 갈린다(`m02:층:이름`). */ + layer?: string; + /** 프로젝트 층이면 프로젝트 id — 칸이 `m02:project:프로젝트id:이름` 이 된다. */ + projectId?: string | null; +} + +const RECOVERY_PREFIX = "OPEN_WEB_CAD__RECOVERY__"; + +/** 자동백업 칸 이름 — 층(과 프로젝트)을 붙여 같은 이름의 양식끼리 안 겹치게 한다. */ +function recoveryScopeOf(options: DrawingTemplateOptions): string { + const name = options.name ?? "drawing"; + if (!options.layer) return `m02:${name}`; + const project = options.layer === "project" ? `:${options.projectId ?? ""}` : ""; + return `m02:${options.layer}${project}:${name}`; +} + +/** 층 없는 옛 칸(`m02:이름`)을 새 칸으로 한 번 옮기고 지운다 — 새 칸에 이미 있으면 그대로 두고 지움. */ +function migrateOldRecovery(options: DrawingTemplateOptions, scope: string): void { + const old = `${RECOVERY_PREFIX}m02:${options.name ?? "drawing"}`; + if (!options.layer || old === `${RECOVERY_PREFIX}${scope}`) return; + try { + const value = localStorage.getItem(old); + if (value === null) return; + if (localStorage.getItem(`${RECOVERY_PREFIX}${scope}`) === null) { + localStorage.setItem(`${RECOVERY_PREFIX}${scope}`, value); + } + localStorage.removeItem(old); + } catch { + // 저장소를 못 써도 편집은 됨 + } } export interface DrawingTemplateHandle { @@ -79,7 +109,8 @@ export function mountDrawingTemplate( options: DrawingTemplateOptions = {}, ): DrawingTemplateHandle { const readOnly = options.readOnly === true; - const recoveryScope = `m02:${options.name ?? "drawing"}`; + const recoveryScope = recoveryScopeOf(options); + migrateOldRecovery(options, recoveryScope); let base: DrawingTemplateDoc = doc; const root = document.createElement("div"); @@ -139,7 +170,13 @@ export function mountDrawingTemplate( const cad = createCadHost({ title: "도면 양식" }); const load = (drawing: DrawingTemplateDoc): void => { cad.beginLoading(); - cad.load(drawing, null, !readOnly, {}, { recoveryScope, readOnly }); + cad.load( + drawing, + null, + !readOnly, + {}, + { recoveryScope, readOnly, hostTitle: "도면 양식 편집" }, + ); }; const actions = document.createElement("div"); @@ -184,27 +221,6 @@ export function mountDrawingTemplate( return base; }; - if (options.onSave && !readOnly) { - const onSave = options.onSave; - const saveButton = createButton({ - label: "저장", - variant: "filled", - onClick: () => { - saveButton.disabled = true; - getDoc() - .then((next) => onSave(next)) - .catch((error) => - showToast( - error instanceof Error ? error.message : "양식을 저장하지 못했습니다.", - "error", - ), - ) - .finally(() => (saveButton.disabled = false)); - }, - }); - actions.append(saveButton); - } - toolbar.append(area, fields, actions); root.append(toolbar, cad.element); container.append(root); diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts index 75b39afe..5418cd04 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts @@ -116,6 +116,8 @@ export function createMain(isAdmin: boolean): MainHandle { onSave: () => void doSave(), readOnly, name: at.name, + layer: at.layer, + projectId: at.projectId, }); return; } diff --git a/ui_template/cad_host/cad_host.ts b/ui_template/cad_host/cad_host.ts index 9651c3e8..34e4a674 100644 --- a/ui_template/cad_host/cad_host.ts +++ b/ui_template/cad_host/cad_host.ts @@ -56,6 +56,8 @@ export interface CadLoadExtra { recoveryScope?: string; /** 보기 전용 — 확정본처럼 그리기·수정이 막힌다. */ readOnly?: boolean; + /** CAD 제목표시줄 부제 — 없으면 「B07 상세 설계」. */ + hostTitle?: string; } export interface CadHost {