/* 466 덧(470 N2) 헬퍼 — 진짜 `createB06StructuresPanel` 의 B06 폼 관 고침(`onPipeUpdate`)이 관을 0.51 m 넘게 * 옮기며 옵션도 고칠 때 옵션이 옮긴 관(초안)에 붙고 캐시 얹기도 새 자리인지. 가짜 모듈 틀 = helper_165_pavement_flow.cjs. * 표준출력 JSON 한 벌 · 짝 = test_466_pipe_move_options.py. */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); function loadTs(file, stubs = {}) { return makeLoader({ root: ROOT, cache: false, requireFor: (full) => (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (stubs[request]) return stubs[request]; throw new Error(`stub 없음: ${request}`); }, }).loadTs(file); } // 세션(structure-pick) — 메모리 한 벌 const store = new Map(); const selection = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts"), { "../../common_util/common_util_pipe_match": loadTs( path.join(ROOT, "common_util", "common_util_pipe_match.ts"), ), // 찾는 거리 정의(472-6) "../b_page_state": { readState: (key, pid) => store.get(`${key}:${pid}`) ?? null, writeState: (key, value, pid) => store.set(`${key}:${pid}`, value), }, }); const PID = "p165"; const TYPES = [ { type_id: "pavement_concrete", group: "G" }, { type_id: "hazard_sign", group: "E" }, { type_id: "pipe", group: "A", managed_by: "pipe_points" }, ]; const PAVE = { structure_id: "pave", type_id: "pavement_concrete", placement: "interval", chainage_m: 30, start_m: 30, end_m: 250, }; const PAVE2 = { structure_id: "pave2", type_id: "pavement_concrete", placement: "interval", chainage_m: 100, start_m: 100, end_m: 200, }; const PIPES = [{ chainage_m: 80.17, facility: "pipe", source: "spacing", options: {} }]; // 카드 = 20 m 측점 + 관 측점(비정규) const CARDS = [...Array.from({ length: 16 }, (_, i) => i * 20), 80.17] .sort((a, b) => a - b) .map((at) => ({ station_id: `s${at}`, chainage_m: at })); const cardAt = (at) => CARDS.find((card) => Math.abs(card.chainage_m - at) < 0.01); /* ── 폼 흉내(B05 패널 · loadForm / loadPipeForm / resetForm 의 알림 차례) ── */ let binding = null; let structures = []; const form = { id: null, pipe: null }; const loadForm = (target) => { form.id = target?.structure_id ?? null; form.pipe = null; binding.onSelect(target); }; const loadPipeForm = (chainageM) => { const hadStructure = form.id !== null; form.id = null; form.pipe = chainageM; if (hadStructure) binding.onSelect(null); }; const resetForm = () => { const hadPipe = form.pipe !== null; form.pipe = null; loadForm(null); if (hadPipe) binding.onPipeSelect(null); }; const node = { addEventListener() {}, querySelector: () => ({ append() {} }) }; const section = { root: node, listRoot: node, facility: { root: node, setRevetSideLabels() {}, recount() {} }, setTypes() {}, setStructures(next) { structures = next; }, setPipeFacilities() {}, hasSelection: () => form.id !== null || form.pipe !== null, selectById(id) { if (id === null) { if (form.id !== null || form.pipe !== null) resetForm(); return; } loadForm(structures.find((entry) => entry.structure_id === id) ?? null); }, selectPipeByChainage(chainageM) { if (chainageM === null) { if (form.pipe !== null) resetForm(); return; } loadPipeForm(chainageM); }, }; const panelModule = loadTs( path.join(ROOT, "B06_Section", "B06_Section_UI_Page_Structures_Panel.ts"), { "../A00_Common/design/design_shared": { bindDesignView: (_shared, _view, parts) => { binding = parts.structures; }, sharedStructures: () => section, }, "../B05_Profile/B05_Profile_Api_Structures": { fetchStructureTypes: async () => TYPES, fetchStructures: async () => ({ structures: [] }), readPendingStructures: () => current.structures, pipesToStructureMarks: (pipes) => pipes.map((pipe) => ({ structure_id: `pipe-${pipe.chainage_m.toFixed(2)}`, type_id: pipe.facility, chainage_m: pipe.chainage_m, start_m: null, end_m: null, })), structureAnchorM: (s) => s.chainage_m ?? s.start_m ?? 0, writePendingStructures() {}, dropStaleStructureTypes: async () => {}, // 정의 판 확인(322-0) staleDefinitionNotice: () => null, // 판 알림(322-2) }, "../B05_Profile/B05_Profile_Api_Pipes_Draft": { readPendingPipes: () => null, writePendingPipes: (_pid, next) => (written = next), }, "../B05_Profile/logic/B05_Profile_UI_Drainage_Facility_Merge": { mergeFacilityOptions: (was, next) => ({ facility: next.facility, options: { ...(was.options ?? {}), ...(next.options ?? {}) }, }), }, "../B05_Profile/B05_Profile_UI_Drainage_Ford_Fill": { fordWaitsOf: () => new Set(), waitsFordHeight: () => false, }, "../B04_PreProcess/B04_PreProcess_Api_Fetch": { fetchDetailPipePoints: async () => ({ pipe_points: PIPES, basins: [] }), }, // 페이지 자료 줄(386-2) — 저장분 한 번 "../A00_Common/design/design_page_data": { pageTypesChecked: async () => false, pageStructures: async () => ({ structures: [] }), pagePipes: async () => ({ pipe_points: PIPES, basins: [] }), }, "../common_util/common_util_summary_item": { linkFields: () => ({}), readGroupItems: (x) => x ?? {}, readSummaryItem: (x) => x ?? null, }, "../B05_Profile/B05_Profile_UI_Structure_Pick_Session": { readStructurePick: () => null }, "../A00_Common/design/design_selection": selection, "@ui/ui_template_elements": { showToast() {} }, // 관 기준 측점 정수 맞춤(234-1) — 일반 측점 없으면 정수 m 만 "../common_util/common_util_structure_station": { snapStructureStation: (value) => Math.floor(value + 0.5), }, "./B06_Section_UI_Adjust_Dock": { adjustDockRoot: () => ({}), refreshAdjustSlots() {}, resetAdjustDock() {}, setAdjustSideSlots() {}, }, "./B06_Section_UI_Page_Structures_Spec": { withSpecDefaults: (o) => o ?? {} }, // 옛 소수 관 맞춤(322-3 고침) · 관 세션 id(323-5) — 프로젝트 없음 = 옛 자리 이름 "../B05_Profile/B05_Profile_UI_Structures_Snap": { snapNotice() {}, snapPlan: () => ({ structures: [], pipes: [], skipped: 0 }), }, "../A00_Common/design/design_pipe_ids": { pipeIdAt: (at) => `pipe-${at.toFixed(2)}`, followMovedPipe() {}, }, // 요구 178 — 알약 측점 짝(진짜 `plantsStation` · 272 측점 심기 한 벌) "../B05_Profile/B05_Profile_UI_Profile_Structures": loadTs( path.join(ROOT, "B05_Profile", "B05_Profile_UI_Profile_Structures.ts"), { "../A00_Common/touch_bridge": { pickPx: (px) => px }, // 기기대응 4-4 — 마우스 반경 그대로 "@ui/ui_template_context_menu": { createMapContextMenu() {} }, "./B05_Profile_UI_IrregularStations": { irregularStationId: (id) => `irregular:${id}`, isPipeStation: () => false, }, "./B05_Profile_Api_Structures": { structureAnchorM: (s) => s.chainage_m ?? s.start_m ?? 0, }, "../A00_Common/design/design_selection": selection, "../common_util/common_util_structure_station": loadTs( path.join(ROOT, "common_util", "common_util_structure_station.ts"), ), }, ), }, ); const current = { structures: [] }; let panel = null; let written = null; const applied = []; (async () => { const out = {}; const run = async (from, to, options) => { store.clear(); PIPES.splice(0, PIPES.length, { chainage_m: 80.17, facility: "pipe", source: "spacing", options: { pipe_diameter_mm: 800 }, }); written = null; applied.length = 0; panel = panelModule.createB06StructuresPanel({ projectId: PID, shared: { view: () => "cross", lane: { set() {}, setTypes() {}, setStored() {} } }, detail: () => ({ cross_sections: CARDS }), stationInterval: () => 20, focusChainage() {}, markStructure() {}, onMarks() {}, applyPipeOptions: (at, patch) => applied.push([at, patch.pipe_diameter_mm ?? null]), }); await panel.load(); binding.onPipeUpdate(from, to, { facility: "pipe", options }); return { pipes: (written ?? []).map((pipe) => [ pipe.chainage_m, pipe.options?.pipe_diameter_mm ?? null, ]), applied: [...applied], }; }; out.far = await run(80.17, 100.4, { pipe_diameter_mm: 1200 }); // 옮김 + 옵션 out.near = await run(80.17, 80, { pipe_diameter_mm: 1000 }); // 0.51 m 안 옮김 + 옵션 out.stay = await run(80.17, 80.17, { pipe_diameter_mm: 900 }); // 옵션만 process.stdout.write(JSON.stringify(out)); })().catch((error) => { console.error(error); process.exit(1); });