- 624-3 도구줄 = 공용 표 「b05」 · 아래 줄 = 공용 겹 줄(띠 · 올린 임도망도 숨김 · 처음 = 등고선 · 하천) · 범례 = 공용 legendRows(상태 글판 위) - 노선 띠 300 m 자르기 = B05 자기 칸(등고선 · 하천 · 1 m)만 · 그 밖 겹 = 안 자르는 둘째 GL 칸(맨 밑 · 켠 겹 있을 때만 · GL 만) · 등고선 끔 = 등고 글도 숨김 - 626-1 추천 보기 = 도구줄 아이콘 + 아래 판(영선 · 토공 최소 · 기울기 최소 · 비중 계산 · 끔) — route_recommend 저장본(지금 설계 지형) 선만 · 다시 셈 · 쓰기 없음 - 지형 칩 → 확인창 → 새 POST /route/design-terrain(설계 지형만 바꿈 · 노선 저장 체인 다시 · 노선 · 초기값 · 추천 저장본 그대로 · 잠금 · 실패 되돌림) - 걷음 — 추천 모달(_Recommend · _Help · _Map · CSS) · 좌측 「노선 추천」 칸 · saveRecommendedRoute · UI_RouteReset(서버 as_initial 길 · 시험은 남김) - 부품 상자 — createPickMenu(아이콘 고르기 메뉴 · 판 = body fixed · ✓) - 낡은 시험 고침 · 지움 — 모달 · 좌측 칸 · [적용] 화면 배선을 보던 시험(283 · 346_3 · 349 · 364_4 · 383_1 · 390_5 · 418 · 421 · 421_2 · 476_2 · 509_2 · 509_5 · 551_2 · 600_3 · b05_routeedit_initial · design_view_parity · modal_top · 133_1 · 133_2) · test_508_2 · helper_283 지움(모달 전용) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
181 lines
9.1 KiB
TypeScript
181 lines
9.1 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_RouteEdit_Apply.ts
|
|
* 노선 편집(평면)의 **노선 저장 이음**(346-3) — [노선 반영] 단추는 없앰.
|
|
*
|
|
* · 페이지 [저장](`design_save`)이 `pendingRouteSave` 로 지금 노선 초안을 받아 한 본문(`POST /design/save` ·
|
|
* `route` 칸 = `RouteSaveBody`)에 싣는다 — 노선이 마지막 저장 그대로면 null(안 보냄 · 472-6 6-B).
|
|
* · 서버가 노선 저장 → 관 · 구조물 다시 얹기 → 세션 횡단 고침 넘김(`routeCarryPart` 가 낸 옛 노선 칸 · 같은 TS)까지
|
|
* 한 번에 · 다 저장한 뒤 `routeSaved` · 페이지는 새로고침 없이 다시 세운다(`rebuildAfterRouteSave`).
|
|
* · 옛 추천 [적용](`as_initial` · 지형 바꿈)의 화면 부름은 걷음(626-1 · 서버 길 · 시험은 남김) —
|
|
* 지형 바꿈 = 도구줄 지형 칩(`_Terrain` · `/route/design-terrain`).
|
|
* 편집기가 살아 있는 동안 여기 등록부에 「지금 초안」 · 「다시 세우기」를 건다(프로젝트 하나에 하나).
|
|
* 실패하면 서버가 옛 노선으로 되살리고 화면 초안은 그대로 남는다(오류 한 줄은 부르는 쪽).
|
|
* ========================================================================== */
|
|
|
|
import { clearResults, clearState, readState } from "../A00_Common/b_page_state";
|
|
import type { RouteDraft } from "../A00_Common/design/logic/design_pipeline_types";
|
|
import { useDesignStore } from "../A00_Common/design/design_store";
|
|
import { commitRouteFacilities } from "../A00_Common/design/design_pipeline_page";
|
|
import { currentRoute, remountRoute } from "../A00_Common/router";
|
|
import { showToast } from "@ui/ui_template_elements";
|
|
import { invalidateSectionDetail } from "../B06_Section/B06_Section_Section_Store";
|
|
import { routeCarryInputs } from "../B06_Section/B06_Section_Station_Keys";
|
|
import {
|
|
sectionConfirmBody,
|
|
type StandardCrossSection,
|
|
} from "../B06_Section/B06_Section_Api_Fetch";
|
|
import {
|
|
readStandardCrossSession,
|
|
readStoredStandard,
|
|
} from "../B06_Section/B06_Section_UI_Standard_Panel";
|
|
import { balloonOffsetsPayload } from "@util/common_util_mass_haul_balance_view";
|
|
import type { SavePart } from "../A00_Common/design/design_save_api";
|
|
import { clearRouteLatestCache } from "./B05_Profile_Api_Fetch";
|
|
import {
|
|
replanRoute,
|
|
type RouteReplanResponse,
|
|
type RouteSaveBody,
|
|
} from "./B05_Profile_Api_Replan";
|
|
import type { SnapSetting } from "./B05_Profile_UI_RouteEdit_SnapStep";
|
|
|
|
type Vertex = [number, number];
|
|
type RouteSettings = SnapSetting & { terrain: string | null };
|
|
type RouteNodes = { planned: Vertex[]; curveOn: boolean[]; curveRadius: Array<number | null> } & {
|
|
curveLock?: RouteDraft["curveLock"];
|
|
curveArc?: Array<number | null>;
|
|
};
|
|
|
|
export interface RouteApplyParams {
|
|
/** 옛 [노선 반영] 자리(단추 · 대기 막) — 346-3 부터 안 씀(대기는 페이지 잠금 막 하나). */
|
|
overlay?: HTMLElement;
|
|
busy?: HTMLElement;
|
|
projectId: string;
|
|
/** 지금 편집값 — 저장하는 순간에 읽는다. */
|
|
nodes: () => RouteNodes;
|
|
/** 마지막 저장 뒤 노선이 바뀌었나 — 안 바뀌었으면 보내지 않는다. */
|
|
changed: () => boolean;
|
|
/** [측점 스냅] 켬끔(요구 224-2) · 간격(390-5) — 곡선표에 적혀 측점 셈이 읽음. */
|
|
snap: () => SnapSetting;
|
|
/** 초기노선 갈래 — 저장에 지금 추천을 싣고, 결과 캐시를 비운 뒤 다시 적는다. */
|
|
initial: {
|
|
current: () => unknown;
|
|
keep: () => void;
|
|
};
|
|
/** 저장이 끝나면 페이지를 새로고침 없이 다시 세운다. */
|
|
onApplied: () => void | Promise<void>;
|
|
}
|
|
|
|
interface RouteSource {
|
|
pending: () => Promise<RouteSaveBody | null>;
|
|
settings: () => RouteSettings;
|
|
saved: () => void;
|
|
rebuild: () => void | Promise<void>;
|
|
}
|
|
|
|
const sources = new Map<string, RouteSource>();
|
|
|
|
/** 노드 · 곡선 편집값 → 계약 `RouteDraft`(R · 길이 고정도 실음 — 서버가 곡선표 `edits` 에 둠 · 430). */
|
|
export function routeDraftOf(nodes: RouteNodes, settings: RouteSettings): RouteDraft {
|
|
return {
|
|
rev: 0,
|
|
nodes: nodes.planned.map(([x, y]): Vertex => [x, y]),
|
|
curveOn: nodes.planned.map((_, index) => nodes.curveOn[index] !== false),
|
|
curveRadius: nodes.planned.map((_, index) => nodes.curveRadius[index] ?? null),
|
|
curveLock: nodes.planned.map((_, index) => nodes.curveLock?.[index] ?? null),
|
|
curveArc: nodes.planned.map((_, index) => nodes.curveArc?.[index] ?? null),
|
|
stationSnap: settings.stationSnap,
|
|
stationSnapStep: settings.stationSnapStep ?? null,
|
|
terrain: settings.terrain,
|
|
};
|
|
}
|
|
|
|
/** 편집기를 등록부에 건다 — 같은 프로젝트에 새 편집기가 서면 그쪽으로 바뀐다. 돌려준 함수 = 편집기가 떠날 때 풂
|
|
* (안 풀면 노선 저장 뒤 다시 선 페이지에서 옛 편집기 「고친 것 있음」이 [저장] · [확정]마다 노선 저장을 다시 돌림 · 445-2). */
|
|
export function bindRouteApply(params: RouteApplyParams): () => void {
|
|
// 손으로 고친 노선 = 설계 지형 그대로 — 지형 바꿈은 추천 [적용]만(509-5)
|
|
const settings = () => ({ ...params.snap(), terrain: null });
|
|
const source: RouteSource = {
|
|
pending: async () => {
|
|
const nodes = params.nodes();
|
|
if (nodes.planned.length < 2 || !params.changed()) return null;
|
|
return {
|
|
draft: routeDraftOf(nodes, settings()),
|
|
initial: params.initial.current(),
|
|
// 손으로 고친 노선 = 계획고 옮김만(344-2 · 349-0v) — 균형 종단은 서버 추천 [적용](`as_initial`) 길만.
|
|
};
|
|
},
|
|
settings,
|
|
saved: () => params.initial.keep(),
|
|
rebuild: params.onApplied,
|
|
};
|
|
sources.set(params.projectId, source);
|
|
return () => void (sources.get(params.projectId) === source && sources.delete(params.projectId));
|
|
}
|
|
|
|
/** 지금 보낼 노선 초안 — 편집기가 있으면 그것(안 바뀌었으면 null) · 없으면 가게 `routeDraft`. */
|
|
export async function pendingRouteSave(projectId: string): Promise<RouteSaveBody | null> {
|
|
const source = sources.get(projectId);
|
|
if (source) return source.pending();
|
|
const draft = useDesignStore(projectId, null).get("routeDraft");
|
|
return draft && draft.nodes.length >= 2 ? { draft } : null;
|
|
}
|
|
|
|
/** 노선 초안 저장(추천 [적용]) — 실패하면 throw(서버가 되살림 · 화면 초안 그대로). 새 노선 번호 · 결과를 돌려줌. */
|
|
export async function saveRouteDraft(
|
|
projectId: string,
|
|
body: RouteSaveBody,
|
|
): Promise<RouteReplanResponse> {
|
|
const saved = await replanRoute(projectId, body);
|
|
routeSaved(projectId);
|
|
return saved;
|
|
}
|
|
|
|
/** 노선을 저장함(서버가 다 저장한 뒤) — 묶음의 초안 전 관 · 구조물 버림(초안 비움을 「버림」으로 되돌리지 않게 ·
|
|
* 저장한 초안 자리가 정본) · 가게 노선 초안 비움 · 조회 결과 캐시 비움(옛 노선 것 · 추천은 새 노선의 출발점이라
|
|
* 다시 적음) · 편집기 「저장됨」. */
|
|
export function routeSaved(projectId: string): void {
|
|
commitRouteFacilities(projectId);
|
|
useDesignStore(projectId, null).set("routeDraft", null);
|
|
clearResults(projectId);
|
|
sources.get(projectId)?.saved();
|
|
}
|
|
|
|
/**
|
|
* 노선 바뀐 [저장]의 횡단 조각(472-6 6-B) — 옛 노선 세션 고침 원본(측점 열쇠 칸 · 누가거리 칸 · 열쇠 목록 · 관 장부)을
|
|
* `carry` 로 · 횡단 본문 = 표준 횡단면 · 풍선 자리(옛 `saveCachedCrossPatches` 와 같은 칸). 새 측점 patch 는 서버가
|
|
* 같은 TS 로 셈(Node `stations.carry`) · 고침이 없어도 한 번 나감(429 ④). 손 상단측은 안 보냄(요구 274 · 새 지형이
|
|
* 정함) — 다 저장한 뒤 세션 상단측 · 관 종류 바꿈 자리를 비우고 버린 상단측 수를 알림.
|
|
*/
|
|
export function routeCarryPart(
|
|
projectId: string,
|
|
fromRouteId: number,
|
|
): { carry: Record<string, unknown>; cross: SavePart } {
|
|
// 세션 고침 → 저장분(세션에 안 베낌 · 272) — 서버 새 측점 기본 설계가 저장분 표준으로 섬
|
|
const standard = (readStandardCrossSession(projectId) ??
|
|
readStoredStandard(projectId)) as StandardCrossSection | null;
|
|
const uphill = Object.keys(readState("uphill", projectId) ?? {}).length;
|
|
return {
|
|
carry: routeCarryInputs(projectId, fromRouteId),
|
|
cross: {
|
|
body: sectionConfirmBody(standard ?? undefined, undefined, {
|
|
balloon_offsets: balloonOffsetsPayload(),
|
|
}),
|
|
after: () => {
|
|
clearState("kindreset", projectId); // 비울 자리를 보냈음(318-4)
|
|
clearState("uphill", projectId);
|
|
if (uphill)
|
|
showToast(`상단측 손 고침 ${uphill}곳 — 노선이 바뀌어 새 지형으로 정했습니다.`, "info");
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
/** 저장 뒤 페이지를 새로고침 없이 다시 세운다 — 편집기가 걸어 둔 길(옛 노선 캐시 비움 포함) · 없으면 라우터. */
|
|
export async function rebuildAfterRouteSave(projectId: string): Promise<void> {
|
|
const source = sources.get(projectId);
|
|
if (source) return source.rebuild();
|
|
clearRouteLatestCache(projectId);
|
|
invalidateSectionDetail(projectId);
|
|
remountRoute(currentRoute());
|
|
}
|