Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1
This commit is contained in:
@@ -145,6 +145,27 @@ function relocateSideRows(panelRoot: HTMLElement): void {
|
||||
const group = target.closest<HTMLElement>("fieldset");
|
||||
if (!group) return;
|
||||
const ownerSlot = slotOf(panelRoot.dataset.extraSide === "inlet" ? "inlet" : "outlet");
|
||||
// 배관 유출 다단 = 그 단의 「흙막이」 컨테이너(54-10) — 옛 추가 기슭막이 칸 대신 그 상자를 켬 ·
|
||||
// 단 수 · 옵션은 부모(유출) 칸에 그대로
|
||||
const soil =
|
||||
ownerSlot === sideSlots.outlet && slotUsable(ownerSlot)
|
||||
? ownerSlot
|
||||
.closest(".b05-structure__spec")
|
||||
?.querySelectorAll<HTMLElement>('fieldset[data-spec-container="soil_add"]')[
|
||||
Number(panelRoot.dataset.extraIndex)
|
||||
]
|
||||
: undefined;
|
||||
if (soil) {
|
||||
moveRows(
|
||||
panelRoot,
|
||||
SIDE_ROWS.replace(".b06-structure-panel__struct--readout, ", ""),
|
||||
ownerSlot,
|
||||
);
|
||||
setGroupLabel(soil, "");
|
||||
soil.classList.add("is-active");
|
||||
soil.scrollIntoView({ block: "nearest" });
|
||||
return;
|
||||
}
|
||||
const owner = ownerSlot.closest("fieldset");
|
||||
if (owner && owner !== group) owner.after(group);
|
||||
group.hidden = false;
|
||||
|
||||
@@ -588,6 +588,7 @@ export function buildStructurePanel(
|
||||
// 이름 자체로(2026-08-30 사용자 지시 1). 어느 구조물을 만지는지 폼에서 읽힌다.
|
||||
// 다단은 칸 이름이 이미 "추가 기슭막이"라 단 번호만 붙인다 — "추가 기슭막이 (2단)".
|
||||
root.dataset.panelTitle = isExtra ? `${extraIndex + 1}단` : (title.textContent ?? "");
|
||||
root.dataset.extraIndex = isExtra ? String(extraIndex) : ""; // 흙막이 컨테이너 차례(54-10)
|
||||
const tiers =
|
||||
tierChannel === "outlet"
|
||||
? deps.extraState()
|
||||
|
||||
@@ -12,7 +12,15 @@ import type { DetailPipeInput } from "../B04_PreProcess/B04_PreProcess_Api_Fetch
|
||||
import { PIPE_MATCH_M } from "../A00_Common/design/design_selection";
|
||||
import type { SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import { applyBoxFormOptions, applyFordFormOptions } from "./B06_Section_UI_Page_Ford_Controls";
|
||||
import { revetWallSpec } from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import {
|
||||
applySpanPatch,
|
||||
EXTRA_WALL_MIN_HEIGHT_M,
|
||||
REVET_EMBED_DEPTH_M,
|
||||
revetFormOf,
|
||||
revetHeightLimit,
|
||||
revetWallSpec,
|
||||
} from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
||||
import type { createStationControls } from "./B06_Section_UI_Page_Station_Controls";
|
||||
|
||||
type StationControls = ReturnType<typeof createStationControls>;
|
||||
@@ -23,6 +31,10 @@ export interface PipeOptionsContext {
|
||||
ford: StationControls["ford"];
|
||||
box: StationControls["box"];
|
||||
revetOffset: StationControls["revetOffset"];
|
||||
/** 다단 단별 구간값 — 흙막이 컨테이너 길이 · 전 · 후(54-10). */
|
||||
structureSpan?: StationControls["structureSpan"];
|
||||
/** 흙막이 컨테이너 수 = 지형이 세운 유출 다단 수(`pipeExtraCount` · 54-10) — 없으면 다단 안 만짐. */
|
||||
soilTiers?: (chainageM: number) => number | null;
|
||||
/** 기하가 잘라 낸 실제 값을 폼에 되돌린다. */
|
||||
overrideOptions: (chainageM: number, values: Record<string, number | string>) => void;
|
||||
refreshCard: (chainageM: number) => void;
|
||||
@@ -129,6 +141,7 @@ export function applyPipeOptionsToCache(
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!culvert.hidden_pipe) applySoilTiers(ctx, owner, patch, whole);
|
||||
own(culvert.inlet, "basin_length_m", num("inlet_basin_length_m"));
|
||||
own(culvert.inlet, "basin_before_m", num("inlet_basin_before_m"));
|
||||
own(culvert.inlet, "basin_after_m", num("inlet_basin_after_m"));
|
||||
@@ -149,6 +162,89 @@ export function applyPipeOptionsToCache(
|
||||
}
|
||||
}
|
||||
|
||||
/** 흙막이 컨테이너가 마지막으로 보낸 높이 · 형태(`측점:extra{n}`) — 기하가 다시 그릴 때마다 키를 지형에
|
||||
* 맞춰 고쳐 되받으므로(syncApplied) 되받은 값과 견주면 안 바꾼 단도 다시 보냄(54-10). */
|
||||
const soilSent = new Map<string, { h: number | null; m: string | null }>();
|
||||
|
||||
/**
|
||||
* 흙막이 n 컨테이너(`soil_add_{n}_*`) → 유출 다단 n 단(`extra{n-1}`) 그림(54-10). 형태 · 높이 = 조정값
|
||||
* (m · h · 빈 칸 = 자동) · 길이 · 전 · 후 = 단별 구간값(빈 칸 = 그대로). 지형이 세운 단까지만 —
|
||||
* 넘친 컨테이너 값은 파일에만 남음. 높이 = 요청 · 서는 키는 기하 규칙 그대로(지형 맞춤 · 형태 한계) ·
|
||||
* 형태 한계 · 하한을 넘은 요청만 폼에 되돌림.
|
||||
*/
|
||||
function applySoilTiers(
|
||||
ctx: PipeOptionsContext,
|
||||
owner: CrossSection,
|
||||
patch: Record<string, number | string>,
|
||||
whole: boolean,
|
||||
): void {
|
||||
const culvert = owner.culvert!;
|
||||
const ownerForm = revetWallSpec(
|
||||
culvert.outlet,
|
||||
ctx.revetOffset.adjustFor(owner, "outlet"),
|
||||
false,
|
||||
culvert.diameter_m,
|
||||
).form;
|
||||
const value = (n: number, item: string): number | undefined => {
|
||||
const raw = patch[`soil_add_${n}_${item}`];
|
||||
return raw === undefined || raw === "" || !Number.isFinite(Number(raw))
|
||||
? undefined
|
||||
: Number(raw);
|
||||
};
|
||||
const blank = whole ? null : undefined;
|
||||
const clipped: Record<string, number> = {};
|
||||
for (let n = 1; n <= (ctx.soilTiers?.(owner.chainage_m) ?? 0); n++) {
|
||||
const role = `extra${n - 1}` as const;
|
||||
const key = `${owner.chainage_m.toFixed(2)}:${role}`;
|
||||
const form = patch[`soil_add_${n}_form`];
|
||||
const want = {
|
||||
h: value(n, "height") ?? blank,
|
||||
m: typeof form === "string" && form ? form : blank,
|
||||
};
|
||||
if (want.h != null) {
|
||||
const limit = revetHeightLimit(
|
||||
revetFormOf(want.m ?? ctx.revetOffset.adjustFor(owner, role).m, ownerForm),
|
||||
);
|
||||
const h = Math.min(
|
||||
Math.max(want.h, EXTRA_WALL_MIN_HEIGHT_M, REVET_EMBED_DEPTH_M + 0.1),
|
||||
limit,
|
||||
);
|
||||
if (Math.abs(h - want.h) > 0.05) {
|
||||
clipped[`soil_add_${n}_height`] = Number(h.toFixed(1));
|
||||
showToast(
|
||||
`흙막이 ${n} 높이는 ${h < want.h ? "형태 한계로" : "최소"} ${h.toFixed(1)}m` +
|
||||
`${h < want.h ? "까지만" : "로"} 적용됩니다.`,
|
||||
"error",
|
||||
);
|
||||
}
|
||||
want.h = Number(h.toFixed(1));
|
||||
}
|
||||
const sent = soilSent.get(key) ?? ctx.revetOffset.adjustFor(owner, role);
|
||||
if (
|
||||
(want.h !== undefined && want.h !== sent.h) ||
|
||||
(want.m !== undefined && want.m !== sent.m)
|
||||
) {
|
||||
ctx.revetOffset.update(owner.chainage_m, role, want);
|
||||
soilSent.set(key, { h: want.h ?? sent.h, m: want.m ?? sent.m });
|
||||
}
|
||||
const span = {
|
||||
lengthM: value(n, "length"),
|
||||
beforeM: value(n, "before"),
|
||||
afterM: value(n, "after"),
|
||||
};
|
||||
if (!ctx.structureSpan || Object.values(span).every((v) => v === undefined)) continue;
|
||||
const current = ctx.structureSpan.tierValuesFor(owner, role);
|
||||
const next = applySpanPatch(current, span);
|
||||
if (
|
||||
(["lengthM", "beforeM", "afterM"] as const).some((k) => Math.abs(next[k] - current[k]) > 1e-6)
|
||||
)
|
||||
ctx.structureSpan.updateTier(owner, role, span);
|
||||
}
|
||||
// 폼에 되돌림은 이 변경을 패널이 옵션에 담은 뒤 — 지금 되돌리면 옛 옵션으로 폼을 다시 세움
|
||||
if (Object.keys(clipped).length)
|
||||
queueMicrotask(() => ctx.overrideOptions(owner.chainage_m, clipped));
|
||||
}
|
||||
|
||||
/** 조정창(기슭막이 조정값)이 정본인 칸 — 초안에서 되살리지 않는다. */
|
||||
const ADJUST_OWNED = /_revet_(height_m|form)$/;
|
||||
|
||||
|
||||
@@ -78,9 +78,9 @@ export interface B06StructuresPanelDeps {
|
||||
detail?: () => SectionDetailResponse | null;
|
||||
/** 지금 그려진 벽 높이(m) — 배관 벽은 스펙이 아니라 기하가 정하므로 폼 기본
|
||||
* 높이도 이 값을 쓴다(조정창 표시값과 같다). */
|
||||
wallHeight?: (chainageM: number, role: "inlet" | "outlet") => number | null;
|
||||
wallHeight?: (chainageM: number, role: "inlet" | "outlet" | `extra${number}`) => number | null;
|
||||
/** 지금 그려진 벽 형태 — 조정값(m)이 있으면 그쪽이 정본이라 폼도 이 값을 보인다. */
|
||||
wallForm?: (chainageM: number, role: "inlet" | "outlet") => string | null;
|
||||
wallForm?: (chainageM: number, role: "inlet" | "outlet" | `extra${number}`) => string | null;
|
||||
/** 그 측점에서 유입·유출 벽이 각각 좌·우 어느 쪽인가(독립 기슭막이 칸 이름표). */
|
||||
wallSideLabels?: (chainageM: number) => { inlet: "좌" | "우"; outlet: "좌" | "우" } | null;
|
||||
/** 측점 간격(m) — 측점번호+잔여거리 환산용. */
|
||||
|
||||
@@ -140,8 +140,8 @@ export function withSpecDefaults(
|
||||
detail: SectionDetailResponse | null,
|
||||
chainageM: number,
|
||||
types: StructureType[],
|
||||
wallHeight?: (chainageM: number, role: "inlet" | "outlet") => number | null,
|
||||
wallForm?: (chainageM: number, role: "inlet" | "outlet") => string | null,
|
||||
wallHeight?: (chainageM: number, role: "inlet" | "outlet" | `extra${number}`) => number | null,
|
||||
wallForm?: (chainageM: number, role: "inlet" | "outlet" | `extra${number}`) => string | null,
|
||||
): Merged | undefined {
|
||||
const owner = detail?.cross_sections.find(
|
||||
(section) => Math.abs(section.chainage_m - chainageM) < PIPE_MATCH_M,
|
||||
@@ -160,5 +160,16 @@ export function withSpecDefaults(
|
||||
const drawnHeight = wallHeight?.(chainageM, prefix);
|
||||
if (drawnHeight != null) merged[`${prefix}_revet_height_m`] = Number(drawnHeight.toFixed(1));
|
||||
}
|
||||
// 다단 n 단 조작값 → 흙막이 n 컨테이너(54-10) — 앞 컨테이너가 비었으면 자리만 둠(차례 지킴)
|
||||
for (let i = 0; i < 9 && !culvert.hidden_pipe; i++) {
|
||||
const form = wallForm?.(chainageM, `extra${i}`);
|
||||
const height = wallHeight?.(chainageM, `extra${i}`);
|
||||
if (!form && height == null) continue;
|
||||
for (let n = 1; n <= i; n++)
|
||||
if (!Object.keys(merged).some((key) => key.startsWith(`soil_add_${n}_`)))
|
||||
merged[`soil_add_${n}_structure`] = "";
|
||||
if (form) merged[`soil_add_${i + 1}_form`] = form;
|
||||
if (height != null) merged[`soil_add_${i + 1}_height`] = Number(height.toFixed(1));
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user