@maplat/ui 0.11.11 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/ui_init.ts CHANGED
@@ -34,6 +34,7 @@ import type { MaplatUi } from "./index";
34
34
  import type { MaplatAppOption } from "./types";
35
35
  import i18n from "i18next";
36
36
  import i18nHttpBackend from "i18next-http-backend";
37
+ import browserLanguage from "./browserlanguage";
37
38
 
38
39
  Swiper.use([Navigation, Pagination]);
39
40
 
@@ -54,12 +55,18 @@ export const META_KEYS = [
54
55
  "description"
55
56
  ];
56
57
 
57
- async function i18nLoader(ui: MaplatUi) {
58
+ async function i18nLoader(ui: MaplatUi, appOption: MaplatAppOption) {
59
+ const appData = ui.core!.appData!;
60
+ const lang = appOption.lang || appData.lang || browserLanguage();
61
+ if (lang) ui.lang = lang;
62
+ if (ui.lang.toLowerCase() == "zh-hk" || ui.lang.toLowerCase() == "zh-hant")
63
+ ui.lang = "zh-TW";
64
+
58
65
  return new Promise<void>((resolve, _reject) => {
59
66
  const translib = i18n.use(i18nHttpBackend);
60
67
  translib.init(
61
68
  {
62
- lng: "ja", //ui.core!.lang,
69
+ lng: ui.lang,
63
70
  fallbackLng: ["en"],
64
71
  backend: {
65
72
  loadPath: "assets/locales/{{lng}}/{{ns}}.json"
@@ -74,25 +81,191 @@ async function i18nLoader(ui: MaplatUi) {
74
81
  });
75
82
  }
76
83
 
84
+ type UiReadyGate = {
85
+ promise: Promise<void>;
86
+ markControlsReady: () => void;
87
+ markSwipersReady: () => void;
88
+ };
89
+
90
+ function getUiReadyGate(ui: MaplatUi): UiReadyGate {
91
+ const key = "__uiReadyGate";
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
+ const existing = (ui as any)[key] as UiReadyGate | undefined;
94
+ if (existing) return existing;
95
+
96
+ let resolveReady: (() => void) | undefined;
97
+ let controlsReady = false;
98
+ let swipersReady = false;
99
+ let resolved = false;
100
+ const promise = new Promise<void>(resolve => {
101
+ resolveReady = resolve;
102
+ });
103
+ const maybeResolve = () => {
104
+ if (resolved || !resolveReady) return;
105
+ if (controlsReady && swipersReady) {
106
+ resolved = true;
107
+ resolveReady();
108
+ }
109
+ };
110
+
111
+ const gate: UiReadyGate = {
112
+ promise,
113
+ markControlsReady: () => {
114
+ controlsReady = true;
115
+ maybeResolve();
116
+ },
117
+ markSwipersReady: () => {
118
+ swipersReady = true;
119
+ maybeResolve();
120
+ }
121
+ };
122
+
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ (ui as any)[key] = gate;
125
+ return gate;
126
+ }
127
+
128
+ type UiDomGate = {
129
+ promise: Promise<void>;
130
+ isReady: () => boolean;
131
+ markReady: () => void;
132
+ };
133
+
134
+ function getUiDomGate(ui: MaplatUi): UiDomGate {
135
+ const key = "__uiDomGate";
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
+ const existing = (ui as any)[key] as UiDomGate | undefined;
138
+ if (existing) return existing;
139
+
140
+ let resolveReady: (() => void) | undefined;
141
+ let ready = false;
142
+ const promise = new Promise<void>(resolve => {
143
+ resolveReady = resolve;
144
+ });
145
+ const gate: UiDomGate = {
146
+ promise,
147
+ isReady: () => ready,
148
+ markReady: () => {
149
+ if (ready) return;
150
+ ready = true;
151
+ resolveReady?.();
152
+ }
153
+ };
154
+
155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
+ (ui as any)[key] = gate;
157
+ return gate;
158
+ }
159
+
160
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
+ function applyMapChanged(ui: MaplatUi, map: any) {
162
+ const core = ui.core!;
163
+ if (!ui.baseSwiper || !ui.overlaySwiper) return;
164
+
165
+ ui.baseSwiper.setSlideMapID(map.mapID);
166
+ ui.overlaySwiper.setSlideMapID(map.mapID);
167
+
168
+ const title = map.officialTitle || map.title || map.label;
169
+ (
170
+ core.mapDivDocument!.querySelector(".map-title span") as HTMLElement
171
+ ).innerText = ui.translate!(title) || "";
172
+
173
+ if (ui.checkOverlayID(map.mapID)) {
174
+ ui.sliderNew.setEnable(true);
175
+ } else {
176
+ ui.sliderNew.setEnable(false);
177
+ }
178
+ const transparency = ui.sliderNew.get("slidervalue") * 100;
179
+ core.mapObject.setTransparency(transparency);
180
+
181
+ ui.updateEnvelope();
182
+ ui.updateUrl();
183
+ }
184
+
77
185
  export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
78
- await i18nLoader(ui);
79
- ui.core = new Core(appOption);
186
+ let resolveI18nReady: (() => void) | undefined;
187
+ let rejectI18nReady: ((error: unknown) => void) | undefined;
188
+ let i18nResolved = false;
189
+ const i18nReady = new Promise<void>((resolve, reject) => {
190
+ resolveI18nReady = resolve;
191
+ rejectI18nReady = reject;
192
+ });
193
+ const existingUiHooks = appOption.uiHooks;
194
+ appOption.uiHooks = {
195
+ ...existingUiHooks,
196
+ onUiConfigure: async (context: unknown) => {
197
+ try {
198
+ await i18nLoader(ui, appOption);
199
+ if (!i18nResolved) {
200
+ i18nResolved = true;
201
+ resolveI18nReady?.();
202
+ }
203
+ } catch (error) {
204
+ if (!i18nResolved) {
205
+ i18nResolved = true;
206
+ rejectI18nReady?.(error);
207
+ }
208
+ throw error;
209
+ }
210
+ if (existingUiHooks?.onUiConfigure) {
211
+ return existingUiHooks.onUiConfigure(context);
212
+ }
213
+ return undefined;
214
+ },
215
+ onAppdataReady: async (context: unknown) => {
216
+ await initUiFromAppData(ui, appOption);
217
+ if (existingUiHooks?.onAppdataReady) {
218
+ return existingUiHooks.onAppdataReady(context);
219
+ }
220
+ return undefined;
221
+ },
222
+ onUiDomReady: async (context: unknown) => {
223
+ await i18nReady;
224
+ translateDom(ui);
225
+ initModalHandlers(ui);
226
+ initSplash(ui);
227
+ initDocumentTitle(ui);
228
+ if (existingUiHooks?.onUiDomReady) {
229
+ return existingUiHooks.onUiDomReady(context);
230
+ }
231
+ return undefined;
232
+ },
233
+ onCoreReady: async (context: unknown) => {
234
+ initControls(ui, appOption);
235
+ await initMapEventListeners(ui);
236
+ initGpsHandlers(ui, appOption);
237
+ if (existingUiHooks?.onCoreReady) {
238
+ return existingUiHooks.onCoreReady(context);
239
+ }
240
+ return undefined;
241
+ }
242
+ };
243
+ const core = (ui.core = new Core(appOption));
244
+ const uiReadyGate = getUiReadyGate(ui);
245
+ core.addEventListener("mapChanged", (evt: unknown) => {
246
+ const map = (evt as CustomEvent).detail;
247
+ (async () => {
248
+ await uiReadyGate.promise;
249
+ applyMapChanged(ui, map);
250
+ })();
251
+ });
252
+ }
253
+
254
+ async function initUiFromAppData(ui: MaplatUi, appOption: MaplatAppOption) {
255
+ const core = ui.core!;
80
256
  if (appOption.icon) {
81
257
  (pointer as Record<string, string>)["defaultpin.png"] = appOption.icon;
82
258
  }
83
-
84
259
  if (appOption.restore) {
85
260
  ui.setShowBorder(appOption.restore.showBorder || false);
86
- if (appOption.restore.hideMarker) {
87
- ui.core!.waitReady.then(() => {
88
- ui.setHideMarker(appOption.restore!.hideMarker);
89
- });
90
- }
91
- if (appOption.restore.openedMarker) {
92
- console.log(appOption.restore.openedMarker);
93
- ui.core!.waitReady.then(() => {
94
- console.log(`Timeout ${appOption.restore!.openedMarker} `);
95
- ui.handleMarkerActionById(appOption.restore!.openedMarker!);
261
+ if (appOption.restore.hideMarker || appOption.restore.openedMarker) {
262
+ core.waitReady.then(() => {
263
+ if (appOption.restore!.hideMarker) {
264
+ ui.setHideMarker(appOption.restore!.hideMarker);
265
+ }
266
+ if (appOption.restore!.openedMarker) {
267
+ ui.handleMarkerActionById(appOption.restore!.openedMarker!);
268
+ }
96
269
  });
97
270
  }
98
271
  } else if (appOption.restoreSession) {
@@ -106,46 +279,45 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
106
279
  !!parseInt(String(localStorage.getItem("showBorder") || "0"), 10)
107
280
  );
108
281
  }
109
- if (ui.core!.initialRestore.hideMarker) {
110
- ui.core!.waitReady.then(() => {
111
- ui.setHideMarker(true);
112
- });
282
+ if (core.initialRestore.hideMarker) {
283
+ ui.setHideMarker(true);
113
284
  }
114
285
  } else {
115
286
  ui.setShowBorder(false);
116
287
  }
117
288
 
289
+ const mapDivDocument = core.mapDivDocument!;
118
290
  ui.enablePoiHtmlNoScroll = appOption.enablePoiHtmlNoScroll || false;
119
291
  if (appOption.enableShare) {
120
- ui.core!.mapDivDocument!.classList.add("enable_share");
292
+ mapDivDocument.classList.add("enable_share");
121
293
  ui.enableShare = true;
122
294
  }
123
295
  if (appOption.enableHideMarker) {
124
- ui.core!.mapDivDocument!.classList.add("enable_hide_marker");
296
+ mapDivDocument.classList.add("enable_hide_marker");
125
297
  ui.enableHideMarker = true;
126
298
  }
127
299
  if (appOption.enableBorder) {
128
- ui.core!.mapDivDocument!.classList.add("enable_border");
300
+ mapDivDocument.classList.add("enable_border");
129
301
  ui.enableBorder = true;
130
302
  }
131
303
  if (appOption.enableMarkerList) {
132
- ui.core!.mapDivDocument!.classList.add("enable_marker_list");
304
+ mapDivDocument.classList.add("enable_marker_list");
133
305
  ui.enableMarkerList = true;
134
306
  }
135
307
  if (appOption.disableNoimage) {
136
308
  ui.disableNoimage = true;
137
309
  }
138
310
  if (appOption.stateUrl) {
139
- ui.core!.mapDivDocument!.classList.add("state_url");
311
+ mapDivDocument.classList.add("state_url");
140
312
  }
141
313
  if (appOption.alwaysGpsOn) {
142
314
  ui.alwaysGpsOn = true;
143
315
  }
144
- if (ui.core!.enableCache) {
145
- ui.core!.mapDivDocument!.classList.add("enable_cache");
316
+ if (core.enableCache) {
317
+ mapDivDocument.classList.add("enable_cache");
146
318
  }
147
319
  if ("ontouchstart" in window) {
148
- ui.core!.mapDivDocument!.classList.add("ol-touch");
320
+ mapDivDocument.classList.add("ol-touch");
149
321
  ui.isTouch = true;
150
322
  }
151
323
  if (appOption.mobileIF) {
@@ -156,21 +328,19 @@ export async function uiInit(ui: MaplatUi, appOption: MaplatAppOption) {
156
328
  }
157
329
 
158
330
  initDom(ui, appOption);
159
- initModalHandlers(ui, appOption);
160
- initMapEventListeners(ui);
161
- initGpsHandlers(ui, appOption);
162
331
  }
163
332
 
164
333
  function initGpsHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
334
+ const core = ui.core!;
165
335
  const enableOutOfMap = !appOption.presentationMode;
166
- const mapDiv = ui.core!.mapDivDocument!;
336
+ const mapDiv = core.mapDivDocument!;
167
337
  const modalBase = mapDiv.querySelector(".modalBase")!;
168
338
  const modalTitle = mapDiv.querySelector(".modal_title") as HTMLElement;
169
339
  const modalGpsDContent = mapDiv.querySelector(
170
340
  ".modal_gpsD_content"
171
341
  ) as HTMLElement;
172
342
 
173
- ui.core!.addEventListener("outOfMap", (_evt: unknown) => {
343
+ core.addEventListener("outOfMap", (_evt: unknown) => {
174
344
  console.log("Event: outOfMap");
175
345
  if (enableOutOfMap) {
176
346
  modalTitle.innerText = ui.t("app.out_of_map") || "";
@@ -181,7 +351,7 @@ function initGpsHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
181
351
  });
182
352
 
183
353
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
184
- ui.core!.addEventListener("gps_error", (evt: any) => {
354
+ core.addEventListener("gps_error", (evt: any) => {
185
355
  console.log("GPS Error:", evt);
186
356
  const errorMap: Record<string, string> = {
187
357
  user_gps_deny: "app.user_gps_deny",
@@ -189,7 +359,6 @@ function initGpsHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
189
359
  gps_timeout: "app.gps_timeout"
190
360
  };
191
361
 
192
- if (!ui.core) return;
193
362
  modalTitle.innerText = ui.t("app.gps_error") || "";
194
363
  modalGpsDContent.innerText =
195
364
  ui.t(errorMap[evt.detail] || "app.gps_error") || "";
@@ -200,7 +369,7 @@ function initGpsHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
200
369
  });
201
370
 
202
371
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
203
- ui.core!.addEventListener("gps_result", (evt: any) => {
372
+ core.addEventListener("gps_result", (evt: any) => {
204
373
  console.log("GPS Result:", evt);
205
374
  if (evt.detail && evt.detail.error) {
206
375
  const error = evt.detail.error;
@@ -273,6 +442,7 @@ function initSwipers(ui: MaplatUi, sources: any[]) {
273
442
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
274
443
  const appBbox: any[] = [];
275
444
  let cIndex = 0;
445
+ const core = ui.core!;
276
446
 
277
447
  for (let i = 0; i < sources.length; i++) {
278
448
  const source = sources[i];
@@ -304,8 +474,8 @@ function initSwipers(ui: MaplatUi, sources: any[]) {
304
474
 
305
475
  if (ui.splashPromise) {
306
476
  ui.splashPromise.then(() => {
307
- const modalElm = ui.core!.mapDivDocument!.querySelector(".modalBase")!;
308
- prepareModal(modalElm, { root: ui.core!.mapDivDocument! }).hide();
477
+ const modalElm = core.mapDivDocument!.querySelector(".modalBase")!;
478
+ prepareModal(modalElm, { root: core.mapDivDocument! }).hide();
309
479
  });
310
480
  }
311
481
 
@@ -346,16 +516,16 @@ function initSwipers(ui: MaplatUi, sources: any[]) {
346
516
  baseSwiper.on("click", (_e: any) => {
347
517
  if (!baseSwiper.clickedSlide) return;
348
518
  const slide = baseSwiper.clickedSlide;
349
- ui.core!.changeMap(slide.getAttribute("data")!);
519
+ core.changeMap(slide.getAttribute("data")!);
350
520
  delete ui._selectCandidateSources;
351
521
  baseSwiper.setSlideIndexAsSelected(
352
522
  parseInt(slide.getAttribute("data-swiper-slide-index") || "0", 10)
353
523
  );
354
524
  });
355
525
  if (baseSources.length < 2) {
356
- ui.core!.mapDivDocument!.querySelector(".base-swiper")!.classList.add(
357
- "single-map"
358
- );
526
+ core
527
+ .mapDivDocument!.querySelector(".base-swiper")!
528
+ .classList.add("single-map");
359
529
  }
360
530
 
361
531
  const overlaySwiper = (ui.overlaySwiper = new Swiper(".overlay-swiper", {
@@ -383,16 +553,16 @@ function initSwipers(ui: MaplatUi, sources: any[]) {
383
553
  overlaySwiper.on("click", (_e: any) => {
384
554
  if (!overlaySwiper.clickedSlide) return;
385
555
  const slide = overlaySwiper.clickedSlide;
386
- ui.core!.changeMap(slide.getAttribute("data")!);
556
+ core.changeMap(slide.getAttribute("data")!);
387
557
  delete ui._selectCandidateSources;
388
558
  overlaySwiper.setSlideIndexAsSelected(
389
559
  parseInt(slide.getAttribute("data-swiper-slide-index") || "0", 10)
390
560
  );
391
561
  });
392
562
  if (overlaySources.length < 2) {
393
- ui.core!.mapDivDocument!.querySelector(".overlay-swiper")!.classList.add(
394
- "single-map"
395
- );
563
+ core
564
+ .mapDivDocument!.querySelector(".overlay-swiper")!
565
+ .classList.add("single-map");
396
566
  }
397
567
 
398
568
  baseSources.forEach(source => {
@@ -425,66 +595,24 @@ function initSwipers(ui: MaplatUi, sources: any[]) {
425
595
 
426
596
  baseSwiper.slideToLoop(0);
427
597
  overlaySwiper.slideToLoop(0);
428
- ellips(ui.core!.mapDivDocument!);
598
+ ellips(core.mapDivDocument!);
599
+ getUiReadyGate(ui).markSwipersReady();
429
600
  }
430
601
 
431
- function initMapEventListeners(ui: MaplatUi) {
432
- ui.core!.addEventListener("mapChanged", (evt: unknown) => {
433
- const map = (evt as CustomEvent).detail;
434
-
435
- ui.baseSwiper.setSlideMapID(map.mapID);
436
- ui.overlaySwiper.setSlideMapID(map.mapID);
437
-
438
- const title = map.officialTitle || map.title || map.label;
439
- (
440
- ui.core!.mapDivDocument!.querySelector(".map-title span") as HTMLElement
441
- ).innerText = ui.translate!(title) || "";
442
-
443
- if (ui.checkOverlayID(map.mapID)) {
444
- ui.sliderNew.setEnable(true);
445
- } else {
446
- ui.sliderNew.setEnable(false);
447
- }
448
- const transparency = ui.sliderNew.get("slidervalue") * 100;
449
- ui.core!.mapObject.setTransparency(transparency);
450
-
451
- ui.updateEnvelope();
452
- ui.updateUrl();
453
- });
602
+ async function initMapEventListeners(ui: MaplatUi) {
603
+ const core = ui.core!;
454
604
 
455
- ui.core!.addEventListener("poi_number", (evt: unknown) => {
605
+ core.addEventListener("poi_number", (evt: unknown) => {
456
606
  const number = (evt as CustomEvent).detail;
457
607
  if (number) {
458
- ui.core!.mapDivDocument!.classList.remove("no_poi");
608
+ core.mapDivDocument!.classList.remove("no_poi");
459
609
  } else {
460
- ui.core!.mapDivDocument!.classList.add("no_poi");
610
+ core.mapDivDocument!.classList.add("no_poi");
461
611
  }
462
612
  });
463
613
 
464
- ui.core!.addEventListener("sourceLoaded", (evt: unknown) => {
465
- const sources = (evt as CustomEvent).detail;
466
- initSwipers(ui, sources);
467
- });
468
-
469
- ui.core!.waitReady.then(() => {
470
- // Capture pointerdown at viewport level to ensure we get pixel before any stopPropagation
471
-
472
- ui.core!.mapObject.getViewport().addEventListener(
473
- "pointerdown",
474
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
475
- (evt: any) => {
476
- ui.lastClickPixel = ui.core!.mapObject.getEventPixel(evt);
477
- ui.lastClickCoordinate = ui.core!.mapObject.getCoordinateFromPixel(
478
- ui.lastClickPixel
479
- );
480
- },
481
- true
482
- );
483
- });
484
-
485
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
486
- ui.core!.addEventListener("clickMarkers", (evt: any) => {
487
- const data = evt.detail;
614
+ core.addEventListener("clickMarkers", (evt: unknown) => {
615
+ const data = (evt as CustomEvent).detail;
488
616
  if (data.length === 1) {
489
617
  ui.handleMarkerAction(data[0]);
490
618
  } else {
@@ -505,24 +633,49 @@ function initMapEventListeners(ui: MaplatUi) {
505
633
  }
506
634
  });
507
635
 
508
- ui.core!.waitReady.then(() => {
636
+ if (!ui.baseSwiper) {
509
637
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
510
- ui.core!.mapObject.on("moveend", (_evt: any) => {
511
- ui.updateUrl();
512
- });
638
+ const sources = Object.values((core as any).cacheHash || {});
639
+ if (sources.length) {
640
+ const domGate = getUiDomGate(ui);
641
+ if (domGate.isReady()) {
642
+ initSwipers(ui, sources);
643
+ } else {
644
+ domGate.promise.then(() => {
645
+ if (!ui.baseSwiper) {
646
+ initSwipers(ui, sources);
647
+ }
648
+ });
649
+ }
650
+ }
651
+ }
652
+ const mapObject = core.mapObject;
653
+
654
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
655
+ mapObject.on("moveend", (_evt: any) => {
656
+ ui.updateUrl();
513
657
  });
658
+
659
+ // Capture pointerdown at viewport level to ensure we get pixel before any stopPropagation
660
+ mapObject.getViewport().addEventListener(
661
+ "pointerdown",
662
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
663
+ (evt: any) => {
664
+ ui.lastClickPixel = core.mapObject.getEventPixel(evt);
665
+ ui.lastClickCoordinate = core.mapObject.getCoordinateFromPixel(
666
+ ui.lastClickPixel
667
+ );
668
+ },
669
+ true
670
+ );
514
671
  }
515
672
 
516
- function initModalHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
673
+ function initModalHandlers(ui: MaplatUi) {
517
674
  let cachedMarkerListMapID: string | undefined;
518
- const mapDiv = ui.core!.mapDivDocument!;
675
+ const core = ui.core!;
676
+ const mapDiv = core.mapDivDocument!;
519
677
  const modalBase = mapDiv.querySelector(".modalBase") as HTMLElement;
520
678
 
521
- const restoreTransparency =
522
- ui.core!.initialRestore.transparency ||
523
- (appOption.restore ? appOption.restore.transparency : undefined);
524
- const enableSplash = !ui.core!.initialRestore.mapID;
525
-
526
679
  // Delegated event listener for share buttons
527
680
  mapDiv.addEventListener("click", (evt: Event) => {
528
681
  const target = evt.target as HTMLElement;
@@ -592,155 +745,9 @@ function initModalHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
592
745
  });
593
746
  });
594
747
 
595
- ui.core!.addEventListener("uiPrepare", (_evt: unknown) => {
596
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
597
- const imageExtractor = function (text: any) {
598
- const regexp = /\$\{([a-zA-Z0-9_\.\/\-]+)\}/g; // eslint-disable-line no-useless-escape
599
- let ret = text;
600
- let match;
601
- while ((match = regexp.exec(text)) != null) {
602
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
603
- ret = ret.replace(match[0], (pointer as any)[match[1]]);
604
- }
605
- return ret;
606
- };
607
-
608
- let i18nTargets = mapDiv.querySelectorAll("[data-i18n], [din]");
609
- i18nTargets.forEach(target => {
610
- const key =
611
- target.getAttribute("data-i18n") || target.getAttribute("din");
612
- (target as HTMLElement).innerText = imageExtractor(ui.t(key as string));
613
- });
614
- i18nTargets = mapDiv.querySelectorAll("[data-i18n-html], [dinh]");
615
- i18nTargets.forEach(target => {
616
- const key =
617
- target.getAttribute("data-i18n-html") || target.getAttribute("dinh");
618
- target.innerHTML = imageExtractor(ui.t(key as string));
619
- });
620
- // Explicitly fix app_loading_body with a more robust selector if needed, or re-run translation for it
621
- const appLoadingBody = mapDiv.querySelector(
622
- '[data-i18n="html.app_loading_body"], [din="html.app_loading_body"]'
623
- );
624
- if (appLoadingBody) {
625
- (appLoadingBody as HTMLElement).innerHTML = imageExtractor(
626
- ui.t("html.app_loading_body")
627
- );
628
- }
629
-
630
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
631
- const options: any = {
632
- reverse: true,
633
- tipLabel: ui.t("control.trans", { ns: "translation" })
634
- };
635
- if (restoreTransparency) {
636
- options.initialValue = restoreTransparency / 100;
637
- }
638
- ui.sliderNew = new SliderNew(options);
639
- ui.core!.appData!.controls = [
640
- new Copyright({
641
- tipLabel: ui.t("control.info", { ns: "translation" })
642
- }),
643
- new CompassRotate({
644
- tipLabel: ui.t("control.compass", { ns: "translation" })
645
- }),
646
- new Zoom({
647
- tipLabel: ui.t("control.zoom", { ns: "translation" })
648
- }),
649
- new SetGPS({
650
- ui,
651
- tipLabel: ui.t("control.gps", { ns: "translation" })
652
- }),
653
- new GoHome({
654
- tipLabel: ui.t("control.home", { ns: "translation" })
655
- }),
656
- ui.sliderNew,
657
- new Maplat({
658
- tipLabel: ui.t("control.help", { ns: "translation" })
659
- })
660
- ];
661
- if (ui.enableShare) {
662
- ui.core!.appData!.controls.push(
663
- new Share({
664
- tipLabel: ui.t("control.share", { ns: "translation" })
665
- })
666
- );
667
- }
668
- if (ui.enableBorder) {
669
- ui.core!.appData!.controls.push(
670
- new Border({
671
- tipLabel: ui.t("control.border", { ns: "translation" })
672
- })
673
- );
674
- }
675
- if (ui.enableHideMarker) {
676
- ui.core!.appData!.controls.push(
677
- new HideMarker({
678
- tipLabel: ui.t("control.hide_marker", { ns: "translation" })
679
- })
680
- );
681
- }
682
- if (ui.enableMarkerList) {
683
- ui.core!.appData!.controls.push(
684
- new MarkerList({
685
- tipLabel: ui.t("control.marker_list", { ns: "translation" })
686
- })
687
- );
688
- }
689
-
690
- // Contextmenu
691
- ui.contextMenu = new ContextMenu({
692
- eventType: "__dummy__",
693
- width: 170,
694
- defaultItems: false,
695
- items: []
696
- });
697
- ui.core!.appData!.controls.push(ui.contextMenu);
698
-
699
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
700
- ui.sliderNew.on("propertychange", (evt: any) => {
701
- if (evt.key === "slidervalue") {
702
- ui.core!.setTransparency(ui.sliderNew.get(evt.key) * 100);
703
- ui.updateUrl();
704
- }
705
- });
706
-
707
- if (enableSplash) {
708
- // Check Splash data
709
- let splash = false;
710
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
711
- if ((ui.core!.appData as any).splash) splash = true;
712
-
713
- // const modal = new bsn.Modal(modalElm, { root: mapDiv });
714
- const modal = prepareModal(modalBase, { root: mapDiv });
715
-
716
- (mapDiv.querySelector(".modal_load_title") as HTMLElement).innerText =
717
- ui.translate(ui.core!.appData!.appName) || "";
718
- if (splash) {
719
- mapDiv
720
- .querySelector(".splash_img")!
721
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
722
- .setAttribute("src", `img/${(ui.core!.appData as any).splash}`);
723
- mapDiv.querySelector(".splash_div")!.classList.remove("hide");
724
- }
725
- ui.modalSetting("load");
726
- modal.show();
727
-
728
- const fadeTime = splash ? 1000 : 200;
729
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
730
- ui.splashPromise = new Promise((resolve: any) => {
731
- setTimeout(() => {
732
- resolve();
733
- }, fadeTime);
734
- });
735
- }
736
-
737
- document.querySelector("title")!.innerHTML =
738
- ui.translate(ui.core!.appName) || "";
739
- });
740
-
741
- ui.core!.waitReady.then(() => {
748
+ core.waitReady.then(() => {
742
749
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
743
- ui.core!.mapObject.on("click_control", (evt: any) => {
750
+ core.mapObject.on("click_control", (evt: any) => {
744
751
  const control = evt.control || (evt.frameState && evt.frameState.control);
745
752
  const modal = prepareModal(modalBase);
746
753
 
@@ -826,13 +833,13 @@ function initModalHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
826
833
  modalBase.addEventListener("hidden.bs.modal", resetPanels);
827
834
 
828
835
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
829
- const currentMapID = (ui.core!.from as any).mapID;
836
+ const currentMapID = (core.from as any).mapID;
830
837
  if (cachedMarkerListMapID === currentMapID) return;
831
838
  cachedMarkerListMapID = currentMapID;
832
839
 
833
840
  listRoot.innerHTML = "";
834
841
 
835
- const layers = ui.core!.listPoiLayers(false, true);
842
+ const layers = core.listPoiLayers(false, true);
836
843
 
837
844
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
838
845
  layers.forEach((layer: any) => {
@@ -868,9 +875,9 @@ function initModalHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
868
875
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
869
876
  checkbox.addEventListener("change", (e: any) => {
870
877
  if (e.target.checked) {
871
- ui.core!.showPoiLayer(layer.id);
878
+ core.showPoiLayer(layer.id);
872
879
  } else {
873
- ui.core!.hidePoiLayer(layer.id);
880
+ core.hidePoiLayer(layer.id);
874
881
  }
875
882
  });
876
883
 
@@ -977,7 +984,7 @@ function initModalHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
977
984
  });
978
985
  } else if (control === "copyright") {
979
986
  ui.modalSetting("map");
980
- const mapData = ui.core!.from!;
987
+ const mapData = core.from!;
981
988
  const titleEl = mapDiv.querySelector(".modal_map .modal_title");
982
989
  if (titleEl) {
983
990
  const titleVal = mapData.get ? mapData.get("title") : mapData.title;
@@ -1030,7 +1037,7 @@ function initModalHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
1030
1037
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1031
1038
  const weiwudi = (mapData as any).weiwudi;
1032
1039
  if (
1033
- ui.core!.enableCache &&
1040
+ core.enableCache &&
1034
1041
  weiwudi &&
1035
1042
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1036
1043
  !(mapData as any).vector
@@ -1206,7 +1213,7 @@ function initModalHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
1206
1213
  modal.show();
1207
1214
  } else if (control === "border") {
1208
1215
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1209
- ui.setShowBorder(!(ui.core!.stateBuffer as any).showBorder);
1216
+ ui.setShowBorder(!(core.stateBuffer as any).showBorder);
1210
1217
  } else if (control === "hideMarker") {
1211
1218
  const current = mapDiv.classList.contains("hide-marker");
1212
1219
  ui.setHideMarker(!current);
@@ -1216,7 +1223,173 @@ function initModalHandlers(ui: MaplatUi, appOption: MaplatAppOption) {
1216
1223
  });
1217
1224
  }
1218
1225
 
1226
+ function translateDom(ui: MaplatUi) {
1227
+ const core = ui.core!;
1228
+ const mapDiv = core.mapDivDocument!;
1229
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1230
+ const imageExtractor = function (text: any) {
1231
+ const regexp = /\$\{([a-zA-Z0-9_\.\/\-]+)\}/g; // eslint-disable-line no-useless-escape
1232
+ let ret = text;
1233
+ let match;
1234
+ while ((match = regexp.exec(text)) != null) {
1235
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1236
+ ret = ret.replace(match[0], (pointer as any)[match[1]]);
1237
+ }
1238
+ return ret;
1239
+ };
1240
+
1241
+ let i18nTargets = mapDiv.querySelectorAll("[data-i18n], [din]");
1242
+ i18nTargets.forEach(target => {
1243
+ const key = target.getAttribute("data-i18n") || target.getAttribute("din");
1244
+ (target as HTMLElement).innerText = imageExtractor(ui.t(key as string));
1245
+ });
1246
+ i18nTargets = mapDiv.querySelectorAll("[data-i18n-html], [dinh]");
1247
+ i18nTargets.forEach(target => {
1248
+ const key =
1249
+ target.getAttribute("data-i18n-html") || target.getAttribute("dinh");
1250
+ target.innerHTML = imageExtractor(ui.t(key as string));
1251
+ });
1252
+ const appLoadingBody = mapDiv.querySelector(
1253
+ '[data-i18n="html.app_loading_body"], [din="html.app_loading_body"]'
1254
+ );
1255
+ if (appLoadingBody) {
1256
+ (appLoadingBody as HTMLElement).innerHTML = imageExtractor(
1257
+ ui.t("html.app_loading_body")
1258
+ );
1259
+ }
1260
+ }
1261
+
1262
+ function initSplash(ui: MaplatUi) {
1263
+ const core = ui.core!;
1264
+ const mapDiv = core.mapDivDocument!;
1265
+ const modalBase = mapDiv.querySelector(".modalBase") as HTMLElement;
1266
+ const enableSplash = !core.initialRestore.mapID;
1267
+ if (!enableSplash) return;
1268
+
1269
+ // Check Splash data
1270
+ let splash = false;
1271
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1272
+ if ((core.appData as any).splash) splash = true;
1273
+
1274
+ const modal = prepareModal(modalBase, { root: mapDiv });
1275
+
1276
+ (mapDiv.querySelector(".modal_load_title") as HTMLElement).innerText =
1277
+ ui.translate(core.appData!.appName) || "";
1278
+ if (splash) {
1279
+ mapDiv
1280
+ .querySelector(".splash_img")!
1281
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1282
+ .setAttribute("src", `img/${(core.appData as any).splash}`);
1283
+ mapDiv.querySelector(".splash_div")!.classList.remove("hide");
1284
+ }
1285
+ ui.modalSetting("load");
1286
+ modal.show();
1287
+
1288
+ const fadeTime = splash ? 1000 : 200;
1289
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1290
+ ui.splashPromise = new Promise((resolve: any) => {
1291
+ setTimeout(() => {
1292
+ resolve();
1293
+ }, fadeTime);
1294
+ });
1295
+ }
1296
+
1297
+ function initDocumentTitle(ui: MaplatUi) {
1298
+ const core = ui.core!;
1299
+ document.querySelector("title")!.innerHTML = ui.translate(core.appName) || "";
1300
+ }
1301
+
1302
+ function initControls(ui: MaplatUi, appOption: MaplatAppOption) {
1303
+ const core = ui.core!;
1304
+
1305
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1306
+ const options: any = {
1307
+ reverse: true,
1308
+ tipLabel: ui.t("control.trans", { ns: "translation" })
1309
+ };
1310
+ const restoreTransparency =
1311
+ core.initialRestore.transparency ||
1312
+ (appOption.restore ? appOption.restore.transparency : undefined);
1313
+ if (restoreTransparency) {
1314
+ options.initialValue = restoreTransparency / 100;
1315
+ }
1316
+ ui.sliderNew = new SliderNew(options);
1317
+ core.appData!.controls = [
1318
+ new Copyright({
1319
+ tipLabel: ui.t("control.info", { ns: "translation" })
1320
+ }),
1321
+ new CompassRotate({
1322
+ tipLabel: ui.t("control.compass", { ns: "translation" })
1323
+ }),
1324
+ new Zoom({
1325
+ tipLabel: ui.t("control.zoom", { ns: "translation" })
1326
+ }),
1327
+ new SetGPS({
1328
+ ui,
1329
+ tipLabel: ui.t("control.gps", { ns: "translation" })
1330
+ }),
1331
+ new GoHome({
1332
+ tipLabel: ui.t("control.home", { ns: "translation" })
1333
+ }),
1334
+ ui.sliderNew,
1335
+ new Maplat({
1336
+ tipLabel: ui.t("control.help", { ns: "translation" })
1337
+ })
1338
+ ];
1339
+ if (ui.enableShare) {
1340
+ core.appData!.controls.push(
1341
+ new Share({
1342
+ tipLabel: ui.t("control.share", { ns: "translation" })
1343
+ })
1344
+ );
1345
+ }
1346
+ if (ui.enableBorder) {
1347
+ core.appData!.controls.push(
1348
+ new Border({
1349
+ tipLabel: ui.t("control.border", { ns: "translation" })
1350
+ })
1351
+ );
1352
+ }
1353
+ if (ui.enableHideMarker) {
1354
+ core.appData!.controls.push(
1355
+ new HideMarker({
1356
+ tipLabel: ui.t("control.hide_marker", { ns: "translation" })
1357
+ })
1358
+ );
1359
+ }
1360
+ if (ui.enableMarkerList) {
1361
+ core.appData!.controls.push(
1362
+ new MarkerList({
1363
+ tipLabel: ui.t("control.marker_list", { ns: "translation" })
1364
+ })
1365
+ );
1366
+ }
1367
+
1368
+ // Contextmenu
1369
+ ui.contextMenu = new ContextMenu({
1370
+ eventType: "__dummy__",
1371
+ width: 170,
1372
+ defaultItems: false,
1373
+ items: []
1374
+ });
1375
+ core.appData!.controls.push(ui.contextMenu);
1376
+
1377
+ core.appData!.controls.forEach(control => {
1378
+ core.mapObject.addControl(control);
1379
+ });
1380
+
1381
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1382
+ ui.sliderNew.on("propertychange", (evt: any) => {
1383
+ if (evt.key === "slidervalue") {
1384
+ core.setTransparency(ui.sliderNew.get(evt.key) * 100);
1385
+ ui.updateUrl();
1386
+ }
1387
+ });
1388
+ getUiReadyGate(ui).markControlsReady();
1389
+ }
1390
+
1219
1391
  function initDom(ui: MaplatUi, appOption: MaplatAppOption) {
1392
+ const core = ui.core!;
1220
1393
  // Inject Custom Toast Styles
1221
1394
  const style = document.createElement("style");
1222
1395
  style.innerHTML = `
@@ -1261,11 +1434,12 @@ function initDom(ui: MaplatUi, appOption: MaplatAppOption) {
1261
1434
  <d c="swiper-button-prev overlay-prev swiper-button-white"> </d>
1262
1435
  </d> `);
1263
1436
  for (let i = newElems.length - 1; i >= 0; i--) {
1264
- ui.core!.mapDivDocument!.insertBefore(
1437
+ core.mapDivDocument!.insertBefore(
1265
1438
  newElems[i],
1266
- ui.core!.mapDivDocument!.firstChild
1439
+ core.mapDivDocument!.lastChild
1267
1440
  );
1268
1441
  }
1442
+ getUiDomGate(ui).markReady();
1269
1443
 
1270
1444
  newElems = createElement(`<d c="modal modalBase" tabindex="-1" role="dialog"
1271
1445
  aria-labelledby="staticModalLabel" aria-hidden="true" data-show="true" data-keyboard="false"
@@ -1386,16 +1560,16 @@ function initDom(ui: MaplatUi, appOption: MaplatAppOption) {
1386
1560
  </d> `);
1387
1561
 
1388
1562
  for (let i = newElems.length - 1; i >= 0; i--) {
1389
- ui.core!.mapDivDocument!.insertBefore(
1563
+ core.mapDivDocument!.insertBefore(
1390
1564
  newElems[i],
1391
- ui.core!.mapDivDocument!.firstChild
1565
+ core.mapDivDocument!.firstChild
1392
1566
  );
1393
1567
  }
1394
1568
 
1395
1569
  // PWA
1396
1570
  if (pwaManifest) {
1397
1571
  if (pwaManifest === true) {
1398
- pwaManifest = `./pwa/${ui.core!.appid}_manifest.json`;
1572
+ pwaManifest = `./pwa/${core.appid}_manifest.json`;
1399
1573
  }
1400
1574
  if (!pwaWorker) {
1401
1575
  pwaWorker = "./service-worker.js";