Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1
This commit is contained in:
@@ -18,8 +18,8 @@ import {
|
||||
type HeightAt,
|
||||
} from "./logic/D03_PlanAnalysis_Draw";
|
||||
import type { PlanNetwork, Vertex } from "./logic/D03_PlanAnalysis_Plan";
|
||||
import { nodeOff } from "./logic/D03_PlanAnalysis_Span";
|
||||
import { pointLabel } from "./logic/D03_PlanAnalysis_PointEdit";
|
||||
import { nodeOff } from "./logic/D03_PlanAnalysis_Span";
|
||||
import {
|
||||
planColors,
|
||||
PLAN_STATION_INTERVAL_M,
|
||||
|
||||
@@ -161,7 +161,13 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
yearStart: () => yearStart,
|
||||
commit,
|
||||
});
|
||||
const pieces = createPlanPieces({ network: () => network, edit });
|
||||
const pieces = createPlanPieces({
|
||||
network: () => network,
|
||||
edit,
|
||||
editable: () => editable,
|
||||
yearOptions: props.yearOptions,
|
||||
commit,
|
||||
});
|
||||
const measure = createPlanMeasure(
|
||||
() => z,
|
||||
() => network,
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
/* =============================================================================
|
||||
* D03_PlanAnalysis_UI_Pieces.ts
|
||||
* D03 좌측 「구역 · 연도」 목록 몸(PLAN 597) — 해 줄(같은 해) · 선 머리 줄(선) · 구간 줄(구간).
|
||||
* 속성 판(`UI_Props.ts`)에서 떼어냄 — 목록 = 보기 + 고르기.
|
||||
* D03 좌측 「구역 · 연도」 목록 몸(PLAN 597 · 요구 463 14) — 구역 = 그은 꺾은선 하나(본선 · 갈래).
|
||||
* 해 줄(같은 해) · 구역 줄(색 띠 = 지도 선과 같은 `routeColor(차례)` · 이름 칸 · 연도 고름 · 지움) ·
|
||||
* 구간 줄(측점 글 작게 · 연도 고름). 누름 = 고름 · 해 고침 · 이름 · 지움은 한 걸음(되돌리기 · 캐시 = 페이지).
|
||||
* 연도 고름은 속성 판과 같은 셈(`setLineYear` · `setPiecesYear`) · 보기만 계정 = 값만.
|
||||
* ========================================================================== */
|
||||
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createIconButton } from "@ui/ui_template_button";
|
||||
import { createInputField, createSelectField } from "@ui/ui_template_input";
|
||||
import { createListRow } from "@ui/ui_template_list_row";
|
||||
import { askConfirm } from "@ui/ui_template_modal";
|
||||
import { routeColor } from "@ui/ui_template_palette";
|
||||
import { stationLabel } from "../common_util/common_util_station";
|
||||
import { lineLength } from "./logic/D03_PlanAnalysis_Draw";
|
||||
import { lineNames, type PlanNetwork } from "./logic/D03_PlanAnalysis_Plan";
|
||||
import {
|
||||
lineNames,
|
||||
nameError,
|
||||
setLineYear,
|
||||
setPiecesYear,
|
||||
type PlanNetwork,
|
||||
} from "./logic/D03_PlanAnalysis_Plan";
|
||||
import { setLineName } from "./logic/D03_PlanAnalysis_PointEdit";
|
||||
import type { PlanEdit } from "./D03_PlanAnalysis_UI_Edit";
|
||||
import { PLAN_STATION_INTERVAL_M } from "./D03_PlanAnalysis_UI_Map";
|
||||
|
||||
@@ -17,6 +30,10 @@ const meters = (m: number): string => `${m.toFixed(1)} m`;
|
||||
export interface PlanPiecesParams {
|
||||
network: () => PlanNetwork;
|
||||
edit: PlanEdit;
|
||||
editable: () => boolean;
|
||||
/** 고를 해 — 사업연도 시작부터 열 해 + 조각에 있는 해. */
|
||||
yearOptions: () => Array<{ value: string; text: string }>;
|
||||
commit: (next: PlanNetwork) => void;
|
||||
}
|
||||
|
||||
export interface PlanPieces {
|
||||
@@ -25,79 +42,191 @@ export interface PlanPieces {
|
||||
render: () => void;
|
||||
}
|
||||
|
||||
/** 좌측 줄 — 누름 = 고름(div · 열쇠 Enter 도). */
|
||||
const pickRow = (row: HTMLElement, onPick: () => void): HTMLElement => {
|
||||
row.tabIndex = 0;
|
||||
row.setAttribute("role", "button");
|
||||
row.addEventListener("click", onPick);
|
||||
row.addEventListener("keydown", (event) => event.key === "Enter" && onPick());
|
||||
return row;
|
||||
};
|
||||
|
||||
export function createPlanPieces(params: PlanPiecesParams): PlanPieces {
|
||||
const { edit } = params;
|
||||
const list = document.createElement("div");
|
||||
list.className = "d03-plan__pieces";
|
||||
let shown = ""; // 지금 그린 틀 열쇠 — 같으면 값만 갈아 끼움(칸 초점 지키기)
|
||||
let syncs: Array<() => void> = [];
|
||||
|
||||
/** 줄 안 칸 눌림은 줄 고름으로 안 번지게. */
|
||||
const isControl = (event: Event): boolean =>
|
||||
!!(event.target as Element).closest("input, select, button, .ui-list-row__trailing");
|
||||
|
||||
/** 줄 하나 — 누름 = 고름(열쇠 Enter 도) · 줄 안 칸은 제외. */
|
||||
const pickable = (row: HTMLElement, onPick: () => void): HTMLElement => {
|
||||
row.tabIndex = 0;
|
||||
row.addEventListener("click", (event) => isControl(event) || onPick());
|
||||
row.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter" && event.target === row) onPick();
|
||||
});
|
||||
return row;
|
||||
};
|
||||
|
||||
/** 연도 고름 칸 — 값이 섞이면 「여러 해」. */
|
||||
function yearSelect(
|
||||
years: () => number[],
|
||||
label: string,
|
||||
pick: (year: number) => void,
|
||||
): { root: HTMLElement; sync: () => void } {
|
||||
const field = createSelectField({
|
||||
options: [],
|
||||
size: 24,
|
||||
ariaLabel: label,
|
||||
disabled: !params.editable(),
|
||||
onChange: (value) => value && pick(Number(value)),
|
||||
});
|
||||
const sync = (): void => {
|
||||
const set = new Set(years());
|
||||
const options = params.yearOptions();
|
||||
field.setOptions(
|
||||
set.size > 1 ? [{ value: "", text: "여러 해" }, ...options] : options,
|
||||
set.size > 1 ? "" : String([...set][0]),
|
||||
);
|
||||
};
|
||||
sync();
|
||||
return { root: field.root, sync };
|
||||
}
|
||||
|
||||
function render(): void {
|
||||
const network = params.network();
|
||||
const names = lineNames(network.lines);
|
||||
const picked = new Set(edit.selected()?.pieces ?? []);
|
||||
const scope = edit.selected()?.scope;
|
||||
const editable = params.editable();
|
||||
const key = JSON.stringify([
|
||||
network.lines.map((line) => line.id),
|
||||
network.pieces.map((piece) => [piece.id, piece.line]),
|
||||
[...picked],
|
||||
scope,
|
||||
editable,
|
||||
params.yearOptions().length,
|
||||
]);
|
||||
if (key === shown) {
|
||||
syncs.forEach((sync) => sync());
|
||||
return;
|
||||
}
|
||||
shown = key;
|
||||
syncs = [];
|
||||
const rows: HTMLElement[] = [];
|
||||
|
||||
const byYear = new Map<number, typeof network.pieces>();
|
||||
for (const piece of network.pieces)
|
||||
byYear.set(piece.year, [...(byYear.get(piece.year) ?? []), piece]);
|
||||
for (const [year, pieces] of [...byYear].sort(([a], [b]) => a - b)) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "d03-plan__piece d03-plan__piece-year";
|
||||
row.dataset.year = String(year);
|
||||
row.classList.toggle(
|
||||
"is-selected",
|
||||
scope === "year" && pieces.every((p) => picked.has(p.id)),
|
||||
);
|
||||
const length = pieces.reduce((sum, p) => sum + p.to_m - p.from_m, 0);
|
||||
row.append(createText(`${year}년 · 구간 ${pieces.length} · ${meters(length)}`, "muted"));
|
||||
rows.push(
|
||||
pickRow(row, () => {
|
||||
const row = createListRow({
|
||||
label: `${year}년`,
|
||||
sub: `구간 ${pieces.length} · ${meters(length)}`,
|
||||
size: 28,
|
||||
active: scope === "year" && pieces.every((p) => picked.has(p.id)),
|
||||
onClick: () => {
|
||||
edit.select([pieces[0].id]);
|
||||
edit.setScope("year");
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
row.dataset.year = String(year);
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
network.lines.forEach((line, index) => {
|
||||
const pieces = network.pieces.filter((piece) => piece.line === line.id);
|
||||
if (!pieces.length) return;
|
||||
const head = document.createElement("div");
|
||||
head.className = "d03-plan__piece d03-plan__piece-head";
|
||||
head.dataset.line = line.id;
|
||||
head.classList.toggle(
|
||||
"is-selected",
|
||||
scope === "line" && pieces.every((p) => picked.has(p.id)),
|
||||
const lineNow = (): (typeof network.lines)[number] =>
|
||||
params.network().lines.find((entry) => entry.id === line.id) ?? line;
|
||||
const piecesNow = (): typeof pieces =>
|
||||
params.network().pieces.filter((piece) => piece.line === line.id);
|
||||
|
||||
const nameField = createInputField({ size: 24, placeholder: names[index] });
|
||||
nameField.input.value = line.name ?? "";
|
||||
nameField.input.setAttribute("aria-label", "구역 이름");
|
||||
nameField.input.dataset.role = "line-name";
|
||||
nameField.input.disabled = !editable;
|
||||
nameField.input.addEventListener("input", () =>
|
||||
nameField.setError(nameError(nameField.input.value) ?? undefined),
|
||||
);
|
||||
head.append(createText(`${names[index]} · ${meters(lineLength(line.vertices))}`, "muted"));
|
||||
nameField.input.addEventListener("change", () => {
|
||||
const error = nameError(nameField.input.value);
|
||||
nameField.setError(error ?? undefined);
|
||||
const text = nameField.input.value.trim();
|
||||
if (!error && text !== (lineNow().name ?? ""))
|
||||
params.commit(setLineName(params.network(), line.id, text));
|
||||
});
|
||||
|
||||
const year = yearSelect(
|
||||
() => piecesNow().map((piece) => piece.year),
|
||||
"구역 연도",
|
||||
(value) => params.commit(setLineYear(params.network(), line.id, value)),
|
||||
);
|
||||
year.root.dataset.role = "line-year";
|
||||
const drop = createIconButton({
|
||||
icon: "trash",
|
||||
title: "구역 지움",
|
||||
size: 24,
|
||||
onClick: async () => {
|
||||
const ok = await askConfirm({
|
||||
message: `「${lineNames(params.network().lines)[index]}」 구역을 지울까요?`,
|
||||
okLabel: "지움",
|
||||
danger: true,
|
||||
});
|
||||
if (!ok) return;
|
||||
edit.select([piecesNow()[0].id]);
|
||||
edit.dropLine();
|
||||
},
|
||||
});
|
||||
drop.dataset.role = "line-drop";
|
||||
drop.hidden = !editable;
|
||||
const tail = document.createElement("span");
|
||||
tail.className = "d03-plan__line-tail";
|
||||
tail.append(year.root, drop);
|
||||
|
||||
const head = createListRow({
|
||||
label: "",
|
||||
sub: `구간 ${pieces.length} · ${meters(lineLength(line.vertices))}`,
|
||||
stripe: routeColor(index),
|
||||
size: 28,
|
||||
trailing: tail,
|
||||
active: scope === "line" && pieces.every((p) => picked.has(p.id)),
|
||||
});
|
||||
head.querySelector(".ui-list-row__label")?.replaceChildren(nameField.root);
|
||||
head.dataset.line = line.id;
|
||||
rows.push(
|
||||
pickRow(head, () => {
|
||||
pickable(head, () => {
|
||||
edit.select([pieces[0].id]);
|
||||
edit.setScope("line");
|
||||
}),
|
||||
);
|
||||
syncs.push(() => {
|
||||
year.sync();
|
||||
nameField.input.placeholder = lineNames(params.network().lines)[index] ?? "";
|
||||
if (document.activeElement !== nameField.input)
|
||||
nameField.input.value = lineNow().name ?? "";
|
||||
const sub = head.querySelector(".ui-list-row__sub");
|
||||
if (sub)
|
||||
sub.textContent = `구간 ${piecesNow().length} · ${meters(lineLength(lineNow().vertices))}`;
|
||||
});
|
||||
|
||||
for (const piece of pieces) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "d03-plan__piece";
|
||||
row.classList.toggle("is-selected", picked.has(piece.id));
|
||||
row.dataset.piece = piece.id;
|
||||
row.append(
|
||||
createText(
|
||||
`${station(piece.from_m)} ~ ${station(piece.to_m)} (${meters(piece.to_m - piece.from_m)})`,
|
||||
"muted",
|
||||
),
|
||||
createText(`${piece.year}년`, "muted"),
|
||||
const pieceNow = (): (typeof pieces)[number] =>
|
||||
params.network().pieces.find((entry) => entry.id === piece.id) ?? piece;
|
||||
const pieceYear = yearSelect(
|
||||
() => [pieceNow().year],
|
||||
"구간 연도",
|
||||
(value) => params.commit(setPiecesYear(params.network(), [piece.id], value)),
|
||||
);
|
||||
rows.push(pickRow(row, () => edit.select([piece.id])));
|
||||
const row = createListRow({
|
||||
label: `${station(piece.from_m)} ~ ${station(piece.to_m)}`,
|
||||
sub: meters(piece.to_m - piece.from_m),
|
||||
size: 28,
|
||||
trailing: pieceYear.root,
|
||||
active: picked.has(piece.id),
|
||||
});
|
||||
row.classList.add("d03-plan__piece-row");
|
||||
row.dataset.piece = piece.id;
|
||||
rows.push(pickable(row, () => edit.select([piece.id])));
|
||||
syncs.push(pieceYear.sync);
|
||||
}
|
||||
});
|
||||
if (!rows.length) rows.push(createText("선을 그으면 구역이 생깁니다.", "muted"));
|
||||
list.replaceChildren(...rows);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,18 @@ import { createInputField } from "@ui/ui_template_input";
|
||||
import { createMapContextMenu, type MapContextMenuItem } from "@ui/ui_template_context_menu";
|
||||
import { createListRow } from "@ui/ui_template_list_row";
|
||||
import { pointColor } from "@ui/ui_template_palette";
|
||||
import { moveNodesAt, type PlanNetwork, type Vertex } from "./logic/D03_PlanAnalysis_Plan";
|
||||
import {
|
||||
moveNodesAt,
|
||||
nameError,
|
||||
type PlanNetwork,
|
||||
type Vertex,
|
||||
} from "./logic/D03_PlanAnalysis_Plan";
|
||||
import {
|
||||
addPoint,
|
||||
MAX_POINTS,
|
||||
movePointTo,
|
||||
parseLonLat,
|
||||
pointLabel,
|
||||
pointNameError,
|
||||
renamePoint,
|
||||
type PointsState,
|
||||
} from "./logic/D03_PlanAnalysis_PointEdit";
|
||||
@@ -228,11 +232,11 @@ export function createPlanPoints(params: PlanPointsParams): PlanPoints {
|
||||
set(latField, ll ? ll[1].toFixed(6) : "");
|
||||
};
|
||||
nameField.input.addEventListener("input", () =>
|
||||
nameField.setError(pointNameError(nameField.input.value.trim()) ?? undefined),
|
||||
nameField.setError(nameError(nameField.input.value.trim()) ?? undefined),
|
||||
);
|
||||
nameField.input.addEventListener("change", () => {
|
||||
const text = nameField.input.value.trim();
|
||||
const error = pointNameError(text);
|
||||
const error = nameError(text);
|
||||
nameField.setError(error ?? undefined);
|
||||
if (!error && text !== (params.names()[i] ?? "")) commitState(renamePoint(state(), i, text));
|
||||
});
|
||||
|
||||
@@ -106,28 +106,6 @@
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.d03-plan__piece-head {
|
||||
margin: 6px 0 0;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.d03-plan__piece {
|
||||
display: flex;
|
||||
gap: var(--spacing-8);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.d03-plan__piece > .ui-text {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.d03-plan__piece > .ui-field {
|
||||
flex: 0 0 6.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 지형 받기 범위 미리보기 — 카카오 지도 */
|
||||
.d03-plan__extent > .ui-text {
|
||||
margin: 0 0 6px;
|
||||
@@ -152,12 +130,6 @@
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* 580-3 고른 선 · 조각 줄 밝힘 */
|
||||
.d03-plan__piece.is-selected {
|
||||
background: var(--color-accent-soft);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
/* 585-2 고른 구간 속성 판 — 도구줄 아래 오른쪽 · 몸 스크롤 · 구간 줄 = 고스트 단추 */
|
||||
.d03-plan__props {
|
||||
top: calc(var(--spacing-8) * 6);
|
||||
@@ -188,14 +160,6 @@
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.d03-plan__piece[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.d03-plan__piece-year {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 597 지점 칸 — [추가] 머리 · 펼친 줄(이름 · 경도 · 위도) */
|
||||
.d03-plan__points-head {
|
||||
display: flex;
|
||||
@@ -231,3 +195,22 @@
|
||||
.d03-plan__draw-fields .ui-field {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 597 구역 · 연도 목록 — 구역 줄(이름 칸 · 연도 고름 · 지움) · 구간 줄(측점 글 작게) */
|
||||
.d03-plan__line-tail {
|
||||
display: flex;
|
||||
gap: var(--spacing-4);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.d03-plan__pieces .ui-list-row__label > .ui-field {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.d03-plan__piece-row {
|
||||
padding-left: calc(var(--spacing-8) * 2);
|
||||
}
|
||||
|
||||
.d03-plan__piece-row .ui-list-row__label {
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/* =============================================================================
|
||||
* D03_PlanAnalysis_PointEdit.ts
|
||||
* D03 지점 칸 손 고침 계산(PLAN 597-2 · 요구 463 7 · 8) — 화면 · fetch 없음(logic/).
|
||||
* 이름 = 20 자 안 · 파일 이름에 못 쓰는 글자(`/ \ : * ? " < > |`) 막음 · 빈 값 = 「지점 N」 보임.
|
||||
* 이름 규칙 = `Plan.ts` 의 `nameError`(20 자 · 파일 이름 못 쓰는 글자 · 서버 400 짝) · 빈 값 = 「지점 N」 보임.
|
||||
* 좌표 = 경위도 손 고침 → 작업 좌표(TM) · 그 자리 노선 노드도 같이(`moveNodesAt`).
|
||||
* ⚠ 이름 한도 · 글자 = `Plan.ts` 의 `NAME_MAX` · `nameError`(594-1)와 같은 규칙 — 그쪽이 들어오면 거기서 가져옴.
|
||||
* ========================================================================== */
|
||||
|
||||
import { moveNodesAt, type PlanNetwork, type Vertex } from "./D03_PlanAnalysis_Plan";
|
||||
@@ -11,15 +10,6 @@ import { lonLatToTm, type TmParams } from "./D03_PlanAnalysis_Tm";
|
||||
|
||||
/** 지점 상한 — 서버 `MAX_POINTS` 와 같음. */
|
||||
export const MAX_POINTS = 40;
|
||||
export const POINT_NAME_MAX = 20;
|
||||
const BAD_CHARS = /[\\/:*?"<>|]/;
|
||||
|
||||
/** 이름 까닭 글 — 쓸 수 있으면 null. */
|
||||
export function pointNameError(name: string): string | null {
|
||||
if (name.length > POINT_NAME_MAX) return `이름은 ${POINT_NAME_MAX} 자까지`;
|
||||
if (BAD_CHARS.test(name)) return '이름에 / \\ : * ? " < > | 는 못 씁니다';
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 보이는 이름 — 빈 값 = 「지점 N」. */
|
||||
export function pointLabel(names: string[], index: number): string {
|
||||
@@ -74,3 +64,14 @@ export function movePointTo(
|
||||
network: moveNodesAt(state.network, from, to),
|
||||
};
|
||||
}
|
||||
|
||||
/** 선 이름 바꿈(597-3) — 빈 값 = 차례 이름으로 돌아감(`name` 비움). */
|
||||
export function setLineName(network: PlanNetwork, lineId: string, name: string): PlanNetwork {
|
||||
const text = name.trim();
|
||||
return {
|
||||
...network,
|
||||
lines: network.lines.map((line) =>
|
||||
line.id === lineId ? { ...line, name: text || null } : line,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ const tm = tmx.tmParams(5187);
|
||||
let n = plan.newLine(plan.emptyNetwork(), [100, 0]);
|
||||
n = plan.normalizePieces(plan.appendVertex(n, 0, [200, 0]), Y);
|
||||
const state = { points: [[100, 0], [500, 500]], names: ["", "가"], network: n };
|
||||
const named = pe.setLineName(pe.setLineName(n, n.lines[0].id, ' 본선A '), 'none', 'x');
|
||||
const cleared = pe.setLineName(named, n.lines[0].id, ' ');
|
||||
const added = pe.addPoint(state, [7, 8]);
|
||||
const full = { points: Array.from({ length: 40 }, (_, i) => [i, 0]), names: [], network: n };
|
||||
const [lon, lat] = tmx.tmToLonLat(tm, 100, 0);
|
||||
@@ -35,10 +37,11 @@ const moved = pe.movePointTo(state, 0, tm, lon + 0.001, lat);
|
||||
const back = tmx.tmToLonLat(tm, ...moved.points[0]);
|
||||
writeFileSync(process.argv[2], JSON.stringify({
|
||||
errors: ["", "가나다", "a/b", "a" + String.fromCharCode(92) + "b", "a:b", "a*b", "a?b", 'a"b', "a<b", "a>b", "a|b", "x".repeat(20), "x".repeat(21)]
|
||||
.map((s) => pe.pointNameError(s)),
|
||||
.map((s) => plan.nameError(s)),
|
||||
labels: [pe.pointLabel(["", " ", "가"], 0), pe.pointLabel(["", " ", "가"], 1), pe.pointLabel(["", " ", "가"], 2), pe.pointLabel([], 3)],
|
||||
parse: [pe.parseLonLat("128.5", "36.9"), pe.parseLonLat("", "36.9"), pe.parseLonLat("128.5", "abc"), pe.parseLonLat("181", "0"), pe.parseLonLat("0", "-91")],
|
||||
added: [added.points, added.names, added.network === n],
|
||||
lineName: [named.lines[0].name, plan.lineNames(named.lines), cleared.lines[0].name, plan.lineNames(cleared.lines)],
|
||||
full: pe.addPoint(full, [1, 1]) === full,
|
||||
renamed: [pe.renamePoint(state, 1, " 나 ").names, pe.renamePoint({ ...state, names: [] }, 1, "다").names],
|
||||
moved: { points: moved.points, lines: moved.network.lines.map((l) => l.vertices), back, expect: [lon + 0.001, lat], untouched: moved.points[1], names: moved.names },
|
||||
@@ -75,7 +78,7 @@ def got(tmp_path_factory: pytest.TempPathFactory) -> dict:
|
||||
def test_name_rule(got: dict) -> None:
|
||||
ok, bad = got["errors"][:2], got["errors"][2:11]
|
||||
assert ok == [None, None] # 빈 이름 · 한글 = 됨
|
||||
assert all(msg and "못 씁니다" in msg for msg in bad) # 파일 이름 못 쓰는 글자 아홉
|
||||
assert all(msg and "쓸 수 없습니다" in msg for msg in bad) # 파일 이름 못 쓰는 글자 아홉
|
||||
assert got["errors"][11] is None # 20 자
|
||||
assert "20 자" in got["errors"][12] # 21 자 = 막음
|
||||
|
||||
@@ -109,3 +112,9 @@ def test_move_by_lonlat_drags_node(got: dict) -> None:
|
||||
assert moved["lines"][0][0] == moved["points"][0] # 그 자리 노선 노드도 같이
|
||||
assert moved["lines"][0][1] == [200, 0] # 다른 노드는 그대로
|
||||
assert moved["untouched"] == [500, 500] and moved["names"] == ["", "가"]
|
||||
|
||||
|
||||
def test_line_name(got: dict) -> None:
|
||||
named, names, cleared, back = got["lineName"]
|
||||
assert named == "본선A" and names == ["본선A"] # 앞뒤 빈칸 뺌 · 이름 우선
|
||||
assert cleared is None and back == ["본선"] # 빈 값 = 차례 이름으로
|
||||
|
||||
Reference in New Issue
Block a user