feat(B05): 구조물 전역 선택 동기화 — 어느 화면에서 골라도 부속 옵션 폼 개방
- 갭: 배관 부속 폼(시설 편집)이 배수유역도 마커 직접 클릭에서만 열림 — 리스트·종단 그래프·3D·유역 클릭은 강조만 되고 폼 미개방 (2026-08-17 확인) - DrainagePanel.selectPipeAtChainage 신설: 밖에서 관 선택 시 마커+폼+유역 강조까지 (같으면 무시). onPipeSelected 콜백: 유역 없는 관도 전 화면 전파, 같은 값 재알림 억제(재계산 후 선택 풀림 방지) - SelectionSync.selectPipeForm 포트: 그래프·3D(syncIrregularSelection)·유역 (selectStationOfPipe) 경로에서 폼 개방. 리스트(onPipeFacilitySelect) 배선 - 재진입은 기존 selectionSyncing 가드 차단. typecheck/build 통과
This commit is contained in:
@@ -88,6 +88,9 @@ export interface DrainagePanel {
|
||||
savePipes: () => Promise<number>;
|
||||
/** 밖에서 유역을 고른다(그래프 측점선·사이드 패널 선택과 맞추기 위함). 이미 같으면 무시. */
|
||||
selectBasinByChainage: (chainageM: number | null) => void;
|
||||
/** 밖(리스트·그래프·3D)에서 관을 고른다 — 마커 선택·시설 폼·유역 강조까지 연다.
|
||||
* 이미 같으면 무시(2026-08-17 전역 선택 동기화). */
|
||||
selectPipeAtChainage: (chainageM: number | null) => void;
|
||||
/** 측점 선택 마킹 — 계획선 위 해당 누가거리에 표식을 그린다(null이면 지움). */
|
||||
markStation: (chainageM: number | null) => void;
|
||||
dispose: () => void;
|
||||
@@ -107,6 +110,9 @@ export interface DrainagePanelCallbacks {
|
||||
) => void;
|
||||
/** 유역을 고르거나 풀 때 그 관의 누가거리(없으면 null)를 넘긴다 — 그래프·사이드 패널 동기화용. */
|
||||
onBasinSelected?: (chainageM: number | null) => void;
|
||||
/** 관 마커 선택이 바뀔 때(클릭·해제) 누가거리를 알린다 — 유역이 없는 관도 전 화면
|
||||
* (그래프·3D·리스트)이 같은 것을 가리키게 한다(2026-08-17 전역 선택 동기화). */
|
||||
onPipeSelected?: (chainageM: number | null) => void;
|
||||
/** 배수유역도 우클릭으로 배관 외 구조물을 넣을 때(누가거리는 계획선 투영값). */
|
||||
onStructureAdd?: (chainageM: number, type: "기성막이" | "대피로" | "기타") => void;
|
||||
}
|
||||
@@ -219,6 +225,8 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
|
||||
let selectedBasin: number | null = null;
|
||||
// 측점 선택 마킹(계획선 위 누가거리). 유역이 없는 구조물 측점도 위치를 보여 준다.
|
||||
let markedChainage: number | null = null;
|
||||
// 마지막으로 밖에 알린 관 선택(누가거리) — 같은 값 재알림을 막는다.
|
||||
let lastNotifiedPipeChainage: number | null = null;
|
||||
// 배관 편집기 — 마커 선택/추가/이동/삭제 시 재그리기와 버튼 상태만 갱신한다.
|
||||
const pipeEditor = createPipeEditor(
|
||||
() => {
|
||||
@@ -370,6 +378,14 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
|
||||
picked ? ((picked.reason || "user") as PipeSource) : undefined,
|
||||
);
|
||||
selectBasin(basinIndexOfPipe(basins, picked ?? null));
|
||||
// 유역 유무와 무관하게 선택 자체를 알린다 — 그래프·3D·리스트 동기화(재진입은
|
||||
// Page의 selectionSyncing 가드가 끊는다). 값이 안 바뀌면 조용히 — 재계산(apply)
|
||||
// 뒤의 재호출이 다른 화면 선택을 풀어 버리지 않게.
|
||||
const pickedChainage = picked ? picked.chainage_m : null;
|
||||
if (pickedChainage !== lastNotifiedPipeChainage) {
|
||||
lastNotifiedPipeChainage = pickedChainage;
|
||||
callbacks.onPipeSelected?.(pickedChainage);
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleDraw(): void {
|
||||
@@ -744,6 +760,18 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
|
||||
pipeEditor.select(index);
|
||||
pipeEditor.deleteSelected();
|
||||
},
|
||||
selectPipeAtChainage(chainageM) {
|
||||
const index =
|
||||
chainageM === null
|
||||
? null
|
||||
: pipeEditor.pipes().findIndex((pipe) => Math.abs(pipe.chainage_m - chainageM) < 0.51);
|
||||
const next = index === -1 ? null : index;
|
||||
if (pipeEditor.selected() === next) return;
|
||||
// select()는 onChange를 울리지 않는다 — 폼·유역·재그리기를 직접 맞춘다.
|
||||
pipeEditor.select(next);
|
||||
syncPipeSelection();
|
||||
scheduleDraw();
|
||||
},
|
||||
async savePipes() {
|
||||
if (!projectId) return 0;
|
||||
const response = await saveDetailPipePoints(
|
||||
|
||||
@@ -248,6 +248,8 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
onIrregularSelect: (station) => syncIrregularSelection(irregularStationId(station.id)),
|
||||
// 배수유역도에서 유역을 고르면 그 관의 구조물 측점을 그래프·3D·사이드 패널에서도 고른다.
|
||||
onBasinSelected: (chainageM) => selectStationOfPipe(chainageM),
|
||||
// 관 마커 선택(유역 없는 관 포함)도 같은 경로로 전 화면을 맞춘다(2026-08-17).
|
||||
onPipeSelected: (chainageM) => selectStationOfPipe(chainageM),
|
||||
// 구조물 서클마크 — 그래프에서 고르면 사이드 폼도 같은 항목을 연다.
|
||||
onStructureSelect: (structureId) => panel.structures.selectById(structureId),
|
||||
onStructureMarkMove: (structureId, toChainage) =>
|
||||
@@ -301,6 +303,8 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
// 통합 「구조물 배치」 목록의 계곡 통과 시설 항목을 강조한다(2026-08-17 통합).
|
||||
selectSidebar: (chainageM) => panel.structures.selectPipeByChainage(chainageM),
|
||||
selectBasin: (chainageM) => profilePanel.drainage.selectBasinByChainage(chainageM),
|
||||
// 배관이면 배수유역 마커 선택 + 부속 옵션 폼까지 연다(2026-08-17 전역 선택 동기화).
|
||||
selectPipeForm: (chainageM) => profilePanel.drainage.selectPipeAtChainage(chainageM),
|
||||
markStation: (chainageM) => profilePanel.drainage.markStation(chainageM),
|
||||
});
|
||||
|
||||
@@ -369,6 +373,8 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
profilePanel.setSelectedStation(id);
|
||||
// 통합 목록에서 배관 항목을 고르면 배수유역도의 그 유역도 함께 강조한다.
|
||||
syncBasinHighlight(id);
|
||||
// 배수유역 마커 선택 + 부속 옵션 폼도 연다(2026-08-17 전역 선택 동기화).
|
||||
profilePanel.drainage.selectPipeAtChainage(chainage);
|
||||
} finally {
|
||||
selectionSyncing = false;
|
||||
}
|
||||
|
||||
@@ -238,6 +238,8 @@ export interface RouteProfilePanelCallbacks {
|
||||
onStructureTypeAdd?: (chainageM: number, typeId: string) => void;
|
||||
/** 배수유역도에서 유역을 고름 — 그 관의 누가거리(해제면 null). 그래프·사이드 패널을 맞춘다. */
|
||||
onBasinSelected?: (chainageM: number | null) => void;
|
||||
/** 배수유역도에서 관 마커를 고름(유역 없는 관 포함) — 전 화면 동기화용(2026-08-17). */
|
||||
onPipeSelected?: (chainageM: number | null) => void;
|
||||
}
|
||||
|
||||
export function createRouteProfilePanel(
|
||||
@@ -298,6 +300,7 @@ export function createRouteProfilePanel(
|
||||
const drainagePanel = createDrainagePanel({
|
||||
onPipesChanged: (pipes) => callbacks?.onPipesChanged?.(pipes),
|
||||
onBasinSelected: (chainageM) => callbacks?.onBasinSelected?.(chainageM),
|
||||
onPipeSelected: (chainageM) => callbacks?.onPipeSelected?.(chainageM),
|
||||
onStructureAdd: (chainage, type) => callbacks?.onStructureAdd?.(chainage, type),
|
||||
});
|
||||
content.append(bodyWrap, drainagePanel.root);
|
||||
|
||||
@@ -30,6 +30,9 @@ export interface SelectionSyncPorts {
|
||||
selectBasin: (chainageM: number | null) => void;
|
||||
/** 배수유역도 계획선 위 측점 마킹(누가거리 기준). 유역 없는 구조물도 위치를 보여 준다. */
|
||||
markStation?: (chainageM: number | null) => void;
|
||||
/** 배수유역도 관 마커 선택 + 시설 폼 열기(누가거리 기준) — 어느 화면에서 배관을
|
||||
* 골라도 부속 옵션 폼까지 열리게 한다(2026-08-17 전역 선택 동기화). */
|
||||
selectPipeForm?: (chainageM: number | null) => void;
|
||||
}
|
||||
|
||||
export interface SelectionSync {
|
||||
@@ -72,6 +75,7 @@ export function createSelectionSync(ports: SelectionSyncPorts): SelectionSync {
|
||||
ports.setSyncing(true);
|
||||
try {
|
||||
ports.selectSidebar(station ? station.chainage_m : null);
|
||||
ports.selectPipeForm?.(station && isPipeStation(station) ? station.chainage_m : null);
|
||||
} finally {
|
||||
ports.setSyncing(false);
|
||||
}
|
||||
@@ -93,6 +97,9 @@ export function createSelectionSync(ports: SelectionSyncPorts): SelectionSync {
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user