700줄 한도 대응. 순수 이동으로 동작 불변. - B06_Section_UI_Page_Design_Sync.ts — 설계 선택 반영·낡음 재계산·소단 동기화·[전체 반영] - B06_Section_UI_Page_Grade_Edit.ts — 종단 계획선 ▲▼ 제공자 - 남은 B06_Section_UI_Page.ts 644줄 소스 문자열 감시 시험 셋이 분리 모듈까지 읽게 함. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fe1QWPTfw11PaKh2LjwXdR
86 lines
3.8 KiB
TypeScript
86 lines
3.8 KiB
TypeScript
/* =============================================================================
|
|
* B06_Section_UI_Page_Grade_Edit.ts
|
|
* 종단 계획선 편집(▲/▼) 제공자. `_UI_Page` 700줄 제한 대응으로 떼어낸 것이며
|
|
* 동작은 그대로다(2026-09-13 분리).
|
|
* ========================================================================== */
|
|
|
|
import {
|
|
createProfileEditStore,
|
|
type ProfileEditStore,
|
|
} from "../B05_Profile/B05_Profile_UI_Profile_Edit";
|
|
import { readAlignment, toDesignProfile } from "../B05_Profile/B05_Profile_UI_Profile_Data";
|
|
import {
|
|
adjustStation,
|
|
buildAlignment,
|
|
toAlignmentBase,
|
|
} from "../B05_Profile/B05_Profile_UI_Profile_Alignment";
|
|
import type { SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
|
import type { SectionViewController } from "./B06_Section_UI_Section_View";
|
|
|
|
type GradeEditProvider = NonNullable<Parameters<SectionViewController["setGradeEdit"]>[0]>;
|
|
|
|
export interface GradeEditContext {
|
|
routeId: () => number | null;
|
|
detail: () => SectionDetailResponse | null;
|
|
view: () => SectionViewController;
|
|
/** 전 측점 횡단 재계산 — 손을 뗀 뒤 한 번만 돈다. */
|
|
reconcile: () => Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* 계획선 편집(▲/▼) — B05 와 **같은 세션 초안**에 쌓는다(2026-09-12 사용자: B06 에만
|
|
* 버튼이 없었다). 누르면 그 자리에서 계획선·전 측점 횡단을 다시 풀고(`reconcile`),
|
|
* 영구저장은 [저장]·[확정]에서만 한다(CLAUDE.md 5장).
|
|
*/
|
|
export function createGradeEdit(ctx: GradeEditContext): GradeEditProvider {
|
|
let gradeStore: ProfileEditStore | null = null;
|
|
let gradeRouteId: number | null = null;
|
|
/** ▲▼ 길게 누르기는 초당 10회(`HOLD_INTERVAL_MS`) 들어온다 — 그보다 길게 잡아
|
|
* 누르는 동안은 선만 움직이고, 손을 뗀 뒤 재계산이 한 번 돈다. */
|
|
const GRADE_RECONCILE_DEBOUNCE_MS = 120;
|
|
let gradeReconcileTimer = 0;
|
|
const scheduleGradeReconcile = (): void => {
|
|
window.clearTimeout(gradeReconcileTimer);
|
|
gradeReconcileTimer = window.setTimeout(() => {
|
|
void ctx.reconcile().then(() => ctx.view().setGradeEdit(gradeEditFor));
|
|
}, GRADE_RECONCILE_DEBOUNCE_MS);
|
|
};
|
|
const gradeEditFor: GradeEditProvider = () => {
|
|
const detail = ctx.detail();
|
|
const currentRouteId = ctx.routeId();
|
|
if (!detail || currentRouteId === null) return null;
|
|
const stored = readAlignment(detail.longitudinal);
|
|
if (!stored) return null; // 선형 저장분이 없는 옛 노선 — 편집할 기준선이 없다.
|
|
if (!gradeStore || gradeRouteId !== currentRouteId) {
|
|
gradeRouteId = currentRouteId;
|
|
gradeStore = createProfileEditStore(currentRouteId, stored.edits, () => undefined);
|
|
}
|
|
const store = gradeStore;
|
|
const base = toAlignmentBase(stored);
|
|
const alignment = buildAlignment(base, store.edits());
|
|
return {
|
|
alignment,
|
|
stepM: alignment.policy.edit_step_m,
|
|
onStation: (chainageM, delta) => {
|
|
store.replace(adjustStation(base, store.edits(), chainageM, delta));
|
|
// ① 선은 **그 자리에서** 움직인다 — 그래프가 읽는 계획선(`design_profiles`)만
|
|
// 갈아 끼우고 다시 그린다(재계산을 기다리면 누른 뒤 한참 뒤에 움직였다).
|
|
const current = ctx.detail();
|
|
if (current) {
|
|
current.longitudinal.design_profiles = [
|
|
toDesignProfile(
|
|
buildAlignment(base, store.edits()),
|
|
current.longitudinal.design_profiles?.[0],
|
|
),
|
|
];
|
|
}
|
|
ctx.view().setGradeEdit(gradeEditFor);
|
|
// ② 전 측점 횡단 재계산·카드 갱신은 무겁다 — 마지막 한 번만(B05 프리뷰와 같은 규칙).
|
|
// 편집분은 세션 초안에 있으므로 재계산이 그것을 그대로 읽는다.
|
|
scheduleGradeReconcile();
|
|
},
|
|
};
|
|
};
|
|
return gradeEditFor;
|
|
}
|