Files
Aislo/B06_Section/B06_Section_UI_Page_Grade_Edit.ts
T
eomsangdonandClaude Opus 5.5 7760dcd777 refactor(B05·B06): 설계 가게 한 곳 · 저장 한 줄기(PLAN 24-2a)
- A00_Common/design/design_store.ts — useDesignStore(projectId, routeId) get · set · subscribe
  (structures · pipes · alignmentDraft · sectionDetail · selection) · 밑은 b_page_state 등록 키
  (selection 등록 · alignmentDraft 는 옛 키 감쌈 — 브레인 판정 B)
- A00_Common/design/design_save.ts — saveDesign(ctx, {confirm}) 한 줄기:
  관 → 구조물 → 계획고 → 암거 옵션 → 오르막 → 횡단 · B05 [임시저장] · B06 [저장] · [확정]이 같은 차례
- 계획고 초안 가게 노선마다 한 벌(B05 Profile_Edit 주인 · B06 Grade_Edit 는 useProfileEditStore)
- 관 목록 주인 = 가게 pipes(Pipes_Draft 창구)
- 시험: test_design_store_save 추가 · save_channels · save_recompute · save_roundtrip 새 차례로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmELpdXFDRZHhfbGuRhSmL
2026-09-29 20:15:28 +09:00

86 lines
3.9 KiB
TypeScript

/* =============================================================================
* B06_Section_UI_Page_Grade_Edit.ts
* 종단 계획선 편집(▲/▼) 제공자. `_UI_Page` 700줄 제한 대응으로 떼어낸 것이며
* 동작은 그대로다(2026-09-13 분리).
* ========================================================================== */
import {
useProfileEditStore,
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 종단 패널의 **같은 가게**(노선마다 한 벌)를 쓴다(계획서 24-2a ·
* 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 = useProfileEditStore(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;
}