/* ============================================================================= * B05_Profile_UI_RouteEdit_SnapStep.ts * 노선 편집 스냅 고르기 하나(390-2 · 요구 242-11 · 589-4) — 측점 No. · 10 m · 끔(끔 = 스냅 꺼짐). * * 간격 = 10 m · 측점 No.(편집 창 측점 간격 · 기본 · 280-4) — 곡선 MC 를 그 간격 배수에 맞춤. * 켬이면 저장 노선에도 같이(`station_snap_step` · 390-5 · 서버 측점 셈이 MC 측점 값 자릿수로 씀 · 옛 노선 = 측점 No.). * 저장 노선이 스냅 켬이면 열 때 그 간격으로 고르기를 맞춤. 추천 · 서버 초기 노선은 측점 No. 그대로. * 고르기 = 도구줄 고르기 메뉴 하나(634-3 · `chrome.snap`) · 처음 = 끔. * 상태줄 글은 고른 간격 따라(390-4) — 측점 No. = 「일반측점에」 · 10 m = 「10 m 배수에」. * 평면 눈금 = 일반측점(스냅이 옮기지 않음 · 280-4) + 곡선 측점(MC 에 측점 없을 때 · 252-2 · `ticks`). * ========================================================================== */ import type { RouteDraft } from "../A00_Common/design/logic/design_pipeline_types"; import type { RouteEditChrome } from "./B05_Profile_UI_RouteEdit_Chrome"; import type { EditedCurve, Vertex } from "./logic/B05_Profile_UI_RouteEdit_Curve"; import { curveSpans, curveStations, stationsAlong } from "./logic/B05_Profile_UI_RouteEdit_Frame"; /** 고르기 값 — 옛 "0.1" · "1"(pref · 저장)은 측점 No. 로 읽음(280-4). */ const STEPS = ["10", "station"]; /** 저장 · 초안에 실리는 스냅 두 칸. */ export type SnapSetting = Pick; type SavedSnap = { station_snap?: boolean; station_snap_step?: number | null }; /** 스냅 뒤 상태줄 글 — `step` = 고르기 값("10" · "station"). */ export function snapFitText(step: string): string { const where = step === "station" ? "일반측점" : `${step} m 배수`; return `곡선 가운데(MC)를 ${where}에 맞췄습니다.`; } export type SnapStep = { /** 지금 스냅 간격(m) — 측점 No. 면 측점 간격. */ (): number; /** 스냅 켬인가(고르기가 끔이 아님). */ on: () => boolean; /** 스냅 뒤 상태줄 글. */ text: () => string; /** 저장 · 초안 두 칸 — 끔이면 간격 null. */ draft: () => SnapSetting; /** 저장 노선을 읽음 — 켬이면 고르기를 그 간격으로 · 돌려줌 = 켬끔. */ load: (plan: SavedSnap) => boolean; /** 읽은 저장 노선과 켬끔 · 간격이 다른가([확인] 보낼지). */ changed: () => boolean; /** 편집 창 일반측점 — 끔이면 null · 켬이면 측점 간격 그대로(옮기지 않음 · 280-4). */ stations: (line: ReadonlyArray, curves?: ReadonlyArray) => number[] | null; /** 평면 눈금 — `stations` + 곡선 측점(252-2) · 곡선 측점이 없으면 `stations` 그대로. */ ticks: ( line: ReadonlyArray, curves: ReadonlyArray, ) => { stations: number[] | null; curveStations: number[] }; }; /** 고르기 배선 — 고름(켬 · 간격 · 끔) 때 `onChange()`. */ export function bindStationSnap( chrome: Pick, stationIntervalM: number, onChange: () => void, ): SnapStep { // 고르기 메뉴 하나(634-3) — 값 = "10" · "station" · 끔 null const menu = chrome.snap; const value = (): string => menu.value() ?? "station"; menu.onPick(() => onChange()); const on = () => menu.value() !== null; const step = (): number | null => (value() === "station" ? null : Number(value())); const draft = (): SnapSetting => ({ stationSnap: on(), stationSnapStep: on() ? step() : null }); let loaded: SnapSetting = { stationSnap: false, stationSnapStep: null }; const stations = (line: ReadonlyArray) => on() ? stationsAlong(line, stationIntervalM, false) : null; const stepM = () => step() ?? stationIntervalM; return Object.assign(stepM, { on, text: () => snapFitText(value()), draft, load: (plan: SavedSnap) => { const snapOn = plan.station_snap === true; const kept = String(plan.station_snap_step ?? "station"); menu.set(snapOn ? (STEPS.includes(kept) ? kept : "station") : null); loaded = { stationSnap: snapOn, stationSnapStep: snapOn ? step() : null }; return snapOn; }, changed: () => { const now = draft(); return ( now.stationSnap !== loaded.stationSnap || now.stationSnapStep !== loaded.stationSnapStep ); }, stations, ticks: (line: ReadonlyArray, curves: ReadonlyArray) => { const base = stationsAlong(line, stationIntervalM, false); // 노선 길이 = 무한 — 호(BC~EC)는 늘 노선 안 const curve = curveStations(base, curveSpans(line, curves), Infinity, on() ? step() : null); return { stations: curve.length ? [...base, ...curve].sort((a, b) => a - b) : stations(line), curveStations: curve, }; }, }); }