/* 233-3 풀기 한 길(Esc) · 배수유역도 다시 얹음 — 진짜 `design_pick_escape` · `design_pick_profile` · * `B06_Section_UI_Page_Pick_Sync` · `design_selection` 을 Node 로 돌림. 창 키 · 폼 · 지도 · 카드는 흉내. * 표준출력 JSON 한 벌 · 짝 = test_233_3_release.py. */ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); function loadTs(file, stubs = {}) { const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; const localRequire = (request) => { if (stubs[request]) return stubs[request]; throw new Error(`stub 없음: ${request}`); }; new Function("exports", "module", "require", js)(box.exports, box, localRequire); return box.exports; } /* 창 키 듣기 · 창(모달) 흉내 */ const keyListeners = new Set(); globalThis.window = { addEventListener: (type, fn) => type === "keydown" && keyListeners.add(fn), removeEventListener: (type, fn) => keyListeners.delete(fn), }; let modalOpen = false; globalThis.document = { querySelector: () => (modalOpen ? {} : null) }; const press = (key, { inInput = false, prevented = false } = {}) => { const event = { key, defaultPrevented: prevented, target: { closest: () => (inInput ? {} : null) }, }; for (const fn of [...keyListeners]) fn(event); }; const store = new Map(); const DSel = loadTs("A00_Common/design/design_selection.ts", { "../b_page_state": { readState: (key, pid) => store.get(`${key}:${pid}`) ?? null, writeState: (key, value, pid) => store.set(`${key}:${pid}`, value), }, }); const ESC = loadTs("A00_Common/design/design_pick_escape.ts", { "./design_selection": DSel }); const IRR = { irregularStationId: (id) => `irregular:${id}`, isPipeStation: (s) => s.origin === "pipe", }; const SY = loadTs("B05_Profile/B05_Profile_UI_Selection.ts", { "../A00_Common/design/design_selection": DSel, "./B05_Profile_UI_IrregularStations": IRR, }); const PP = loadTs("A00_Common/design/design_pick_profile.ts", { "./design_selection": DSel, "./design_pick_escape": ESC, "../../B05_Profile/B05_Profile_UI_Selection": SY, "../../B05_Profile/B05_Profile_UI_IrregularStations": IRR, }); const SYNC = loadTs("B06_Section/B06_Section_UI_Page_Pick_Sync.ts", { "../A00_Common/design/design_selection": DSel, "../A00_Common/design/design_pick_escape": ESC, }); const PID = "p233c"; const sel = (chainageM, structureId = null, from = "b05") => DSel.selectDesign(PID, chainageM === null ? DSel.NO_SELECTION : { chainageM, structureId }, { from, }); /* ── 종단 보기(B05) — 종단도 · 배수유역도 · 좌측 폼 흉내 ── */ const map = { pipe: null, mark: null, notifies: 0 }; const profile = { station: null, structure: null }; const form = { open: "wall", resets: 0 }; let b05Visible = true; const b05 = PP.bindProfilePick( PID, { profile: { setSelectedStation: (id) => Object.assign(profile, { station: id, structure: null }), setSelectedStructure: (id) => Object.assign(profile, { station: null, structure: id }), drainage: { // 진짜 판 꼴 — 0.51 m 안 관을 고름 · notify 거짓이면 밖으로 안 알림 selectPipeAtChainage: (at, notify = true) => { map.pipe = at === null ? null : Math.abs(at - 80.17) < 0.51 ? 80.17 : null; if (notify) map.notifies += 1; }, markStation: (at) => (map.mark = at), }, }, form: { hasSelection: () => form.open !== null, selectById: () => { form.open = null; form.resets += 1; }, }, }, { visible: () => b05Visible, onDispose() {} }, { irregular: () => [{ id: "p80", chainage_m: 80.17, origin: "pipe" }], pipes: () => [{ chainage_m: 80.17 }], sections: () => [80, 100, 120].map((at) => ({ station_id: `s${at}`, chainage_m: at })), }, ); /* ── 횡단 보기(B06) — 카드 · 형상 · 폼 흉내 ── */ const cross = []; let b06Visible = false; SYNC.bindCrossPick( PID, { visible: () => b06Visible, onDispose() {} }, { sections: () => [], controls: { revetOffset: { highlightFor: () => false, selectedFor: () => false, select() {} }, ford: { selectedFor: () => false, select() {} }, box: { selectedFor: () => false, select() {} }, }, releaseForm: () => cross.push("releaseForm"), view: { focusStation: (id) => cross.push(`focus:${id}`), refreshCards: () => undefined, selectMark: (id) => cross.push(`mark:${id}`), }, reapply: () => cross.push("reapply"), }, ); const snap = () => ({ session: DSel.readSelection(PID), profile: { ...profile }, map: { ...map }, form: { ...form }, }); const out = {}; sel(80.17); out.pipePick = snap(); // 배수유역도 재계산이 관 · 유역 고름을 지움 → 같은 값으로 다시 얹음(알림 없이) map.pipe = null; map.mark = null; b05.paint(); out.afterRecompute = snap(); press("Escape", { inInput: true }); out.escInInput = snap(); modalOpen = true; press("Escape"); modalOpen = false; out.escModal = snap(); press("Escape", { prevented: true }); out.escPrevented = snap(); press("Escape"); out.esc = snap(); out.crossWhileHidden = cross.splice(0); // 목록 다시 누름 같은 B05 풀기(값 비움)는 폼을 그 길이 이미 닫음 — 구독은 폼을 또 닫지 않음 form.open = "sign"; sel(100, "sign"); sel(null); out.listRelease = snap(); // 횡단 보기가 보일 때 Esc = 카드 · 폼 · 범위 · 형상 모두 풂 b05Visible = false; b06Visible = true; sel(120, null, "b06"); press("Escape"); out.crossEsc = cross.splice(0); out.crossSession = DSel.readSelection(PID); out.listeners = keyListeners.size; process.stdout.write(JSON.stringify(out));