- 지도 아래 한 줄 띠: 원지반선 · 계획선 · 측점 눈금 · 켬/끔 단추(추천 창 「종단 반영」과 한 벌) - 켬 = 서버 새 통로 /route/profile-preview 가 [확인]과 같은 폴리라인(같은 R · CSV 자릿수)으로 균형 종단 셈 · 끔 = /route/elevations 지반선만 - 갱신은 놓을 때 · 되돌리기 · 추천 적용 · 켬/끔 때만 · 최신 응답만 채택 · 기다리는 동안 옛 그림 흐림 - [확인]은 띠 응답을 기다린 뒤 띠에 보인 계획선(균형 종단 · 지반고)을 그대로 적용 — 추천을 손으로 고쳐도 켬이면 균형 종단 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
102 lines
4.5 KiB
TypeScript
102 lines
4.5 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_RouteEdit_Apply.ts
|
|
* 계획노선 편집 모달의 **[확인]** — 무거운 재계산과 대기 표시.
|
|
*
|
|
* 다시 계산은 **노선이 바뀐 뒤 [확인]에서만** 돈다(계획서 20 ④). 노선이 창을 연 그대로면
|
|
* 계산 없이 닫는다. [초기화](옛 「예상노선으로」)는 편집값만 되돌린다(`_Initial`).
|
|
*
|
|
* 누르면 서버가 배수유역부터 종·횡단·유토곡선까지 전 단계를 다시 돈다(약 90초). 중간 취소는
|
|
* 만들지 않기로 했으므로(계획서 0-2, 2026-09-09) **얼마나 지났는지**를 초로 보여 사람이
|
|
* 멈춘 것인지 도는 것인지 알 수 있게 한다.
|
|
* ========================================================================== */
|
|
|
|
import { clearDrafts, clearResults } from "../A00_Common/b_page_state";
|
|
import { showToast } from "@ui/ui_template_elements";
|
|
import { replanRoute } from "./B05_Profile_Api_Replan";
|
|
|
|
type Vertex = [number, number];
|
|
|
|
export interface RouteApplyParams {
|
|
overlay: HTMLElement;
|
|
/** 화면 전체를 덮는 대기 막. 안에 `<span>` 한 개가 글을 받는다. */
|
|
busy: HTMLElement;
|
|
projectId: string;
|
|
/** 지금 편집값 — 누른 순간에 읽는다. */
|
|
nodes: () => { planned: Vertex[]; curveOn: boolean[]; curveRadius: Array<number | null> };
|
|
/** 창을 연 뒤 노선이 바뀌었나 — 안 바뀌었으면 다시 계산하지 않는다. */
|
|
changed: () => boolean;
|
|
/** 초기노선 갈래 — [확인]에 지금 초기값을 싣고, 결과 캐시를 비운 뒤 다시 적는다. */
|
|
initial: {
|
|
current: () => unknown;
|
|
keep: () => void;
|
|
};
|
|
/** 종단 띠 — 응답을 기다려 띠에 보인 계획선(균형 종단 · 지반고)을 그대로 적용한다(PLAN 75-1). */
|
|
profileShown: () => Promise<boolean>;
|
|
/** 성공하면 모달을 닫고 화면을 다시 읽는다. */
|
|
close: () => void;
|
|
onApplied: () => void | Promise<void>;
|
|
}
|
|
|
|
/** [확인]을 붙인다. 리스너는 모달과 수명이 같다. */
|
|
export function bindRouteApply(params: RouteApplyParams): void {
|
|
const { overlay, busy, projectId } = params;
|
|
|
|
async function runHeavy(label: string, task: () => Promise<unknown>): Promise<void> {
|
|
busy.hidden = false;
|
|
// ⚠ 「몇 분」은 옛 값이었다 — 0-11 로 **약 90초**가 됐다(2026-09-09 실측 네 번:
|
|
// 87.3 · 90.0 · 93.9 · 95.4초).
|
|
const message = busy.querySelector("span")!;
|
|
const started = Date.now();
|
|
const tick = (): void => {
|
|
const seconds = Math.round((Date.now() - started) / 1000);
|
|
message.textContent = `${label} — 배수유역부터 다시 계산 중입니다. 1분 반쯤 걸립니다 (${seconds}초 지남).`;
|
|
};
|
|
tick();
|
|
const timer = window.setInterval(tick, 1000);
|
|
try {
|
|
await task();
|
|
// 노선이 바뀌면 세션 초안·조회 캐시는 옛 노선 것이라 남기지 않는다(PLAN 0-7 확정 5).
|
|
clearDrafts(projectId);
|
|
clearResults(projectId);
|
|
params.initial.keep(); // 초기값은 새 노선의 출발점 — 옛 노선 결과와 함께 버리지 않는다.
|
|
showToast("노선을 다시 계산했습니다.", "success");
|
|
params.close();
|
|
await params.onApplied();
|
|
} catch (error) {
|
|
busy.hidden = true;
|
|
showToast(error instanceof Error ? error.message : "노선 재계산에 실패했습니다.", "error");
|
|
} finally {
|
|
window.clearInterval(timer); // 성공·실패·닫힘 어느 쪽이든 멈춘다
|
|
}
|
|
}
|
|
|
|
overlay.querySelector('[data-act="apply"]')!.addEventListener("click", () => {
|
|
const { planned, curveOn, curveRadius } = params.nodes();
|
|
if (planned.length < 2) {
|
|
showToast("노선은 노드가 2개 이상이어야 합니다.", "error");
|
|
return;
|
|
}
|
|
if (!params.changed()) {
|
|
showToast("노선이 그대로라 다시 계산하지 않았습니다.", "info");
|
|
params.close();
|
|
return;
|
|
}
|
|
void runHeavy("계획노선 반영", async () =>
|
|
replanRoute(
|
|
projectId,
|
|
planned.map(([x, y], index) => ({
|
|
x,
|
|
y,
|
|
curve: curveOn[index] !== false,
|
|
radius_m: curveRadius[index] ?? null,
|
|
})),
|
|
{
|
|
initial: params.initial.current(),
|
|
// 띠 응답 전에 눌렀으면 기다린다 — 서버는 같은 노선 · 같은 셈으로 띠 계획선을 다시 낸다.
|
|
balanced_profile: await params.profileShown(),
|
|
},
|
|
),
|
|
);
|
|
});
|
|
}
|