Files
Aislo/B05_Profile/B05_Profile_UI_Selection.ts
T
eomsangdonandClaude Sonnet 5.5 8a9456579a refactor(B05·B06): 측점 · 구조물 고름을 selection 한 곳으로 · 관 찾는 거리 PIPE_MATCH_M 한 값 · 횡단 카드 누름이 옹벽 · 세월교 · BOX · 기슭막이 폼을 엶 (24-2d)
- 목록 · 종단 알약 · 3D · 횡단 카드 · 벽 어디서 골라도 selectDesign 한 길로 세션에 적음(구조물 id 포함) — B05 목록 구조물 고름도 B06 으로 이어짐
- 폼이 열 항목 풀이 resolveSelection 한 곳(관 먼저 · 구조물 다음 · 가장 가까운 것) — 공용 폼의 0.05 없앰
- B06 벽 · 구체 누름은 showAtChainage 하나를 탐(옛 관 전용 showPipeAt 없앰) · 벽 누름 중 폼이 카드를 먼저 고르는 이중 토글 막음
- B06 진입 때 구조물 id 로 카드를 찾고, 못 찾으면 다시 부르게 함 · B05 진입 때 구조물 id 가 있을 때만 구조물로 연다
- 시험 test_b05_b06_selection_single 21

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ly2oHAaTDYRjcniAmHR81z
2026-09-29 21:02:55 +09:00

116 lines
5.6 KiB
TypeScript

/* =============================================================================
* 측점 선택 동기화 (B05)
*
* 같은 측점을 네 곳이 각자 그린다 — 3D 마커 · 종단 그래프 세로선 · 좌측 구조물 폼 ·
* 배수유역도의 세부유역. 어느 하나에서 고르면 나머지 셋도 같은 것을 가리켜야 한다
* (2026-08-02 사용자 지시).
*
* 서로가 서로를 갱신하므로 `isSyncing` 가드로 재진입을 막는다. 배수유역 강조는 "배관"
* 구조물(`origin: "pipe"`)일 때만 붙는다 — 다른 구조물에는 대응하는 유역이 없다.
* ========================================================================== */
import { PIPE_MATCH_M } from "../A00_Common/design/design_selection";
import {
irregularStationId,
isPipeStation,
type IrregularStation,
} from "./B05_Profile_UI_IrregularStations";
export interface SelectionSyncPorts {
/** 현재 구조물 측점 목록(수동 + 배관 투영분). */
stations: () => ReadonlyArray<IrregularStation>;
isSyncing: () => boolean;
setSyncing: (value: boolean) => void;
/** 3D 뷰어 마커 선택. */
selectMarker: (stationId: string | null) => void;
/** 종단 그래프 세로선 선택. */
selectGraph: (stationId: string | null) => void;
/** 좌측 구조물 폼 선택(누가거리 기준). */
selectSidebar: (chainageM: number | null) => void;
/** 배수유역도 세부유역 강조(누가거리 기준). */
selectBasin: (chainageM: number | null) => void;
/** 배수유역도 계획선 위 측점 마킹(누가거리 기준). 유역 없는 구조물도 위치를 보여 준다. */
markStation?: (chainageM: number | null) => void;
/** 배수유역도 관 마커 선택(누가거리 기준) — 어느 화면에서 배관을 골라도 지도
* 마커·유역 강조가 따라오게 한다(2026-08-17 전역 선택 동기화). 부속 옵션 폼은
* 사이드 「구조물 배치」가 selectSidebar 경로에서 연다. */
selectPipeForm?: (chainageM: number | null) => void;
/** 3D 코리도 구조물 강조(누가거리 기준) — 좌측 폼·종단에서 골라도 3D가 따라온다
* (2026-09-04). 3D에서 부재를 집어 이미 그 측점을 고른 상태면 그대로 둔다. */
selectStructure3D?: (chainageM: number | null) => void;
}
export interface SelectionSync {
/** 그래프·3D에서 측점을 골랐을 때 — 좌측 폼과 배수유역도를 맞춘다. */
syncIrregularSelection: (stationId: string | null) => void;
/** 고른 측점이 배관이면 그 유역을 강조한다(아니면 강조 해제). */
syncBasinHighlight: (stationId: string | null) => void;
/** 배수유역도에서 유역을 골랐을 때 — 그래프·3D·좌측 폼을 맞춘다. */
selectStationOfPipe: (chainageM: number | null) => void;
}
export function createSelectionSync(ports: SelectionSyncPorts): SelectionSync {
const prefix = irregularStationId("");
/** 측점 id로 구조물 측점을 찾는다(비정규 id가 아니면 undefined). */
function irregularOf(stationId: string | null): IrregularStation | undefined {
if (!stationId?.startsWith(prefix)) return undefined;
const id = stationId.slice(prefix.length);
return ports.stations().find((entry) => entry.id === id);
}
function syncBasinHighlight(stationId: string | null): void {
const station = irregularOf(stationId);
ports.selectBasin(station && isPipeStation(station) ? station.chainage_m : null);
// 배수유역도 측점 마킹 — 유역이 없는 구조물(대피로 등)도 위치는 표시한다(2026-08-05).
ports.markStation?.(station ? station.chainage_m : null);
}
return {
syncBasinHighlight,
syncIrregularSelection(stationId) {
if (ports.isSyncing()) return;
syncBasinHighlight(stationId);
// 규칙 측점을 고른 것이면 구조물 폼은 건드리지 않는다(고를 항목이 없다).
if (stationId !== null && !stationId.startsWith(prefix)) return;
const station = irregularOf(stationId);
ports.setSyncing(true);
try {
ports.selectSidebar(station ? station.chainage_m : null);
ports.selectPipeForm?.(station && isPipeStation(station) ? station.chainage_m : null);
ports.selectStructure3D?.(station ? station.chainage_m : null);
} finally {
ports.setSyncing(false);
}
},
selectStationOfPipe(chainageM) {
if (ports.isSyncing()) return;
const matched =
chainageM === null
? undefined
: ports
.stations()
.find(
(entry) =>
isPipeStation(entry) && Math.abs(entry.chainage_m - chainageM) < PIPE_MATCH_M,
);
const id = matched ? irregularStationId(matched.id) : null;
ports.setSyncing(true);
try {
ports.selectMarker(id);
ports.selectGraph(id);
// 투영 측점이 아직 없어도(방금 넣은 임시 관 — 재계산 전) 누가거리는 그대로
// 넘긴다. null을 넘기면 사이드가 "해제"로 받아 임시 배치를 스스로 취소한다
// (2026-08-18 임시 배치 자기 삭제 버그).
ports.selectSidebar(matched ? matched.chainage_m : chainageM);
// 유역·마커 어느 쪽에서 왔든 시설 폼도 그 관을 연다(투영 측점이 없어도
// 누가거리로 근사 매칭 — Drainage 쪽이 못 찾으면 해제된다).
ports.selectPipeForm?.(matched ? matched.chainage_m : chainageM);
ports.selectStructure3D?.(matched ? matched.chainage_m : chainageM);
} finally {
ports.setSyncing(false);
}
},
};
}