@maplat/ui 0.11.3 → 0.11.5

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_init.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { MaplatApp as Core, createElement } from "@maplat/core";
2
- import * as bsn from "bootstrap.native";
1
+ import { MaplatApp as Core, createElement, MaplatApp } from "@maplat/core";
2
+
3
3
  import pointer from "./pointer_images";
4
4
  import { Swiper } from "./swiper_ex";
5
5
  import { Navigation, Pagination } from "swiper";
@@ -21,7 +21,7 @@ import ContextMenu from "./contextmenu";
21
21
  import Weiwudi from "@c4h/weiwudi";
22
22
  import absoluteUrl from "./absolute_url";
23
23
  import * as QRCode from "qrcode";
24
- import { ellips, isBasemap, encBytes } from "./ui_utils";
24
+ import { ellips, encBytes, isBasemap, prepareModal } from "./ui_utils";
25
25
 
26
26
  import { poiWebControl } from "./ui_marker";
27
27
 
@@ -88,12 +88,6 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
88
88
  ui.setShowBorder(false);
89
89
  }
90
90
 
91
- const enableSplash = !ui.core!.initialRestore.mapID;
92
- const restoreTransparency =
93
- ui.core!.initialRestore.transparency ||
94
- (appOption.restore ? appOption.restore.transparency : undefined);
95
- const enableOutOfMap = !appOption.presentationMode;
96
-
97
91
  ui.enablePoiHtmlNoScroll = appOption.enablePoiHtmlNoScroll || false;
98
92
  if (appOption.enableShare) {
99
93
  ui.core!.mapDivDocument!.classList.add("enable_share");
@@ -134,63 +128,378 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
134
128
  ui.appEnvelope = true;
135
129
  }
136
130
 
137
- // Inject Custom Toast Styles
138
- const style = document.createElement("style");
139
- style.innerHTML = `
140
- .custom-toast {
141
- visibility: hidden;
142
- min-width: 250px;
143
- margin-left: -125px;
144
- background-color: #333;
145
- color: #fff;
146
- text-align: center;
147
- border-radius: 2px;
148
- padding: 16px;
149
- position: fixed;
150
- z-index: 9999;
151
- left: 50%;
152
- bottom: 30px;
153
- font-size: 17px;
154
- opacity: 0;
155
- transition: opacity 0.3s;
131
+ initDom(ui, appOption);
132
+ initModalHandlers(ui, appOption);
133
+ initMapEventListeners(ui);
134
+ initGpsHandlers(ui, appOption);
135
+ }
136
+
137
+ function initGpsHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
138
+ const enableOutOfMap = !appOption.presentationMode;
139
+ const mapDiv = ui.core!.mapDivDocument!;
140
+ const modalBase = mapDiv.querySelector(".modalBase")!;
141
+ const modalTitle = mapDiv.querySelector(".modal_title") as HTMLElement;
142
+ const modalGpsDContent = mapDiv.querySelector(
143
+ ".modal_gpsD_content"
144
+ ) as HTMLElement;
145
+
146
+ ui.core!.addEventListener("outOfMap", (_evt: unknown) => {
147
+ console.log("Event: outOfMap");
148
+ if (enableOutOfMap) {
149
+ modalTitle.innerText = ui.core!.t("app.out_of_map") || "";
150
+ modalGpsDContent.innerText = ui.core!.t("app.out_of_map_area") || "";
151
+ ui.modalSetting("gpsD");
152
+ prepareModal(modalBase, { root: mapDiv }).show();
153
+ }
154
+ });
155
+
156
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
+ ui.core!.addEventListener("gps_error", (evt: any) => {
158
+ console.log("GPS Error:", evt);
159
+ const errorMap: Record<string, string> = {
160
+ user_gps_deny: "app.user_gps_deny",
161
+ gps_miss: "app.gps_miss",
162
+ gps_timeout: "app.gps_timeout"
163
+ };
164
+
165
+ if (!ui.core) return;
166
+ modalTitle.innerText = ui.core.t("app.gps_error") || "";
167
+ modalGpsDContent.innerText =
168
+ ui.core.t(errorMap[evt.detail] || "app.gps_error") || "";
169
+ ui.modalSetting("gpsD");
170
+ prepareModal(modalBase, {
171
+ root: mapDiv
172
+ }).show();
173
+ });
174
+
175
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
176
+ ui.core!.addEventListener("gps_result", (evt: any) => {
177
+ console.log("GPS Result:", evt);
178
+ if (evt.detail && evt.detail.error) {
179
+ const error = evt.detail.error;
180
+ if (error === "gps_off") {
181
+ ui.lastGPSError = undefined;
182
+ return;
156
183
  }
157
- .custom-toast.show {
158
- visibility: visible;
159
- opacity: 1;
184
+
185
+ ui.lastGPSError = error;
186
+ if (ui.alwaysGpsOn && error === "gps_out") return;
187
+
188
+ if (!ui.core) return;
189
+
190
+ const modal = prepareModal(modalBase, {
191
+ root: mapDiv
192
+ });
193
+
194
+ if (error === "gps_out") {
195
+ modalTitle.innerText = ui.core.t("app.out_of_map") || "";
196
+ modalGpsDContent.innerText = ui.core.t("app.out_of_map_area") || "";
197
+ } else {
198
+ const errorMap: Record<string, string> = {
199
+ user_gps_deny: "app.user_gps_deny",
200
+ gps_miss: "app.gps_miss",
201
+ gps_timeout: "app.gps_timeout"
202
+ };
203
+
204
+ modalTitle.innerText = ui.core.t("app.gps_error") || "";
205
+ modalGpsDContent.innerText =
206
+ ui.core.t(errorMap[error] || "app.gps_error") || "";
160
207
  }
161
- `;
162
- document.head.appendChild(style);
163
208
 
164
- let pwaManifest = appOption.pwaManifest;
165
- let pwaWorker = appOption.pwaWorker;
166
- let pwaScope = appOption.pwaScope;
209
+ ui.modalSetting("gpsD");
210
+ modal.show();
211
+ } else {
212
+ ui.lastGPSError = undefined;
213
+ }
214
+ });
215
+ }
167
216
 
168
- // Inject FontAwesome CDN for reliable icons (REMOVED)
169
- // Icons: Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0)
217
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
218
+ function initSwipers(ui: MaplatUi, sources: any[]) {
219
+ const colors = [
220
+ "maroon",
221
+ "deeppink",
222
+ "indigo",
223
+ "olive",
224
+ "royalblue",
225
+ "red",
226
+ "hotpink",
227
+ "green",
228
+ "yellow",
229
+ "navy",
230
+ "saddlebrown",
231
+ "fuchsia",
232
+ "darkslategray",
233
+ "yellowgreen",
234
+ "blue",
235
+ "mediumvioletred",
236
+ "purple",
237
+ "lime",
238
+ "darkorange",
239
+ "teal",
240
+ "crimson",
241
+ "darkviolet",
242
+ "darkolivegreen",
243
+ "steelblue",
244
+ "aqua"
245
+ ];
246
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
247
+ const appBbox: any[] = [];
248
+ let cIndex = 0;
170
249
 
171
- // Add UI HTML Element
172
- let newElems = createElement(`<d c="ol-control map-title"><s></s></d>
173
- <d c="swiper-container ol-control base-swiper prevent-default-ui">
174
- <d c="swiper-wrapper"> </d>
175
- <d c="swiper-button-next base-next swiper-button-white"> </d>
176
- <d c="swiper-button-prev base-prev swiper-button-white"> </d>
177
- </d>
178
- <d c="swiper-container ol-control overlay-swiper prevent-default-ui">
179
- <d c="swiper-wrapper"> </d>
180
- <d c="swiper-button-next overlay-next swiper-button-white"> </d>
181
- <d c="swiper-button-prev overlay-prev swiper-button-white"> </d>
182
- </d> `);
183
- for (let i = newElems.length - 1; i >= 0; i--) {
184
- ui.core!.mapDivDocument!.insertBefore(
185
- newElems[i],
186
- ui.core!.mapDivDocument!.firstChild
250
+ for (let i = 0; i < sources.length; i++) {
251
+ const source = sources[i];
252
+ if (source.envelope) {
253
+ if (ui.appEnvelope) {
254
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
255
+ source.envelope.geometry.coordinates[0].map((xy: any) => {
256
+ if (appBbox.length === 0) {
257
+ appBbox[0] = appBbox[2] = xy[0];
258
+ appBbox[1] = appBbox[3] = xy[1];
259
+ } else {
260
+ if (xy[0] < appBbox[0]) appBbox[0] = xy[0];
261
+ if (xy[0] > appBbox[2]) appBbox[2] = xy[0];
262
+
263
+ if (xy[1] < appBbox[1]) appBbox[1] = xy[1];
264
+ if (xy[1] > appBbox[3]) appBbox[3] = xy[1];
265
+ }
266
+ });
267
+ }
268
+ source.envelopeColor = colors[cIndex];
269
+ cIndex++;
270
+ if (cIndex === colors.length) cIndex = 0;
271
+
272
+ const xys = source.envelope.geometry.coordinates[0];
273
+ source.envelopeAreaIndex = ui.areaIndex(xys);
274
+ }
275
+ }
276
+ if (ui.appEnvelope) console.log(`This app's envelope is: ${appBbox}`);
277
+
278
+ if (ui.splashPromise) {
279
+ ui.splashPromise.then(() => {
280
+ const modalElm = ui.core!.mapDivDocument!.querySelector(".modalBase")!;
281
+ prepareModal(modalElm, { root: ui.core!.mapDivDocument! }).hide();
282
+ });
283
+ }
284
+
285
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
286
+ const baseSources: any[] = [];
287
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
288
+ const overlaySources: any[] = [];
289
+ sources.forEach(source => {
290
+ if (isBasemap(source)) {
291
+ baseSources.push(source);
292
+ } else {
293
+ overlaySources.push(source);
294
+ }
295
+ });
296
+
297
+ const baseSwiper = (ui.baseSwiper = new Swiper(".base-swiper", {
298
+ slidesPerView: 2,
299
+ spaceBetween: 15,
300
+ breakpoints: {
301
+ 480: {
302
+ slidesPerView: 1.4,
303
+ spaceBetween: 10
304
+ }
305
+ },
306
+ centeredSlides: true,
307
+ threshold: 2,
308
+ preventClicks: true,
309
+ preventClicksPropagation: true,
310
+ observer: true,
311
+ observeParents: true,
312
+ loop: baseSources.length >= 2,
313
+ navigation: {
314
+ nextEl: ".base-next",
315
+ prevEl: ".base-prev"
316
+ }
317
+ }));
318
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
319
+ baseSwiper.on("click", (_e: any) => {
320
+ if (!baseSwiper.clickedSlide) return;
321
+ const slide = baseSwiper.clickedSlide;
322
+ ui.core!.changeMap(slide.getAttribute("data")!);
323
+ delete ui._selectCandidateSources;
324
+ baseSwiper.setSlideIndexAsSelected(
325
+ parseInt(slide.getAttribute("data-swiper-slide-index") || "0", 10)
326
+ );
327
+ });
328
+ if (baseSources.length < 2) {
329
+ ui.core!.mapDivDocument!.querySelector(".base-swiper")!.classList.add(
330
+ "single-map"
331
+ );
332
+ }
333
+
334
+ const overlaySwiper = (ui.overlaySwiper = new Swiper(".overlay-swiper", {
335
+ slidesPerView: 2,
336
+ spaceBetween: 15,
337
+ breakpoints: {
338
+ 480: {
339
+ slidesPerView: 1.4,
340
+ spaceBetween: 10
341
+ }
342
+ },
343
+ centeredSlides: true,
344
+ threshold: 2,
345
+ preventClicks: true,
346
+ preventClicksPropagation: true,
347
+ observer: true,
348
+ observeParents: true,
349
+ loop: overlaySources.length >= 2,
350
+ navigation: {
351
+ nextEl: ".overlay-next",
352
+ prevEl: ".overlay-prev"
353
+ }
354
+ }));
355
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
356
+ overlaySwiper.on("click", (_e: any) => {
357
+ if (!overlaySwiper.clickedSlide) return;
358
+ const slide = overlaySwiper.clickedSlide;
359
+ ui.core!.changeMap(slide.getAttribute("data")!);
360
+ delete ui._selectCandidateSources;
361
+ overlaySwiper.setSlideIndexAsSelected(
362
+ parseInt(slide.getAttribute("data-swiper-slide-index") || "0", 10)
363
+ );
364
+ });
365
+ if (overlaySources.length < 2) {
366
+ ui.core!.mapDivDocument!.querySelector(".overlay-swiper")!.classList.add(
367
+ "single-map"
187
368
  );
188
369
  }
189
370
 
371
+ baseSources.forEach(source => {
372
+ const thumbKey = source.thumbnail ? source.thumbnail.split("/").pop() : "";
373
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
374
+ const thumbUrl = (pointer as any)[thumbKey] || source.thumbnail;
375
+ baseSwiper.appendSlide(
376
+ `<div class="swiper-slide" data="${source.mapID}">` +
377
+ `<img crossorigin="anonymous" src="${
378
+ thumbUrl
379
+ }"><div> ${ui.core!.translate(source.label)}</div> </div> `
380
+ );
381
+ });
382
+ overlaySources.forEach(source => {
383
+ const colorCss = source.envelope ? ` ${source.envelopeColor}` : "";
384
+ const thumbKey = source.thumbnail ? source.thumbnail.split("/").pop() : "";
385
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
386
+ const thumbUrl = (pointer as any)[thumbKey] || source.thumbnail;
387
+ overlaySwiper.appendSlide(
388
+ `<div class="swiper-slide${colorCss}" data="${source.mapID}">` +
389
+ `<img crossorigin="anonymous" src="${
390
+ thumbUrl
391
+ }"><div> ${ui.core!.translate(source.label)}</div> </div> `
392
+ );
393
+ });
394
+
395
+ overlaySwiper.on("slideChange", () => {
396
+ ui.updateEnvelope();
397
+ });
398
+
399
+ baseSwiper.slideToLoop(0);
400
+ overlaySwiper.slideToLoop(0);
401
+ ellips(ui.core!.mapDivDocument!);
402
+ }
403
+
404
+ function initMapEventListeners(ui: MaplatUi) {
405
+ ui.core!.addEventListener("mapChanged", (evt: unknown) => {
406
+ const map = (evt as CustomEvent).detail;
407
+
408
+ ui.baseSwiper.setSlideMapID(map.mapID);
409
+ ui.overlaySwiper.setSlideMapID(map.mapID);
410
+
411
+ const title = map.officialTitle || map.title || map.label;
412
+ (
413
+ ui.core!.mapDivDocument!.querySelector(".map-title span") as HTMLElement
414
+ ).innerText = ui.core!.translate(title) || "";
415
+
416
+ if (ui.checkOverlayID(map.mapID)) {
417
+ ui.sliderNew.setEnable(true);
418
+ } else {
419
+ ui.sliderNew.setEnable(false);
420
+ }
421
+ const transparency = ui.sliderNew.get("slidervalue") * 100;
422
+ ui.core!.mapObject.setTransparency(transparency);
423
+
424
+ ui.updateEnvelope();
425
+ ui.updateUrl();
426
+ });
427
+
428
+ ui.core!.addEventListener("poi_number", (evt: unknown) => {
429
+ const number = (evt as CustomEvent).detail;
430
+ if (number) {
431
+ ui.core!.mapDivDocument!.classList.remove("no_poi");
432
+ } else {
433
+ ui.core!.mapDivDocument!.classList.add("no_poi");
434
+ }
435
+ });
436
+
437
+ ui.core!.addEventListener("sourceLoaded", (evt: unknown) => {
438
+ const sources = (evt as CustomEvent).detail;
439
+ initSwipers(ui, sources);
440
+ });
441
+
442
+ ui.core!.waitReady.then(() => {
443
+ // Capture pointerdown at viewport level to ensure we get pixel before any stopPropagation
444
+
445
+ ui.core!.mapObject.getViewport().addEventListener(
446
+ "pointerdown",
447
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
448
+ (evt: any) => {
449
+ ui.lastClickPixel = ui.core!.mapObject.getEventPixel(evt);
450
+ ui.lastClickCoordinate = ui.core!.mapObject.getCoordinateFromPixel(
451
+ ui.lastClickPixel
452
+ );
453
+ },
454
+ true
455
+ );
456
+ });
457
+
458
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
459
+ ui.core!.addEventListener("clickMarkers", (evt: any) => {
460
+ const data = evt.detail;
461
+ if (data.length === 1) {
462
+ ui.handleMarkerAction(data[0]);
463
+ } else {
464
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
465
+ const list: any[] = [];
466
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
467
+ data.forEach((datum: any) => {
468
+ list.push({
469
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
470
+ icon: datum.icon || (pointer as any)["defaultpin.png"],
471
+ text: ui.core!.translate(datum.name),
472
+ callback: () => {
473
+ ui.handleMarkerAction(datum);
474
+ }
475
+ });
476
+ });
477
+ ui.showContextMenu(list);
478
+ }
479
+ });
480
+
481
+ ui.core!.waitReady.then(() => {
482
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
483
+ ui.core!.mapObject.on("moveend", (_evt: any) => {
484
+ ui.updateUrl();
485
+ });
486
+ });
487
+ }
488
+
489
+ function initModalHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
490
+ let cachedMarkerListMapID: string | undefined;
491
+ const mapDiv = ui.core!.mapDivDocument!;
492
+ const modalBase = mapDiv.querySelector(".modalBase") as HTMLElement;
493
+
494
+ const restoreTransparency =
495
+ ui.core!.initialRestore.transparency ||
496
+ (appOption.restore ? appOption.restore.transparency : undefined);
497
+ const enableSplash = !ui.core!.initialRestore.mapID;
498
+
190
499
  // Delegated event listener for share buttons
191
- ui.core!.mapDivDocument!.addEventListener("click", (evt: Event) => {
500
+ mapDiv.addEventListener("click", (evt: Event) => {
192
501
  const target = evt.target as HTMLElement;
193
- const btn = target.closest(".share");
502
+ const btn = target.closest(".share") || target.closest(".share_button");
194
503
  if (!btn) return;
195
504
 
196
505
  console.log("Share button clicked:", btn);
@@ -228,184 +537,33 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
228
537
  "_blank",
229
538
  "width=650,height=450,menubar=no,toolbar=no,scrollbars=yes"
230
539
  );
540
+ } else if (cmds[0] === "qr") {
541
+ const qrDiv =
542
+ mapDiv.querySelector(".qr_view_poi") ||
543
+ mapDiv.querySelector(".qr_view");
544
+
545
+ if (qrDiv) {
546
+ QRCode.toCanvas(
547
+ uri,
548
+ { width: 128, margin: 1 },
549
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
550
+ (err: any, canvas: any) => {
551
+ if (!err) {
552
+ qrDiv.innerHTML = "";
553
+ qrDiv.appendChild(canvas);
554
+ }
555
+ }
556
+ );
557
+ }
231
558
  }
232
559
  });
233
560
 
234
- const prevDefs = ui.core!.mapDivDocument!.querySelectorAll(
235
- ".prevent-default-ui"
236
- );
237
- for (let i = 0; i < prevDefs.length; i++) {
238
- const target = prevDefs[i];
561
+ const prevDefs = mapDiv.querySelectorAll(".prevent-default-ui");
562
+ prevDefs.forEach(target => {
239
563
  target.addEventListener("touchstart", (evt: Event) => {
240
564
  evt.preventDefault();
241
565
  });
242
- }
243
-
244
- newElems = createElement(`<d c="modal modalBase" tabindex="-1" role="dialog"
245
- aria-labelledby="staticModalLabel" aria-hidden="true" data-show="true" data-keyboard="false"
246
- data-backdrop="static">
247
- <d c="modal-dialog">
248
- <d c="modal-content">
249
- <d c="modal-header">
250
- <button type="button" c="close" data-dismiss="modal">
251
- <s aria-hidden="true">&#215;</s><s c="sr-only" din="html.close"></s>
252
- </button>
253
- <h4 c="modal-title">
254
-
255
- <s c="modal_title"></s>
256
- <s c="modal_load_title"></s>
257
- <s c="modal_gpsW_title" din="html.acquiring_gps"></s>
258
- <s c="modal_help_title" din="html.help_title"></s>
259
- <s c="modal_share_title" din="html.share_title"></s>
260
- <s c="modal_marker_list_title" din="html.marker_list_title"></s>
261
-
262
- </h4>
263
- </d>
264
- <d c="modal-body">
265
-
266
- <d c="modal_help_content">
267
- <d c="help_content">
268
- <s dinh="html.help_using_maplat"></s>
269
- <p c="col-xs-12 help_img"><img src="${pointer["fullscreen.png"]}"></p>
270
- <h4 din="html.help_operation_title"></h4>
271
- <p dinh="html.help_operation_content" c="recipient"></p>
272
- <h4 din="html.help_selection_title"></h4>
273
- <p dinh="html.help_selection_content" c="recipient"></p>
274
- <h4 din="html.help_gps_title"></h4>
275
- <p dinh="html.help_gps_content" c="recipient"></p>
276
- <h4 din="html.help_poi_title"></h4>
277
- <p dinh="html.help_poi_content" c="recipient"></p>
278
- <h4 din="html.help_etc_title"></h4>
279
- <ul>
280
- <li dinh="html.help_etc_attr" c="recipient"></li>
281
- <li dinh="html.help_etc_help" c="recipient"></li>
282
- <s c="share_help"><li dinh="html.help_share_help" c="recipient"></li></s>
283
- <s c="border_help"><li dinh="html.help_etc_border" c="recipient"></li></s>
284
- <s c="hide_marker_help"><li dinh="html.help_etc_hide_marker" c="recipient"></li></s>
285
- <s c="marker_list_help"><li dinh="html.help_etc_marker_list" c="recipient"></li></s>
286
- <li dinh="html.help_etc_slider" c="recipient"></li>
287
- </ul>
288
- <p><a href="https://www.maplat.jp/" target="_blank">Maplat</a>
289
- © 2015- Kohei Otsuka, Code for History</p>
290
- </d>
291
- </d>
292
-
293
- <d c="modal_poi_content">
294
- <d c="poi_web_div"></d>
295
- <d c="modal_share_poi"></d>
296
- <p><img src="" height="0px" width="0px"></p>
297
- </d>
298
-
299
- <d c="modal_share_content">
300
- <h4 din="html.share_app_title"></h4>
301
- <d id="___maplat_app_toast_${ui.html_id_seed}"></d>
302
- <d c="recipient row">
303
- <d c="form-group col-xs-4 text-center"><button title="Copy to clipboard" class="share btn btn-light" data="cp_app"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M224 0c-35.3 0-64 28.7-64 64V96H96c-35.3 0-64 28.7-64 64V448c0 35.3 28.7 64 64 64H288c35.3 0 64-28.7 64-64V384h64c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H224zM288 384H96V160H224c0-17.7 14.3-32 32-32h64V256c0 17.7 14.3 32 32 32h96V384H288z"/></svg>&nbsp;<small din="html.share_copy"></small></button></d>
304
- <d c="form-group col-xs-4 text-center"><button title="Twitter" class="share btn btn-light" data="tw_app"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"/></svg>&nbsp;<small>Twitter</small></button></d>
305
- <d c="form-group col-xs-4 text-center"><button title="Facebook" class="share btn btn-light" data="fb_app"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256C504 119 393 8 256 8S8 119 8 256c0 121.3 87.1 222.4 203 240.5V327.9h-61v-71.9h61V203c0-60.8 35.8-93.7 89.2-93.7 25.5 0 50.4 1.8 56.1 2.6v62.4h-35.4c-29.5 0-37.4 18.2-37.4 42.1v59.6h68.9l-11 71.9h-57.9V496.5C416.9 478.4 504 377.3 504 256z"/></svg>&nbsp;<small>Facebook</small></button></d>
306
- </d>
307
- <d c="qr_app center-block" style="width:128px;"></d>
308
- <d c="modal_share_state">
309
- <h4 din="html.share_state_title"></h4>
310
- <d id="___maplat_view_toast_${ui.html_id_seed}"></d>
311
- <d c="recipient row">
312
- <d c="form-group col-xs-4 text-center"><button title="Copy to clipboard" c="share btn btn-light" data="cp_view"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M224 0c-35.3 0-64 28.7-64 64V96H96c-35.3 0-64 28.7-64 64V448c0 35.3 28.7 64 64 64H288c35.3 0 64-28.7 64-64V384h64c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H224zM288 384H96V160H224c0-17.7 14.3-32 32-32h64V256c0 17.7 14.3 32 32 32h96V384H288z"/></svg>&nbsp;<small din="html.share_copy"></small></button></d>
313
- <d c="form-group col-xs-4 text-center"><button title="Twitter" c="share btn btn-light" data="tw_view"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"/></svg>&nbsp;<small>Twitter</small></button></d>
314
- <d c="form-group col-xs-4 text-center"><button title="Facebook" c="share btn btn-light" data="fb_view"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256C504 119 393 8 256 8S8 119 8 256c0 121.3 87.1 222.4 203 240.5V327.9h-61v-71.9h61V203c0-60.8 35.8-93.7 89.2-93.7 25.5 0 50.4 1.8 56.1 2.6v62.4h-35.4c-29.5 0-37.4 18.2-37.4 42.1v59.6h68.9l-11 71.9h-57.9V496.5C416.9 478.4 504 377.3 504 256z"/></svg>&nbsp;<small>Facebook</small></button></d>
315
- </d>
316
- <d c="qr_view center-block" style="width:128px;"></d>
317
- </d>
318
- <p><img src="" height="0px" width="0px"></p>
319
- </d>
320
-
321
- <d c="modal_map_content">
322
- ${META_KEYS.map(key => {
323
- if (key == "title" || key == "officialTitle") return "";
324
- return `<d c="recipients ${key}_div"><dl c="dl-horizontal">
325
- <dt din="html.${key}"></dt>
326
- <dd c="${key}_dd"></dd>
327
- </dl></d> `;
328
- }).join("")}
329
- <d c="recipients modal_cache_content"><dl c="dl-horizontal">
330
- <dt din="html.cache_handle"></dt>
331
- <dd><s c="cache_size"></s></dd>
332
- <dt></dt>
333
- <dd><s c="pull-right"><button c="cache_fetch btn btn-default" href="#" din="html.cache_fetch"></button>
334
- <button c="cache_delete btn btn-default" href="#" din="html.cache_delete"></button></s></dd>
335
- </dl></d>
336
- </d>
337
-
338
- <d c="modal_load_content">
339
- <p c="recipient"><img src="${pointer["loading.png"]}"><s din="html.app_loading_body"></s></p>
340
- <d c="splash_div hide row"><p c="col-xs-12 poi_img"><img c="splash_img" src=""></p></d>
341
- <p><img src="" height="0px" width="0px"></p>
342
- </d>
343
-
344
- <d c="modal_marker_list_content">
345
- <ul c="list-group"></ul>
346
- </d>
347
-
348
- <p c="modal_gpsD_content" c="recipient"></p>
349
- <p c="modal_gpsW_content" c="recipient"></p>
350
-
351
- </d>
352
- </d>
353
- </d>
354
- </d> `);
355
- for (let i = newElems.length - 1; i >= 0; i--) {
356
- ui.core!.mapDivDocument!.insertBefore(
357
- newElems[i],
358
- ui.core!.mapDivDocument!.firstChild
359
- );
360
- }
361
-
362
- // PWA
363
- if (pwaManifest) {
364
- if (pwaManifest === true) {
365
- pwaManifest = `./pwa/${ui.core!.appid}_manifest.json`;
366
- }
367
- if (!pwaWorker) {
368
- pwaWorker = "./service-worker.js";
369
- }
370
- if (!pwaScope) {
371
- pwaScope = "./";
372
- }
373
-
374
- const head = document.querySelector("head");
375
- if (head) {
376
- if (!head.querySelector('link[rel="manifest"]')) {
377
- head.appendChild(
378
- createElement(`<link rel="manifest" href="${pwaManifest}">`)[0]
379
- );
380
- }
381
- }
382
- try {
383
- Weiwudi.registerSW(pwaWorker, { scope: pwaScope });
384
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
385
- } catch (_e) {} // eslint-disable-line no-empty
386
-
387
- if (head && !head.querySelector('link[rel="apple-touch-icon"]')) {
388
- const xhr = new XMLHttpRequest();
389
- xhr.open("GET", pwaManifest, true);
390
- xhr.responseType = "json";
391
-
392
- xhr.onload = function (_e: ProgressEvent) {
393
- let value = this.response;
394
- if (!value) return;
395
- if (typeof value != "object") value = JSON.parse(value);
396
-
397
- if (value.icons) {
398
- for (let i = 0; i < value.icons.length; i++) {
399
- const src = absoluteUrl(pwaManifest as string, value.icons[i].src);
400
- const sizes = value.icons[i].sizes;
401
- const tag = `<link rel="apple-touch-icon" sizes="${sizes}" href="${src}">`;
402
- head.appendChild(createElement(tag)[0]);
403
- }
404
- }
405
- };
406
- xhr.send();
407
- }
408
- }
566
+ });
409
567
 
410
568
  ui.core!.addEventListener("uiPrepare", (_evt: unknown) => {
411
569
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -419,25 +577,21 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
419
577
  }
420
578
  return ret;
421
579
  };
422
- let i18nTargets =
423
- ui.core!.mapDivDocument!.querySelectorAll("[data-i18n], [din]");
424
- for (let i = 0; i < i18nTargets.length; i++) {
425
- const target = i18nTargets[i];
580
+
581
+ let i18nTargets = mapDiv.querySelectorAll("[data-i18n], [din]");
582
+ i18nTargets.forEach(target => {
426
583
  const key =
427
584
  target.getAttribute("data-i18n") || target.getAttribute("din");
428
585
  (target as HTMLElement).innerText = imageExtractor(ui.core!.t(key));
429
- }
430
- i18nTargets = ui.core!.mapDivDocument!.querySelectorAll(
431
- "[data-i18n-html], [dinh]"
432
- );
433
- for (let i = 0; i < i18nTargets.length; i++) {
434
- const target = i18nTargets[i];
586
+ });
587
+ i18nTargets = mapDiv.querySelectorAll("[data-i18n-html], [dinh]");
588
+ i18nTargets.forEach(target => {
435
589
  const key =
436
590
  target.getAttribute("data-i18n-html") || target.getAttribute("dinh");
437
591
  target.innerHTML = imageExtractor(ui.core!.t(key));
438
- }
592
+ });
439
593
  // Explicitly fix app_loading_body with a more robust selector if needed, or re-run translation for it
440
- const appLoadingBody = ui.core!.mapDivDocument!.querySelector(
594
+ const appLoadingBody = mapDiv.querySelector(
441
595
  '[data-i18n="html.app_loading_body"], [din="html.app_loading_body"]'
442
596
  );
443
597
  if (appLoadingBody) {
@@ -529,447 +683,39 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
529
683
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
530
684
  if ((ui.core!.appData as any).splash) splash = true;
531
685
 
532
- const modalElm = ui.core!.mapDivDocument!.querySelector(".modalBase")!;
533
- const modal = new bsn.Modal(modalElm, { root: ui.core!.mapDivDocument! });
534
- (
535
- ui.core!.mapDivDocument!.querySelector(
536
- ".modal_load_title"
537
- ) as HTMLElement
538
- ).innerText = ui.core!.translate(ui.core!.appData!.appName) || "";
539
- if (splash) {
540
- ui.core!.mapDivDocument!.querySelector(".splash_img")!
541
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
542
- .setAttribute("src", `img/${(ui.core!.appData as any).splash}`);
543
- ui.core!.mapDivDocument!.querySelector(".splash_div")!.classList.remove(
544
- "hide"
545
- );
546
- }
547
- ui.modalSetting("load");
548
- modal.show();
549
-
550
- const fadeTime = splash ? 1000 : 200;
551
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
552
- ui.splashPromise = new Promise((resolve: any) => {
553
- setTimeout(() => {
554
- resolve();
555
- }, fadeTime);
556
- });
557
- }
558
-
559
- document.querySelector("title")!.innerHTML =
560
- ui.core!.translate(ui.core!.appName) || "";
561
- });
562
-
563
- ui.core!.addEventListener("mapChanged", (evt: unknown) => {
564
- const map = (evt as CustomEvent).detail;
565
-
566
- ui.baseSwiper.setSlideMapID(map.mapID);
567
- ui.overlaySwiper.setSlideMapID(map.mapID);
686
+ // const modal = new bsn.Modal(modalElm, { root: mapDiv });
687
+ const modal = prepareModal(modalBase, { root: mapDiv });
568
688
 
569
- const title = map.officialTitle || map.title || map.label;
570
- (
571
- ui.core!.mapDivDocument!.querySelector(".map-title span") as HTMLElement
572
- ).innerText = ui.core!.translate(title) || "";
573
-
574
- if (ui.checkOverlayID(map.mapID)) {
575
- ui.sliderNew.setEnable(true);
576
- } else {
577
- ui.sliderNew.setEnable(false);
578
- }
579
- const transparency = ui.sliderNew.get("slidervalue") * 100;
580
- ui.core!.mapObject.setTransparency(transparency);
581
-
582
- ui.updateEnvelope();
583
- ui.updateUrl();
584
- });
585
-
586
- ui.core!.addEventListener("poi_number", (evt: unknown) => {
587
- const number = (evt as CustomEvent).detail;
588
- if (number) {
589
- ui.core!.mapDivDocument!.classList.remove("no_poi");
590
- } else {
591
- ui.core!.mapDivDocument!.classList.add("no_poi");
592
- }
593
- });
594
-
595
- ui.core!.addEventListener("outOfMap", (_evt: unknown) => {
596
- console.log("Event: outOfMap");
597
- if (enableOutOfMap) {
598
- (
599
- ui.core!.mapDivDocument!.querySelector(".modal_title") as HTMLElement
600
- ).innerText = ui.core!.t("app.out_of_map") || "";
601
- (
602
- ui.core!.mapDivDocument!.querySelector(
603
- ".modal_gpsD_content"
604
- ) as HTMLElement
605
- ).innerText = ui.core!.t("app.out_of_map_area") || "";
606
- const modalElm = ui.core!.mapDivDocument!.querySelector(".modalBase")!;
607
- const modal =
608
- bsn.Modal.getInstance(modalElm) ||
609
- new bsn.Modal(modalElm, { root: ui.core!.mapDivDocument! });
610
-
611
- const closeBtns = modalElm.querySelectorAll(
612
- ".close, .modal-footer button"
613
- );
614
- for (let i = 0; i < closeBtns.length; i++) {
615
- closeBtns[i].addEventListener("click", () => {
616
- modal.hide();
617
- });
618
- }
619
-
620
- ui.modalSetting("gpsD");
621
- modal.show();
622
- }
623
- });
624
-
625
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
626
- ui.core!.addEventListener("gps_error", (evt: any) => {
627
- console.log("GPS Error:", evt);
628
- const errorMap: Record<string, string> = {
629
- user_gps_deny: "app.user_gps_deny",
630
- gps_miss: "app.gps_miss",
631
- gps_timeout: "app.gps_timeout"
632
- };
633
-
634
- if (!ui.core) return;
635
- (ui.core.mapDivDocument!.querySelector(
636
- ".modal_title"
637
- ) as HTMLElement)!.innerText = ui.core.t("app.gps_error") || "";
638
- (ui.core.mapDivDocument!.querySelector(
639
- ".modal_gpsD_content"
640
- ) as HTMLElement)!.innerText =
641
- ui.core.t(errorMap[evt.detail] || "app.gps_error") || "";
642
- const modalElm = ui.core.mapDivDocument!.querySelector(".modalBase")!;
643
- const modal =
644
- bsn.Modal.getInstance(modalElm) ||
645
- new bsn.Modal(modalElm, {
646
- root: ui.core.mapDivDocument
647
- });
648
-
649
- const closeBtns = modalElm.querySelectorAll(".close, .modal-footer button");
650
- for (let i = 0; i < closeBtns.length; i++) {
651
- closeBtns[i].addEventListener("click", () => {
652
- modal.hide();
653
- });
654
- }
655
-
656
- ui.modalSetting("gpsD");
657
- modal.show();
658
- });
659
-
660
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
661
- ui.core!.addEventListener("gps_result", (evt: any) => {
662
- console.log("GPS Result:", evt);
663
- if (evt.detail && evt.detail.error) {
664
- const error = evt.detail.error;
665
- if (error === "gps_off") {
666
- ui.lastGPSError = undefined;
667
- return;
668
- }
669
-
670
- ui.lastGPSError = error;
671
- if (ui.alwaysGpsOn && error === "gps_out") return;
672
-
673
- if (!ui.core) return;
674
-
675
- const modalElm = ui.core.mapDivDocument!.querySelector(".modalBase")!;
676
- const modal =
677
- bsn.Modal.getInstance(modalElm) ||
678
- new bsn.Modal(modalElm, {
679
- root: ui.core.mapDivDocument
680
- });
681
-
682
- const closeBtns = modalElm.querySelectorAll(
683
- ".close, .modal-footer button"
684
- );
685
- for (let i = 0; i < closeBtns.length; i++) {
686
- closeBtns[i].addEventListener("click", () => {
687
- modal.hide();
688
- });
689
- }
690
-
691
- if (error === "gps_out") {
692
- (ui.core.mapDivDocument!.querySelector(
693
- ".modal_title"
694
- ) as HTMLElement)!.innerText = ui.core.t("app.out_of_map") || "";
695
- (ui.core.mapDivDocument!.querySelector(
696
- ".modal_gpsD_content"
697
- ) as HTMLElement)!.innerText = ui.core.t("app.out_of_map_area") || "";
698
- } else {
699
- const errorMap: Record<string, string> = {
700
- user_gps_deny: "app.user_gps_deny",
701
- gps_miss: "app.gps_miss",
702
- gps_timeout: "app.gps_timeout"
703
- };
704
-
705
- (ui.core.mapDivDocument!.querySelector(
706
- ".modal_title"
707
- ) as HTMLElement)!.innerText = ui.core.t("app.gps_error") || "";
708
- (ui.core.mapDivDocument!.querySelector(
709
- ".modal_gpsD_content"
710
- ) as HTMLElement)!.innerText =
711
- ui.core.t(errorMap[error] || "app.gps_error") || "";
712
- }
713
-
714
- ui.modalSetting("gpsD");
715
- modal.show();
716
- } else {
717
- ui.lastGPSError = undefined;
718
- }
719
- });
720
-
721
- ui.core!.addEventListener("sourceLoaded", (evt: unknown) => {
722
- const sources = (evt as CustomEvent).detail;
723
- const colors = [
724
- "maroon",
725
- "deeppink",
726
- "indigo",
727
- "olive",
728
- "royalblue",
729
- "red",
730
- "hotpink",
731
- "green",
732
- "yellow",
733
- "navy",
734
- "saddlebrown",
735
- "fuchsia",
736
- "darkslategray",
737
- "yellowgreen",
738
- "blue",
739
- "mediumvioletred",
740
- "purple",
741
- "lime",
742
- "darkorange",
743
- "teal",
744
- "crimson",
745
- "darkviolet",
746
- "darkolivegreen",
747
- "steelblue",
748
- "aqua"
749
- ];
750
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
751
- const appBbox: any[] = [];
752
- let cIndex = 0;
753
-
754
- for (let i = 0; i < sources.length; i++) {
755
- const source = sources[i];
756
- if (source.envelope) {
757
- if (ui.appEnvelope) {
758
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
759
- source.envelope.geometry.coordinates[0].map((xy: any) => {
760
- if (appBbox.length === 0) {
761
- appBbox[0] = appBbox[2] = xy[0];
762
- appBbox[1] = appBbox[3] = xy[1];
763
- } else {
764
- if (xy[0] < appBbox[0]) appBbox[0] = xy[0];
765
- if (xy[0] > appBbox[2]) appBbox[2] = xy[0];
766
-
767
- if (xy[1] < appBbox[1]) appBbox[1] = xy[1];
768
- if (xy[1] > appBbox[3]) appBbox[3] = xy[1];
769
- }
770
- });
771
- }
772
- source.envelopeColor = colors[cIndex];
773
- cIndex++;
774
- if (cIndex === colors.length) cIndex = 0;
775
-
776
- const xys = source.envelope.geometry.coordinates[0];
777
- source.envelopeAreaIndex = ui.areaIndex(xys);
778
- }
779
- }
780
- if (ui.appEnvelope) console.log(`This app's envelope is: ${appBbox}`);
781
-
782
- if (ui.splashPromise) {
783
- ui.splashPromise.then(() => {
784
- const modalElm = ui.core!.mapDivDocument!.querySelector(".modalBase")!;
785
- const modal =
786
- bsn.Modal.getInstance(modalElm) ||
787
- new bsn.Modal(modalElm, { root: ui.core!.mapDivDocument! });
788
- ui.modalSetting("load");
789
- modal.hide();
790
- });
791
- }
792
-
793
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
794
- const baseSources: any[] = [];
795
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
796
- const overlaySources: any[] = [];
797
- for (let i = 0; i < sources.length; i++) {
798
- const source = sources[i];
799
- if (isBasemap(source)) {
800
- baseSources.push(source);
801
- } else {
802
- overlaySources.push(source);
803
- }
804
- }
805
-
806
- const baseSwiper = (ui.baseSwiper = new Swiper(".base-swiper", {
807
- slidesPerView: 2,
808
- spaceBetween: 15,
809
- breakpoints: {
810
- 480: {
811
- slidesPerView: 1.4,
812
- spaceBetween: 10
813
- }
814
- },
815
- centeredSlides: true,
816
- threshold: 2,
817
- preventClicks: true,
818
- preventClicksPropagation: true,
819
- observer: true,
820
- observeParents: true,
821
- loop: baseSources.length >= 2,
822
- navigation: {
823
- nextEl: ".base-next",
824
- prevEl: ".base-prev"
825
- }
826
- }));
827
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
828
- baseSwiper.on("click", (_e: any) => {
829
- if (!baseSwiper.clickedSlide) return;
830
- const slide = baseSwiper.clickedSlide;
831
- ui.core!.changeMap(slide.getAttribute("data")!);
832
- delete ui._selectCandidateSources;
833
- baseSwiper.setSlideIndexAsSelected(
834
- parseInt(slide.getAttribute("data-swiper-slide-index") || "0", 10)
835
- );
836
- });
837
- if (baseSources.length < 2) {
838
- ui.core!.mapDivDocument!.querySelector(".base-swiper")!.classList.add(
839
- "single-map"
840
- );
841
- }
842
-
843
- const overlaySwiper = (ui.overlaySwiper = new Swiper(".overlay-swiper", {
844
- slidesPerView: 2,
845
- spaceBetween: 15,
846
- breakpoints: {
847
- 480: {
848
- slidesPerView: 1.4,
849
- spaceBetween: 10
850
- }
851
- },
852
- centeredSlides: true,
853
- threshold: 2,
854
- preventClicks: true,
855
- preventClicksPropagation: true,
856
- observer: true,
857
- observeParents: true,
858
- loop: overlaySources.length >= 2,
859
- navigation: {
860
- nextEl: ".overlay-next",
861
- prevEl: ".overlay-prev"
862
- }
863
- }));
864
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
865
- overlaySwiper.on("click", (_e: any) => {
866
- if (!overlaySwiper.clickedSlide) return;
867
- const slide = overlaySwiper.clickedSlide;
868
- ui.core!.changeMap(slide.getAttribute("data")!);
869
- delete ui._selectCandidateSources;
870
- overlaySwiper.setSlideIndexAsSelected(
871
- parseInt(slide.getAttribute("data-swiper-slide-index") || "0", 10)
872
- );
873
- });
874
- if (overlaySources.length < 2) {
875
- ui.core!.mapDivDocument!.querySelector(".overlay-swiper")!.classList.add(
876
- "single-map"
877
- );
878
- }
879
-
880
- for (let i = 0; i < baseSources.length; i++) {
881
- const source = baseSources[i];
882
- const thumbKey = source.thumbnail
883
- ? source.thumbnail.split("/").pop()
884
- : "";
885
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
886
- const thumbUrl = (pointer as any)[thumbKey] || source.thumbnail;
887
- baseSwiper.appendSlide(
888
- `<div class="swiper-slide" data="${source.mapID}">` +
889
- `<img crossorigin="anonymous" src="${
890
- thumbUrl
891
- }"><div> ${ui.core!.translate(source.label)}</div> </div> `
892
- );
893
- }
894
- for (let i = 0; i < overlaySources.length; i++) {
895
- const source = overlaySources[i];
896
- const colorCss = source.envelope ? ` ${source.envelopeColor}` : "";
897
- const thumbKey = source.thumbnail
898
- ? source.thumbnail.split("/").pop()
899
- : "";
900
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
901
- const thumbUrl = (pointer as any)[thumbKey] || source.thumbnail;
902
- overlaySwiper.appendSlide(
903
- `<div class="swiper-slide${colorCss}" data="${source.mapID}">` +
904
- `<img crossorigin="anonymous" src="${
905
- thumbUrl
906
- }"><div> ${ui.core!.translate(source.label)}</div> </div> `
907
- );
908
- }
909
-
910
- overlaySwiper.on("slideChange", () => {
911
- ui.updateEnvelope();
912
- });
913
-
914
- baseSwiper.slideToLoop(0);
915
- overlaySwiper.slideToLoop(0);
916
- ellips(ui.core!.mapDivDocument!);
917
- });
918
-
919
- ui.core!.waitReady.then(() => {
920
- // Capture pointerdown at viewport level to ensure we get pixel before any stopPropagation
921
-
922
- ui.core!.mapObject.getViewport().addEventListener(
923
- "pointerdown",
924
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
925
- (evt: any) => {
926
- ui.lastClickPixel = ui.core!.mapObject.getEventPixel(evt);
927
- ui.lastClickCoordinate = ui.core!.mapObject.getCoordinateFromPixel(
928
- ui.lastClickPixel
929
- );
930
- },
931
- true
932
- );
933
- });
934
-
935
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
936
- ui.core!.addEventListener("clickMarkers", (evt: any) => {
937
- const data = evt.detail;
938
- if (data.length === 1) {
939
- ui.handleMarkerAction(data[0]);
940
- } else {
941
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
942
- const list: any[] = [];
943
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
944
- data.forEach((datum: any) => {
945
- list.push({
689
+ (mapDiv.querySelector(".modal_load_title") as HTMLElement).innerText =
690
+ ui.core!.translate(ui.core!.appData!.appName) || "";
691
+ if (splash) {
692
+ mapDiv
693
+ .querySelector(".splash_img")!
946
694
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
947
- icon: datum.icon || (pointer as any)["defaultpin.png"],
948
- text: ui.core!.translate(datum.name),
949
- callback: () => {
950
- ui.handleMarkerAction(datum);
951
- }
952
- });
695
+ .setAttribute("src", `img/${(ui.core!.appData as any).splash}`);
696
+ mapDiv.querySelector(".splash_div")!.classList.remove("hide");
697
+ }
698
+ ui.modalSetting("load");
699
+ modal.show();
700
+
701
+ const fadeTime = splash ? 1000 : 200;
702
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
703
+ ui.splashPromise = new Promise((resolve: any) => {
704
+ setTimeout(() => {
705
+ resolve();
706
+ }, fadeTime);
953
707
  });
954
- ui.showContextMenu(list);
955
708
  }
709
+
710
+ document.querySelector("title")!.innerHTML =
711
+ ui.core!.translate(ui.core!.appName) || "";
956
712
  });
957
713
 
958
714
  ui.core!.waitReady.then(() => {
959
715
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
960
716
  ui.core!.mapObject.on("click_control", (evt: any) => {
961
717
  const control = evt.control || (evt.frameState && evt.frameState.control);
962
- const modalElm = ui.core!.mapDivDocument!.querySelector(".modalBase")!;
963
- const modal = bsn.Modal.getInstance(modalElm) || new bsn.Modal(modalElm);
964
-
965
- const closeBtns = modalElm.querySelectorAll(
966
- ".close, .modal-footer button"
967
- );
968
- for (let i = 0; i < closeBtns.length; i++) {
969
- closeBtns[i].addEventListener("click", () => {
970
- modal.hide();
971
- });
972
- }
718
+ const modal = prepareModal(modalBase);
973
719
 
974
720
  if (control === "help") {
975
721
  ui.modalSetting("help");
@@ -977,7 +723,7 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
977
723
  } else if (control === "share") {
978
724
  ui.modalSetting("share");
979
725
 
980
- const modalBody = modalElm.querySelector(".modal-body") as HTMLElement;
726
+ const modalBody = modalBase.querySelector(".modal-body") as HTMLElement;
981
727
 
982
728
  const baseUrl = ui.getShareUrl("app");
983
729
  const viewUrl = ui.getShareUrl("view");
@@ -1018,9 +764,45 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
1018
764
  ui.modalSetting("marker_list");
1019
765
  modal.show();
1020
766
 
1021
- const listRoot = modalElm.querySelector(
767
+ const listRoot = modalBase.querySelector(
1022
768
  ".modal_marker_list_content ul.list-group"
1023
769
  ) as HTMLElement;
770
+
771
+ // Reset all panel states when modal is closed
772
+ const resetPanels = () => {
773
+ // Close all Layer panels
774
+ const allLayerPanels =
775
+ listRoot.querySelectorAll(".list_poiitems_div");
776
+ allLayerPanels.forEach(panel => {
777
+ panel.classList.remove("open");
778
+ });
779
+
780
+ // Close and reset all POI Content panels
781
+ const allPoiContentDivs = listRoot.querySelectorAll(
782
+ ".list_poicontent_div"
783
+ );
784
+ allPoiContentDivs.forEach(contentDiv => {
785
+ contentDiv.classList.remove("open");
786
+ contentDiv.innerHTML = "";
787
+ });
788
+
789
+ // Unselect marker
790
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
791
+ (ui.core as any).unselectMarker?.();
792
+
793
+ // Remove this event listener after execution
794
+ modalBase.removeEventListener("hidden.bs.modal", resetPanels);
795
+ };
796
+
797
+ // Remove old listener if exists and add new one
798
+ modalBase.removeEventListener("hidden.bs.modal", resetPanels);
799
+ modalBase.addEventListener("hidden.bs.modal", resetPanels);
800
+
801
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
802
+ const currentMapID = (ui.core!.from as any).mapID;
803
+ if (cachedMarkerListMapID === currentMapID) return;
804
+ cachedMarkerListMapID = currentMapID;
805
+
1024
806
  listRoot.innerHTML = "";
1025
807
 
1026
808
  const layers = ui.core!.listPoiLayers(false, true);
@@ -1072,7 +854,40 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
1072
854
  layerLi
1073
855
  .querySelector(".layer_label")!
1074
856
  .addEventListener("click", () => {
1075
- poiListUl.classList.toggle("open");
857
+ const isCurrentlyOpen = poiListUl.classList.contains("open");
858
+
859
+ // Close all other Layer panels at the same level
860
+ const allLayerPanels =
861
+ listRoot.querySelectorAll(".list_poiitems_div");
862
+ allLayerPanels.forEach(panel => {
863
+ if (panel !== poiListUl) {
864
+ panel.classList.remove("open");
865
+ // Reset all child POI Content panels
866
+ const poiContentDivs = panel.parentElement!.querySelectorAll(
867
+ ".list_poicontent_div"
868
+ );
869
+ poiContentDivs.forEach(contentDiv => {
870
+ contentDiv.classList.remove("open");
871
+ contentDiv.innerHTML = "";
872
+ });
873
+ }
874
+ });
875
+
876
+ // Toggle current panel
877
+ if (isCurrentlyOpen) {
878
+ poiListUl.classList.remove("open");
879
+ // Reset all child POI Content panels in this Layer
880
+ const poiContentDivs = poiListUl.querySelectorAll(
881
+ ".list_poicontent_div"
882
+ );
883
+ poiContentDivs.forEach(contentDiv => {
884
+ contentDiv.classList.remove("open");
885
+ contentDiv.innerHTML = "";
886
+ });
887
+ (ui.core as MaplatApp).unselectMarker?.();
888
+ } else {
889
+ poiListUl.classList.add("open");
890
+ }
1076
891
  });
1077
892
 
1078
893
  listRoot.appendChild(layerLi);
@@ -1097,18 +912,34 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
1097
912
  // let poiImgHide: any;
1098
913
 
1099
914
  poiLi.addEventListener("click", () => {
1100
- if (!poiContentDiv.classList.contains("open")) {
1101
- poiContentDiv.classList.add("open");
1102
-
1103
- poiWebControl(ui, poiContentDiv, poi);
915
+ const isCurrentlyOpen =
916
+ poiContentDiv.classList.contains("open");
917
+
918
+ // Close all other POI panels at the same level (within the same Layer)
919
+ const allPoiContentDivs = poiListUl.querySelectorAll(
920
+ ".list_poicontent_div"
921
+ );
922
+ allPoiContentDivs.forEach(contentDiv => {
923
+ if (
924
+ contentDiv !== poiContentDiv &&
925
+ contentDiv.classList.contains("open")
926
+ ) {
927
+ contentDiv.classList.remove("open");
928
+ contentDiv.innerHTML = "";
929
+ }
930
+ });
1104
931
 
1105
- ui.core!.selectMarker?.(poi.namespaceID);
1106
- } else {
932
+ // Toggle current panel
933
+ if (isCurrentlyOpen) {
1107
934
  poiContentDiv.classList.remove("open");
1108
-
1109
- // if (poiImgHide) poiImgHide();
1110
935
  poiContentDiv.innerHTML = "";
1111
- ui.core!.unselectMarker?.();
936
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
937
+ (ui.core as any).unselectMarker?.();
938
+ } else {
939
+ poiContentDiv.classList.add("open");
940
+ poiWebControl(ui, poiContentDiv, poi, false);
941
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
942
+ (ui.core as any).selectMarker?.(poi.namespaceID);
1112
943
  }
1113
944
  });
1114
945
 
@@ -1117,12 +948,10 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
1117
948
  });
1118
949
  }
1119
950
  });
1120
- modal.show();
1121
951
  } else if (control === "copyright") {
1122
952
  ui.modalSetting("map");
1123
953
  const mapData = ui.core!.from!;
1124
- const modalRoot = ui.core!.mapDivDocument!;
1125
- const titleEl = modalRoot.querySelector(".modal_map .modal_title");
954
+ const titleEl = mapDiv.querySelector(".modal_map .modal_title");
1126
955
  if (titleEl) {
1127
956
  const titleVal = mapData.get ? mapData.get("title") : mapData.title;
1128
957
  (titleEl as HTMLElement).innerText =
@@ -1135,7 +964,7 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
1135
964
  ? mapData.get(key)
1136
965
  : // eslint-disable-next-line @typescript-eslint/no-explicit-any
1137
966
  (mapData as any)[key];
1138
- const container = modalRoot.querySelector(`.modal_map .${key}_div`);
967
+ const container = mapDiv.querySelector(`.modal_map .${key}_div`);
1139
968
  if (container) {
1140
969
  if (val) {
1141
970
  (container as HTMLElement).style.display = "block";
@@ -1158,7 +987,7 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
1158
987
  }
1159
988
  });
1160
989
 
1161
- const cacheDiv = modalRoot.querySelector(
990
+ const cacheDiv = mapDiv.querySelector(
1162
991
  ".modal_cache_content"
1163
992
  ) as HTMLElement;
1164
993
  const cacheSize = cacheDiv.querySelector(".cache_size") as HTMLElement;
@@ -1308,15 +1137,14 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
1308
1137
  // And weiwudi_gw_logic sends type: 'canceled'.
1309
1138
  weiwudi.addEventListener("canceled", fetchHandler);
1310
1139
 
1311
- const modalEl = modalRoot.querySelector(".modalBase") as HTMLElement;
1312
1140
  const removeListeners = () => {
1313
1141
  weiwudi.removeEventListener("proceed", fetchHandler);
1314
1142
  weiwudi.removeEventListener("finish", fetchHandler);
1315
1143
  weiwudi.removeEventListener("stop", fetchHandler);
1316
1144
  weiwudi.removeEventListener("canceled", fetchHandler);
1317
- modalEl.removeEventListener("hidden.bs.modal", removeListeners);
1145
+ modalBase.removeEventListener("hidden.bs.modal", removeListeners);
1318
1146
  };
1319
- modalEl.addEventListener("hidden.bs.modal", removeListeners);
1147
+ modalBase.addEventListener("hidden.bs.modal", removeListeners);
1320
1148
 
1321
1149
  const newElem = cacheFetch.cloneNode(true);
1322
1150
  cacheFetch.parentNode!.replaceChild(newElem, cacheFetch);
@@ -1363,15 +1191,231 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
1363
1191
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1364
1192
  ui.setShowBorder(!(ui.core!.stateBuffer as any).showBorder);
1365
1193
  } else if (control === "hideMarker") {
1366
- const current =
1367
- ui.core!.mapDivDocument!.classList.contains("hide-marker");
1194
+ const current = mapDiv.classList.contains("hide-marker");
1368
1195
  ui.setHideMarker(!current);
1369
1196
  }
1370
1197
  ui.updateUrl();
1371
1198
  });
1372
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1373
- ui.core!.mapObject.on("moveend", (_evt: any) => {
1374
- ui.updateUrl();
1375
- });
1376
1199
  });
1377
1200
  }
1201
+
1202
+ function initDom(ui: MaplatUi, appOption: MaplatAppOption) {
1203
+ // Inject Custom Toast Styles
1204
+ const style = document.createElement("style");
1205
+ style.innerHTML = `
1206
+ .custom-toast {
1207
+ visibility: hidden;
1208
+ min-width: 250px;
1209
+ margin-left: -125px;
1210
+ background-color: #333;
1211
+ color: #fff;
1212
+ text-align: center;
1213
+ border-radius: 2px;
1214
+ padding: 16px;
1215
+ position: fixed;
1216
+ z-index: 9999;
1217
+ left: 50%;
1218
+ bottom: 30px;
1219
+ font-size: 17px;
1220
+ opacity: 0;
1221
+ transition: opacity 0.3s;
1222
+ }
1223
+ .custom-toast.show {
1224
+ visibility: visible;
1225
+ opacity: 1;
1226
+ }
1227
+ `;
1228
+ document.head.appendChild(style);
1229
+
1230
+ let pwaManifest = appOption.pwaManifest;
1231
+ let pwaWorker = appOption.pwaWorker;
1232
+ let pwaScope = appOption.pwaScope;
1233
+
1234
+ // Add UI HTML Element
1235
+ let newElems = createElement(`<d c="ol-control map-title"><s></s></d>
1236
+ <d c="swiper-container ol-control base-swiper prevent-default-ui">
1237
+ <d c="swiper-wrapper"> </d>
1238
+ <d c="swiper-button-next base-next swiper-button-white"> </d>
1239
+ <d c="swiper-button-prev base-prev swiper-button-white"> </d>
1240
+ </d>
1241
+ <d c="swiper-container ol-control overlay-swiper prevent-default-ui">
1242
+ <d c="swiper-wrapper"> </d>
1243
+ <d c="swiper-button-next overlay-next swiper-button-white"> </d>
1244
+ <d c="swiper-button-prev overlay-prev swiper-button-white"> </d>
1245
+ </d> `);
1246
+ for (let i = newElems.length - 1; i >= 0; i--) {
1247
+ ui.core!.mapDivDocument!.insertBefore(
1248
+ newElems[i],
1249
+ ui.core!.mapDivDocument!.firstChild
1250
+ );
1251
+ }
1252
+
1253
+ newElems = createElement(`<d c="modal modalBase" tabindex="-1" role="dialog"
1254
+ aria-labelledby="staticModalLabel" aria-hidden="true" data-show="true" data-keyboard="false"
1255
+ data-backdrop="static">
1256
+ <d c="modal-dialog">
1257
+ <d c="modal-content">
1258
+ <d c="modal-header">
1259
+ <button type="button" c="close" data-dismiss="modal">
1260
+ <s aria-hidden="true">&#215;</s><s c="sr-only" din="html.close"></s>
1261
+ </button>
1262
+ <h4 c="modal-title">
1263
+
1264
+ <s c="modal_title"></s>
1265
+ <s c="modal_load_title"></s>
1266
+ <s c="modal_gpsW_title" din="html.acquiring_gps"></s>
1267
+ <s c="modal_help_title" din="html.help_title"></s>
1268
+ <s c="modal_share_title" din="html.share_title"></s>
1269
+ <s c="modal_marker_list_title" din="html.marker_list_title"></s>
1270
+
1271
+ </h4>
1272
+ </d>
1273
+ <d c="modal-body">
1274
+
1275
+ <d c="modal_help_content">
1276
+ <d c="help_content">
1277
+ <s dinh="html.help_using_maplat"></s>
1278
+ <p c="col-xs-12 help_img"><img src="${pointer["fullscreen.png"]}"></p>
1279
+ <h4 din="html.help_operation_title"></h4>
1280
+ <p dinh="html.help_operation_content" c="recipient"></p>
1281
+ <h4 din="html.help_selection_title"></h4>
1282
+ <p dinh="html.help_selection_content" c="recipient"></p>
1283
+ <h4 din="html.help_gps_title"></h4>
1284
+ <p dinh="html.help_gps_content" c="recipient"></p>
1285
+ <h4 din="html.help_poi_title"></h4>
1286
+ <p dinh="html.help_poi_content" c="recipient"></p>
1287
+ <h4 din="html.help_etc_title"></h4>
1288
+ <ul>
1289
+ <li dinh="html.help_etc_attr" c="recipient"></li>
1290
+ <li dinh="html.help_etc_help" c="recipient"></li>
1291
+ <s c="share_help"><li dinh="html.help_share_help" c="recipient"></li></s>
1292
+ <s c="border_help"><li dinh="html.help_etc_border" c="recipient"></li></s>
1293
+ <s c="hide_marker_help"><li dinh="html.help_etc_hide_marker" c="recipient"></li></s>
1294
+ <s c="marker_list_help"><li dinh="html.help_etc_marker_list" c="recipient"></li></s>
1295
+ <li dinh="html.help_etc_slider" c="recipient"></li>
1296
+ </ul>
1297
+ <p><a href="https://www.maplat.jp/" target="_blank">Maplat</a>
1298
+ © 2015- Kohei Otsuka, Code for History</p>
1299
+ </d>
1300
+ </d>
1301
+
1302
+ <d c="modal_poi_content">
1303
+ <d c="poi_web_div"></d>
1304
+ <d c="poi_share_buttons recipient row">
1305
+ <d c="form-group col-xs-4 text-center"><button title="Copy to clipboard" class="share btn btn-light" data="cp_view"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M224 0c-35.3 0-64 28.7-64 64V96H96c-35.3 0-64 28.7-64 64V448c0 35.3 28.7 64 64 64H288c35.3 0 64-28.7 64-64V384h64c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H224zM288 384H96V160H224c0-17.7 14.3-32 32-32h64V256c0 17.7 14.3 32 32 32h96V384H288z"/></svg>&nbsp;<small din="html.share_copy"></small></button></d>
1306
+ <d c="form-group col-xs-4 text-center"><button title="Twitter" class="share btn btn-light" data="tw_view"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"/></svg>&nbsp;<small>Twitter</small></button></d>
1307
+ <d c="form-group col-xs-4 text-center"><button title="Facebook" class="share btn btn-light" data="fb_view"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256C504 119 393 8 256 8S8 119 8 256c0 121.3 87.1 222.4 203 240.5V327.9h-61v-71.9h61V203c0-60.8 35.8-93.7 89.2-93.7 25.5 0 50.4 1.8 56.1 2.6v62.4h-35.4c-29.5 0-37.4 18.2-37.4 42.1v59.6h68.9l-11 71.9h-57.9V496.5C416.9 478.4 504 377.3 504 256z"/></svg>&nbsp;<small>Facebook</small></button></d>
1308
+ </d>
1309
+ <d c="qr_view_poi center-block" style="width:128px;"></d>
1310
+ <d c="modal_share_poi"></d>
1311
+ <p><img src="" height="0px" width="0px"></p>
1312
+ </d>
1313
+
1314
+ <d c="modal_share_content">
1315
+ <h4 din="html.share_app_title"></h4>
1316
+ <d id="___maplat_app_toast_${ui.html_id_seed}"></d>
1317
+ <d c="recipient row">
1318
+ <d c="form-group col-xs-4 text-center"><button title="Copy to clipboard" class="share btn btn-light" data="cp_app"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M224 0c-35.3 0-64 28.7-64 64V96H96c-35.3 0-64 28.7-64 64V448c0 35.3 28.7 64 64 64H288c35.3 0 64-28.7 64-64V384h64c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H224zM288 384H96V160H224c0-17.7 14.3-32 32-32h64V256c0 17.7 14.3 32 32 32h96V384H288z"/></svg>&nbsp;<small din="html.share_copy"></small></button></d>
1319
+ <d c="form-group col-xs-4 text-center"><button title="Twitter" class="share btn btn-light" data="tw_app"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"/></svg>&nbsp;<small>Twitter</small></button></d>
1320
+ <d c="form-group col-xs-4 text-center"><button title="Facebook" class="share btn btn-light" data="fb_app"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256C504 119 393 8 256 8S8 119 8 256c0 121.3 87.1 222.4 203 240.5V327.9h-61v-71.9h61V203c0-60.8 35.8-93.7 89.2-93.7 25.5 0 50.4 1.8 56.1 2.6v62.4h-35.4c-29.5 0-37.4 18.2-37.4 42.1v59.6h68.9l-11 71.9h-57.9V496.5C416.9 478.4 504 377.3 504 256z"/></svg>&nbsp;<small>Facebook</small></button></d>
1321
+ </d>
1322
+ <d c="qr_app center-block" style="width:128px;"></d>
1323
+ <d c="modal_share_state">
1324
+ <h4 din="html.share_state_title"></h4>
1325
+ <d id="___maplat_view_toast_${ui.html_id_seed}"></d>
1326
+ <d c="recipient row">
1327
+ <d c="form-group col-xs-4 text-center"><button title="Copy to clipboard" c="share btn btn-light" data="cp_view"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M224 0c-35.3 0-64 28.7-64 64V96H96c-35.3 0-64 28.7-64 64V448c0 35.3 28.7 64 64 64H288c35.3 0 64-28.7 64-64V384h64c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H224zM288 384H96V160H224c0-17.7 14.3-32 32-32h64V256c0 17.7 14.3 32 32 32h96V384H288z"/></svg>&nbsp;<small din="html.share_copy"></small></button></d>
1328
+ <d c="form-group col-xs-4 text-center"><button title="Twitter" c="share btn btn-light" data="tw_view"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"/></svg>&nbsp;<small>Twitter</small></button></d>
1329
+ <d c="form-group col-xs-4 text-center"><button title="Facebook" c="share btn btn-light" data="fb_view"><svg style="width:14px;height:14px;vertical-align:text-bottom;" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256C504 119 393 8 256 8S8 119 8 256c0 121.3 87.1 222.4 203 240.5V327.9h-61v-71.9h61V203c0-60.8 35.8-93.7 89.2-93.7 25.5 0 50.4 1.8 56.1 2.6v62.4h-35.4c-29.5 0-37.4 18.2-37.4 42.1v59.6h68.9l-11 71.9h-57.9V496.5C416.9 478.4 504 377.3 504 256z"/></svg>&nbsp;<small>Facebook</small></button></d>
1330
+ </d>
1331
+ <d c="qr_view center-block" style="width:128px;"></d>
1332
+ </d>
1333
+ <p><img src="" height="0px" width="0px"></p>
1334
+ </d>
1335
+
1336
+ <d c="modal_map_content">
1337
+ ${META_KEYS.map(key => {
1338
+ if (key == "title" || key == "officialTitle") return "";
1339
+ return `<d c="recipients ${key}_div"><dl c="dl-horizontal">
1340
+ <dt din="html.${key}"></dt>
1341
+ <dd c="${key}_dd"></dd>
1342
+ </dl></d> `;
1343
+ }).join("")}
1344
+ <d c="recipients modal_cache_content"><dl c="dl-horizontal">
1345
+ <dt din="html.cache_handle"></dt>
1346
+ <dd><s c="cache_size"></s></dd>
1347
+ <dt></dt>
1348
+ <dd><s c="pull-right"><button c="cache_fetch btn btn-default" href="#" din="html.cache_fetch"></button>
1349
+ <button c="cache_delete btn btn-default" href="#" din="html.cache_delete"></button></s></dd>
1350
+ </dl></d>
1351
+ </d>
1352
+
1353
+ <d c="modal_load_content">
1354
+ <p c="recipient"><img src="${pointer["loading.png"]}"><s din="html.app_loading_body"></s></p>
1355
+ <d c="splash_div hide row"><p c="col-xs-12 poi_img"><img c="splash_img" src=""></p></d>
1356
+ <p><img src="" height="0px" width="0px"></p>
1357
+ </d>
1358
+
1359
+ <d c="modal_marker_list_content">
1360
+ <ul c="list-group"></ul>
1361
+ </d>
1362
+
1363
+ <p c="modal_gpsD_content" c="recipient"></p>
1364
+ <p c="modal_gpsW_content" c="recipient"></p>
1365
+
1366
+ </d>
1367
+ </d>
1368
+ </d>
1369
+ </d> `);
1370
+
1371
+ for (let i = newElems.length - 1; i >= 0; i--) {
1372
+ ui.core!.mapDivDocument!.insertBefore(
1373
+ newElems[i],
1374
+ ui.core!.mapDivDocument!.firstChild
1375
+ );
1376
+ }
1377
+
1378
+ // PWA
1379
+ if (pwaManifest) {
1380
+ if (pwaManifest === true) {
1381
+ pwaManifest = `./pwa/${ui.core!.appid}_manifest.json`;
1382
+ }
1383
+ if (!pwaWorker) {
1384
+ pwaWorker = "./service-worker.js";
1385
+ }
1386
+ if (!pwaScope) {
1387
+ pwaScope = "./";
1388
+ }
1389
+
1390
+ const head = document.querySelector("head");
1391
+ if (head) {
1392
+ if (!head.querySelector('link[rel="manifest"]')) {
1393
+ head.appendChild(
1394
+ createElement(`<link rel="manifest" href="${pwaManifest}">`)[0]
1395
+ );
1396
+ }
1397
+ }
1398
+ try {
1399
+ Weiwudi.registerSW(pwaWorker, { scope: pwaScope });
1400
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1401
+ } catch (_e) {} // eslint-disable-line no-empty
1402
+
1403
+ if (head && !head.querySelector('link[rel="apple-touch-icon"]')) {
1404
+ fetch(pwaManifest)
1405
+ .then(response => response.json())
1406
+ .then(value => {
1407
+ if (value.icons) {
1408
+ value.icons.forEach((icon: { src: string; sizes: string }) => {
1409
+ const src = absoluteUrl(pwaManifest as string, icon.src);
1410
+ const sizes = icon.sizes;
1411
+ const tag = `<link rel="apple-touch-icon" sizes="${sizes}" href="${src}">`;
1412
+ head.appendChild(createElement(tag)[0]);
1413
+ });
1414
+ }
1415
+ })
1416
+ .catch(err => {
1417
+ console.error("Failed to fetch PWA manifest:", err);
1418
+ });
1419
+ }
1420
+ }
1421
+ }