Files
Aislo/B05_Profile/B05_Profile_UI_RouteEdit_RecPreview.ts
T
eomsangdonandClaude Sonnet 5.5 1288e21e55 feat(756-1): 추천 선 진하게 · 스냅 한 아이콘 · 추천노선 여럿 고르기 · 켠 동안 등고선 띠 넓힘
- 예정노선 수정 켬에도 추천 선 불투명 그대로(Render)
- 스냅 + 스냅 간격 = 고르기 메뉴 하나(측점 No. · 10 m · 끔) · 도구줄 표 snap-step 줄 걷음
- 추천노선 = 여럿 고르기(공용 부품 multi · 줄 색 점 PickMenuItem.color · 끔 줄 없음) · 선 색 --draw-map-rec-1~4 · 예정노선 가이드 선은 켬끔 한 줄
- 켠 동안 등고선 띠 = LAS 는 자르지 않음 · 도엽은 계획선 + 예정노선으로 넓힘(캔버스 · GL · 도엽 다시 받기)
- 시험 624_2 · 634_2 · 626_1 · 636_2 · 390_4 · b05_routeedit_initial 을 새 꼴로 고침 · 새 시험 756_1

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:58:28 +09:00

113 lines
4.7 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_RouteEdit_RecPreview.ts
* 평면 노선 추천 미리보기(PLAN 626-1 · 요구 530 · 결정 D1 · D7 · D8) — 평면 도구줄 아이콘 하나 + 아래 판
* (영선 · 토공 최소 · 기울기 최소 · 비중 계산) · 여럿 고름(589-12 · 줄마다 색 · 하나도 안 고르면 꺼짐) ·
* 선만(위 캔버스 · 노선 아래 그림 · 집기 없음).
* 선 = 노선 추천 저장본(`B05_Profile/route_recommend/` · 지금 설계 지형 · 체인이 미리 셈) 그대로 — 다시 셈 · 쓰기 없음 ·
* 저장본 없으면(409) 알림 · 끔. 옛 추천 모달 · 좌측 「노선 추천」 칸 · [적용](`as_initial`) 은 걷음(서버 길 · 시험은 남김).
* 부르는 곳 = `_Chrome`(도구줄 `recommend` 칸) · `_RouteEdit`(그리기 `overStations` 앞).
* 메뉴 · 선 그리기 = 공용 `A00_Common/map/map_route_preview`(636-2 · 예정노선 가이드 선과 같이 씀).
* ========================================================================== */
import { showToast } from "@ui/ui_template_elements";
import { themeColor } from "@ui/ui_template_palette";
import { createRoutePreviewMenu, paintRoutePreview } from "../A00_Common/map/map_route_preview";
import type { AnalysisTerrain } from "./B05_Profile_Api_Replan";
import { fetchRouteInitial, type RouteInitialLogic } from "./B05_Profile_Api_RouteInitial";
import { LOGIC_LABEL } from "./B05_Profile_UI_RouteEdit_Initial_Model";
type Vertex = [number, number];
/** 드롭다운 넷(요구 530 · 「예상노선 추적」 = 초기값이라 뺌). */
const MODES = [
"gradeline",
"earthwork",
"gentle",
"weighted",
] as const satisfies readonly RouteInitialLogic[];
/** 갈래별 선 색(`--draw-map-rec-1~4`) · 점선 꼴(색만으로 못 가르는 눈도 가름). */
const REC_VARS = ["--draw-map-rec-1", "--draw-map-rec-2", "--draw-map-rec-3", "--draw-map-rec-4"];
const REC_FALLBACK = ["#ffe94d", "#d58bff", "#ff7ab8", "#a3e635"];
const REC_COLORS = REC_VARS.map((name, index) => `var(${name}, ${REC_FALLBACK[index]})`);
const REC_DASHES = [
[10, 6],
[3, 6],
[14, 4, 3, 4],
[6, 3],
];
export interface RecPreview {
root: HTMLElement;
/** 노선 그리기 안(돌림 안) — 고른 선들(갈래마다 색). */
draw: (context: CanvasRenderingContext2D, toScreen: (vertex: Vertex) => [number, number]) => void;
/** 검증 창구 — 고른 갈래들 · 보이는 선 점 수(갈래별). */
state: () => { modes: string[]; points: Record<string, number> };
}
export function createRecPreview(opts: {
projectId: string;
/** 지금 설계 지형(저장본 고름 · D7). */
terrain: () => AnalysisTerrain;
redraw: () => void;
}): RecPreview {
const lines = new Map<string, Vertex[]>();
const shown = new Map<RouteInitialLogic, Vertex[]>();
const turns = new Map<RouteInitialLogic, number>();
const menu = createRoutePreviewMenu({
items: MODES.map((mode, index) => ({
value: mode,
label: LOGIC_LABEL[mode],
color: REC_COLORS[index],
})),
onPick: (mode) => void pick(mode as RouteInitialLogic),
});
async function pick(mode: RouteInitialLogic): Promise<void> {
const mine = (turns.get(mode) ?? 0) + 1;
turns.set(mode, mine);
shown.delete(mode);
opts.redraw();
if (!menu.values().includes(mode)) return;
const key = `${mode}|${opts.terrain()}`;
try {
if (!lines.has(key)) {
const body = (await fetchRouteInitial(opts.projectId, {
mode,
with_profile: true,
terrain: opts.terrain(),
})) as { planned?: number[][] };
const planned = (body.planned ?? []).map(([x, y]) => [x, y] as Vertex);
if (planned.length < 2) throw new Error("추천 선이 비었음");
lines.set(key, planned);
}
if (mine !== turns.get(mode)) return;
shown.set(mode, lines.get(key)!);
opts.redraw();
} catch (error) {
if (mine !== turns.get(mode)) return;
menu.setItem(mode, { checked: false });
const text = error instanceof Error ? error.message : String(error);
showToast(`추천 노선 「${LOGIC_LABEL[mode]}」 못 봄 — ${text}`, "info");
}
}
return {
root: menu.root,
draw(context, toScreen) {
MODES.forEach((mode, index) => {
const line = shown.get(mode);
if (line)
paintRoutePreview(context, line, toScreen, {
color: themeColor(REC_VARS[index], REC_FALLBACK[index]),
dash: REC_DASHES[index],
});
});
},
state: () => ({
modes: menu.values(),
points: Object.fromEntries([...shown].map(([mode, line]) => [mode, line.length])),
}),
};
}