Files
Aislo/B05_Profile/B05_Profile_UI_RouteEdit_SnapStep.ts
T
eomsangdonandClaude Opus 5.5 61733d064d feat(지도): 634-2 · 634-3 아이콘 표 두 장 · 묶음 메뉴 · 한 행 배선 · 호버 = 표(요구 546)
- 겹 표(map_layer_list) = 그룹1 22 줄 표 차례 · title = 호버제목 · group(수계망도 · 도로 · 토지·건물 · 제한·재해) · pages · 숨김 표 걷음 · 도로300m 한 줄(「도로」 묶음 · 기능 남김)
- 겹 줄(map_layer_bar) = 단독 넷 | 묶음 넷(여럿 고르기 메뉴 · 켠 수 배지 · 안 닫힘) | 그래픽 카드 · sync 가 단추 · 메뉴 줄 막힘 · 글을 맞춤(why = 페이지 까닭)
- 도구 표(map_toolbar) = 그룹2 17 줄 title · mapToolGroups 가 글을 표로 맞춤 · 기본 단추 생성 · 지형 칩 막힘 「수치지형도/라이다 — 까닭」
- 띠 = 한 자료 · 겹 둘(GL fillEdge 거름 · 옛 캔버스 · 범례 줄 따로) · B05 띠 받기(D03 과 같은 서버 길 · bandCollection 공용)
- D03 = 겹 줄 · 도구줄 한 행(createMapToolRow) · 222px 기댐 · 줄바꿈 CSS 걷음 · 측점 · 기울기 호버 덮어쓰기 걷음 · LAS 올리기 호버 걷음
- B05 = 세로 두 줄 → 한 행 · 호버 덮어쓰기 걷음(도구줄 · 1m 등고선 · 3D · 추천) · 겹 막힘 querySelector 덮어쓰기 → 공용 sync · 임도(업로드) 보임 + 「받은 임도 자료 없음」 막힘 · 스냅 간격 날 select → 고르기 메뉴(하나)
- 켬끔 기억 열쇠 · 측점 · 기울기 처음 값 · 그래픽 카드 취향 열쇠 둘 그대로
- 낡은 시험 고침(568_1 · 622_2 · 624_2 · 624_3 · 553_4 · 625_draw · b05_routeedit_initial · 626_3 · 390_4 · 346_2 · 353_1 · 581_2 · 584_1 · 584_3) — 표 꼴 row() · 호버 글 = 표 · 한 행 · 고르기 메뉴로 바뀐 글을 잡음 · 새 지킴 634_2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:40:26 +09:00

112 lines
5.7 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_RouteEdit_SnapStep.ts
* 노선 편집 [측점 스냅] 단추 · 스냅 간격 고르기(390-2 · 요구 242-11).
*
* 간격 = 10 m · 측점 No.(편집 창 측점 간격 · 기본 · 280-4) — 곡선 MC 를 그 간격 배수에 맞춤.
* 고른 값은 프로젝트별 화면 기억(pref `routeedit-snap-step`) · 스냅 켬이면 저장 노선에도 같이
* (`station_snap_step` · 390-5 · 서버 측점 셈이 MC 측점 값 자릿수로 씀 · 옛 노선 = 측점 No.).
* 저장 노선이 스냅 켬이면 열 때 그 간격으로 고르기를 맞춤. 추천 · 서버 초기 노선은 측점 No. 그대로.
* 고르기 = 도구줄 고르기 메뉴 하나(634-3 · `chrome.snapStep`) · 스냅 끔이면 흐림(CSS) · 고를 수는 있음.
* 상태줄 글은 고른 간격 따라(390-4) — 측점 No. = 「일반측점에」 · 10 m = 「10 m 배수에」.
* 평면 눈금 = 일반측점(스냅이 옮기지 않음 · 280-4) + 곡선 측점(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, stationsAlong } from "./logic/B05_Profile_UI_RouteEdit_Frame";
/** 고르기 값 — 옛 "0.1" · "1"(pref · 저장)은 측점 No. 로 읽음(280-4). */
const STEPS = ["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` = 고르기 값("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 · 켬이면 측점 간격 그대로(옮기지 않음 · 280-4). */
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, "stationSnapButton" | "snapStep">,
projectId: string,
stationIntervalM: number,
onChange: (toggle: boolean) => void,
): SnapStep {
// 고르기 메뉴 하나(634-3 · 옛 날 <select> 대신) — 값 = "10" · "station"
const menu = chrome.snapStep;
const value = (): string => menu.value() ?? "station";
const saved = readState<string>("routeedit-snap-step", projectId);
menu.set(saved && STEPS.includes(saved) ? saved : "station");
menu.onPick(() => {
writeState("routeedit-snap-step", value(), projectId);
onChange(false);
});
chrome.stationSnapButton.addEventListener("click", () => onChange(true));
const on = () => chrome.stationSnapButton.getAttribute("aria-pressed") === "true";
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<Vertex>) =>
on() ? stationsAlong(line, stationIntervalM, false) : null;
const stepM = () => step() ?? stationIntervalM;
return Object.assign(stepM, {
text: () => snapFitText(value()),
draft,
load: (plan: SavedSnap) => {
const snapOn = plan.station_snap === true;
const kept = String(plan.station_snap_step ?? "station");
if (snapOn) menu.set(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 = 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,
};
},
});
}