- 서버 generate_sections ↔ TS buildStations 짝 · curve_stations ↔ curveStations 짝 · 종류 curve · 열쇠 curve:<누가거리> · 이름 뒤 (MC) - 값 = 스냅 간격 자릿수(0.1 = 소수 한 자리 · 그 밖 · 스냅 끔 = 정수) · 0.005 m 안 측점(일반 · 옮긴 · 관 · 구조물) 있으면 안 더함 · BC · EC 안 더함 · 224-2 옮김 그대로 - 격자 번호 안 가져감 · 노선 저장 이월 열쇠 · B06 종류(curve) · 종단 선 색 · 카드 · 종단 이름 (MC) · 평면 눈금 (MC) · 오른쪽 횡단 일반측점 갈래 - 시험: test_407_curve_station 새로 · 344_3 둘 · 224_2 · 346_1 헬퍼 저장 노선 곡선표 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
120 lines
5.8 KiB
TypeScript
120 lines
5.8 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_RouteEdit_SnapStep.ts
|
|
* 노선 편집 [측점 스냅] 단추 · 스냅 간격 고르기(390-2 · 요구 242-11).
|
|
*
|
|
* 간격 = 0.1 m · 1 m · 10 m · 측점 No.(편집 창 측점 간격 · 기본) — 곡선 MC 를 그 간격 배수에 맞춤.
|
|
* 고른 값은 프로젝트별 화면 기억(pref `routeedit-snap-step`) · 스냅 켬이면 저장 노선에도 같이
|
|
* (`station_snap_step` · 390-5 · 서버 측점 셈이 MC 측점 값 자릿수로 씀 · 옛 노선 = 측점 No.).
|
|
* 저장 노선이 스냅 켬이면 열 때 그 간격으로 고르기를 맞춤. 추천 · 서버 초기 노선은 측점 No. 그대로.
|
|
* 스냅 끔이면 고르기는 흐림(CSS) · 고를 수는 있음.
|
|
* 상태줄 글은 고른 간격 따라(390-4) — 측점 No. = 「일반측점에」 · 그 밖 = 「1 m 배수에」 꼴.
|
|
* 평면 눈금 = 일반측점(스냅 켬이면 옮긴 자리) + 곡선 측점(MC 에 측점 없을 때 · 252-2 · `ticks`).
|
|
* ========================================================================== */
|
|
|
|
import { readState, writeState } from "../A00_Common/b_page_state";
|
|
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,
|
|
snapCurveStations,
|
|
stationsAlong,
|
|
} from "./logic/B05_Profile_UI_RouteEdit_Frame";
|
|
|
|
const STEPS = ["0.1", "1", "10", "station"];
|
|
|
|
/** 저장 · 초안에 실리는 스냅 두 칸. */
|
|
export type SnapSetting = Pick<RouteDraft, "stationSnap" | "stationSnapStep">;
|
|
|
|
type SavedSnap = { station_snap?: boolean; station_snap_step?: number | null };
|
|
|
|
/** [측점 스냅] 단추 켬끔 모양(`is-active` · `aria-pressed`). */
|
|
export function paintStationSnap(button: HTMLButtonElement, on: boolean): void {
|
|
button.classList.toggle("is-active", on);
|
|
button.setAttribute("aria-pressed", String(on));
|
|
}
|
|
|
|
/** 스냅 뒤 상태줄 글 — `step` = 고르기 값("0.1" · "1" · "10" · "station"). */
|
|
export function snapFitText(step: string): string {
|
|
const where = step === "station" ? "일반측점" : `${step} m 배수`;
|
|
return `곡선 가운데(MC)를 ${where}에 맞췄습니다.`;
|
|
}
|
|
|
|
export type SnapStep = {
|
|
/** 지금 스냅 간격(m) — 측점 No. 면 측점 간격. */
|
|
(): number;
|
|
/** 스냅 뒤 상태줄 글. */
|
|
text: () => string;
|
|
/** 저장 · 초안 두 칸 — 끔이면 간격 null. */
|
|
draft: () => SnapSetting;
|
|
/** 저장 노선을 읽음 — 켬이면 고르기를 그 간격으로 · 돌려줌 = 켬끔. */
|
|
load: (plan: SavedSnap) => boolean;
|
|
/** 읽은 저장 노선과 켬끔 · 간격이 다른가([확인] 보낼지). */
|
|
changed: () => boolean;
|
|
/** 편집 창 측점 — 끔이면 null · 켬이면 호 안 하나를 MC 로(자릿수 = 간격 · 셈은 서버 짝 `_Frame`). */
|
|
stations: (line: ReadonlyArray<Vertex>, curves: ReadonlyArray<EditedCurve>) => number[] | null;
|
|
/** 평면 눈금 — `stations` + 곡선 측점(252-2) · 곡선 측점이 없으면 `stations` 그대로. */
|
|
ticks: (
|
|
line: ReadonlyArray<Vertex>,
|
|
curves: ReadonlyArray<EditedCurve>,
|
|
) => { stations: number[] | null; curveStations: number[] };
|
|
};
|
|
|
|
/** 단추 · 고르기 배선 — `onChange(true)` = 단추 누름(켬끔 바꿈) · `onChange(false)` = 간격 바꿈. */
|
|
export function bindStationSnap(
|
|
chrome: Pick<RouteEditChrome, "overlay" | "stationSnapButton">,
|
|
projectId: string,
|
|
stationIntervalM: number,
|
|
onChange: (toggle: boolean) => void,
|
|
): SnapStep {
|
|
const select = chrome.overlay.querySelector<HTMLSelectElement>('[data-act="snap-step"]')!;
|
|
const saved = readState<string>("routeedit-snap-step", projectId);
|
|
select.value = saved && STEPS.includes(saved) ? saved : "station";
|
|
select.addEventListener("change", () => {
|
|
writeState("routeedit-snap-step", select.value, projectId);
|
|
onChange(false);
|
|
});
|
|
chrome.stationSnapButton.addEventListener("click", () => onChange(true));
|
|
const on = () => chrome.stationSnapButton.getAttribute("aria-pressed") === "true";
|
|
const step = (): number | null => (select.value === "station" ? null : Number(select.value));
|
|
const draft = (): SnapSetting => ({ stationSnap: on(), stationSnapStep: on() ? step() : null });
|
|
let loaded: SnapSetting = { stationSnap: false, stationSnapStep: null };
|
|
const stations = (line: ReadonlyArray<Vertex>, curves: ReadonlyArray<EditedCurve>) =>
|
|
on()
|
|
? snapCurveStations(
|
|
stationsAlong(line, stationIntervalM, false),
|
|
curveSpans(line, curves),
|
|
step(),
|
|
)
|
|
: null;
|
|
const stepM = () => step() ?? stationIntervalM;
|
|
return Object.assign(stepM, {
|
|
text: () => snapFitText(select.value),
|
|
draft,
|
|
load: (plan: SavedSnap) => {
|
|
const snapOn = plan.station_snap === true;
|
|
const kept = String(plan.station_snap_step ?? "station");
|
|
if (snapOn) select.value = STEPS.includes(kept) ? kept : "station";
|
|
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<Vertex>, curves: ReadonlyArray<EditedCurve>) => {
|
|
const base = stations(line, curves) ?? 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, curves),
|
|
curveStations: curve,
|
|
};
|
|
},
|
|
});
|
|
}
|