/* ============================================================================= * M02_MasterTemplete_AreaDrawing.ts * 면적산출 상세 오른쪽 「도면」 패널 몸(PLAN 47-6) — 그 열 구조물 도면을 보기 · 고르기(고치기 없음). * 처음 펼 때 읽고 그림 — 접힌 채 iframe 을 띄우지 않음. 여러 장 = 공용 탭 줄 고르기만(105-3 · 보내기는 지금 장). * 83-3 — 머리 [면적산출로 보내기] = CAD 에 고른 범위 요청(`aislo:b08:export-table-request`) → 받은 표 자료 * (`aislo:b08:export-table` · CAD `integration/aislo-table-export.ts`)를 `onTable` 로(새 시트 만들기는 부른 쪽). * CAD 명령 TOAREA 로 보낸 것도 같은 길. 고칠 수 없는 면적(읽기 층)이면 단추 없음. * 112-1 — 면적 칸을 고치면(저장 전) 연결 표 값이 바로 따라 바뀜: 지금 시트의 칸 글을 서버와 같은 코드 * (`spreadsheet_area_table.ts`)로 브라우저가 만들어 CAD 연결 표 갈기 메시지(보기 전용 갈음)로 보냄 · * 패널이 펼쳐진 동안 · 값이 바뀐 표만 · 도면 문서는 안 고침([저장] 때는 서버 refresh_drawing 이 정본). * ========================================================================== */ import { createButton, el } from "@ui/ui_template_elements"; import { readTemplate, type Layer, type StructureDoc } from "./M02_MasterTemplete_Api_Fetch"; import { mountStructureSheets } from "./M02_MasterTemplete_Drawing"; import type { DrawingTable } from "./M02_MasterTemplete_AreaFromDrawing"; import { sameValues, type TableLink } from "./M02_MasterTemplete_AreaToDrawing"; import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet_types"; const TEXT = { loading: "도면 불러오는 중…", none: "이 열은 구조물 도면이 없음", send: "면적산출로 보내기", sendTip: "도면에서 고른 범위(표 · 선으로 그린 표 · 글)를 면적산출 새 시트로 한 번 옮김 — 연동 아님 · 누름 · 끌기로 고름", }; export const EXPORT_TABLE_REQUEST = "aislo:b08:export-table-request"; export const EXPORT_TABLE = "aislo:b08:export-table"; /** 고르기만 — 제목줄 · 리본 · 패널 · 상태줄 숨김(카드 고치기 갈래와 같은 숨김 · 명령 안내 줄은 남김) */ const HIDE = ".cad-titlebar, .cad-ribbon, .cad-inspector, .cad-statusbar, .cad-quick-access" + " { display: none !important; }"; function hideChrome(frame: HTMLIFrameElement): void { const apply = (): void => { const inner = frame.contentDocument; if (!inner?.head || inner.getElementById("m02-area-hide")) return; const style = inner.createElement("style"); style.id = "m02-area-hide"; style.textContent = HIDE; inner.head.append(style); }; frame.addEventListener("load", apply); apply(); } export interface AreaDrawingOptions { /** 받은 표 자료 — 없으면(읽기 층) 보내기 단추 없음 */ onTable?: (table: DrawingTable) => void; /** 면적 화면에서 [보내기 취소]한 연결(저장 전) — 실시간 값 갈기에서 뺌 */ cancelled?: () => ReadonlySet; } /** 도면 손잡이 중 실시간 갈기에 쓰는 것(`mountStructureSheets` 의 `updateTable`) */ interface LiveHandle { updateTable?: (link: string, cells: unknown[][], preview?: boolean) => Promise; } const LIVE_MS = 150; // 칸 고침이 이어지는 동안 모았다가 한 번에 export interface AreaDrawing { root: HTMLElement; /** 패널이 펼쳐짐 — 처음 한 번만 읽고 그림 */ show: () => void; /** 패널이 접힘 — 접힌 동안은 실시간 갈기 건너뜀 */ hide: () => void; /** 면적 시트가 바뀜(저장 전) — 지금 책을 주면 연결 표 값을 따라 바꿈 */ sync: (book: () => Workbook | null | undefined) => void; destroy: () => void; } export function createAreaDrawing( layer: Layer, column: string, opts: AreaDrawingOptions = {}, ): AreaDrawing { const root = el("div", { className: "m02-apanel__drawing" }); const stage = el("div", { className: "m02-apanel__drawstage" }); let view: { destroy: () => void } | null = null; let frame: HTMLIFrameElement | null = null; let started = false; let dead = false; let open = false; let rows: TableLink[] = []; let bookOf: (() => Workbook | null | undefined) | null = null; let timer: number | undefined; /** 연결별로 마지막에 도면에 비춘 칸 글(없으면 저장된 `값`) */ const pushed = new Map>(); const note = (text: string): void => stage.replaceChildren(el("p", { className: "m02-apanel__note", text })); const send = createButton({ label: TEXT.send, size: "sm", variant: "ghost" }); send.title = TEXT.sendTip; send.dataset.areaFromDrawing = ""; send.disabled = true; // 도면이 서면 켬 send.addEventListener("click", () => frame?.contentWindow?.postMessage({ type: EXPORT_TABLE_REQUEST }, window.location.origin), ); if (opts.onTable) root.append(el("div", { className: "m02-apanel__drawbar", children: [send] })); root.append(stage); const onMessage = (event: MessageEvent): void => { if (!frame || event.source !== frame.contentWindow) return; if (event.origin !== window.location.origin) return; const data = event.data as { type?: string; table?: DrawingTable } | null; if (data?.type === EXPORT_TABLE && data.table && opts.onTable) opts.onTable(data.table); }; window.addEventListener("message", onMessage); /** 지금 시트 → 연결 표마다 칸 글을 만들어 달라진 것만 도면에 비춤(서버 `areaTable` 과 같은 코드) */ async function push(): Promise { const book = bookOf?.(); const handle = view as LiveHandle | null; if (dead || !open || !book || !handle?.updateTable || !rows.length) return; const { areaTable } = await import("../A00_Common/spreadsheet/spreadsheet_area_table"); for (const row of rows) { if (opts.cancelled?.().has(row.연결)) continue; try { const got = areaTable({ book, 시트: row.시트, 범위: row.범위, 이름: row.이름, 글자크기: row.글자크기, }); if (sameValues(pushed.get(row.연결) ?? row.값, got.값)) continue; pushed.set(row.연결, got.값); await handle.updateTable(row.연결, got.표.shapeData.cells, true).catch(() => 0); } catch { // 이름이 지워짐 · 범위 못 바꿈 — 표는 그대로(저장 때 서버가 판정) } } } const later = (): void => { window.clearTimeout(timer); timer = window.setTimeout(() => void push(), LIVE_MS); }; const show = (): void => { open = true; if (started) return later(); // 접힌 사이 바뀐 값을 따라잡음 started = true; note(TEXT.loading); readTemplate(layer, "structure", column, null) .then((got) => { if (dead) return; const doc = got.문서 as StructureDoc | null; if (!doc?.도면) return note(TEXT.none); stage.replaceChildren(); rows = doc.연결표 ?? []; // 보기 전용 + 고르기(누름 · 끌기) — 미리보기(끌기 = 화면 옮기기)는 고르기가 꺼져 있어 안 씀 const sheets = doc as unknown as Parameters[1]; view = mountStructureSheets(stage, sheets, { readOnly: true, name: `preview:${column}`, layer, panelsCollapsed: true, originMarker: true, noArea: true, frame: true, noImport: true, pickTabs: true, }); frame = stage.querySelector("iframe"); if (frame) hideChrome(frame); send.disabled = !frame; later(); }) .catch(() => !dead && note(TEXT.none)); }; return { root, show, hide: () => (open = false), sync: (book) => ((bookOf = book), later()), destroy: () => { dead = true; window.clearTimeout(timer); window.removeEventListener("message", onMessage); view?.destroy(); root.remove(); }, }; }