Files
Aislo/B05_Profile/B05_Profile_UI_RouteEdit_Footprint.ts
T
eomsangdonandClaude Opus 5.5 281c71b55b refactor(logic): 348-1 순수 계산 TS 49 파일 <페이지>/logic/ 로 옮김 — 이름 앞머리 그대로 · 동작 고침 없음
- 옮김: B04 2 · B05 23 · B06 11 · A00_Common/design 13 (9,299 줄) · 이미 logic/ 에 있던 16 파일 그대로
- 안 옮김: 묶음 입구(package.json build:*) · 파이썬 · 순수지만 계산 아닌 8 (보기 결선 · 콜백 · API 타입 · 갈아끼움 알림판)
- import 266 줄(142 파일) · 시험 · 주석 · 짝 표시 문자열 경로 고침 · B05 · B06 CLAUDE.md 짝 줄 logic/
- 시험 도우미 새 경로: 가짜 require 열쇠 · B05()/B06() 부름 · 형제 import 자리 · tsc 묶어 돌리는 시험 · 정규식 · rglob
- design_view_cross 줄 한도 시험 685 → 688 (긴 import 경로 줄바꿈 3줄)
- 확인: typecheck 0 · 서버 묶음 넷(corridor · server-calc · b07-extent · b07-note) 다시 지어 코드 같음(//#region 경로 주석만 다름) · 관련 시험 270 파일 — 남은 실패 19 는 옮기기 전과 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
2026-10-16 13:44:50 +09:00

94 lines
3.3 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_RouteEdit_Footprint.ts
* 노선 편집 평면에 **평면 발자국**(도로 끝 · 비탈 끝)을 그린다(PLAN 346-2).
*
* 값 = 가게 키 `footprint`(계산 묶음 footprint 단계가 씀) · 선 갈래 = `logic/_Footprint_Strokes`.
* 가게 값이 바뀌거나 [발자국] 단추를 누르면 마지막 장면을 한 번 다시 그린다(프레임마다 한 번) —
* 편집기 본체가 그릴 때마다 `drawFootprint` 가 다시 그릴 길을 새로 받아 둔다.
* 켬끔은 이 페이지 동안만(기본 켬 · 계정 설정 아님).
* ========================================================================== */
import { draftProjectId, useDesignStore } from "../A00_Common/design/design_store";
import type { Vertex } from "./logic/B05_Profile_UI_RouteEdit_Curve";
import {
type FootprintStrokeKind,
type FootprintValue,
footprintStrokes,
} from "./logic/B05_Profile_Footprint_Strokes";
import "./B05_Profile_UI_Style_RouteEdit_Footprint.css";
/** 갈래 → 색 토큰 · 대체 색. */
const STROKE_STYLE: Record<FootprintStrokeKind, [string, string]> = {
road: ["--b05-footprint-road", "#475569"],
cut: ["--b05-footprint-cut", "#dc2626"],
fill: ["--b05-footprint-fill", "#2563eb"],
slope: ["--b05-footprint-road", "#475569"],
};
const STROKE_WIDTH = 1.4;
let shown = true;
let redraw: (() => void) | null = null;
let frame = 0;
let listening: { projectId: string; stop: () => void } | null = null;
function scheduleRedraw(): void {
if (frame) return;
frame = requestAnimationFrame(() => {
frame = 0;
redraw?.();
});
}
function listen(projectId: string): void {
if (listening?.projectId === projectId) return;
listening?.stop();
const stop = useDesignStore(projectId, null).subscribe("footprint", scheduleRedraw);
listening = { projectId, stop };
}
/** [발자국] 단추 — 누르면 켬끔 · 다시 그림. */
export function bindFootprintToggle(button: HTMLButtonElement): void {
const paint = (): void => {
button.classList.toggle("is-active", shown);
button.setAttribute("aria-pressed", String(shown));
};
paint();
button.addEventListener("click", () => {
shown = !shown;
paint();
scheduleRedraw();
});
}
/** 발자국을 그린다(회전 · 등고선 위 · 노선 아래 자리에서 부름). `again` = 장면을 다시 그리는 길. */
export function drawFootprint(
context: CanvasRenderingContext2D,
toScreen: (vertex: Vertex) => [number, number],
style: CSSStyleDeclaration,
again: () => void,
): void {
redraw = again;
const projectId = draftProjectId();
listen(projectId);
if (!shown) return;
const strokes = footprintStrokes(
useDesignStore(projectId, null).get("footprint") as FootprintValue | null,
);
if (!strokes.length) return;
context.save();
context.setLineDash([]);
context.lineWidth = STROKE_WIDTH;
for (const { kind, points } of strokes) {
const [token, fallback] = STROKE_STYLE[kind];
context.strokeStyle = style.getPropertyValue(token).trim() || fallback;
context.beginPath();
points.forEach((vertex, index) => {
const [x, y] = toScreen(vertex);
if (index === 0) context.moveTo(x, y);
else context.lineTo(x, y);
});
context.stroke();
}
context.restore();
}