feat(D 예정노선): 노드 판 — 방식 · 값 고침(앞 노드 기준 다시 놓음 · 뒤 노드 그대로 · 어긋남 ⚠) · [지점으로] · [다시 긋기] 이음 (PLAN 598-4 · 요구 463-11 · 464)

- logic/Select placeNode — 지점 자리 노드면 지점 같이 · 첫 노드 = 까닭 글
- UI_Props 노드 판 = 599-1 방식 칸(createDrawKindFields) · 방식 고름 = 바로 · 수 칸 = Enter · 칸 떠날 때
- 판 · 메뉴 [다시 긋기] = draw.respan(599-3)
- 시험 598_1 placeNode 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 15:02:48 +09:00
co-authored by Claude Opus 5.5
parent b44066da61
commit ed7f7fa674
5 changed files with 120 additions and 12 deletions
+55 -7
View File
@@ -12,7 +12,7 @@
* 좌측 「구역 · 연도」 목록 = `UI_Pieces.ts`(페이지가 붙임).
* ========================================================================== */
import { createButton } from "@ui/ui_template_elements";
import { createButton, showToast } from "@ui/ui_template_elements";
import { createFloatPanel } from "@ui/ui_template_float_panel";
import { createSelectField } from "@ui/ui_template_input";
import { createKv, type KvRow } from "@ui/ui_template_kv";
@@ -26,8 +26,20 @@ import {
type PlanPiece,
type Vertex,
} from "./logic/D03_PlanAnalysis_Plan";
import { segmentPath, segmentPieces, segmentRange } from "./logic/D03_PlanAnalysis_Select";
import {
placeNode,
segmentPath,
segmentPieces,
segmentRange,
} from "./logic/D03_PlanAnalysis_Select";
import { nodeOff, nodeRecord } from "./logic/D03_PlanAnalysis_Span";
import { tmParams, tmToLonLat } from "./logic/D03_PlanAnalysis_Tm";
import {
createDrawKindFields,
DRAW_DEFAULT_LENGTH_M,
DRAW_DEFAULT_SLOPE_PCT,
type DrawKindValue,
} from "./D03_PlanAnalysis_UI_DrawPanel";
import type { PlanEdit, PlanSelection } from "./D03_PlanAnalysis_UI_Edit";
import { PLAN_STATION_INTERVAL_M } from "./D03_PlanAnalysis_UI_Map";
import { selectionLength } from "./D03_PlanAnalysis_UI_Select";
@@ -50,8 +62,10 @@ export interface PlanPropsParams {
/** 지점(노드 판 [지점으로] — 이미 있으면 숨김). */
points: () => Vertex[];
addPoint: (point: Vertex) => void;
/** [다시 긋기](599 · 없으면 단추 없음) — 구역 id · 앞 노드 · 뒤 노드. */
respan?: (line: string, from: number, to: number) => boolean;
/** 노드 판 방식 · 값 바꿈 — 노드 · 그 자리 지점 한 걸음. */
commitPoints: (points: Vertex[], next: PlanNetwork) => void;
/** [다시 긋기](599-3) — 구역 id · 앞 노드 · 뒤 노드. */
respan: (line: string, from: number, to: number) => boolean;
}
export interface PlanProps {
@@ -125,6 +139,36 @@ export function createPlanProps(params: PlanPropsParams): PlanProps {
return rows;
}
/** 노드 방식 · 값(598-4 · 599-1 부품) — 방식 고름 = 바로 · 수 칸 = Enter · 칸 떠날 때 · 앞 노드 기준 다시 놓음. */
function nodeKind(line: number, vertex: number): HTMLElement {
const record = params.network().lines[line].nodes?.[vertex] ?? null;
let value: DrawKindValue = {
kind: record?.kind ?? "free",
lengthM: record?.length_m ?? DRAW_DEFAULT_LENGTH_M,
slopePct: record?.slope_pct ?? DRAW_DEFAULT_SLOPE_PCT,
};
const apply = (): void => {
const network = params.network();
const next = nodeRecord(value.kind, value.lengthM, value.slopePct);
const out = placeNode(network, params.points(), line, vertex, next, params.heightAt());
if (typeof out === "string") return void showToast(out, "info");
if (JSON.stringify(out.network.lines) !== JSON.stringify(network.lines))
params.commitPoints(out.points, out.network);
};
const fields = createDrawKindFields({
value,
onChange: (next) => {
const kindChanged = next.kind !== value.kind;
value = next;
if (kindChanged) apply();
},
});
fields.root.addEventListener("change", apply);
fields.root.dataset.role = "props-node-kind";
fields.setDisabled(!params.editable());
return fields.root;
}
/** 고름 꼴마다 — 머리 글 · 몸 · 단추. */
function body(
picked: PlanSelection,
@@ -159,13 +203,17 @@ export function createPlanProps(params: PlanPropsParams): PlanProps {
{ label: "앞 선 기울기", value: percent(groundSlopePct(a, b, z)) },
);
}
const entry = network.lines.find((l) => l.id === line)!;
if (nodeOff(entry, vertex, z)) rows.push({ label: "기록", value: "⚠ 지금 자리와 어긋남" });
const parts: HTMLElement[] = [createKv(rows)];
if (vertex > 0) parts.push(nodeKind(network.lines.indexOf(entry), vertex));
const toPoint = button("지점으로", "props-to-point", () =>
params.addPoint([point[0], point[1]]),
);
toPoint.hidden = params.points().some((p) => samePoint(p, point));
return {
title: `${nameOf(line)} · 노드 ${vertex + 1}`,
parts: [createKv(rows)],
parts,
actions: [
button("노드 지움", "props-drop-node", () => edit.dropNode(picked.node!)),
toPoint,
@@ -191,10 +239,10 @@ export function createPlanProps(params: PlanPropsParams): PlanProps {
}
rows.push({ label: "연도", value: years.map((y) => `${y}년`).join(" · ") });
const actions = [button("선 지움", "props-drop-segment", edit.dropSelected)];
if (single && params.respan)
if (single)
actions.unshift(
button("다시 긋기", "props-respan", () =>
params.respan!(single.line, single.index, single.index + 1),
params.respan(single.line, single.index, single.index + 1),
),
);
return {