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 {