/* 233-4 빈 곳 누름 = 어디서도 안 풂 — 진짜 소스의 누름 처리 본문을 떼어(TS 그 자리 트랜스파일) 돌림. * ① 종단 그래프 빈 곳 누름(`_Profile_Render` chartWrap click) ② 횡단 그리드 빈 곳 누름(`_Section_View` grid click) * ③ 같은 것 다시 누름 = 풂(종단 측점선 `design_profile` selectStation · 횡단 카드 `_Section_View` selectStation) * ④ Esc = 풂(`design_pick_escape` + `design_selection`). 표준출력 JSON 한 벌 · 짝 = test_233_4_empty_click.py. */ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); const read = (...parts) => fs.readFileSync(path.join(ROOT, ...parts), "utf8").replace(/\r/g, ""); const run = (code, names, values) => { const js = ts.transpileModule(code, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; return new Function(...names, js)(...values); }; /** `start` 줄부터 같은 들여쓰기로 닫는 `end` 줄까지 떼어 냄. */ const block = (src, start, end) => { const from = src.indexOf(start); if (from < 0) throw new Error(`본문 없음: ${start}`); const to = src.indexOf(end, from); return src.slice(from, to + end.length); }; const out = {}; const target = (inStation = false, inCard = false) => ({ closest: (sel) => (sel.includes("b06-chart__station") && inStation) || (sel.includes("b06-cross-card") && inCard) ? {} : null, }); /* ① 종단 그래프 빈 곳 누름 */ const render = read("B05_Profile", "B05_Profile_UI_Profile_Render.ts"); const chartClick = block(render, ` chartWrap.addEventListener("click", (event) => {`, "\n });"); const calls = []; let tool = false; const ctx = { toolActive: () => tool, handleToolPick: (at) => (calls.push(`tool:${at}`), true), selectedStationId: () => "s120", selectedStructureId: () => "wall", selectStation: (id) => calls.push(`station:${id}`), setSelectedStructureId: (id) => calls.push(`structure:${id}`), redraw: () => calls.push("redraw"), }; const callbacks = { onStructureSelect: (id) => calls.push(`onStructureSelect:${id}`) }; let handler = null; const chartWrap = { addEventListener: (_type, fn) => (handler = fn), getBoundingClientRect: () => ({ left: 0 }), scrollLeft: 0, }; run( chartClick, ["ctx", "callbacks", "chartWrap", "chainageInverter", "longitudinal", "width", "layout"], [ctx, callbacks, chartWrap, () => (x) => x, null, 100, { originOffset: 0 }], ); handler({ target: target(), clientX: 42 }); out.profileEmpty = calls.splice(0); tool = true; // 도구 중 빈 곳 누름은 도구 몫 그대로 handler({ target: target(), clientX: 42 }); out.profileEmptyTool = calls.splice(0); /* ② 횡단 그리드 빈 곳 누름 — 그리드에 걸린 누름 듣개를 모두 돌림 */ const view = read("B06_Section", "B06_Section_UI_Section_View.ts"); const gridStart = view.indexOf(' grid.className = "b06-section__grid";'); const gridCode = view.slice(gridStart, view.indexOf(" // 열 수 = 사용자 값", gridStart)); // 그리드 누름 듣개 전부 const gridCalls = []; const grid = { listeners: [], addEventListener: (_t, fn) => grid.listeners.push(fn) }; run( gridCode.replace(' grid.className = "b06-section__grid";', ""), ["grid", "clearSelection", "pavementAt", "spanMarks", "isPavement", "pavementSelectListener"], [grid, () => gridCalls.push("clearSelection"), () => null, [], () => false, null], ); for (const fn of grid.listeners) fn({ target: target(), stopPropagation() {}, stopImmediatePropagation() {} }); out.gridListeners = grid.listeners.length; out.gridEmpty = gridCalls.splice(0); /* ③ 같은 것 다시 누름 = 풂 */ const dpSrc = read("A00_Common", "design", "design_profile.ts"); const dpSelect = block( dpSrc, " function selectStation(stationId: string | null): void {", "\n }", ); const profileState = run( `let selectedStationId = null; let selectedStructureId = null; ${dpSelect} return { selectStation, line: () => selectedStationId };`, ["structureIdAtStation", "draw", "onSelectStation"], [() => null, () => undefined, () => undefined], ); profileState.selectStation("s120"); out.profileFirst = profileState.line(); profileState.selectStation("s120"); out.profileAgain = profileState.line(); const svSelect = block( view, " const selectStation = (stationId: string, scroll: boolean): void => {", "\n };", ); const cardState = run( `let selectedStationId = null; let activeAreaKey = null; ${svSelect} return { selectStation, card: () => selectedStationId };`, ["applySelection", "revealCard"], [() => undefined, () => undefined], ); cardState.selectStation("s120", true); out.cardFirst = cardState.card(); cardState.selectStation("s120", true); out.cardAgain = cardState.card(); /* ④ Esc = 풂 */ const store = new Map(); const loadTs = (file, stubs) => { const box = { exports: {} }; const js = ts.transpileModule(read(...file.split("/")), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; new Function("exports", "module", "require", js)(box.exports, box, (r) => stubs[r]); return box.exports; }; const DSel = loadTs("A00_Common/design/design_selection.ts", { "../b_page_state": { readState: (k, p) => store.get(`${k}:${p}`) ?? null, writeState: (k, v, p) => store.set(`${k}:${p}`, v), }, }); let onKey = null; globalThis.window = { addEventListener: (_t, fn) => (onKey = fn), removeEventListener() {} }; globalThis.document = { querySelector: () => null }; const ESC = loadTs("A00_Common/design/design_pick_escape.ts", { "./design_selection": DSel }); ESC.bindEscapeRelease("p", { visible: () => true, onDispose() {} }); DSel.selectDesign("p", { chainageM: 120, structureId: null }, { from: "b06" }); out.beforeEsc = DSel.readSelection("p"); onKey({ key: "Escape", defaultPrevented: false, target: { closest: () => null } }); out.afterEsc = DSel.readSelection("p"); process.stdout.write(JSON.stringify(out));