feat(B07): 목록에서 연 도면 = 탭 — 열기 · 닫기 · 전환 · 이전/다음 · 탭 전환 때 미저장 편집 지킴(82-4)
- 공용 탭 줄: 마지막 한 탭 닫기 없음 · 지금 탭 미저장 점 · 탭 하나만 저장 표시 지움 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
This commit is contained in:
@@ -52,6 +52,7 @@ import { appendStructureEntities } from "./B07_DesignDetail_UI_Cad_Structures";
|
||||
// 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";
|
||||
import { createCadTabs, type CadTab } from "@ui/cad_host/cad_host_tabs";
|
||||
|
||||
/** CAD 앱 수량 패널로 넘기는 설계 컨텍스트 (openwebcad DesignMeta와 동일 형식). */
|
||||
interface DesignMeta {
|
||||
@@ -109,7 +110,9 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
// (2026-09-01 사용자 확정) — 예전에는 이 통지가 확정을 풀어, 되돌리기나 색
|
||||
// 고르기 같은 곁가지 동작에도 확정이 조용히 날아갔다.
|
||||
onChanged: (dirty) => {
|
||||
if (!frameEditor.isEditing()) cadDirty = dirty;
|
||||
if (frameEditor.isEditing()) return;
|
||||
cadDirty = dirty;
|
||||
if (dirty) tabs.markDirty();
|
||||
},
|
||||
onNavigate: (direction) => navigateDrawing(direction),
|
||||
onExport: (fileFormat) => void exportDrawingFile(fileFormat),
|
||||
@@ -248,7 +251,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
|
||||
/** 저장하지 않은 편집이 있으면 묻는다. 버리기로 해야 이동한다. */
|
||||
const mayDiscardEdits = (): boolean => {
|
||||
if (!cadDirty) return true;
|
||||
if (!cadDirty && !tabs.anyDirty()) return true;
|
||||
return window.confirm(
|
||||
"저장하지 않은 편집이 있습니다.\n" +
|
||||
"지금 도면을 바꾸면 편집이 사라집니다. 버리고 이동할까요?\n\n" +
|
||||
@@ -259,7 +262,8 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
/** 도면 전환 중인가 — 겹쳐 누르면 늦게 온 응답이 화면을 덮는다. */
|
||||
let loadInFlight = false;
|
||||
|
||||
const loadDrawing = async (drawing: DesignDrawingItem, index: number) => {
|
||||
/** `stash` = 탭에 받아 둔 저장 안 한 편집본(82-4) — 있으면 그것을 싣고 미저장 그대로 */
|
||||
const loadDrawing = async (drawing: DesignDrawingItem, index: number, stash?: CadDrawing) => {
|
||||
if (!projectId || loadInFlight) return;
|
||||
loadInFlight = true;
|
||||
highlightActive(drawing.id);
|
||||
@@ -271,10 +275,10 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
currentDrawing = drawing;
|
||||
currentIndex = index;
|
||||
currentConfirmed = response.confirmed;
|
||||
cadDirty = false; // 새 도면을 실었다 — 미저장 편집은 이 도면 것이 아니다
|
||||
cadDirty = !!stash; // 새 도면을 실었다 — 미저장은 받아 둔 편집본일 때만
|
||||
applyConfirmButtonState();
|
||||
updateInfoPanel(drawing, response);
|
||||
cad.load(response.drawing, buildMeta(drawing, response, index));
|
||||
cad.load(stash ?? response.drawing, buildMeta(drawing, response, index));
|
||||
if (response.drawing.stale_layout) {
|
||||
showToast(
|
||||
"옛 배치로 확정된 장입니다. [수정]을 누르면 박스를 그림에 1 mm로 붙인 새 배치로 다시 만듭니다.",
|
||||
@@ -290,17 +294,37 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 목록에서 연 도면 = 탭(82-4) — 탭을 바꿀 때 지금 도면을 받아 두고(저장 안 한 편집 그대로) 다음 장을 싣는다.
|
||||
* 새 장 · 지우기는 없음(서버 길 없음) · 닫기는 미저장 탭만 물음 · 마지막 한 탭은 못 닫음.
|
||||
*/
|
||||
const tabs = createCadTabs<CadDrawing>({
|
||||
features: { close: true },
|
||||
capture: async () => ({ drawing: (await requestCadDrawing()).drawing, dirty: cadDirty }),
|
||||
canLeave: () => {
|
||||
if (!frameEditor.isEditing()) return !loadInFlight;
|
||||
showToast("도각 편집을 마친 뒤 다른 도면으로 옮기세요.", "warning");
|
||||
return false;
|
||||
},
|
||||
show: async (tab: CadTab<CadDrawing>) => {
|
||||
const index = drawings.findIndex((item) => item.id === tab.id);
|
||||
if (index >= 0)
|
||||
await loadDrawing(drawings[index], index, tab.dirty ? tab.drawing : undefined);
|
||||
},
|
||||
});
|
||||
const openTab = (drawing: DesignDrawingItem): Promise<boolean> =>
|
||||
tabs.open({ id: drawing.id, name: drawing.label, title: drawing.label });
|
||||
|
||||
const selectDrawing = (drawing: DesignDrawingItem) => {
|
||||
if (drawing === currentDrawing || !mayDiscardEdits()) return;
|
||||
void loadDrawing(drawing, drawings.indexOf(drawing));
|
||||
if (drawing === currentDrawing) return;
|
||||
void openTab(drawing);
|
||||
};
|
||||
|
||||
const navigateDrawing = (direction: "prev" | "next") => {
|
||||
if (currentIndex < 0) return;
|
||||
const target = direction === "prev" ? currentIndex - 1 : currentIndex + 1;
|
||||
if (target < 0 || target >= drawings.length) return;
|
||||
if (!mayDiscardEdits()) return;
|
||||
void loadDrawing(drawings[target], target);
|
||||
void openTab(drawings[target]);
|
||||
};
|
||||
|
||||
const requestCadDrawing = (): Promise<SaveResult> => cad.requestSave();
|
||||
@@ -319,6 +343,8 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
currentConfirmed = true;
|
||||
currentDrawing.confirmed = true;
|
||||
cadDirty = false; // 저장했다
|
||||
const savedTab = tabs.active();
|
||||
if (savedTab) tabs.markClean(savedTab); // 받아 둔 편집본은 저장됨
|
||||
applyConfirmButtonState();
|
||||
drawingCache.delete(currentDrawing.id); // 확정본은 서버 저장분이 정본이다.
|
||||
|
||||
@@ -462,12 +488,15 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
|
||||
drawingPanel.append(infoPanelHost, confirmActions);
|
||||
|
||||
const cadStack = document.createElement("div");
|
||||
cadStack.className = "b07-cad-stack";
|
||||
cadStack.append(tabs.element, cad.element);
|
||||
const layout = createWorkflowLayout({
|
||||
title: L("B07_Design_Title"),
|
||||
steps: workflowSteps(),
|
||||
activeStep: 4,
|
||||
leftPanel: drawingPanel,
|
||||
mainContent: cad.element,
|
||||
mainContent: cadStack,
|
||||
stages: workflowState?.stages,
|
||||
currentStage: workflowState?.current_stage,
|
||||
routes: WORKFLOW_STEP_ROUTES,
|
||||
@@ -492,7 +521,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
const reopen = drawings.findIndex((item) => item.id === reopenDrawingId);
|
||||
reopenDrawingId = undefined;
|
||||
const first = Math.max(reopen, 0);
|
||||
void loadDrawing(drawings[first], first).then(() => {
|
||||
void openTab(drawings[first]).then(() => {
|
||||
// 첫 장을 띄운 뒤에 나머지를 받는다 — 진입 속도를 뺏지 않는다.
|
||||
void prefetchAllDrawings();
|
||||
});
|
||||
|
||||
@@ -328,3 +328,16 @@
|
||||
.b07-plan-tools__remove {
|
||||
padding: 0 var(--spacing-8);
|
||||
}
|
||||
|
||||
/* 목록에서 연 도면 탭 줄 + CAD(82-4) */
|
||||
.b07-cad-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.b07-cad-stack > .cad-host {
|
||||
flex: 1 1 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user