refactor(b06): 횡단 카드 렌더러 기능별 나눔 — 조정창 두 벌 · 지반선 · 팬 범위 분리(137-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jonfdxn1Xit9bdkNJpE4G
This commit is contained in:
@@ -36,7 +36,12 @@ import type { CulvertDesignTrim } from "./B06_Section_UI_Cross_Culvert";
|
||||
import { appendBoxOverlay, computeBoxLayout } from "./B06_Section_UI_Cross_Box";
|
||||
import type { BoxControl } from "./B06_Section_UI_Cross_Box_Panel";
|
||||
import { createBodyWiring } from "./B06_Section_UI_Cross_View_Bodies";
|
||||
import { appendCardFrame, applyStructureAreas } from "./B06_Section_UI_Cross_View_Frame";
|
||||
import {
|
||||
appendCardFrame,
|
||||
appendGroundProfile,
|
||||
applyStructureAreas,
|
||||
drawnContentBounds,
|
||||
} from "./B06_Section_UI_Cross_View_Frame";
|
||||
import { appendFordOverlay, computeFordLayout } from "./B06_Section_UI_Cross_Ford";
|
||||
import type { FordControl } from "./B06_Section_UI_Cross_Ford_Panel";
|
||||
import { computeCardCulvert } from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
@@ -48,16 +53,14 @@ import type {
|
||||
RevetOffsetControl,
|
||||
StructureSpanControl,
|
||||
} from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
import { buildStructurePanel } from "./B06_Section_UI_Cross_Structure_Panel";
|
||||
import { adoptAdjustPanel, releaseAdjustPanel } from "./B06_Section_UI_Adjust_Dock";
|
||||
import { culvertCardState, structurePanelDeps } from "./B06_Section_UI_Cross_View_Structure";
|
||||
import { culvertCardState } from "./B06_Section_UI_Cross_View_Structure";
|
||||
import { buildCardPanels, emptyCardCulvertState } from "./B06_Section_UI_Cross_View_Panels";
|
||||
import { attachZoomPan, buildZoomControls, cardZoomStates } from "./B06_Section_UI_Cross_View_Zoom";
|
||||
import type { CrossWidthActions } from "./B06_Section_UI_Cross_View_Zoom";
|
||||
import type { CutSlopeControl } from "./B06_Section_UI_Cross_CutSlope";
|
||||
import { createCrossAxes, IDENTITY_VIEW } from "./B06_Section_UI_Cross_Axes";
|
||||
import type { RevetHighlightSetter, RevetKey, RevetPick } from "./B06_Section_UI_Cross_Culvert";
|
||||
import type { RevetHighlightSetter, RevetPick } from "./B06_Section_UI_Cross_Culvert";
|
||||
import { REVET_FORM_DEFAULT } from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import {
|
||||
CROSS_PAD,
|
||||
CROSS_WIDTH,
|
||||
@@ -186,20 +189,8 @@ export function createCrossSectionCard(
|
||||
let highlightRevet: RevetPick | null = revetOffset?.highlightFor(section) ?? activeRevet;
|
||||
let setRevetActive: RevetHighlightSetter = () => undefined;
|
||||
let showRevetControl: (visible: boolean) => void = () => undefined;
|
||||
let culvertInletIsBasin = false; // 유입이 집수정인가 — 조정창 표시·제목 분기
|
||||
// 이 카드의 기준벽이 **독립 기슭막이**인가(관 없는 숨김 세트). 조정창 걸음이
|
||||
// 배관(1m)과 다르다(0.1m — 2026-08-29 사용자).
|
||||
let culvertHiddenPipe = false;
|
||||
/** 드롭다운 선택지 가용성(2026-08-22) — 기하 판정을 조정창에 전달. */
|
||||
let culvertInletOptions = { revetAllowed: true, basinLUAllowed: true };
|
||||
/** 추가 기슭막이 상태(2026-08-22) — 끝 성토부 5m 이상(canAdd)·세워진 개수. */
|
||||
let culvertExtraState = { canAdd: false, count: 0 };
|
||||
/** 집수정 계류측 다단 상태(2026-08-22) — 조정창의 집수정 단 수 행이 쓴다. */
|
||||
let culvertBasinExtraState = { canAdd: false, count: 0 };
|
||||
/** 마지막 계산의 벽 제원(높이·재질) — 배관 벽은 순수 높이(2026-08-23 사용자). */
|
||||
let culvertWallSpecs = new Map<RevetKey, { height: number; form: string }>();
|
||||
/** 마지막 계산의 실제 적용 d(상하) — d 미지정(자동) 벽의 ▲▼ 시작값. */
|
||||
let culvertAppliedD = new Map<RevetKey, number>();
|
||||
/** 조정창이 읽는 마지막 계산 결과(벽 제원 · 단 수 등) — `_Cross_View_Panels`. */
|
||||
const culvertState = emptyCardCulvertState();
|
||||
/**
|
||||
* 기슭막이를 고른다. 구조물 선택과 절·성토 면적 강조는 **같은 레벨**이라 하나를
|
||||
* 고르면 다른 하나는 풀린다(2026-08-21 사용자). 또 구조물을 고르면 그 **측점 카드도
|
||||
@@ -367,25 +358,11 @@ export function createCrossSectionCard(
|
||||
// 상태의 가운데 버튼 팬으로 끌어다 본다(2026-08-23 사용자 지시). 축 눈금은 팬을 따라
|
||||
// 다시 그려지므로(_UI_Cross_Axes) 값이 어긋나지 않는다.
|
||||
const drawSamples = section.samples;
|
||||
const segments: string[] = [];
|
||||
let current: string[] = [];
|
||||
for (const sample of drawSamples) {
|
||||
if (!validElevation(sample)) {
|
||||
if (current.length > 1) segments.push(current.join(" "));
|
||||
current = [];
|
||||
continue;
|
||||
}
|
||||
const elevated = elevationMid + (sample.elevation_m - elevationMid) * exaggeration;
|
||||
current.push(`${x(sample.offset_m ?? 0)},${y(elevated)}`);
|
||||
}
|
||||
if (current.length > 1) segments.push(current.join(" "));
|
||||
segments.forEach((points) =>
|
||||
plotLayer.append(svgElement("polyline", { points, class: "b06-chart__cross-profile" })),
|
||||
);
|
||||
const toDisplayY = (elevation: number): number =>
|
||||
y(elevationMid + (elevation - elevationMid) * exaggeration);
|
||||
appendGroundProfile(plotLayer, drawSamples, x, toDisplayY);
|
||||
|
||||
if (section.design) {
|
||||
const toDisplayY = (elevation: number): number =>
|
||||
y(elevationMid + (elevation - elevationMid) * exaggeration);
|
||||
// 면적 밴드는 지면선 위·설계선 아래에 깔아 선이 밴드에 가리지 않게 한다.
|
||||
// 선택 여부와 무관하게 항상 깐다 — 평소에는 투명이고, 선택이 바뀔 때 카드를 새로 만들지
|
||||
// 않고 클래스만 켜면 되므로 줌·팬이 살아남는다.
|
||||
@@ -460,11 +437,11 @@ export function createCrossSectionCard(
|
||||
// 눌러도 안 움직이는데 숫자만 커지는 것 방지.
|
||||
revetOffset?.syncApplied(section.chainage_m, "own", ownLayout.appliedAdjust);
|
||||
// 재질은 구조물 옵션(형태)이 정본이라 조정창에서 숨긴다 — 자리만 채운다.
|
||||
culvertWallSpecs.set("own", {
|
||||
culvertState.wallSpecs.set("own", {
|
||||
height: ownLayout.heightM,
|
||||
form: section.revetment?.form ?? REVET_FORM_DEFAULT,
|
||||
});
|
||||
culvertAppliedD.set("own", ownLayout.appliedAdjust.d ?? 0);
|
||||
culvertState.appliedD.set("own", ownLayout.appliedAdjust.d ?? 0);
|
||||
const baseSetActive = setRevetActive;
|
||||
setRevetActive = (key) => {
|
||||
baseSetActive(key);
|
||||
@@ -512,16 +489,9 @@ export function createCrossSectionCard(
|
||||
);
|
||||
}
|
||||
// 배수관 세트 — 조정창이 쓸 카드 상태를 받아 둔다(2026-08-19) · 링크 카드도 채운다(2026-08-24).
|
||||
culvertInletIsBasin = !!culvertLayout?.basin;
|
||||
culvertHiddenPipe = culvertLayout?.culvert.hidden_pipe === true;
|
||||
if (culvertLayout) {
|
||||
const state = culvertCardState(culvertLayout);
|
||||
culvertInletOptions = state.inletOptions;
|
||||
culvertExtraState = state.extraState;
|
||||
culvertBasinExtraState = state.basinExtraState;
|
||||
culvertWallSpecs = state.wallSpecs;
|
||||
culvertAppliedD = state.appliedD;
|
||||
}
|
||||
culvertState.inletIsBasin = !!culvertLayout?.basin;
|
||||
culvertState.hiddenPipe = culvertLayout?.culvert.hidden_pipe === true;
|
||||
if (culvertLayout) Object.assign(culvertState, culvertCardState(culvertLayout));
|
||||
if (boxLayout) {
|
||||
bodies.attachBox(
|
||||
appendBoxOverlay(plotLayer, boxLayout, x, toDisplayY, box ? bodies.toggleBox : undefined),
|
||||
@@ -561,7 +531,7 @@ export function createCrossSectionCard(
|
||||
valid,
|
||||
centerX: x(0),
|
||||
designElevation: hasDesign ? (designElevation as number) : null,
|
||||
toY: (elevation) => y(elevationMid + (elevation - elevationMid) * exaggeration),
|
||||
toY: toDisplayY,
|
||||
widthPx,
|
||||
heightPx,
|
||||
});
|
||||
@@ -570,19 +540,7 @@ export function createCrossSectionCard(
|
||||
chartWrap.className = "b06-cross-card__chart-wrap";
|
||||
chartWrap.style.setProperty("--b06-plot-left", `${CROSS_PAD.left}px`);
|
||||
// 팬 한계로 쓸 **그려진 범위** — 캐시 전 구간의 좌우 끝과 표고 상·하단.
|
||||
const drawnOffsets = drawSamples.map((sample) => sample.offset_m ?? 0);
|
||||
const drawnDisplayY = drawSamples
|
||||
.filter(validElevation)
|
||||
.map((sample) => y(elevationMid + (sample.elevation_m - elevationMid) * exaggeration));
|
||||
const contentBounds =
|
||||
drawnOffsets.length && drawnDisplayY.length
|
||||
? {
|
||||
x: x(Math.max(...drawnOffsets)),
|
||||
width: x(Math.min(...drawnOffsets)) - x(Math.max(...drawnOffsets)),
|
||||
y: Math.min(...drawnDisplayY),
|
||||
height: Math.max(...drawnDisplayY) - Math.min(...drawnDisplayY),
|
||||
}
|
||||
: undefined;
|
||||
const contentBounds = drawnContentBounds(drawSamples, x, toDisplayY);
|
||||
const zoomPan = attachZoomPan(
|
||||
svg,
|
||||
plotLayer,
|
||||
@@ -600,79 +558,24 @@ export function createCrossSectionCard(
|
||||
// 절·성토 면적값은 그래프 중상단 오버레이로 표시(E-4). 값 칸은 항상 강조 토글이다.
|
||||
const readout = buildAreaReadout(section.design, toggleArea);
|
||||
setChipActive = readout.setActive;
|
||||
// 구조물 조정은 도면 안 오버레이 창(2026-08-21). 화면 좌(◀) = offset 증가 —
|
||||
// 벽 기준 이동량(outward 부호)으로 환산해 넘긴다.
|
||||
// 유입측 벽 — 기준벽과 유입측 다단(bextra). 빼먹으면 그 벽만 좌우가 뒤집힌다.
|
||||
const inletSideRole = (role: RevetKey): boolean =>
|
||||
role === "inlet" || role.startsWith("bextra");
|
||||
const outwardOf = (role: RevetKey): number =>
|
||||
// 독립 기슭막이는 배관 유입/유출과 무관 — 정본 설치 측(좌 = +offset)으로 부호를 잡는다.
|
||||
role === "own"
|
||||
? section.revetment?.side === "우"
|
||||
? -1
|
||||
: 1
|
||||
: ((section.uphill_side ?? "left") === "left") === inletSideRole(role)
|
||||
? 1
|
||||
: -1;
|
||||
const heightOfWall = (key: RevetKey): number => culvertWallSpecs.get(key)?.height ?? 0;
|
||||
const formOfWall = (key: RevetKey): string =>
|
||||
culvertWallSpecs.get(key)?.form ?? REVET_FORM_DEFAULT;
|
||||
// 연동 중인 링크 카드의 조작은 **소유 측점**에 실린다(2026-08-24) — 풀면 자기 값.
|
||||
const adjustTarget = (): CrossSection =>
|
||||
isLinkedCulvert && culvertLink && (revetLink?.linkedFor(section) ?? true)
|
||||
? culvertLink.source
|
||||
: section;
|
||||
// 연동을 푼 측점에서 **아직 손대지 않은** 벽은 소유 측점 값을 보여 준다 — 창이
|
||||
// 0(자동)을 보여 주면 첫 ◀ 한 번에 벽이 원위치로 튄다(2026-08-30).
|
||||
const adjustOf = (key: RevetKey): WallAdjust => {
|
||||
const stored = revetOffset?.storedAdjustFor(adjustTarget(), key);
|
||||
if (stored) return stored;
|
||||
const source = isLinkedCulvert && culvertLink ? culvertLink.source : section;
|
||||
return revetOffset?.adjustFor(source, key) ?? { x: 0, d: null, h: null, m: null };
|
||||
};
|
||||
const panelContext = {
|
||||
// 배수관 · 흙막이 조정창 두 벌(카드 안 · 좌측 사본) — `_Cross_View_Panels`(137-1).
|
||||
const panel = buildCardPanels({
|
||||
section,
|
||||
scrollKey: section.station_id,
|
||||
adjustChainage: () => adjustTarget().chainage_m,
|
||||
isLinked: isLinkedCulvert,
|
||||
culvertLink,
|
||||
isLinkedCulvert,
|
||||
revetOffset,
|
||||
inletStructure,
|
||||
extraWalls,
|
||||
structureSpan,
|
||||
revetLink,
|
||||
adjustOf,
|
||||
outwardOf,
|
||||
heightOfWall,
|
||||
drawnWallKeys: () => [...culvertWallSpecs.keys()],
|
||||
formOfWall,
|
||||
appliedD: () => culvertAppliedD,
|
||||
inletIsBasin: () => culvertInletIsBasin,
|
||||
hiddenPipe: () => culvertHiddenPipe,
|
||||
bodyGuard: () => !!(section.culvert ?? culvertLink?.source.culvert)?.body,
|
||||
fordGuard: () => (section.culvert ?? culvertLink?.source.culvert)?.body === "ford",
|
||||
inletOptions: () => culvertInletOptions,
|
||||
extraState: () => culvertExtraState,
|
||||
basinExtraState: () => culvertBasinExtraState,
|
||||
state: culvertState,
|
||||
activeRevet: () => activeRevet,
|
||||
setActiveRevet: (key: RevetPick | null) => {
|
||||
setActiveRevet: (key) => {
|
||||
activeRevet = key;
|
||||
},
|
||||
toggleRevet,
|
||||
};
|
||||
const panel = buildStructurePanel(structurePanelDeps(panelContext));
|
||||
// 좌측 [횡단 조정] 사본 — 같은 deps라 어느 쪽 조작이든 카드 재렌더에서 두 창이
|
||||
// 함께 새 값으로 그려진다(2026-08-29 일원화). 스크롤 기억 키만 분리한다.
|
||||
const dockPanel = buildStructurePanel(
|
||||
structurePanelDeps({ ...panelContext, scrollKey: `dock:${section.station_id}` }),
|
||||
{ dock: true },
|
||||
);
|
||||
showRevetControl = (visible) => {
|
||||
const key = visible ? activeRevet : null;
|
||||
panel.show(key);
|
||||
dockPanel.show(key);
|
||||
if (key) adoptAdjustPanel(dockPanel.root);
|
||||
else releaseAdjustPanel(dockPanel.root);
|
||||
};
|
||||
});
|
||||
showRevetControl = (visible) => panel.show(visible ? activeRevet : null);
|
||||
showRevetControl(activeRevet !== null);
|
||||
// 세월교 조정창 — 배수관 창과 조작 축이 달라 따로 만든다(2026-08-25 사용자).
|
||||
// 우측 상단 줌 버튼이 원배율에서 표시 반폭까지 다룬다(하단 ◀/▶/↺ 폐지, 2026-08-23).
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
||||
import { computeStructureAreas, type DesignTrim } from "@util/common_util_cross_structure_areas";
|
||||
import { CROSS_PAD, L, svgElement, svgText } from "./B06_Section_UI_Section_Common";
|
||||
import { CROSS_PAD, L, svgElement, svgText, validElevation } from "./B06_Section_UI_Section_Common";
|
||||
|
||||
type Sample = CrossSection["samples"][number];
|
||||
|
||||
@@ -124,3 +124,43 @@ export function applyStructureAreas(
|
||||
design.cut_soil_area_m2 = Number(areas.cutSoilAreaM2.toFixed(4));
|
||||
}
|
||||
}
|
||||
|
||||
/** 원지반선 — 끊긴 샘플(무효 표고)마다 폴리선을 나눠 그린다. `toY` = 표고 → 화면 y(배율 반영). */
|
||||
export function appendGroundProfile(
|
||||
plotLayer: SVGElement,
|
||||
samples: Sample[],
|
||||
x: (offset: number) => number,
|
||||
toY: (elevation: number) => number,
|
||||
): void {
|
||||
const segments: string[] = [];
|
||||
let current: string[] = [];
|
||||
for (const sample of samples) {
|
||||
if (!validElevation(sample)) {
|
||||
if (current.length > 1) segments.push(current.join(" "));
|
||||
current = [];
|
||||
continue;
|
||||
}
|
||||
current.push(`${x(sample.offset_m ?? 0)},${toY(sample.elevation_m)}`);
|
||||
}
|
||||
if (current.length > 1) segments.push(current.join(" "));
|
||||
segments.forEach((points) =>
|
||||
plotLayer.append(svgElement("polyline", { points, class: "b06-chart__cross-profile" })),
|
||||
);
|
||||
}
|
||||
|
||||
/** 팬 한계로 쓸 **그려진 범위** — 캐시 전 구간의 좌우 끝과 표고 상 · 하단(없으면 undefined). */
|
||||
export function drawnContentBounds(
|
||||
samples: Sample[],
|
||||
x: (offset: number) => number,
|
||||
toY: (elevation: number) => number,
|
||||
): { x: number; width: number; y: number; height: number } | undefined {
|
||||
const offsets = samples.map((sample) => sample.offset_m ?? 0);
|
||||
const displayY = samples.filter(validElevation).map((sample) => toY(sample.elevation_m));
|
||||
if (!offsets.length || !displayY.length) return undefined;
|
||||
return {
|
||||
x: x(Math.max(...offsets)),
|
||||
width: x(Math.min(...offsets)) - x(Math.max(...offsets)),
|
||||
y: Math.min(...displayY),
|
||||
height: Math.max(...displayY) - Math.min(...displayY),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/* =============================================================================
|
||||
* B06_Section_UI_Cross_View_Panels.ts
|
||||
* 횡단 카드 ↔ **배수관 · 흙막이 조정창 두 벌**(카드 안 · 좌측 [횡단 조정] 사본) 배선 —
|
||||
* 카드 렌더러(`_UI_Cross_View.ts`)가 700줄을 넘어 떼었다(137-1). 그리기 · 기하는 하지 않는다.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
||||
import type { RevetKey, RevetPick } from "./B06_Section_UI_Cross_Culvert";
|
||||
import { REVET_FORM_DEFAULT } from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import type {
|
||||
CulvertLink,
|
||||
ExtraWallControl,
|
||||
InletStructureControl,
|
||||
RevetLinkControl,
|
||||
RevetOffsetControl,
|
||||
StructureSpanControl,
|
||||
} from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
import { buildStructurePanel } from "./B06_Section_UI_Cross_Structure_Panel";
|
||||
import { adoptAdjustPanel, releaseAdjustPanel } from "./B06_Section_UI_Adjust_Dock";
|
||||
import { structurePanelDeps } from "./B06_Section_UI_Cross_View_Structure";
|
||||
|
||||
/** 조정창이 읽는 카드 상태 — 마지막 계산 결과(그릴 때마다 바뀜). */
|
||||
export interface CardCulvertState {
|
||||
/** 유입이 집수정인가 — 조정창 표시 · 제목 분기. */
|
||||
inletIsBasin: boolean;
|
||||
/** 기준벽이 **독립 기슭막이**인가(관 없는 숨김 세트) — 걸음 0.1 m(2026-08-29 사용자). */
|
||||
hiddenPipe: boolean;
|
||||
/** 드롭다운 선택지 가용성(2026-08-22) — 기하 판정을 조정창에 전달. */
|
||||
inletOptions: { revetAllowed: boolean; basinLUAllowed: boolean };
|
||||
/** 추가 기슭막이 상태(2026-08-22) — 끝 성토부 5m 이상(canAdd) · 세워진 개수. */
|
||||
extraState: { canAdd: boolean; count: number };
|
||||
/** 집수정 계류측 다단 상태(2026-08-22) — 조정창의 집수정 단 수 행이 쓴다. */
|
||||
basinExtraState: { canAdd: boolean; count: number };
|
||||
/** 마지막 계산의 벽 제원(높이 · 재질) — 배관 벽은 순수 높이(2026-08-23 사용자). */
|
||||
wallSpecs: Map<RevetKey, { height: number; form: string }>;
|
||||
/** 마지막 계산의 실제 적용 d(상하) — d 미지정(자동) 벽의 ▲▼ 시작값. */
|
||||
appliedD: Map<RevetKey, number>;
|
||||
}
|
||||
|
||||
export const emptyCardCulvertState = (): CardCulvertState => ({
|
||||
inletIsBasin: false,
|
||||
hiddenPipe: false,
|
||||
inletOptions: { revetAllowed: true, basinLUAllowed: true },
|
||||
extraState: { canAdd: false, count: 0 },
|
||||
basinExtraState: { canAdd: false, count: 0 },
|
||||
wallSpecs: new Map(),
|
||||
appliedD: new Map(),
|
||||
});
|
||||
|
||||
export interface CardPanelsInput {
|
||||
section: CrossSection;
|
||||
culvertLink?: CulvertLink;
|
||||
isLinkedCulvert: boolean;
|
||||
revetOffset?: RevetOffsetControl;
|
||||
inletStructure?: InletStructureControl;
|
||||
extraWalls?: ExtraWallControl;
|
||||
structureSpan?: StructureSpanControl;
|
||||
revetLink?: RevetLinkControl;
|
||||
state: CardCulvertState;
|
||||
activeRevet: () => RevetPick | null;
|
||||
setActiveRevet: (key: RevetPick | null) => void;
|
||||
toggleRevet: (key: RevetPick) => void;
|
||||
}
|
||||
|
||||
/** 카드 안 조정창 + 좌측 사본을 만든다. `show` = 고른 벽(없으면 null)으로 두 창을 맞춤. */
|
||||
export function buildCardPanels(input: CardPanelsInput): {
|
||||
root: HTMLElement;
|
||||
show: (key: RevetPick | null) => void;
|
||||
} {
|
||||
const { section, culvertLink, isLinkedCulvert, revetOffset, revetLink, state } = input;
|
||||
// 구조물 조정은 도면 안 오버레이 창(2026-08-21). 화면 좌(◀) = offset 증가 —
|
||||
// 벽 기준 이동량(outward 부호)으로 환산해 넘긴다.
|
||||
// 유입측 벽 — 기준벽과 유입측 다단(bextra). 빼먹으면 그 벽만 좌우가 뒤집힌다.
|
||||
const inletSideRole = (role: RevetKey): boolean => role === "inlet" || role.startsWith("bextra");
|
||||
const outwardOf = (role: RevetKey): number =>
|
||||
// 독립 기슭막이는 배관 유입/유출과 무관 — 정본 설치 측(좌 = +offset)으로 부호를 잡는다.
|
||||
role === "own"
|
||||
? section.revetment?.side === "우"
|
||||
? -1
|
||||
: 1
|
||||
: ((section.uphill_side ?? "left") === "left") === inletSideRole(role)
|
||||
? 1
|
||||
: -1;
|
||||
const heightOfWall = (key: RevetKey): number => state.wallSpecs.get(key)?.height ?? 0;
|
||||
const formOfWall = (key: RevetKey): string =>
|
||||
state.wallSpecs.get(key)?.form ?? REVET_FORM_DEFAULT;
|
||||
// 연동 중인 링크 카드의 조작은 **소유 측점**에 실린다(2026-08-24) — 풀면 자기 값.
|
||||
const adjustTarget = (): CrossSection =>
|
||||
isLinkedCulvert && culvertLink && (revetLink?.linkedFor(section) ?? true)
|
||||
? culvertLink.source
|
||||
: section;
|
||||
// 연동을 푼 측점에서 **아직 손대지 않은** 벽은 소유 측점 값을 보여 준다 — 창이
|
||||
// 0(자동)을 보여 주면 첫 ◀ 한 번에 벽이 원위치로 튄다(2026-08-30).
|
||||
const adjustOf = (key: RevetKey): WallAdjust => {
|
||||
const stored = revetOffset?.storedAdjustFor(adjustTarget(), key);
|
||||
if (stored) return stored;
|
||||
const source = isLinkedCulvert && culvertLink ? culvertLink.source : section;
|
||||
return revetOffset?.adjustFor(source, key) ?? { x: 0, d: null, h: null, m: null };
|
||||
};
|
||||
const panelContext = {
|
||||
section,
|
||||
scrollKey: section.station_id,
|
||||
adjustChainage: () => adjustTarget().chainage_m,
|
||||
isLinked: isLinkedCulvert,
|
||||
revetOffset,
|
||||
inletStructure: input.inletStructure,
|
||||
extraWalls: input.extraWalls,
|
||||
structureSpan: input.structureSpan,
|
||||
revetLink,
|
||||
adjustOf,
|
||||
outwardOf,
|
||||
heightOfWall,
|
||||
drawnWallKeys: () => [...state.wallSpecs.keys()],
|
||||
formOfWall,
|
||||
appliedD: () => state.appliedD,
|
||||
inletIsBasin: () => state.inletIsBasin,
|
||||
hiddenPipe: () => state.hiddenPipe,
|
||||
bodyGuard: () => !!(section.culvert ?? culvertLink?.source.culvert)?.body,
|
||||
fordGuard: () => (section.culvert ?? culvertLink?.source.culvert)?.body === "ford",
|
||||
inletOptions: () => state.inletOptions,
|
||||
extraState: () => state.extraState,
|
||||
basinExtraState: () => state.basinExtraState,
|
||||
activeRevet: input.activeRevet,
|
||||
setActiveRevet: input.setActiveRevet,
|
||||
toggleRevet: input.toggleRevet,
|
||||
};
|
||||
const panel = buildStructurePanel(structurePanelDeps(panelContext));
|
||||
// 좌측 [횡단 조정] 사본 — 같은 deps라 어느 쪽 조작이든 카드 재렌더에서 두 창이
|
||||
// 함께 새 값으로 그려진다(2026-08-29 일원화). 스크롤 기억 키만 분리한다.
|
||||
const dockPanel = buildStructurePanel(
|
||||
structurePanelDeps({ ...panelContext, scrollKey: `dock:${section.station_id}` }),
|
||||
{ dock: true },
|
||||
);
|
||||
return {
|
||||
root: panel.root,
|
||||
show: (key) => {
|
||||
panel.show(key);
|
||||
dockPanel.show(key);
|
||||
if (key) adoptAdjustPanel(dockPanel.root);
|
||||
else releaseAdjustPanel(dockPanel.root);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user