@mailwoman/map-tui 9.1.1 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +59 -0
  2. package/lib/browser.ts +525 -0
  3. package/lib/cli-args.ts +233 -0
  4. package/lib/cli.ts +141 -0
  5. package/{frame.ts → lib/frame.ts} +33 -6
  6. package/lib/index.ts +13 -0
  7. package/lib/input.ts +288 -0
  8. package/{mercator.ts → lib/mercator.ts} +20 -0
  9. package/{mvt.ts → lib/mvt.ts} +2 -2
  10. package/{raster.ts → lib/raster.ts} +1 -1
  11. package/{renderer.ts → lib/renderer.ts} +86 -36
  12. package/lib/style.ts +115 -0
  13. package/{tile-source.ts → lib/tile-source.ts} +76 -20
  14. package/out/browser.d.ts +126 -0
  15. package/out/browser.d.ts.map +1 -0
  16. package/out/browser.js +373 -0
  17. package/out/browser.js.map +1 -0
  18. package/out/cli-args.d.ts +47 -0
  19. package/out/cli-args.d.ts.map +1 -0
  20. package/out/cli-args.js +171 -0
  21. package/out/cli-args.js.map +1 -0
  22. package/out/cli.d.ts +8 -0
  23. package/out/cli.d.ts.map +1 -0
  24. package/out/cli.js +119 -0
  25. package/out/cli.js.map +1 -0
  26. package/out/frame.d.ts +20 -2
  27. package/out/frame.d.ts.map +1 -1
  28. package/out/frame.js +28 -3
  29. package/out/frame.js.map +1 -1
  30. package/out/index.d.ts +7 -7
  31. package/out/index.d.ts.map +1 -1
  32. package/out/index.js +7 -7
  33. package/out/index.js.map +1 -1
  34. package/out/input.d.ts +93 -0
  35. package/out/input.d.ts.map +1 -0
  36. package/out/input.js +214 -0
  37. package/out/input.js.map +1 -0
  38. package/out/mercator.d.ts +13 -0
  39. package/out/mercator.d.ts.map +1 -1
  40. package/out/mercator.js +16 -0
  41. package/out/mercator.js.map +1 -1
  42. package/out/mvt.d.ts.map +1 -1
  43. package/out/mvt.js +2 -2
  44. package/out/mvt.js.map +1 -1
  45. package/out/raster.d.ts +1 -1
  46. package/out/raster.d.ts.map +1 -1
  47. package/out/raster.js.map +1 -1
  48. package/out/renderer.d.ts +11 -14
  49. package/out/renderer.d.ts.map +1 -1
  50. package/out/renderer.js +46 -26
  51. package/out/renderer.js.map +1 -1
  52. package/out/style.d.ts +22 -11
  53. package/out/style.d.ts.map +1 -1
  54. package/out/style.js +49 -4
  55. package/out/style.js.map +1 -1
  56. package/out/tile-source.d.ts +25 -4
  57. package/out/tile-source.d.ts.map +1 -1
  58. package/out/tile-source.js +46 -14
  59. package/out/tile-source.js.map +1 -1
  60. package/out/tsconfig.test.tsbuildinfo +1 -1
  61. package/package.json +105 -6
  62. package/index.ts +0 -13
  63. package/out/tsconfig.tsbuildinfo +0 -1
  64. package/style.ts +0 -62
@@ -12,16 +12,27 @@
12
12
  * same viewport don't re-decode.
13
13
  */
14
14
 
15
- import { type FileHandle, open } from "node:fs/promises"
16
-
15
+ import { type FileHandle, open } from "@mailwoman/core/fs/readers"
16
+ import { Parser } from "htmlparser2"
17
17
  import { PMTiles, type RangeResponse, type Source } from "pmtiles"
18
18
 
19
- import { type DecodedLayer, decodeMVT } from "./mvt.ts"
19
+ import { type DecodedLayer, decodeMVT } from "#mvt"
20
20
 
21
21
  export interface DecodedTile {
22
22
  layers: DecodedLayer[]
23
23
  }
24
24
 
25
+ /**
26
+ * What a renderer needs from a tile archive — the read surface of {@link TileSource}, separated so a renderer can be
27
+ * driven by any provider: a single archive, a stub in tests, or a composite over several archives.
28
+ */
29
+ export interface TileProvider {
30
+ readonly minZoom: number
31
+ readonly maxZoom: number
32
+ readonly attribution: string
33
+ getTile(z: number, x: number, y: number): Promise<DecodedTile | null>
34
+ }
35
+
25
36
  class FilePMTilesSource implements Source {
26
37
  private readonly path: string
27
38
  private readonly handle: FileHandle
@@ -45,6 +56,39 @@ class FilePMTilesSource implements Source {
45
56
 
46
57
  const TILE_CACHE_LIMIT = 64
47
58
 
59
+ /**
60
+ * Plain-text attribution out of archive metadata (HTML tags stripped, entities decoded); empty string when absent.
61
+ */
62
+ export function readAttribution(metadata: unknown): string {
63
+ if (
64
+ typeof metadata !== "object" ||
65
+ metadata === null ||
66
+ !("attribution" in metadata) ||
67
+ typeof (metadata as { attribution: unknown }).attribution !== "string"
68
+ ) {
69
+ return ""
70
+ }
71
+
72
+ return htmlText((metadata as { attribution: string }).attribution)
73
+ }
74
+
75
+ /**
76
+ * Plain text out of an HTML fragment via `htmlparser2`'s event parser — a hand scan misreads `<` inside attribute
77
+ * values and unclosed tags, and the parser's own entity decoding covers the full named set a metadata field can carry.
78
+ * Local rather than `@mailwoman/core`'s `htmlToText`: this package stays standalone by design, and one attribution
79
+ * string does not price core's shipped data into every consumer.
80
+ */
81
+ function htmlText(html: string): string {
82
+ let text = ""
83
+
84
+ const parser = new Parser({ ontext: (chunk) => (text += chunk) }, { decodeEntities: true })
85
+
86
+ parser.write(html)
87
+ parser.end()
88
+
89
+ return text.replaceAll(/\s+/gu, " ").trim()
90
+ }
91
+
48
92
  /**
49
93
  * A single LRU cache slot. Wrapping the decoded tile in an object lets `getTile` tell "cached and known absent" (`{
50
94
  * tile: null }`) apart from "not yet cached" (no entry in the Map) using plain presence, with no comparison against
@@ -54,7 +98,7 @@ interface CacheEntry {
54
98
  tile: DecodedTile | null
55
99
  }
56
100
 
57
- export class TileSource {
101
+ export class TileSource implements TileProvider, AsyncDisposable {
58
102
  readonly minZoom: number
59
103
  readonly maxZoom: number
60
104
 
@@ -63,11 +107,20 @@ export class TileSource {
63
107
  */
64
108
  readonly attribution: string
65
109
 
66
- private readonly handle: FileHandle
110
+ /**
111
+ * Null for HTTP sources — fetch connections have no handle to hold or close.
112
+ */
113
+ private readonly handle: FileHandle | null
67
114
  private readonly pmtiles: PMTiles
68
115
  private readonly cache = new Map<string, CacheEntry>()
69
116
 
70
- private constructor(handle: FileHandle, pmtiles: PMTiles, minZoom: number, maxZoom: number, attribution: string) {
117
+ private constructor(
118
+ handle: FileHandle | null,
119
+ pmtiles: PMTiles,
120
+ minZoom: number,
121
+ maxZoom: number,
122
+ attribution: string
123
+ ) {
71
124
  this.handle = handle
72
125
  this.pmtiles = pmtiles
73
126
  this.minZoom = minZoom
@@ -75,21 +128,24 @@ export class TileSource {
75
128
  this.attribution = attribution
76
129
  }
77
130
 
78
- static async open(path: string): Promise<TileSource> {
79
- const handle = await open(path, "r")
80
- const pmtiles = new PMTiles(new FilePMTilesSource(path, handle))
131
+ /**
132
+ * Opens a local `.pmtiles` path, or an `http(s)://` URL read via range requests — a hosted archive needs no tile
133
+ * server, only a host honoring `Range` (any static file server or object store does).
134
+ */
135
+ static async open(pathOrURL: string): Promise<TileSource> {
136
+ if (/^https?:\/\//u.test(pathOrURL)) {
137
+ const pmtiles = new PMTiles(pathOrURL)
138
+ const [header, metadata] = await Promise.all([pmtiles.getHeader(), pmtiles.getMetadata()])
81
139
 
82
- const [header, metadata] = await Promise.all([pmtiles.getHeader(), pmtiles.getMetadata()])
140
+ return new TileSource(null, pmtiles, header.minZoom, header.maxZoom, readAttribution(metadata))
141
+ }
142
+
143
+ const handle = await open(pathOrURL, "r")
144
+ const pmtiles = new PMTiles(new FilePMTilesSource(pathOrURL, handle))
83
145
 
84
- const attribution =
85
- typeof metadata === "object" &&
86
- metadata !== null &&
87
- "attribution" in metadata &&
88
- typeof (metadata as { attribution: unknown }).attribution === "string"
89
- ? (metadata as { attribution: string }).attribution.replaceAll(/<[^>]+>/gu, "").trim()
90
- : ""
146
+ const [header, metadata] = await Promise.all([pmtiles.getHeader(), pmtiles.getMetadata()])
91
147
 
92
- return new TileSource(handle, pmtiles, header.minZoom, header.maxZoom, attribution)
148
+ return new TileSource(handle, pmtiles, header.minZoom, header.maxZoom, readAttribution(metadata))
93
149
  }
94
150
 
95
151
  /**
@@ -124,7 +180,7 @@ export class TileSource {
124
180
  return tile
125
181
  }
126
182
 
127
- async close(): Promise<void> {
128
- await this.handle.close()
183
+ async [Symbol.asyncDispose](): Promise<void> {
184
+ await this.handle?.[Symbol.asyncDispose]()
129
185
  }
130
186
  }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+ import type { TileSource } from "#tile-source";
7
+ /**
8
+ * The subset of a readable stream the browser drives. Structural so `process.stdin` satisfies it without the app being
9
+ * welded to it — the same reasoning asciify applies to its own output type.
10
+ */
11
+ export interface BrowserInput {
12
+ setRawMode?(mode: boolean): unknown;
13
+ setEncoding(encoding: "utf8"): unknown;
14
+ resume(): unknown;
15
+ pause(): unknown;
16
+ on(event: "data", listener: (chunk: string) => void): unknown;
17
+ off(event: "data", listener: (chunk: string) => void): unknown;
18
+ }
19
+ /**
20
+ * The subset of a writable terminal the browser drives.
21
+ */
22
+ export interface BrowserOutput {
23
+ write(chunk: string): unknown;
24
+ columns?: number | undefined;
25
+ rows?: number | undefined;
26
+ on(event: "resize", listener: () => void): unknown;
27
+ off(event: "resize", listener: () => void): unknown;
28
+ }
29
+ export interface MapBrowserOptions {
30
+ source: TileSource;
31
+ input: BrowserInput;
32
+ output: BrowserOutput;
33
+ lat: number;
34
+ lon: number;
35
+ zoom: number;
36
+ }
37
+ export declare class MapBrowser {
38
+ private readonly source;
39
+ private readonly renderer;
40
+ private readonly input;
41
+ private readonly output;
42
+ private readonly terminal;
43
+ private centerLon;
44
+ private centerLat;
45
+ private zoom;
46
+ private columns;
47
+ private paneRows;
48
+ private drag;
49
+ private renderInFlight;
50
+ private renderQueued;
51
+ private started;
52
+ private restored;
53
+ private error;
54
+ private resolveExit;
55
+ /**
56
+ * The trailing bytes of the last chunk that could not be decoded yet — a sequence the kernel split across two reads.
57
+ * Threading it back through `decodeInputChunk` is what keeps a split mouse report from being read as an Esc keypress
58
+ * (which used to quit); the decoder itself stays pure.
59
+ */
60
+ private pendingInput;
61
+ private readonly onData;
62
+ private readonly onResize;
63
+ constructor(options: MapBrowserOptions);
64
+ /**
65
+ * Runs until the user quits, resolving with the process exit code (0 for a normal quit, 130 for Ctrl+C). The terminal
66
+ * is restored before this resolves.
67
+ */
68
+ run(): Promise<number>;
69
+ /**
70
+ * Asks the browser to exit with a code. Safe to call from a signal handler, and a no-op once an exit is already under
71
+ * way.
72
+ */
73
+ requestExit(code: number): void;
74
+ /**
75
+ * Enters the alternate screen and takes over input. Paired with {@link restore}.
76
+ */
77
+ start(): void;
78
+ /**
79
+ * Puts the terminal back exactly as it was found. Idempotent: the normal exit path, a signal handler and a
80
+ * process-level `exit` hook may each call it.
81
+ */
82
+ restore(): void;
83
+ private handleInput;
84
+ private panBySteps;
85
+ /**
86
+ * Moves the center by a cell delta, through world pixels so the step is the same distance on screen at every latitude
87
+ * — the naive degrees-per-keypress version crawls at the equator and sprints near the poles.
88
+ */
89
+ private panByCells;
90
+ private setCenter;
91
+ /**
92
+ * Longitude/latitude at the center of a pane cell.
93
+ */
94
+ private cellToLonLat;
95
+ /**
96
+ * Zooms one or more whole levels. With an anchor cell (the wheel's pointer), the center shifts so whatever was under
97
+ * the pointer stays under it; without one, the pane center holds.
98
+ */
99
+ private zoomBy;
100
+ private withinPane;
101
+ private beginDrag;
102
+ /**
103
+ * Pans relative to where the drag STARTED, not the previous motion report. Accumulating per-report deltas would
104
+ * drift, since each one is rounded to a whole cell.
105
+ */
106
+ private continueDrag;
107
+ /**
108
+ * A press and release with no motion between them is a click, which centers the map — mapscii's behavior, and the
109
+ * reason centering waits for the release rather than acting on the press.
110
+ */
111
+ private endDrag;
112
+ /**
113
+ * Reads the terminal's size and re-sizes the pane around the status bar.
114
+ */
115
+ private measure;
116
+ /**
117
+ * Requests a frame. Renders never overlap: a request arriving mid-render is coalesced into one more pass, so a held
118
+ * arrow key queues a single redraw rather than a backlog of them.
119
+ */
120
+ private scheduleRender;
121
+ private renderLoop;
122
+ private renderOnce;
123
+ private statusText;
124
+ private drawStatusBar;
125
+ }
126
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../lib/browser.ts"],"names":[],"mappings":"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"}
package/out/browser.js ADDED
@@ -0,0 +1,373 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+ /**
7
+ * The interactive map browser — a full-screen, alternate-screen terminal app over `MapRenderer`.
8
+ *
9
+ * `MapBrowser` owns exactly three things the frame-first library deliberately does not: terminal MODE (alternate
10
+ * screen, hidden cursor, raw stdin, mouse reporting), viewport STATE (center, zoom, drag anchor), and the write path.
11
+ * Frames still come from `MapRenderer.renderFrame` as values; `blitFrame` copies one into an `AsciifyTerminal`, whose
12
+ * damage diff decides what actually goes down the wire.
13
+ *
14
+ * The pane is the terminal minus its bottom row, which is the status bar. `AsciifyTerminal` is told that size and never
15
+ * addresses a cell outside it, so the two writers never fight over a cell.
16
+ *
17
+ * Every mode change made in {@link MapBrowser.start} is undone by {@link MapBrowser.restore}, which is idempotent so a
18
+ * signal handler, an `exit` hook and the normal path can all call it. A terminal left in raw mode with mouse reporting
19
+ * on is not a recoverable shell, so restore is the one operation that must survive any exit path.
20
+ */
21
+ import { errorMessage } from "@mailwoman/core/errors/schema";
22
+ import { clamp } from "@mailwoman/core/numeric";
23
+ import { AsciifyTerminal, cursorTo, SGR_RESET } from "@sister.software/asciify/tui";
24
+ import { blitFrame } from "#frame";
25
+ import { decodeInputChunk, MOUSE_DISABLE, MOUSE_ENABLE } from "#input";
26
+ import { lonLatToWorldPx, SUBPIXEL_COLUMNS_PER_CELL, SUBPIXEL_ROWS_PER_CELL, worldPxToLonLat, wrapLongitude, } from "#mercator";
27
+ import { MapRenderer } from "#renderer";
28
+ const ALT_SCREEN_ENTER = "\u001B[?1049h";
29
+ const ALT_SCREEN_EXIT = "\u001B[?1049l";
30
+ const CURSOR_HIDE = "\u001B[?25l";
31
+ const CURSOR_SHOW = "\u001B[?25h";
32
+ const CLEAR_SCREEN = "\u001B[2J";
33
+ const CLEAR_LINE = "\u001B[2K";
34
+ const REVERSE_VIDEO = "\u001B[7m";
35
+ /**
36
+ * Rows reserved at the bottom of the terminal for the status bar.
37
+ */
38
+ const STATUS_BAR_ROWS = 1;
39
+ /**
40
+ * Size assumed when the output reports none. A pty opened without a window size (util-linux `script`, some CI
41
+ * harnesses) reports 0 columns and 0 rows, which is neither an error nor a usable grid.
42
+ */
43
+ const FALLBACK_COLUMNS = 80;
44
+ const FALLBACK_ROWS = 24;
45
+ /**
46
+ * Floor on the pane's dimensions. Below this the renderer is being asked for a grid too small to mean anything, and a
47
+ * zero-sized one would divide by zero in the projection.
48
+ */
49
+ const MIN_COLUMNS = 4;
50
+ const MIN_PANE_ROWS = 1;
51
+ /**
52
+ * How much of the pane one arrow-key press travels. An eighth is far enough to feel like progress at a keystroke and
53
+ * short enough that the next frame still overlaps the last.
54
+ */
55
+ const PAN_FRACTION = 0.125;
56
+ /**
57
+ * Web-Mercator's latitude cutoff — the projection is undefined at the poles, and the square tile pyramid ends here.
58
+ */
59
+ const MERCATOR_LATITUDE_LIMIT = 85.05112878;
60
+ const COORDINATE_DIGITS = 4;
61
+ /**
62
+ * Clips a string to a cell budget, counting codepoints — the status bar's arrows are one cell each but two UTF-16
63
+ * units, and `String.prototype.slice` would split one in half.
64
+ */
65
+ function clipToCells(text, cells) {
66
+ const codePoints = Array.from(text);
67
+ return codePoints.length <= cells ? text : codePoints.slice(0, cells).join("");
68
+ }
69
+ export class MapBrowser {
70
+ source;
71
+ renderer;
72
+ input;
73
+ output;
74
+ terminal;
75
+ centerLon;
76
+ centerLat;
77
+ zoom;
78
+ columns = FALLBACK_COLUMNS;
79
+ paneRows = FALLBACK_ROWS - STATUS_BAR_ROWS;
80
+ drag = null;
81
+ renderInFlight = false;
82
+ renderQueued = false;
83
+ started = false;
84
+ restored = false;
85
+ error = null;
86
+ resolveExit = null;
87
+ /**
88
+ * The trailing bytes of the last chunk that could not be decoded yet — a sequence the kernel split across two reads.
89
+ * Threading it back through `decodeInputChunk` is what keeps a split mouse report from being read as an Esc keypress
90
+ * (which used to quit); the decoder itself stays pure.
91
+ */
92
+ pendingInput = "";
93
+ onData = (chunk) => {
94
+ const decoded = decodeInputChunk(chunk, this.pendingInput);
95
+ this.pendingInput = decoded.pending;
96
+ for (const event of decoded.events) {
97
+ this.handleInput(event);
98
+ }
99
+ };
100
+ onResize = () => {
101
+ this.measure();
102
+ this.output.write(CLEAR_SCREEN);
103
+ this.terminal.invalidate();
104
+ this.scheduleRender();
105
+ };
106
+ constructor(options) {
107
+ this.source = options.source;
108
+ this.renderer = new MapRenderer(options.source);
109
+ this.input = options.input;
110
+ this.output = options.output;
111
+ this.centerLon = wrapLongitude(options.lon);
112
+ this.centerLat = clamp(options.lat, -MERCATOR_LATITUDE_LIMIT, MERCATOR_LATITUDE_LIMIT);
113
+ this.zoom = clamp(Math.round(options.zoom), options.source.minZoom, options.source.maxZoom);
114
+ this.terminal = new AsciifyTerminal(this.output, {
115
+ mode: "braille",
116
+ colorDepth: "truecolor",
117
+ synchronizedOutput: true,
118
+ });
119
+ }
120
+ /**
121
+ * Runs until the user quits, resolving with the process exit code (0 for a normal quit, 130 for Ctrl+C). The terminal
122
+ * is restored before this resolves.
123
+ */
124
+ async run() {
125
+ this.start();
126
+ const code = await new Promise((resolve) => {
127
+ this.resolveExit = resolve;
128
+ });
129
+ this.restore();
130
+ return code;
131
+ }
132
+ /**
133
+ * Asks the browser to exit with a code. Safe to call from a signal handler, and a no-op once an exit is already under
134
+ * way.
135
+ */
136
+ requestExit(code) {
137
+ const resolve = this.resolveExit;
138
+ if (!resolve)
139
+ return;
140
+ this.resolveExit = null;
141
+ resolve(code);
142
+ }
143
+ /**
144
+ * Enters the alternate screen and takes over input. Paired with {@link restore}.
145
+ */
146
+ start() {
147
+ if (this.started)
148
+ return;
149
+ this.started = true;
150
+ this.output.write(ALT_SCREEN_ENTER + CURSOR_HIDE + CLEAR_SCREEN + MOUSE_ENABLE);
151
+ this.input.setRawMode?.(true);
152
+ this.input.setEncoding("utf8");
153
+ this.input.resume();
154
+ this.input.on("data", this.onData);
155
+ this.output.on("resize", this.onResize);
156
+ this.measure();
157
+ this.scheduleRender();
158
+ }
159
+ /**
160
+ * Puts the terminal back exactly as it was found. Idempotent: the normal exit path, a signal handler and a
161
+ * process-level `exit` hook may each call it.
162
+ */
163
+ restore() {
164
+ if (!this.started || this.restored)
165
+ return;
166
+ this.restored = true;
167
+ this.input.off("data", this.onData);
168
+ this.output.off("resize", this.onResize);
169
+ this.input.setRawMode?.(false);
170
+ this.input.pause();
171
+ this.output.write(MOUSE_DISABLE + SGR_RESET + CURSOR_SHOW + ALT_SCREEN_EXIT);
172
+ }
173
+ handleInput(event) {
174
+ switch (event.kind) {
175
+ case "quit":
176
+ return this.requestExit(0);
177
+ case "interrupt":
178
+ return this.requestExit(130);
179
+ case "pan":
180
+ return this.panBySteps(event.dx, event.dy);
181
+ case "zoom":
182
+ return this.zoomBy(event.delta, null);
183
+ case "wheel":
184
+ return this.zoomBy(event.delta, { column: event.column, row: event.row });
185
+ case "press":
186
+ return this.beginDrag(event.column, event.row);
187
+ case "drag":
188
+ return this.continueDrag(event.column, event.row);
189
+ case "release":
190
+ return this.endDrag();
191
+ }
192
+ }
193
+ panBySteps(dx, dy) {
194
+ const columnStep = Math.max(1, Math.round(this.columns * PAN_FRACTION));
195
+ const rowStep = Math.max(1, Math.round(this.paneRows * PAN_FRACTION));
196
+ this.panByCells(dx * columnStep, dy * rowStep);
197
+ this.scheduleRender();
198
+ }
199
+ /**
200
+ * Moves the center by a cell delta, through world pixels so the step is the same distance on screen at every latitude
201
+ * — the naive degrees-per-keypress version crawls at the equator and sprints near the poles.
202
+ */
203
+ panByCells(columns, rows) {
204
+ const center = lonLatToWorldPx(this.centerLon, this.centerLat, this.zoom);
205
+ const next = worldPxToLonLat(center.x + columns * SUBPIXEL_COLUMNS_PER_CELL, center.y + rows * SUBPIXEL_ROWS_PER_CELL, this.zoom);
206
+ this.setCenter(next.lon, next.lat);
207
+ }
208
+ setCenter(lon, lat) {
209
+ this.centerLon = wrapLongitude(lon);
210
+ this.centerLat = clamp(lat, -MERCATOR_LATITUDE_LIMIT, MERCATOR_LATITUDE_LIMIT);
211
+ }
212
+ /**
213
+ * Longitude/latitude at the center of a pane cell.
214
+ */
215
+ cellToLonLat(column, row) {
216
+ const center = lonLatToWorldPx(this.centerLon, this.centerLat, this.zoom);
217
+ const originX = center.x - (this.columns * SUBPIXEL_COLUMNS_PER_CELL) / 2;
218
+ const originY = center.y - (this.paneRows * SUBPIXEL_ROWS_PER_CELL) / 2;
219
+ return worldPxToLonLat(originX + column * SUBPIXEL_COLUMNS_PER_CELL + SUBPIXEL_COLUMNS_PER_CELL / 2, originY + row * SUBPIXEL_ROWS_PER_CELL + SUBPIXEL_ROWS_PER_CELL / 2, this.zoom);
220
+ }
221
+ /**
222
+ * Zooms one or more whole levels. With an anchor cell (the wheel's pointer), the center shifts so whatever was under
223
+ * the pointer stays under it; without one, the pane center holds.
224
+ */
225
+ zoomBy(delta, anchor) {
226
+ const next = clamp(this.zoom + delta, this.source.minZoom, this.source.maxZoom);
227
+ if (next === this.zoom)
228
+ return;
229
+ if (!anchor || !this.withinPane(anchor.column, anchor.row)) {
230
+ this.zoom = next;
231
+ this.scheduleRender();
232
+ return;
233
+ }
234
+ const target = this.cellToLonLat(anchor.column, anchor.row);
235
+ this.zoom = next;
236
+ const landed = this.cellToLonLat(anchor.column, anchor.row);
237
+ const center = lonLatToWorldPx(this.centerLon, this.centerLat, this.zoom);
238
+ const targetPx = lonLatToWorldPx(target.lon, target.lat, this.zoom);
239
+ const landedPx = lonLatToWorldPx(landed.lon, landed.lat, this.zoom);
240
+ const corrected = worldPxToLonLat(center.x + (targetPx.x - landedPx.x), center.y + (targetPx.y - landedPx.y), this.zoom);
241
+ this.setCenter(corrected.lon, corrected.lat);
242
+ this.scheduleRender();
243
+ }
244
+ withinPane(column, row) {
245
+ return column >= 0 && column < this.columns && row >= 0 && row < this.paneRows;
246
+ }
247
+ beginDrag(column, row) {
248
+ if (!this.withinPane(column, row))
249
+ return;
250
+ this.drag = {
251
+ column,
252
+ row,
253
+ centerLon: this.centerLon,
254
+ centerLat: this.centerLat,
255
+ zoom: this.zoom,
256
+ moved: false,
257
+ };
258
+ }
259
+ /**
260
+ * Pans relative to where the drag STARTED, not the previous motion report. Accumulating per-report deltas would
261
+ * drift, since each one is rounded to a whole cell.
262
+ */
263
+ continueDrag(column, row) {
264
+ const anchor = this.drag;
265
+ if (!anchor || anchor.zoom !== this.zoom)
266
+ return;
267
+ anchor.moved = true;
268
+ const center = lonLatToWorldPx(anchor.centerLon, anchor.centerLat, anchor.zoom);
269
+ const next = worldPxToLonLat(center.x + (anchor.column - column) * SUBPIXEL_COLUMNS_PER_CELL, center.y + (anchor.row - row) * SUBPIXEL_ROWS_PER_CELL, anchor.zoom);
270
+ this.setCenter(next.lon, next.lat);
271
+ this.scheduleRender();
272
+ }
273
+ /**
274
+ * A press and release with no motion between them is a click, which centers the map — mapscii's behavior, and the
275
+ * reason centering waits for the release rather than acting on the press.
276
+ */
277
+ endDrag() {
278
+ const anchor = this.drag;
279
+ this.drag = null;
280
+ if (!anchor || anchor.moved)
281
+ return;
282
+ // A wheel event between press and release moved the map out from under the click, so the cell no longer names
283
+ // the place the user pointed at.
284
+ if (anchor.zoom !== this.zoom)
285
+ return;
286
+ const target = this.cellToLonLat(anchor.column, anchor.row);
287
+ this.setCenter(target.lon, target.lat);
288
+ this.scheduleRender();
289
+ }
290
+ /**
291
+ * Reads the terminal's size and re-sizes the pane around the status bar.
292
+ */
293
+ measure() {
294
+ // `||` and not `??`: a pty with no window size reports 0, which is as unusable as absent.
295
+ const columns = Math.floor(this.output.columns || FALLBACK_COLUMNS);
296
+ const rows = Math.floor(this.output.rows || FALLBACK_ROWS);
297
+ this.columns = Math.max(MIN_COLUMNS, columns);
298
+ this.paneRows = Math.max(MIN_PANE_ROWS, rows - STATUS_BAR_ROWS);
299
+ this.terminal.setSize(this.columns, this.paneRows);
300
+ }
301
+ /**
302
+ * Requests a frame. Renders never overlap: a request arriving mid-render is coalesced into one more pass, so a held
303
+ * arrow key queues a single redraw rather than a backlog of them.
304
+ */
305
+ scheduleRender() {
306
+ if (this.renderInFlight) {
307
+ this.renderQueued = true;
308
+ return;
309
+ }
310
+ void this.renderLoop();
311
+ }
312
+ async renderLoop() {
313
+ this.renderInFlight = true;
314
+ try {
315
+ do {
316
+ this.renderQueued = false;
317
+ await this.renderOnce();
318
+ } while (this.renderQueued && !this.restored);
319
+ }
320
+ finally {
321
+ this.renderInFlight = false;
322
+ }
323
+ }
324
+ async renderOnce() {
325
+ const viewport = {
326
+ centerLon: this.centerLon,
327
+ centerLat: this.centerLat,
328
+ zoom: this.zoom,
329
+ columns: this.columns,
330
+ rows: this.paneRows,
331
+ };
332
+ try {
333
+ const frame = await this.renderer.renderFrame(viewport);
334
+ // The terminal may have been restored while the tiles were in flight; writing then would paint over the
335
+ // user's shell.
336
+ if (this.restored)
337
+ return;
338
+ // A resize between the request and now leaves the frame the wrong shape for the pane — drop it and let the
339
+ // resize's own render answer instead.
340
+ if (frame.columns !== this.columns || frame.rows !== this.paneRows)
341
+ return;
342
+ this.error = null;
343
+ blitFrame(this.terminal, frame);
344
+ this.terminal.flush();
345
+ this.drawStatusBar(frame.attribution);
346
+ }
347
+ catch (error) {
348
+ this.error = errorMessage(error);
349
+ if (!this.restored) {
350
+ this.drawStatusBar("");
351
+ }
352
+ }
353
+ }
354
+ statusText(attribution) {
355
+ if (this.error)
356
+ return `⚠ ${this.error} q quit`;
357
+ const lat = this.centerLat.toFixed(COORDINATE_DIGITS);
358
+ const lon = this.centerLon.toFixed(COORDINATE_DIGITS);
359
+ const status = `${lat},${lon} z${this.zoom} ←↑↓→ pan +/- zoom q quit`;
360
+ if (!attribution.length)
361
+ return status;
362
+ const credited = `${status} © ${attribution}`;
363
+ // Attribution is a courtesy to the tile source, never a reason to push the controls off the bar.
364
+ return Array.from(credited).length < this.columns ? credited : status;
365
+ }
366
+ drawStatusBar(attribution) {
367
+ // One cell short of the width: writing the bottom-right cell leaves some terminals in a pending-wrap state.
368
+ const width = Math.max(0, this.columns - 1);
369
+ const line = clipToCells(this.statusText(attribution), width).padEnd(width);
370
+ this.output.write(`${cursorTo(0, this.paneRows)}${CLEAR_LINE}${REVERSE_VIDEO}${line}${SGR_RESET}`);
371
+ }
372
+ }
373
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../lib/browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAEnF,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAClC,OAAO,EAAE,gBAAgB,EAAoB,aAAa,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AACxF,OAAO,EACN,eAAe,EACf,yBAAyB,EACzB,sBAAsB,EACtB,eAAe,EACf,aAAa,GACb,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAGvC,MAAM,gBAAgB,GAAG,eAAe,CAAA;AACxC,MAAM,eAAe,GAAG,eAAe,CAAA;AACvC,MAAM,WAAW,GAAG,aAAa,CAAA;AACjC,MAAM,WAAW,GAAG,aAAa,CAAA;AACjC,MAAM,YAAY,GAAG,WAAW,CAAA;AAChC,MAAM,UAAU,GAAG,WAAW,CAAA;AAC9B,MAAM,aAAa,GAAG,WAAW,CAAA;AAEjC;;GAEG;AACH,MAAM,eAAe,GAAG,CAAC,CAAA;AAEzB;;;GAGG;AACH,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAC3B,MAAM,aAAa,GAAG,EAAE,CAAA;AAExB;;;GAGG;AACH,MAAM,WAAW,GAAG,CAAC,CAAA;AACrB,MAAM,aAAa,GAAG,CAAC,CAAA;AAEvB;;;GAGG;AACH,MAAM,YAAY,GAAG,KAAK,CAAA;AAE1B;;GAEG;AACH,MAAM,uBAAuB,GAAG,WAAW,CAAA;AAE3C,MAAM,iBAAiB,GAAG,CAAC,CAAA;AA4C3B;;;GAGG;AACH,SAAS,WAAW,CAAC,IAAY,EAAE,KAAa;IAC/C,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEnC,OAAO,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAC/E,CAAC;AAED,MAAM,OAAO,UAAU;IACL,MAAM,CAAY;IAClB,QAAQ,CAAa;IACrB,KAAK,CAAc;IACnB,MAAM,CAAe;IACrB,QAAQ,CAAiB;IAElC,SAAS,CAAQ;IACjB,SAAS,CAAQ;IACjB,IAAI,CAAQ;IAEZ,OAAO,GAAG,gBAAgB,CAAA;IAC1B,QAAQ,GAAG,aAAa,GAAG,eAAe,CAAA;IAE1C,IAAI,GAAsB,IAAI,CAAA;IAC9B,cAAc,GAAG,KAAK,CAAA;IACtB,YAAY,GAAG,KAAK,CAAA;IACpB,OAAO,GAAG,KAAK,CAAA;IACf,QAAQ,GAAG,KAAK,CAAA;IAChB,KAAK,GAAkB,IAAI,CAAA;IAC3B,WAAW,GAAoC,IAAI,CAAA;IAE3D;;;;OAIG;IACK,YAAY,GAAG,EAAE,CAAA;IAER,MAAM,GAAG,CAAC,KAAa,EAAQ,EAAE;QACjD,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,CAAA;QAE1D,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,OAAO,CAAA;QAEnC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACpC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QACxB,CAAC;IACF,CAAC,CAAA;IAEgB,QAAQ,GAAG,GAAS,EAAE;QACtC,IAAI,CAAC,OAAO,EAAE,CAAA;QACd,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;QAC/B,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAA;QAC1B,IAAI,CAAC,cAAc,EAAE,CAAA;IACtB,CAAC,CAAA;IAED,YAAY,OAA0B;QACrC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC5B,IAAI,CAAC,QAAQ,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAC/C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAE5B,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC3C,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,uBAAuB,EAAE,uBAAuB,CAAC,CAAA;QACtF,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAE3F,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE;YAChD,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,WAAW;YACvB,kBAAkB,EAAE,IAAI;SACxB,CAAC,CAAA;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG;QACR,IAAI,CAAC,KAAK,EAAE,CAAA;QAEZ,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;YAClD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAA;QAC3B,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,OAAO,EAAE,CAAA;QAEd,OAAO,IAAI,CAAA;IACZ,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,IAAY;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAA;QAEhC,IAAI,CAAC,OAAO;YAAE,OAAM;QAEpB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;QACvB,OAAO,CAAC,IAAI,CAAC,CAAA;IACd,CAAC;IAED;;OAEG;IACH,KAAK;QACJ,IAAI,IAAI,CAAC,OAAO;YAAE,OAAM;QAExB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,CAAC,CAAA;QAE/E,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAA;QAC7B,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QAC9B,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAA;QACnB,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QAEvC,IAAI,CAAC,OAAO,EAAE,CAAA;QACd,IAAI,CAAC,cAAc,EAAE,CAAA;IACtB,CAAC;IAED;;;OAGG;IACH,OAAO;QACN,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAM;QAE1C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QAEpB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QACnC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QACxC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QAElB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,eAAe,CAAC,CAAA;IAC7E,CAAC;IAEO,WAAW,CAAC,KAAkB;QACrC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACpB,KAAK,MAAM;gBACV,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;YAE3B,KAAK,WAAW;gBACf,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;YAE7B,KAAK,KAAK;gBACT,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,CAAA;YAE3C,KAAK,MAAM;gBACV,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;YAEtC,KAAK,OAAO;gBACX,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;YAE1E,KAAK,OAAO;gBACX,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;YAE/C,KAAK,MAAM;gBACV,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;YAElD,KAAK,SAAS;gBACb,OAAO,IAAI,CAAC,OAAO,EAAE,CAAA;QACvB,CAAC;IACF,CAAC;IAEO,UAAU,CAAC,EAAU,EAAE,EAAU;QACxC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,YAAY,CAAC,CAAC,CAAA;QACvE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAA;QAErE,IAAI,CAAC,UAAU,CAAC,EAAE,GAAG,UAAU,EAAE,EAAE,GAAG,OAAO,CAAC,CAAA;QAC9C,IAAI,CAAC,cAAc,EAAE,CAAA;IACtB,CAAC;IAED;;;OAGG;IACK,UAAU,CAAC,OAAe,EAAE,IAAY;QAC/C,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAEzE,MAAM,IAAI,GAAG,eAAe,CAC3B,MAAM,CAAC,CAAC,GAAG,OAAO,GAAG,yBAAyB,EAC9C,MAAM,CAAC,CAAC,GAAG,IAAI,GAAG,sBAAsB,EACxC,IAAI,CAAC,IAAI,CACT,CAAA;QAED,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IACnC,CAAC;IAEO,SAAS,CAAC,GAAW,EAAE,GAAW;QACzC,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;QACnC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC,uBAAuB,EAAE,uBAAuB,CAAC,CAAA;IAC/E,CAAC;IAED;;OAEG;IACK,YAAY,CAAC,MAAc,EAAE,GAAW;QAC/C,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QACzE,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAA;QACzE,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAA;QAEvE,OAAO,eAAe,CACrB,OAAO,GAAG,MAAM,GAAG,yBAAyB,GAAG,yBAAyB,GAAG,CAAC,EAC5E,OAAO,GAAG,GAAG,GAAG,sBAAsB,GAAG,sBAAsB,GAAG,CAAC,EACnE,IAAI,CAAC,IAAI,CACT,CAAA;IACF,CAAC;IAED;;;OAGG;IACK,MAAM,CAAC,KAAa,EAAE,MAA8C;QAC3E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAE/E,IAAI,IAAI,KAAK,IAAI,CAAC,IAAI;YAAE,OAAM;QAE9B,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;YAChB,IAAI,CAAC,cAAc,EAAE,CAAA;YAErB,OAAM;QACP,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;QAE3D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAEhB,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;QAC3D,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QACzE,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QACnE,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAEnE,MAAM,SAAS,GAAG,eAAe,CAChC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,EACpC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,EACpC,IAAI,CAAC,IAAI,CACT,CAAA;QAED,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,CAAA;QAC5C,IAAI,CAAC,cAAc,EAAE,CAAA;IACtB,CAAC;IAEO,UAAU,CAAC,MAAc,EAAE,GAAW;QAC7C,OAAO,MAAM,IAAI,CAAC,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAA;IAC/E,CAAC;IAEO,SAAS,CAAC,MAAc,EAAE,GAAW;QAC5C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC;YAAE,OAAM;QAEzC,IAAI,CAAC,IAAI,GAAG;YACX,MAAM;YACN,GAAG;YACH,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,KAAK;SACZ,CAAA;IACF,CAAC;IAED;;;OAGG;IACK,YAAY,CAAC,MAAc,EAAE,GAAW;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAA;QAExB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;YAAE,OAAM;QAEhD,MAAM,CAAC,KAAK,GAAG,IAAI,CAAA;QAEnB,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;QAE/E,MAAM,IAAI,GAAG,eAAe,CAC3B,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,yBAAyB,EAC/D,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,sBAAsB,EACtD,MAAM,CAAC,IAAI,CACX,CAAA;QAED,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;QAClC,IAAI,CAAC,cAAc,EAAE,CAAA;IACtB,CAAC;IAED;;;OAGG;IACK,OAAO;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAA;QAExB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAEhB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK;YAAE,OAAM;QAEnC,8GAA8G;QAC9G,iCAAiC;QACjC,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;YAAE,OAAM;QAErC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;QAE3D,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;QACtC,IAAI,CAAC,cAAc,EAAE,CAAA;IACtB,CAAC;IAED;;OAEG;IACK,OAAO;QACd,0FAA0F;QAC1F,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAA;QACnE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,aAAa,CAAC,CAAA;QAE1D,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;QAC7C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,GAAG,eAAe,CAAC,CAAA;QAE/D,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;IACnD,CAAC;IAED;;;OAGG;IACK,cAAc;QACrB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;YAExB,OAAM;QACP,CAAC;QAED,KAAK,IAAI,CAAC,UAAU,EAAE,CAAA;IACvB,CAAC;IAEO,KAAK,CAAC,UAAU;QACvB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;QAE1B,IAAI,CAAC;YACJ,GAAG,CAAC;gBACH,IAAI,CAAC,YAAY,GAAG,KAAK,CAAA;gBAEzB,MAAM,IAAI,CAAC,UAAU,EAAE,CAAA;YACxB,CAAC,QAAQ,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAC;QAC9C,CAAC;gBAAS,CAAC;YACV,IAAI,CAAC,cAAc,GAAG,KAAK,CAAA;QAC5B,CAAC;IACF,CAAC;IAEO,KAAK,CAAC,UAAU;QACvB,MAAM,QAAQ,GAAG;YAChB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,IAAI,CAAC,QAAQ;SACnB,CAAA;QAED,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;YAEvD,wGAAwG;YACxG,gBAAgB;YAChB,IAAI,IAAI,CAAC,QAAQ;gBAAE,OAAM;YAEzB,2GAA2G;YAC3G,sCAAsC;YACtC,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,QAAQ;gBAAE,OAAM;YAE1E,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;YAEjB,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;YAC/B,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;YACrB,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;QACtC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;YAEhC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACpB,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA;YACvB,CAAC;QACF,CAAC;IACF,CAAC;IAEO,UAAU,CAAC,WAAmB;QACrC,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,IAAI,CAAC,KAAK,UAAU,CAAA;QAEhD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAA;QACrD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAA;QACrD,MAAM,MAAM,GAAG,GAAG,GAAG,IAAI,GAAG,KAAK,IAAI,CAAC,IAAI,8BAA8B,CAAA;QAExE,IAAI,CAAC,WAAW,CAAC,MAAM;YAAE,OAAO,MAAM,CAAA;QAEtC,MAAM,QAAQ,GAAG,GAAG,MAAM,OAAO,WAAW,EAAE,CAAA;QAE9C,iGAAiG;QACjG,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAA;IACtE,CAAC;IAEO,aAAa,CAAC,WAAmB;QACxC,4GAA4G;QAC5G,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAA;QAC3C,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAE3E,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,UAAU,GAAG,aAAa,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC,CAAA;IACnG,CAAC;CACD"}