- 상세 가게 forgetSectionMemory — 페이지 dispose 에서 메모리 · 받는 중 약속만 비움(세션 정본 그대로) · 다시 열기 = 새로고침 길 - 묶음 열 때 관 · 구조물 · 추가 측점 초안이 있으면 측점을 냄 — 새로고침 · 다시 열기 = 초안 측점(옛: 새로고침 뒤 관 초안 측점 빠짐) - 버린 노선 초안 차례 끝 · 조용히 연 차례 끝 = 가게 측점 · 종단 = 지금 상세 측점 · 저장 기준(갈아끼움 없음) - 화면 선만 바뀜(비정규 측점 도착) = 상세가 그 선 대비 낡았으면 cross 한 번 더(recrossIfStale) - 시험 test_272_detail_memory · test_272_drop_store_stations Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
125 lines
6.3 KiB
TypeScript
125 lines
6.3 KiB
TypeScript
/* =============================================================================
|
|
* A00_Common/design/design_pipeline_page_publish.ts
|
|
* 페이지 계산 묶음 → 가게(stations · profile · footprint · generation) 잇기(`design_pipeline_page` 700줄 쪼갬 · 466).
|
|
*
|
|
* 조용한 차례(열 때 · 노선 초안을 버린 차례)는 측점 · 종단을 바로 안 냄(상세 = 저장 정본 · 초안 전 상세 그대로) ·
|
|
* 그 목록을 「보인 측점」으로 적어 뒤 같은 목록 차례도 안 냄(관 · 구조물 메아리가 상세를 갈아끼우지 않게 · 349-0q).
|
|
* 조용한 차례 끝(열 때 · 버린 차례의 되돌림 뒤) = 가게 측점 · 종단 = 지금 상세가 쥔 측점 · 그 차례 저장 기준 종단
|
|
* (466 · 요구 272) — 상세에서 지은 목록이라 갈아끼움은 「같은 목록」(349-0o 그대로) · 가게에 초안 측점이 안 남음.
|
|
* 화면 선만 바뀜(정본 그대로) = 상세가 그 선 대비 낡았으면 cross 한 번 더(`recrossIfStale` · 466).
|
|
* 상세 가게는 늦게 불러옴(가게 ↔ 묶음 순환 없음 · Section_Store `recrossSaved` 와 같은 꼴).
|
|
* ========================================================================== */
|
|
|
|
import { publishToStore, type DesignPipeline } from "./logic/design_pipeline";
|
|
import type { PipelineOutputs, PipelineStation, StationKind } from "./logic/design_pipeline_types";
|
|
import type { DesignStore } from "./design_store";
|
|
import type { CrossSection } from "../../B06_Section/B06_Section_Api_Fetch";
|
|
|
|
export interface PagePublisher {
|
|
/** 조용한 차례 끝(열 때 · 노선 초안을 버린 되돌림 뒤) — 가게 측점 = 지금 상세 측점 · 종단 = 그 차례 종단.
|
|
* 상세 없음(떠남 · 아직 안 받음) · 뒤 차례가 떴으면 안 냄(그 차례 몫). */
|
|
shown(projectId: string, routeId: number): Promise<void>;
|
|
/** 열고 맞춤까지 끝 — 그 뒤로 낸 측점이 없으면(조용히 엶) `shown`(지난 페이지 초안 측점이 남지 않게). */
|
|
opened(projectId: string, since: number, routeId: unknown): Promise<void>;
|
|
/** 화면 선만 바뀜 — 상세가 그 선 대비 낡았으면 다 돈 뒤 cross 한 번 더. */
|
|
recheck(projectId: string, routeId: number): void;
|
|
}
|
|
|
|
/** 열쇠 머리 → 측점 종류(`grid:` · `pipe:` · `structure:` · `end:` · `curve:`). */
|
|
const kindOfKey = (key: string): StationKind => {
|
|
const head = key.slice(0, key.indexOf(":"));
|
|
return (["grid", "pipe", "structure", "end", "curve"] as const).find((k) => k === head) ?? "grid";
|
|
};
|
|
|
|
/** 상세 횡단 → 가게 측점 꼴(같은 자리 · 열쇠 묶음 측점이 없을 때 — 자리 · 축은 상세 것 · 지반은 안 실음). */
|
|
function fromSection(section: CrossSection, key: string): PipelineStation {
|
|
const kind = kindOfKey(key);
|
|
const raw = section as unknown as {
|
|
center_x?: number;
|
|
center_y?: number;
|
|
azimuth_deg?: number;
|
|
frame?: { left_xy?: [number, number] };
|
|
};
|
|
return {
|
|
key,
|
|
chainageM: section.chainage_m,
|
|
x: raw.center_x ?? 0,
|
|
y: raw.center_y ?? 0,
|
|
azimuthDeg: raw.azimuth_deg ?? 0,
|
|
left: raw.frame?.left_xy ?? [0, 0],
|
|
kind,
|
|
id: kind === "pipe" || kind === "structure" ? key.slice(key.indexOf(":") + 1) : null,
|
|
ground: null,
|
|
planRadiusM: null,
|
|
curveOuterSide: null,
|
|
curveWideningM: null,
|
|
};
|
|
}
|
|
|
|
/** 묶음 출력을 가게에 냄 — `quietGeneration` = 측점 · 종단을 안 내는 차례 번호(부를 때마다 읽음). */
|
|
export function publishPagePipeline(
|
|
pipe: DesignPipeline,
|
|
store: DesignStore,
|
|
quietGeneration: () => number,
|
|
): PagePublisher {
|
|
const publish = publishToStore(store);
|
|
/** 상세가 맞춘 측점 목록(열쇠 · 자리) — 열 때 · 되돌린 차례 측점 · 마지막으로 낸 측점. 같은 목록이면 가게에
|
|
* 다시 안 냄(되돌린 상세 측점이 묶음 측점과 달라도 · 관 · 구조물 메아리가 상세를 갈아끼우지 않게 · 349-0q). */
|
|
let stationsShown: string | null = null;
|
|
/** 마지막으로 가게에 측점을 낸 차례 번호. */
|
|
let published = -1;
|
|
pipe.subscribe((event) => {
|
|
const saved = event.kind === "stage" && (event.stage === "ground" || event.stage === "profile");
|
|
const quiet = saved && event.generation === quietGeneration();
|
|
if (event.kind === "stage" && event.stage === "ground") {
|
|
const list = JSON.stringify(
|
|
(event.value as PipelineOutputs["ground"] | null)?.stations.map((station) => [
|
|
station.key,
|
|
station.chainageM,
|
|
station.x,
|
|
station.y,
|
|
]) ?? null,
|
|
);
|
|
if (!quiet && list === stationsShown) return;
|
|
stationsShown = list;
|
|
}
|
|
if (!quiet) publish(event);
|
|
if (!quiet && event.kind === "stage" && event.stage === "ground") published = event.generation;
|
|
}, "가게");
|
|
|
|
const publisher: PagePublisher = {
|
|
async opened(projectId, since, routeId) {
|
|
if (typeof routeId !== "number" || published >= since || pipe.input("route")) return;
|
|
await publisher.shown(projectId, routeId).catch(() => undefined);
|
|
},
|
|
async shown(projectId, routeId) {
|
|
const generation = pipe.generation;
|
|
const [{ peekSectionDetail }, { registeredKeys, stationIntervalOf }] = await Promise.all([
|
|
import("../../B06_Section/B06_Section_Section_Store"),
|
|
import("../../B06_Section/B06_Section_Station_Keys"),
|
|
]);
|
|
if (pipe.generation !== generation || pipe.input("route")) return;
|
|
const detail = peekSectionDetail(projectId, routeId);
|
|
if (!detail) return;
|
|
const sections = detail.cross_sections;
|
|
const keys = registeredKeys(projectId, routeId, sections, stationIntervalOf(detail));
|
|
const ground = pipe.output("ground")?.stations ?? [];
|
|
const stations = sections.map((section, i): PipelineStation => {
|
|
const hit = ground.find(
|
|
(station) =>
|
|
station.key === keys[i] && Math.abs(station.chainageM - section.chainage_m) < 1e-3,
|
|
);
|
|
return hit ? { ...hit, chainageM: section.chainage_m } : fromSection(section, keys[i]);
|
|
});
|
|
store.set("stations", stations);
|
|
store.set("profile", pipe.output("profile"));
|
|
},
|
|
recheck(projectId, routeId) {
|
|
void import("../../B06_Section/B06_Section_Section_Store")
|
|
.then((section) => section.recrossIfStale(projectId, routeId))
|
|
.catch(() => undefined);
|
|
},
|
|
};
|
|
return publisher;
|
|
}
|