Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1

This commit is contained in:
2026-10-16 14:18:51 +09:00
5 changed files with 441 additions and 10 deletions
+43 -5
View File
@@ -43,6 +43,7 @@ import {
markAlignmentSaved,
readAlignmentDraft,
} from "../../B05_Profile/B05_Profile_UI_Profile_Edit";
import { readAlignment } from "../../B05_Profile/logic/B05_Profile_UI_Profile_Data";
import {
analysisTerrain,
fetchRoutePlan,
@@ -141,7 +142,7 @@ export function routeDraftBridge(
export const pipelineGrade = (projectId: string, edits: AlignmentEdits): void =>
pages.get(projectId)?.grade(edits);
/** 저장 종단 기준이 바뀜(새 상세 · 구조물 측점) — 처음부터 다시. */
/** 저장 정본이 바뀜(새로 받은 상세 · 새 노선) — 처음부터 다시. 갈아끼운 상세 · 같은 상세 다시 그림은 안 셈. */
export const restartPagePipeline = (projectId: string): void => pages.get(projectId)?.restart();
/** 유토곡선 설정(토량환산계수 · 자연방토 경사) — masshaul 단계 재료 · 표준단면이 막 왔으면 횡단 입력도. */
@@ -178,19 +179,43 @@ function createPagePipeline(
let savedStations: PipelineStation[] = [];
let crossSource: { detail: SectionDetailResponse; source: PipelineCrossSource | null } | null =
null;
/** 저장 정본(서버가 준 상세 · 노선 번호) — 측점 갈아끼움(swap)이 만든 새 상세는 지반 표본 배열을
* 그대로 둠 → 정본 아님(묶음 출력 → 상세 → 다시 묶음 고리를 끊음). */
let canon: { routeId: unknown; samples: unknown; detail: SectionDetailResponse | null } | null =
null;
let shown: SectionDetailResponse | null = null;
let ready = false;
let started = false;
/** 앞 종단이 노선 초안으로 셈한 것인가 — 초안을 버리면 저장 기준으로 다시 그림. */
let drawnDraft = false;
let disposed = false;
/** 열 때 · 정본이 바뀐 차례(start) 번호 — 측점 · 종단을 가게에 안 냄(상세 = 저장 정본 그대로). */
let openGeneration = -1;
const savedLine = (): Vertex[] | null =>
plan ? plan.planned.map(([x, y]): Vertex => [x, y]) : null;
const savedCurves = () =>
(plan?.curves ?? []).map(({ start, apex, end, radius_m }) => ({ start, apex, end, radius_m }));
/** 저장 정본이 바뀌었나(새로 받은 상세 · 새 노선) — 바뀌었으면 잡아 둠. */
const canonChanged = (): boolean => {
const { detail = null, routeId } = link.pipelineSource();
const samples = detail?.longitudinal.samples;
if (canon && canon.routeId === routeId && canon.samples === samples) return false;
canon = { routeId, samples, detail };
return true;
};
/** 저장 노선 계획고 편집(세션 초안 → 저장분) — 노선 초안을 버리면 이것으로 돌아옴. */
const savedEdits = (): AlignmentEdits => {
const routeId = canon?.routeId;
const longitudinal = canon?.detail?.longitudinal;
return (
(typeof routeId === "number" ? readAlignmentDraft(routeId) : null) ??
(longitudinal && readAlignment(longitudinal)?.edits) ??
emptyEdits()
);
};
const crossNow = (): PipelineCrossSource | null => {
const detail = link.pipelineSource().detail ?? null;
const detail = canon?.detail ?? null;
if (!detail) return null;
if (crossSource?.detail !== detail || !crossSource.source)
crossSource = { detail, source: pipelineCrossSource(projectId, detail, savedStations) };
@@ -204,7 +229,14 @@ function createPagePipeline(
minRadiusM: () => plan?.min_radius_m ?? 0,
savedLine: () => plan && { line: savedLine()!, curves: savedCurves() },
}),
follow: createFollowStage({ savedLine }),
follow: createFollowStage({
savedLine,
saved: () => ({
pipes: store.get("pipes") ?? savedPipes,
structures: store.get("structures") ?? savedStructures,
alignment: savedEdits(),
}),
}),
stations: createStationsStage({ intervalM: options.stationIntervalM, types: () => types }),
ground: createGroundStage(groundStageDeps(projectId)),
planline: createPlanlineStage({ savedBase: () => link.pipelineSource().base, savedLine }),
@@ -231,7 +263,11 @@ function createPagePipeline(
},
},
});
pipe.subscribe(publishToStore(store));
const publish = publishToStore(store);
pipe.subscribe((event) => {
const saved = event.kind === "stage" && (event.stage === "ground" || event.stage === "profile");
if (!(saved && event.generation === openGeneration)) publish(event);
});
pipe.subscribe((event) => {
if (event.kind !== "stage" || (event.stage !== "profile" && event.stage !== "cross")) return;
const draft = pipe.input("route") !== null;
@@ -270,8 +306,10 @@ function createPagePipeline(
};
const start = (): void => {
if (disposed || !ready || !link.pipelineSource().base) return;
canonChanged();
started = true;
void pipe.start(inputsNow());
openGeneration = pipe.generation;
};
/** 저장 노선(그린 선 · 곡선표 · 기본 반경) · 그 측점 열쇠 — [저장] 뒤 다시 받음(`restart`). */
@@ -343,7 +381,7 @@ function createPagePipeline(
},
restart() {
if (!started) return start();
void loadPlan().then(start);
if (canonChanged()) void loadPlan().then(start); // 갈아끼움 · 같은 상세 다시 그림은 안 셈
},
earthwork(context) {
earthwork = context;
+3 -3
View File
@@ -494,13 +494,13 @@ export function createDesignProfile(ctx: DesignProfileContext) {
const routeChanged = nextRouteId !== routeId;
const carried = routeChanged && store.edited() ? store.edits() : null;
// 서버 저장분을 기준으로 삼되, 남아 있는 세션 초안이 있으면 그쪽을 우선한다.
if (routeChanged || !store.dirty()) {
if (routeChanged || (store !== preview && !store.dirty())) {
routeId = nextRouteId ?? routeId;
savedEdits = stored?.edits ?? emptyEdits();
store = createProfileEditStore(routeId, savedEdits, () => rebuild());
}
storedBase = stored ? toAlignmentBase(stored) : null;
base = listed();
if (store !== preview) base = listed(); // 노선 초안 그림은 그대로(갈아끼운 상세)
// 이월분은 base 설정 후 미저장 초안으로 커밋한다(확정 시 전송·재탐색 후 새로고침에도 유지).
if (carried) store.replace(carried);
alignment = base ? buildAlignment(base, store.edits()) : null;
@@ -580,7 +580,7 @@ export function createDesignProfile(ctx: DesignProfileContext) {
/** 비정규 측점 목록을 반영해 그래프(세로선+라벨)·테이블(주석)을 다시 그린다. */
setIrregularStations(stations: IrregularStation[]) {
irregularStations = stations;
base = listed();
if (store !== preview) base = listed();
if (base) alignment = buildAlignment(base, store.edits());
pagePipeline.restartPagePipeline(projectId);
draw();