fix(530-3): D03 화면 검증 흠 — 저장된 갈래 밝힘 · 움직인 끌기만 지점 옮김 · 횡단 꾸밈 · 진행 못 읽으면 알림
- 카드 고름 = 서버 이은 누가(저장 갈래) 위에 캐시 고름 - 지점 누름(손 떨림 안)은 끌기 아님 · 집은 자리 차 지킴 - 횡단 판 B05 RouteEdit 꾸밈 css · 끌기 끝나면 옛 횡단 비움 - 진행 요청 실패 = 알림 뒤 화면 되돌림(서버 일은 그대로) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,18 +4,28 @@
|
||||
* `generate_sections` · 격자 = 그 연도 DEM 평활본) · 그림 = B05 `buildCrossSvg` 지반만.
|
||||
* ========================================================================== */
|
||||
|
||||
import "../B05_Profile/B05_Profile_UI_Style_RouteEdit.css";
|
||||
import { createWorkflowPanelHandle } from "@ui/ui_template_overlay";
|
||||
import { stationLabel } from "../common_util/common_util_station";
|
||||
import type { DtmGrid } from "../common_util/common_util_surface_sampler";
|
||||
import { buildCrossSvg } from "../B05_Profile/B05_Profile_UI_RouteEdit_Cross_Draw";
|
||||
import { sectionsGround } from "../B05_Profile/logic/B05_Profile_Sections_Ground";
|
||||
import {
|
||||
sectionsGround,
|
||||
type StationGround,
|
||||
} from "../B05_Profile/logic/B05_Profile_Sections_Ground";
|
||||
import type { Vertex } from "./logic/D03_PlanAnalysis_Plan";
|
||||
import { PLAN_STATION_INTERVAL_M } from "./D03_PlanAnalysis_UI_Map";
|
||||
|
||||
export interface PlanCross {
|
||||
root: HTMLElement;
|
||||
/** `chainage` = 등록 누가 · `localM` = 그 연도 노선 위 누가. */
|
||||
show: (chainage: number, year: number, line: Vertex[], localM: number, grid: DtmGrid) => void;
|
||||
/** `chainage` = 등록 누가 · `localM` = 그 연도 노선 위 누가 · 돌려줌 = 그린 측점 지반(못 읽으면 null). */
|
||||
show: (
|
||||
chainage: number,
|
||||
year: number,
|
||||
line: Vertex[],
|
||||
localM: number,
|
||||
grid: DtmGrid,
|
||||
) => StationGround | null;
|
||||
clear: (text: string) => void;
|
||||
}
|
||||
|
||||
@@ -48,7 +58,10 @@ export function createPlanCross(): PlanCross {
|
||||
clear,
|
||||
show(chainage, year, line, localM, grid) {
|
||||
const station = sectionsGround(line, [localM], grid).stations[0];
|
||||
if (!station) return clear("이 측점의 지반을 읽지 못했습니다.");
|
||||
if (!station) {
|
||||
clear("이 측점의 지반을 읽지 못했습니다.");
|
||||
return null;
|
||||
}
|
||||
const name = stationLabel(chainage, PLAN_STATION_INTERVAL_M, 1);
|
||||
const z = station.center_z === null ? "—" : `${station.center_z.toFixed(2)} m`;
|
||||
head.textContent = `${year}년 · ${name} · 중심 지반고 ${z}`;
|
||||
@@ -71,6 +84,7 @@ export function createPlanCross(): PlanCross {
|
||||
true,
|
||||
);
|
||||
body.replaceChildren(plot.svg);
|
||||
return station;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -289,7 +289,8 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
|
||||
};
|
||||
|
||||
// 지점 끌기 — 지도 옮기기보다 먼저 듣고 지점을 집었으면 옮기기에 안 넘김
|
||||
let dragging: { index: number; id: number } | null = null;
|
||||
// 집은 자리와 지점 가운데 차(px)를 지킴 · 손 떨림(CLICK_SLOP_PX) 안이면 끌기 아님(누름)
|
||||
let dragging: { index: number; id: number; dx: number; dy: number; moved: boolean } | null = null;
|
||||
let press: [number, number] | null = null;
|
||||
canvas.addEventListener("pointerdown", (event) => {
|
||||
press = [event.clientX, event.clientY];
|
||||
@@ -297,7 +298,9 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
|
||||
const index = nodeAt(...at(event));
|
||||
if (index < 0) return;
|
||||
event.stopImmediatePropagation();
|
||||
dragging = { index, id: event.pointerId };
|
||||
const [px, py] = at(event);
|
||||
const [sx, sy] = toScreen(scene.nodes[index]);
|
||||
dragging = { index, id: event.pointerId, dx: sx - px, dy: sy - py, moved: false };
|
||||
canvas.setPointerCapture(event.pointerId);
|
||||
canvas.style.cursor = "grabbing";
|
||||
});
|
||||
@@ -307,14 +310,19 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
|
||||
canvas.style.cursor = nodeAt(...at(event)) >= 0 ? "grab" : "";
|
||||
return;
|
||||
}
|
||||
events.onDrag(dragging.index, screenToMetric(meta, view, ...at(event)));
|
||||
if (!press) return;
|
||||
const far = Math.hypot(event.clientX - press[0], event.clientY - press[1]) > CLICK_SLOP_PX;
|
||||
if (!dragging.moved && !far) return;
|
||||
dragging.moved = true;
|
||||
const [px, py] = at(event);
|
||||
events.onDrag(dragging.index, screenToMetric(meta, view, px + dragging.dx, py + dragging.dy));
|
||||
});
|
||||
const endDrag = (event: PointerEvent): void => {
|
||||
if (!dragging || event.pointerId !== dragging.id) return;
|
||||
const index = dragging.index;
|
||||
const { index, moved } = dragging;
|
||||
dragging = null;
|
||||
canvas.style.cursor = "";
|
||||
events.onDragEnd(index);
|
||||
if (moved) events.onDragEnd(index);
|
||||
};
|
||||
canvas.addEventListener("pointerup", endDrag);
|
||||
canvas.addEventListener("pointercancel", endDrag);
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
} from "../B04_PreProcess/B04_PreProcess_UI_MapRender";
|
||||
import { prepareLayer } from "../B04_PreProcess/logic/B04_PreProcess_UI_MapRender_Prepare";
|
||||
import { stationLabel } from "../common_util/common_util_station";
|
||||
import type { StationGround } from "../B05_Profile/logic/B05_Profile_Sections_Ground";
|
||||
import {
|
||||
fetchPlanAnalysis,
|
||||
fetchPlanPoints,
|
||||
@@ -139,6 +140,7 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
let selected: number | null = null;
|
||||
let editable = false;
|
||||
let busy: "terrain" | "analyze" | null = null;
|
||||
let lastCross: StationGround | null = null;
|
||||
|
||||
const moved = (): boolean => !sameNodes(nodes, savedNodes);
|
||||
const sentPoints = () => (moved() ? pointsOf(years, nodes) : null);
|
||||
@@ -171,7 +173,9 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
routes: {},
|
||||
},
|
||||
);
|
||||
rows = chainRows(results, choices, startM);
|
||||
// 고름 = 저장된 갈래(서버 이은 누가) 위에 캐시 고름
|
||||
const saved = Object.fromEntries((analysis?.chain ?? []).map((row) => [row.year, row.mode]));
|
||||
rows = chainRows(results, { ...saved, ...choices }, startM);
|
||||
const line = joinedLine(results, rows);
|
||||
const others: Vertex[][] = results.flatMap((result, i) => {
|
||||
const routes = PLAN_MODES.filter((mode) => mode !== rows[i].mode)
|
||||
@@ -264,6 +268,9 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
i === index - 1 || i === index ? { ...entry, analysis: null } : entry,
|
||||
),
|
||||
};
|
||||
selected = null;
|
||||
lastCross = null;
|
||||
cross.clear("지점을 옮겼습니다 — [분석] 뒤 측점을 누르세요.");
|
||||
keepDraft();
|
||||
refresh();
|
||||
}
|
||||
@@ -275,7 +282,7 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
const result = results.find((entry) => entry.year === row.year)!;
|
||||
selected = chainage;
|
||||
map.update({ selected });
|
||||
cross.show(
|
||||
lastCross = cross.show(
|
||||
chainage,
|
||||
row.year,
|
||||
result.routes[row.mode].planned ?? [],
|
||||
@@ -356,9 +363,14 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
}
|
||||
await sleep(POLL_MS);
|
||||
}
|
||||
} catch (error) {
|
||||
// 진행을 못 읽음(세션 끊김 등) — 서버 일은 그대로 돎 · 다시 열면 이어 지켜봄
|
||||
showToast(`진행을 읽지 못했습니다 — ${errorText(error)}`, "error");
|
||||
return null;
|
||||
} finally {
|
||||
busy = null;
|
||||
bar.hidden = true;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,6 +461,7 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
rows: () => rows,
|
||||
nodes: () => nodes,
|
||||
pick: onStation,
|
||||
cross: () => lastCross,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user