feat(B06): 세월교 좌측 폼을 배관 측점과 같은 양식으로 맞춘다

사용자 지시 3건(2026-08-30).

1) 횡단도에서 세월교 측벽을 고르면 좌측 폼의 그 측 칸이 선다 — 배관 기슭막이가
   쓰던 경로를 그대로 탄다. `SideSlots`에 `wingInlet`·`wingOutlet`을 더하고
   `slotOf()`가 배관 칸 → 독립 기슭막이 칸 → 날개벽 칸 순으로 지금 서 있는 쪽을
   고른다. 세월교 조정창은 `dataset.side`·`dataset.panelTitle`을 세워 어느 칸을
   가리키는지 알린다. 벽 선택 뒤 `refreshAdjustSlots()`도 배관과 같게 붙였다.

2) 월류 폭·높이·수량·개략 단면 결과를 관경 바로 다음으로 옮겼다(바닥 경사도 같은
   묶음에 둔다 — 물넘이포장 전용이라 세월교에서는 숨는다). 개략 단면 안내문은
   읽는 글이라 10px·opacity 0.75로 줄였다.

3) 세월교·날개벽 숫자 칸을 기슭막이·집수정과 같은 [-][값][+] 묶음으로 바꿨다
   (월류 폭·높이·바닥 경사·수량·날개 높이·길이·각도). `stepper()`에 표시 자릿수
   인자를 더해 련·각도는 정수로 보인다("1.0련"·"45.0°" 방지). 기본값 1이라 기존
   호출부는 그대로다.

자체검증(공용 브라우저 5174, 측점 7+9.7 세월교):
- tsc --noEmit 통과.
- 유입 측벽 선택 → `날개벽(유입) (세월교)` 칸 강조, 유출 측벽 → `날개벽(유출)
  (세월교)`. 한 번에 한 칸만 선다.
- 폼 항목 순서 실측: 관종 > 관경 > 월류 폭 > 월류 높이 > 수량 > 날개벽(유입) …
  안내문 font-size 10px, 보이는 스텝 묶음 9개.
- 수량 + → 폼 "2"·조정창 "2련", 각도 + → 폼 "50"·조정창 "50°" (소수점 없음).
- 회귀: 배관 측점 기슭막이 선택 → `유출구 (기슭막이(유출))` 강조 + 높이·길이·
  기준측점 전/후 행 이식 정상.
- 조작값은 원래대로(수량 1·각도 45) 되돌려 놓았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-30 12:05:22 +09:00
co-authored by Claude Opus 5
parent 03840ff3d6
commit e78feab5ed
6 changed files with 68 additions and 18 deletions
+12 -4
View File
@@ -22,6 +22,10 @@ interface SideSlots {
/** 독립 기슭막이 좌·우 칸 — 배관 유입구·유출구 칸이 숨어 있을 때 이쪽으로 간다. */
revetInlet: HTMLElement;
revetOutlet: HTMLElement;
/** 세월교 날개벽(유입·유출) 칸 — 세월교 측점에서는 배관·독립 칸이 모두 숨어 있어
* 이쪽이 그 측의 자리다(2026-08-30 사용자 지시 1). */
wingInlet: HTMLElement;
wingOutlet: HTMLElement;
}
let sideSlots: SideSlots | null = null;
@@ -51,12 +55,14 @@ function slotUsable(target: HTMLElement | undefined): target is HTMLElement {
return !!group && !group.hidden;
}
/** 배관이면 유입구·유출구 칸, 독립 기슭막이면 좌·우 칸 — 지금 서 있는 쪽을 준다. */
/** 배관이면 유입구·유출구 칸, 독립 기슭막이면 좌·우 칸, 세월교면 날개벽 칸 —
* 지금 서 있는 쪽을 준다(같은 측의 자리가 시설 종류마다 다른 칸에 있다). */
function slotOf(side: "inlet" | "outlet"): HTMLElement {
const slots = sideSlots!;
return slotUsable(slots[side])
? slots[side]
: slots[side === "inlet" ? "revetInlet" : "revetOutlet"];
const revet = side === "inlet" ? slots.revetInlet : slots.revetOutlet;
const wing = side === "inlet" ? slots.wingInlet : slots.wingOutlet;
if (slotUsable(slots[side])) return slots[side];
return slotUsable(revet) ? revet : wing;
}
/** 옮겨 온 행과 그 **제자리 표식** — 되돌릴 때 원래 순서로 꽂기 위해 짝으로 둔다.
@@ -85,6 +91,8 @@ function clearSideSlots(): void {
sideSlots?.extra,
sideSlots?.revetInlet,
sideSlots?.revetOutlet,
sideSlots?.wingInlet,
sideSlots?.wingOutlet,
]) {
target?.replaceChildren();
const group = target?.closest("fieldset");
@@ -265,6 +265,11 @@ export function buildFordPanel(deps: FordPanelDeps, opts?: { dock?: boolean }):
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();
if (role !== null) arrows.attach();
render();
@@ -266,6 +266,8 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
extra: section.facility.extraSlot,
revetInlet: section.facility.revetInletSlot,
revetOutlet: section.facility.revetOutletSlot,
wingInlet: section.facility.wingInSlot,
wingOutlet: section.facility.wingOutSlot,
});
async function load(): Promise<void> {
@@ -441,6 +443,9 @@ export function wireStructureSelection(
markWallSelecting();
origFord(chainageM, role);
panel.showPipeAt(role ? chainageM : null);
// 폼이 이 시설로 갈아 끼워진 뒤에 이식을 다시 돌린다 — 날개벽 칸이 그제야 선다
// (배관 기슭막이와 같은 순서, 2026-08-30 사용자 지시 1).
refreshAdjustSlots();
};
const origBox = stationControls.box.select;
stationControls.box.select = (chainageM, role) => {