/* 514-1 알약 · 평면 마커 우클릭 「구조물 삭제」(요구 328-1) — `design_pipe_ids.removeMarkOwner` · * `_RouteEdit_StructureMenu` 를 Node 로 돌려 JSON 한 줄(`test_514_1_mark_remove.py` 가 판정). * 관 장부 · 메뉴 판 · 마커 집기는 가짜 · 주인 가르기 · 평면 항목 짓기는 진짜. * node helper_514_1_mark_remove.cjs */ const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B05 = path.join(ROOT, "B05_Profile"); const opens = []; const calls = []; const ui = () => ({ createMapContextMenu: () => ({ element: { parentElement: null }, open: (x, y, items) => opens.push({ x, y, items }), close() {}, contains: () => false, }), }); // 평면 마커 = 측점선 없는 대피로 20 m(화면 40 px) const MARK = { id: "evac-1", chainageM: 20 }; const STUBS = { "../b_page_state": { readState: () => ({ next: 4, ids: [{ id: "pipe-s3", at: 55 }] }), writeState() {}, }, "./design_selection": { PIPE_MATCH_M: 0.51, isNear: () => false, nearestWithin: () => null, readSelection: () => null, selectDesign() {}, }, common_util_pick_tag: { setPipeTagSource() {} }, "../A00_Common/touch_bridge": { pickPx: (v) => v }, "./B05_Profile_UI_IrregularStations": { irregularStationId: (id) => `irr:${id}` }, "./B05_Profile_Api_Structures": { structureAnchorM: (s) => s.chainage_m ?? 0 }, "../A00_Common/design/design_selection": { PIPE_MATCH_M: 0.51 }, "./B05_Profile_UI_RouteEdit_StructureMarks": { planMarkAt: (_c, _l, toScreen, px, py) => { const [x, y] = toScreen([MARK.chainageM, 0]); const distance = Math.hypot(x - px, y - py); return distance <= 10 ? { mark: MARK, distance } : null; }, }, }; const { loadTs } = makeLoader({ root: ROOT, stubs: STUBS, ui }); const ids = loadTs(path.join(ROOT, "A00_Common", "design", "design_pipe_ids.ts")); ids.usePipeIds("p1"); const shared = loadTs(path.join(B05, "B05_Profile_UI_Profile_Structures.ts")); const plan = loadTs(path.join(B05, "B05_Profile_UI_RouteEdit_StructureMenu.ts")); const out = {}; // 알약 주인 가르기 — 관 세션 id · 옛 자리 이름 관 = 관 지점 · 그 밖 = 구조물 id const remove = { pipe: (m) => calls.push(["pipe", m]), structure: (id) => calls.push(["structure", id]), }; ["pipe-s3", "pipe-12.50", "evac-1", "cross-2"].forEach((id) => ids.removeMarkOwner(id, remove)); out.owners = calls.splice(0); function fakeElement() { const listeners = {}; return { isConnected: true, addEventListener: (type, fn) => (listeners[type] = listeners[type] || []).push(fn), getBoundingClientRect: () => ({ left: 0, top: 0 }), append(child) { child.parentElement = this; }, parentElement: null, fire(type, clientX, clientY) { (listeners[type] || []).forEach((fn) => fn({ clientX, clientY, target: null, preventDefault() {} }), ); }, }; } // 종단 그래프 = 재료(관 측점 40 m 하나) const host = fakeElement(); shared.mountStructureMenu(host, { stations: [{ id: "pipe-1", chainage_m: 40, origin: "pipe" }], x: (m) => m * 2, chainageAt: (px) => px / 2, maxChainageM: 100, onRemove: (station) => calls.push(["remove", station.id]), onRemoveMark: (id) => calls.push(["removeMark", id]), onAddPipe: () => {}, structureGroups: () => [{ label: "A군", types: [{ type: { name: "배수관", type_id: "pipe" } }] }], onAddStructureType: () => {}, }); const canvas = fakeElement(); canvas.parentElement = fakeElement(); const line = [ [0, 0], [100, 0], ]; plan.bindPlanStructureMenu( { canvas, toScreen: (v) => [v[0] * 2, v[1] * 2], unrotate: (x, y) => [x, y], state: () => ({ plannedLine: line, planned: line }), }, () => -1, ); const planAt = (x, y) => { canvas.fire("contextmenu", x, y); const items = opens.at(-1).items; return items.map((item) => (Array.isArray(item) ? item[0] : item.label)); }; out.planMark = planAt(41, 3); // 대피로 마커 opens.at(-1).items[0][1](); out.planPipe = planAt(81, 2); // 관 측점선 = 측점 먼저 opens.at(-1).items[0][1](); out.planEmpty = planAt(140, 2); // 빈 자리 = 추가 메뉴 out.calls = calls; process.stdout.write(JSON.stringify(out));