- 새 logic/B05_Profile_PlanContext.ts — 흐릴 세트(내 것 빼고) · 이음점(남 세트 끝) · 받을 때 이어진 이음점 · 벗어남 안내 · 이름표 자리 - 새 B05_Profile_UI_PlanContext.ts — plan-context 실시간 읽기 · 옅은 선 + 세트 이름표 · 누름 칸 없음(보기만) - 키트 · 긋기 · 고르기 snaps 덧칸 — 긋기 · 노드 끌기가 이음점에 붙기만 - 기능 표 others 줄(B05 보기만) - 시험 test_635_4(6) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
3.7 KiB
TypeScript
98 lines
3.7 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_PlanContext.ts
|
|
* B05 흐린 다른 세트 겹(PLAN 635-4 · 요구 546 · 547 · 결정 5 · 6 · 7) — 같은 예정노선의 다른 세트(다른 해 · 같은 해 다른 업체).
|
|
* 자료 = `GET /api/projects/{B}/plan-context` 실시간 읽기(보기 전용 · 파일 안 만듦 · 업체 이름 없음).
|
|
* 그림 = 옅은 선 + 세트 이름표 「2028-1」 · 누름 · 고르기 · 고침 없음(겹에 누름 받는 칸 없음 · 기능 표 `others` = 보기만).
|
|
* 붙기 = 남 세트 끝(이음점)을 긋기 · 노드 끌기 붙을 자리로 내놓음(`joints`) · 고정 안 함 → 벗어나면 `note` 한 줄.
|
|
* 셈 = `logic/B05_Profile_PlanContext.ts`.
|
|
* ========================================================================== */
|
|
|
|
import { themeColor } from "@ui/ui_template_palette";
|
|
import { planLabel, strokeCased } from "../D03_PlanAnalysis/D03_PlanAnalysis_UI_DrawPaint";
|
|
import type { ScreenMap } from "../D03_PlanAnalysis/D03_PlanAnalysis_UI_Map";
|
|
import { requestJson } from "./B05_Profile_Api_Replan";
|
|
import {
|
|
jointGaps,
|
|
jointNote,
|
|
jointPoints,
|
|
labelPoint,
|
|
linkedJoints,
|
|
otherSets,
|
|
type PlanContext,
|
|
type Vertex,
|
|
} from "./logic/B05_Profile_PlanContext";
|
|
|
|
/** 흐린 세트 불투명. */
|
|
const FAINT_ALPHA = 0.45;
|
|
|
|
export interface PlanContextLayer {
|
|
/** 문맥 다시 읽음(연결 없음 · 실패 = 빈 겹). */
|
|
load: () => Promise<void>;
|
|
/** 붙을 자리(남 세트 끝). */
|
|
joints: () => Vertex[];
|
|
/** 지금 예정노선이 받을 때 이어졌던 이음점에서 벗어났으면 안내 한 줄. */
|
|
note: (line: ReadonlyArray<Vertex>) => string | null;
|
|
paint: (context: CanvasRenderingContext2D, map: Pick<ScreenMap, "toScreen" | "upright">) => void;
|
|
/** 검증 창구 — 흐린 세트 열쇠 · 꼭짓점 수 · 이음점. */
|
|
probe: () => {
|
|
sets: Array<{ key: string; points: number }>;
|
|
joints: Vertex[];
|
|
mine: string | null;
|
|
};
|
|
}
|
|
|
|
export function createPlanContextLayer(projectId: string, redraw: () => void): PlanContextLayer {
|
|
let context: PlanContext | null = null;
|
|
|
|
return {
|
|
async load() {
|
|
try {
|
|
context = await requestJson<PlanContext>(
|
|
`/projects/${projectId}/plan-context`,
|
|
{ method: "GET" },
|
|
30000,
|
|
);
|
|
} catch {
|
|
context = null; // 보기 전용 겹 — 못 읽으면 안 그림
|
|
}
|
|
redraw();
|
|
},
|
|
joints: () => jointPoints(context).map((joint) => joint.point),
|
|
note: (line) => jointNote(jointGaps(line, linkedJoints(context))),
|
|
paint(ctx, map) {
|
|
const sets = otherSets(context);
|
|
if (!sets.length) return;
|
|
const color = themeColor("--draw-text-secondary", "#9ca3af");
|
|
ctx.save();
|
|
ctx.globalAlpha = FAINT_ALPHA;
|
|
for (const set of sets)
|
|
for (const line of set.lines) {
|
|
if (line.length < 2) continue;
|
|
ctx.beginPath();
|
|
line.forEach((point, i) => {
|
|
const [sx, sy] = map.toScreen(point);
|
|
if (i) ctx.lineTo(sx, sy);
|
|
else ctx.moveTo(sx, sy);
|
|
});
|
|
strokeCased(ctx, color, 3);
|
|
}
|
|
ctx.globalAlpha = Math.min(1, FAINT_ALPHA + 0.3);
|
|
for (const set of sets) {
|
|
const at = labelPoint(set);
|
|
if (!at) continue;
|
|
const [sx, sy] = map.toScreen(at);
|
|
planLabel(ctx, map, sx, sy - 12, set.key, "600 12px");
|
|
}
|
|
ctx.restore();
|
|
},
|
|
probe: () => ({
|
|
sets: otherSets(context).map((set) => ({
|
|
key: set.key,
|
|
points: set.lines.reduce((sum, line) => sum + line.length, 0),
|
|
})),
|
|
joints: jointPoints(context).map((joint) => joint.point),
|
|
mine: context?.mine ?? null,
|
|
}),
|
|
};
|
|
}
|