fix(B06): 횡단 세월교 · BOX 본체 클릭 = 좌측 본체 상자 고름 · 강조(PLAN 66-1)
- 세월교 바닥판 · 관 판정면 추가 → 「세월교」 상자 강조 · 스크롤 · 그림 바닥판 · 관 붉게 - BOX 구체 가운데 1/3 = 본체 · 양쪽 = 그 끝(유입 · 유출 상자 그대로) - 고름 값에 body 추가(FordPick · BoxPick) · 조정창은 본체에서 안 뜸 - B05 넘김 키 body 도 같은 길 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
This commit is contained in:
@@ -151,6 +151,19 @@ function moveRows(panelRoot: HTMLElement, selector: string, target: HTMLElement)
|
|||||||
function relocateSideRows(panelRoot: HTMLElement, scroll = true): void {
|
function relocateSideRows(panelRoot: HTMLElement, scroll = true): void {
|
||||||
clearSideSlots();
|
clearSideSlots();
|
||||||
const side = panelRoot.dataset.side;
|
const side = panelRoot.dataset.side;
|
||||||
|
// 세월교 · BOX 본체 = 옮길 줄 없이 그 명세의 본체 상자만 강조(66-1) — 날개벽 칸이 든 명세 폼에서 찾음
|
||||||
|
if (side === "body") {
|
||||||
|
const body = sideSlots?.wingInlet
|
||||||
|
.closest(".b05-structure__spec")
|
||||||
|
?.querySelector<HTMLElement>(
|
||||||
|
'fieldset[data-spec-container="ford"], fieldset[data-spec-container="body"]',
|
||||||
|
);
|
||||||
|
if (!body || body.closest("[hidden]")) return;
|
||||||
|
setGroupLabel(body, "");
|
||||||
|
body.classList.add("is-active");
|
||||||
|
if (scroll) body.scrollIntoView({ block: "nearest" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!sideSlots || (side !== "inlet" && side !== "outlet" && side !== "extra")) return;
|
if (!sideSlots || (side !== "inlet" && side !== "outlet" && side !== "extra")) return;
|
||||||
const target = side === "extra" ? sideSlots.extra : slotOf(side);
|
const target = side === "extra" ? sideSlots.extra : slotOf(side);
|
||||||
// 추가 기슭막이 칸은 평소 숨어 있다 — 고른 벽이 다단이면 **그 단이 딸린 칸 바로
|
// 추가 기슭막이 칸은 평소 숨어 있다 — 고른 벽이 다단이면 **그 단이 딸린 칸 바로
|
||||||
|
|||||||
@@ -27,8 +27,11 @@ export type {
|
|||||||
|
|
||||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
|
/** 고르는 부재 — 좌 · 우 끝 또는 본체(구체 가운데 1/3 · 좌측 「본체」 상자 · 66-1). */
|
||||||
|
export type BoxPick = BoxSideRole | "body";
|
||||||
|
|
||||||
/** 선택 강조를 카드 재생성 없이 갈아 끼우는 setter. */
|
/** 선택 강조를 카드 재생성 없이 갈아 끼우는 setter. */
|
||||||
export type BoxHighlightSetter = (role: BoxSideRole | null) => void;
|
export type BoxHighlightSetter = (role: BoxPick | null) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BOX암거 구체 오버레이 — computeBoxLayout 결과를 그린다.
|
* BOX암거 구체 오버레이 — computeBoxLayout 결과를 그린다.
|
||||||
@@ -39,7 +42,7 @@ export function appendBoxOverlay(
|
|||||||
layout: BoxLayout,
|
layout: BoxLayout,
|
||||||
x: (offset: number) => number,
|
x: (offset: number) => number,
|
||||||
toDisplayY: (elevation: number) => number,
|
toDisplayY: (elevation: number) => number,
|
||||||
onSelectSide?: (role: BoxSideRole) => void,
|
onSelectSide?: (role: BoxPick) => void,
|
||||||
): BoxHighlightSetter {
|
): BoxHighlightSetter {
|
||||||
const { box } = layout;
|
const { box } = layout;
|
||||||
const toXY = (point: OffsetPoint): [number, number] => [
|
const toXY = (point: OffsetPoint): [number, number] => [
|
||||||
@@ -133,21 +136,30 @@ export function appendBoxOverlay(
|
|||||||
label.textContent = layout.label.text;
|
label.textContent = layout.label.text;
|
||||||
layer.append(label);
|
layer.append(label);
|
||||||
|
|
||||||
// 좌·우 절반을 덮는 투명 겹면 — 어느 끝을 조정할지 클릭으로 고른다.
|
// 투명 겹면 — 구체 가운데 1/3 = 본체, 그 바깥 양쪽 = 그 끝(66-1). 어느 것을 조정할지 클릭으로 고른다.
|
||||||
const hits = new Map<BoxSideRole, SVGPolygonElement>();
|
const hits = new Map<BoxPick, SVGPolygonElement>();
|
||||||
if (onSelectSide) {
|
if (onSelectSide) {
|
||||||
const midOffset = (layout.sides[0].offset + layout.sides[1].offset) / 2;
|
const midOffset = (layout.sides[0].offset + layout.sides[1].offset) / 2;
|
||||||
|
const third = (layout.sides[0].offset - layout.sides[1].offset) / 6;
|
||||||
const top = Math.max(...layout.topSlab.map((point) => point.elevation));
|
const top = Math.max(...layout.topSlab.map((point) => point.elevation));
|
||||||
const bottom = Math.min(...layout.bottomSlab.map((point) => point.elevation));
|
const bottom = Math.min(...layout.bottomSlab.map((point) => point.elevation));
|
||||||
for (const side of layout.sides) {
|
const band = (from: number, to: number): OffsetPoint[] => [
|
||||||
const outer = side.apron.length ? side.apron[1].offset : side.offset;
|
{ offset: from, elevation: top },
|
||||||
|
{ offset: to, elevation: top },
|
||||||
|
{ offset: to, elevation: bottom },
|
||||||
|
{ offset: from, elevation: bottom },
|
||||||
|
];
|
||||||
|
const parts: Array<[BoxPick, OffsetPoint[]]> = [
|
||||||
|
["body", band(midOffset - third, midOffset + third)],
|
||||||
|
...layout.sides.map((side): [BoxPick, OffsetPoint[]] => {
|
||||||
|
const outer = side.apron.length ? side.apron[1].offset : side.offset;
|
||||||
|
const inner = midOffset + Math.sign(side.offset - midOffset) * Math.abs(third);
|
||||||
|
return [side.role, band(inner, outer)];
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
for (const [role, points] of parts) {
|
||||||
const hit = polygon(
|
const hit = polygon(
|
||||||
[
|
points,
|
||||||
{ offset: midOffset, elevation: top },
|
|
||||||
{ offset: outer, elevation: top },
|
|
||||||
{ offset: outer, elevation: bottom },
|
|
||||||
{ offset: midOffset, elevation: bottom },
|
|
||||||
],
|
|
||||||
// 강조용 클래스를 하나 더 단다 — 고른 끝을 옅게 칠한다(2026-08-30 사용자:
|
// 강조용 클래스를 하나 더 단다 — 고른 끝을 옅게 칠한다(2026-08-30 사용자:
|
||||||
// BOX암거는 선택해도 아무 표시가 없었다). 구체 부재는 좌·우가 한 몸이라
|
// BOX암거는 선택해도 아무 표시가 없었다). 구체 부재는 좌·우가 한 몸이라
|
||||||
// 그쪽을 칠하면 어느 끝을 고른 것인지 되레 흐려진다.
|
// 그쪽을 칠하면 어느 끝을 고른 것인지 되레 흐려진다.
|
||||||
@@ -156,14 +168,14 @@ export function appendBoxOverlay(
|
|||||||
);
|
);
|
||||||
hit.addEventListener("click", (event) => {
|
hit.addEventListener("click", (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
onSelectSide(side.role);
|
onSelectSide(role);
|
||||||
});
|
});
|
||||||
hits.set(side.role, hit);
|
hits.set(role, hit);
|
||||||
layer.append(hit);
|
layer.append(hit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (role: BoxSideRole | null): void => {
|
return (role: BoxPick | null): void => {
|
||||||
for (const [side, hit] of hits) hit.classList.toggle("is-active", side === role);
|
for (const [side, hit] of hits) hit.classList.toggle("is-active", side === role);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
* ========================================================================== */
|
* ========================================================================== */
|
||||||
|
|
||||||
import type { BoxSideAdjust, BoxSideRole } from "./B06_Section_UI_Cross_Box_Geom";
|
import type { BoxSideAdjust, BoxSideRole } from "./B06_Section_UI_Cross_Box_Geom";
|
||||||
|
import type { BoxPick } from "./B06_Section_UI_Cross_Box";
|
||||||
import {
|
import {
|
||||||
bindArrowKeys,
|
bindArrowKeys,
|
||||||
bindDragHandle,
|
bindDragHandle,
|
||||||
@@ -41,7 +42,8 @@ export interface BoxPanelDeps {
|
|||||||
export interface BoxPanelHandle {
|
export interface BoxPanelHandle {
|
||||||
root: HTMLElement;
|
root: HTMLElement;
|
||||||
/** null이면 숨긴다. 값이 오면 그 측 기준으로 다시 그린다. */
|
/** null이면 숨긴다. 값이 오면 그 측 기준으로 다시 그린다. */
|
||||||
show: (role: BoxSideRole | null) => void;
|
/** 본체는 창 없이 좌측 「본체」 상자만(66-1). */
|
||||||
|
show: (role: BoxPick | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 한 걸음(m) — 사용자 지정 0.1m. */
|
/** 한 걸음(m) — 사용자 지정 0.1m. */
|
||||||
@@ -133,15 +135,16 @@ export function buildBoxPanel(deps: BoxPanelDeps, opts?: { dock?: boolean }): Bo
|
|||||||
return {
|
return {
|
||||||
root,
|
root,
|
||||||
show(role) {
|
show(role) {
|
||||||
current = role;
|
current = role === "body" ? null : role;
|
||||||
root.classList.toggle("is-hidden", role === null);
|
root.classList.toggle("is-hidden", current === null);
|
||||||
// 좌측 [횡단 조정]에 어느 칸을 세울지 알린다 — 날개벽(유입)/(유출) 칸이다
|
// 좌측 [횡단 조정]에 어느 칸을 세울지 알린다 — 날개벽(유입)/(유출) 칸이다
|
||||||
// (세월교와 같은 규약, 2026-08-30 사용자).
|
// (세월교와 같은 규약, 2026-08-30 사용자).
|
||||||
root.dataset.side = role === null ? "" : (deps.wingRoleFor?.(role) ?? "");
|
root.dataset.side =
|
||||||
|
role === null || role === "body" ? (role ?? "") : (deps.wingRoleFor?.(role) ?? "");
|
||||||
root.dataset.panelTitle = role === null ? "" : "BOX암거";
|
root.dataset.panelTitle = role === null ? "" : "BOX암거";
|
||||||
arrows.detach();
|
arrows.detach();
|
||||||
drag.detach();
|
drag.detach();
|
||||||
if (role !== null) {
|
if (current !== null) {
|
||||||
arrows.attach();
|
arrows.attach();
|
||||||
drag.attach();
|
drag.attach();
|
||||||
}
|
}
|
||||||
@@ -163,6 +166,6 @@ export interface BoxControl {
|
|||||||
role: "inlet" | "outlet",
|
role: "inlet" | "outlet",
|
||||||
patch: Partial<{ installed: boolean; height_m: number; length_m: number; angle_deg: number }>,
|
patch: Partial<{ installed: boolean; height_m: number; length_m: number; angle_deg: number }>,
|
||||||
) => void;
|
) => void;
|
||||||
selectedFor: (chainageM: number) => BoxSideRole | null;
|
selectedFor: (chainageM: number) => BoxPick | null;
|
||||||
select: (chainageM: number, role: BoxSideRole | null) => void;
|
select: (chainageM: number, role: BoxPick | null) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,11 @@ export type {
|
|||||||
|
|
||||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
|
/** 고르는 부재 — 측벽(유입 · 유출) 또는 본체(바닥판 · 관 · 좌측 「세월교」 상자 · 66-1). */
|
||||||
|
export type FordPick = FordWallRole | "body";
|
||||||
|
|
||||||
/** 벽 강조를 카드 재생성 없이 갈아 끼우는 setter. */
|
/** 벽 강조를 카드 재생성 없이 갈아 끼우는 setter. */
|
||||||
export type FordHighlightSetter = (key: FordWallRole | null) => void;
|
export type FordHighlightSetter = (key: FordPick | null) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 세월교 구체 오버레이 — computeFordLayout 결과를 그린다.
|
* 세월교 구체 오버레이 — computeFordLayout 결과를 그린다.
|
||||||
@@ -41,7 +44,7 @@ export function appendFordOverlay(
|
|||||||
layout: FordLayout,
|
layout: FordLayout,
|
||||||
x: (offset: number) => number,
|
x: (offset: number) => number,
|
||||||
toDisplayY: (elevation: number) => number,
|
toDisplayY: (elevation: number) => number,
|
||||||
onSelectWall?: (key: FordWallRole) => void,
|
onSelectWall?: (key: FordPick) => void,
|
||||||
): FordHighlightSetter {
|
): FordHighlightSetter {
|
||||||
const { ford } = layout;
|
const { ford } = layout;
|
||||||
const toXY = (point: OffsetPoint): [number, number] => [
|
const toXY = (point: OffsetPoint): [number, number] => [
|
||||||
@@ -80,7 +83,7 @@ export function appendFordOverlay(
|
|||||||
}): string => (wing.installed ? `${wing.length_m ?? 0}m·${wing.angle_deg ?? 0}°` : "없음");
|
}): string => (wing.installed ? `${wing.length_m ?? 0}m·${wing.angle_deg ?? 0}°` : "없음");
|
||||||
|
|
||||||
// ① 바닥판 — 두 측벽 사이를 잇는 판. 각 측 바깥 연장은 측벽 부재로 함께 그린다.
|
// ① 바닥판 — 두 측벽 사이를 잇는 판. 각 측 바깥 연장은 측벽 부재로 함께 그린다.
|
||||||
layer.append(
|
const slab = layer.appendChild(
|
||||||
polygon(
|
polygon(
|
||||||
layout.slabBridge,
|
layout.slabBridge,
|
||||||
"b06-chart__ford-slab",
|
"b06-chart__ford-slab",
|
||||||
@@ -115,14 +118,14 @@ export function appendFordOverlay(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ③ 관 — 바닥판 상면에 안힌다. 단면엔 1개만, 련수는 라벨로 알린다.
|
// ③ 관 — 바닥판 상면에 안힌다. 단면엔 1개만, 련수는 라벨로 알린다.
|
||||||
layer.append(
|
const pipeOuter = polygon(
|
||||||
polygon(
|
layout.pipe.outer,
|
||||||
layout.pipe.outer,
|
"b06-chart__culvert-pipe b06-chart__culvert-pipe--outer",
|
||||||
"b06-chart__culvert-pipe b06-chart__culvert-pipe--outer",
|
`${layout.label.text} — 관 밑이 바닥판 상면`,
|
||||||
`${layout.label.text} — 관 밑이 바닥판 상면`,
|
|
||||||
),
|
|
||||||
polygon(layout.pipe.inner, "b06-chart__culvert-pipe", layout.label.text),
|
|
||||||
);
|
);
|
||||||
|
const pipeInner = polygon(layout.pipe.inner, "b06-chart__culvert-pipe", layout.label.text);
|
||||||
|
layer.append(pipeOuter, pipeInner);
|
||||||
|
const bodyShapes = [slab, pipeOuter, pipeInner];
|
||||||
|
|
||||||
// ④ 계류측 마감선 — 구조물이 원지반 위면 성토부선, 안이면 절토선(집수정과 같은 체계).
|
// ④ 계류측 마감선 — 구조물이 원지반 위면 성토부선, 안이면 절토선(집수정과 같은 체계).
|
||||||
for (const side of layout.sides) {
|
for (const side of layout.sides) {
|
||||||
@@ -142,6 +145,15 @@ export function appendFordOverlay(
|
|||||||
// 관·채움이 벽 위에 겹쳐 클릭을 가로챈다 — 투명 겹면을 맨 위에 얹어 벽을 고른다
|
// 관·채움이 벽 위에 겹쳐 클릭을 가로챈다 — 투명 겹면을 맨 위에 얹어 벽을 고른다
|
||||||
// (배수관 기슭막이 `culvert-revet-hit`와 같은 수법).
|
// (배수관 기슭막이 `culvert-revet-hit`와 같은 수법).
|
||||||
if (onSelectWall) {
|
if (onSelectWall) {
|
||||||
|
// 본체(바닥판 · 관) 판정면 — 벽 판정면보다 먼저 깔아 겹친 자리는 벽이 먹는다(66-1).
|
||||||
|
for (const points of [layout.slabBridge, layout.pipe.outer]) {
|
||||||
|
const hit = polygon(points, "b06-chart__culvert-revet-hit", "세월교 본체 선택");
|
||||||
|
hit.addEventListener("click", (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
onSelectWall("body");
|
||||||
|
});
|
||||||
|
layer.append(hit);
|
||||||
|
}
|
||||||
for (const side of layout.sides) {
|
for (const side of layout.sides) {
|
||||||
const wall = side.parts.find((part) => part.kind === "wall");
|
const wall = side.parts.find((part) => part.kind === "wall");
|
||||||
if (!wall) continue;
|
if (!wall) continue;
|
||||||
@@ -163,9 +175,10 @@ export function appendFordOverlay(
|
|||||||
label.textContent = layout.label.text;
|
label.textContent = layout.label.text;
|
||||||
layer.append(label);
|
layer.append(label);
|
||||||
|
|
||||||
return (key: FordWallRole | null): void => {
|
return (key: FordPick | null): void => {
|
||||||
for (const [role, shapes] of walls) {
|
for (const [role, shapes] of walls) {
|
||||||
for (const shape of shapes) shape.classList.toggle("is-active", role === key);
|
for (const shape of shapes) shape.classList.toggle("is-active", role === key);
|
||||||
}
|
}
|
||||||
|
for (const shape of bodyShapes) shape.classList.toggle("is-active", key === "body");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
* ========================================================================== */
|
* ========================================================================== */
|
||||||
|
|
||||||
import type { FordAdjust, FordWallAdjust, FordWallRole } from "./B06_Section_UI_Cross_Ford_Geom";
|
import type { FordAdjust, FordWallAdjust, FordWallRole } from "./B06_Section_UI_Cross_Ford_Geom";
|
||||||
|
import type { FordPick } from "./B06_Section_UI_Cross_Ford";
|
||||||
import {
|
import {
|
||||||
bindArrowKeys,
|
bindArrowKeys,
|
||||||
bindDragHandle,
|
bindDragHandle,
|
||||||
@@ -44,8 +45,8 @@ export interface FordPanelDeps {
|
|||||||
|
|
||||||
export interface FordPanelHandle {
|
export interface FordPanelHandle {
|
||||||
root: HTMLElement;
|
root: HTMLElement;
|
||||||
/** null이면 숨긴다. 값이 오면 그 측벽 기준으로 다시 그린다. */
|
/** null이면 숨긴다. 값이 오면 그 측벽 기준으로 다시 그린다 — 본체는 창 없이 좌측 「세월교」 상자만(66-1). */
|
||||||
show: (role: FordWallRole | null) => void;
|
show: (role: FordPick | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 한 걸음 — 집수정 9키와 같은 0.1m 눈금. */
|
/** 한 걸음 — 집수정 9키와 같은 0.1m 눈금. */
|
||||||
@@ -157,8 +158,8 @@ export function buildFordPanel(deps: FordPanelDeps, opts?: { dock?: boolean }):
|
|||||||
return {
|
return {
|
||||||
root,
|
root,
|
||||||
show(role) {
|
show(role) {
|
||||||
current = role;
|
current = role === "body" ? null : role;
|
||||||
root.classList.toggle("is-hidden", role === null);
|
root.classList.toggle("is-hidden", current === null);
|
||||||
// 좌측 폼에 어느 칸을 세울지 알린다 — 세월교는 그 측 날개벽 칸이다
|
// 좌측 폼에 어느 칸을 세울지 알린다 — 세월교는 그 측 날개벽 칸이다
|
||||||
// (배관 조정창이 유입구·유출구 칸을 가리키는 것과 같은 규약, 2026-08-30 지시 1).
|
// (배관 조정창이 유입구·유출구 칸을 가리키는 것과 같은 규약, 2026-08-30 지시 1).
|
||||||
root.dataset.side = role ?? "";
|
root.dataset.side = role ?? "";
|
||||||
@@ -166,7 +167,7 @@ export function buildFordPanel(deps: FordPanelDeps, opts?: { dock?: boolean }):
|
|||||||
root.dataset.panelTitle = role === null ? "" : "세월교";
|
root.dataset.panelTitle = role === null ? "" : "세월교";
|
||||||
arrows.detach();
|
arrows.detach();
|
||||||
drag.detach();
|
drag.detach();
|
||||||
if (role !== null) {
|
if (current !== null) {
|
||||||
arrows.attach();
|
arrows.attach();
|
||||||
drag.attach();
|
drag.attach();
|
||||||
}
|
}
|
||||||
@@ -198,6 +199,6 @@ export interface FordControl {
|
|||||||
patch: Partial<{ installed: boolean; height_m: number; length_m: number; angle_deg: number }>,
|
patch: Partial<{ installed: boolean; height_m: number; length_m: number; angle_deg: number }>,
|
||||||
) => void;
|
) => void;
|
||||||
/** 지금 선택된 측벽(측점별). */
|
/** 지금 선택된 측벽(측점별). */
|
||||||
selectedFor: (chainageM: number) => FordWallRole | null;
|
selectedFor: (chainageM: number) => FordPick | null;
|
||||||
select: (chainageM: number, role: FordWallRole | null) => void;
|
select: (chainageM: number, role: FordPick | null) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,17 +10,13 @@ import type {
|
|||||||
FordAdjust,
|
FordAdjust,
|
||||||
FordHighlightSetter,
|
FordHighlightSetter,
|
||||||
FordLayout,
|
FordLayout,
|
||||||
|
FordPick,
|
||||||
FordWallRole,
|
FordWallRole,
|
||||||
} from "./B06_Section_UI_Cross_Ford";
|
} from "./B06_Section_UI_Cross_Ford";
|
||||||
import { buildFordPanel } from "./B06_Section_UI_Cross_Ford_Panel";
|
import { buildFordPanel } from "./B06_Section_UI_Cross_Ford_Panel";
|
||||||
import type { FordControl, FordPanelHandle } from "./B06_Section_UI_Cross_Ford_Panel";
|
import type { FordControl, FordPanelHandle } from "./B06_Section_UI_Cross_Ford_Panel";
|
||||||
import { fordCardState, fordPanelDeps } from "./B06_Section_UI_Cross_View_Ford";
|
import { fordCardState, fordPanelDeps } from "./B06_Section_UI_Cross_View_Ford";
|
||||||
import type {
|
import type { BoxAdjust, BoxHighlightSetter, BoxLayout, BoxPick } from "./B06_Section_UI_Cross_Box";
|
||||||
BoxAdjust,
|
|
||||||
BoxHighlightSetter,
|
|
||||||
BoxLayout,
|
|
||||||
BoxSideRole,
|
|
||||||
} from "./B06_Section_UI_Cross_Box";
|
|
||||||
import { buildBoxPanel } from "./B06_Section_UI_Cross_Box_Panel";
|
import { buildBoxPanel } from "./B06_Section_UI_Cross_Box_Panel";
|
||||||
import type { BoxControl, BoxPanelHandle } from "./B06_Section_UI_Cross_Box_Panel";
|
import type { BoxControl, BoxPanelHandle } from "./B06_Section_UI_Cross_Box_Panel";
|
||||||
import { boxPanelDeps, structureToggle } from "./B06_Section_UI_Cross_View_Box";
|
import { boxPanelDeps, structureToggle } from "./B06_Section_UI_Cross_View_Box";
|
||||||
@@ -40,8 +36,9 @@ export interface BodyWiringDeps {
|
|||||||
export interface BodyWiring {
|
export interface BodyWiring {
|
||||||
fordAdjust: () => FordAdjust | undefined;
|
fordAdjust: () => FordAdjust | undefined;
|
||||||
boxAdjust: () => BoxAdjust | undefined;
|
boxAdjust: () => BoxAdjust | undefined;
|
||||||
toggleFord: (role: FordWallRole) => void;
|
/** 본체(`body`)는 조정창 없이 좌측 본체 상자만 고름(66-1). */
|
||||||
toggleBox: (role: BoxSideRole) => void;
|
toggleFord: (role: FordPick) => void;
|
||||||
|
toggleBox: (role: BoxPick) => void;
|
||||||
/** 오버레이가 만들어 준 강조 setter를 등록하고 현재 선택을 다시 칠한다. */
|
/** 오버레이가 만들어 준 강조 setter를 등록하고 현재 선택을 다시 칠한다. */
|
||||||
attachFord: (setter: FordHighlightSetter, layout: FordLayout) => void;
|
attachFord: (setter: FordHighlightSetter, layout: FordLayout) => void;
|
||||||
attachBox: (setter: BoxHighlightSetter, layout: BoxLayout) => void;
|
attachBox: (setter: BoxHighlightSetter, layout: BoxLayout) => void;
|
||||||
@@ -93,15 +90,15 @@ export function createBodyWiring(deps: BodyWiringDeps): BodyWiring {
|
|||||||
const boxDock = boxDeps ? buildBoxPanel(boxDeps, { dock: true }) : null;
|
const boxDock = boxDeps ? buildBoxPanel(boxDeps, { dock: true }) : null;
|
||||||
const syncDock = (
|
const syncDock = (
|
||||||
dock: FordPanelHandle | BoxPanelHandle | null,
|
dock: FordPanelHandle | BoxPanelHandle | null,
|
||||||
role: FordWallRole | BoxSideRole | null,
|
role: FordPick | BoxPick | null,
|
||||||
): void => {
|
): void => {
|
||||||
if (!dock) return;
|
if (!dock) return;
|
||||||
(dock as { show: (role: FordWallRole | BoxSideRole | null) => void }).show(role);
|
(dock as { show: (role: FordPick | BoxPick | null) => void }).show(role);
|
||||||
if (role) adoptAdjustPanel(dock.root);
|
if (role) adoptAdjustPanel(dock.root);
|
||||||
else releaseAdjustPanel(dock.root);
|
else releaseAdjustPanel(dock.root);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleFord = structureToggle<FordWallRole>({
|
const toggleFord = structureToggle<FordPick>({
|
||||||
current: () => fordRole,
|
current: () => fordRole,
|
||||||
setCurrent: (value) => (fordRole = value),
|
setCurrent: (value) => (fordRole = value),
|
||||||
setActive: (value) => setFordActive(value),
|
setActive: (value) => setFordActive(value),
|
||||||
@@ -114,7 +111,7 @@ export function createBodyWiring(deps: BodyWiringDeps): BodyWiring {
|
|||||||
clearArea: deps.clearArea,
|
clearArea: deps.clearArea,
|
||||||
selectCard: deps.selectCard,
|
selectCard: deps.selectCard,
|
||||||
});
|
});
|
||||||
const toggleBox = structureToggle<BoxSideRole>({
|
const toggleBox = structureToggle<BoxPick>({
|
||||||
current: () => boxRole,
|
current: () => boxRole,
|
||||||
setCurrent: (value) => (boxRole = value),
|
setCurrent: (value) => (boxRole = value),
|
||||||
setActive: (value) => setBoxActive(value),
|
setActive: (value) => setBoxActive(value),
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import {
|
|||||||
} from "@util/common_util_culvert_sets";
|
} from "@util/common_util_culvert_sets";
|
||||||
import type { CrossDesign, CrossSection } from "./B06_Section_Api_Fetch";
|
import type { CrossDesign, CrossSection } from "./B06_Section_Api_Fetch";
|
||||||
import { DEFAULT_FORD_WALL_ADJUST } from "./B06_Section_UI_Cross_Ford";
|
import { DEFAULT_FORD_WALL_ADJUST } from "./B06_Section_UI_Cross_Ford";
|
||||||
import type { FordAdjust, FordWallAdjust, FordWallRole } from "./B06_Section_UI_Cross_Ford";
|
import type { FordAdjust, FordPick, FordWallAdjust } from "./B06_Section_UI_Cross_Ford";
|
||||||
import type { FordControl } from "./B06_Section_UI_Cross_Ford_Panel";
|
import type { FordControl } from "./B06_Section_UI_Cross_Ford_Panel";
|
||||||
import { DEFAULT_BOX_SIDE_ADJUST } from "./B06_Section_UI_Cross_Box";
|
import { DEFAULT_BOX_SIDE_ADJUST } from "./B06_Section_UI_Cross_Box";
|
||||||
import type { BoxAdjust, BoxSideAdjust, BoxSideRole } from "./B06_Section_UI_Cross_Box";
|
import type { BoxAdjust, BoxPick, BoxSideAdjust } from "./B06_Section_UI_Cross_Box";
|
||||||
import type { BoxControl } from "./B06_Section_UI_Cross_Box_Panel";
|
import type { BoxControl } from "./B06_Section_UI_Cross_Box_Panel";
|
||||||
|
|
||||||
/** 날개벽 한 벌의 저장 키 — 세월교·BOX암거가 같은 옵션 이름을 쓴다(`wing_in*`/`wing_out*`). */
|
/** 날개벽 한 벌의 저장 키 — 세월교·BOX암거가 같은 옵션 이름을 쓴다(`wing_in*`/`wing_out*`). */
|
||||||
@@ -125,7 +125,7 @@ export function createFordControls(deps: FordControlDeps): FordControls {
|
|||||||
* 실어 3D가 같은 값으로 다시 그리게 한다. 관경·수량은 B05 정본이 원천이라
|
* 실어 3D가 같은 값으로 다시 그리게 한다. 관경·수량은 B05 정본이 원천이라
|
||||||
* 배수관 구간값과 같은 저장기(`pipe_points` 되쓰기)로 보낸다. */
|
* 배수관 구간값과 같은 저장기(`pipe_points` 되쓰기)로 보낸다. */
|
||||||
const fordAdjustments = new Map<string, FordAdjust>();
|
const fordAdjustments = new Map<string, FordAdjust>();
|
||||||
const fordSelections = new Map<string, FordWallRole | null>();
|
const fordSelections = new Map<string, FordPick | null>();
|
||||||
const fordSessionKey = (): string | null => deps.sessionKey();
|
const fordSessionKey = (): string | null => deps.sessionKey();
|
||||||
|
|
||||||
function loadFordAdjustments(): void {
|
function loadFordAdjustments(): void {
|
||||||
@@ -322,7 +322,7 @@ export function createBoxControls(deps: FordControlDeps): {
|
|||||||
} {
|
} {
|
||||||
const { round1, clampMove, sectionAt, patchCachedDesign } = deps;
|
const { round1, clampMove, sectionAt, patchCachedDesign } = deps;
|
||||||
const adjustments = new Map<string, BoxAdjust>();
|
const adjustments = new Map<string, BoxAdjust>();
|
||||||
const selections = new Map<string, BoxSideRole | null>();
|
const selections = new Map<string, BoxPick | null>();
|
||||||
|
|
||||||
function load(): void {
|
function load(): void {
|
||||||
adjustments.clear();
|
adjustments.clear();
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
* 부재키는 **횡단도가 그 부재를 부르는 이름 그대로**다. 이름이 겹쳐도(배수관 유입 ↔
|
* 부재키는 **횡단도가 그 부재를 부르는 이름 그대로**다. 이름이 겹쳐도(배수관 유입 ↔
|
||||||
* 세월교 유입) 한 측점에는 시설이 하나뿐이라 헷갈리지 않는다 — 그 측점이 세월교면
|
* 세월교 유입) 한 측점에는 시설이 하나뿐이라 헷갈리지 않는다 — 그 측점이 세월교면
|
||||||
* 세월교 조정창, BOX암거면 BOX 조정창, 아니면 기슭막이 조정창으로 보낸다.
|
* 세월교 조정창, BOX암거면 BOX 조정창, 아니면 기슭막이 조정창으로 보낸다.
|
||||||
* 본체(`body` — 세월교 월류부 상판·BOX 구체)는 고르는 부재가 아니라 카드까지만 연다.
|
* 본체(`body` — 세월교 월류부 상판·BOX 구체)는 세월교 · BOX 에서 좌측 본체 상자를 고른다(66-1) ·
|
||||||
|
* 그 밖 시설은 카드까지만 연다.
|
||||||
*
|
*
|
||||||
* 세월교·BOX는 조정창 하나가 그 측 부재 여럿(벽·바닥·날개벽·에이프런)을 함께 다루므로
|
* 세월교·BOX는 조정창 하나가 그 측 부재 여럿(벽·바닥·날개벽·에이프런)을 함께 다루므로
|
||||||
* 부재키가 `측이름:부재`(예 `inlet:wall`) 꼴이다 — **앞칸이 조정창 이름**이다. 3D 강조는
|
* 부재키가 `측이름:부재`(예 `inlet:wall`) 꼴이다 — **앞칸이 조정창 이름**이다. 3D 강조는
|
||||||
@@ -29,8 +30,8 @@ interface PickTargets {
|
|||||||
focusStation: (stationId: string) => void;
|
focusStation: (stationId: string) => void;
|
||||||
refreshCard: (chainageM: number) => void;
|
refreshCard: (chainageM: number) => void;
|
||||||
revetSelect: (chainageM: number, key: RevetKey) => void;
|
revetSelect: (chainageM: number, key: RevetKey) => void;
|
||||||
fordSelect: (chainageM: number, role: "inlet" | "outlet") => void;
|
fordSelect: (chainageM: number, role: "inlet" | "outlet" | "body") => void;
|
||||||
boxSelect: (chainageM: number, role: "left" | "right") => void;
|
boxSelect: (chainageM: number, role: "left" | "right" | "body") => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,12 +64,12 @@ export function applyStructurePick(
|
|||||||
const key = handoff.key?.split(":")[0];
|
const key = handoff.key?.split(":")[0];
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
targets.focusStation(target.station_id);
|
targets.focusStation(target.station_id);
|
||||||
if (!key || key === "body") return;
|
if (!key) return;
|
||||||
if (target.ford) {
|
if (target.ford) {
|
||||||
if (key !== "inlet" && key !== "outlet") return;
|
if (key !== "inlet" && key !== "outlet" && key !== "body") return;
|
||||||
targets.fordSelect(target.chainage_m, key);
|
targets.fordSelect(target.chainage_m, key);
|
||||||
} else if (target.box) {
|
} else if (target.box) {
|
||||||
if (key !== "left" && key !== "right") return;
|
if (key !== "left" && key !== "right" && key !== "body") return;
|
||||||
targets.boxSelect(target.chainage_m, key);
|
targets.boxSelect(target.chainage_m, key);
|
||||||
} else {
|
} else {
|
||||||
if (!REVET_KEYS.test(key)) return;
|
if (!REVET_KEYS.test(key)) return;
|
||||||
|
|||||||
@@ -374,6 +374,14 @@
|
|||||||
stroke-width: 1;
|
stroke-width: 1;
|
||||||
stroke-dasharray: 4 3;
|
stroke-dasharray: 4 3;
|
||||||
stroke-linejoin: round;
|
stroke-linejoin: round;
|
||||||
|
/* 세월교 본체(바닥판 · 관) 선택 강조(66-1) — 측벽 강조와 같은 색. */
|
||||||
|
.b06-chart__ford-slab.is-active,
|
||||||
|
.b06-chart__culvert-pipe.is-active {
|
||||||
|
fill: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
||||||
|
stroke: var(--color-danger);
|
||||||
|
stroke-width: 2.2;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 터파기 선(2026-09-09 사용자 확정 ④) — **파선**으로 그린다. 구조물 실체가 아니라
|
/* 터파기 선(2026-09-09 사용자 확정 ④) — **파선**으로 그린다. 구조물 실체가 아니라
|
||||||
|
|||||||
Reference in New Issue
Block a user