/* ============================================================================= * B05_Profile_UI_Profile_Preview.ts * 계획선 편집 → 횡단 설계 프리뷰 반영 (패널 본체에서 분리, 2026-09-02 · 700줄 한계). * * 계획고가 바뀌면 측점별 횡단 단면적도 바뀐다 — 다시 셈은 계산 묶음 cross 단계 **한 곳**(`design_shared` * savedCross · 387-4)이 하고 여기서는 그 차례만 기다림(▲ 한 번 = cross 한 번). 예전에는 그리기가 낡음을 보면 * 여기서도 따로 다시 셈 → 묶음과 두 번 셈. * 기다리는 동안 「다시 계산 중」 · 묶음 차례가 cross 를 지났는데 남은 낡음은 곡선을 그리고 그 측점만 표시(342-1 · 349-0f). * ========================================================================== */ import type { AlignmentEdits, ProfileAlignment } from "./logic/B05_Profile_UI_Profile_Alignment"; import { holdScreenAlignment } from "../B06_Section/B06_Section_Cross_Refresh"; import { staleChainages } from "../B06_Section/B06_Section_UI_Section_Common"; import type { SectionDetailResponse } from "../B06_Section/B06_Section_Api_Fetch"; import { pagePipeline, pipelineCross } from "../A00_Common/design/design_pipeline_page"; import { useDesignStore } from "../A00_Common/design/design_store"; import type { DesignPipeline, PipelineEvent } from "../A00_Common/design/logic/design_pipeline"; /** cross 를 지났다고 볼 단계(cross 가 같은 입력이라 건너뛰어도 뒤 단계가 알림). */ const AFTER_CROSS = new Set(["cross", "footprint", "masshaul"]); /** 묶음 차례가 cross 를 못 낸 채 끝나도(단계 실패) 「다시 계산 중」을 푸는 때(ms). */ const DRAFT_WAIT_MS = 3000; export interface CrossPreviewContext { projectId: string; detail: () => SectionDetailResponse | null; routeId: () => number | null; edits: () => AlignmentEdits; /** 화면이 쥔 계획선(측점 한 벌) — 다시 셈 · B06 · 3D 가 같은 선을 받음(342-1). */ alignment: () => ProfileAlignment | null; /** 부를 때마다 읽음 — 보기마다 다름. */ debounceMs: () => number; /** 반영이 끝난 뒤 다시 그린다 — 다시 계산한 측점 누가거리를 넘긴다(횡단 카드 갱신용). */ onApplied: (updated: number[]) => void; } export interface CrossPreview { /** 묶음에 cross 다시 부탁(같은 입력이면 안 셈). */ schedule: () => void; /** * 그리기가 잰 낡음 지문 — 돌려주는 값 = 묶음 cross 차례를 기다리는 중인가. 차례가 지난 뒤에도 남은 낡음은 * false — 곡선은 그리고 그 측점만 표시(342-1 · 사용자 확정 10-16). `_draft` = 옛 갈래(노선 초안) · 같은 길. */ scheduleIfStale: (key: string, _draft?: boolean) => boolean; /** 패널을 걷을 때 기다림 타이머를 끈다. */ dispose: () => void; } export function createCrossPreview(ctx: CrossPreviewContext): CrossPreview { let warned = ""; const release = holdScreenAlignment((routeId) => routeId === ctx.routeId() ? ctx.alignment() : null, ); /** 기다리는 묶음 차례 — 그 차례 cross(또는 뒤 단계)가 오면 풂. */ let awaiting: number | null = null; let waitTimer = 0; let watched: DesignPipeline | null = null; const onStage = (event: PipelineEvent): void => { if (event.kind === "stage" && event.generation === awaiting && AFTER_CROSS.has(event.stage)) awaiting = null; }; const stopGeneration = useDesignStore(ctx.projectId, null).subscribe("generation", (gen) => { const pipe = pagePipeline(ctx.projectId); if (!pipe || gen === null) return; if (watched !== pipe) (watched = pipe).subscribe(onStage, "다시 셈 대기"); awaiting = gen; window.clearTimeout(waitTimer); waitTimer = window.setTimeout(() => { if (awaiting !== gen) return; awaiting = null; ctx.onApplied([]); }, DRAFT_WAIT_MS); }); const warnLeft = (key: string): void => { if (!key || key === warned) return; warned = key; console.warn( `[횡단] 다시 셈 뒤에도 낡은 측점 ${staleChainages(key).length}곳 — 곡선은 그리고 그 측점만 표시`, staleChainages(key), ); }; return { scheduleIfStale(key) { if (awaiting === null) warnLeft(key); return key !== "" && awaiting !== null; }, schedule: () => pipelineCross(ctx.projectId), dispose() { window.clearTimeout(waitTimer); stopGeneration(); release(); }, }; }