diff --git a/M02_MasterTemplete/M02_MasterTemplete_AreaDrawing.ts b/M02_MasterTemplete/M02_MasterTemplete_AreaDrawing.ts index 491ddcbf6..d9f882959 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_AreaDrawing.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_AreaDrawing.ts @@ -1,7 +1,7 @@ /* ============================================================================= * M02_MasterTemplete_AreaDrawing.ts * 면적산출 상세 오른쪽 「도면」 패널 몸(PLAN 47-6) — 그 열 구조물 도면을 보기 · 고르기(고치기 없음). - * 처음 펼 때 읽고 그림 — 접힌 채 iframe 을 띄우지 않음. + * 처음 펼 때 읽고 그림 — 접힌 채 iframe 을 띄우지 않음. 여러 장 = 공용 탭 줄 고르기만(105-3 · 보내기는 지금 장). * 83-3 — 머리 [면적산출로 보내기] = CAD 에 고른 범위 요청(`aislo:b08:export-table-request`) → 받은 표 자료 * (`aislo:b08:export-table` · CAD `integration/aislo-table-export.ts`)를 `onTable` 로(새 시트 만들기는 부른 쪽). * CAD 명령 TOAREA 로 보낸 것도 같은 길. 고칠 수 없는 면적(읽기 층)이면 단추 없음. @@ -9,7 +9,7 @@ import { createButton, el } from "@ui/ui_template_elements"; import { readTemplate, type Layer, type StructureDoc } from "./M02_MasterTemplete_Api_Fetch"; -import { mountDrawingTemplate, type DrawingTemplateDoc } from "./M02_MasterTemplete_Drawing"; +import { mountStructureSheets } from "./M02_MasterTemplete_Drawing"; import type { DrawingTable } from "./M02_MasterTemplete_AreaFromDrawing"; const TEXT = { @@ -96,7 +96,8 @@ export function createAreaDrawing( if (!doc?.도면) return note(TEXT.none); stage.replaceChildren(); // 보기 전용 + 고르기(누름 · 끌기) — 미리보기(끌기 = 화면 옮기기)는 고르기가 꺼져 있어 안 씀 - view = mountDrawingTemplate(stage, doc.도면 as unknown as DrawingTemplateDoc, { + const sheets = doc as unknown as Parameters[1]; + view = mountStructureSheets(stage, sheets, { readOnly: true, name: `preview:${column}`, layer, @@ -105,6 +106,7 @@ export function createAreaDrawing( noArea: true, frame: true, noImport: true, + pickTabs: true, }); frame = stage.querySelector("iframe"); if (frame) hideChrome(frame); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Drawing.css b/M02_MasterTemplete/M02_MasterTemplete_Drawing.css index 488d56cee..2aa856e6a 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Drawing.css +++ b/M02_MasterTemplete/M02_MasterTemplete_Drawing.css @@ -87,25 +87,6 @@ box-shadow: 0 4px 16px rgb(0 0 0 / 25%); } -/* 카드 미리보기 「장 n」(82-2) — 1장만 보이는 여러 장 도면 */ -.m02-drawing:has(> .m02-drawing__sheets) { - position: relative; -} - -.m02-drawing__sheets { - position: absolute; - z-index: 3; - top: 6px; - right: 8px; - padding: 1px 8px; - border: 1px solid var(--color-border); - border-radius: 10px; - background: var(--color-surface-raised); - color: var(--color-text-secondary); - font-size: 11px; - pointer-events: none; -} - /* 탭 줄(82-1) — 그림 칸에 붙임 */ .m02-drawing > .cad-tabs { margin-bottom: calc(-1 * var(--spacing-8)); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts b/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts index 6334ac418..537438d55 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts @@ -58,6 +58,8 @@ export interface DrawingTemplateOptions { frameNumber?: string; /** 탭 줄(82-1) — 그림 칸 바로 위에 놓음 */ tabs?: HTMLElement; + /** 여러 장 탭 줄은 고르기만(105-3 카드 · 면적 도면 패널) — 추가 · 이름 · 지우기 · 끌기 없음 */ + pickTabs?: boolean; } const RECOVERY_PREFIX = "OPEN_WEB_CAD__RECOVERY__"; @@ -439,7 +441,8 @@ export function mountStructureSheets( ); const tabs: CadTabs = createCadTabs({ - features: readOnly ? {} : { add: true, rename: true, remove: true, reorder: true }, + features: + readOnly || options.pickTabs ? {} : { add: true, rename: true, remove: true, reorder: true }, capture: async () => { const got = await drawing.getDoc(); if (placing && placed(got, placing.link)) placing = null; @@ -485,7 +488,7 @@ export function mountStructureSheets( ); retitle(); tabs.set(tabs.tabs(), FIRST_TAB); // 풍선 글을 그림 - if (readOnly && !more.length) tabs.element.hidden = true; // 볼 장이 하나 — 줄 없음 + if ((readOnly || options.pickTabs) && !more.length) tabs.element.hidden = true; // 볼 장이 하나 — 줄 없음 const drawing = mountDrawingTemplate(container, doc.도면, { ...options, diff --git a/M02_MasterTemplete/M02_MasterTemplete_DrawingCard.ts b/M02_MasterTemplete/M02_MasterTemplete_DrawingCard.ts index cf2d79135..0f9e2c655 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_DrawingCard.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_DrawingCard.ts @@ -2,7 +2,8 @@ * M02_MasterTemplete_DrawingCard.ts * 항목 카드 「도면 정보」 미리보기 — 고칠 수 있으면 메뉴(제목줄 · 리본 · 패널 · 상태줄) 없이 * **단축키로 그리기 · 고치기**(전체 편집기 [도면 수정]과 같은 웹캐드 · 같은 단축키) · 카드 [저장] - * (전체 편집기와 같은 저장 길 = 그 항목 구조물 도면 파일 `도면` 칸만 갈아 끼움 · PLAN 31-4). + * (전체 편집기와 같은 저장 길 = 그 항목 구조물 도면 파일 `도면` · `더한장` 칸을 갈아 끼움 · PLAN 31-4). + * 여러 장은 공용 탭 줄(고르기만 · 105-3) — 저장은 지금 탭이 아니라 문서 전체(`getDoc`). * [저장] · ? · 점(●) · 알림 글은 공용 카드 틀(`M02_MasterTemplete_CardShell` · 31-7) — 여기는 CAD 와 저장 길만. * 고칠 수 없으면(읽기 층 · 저장 길 없는 자료 자리) 예전처럼 보기만. * CAD 앱은 안 고침 — 메뉴는 같은 출처 iframe 에 스타일을 얹어 숨김. @@ -11,10 +12,9 @@ import type { StructureDoc } from "./M02_MasterTemplete_Api_Fetch"; import type { SaveControl } from "./M02_MasterTemplete_CardShell"; import { - mountDrawingTemplate, - type DrawingTemplateDoc, - type DrawingTemplateHandle, + mountStructureSheets, type DrawingTemplateOptions, + type StructureSheetsHandle, } from "./M02_MasterTemplete_Drawing"; import type { ItemSource } from "./M02_MasterTemplete_ItemSource"; @@ -70,9 +70,11 @@ function mountStructure( source: Where, column: string, hooks?: Pick, -): DrawingTemplateHandle { +): StructureSheetsHandle { const edit = !!hooks; - const handle = mountDrawingTemplate(host, doc.도면 as unknown as DrawingTemplateDoc, { + const sheets = doc as unknown as Parameters[1]; + // 여러 장(82-2) = 공용 탭 줄 고르기만(105-3) · 도각 `{{도면번호}}` = 장 도번 + const handle = mountStructureSheets(host, sheets, { readOnly: !edit, name: `preview:${column}`, // 자동백업 칸은 전체 편집 화면과 갈라 둠 layer: source.layer, @@ -82,21 +84,12 @@ function mountStructure( originMarker: true, // 좌표 0,0 기준점 십자 noArea: true, // 작도 영역 칸 없음 frame: true, // 도각 = 보여주기만 · 외곽 좌하단 (0,0)(56장) - frameNumber: doc.도번, // 도각 `{{도면번호}}` = 1장 도번(82-2) noImport: true, // 파일 불러오기는 도면 상세에서만(47-2) + pickTabs: true, ...hooks, }); const frame = host.querySelector("iframe"); if (frame) hideChrome(frame, edit ? HIDE_EDIT : HIDE_VIEW); - // 여러 장(82-2) — 미리보기는 1장 · 장 수만 알림(다른 장은 도면 상세 탭에서) - const sheets = 1 + (doc.더한장?.length ?? 0); - if (sheets > 1) { - const badge = document.createElement("span"); - badge.className = "m02-drawing__sheets"; - badge.textContent = `장 ${sheets}`; - badge.title = `1장을 보는 중 · 모두 ${sheets}장 — 다른 장은 도면 상세에서`; - host.querySelector(".m02-drawing")?.append(badge); - } return handle; } @@ -112,7 +105,7 @@ export function mountDrawingView( } export interface DrawingCardOptions { - /** 구조물 도면 문서 통째(`도번` · `도면` …) — 저장은 `도면` 칸만 갈아 끼움 */ + /** 구조물 도면 문서 통째(`도번` · `도면` …) — 저장은 `도면` · `더한장` 칸만 갈아 끼움 */ doc: StructureDoc; /** 읽은 판 — 저장 때 보냄(다르면 409) */ version: string | null; @@ -136,10 +129,11 @@ export function mountDrawingCard(host: HTMLElement, opt: DrawingCardOptions): Dr let doc = opt.doc; let version = opt.version ?? ""; - /** 전체 편집기 [저장]과 같은 길 — 읽은 문서에 `도면` 만 갈아 끼워 그 항목 파일에(실패는 던짐) */ - async function doSave(drawing?: DrawingTemplateDoc): Promise { + /** 전체 편집기 [저장]과 같은 길 — 읽은 문서에 `도면` · `더한장`(모든 장)을 갈아 끼워 그 항목 파일에 + * (실패는 던짐) · CAD Ctrl+S 가 넘기는 지금 탭 장은 안 씀 — 2장 보다 저장해도 1장 그대로(105-3) */ + async function doSave(): Promise { if (!write) return; - const next = { ...doc, 도면: (drawing ?? (await handle.getDoc())) as StructureDoc["도면"] }; + const next = { ...doc, ...((await handle.getDoc()) as Pick) }; version = (await write("structure", opt.column, version, next)).판; doc = next; } @@ -152,7 +146,7 @@ export function mountDrawingCard(host: HTMLElement, opt: DrawingCardOptions): Dr edit ? { onChanged: (dirty: boolean) => opt.saver.mark(dirty), - onSave: (drawing: DrawingTemplateDoc) => void opt.saver.run(() => doSave(drawing)), + onSave: () => void opt.saver.run(() => doSave()), } : undefined, ); diff --git a/resources/tester/test_cad_host_tabs.py b/resources/tester/test_cad_host_tabs.py index 975ab117f..4788a5d9f 100644 --- a/resources/tester/test_cad_host_tabs.py +++ b/resources/tester/test_cad_host_tabs.py @@ -243,10 +243,9 @@ def test_안_실린_장_표도_갈음(got): assert got["m02"]["t2"] == "새" -def test_카드는_1장_그리고_장_수(): +def test_카드는_탭_고르기만(): card = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_DrawingCard.ts").read_text("utf-8") - assert "const sheets = 1 + (doc.더한장?.length ?? 0);" in card - assert "badge.textContent = `장 ${sheets}`;" in card + assert "mountStructureSheets(host, sheets, {" in card and "pickTabs: true," in card # 105-3 main = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts").read_text("utf-8") assert '"mountStructureSheets"' in main assert '...(edited as Pick),' in main diff --git a/resources/tester/test_m02_drawing_card.py b/resources/tester/test_m02_drawing_card.py index b0a015979..c5591e264 100644 --- a/resources/tester/test_m02_drawing_card.py +++ b/resources/tester/test_m02_drawing_card.py @@ -3,7 +3,9 @@ 코드 글 모양으로 봄(CAD 는 iframe · 화면 검증은 ORCA). - 고치기 = 층 고칠 권한 + 자료 자리의 저장 길(`write`) 둘 다 · 아니면 예전처럼 보기만. - 고칠 때 CAD 메뉴(제목줄 · 리본 · 패널 · 상태줄)는 숨김 · CAD 앱은 안 고침(같은 출처 스타일). - - 저장 = 전체 편집기와 같은 길(`saveTemplate`) · 읽은 문서에 `도면` 만 갈아 끼움 · Ctrl+S 도 같음. + - 저장 = 전체 편집기와 같은 길(`saveTemplate`) · 읽은 문서에 `도면` · `더한장`(모든 장)을 갈아 끼움 · + Ctrl+S 도 같음 — 지금 탭 장을 안 받음(2장 보다 저장해도 1장 그대로 · 105-3). + - 여러 장 = 공용 탭 줄 고르기만(`mountStructureSheets` · `pickTabs`) · 「장 n」 배지 없음. - 저장 안 한 카드 고침 = 다른 항목 열기 · 페이지 떠나기 때 물음. """ @@ -23,9 +25,8 @@ def test_고치기는_권한과_저장_길_둘_다(): assert "editable && !!source.write;" in card assert "readOnly: !edit," in card and "preview: !edit," in card assert ( - "onSave: (drawing: DrawingTemplateDoc) => void opt.saver.run(() => doSave(drawing))," - in card - ) # CAD Ctrl+S = 카드 저장 + "onSave: () => void opt.saver.run(() => doSave())," in card + ) # CAD Ctrl+S = 카드 저장(문서 전체) source = _text("M02_MasterTemplete_ItemSource.ts") assert ( "write: (part, column, 판, 문서) => saveTemplate(layer, part, column, null, 판, 문서)" @@ -33,11 +34,16 @@ def test_고치기는_권한과_저장_길_둘_다(): ) -def test_메뉴는_숨기고_저장은_도면_칸만(): +def test_메뉴는_숨기고_저장은_문서_전체(): card = _text("M02_MasterTemplete_DrawingCard.ts") for part in (".cad-titlebar", ".cad-ribbon", ".cad-inspector", ".cad-statusbar"): assert part in card.split("const HIDE_EDIT")[1].split(";\n")[0] - assert "const next = { ...doc, 도면:" in card # 도번 · 열 · 판 은 읽은 그대로 + # 도번 · 열 · 판 은 읽은 그대로 · 모든 장(지금 탭 하나가 1장을 덮던 흠 · 105-3) + assert ( + 'const next = { ...doc, ...((await handle.getDoc()) as Pick) };' + in card + ) + assert "async function doSave(): Promise" in card assert 'write("structure", opt.column, version, next)' in card for key in ("L · PL", "E · Delete", "Ctrl+Z · Ctrl+Y", "Ctrl+S"): assert f'["{key}"' in card # ? 도움말 — 전체 편집기 명령 별칭과 같은 것 @@ -46,10 +52,30 @@ def test_메뉴는_숨기고_저장은_도면_칸만(): def test_저장_안_한_카드_고침은_물음(): cards = _text("M02_MasterTemplete_ItemCards.ts") # 물음 한 번(31-7) — 어느 카드든(도면 · 엑셀) 고침이 있으면 · 틀의 저장 표시(saver)로 봄 - assert "(await leaveOrStay(TEXT.discard, { save: saveAll, stillDirty: anyDirty })) === \"stay\"" in cards + assert ( + '(await leaveOrStay(TEXT.discard, { save: saveAll, stillDirty: anyDirty })) === "stay"' + in cards + ) assert "const anyDirty = (): boolean => cards.some((c) => c.saver.dirty());" in cards assert "dirty: anyDirty," in cards assert "dirty: () => cards.dirty()," in _text("M02_MasterTemplete_Structure.ts") main = _text("M02_MasterTemplete_UI_Main.ts") assert "const busy = (): boolean => dirty || !!panel?.dirty() || toDrawing.waiting();" in main assert main.count("!busy()") >= 2 # 떠날 때 물음 · 창 닫기 막음 + + +def test_카드_미리보기는_탭_고르기만(): + card = _text("M02_MasterTemplete_DrawingCard.ts") + assert "const handle = mountStructureSheets(host, sheets, {" in card + assert "pickTabs: true," in card + assert "m02-drawing__sheets" not in card # 「장 n」 배지 지움 + panel = _text("M02_MasterTemplete_AreaDrawing.ts") # 면적 도면 패널도 탭(고르기만) + assert "view = mountStructureSheets(stage, sheets, {" in panel and "pickTabs: true," in panel + drawing = _text("M02_MasterTemplete_Drawing.ts") + assert ( + "readOnly || options.pickTabs ? {} : { add: true, rename: true, remove: true, reorder: true }," + in drawing + ) + assert ( + "if ((readOnly || options.pickTabs) && !more.length) tabs.element.hidden = true;" in drawing + )