/* 472-5 헬퍼 — node helper_472_5_shop.mjs <묶음.mjs> · 표준출력 JSON. * ① 되돌리기 · 다시하기가 가게 듣는 쪽(관 · 구조물 사본)을 부르는가 ② 배수 편집기가 남이 고친 관 초안을 받는가. */ import { pathToFileURL } from "node:url"; const storage = () => { const m = new Map(); return { getItem: (k) => (m.has(k) ? m.get(k) : null), setItem: (k, v) => m.set(k, String(v)), removeItem: (k) => m.delete(k), key: (i) => [...m.keys()][i] ?? null, get length() { return m.size; }, clear: () => m.clear(), }; }; const any = new Proxy(function () {}, { get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : any), apply: () => any, construct: () => any, }); globalThis.window = { sessionStorage: storage(), localStorage: storage() }; // 손길 듣기 없음 = 열 때 규칙 꺼짐 globalThis.sessionStorage = window.sessionStorage; globalThis.localStorage = window.localStorage; globalThis.document = any; const m = await import(pathToFileURL(process.argv[2]).href); const P = "p472five"; localStorage.setItem("frd_current_project_id", P); m.pinDraftProject(P); const store = m.useDesignStore(P, null); const A = [{ structure_id: "a", type_id: "berm", placement: "point", chainage_m: 20, options: {} }]; const AB = [ ...A, { structure_id: "b", type_id: "berm", placement: "point", chainage_m: 40, options: {} }, ]; const pipes1 = [ { id: "k1", chainage_m: 100, source: "user", facility: "pipe", options: { diameter_mm: 800 } }, ]; const pipes2 = [ { id: "k1", chainage_m: 100, source: "user", facility: "pipe", options: { diameter_mm: 1000 } }, ]; store.set("structures", A); store.set("pipes", pipes1); m.writePlusDraft(P, [60]); const history = m.createProfileHistory(() => undefined, 100); store.set("structures", AB); store.set("pipes", pipes2); m.writePlusDraft(P, [60, 80]); history.record(); const heard = { structures: [], pipes: [], plusStations: [] }; store.subscribe("structures", (v) => heard.structures.push(v)); store.subscribe("pipes", (v) => heard.pipes.push(v)); store.subscribe("plusStations", (v) => heard.plusStations.push(v)); // 계산 묶음 먹이기 — 되돌리기 한 번에 출처마다 한 번만(되살림 훅 한 길 · 472-5 겹침 정리) const fed = []; const input = { pipes: pipes2, structures: AB, plusStations: [60, 80] }; const keyOf = { pipe: "pipes", structure: "structures", plus: "plusStations" }; m.feedFacilities({ projectId: P, store, ready: () => true, gone: () => false, saved: () => null, input: (key) => input[key], routed: () => false, feed: (source, value) => { fed.push(source); input[keyOf[source]] = value; }, resync: async () => undefined, }); const tick = () => new Promise((done) => setTimeout(done, 5)); history.undo(); await tick(); const afterUndo = { structures: heard.structures.at(-1) ?? null, pipes: heard.pipes.at(-1) ?? null, }; const counts = { structures: heard.structures.length, pipes: heard.pipes.length, plusStations: heard.plusStations.length, fed: [...fed].sort(), }; history.redo(); await tick(); const afterRedo = { structures: heard.structures.at(-1) ?? null, pipes: heard.pipes.at(-1) ?? null, }; // ② 배수 편집기 — 같은 몸통 = 메아리(안 받음) · 옵션만 바뀜 = 받음 · 고른 관 유지 const editor = m.createPipeEditor(() => undefined); const facilities = m.createFacilityStore(); editor.setPipes([ { chainage_m: 100, reason: "user", id: "k1" }, { chainage_m: 140, reason: "user", id: "k2" }, ]); facilities.replaceFromResponse(pipes1); editor.select(0); const body = () => facilities.attach( editor.pipes().map((p) => ({ chainage_m: p.chainage_m, source: p.reason, id: p.id })), ); const echo = m.adoptEditorPipes(editor, facilities, body(), body()); const next = [ { id: "k2", chainage_m: 140, source: "user", facility: "pipe" }, { id: "k1", chainage_m: 100, source: "user", facility: "pipe", options: { diameter_mm: 1200 } }, ]; const adopted = m.adoptEditorPipes(editor, facilities, body(), next); const picked = editor.pipes()[editor.selected() ?? -1]?.id ?? null; console.log( JSON.stringify({ afterUndo, afterRedo, counts, echo, adopted, picked, options: facilities.get(100)?.options ?? null, }), );