@mapka/maplibre-gl-sdk 0.12.0 → 0.13.1
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.
- package/README.md +330 -4
- package/lib/.buildInfo.json +1 -1
- package/lib/components/DownloadIcon.d.ts +4 -0
- package/lib/components/DownloadIcon.d.ts.map +1 -0
- package/lib/components/DownloadIcon.js +10 -0
- package/lib/components/PopupContent.d.ts.map +1 -1
- package/lib/components/ProgressDownIcon.d.ts +4 -0
- package/lib/components/ProgressDownIcon.d.ts.map +1 -0
- package/lib/components/ProgressDownIcon.js +14 -0
- package/lib/constants/styles.d.ts +5 -0
- package/lib/constants/styles.d.ts.map +1 -0
- package/lib/constants/styles.js +3 -0
- package/lib/controls/MapkaExportControl.d.ts +21 -0
- package/lib/controls/MapkaExportControl.d.ts.map +1 -0
- package/lib/controls/MapkaExportControl.js +76 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -0
- package/lib/map.d.ts +11 -2
- package/lib/map.d.ts.map +1 -1
- package/lib/map.js +10 -5
- package/lib/modules/export.d.ts +4 -0
- package/lib/modules/export.d.ts.map +1 -0
- package/lib/modules/export.js +40 -0
- package/lib/modules/popup.d.ts +2 -2
- package/lib/modules/popup.d.ts.map +1 -1
- package/lib/modules/popup.js +19 -6
- package/lib/types/export.d.ts +7 -0
- package/lib/types/export.d.ts.map +1 -0
- package/lib/types/export.js +1 -0
- package/package.json +3 -2
- package/src/components/DownloadIcon.tsx +25 -0
- package/src/components/PopupContent.tsx +1 -0
- package/src/components/ProgressDownIcon.tsx +29 -0
- package/src/constants/styles.ts +5 -0
- package/src/controls/MapkaExportControl.tsx +111 -0
- package/src/index.ts +6 -0
- package/src/map.ts +17 -4
- package/src/modules/export.ts +55 -0
- package/src/modules/popup.tsx +26 -24
- package/src/types/export.ts +6 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// biome-ignore lint/correctness/noUnusedImports: preact jsx
|
|
2
|
+
import { h } from "preact";
|
|
3
|
+
import { render } from "preact";
|
|
4
|
+
import { DownloadIcon } from "../components/DownloadIcon.js";
|
|
5
|
+
import { ProgressDownIcon } from "../components/ProgressDownIcon.js";
|
|
6
|
+
import type { IControl } from "maplibre-gl";
|
|
7
|
+
import type { MapkaMap } from "../map.js";
|
|
8
|
+
import type { MapkaExportOptions } from "../types/export.js";
|
|
9
|
+
|
|
10
|
+
export interface MapkaExportControlOptions extends MapkaExportOptions {
|
|
11
|
+
filename?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const Button = ({ isExporting, onClick }: { isExporting: boolean; onClick: () => void }) => {
|
|
15
|
+
return (
|
|
16
|
+
<button
|
|
17
|
+
type="button"
|
|
18
|
+
className="maplibregl-ctrl maplibregl-ctrl-group"
|
|
19
|
+
title="Export map as PNG"
|
|
20
|
+
aria-label="Export map as PNG"
|
|
21
|
+
disabled={isExporting}
|
|
22
|
+
onClick={onClick}
|
|
23
|
+
>
|
|
24
|
+
{isExporting ? <ProgressDownIcon /> : <DownloadIcon />}
|
|
25
|
+
</button>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class MapkaExportControl implements IControl {
|
|
30
|
+
private map: MapkaMap | undefined;
|
|
31
|
+
private container: HTMLDivElement | undefined;
|
|
32
|
+
private options: MapkaExportControlOptions;
|
|
33
|
+
private isExporting = false;
|
|
34
|
+
|
|
35
|
+
constructor(options: MapkaExportControlOptions = {}) {
|
|
36
|
+
this.options = {
|
|
37
|
+
filename: "map-export",
|
|
38
|
+
...options,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private downloadImage(img: HTMLImageElement): void {
|
|
43
|
+
const link = document.createElement("a");
|
|
44
|
+
link.download = `${this.options.filename}.png`;
|
|
45
|
+
link.href = img.src;
|
|
46
|
+
|
|
47
|
+
link.click();
|
|
48
|
+
link.remove();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private handleError(error: Error) {
|
|
52
|
+
this.map?.logger.error(error, "Failed to export map image");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private onClick = async (): Promise<void> => {
|
|
56
|
+
if (!this.map || this.isExporting) return;
|
|
57
|
+
|
|
58
|
+
this.isExporting = true;
|
|
59
|
+
this.render();
|
|
60
|
+
|
|
61
|
+
this.map
|
|
62
|
+
.export({
|
|
63
|
+
hideControls: true,
|
|
64
|
+
hideMarkers: false,
|
|
65
|
+
hidePopups: false,
|
|
66
|
+
...this.options,
|
|
67
|
+
})
|
|
68
|
+
.then((img) => this.downloadImage(img))
|
|
69
|
+
.catch((error) => this.handleError(error))
|
|
70
|
+
.finally(() => {
|
|
71
|
+
this.isExporting = false;
|
|
72
|
+
this.render();
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
private render(): void {
|
|
77
|
+
if (!this.container) {
|
|
78
|
+
this.map?.logger.error("Export control container not found for rendering");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
render(<Button isExporting={this.isExporting} onClick={this.onClick} />, this.container);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private unmount(): void {
|
|
85
|
+
if (!this.container) {
|
|
86
|
+
this.map?.logger.error("Export control container not found during unmount");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
render(null, this.container);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public onAdd(map: MapkaMap): HTMLElement {
|
|
93
|
+
this.map = map;
|
|
94
|
+
|
|
95
|
+
this.container = document.createElement("div");
|
|
96
|
+
this.container.className = "mapka-export-control";
|
|
97
|
+
|
|
98
|
+
this.render();
|
|
99
|
+
|
|
100
|
+
return this.container;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public onRemove(): void {
|
|
104
|
+
this.unmount();
|
|
105
|
+
|
|
106
|
+
this.container?.remove?.();
|
|
107
|
+
|
|
108
|
+
this.container = undefined;
|
|
109
|
+
this.map = undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,12 @@ export * from "maplibre-gl";
|
|
|
2
2
|
export * from "./types/layer.js";
|
|
3
3
|
export * from "./types/marker.js";
|
|
4
4
|
export * from "./types/style.js";
|
|
5
|
+
export * from "./types/export.js";
|
|
6
|
+
export * from "./constants/styles.js";
|
|
5
7
|
|
|
6
8
|
export { MapkaMap as Map } from "./map.js";
|
|
7
9
|
export { MapkaMapOptions as MapOptions } from "./map.js";
|
|
10
|
+
export {
|
|
11
|
+
MapkaExportControl,
|
|
12
|
+
MapkaExportControlOptions,
|
|
13
|
+
} from "./controls/MapkaExportControl.js";
|
package/src/map.ts
CHANGED
|
@@ -27,6 +27,8 @@ import type {
|
|
|
27
27
|
StyleSpecification,
|
|
28
28
|
} from "maplibre-gl";
|
|
29
29
|
import type { MapkaMarkerOptions, MapkaPopupOptions } from "./types/marker.js";
|
|
30
|
+
import type { MapkaExportOptions } from "./types/export.js";
|
|
31
|
+
import { exportMap } from "./modules/export.js";
|
|
30
32
|
|
|
31
33
|
export interface MapkaMapOptions extends MapOptions {
|
|
32
34
|
maxPopups?: number;
|
|
@@ -72,9 +74,16 @@ export type MapMapkaMarker = {
|
|
|
72
74
|
marker: Marker;
|
|
73
75
|
};
|
|
74
76
|
|
|
77
|
+
interface Logger {
|
|
78
|
+
log: (...args: unknown[]) => void;
|
|
79
|
+
warn: (...args: unknown[]) => void;
|
|
80
|
+
error: (...args: unknown[]) => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
export class MapkaMap extends maplibregl.Map {
|
|
76
84
|
static env: string = "prod";
|
|
77
85
|
|
|
86
|
+
public logger: Logger = console;
|
|
78
87
|
public markers: MapMapkaMarker[] = [];
|
|
79
88
|
|
|
80
89
|
public maxPopups: number = 1;
|
|
@@ -129,19 +138,23 @@ export class MapkaMap extends maplibregl.Map {
|
|
|
129
138
|
removeMarkers(this);
|
|
130
139
|
}
|
|
131
140
|
|
|
132
|
-
public openPopup(popup: MapkaPopupOptions
|
|
133
|
-
return openPopup(this, popup
|
|
141
|
+
public openPopup(popup: MapkaPopupOptions) {
|
|
142
|
+
return openPopup(this, popup);
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
public closePopup(id: string) {
|
|
137
146
|
closePopupsById(this, id);
|
|
138
147
|
}
|
|
139
148
|
|
|
140
|
-
public updatePopup(popup: MapkaPopupOptions
|
|
141
|
-
return updatePopup(this, popup
|
|
149
|
+
public updatePopup(popup: MapkaPopupOptions) {
|
|
150
|
+
return updatePopup(this, popup);
|
|
142
151
|
}
|
|
143
152
|
|
|
144
153
|
public removePopups() {
|
|
145
154
|
removePopups(this);
|
|
146
155
|
}
|
|
156
|
+
|
|
157
|
+
public async export(options?: MapkaExportOptions) {
|
|
158
|
+
return exportMap(this, options);
|
|
159
|
+
}
|
|
147
160
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { toPng } from "html-to-image";
|
|
2
|
+
import type { MapkaMap } from "../map.js";
|
|
3
|
+
import type { MapkaExportOptions } from "../types/export.js";
|
|
4
|
+
|
|
5
|
+
export async function exportMap(
|
|
6
|
+
map: MapkaMap,
|
|
7
|
+
options: MapkaExportOptions = {},
|
|
8
|
+
): Promise<HTMLImageElement> {
|
|
9
|
+
const { hideControls = false, hideMarkers = false, hidePopups = false, bbox } = options;
|
|
10
|
+
|
|
11
|
+
const container = map.getContainer();
|
|
12
|
+
const originalBounds = map.getBounds();
|
|
13
|
+
|
|
14
|
+
if (bbox) {
|
|
15
|
+
map.fitBounds(bbox, { padding: 20, animate: false });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return new Promise<HTMLImageElement>((resolve, reject) => {
|
|
19
|
+
map.once("render", () => {
|
|
20
|
+
toPng(container, {
|
|
21
|
+
skipFonts: navigator.userAgent.includes("Firefox"),
|
|
22
|
+
filter: (node) => {
|
|
23
|
+
if (node.classList) {
|
|
24
|
+
const isControlsVisibleOrNotControl =
|
|
25
|
+
!hideControls || !node.classList.contains("maplibregl-control-container");
|
|
26
|
+
const isMarkersVisibleOrNotMarker =
|
|
27
|
+
!hideMarkers || !node.classList.contains("maplibregl-marker");
|
|
28
|
+
const isPopupsVisibleOrNotPopup =
|
|
29
|
+
!hidePopups || !node.classList.contains("maplibregl-popup");
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
isControlsVisibleOrNotControl &&
|
|
33
|
+
isMarkersVisibleOrNotMarker &&
|
|
34
|
+
isPopupsVisibleOrNotPopup
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
.then((dataUrl) => {
|
|
41
|
+
const img = new Image();
|
|
42
|
+
img.src = dataUrl;
|
|
43
|
+
img.onload = () => resolve(img);
|
|
44
|
+
img.onerror = reject;
|
|
45
|
+
})
|
|
46
|
+
.catch(reject)
|
|
47
|
+
.finally(() => {
|
|
48
|
+
if (bbox) {
|
|
49
|
+
map.fitBounds(originalBounds, { animate: false });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
map.triggerRepaint();
|
|
54
|
+
});
|
|
55
|
+
}
|
package/src/modules/popup.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import { render } from "preact";
|
|
|
6
6
|
import { remove } from "es-toolkit/array";
|
|
7
7
|
import type { MapkaPopupOptions } from "../types/marker.js";
|
|
8
8
|
import type { MapkaMap } from "../map.js";
|
|
9
|
-
import { isEqual } from "es-toolkit";
|
|
9
|
+
import { isEqual, isPlainObject } from "es-toolkit";
|
|
10
10
|
|
|
11
11
|
export function getPopupId(popup: { id?: string }) {
|
|
12
12
|
return popup.id ?? `popup-${crypto.randomUUID()}`;
|
|
@@ -20,12 +20,15 @@ export function enforceMaxPopups(map: MapkaMap) {
|
|
|
20
20
|
if (map.popups.length > map.maxPopups) {
|
|
21
21
|
const popupToRemove = map.popups.shift();
|
|
22
22
|
popupToRemove?.popup.remove();
|
|
23
|
+
if (isPlainObject(popupToRemove?.options.content)) {
|
|
24
|
+
render(null, popupToRemove.container);
|
|
25
|
+
}
|
|
23
26
|
popupToRemove?.container.remove();
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
export function openPopup(map: MapkaMap, options: MapkaPopupOptions
|
|
28
|
-
const { lngLat, content, closeButton, ...popupOptions } = options;
|
|
30
|
+
export function openPopup(map: MapkaMap, options: MapkaPopupOptions) {
|
|
31
|
+
const { lngLat, content, closeButton, id = getPopupId(options), ...popupOptions } = options;
|
|
29
32
|
if (content instanceof HTMLElement) {
|
|
30
33
|
const popup = new Popup({
|
|
31
34
|
...popupOptions,
|
|
@@ -70,14 +73,10 @@ export function openPopup(map: MapkaMap, options: MapkaPopupOptions, id: string)
|
|
|
70
73
|
return id;
|
|
71
74
|
} else if (typeof content === "function") {
|
|
72
75
|
const newContent = content(id);
|
|
73
|
-
return openPopup(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
content: newContent,
|
|
78
|
-
},
|
|
79
|
-
id,
|
|
80
|
-
);
|
|
76
|
+
return openPopup(map, {
|
|
77
|
+
...options,
|
|
78
|
+
content: newContent,
|
|
79
|
+
});
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
throw new Error("Invalid popup content");
|
|
@@ -102,11 +101,9 @@ export function updatePopupBaseOptions(
|
|
|
102
101
|
return popup;
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
export function updatePopup(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
id: string,
|
|
109
|
-
) {
|
|
104
|
+
export function updatePopup(map: MapkaMap, { content, ...newOptions }: MapkaPopupOptions) {
|
|
105
|
+
const id = getPopupId(newOptions);
|
|
106
|
+
|
|
110
107
|
if (content instanceof HTMLElement) {
|
|
111
108
|
const mapkaPopups = map.popups.filter((popup) => popup.id === id);
|
|
112
109
|
for (const { popup, options } of mapkaPopups) {
|
|
@@ -125,14 +122,10 @@ export function updatePopup(
|
|
|
125
122
|
}
|
|
126
123
|
} else if (typeof content === "function") {
|
|
127
124
|
const newContent = content(id);
|
|
128
|
-
return updatePopup(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
content: newContent,
|
|
133
|
-
},
|
|
134
|
-
id,
|
|
135
|
-
);
|
|
125
|
+
return updatePopup(map, {
|
|
126
|
+
...newOptions,
|
|
127
|
+
content: newContent,
|
|
128
|
+
});
|
|
136
129
|
}
|
|
137
130
|
}
|
|
138
131
|
|
|
@@ -142,6 +135,9 @@ export function closeOnMapClickPopups(map: MapkaMap) {
|
|
|
142
135
|
);
|
|
143
136
|
for (const popup of popupsToCloseOnMapClick) {
|
|
144
137
|
popup.popup.remove();
|
|
138
|
+
if (isPlainObject(popup.options.content)) {
|
|
139
|
+
render(null, popup.container);
|
|
140
|
+
}
|
|
145
141
|
popup.container.remove();
|
|
146
142
|
}
|
|
147
143
|
}
|
|
@@ -150,6 +146,9 @@ export function closePopupsById(map: MapkaMap, id: string) {
|
|
|
150
146
|
const removedPopups = remove(map.popups, (popup) => popup.id === id);
|
|
151
147
|
for (const popup of removedPopups) {
|
|
152
148
|
popup.popup.remove();
|
|
149
|
+
if (isPlainObject(popup.options.content)) {
|
|
150
|
+
render(null, popup.container);
|
|
151
|
+
}
|
|
153
152
|
popup.container.remove();
|
|
154
153
|
}
|
|
155
154
|
}
|
|
@@ -157,6 +156,9 @@ export function closePopupsById(map: MapkaMap, id: string) {
|
|
|
157
156
|
export function removePopups(map: MapkaMap) {
|
|
158
157
|
for (const popup of map.popups) {
|
|
159
158
|
popup.popup.remove();
|
|
159
|
+
if (isPlainObject(popup.options.content)) {
|
|
160
|
+
render(null, popup.container);
|
|
161
|
+
}
|
|
160
162
|
popup.container.remove();
|
|
161
163
|
}
|
|
162
164
|
map.popups = [];
|