Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1

This commit is contained in:
2026-10-10 19:14:12 +09:00
18 changed files with 966 additions and 90 deletions
+5 -5
View File
@@ -289,18 +289,18 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
const pipeColor = (chainage: number): string => pipeMarkerColor(basins, chainage);
/** 마커 선택 ↔ 유역 강조 동기화 + 삭제 버튼 갱신(편집 폼은 사이드 소관). */
function syncPipeSelection(): void {
function syncPipeSelection(notify = true): void {
const index = pipeEditor.selected();
deleteButton.disabled = index === null;
const picked = index === null ? null : pipeEditor.pipes()[index];
selectBasin(basinIndexOfPipe(basins, picked ?? null));
selectBasin(basinIndexOfPipe(basins, picked ?? null), notify);
// 유역 유무와 무관하게 선택 자체를 알린다 — 그래프·3D·리스트 동기화(재진입은
// Page의 selectionSyncing 가드가 끊는다). 값이 안 바뀌면 조용히 — 재계산(apply)
// 뒤의 재호출이 다른 화면 선택을 풀어 버리지 않게.
const pickedChainage = picked ? picked.chainage_m : null;
if (pickedChainage !== lastNotifiedPipeChainage) {
lastNotifiedPipeChainage = pickedChainage;
callbacks.onPipeSelected?.(pickedChainage);
if (notify) callbacks.onPipeSelected?.(pickedChainage);
}
}
@@ -647,7 +647,7 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
pipeEditor.select(index);
pipeEditor.deleteSelected();
},
selectPipeAtChainage(chainageM) {
selectPipeAtChainage(chainageM, notify = true) {
const index =
chainageM === null
? null
@@ -658,7 +658,7 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
if (pipeEditor.selected() === next) return;
// select()는 onChange를 울리지 않는다 — 폼·유역·재그리기를 직접 맞춘다.
pipeEditor.select(next);
syncPipeSelection();
syncPipeSelection(notify);
scheduleDraw();
},
async savePipes() {
@@ -45,7 +45,8 @@ export interface DrainagePanel {
selectBasinByChainage: (chainageM: number | null) => void;
/** 밖(리스트·그래프·3D)에서 관을 고른다 — 마커 선택·유역 강조를 맞춘다.
* 이미 같으면 무시(2026-08-17 전역 선택 동기화). */
selectPipeAtChainage: (chainageM: number | null) => void;
/** notify 거짓 = 고름 한 칸에서 파생해 칠하기만(밖으로 안 알림 · 재계산 뒤 다시 얹음 · 233-3). */
selectPipeAtChainage: (chainageM: number | null, notify?: boolean) => void;
/** 측점 선택 마킹 — 계획선 위 해당 누가거리에 표식을 그린다(null이면 지움). */
markStation: (chainageM: number | null) => void;
/** 구간형 구조물이 놓인 자리 — 계획선 위에 띠로 얹는다(계획서 3-6).
+3 -2
View File
@@ -494,7 +494,8 @@ export function createRouteMarkers(
if (center) selectionPin.position.copy(center);
}
function selectStation(stationId: string | null): void {
/** notify 거짓 = 고름 한 칸에서 파생해 칠하기만(알림 없음 · 233-2). */
function selectStation(stationId: string | null, notify = true): void {
selectedStationId = stationId;
stationGroup.children.forEach((object) => {
// 측점 띠(stationId 보유 Mesh)만 색을 바꾼다.
@@ -503,7 +504,7 @@ export function createRouteMarkers(
(object.material as THREE.MeshBasicMaterial).color.set(selected ? 0xef4444 : 0xfacc15);
});
syncSelectionPin();
stationSelectionListener?.(selectedStationId);
if (notify) stationSelectionListener?.(selectedStationId);
}
return {
+3
View File
@@ -69,6 +69,8 @@ interface PanelCallbacks {
onPipeFacilityRemove: (chainageM: number) => void;
/** null = 사이드 목록 재클릭 해제 — 전 화면 선택도 함께 푼다(2026-08-18). */
onPipeFacilitySelect: (chainageM: number | null) => void;
/** 목록 다시 누름 · 새로 놓기 = 풀기 한 길(233-3). */
onListRelease?: () => void;
/** 이어 공사 시작 기준(시작 측점·누가거리 시작)이 바뀔 때. */
onStationDisplayChange: (offset: { station: number; cumulative: number }) => void;
}
@@ -104,6 +106,7 @@ export function createRoutePanel(callbacks: PanelCallbacks, shared: DesignPageSh
onPipeUpdate: callbacks.onPipeFacilityUpdate,
onPipeRemove: callbacks.onPipeFacilityRemove,
onPipeSelect: callbacks.onPipeFacilitySelect,
onListRelease: callbacks.onListRelease,
},
});
@@ -23,6 +23,7 @@ import {
type SummaryItem,
} from "../common_util/common_util_summary_item";
import { STRUCTURE_SPECS } from "../A00_Common/design/design_structure_pipe";
import { PIPE_MATCH_M } from "../A00_Common/design/design_selection";
import { pipeExtraCount } from "../A00_Common/design/design_structure_pipe_calc";
import {
containerTitle,
@@ -229,7 +230,8 @@ export function renderStructureList(params: StructureListParams): void {
} else {
const { pipe } = row;
const picked =
selectedPipeChainage !== null && Math.abs(selectedPipeChainage - pipe.chainage_m) < 0.05;
selectedPipeChainage !== null &&
Math.abs(selectedPipeChainage - pipe.chainage_m) < PIPE_MATCH_M; // 고름 찾는 거리 한 값(233-3)
item.classList.toggle("is-selected", picked);
station.textContent = formatStation(pipe.chainage_m, intervalM);
item.addEventListener("click", () => params.onSelectPipe(pipe));
@@ -566,10 +566,10 @@ export function createStructuresSection(
// 이 배열을 참조로 받아 두므로, 새 배열로 바꾸면 그쪽이 옛 배열을 고쳐
// 목록에 반영되지 않는다(2026-09-04 실측: 저장 전 항목 [삭제]가 안 먹던 원인).
structures.splice(0, structures.length, ...next.map((entry) => ({ ...entry })));
// 실시간 반영으로 되돌아온 목록이면 폼을 닫지 않는다 — 고치던 칸이 사라진다.
// [저장] 뒤 같은 id 가 남으면 그대로 고름 — 다시 열기와 같게(53-2).
// 실시간 반영 목록이면 폼 안 닫음(고치던 칸) · [저장] 뒤 같은 id · 관 폼은 그대로(53-2 · 233-3)
const kept = structures.find((entry) => entry.structure_id === editingId) ?? null;
if (!applyingLive) loadForm(editingId === null ? null : kept);
if (!applyingLive && selectedPipeChainage === null)
loadForm(editingId === null ? null : kept);
renderList();
},
getStructures: () => [...structures],
@@ -593,12 +593,12 @@ export function createStructuresSection(
syncButtons();
}
}
if (
selectedPipeChainage !== null &&
!pipeFacilities.some(
(pipe) => Math.abs(pipe.chainage_m - selectedPipeChainage!) < PIPE_MATCH_M,
)
) {
const kept = pipeFacilities.find(
(pipe) => Math.abs(pipe.chainage_m - (selectedPipeChainage ?? NaN)) < PIPE_MATCH_M,
);
if (kept)
selectedPipeChainage = kept.chainage_m; // 조금 옮겨진 관도 고름 따라감(233-3)
else if (selectedPipeChainage !== null) {
// 고르고 있던 관이 사라졌다(삭제·재계산 이동) — 폼도 함께 닫는다.
selectedPipeChainage = null;
facilityOptions.setFacility(null);