From 52cd7e46cb08cd4559ffbe0e35944c2e1fe09568 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 8 Oct 2026 13:05:17 +0900 Subject: [PATCH] =?UTF-8?q?fix(M02):=20=EB=B3=B4=EB=82=B8=20=ED=91=9C=20?= =?UTF-8?q?=EB=86=93=EA=B8=B0=20=EC=A0=84=20=ED=83=AD=EC=9D=84=20=EB=B0=94?= =?UTF-8?q?=EA=BE=B8=EB=A9=B4=20=EA=B7=B8=20=EC=9E=A5=EC=97=90=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=EB=86=93=EA=B8=B0=20=C2=B7=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=92=A4=20=EC=A7=80=EA=B8=88=20=EC=9E=A5=20=EB=AF=B8=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=ED=91=9C=EC=8B=9C=20=EC=A7=80=EC=9B=80(82-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK --- .../M02_MasterTemplete_Drawing.ts | 40 +++++++++++++++---- resources/tester/test_cad_host_tabs.py | 27 +++++++++++-- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts b/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts index c245f122c..6334ac418 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts @@ -92,7 +92,12 @@ export interface DrawingTemplateHandle { placeTable: (entity: Record, link: string) => void; updateTable: (link: string, cells: unknown[][]) => Promise; /** 다른 장을 같은 CAD 에 실음(82-1) — 백업 칸 · 도각 도번도 그 장 것으로 */ - loadSheet: (drawing: DrawingTemplateDoc, sheet: string, frameNumber?: string) => void; + loadSheet: ( + drawing: DrawingTemplateDoc, + sheet: string, + frameNumber?: string, + after?: () => void, + ) => Promise; destroy: () => void; } @@ -249,7 +254,8 @@ export function mountDrawingTemplate( onHostSave: hostSave, }); let loadSeq = 0; - const load = async (drawing: DrawingTemplateDoc): Promise => { + /** `after` = 싣기를 보낸 바로 뒤(이 싣기가 마지막일 때만) — 표 놓기를 「열린 뒤」로 미룰 때 */ + const load = async (drawing: DrawingTemplateDoc, after?: () => void): Promise => { const mine = ++loadSeq; cad.beginLoading(); const frame = options.frame ? await fetchFrame(frameNumber) : null; @@ -279,6 +285,7 @@ export function mountDrawingTemplate( originMarker: options.originMarker === true, }, ); + after?.(); }; const actions = document.createElement("div"); @@ -346,11 +353,11 @@ export function mountDrawingTemplate( getDoc, placeTable: cad.placeTable, updateTable: cad.updateTable, - loadSheet: (drawing, sheet, number) => { + loadSheet: (drawing, sheet, number, after) => { recoveryScope = recoveryScopeOf(options, sheet); frameNumber = number ?? ""; base = drawing; - void load(drawing); + return load(drawing, after); }, destroy: () => { areaModal?.close(); @@ -417,6 +424,13 @@ export function mountStructureSheets( const more = doc.더한장 ?? []; let cadDirty = false; let listDirty = false; + /** 아직 안 놓은 보낸 표(82-3) — 놓기 전에 탭을 바꾸면 그 장에 다시 걺(놓을 장 = 지금 탭) */ + let placing: { entity: Record; link: string } | null = null; + const placed = (got: DrawingTemplateDoc, link: string): boolean => + got.entities.some((e) => { + const style = (e as { shapeData?: { style?: Record } }).shapeData?.style; + return e.type === "Table" && style?.연결 === link; + }); const report = (): void => options.onChanged?.(cadDirty || listDirty || tabs.anyDirty()); const numberOf = (id: string): string => sheetNumber( @@ -426,13 +440,19 @@ export function mountStructureSheets( const tabs: CadTabs = createCadTabs({ features: readOnly ? {} : { add: true, rename: true, remove: true, reorder: true }, - capture: async () => ({ drawing: await drawing.getDoc(), dirty: cadDirty }), - show: (tab) => { + capture: async () => { + const got = await drawing.getDoc(); + if (placing && placed(got, placing.link)) placing = null; + return { drawing: got, dirty: cadDirty }; + }, + show: async (tab) => { cadDirty = false; - drawing.loadSheet( + // 놓기는 싣기를 보낸 바로 뒤 — cad_host 가 「열린 뒤」로 미룸(먼저 보내면 싣기가 도구를 끔) + await drawing.loadSheet( tab.drawing ?? { entities: [], layers: [BLANK_LAYER] }, tab.id === FIRST_TAB ? "" : tab.id, numberOf(tab.id), + () => placing && drawing.placeTable(placing.entity, placing.link), ); report(); }, @@ -491,6 +511,7 @@ export function mountStructureSheets( 도면: tab.drawing ?? { format: 6, entities: [], layers: [{ ...BLANK_LAYER }] }, })); listDirty = false; + cadDirty = false; // 받기(requestSave)가 CAD 미저장도 내림 — 다음 고침이 다시 알림 tabs.markClean(undefined, true); return { 도면: first?.drawing ?? doc.도면, @@ -501,7 +522,10 @@ export function mountStructureSheets( const id = tabs.active()?.id ?? FIRST_TAB; return id === FIRST_TAB ? "" : id; }, - placeTable: drawing.placeTable, + placeTable: (entity, link) => { + placing = { entity, link }; + drawing.placeTable(entity, link); + }, // 실린 장은 CAD · 안 실린 장은 받아 둔 편집본에서(82-3) updateTable: async (link, cells) => { const now = tabs.active(); diff --git a/resources/tester/test_cad_host_tabs.py b/resources/tester/test_cad_host_tabs.py index e9ee764b8..975ab117f 100644 --- a/resources/tester/test_cad_host_tabs.py +++ b/resources/tester/test_cad_host_tabs.py @@ -103,13 +103,14 @@ const tabsMod = load("ui_template/cad_host/cad_host_tabs.ts", { "@ui/ui_template // ⑤ M02 여러 장 — 가짜 CAD(requestSave = 지금 실린 도면 + 편집) const cadLoads = []; + const ev = []; let inCad = null; const fakeCad = { element: node("div"), beginLoading() {}, - load: (drawing, meta, frameEdit, fields, extra) => (cadLoads.push(extra.recoveryScope), (inCad = drawing)), + load: (drawing, meta, frameEdit, fields, extra) => (ev.push("load"), cadLoads.push(extra.recoveryScope), (inCad = drawing)), requestSave: async () => ({ drawing: inCad, quantityTable: null }), - placeTable() {}, + placeTable: (entity, link) => ev.push("place " + link), updateTable: async () => 1, destroy() {}, }; @@ -142,6 +143,18 @@ const tabsMod = load("ui_template/cad_host/cad_host_tabs.ts", { "@ui/ui_template const third = bar.kids.find((k) => k.dataset.tabId && k.dataset.active === "true").dataset.tabId; await bar.kids.find((k) => k.dataset.tabId === "#1").on.click(); await new Promise((r) => setTimeout(r, 5)); + // 보낸 표를 놓기 전에 탭을 바꾸면 그 장에서 다시 놓기(싣기 뒤) · 놓은 뒤에는 안 걺 + const tabNodes = () => bar.kids.filter((k) => k.dataset.tabId); + ev.length = 0; + sheets.placeTable(table("T9"), "T9"); + await tabNodes()[1].on.click(); + await new Promise((r) => setTimeout(r, 5)); + out.replace = ev.slice(); + inCad = { ...inCad, entities: [...inCad.entities, table("T9")] }; // 놓음 + ev.length = 0; + await tabNodes()[0].on.click(); + await new Promise((r) => setTimeout(r, 5)); + out.placedOnce = ev.slice(); out.updated = await sheets.updateTable("T2", [[{ text: "새" }]]); const got = await sheets.getDoc(); out.m02 = { @@ -210,15 +223,21 @@ def test_B07_목록에서_연_도면이_탭_미저장_편집_지킴(): def test_여러_장_문서_장_도번_백업_칸(got): m02 = got["m02"] assert m02["first"] == 2 - assert m02["more"] == [["단면", 1], ["3장", 1]] + assert m02["more"] == [["단면", 2], ["3장", 1]] # 단면에 T9 표를 놓음 assert m02["newId"] is True and m02["active"] == "" assert m02["scopes"][0] == "m02:system:rv" and m02["scopes"][1].startswith("m02:system:rv#s") assert m02["number"] == "SD-1-3" q = "/api/m02/structure-frame?number=" - assert m02["frames"] == [q + "SD-1", q + "SD-1-3"] # 장 도번마다 한 번(1장은 다시 안 받음) + # 장 도번마다 한 번(같은 도번은 다시 안 받음) + assert m02["frames"] == [q + "SD-1", q + "SD-1-3", q + "SD-1-2"] assert m02["dirty"] is True # 장 더함 = 저장 안 한 고침 +def test_놓기_전_탭을_바꾸면_그_장에_다시_놓기(got): + assert got["replace"] == ["place T9", "load", "place T9"] # 싣기 뒤에 다시 + assert got["placedOnce"] == ["load"] + + def test_안_실린_장_표도_갈음(got): assert got["updated"] == 2 # 받아 둔 둘째 장 1 + 실린 장(가짜 CAD) 1 assert got["m02"]["t2"] == "새"