diff --git a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Pieces.ts b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Pieces.ts new file mode 100644 index 000000000..9cca359f9 --- /dev/null +++ b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Pieces.ts @@ -0,0 +1,105 @@ +/* ============================================================================= + * D03_PlanAnalysis_UI_Pieces.ts + * D03 좌측 「구역 · 연도」 목록 몸(PLAN 597) — 해 줄(같은 해) · 선 머리 줄(선) · 구간 줄(구간). + * 속성 판(`UI_Props.ts`)에서 떼어냄 — 목록 = 보기 + 고르기. + * ========================================================================== */ + +import { createText } from "@ui/ui_template_text"; +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 type { PlanEdit } from "./D03_PlanAnalysis_UI_Edit"; +import { PLAN_STATION_INTERVAL_M } from "./D03_PlanAnalysis_UI_Map"; + +const station = (m: number): string => stationLabel(m, PLAN_STATION_INTERVAL_M, 1); +const meters = (m: number): string => `${m.toFixed(1)} m`; + +export interface PlanPiecesParams { + network: () => PlanNetwork; + edit: PlanEdit; +} + +export interface PlanPieces { + /** 좌측 「구역 · 연도」 목록 몸. */ + list: HTMLElement; + 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"; + + 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 rows: HTMLElement[] = []; + const byYear = new Map(); + 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, () => { + edit.select([pieces[0].id]); + edit.setScope("year"); + }), + ); + } + 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)), + ); + head.append(createText(`${names[index]} · ${meters(lineLength(line.vertices))}`, "muted")); + rows.push( + pickRow(head, () => { + edit.select([pieces[0].id]); + edit.setScope("line"); + }), + ); + 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"), + ); + rows.push(pickRow(row, () => edit.select([piece.id]))); + } + }); + if (!rows.length) rows.push(createText("선을 그으면 구역이 생깁니다.", "muted")); + list.replaceChildren(...rows); + } + + return { list, render }; +} diff --git a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Props.ts b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Props.ts index 98eaede41..12da5c3cc 100644 --- a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Props.ts +++ b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Props.ts @@ -8,19 +8,18 @@ * [방향 바꾸기] [갈래 시점 지정] [선 지움](선 하나일 때) · [마크 지움](마크 고름) · 보기만 계정 = 값만. * 측점 방향(585-3 · 요구 446 3) = 사용자가 정함 — 본선 = [방향 바꾸기] · 다른 선에 닿은 끝 = [갈래 시점 지정] * (그 점이 시점 · 꼬리에 닿은 머리 = 한 선으로 이음) · 자동 판정 없음. - * 좌측 목록 = 보기 + 고르기만(해 고침은 속성 판 한 곳) — 해 줄(같은 해) · 선 머리 줄(선) · 구간 줄(구간). + * 좌측 「구역 · 연도」 목록 = `UI_Pieces.ts`. * ========================================================================== */ import { createButton } from "@ui/ui_template_elements"; import { createFloatPanel } from "@ui/ui_template_float_panel"; import { createSelectField } from "@ui/ui_template_input"; import { createSegment } from "@ui/ui_template_segment"; -import { createText } from "@ui/ui_template_text"; import { stationLabel } from "../common_util/common_util_station"; -import { lineLength } from "./logic/D03_PlanAnalysis_Draw"; import { lineNames, setPiecesYear, type PlanNetwork } from "./logic/D03_PlanAnalysis_Plan"; import type { PlanEdit, PlanScope } from "./D03_PlanAnalysis_UI_Edit"; import { PLAN_STATION_INTERVAL_M } from "./D03_PlanAnalysis_UI_Map"; +import { createPlanPieces } from "./D03_PlanAnalysis_UI_Pieces"; /** 조각 연도 고를 수 — 사업연도 시작부터. */ const YEAR_SPAN = 10; @@ -57,8 +56,7 @@ export function createPlanProps(params: PlanPropsParams): PlanProps { float.root.classList.add("d03-plan__props"); float.root.dataset.role = "plan-props"; float.root.hidden = true; - const list = document.createElement("div"); - list.className = "d03-plan__pieces"; + const pieceList = createPlanPieces({ network: params.network, edit }); function yearOptions(): Array<{ value: string; text: string }> { const start = params.yearStart(); @@ -167,83 +165,12 @@ export function createPlanProps(params: PlanPropsParams): PlanProps { float.body.replaceChildren(scope.root, year.root, rows, actions); } - /** 좌측 줄 — 누름 = 고름(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; - }; - - function renderList(): void { - const network = params.network(); - const names = lineNames(network.lines); - const picked = new Set(edit.selected()?.pieces ?? []); - const scope = edit.selected()?.scope; - const rows: HTMLElement[] = []; - const byYear = new Map(); - 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, () => { - edit.select([pieces[0].id]); - edit.setScope("year"); - }), - ); - } - 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)), - ); - head.append(createText(`${names[index]} · ${meters(lineLength(line.vertices))}`, "muted")); - rows.push( - pickRow(head, () => { - edit.select([pieces[0].id]); - edit.setScope("line"); - }), - ); - 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"), - ); - rows.push(pickRow(row, () => edit.select([piece.id]))); - } - }); - if (!rows.length) rows.push(createText("선을 그으면 구역이 생깁니다.", "muted")); - list.replaceChildren(...rows); - } - return { panel: float.root, - list, + list: pieceList.list, render() { renderPanel(); - renderList(); + pieceList.render(); }, yearOptions, };