세월교와 같은 문제가 BOX암거에도 있었다 — 좌측 「구조물 배치」의 본체 규격·날개벽을 바꿔도 도면이 그대로였고, 폼 기본값이 스펙에서 오지 않았으며, 구체를 골라도 좌측 칸이 서지 않았다. `applyPipeOptionsToCache`가 `section.box`를 모르고 즉시 return하던 것이 뿌리다(세월교와 동일). - `applyBoxFormOptions()` — 폼 옵션(`body_*`·`wing_*`)을 제어기로 보낸다. `BoxControl`에 `setBody`·`setWing`을 더했다: 캐시(`section.box`)를 먼저 고치고 (`span_m`은 백엔드 `_box_set`과 같은 식으로 다시 잡는다) 정본 예약 + 카드 갱신. - 날개벽 옵션 조립·해석을 `wingOptions()`·`wingPatchFrom()`으로 묶어 세월교와 나눠 쓴다 — 세월교 쪽 중복 코드가 사라졌다. - `withBoxSpec()` — 폼 기본값을 `section.box`(내공 폭·높이, 날개벽)에서 읽는다. - 좌·우 끝을 고르면 폼의 **날개벽(유입)/(유출)** 칸이 선다. 좌·우 ↔ 유입/유출 매핑은 기하가 날개벽을 고르는 기준(`uphill_side`)과 같다. 조정창은 결국 지우고 폼 이름(유입/유출)만 쓰기로 한 사용자 확정(2026-08-30)을 따랐다. 구체 길이·표고는 폼에 대응 칸이 없어 조정창 축으로 남는다(본체 높이는 규격이 이미 정한다 — 2026-08-30 사용자). 자체검증(공용 브라우저 5174 + 워크트리 백엔드 8001, 측점 10+0.9 BOX암거): - tsc --noEmit 통과. - 구체 선택 시 좌측 `날개벽(유입) (BOX암거)` 칸 강조(수정 전 강조 없음). - 본체 규격 2.0×2.0 → 3.0×3.0 → 도면 라벨 `BOX 3.0×3.0`, 다시 2.0×2.0 → `BOX 2.0×2.0`. 한 번 고른 상태에서 연속 조작도 그대로 먹는다. 수정 전에는 라벨이 5.34m 구체와 함께 전혀 움직이지 않았다. - 회귀: 세월교 날개 길이 2→4→2에서 조정창 표시가 4.0m→2.0m로 따라온다 (`wingOptions`/`wingPatchFrom` 공용화 뒤에도 동일). - 조작값은 원래대로(BOX 2.0×2.0 · 세월교 날개 길이 2m) 되돌려 놓았다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
167 lines
6.2 KiB
TypeScript
167 lines
6.2 KiB
TypeScript
/* =============================================================================
|
|
* B06_Section_UI_Cross_View_Bodies.ts
|
|
* 횡단 카드 ↔ **구체 구조물(세월교·BOX암거)** 배선 한 벌 — 선택 토글·강조 setter·
|
|
* 조정창을 묶어 카드 렌더러(`_UI_Cross_View.ts`)에서 700줄 제한으로 분리했다
|
|
* (2026-08-25). 기하나 그리기는 하지 않는다.
|
|
* ========================================================================== */
|
|
|
|
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
|
import type {
|
|
FordAdjust,
|
|
FordHighlightSetter,
|
|
FordLayout,
|
|
FordWallRole,
|
|
} from "./B06_Section_UI_Cross_Ford";
|
|
import { buildFordPanel } from "./B06_Section_UI_Cross_Ford_Panel";
|
|
import type { FordControl, FordPanelHandle } from "./B06_Section_UI_Cross_Ford_Panel";
|
|
import { fordCardState, fordPanelDeps } from "./B06_Section_UI_Cross_View_Ford";
|
|
import type {
|
|
BoxAdjust,
|
|
BoxHighlightSetter,
|
|
BoxLayout,
|
|
BoxSideRole,
|
|
} from "./B06_Section_UI_Cross_Box";
|
|
import { buildBoxPanel } from "./B06_Section_UI_Cross_Box_Panel";
|
|
import type { BoxControl, BoxPanelHandle } from "./B06_Section_UI_Cross_Box_Panel";
|
|
import { boxPanelDeps, structureToggle } from "./B06_Section_UI_Cross_View_Box";
|
|
import { adoptAdjustPanel, releaseAdjustPanel } from "./B06_Section_UI_Adjust_Dock";
|
|
|
|
export interface BodyWiringDeps {
|
|
section: CrossSection;
|
|
ford?: FordControl;
|
|
box?: BoxControl;
|
|
/** 카드가 지금 선택 상태인가 — 구조물을 고르면 카드도 함께 선택된다. */
|
|
isCardSelected: () => boolean;
|
|
/** 면적 강조 해제(구조물 선택과 배타). */
|
|
clearArea: () => void;
|
|
selectCard: () => void;
|
|
}
|
|
|
|
export interface BodyWiring {
|
|
fordAdjust: () => FordAdjust | undefined;
|
|
boxAdjust: () => BoxAdjust | undefined;
|
|
toggleFord: (role: FordWallRole) => void;
|
|
toggleBox: (role: BoxSideRole) => void;
|
|
/** 오버레이가 만들어 준 강조 setter를 등록하고 현재 선택을 다시 칠한다. */
|
|
attachFord: (setter: FordHighlightSetter, layout: FordLayout) => void;
|
|
attachBox: (setter: BoxHighlightSetter, layout: BoxLayout) => void;
|
|
/** 카드 선택이 풀리거나 면적이 켜지면 구조물 선택도 푼다. */
|
|
clearSelection: () => void;
|
|
panels: Array<FordPanelHandle | BoxPanelHandle>;
|
|
}
|
|
|
|
export function createBodyWiring(deps: BodyWiringDeps): BodyWiring {
|
|
const { section, ford, box } = deps;
|
|
const chainage = section.chainage_m;
|
|
let fordRole = section.ford ? (ford?.selectedFor(chainage) ?? null) : null;
|
|
let boxRole = section.box ? (box?.selectedFor(chainage) ?? null) : null;
|
|
let setFordActive: FordHighlightSetter = () => {};
|
|
let setBoxActive: BoxHighlightSetter = () => {};
|
|
let fordSlabLengthM = 0;
|
|
let fordHeights = new Map<FordWallRole, number>();
|
|
let boxLayout: BoxLayout | null = null;
|
|
|
|
const fordDeps =
|
|
section.ford && ford
|
|
? fordPanelDeps({
|
|
section,
|
|
ford,
|
|
heightFor: (role) => fordHeights.get(role) ?? 0,
|
|
slabLengthM: () => fordSlabLengthM,
|
|
close: () => {
|
|
if (fordRole) toggleFord(fordRole);
|
|
},
|
|
})
|
|
: null;
|
|
const boxDeps =
|
|
section.box && box
|
|
? boxPanelDeps({
|
|
chainageM: chainage,
|
|
box,
|
|
layout: () => boxLayout,
|
|
inletOnLeft: (section.uphill_side ?? "left") === "left",
|
|
close: () => {
|
|
if (boxRole) toggleBox(boxRole);
|
|
},
|
|
})
|
|
: null;
|
|
const fordPanel = fordDeps ? buildFordPanel(fordDeps) : null;
|
|
const boxPanel = boxDeps ? buildBoxPanel(boxDeps) : null;
|
|
// 좌측 [횡단 조정] 사본 — 배수관 조정창과 같은 규칙(2026-08-29 일원화): 같은 deps로
|
|
// 하나 더 만들어 고른 동안만 좌측 슬롯에 세운다.
|
|
const fordDock = fordDeps ? buildFordPanel(fordDeps, { dock: true }) : null;
|
|
const boxDock = boxDeps ? buildBoxPanel(boxDeps, { dock: true }) : null;
|
|
const syncDock = (
|
|
dock: FordPanelHandle | BoxPanelHandle | null,
|
|
role: FordWallRole | BoxSideRole | null,
|
|
): void => {
|
|
if (!dock) return;
|
|
(dock as { show: (role: FordWallRole | BoxSideRole | null) => void }).show(role);
|
|
if (role) adoptAdjustPanel(dock.root);
|
|
else releaseAdjustPanel(dock.root);
|
|
};
|
|
|
|
const toggleFord = structureToggle<FordWallRole>({
|
|
current: () => fordRole,
|
|
setCurrent: (value) => (fordRole = value),
|
|
setActive: (value) => setFordActive(value),
|
|
showPanel: (visible) => {
|
|
fordPanel?.show(visible ? fordRole : null);
|
|
syncDock(fordDock, visible ? fordRole : null);
|
|
},
|
|
persist: (value) => ford?.select(chainage, value),
|
|
isCardSelected: deps.isCardSelected,
|
|
clearArea: deps.clearArea,
|
|
selectCard: deps.selectCard,
|
|
});
|
|
const toggleBox = structureToggle<BoxSideRole>({
|
|
current: () => boxRole,
|
|
setCurrent: (value) => (boxRole = value),
|
|
setActive: (value) => setBoxActive(value),
|
|
showPanel: (visible) => {
|
|
boxPanel?.show(visible ? boxRole : null);
|
|
syncDock(boxDock, visible ? boxRole : null);
|
|
},
|
|
persist: (value) => box?.select(chainage, value),
|
|
isCardSelected: deps.isCardSelected,
|
|
clearArea: deps.clearArea,
|
|
selectCard: deps.selectCard,
|
|
});
|
|
|
|
fordPanel?.show(fordRole);
|
|
boxPanel?.show(boxRole);
|
|
syncDock(fordDock, fordRole);
|
|
syncDock(boxDock, boxRole);
|
|
|
|
return {
|
|
fordAdjust: () => ford?.adjustFor(chainage),
|
|
boxAdjust: () => box?.adjustFor(chainage),
|
|
toggleFord,
|
|
toggleBox,
|
|
attachFord: (setter, layout) => {
|
|
setFordActive = setter;
|
|
const state = fordCardState(layout);
|
|
fordSlabLengthM = state.slabLengthM;
|
|
fordHeights = state.heights;
|
|
if (fordRole) setter(fordRole);
|
|
// 창은 그리기 전에 만들어져 값이 비어 있다 — 계산 결과가 들어온 뒤 다시 렌더한다.
|
|
fordPanel?.show(fordRole);
|
|
syncDock(fordDock, fordRole);
|
|
},
|
|
attachBox: (setter, layout) => {
|
|
setBoxActive = setter;
|
|
boxLayout = layout;
|
|
if (boxRole) setter(boxRole);
|
|
boxPanel?.show(boxRole);
|
|
syncDock(boxDock, boxRole);
|
|
},
|
|
clearSelection: () => {
|
|
if (fordRole !== null) toggleFord(fordRole);
|
|
if (boxRole !== null) toggleBox(boxRole);
|
|
},
|
|
panels: [fordPanel, boxPanel].filter(
|
|
(panel): panel is FordPanelHandle | BoxPanelHandle => !!panel,
|
|
),
|
|
};
|
|
}
|