Files
Aislo/A00_Common/design/design_pipe_ids.ts
T
eomsangdonandClaude Opus 5.5 a90f8595b8 refactor(472-2 2-B 세션 끌림): 서버 Node 묶음(design-calc · sc · co)이 화면 · 세션 코드를 안 끔
- 그림 신원표 = 새 common_util_pick_tag(tagShape · pipeTagId) — 관 id 는 화면이 열 때(usePipeIds) 세션 장부를 꽂음 · 안 꽂으면 옛 자리 이름(장부가 프로젝트 모를 때와 같은 값)
- 구조물 그림 다섯(Box · Culvert · Ford · Ford_Pavement · Revetment)이 design_pipe_ids · design_pick_match 대신 이것을 씀
- Culvert_Wire 한계 토스트 = 알림 창구(setCulvertNotice) — 화면 Cross_View 가 토스트를 꽂음 · Wire 가 ui_template · Section_Common 안 끔
- 472-3 알려진 끌림 다섯 걷음(b_page_state · pick_match · pipe_ids · selection · Section_Common) · 남은 줄 = 헬퍼 정규식 헛잡음
- 헬퍼 286_1 흉내 → 진짜 pick_tag · 폴더 지침 끌림 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 09:09:47 +09:00

132 lines
6.5 KiB
TypeScript

/* =============================================================================
* A00_Common/design/design_pipe_ids.ts
* 관 세션 id(323-5) — 관을 처음 볼 때 자리마다 붙이는 id · 옮겨도 그대로 · 저장 안 함(파일 모양 그대로).
*
* id = `pipe-s` + 차례(`pipe-s3`) — 머리 `pipe-` 로 관임을 가름(옛 자리 이름 `pipe-{누가거리}` 와 같은 머리) ·
* 누가거리는 id 에서 읽지 않음(`pipeChainageOf`). 장부 = 세션 `pipe-ids`(고름 `structure-pick` 과 같은 수명 ·
* 초안 비우기에 함께 비움). 자리 → id = 찾는 거리(`PIPE_MATCH_M`) 안 가장 가까운 것 · 없으면 새 차례 ·
* 옮김 = `movePipeId`(고름 따라가기 `followMovedPipe` 가 부름).
* 프로젝트 = 보기가 열 때 정함(`usePipeIds`) — 그리는 함수(카드 · 알약)가 프로젝트를 몰라도 같은 id.
* 프로젝트가 없으면(시험 · 도움 파일) 옛 자리 이름.
* ========================================================================== */
import { setPipeTagSource } from "../../common_util/common_util_pick_tag";
import { readState, writeState } from "../b_page_state";
import { isNear, nearestWithin, readSelection, selectDesign } from "./design_selection";
/** 관 찾는 거리 — 정의는 `design_selection` 한 곳(배수 패널이 관 옮김과 함께 가져감). */
export { PIPE_MATCH_M } from "./design_selection";
interface Book {
next: number;
ids: Array<{ id: string; at: number }>;
}
let active: string | null = null;
/** 지금 프로젝트 — 종단 · 횡단 보기가 열 때. */
export function usePipeIds(projectId: string | null): void {
active = projectId;
// 그림 신원표(common_util_pick_tag)가 이 장부를 씀 — 프로젝트 없으면 옛 자리 이름(pipeIdAt 와 같음)
setPipeTagSource(projectId ? (chainageM) => pipeIdAt(chainageM) : null);
}
const bookOf = (projectId: string): Book =>
readState<Book>("pipe-ids", projectId) ?? { next: 1, ids: [] };
/** 그 자리 관의 id — 장부에 없으면 새 차례로 적음. */
export function pipeIdAt(chainageM: number, projectId: string | null = active): string {
if (!projectId) return `pipe-${chainageM.toFixed(2)}`;
const book = bookOf(projectId);
const hit = nearestWithin(book.ids, chainageM, (entry) => entry.at);
if (hit) return hit.id;
const id = `pipe-s${book.next}`;
writeState(
"pipe-ids",
{ next: book.next + 1, ids: [...book.ids, { id, at: chainageM }] },
projectId,
);
return id;
}
/** 그 자리 관의 id — 장부에 있을 때만(안 지음 · 411-E). */
export function pipeIdNear(chainageM: number, projectId: string | null = active): string | null {
if (!projectId) return null;
return nearestWithin(bookOf(projectId).ids, chainageM, (entry) => entry.at)?.id ?? null;
}
/** 지금 관 자리(`keep`) 근처에 없는 기록을 뺌 — 옮긴 사이 옛 측점이 지은 헛 id 정리(411-E). */
export function prunePipeIds(keep: ReadonlyArray<number>, projectId: string | null = active): void {
if (!projectId || !keep.length) return; // 빈 목록 = 아직 못 받음 · 다 지움 — 장부 그대로
const book = bookOf(projectId);
const ids = book.ids.filter((entry) => keep.some((at) => isNear(entry.at, at)));
if (ids.length !== book.ids.length) writeState("pipe-ids", { next: book.next, ids }, projectId);
}
/** id 의 관 자리 — 장부 · 옛 자리 이름 순 · 관 id 가 아니면 null. */
export function pipeChainageOf(id: string, projectId: string | null = active): number | null {
if (!id.startsWith("pipe-")) return null;
const hit = projectId ? bookOf(projectId).ids.find((entry) => entry.id === id) : undefined;
if (hit) return hit.at;
const legacy = Number(id.slice("pipe-".length));
return Number.isFinite(legacy) ? legacy : null;
}
/** 관을 옮김 — 옛 자리 id 가 새 자리로(새 자리에 남은 옛 기록은 뺌) · 옮긴 관의 id. */
export function movePipeId(fromM: number, toM: number, projectId: string | null = active): string {
if (!projectId) return `pipe-${toM.toFixed(2)}`;
const book = bookOf(projectId);
const moving = nearestWithin(book.ids, fromM, (entry) => entry.at);
if (!moving) return pipeIdAt(toM, projectId);
const stale = nearestWithin(
book.ids.filter((entry) => entry !== moving),
toM,
(entry) => entry.at,
);
const ids = book.ids
.filter((entry) => entry !== stale)
.map((entry) => (entry === moving ? { ...entry, at: toM } : entry));
writeState("pipe-ids", { next: book.next, ids }, projectId);
return moving.id;
}
/** 관 세션 id 를 그 자리로(화면 목록 맞춤 · 411-B) — 짝 = 관 고정 id 가 쥔 세션 id(노선 따라감 · 끌기 · 되돌리기
* 어느 길로 옮겨도 그 id) · 그 자리 근처의 다른 기록(옛 측점 열쇠가 지은 헛 id)은 뺌 · 장부에 없는 id 는 적음. */
export function placePipeIds(
places: ReadonlyArray<readonly [string, number]>,
projectId: string | null = active,
): void {
if (!projectId || !places.length) return;
const book = bookOf(projectId);
const to = new Map(places);
const ids = book.ids
.filter((entry) => to.has(entry.id) || !places.some(([, at]) => isNear(entry.at, at)))
.map((entry) => (to.has(entry.id) ? { ...entry, at: to.get(entry.id)! } : entry));
for (const [id, at] of places) if (!ids.some((entry) => entry.id === id)) ids.push({ id, at });
if (JSON.stringify(ids) !== JSON.stringify(book.ids))
writeState("pipe-ids", { next: book.next, ids }, projectId);
}
/** 관 목록에 세션 id 를 얹은 사본 — 측점 계획의 덮는 구조물 id(종단 측점 id · 알약 짝)가 세션 id. */
export function withPipeIds<T extends { chainage_m: number }>(
pipes: ReadonlyArray<T>,
projectId: string | null = active,
): Array<T & { id: string }> {
return pipes.map((pipe) => ({ ...pipe, id: pipeIdAt(pipe.chainage_m, projectId) }));
}
/** 관을 옮김 — 관 세션 id 가 새 자리로(323-5 · id 그대로) · 그 관을 골라 둔 고름이면 자리만 새로(부재키
* 그대로 · 옛 자리 이름 id 면 세션 id 로). 다른 것을 골랐으면 그대로. */
export function followMovedPipe(projectId: string | null, fromM: number, toM: number): void {
const id = movePipeId(fromM, toM, projectId);
const picked = readSelection(projectId);
if (!picked || picked.chainageM === null || !isNear(picked.chainageM, fromM)) return;
const sid = picked.structureId;
if (sid !== null && !sid.startsWith("pipe-")) return;
selectDesign(
projectId,
{ chainageM: toM, structureId: sid === null ? null : id },
{ partKey: picked.partKey },
);
}