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 {