- 자동 배관·수동 배관은 같은 구조물(2026-08-17 사용자 지시) — 별도 편집 UI (createFacilityEditor) 폐지. 배관/BOX암거/물넘이/세월교도 일반 구조물과 같은 흐름(구조물군 → 종류 → 시작·기준·종료 측점 → 옵션 → 추가/수정/삭제) - createFacilityOptionsForm: 부속 옵션 서브폼이 구조물 폼 옵션 자리에 삽입. 시설 종류는 종류 드롭다운, 위치는 측점 칸이 담당 - loadPipeForm: 목록·그래프·3D·배수유역도 어디서 골라도 같은 폼에 로드, 버튼 [수정]. onPipeUpdate 신설(기준점 이동=관 이동, 옵션만=재계산) - 기슭막이 총/앞/뒤 → 시작·종료 측점 칸 자동 채움. onPipesChanged에 source·options 추가. 배수유역 패널은 지도·정본 보관·재계산만 담당 - 700줄 대응: 입력 조각을 B05_Profile_UI_Structures_Fields.ts로 분리 (패널 685줄). tmp/tests 98건·typecheck·build 통과
110 lines
5.0 KiB
TypeScript
110 lines
5.0 KiB
TypeScript
/* =============================================================================
|
|
* 측점 선택 동기화 (B05)
|
|
*
|
|
* 같은 측점을 네 곳이 각자 그린다 — 3D 마커 · 종단 그래프 세로선 · 좌측 구조물 폼 ·
|
|
* 배수유역도의 세부유역. 어느 하나에서 고르면 나머지 셋도 같은 것을 가리켜야 한다
|
|
* (2026-08-02 사용자 지시).
|
|
*
|
|
* 서로가 서로를 갱신하므로 `isSyncing` 가드로 재진입을 막는다. 배수유역 강조는 "배관"
|
|
* 구조물(`origin: "pipe"`)일 때만 붙는다 — 다른 구조물에는 대응하는 유역이 없다.
|
|
* ========================================================================== */
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
// 배수유역도 측점 마킹 — 유역이 없는 구조물(대피로 등)도 위치는 표시한다(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);
|
|
} 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);
|
|
// 유역·마커 어느 쪽에서 왔든 시설 폼도 그 관을 연다(투영 측점이 없어도
|
|
// 누가거리로 근사 매칭 — Drainage 쪽이 못 찾으면 해제된다).
|
|
ports.selectPipeForm?.(matched ? matched.chainage_m : chainageM);
|
|
} finally {
|
|
ports.setSyncing(false);
|
|
}
|
|
},
|
|
};
|
|
}
|