@maplat/ui 0.11.11 → 0.12.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.
package/src/ui_marker.ts CHANGED
@@ -1,409 +1,409 @@
1
- // import { Swiper } from "./swiper_ex";
2
- import "@c4h/chuci";
3
- import QRCode from "qrcode";
4
- import { point, polygon, booleanPointInPolygon } from "@turf/turf";
5
- import { createElement, resolveRelativeLink, prepareModal } from "./ui_utils";
6
-
7
- function detectMediaType(src: string): string {
8
- if (src.includes("youtube.com") || src.includes("youtu.be")) {
9
- return "youtube";
10
- }
11
- const ext = src.split(".").pop()?.toLowerCase();
12
- switch (ext) {
13
- case "mp4":
14
- case "webm":
15
- return "video";
16
- case "obj":
17
- return "3dmodel";
18
- case "splat":
19
- case "ply":
20
- return "gaussian";
21
- default:
22
- return "image";
23
- }
24
- }
25
- import type { MaplatUi } from "./index";
26
- import type { MarkerData, MediaSetting, MediaObject } from "./types";
27
-
28
- export function poiWebControl(
29
- ui: MaplatUi,
30
- div: HTMLElement,
31
- data: MarkerData,
32
- showShare: boolean = true
33
- ) {
34
- // let poiSwiper: SwiperInstance | undefined;
35
- div.innerHTML = "";
36
-
37
- if (data.url || data.html) {
38
- const htmlDiv =
39
- createElement(`<div class="${ui.enablePoiHtmlNoScroll ? "" : " embed-responsive embed-responsive-60vh"}">
40
- <iframe class="poi_iframe iframe_poi" frameborder="0" src=""${ui.enablePoiHtmlNoScroll ? ` onload="window.addEventListener('message', (e) =>{if (e.data[0] == 'setHeight') {this.style.height = e.data[1];}});" scrolling="no"` : ""}></iframe>
41
- </div>`)[0] as HTMLElement;
42
- div.appendChild(htmlDiv);
43
- const iframe = htmlDiv.querySelector(".poi_iframe") as HTMLIFrameElement;
44
-
45
- if (data.html) {
46
- const loadEvent = (event: Event) => {
47
- if (!event.currentTarget) return;
48
- event.currentTarget.removeEventListener(event.type, loadEvent);
49
- const cssLink = createElement(
50
- '<style type="text/css">html, body { height: 100vh; }\n img { width: 100%; }</style>'
51
- );
52
- const jsLink = createElement(
53
- `<script>
54
- const heightGetter = document.querySelector("#heightGetter");
55
- const resizeObserver = new ResizeObserver(entries => {
56
- window.parent.postMessage(["setHeight", (entries[0].target.clientHeight + 16) + "px"], "*");
57
- });
58
- if(heightGetter) resizeObserver.observe(heightGetter);
59
- </script>`
60
- );
61
- iframe.contentDocument!.head.appendChild(cssLink[0]);
62
- iframe.contentDocument!.head.appendChild(jsLink[0]);
63
- };
64
- iframe.addEventListener("load", loadEvent);
65
- iframe.removeAttribute("src");
66
- iframe.setAttribute(
67
- "srcdoc",
68
- `<div id="heightGetter">${ui.translate!(data.html) || ""}</div>`
69
- );
70
- } else {
71
- iframe.removeAttribute("srcdoc");
72
- iframe.setAttribute("src", ui.translate!(data.url) || "");
73
- }
74
- } else {
75
- const slides: string[] = [];
76
- const mediaList = (data.media || data.image) as MediaSetting[] | undefined;
77
- const inputs = mediaList
78
- ? Array.isArray(mediaList)
79
- ? mediaList
80
- : [mediaList]
81
- : [];
82
- let swiperStr = "";
83
-
84
- if (inputs.length > 0) {
85
- inputs.forEach((item: MediaSetting) => {
86
- let mediaObj: MediaObject;
87
- if (typeof item === "string") {
88
- mediaObj = {
89
- src: item,
90
- type: detectMediaType(item)
91
- };
92
- } else {
93
- mediaObj = { ...item }; // Clone to avoid mutation if needed
94
- if (!mediaObj.type) {
95
- mediaObj.type = detectMediaType(mediaObj.src);
96
- }
97
- // desc compatibility for caption
98
- if (mediaObj.desc && !mediaObj.caption) {
99
- mediaObj.caption = mediaObj.desc;
100
- }
101
- }
102
-
103
- const tmpSrc = resolveRelativeLink(mediaObj.src, "img"); // Assume 'img' type resolve works for most media assets or general path
104
-
105
- let slideAttrs = `image-url="${tmpSrc}" image-type="${mediaObj.type}"`;
106
-
107
- if (mediaObj.thumbnail) {
108
- slideAttrs += ` thumbnail-url="${resolveRelativeLink(mediaObj.thumbnail, "img")}"`;
109
- } else {
110
- // Default thumbnail to src for images, but for others (video etc) this might fail if no explicit thumb provided.
111
- // Legacy behavior was image-only so src was thumb.
112
- // For non-image types without thumbnail, Chuci might handle or show placeholder.
113
- // Let's use src as thumb for images or if nothing else.
114
- if (mediaObj.type === "image") {
115
- slideAttrs += ` thumbnail-url="${tmpSrc}"`;
116
- }
117
- }
118
-
119
- // Map other attributes
120
- for (const key of Object.keys(mediaObj)) {
121
- if (["src", "type", "thumbnail", "desc"].includes(key)) continue;
122
- const val = mediaObj[key];
123
- if (typeof val === "boolean") {
124
- if (val) slideAttrs += ` ${key}`;
125
- } else if (val !== undefined && val !== null) {
126
- slideAttrs += ` ${key}="${val}"`;
127
- }
128
- }
129
-
130
- slides.push(`<cc-swiper-slide ${slideAttrs}></cc-swiper-slide>`);
131
- });
132
-
133
- swiperStr = ` <div class="col-xs-12 poi_img_swiper">
134
- <cc-swiper>${slides.join("")}</cc-swiper>
135
- </div>`;
136
- }
137
- // Logic for noimage skipped/simplified as requested in previous interactions
138
-
139
- const htmlDiv = createElement(`<div class="poi_data">
140
- ${swiperStr}
141
- <p class="recipient poi_address"></p>
142
- <p class="recipient poi_desc"></p>
143
- </div>`)[0] as HTMLElement;
144
- div.appendChild(htmlDiv);
145
-
146
- // Inject custom CSS for Swiper if not exists (global check)
147
- if (!document.getElementById("poi-swiper-style")) {
148
- const style = document.createElement("style");
149
- style.id = "poi-swiper-style";
150
- style.innerHTML = `
151
- cc-swiper { --cc-slider-theme-color: #007aff; --cc-slider-navigation-color: #007aff; height: 300px; }
152
- cc-viewer { --cc-viewer-z-index: 100000; }
153
- `;
154
- document.head.appendChild(style);
155
- }
156
-
157
- (htmlDiv.querySelector(".poi_address") as HTMLElement).innerText =
158
- ui.translate!(data.address) || "";
159
- (htmlDiv.querySelector(".poi_desc") as HTMLElement).innerHTML = (
160
- ui.translate!(data.desc) || ""
161
- ).replace(/\n/g, "<br>");
162
-
163
- // Show/hide share buttons based on showShare parameter
164
- const shareButtons =
165
- ui.core!.mapDivDocument!.querySelector(".poi_share_buttons");
166
- const qrDiv = ui.core!.mapDivDocument!.querySelector(".qr_view_poi");
167
-
168
- if (showShare) {
169
- shareButtons?.classList.remove("hide");
170
- qrDiv?.classList.remove("hide");
171
-
172
- // Auto-generate QR code
173
- if (qrDiv) {
174
- QRCode.toCanvas(
175
- window.location.href,
176
- { width: 128, margin: 1 },
177
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
- (err: any, canvas: any) => {
179
- if (!err) {
180
- qrDiv.innerHTML = "";
181
- qrDiv.appendChild(canvas);
182
- }
183
- }
184
- );
185
- }
186
- } else {
187
- shareButtons?.classList.add("hide");
188
- qrDiv?.classList.add("hide");
189
- }
190
- }
191
-
192
- return undefined;
193
- }
194
-
195
- export function handleMarkerAction(ui: MaplatUi, data: MarkerData) {
196
- const modalElm = ui.core!.mapDivDocument!.querySelector(".modalBase")!;
197
- const modal = prepareModal(modalElm);
198
-
199
- if (data.directgo) {
200
- let blank = false;
201
- let href = "";
202
- if (typeof data.directgo == "string") {
203
- href = data.directgo;
204
- } else {
205
- href = data.directgo.href;
206
- blank = data.directgo.blank || false;
207
- }
208
- if (blank) {
209
- window.open(href, "_blank");
210
- } else {
211
- window.location.href = href;
212
- }
213
- return;
214
- }
215
-
216
- const titleEl =
217
- ui.core!.mapDivDocument!.querySelector(".modal_poi_title") ||
218
- ui.core!.mapDivDocument!.querySelector(".modal_title");
219
- if (titleEl)
220
- (titleEl as HTMLElement).innerText = ui.translate!(data.name) || "";
221
-
222
- // Prepare container - ensure poi_web_div exists or target it
223
- let poiWebDiv = ui.core!.mapDivDocument!.querySelector(".poi_web_div");
224
- if (!poiWebDiv) {
225
- // Fallback or create? simpler to assume ui_init will be updated,
226
- // but for robustness we can clear modal_poi_content and append it if missing,
227
- // OR target existing .poi_web (renaming it effectively).
228
- // Let's assume ui_init.ts will provide .poi_web_div.
229
- // If not present, we grab .modal_poi_content and insert it?
230
- const modalPoiContent =
231
- ui.core!.mapDivDocument!.querySelector(".modal_poi_content");
232
- if (modalPoiContent) {
233
- poiWebDiv = createElement(
234
- '<div class="poi_web_div"></div>'
235
- )[0] as HTMLElement;
236
- // Prepend to avoid messing with share buttons at bottom?
237
- modalPoiContent.insertBefore(poiWebDiv, modalPoiContent.firstChild);
238
- }
239
- }
240
-
241
- // Show share buttons only if both enableShare and stateUrl are true
242
- const showShare = ui.enableShare && !!ui.appOption.stateUrl;
243
- poiWebControl(ui, poiWebDiv as HTMLElement, data, showShare);
244
-
245
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
246
- const hiddenFunc = (_event: any) => {
247
- modalElm.removeEventListener("hidden.bs.modal", hiddenFunc, false);
248
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
249
- (ui.core as any).unselectMarker?.();
250
- ui.selectedMarkerNamespaceID = undefined;
251
- ui.updateUrl();
252
- };
253
- modalElm.addEventListener("hidden.bs.modal", hiddenFunc, false);
254
-
255
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
- (ui.core as any).selectMarker?.(data.namespaceID);
257
- ui.selectedMarkerNamespaceID = data.namespaceID;
258
- ui.modalSetting("poi");
259
- modal.show();
260
- ui.updateUrl();
261
- }
262
-
263
- export function showContextMenu(ui: MaplatUi, list: MarkerData[]) {
264
- if (!ui.contextMenu) return;
265
-
266
- ui.contextMenu.clear();
267
- ui.contextMenu.extend(list);
268
-
269
- const pixel = ui.lastClickPixel;
270
- const coordinate = ui.lastClickCoordinate;
271
-
272
- if (!pixel) {
273
- console.warn("[Debug] No lastClickPixel for ContextMenu");
274
- return;
275
- }
276
-
277
- const openHandler = () => {
278
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
279
- (ui.contextMenu as any).un("open", openHandler);
280
- const closeHandler = () => {
281
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
282
- (ui.contextMenu as any).un("close", closeHandler);
283
- };
284
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
- (ui.contextMenu as any).on("close", closeHandler);
286
- };
287
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
288
- (ui.contextMenu as any).on("open", openHandler);
289
-
290
- // Need direct access to internal openMenu if possible, or standard open
291
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
292
- (ui.contextMenu as any).Internal?.openMenu(pixel, coordinate);
293
-
294
- // One-time click to close (mimic legacy)
295
- const viewport = ui.core!.mapObject.getViewport();
296
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
297
- const closeMenuHandler = (e: any) => {
298
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
299
- if ((ui.contextMenu as any).Internal.opened) {
300
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
301
- (ui.contextMenu as any).Internal?.closeMenu();
302
- e.stopPropagation();
303
- viewport.removeEventListener(e.type, closeMenuHandler, false);
304
- }
305
- };
306
- viewport.addEventListener("pointerdown", closeMenuHandler, false);
307
- }
308
-
309
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
310
- export async function xyToMapIDs(ui: MaplatUi, xy: any, threshold = 10) {
311
- const point_ = point(xy);
312
-
313
- const map = ui.core!.mapObject;
314
- const size = map.getSize();
315
- const extent = [[0, 0], [size[0], 0], size, [0, size[1]], [0, 0]];
316
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
- const sysCoords = extent.map((pixel: any) =>
318
- map.getCoordinateFromPixel(pixel)
319
- );
320
- const mercs = await (typeof ui.core!.from!.xy2SysCoord !== "function" // ERROR HERE - wait, index.ts line 1273 - checking source
321
- ? Promise.resolve(sysCoords)
322
- : Promise.all(
323
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
324
- sysCoords.map((sysCoord: any) =>
325
- ui.core!.from!.sysCoord2MercAsync(sysCoord)
326
- )
327
- ));
328
- const areaIndex = ui.areaIndex(mercs);
329
-
330
- return Promise.all(
331
- Object.keys(ui.core!.cacheHash!)
332
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
333
- .filter((key: any) => ui.core!.cacheHash[key].envelope)
334
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
335
- .map((key: any) => {
336
- const source = ui.core!.cacheHash[key];
337
- return Promise.all([
338
- Promise.resolve(source),
339
- Promise.all(
340
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
341
- source.envelope.geometry.coordinates[0].map((coord: any) =>
342
- ui.core!.from!.merc2SysCoordAsync(coord)
343
- )
344
- )
345
- ]);
346
- })
347
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
348
- ).then((sources: any) => {
349
- const mapIDs = sources
350
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
351
- .reduce((prev: any, curr: any) => {
352
- const source = curr[0];
353
- const mercXys = curr[1];
354
- if (source.mapID !== ui.core!.from!.mapID) {
355
- const polygon_ = polygon([mercXys]);
356
- if (booleanPointInPolygon(point_, polygon_)) {
357
- prev.push(source);
358
- }
359
- }
360
- return prev;
361
- }, [])
362
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
363
- .filter((source: any) => source.envelopeAreaIndex / areaIndex < threshold)
364
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
365
- .sort((a: any, b: any) => a.envelopeAreaIndex - b.envelopeAreaIndex)
366
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
367
- .map((source: any) => source.mapID);
368
- return mapIDs;
369
- });
370
- }
371
-
372
- export function setHideMarker(ui: MaplatUi, flag: boolean) {
373
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
374
- ui.core!.requestUpdateState({ hideMarker: flag ? 1 : 0 } as any);
375
- if (flag) {
376
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
377
- if ((ui.core as any).hideAllMarkers) {
378
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
379
- (ui.core as any).hideAllMarkers();
380
- }
381
- ui.core!.mapDivDocument!.classList.add("hide-marker");
382
- } else {
383
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
384
- if ((ui.core as any).showAllMarkers) {
385
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
386
- (ui.core as any).showAllMarkers();
387
- }
388
- ui.core!.mapDivDocument!.classList.remove("hide-marker");
389
- }
390
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
391
- if (ui.core!.restoreSession as any) {
392
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
393
- ui.core!.requestUpdateState({ hideMarker: flag ? 1 : 0 } as any);
394
- }
395
- }
396
-
397
- export function checkOverlayID(ui: MaplatUi, mapID: string) {
398
- const swiper = ui.overlaySwiper;
399
- const sliders = Array.from(swiper.$el[0].querySelectorAll(".swiper-slide"));
400
- return sliders.some(slider => slider.getAttribute("data") === mapID);
401
- }
402
-
403
- export function handleMarkerActionById(ui: MaplatUi, markerId: string) {
404
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
405
- const data = (ui.core as any).getMarker(markerId);
406
- if (data) {
407
- handleMarkerAction(ui, data);
408
- }
409
- }
1
+ // import { Swiper } from "./swiper_ex";
2
+ import "@c4h/chuci";
3
+ import QRCode from "qrcode";
4
+ import { point, polygon, booleanPointInPolygon } from "@turf/turf";
5
+ import { createElement, resolveRelativeLink, prepareModal } from "./ui_utils";
6
+
7
+ function detectMediaType(src: string): string {
8
+ if (src.includes("youtube.com") || src.includes("youtu.be")) {
9
+ return "youtube";
10
+ }
11
+ const ext = src.split(".").pop()?.toLowerCase();
12
+ switch (ext) {
13
+ case "mp4":
14
+ case "webm":
15
+ return "video";
16
+ case "obj":
17
+ return "3dmodel";
18
+ case "splat":
19
+ case "ply":
20
+ return "gaussian";
21
+ default:
22
+ return "image";
23
+ }
24
+ }
25
+ import type { MaplatUi } from "./index";
26
+ import type { MarkerData, MediaSetting, MediaObject } from "./types";
27
+
28
+ export function poiWebControl(
29
+ ui: MaplatUi,
30
+ div: HTMLElement,
31
+ data: MarkerData,
32
+ showShare: boolean = true
33
+ ) {
34
+ // let poiSwiper: SwiperInstance | undefined;
35
+ div.innerHTML = "";
36
+
37
+ if (data.url || data.html) {
38
+ const htmlDiv =
39
+ createElement(`<div class="${ui.enablePoiHtmlNoScroll ? "" : " embed-responsive embed-responsive-60vh"}">
40
+ <iframe class="poi_iframe iframe_poi" frameborder="0" src=""${ui.enablePoiHtmlNoScroll ? ` onload="window.addEventListener('message', (e) =>{if (e.data[0] == 'setHeight') {this.style.height = e.data[1];}});" scrolling="no"` : ""}></iframe>
41
+ </div>`)[0] as HTMLElement;
42
+ div.appendChild(htmlDiv);
43
+ const iframe = htmlDiv.querySelector(".poi_iframe") as HTMLIFrameElement;
44
+
45
+ if (data.html) {
46
+ const loadEvent = (event: Event) => {
47
+ if (!event.currentTarget) return;
48
+ event.currentTarget.removeEventListener(event.type, loadEvent);
49
+ const cssLink = createElement(
50
+ '<style type="text/css">html, body { height: 100vh; }\n img { width: 100%; }</style>'
51
+ );
52
+ const jsLink = createElement(
53
+ `<script>
54
+ const heightGetter = document.querySelector("#heightGetter");
55
+ const resizeObserver = new ResizeObserver(entries => {
56
+ window.parent.postMessage(["setHeight", (entries[0].target.clientHeight + 16) + "px"], "*");
57
+ });
58
+ if(heightGetter) resizeObserver.observe(heightGetter);
59
+ </script>`
60
+ );
61
+ iframe.contentDocument!.head.appendChild(cssLink[0]);
62
+ iframe.contentDocument!.head.appendChild(jsLink[0]);
63
+ };
64
+ iframe.addEventListener("load", loadEvent);
65
+ iframe.removeAttribute("src");
66
+ iframe.setAttribute(
67
+ "srcdoc",
68
+ `<div id="heightGetter">${ui.translate!(data.html) || ""}</div>`
69
+ );
70
+ } else {
71
+ iframe.removeAttribute("srcdoc");
72
+ iframe.setAttribute("src", ui.translate!(data.url) || "");
73
+ }
74
+ } else {
75
+ const slides: string[] = [];
76
+ const mediaList = (data.media || data.image) as MediaSetting[] | undefined;
77
+ const inputs = mediaList
78
+ ? Array.isArray(mediaList)
79
+ ? mediaList
80
+ : [mediaList]
81
+ : [];
82
+ let swiperStr = "";
83
+
84
+ if (inputs.length > 0) {
85
+ inputs.forEach((item: MediaSetting) => {
86
+ let mediaObj: MediaObject;
87
+ if (typeof item === "string") {
88
+ mediaObj = {
89
+ src: item,
90
+ type: detectMediaType(item)
91
+ };
92
+ } else {
93
+ mediaObj = { ...item }; // Clone to avoid mutation if needed
94
+ if (!mediaObj.type) {
95
+ mediaObj.type = detectMediaType(mediaObj.src);
96
+ }
97
+ // desc compatibility for caption
98
+ if (mediaObj.desc && !mediaObj.caption) {
99
+ mediaObj.caption = mediaObj.desc;
100
+ }
101
+ }
102
+
103
+ const tmpSrc = resolveRelativeLink(mediaObj.src, "img"); // Assume 'img' type resolve works for most media assets or general path
104
+
105
+ let slideAttrs = `image-url="${tmpSrc}" image-type="${mediaObj.type}"`;
106
+
107
+ if (mediaObj.thumbnail) {
108
+ slideAttrs += ` thumbnail-url="${resolveRelativeLink(mediaObj.thumbnail, "img")}"`;
109
+ } else {
110
+ // Default thumbnail to src for images, but for others (video etc) this might fail if no explicit thumb provided.
111
+ // Legacy behavior was image-only so src was thumb.
112
+ // For non-image types without thumbnail, Chuci might handle or show placeholder.
113
+ // Let's use src as thumb for images or if nothing else.
114
+ if (mediaObj.type === "image") {
115
+ slideAttrs += ` thumbnail-url="${tmpSrc}"`;
116
+ }
117
+ }
118
+
119
+ // Map other attributes
120
+ for (const key of Object.keys(mediaObj)) {
121
+ if (["src", "type", "thumbnail", "desc"].includes(key)) continue;
122
+ const val = mediaObj[key];
123
+ if (typeof val === "boolean") {
124
+ if (val) slideAttrs += ` ${key}`;
125
+ } else if (val !== undefined && val !== null) {
126
+ slideAttrs += ` ${key}="${val}"`;
127
+ }
128
+ }
129
+
130
+ slides.push(`<cc-swiper-slide ${slideAttrs}></cc-swiper-slide>`);
131
+ });
132
+
133
+ swiperStr = ` <div class="col-xs-12 poi_img_swiper">
134
+ <cc-swiper>${slides.join("")}</cc-swiper>
135
+ </div>`;
136
+ }
137
+ // Logic for noimage skipped/simplified as requested in previous interactions
138
+
139
+ const htmlDiv = createElement(`<div class="poi_data">
140
+ ${swiperStr}
141
+ <p class="recipient poi_address"></p>
142
+ <p class="recipient poi_desc"></p>
143
+ </div>`)[0] as HTMLElement;
144
+ div.appendChild(htmlDiv);
145
+
146
+ // Inject custom CSS for Swiper if not exists (global check)
147
+ if (!document.getElementById("poi-swiper-style")) {
148
+ const style = document.createElement("style");
149
+ style.id = "poi-swiper-style";
150
+ style.innerHTML = `
151
+ cc-swiper { --cc-slider-theme-color: #007aff; --cc-slider-navigation-color: #007aff; height: 300px; }
152
+ cc-viewer { --cc-viewer-z-index: 100000; }
153
+ `;
154
+ document.head.appendChild(style);
155
+ }
156
+
157
+ (htmlDiv.querySelector(".poi_address") as HTMLElement).innerText =
158
+ ui.translate!(data.address) || "";
159
+ (htmlDiv.querySelector(".poi_desc") as HTMLElement).innerHTML = (
160
+ ui.translate!(data.desc) || ""
161
+ ).replace(/\n/g, "<br>");
162
+
163
+ // Show/hide share buttons based on showShare parameter
164
+ const shareButtons =
165
+ ui.core!.mapDivDocument!.querySelector(".poi_share_buttons");
166
+ const qrDiv = ui.core!.mapDivDocument!.querySelector(".qr_view_poi");
167
+
168
+ if (showShare) {
169
+ shareButtons?.classList.remove("hide");
170
+ qrDiv?.classList.remove("hide");
171
+
172
+ // Auto-generate QR code
173
+ if (qrDiv) {
174
+ QRCode.toCanvas(
175
+ window.location.href,
176
+ { width: 128, margin: 1 },
177
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
+ (err: any, canvas: any) => {
179
+ if (!err) {
180
+ qrDiv.innerHTML = "";
181
+ qrDiv.appendChild(canvas);
182
+ }
183
+ }
184
+ );
185
+ }
186
+ } else {
187
+ shareButtons?.classList.add("hide");
188
+ qrDiv?.classList.add("hide");
189
+ }
190
+ }
191
+
192
+ return undefined;
193
+ }
194
+
195
+ export function handleMarkerAction(ui: MaplatUi, data: MarkerData) {
196
+ const modalElm = ui.core!.mapDivDocument!.querySelector(".modalBase")!;
197
+ const modal = prepareModal(modalElm);
198
+
199
+ if (data.directgo) {
200
+ let blank = false;
201
+ let href = "";
202
+ if (typeof data.directgo == "string") {
203
+ href = data.directgo;
204
+ } else {
205
+ href = data.directgo.href;
206
+ blank = data.directgo.blank || false;
207
+ }
208
+ if (blank) {
209
+ window.open(href, "_blank");
210
+ } else {
211
+ window.location.href = href;
212
+ }
213
+ return;
214
+ }
215
+
216
+ const titleEl =
217
+ ui.core!.mapDivDocument!.querySelector(".modal_poi_title") ||
218
+ ui.core!.mapDivDocument!.querySelector(".modal_title");
219
+ if (titleEl)
220
+ (titleEl as HTMLElement).innerText = ui.translate!(data.name) || "";
221
+
222
+ // Prepare container - ensure poi_web_div exists or target it
223
+ let poiWebDiv = ui.core!.mapDivDocument!.querySelector(".poi_web_div");
224
+ if (!poiWebDiv) {
225
+ // Fallback or create? simpler to assume ui_init will be updated,
226
+ // but for robustness we can clear modal_poi_content and append it if missing,
227
+ // OR target existing .poi_web (renaming it effectively).
228
+ // Let's assume ui_init.ts will provide .poi_web_div.
229
+ // If not present, we grab .modal_poi_content and insert it?
230
+ const modalPoiContent =
231
+ ui.core!.mapDivDocument!.querySelector(".modal_poi_content");
232
+ if (modalPoiContent) {
233
+ poiWebDiv = createElement(
234
+ '<div class="poi_web_div"></div>'
235
+ )[0] as HTMLElement;
236
+ // Prepend to avoid messing with share buttons at bottom?
237
+ modalPoiContent.insertBefore(poiWebDiv, modalPoiContent.firstChild);
238
+ }
239
+ }
240
+
241
+ // Show share buttons only if both enableShare and stateUrl are true
242
+ const showShare = ui.enableShare && !!ui.appOption.stateUrl;
243
+ poiWebControl(ui, poiWebDiv as HTMLElement, data, showShare);
244
+
245
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
246
+ const hiddenFunc = (_event: any) => {
247
+ modalElm.removeEventListener("hidden.bs.modal", hiddenFunc, false);
248
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
249
+ (ui.core as any).unselectMarker?.();
250
+ ui.selectedMarkerNamespaceID = undefined;
251
+ ui.updateUrl();
252
+ };
253
+ modalElm.addEventListener("hidden.bs.modal", hiddenFunc, false);
254
+
255
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
+ (ui.core as any).selectMarker?.(data.namespaceID);
257
+ ui.selectedMarkerNamespaceID = data.namespaceID;
258
+ ui.modalSetting("poi");
259
+ modal.show();
260
+ ui.updateUrl();
261
+ }
262
+
263
+ export function showContextMenu(ui: MaplatUi, list: MarkerData[]) {
264
+ if (!ui.contextMenu) return;
265
+
266
+ ui.contextMenu.clear();
267
+ ui.contextMenu.extend(list);
268
+
269
+ const pixel = ui.lastClickPixel;
270
+ const coordinate = ui.lastClickCoordinate;
271
+
272
+ if (!pixel) {
273
+ console.warn("[Debug] No lastClickPixel for ContextMenu");
274
+ return;
275
+ }
276
+
277
+ const openHandler = () => {
278
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
279
+ (ui.contextMenu as any).un("open", openHandler);
280
+ const closeHandler = () => {
281
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
282
+ (ui.contextMenu as any).un("close", closeHandler);
283
+ };
284
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
+ (ui.contextMenu as any).on("close", closeHandler);
286
+ };
287
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
288
+ (ui.contextMenu as any).on("open", openHandler);
289
+
290
+ // Need direct access to internal openMenu if possible, or standard open
291
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
292
+ (ui.contextMenu as any).Internal?.openMenu(pixel, coordinate);
293
+
294
+ // One-time click to close (mimic legacy)
295
+ const viewport = ui.core!.mapObject.getViewport();
296
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
297
+ const closeMenuHandler = (e: any) => {
298
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
299
+ if ((ui.contextMenu as any).Internal.opened) {
300
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
301
+ (ui.contextMenu as any).Internal?.closeMenu();
302
+ e.stopPropagation();
303
+ viewport.removeEventListener(e.type, closeMenuHandler, false);
304
+ }
305
+ };
306
+ viewport.addEventListener("pointerdown", closeMenuHandler, false);
307
+ }
308
+
309
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
310
+ export async function xyToMapIDs(ui: MaplatUi, xy: any, threshold = 10) {
311
+ const point_ = point(xy);
312
+
313
+ const map = ui.core!.mapObject;
314
+ const size = map.getSize();
315
+ const extent = [[0, 0], [size[0], 0], size, [0, size[1]], [0, 0]];
316
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
+ const sysCoords = extent.map((pixel: any) =>
318
+ map.getCoordinateFromPixel(pixel)
319
+ );
320
+ const mercs = await (typeof ui.core!.from!.xy2SysCoord !== "function" // ERROR HERE - wait, index.ts line 1273 - checking source
321
+ ? Promise.resolve(sysCoords)
322
+ : Promise.all(
323
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
324
+ sysCoords.map((sysCoord: any) =>
325
+ ui.core!.from!.sysCoord2MercAsync(sysCoord)
326
+ )
327
+ ));
328
+ const areaIndex = ui.areaIndex(mercs);
329
+
330
+ return Promise.all(
331
+ Object.keys(ui.core!.cacheHash!)
332
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
333
+ .filter((key: any) => ui.core!.cacheHash[key].envelope)
334
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
335
+ .map((key: any) => {
336
+ const source = ui.core!.cacheHash[key];
337
+ return Promise.all([
338
+ Promise.resolve(source),
339
+ Promise.all(
340
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
341
+ source.envelope.geometry.coordinates[0].map((coord: any) =>
342
+ ui.core!.from!.merc2SysCoordAsync(coord)
343
+ )
344
+ )
345
+ ]);
346
+ })
347
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
348
+ ).then((sources: any) => {
349
+ const mapIDs = sources
350
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
351
+ .reduce((prev: any, curr: any) => {
352
+ const source = curr[0];
353
+ const mercXys = curr[1];
354
+ if (source.mapID !== ui.core!.from!.mapID) {
355
+ const polygon_ = polygon([mercXys]);
356
+ if (booleanPointInPolygon(point_, polygon_)) {
357
+ prev.push(source);
358
+ }
359
+ }
360
+ return prev;
361
+ }, [])
362
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
363
+ .filter((source: any) => source.envelopeAreaIndex / areaIndex < threshold)
364
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
365
+ .sort((a: any, b: any) => a.envelopeAreaIndex - b.envelopeAreaIndex)
366
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
367
+ .map((source: any) => source.mapID);
368
+ return mapIDs;
369
+ });
370
+ }
371
+
372
+ export function setHideMarker(ui: MaplatUi, flag: boolean) {
373
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
374
+ ui.core!.requestUpdateState({ hideMarker: flag ? 1 : 0 } as any);
375
+ if (flag) {
376
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
377
+ if ((ui.core as any).hideAllMarkers) {
378
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
379
+ (ui.core as any).hideAllMarkers();
380
+ }
381
+ ui.core!.mapDivDocument!.classList.add("hide-marker");
382
+ } else {
383
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
384
+ if ((ui.core as any).showAllMarkers) {
385
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
386
+ (ui.core as any).showAllMarkers();
387
+ }
388
+ ui.core!.mapDivDocument!.classList.remove("hide-marker");
389
+ }
390
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
391
+ if (ui.core!.restoreSession as any) {
392
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
393
+ ui.core!.requestUpdateState({ hideMarker: flag ? 1 : 0 } as any);
394
+ }
395
+ }
396
+
397
+ export function checkOverlayID(ui: MaplatUi, mapID: string) {
398
+ const swiper = ui.overlaySwiper;
399
+ const sliders = Array.from(swiper.$el[0].querySelectorAll(".swiper-slide"));
400
+ return sliders.some(slider => slider.getAttribute("data") === mapID);
401
+ }
402
+
403
+ export function handleMarkerActionById(ui: MaplatUi, markerId: string) {
404
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
405
+ const data = (ui.core as any).getMarker(markerId);
406
+ if (data) {
407
+ handleMarkerAction(ui, data);
408
+ }
409
+ }