@mapka/maplibre-gl-sdk 0.16.1 → 0.16.3
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 +10 -5
- package/lib/.buildInfo.json +1 -1
- package/lib/components/ImageCarousel.d.ts +7 -0
- package/lib/components/ImageCarousel.d.ts.map +1 -0
- package/lib/components/ImageCarousel.js +22 -0
- package/lib/components/PopupContent.d.ts +5 -1
- package/lib/components/PopupContent.d.ts.map +1 -1
- package/lib/components/PopupContent.js +12 -40
- package/lib/components/PopupDataRows.d.ts +7 -0
- package/lib/components/PopupDataRows.d.ts.map +1 -0
- package/lib/components/PopupDataRows.js +22 -0
- package/lib/components/PopupList.d.ts +10 -0
- package/lib/components/PopupList.d.ts.map +1 -0
- package/lib/components/PopupList.js +8 -0
- package/lib/components/PopupListItem.d.ts +13 -0
- package/lib/components/PopupListItem.d.ts.map +1 -0
- package/lib/components/PopupListItem.js +11 -0
- package/lib/components/icons/ChevronLeftIcon.d.ts +2 -0
- package/lib/components/icons/ChevronLeftIcon.d.ts.map +1 -0
- package/lib/components/icons/ChevronLeftIcon.js +4 -0
- package/lib/components/icons/ChevronRightIcon.d.ts +2 -0
- package/lib/components/icons/ChevronRightIcon.d.ts.map +1 -0
- package/lib/components/icons/ChevronRightIcon.js +4 -0
- package/lib/components/icons/CircleIcon.d.ts.map +1 -0
- package/lib/components/icons/CloseIcon.d.ts +2 -0
- package/lib/components/icons/CloseIcon.d.ts.map +1 -0
- package/lib/components/icons/CloseIcon.js +4 -0
- package/lib/components/icons/DownloadIcon.d.ts.map +1 -0
- package/lib/components/icons/FreehandIcon.d.ts.map +1 -0
- package/lib/components/icons/HeartIcon.d.ts +4 -0
- package/lib/components/icons/HeartIcon.d.ts.map +1 -0
- package/lib/components/icons/HeartIcon.js +4 -0
- package/lib/components/icons/LineIcon.d.ts.map +1 -0
- package/lib/components/icons/PencilIcon.d.ts.map +1 -0
- package/lib/components/icons/PolygonIcon.d.ts.map +1 -0
- package/lib/components/icons/ProgressDownIcon.d.ts +2 -0
- package/lib/components/icons/ProgressDownIcon.d.ts.map +1 -0
- package/lib/components/icons/RectangleIcon.d.ts.map +1 -0
- package/lib/components/icons/SelectIcon.d.ts.map +1 -0
- package/lib/components/icons/TrashIcon.d.ts.map +1 -0
- package/lib/controls/MapkaDrawControl.js +7 -7
- package/lib/controls/MapkaExportControl.js +2 -2
- package/lib/map.d.ts +8 -7
- package/lib/map.d.ts.map +1 -1
- package/lib/map.js +15 -9
- package/lib/modules/layerPopup.d.ts +2 -1
- package/lib/modules/layerPopup.d.ts.map +1 -1
- package/lib/modules/layerPopup.js +22 -15
- package/lib/modules/markers.d.ts +5 -0
- package/lib/modules/markers.d.ts.map +1 -1
- package/lib/modules/markers.js +21 -16
- package/lib/modules/popup.d.ts +3 -10
- package/lib/modules/popup.d.ts.map +1 -1
- package/lib/modules/popup.js +116 -100
- package/lib/modules/popupGroups.d.ts +14 -0
- package/lib/modules/popupGroups.d.ts.map +1 -0
- package/lib/modules/popupGroups.js +130 -0
- package/lib/styles.css +1 -1
- package/lib/types/popup.d.ts +10 -1
- package/lib/types/popup.d.ts.map +1 -1
- package/package.json +26 -7
- package/src/components/ImageCarousel.css +73 -0
- package/src/components/ImageCarousel.tsx +76 -0
- package/src/components/PopupContent.css +52 -189
- package/src/components/PopupContent.tsx +26 -195
- package/src/components/PopupDataRows.css +41 -0
- package/src/components/PopupDataRows.tsx +39 -0
- package/src/components/PopupList.css +24 -0
- package/src/components/PopupList.tsx +27 -0
- package/src/components/PopupListItem.css +61 -0
- package/src/components/PopupListItem.tsx +40 -0
- package/src/components/icons/ChevronLeftIcon.tsx +20 -0
- package/src/components/icons/ChevronRightIcon.tsx +20 -0
- package/src/components/icons/CloseIcon.tsx +13 -0
- package/src/components/icons/HeartIcon.tsx +18 -0
- package/src/components/{ProgressDownIcon.tsx → icons/ProgressDownIcon.tsx} +0 -3
- package/src/controls/MapkaDrawControl.tsx +7 -7
- package/src/controls/MapkaExportControl.tsx +2 -2
- package/src/map.ts +22 -20
- package/src/modules/layerPopup.ts +32 -21
- package/src/modules/markers.ts +26 -16
- package/src/modules/popup.tsx +129 -112
- package/src/modules/popupGroups.ts +190 -0
- package/src/styles.css +4 -0
- package/src/types/popup.ts +12 -1
- package/lib/components/CircleIcon.d.ts.map +0 -1
- package/lib/components/DownloadIcon.d.ts.map +0 -1
- package/lib/components/FreehandIcon.d.ts.map +0 -1
- package/lib/components/LineIcon.d.ts.map +0 -1
- package/lib/components/PencilIcon.d.ts.map +0 -1
- package/lib/components/PolygonIcon.d.ts.map +0 -1
- package/lib/components/ProgressDownIcon.d.ts +0 -4
- package/lib/components/ProgressDownIcon.d.ts.map +0 -1
- package/lib/components/RectangleIcon.d.ts.map +0 -1
- package/lib/components/SelectIcon.d.ts.map +0 -1
- package/lib/components/TrashIcon.d.ts.map +0 -1
- /package/lib/components/{CircleIcon.d.ts → icons/CircleIcon.d.ts} +0 -0
- /package/lib/components/{CircleIcon.js → icons/CircleIcon.js} +0 -0
- /package/lib/components/{DownloadIcon.d.ts → icons/DownloadIcon.d.ts} +0 -0
- /package/lib/components/{DownloadIcon.js → icons/DownloadIcon.js} +0 -0
- /package/lib/components/{FreehandIcon.d.ts → icons/FreehandIcon.d.ts} +0 -0
- /package/lib/components/{FreehandIcon.js → icons/FreehandIcon.js} +0 -0
- /package/lib/components/{LineIcon.d.ts → icons/LineIcon.d.ts} +0 -0
- /package/lib/components/{LineIcon.js → icons/LineIcon.js} +0 -0
- /package/lib/components/{PencilIcon.d.ts → icons/PencilIcon.d.ts} +0 -0
- /package/lib/components/{PencilIcon.js → icons/PencilIcon.js} +0 -0
- /package/lib/components/{PolygonIcon.d.ts → icons/PolygonIcon.d.ts} +0 -0
- /package/lib/components/{PolygonIcon.js → icons/PolygonIcon.js} +0 -0
- /package/lib/components/{ProgressDownIcon.js → icons/ProgressDownIcon.js} +0 -0
- /package/lib/components/{RectangleIcon.d.ts → icons/RectangleIcon.d.ts} +0 -0
- /package/lib/components/{RectangleIcon.js → icons/RectangleIcon.js} +0 -0
- /package/lib/components/{SelectIcon.d.ts → icons/SelectIcon.d.ts} +0 -0
- /package/lib/components/{SelectIcon.js → icons/SelectIcon.js} +0 -0
- /package/lib/components/{TrashIcon.d.ts → icons/TrashIcon.d.ts} +0 -0
- /package/lib/components/{TrashIcon.js → icons/TrashIcon.js} +0 -0
- /package/src/components/{CircleIcon.tsx → icons/CircleIcon.tsx} +0 -0
- /package/src/components/{DownloadIcon.tsx → icons/DownloadIcon.tsx} +0 -0
- /package/src/components/{FreehandIcon.tsx → icons/FreehandIcon.tsx} +0 -0
- /package/src/components/{LineIcon.tsx → icons/LineIcon.tsx} +0 -0
- /package/src/components/{PencilIcon.tsx → icons/PencilIcon.tsx} +0 -0
- /package/src/components/{PolygonIcon.tsx → icons/PolygonIcon.tsx} +0 -0
- /package/src/components/{RectangleIcon.tsx → icons/RectangleIcon.tsx} +0 -0
- /package/src/components/{SelectIcon.tsx → icons/SelectIcon.tsx} +0 -0
- /package/src/components/{TrashIcon.tsx → icons/TrashIcon.tsx} +0 -0
package/src/modules/popup.tsx
CHANGED
|
@@ -1,167 +1,184 @@
|
|
|
1
1
|
import { Popup } from "maplibre-gl";
|
|
2
2
|
import { PopupContent } from "../components/PopupContent.js";
|
|
3
|
+
import { PopupList } from "../components/PopupList.js";
|
|
3
4
|
import { render } from "preact";
|
|
4
5
|
import { remove } from "es-toolkit/array";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import { isPlainObject, without } from "es-toolkit";
|
|
7
|
+
import { computePopupGroups } from "./popupGroups.js";
|
|
8
|
+
import type { MapkaPopupOptions, MapkaPopupOptionsResolved } from "../types/popup.js";
|
|
9
|
+
import type { MapkaMap, MapMapkaPopup } from "../map.js";
|
|
8
10
|
|
|
9
11
|
export function getPopupId(popup: { id?: string }) {
|
|
10
12
|
return popup.id ?? `popup-${crypto.randomUUID()}`;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
return () => map
|
|
15
|
+
function getOnClose(map: MapkaMap, id: string) {
|
|
16
|
+
return () => closePopupsByIds(map, [id]);
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
function hasObjectContent(options: MapkaPopupOptions[]) {
|
|
20
|
+
return options.some((opt) => isPlainObject(opt.content));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resolveContentCreators(options: MapkaPopupOptions[]): MapkaPopupOptionsResolved[] {
|
|
24
|
+
return options.map((opt) => {
|
|
25
|
+
const id = getPopupId(opt);
|
|
26
|
+
|
|
27
|
+
if (typeof opt.content === "function") {
|
|
28
|
+
return {
|
|
29
|
+
...opt,
|
|
30
|
+
id,
|
|
31
|
+
content: opt.content(id),
|
|
32
|
+
};
|
|
33
|
+
} else {
|
|
34
|
+
return {
|
|
35
|
+
...opt,
|
|
36
|
+
id,
|
|
37
|
+
content: opt.content,
|
|
38
|
+
};
|
|
23
39
|
}
|
|
24
|
-
|
|
25
|
-
}
|
|
40
|
+
});
|
|
26
41
|
}
|
|
27
42
|
|
|
28
|
-
|
|
29
|
-
const { lngLat, content, closeButton,
|
|
30
|
-
if (content instanceof HTMLElement) {
|
|
31
|
-
const popup = new Popup({
|
|
32
|
-
...popupOptions,
|
|
33
|
-
closeButton: false,
|
|
34
|
-
closeOnClick: false,
|
|
35
|
-
})
|
|
36
|
-
.setLngLat(lngLat)
|
|
37
|
-
.setDOMContent(content)
|
|
38
|
-
.addTo(map);
|
|
43
|
+
function createNewPopup(map: MapkaMap, options: MapkaPopupOptionsResolved[]) {
|
|
44
|
+
const [{ lngLat, id, content, closeButton, ...opts }] = options;
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const container = document.createElement("div");
|
|
51
|
-
container.classList.add("mapka-popup-container");
|
|
52
|
-
|
|
53
|
-
render(<PopupContent {...content} closeButton={closeButton} onClose={onClose} />, container);
|
|
54
|
-
|
|
55
|
-
const popup = new Popup({
|
|
56
|
-
...popupOptions,
|
|
46
|
+
const ids = options.map(getPopupId);
|
|
47
|
+
const container = document.createElement("div");
|
|
48
|
+
container.classList.add("mapka-popup-container");
|
|
49
|
+
|
|
50
|
+
let popup: Popup | undefined;
|
|
51
|
+
if (options.length > 1) {
|
|
52
|
+
render(<PopupList items={options} />, container);
|
|
53
|
+
|
|
54
|
+
popup = new Popup({
|
|
55
|
+
...opts,
|
|
57
56
|
closeButton: false,
|
|
58
|
-
closeOnClick:
|
|
57
|
+
closeOnClick: true,
|
|
59
58
|
})
|
|
60
59
|
.setLngLat(lngLat)
|
|
61
60
|
.setDOMContent(container)
|
|
62
61
|
.addTo(map);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
62
|
+
} else {
|
|
63
|
+
if (content instanceof HTMLElement) {
|
|
64
|
+
popup = new Popup({
|
|
65
|
+
...opts,
|
|
66
|
+
closeButton: false,
|
|
67
|
+
})
|
|
68
|
+
.setLngLat(lngLat)
|
|
69
|
+
.setDOMContent(content)
|
|
70
|
+
.addTo(map);
|
|
71
|
+
} else {
|
|
72
|
+
popup = new Popup({
|
|
73
|
+
...opts,
|
|
74
|
+
closeButton: false,
|
|
75
|
+
})
|
|
76
|
+
.setLngLat(lngLat)
|
|
77
|
+
.setDOMContent(container)
|
|
78
|
+
.addTo(map);
|
|
79
|
+
|
|
80
|
+
render(<PopupContent {...content} onClose={getOnClose(map, id)} />, container);
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
}
|
|
84
|
+
if (!popup) return;
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
map.popups.push({
|
|
87
|
+
container,
|
|
88
|
+
ids,
|
|
89
|
+
options,
|
|
90
|
+
popup,
|
|
91
|
+
});
|
|
84
92
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
return ids;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function enforceMaxPopups(map: MapkaMap) {
|
|
97
|
+
if (map.popups.length > map.maxPopups) {
|
|
98
|
+
const popupToRemove = map.popups.shift();
|
|
99
|
+
if (popupToRemove) {
|
|
100
|
+
popupToRemove.popup.remove();
|
|
101
|
+
if (hasObjectContent(popupToRemove.options)) {
|
|
102
|
+
render(null, popupToRemove.container);
|
|
103
|
+
}
|
|
104
|
+
popupToRemove.container.remove();
|
|
105
|
+
}
|
|
98
106
|
}
|
|
99
|
-
return popup;
|
|
100
107
|
}
|
|
101
108
|
|
|
102
|
-
export function
|
|
103
|
-
const
|
|
109
|
+
export function reconciliatePopups(map: MapkaMap, options: MapkaPopupOptions[] = []) {
|
|
110
|
+
const resolved = resolveContentCreators(options);
|
|
111
|
+
const actions = computePopupGroups(map, resolved);
|
|
104
112
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
} else if (typeof content === "object") {
|
|
112
|
-
const onClose = getOnClose(map, id);
|
|
113
|
-
const mapkaPopups = map.popups.filter((popup) => popup.id === id);
|
|
114
|
-
|
|
115
|
-
for (const { popup, container, options } of mapkaPopups) {
|
|
116
|
-
const { closeButton } = options;
|
|
117
|
-
render(<PopupContent {...content} closeButton={closeButton} onClose={onClose} />, container);
|
|
118
|
-
updatePopupBaseOptions(popup, options, newOptions);
|
|
119
|
-
popup.setDOMContent(container);
|
|
113
|
+
for (const action of actions) {
|
|
114
|
+
if (action.type === "close") {
|
|
115
|
+
closePopupsByIds(map, action.ids);
|
|
116
|
+
} else if (action.type === "create") {
|
|
117
|
+
createNewPopup(map, action.options);
|
|
120
118
|
}
|
|
121
|
-
} else if (typeof content === "function") {
|
|
122
|
-
const newContent = content(id);
|
|
123
|
-
return updatePopup(map, {
|
|
124
|
-
...newOptions,
|
|
125
|
-
content: newContent,
|
|
126
|
-
});
|
|
127
119
|
}
|
|
120
|
+
|
|
121
|
+
enforceMaxPopups(map);
|
|
122
|
+
|
|
123
|
+
return resolved.map((opt) => opt.id);
|
|
128
124
|
}
|
|
129
125
|
|
|
130
|
-
/**
|
|
131
|
-
* Close all popups that have closeOnClick set to true or undefined
|
|
132
|
-
*/
|
|
133
126
|
export function closeOnMapClickPopups(map: MapkaMap) {
|
|
134
|
-
const popupsToCloseOnMapClick = remove(
|
|
135
|
-
|
|
136
|
-
(
|
|
137
|
-
|
|
127
|
+
const popupsToCloseOnMapClick = remove(map.popups, (popup) => {
|
|
128
|
+
const [first] = popup.options;
|
|
129
|
+
return (
|
|
130
|
+
popup.ids.length > 1 || first?.closeOnClick === true || first?.closeOnClick === undefined
|
|
131
|
+
);
|
|
132
|
+
});
|
|
138
133
|
for (const popup of popupsToCloseOnMapClick) {
|
|
139
134
|
popup.popup.remove();
|
|
140
|
-
if (
|
|
135
|
+
if (hasObjectContent(popup.options)) {
|
|
141
136
|
render(null, popup.container);
|
|
142
137
|
}
|
|
143
138
|
popup.container.remove();
|
|
144
139
|
}
|
|
145
140
|
}
|
|
146
141
|
|
|
147
|
-
export function
|
|
148
|
-
const
|
|
149
|
-
for (const popup of removedPopups) {
|
|
142
|
+
export function closePopups(map: MapkaMap) {
|
|
143
|
+
for (const popup of map.popups) {
|
|
150
144
|
popup.popup.remove();
|
|
151
|
-
if (
|
|
145
|
+
if (hasObjectContent(popup.options)) {
|
|
152
146
|
render(null, popup.container);
|
|
153
147
|
}
|
|
154
148
|
popup.container.remove();
|
|
155
149
|
}
|
|
150
|
+
map.popups = [];
|
|
156
151
|
}
|
|
157
152
|
|
|
158
|
-
export function
|
|
153
|
+
export function closePopupsByIds(map: MapkaMap, ids: string[]) {
|
|
154
|
+
const toClose: MapMapkaPopup[] = [];
|
|
155
|
+
const toReRender: MapMapkaPopup[] = [];
|
|
156
|
+
|
|
159
157
|
for (const popup of map.popups) {
|
|
158
|
+
const itemIndex = popup.ids.findIndex((id) => ids.includes(id));
|
|
159
|
+
if (itemIndex < 0) continue;
|
|
160
|
+
|
|
161
|
+
if (popup.ids.length === 1) {
|
|
162
|
+
toClose.push(popup);
|
|
163
|
+
} else if (popup.ids.length > 1) {
|
|
164
|
+
popup.ids.splice(itemIndex, 1);
|
|
165
|
+
popup.options.splice(itemIndex, 1);
|
|
166
|
+
|
|
167
|
+
toClose.push(popup);
|
|
168
|
+
toReRender.push(popup);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const popup of toClose) {
|
|
160
173
|
popup.popup.remove();
|
|
161
|
-
if (
|
|
174
|
+
if (hasObjectContent(popup.options)) {
|
|
162
175
|
render(null, popup.container);
|
|
163
176
|
}
|
|
164
177
|
popup.container.remove();
|
|
178
|
+
map.popups = without(map.popups, popup);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const popup of toReRender) {
|
|
182
|
+
createNewPopup(map, popup.options);
|
|
165
183
|
}
|
|
166
|
-
map.popups = [];
|
|
167
184
|
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import Supercluster from "supercluster";
|
|
2
|
+
import type { MapkaMap, MapMapkaPopup } from "../map.js";
|
|
3
|
+
import type { MapkaPopupOptionsResolved } from "../types/popup.js";
|
|
4
|
+
|
|
5
|
+
export type ClosePopupAction = { type: "close"; ids: string[] };
|
|
6
|
+
export type CreatePopupAction = { type: "create"; options: MapkaPopupOptionsResolved[] };
|
|
7
|
+
|
|
8
|
+
export type PopupGroupAction = ClosePopupAction | CreatePopupAction;
|
|
9
|
+
|
|
10
|
+
interface PopupCluster {
|
|
11
|
+
options: MapkaPopupOptionsResolved[];
|
|
12
|
+
lngLat: number[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface PopupPointProps {
|
|
16
|
+
index: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const WORLD_BOUNDS: [number, number, number, number] = [-180, -90, 180, 90];
|
|
20
|
+
|
|
21
|
+
function clustersByLocation(
|
|
22
|
+
previous: MapkaPopupOptionsResolved[],
|
|
23
|
+
newOptions: MapkaPopupOptionsResolved[],
|
|
24
|
+
zoom: number,
|
|
25
|
+
): PopupCluster[] {
|
|
26
|
+
const all = [...previous, ...newOptions];
|
|
27
|
+
|
|
28
|
+
const features: Supercluster.PointFeature<PopupPointProps>[] = all.map(({ lngLat }, index) => ({
|
|
29
|
+
type: "Feature",
|
|
30
|
+
properties: { index },
|
|
31
|
+
geometry: {
|
|
32
|
+
type: "Point",
|
|
33
|
+
coordinates: lngLat,
|
|
34
|
+
},
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
const sc = new Supercluster<PopupPointProps>({
|
|
38
|
+
radius: 20,
|
|
39
|
+
extent: 512,
|
|
40
|
+
});
|
|
41
|
+
sc.load(features);
|
|
42
|
+
|
|
43
|
+
const results = sc.getClusters(WORLD_BOUNDS, zoom);
|
|
44
|
+
const clusters: PopupCluster[] = [];
|
|
45
|
+
|
|
46
|
+
for (const { properties, geometry } of results) {
|
|
47
|
+
if ("cluster" in properties) {
|
|
48
|
+
const leaves = sc.getLeaves(properties.cluster_id, Infinity);
|
|
49
|
+
clusters.push({
|
|
50
|
+
options: leaves.map((point) => all[point.properties.index]),
|
|
51
|
+
lngLat: geometry.coordinates,
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
const opt = all[properties.index];
|
|
55
|
+
clusters.push({
|
|
56
|
+
options: [opt],
|
|
57
|
+
lngLat: opt.lngLat,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return clusters;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function clustersFromPopups(
|
|
66
|
+
notUpdated: MapkaPopupOptionsResolved[],
|
|
67
|
+
popups: MapMapkaPopup[],
|
|
68
|
+
): PopupCluster[] {
|
|
69
|
+
const clusters: PopupCluster[] = [];
|
|
70
|
+
for (const popup of popups) {
|
|
71
|
+
clusters.push({
|
|
72
|
+
options: popup.options.filter((opt) => notUpdated.includes(opt)),
|
|
73
|
+
lngLat: popup.popup.getLngLat().toArray(),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return clusters.filter((cluster) => cluster.options.length > 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function clusterKey(cluster: PopupCluster): string {
|
|
80
|
+
return cluster.options
|
|
81
|
+
.map((o) => o.id)
|
|
82
|
+
.sort()
|
|
83
|
+
.join("-");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function actionsByClustersChanges(
|
|
87
|
+
prev: PopupCluster[],
|
|
88
|
+
next: PopupCluster[],
|
|
89
|
+
): {
|
|
90
|
+
close: ClosePopupAction[];
|
|
91
|
+
create: CreatePopupAction[];
|
|
92
|
+
} {
|
|
93
|
+
const prevByKey = new Map(prev.map((c) => [clusterKey(c), c]));
|
|
94
|
+
const nextByKey = new Map(next.map((c) => [clusterKey(c), c]));
|
|
95
|
+
|
|
96
|
+
const close: ClosePopupAction[] = [];
|
|
97
|
+
const create: CreatePopupAction[] = [];
|
|
98
|
+
|
|
99
|
+
for (const [key, cluster] of prevByKey) {
|
|
100
|
+
if (!nextByKey.has(key)) {
|
|
101
|
+
close.push({
|
|
102
|
+
type: "close",
|
|
103
|
+
ids: cluster.options.map((o) => o.id),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const [key, cluster] of nextByKey) {
|
|
109
|
+
if (!prevByKey.has(key)) {
|
|
110
|
+
create.push({
|
|
111
|
+
type: "create",
|
|
112
|
+
options: cluster.options,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { close, create };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function actionsByProximity(
|
|
121
|
+
popups: MapMapkaPopup[],
|
|
122
|
+
newOptions: MapkaPopupOptionsResolved[],
|
|
123
|
+
zoom: number,
|
|
124
|
+
): PopupGroupAction[] {
|
|
125
|
+
const updated: MapkaPopupOptionsResolved[] = [];
|
|
126
|
+
const nonUpdated: MapkaPopupOptionsResolved[] = [];
|
|
127
|
+
|
|
128
|
+
for (const popup of popups) {
|
|
129
|
+
for (const opt of popup.options) {
|
|
130
|
+
if (newOptions.find((o) => o.id === opt.id)) {
|
|
131
|
+
updated.push(opt);
|
|
132
|
+
} else {
|
|
133
|
+
nonUpdated.push(opt);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const prevClusters = clustersFromPopups(nonUpdated, popups);
|
|
139
|
+
const nextClusters = clustersByLocation(nonUpdated, newOptions, zoom);
|
|
140
|
+
const { close, create } = actionsByClustersChanges(prevClusters, nextClusters);
|
|
141
|
+
|
|
142
|
+
const updatedIds = updated.map((opt) => opt.id);
|
|
143
|
+
const closeIds = close.flatMap((opt) => opt.ids);
|
|
144
|
+
|
|
145
|
+
return [
|
|
146
|
+
{
|
|
147
|
+
type: "close" as const,
|
|
148
|
+
ids: updatedIds.concat(closeIds),
|
|
149
|
+
},
|
|
150
|
+
...create,
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function actionsByChanges(
|
|
155
|
+
popups: MapMapkaPopup[],
|
|
156
|
+
newOptions: MapkaPopupOptionsResolved[],
|
|
157
|
+
): PopupGroupAction[] {
|
|
158
|
+
const updated = [];
|
|
159
|
+
|
|
160
|
+
for (const popup of popups) {
|
|
161
|
+
if (newOptions.find((o) => popup.ids.includes(o.id))) {
|
|
162
|
+
updated.push(popup);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return [
|
|
167
|
+
...updated.map((popup) => ({
|
|
168
|
+
type: "close" as const,
|
|
169
|
+
ids: popup.ids,
|
|
170
|
+
})),
|
|
171
|
+
{
|
|
172
|
+
type: "create" as const,
|
|
173
|
+
options: newOptions,
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function computePopupGroups(
|
|
179
|
+
map: MapkaMap,
|
|
180
|
+
newOptions: MapkaPopupOptionsResolved[],
|
|
181
|
+
): PopupGroupAction[] {
|
|
182
|
+
const popups = map.getPopups();
|
|
183
|
+
const zoom = map.getZoom();
|
|
184
|
+
|
|
185
|
+
if (map.scrollPopups) {
|
|
186
|
+
return actionsByProximity(popups, newOptions, zoom);
|
|
187
|
+
} else {
|
|
188
|
+
return actionsByChanges(popups, newOptions);
|
|
189
|
+
}
|
|
190
|
+
}
|
package/src/styles.css
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
@import "../../../node_modules/maplibre-gl/dist/maplibre-gl.css";
|
|
2
2
|
|
|
3
|
+
@import "./components/ImageCarousel.css";
|
|
4
|
+
@import "./components/PopupList.css";
|
|
5
|
+
@import "./components/PopupListItem.css";
|
|
6
|
+
@import "./components/PopupDataRows.css";
|
|
3
7
|
@import "./components/PopupContent.css";
|
|
4
8
|
@import "./controls/MapkaDrawControl.css";
|
package/src/types/popup.ts
CHANGED
|
@@ -5,6 +5,11 @@ export interface MapkaPopupRow {
|
|
|
5
5
|
value: unknown;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export interface MapkaPopupAction {
|
|
9
|
+
label: string;
|
|
10
|
+
onClick?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
export type MapkaLayerPopupRow = {
|
|
9
14
|
name: ExpressionSpecification | string;
|
|
10
15
|
value: ExpressionSpecification | unknown;
|
|
@@ -15,7 +20,8 @@ export interface MapkaPopupContent {
|
|
|
15
20
|
description?: string;
|
|
16
21
|
rows?: MapkaPopupRow[];
|
|
17
22
|
imageUrls?: string[];
|
|
18
|
-
|
|
23
|
+
/** Primary action button displayed in the popup */
|
|
24
|
+
primaryAction?: MapkaPopupAction;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
type CreatePopupElement = (id: string) => HTMLElement;
|
|
@@ -34,6 +40,11 @@ export interface MapkaPopupOptions extends PopupOptions {
|
|
|
34
40
|
content: MapkaPopupCreator;
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
export interface MapkaPopupOptionsResolved extends MapkaPopupOptions {
|
|
44
|
+
id: string;
|
|
45
|
+
content: HTMLElement | MapkaPopupContent;
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
export interface MapkaLayerPopupContent {
|
|
38
49
|
title: ExpressionSpecification | string;
|
|
39
50
|
description: ExpressionSpecification | string;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CircleIcon.d.ts","sourceRoot":"","sources":["../../src/components/CircleIcon.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,oCAmBtB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DownloadIcon.d.ts","sourceRoot":"","sources":["../../src/components/DownloadIcon.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,oCAqBxB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FreehandIcon.d.ts","sourceRoot":"","sources":["../../src/components/FreehandIcon.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,oCAmBxB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LineIcon.d.ts","sourceRoot":"","sources":["../../src/components/LineIcon.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,oCAqBpB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PencilIcon.d.ts","sourceRoot":"","sources":["../../src/components/PencilIcon.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,oCAoBtB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PolygonIcon.d.ts","sourceRoot":"","sources":["../../src/components/PolygonIcon.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,oCA0BvB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProgressDownIcon.d.ts","sourceRoot":"","sources":["../../src/components/ProgressDownIcon.tsx"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B,eAAO,MAAM,gBAAgB,qBAyB5B,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RectangleIcon.d.ts","sourceRoot":"","sources":["../../src/components/RectangleIcon.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,oCAmBzB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SelectIcon.d.ts","sourceRoot":"","sources":["../../src/components/SelectIcon.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,oCAoBtB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TrashIcon.d.ts","sourceRoot":"","sources":["../../src/components/TrashIcon.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,oCAuBrB,CAAC"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|