Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1
design_page.ts import 충돌 = 424 의 좌우 이동 import 지움 + 428 pipelineDetail 살림 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6BNBFXFndbqYxu6sZuknK
This commit is contained in:
@@ -57,6 +57,8 @@ import { useStationKeys } from "./B06_Section_Station_Keys";
|
||||
import { hasStaleDesigns, staleKey } from "./B06_Section_UI_Section_Common";
|
||||
import { applyStructureAreaRows, structureAreaRows } from "./B06_Section_Structure_Layouts";
|
||||
import { withPipeOptions } from "./B06_Section_Section_Adjusts";
|
||||
import { extendUnclosed } from "../B05_Profile/logic/B05_Profile_Sections_Ground";
|
||||
import { designTerrainGridNow } from "../B05_Profile/B05_Profile_Api_TerrainGrid";
|
||||
|
||||
/** 계획선 편집 델타 — B05 `AlignmentEdits`와 저장분 `profile_alignment.edits`가 같은 모양이다. */
|
||||
export interface CrossRefreshEdits {
|
||||
@@ -367,6 +369,8 @@ function refreshLocally(input: CrossRefreshInput): number[] | null {
|
||||
return Math.max(session ?? (typeof stored === "number" ? stored : 0), 0);
|
||||
};
|
||||
|
||||
// 미교차 넓힘 격자 — 받기 전이면 null(받은 뒤 다시 셈이 넓힘 · 지문에 실음)
|
||||
const grid = designTerrainGridNow(projectId)?.grid ?? null;
|
||||
const updated: number[] = [];
|
||||
for (const section of detail.cross_sections) {
|
||||
const previous = section.design;
|
||||
@@ -432,7 +436,7 @@ function refreshLocally(input: CrossRefreshInput): number[] | null {
|
||||
// 국부 연산(346-2) — 입력 지문(지반 표본 · 계획고 · 횡단 입력 · 구조물 맥락 · 표준값)이 같고
|
||||
// 설계가 그때 낸 그 객체면 앞 결과 그대로. 계획고 ▲ 한 번에 다시 세는 측점은 곡선 둘레 몇 곳뿐.
|
||||
const samples = section.samples ?? [];
|
||||
const fingerprint = `${standardKey}|${elevation}|${samples.length}|${JSON.stringify({ ...options, standard: null })}`;
|
||||
const fingerprint = `${standardKey}|${elevation}|${samples.length}|${grid ? "grid" : ""}|${JSON.stringify({ ...options, standard: null })}`;
|
||||
const memo = memos.get(section);
|
||||
if (
|
||||
memo?.samples === section.samples &&
|
||||
@@ -444,6 +448,21 @@ function refreshLocally(input: CrossRefreshInput): number[] | null {
|
||||
let next;
|
||||
try {
|
||||
next = computeCrossDesign(samples, elevation, options);
|
||||
// 미교차 넓힘 — 서버 [저장] `extend_unclosed_sections` 짝 · 넓힌 표본을 측점에 남김(428 · 묶음 cross 와 같음)
|
||||
const wide =
|
||||
grid && next.slope_unclosed
|
||||
? extendUnclosed(
|
||||
samples,
|
||||
[section.center_x, section.center_y],
|
||||
section.frame.left_xy,
|
||||
(more) => computeCrossDesign(more as typeof samples, elevation, options),
|
||||
grid,
|
||||
)
|
||||
: null;
|
||||
if (wide && (wide.info.left_m || wide.info.right_m)) {
|
||||
section.samples = wide.samples as typeof samples;
|
||||
next = computeCrossDesign(section.samples, elevation, options);
|
||||
}
|
||||
} catch {
|
||||
continue; // 샘플 부족·값 손상 측점은 건너뛴다(서버 엔진과 같은 태도).
|
||||
}
|
||||
|
||||
@@ -10,11 +10,15 @@
|
||||
* 대신 **왜 값이 없는지**가 화면에 남는다.
|
||||
* ⚠ 지표 샘플링 조건이 저장에 없으면 **단추를 잠그고 사유를 보인다** — 조건을 지어내면
|
||||
* 그 측점만 다른 지표에서 뽑혀 옆 측점과 지반고가 어긋난다.
|
||||
* [저장] 뒤 다시 봄(428) — 관 · 구조물 초안이 정본으로 나가 비면 저장이 끝난 뒤 한 번 더 묻고 줄을 갈음
|
||||
* (서버가 관 PUT 끝에 측점을 다시 세움 · 옛 알림이 남지 않게).
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
import { createButton, showToast } from "@ui/ui_template_elements";
|
||||
import { attachHoverInfo } from "@ui/ui_template_hover_info";
|
||||
import { useDesignStore } from "../A00_Common/design/design_store";
|
||||
import { savingDesign } from "../A00_Common/design/design_save";
|
||||
|
||||
interface MissingStation {
|
||||
chainage_m: number;
|
||||
@@ -49,12 +53,14 @@ export async function mountMissingStationNotice(
|
||||
projectId: string,
|
||||
onCreated: () => void | Promise<void>,
|
||||
): Promise<void> {
|
||||
watchSaves(host, projectId, onCreated);
|
||||
let data: MissingResponse;
|
||||
try {
|
||||
data = await call(projectId, "GET");
|
||||
} catch {
|
||||
return; // 점검이 안 되는 것으로 화면을 막지 않는다 — 이 줄은 덤이다.
|
||||
}
|
||||
host.querySelector(":scope > .b06-missing-stations")?.remove(); // 다시 봄 = 줄 하나
|
||||
const missing = data.missing ?? [];
|
||||
if (!missing.length) return;
|
||||
|
||||
@@ -99,3 +105,27 @@ export async function mountMissingStationNotice(
|
||||
}
|
||||
host.prepend(box);
|
||||
}
|
||||
|
||||
const watched = new WeakSet<HTMLElement>();
|
||||
|
||||
/** 관 · 구조물 초안이 비면(저장 · 되돌림) 저장이 끝난 뒤 다시 물음 — 자리(`host`)가 떠나면 끊음. */
|
||||
function watchSaves(
|
||||
host: HTMLElement,
|
||||
projectId: string,
|
||||
onCreated: () => void | Promise<void>,
|
||||
): void {
|
||||
if (watched.has(host)) return;
|
||||
watched.add(host);
|
||||
const store = useDesignStore(projectId, null);
|
||||
let timer = 0;
|
||||
// ponytail: 저장 끝 신호가 없어 0.3 s 마다 봄 — 저장 끝 알림 창구가 생기면 그리로
|
||||
const recheck = (): void => {
|
||||
window.clearTimeout(timer);
|
||||
if (!host.isConnected) return stops.forEach((stop) => stop());
|
||||
if (savingDesign(projectId)) timer = window.setTimeout(recheck, 300);
|
||||
else void mountMissingStationNotice(host, projectId, onCreated);
|
||||
};
|
||||
const stops = (["pipes", "structures"] as const).map((key) =>
|
||||
store.subscribe(key, (value) => value === null && recheck()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
* · 같은 측점(열쇠 · 없으면 같은 자리 같은 종류)은 앞 횡단을 이어 받음 — 설계 · 구조물 제원 · 이름 그대로 ·
|
||||
* 자리(누가거리 · 좌표 · 방위 · 확폭)와 지반 표본(채워 왔으면)만 새것.
|
||||
* · 새 측점 = 지반 표본 + 기본 설계(계획고 0 → 낡음 → 브라우저 다시 셈이 곧 채움).
|
||||
* · 상단측 = 새 지반 표본으로 다시(단면 유형 기본값 재료 · 서버 횡단 파일 `uphill_side` 짝 · 428).
|
||||
* · 0.5 m 넘게 옮긴 관 · 구조물 측점 = 기본 설계 + 사용자가 만진 칸만 — 서버가 그 행을 지우고 기본 설계에
|
||||
* [저장] 수정분을 얹는 것과 같음(`sync_cross_section_rows` · `compute_default_designs` · 428).
|
||||
* · 빠진 측점 = 목록에서 뺌(세션 고침은 열쇠로 남음 · 노선이 바뀐 때만 「노선 밖 고침」 · 부르는 쪽).
|
||||
* · 옛 상세는 그대로 둠 — 새 객체를 돌려줌(쥔 쪽이 갈아끼움).
|
||||
*
|
||||
@@ -15,6 +18,7 @@
|
||||
|
||||
import type { PipelineStation } from "../../A00_Common/design/logic/design_pipeline_types";
|
||||
import type { CrossSection, SectionDetailResponse, SectionStation } from "../B06_Section_Api_Fetch";
|
||||
import { uphillSide } from "../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
|
||||
|
||||
interface Sample {
|
||||
offset_m: number;
|
||||
@@ -51,15 +55,36 @@ export function formatStation(chainageM: number): string {
|
||||
export const stationIdOf = (chainageM: number): string =>
|
||||
`station_${String(Math.round(chainageM * 1000)).padStart(12, "0")}`;
|
||||
|
||||
/** 새 측점의 기본 설계 — 계획고 0 이라 늘 낡음 → 다시 셈이 세션 선택 · 기본값으로 채움. */
|
||||
const freshDesign = () =>
|
||||
/** 새 측점의 기본 설계 — 계획고 0 이라 늘 낡음 → 다시 셈이 세션 선택 · 기본값으로 채움 · 단면 유형 = 상단측 깎기. */
|
||||
const freshDesign = (uphill: CrossSection["uphill_side"]) =>
|
||||
({
|
||||
design_elevation_m: 0,
|
||||
ground_type: "ripping_rock",
|
||||
section_mode: "left_cut",
|
||||
section_mode: `${uphill ?? "left"}_cut`,
|
||||
ditch_type: "standard",
|
||||
}) as unknown as NonNullable<CrossSection["design"]>;
|
||||
|
||||
/** 서버가 행을 지우고 새로 세우는 옮김 폭(m) — `sync_cross_section_rows` 의 0.5 m 짝. */
|
||||
const FACILITY_RESEED_M = 0.5;
|
||||
/** 다시 셈 입력 중 기본 설계가 정하는 칸 — 옮긴 시설 측점은 버림(세션 고침은 열쇠째 옮겨 다시 얹힘). */
|
||||
const AUTO_INPUT_KEYS = [
|
||||
"ditch_side",
|
||||
"rock_boundary_offset_m",
|
||||
"two_stage_slope",
|
||||
"ditch_choice",
|
||||
"ditch_enabled",
|
||||
];
|
||||
|
||||
/** 옮긴 시설 측점 설계 — 기본 설계 위에 앞 설계의 나머지(사용자가 만진 칸 · 결과 칸은 다시 셈이 갈음). */
|
||||
function reseeded(
|
||||
prior: CrossSection["design"],
|
||||
uphill: CrossSection["uphill_side"],
|
||||
): NonNullable<CrossSection["design"]> {
|
||||
const design = { ...prior, ...freshDesign(uphill) } as Record<string, unknown>;
|
||||
for (const key of AUTO_INPUT_KEYS) delete design[key];
|
||||
return design as unknown as NonNullable<CrossSection["design"]>;
|
||||
}
|
||||
|
||||
function kindOf(station: PipelineStation): SectionStation["kind"] {
|
||||
if (station.kind === "end") return station.key.endsWith("EP") ? "ep" : "bp";
|
||||
if (station.kind === "curve") return "curve";
|
||||
@@ -169,7 +194,18 @@ export function swapCrossSections(
|
||||
};
|
||||
longStations.push({ ...place, ...(prior?.structure ? { structure: prior.structure } : {}) });
|
||||
const samples = station.ground ? groundSamples(station) : (prior?.samples ?? []);
|
||||
return prior ? { ...prior, ...place, samples } : { ...place, samples, design: freshDesign() };
|
||||
const uphill = station.ground ? uphillSide(station) : (prior?.uphill_side ?? null);
|
||||
if (!prior) return { ...place, uphill_side: uphill, samples, design: freshDesign(uphill) };
|
||||
const reseed =
|
||||
(station.kind === "pipe" || station.kind === "structure") &&
|
||||
Math.abs(prior.chainage_m - station.chainageM) > FACILITY_RESEED_M;
|
||||
return {
|
||||
...prior,
|
||||
...place,
|
||||
uphill_side: uphill,
|
||||
samples,
|
||||
...(reseed ? { design: reseeded(prior.design, uphill) } : {}),
|
||||
};
|
||||
});
|
||||
const removed = detail.cross_sections
|
||||
.map((section, i) => (used.has(section) ? null : oldKeys[i]))
|
||||
|
||||
Reference in New Issue
Block a user