Files
Aislo/B05_wf2_Route/B05_wf2_Route_UI_Selection.ts
T
eomsangdonandClaude Opus 5 d9e8125e4c fix(B05): 손으로 넣은 "배관" 처리 통일 + 첫 진입 시 배수유역도 공백
1) 사이드바 폼으로 이름을 "배관"이라 적어 넣은 항목은 origin이 "user"라 [초기화]로
   지워지지 않고 배수유역도와도 어긋난 채 남았다. isPipeStation()(origin이 pipe이거나
   이름이 "배관")으로 판정을 통일해 목록 교체·이동·삭제·선택 동기화가 같은 규칙을 쓴다.

2) 대시보드에서 곧장 B05로 들어오면 배수유역도가 비어 보이고 새로고침해야 나왔다.
   패널이 배치되기 전(0×0)에 fitToRoute()가 돌아 엉뚱한 배율이 굳은 것이다. 크기가
   2px 미만이면 맞춤을 미뤘다가 첫 배치 때 다시 맞춘다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 00:48:05 +09:00

98 lines
3.9 KiB
TypeScript

/* =============================================================================
* 측점 선택 동기화 (B05)
*
* 같은 측점을 네 곳이 각자 그린다 — 3D 마커 · 종단 그래프 세로선 · 좌측 구조물 폼 ·
* 배수유역도의 세부유역. 어느 하나에서 고르면 나머지 셋도 같은 것을 가리켜야 한다
* (2026-08-02 사용자 지시).
*
* 서로가 서로를 갱신하므로 `isSyncing` 가드로 재진입을 막는다. 배수유역 강조는 "배관"
* 구조물(`origin: "pipe"`)일 때만 붙는다 — 다른 구조물에는 대응하는 유역이 없다.
* ========================================================================== */
import {
irregularStationId,
isPipeStation,
type IrregularStation,
} from "./B05_wf2_Route_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;
}
export interface SelectionSync {
/** 그래프·3D에서 측점을 골랐을 때 — 좌측 폼과 배수유역도를 맞춘다. */
syncIrregularSelection: (stationId: string | null) => void;
/** 고른 측점이 배관이면 그 유역을 강조한다(아니면 강조 해제). */
syncBasinHighlight: (stationId: string | null) => void;
/** 배수유역도에서 유역을 골랐을 때 — 그래프·3D·좌측 폼을 맞춘다. */
selectStationOfPipe: (chainageM: number | null) => void;
}
/** 같은 누가거리로 볼 여유(m). 관 지점과 구조물 측점은 소수점 둘째 자리까지 같은 값을 쓴다. */
const SAME_CHAINAGE_M = 0.51;
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);
}
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);
} 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) < SAME_CHAINAGE_M,
);
const id = matched ? irregularStationId(matched.id) : null;
ports.setSyncing(true);
try {
ports.selectMarker(id);
ports.selectGraph(id);
ports.selectSidebar(matched ? matched.chainage_m : null);
} finally {
ports.setSyncing(false);
}
},
};
}