fix(B05): 손으로 넣은 "배관" 처리 통일 + 첫 진입 시 배수유역도 공백
1) 사이드바 폼으로 이름을 "배관"이라 적어 넣은 항목은 origin이 "user"라 [초기화]로 지워지지 않고 배수유역도와도 어긋난 채 남았다. isPipeStation()(origin이 pipe이거나 이름이 "배관")으로 판정을 통일해 목록 교체·이동·삭제·선택 동기화가 같은 규칙을 쓴다. 2) 대시보드에서 곧장 B05로 들어오면 배수유역도가 비어 보이고 새로고침해야 나왔다. 패널이 배치되기 전(0×0)에 fitToRoute()가 돌아 엉뚱한 배율이 굳은 것이다. 크기가 2px 미만이면 맞춤을 미뤘다가 첫 배치 때 다시 맞춘다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,7 @@ import {
|
||||
MAX_PANEL_WIDTH_RATIO,
|
||||
MIN_PANEL_WIDTH,
|
||||
renderBasinRows,
|
||||
summaryText,
|
||||
basinIndexOfPipe,
|
||||
pipeMarkerColor,
|
||||
pointInRing,
|
||||
@@ -382,11 +383,7 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
|
||||
selectBasin(null, false);
|
||||
renderBasinList();
|
||||
syncPipeSelection();
|
||||
summary.textContent = L("B05_Drainage_Summary")
|
||||
.replace("{pipes}", String(pipeEditor.pipes().length))
|
||||
.replace("{basins}", String(basins.length))
|
||||
.replace("{source}", zSource || "-");
|
||||
summary.hidden = basins.length === 0;
|
||||
summaryText(summary, pipeEditor.pipes().length, basins.length, zSource);
|
||||
callbacks.onPipesChanged?.(pipeEditor.chainages());
|
||||
}
|
||||
|
||||
@@ -440,8 +437,18 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
|
||||
* B05는 노선 주변 배수유역을 보는 화면이라 도로에 맞춰 확대한 상태로 연다
|
||||
* (2026-08-01 사용자 지시). 배경 전체를 보려면 휠로 축소하면 된다.
|
||||
* 노선이 없으면 배경 전체를 그대로 보여준다. */
|
||||
/** 아직 레이아웃 전(폭·높이 0)이라 화면 맞춤을 미뤄 둔 상태. 첫 배치 때 다시 맞춘다. */
|
||||
let fitPending = false;
|
||||
|
||||
function fitToRoute(): void {
|
||||
const rect = viewport.getBoundingClientRect();
|
||||
// 대시보드에서 곧장 들어오면 패널이 아직 배치되기 전이라 0×0이다. 그 상태로 맞추면
|
||||
// 엉뚱한 배율이 굳어 지도가 보이지 않는다(새로고침하면 보이던 원인 — 2026-08-02 사용자 보고).
|
||||
if (rect.width < 2 || rect.height < 2) {
|
||||
fitPending = true;
|
||||
return;
|
||||
}
|
||||
fitPending = false;
|
||||
const fitted = fitViewToRoute(meta, routePoints, rect.width, rect.height);
|
||||
scale = fitted.scale;
|
||||
offsetX = fitted.offsetX;
|
||||
@@ -600,7 +607,11 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
|
||||
offsetX = next.offsetX;
|
||||
offsetY = next.offsetY;
|
||||
},
|
||||
redraw: scheduleDraw,
|
||||
redraw: () => {
|
||||
// 배치가 잡히면 미뤄 둔 화면 맞춤을 그때 수행한다.
|
||||
if (fitPending) fitToRoute();
|
||||
scheduleDraw();
|
||||
},
|
||||
});
|
||||
|
||||
function setCollapsed(collapsed: boolean): void {
|
||||
|
||||
@@ -504,3 +504,17 @@ export function basinIndexOfPipe(
|
||||
const basin = basins.find((item) => Math.abs(item.chainage_m - pipe.chainage_m) < 0.51);
|
||||
return basin ? basin.index : null;
|
||||
}
|
||||
|
||||
/** 관 개수·세부유역 수·종단 Z 출처 한 줄. 유역이 없으면 줄 자체를 감춘다. */
|
||||
export function summaryText(
|
||||
element: HTMLElement,
|
||||
pipeCount: number,
|
||||
basinCount: number,
|
||||
zSource: string,
|
||||
): void {
|
||||
element.textContent = L("B05_Drainage_Summary")
|
||||
.replace("{pipes}", String(pipeCount))
|
||||
.replace("{basins}", String(basinCount))
|
||||
.replace("{source}", zSource || "-");
|
||||
element.hidden = basinCount === 0;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@ export interface IrregularStation {
|
||||
/** 관 매설 지점이 구조물 목록에 실체화될 때 쓰는 이름. 향후 드롭다운으로 바꾼다. */
|
||||
export const PIPE_STRUCTURE_NAME = "배관";
|
||||
|
||||
/** 배관으로 볼 항목인가. 손으로 이름을 "배관"이라 적은 것도 관 지점 정본을 따르게 한다 —
|
||||
* 그렇지 않으면 [초기화]로 지워지지 않고 배수유역도와도 어긋난 채 남는다(2026-08-02 사용자 보고). */
|
||||
export function isPipeStation(station: { origin?: "user" | "pipe"; structure: string }): boolean {
|
||||
return station.origin === "pipe" || station.structure.trim() === PIPE_STRUCTURE_NAME;
|
||||
}
|
||||
|
||||
export interface IrregularStationsSection {
|
||||
root: HTMLElement;
|
||||
getStations: () => IrregularStation[];
|
||||
@@ -289,7 +295,7 @@ export function createIrregularStationsSection(
|
||||
setPipeStations(chainages) {
|
||||
// 배관 항목은 통째로 갈아 끼운다 — 정본은 배수유역도의 관 목록이다.
|
||||
for (let index = stations.length - 1; index >= 0; index -= 1) {
|
||||
if (stations[index].origin === "pipe") stations.splice(index, 1);
|
||||
if (isPipeStation(stations[index])) stations.splice(index, 1);
|
||||
}
|
||||
chainages.forEach((chainage) => {
|
||||
const { station, remainder } = splitChainage(chainage);
|
||||
|
||||
@@ -39,6 +39,7 @@ import { createRouteViewer } from "./B05_wf2_Route_UI_Viewer";
|
||||
import {
|
||||
irregularLabel,
|
||||
irregularStationId,
|
||||
isPipeStation,
|
||||
type IrregularStation,
|
||||
} from "./B05_wf2_Route_UI_IrregularStations";
|
||||
import {
|
||||
@@ -194,7 +195,7 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
// 정본은 배수유역도의 관 지점이며, 구조물 목록은 그것을 실체화한 것이다.
|
||||
onPipesChanged: (chainages) => panel.irregularStations.setPipeStations(chainages),
|
||||
onStructureMove: (from, to, station) => {
|
||||
if (station.origin === "pipe") {
|
||||
if (isPipeStation(station)) {
|
||||
// 배관은 관 지점 정본을 거쳐야 세부유역까지 함께 다시 나뉜다.
|
||||
profilePanel.drainage.movePipe(from, to);
|
||||
return;
|
||||
@@ -202,7 +203,7 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
panel.irregularStations.moveByChainage(from, to);
|
||||
},
|
||||
onStructureRemove: (station) => {
|
||||
if (station.origin === "pipe") {
|
||||
if (isPipeStation(station)) {
|
||||
profilePanel.drainage.removePipe(station.chainage_m);
|
||||
return;
|
||||
}
|
||||
@@ -416,7 +417,7 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
// 좌측 구조물 폼에서 "배관" 항목을 고쳤거나 지웠으면 배수유역도까지 따라가야 한다.
|
||||
// 관 목록이 그대로면 배수유역도가 아무 일도 하지 않으므로 되먹임 고리는 여기서 끊긴다.
|
||||
profilePanel.drainage.setPipeChainages(
|
||||
stations.filter((entry) => entry.origin === "pipe").map((entry) => entry.chainage_m),
|
||||
stations.filter(isPipeStation).map((entry) => entry.chainage_m),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import { createMapContextMenu } from "@ui/ui_template_context_menu";
|
||||
import type { IrregularStation } from "./B05_wf2_Route_UI_IrregularStations";
|
||||
import { isPipeStation, type IrregularStation } from "./B05_wf2_Route_UI_IrregularStations";
|
||||
|
||||
/** 선을 잡았다고 볼 좌우 여유(px). 선 자체는 얇아 그대로는 집기 어렵다. */
|
||||
const GRAB_SLACK_PX = 6;
|
||||
@@ -59,7 +59,7 @@ export function mountStructureMenu(host: HTMLElement, options: StructureLineOpti
|
||||
menu.open(localX, event.clientY - rect.top, [
|
||||
near
|
||||
? [
|
||||
near.station.origin === "pipe" ? "배관 삭제" : "구조물 삭제",
|
||||
isPipeStation(near.station) ? "배관 삭제" : "구조물 삭제",
|
||||
() => options.onRemove(near.station),
|
||||
]
|
||||
: ["배관 추가", () => options.onAddPipe(Number(chainage.toFixed(2)))],
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
* 구조물(`origin: "pipe"`)일 때만 붙는다 — 다른 구조물에는 대응하는 유역이 없다.
|
||||
* ========================================================================== */
|
||||
|
||||
import { irregularStationId, type IrregularStation } from "./B05_wf2_Route_UI_IrregularStations";
|
||||
import {
|
||||
irregularStationId,
|
||||
isPipeStation,
|
||||
type IrregularStation,
|
||||
} from "./B05_wf2_Route_UI_IrregularStations";
|
||||
|
||||
export interface SelectionSyncPorts {
|
||||
/** 현재 구조물 측점 목록(수동 + 배관 투영분). */
|
||||
@@ -50,7 +54,7 @@ export function createSelectionSync(ports: SelectionSyncPorts): SelectionSync {
|
||||
|
||||
function syncBasinHighlight(stationId: string | null): void {
|
||||
const station = irregularOf(stationId);
|
||||
ports.selectBasin(station?.origin === "pipe" ? station.chainage_m : null);
|
||||
ports.selectBasin(station && isPipeStation(station) ? station.chainage_m : null);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -77,8 +81,7 @@ export function createSelectionSync(ports: SelectionSyncPorts): SelectionSync {
|
||||
.stations()
|
||||
.find(
|
||||
(entry) =>
|
||||
entry.origin === "pipe" &&
|
||||
Math.abs(entry.chainage_m - chainageM) < SAME_CHAINAGE_M,
|
||||
isPipeStation(entry) && Math.abs(entry.chainage_m - chainageM) < SAME_CHAINAGE_M,
|
||||
);
|
||||
const id = matched ? irregularStationId(matched.id) : null;
|
||||
ports.setSyncing(true);
|
||||
|
||||
Reference in New Issue
Block a user