1
2
3
4
5
6
7
8import { MapView } from "mapgpu";
9import { RasterTileLayer } from "mapgpu/layers";
10import { RenderEngine } from "mapgpu/render";
11
12import type { RunResultObject } from "@/components/examples/ExampleCanvas";
13
14type EventRow = { at: number; name: string; body: string };
15
16const MAX_ROWS = 5;
17
18function fmtLonLat(p: [number, number] | null): string {
19 if (!p) return "off-map";
20 return `${p[0].toFixed(3)}, ${p[1].toFixed(3)}`;
21}
22
23function fmtTime(ms: number): string {
24 const d = new Date(ms);
25 return `${String(d.getSeconds()).padStart(2, "0")}.${String(d.getMilliseconds()).padStart(3, "0")}`;
26}
27
28export async function run(container: HTMLElement): Promise<RunResultObject> {
29 const view = new MapView({
30 container,
31 renderEngine: new RenderEngine(),
32 mode: "2d",
33 center: [32.866, 39.925],
34 zoom: 6,
35 minZoom: 2,
36 maxZoom: 18,
37 backgroundColor: "transparent",
38 });
39
40 view.map.add(
41 new RasterTileLayer({
42 id: "osm",
43 urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
44 maxZoom: 19,
45 attribution: "© OpenStreetMap contributors",
46 }),
47 );
48
49
50
51 const panel = document.createElement("div");
52 panel.style.cssText = `
53 position: absolute; top: 12px; right: 12px; z-index: 1000;
54 min-width: min(260px, calc(100% - 24px)); max-width: min(320px, calc(100% - 24px));
55 padding: 10px 12px;
56 background: rgba(7, 9, 12, 0.82); backdrop-filter: blur(6px);
57 border: 1px solid rgba(255, 92, 26, 0.35); border-radius: 6px;
58 font-family: ui-monospace, monospace; font-size: 11px;
59 color: #cdd5e0; line-height: 1.5;
60 pointer-events: none;
61 `;
62 const header = document.createElement("div");
63 header.textContent = "event log · last 5";
64 header.style.cssText =
65 "color:#ff5c1a; letter-spacing:.12em; text-transform:uppercase; font-size:9px; margin-bottom:6px;";
66 const body = document.createElement("div");
67 panel.appendChild(header);
68 panel.appendChild(body);
69 container.appendChild(panel);
70
71 const rows: EventRow[] = [];
72 const render = () => {
73 body.innerHTML = rows
74 .map(
75 (r) =>
76 `<div><span style="color:#5b6475">${fmtTime(r.at)}</span> ` +
77 `<span style="color:#82aaff">${r.name}</span> ` +
78 `<span style="color:#e7ecf2">${r.body}</span></div>`,
79 )
80 .join("");
81 };
82 const push = (name: string, body: string) => {
83 rows.unshift({ at: Date.now(), name, body });
84 if (rows.length > MAX_ROWS) rows.length = MAX_ROWS;
85 render();
86 };
87
88
89
90
91 const onClick = (data: unknown) => {
92 const e = data as { screenX: number; screenY: number; mapPoint: [number, number] | null };
93 push("click", fmtLonLat(e.mapPoint));
94 };
95 const onDblClick = (data: unknown) => {
96 const e = data as { mapPoint: [number, number] | null };
97 push("dblclick", fmtLonLat(e.mapPoint));
98 };
99 const onViewChange = (data: unknown) => {
100 const e = data as { center: [number, number]; zoom: number; mode: "2d" | "3d" };
101 push("view-change", `z=${e.zoom.toFixed(2)} · ${fmtLonLat(e.center)} · ${e.mode}`);
102 };
103 const onZoomEnd = (data: unknown) => {
104 const e = data as { zoom: number };
105 push("zoomend", `z=${e.zoom.toFixed(2)}`);
106 };
107 const onMoveEnd = (data: unknown) => {
108 const e = data as { center: [number, number] };
109 push("moveend", fmtLonLat(e.center));
110 };
111
112 view.on("click", onClick);
113 view.on("dblclick", onDblClick);
114 view.on("view-change", onViewChange);
115 view.on("zoomend", onZoomEnd);
116 view.on("moveend", onMoveEnd);
117
118 push("ready", "subscribed to 5 events");
119
120 await view.when();
121
122 return {
123 dispose: () => {
124 view.off("click", onClick);
125 view.off("dblclick", onDblClick);
126 view.off("view-change", onViewChange);
127 view.off("zoomend", onZoomEnd);
128 view.off("moveend", onMoveEnd);
129 panel.remove();
130 view.destroy();
131 },
132 controls: [
133 {
134 kind: "button",
135 id: "clear",
136 label: "Clear log",
137 onClick: () => {
138 rows.length = 0;
139 render();
140 },
141 },
142 ],
143 };
144}