refactor(b06): 종단 화면 본체를 700줄 아래로 — 그리는 몫을 따로 뺌
`B06_Section_UI_Section_View.ts` 가 817줄로 한도를 넘겼음. 종단 그래프·구조물 알약 레인· 우클릭 메뉴·누가토량 배지·고정 Y축을 세우는 몫과 가로 스크롤 세로창 배선을 `B06_Section_UI_Section_View_Draw.ts` 로 옮겨 693줄로 줄임. 높이 재기·되먹임 보정·선택 상태는 본체가 그대로 쥐고, 옮긴 쪽은 넘겨받은 인자만 쓰므로 화면 상태가 두 곳에 남지 않음. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fe1QWPTfw11PaKh2LjwXdR
This commit is contained in:
@@ -17,8 +17,6 @@ import type { CutSlopeControl } from "./B06_Section_UI_Cross_CutSlope";
|
||||
import { readStateRaw, writeStateRaw } from "../A00_Common/b_page_state";
|
||||
import { createPanelResizer } from "@ui/ui_template_resizer";
|
||||
import { createWorkflowPanelHandle } from "@ui/ui_template_overlay";
|
||||
// 가로 스크롤 고정 Y축 — B05와 같은 오버레이를 쓴다(정의처: B05 MassHaul 모듈 + 그 CSS).
|
||||
import { buildStickyYAxis } from "../B05_Profile/B05_Profile_UI_Profile_MassHaul";
|
||||
import type {
|
||||
CrossSection,
|
||||
EarthworkConversion,
|
||||
@@ -45,38 +43,19 @@ import {
|
||||
effectiveCardHalfWidth,
|
||||
} from "./B06_Section_UI_Cross_View_Metrics";
|
||||
import { CROSS_HEIGHT } from "./B06_Section_UI_Section_Common";
|
||||
import { LONG_PAD } from "./B06_Section_UI_Section_Common";
|
||||
import { STRUCTURE_LANE_HEIGHT_PX } from "../B05_Profile/B05_Profile_UI_Structures_Marks";
|
||||
import type { SectionStructureEdit } from "./B06_Section_UI_Section_View_Menu";
|
||||
import { attachWindowScroll, drawLongitudinalPanel } from "./B06_Section_UI_Section_View_Draw";
|
||||
import {
|
||||
buildStructureLane,
|
||||
STRUCTURE_LANE_HEIGHT_PX,
|
||||
} from "../B05_Profile/B05_Profile_UI_Structures_Marks";
|
||||
import {
|
||||
mountSectionStructureMenu,
|
||||
moveMarkById,
|
||||
type SectionStructureEdit,
|
||||
} from "./B06_Section_UI_Section_View_Menu";
|
||||
import {
|
||||
structureAnchorM,
|
||||
type StructureInstance,
|
||||
type StructureType,
|
||||
} from "../B05_Profile/B05_Profile_Api_Structures";
|
||||
import { culvertLinkFor as culvertLink } from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
import type { CulvertLink } from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
import { longitudinalMinimumWidth } from "./B06_Section_UI_Longitudinal";
|
||||
import {
|
||||
buildLongitudinalChart,
|
||||
visibleChainageRange,
|
||||
visibleElevationRange,
|
||||
} from "./B06_Section_UI_Section_View_Chart";
|
||||
import { configureBalloonOffsets } from "@util/common_util_mass_haul_balance_view";
|
||||
import { computeMassHaulSeries } from "@util/common_util_mass_haul";
|
||||
import { badgeValuesFrom, createMassHaulBadge } from "@util/common_util_mass_haul_badge";
|
||||
import {
|
||||
applyElevationWindow,
|
||||
needsFullRedraw,
|
||||
Y_AXIS_WINDOW_CLASS,
|
||||
type ElevationWindowResult,
|
||||
} from "@util/common_util_chart_ywindow";
|
||||
import { createMassHaulBadge } from "@util/common_util_mass_haul_badge";
|
||||
import { type ElevationWindowResult } from "@util/common_util_chart_ywindow";
|
||||
import {
|
||||
BASE_PANEL_HEIGHT,
|
||||
chartHeights,
|
||||
@@ -512,119 +491,24 @@ export function createSectionView(
|
||||
// 생겨 패널이 16,000px 까지 부푼다(2026-09-07 실측). 빼 두면 한 번에 수렴한다.
|
||||
const laneHeight = markStructures.length && markTypes.length ? STRUCTURE_LANE_HEIGHT_PX : 0;
|
||||
const heights = chartHeights(Math.max(lastChartAvailable - laneHeight, MIN_LONG_HEIGHT));
|
||||
const moveStationMark = (markId: string, toChainageM: number): void => {
|
||||
if (!structureEdit) return;
|
||||
moveMarkById(markId, toChainageM, {
|
||||
structures: markStructures,
|
||||
stations: detail.longitudinal.stations ?? [],
|
||||
edit: structureEdit,
|
||||
});
|
||||
};
|
||||
const minWidth = longitudinalMinimumWidth(detail.longitudinal, cachedStationInterval);
|
||||
const chartWidth = Math.max(renderWidth, minWidth);
|
||||
const chart = buildLongitudinalChart({
|
||||
updateChartWindow = drawLongitudinalPanel({
|
||||
detail,
|
||||
chartWrap,
|
||||
renderWidth: Math.max(renderWidth, minWidth),
|
||||
chartHeight: heights.long,
|
||||
keepScrollLeft,
|
||||
selectedStationId,
|
||||
exaggeration: currentExaggeration,
|
||||
selectStation: (stationId) => selectStation(stationId, true),
|
||||
stationInterval: cachedStationInterval,
|
||||
chartWidth,
|
||||
chartHeight: heights.long,
|
||||
minWidth,
|
||||
scrollLeft: keepScrollLeft,
|
||||
viewportWidth: chartWrap.clientWidth || chartWidth,
|
||||
// 측점선을 끌면 그 구조물이 옮겨 간다 — 관은 예약 이동, 구조물은 정본 이동.
|
||||
onDragStation: structureEdit
|
||||
? (stationId, toChainageM) => moveStationMark(stationId, toChainageM)
|
||||
: undefined,
|
||||
structures: markStructures,
|
||||
types: markTypes,
|
||||
edit: structureEdit,
|
||||
conversion: currentConversion,
|
||||
naturalSpoilSlope: currentNaturalSpoilSlope,
|
||||
selectStation: (stationId) => selectStation(stationId, true),
|
||||
setMassBadge: (values) => massBadge.set(values),
|
||||
});
|
||||
const longAxis = chart.axis;
|
||||
const nodes: Element[] = [chart.node];
|
||||
// 구조물 알약 레인 — B05 종단과 **같은 부품**(`buildStructureLane`)을 그대로 쓴다.
|
||||
// 배수관·세월교도 여기 같은 알약으로 선다(2026-09-07 사용자 지시 4 표시 통일).
|
||||
if (markStructures.length && markTypes.length) {
|
||||
nodes.push(
|
||||
buildStructureLane({
|
||||
structures: markStructures,
|
||||
types: markTypes,
|
||||
x: chart.toX,
|
||||
chainageAt: chart.toChainage,
|
||||
maxChainageM: chart.maxChainageM,
|
||||
widthPx: chartWidth,
|
||||
axisWidthPx: LONG_PAD.left,
|
||||
stationIntervalM: cachedStationInterval,
|
||||
selectedId: null,
|
||||
// B06 에서 알약을 누르면 그 측점 카드를 고른다 — 목록 클릭과 같은 규칙.
|
||||
onSelect: (structureId) => {
|
||||
if (!structureId) return;
|
||||
const hit = markStructures.find((entry) => entry.structure_id === structureId);
|
||||
if (!hit) return;
|
||||
const at = structureAnchorM(hit);
|
||||
// 그 자리에 가장 가까운 측점 카드를 고른다 — 좌측 목록 클릭과 같은 규칙.
|
||||
let best: { id: string; gap: number } | null = null;
|
||||
for (const section of currentDetail?.cross_sections ?? []) {
|
||||
const gap = Math.abs(section.chainage_m - at);
|
||||
if (!best || gap < best.gap) best = { id: section.station_id, gap };
|
||||
}
|
||||
if (best) selectStation(best.id, true);
|
||||
},
|
||||
// 알약 끌기도 B05 와 같게 받는다(2026-09-12 일원화) — 정본은 [저장]·[확정]에서.
|
||||
onMove: (structureId, toChainageM) => moveStationMark(structureId, toChainageM),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
chartWrap.replaceChildren(...nodes);
|
||||
// 종단 그래프 우클릭 — B05 와 같은 메뉴다(가까운 구조물이 있으면 삭제, 없으면
|
||||
// 구조물군 → 종류 2단 추가). 그래프를 다시 그릴 때마다 붙인다(상태를 안 남긴다).
|
||||
if (structureEdit) {
|
||||
mountSectionStructureMenu(chartWrap, {
|
||||
structures: markStructures,
|
||||
types: markTypes,
|
||||
x: chart.toX,
|
||||
chainageAt: chart.toChainage,
|
||||
maxChainageM: chart.maxChainageM,
|
||||
edit: structureEdit,
|
||||
});
|
||||
}
|
||||
// 유토곡선 그래프는 B06 에서 **그리지 않는다**(2026-09-06 사용자 지시) — 자리를 많이
|
||||
// 먹는데 정작 필요한 값은 마지막 지점 누가토량 하나다. 곡선은 B05 유토곡선 패널에서
|
||||
// 펼쳐 본다. 여기서는 같은 계산으로 값만 내 좌측 상단 배지에 올린다(기준: 횡단).
|
||||
if (currentConversion) {
|
||||
const series = computeMassHaulSeries(
|
||||
detail.longitudinal,
|
||||
detail.cross_sections,
|
||||
currentConversion,
|
||||
currentNaturalSpoilSlope,
|
||||
);
|
||||
const cross = series.find((entry) => entry.basis === "cross") ?? series[0];
|
||||
massBadge.set(cross ? badgeValuesFrom(cross.result) : null);
|
||||
} else {
|
||||
massBadge.set(null);
|
||||
}
|
||||
// 종단 고정 Y축 — 0크기 sticky 앵커라 **첫 자식**으로 넣어야 세로 기준이 컨테이너
|
||||
// 상단이 된다(SVG 뒤에 넣으면 앵커가 차트 아래로 밀린다).
|
||||
if (longAxis) {
|
||||
const overlay = buildStickyYAxis(longAxis, heights.long);
|
||||
// 세로 창을 따라 움직이는 축임을 표시한다(유토곡선 축과 구분).
|
||||
overlay.classList.add(Y_AXIS_WINDOW_CLASS);
|
||||
chartWrap.prepend(overlay);
|
||||
}
|
||||
|
||||
// 세로 창 갱신기 — 스크롤마다 종단은 변환으로, 유토곡선은 곡선만 다시 그려 따라온다.
|
||||
updateChartWindow = () => {
|
||||
const { fromM, toM } = visibleChainageRange(
|
||||
chart.toChainage,
|
||||
chart.maxChainageM,
|
||||
chartWrap.scrollLeft,
|
||||
chartWrap.clientWidth || chartWidth,
|
||||
);
|
||||
return applyElevationWindow(
|
||||
chartWrap,
|
||||
visibleElevationRange(detail, fromM, toM) ?? undefined,
|
||||
);
|
||||
};
|
||||
chartWrap.scrollLeft = keepScrollLeft;
|
||||
// 상단 패널이 sticky라 선택 카드가 그 아래로 숨는다 — 패널 높이만큼 스크롤 여백을 잡아 준다.
|
||||
syncScrollMargin();
|
||||
|
||||
@@ -641,22 +525,11 @@ export function createSectionView(
|
||||
}
|
||||
}
|
||||
|
||||
/** 상단 패널을 통째로 다시 그리기까지 기다리는 시간(ms) — 눈금 갱신으로도 못 살릴 때만. */
|
||||
const SCROLL_SETTLE_MS = 80;
|
||||
let settledScrollLeft = 0;
|
||||
let scrollSettleTimer = 0;
|
||||
// 세로 맞춤은 **그리기가 아니라 변환**이다(2026-09-04 사용자 확정, B05와 같은 규칙) —
|
||||
// 스크롤마다 겹 하나의 변환만 갈아 끼우므로 실시간으로 따라온다.
|
||||
chartWrap.addEventListener("scroll", () => {
|
||||
const fitted = updateChartWindow?.() ?? null;
|
||||
window.clearTimeout(scrollSettleTimer);
|
||||
if (!needsFullRedraw(fitted)) return;
|
||||
scrollSettleTimer = window.setTimeout(() => {
|
||||
if (Math.abs(chartWrap.scrollLeft - settledScrollLeft) < 1) return;
|
||||
settledScrollLeft = chartWrap.scrollLeft;
|
||||
drawPanel();
|
||||
}, SCROLL_SETTLE_MS);
|
||||
});
|
||||
attachWindowScroll(
|
||||
chartWrap,
|
||||
() => updateChartWindow,
|
||||
() => drawPanel(),
|
||||
);
|
||||
|
||||
const draw = (): void => {
|
||||
if (!currentDetail || !Number.isFinite(renderWidth) || renderWidth <= 0) return;
|
||||
|
||||
Reference in New Issue
Block a user