Files
Aislo/B06_Section/B06_Section_UI_Page_Design_Sync.ts
T

164 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* B06_Section_UI_Page_Design_Sync.ts
* 측점 설계 선택 반영·재계산 창구. `_UI_Page` 700줄 제한 대응으로 떼어낸 것이며
* 동작은 그대로다(2026-09-13 분리).
* - `handleDesignChange` : 버튼 선택 즉시 반영 + 세션 초안 기록 + 재계산
* - `recomputeIfRock` : 암 경계·암 절토 경사 변경 후 암 측점만 재계산
* - `reconcileStaleDesigns`: 횡단 다시 셈을 계산 묶음에 부탁(cross 단계 한 곳 · 387-4)
* - `syncBermSpans` : 구조물 측점 열쇠(소단 사본은 페이지 시설 줄이 씀 · 467)
* - `applyPanelToAll` : 표준 횡단면 설정 [전체 반영]
* 고친 뒤 계산 묶음 cross 입력도 다시(`pipelineCross` · 노선 초안 동안 카드 = 묶음 새 노선 상세 · 365-2).
* ========================================================================== */
import {
readCrossDesignChoice,
writeBermDepth,
writeCrossDesignChoice,
writeShoulderExtend,
type CrossDesignChoice,
} from "./B06_Section_Cross_Design_Session";
import { showToast } from "@ui/ui_template_elements";
import { L } from "./B06_Section_UI_Page_Common";
import type { SectionDetailResponse } from "./B06_Section_Api_Fetch";
import type { CrossDesignChange, SectionViewController } from "./B06_Section_UI_Section_View";
import type { StructureInstance } from "../B05_Profile/B05_Profile_Api_Structures";
import { setCrossDetailHost } from "./B06_Section_UI_Cross_Detail";
import { useStructureIds } from "./B06_Section_Station_Keys";
import { pipelineCross, reconcilePagePipeline } from "../A00_Common/design/design_pipeline_page";
import { forceCross } from "../A00_Common/design/design_pipeline_cross";
export interface DesignSyncContext {
projectId: string | null;
routeId: () => number | null;
detail: () => SectionDetailResponse | null;
/** 뷰는 이 모듈보다 **뒤에** 만들어지므로 그때 채워지는 참조를 통해 부른다. */
view: () => SectionViewController;
}
export interface DesignSyncController {
handleDesignChange: (chainageM: number, change: CrossDesignChange) => Promise<void>;
recomputeIfRock: (chainageM: number) => void;
reconcileStaleDesigns: (options?: { force?: boolean }) => Promise<void>;
syncBermSpans: (structures: ReadonlyArray<StructureInstance>) => void;
applyPanelToAll: () => Promise<void>;
}
/** 카드 고름 칸 — 세션 `crossdesign` 에 담는 것(바꾼 칸만 · 272). */
const CHOICE_KEYS = [
"ground_type",
"section_mode",
"ditch_side",
"ditch_type",
"paved",
"two_stage_slope",
"ditch_choice",
] as const satisfies ReadonlyArray<keyof CrossDesignChoice & keyof CrossDesignChange>;
export function createDesignSync(ctx: DesignSyncContext): DesignSyncController {
const { projectId } = ctx;
// 횡단 [상세] 모달(138-4)이 쓰는 프로젝트 · 노선 · 상세 · 칸 크기 그림(173-4)
if (projectId)
setCrossDetailHost({
projectId,
routeId: ctx.routeId,
detail: ctx.detail,
chart: (stationId, widthPx, heightPx) => ctx.view().detailChart(stationId, widthPx, heightPx),
});
/**
* 측점 설계 버튼 변경 처리: 선택을 세션 초안에 쓰고 브라우저 다시 셈(바뀐 카드만 다시 그림 · 전체 재렌더 없음).
* 다시 셈 입력 우선순위 = 세션 고름 → 저장 정본 → config 기본값.
*/
async function handleDesignChange(chainageM: number, change: CrossDesignChange): Promise<void> {
const sectionDetail = ctx.detail();
const currentRouteId = ctx.routeId();
if (!projectId || currentRouteId === null || !sectionDetail) return;
const target = sectionDetail.cross_sections.find(
(section) => Math.abs(section.chainage_m - chainageM) < 0.01,
);
if (!target) return;
// 고름은 세션에만 — 메모리 상세 설계를 안 덮음(덮으면 ↶ 로 세션 칸을 지워도 다시 셈이 덮인 값으로 물러섬 ·
// 467 덧) · 카드는 다시 셈 알림(`onCrossDesignsUpdated`)이 그림
// 설계선 쉬프트(139-6) — 그 쪽 노견 확장만 세션에 · 브라우저 재계산(같은 창구)
const shift = change.shoulder_extend;
if (shift) {
writeShoulderExtend(projectId, currentRouteId, chainageM, shift.side, shift.valueM);
await reconcileStaleDesigns({ force: true });
return;
}
// 소단 높이(194-1) — 그 쪽 깊이만 세션에 · 브라우저 재계산(같은 창구)
const berm = change.berm_depth;
if (berm) {
writeBermDepth(projectId, currentRouteId, chainageM, berm.side, berm.valueM);
await reconcileStaleDesigns({ force: true });
return;
}
// 선택은 **세션 초안**으로 남긴다 — 화면을 오가거나 새로고침해도 남고,
// [저장]·[확정] 때 한 번에 정본으로 나간다(2026-09-06 사용자 확정: 캐시가 저절로
// 영구저장소로 새면 안 된다). 예전에는 여기서 서버가 계산하고 바로 저장했다.
// 바꾼 칸만(272) — 같은 측점 앞서 바꾼 칸에 얹음
const picked = Object.fromEntries(
CHOICE_KEYS.filter((key) => change[key] !== undefined).map((key) => [key, change[key]]),
) as CrossDesignChoice;
if (!Object.keys(picked).length) return;
writeCrossDesignChoice(projectId, currentRouteId, chainageM, {
...readCrossDesignChoice(projectId, currentRouteId, chainageM),
...picked,
});
// (3) 계산은 브라우저 안에서 — B05·B06 이 같이 쓰는 창구 하나로 돌린다.
await reconcileStaleDesigns({ force: true });
}
/** 암 경계 · 암 경사 변경 뒤 다시 셈(암 측점 2단계 무릎 · 단면적) — 지금 값을 「사용자 선택」으로
* 박지 않음(자동 판정이 굳으면 노선이 바뀌어도 재판정 안 됨 · 346-2). 암 경계는 세션에서 읽음. */
function recomputeIfRock(_chainageM: number): void {
void reconcileStaleDesigns({ force: true });
}
/**
* 횡단 다시 셈 = 계산 묶음 cross 단계 **한 곳**(387-4) — 여기서는 묶음에 부탁하고 그 차례가 끝날 때까지
* 기다림. 입력이 같으면 안 셈 · `force` = 입력 칸 밖 세션 고침(노견 · 소단 · 단면 유형 · 조정창)이라 한 번 더.
* 바뀐 카드는 묶음 알림(`onCrossDesignsUpdated`)이 다시 그림.
*/
async function reconcileStaleDesigns(options?: { force?: boolean }): Promise<void> {
if (!projectId) return;
if (options?.force) forceCross();
pipelineCross(projectId);
await reconcilePagePipeline(projectId);
}
/** 구조물 측점 열쇠 = 구조물 id(344-3) — 소단 사본(세션 `berm`)은 페이지 시설 줄 한 곳이 씀(B05 만 열어도 · 467). */
function syncBermSpans(structures: ReadonlyArray<StructureInstance>): void {
if (!projectId || ctx.routeId() === null) return;
useStructureIds(projectId, structures);
}
/** 패널 [전체 반영](N-2-1 · 346-2): 표준값으로 전 측점을 **한 번** 다시 셈 — 측점마다 돌면
* 66 × 66 = 4.1 s 였음. 표준값은 다시 셈이 `effectiveStandardCross` 로 읽고, 측점별 선택은
* 세션에 있는 것만 씀(지금 값을 「사용자 선택」으로 박지 않음 · 사용자 확정 10-16). */
let applyingAll = false;
async function applyPanelToAll(): Promise<void> {
const sectionDetail = ctx.detail();
if (applyingAll || !sectionDetail || !projectId || ctx.routeId() === null) return;
if (!sectionDetail.cross_sections.some((section) => section.design)) return;
// 화면을 잠그지 않는다(CLAUDE.md 5장) — 도는 동안 다시 누르는 것만 막는다.
applyingAll = true;
try {
await reconcileStaleDesigns({ force: true });
showToast(L("B06_Std_ApplyAll_Success"), "success");
} finally {
applyingAll = false;
}
}
return {
handleDesignChange,
recomputeIfRock,
reconcileStaleDesigns,
syncBermSpans,
applyPanelToAll,
};
}