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

This commit is contained in:
2026-10-16 11:53:52 +09:00
15 changed files with 467 additions and 49 deletions
+2 -6
View File
@@ -53,7 +53,6 @@ import {
structureAnchorM,
} from "../../B05_Profile/B05_Profile_Api_Structures";
import { pipeToFacilityItem } from "../../B05_Profile/B05_Profile_UI_Structures_Panel_Types";
import { saveCorridorIfDirty } from "../../B05_Profile/B05_Profile_UI_Corridor";
import {
profileSaveParts,
type PageActionContext,
@@ -135,7 +134,7 @@ export async function mountProfileView(
{
// 계획선 편집 프리뷰가 횡단 설계선을 갱신해도 3D는 **자동으로 따라오지 않는다**
// (2026-09-01 사용자 지시). 갱신은 상단 [3D 업데이트] 버튼으로 모았다.
onCrossDesignsUpdated: () => panel.setCorridorPending(true),
onCrossDesignsUpdated: () => sections.setCorridorPending(true),
// 관 매설 목록 → 그래프 배관 측점선·통합 목록을 한 방향으로 맞춘다. 정본은 배수유역도의
// 관 지점이다. 숨어 있을 때는 담아만 두고 다시 보일 때 싣는다(폼은 한 벌이라 횡단 보기 몫).
onPipesChanged: (pipes) => {
@@ -188,6 +187,7 @@ export async function mountProfileView(
roadWidths: DEFAULT_ROAD_WIDTHS,
detail: null,
corridorVisible: true,
corridorPending: false,
routeReady: false,
restoring: true,
};
@@ -404,10 +404,6 @@ export async function mountProfileView(
}
profilePick.paint();
},
// 보기를 떠나는 자리 = 코리도 영구저장 시점(2026-08-23 사용자 확정) — 이동은 막지 않는다.
onHide: () => {
if (state.latest?.route?.id) void saveCorridorIfDirty(activeProjectId, state.latest.route.id);
},
});
/** [노선 반영] 끝 — 서버가 배수유역부터 다시 셌고 그 결과가 새 초기값이다. 새로고침 대신 페이지를
+33 -20
View File
@@ -7,7 +7,7 @@
import { showToast } from "@ui/ui_template_elements";
import { updateContourInterval } from "../../B05_Profile/B05_Profile_Api_Fetch";
import { refreshCorridor, saveCorridorIfDirty } from "../../B05_Profile/B05_Profile_UI_Corridor";
import { refreshCorridor } from "../../B05_Profile/B05_Profile_UI_Corridor";
import type { createLatestSource } from "../../B05_Profile/B05_Profile_UI_Page_Latest";
import { toBounds } from "../../B05_Profile/B05_Profile_UI_Page_Helpers";
import type { ViewControlCallbacks } from "../../B05_Profile/B05_Profile_UI_Panel_View";
@@ -48,27 +48,36 @@ export function createProfile3d(deps: Profile3dDeps) {
onResetView: () => viewer.setView("top"),
onProjection: viewer.setProjection,
// [3D 업데이트](2026-09-01) — 밀린 계획선 편집을 예상형상·측점선에 한 번에 반영한다.
onCorridorRefresh: async () => {
const detail = state.detail;
const latest = state.latest;
if (!detail || !latest?.route?.id) return;
const routePoints = latest.route_points ?? [];
if (routePoints.length <= 1) return;
await refreshCorridor(
viewer,
projectId,
latest.route.id,
detail,
routePoints,
deps.profilePanel().alignmentSamples() ?? undefined,
);
// 측점 바·라벨·램프도 계획고를 따라 움직여야 한다(2026-08-23 지적 ③).
deps.sections().renderStationLines(detail);
// 만든 것을 **바로 올려 둔다**(2026-09-06) — 다음 진입이 네트워크 없이 선다.
void saveCorridorIfDirty(projectId, latest.route.id);
},
onCorridorRefresh: () => refreshOnce(),
};
/** 짓는 중이면 그 결과를 같이 기다린다 — 단추 · 모달 열기가 겹쳐도 한 번만 짓는다. */
let refreshing: Promise<void> | null = null;
function refreshOnce(): Promise<void> {
refreshing ??= refreshCorridorNow().finally(() => (refreshing = null));
return refreshing;
}
/** 낡은 3D 길 모형을 지금 노선 · 계획선으로 — 저장본이 맞으면 받기만 · 아니면 워커 미리보기. */
async function refreshCorridorNow(): Promise<void> {
const detail = state.detail;
const latest = state.latest;
if (!detail || !latest?.route?.id) return;
const routePoints = latest.route_points ?? [];
if (routePoints.length <= 1) return;
await refreshCorridor(
viewer,
projectId,
latest.route.id,
detail,
routePoints,
deps.profilePanel().alignmentSamples() ?? undefined,
);
// 측점 바·라벨·램프도 계획고를 따라 움직여야 한다(2026-08-23 지적 ③).
deps.sections().renderStationLines(detail);
deps.sections().setCorridorPending(false);
}
async function applyContours(interval: number): Promise<void> {
// 화면 전체를 잠그지 않는다 — 진행 표시는 [재적용] 버튼이 맡는다(CLAUDE.md 5장).
try {
@@ -122,6 +131,10 @@ export function createProfile3d(deps: Profile3dDeps) {
viewer,
loadTerrain,
visible: () => deps.host.visible(),
// 낡은 채 열면 그때 한 번 다시 짓는다(워커 · 345-4) — 닫혀 있는 동안은 안 짓는다.
onOpen: () => {
if (state.corridorPending) void refreshOnce();
},
});
return { viewCallbacks, applyContours, modal };
@@ -18,6 +18,8 @@ export interface Viewer3dModalDeps {
loadTerrain: () => Promise<void>;
/** 종단 보기가 보이는가 — 숨은 보기에서는 안 연다. */
visible: () => boolean;
/** 열 때마다(지형이 선 뒤) — 낡은 3D 길 모형 다시 짓기(345-4). */
onOpen?: () => void;
}
export function createViewer3dModal(deps: Viewer3dModalDeps) {
@@ -54,11 +56,13 @@ export function createViewer3dModal(deps: Viewer3dModalDeps) {
},
});
viewer.setActive(true);
if (terrain) return;
const progress = createProgressCircle({ label: "3D 지형을 불러오는 중…", overlay: true });
progress.set(null);
viewer.root.append(progress.root);
terrain = deps.loadTerrain().finally(() => progress.remove());
if (!terrain) {
const progress = createProgressCircle({ label: "3D 지형을 불러오는 중…", overlay: true });
progress.set(null);
viewer.root.append(progress.root);
terrain = deps.loadTerrain().finally(() => progress.remove());
}
void terrain.then(() => modal && deps.onOpen?.()); // 그 사이 닫았으면 안 지음
}
/** 열려 있으면 닫는다 — 보기를 떠날 때 · 페이지를 떠날 때. */
@@ -29,6 +29,8 @@ export interface ProfileViewState {
detail: SectionDetailResponse | null;
/** [예상형상] 표시 상태 — 측점 바를 계획고 위로 올릴지 판단한다(패널 기본 ON). */
corridorVisible: boolean;
/** 3D 길 모형이 지금 정본보다 낡았나 — [3D 업데이트] 표시와 한 값 · 모달을 열 때 다시 짓는 기준(345-4). */
corridorPending: boolean;
routeReady: boolean;
restoring: boolean;
}
@@ -92,7 +94,7 @@ export function createProfileSections(deps: ProfileSectionsDeps) {
detail,
routePoints,
profilePanel.alignmentSamples() ?? undefined,
).then((fresh) => panel.setCorridorPending(!fresh));
).then((fresh) => setCorridorPending(!fresh));
}
}
@@ -152,6 +154,12 @@ export function createProfileSections(deps: ProfileSectionsDeps) {
}
}
/** 「3D 늦음」 — [3D 업데이트] 표시(setCorridorPending 그대로)와 상태 한 칸을 같이 맞춘다. */
function setCorridorPending(pending: boolean): void {
state.corridorPending = pending;
deps.panel().setCorridorPending(pending);
}
function renderLatest(next: RouteLatestResponse): void {
state.latest = next;
state.routeReady = Boolean(next.route && next.route_points.length > 1);
@@ -175,5 +183,6 @@ export function createProfileSections(deps: ProfileSectionsDeps) {
renderSections,
restoreSections,
renderLatest,
setCorridorPending,
};
}