- POST /design/save 에 route(노선 초안) · carry(옛 노선 세션 칸) 칸 · 서버 = 노선 저장 → Node facility.rebase(rebasePipeDraft · rebaseStructureDraft) → 6-A 차례(새 노선) → Node stations.carry(routeCarryPatches) → 횡단 저장 - 되돌림 범위 = 노선 행 · 자식 표 · 단계 · 배수 폴더 · 노선 파일 · 지표면 확정(설계 지형 바꿈) - 손 상단측은 안 넘김(요구 274) · 노선 바뀐 [확정] = [저장](새 노선 본 뒤 다시) - 브라우저 = 노선 초안 + 사용자 칸만 한 본문 · 옛 차례 길(saveRouteDraft 뒤 flush · carryAfterRouteSave · keepDraftsOnNewRoute) · 부름 0 된 flush 창구 여섯 · saveCachedCrossPatches 걷음 - 등록부 R9 stations.carry · F10 facility.rebase · 옛 차례 단언 시험 한 본문 꼴로 · test_472_6_route_save(옛 길 = 새 길 · 실패 되돌림) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
7.0 KiB
TypeScript
149 lines
7.0 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_Api_PlusStations.ts
|
|
* 추가 측점(368-2 · 요구 240-21) — 종단 원지반선에서 넣은 일반 측점(정수 m · 이름 없음).
|
|
*
|
|
* 초안 = 설계 가게 `plusStations`(세션 · 되돌리기 사진첩에 듦) · 저장분 = 서버 `plus_stations.json`
|
|
* (`GET/PUT /section/plus-stations`) · [저장] 차례 = 구조물 뒤 · 계획고 앞(`design_save`).
|
|
* 측점 계획(`planStations`)이 읽는 곳 = 초안 ?? 저장분(`setPlusStations` · 부를 때마다 읽음).
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL, API_TIMEOUT_MS } from "../config/config_frontend";
|
|
import { draftProjectId, useDesignStore } from "../A00_Common/design/design_store";
|
|
import { onPageHistory } from "./B05_Profile_UI_Profile_History";
|
|
import type { SavePart } from "../A00_Common/design/design_save_api";
|
|
|
|
const saved = new Map<string, number[]>();
|
|
const loaded = new Map<string, Promise<void>>();
|
|
|
|
async function request(projectId: string, init: RequestInit = {}): Promise<number[]> {
|
|
const controller = new AbortController();
|
|
const timer = window.setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/projects/${projectId}/section/plus-stations`, {
|
|
credentials: "include",
|
|
headers: { "Content-Type": "application/json" },
|
|
signal: controller.signal,
|
|
...init,
|
|
});
|
|
const payload = await response.json().catch(() => null);
|
|
if (!response.ok)
|
|
throw new Error(payload?.message ?? `요청이 실패했습니다 (${response.status}).`);
|
|
return Array.isArray(payload?.chainages) ? payload.chainages.map(Number) : [];
|
|
} finally {
|
|
window.clearTimeout(timer);
|
|
}
|
|
}
|
|
|
|
export const readPlusDraft = (projectId: string): number[] | null =>
|
|
useDesignStore(projectId, null).get("plusStations");
|
|
|
|
/** 지금 추가 측점 — 초안이 있으면 초안(빈 목록 = 다 지움) · 없으면 저장분. */
|
|
export const plusStationsOf = (projectId: string): number[] =>
|
|
readPlusDraft(projectId) ?? saved.get(projectId) ?? [];
|
|
|
|
/** 초안을 씀(정렬 · 같은 m 하나) — 바뀜 신호로 B05 종단 · B06 목록이 다시 맞춤. */
|
|
export function writePlusDraft(projectId: string, next: number[] | null): void {
|
|
const list = next && [...new Set(next.map(Math.round))].sort((a, b) => a - b);
|
|
useDesignStore(projectId, null).set("plusStations", list);
|
|
}
|
|
|
|
/** 저장분이 바뀜 — 듣는 쪽을 부름(초안 칸 값 그대로 다시 씀). */
|
|
function notify(projectId: string): void {
|
|
useDesignStore(projectId, null).set("plusStations", readPlusDraft(projectId));
|
|
}
|
|
|
|
// 되돌리기 · 다시하기는 세션을 바로 되씀 — 바뀜 신호를 다시 냄(종단 측점선 · B06 목록) · 다른 되살림이
|
|
// 측점 목록을 다시 세운 뒤(같은 차례 안에서 덮이지 않게)
|
|
onPageHistory({ restore: () => void setTimeout(() => notify(draftProjectId()), 0) });
|
|
|
|
/** 저장분을 한 번 받음(못 받으면 빈 목록) — 읽는 곳 두기(`setPlusStations`)는 횡단 상세 가게 몫. */
|
|
export function loadSavedPlusStations(projectId: string): Promise<void> {
|
|
if (!loaded.has(projectId))
|
|
loaded.set(
|
|
projectId,
|
|
request(projectId)
|
|
.then((list) => {
|
|
saved.set(projectId, list);
|
|
if (list.length) notify(projectId);
|
|
})
|
|
.catch(() => undefined),
|
|
);
|
|
return loaded.get(projectId)!;
|
|
}
|
|
|
|
/** 노선 초안 전 추가 측점 초안(없으면 null) — 초안을 버리면 이것으로(459). */
|
|
const beforeRoute = new Map<string, number[] | null>();
|
|
/** follow 만 쓴 초안(사용자 초안 없던 때) — 그대로면 [저장]이 PUT 안 함(서버 따라감이 정본 · 459). */
|
|
const followedOnly = new Map<string, string>();
|
|
|
|
/** 노선 밖 알림 글(459 · 458 「노선 밖 N 곳」과 같은 말). */
|
|
export const outsideNotice = (count: number): string =>
|
|
`노선 밖 ${count} 곳 — 측점 · 알약에서 뺌(좌표는 그대로 · 노선을 다시 늘이면 돌아옴)`;
|
|
|
|
/** 프로젝트마다 마지막으로 알린 노선 밖 수 — 같으면 다시 안 알림. */
|
|
const warnedOutside = new Map<string, number>();
|
|
|
|
/** follow 출력 꼴(묶음 `FollowPatch` 중 쓰는 칸만). */
|
|
interface FollowedDraft {
|
|
plusStations?: ReadonlyArray<number> | null;
|
|
pipes?: unknown;
|
|
structures?: unknown;
|
|
}
|
|
|
|
/**
|
|
* 노선 초안 동안 따라감 뒤처리(459) — 묶음 follow 출력의 추가 측점(새 노선 정수 m)을 초안에 씀 · 노선 밖 관 ·
|
|
* 구조물 수를 알림(바뀐 때만). `next = null` = 노선 초안을 버림(초안 전 값으로). 부르는 곳 =
|
|
* design_pipeline_page follow 구독(관 · 구조물 `writeFacilities` 곁 · 초안 버림 갈래도).
|
|
*/
|
|
export function followPlusDraft(projectId: string, next: FollowedDraft | null | undefined): void {
|
|
if (next === null) {
|
|
warnedOutside.delete(projectId);
|
|
if (!beforeRoute.has(projectId)) return;
|
|
writePlusDraft(projectId, beforeRoute.get(projectId)!);
|
|
beforeRoute.delete(projectId);
|
|
followedOnly.delete(projectId);
|
|
return;
|
|
}
|
|
if (!next) return;
|
|
const outside = [next.pipes, next.structures]
|
|
.flatMap((list) => (Array.isArray(list) ? list : []))
|
|
.filter((item) => (item as { outside?: boolean })?.outside).length;
|
|
if (outside !== (warnedOutside.get(projectId) ?? 0)) {
|
|
warnedOutside.set(projectId, outside);
|
|
// 알림 틀은 늦게 불러옴(이 모듈을 쓰는 Node 시험이 화면 모듈 없이 돎)
|
|
if (outside)
|
|
void import("@ui/ui_template_elements").then(({ showToast }) =>
|
|
showToast(outsideNotice(outside), "warning", 6000),
|
|
);
|
|
}
|
|
const plus = next.plusStations;
|
|
if (!plus) return;
|
|
if (!beforeRoute.has(projectId)) beforeRoute.set(projectId, readPlusDraft(projectId));
|
|
writePlusDraft(projectId, [...plus]);
|
|
if (beforeRoute.get(projectId)) followedOnly.delete(projectId);
|
|
else followedOnly.set(projectId, JSON.stringify(readPlusDraft(projectId)));
|
|
}
|
|
|
|
/** 한 본문 [저장] 조각(472-6) — 사용자 초안이 있으면 `{chainages}` · 없으면(follow 만 쓴 초안 포함 · 459) null ·
|
|
* 저장 뒤 저장분을 받은 목록(없으면 다시 받음)으로 · 초안 비움. */
|
|
export function plusStationsPart(projectId: string): SavePart {
|
|
const auto = followedOnly.get(projectId);
|
|
followedOnly.delete(projectId);
|
|
beforeRoute.delete(projectId);
|
|
const kept = readPlusDraft(projectId);
|
|
if (kept && auto === JSON.stringify(kept))
|
|
useDesignStore(projectId, null).set("plusStations", null);
|
|
const draft = readPlusDraft(projectId);
|
|
return {
|
|
body: draft ? { chainages: draft } : null,
|
|
after: async (response) => {
|
|
const given = response?.chainages;
|
|
const list = Array.isArray(given) ? given.map(Number) : await request(projectId);
|
|
saved.set(projectId, list);
|
|
loaded.set(projectId, Promise.resolve());
|
|
if (draft) useDesignStore(projectId, null).set("plusStations", null);
|
|
else notify(projectId);
|
|
},
|
|
};
|
|
}
|