/* ============================================================================= * B05_Profile_UI_RouteEdit_Shift.ts * 횡단 중심점 좌우 → 노선 편집(356-3) — B06 카드가 부르는 host(`setRouteShiftHost`)를 편집기가 등록. * * 한 번 = 노드 옮김 · 끼움 한 번(고름은 `logic/B05_Profile_RouteShift`) → 편집기 한 걸음(applyEdit) → * 계산 묶음이 뒤 측점을 조용히 다시 셈 → 새 종단이 오면 옮기기 전 종단을 그대로(노면 그대로 · 364-7 · * 바뀐 구간 첫 계획선 맞춤에 휩쓸리지 않게 · 계획고 편집). 둘을 페이지 되돌리기 한 걸음으로(사진첩 pause · resume). * ========================================================================== */ import type { DesignPipeline } from "../A00_Common/design/logic/design_pipeline"; import { pagePipeline, pipelineGrade } from "../A00_Common/design/design_pipeline_page"; import { setRouteShiftHost } from "../B06_Section/B06_Section_UI_Cross_Center"; import { pageHistory } from "./B05_Profile_UI_Profile_History"; import type { RouteEditHost } from "./B05_Profile_UI_RouteEdit"; import type { RouteEditSnapshot } from "./B05_Profile_UI_RouteEdit_History"; import type { EditedCurve } from "./logic/B05_Profile_UI_RouteEdit_Curve"; import { spliceNode } from "./logic/B05_Profile_UI_RouteEdit_Edits"; import { emptyEdits, type AlignmentEdits } from "./logic/B05_Profile_UI_Profile_Alignment"; import { holdGrade, planRouteShift } from "./logic/B05_Profile_RouteShift"; type Vertex = [number, number]; /** 새 종단을 기다리는 한계(ms) — 넘으면 높이 보정 없이 한 걸음을 닫음. */ const PROFILE_WAIT_MS = 10000; /** 다음 조작의 종단 단계(가장 늦은 차례)가 끝날 때 true · 한계를 넘으면 false. */ function nextProfile(pipe: DesignPipeline): Promise { return new Promise((resolve) => { const done = (ok: boolean): void => { clearTimeout(timer); stop(); resolve(ok); }; const timer = setTimeout(() => done(false), PROFILE_WAIT_MS); const stop = pipe.subscribe((event) => { if ( event.kind === "stage" && event.stage === "profile" && event.generation === pipe.generation ) done(true); }, "중심점 좌우"); }); } /** * 편집기가 자료를 읽은 뒤 부름 — `columns` = 지금 편집값(산 배열 · 끼움 · 옮김이 그 자리를 고침) · * `apply` = 편집기 한 걸음 · `drawn` = [그린 계획노선, 곡선표]. 편집기가 떠나면 host 를 뗌. */ export function bindRouteShift( host: RouteEditHost, projectId: string, columns: () => RouteEditSnapshot, apply: (message: string) => void, drawn: () => [Vertex[], EditedCurve[]], ): void { setRouteShiftHost(async (chainageM, deltaM) => { const pipe = pagePipeline(projectId); if (pipe && !pipe.output("profile")) await nextProfile(pipe); // 막 세운 묶음(B06 바로 열기) — 첫 셈 const now = columns(); const [line, curves] = drawn(); const plan = planRouteShift({ planned: now.planned, line: line.length ? line : now.planned, curves, locked: now.apexLock, chainageM, deltaM, }); if (!plan) return "노선을 아직 못 읽었습니다."; if (plan.kind === "locked") return "고정한 노드라 옮기지 않았습니다 — 고정을 먼저 푸세요."; const before = pipe?.output("profile"); const lineBefore = pipe?.output("route")?.line ?? [...line]; // 끼움 · 옮김이 산 배열을 고침 const album = pageHistory(); album?.pause(); try { const routed = pipe ? nextProfile(pipe) : null; if (plan.kind === "move") now.planned[plan.index] = plan.to; else spliceNode(now, plan.index, plan.at); const side = deltaM > 0 ? "좌" : "우"; apply(`횡단 중심점 — 측점 노선을 ${side}로 ${Math.abs(deltaM).toFixed(1)}m 옮겼습니다.`); if (!pipe || !before || !(await routed)) return null; const base = pipe.output("planline"); const edits = (pipe.input("alignment") as AlignmentEdits | null) ?? emptyEdits(); const lineAfter = pipe.output("route")?.line ?? drawn()[0]; const pinned = base && holdGrade(base, edits, before, lineBefore, lineAfter); if (!pinned) return null; const graded = nextProfile(pipe); pipelineGrade(projectId, pinned); await graded; return null; } finally { album?.resume(); } }); host.onDispose(() => setRouteShiftHost(null)); }