세월교와 같은 문제가 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>
68 lines
2.9 KiB
TypeScript
68 lines
2.9 KiB
TypeScript
/* =============================================================================
|
|
* B06_Section_UI_Cross_View_Box.ts
|
|
* 횡단 카드 ↔ **BOX암거 조정창** 배선과 구조물 선택 토글 — 카드 렌더러
|
|
* (`_UI_Cross_View.ts`)에서 700줄 제한으로 분리했다(2026-08-25).
|
|
* 기하나 그리기는 하지 않는다.
|
|
* ========================================================================== */
|
|
|
|
import type { BoxLayout } from "./B06_Section_UI_Cross_Box";
|
|
import type { BoxControl, BoxPanelDeps } from "./B06_Section_UI_Cross_Box_Panel";
|
|
|
|
export interface BoxPanelContext {
|
|
chainageM: number;
|
|
box: BoxControl;
|
|
/** 마지막 계산 결과 — 창이 길이·물매를 읽는다. */
|
|
layout: () => BoxLayout | null;
|
|
/** 유입(상류)이 화면 좌측인가 — 좌·우를 폼의 유입/유출 칸으로 옮길 때 쓴다
|
|
* (기하가 날개벽을 고르는 기준과 같다, 2026-08-30). */
|
|
inletOnLeft: boolean;
|
|
close: () => void;
|
|
}
|
|
|
|
export function boxPanelDeps(context: BoxPanelContext): BoxPanelDeps {
|
|
const { chainageM, box } = context;
|
|
return {
|
|
adjustFor: (role) => box.adjustFor(chainageM)[role],
|
|
// 화면 좌(◀) = offset 증가. 좌측 끝은 그대로, 우측 끝은 부호를 뒤집어야 바깥이 된다.
|
|
nudgeLength: (role, screenDeltaM) =>
|
|
box.update(chainageM, role, {
|
|
lengthM: box.adjustFor(chainageM)[role].lengthM + screenDeltaM * (role === "left" ? 1 : -1),
|
|
}),
|
|
nudgeRise: (role, deltaM) =>
|
|
box.update(chainageM, role, { riseM: box.adjustFor(chainageM)[role].riseM + deltaM }),
|
|
reset: (role) => box.reset(chainageM, role),
|
|
wingRoleFor: (role) => ((role === "left") === context.inletOnLeft ? "inlet" : "outlet"),
|
|
bodyLengthM: () => context.layout()?.bodyLengthM ?? 0,
|
|
slopeRatio: () => context.layout()?.slopeRatio ?? null,
|
|
close: context.close,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 구조물 선택 토글 한 벌 — 세월교 측벽·BOX암거 끝이 같은 규칙을 쓴다(2026-08-21 확정
|
|
* 규칙: 구조물 선택과 면적 강조는 배타, 고르면 그 측점 카드도 함께 선택된다).
|
|
*/
|
|
export function structureToggle<T>(deps: {
|
|
current: () => T | null;
|
|
setCurrent: (value: T | null) => void;
|
|
setActive: (value: T | null) => void;
|
|
showPanel: (visible: boolean) => void;
|
|
persist: (value: T | null) => void;
|
|
isCardSelected: () => boolean;
|
|
clearArea: () => void;
|
|
selectCard: () => void;
|
|
}): (value: T) => void {
|
|
return (value: T): void => {
|
|
const wasSelected = deps.isCardSelected();
|
|
const next = deps.current() === value ? null : value;
|
|
deps.setCurrent(next);
|
|
deps.setActive(next);
|
|
deps.showPanel(next !== null);
|
|
deps.persist(next);
|
|
if (next === null) return;
|
|
deps.clearArea();
|
|
// 미선택 카드는 카드 선택만 부른다 — 그 경로가 부모의 면적 강조를 이미 비운다.
|
|
if (!wasSelected) deps.selectCard();
|
|
};
|
|
}
|