From 6d13b3b7817d9c968f2a3393352e826288d1f76c Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 1 Aug 2026 23:57:40 +0900 Subject: [PATCH] =?UTF-8?q?refactor(B05):=20=EB=B0=B0=EA=B4=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=C2=B7=EC=82=AD=EC=A0=9C=20=EC=9A=B0=ED=81=B4=EB=A6=AD?= =?UTF-8?q?=20=EB=A9=94=EB=89=B4=EB=A5=BC=20=EC=A2=85=EB=8B=A8=20=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=ED=94=84=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 구조물 조작이 테이블과 그래프로 나뉘어 있었다. 이동은 그래프 측점선, 추가·삭제는 테이블이라 어디서 무엇을 하는지 알기 어려웠다. 메뉴를 그래프 칸으로 옮겨 셋을 한 자리에 모은다. 테이블은 값만 보여 준다. - mountStructureMenu의 부착 대상을 테이블 → 그래프 칸(.b05-profile__chart)으로 교체 - ProfileTableOptions.structureLines 제거 - CSS 블록명 b05-profile-table__context-menu → b05-profile-chart__context-menu Co-Authored-By: Claude Opus 5 (1M context) --- .../B05_wf2_Route_UI_Profile_Panel.ts | 17 ++++++++++------- .../B05_wf2_Route_UI_Profile_Structures.ts | 19 +++++++++---------- .../B05_wf2_Route_UI_Profile_Table.ts | 14 -------------- B05_wf2_Route/B05_wf2_Route_UI_Style.css | 10 +++++----- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts b/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts index ef659648..e78fe1b3 100644 --- a/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts +++ b/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts @@ -22,6 +22,7 @@ import { LONG_PAD } from "../B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Sectio import { createWorkflowPanelHandle } from "@ui/ui_template_overlay"; import { createPanelResizer } from "@ui/ui_template_resizer"; import { createDrainagePanel } from "./B05_wf2_Route_UI_Drainage_Panel"; +import { mountStructureMenu } from "./B05_wf2_Route_UI_Profile_Structures"; import { hasLegacyAlignment, normalizedLongitudinal, @@ -457,13 +458,6 @@ export function createRouteProfilePanel( // 값 열 계획고 직접 입력 → 규칙 측점과 동일한 station_offset 파이프라인. onAdjustStation: (chainage, delta) => base && applyEdits(adjustStation(base, store.edits(), chainage, delta)), - // 구조물 우클릭 메뉴 — 배관 추가 / 구조물 삭제. 이동은 그래프 측점선에서 한다. - structureLines: { - chainageAt: chainageInverter(longitudinal, width, layout.originOffset), - maxChainageM: maxChainageOf(longitudinal), - onRemove: (station) => callbacks?.onStructureRemove?.(station), - onAddPipe: (chainage) => callbacks?.onPipeAdd?.(chainage), - }, }) : null; @@ -511,6 +505,15 @@ export function createRouteProfilePanel( }, ), ); + // 구조물 우클릭 메뉴 — 측점선 위면 삭제, 빈 자리면 배관 추가. 이동도 여기(측점선 끌기)서 한다. + mountStructureMenu(chartWrap, { + stations: irregularStations, + x, + chainageAt: chainageInverter(longitudinal, width, layout.originOffset), + maxChainageM: maxChainageOf(longitudinal), + onRemove: (station) => callbacks?.onStructureRemove?.(station), + onAddPipe: (chainage) => callbacks?.onPipeAdd?.(chainage), + }); // 가로 스크롤에도 고정되는 sticky Y축 오버레이(SVG와 같은 눈금·불투명 배경으로 값 누출 차단). if (yAxis) chartWrap.append(buildStickyYAxis(yAxis, chartHeight)); if (alignment) { diff --git a/B05_wf2_Route/B05_wf2_Route_UI_Profile_Structures.ts b/B05_wf2_Route/B05_wf2_Route_UI_Profile_Structures.ts index b12261d5..493db944 100644 --- a/B05_wf2_Route/B05_wf2_Route_UI_Profile_Structures.ts +++ b/B05_wf2_Route/B05_wf2_Route_UI_Profile_Structures.ts @@ -1,9 +1,8 @@ /* ============================================================================= * 종단 테이블 구조물 배치 라인 (B05) * - * 테이블 위 우클릭 메뉴만 담당한다. 세로선은 **그리지 않는다** — 구조물 위치는 그래프의 측점선이 - * 이미 보여 주므로 테이블에까지 얹으면 같은 것을 두 번 그리는 셈이다(2026-08-01 사용자 지시). - * 위치를 옮기는 것도 그래프 측점선에서 한다. + * 종단 **그래프** 위 우클릭 메뉴. 구조물 조작은 전부 그래프에서 한다 — 측점선을 끌어 옮기고, + * 우클릭으로 배관을 넣거나 지운다(2026-08-01 사용자 지시). 테이블은 값만 보여 준다. * * 가까운 구조물이 있으면 삭제, 없으면 그 자리에 배관 추가를 띄운다. 배관은 배수유역도의 관 지점이 * 투영된 것이라(`origin: "pipe"`), 지우면 관 지점 정본(`pipe_points.json`)이 바뀌고 세부유역도 @@ -32,23 +31,23 @@ export interface StructureLineOptions { } /** - * 테이블 요소에 우클릭 메뉴를 붙인다. 테이블은 매 그리기마다 새로 만들어지므로 이 함수도 + * 그래프 칸에 우클릭 메뉴를 붙인다. 그래프는 매 그리기마다 새로 만들어지므로 이 함수도 * 그때마다 다시 부른다 — 상태를 밖에 남기지 않는다. */ -export function mountStructureMenu(table: HTMLElement, options: StructureLineOptions): void { - const menu = createMapContextMenu("b05-profile-table"); +export function mountStructureMenu(host: HTMLElement, options: StructureLineOptions): void { + const menu = createMapContextMenu("b05-profile-chart"); function clamp(chainageM: number): number { return Math.min(Math.max(chainageM, 0), options.maxChainageM); } // 우클릭 — 가까운 구조물이 있으면 삭제, 없으면 그 자리에 배관을 넣는다. - table.addEventListener("contextmenu", (event) => { + host.addEventListener("contextmenu", (event) => { if (menu.contains(event.target)) { event.preventDefault(); return; } - const rect = table.getBoundingClientRect(); + const rect = host.getBoundingClientRect(); const localX = event.clientX - rect.left; const chainage = clamp(options.chainageAt(localX)); // 선을 그리지 않으므로 화면 거리로 가장 가까운 구조물을 찾는다. @@ -66,9 +65,9 @@ export function mountStructureMenu(table: HTMLElement, options: StructureLineOpt : ["배관 추가", () => options.onAddPipe(Number(chainage.toFixed(2)))], ]); }); - table.addEventListener("pointerdown", (event) => { + host.addEventListener("pointerdown", (event) => { if (!menu.contains(event.target)) menu.close(); }); - table.append(menu.element); + host.append(menu.element); } diff --git a/B05_wf2_Route/B05_wf2_Route_UI_Profile_Table.ts b/B05_wf2_Route/B05_wf2_Route_UI_Profile_Table.ts index 9e0f5e2c..adbdfad8 100644 --- a/B05_wf2_Route/B05_wf2_Route_UI_Profile_Table.ts +++ b/B05_wf2_Route/B05_wf2_Route_UI_Profile_Table.ts @@ -19,10 +19,6 @@ import type { } from "./B05_wf2_Route_UI_Profile_Alignment"; import { stationLabel } from "../B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_Common"; import { irregularStationId, type IrregularStation } from "./B05_wf2_Route_UI_IrregularStations"; -import { - mountStructureMenu, - type StructureLineOptions, -} from "./B05_wf2_Route_UI_Profile_Structures"; export interface ProfileTableOptions { alignment: ProfileAlignment; @@ -46,8 +42,6 @@ export interface ProfileTableOptions { onCurveRadiusChange: (curve: AlignmentCurve, radiusM: number | null) => void; /** 임의 chainage의 계획고를 delta만큼 조정(비정규 측점 값 열 직접 입력용). */ onAdjustStation?: (chainageM: number, deltaM: number) => void; - /** 구조물 우클릭 메뉴(배관 추가·구조물 삭제). 없으면 메뉴를 붙이지 않는다. */ - structureLines?: Omit; } interface StationRowSpec { @@ -541,14 +535,6 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement { ), ); } - // 구조물 우클릭 메뉴 — 세로선은 그래프가 그리므로 여기서는 메뉴만 붙인다. - if (options.structureLines) { - mountStructureMenu(table, { - ...options.structureLines, - stations: options.irregularStations ?? [], - x, - }); - } return table; } diff --git a/B05_wf2_Route/B05_wf2_Route_UI_Style.css b/B05_wf2_Route/B05_wf2_Route_UI_Style.css index e0db0888..7c929413 100644 --- a/B05_wf2_Route/B05_wf2_Route_UI_Style.css +++ b/B05_wf2_Route/B05_wf2_Route_UI_Style.css @@ -685,9 +685,9 @@ /* ─── 선택된 비정규 측점 값 열 오버레이 (규칙 열과 같은 12행) ────────────── 세로 점선·구조물 태그 없이, 선택 시에만 값 열을 테이블 위에 겹쳐 보여준다. */ -/* ── 구조물 우클릭 메뉴 (배관 추가 · 구조물 삭제) ────────────────────────── */ +/* ── 구조물 우클릭 메뉴 (그래프 위 — 배관 추가 · 구조물 삭제) ─────────────── */ -.b05-profile-table__context-menu { +.b05-profile-chart__context-menu { position: absolute; z-index: 7; display: flex; @@ -700,11 +700,11 @@ box-shadow: 0 4px 16px rgb(0 0 0 / 25%); } -.b05-profile-table__context-menu[hidden] { +.b05-profile-chart__context-menu[hidden] { display: none; } -.b05-profile-table__context-menu-item { +.b05-profile-chart__context-menu-item { padding: var(--spacing-4) var(--spacing-8); border: 0; border-radius: var(--radius-cards); @@ -715,7 +715,7 @@ cursor: pointer; } -.b05-profile-table__context-menu-item:hover { +.b05-profile-chart__context-menu-item:hover { background: var(--color-surface-sunken); }