feat(structure): 54-47 상자 칸 고침 · 새 상자 → 횡단 카드 이동 · 강조 · 54-48 도수로/돌붙임 하나만

- 상자 안 칸을 고치면 지금 보기의 목록 고름 길로 이동(횡단 = 측점 카드 · 종단 = 측점선 · 알약)
- 같은 칸을 거듭 고치면 스크롤 한 번(다시 그려도 칸 이름으로 셈)
- 단 늘림 · [추가]로 상자가 새로 서면 그 상자 강조
- 배관 도수로/돌붙임 컨테이너 max 1 — 하나 있으면 [추가] 숨김

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
2026-10-06 16:00:13 +09:00
co-authored by Claude Opus 5.5
parent 83cb0d8803
commit c64f14efb8
3 changed files with 49 additions and 13 deletions
@@ -171,6 +171,7 @@ export const PIPE_SPEC: StructureSpec = {
required: false,
prefix: "chute_add",
link: "gchuteadd",
max: 1, // 하나만(54-48) — 옛 둘 이상은 보이되 [추가]만 숨김
items: [
{
id: "structure",
+2
View File
@@ -377,6 +377,8 @@ export async function mountCrossView(
from: "b06",
}),
(chainageM) => sectionView.refreshCard(chainageM),
// 지금 보기의 목록 고름 길 — 횡단 = 측점 카드 · 종단 = 측점선 · 알약(54-47)
(chainageM) => shared.bindings[shared.view()]?.structures?.onPipeSelect?.(chainageM),
);
// 횡단도(카드) 자체를 골라도 그 측점 구조물 정보를 폼에 올린다(2026-08-29 사용자).
// 연동으로 옆에서 넘어온 카드는 **소유 측점** 시설을 보여 준다.
@@ -571,6 +571,8 @@ export function wireStructureSelection(
remember?: (chainageM: number, key: string) => void,
/** 폼에서 고른 벽을 카드 조정창에도 세움(54-30 · 목록 넘김 `applyStructurePick` 과 같은 차례). */
refreshCard?: (chainageM: number) => void,
/** 폼에 올린 시설로 보기를 옮김 — 목록에서 고른 길(54-47). */
reveal?: (chainageM: number) => void,
): { syncInletStructure: () => void } {
const ownerChainageOf = (chainageM: number): number => {
const sectionAt = detail()?.cross_sections.find(
@@ -614,23 +616,54 @@ export function wireStructureSelection(
};
// 관 상자 칸을 고치거나 고르면 그 부분을 고름 — 횡단에서 누른 것과 같은 길(54-30)
panel.root.addEventListener("change", (event) => {
// 단 수 칸은 고른 단을 지킴(단 수 바꿈 뒤 강조 유지)
if ((event.target as HTMLElement).closest('[data-spec-item="outlet.tiers"]')) return;
const box = (event.target as HTMLElement).closest<HTMLElement>("fieldset[data-spec-container]");
const keyOf = (box: HTMLElement): RevetKey | null => {
const id = box.dataset.specContainer;
if (id === "inlet" || id === "outlet") return id;
return id === "soil_add" ? `extra${Number(box.dataset.specIndex)}` : null;
};
const pickBox = (box: HTMLElement): void => {
const chainageM = panel.currentChainage();
const target = chainageM === null ? undefined : sectionAt(chainageM);
if (!box || !target?.culvert) return;
const id = box.dataset.specContainer;
const key: RevetKey | null =
id === "inlet" || id === "outlet"
? id
: id === "soil_add"
? `extra${Number(box.dataset.specIndex)}`
: null;
if (!key || stationControls.revetOffset.highlightFor(target) === key) return;
const key = keyOf(box);
if (!key || !target?.culvert || stationControls.revetOffset.highlightFor(target) === key)
return;
stationControls.revetOffset.select(target.chainage_m, key);
refreshCard?.(target.chainage_m);
};
// 그 측점 카드 · 알약으로 감(목록에서 고른 길 · 54-47) — 같은 칸을 잇달아 고치면 한 번만(다시 그려도 칸 이름으로)
const fieldOf = (el: HTMLElement): string => {
const box = el.closest<HTMLElement>("fieldset[data-spec-container]");
const item = el.closest<HTMLElement>("[data-spec-item]")?.dataset.specItem;
return `${box?.dataset.specContainer}:${box?.dataset.specIndex}:${item}`;
};
let revealed: string | null = null;
const revealAt = (chainageM: number, field: string | null): void => {
if (field && field === revealed) return;
revealed = field;
reveal?.(chainageM);
};
panel.root.addEventListener("focusin", (event) => {
if (fieldOf(event.target as HTMLElement) !== revealed) revealed = null;
});
panel.root.addEventListener("change", ({ target: el }) => {
const box = (el as HTMLElement).closest<HTMLElement>("fieldset[data-spec-container]");
const chainageM = panel.currentChainage();
if (!box || chainageM === null) return;
revealAt(chainageM, fieldOf(el as HTMLElement));
// 단 수 칸은 고른 단을 지킴(단 수 바꿈 뒤 강조 유지) — 새 단은 다시 그린 뒤 아래에서 고름
if (!(el as HTMLElement).closest('[data-spec-item="outlet.tiers"]')) pickBox(box);
});
// 상자가 새로 서면(단 늘림 · [추가]) 그 상자를 고름 — 같은 시설을 다시 그린 때만
let painted: { chainageM: number | null; keys: Set<string> } | null = null;
panel.root.addEventListener("specpaint", () => {
const chainageM = panel.currentChainage();
const boxes = [...panel.root.querySelectorAll<HTMLElement>("fieldset[data-spec-container]")];
const before = painted?.chainageM === chainageM ? painted.keys : null;
painted = { chainageM, keys: new Set(boxes.map(fieldOf)) };
const born = before ? boxes.filter((box) => !before.has(fieldOf(box))).at(-1) : undefined;
if (!born || chainageM === null) return;
revealAt(chainageM, null);
pickBox(born);
});
const origFord = stationControls.ford.select;