/* PLAN 179-4 단위검증 헬퍼 — [상세] 표 손질(`B06_Section_UI_Cross_Detail_Table.ts`). * 화면 TS 를 그 자리에서 트랜스파일해 Node 로 돌림. 입력 = 표준입력 JSON {design, sources} · * 결과 = 표준출력 JSON 한 벌. 짝 = `test_179_4_detail_table.py`(파이썬 `cell_value` 거울). */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B06 = path.join(ROOT, "B06_Section"); const common = fs.readFileSync(path.join(B06, "B06_Section_UI_Section_Common.ts"), "utf8"); const CROSS_PAD = Function(`return ${/export const CROSS_PAD = (\{[^}]*\})/.exec(common)[1]}`)(); // 세션 저장소 — 진짜 b_page_state 를 그대로 돌림 const store = () => { const map = new Map(); return { getItem: (key) => (map.has(key) ? map.get(key) : null), setItem: (key, value) => map.set(key, String(value)), removeItem: (key) => map.delete(key), key: (index) => [...map.keys()][index] ?? null, get length() { return map.size; }, }; }; globalThis.window = { sessionStorage: store(), localStorage: store() }; // 조정창 공용 부품(Select → Design_Pick → Cross_Panel_Base)이 불러올 때 문서에 귀를 닮 globalThis.document = { addEventListener: () => {}, removeEventListener: () => {} }; const STUBS = { "@ui/ui_template_context_menu": { createMapContextMenu: () => ({}) }, "./B06_Section_UI_Section_Common": { CROSS_PAD }, }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); const { loadTs } = makeLoader({ root: ROOT, cache: false, requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.endsWith(".css")) return {}; if (request.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6)))); return loadTs(resolve(path.join(path.dirname(full), request))); }, }); const T = loadTs(path.join(B06, "B06_Section_UI_Cross_Detail_Table.ts")); const input = JSON.parse(fs.readFileSync(0, "utf8")); const out = {}; // ① 칸 값 거울 — 같은 출처 · 설계를 파이썬 `cell_value` 와 견줌 out.local = input.sources.map((source) => T.localCellValue(source, input.design)); // ② 그래프 고르기 — 띠 → 표 키 · 판정 선 화면 px → m(카드 x · toDisplayY 의 역) const fake = (classes, attrs = {}) => { const self = { classList: { contains: (name) => classes.includes(name) }, getAttribute: (name) => attrs[name] ?? null, closest: (selector) => selector.split(",").some((part) => classes.includes(part.trim().slice(1))) ? self : null, }; return self; }; out.area = { cut_soil: T.areaSourceOf(fake(["b06-chart__area", "b06-chart__area--cut_soil"])), cut_rock: T.areaSourceOf( fake(["b06-chart__area", "b06-chart__area--cut_rock", "b06-chart__area--cut_rr"]), ), fill: T.areaSourceOf(fake(["b06-chart__area", "b06-chart__area--fill"])), spoil: T.areaSourceOf(fake(["b06-chart__spoil-fill"])), other: T.areaSourceOf(fake(["b06-chart__ground"])), }; const AXES = { widthPx: 600, heightPx: 300, pixelsPerMeter: 10, maxOffset: 24, displayMax: 120, elevationMid: 110, exaggeration: 2, }; const px = ([offset, elevation]) => [ CROSS_PAD.left + (AXES.maxOffset - offset) * AXES.pixelsPerMeter, CROSS_PAD.top + (AXES.displayMax - (AXES.elevationMid + (elevation - AXES.elevationMid) * AXES.exaggeration)) * AXES.pixelsPerMeter, ]; // 화면 px → m(209-1 `_Cross_Detail_Select.pxToM`) · 길이 = 옛 저장 꼴(두 끝) 그대로 읽음 const S = loadTs(path.join(B06, "B06_Section_UI_Cross_Detail_Select.ts")); out.pxBack = input.pick.map(px).map(S.pxToM(AXES)); out.length = { src: "length", start: input.pick[0], end: input.pick[1] }; out.lengthValue = T.localCellValue(out.length, input.design); // ③ 우클릭 메뉴 항목 const noop = () => {}; const act = { pick: noop, auto: noop, addRow: noop, addColumn: noop, removeRow: noop, removeColumn: noop, }; const labels = (items) => items.map((item) => (typeof item === "string" ? item : item[0])); const empty = { cells: {}, rows: [], columns: [] }; out.menuHeader = labels( T.cellMenuItems(empty, { key: "ground", rowId: null, columnId: null }, act), ); out.menuBase = labels( T.cellMenuItems( { ...empty, cells: { cut_soil: { src: "area", key: "embankment" } } }, { key: "cut_soil", rowId: "b0", columnId: null }, act, ), ); out.menuAdded = labels( T.cellMenuItems( { cells: {}, rows: [{ id: "r1", after: "b0", grouped: true, labels: [] }], columns: [{ id: "c1", label: "" }], }, { key: "c:c1:r1", rowId: "r1", columnId: "c1" }, act, ), ); const full = { cells: {}, rows: Array.from({ length: T.MAX_ROWS }, (_, i) => ({ id: `r${i + 1}`, after: null, grouped: false, labels: [], })), columns: Array.from({ length: T.MAX_COLUMNS }, (_, i) => ({ id: `c${i + 1}`, label: "" })), }; out.menuFull = labels(T.cellMenuItems(full, { key: "fence", rowId: "b0", columnId: null }, act)); let picked = null; T.cellMenuItems( empty, { key: "fence", rowId: "b0", columnId: null }, { ...act, pick: (kind) => (picked = kind), }, ).find((item) => item[0] === "그래프에서 고르기 — 길이")[1](); out.menuPicks = picked; // ④ 표 손질 → 세션 초안 → patch(Persist 가 싣는 꼴) const section = { station_id: "s40", chainage_m: 40, design: { ...input.design } }; const table = T.detailTableOf(section, "p1", 7); out.fresh = JSON.parse(JSON.stringify(table)); T.setCell(table, "cut_soil", { src: "area", key: "embankment" }); T.setCell(table, "fence", out.length); const r1 = T.addRow(table, "b0", true); const r2 = T.addRow(table, null, false); const r3 = T.addRow(table, r1, true); const c1 = T.addColumn(table); T.setRowLabel(table, r1, 0, "풍화암"); T.setColumnLabel(table, c1, "비고"); T.setCell(table, `c:${c1}:${r1}`, { src: "text", text: "L형" }); T.setCell(table, `r:${r2}:2`, { src: "area", key: "cut_rock" }); T.writeDetailTable(section, table, "p1", 7); out.ids = [r1, r2, r3, c1]; out.cacheHas = section.design.detail_table === table; out.drafts = Object.fromEntries(T.readDetailTables("p1", 7)); out.forTable = T.designForTable({ ...section, design: { paved: true } }, "p1", 7).detail_table; // 지움 — r1 을 지우면 r3 은 r1 자리(b0 아래)로 · 그 행 열 칸도 지움 const removed = T.detailTableOf(section, "p1", 7); T.removeRow(removed, r1); T.removeColumn(removed, c1); out.removed = removed; out.autoBack = (() => { const back = T.detailTableOf(section, "p1", 7); T.setCell(back, "cut_soil", null); return Object.keys(back.cells); })(); out.span = [ T.groupSpan( [ ["깍기", "토사", "a"], [null, "x", "r"], [null, "암석", "b"], ["쌓기", null, "c"], ], 0, ), T.groupSpan( [ ["쌓기", null, "c"], ["층따기", null, "d"], ], 0, ), ]; process.stdout.write(JSON.stringify(out));