/* ============================================================================= * A00_Common/design/design_page_data.ts * B05 · B06 한 페이지 자료 줄(PLAN 386-2) — 저장분(구조물 · 관 · 정의 판 확인)을 페이지에서 한 번만 받음. * * 받는 쪽 = 묶음(`design_pipeline_page`) · 종단 보기 구조물 칸 · 횡단 보기 구조물 칸 · 초안 측점 * (`B06_Section_Section_Drafts`) · [저장] 관 얹기(`design_save`) — 보기 전환 · 다시 보임 = 요청 0. * 다시 받기 = 저장 정본이 바뀐 뒤 한 번 — `invalidateSectionDetail` → `forgetDraftStations` → 여기 비움 * ([저장] · [확정] · [초기화] · 노선 [적용] · 파일 다시 올림) · 페이지를 떠나면 비움. * 종류 목록은 `fetchStructureTypes` 가 탭 수명 동안 쥠 — 정의 판 확인만 페이지마다 한 번. * 돌려줄 때마다 사본 — 받는 쪽이 고쳐도 다른 쪽 저장분이 안 바뀜. * 「계산 중」 — 묶음 차례가 떠 있는 동안 좌측 아래 점(`onPageBusy`). * 노선 id 한 칸(386-2b) — 횡단 context(서버 지금 노선 · 세션 캐시)에서 채움 · 두 보기 · 종단도 · [저장] · * 평면 횡단 판이 여기서만 읽음 · 노선이 바뀌면 페이지를 통째 다시 세움(remountRoute) → 페이지 동안 고정. * ========================================================================== */ import { fetchDetailPipePoints } from "../../B04_PreProcess/B04_PreProcess_Api_Fetch"; import type { SectionContextResponse } from "../../B06_Section/B06_Section_Api_Fetch"; import { setPageRouteId } from "./design_page_route"; export { pageRouteId } from "./design_page_route"; import { dropStaleStructureTypes, fetchStructures, } from "../../B05_Profile/B05_Profile_Api_Structures"; const memo = new Map>(); function once(key: string, load: () => Promise): Promise { let hit = memo.get(key) as Promise | undefined; if (!hit) { hit = load(); memo.set(key, hit); hit.catch(() => memo.get(key) === hit && memo.delete(key)); // 실패는 다음에 다시 물음 } return hit.then((value) => structuredClone(value)); } /** 저장 구조물(structures.json · 판번호 · 정의 판). */ export const pageStructures = (projectId: string): ReturnType => once(`structures:${projectId}`, () => fetchStructures(projectId)); /** 저장 관(pipe_points.json · 유역). */ export const pagePipes = (projectId: string): ReturnType => once(`pipes:${projectId}`, () => fetchDetailPipePoints(projectId)); /** 정의 판이 바뀌었으면 종류 캐시 버림 — 페이지마다 한 번(탭이 다시 보일 때는 `onStaleStructureTypes`). */ export const pageTypesChecked = (projectId: string): Promise => once(`types:${projectId}`, () => dropStaleStructureTypes()); /** 저장 정본이 바뀜 · 페이지를 떠남(`leaving` = 노선 id 칸도) — 다음에 부르면 한 번 다시 받음. */ export function forgetPageData(projectId: string, leaving = false): void { for (const key of [...memo.keys()]) if (key.endsWith(`:${projectId}`)) memo.delete(key); if (leaving) setPageRouteId(projectId, null); } /** 횡단 context(세션 캐시 · 새 요청 없음) — 노선 id 칸을 채움 · 두 보기가 이것으로만 받음. 서버 창구는 부를 때 * 불러옴(묶음 · 초안 측점이 끄는 이 파일이 화면 모듈을 안 끌게). */ export async function pageSectionContext(projectId: string): Promise { const { fetchSectionContext } = await import("../../B06_Section/B06_Section_Api_Fetch"); const context = await fetchSectionContext(projectId); setPageRouteId(projectId, context.route_id); // 노선 id 한 칸(design_page_route) return context; } const busy = new Map(); const busyListeners = new Map void>>(); const notify = (projectId: string): void => { const now = (busy.get(projectId) ?? 0) > 0; busyListeners.get(projectId)?.forEach((fn) => fn(now)); }; /** 묶음 차례가 떠 있나 — 그 사이 폼이 다시 실려도 좌측 패널은 안 폄(`design_user_pick` · 398-1). */ export const pageBusy = (projectId: string): boolean => (busy.get(projectId) ?? 0) > 0; /** 묶음 차례 하나가 뜸 — 끝나면(밀림 · 실패 포함) 내림. */ export function trackPageBusy(projectId: string, run: Promise | null): void { if (!run) return; busy.set(projectId, (busy.get(projectId) ?? 0) + 1); notify(projectId); const done = (): void => { busy.set(projectId, Math.max(0, (busy.get(projectId) ?? 1) - 1)); notify(projectId); }; run.then(done, done); } /** 「계산 중」 켬 · 끔 듣기 — 떼는 함수를 돌려줌. */ export function onPageBusy(projectId: string, fn: (busy: boolean) => void): () => void { const set = busyListeners.get(projectId) ?? new Set(); busyListeners.set(projectId, set.add(fn)); fn((busy.get(projectId) ?? 0) > 0); return () => set.delete(fn); }