Files
Aislo/B06_Section/B06_Section_UI_Cross_Ford_Panel.ts
T
eomsangdonandClaude Opus 5.5 7f04bf961c feat(B06): 관 · 옹벽 칸 한 곳 — 조정 창은 손잡이 · 읽기만(PLAN 24-4a)
- 옹벽 · 세월교 · BOX 조정 창의 값 칸(셀렉트 · −/+) 걷음 — 읽기 · 십자 · 그림 끌기 손잡이만(input 0)
- 그림 끌기 손잡이: 고른 구조물을 끌면 십자와 같은 걸음 · 같은 동작 · 끄는 동안 미리 보기
- 좌측 [횡단 조정] 사본 상자 없앰 — 폼에 없던 줄(읽기 · 단 수 · 옵션 · 집수정 내공 · 세월교 측벽 높이 · 다단 벽)만 폼 칸에 옮겨 붙음
- 집수정 기준측점 전 · 후를 폼 칸으로(옵션 보이기 따름) · 옮겨 붙은 줄도 옵션 보이기 규칙
- 폼 값 = 칸 전부: 비운 칸은 그림 자동 · 가게(pipes)에서도 지움 · 조정 예약(culvertopt)에 따로 담지 않음
- 시험 test_b06_adjust_single_form 6건 · 거울 시험(집수정 전 · 후 폼 칸) 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
2026-09-29 23:54:12 +09:00

204 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* B06_Section_UI_Cross_Ford_Panel.ts
* 세월교 측벽 **조정 오버레이 창**(2026-08-25 사용자 확정).
*
* 배수관 조정창(`_Cross_Structure_Panel.ts`)과 같은 템플릿(공용 부품
* `_Cross_Panel_Base.ts`)을 쓴다 — 머리줄(제목·닫기), 2행 항목 구조,
* **십자 조작 세트는 창 맨 아래**, 키보드 방향키 제어 포함(2026-08-25 일원화).
*
* 조작: 측벽 좌우(노견 확장) · 측벽 상하(1:1.2 대각) — 십자 · 그림 끌기. 측벽 높이는 폼 날개벽
* 칸으로 옮겨 붙는 줄 · 관경 · 수량 · 날개벽 제원은 폼 칸 하나(계획서 24-4a).
* 바닥판 연장은 날개벽 길이×cos(각도)가 정하므로 읽기 전용으로 보여 준다.
* ========================================================================== */
import type { FordAdjust, FordWallAdjust, FordWallRole } from "./B06_Section_UI_Cross_Ford_Geom";
import {
bindArrowKeys,
bindDragHandle,
buildPanelShell,
dpadButton,
dragReadout,
makeButton,
makeRow,
} from "./B06_Section_UI_Cross_Panel_Base";
/** 조정창이 쓰는 값·동작. 카드가 자기 상태에 맞춰 물려 준다. */
export interface FordPanelDeps {
/** 지금 조작값(적용된 결과 — 한계에 잘린 뒤 값). */
adjustFor: (role: FordWallRole) => FordWallAdjust;
/** 실제 그려진 벽 높이(m) — 자동일 때도 숫자를 보여 준다. */
heightFor: (role: FordWallRole) => number;
/** 높이 ±0.1m. */
nudgeHeight: (role: FordWallRole, deltaM: number) => void;
/** 좌우: 화면 좌(+)/우(−)로 미는 양(m). 부호 환산은 카드가 한다. */
nudge: (role: FordWallRole, screenDeltaM: number) => void;
/** 상하: 1:1.2 사면을 타는 대각 이동(수평 성분 m, + = 사면 아래). */
nudgeSlope: (role: FordWallRole, deltaM: number) => void;
/** 자동 자리로 되돌린다(세 축 모두). */
reset: (role: FordWallRole) => void;
/** 바닥판 길이(m) — 날개벽 투영이 정한 값을 읽기 전용으로 보여 준다. */
slabLengthM: () => number;
/** 창을 닫는다 = 구조물 선택 해제. */
close: () => void;
}
export interface FordPanelHandle {
root: HTMLElement;
/** null이면 숨긴다. 값이 오면 그 측벽 기준으로 다시 그린다. */
show: (role: FordWallRole | null) => void;
}
/** 한 걸음 — 집수정 9키와 같은 0.1m 눈금. */
const STEP_M = 0.1;
/** 세월교 측벽 조정 오버레이 창을 만든다. 처음에는 숨어 있다.
* 관경 · 수량 · 날개벽은 좌측 폼 칸이 정본이라 창에 두지 않는다(계획서 24-4a).
* `opts.dock` — 좌측 폼에 옮겨 붙을 줄(읽기 · 측벽 높이)만 만든다 — 폼에 없던 칸이다.
* 오버레이는 읽기 + 십자 + 그림 끌기 손잡이. */
export function buildFordPanel(deps: FordPanelDeps, opts?: { dock?: boolean }): FordPanelHandle {
const shell = buildPanelShell(() => deps.close());
const { root, title } = shell;
if (opts?.dock) root.classList.add("b06-structure-panel--dock");
const value = document.createElement("div");
value.className = "b06-structure-panel__value";
if (opts?.dock) value.classList.add("b06-structure-panel__struct--readout");
const heightLine = document.createElement("span");
const moveLine = document.createElement("span");
const slabLine = document.createElement("span");
const dragLine = dragReadout();
value.append(heightLine, moveLine, slabLine, dragLine);
let current: FordWallRole | null = null;
const act = (run: (role: FordWallRole) => void) => () => {
if (current) run(current);
};
// 측벽 높이 — 폼에 칸이 없던 값이라 폼 날개벽 칸으로 옮겨 붙는 줄(24-4a).
const heightRow = makeRow("측벽 높이");
heightRow.row.classList.add("b06-structure-panel__struct--wall-height");
const heightValue = document.createElement("span");
heightValue.className = "b06-structure-panel__hval";
heightRow.controls.append(
makeButton(
"−",
"측벽 높이 0.1m 낮추기",
act((role) => deps.nudgeHeight(role, -STEP_M)),
),
heightValue,
makeButton(
"+",
"측벽 높이 0.1m 높이기",
act((role) => deps.nudgeHeight(role, STEP_M)),
),
);
// 십자 조작 세트 — **창 맨 아래**(템플릿 규약).
const moveRow = makeRow("이동");
moveRow.controls.classList.add("b06-structure-panel__buttons");
moveRow.controls.append(
dpadButton(
"▲",
"up",
"사면 위로(1:1.2 대각)",
act((role) => deps.nudgeSlope(role, -STEP_M)),
),
dpadButton(
"◀",
"left",
"화면 왼쪽으로",
act((role) => deps.nudge(role, STEP_M)),
),
dpadButton("↺", "reset", "이 측벽 조정값 초기화", act(deps.reset)),
dpadButton(
"▶",
"right",
"화면 오른쪽으로",
act((role) => deps.nudge(role, -STEP_M)),
),
dpadButton(
"▼",
"down",
"사면 아래로(1:1.2 대각)",
act((role) => deps.nudgeSlope(role, STEP_M)),
),
);
root.append(value, opts?.dock ? heightRow.row : moveRow.row);
const arrows = opts?.dock
? { attach: (): void => undefined, detach: (): void => undefined }
: bindArrowKeys(root, () => [moveRow.controls]);
// 그림 위 끌기 손잡이 — 십자와 같은 걸음 · 같은 동작(24-4a).
const drag = opts?.dock
? { attach: (): void => undefined, detach: (): void => undefined }
: bindDragHandle(root, {
stepM: () => (current ? STEP_M : null),
preview: (text) => (dragLine.textContent = text),
commit: (left, down) => {
if (!current) return;
if (left) deps.nudge(current, left * STEP_M);
if (down) deps.nudgeSlope(current, down * STEP_M);
},
});
function render(): void {
if (!current) return;
const role = current === "inlet" ? "유입" : "유출";
title.textContent = `세월교 ${role} 측벽`;
const adjust: FordWallAdjust = deps.adjustFor(current);
heightLine.textContent =
`높이 ${deps.heightFor(current).toFixed(2)}m` + (adjust.heightM === null ? " (자동)" : "");
moveLine.textContent = `좌우 ${adjust.lateralM.toFixed(1)}m · 상하 ${adjust.slopeM.toFixed(1)}m`;
slabLine.textContent = `바닥판 ${deps.slabLengthM().toFixed(2)}m`;
heightValue.textContent = `${deps.heightFor(current).toFixed(1)}m`;
}
return {
root,
show(role) {
current = role;
root.classList.toggle("is-hidden", role === null);
// 좌측 폼에 어느 칸을 세울지 알린다 — 세월교는 그 측 날개벽 칸이다
// (배관 조정창이 유입구·유출구 칸을 가리키는 것과 같은 규약, 2026-08-30 지시 1).
root.dataset.side = role ?? "";
// 칸 이름이 이미 "날개벽(유입)"이라 괄호에는 구조물 이름만 붙인다.
root.dataset.panelTitle = role === null ? "" : "세월교";
arrows.detach();
drag.detach();
if (role !== null) {
arrows.attach();
drag.attach();
}
render();
},
};
}
/** 측점별 세월교 조작값 제어 — 페이지(세션·정본)가 구현한다. */
export interface FordControl {
adjustFor: (chainageM: number) => FordAdjust;
update: (chainageM: number, role: FordWallRole, patch: Partial<FordWallAdjust>) => void;
reset: (chainageM: number, role: FordWallRole) => void;
/** 관경(mm)·수량(련)·관종·월류 폭 저장 — B05 정본(`pipe_points`)으로 간다.
* 좌측 「구조물 배치」 폼만 보낸다(조정창에 칸 없음 · 계획서 24-4a). */
setPipe: (
chainageM: number,
patch: {
pipe_diameter_mm?: number;
pipe_count?: number;
pipe_kind?: string;
ford_width_m?: number;
},
) => void;
/** 날개벽 제원 저장 — 좌측 폼이 보낸다(계획서 24-4a). 같은 정본으로 간다. */
setWing: (
chainageM: number,
role: FordWallRole,
patch: Partial<{ installed: boolean; height_m: number; length_m: number; angle_deg: number }>,
) => void;
/** 지금 선택된 측벽(측점별). */
selectedFor: (chainageM: number) => FordWallRole | null;
select: (chainageM: number, role: FordWallRole | null) => void;
}