Files
Aislo/B06_Section/B06_Section_UI_Cross_View_Box.ts
T
eomsangdonandClaude Opus 5.5 42d0d725a5 feat(B06): 횡단도 안 조정창 = 제목 · 단추만 · 그림 끌기 두 축 함께 + 놓을 때 패널 걸음 맞춤 · 미리 보기 = 맞춘 자리(108-1 · 108-2)
- 조정창(오버레이): 읽기 줄 · 줄 이름 · 머리줄 집수정 ↺ 지움 · 끄는 중 미리 보기 글만 · 좌측 사본 그대로
- 끌기: 벽 · 집수정 · 세월교 = 수평 + 1:1.2 사면 · 다단 · 독립 벽 = 수평 + 수직 · BOX = 길이 + 표고
- 한계(±10 m · 노견 안쪽 · 노면 위 · 다단 선반 · BOX 길이 0)면 가장 가까운 허용 자리 · 반영 한 번
- 시험 test_108_drag_box_hit(끌기 · 걸음 · 미리 보기 = placePipeWall 결과 · 한계 · BOX 판정면)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
2026-10-08 16:22:21 +09:00

75 lines
3.2 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 }),
move: (role, screenDeltaM, riseDeltaM) => {
const from = box.adjustFor(chainageM)[role];
box.update(chainageM, role, {
lengthM: Math.round((from.lengthM + screenDeltaM * (role === "left" ? 1 : -1)) * 10) / 10,
riseM: Math.round((from.riseM + riseDeltaM) * 10) / 10,
});
},
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();
};
}