Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1
This commit is contained in:
@@ -274,7 +274,7 @@ export function renderDesignPage(root: HTMLElement): MountedPage {
|
||||
slots.clear();
|
||||
shared.profile?.dispose();
|
||||
stopBusy();
|
||||
forgetPageData(projectId); // 다시 열면 저장분 새로(386-2)
|
||||
forgetPageData(projectId, true); // 다시 열면 저장분 · 노선 id 새로(386-2)
|
||||
delete (window as unknown as { __designPage?: unknown }).__designPage;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -9,9 +9,14 @@
|
||||
* 종류 목록은 `fetchStructureTypes` 가 탭 수명 동안 쥠 — 정의 판 확인만 페이지마다 한 번.
|
||||
* 돌려줄 때마다 사본 — 받는 쪽이 고쳐도 다른 쪽 저장분이 안 바뀜.
|
||||
* 「계산 중」 — 묶음 차례가 떠 있는 동안 좌측 아래 점(`onPageBusy`).
|
||||
* 노선 id 한 칸(386-2b) — 횡단 context(서버 지금 노선 · 세션 캐시)에서 채움 · 두 보기 · 종단도 · [저장] ·
|
||||
* 평면 횡단 판이 여기서만 읽음 · 노선이 바뀌면 페이지를 통째 다시 세움(remountRoute) → 페이지 동안 고정.
|
||||
* ========================================================================== */
|
||||
|
||||
import { fetchDetailPipePoints } from "../../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import type { SectionContextResponse } from "../../B06_Section/B06_Section_Api_Fetch";
|
||||
import { setPageRouteId } from "./design_page_route";
|
||||
export { pageRouteId } from "./design_page_route";
|
||||
import {
|
||||
dropStaleStructureTypes,
|
||||
fetchStructures,
|
||||
@@ -41,9 +46,19 @@ export const pagePipes = (projectId: string): ReturnType<typeof fetchDetailPipeP
|
||||
export const pageTypesChecked = (projectId: string): Promise<boolean> =>
|
||||
once(`types:${projectId}`, () => dropStaleStructureTypes());
|
||||
|
||||
/** 저장 정본이 바뀜 · 페이지를 떠남 — 다음에 부르면 한 번 다시 받음. */
|
||||
export function forgetPageData(projectId: string): void {
|
||||
/** 저장 정본이 바뀜 · 페이지를 떠남(`leaving` = 노선 id 칸도) — 다음에 부르면 한 번 다시 받음. */
|
||||
export function forgetPageData(projectId: string, leaving = false): void {
|
||||
for (const key of [...memo.keys()]) if (key.endsWith(`:${projectId}`)) memo.delete(key);
|
||||
if (leaving) setPageRouteId(projectId, null);
|
||||
}
|
||||
|
||||
/** 횡단 context(세션 캐시 · 새 요청 없음) — 노선 id 칸을 채움 · 두 보기가 이것으로만 받음. 서버 창구는 부를 때
|
||||
* 불러옴(묶음 · 초안 측점이 끄는 이 파일이 화면 모듈을 안 끌게). */
|
||||
export async function pageSectionContext(projectId: string): Promise<SectionContextResponse> {
|
||||
const { fetchSectionContext } = await import("../../B06_Section/B06_Section_Api_Fetch");
|
||||
const context = await fetchSectionContext(projectId);
|
||||
setPageRouteId(projectId, context.route_id); // 노선 id 한 칸(design_page_route)
|
||||
return context;
|
||||
}
|
||||
|
||||
const busy = new Map<string, number>();
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/* =============================================================================
|
||||
* A00_Common/design/design_page_route.ts
|
||||
* 한 페이지 노선 id 한 칸(386-2b) — 채우는 곳은 페이지 자료 줄 `pageSectionContext`(design_page_data) 하나 ·
|
||||
* 읽는 곳은 `pageRouteId` 만. 노선이 바뀌면 페이지를 통째 다시 세움(remountRoute) → 페이지 동안 고정.
|
||||
* 끌어오는 모듈 없음(평면 횡단 판 · 묶음 시험이 화면 · 서버 창구를 안 끌게).
|
||||
* ========================================================================== */
|
||||
|
||||
const routes = new Map<string, number>();
|
||||
|
||||
/** 이 페이지의 노선 id(없으면 null). */
|
||||
export const pageRouteId = (projectId: string): number | null => routes.get(projectId) ?? null;
|
||||
|
||||
/** 자료 줄만 부름 — context 노선(null = 비움). */
|
||||
export function setPageRouteId(projectId: string, routeId: number | null): void {
|
||||
if (routeId === null) routes.delete(projectId);
|
||||
else routes.set(projectId, routeId);
|
||||
}
|
||||
@@ -72,13 +72,14 @@ import {
|
||||
import { planCurveWarning, sampleAt } from "../../B05_Profile/logic/B05_Profile_UI_Profile_Guard";
|
||||
import { listedBase } from "./design_profile_base";
|
||||
import * as pagePipeline from "./design_pipeline_page";
|
||||
import type { RouteProfilePanelCallbacks } from "../../B05_Profile/B05_Profile_UI_Profile_Panel_Types";
|
||||
import type { ElevationWindowResult } from "@util/common_util_chart_ywindow";
|
||||
import { frameOf } from "@util/common_util_view_window";
|
||||
import type { DesignSideBoard, DesignSideFactory, DesignView } from "./design_profile_slots";
|
||||
import type { DesignSideBoard, DesignView } from "./design_profile_slots";
|
||||
import { noticeDroppedEdits } from "./design_save";
|
||||
import { createProfileOverlays } from "./design_profile_overlays";
|
||||
import { bindProfileViewport, createSelectionReveal } from "./design_profile_viewport";
|
||||
import { CROSS_PREVIEW_DEBOUNCE_MS, type DesignProfileContext } from "./design_profile_context";
|
||||
import { pageRouteId } from "./design_page_route";
|
||||
import "../../B06_Section/B06_Section_UI_Style.css";
|
||||
// SVG 차트 색상(.b06-chart__*)의 정의처는 _Style_Cross.css다. 이걸 빼면 B05로 바로 진입했을 때
|
||||
// 배경 rect가 브라우저 기본 fill(검정)로 그려진다 — B06을 먼저 방문해야 정상으로 보이던 원인.
|
||||
@@ -92,31 +93,7 @@ export type {
|
||||
DesignSideHost,
|
||||
DesignView,
|
||||
} from "./design_profile_slots";
|
||||
|
||||
/** 종단도를 만들 때 넘기는 것. */
|
||||
export interface DesignProfileContext {
|
||||
projectId: string;
|
||||
/** 처음 보기. */
|
||||
view: DesignView;
|
||||
/** 오른쪽 판 — 없으면 판 없이 종단 열만. */
|
||||
side?: DesignSideFactory;
|
||||
/** 도구줄 [초기화] 바로 왼쪽에 놓는 단추([새 창]) — 다시 그려도 그 자리. */
|
||||
toolLead?: HTMLElement;
|
||||
/** 측점 선택 변경 — 같은 측점 재선택·빈 곳 클릭이면 null(해제)로 알린다. */
|
||||
onSelectStation: (stationId: string | null) => void;
|
||||
/** [초기선 복원] 클릭 시 함께 실행(비정규 측점 등 다른 조작값도 초기화하려고 페이지가 넘긴다). */
|
||||
onResetAll?: () => void;
|
||||
callbacks?: RouteProfilePanelCallbacks;
|
||||
}
|
||||
|
||||
/** 계획고 편집 후 횡단을 다시 계산하기까지 기다리는 시간(ms).
|
||||
* ▲/▼ 길게 누르기(초당 10회)·끌기에서 매 프레임 다시 돌지 않게 마지막 값만 계산한다.
|
||||
* 계산이 브라우저 안에서 끝나면서(2026-09-03) 서버 왕복이 사라져 250→60ms 로 줄였다 —
|
||||
* 실측 전 측점(128) 재계산 7.5ms 라 손을 떼는 즉시 유토곡선이 따라온다.
|
||||
* 60→16ms(2026-09-06) — ▲ 한 번에 라벨이 서기까지 중앙 130ms 를 쟀는데 그중 60ms 가
|
||||
* 이 대기였다. 한 프레임(16ms)이면 연속 입력은 여전히 마지막 값만 계산한다.
|
||||
* 횡단 보기는 120ms — 계산마다 카드를 갈아 끼워 길게 누르면 멈춤이 쌓임(9da48d5e7 · 54-22). */
|
||||
const CROSS_PREVIEW_DEBOUNCE_MS = 16;
|
||||
export type { DesignProfileContext } from "./design_profile_context";
|
||||
|
||||
export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
const { projectId, onSelectStation, onResetAll, callbacks } = ctx;
|
||||
@@ -491,7 +468,8 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
root.dataset.view = next;
|
||||
drainagePanel?.setView?.(next);
|
||||
},
|
||||
render(nextDetail: SectionDetailResponse, nextStationInterval?: number, nextRouteId?: number) {
|
||||
render(nextDetail: SectionDetailResponse, nextStationInterval?: number, givenRouteId?: number) {
|
||||
const nextRouteId = pageRouteId(projectId) ?? givenRouteId; // 노선 id 한 칸(386-2b)
|
||||
detail = nextDetail;
|
||||
stationInterval = nextStationInterval;
|
||||
const stored = readAlignment(nextDetail.longitudinal);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/* =============================================================================
|
||||
* A00_Common/design/design_profile_context.ts
|
||||
* 종단도 한 벌(`design_profile`)을 만들 때 넘기는 것 · 횡단 미리보기 대기 — 700줄 쪼갬(386-2b).
|
||||
* ========================================================================== */
|
||||
|
||||
import type { RouteProfilePanelCallbacks } from "../../B05_Profile/B05_Profile_UI_Profile_Panel_Types";
|
||||
import type { DesignSideFactory, DesignView } from "./design_profile_slots";
|
||||
|
||||
/** 종단도를 만들 때 넘기는 것. */
|
||||
export interface DesignProfileContext {
|
||||
projectId: string;
|
||||
/** 처음 보기. */
|
||||
view: DesignView;
|
||||
/** 오른쪽 판 — 없으면 판 없이 종단 열만. */
|
||||
side?: DesignSideFactory;
|
||||
/** 도구줄 [초기화] 바로 왼쪽에 놓는 단추([새 창]) — 다시 그려도 그 자리. */
|
||||
toolLead?: HTMLElement;
|
||||
/** 측점 선택 변경 — 같은 측점 재선택·빈 곳 클릭이면 null(해제)로 알린다. */
|
||||
onSelectStation: (stationId: string | null) => void;
|
||||
/** [초기선 복원] 클릭 시 함께 실행(비정규 측점 등 다른 조작값도 초기화하려고 페이지가 넘긴다). */
|
||||
onResetAll?: () => void;
|
||||
callbacks?: RouteProfilePanelCallbacks;
|
||||
}
|
||||
|
||||
/** 계획고 편집 후 횡단을 다시 계산하기까지 기다리는 시간(ms).
|
||||
* ▲/▼ 길게 누르기(초당 10회)·끌기에서 매 프레임 다시 돌지 않게 마지막 값만 계산한다.
|
||||
* 계산이 브라우저 안에서 끝나면서(2026-09-03) 서버 왕복이 사라져 250→60ms 로 줄였다 —
|
||||
* 실측 전 측점(128) 재계산 7.5ms 라 손을 떼는 즉시 유토곡선이 따라온다.
|
||||
* 60→16ms(2026-09-06) — ▲ 한 번에 라벨이 서기까지 중앙 130ms 를 쟀는데 그중 60ms 가
|
||||
* 이 대기였다. 한 프레임(16ms)이면 연속 입력은 여전히 마지막 값만 계산한다.
|
||||
* 횡단 보기는 120ms — 계산마다 카드를 갈아 끼워 길게 누르면 멈춤이 쌓임(9da48d5e7 · 54-22). */
|
||||
export const CROSS_PREVIEW_DEBOUNCE_MS = 16;
|
||||
@@ -19,8 +19,8 @@ import { showToast } from "@ui/ui_template_elements";
|
||||
import { mountMissingStationNotice } from "../../B06_Section/B06_Section_UI_Missing_Stations";
|
||||
import { onStaleStructureTypes } from "../../B05_Profile/B05_Profile_Api_Structures";
|
||||
import { pipeIdAt } from "./design_pipe_ids";
|
||||
import { pageRouteId, pageSectionContext } from "./design_page_data";
|
||||
import {
|
||||
fetchSectionContext,
|
||||
getSections,
|
||||
type SectionContextResponse,
|
||||
type SectionDetailResponse,
|
||||
@@ -579,7 +579,7 @@ export async function mountCrossView(
|
||||
return;
|
||||
}
|
||||
try {
|
||||
context = await fetchSectionContext(projectId);
|
||||
context = await pageSectionContext(projectId); // 노선 id 한 칸을 채움(386-2b)
|
||||
} catch {
|
||||
showToast(L("B06_Profile_Context_Failed"), "error");
|
||||
}
|
||||
@@ -601,13 +601,12 @@ export async function mountCrossView(
|
||||
rememberRockBoundaryDefault(projectId, context.rock_boundary_default_offset_m);
|
||||
standardPanelSlot.append(standardPanel.root);
|
||||
|
||||
if (context.route_id === null) {
|
||||
currentRouteId = pageRouteId(projectId);
|
||||
if (currentRouteId === null) {
|
||||
// 자료가 통째로 없으면(새 자료가 올라와 옛 결과가 지워진 경우) 대시보드로 돌려보낸다.
|
||||
leaveForDashboard();
|
||||
return;
|
||||
}
|
||||
|
||||
currentRouteId = context.route_id;
|
||||
rockStore.load();
|
||||
cutSlopeStore.load();
|
||||
stationControls.load();
|
||||
@@ -615,13 +614,13 @@ export async function mountCrossView(
|
||||
// 섰으면(종단 보기 [확정]) 한 벌 폼은 종단 보기 몫이라 싣지 않는다 — 보일 때 싣는다.
|
||||
if (host.visible()) void structuresPanel.load().then(restoreStructurePick);
|
||||
try {
|
||||
const existing = await getSections(projectId, context.route_id);
|
||||
const existing = await getSections(projectId, currentRouteId);
|
||||
if (!existing.longitudinal) {
|
||||
leaveForDashboard();
|
||||
return;
|
||||
}
|
||||
// 공유 캐시 — 종단 보기가 이미 받아 뒀으면 같은 객체를 즉시 재사용한다.
|
||||
sectionDetail = await loadSectionDetail(projectId, context.route_id);
|
||||
sectionDetail = await loadSectionDetail(projectId, currentRouteId);
|
||||
structuresPanel.facility.recount(); // 단 수는 횡단 자료로 셈 — 흙막이 상자 · 목록 램프 다시(54-15)
|
||||
// 구조물 초안(저장 안 한 벽) · 그 면적은 상세 가게가 얹어 줌(387-4 · 화면은 안 얹음).
|
||||
// 단일 소스(DB data.options) 우선, options 스냅샷이 없는 과거 데이터는 샘플 최대 offset으로 추정
|
||||
|
||||
@@ -21,6 +21,7 @@ import { leaveForDashboard } from "../b_missing_data_guard";
|
||||
import { currentRoute, remountRoute } from "../router";
|
||||
import { mountRouteEditor } from "../../B05_Profile/B05_Profile_UI_RouteEdit";
|
||||
import { routeDraftBridge } from "./design_pipeline_page";
|
||||
import { pageRouteId, pageSectionContext } from "./design_page_data";
|
||||
import { createSelectionSync, graphTargetAt } from "../../B05_Profile/B05_Profile_UI_Selection";
|
||||
import {
|
||||
readStructurePick,
|
||||
@@ -47,7 +48,6 @@ import {
|
||||
rememberRockBoundaryDefault,
|
||||
rememberStandardDefaults,
|
||||
} from "../../B06_Section/B06_Section_UI_Standard_Panel";
|
||||
import { fetchSectionContext } from "../../B06_Section/B06_Section_Api_Fetch";
|
||||
import { invalidateSectionDetail } from "../../B06_Section/B06_Section_Section_Store";
|
||||
import {
|
||||
onStaleStructureTypes,
|
||||
@@ -490,7 +490,7 @@ export async function mountProfileView(
|
||||
const cachedLatest = latestSource.read();
|
||||
const [latestResponse, sectionContext, configuredRoadWidths] = await Promise.all([
|
||||
cachedLatest ? Promise.resolve(cachedLatest) : loadLatest(true),
|
||||
fetchSectionContext(activeProjectId),
|
||||
pageSectionContext(activeProjectId), // 노선 id 한 칸을 채움(386-2b)
|
||||
fetchRoadWidths(activeProjectId),
|
||||
]);
|
||||
if (host.disposed()) return;
|
||||
@@ -540,7 +540,8 @@ export async function mountProfileView(
|
||||
});
|
||||
|
||||
// ③ 종단면·횡단 자료 — 하단 패널을 3D보다 먼저 채운다.
|
||||
if (latestResponse.route) await restoreSections(latestResponse.route.id);
|
||||
const routeId = pageRouteId(activeProjectId); // 노선 id 한 칸(386-2b)
|
||||
if (latestResponse.route && routeId !== null) await restoreSections(routeId);
|
||||
// 횡단 보기가 먼저 서서 배수유역도가 관 목록을 이미 알렸다면 그것을 먼저 싣는다 — 빈 목록으로
|
||||
// 시작하면 아래 구조물 로딩이 관 대조로 관을 지우고 초안 옵션이 자동 배치로 되돌아간다(24-7a).
|
||||
if (lastPipes) applyPipes(lastPipes);
|
||||
@@ -559,7 +560,7 @@ export async function mountProfileView(
|
||||
return;
|
||||
}
|
||||
renderLatest(fresh);
|
||||
if (fresh.route?.id) await restoreSections(fresh.route.id);
|
||||
if (fresh.route && routeId !== null) await restoreSections(routeId);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { RouteViewer } from "../../B05_Profile/B05_Profile_UI_Viewer";
|
||||
import type { DesignPageHost } from "./design_page";
|
||||
import type { createProfileSections, ProfileViewState } from "./design_view_profile_sections";
|
||||
import { createViewer3dModal } from "./design_view_profile_3d_modal";
|
||||
import { pageRouteId } from "./design_page_route";
|
||||
|
||||
export interface Profile3dDeps {
|
||||
projectId: string;
|
||||
@@ -62,13 +63,14 @@ export function createProfile3d(deps: Profile3dDeps) {
|
||||
async function refreshCorridorNow(): Promise<void> {
|
||||
const detail = state.detail;
|
||||
const latest = state.latest;
|
||||
if (!detail || !latest?.route?.id) return;
|
||||
const routeId = pageRouteId(projectId); // 노선 id 한 칸(386-2b)
|
||||
if (!detail || !latest?.route || routeId === null) return;
|
||||
const routePoints = latest.route_points ?? [];
|
||||
if (routePoints.length <= 1) return;
|
||||
await refreshCorridor(
|
||||
viewer,
|
||||
projectId,
|
||||
latest.route.id,
|
||||
routeId,
|
||||
detail,
|
||||
routePoints,
|
||||
deps.profilePanel().alignmentSamples() ?? undefined,
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
swapSectionStations,
|
||||
} from "../../B06_Section/B06_Section_Section_Store";
|
||||
import { useDesignStore } from "./design_store";
|
||||
import { pageRouteId } from "./design_page_route";
|
||||
import { migrateLegacyStations } from "../../B05_Profile/B05_Profile_Api_Structures";
|
||||
import { loadCorridorIfFresh } from "../../B05_Profile/B05_Profile_UI_Corridor";
|
||||
import { stationLinesOf } from "../../B05_Profile/B05_Profile_UI_Page_Stations";
|
||||
@@ -97,7 +98,7 @@ export function createProfileSections(deps: ProfileSectionsDeps) {
|
||||
void loadCorridorIfFresh(
|
||||
viewer,
|
||||
projectId,
|
||||
routeId ?? state.latest?.route?.id,
|
||||
routeId ?? pageRouteId(projectId) ?? undefined, // 노선 id 한 칸(386-2b)
|
||||
detail,
|
||||
routePoints,
|
||||
profilePanel.alignmentSamples() ?? undefined,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { confirmRoute, type RouteLatestResponse } from "./B05_Profile_Api_Fetch";
|
||||
import type { DesignViewSave } from "../A00_Common/design/design_shared";
|
||||
import { invalidateSectionDetail } from "../B06_Section/B06_Section_Section_Store";
|
||||
import { pageRouteId } from "../A00_Common/design/design_page_route";
|
||||
import type { createRoutePanel } from "./B05_Profile_UI_Panel";
|
||||
import type { createRouteProfilePanel } from "./B05_Profile_UI_Profile_Panel";
|
||||
import type { createStructuresBridge } from "./B05_Profile_UI_Page_Structures";
|
||||
@@ -40,7 +41,7 @@ export interface PageActionContext {
|
||||
export function profileSaveParts(ctx: PageActionContext): DesignViewSave {
|
||||
const projectId = ctx.projectId;
|
||||
return {
|
||||
routeId: () => (ctx.routeReady() ? (ctx.latest()?.route?.id ?? null) : null),
|
||||
routeId: () => (ctx.routeReady() ? pageRouteId(projectId) : null), // 노선 id 한 칸(386-2b)
|
||||
steps: {
|
||||
// 구조물 — 저장 뒤 서버 목록(새 식별자)으로 화면을 맞춘다. 실패는 자체 토스트로 알리고
|
||||
// 세션에 남겨 두므로 저장 전체를 멈추지 않는다(2026-08-29 — CLAUDE.md 5장).
|
||||
|
||||
@@ -40,7 +40,8 @@ import {
|
||||
CROSS_PLOT_PAD,
|
||||
summarizeCross,
|
||||
} from "./B05_Profile_UI_RouteEdit_Cross_Draw";
|
||||
import { readByKey, routeStateKeys, stateKey } from "../A00_Common/b_page_state";
|
||||
import { readStateRaw } from "../A00_Common/b_page_state";
|
||||
import { pageRouteId } from "../A00_Common/design/design_page_route";
|
||||
import { stationLabel as formatStation } from "../common_util/common_util_station";
|
||||
import { sidePanelsOf } from "./B05_Profile_UI_RouteEdit_Chrome";
|
||||
import type { FootprintRecord } from "./logic/B05_Profile_PlanFootprint";
|
||||
@@ -107,17 +108,11 @@ interface CrossPane {
|
||||
|
||||
/**
|
||||
* 우측 패널 「횡단 반폭(m)」에 **적용해 둔 값**(m). 적용한 적이 없으면 undefined — 서버가 기본값을 쓴다.
|
||||
*
|
||||
* B06 이 [적용]에서 노선 단위 세션 키에 적는다. 이 창은 노선 번호를 몰라 프로젝트 접두어로
|
||||
* 훑고, 노선이 여럿 남아 있으면 번호가 가장 큰(최신) 것을 쓴다.
|
||||
* B06 이 [적용]에서 노선 단위 세션 키에 적음 · 노선 = 페이지 노선 id 한 칸(386-2b).
|
||||
*/
|
||||
function appliedHalfWidth(projectId: string): number | undefined {
|
||||
const prefix = stateKey("cross-display", projectId, "");
|
||||
if (!prefix) return undefined;
|
||||
const latest = routeStateKeys("cross-display", projectId).sort(
|
||||
(a, b) => Number(b.slice(prefix.length)) - Number(a.slice(prefix.length)),
|
||||
)[0];
|
||||
const value = Number(latest ? readByKey(latest) : NaN);
|
||||
const routeId = pageRouteId(projectId);
|
||||
const value = Number(routeId === null ? NaN : readStateRaw("cross-display", projectId, routeId));
|
||||
return Number.isFinite(value) && value > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,10 @@ const drafts = loadTs("B06_Section/B06_Section_Section_Drafts.ts", {
|
||||
"../A00_Common/design/design_page_data": loadTs("A00_Common/design/design_page_data.ts", {
|
||||
"../../B04_PreProcess/B04_PreProcess_Api_Fetch": serverFetch,
|
||||
"../../B05_Profile/B05_Profile_Api_Structures": serverStructures,
|
||||
"../../B06_Section/B06_Section_Api_Fetch": {
|
||||
fetchSectionContext: async () => ({ route_id: 1 }),
|
||||
},
|
||||
"./design_page_route": loadTs("A00_Common/design/design_page_route.ts"), // 노선 id 한 칸(386-2b)
|
||||
}),
|
||||
"../B05_Profile/B05_Profile_Api_Pipes_Draft": { readPendingPipes: () => draftPipes },
|
||||
"../B05_Profile/B05_Profile_Api_PlusStations": { readPlusDraft: () => null },
|
||||
|
||||
@@ -7,7 +7,14 @@ const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "t
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const calls = { structures: 0, pipes: 0, version: 0 };
|
||||
let failPipes = false;
|
||||
const routeJs = ts.transpileModule(
|
||||
fs.readFileSync(path.join(ROOT, "A00_Common", "design", "design_page_route.ts"), "utf8"),
|
||||
{ compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } },
|
||||
).outputText;
|
||||
const routeBox = { exports: {} };
|
||||
new Function("exports", "module", "require", routeJs)(routeBox.exports, routeBox, () => ({}));
|
||||
const STUBS = {
|
||||
"./design_page_route": routeBox.exports, // 노선 id 한 칸(386-2b · 진짜)
|
||||
"../../B04_PreProcess/B04_PreProcess_Api_Fetch": {
|
||||
fetchDetailPipePoints: async () => {
|
||||
calls.pipes += 1;
|
||||
@@ -15,6 +22,10 @@ const STUBS = {
|
||||
return { pipe_points: [{ chainage_m: 10, facility: "pipe" }], basins: [] };
|
||||
},
|
||||
},
|
||||
// 횡단 context(세션 캐시) — 노선 id 한 칸(386-2b)
|
||||
"../../B06_Section/B06_Section_Api_Fetch": {
|
||||
fetchSectionContext: async (projectId) => ({ route_id: projectId === "p-none" ? null : 304 }),
|
||||
},
|
||||
"../../B05_Profile/B05_Profile_Api_Structures": {
|
||||
fetchStructures: async () => {
|
||||
calls.structures += 1;
|
||||
@@ -85,6 +96,17 @@ const tick = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||
data.trackPageBusy(P, Promise.resolve());
|
||||
await tick();
|
||||
out.busy = seen;
|
||||
// 노선 id 한 칸(386-2b) — context 가 채움 · 저장 정본 바뀜(비움)엔 그대로 · 페이지 떠남에 비움
|
||||
const route = { before: data.pageRouteId(P) };
|
||||
route.context = (await data.pageSectionContext(P)).route_id;
|
||||
route.after = data.pageRouteId(P);
|
||||
data.forgetPageData(P);
|
||||
route.keptOnSave = data.pageRouteId(P);
|
||||
data.forgetPageData(P, true);
|
||||
route.leaving = data.pageRouteId(P);
|
||||
await data.pageSectionContext("p-none");
|
||||
route.none = data.pageRouteId("p-none");
|
||||
out.route = route;
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
})().catch((error) => {
|
||||
console.error(error);
|
||||
|
||||
@@ -137,6 +137,8 @@ const { createCrossPreview } = loadTs(
|
||||
path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Cross.ts"),
|
||||
);
|
||||
const pick = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts"));
|
||||
// 페이지 노선 id 한 칸(386-2b) — 판이 읽는 적용값 = 이 노선 것만
|
||||
const pageRoute = loadTs(path.join(ROOT, "A00_Common", "design", "design_page_route.ts"));
|
||||
|
||||
const record = (key, chainage_m, left, right, leftCut, rightCut) => ({
|
||||
key,
|
||||
@@ -161,12 +163,16 @@ async function run() {
|
||||
out.before = cross.watched();
|
||||
await cross.open(100);
|
||||
out.empty = { watched: cross.watched() };
|
||||
pageRoute.setPageRouteId("p-1", 3);
|
||||
session.setItem("aislo:draft:cross-display:p-1:3", "15");
|
||||
await cross.refresh();
|
||||
out.applied15 = { watched: cross.watched() };
|
||||
session.setItem("aislo:draft:cross-display:p-1:12", "8.5");
|
||||
session.setItem("aislo:draft:cross-display:p-2:99", "40");
|
||||
await cross.refresh();
|
||||
out.otherRoute = { watched: cross.watched() }; // 다른 노선 · 다른 프로젝트 값은 안 봄
|
||||
pageRoute.setPageRouteId("p-1", 12);
|
||||
await cross.refresh();
|
||||
out.latestRoute = { watched: cross.watched() };
|
||||
session.setItem("aislo:draft:cross-display:p-1:12", "abc");
|
||||
await cross.refresh();
|
||||
|
||||
@@ -87,7 +87,7 @@ def test_비우는_자리():
|
||||
store = _read("B06_Section/B06_Section_Section_Store.ts")
|
||||
assert "forgetPageData(projectId)" in drafts # 저장 정본 바뀜(invalidate → forgetDraftStations)
|
||||
assert "forgetDraftStations(projectId)" in store
|
||||
assert "forgetPageData(projectId)" in page # 페이지를 떠남
|
||||
assert "forgetPageData(projectId, true)" in page # 페이지를 떠남(노선 id 칸도 · 386-2b)
|
||||
assert "onPageBusy(projectId" in page
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
"""PLAN 386-2b sub7 — 노선 id 한 칸(design_page_data).
|
||||
|
||||
못박는 것
|
||||
- 출처 하나 = `pageSectionContext`(횡단 context · 세션 캐시)가 채운 칸 — 두 보기는 context 를 이것으로만 받음.
|
||||
- 읽는 곳(옛 출처 다섯) = 종단 보기 [저장] 몫(PA) · 횡단 보기 · 종단도 render · 평면 횡단 판(RC) · 3D · 측점 그림 —
|
||||
`pageRouteId` 만 · 묶음(PP)은 종단도 `pipelineSource().routeId`(= 칸)를 받음.
|
||||
- 칸 = 저장 정본 바뀜(invalidate)엔 그대로 · 페이지 떠남에 비움 · context 노선 없음 = null.
|
||||
|
||||
짝: `helper_384_2_page_data.cjs`(진짜 TS · 서버 흉내) — `test_384_2_page_data.py` 와 같은 헬퍼.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
|
||||
READERS = {
|
||||
"B05_Profile/B05_Profile_UI_Page_Actions.ts": "pageRouteId(projectId)",
|
||||
"A00_Common/design/design_view_cross.ts": "currentRouteId = pageRouteId(projectId);",
|
||||
"A00_Common/design/design_profile.ts": "pageRouteId(projectId) ?? givenRouteId",
|
||||
"B05_Profile/B05_Profile_UI_RouteEdit_Cross.ts": "pageRouteId(projectId)",
|
||||
"A00_Common/design/design_view_profile.ts": "pageRouteId(activeProjectId)",
|
||||
"A00_Common/design/design_view_profile_3d.ts": "pageRouteId(projectId)",
|
||||
"A00_Common/design/design_view_profile_sections.ts": "pageRouteId(projectId)",
|
||||
}
|
||||
|
||||
|
||||
def _read(rel: str) -> str:
|
||||
return (ROOT / rel).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", str(HERE / "helper_384_2_page_data.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_칸_채움_비움(r):
|
||||
route = r["route"]
|
||||
assert route["before"] is None and route["context"] == 304 and route["after"] == 304
|
||||
assert route["keptOnSave"] == 304 # 저장 정본 바뀜(비움)엔 그대로
|
||||
assert route["leaving"] is None # 페이지 떠남
|
||||
assert route["none"] is None # context 노선 없음
|
||||
|
||||
|
||||
@pytest.mark.parametrize("rel", sorted(READERS))
|
||||
def test_읽는_곳은_칸만(rel):
|
||||
src = _read(rel)
|
||||
assert READERS[rel] in src
|
||||
assert "context.route_id" not in src
|
||||
used = [
|
||||
line
|
||||
for line in src.splitlines()
|
||||
if re.search(r"(latest(Response)?|ctx\.latest\(\))\??\.route\??\.id\b", line)
|
||||
and "!==" not in line # 노선이 바뀌었나 견줌(다시 세움) = 출처 아님
|
||||
]
|
||||
assert not used
|
||||
|
||||
|
||||
def test_출처는_자료_줄_하나():
|
||||
data = _read("A00_Common/design/design_page_data.ts")
|
||||
assert "setPageRouteId(projectId, context.route_id)" in data
|
||||
slot = _read("A00_Common/design/design_page_route.ts")
|
||||
assert not re.search(r"^import ", slot, re.M) # 끄는 모듈 없음
|
||||
found = subprocess.run( # noqa: S603, S607 — 고정 실행 파일
|
||||
["git", "grep", "-l", "setPageRouteId(", "--", "*.ts"],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
users = set(found.stdout.split()) | {
|
||||
"A00_Common/design/design_page_route.ts"
|
||||
} # 새 파일(추적 전)
|
||||
assert sorted(users) == [
|
||||
"A00_Common/design/design_page_data.ts",
|
||||
"A00_Common/design/design_page_route.ts",
|
||||
]
|
||||
for rel in (
|
||||
"A00_Common/design/design_view_cross.ts",
|
||||
"A00_Common/design/design_view_profile.ts",
|
||||
):
|
||||
src = _read(rel)
|
||||
assert "fetchSectionContext(" not in src and "pageSectionContext(" in src
|
||||
cross = _read("B05_Profile/B05_Profile_UI_RouteEdit_Cross.ts")
|
||||
assert "routeStateKeys" not in cross # 노선 번호를 몰라 훑던 길 지움
|
||||
page = _read("A00_Common/design/design_page.ts")
|
||||
assert "forgetPageData(projectId, true)" in page
|
||||
@@ -39,7 +39,9 @@ def test_적용값이_있으면_그_값_양쪽(results):
|
||||
assert results["applied15"]["watched"] == {"chainageM": 100, "leftM": 15, "rightM": 15}
|
||||
|
||||
|
||||
def test_노선이_여럿이면_최신_노선_값_다른_프로젝트는_무시(results):
|
||||
def test_페이지_노선_값만_다른_노선_프로젝트는_무시(results):
|
||||
# 노선 = 페이지 노선 id 한 칸(386-2b · 옛 「가장 큰 노선 번호」 훑기 지움)
|
||||
assert results["otherRoute"]["watched"]["leftM"] == 15
|
||||
assert results["latestRoute"]["watched"]["leftM"] == 8.5
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user