- 고정 한 벌 — 시점 · 종점(기본 고정) · 교각점을 한 칸 줄로 봄 · 저장 · 읽기는 _RouteEdit_Lock.ts(readRouteLocks = 초기노선 계산에 넘길 한 곳) - 곧은 선은 양 끝 · 호는 교각점과 앞뒤 점이 모두 고정일 때 고정 색(--map-route-locked · 밝음 #db2777 · 어두움 #f472b6) · 고정 점도 같은 색 - 시점 · 종점을 누르면 고정 단추만 뜸 · 풀면 끌어 옮김 · 고정이면 끌기 · 지우기 막힘 - 되돌리기 · [취소] · 다시 열기 · [확인] 뒤에도 고정 유지(pref 통 · 좌표로 되짚음) · [확인] 몸통은 그대로 - 시험 test_b05_routeedit_lock 5건 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BU9gbNE3aWFUvE61pS2WGt
85 lines
3.6 KiB
TypeScript
85 lines
3.6 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_RouteEdit_Lock.ts
|
|
* 계획노선 편집 창의 **고정 한 벌** — 시점 · 종점과 교각점 자리(계획서 22-4 · 22-5).
|
|
*
|
|
* 모달은 노드마다 한 칸씩 `apexLock` 을 쥔다 — 0번 = 시점, 끝 칸 = 종점, 사이 = 교각점.
|
|
* 시점 · 종점은 **기본 고정**이다. 켜진 자리는 고르기만 되고 안 끌린다(`_Pointer`).
|
|
*
|
|
* 창을 닫아도([취소] · [확인] 포함) 고정은 남는다 — 화면 상태 보관소의 pref 통(프로젝트 칸)에
|
|
* 바로 적고, 다시 열 때 서버 노드에 **좌표로** 다시 짚는다(노드 번호는 편집마다 밀린다).
|
|
* draft 통에 두지 않는다 — [저장] 대상이 아닌데 미저장 표시가 켜지고 [저장] 뒤 지워진다.
|
|
* 초기노선 계산(POST /route/initial)에 넘길 때도 `readRouteLocks` 한 곳만 읽는다.
|
|
* DOM 을 안 만진다 — 시험이 Node 로 돈다.
|
|
* ========================================================================== */
|
|
|
|
import { readState, writeState } from "../A00_Common/b_page_state";
|
|
|
|
type Vertex = [number, number];
|
|
|
|
/** 고정 한 벌. `apex` 는 고정한 교각점 좌표(사업지 좌표계 m). */
|
|
export interface RouteLocks {
|
|
start: boolean;
|
|
end: boolean;
|
|
apex: Vertex[];
|
|
}
|
|
|
|
/** 같은 교각점으로 볼 거리(m) — 서버가 되돌려 준 좌표의 끝자리 어긋남만 흡수한다. */
|
|
const SAME_POINT_M = 0.05;
|
|
|
|
const isVertex = (value: unknown): value is Vertex =>
|
|
Array.isArray(value) && value.length >= 2 && value.every((n) => Number.isFinite(n));
|
|
|
|
/** 저장된 고정 — 없거나 깨졌으면 시점 · 종점만 고정. */
|
|
export function readRouteLocks(projectId: string): RouteLocks {
|
|
const saved = readState<Partial<RouteLocks>>("routeedit-locks", projectId);
|
|
return {
|
|
start: saved?.start !== false,
|
|
end: saved?.end !== false,
|
|
apex: Array.isArray(saved?.apex) ? saved.apex.filter(isVertex) : [],
|
|
};
|
|
}
|
|
|
|
/** 노드 칸 → 고정 한 벌. */
|
|
export function routeLocksOf(
|
|
planned: ReadonlyArray<Vertex>,
|
|
locked: ReadonlyArray<boolean>,
|
|
): RouteLocks {
|
|
const last = planned.length - 1;
|
|
return {
|
|
start: locked[0] === true,
|
|
end: locked[last] === true,
|
|
apex: planned
|
|
.filter((_, index) => index > 0 && index < last && locked[index] === true)
|
|
.map(([x, y]) => [x, y]),
|
|
};
|
|
}
|
|
|
|
export function writeRouteLocks(
|
|
projectId: string,
|
|
planned: ReadonlyArray<Vertex>,
|
|
locked: ReadonlyArray<boolean>,
|
|
): void {
|
|
if (planned.length < 2) return; // 노선을 못 읽은 채 닫으면 적어 둔 고정을 지우지 않는다.
|
|
writeState("routeedit-locks", routeLocksOf(planned, locked), projectId);
|
|
}
|
|
|
|
/** 고정 한 벌 → 노드 칸. 교각점은 좌표가 가까운 자리만 켠다. */
|
|
export function lockColumn(planned: ReadonlyArray<Vertex>, locks: RouteLocks): boolean[] {
|
|
const last = planned.length - 1;
|
|
return planned.map(([x, y], index) =>
|
|
index === 0
|
|
? locks.start
|
|
: index === last
|
|
? locks.end
|
|
: locks.apex.some(([ax, ay]) => Math.hypot(ax - x, ay - y) <= SAME_POINT_M),
|
|
);
|
|
}
|
|
|
|
/** 곧은 선(노드 `index` → `index + 1`)은 **양 끝이 모두** 고정일 때 고정. */
|
|
export const straightLocked = (locked: ReadonlyArray<boolean>, index: number): boolean =>
|
|
locked[index] === true && locked[index + 1] === true;
|
|
|
|
/** 호(교각점 `index`)는 **그 교각점과 앞뒤 점이 모두** 고정일 때 고정. */
|
|
export const arcLocked = (locked: ReadonlyArray<boolean>, index: number): boolean =>
|
|
locked[index - 1] === true && locked[index] === true && locked[index + 1] === true;
|