Files
Aislo/B06_Section/B06_Section_UI_Page_Link_Session.ts
T

170 lines
6.9 KiB
TypeScript

/* =============================================================================
* B06_Section_UI_Page_Link_Session.ts
* 연동 기슭막이 거들기 — 측점 제어기(`_UI_Page_Station_Controls.ts`)에서 700줄 제한으로 분리(2026-08-25).
* 저장 묶음 접기(세션 칸 → 측점별 patch 재료) · 연동 풀 때 굳히기 · 좌측 관 보호공 [연동] · [경사] 다리.
* 조작값 칸 창구 = `B06_Section_Session_Cells`(메모리 맵 없음 · 472-5 5-D).
* ========================================================================== */
import type { CrossSection, StoredWallSpan } from "./B06_Section_Api_Fetch";
import type { RevetKey } from "./B06_Section_UI_Cross_Culvert";
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
import {
computeCardCulvert,
culvertLinkFor,
culvertOwnerFor,
culvertReach,
culvertWallsStandAt,
ownerAppliedAdjusts,
type ExtraWallControl,
type InletStructureControl,
type RevetLinkControl,
type RevetOffsetControl,
type SpanValues,
} from "./B06_Section_UI_Cross_Culvert_Wire";
import { culvertCardState } from "./B06_Section_UI_Cross_View_Structure";
import type { PipeRevetChoice } from "../A00_Common/design/design_structure_pipe_calc";
/* ── 확정 · 임시저장 payload — 세션 키(누가거리 · `:역할` · `b` 앞머리)를 측점별로 되접음 ── */
type Each<V> = { forEach(fn: (value: V, key: string) => void): void };
/** 측점별 기슭막이 4축 조작값(역할 → 값 · null = 되돌림). */
export function foldRevetAdjusts(
shifts: Each<WallAdjust | null>,
): Map<number, Record<string, WallAdjust | null>> {
const result = new Map<number, Record<string, WallAdjust | null>>();
shifts.forEach((adjust, key) => {
const [chainage, role] = key.split(":");
const value = Number(chainage);
if (!Number.isFinite(value) || !role) return;
const bucket = result.get(value) ?? {};
bucket[role] = adjust;
result.set(value, bucket);
});
return result;
}
/** 측점별 연동 해제 · 종단경사 반영 플래그. */
export function foldLinkFlags(
detachedFlags: Each<boolean>,
gradeFlags: Each<boolean>,
): Map<number, { detached?: boolean; followGrade?: boolean }> {
const result = new Map<number, { detached?: boolean; followGrade?: boolean }>();
const put = (key: string, patch: { detached?: boolean; followGrade?: boolean }): void => {
const value = Number(key);
if (!Number.isFinite(value)) return;
result.set(value, { ...(result.get(value) ?? {}), ...patch });
};
detachedFlags.forEach((detached, key) => put(key, { detached }));
gradeFlags.forEach((followGrade, key) => put(key, { followGrade }));
return result;
}
/** 측점별 다단 단 수 — 세션에 있는 쪽만 · 없는 쪽은 바탕(캐시 설계) 값 그대로(445 H3 · `mergeCrossPatches`). */
export function foldExtraCounts(
counts: Each<number>,
): Map<number, Partial<{ outlet: number; basin: number }>> {
const result = new Map<number, Partial<{ outlet: number; basin: number }>>();
counts.forEach((count, key) => {
const basin = key.startsWith("b");
const value = Number(basin ? key.slice(1) : key);
if (!Number.isFinite(value)) return;
const bucket = result.get(value) ?? {};
bucket[basin ? "basin" : "outlet"] = count;
result.set(value, bucket);
});
return result;
}
/** 측점별 다단 단별 구간값. */
export function foldExtraSpans(
spans: Each<SpanValues>,
): Map<number, Record<string, StoredWallSpan>> {
const result = new Map<number, Record<string, StoredWallSpan>>();
spans.forEach((value, key) => {
const [chainage, wall] = key.split(":");
const chainageM = Number(chainage);
if (!Number.isFinite(chainageM) || !wall) return;
const bucket = result.get(chainageM) ?? {};
bucket[wall] = { length_m: value.lengthM, before_m: value.beforeM, after_m: value.afterM };
result.set(chainageM, bucket);
});
return result;
}
/**
* 연동을 푸는 순간(카드 [연동] · 좌측 [연동] 둘 다 · 139-2) — 그 카드가 그리던 벽마다 4축을 이
* 측점 값으로 굳힘: 위치(x · d) = 소유 측점 적용값(물려받던 값) · 높이 · 형태 = 그 카드가 그린 값.
* d 가 차서 이 측점 지형으로 자동 자리 · 관 정수 맞춤을 다시 타지 않음(튐 없음). 연동 중에 부름.
*/
export function freezeLinkedWalls(
section: CrossSection,
sections: readonly CrossSection[],
c: {
revetOffset: RevetOffsetControl;
inletStructure?: InletStructureControl;
extraWalls?: ExtraWallControl;
revetLink: RevetLinkControl;
},
): void {
const link = section.culvert ? undefined : culvertLinkFor(section, sections, () => null);
const owner =
link && ownerAppliedAdjusts(link.source, c.revetOffset, c.inletStructure, c.extraWalls);
if (!link || !owner) return;
const card = computeCardCulvert(
section,
section.samples,
null,
c.revetOffset,
c.inletStructure,
c.extraWalls,
link,
c.revetLink,
);
const drawn = card ? culvertCardState(card).wallSpecs : new Map();
const roles: Array<[RevetKey, WallAdjust]> = [
["inlet", owner.inlet],
["outlet", owner.outlet],
...owner.extras.map((a, i): [RevetKey, WallAdjust] => [`extra${i}` as RevetKey, a]),
...owner.basinExtras.map((a, i): [RevetKey, WallAdjust] => [`bextra${i}` as RevetKey, a]),
];
for (const [role, applied] of roles) {
const wall = drawn.get(role);
if (!wall) continue;
const round3 = (v: number): number => Math.round(v * 1000) / 1000;
c.revetOffset.update(section.chainage_m, role, {
x: round3(applied.x),
d: applied.d == null ? null : round3(applied.d),
h: wall.height,
m: wall.form,
});
}
}
/**
* 좌측 관 보호공 [연동] · [경사](54-30) — 옛 연동 제어 그대로 읽고 씀. 연동 = 벽이 서는 옆 측점이
* 있을 때만 · 켬 = 그 측점이 모두 연동 · 끄면 모두 따로. 경사 = 보호공 구간이 있을 때만.
* 풀 때 굳히기 · 다시 걸 때 지우기는 `setLinked` 가 함(139-2 · 카드 단추와 한 길).
*/
export function pipeRevetChoiceBridge(revetLink: RevetLinkControl) {
const linkCards = (owner: CrossSection, sections: readonly CrossSection[]) =>
sections.filter((s) => culvertOwnerFor(s, sections) === owner && culvertWallsStandAt(owner, s));
return {
read: (owner: CrossSection, sections: readonly CrossSection[]): PipeRevetChoice => {
const cards = linkCards(owner, sections);
return {
linked: cards.length ? cards.every((s) => revetLink.linkedFor(s)) : null,
grade: culvertReach(owner) ? revetLink.followGradeFor(owner) : null,
};
},
write: (
owner: CrossSection,
sections: readonly CrossSection[],
kind: "link" | "grade",
on: boolean,
): void => {
if (kind === "grade") revetLink.setFollowGrade(owner, on);
else linkCards(owner, sections).forEach((s) => revetLink.setLinked(s.chainage_m, on));
},
};
}