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