1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | import theTravelNotes from './TravelNotes.js'; |
26 | import theTravelNotesData from '../../data/TravelNotesData.js'; |
27 | import theConfig from '../../data/Config.js'; |
28 | import ConfigOverloader from '../../data/ConfigOverloader.js'; |
29 | import theTranslator from '../../core/uiLib/Translator.js'; |
30 | import theNoteDialogToolbarData from '../../dialogs/notesDialog/toolbar/NoteDialogToolbarData.js'; |
31 | import theOsmSearchDictionary from '../../core/osmSearch/OsmSearchDictionary.js'; |
32 | import theMapLayersCollection from '../../data/MapLayersCollection.js'; |
33 | import theErrorsUI from '../../uis/errorsUI/ErrorsUI.js'; |
34 | import EventListenersLoader from './EventListenersLoader.js'; |
35 | |
36 | import { LAT_LNG, ZERO, ONE, NOT_FOUND, HTTP_STATUS_OK } from '../Constants.js'; |
37 | |
38 | import { LeafletMap } from '../../leaflet/LeafletImports.js'; |
39 | |
40 | |
41 | |
42 | |
43 | |
44 | |
45 | |
46 | |
47 | class AppLoader { |
48 | |
49 | |
50 | |
51 | |
52 | |
53 | |
54 | #travelUrl; |
55 | |
56 | |
57 | |
58 | |
59 | |
60 | |
61 | #language; |
62 | |
63 | |
64 | |
65 | |
66 | |
67 | |
68 | #originAndPath; |
69 | |
70 | |
71 | |
72 | |
73 | |
74 | |
75 | #errorMessage; |
76 | |
77 | |
78 | |
79 | |
80 | |
81 | #readURL ( ) { |
82 | const docURL = new URL ( window.location ); |
83 | |
84 | |
85 | let strTravelUrl = docURL.searchParams.get ( 'fil' ); |
86 | if ( strTravelUrl && ZERO !== strTravelUrl.length ) { |
87 | try { |
88 | strTravelUrl = atob ( strTravelUrl ); |
89 | |
90 | |
91 | if ( strTravelUrl.match ( /[^\w-%:./]/ ) ) { |
92 | |
93 | throw new Error ( 'invalid char in the url encoded in the fil parameter' ); |
94 | } |
95 | |
96 | |
97 | const travelURL = new URL ( strTravelUrl, docURL.protocol + '//' + docURL.hostname ); |
98 | if ( |
99 | docURL.protocol && travelURL.protocol && docURL.protocol === travelURL.protocol |
100 | && |
101 | docURL.hostname && travelURL.hostname && docURL.hostname === travelURL.hostname |
102 | ) { |
103 | this.#travelUrl = encodeURI ( travelURL.href ); |
104 | } |
105 | else { |
106 | throw new Error ( 'The distant file is not on the same site than the app' ); |
107 | } |
108 | } |
109 | catch ( err ) { |
110 | if ( err instanceof Error ) { |
111 | console.error ( err ); |
112 | } |
113 | } |
114 | } |
115 | |
116 | |
117 | const urlLng = docURL.searchParams.get ( 'lng' ); |
118 | if ( urlLng ) { |
119 | if ( urlLng.match ( /^[A-Z,a-z]{2}$/ ) ) { |
120 | this.#language = urlLng.toLowerCase ( ); |
121 | } |
122 | } |
123 | } |
124 | |
125 | |
126 | |
127 | |
128 | |
129 | async #loadConfig ( ) { |
130 | const configResponse = await fetch ( this.#originAndPath + 'Config.json' ); |
131 | |
132 | if ( HTTP_STATUS_OK === configResponse.status && configResponse.ok ) { |
133 | const config = await configResponse.json ( ); |
134 | |
135 | |
136 | config.travelNotes.language = this.#language || config.travelNotes.language; |
137 | |
138 | |
139 | if ( 'wwwouaiebe.github.io' === window.location.hostname ) { |
140 | config.ApiKeysDialog.haveUnsecureButtons = true; |
141 | config.errorsUI.showHelp = true; |
142 | config.mapLayersToolbar.theDevil.addButton = false; |
143 | |
144 | config.note.maxManeuversNotes = 12; |
145 | config.note.haveBackground = true; |
146 | config.noteDialog.theDevil.addButton = false; |
147 | |
148 | config.printRouteMap.maxTiles = 10; |
149 | config.route.showDragTooltip = NOT_FOUND; |
150 | } |
151 | |
152 | |
153 | new ConfigOverloader ( ).overload ( config ); |
154 | |
155 | |
156 | theTravelNotesData.providers.forEach ( |
157 | provider => { |
158 | provider.userLanguage = theConfig.travelNotes.language; |
159 | } |
160 | ); |
161 | return true; |
162 | } |
163 | return false; |
164 | } |
165 | |
166 | |
167 | |
168 | |
169 | |
170 | |
171 | |
172 | async #loadTranslations ( translationPromiseResult, defaultTranslationPromiseResult ) { |
173 | if ( |
174 | 'fulfilled' === translationPromiseResult.status |
175 | && |
176 | HTTP_STATUS_OK === translationPromiseResult.value.status |
177 | && |
178 | translationPromiseResult.value.ok |
179 | ) { |
180 | theTranslator.setTranslations ( await translationPromiseResult.value.json ( ) ); |
181 | return true; |
182 | } |
183 | if ( |
184 | 'fulfilled' === defaultTranslationPromiseResult.status |
185 | && |
186 | HTTP_STATUS_OK === defaultTranslationPromiseResult.value.status |
187 | && |
188 | defaultTranslationPromiseResult.value.ok |
189 | ) { |
190 | theTranslator.setTranslations ( await defaultTranslationPromiseResult.value.json ( ) ); |
191 | this.#errorMessage += |
192 | 'Not possible to load the TravelNotes' + |
193 | this.#language.toUpperCase ( ) + |
194 | '.json file. English will be used. '; |
195 | return true; |
196 | } |
197 | this.#errorMessage += 'Not possible to load the translations. '; |
198 | return false; |
199 | } |
200 | |
201 | |
202 | |
203 | |
204 | |
205 | |
206 | |
207 | async #loadNoteDialogConfig ( noteDialogPromiseResult, defaultNoteDialogPromiseResult ) { |
208 | if ( |
209 | 'fulfilled' === noteDialogPromiseResult.status |
210 | && |
211 | HTTP_STATUS_OK === noteDialogPromiseResult.value.status |
212 | && |
213 | noteDialogPromiseResult.value.ok |
214 | ) { |
215 | const noteDialogData = await noteDialogPromiseResult.value.json ( ); |
216 | theNoteDialogToolbarData.loadJson ( noteDialogData ); |
217 | return true; |
218 | } |
219 | if ( |
220 | 'fulfilled' === defaultNoteDialogPromiseResult.status |
221 | && |
222 | HTTP_STATUS_OK === defaultNoteDialogPromiseResult.value.status |
223 | && |
224 | defaultNoteDialogPromiseResult.value.ok |
225 | ) { |
226 | const defaultNoteDialogData = await defaultNoteDialogPromiseResult.value.json ( ); |
227 | theNoteDialogToolbarData.loadJson ( defaultNoteDialogData ); |
228 | this.#errorMessage += |
229 | 'Not possible to load the TravelNotesNoteDialog' + |
230 | this.#language.toUpperCase ( ) + |
231 | '.json file. English will be used. '; |
232 | return true; |
233 | } |
234 | this.#errorMessage += 'Not possible to load the translations for the note dialog. '; |
235 | return false; |
236 | } |
237 | |
238 | |
239 | |
240 | |
241 | |
242 | |
243 | |
244 | async #loadOsmSearchDictionary ( searchDictPromiseResult, defaultSearchDictPromiseResult ) { |
245 | if ( |
246 | 'fulfilled' === searchDictPromiseResult.status |
247 | && |
248 | HTTP_STATUS_OK === searchDictPromiseResult.value.status |
249 | && |
250 | searchDictPromiseResult.value.ok |
251 | ) { |
252 | theOsmSearchDictionary.parseDictionary ( await searchDictPromiseResult.value.text ( ) ); |
253 | return true; |
254 | } |
255 | if ( |
256 | 'fulfilled' === defaultSearchDictPromiseResult.status |
257 | && |
258 | HTTP_STATUS_OK === defaultSearchDictPromiseResult.value.status |
259 | && |
260 | defaultSearchDictPromiseResult.value.ok |
261 | ) { |
262 | theOsmSearchDictionary.parseDictionary ( await defaultSearchDictPromiseResult.value.text ( ) ); |
263 | this.#errorMessage += |
264 | 'Not possible to load the TravelNotesSearchDictionary' + |
265 | this.#language.toUpperCase ( ) + |
266 | '.csv file. English will be used. '; |
267 | return true; |
268 | } |
269 | this.#errorMessage += 'Not possible to load the search dictionary. OSM search will not be available.'; |
270 | return true; |
271 | } |
272 | |
273 | |
274 | |
275 | |
276 | |
277 | |
278 | async #loadMapLayers ( layersPromiseResult ) { |
279 | if ( |
280 | 'fulfilled' === layersPromiseResult.status |
281 | && |
282 | HTTP_STATUS_OK === layersPromiseResult.value.status |
283 | && |
284 | layersPromiseResult.value.ok |
285 | ) { |
286 | theMapLayersCollection.addMapLayers ( await layersPromiseResult.value.json ( ) ); |
287 | return true; |
288 | } |
289 | this.#errorMessage += |
290 | 'Not possible to load the TravelNotesLayers.json file. Only the OpenStreetMap background will be available. '; |
291 | return true; |
292 | } |
293 | |
294 | |
295 | |
296 | |
297 | |
298 | async #loadJsonFiles ( ) { |
299 | |
300 | |
301 | const results = await Promise.allSettled ( [ |
302 | fetch ( this.#originAndPath + this.#language.toUpperCase ( ) + '.json' ), |
303 | fetch ( this.#originAndPath + 'EN.json' ), |
304 | fetch ( this.#originAndPath + 'NoteDialog' + this.#language.toUpperCase ( ) + '.json' ), |
305 | fetch ( this.#originAndPath + 'NoteDialogEN.json' ), |
306 | fetch ( this.#originAndPath + 'SearchDictionary' + this.#language.toUpperCase ( ) + '.csv' ), |
307 | fetch ( this.#originAndPath + 'SearchDictionaryEN.csv' ), |
308 | fetch ( this.#originAndPath + 'Layers.json' ) |
309 | ] ); |
310 | |
311 | |
312 | const jsonSuccess = |
313 | await this.#loadTranslations ( results [ 0 ], results [ 1 ] ) |
314 | && |
315 | await this.#loadNoteDialogConfig ( results [ 2 ], results [ 3 ] ) |
316 | && |
317 | await this.#loadOsmSearchDictionary ( results [ 4 ], results [ 5 ] ) |
318 | && |
319 | await this.#loadMapLayers ( results [ 6 ] ); |
320 | |
321 | |
322 | |
323 | if ( '' !== this.#errorMessage && jsonSuccess ) { |
324 | theErrorsUI.showError ( this.#errorMessage ); |
325 | } |
326 | else if ( '' !== this.#errorMessage ) { |
327 | document.body.textContent = this.#errorMessage; |
328 | } |
329 | |
330 | return jsonSuccess; |
331 | } |
332 | |
333 | |
334 | |
335 | |
336 | |
337 | #loadTravelNotes ( ) { |
338 | EventListenersLoader.addEventsListeners ( ); |
339 | |
340 | document.body.style [ 'font-size' ] = String ( theConfig.fontSize.initialValue ) + 'mm'; |
341 | |
342 | |
343 | const mapDiv = document.createElement ( 'div' ); |
344 | mapDiv.id = 'TravelNotes-Map'; |
345 | document.body.appendChild ( mapDiv ); |
346 | theTravelNotesData.map = new LeafletMap ( mapDiv.id, { attributionControl : false, zoomControl : false } ) |
347 | .setView ( [ LAT_LNG.defaultValue, LAT_LNG.defaultValue ], ZERO ); |
348 | |
349 | if ( this.#travelUrl ) { |
350 | theTravelNotes.addReadOnlyTravel ( this.#travelUrl ); |
351 | } |
352 | else { |
353 | EventListenersLoader.addUnloadEventsListeners ( ); |
354 | theTravelNotes.addToolbarsMenusUIs ( ); |
355 | } |
356 | } |
357 | |
358 | |
359 | |
360 | |
361 | |
362 | constructor ( ) { |
363 | Object.freeze ( this ); |
364 | this.#originAndPath = |
365 | window.location.href.substring ( ZERO, window.location.href.lastIndexOf ( '/' ) + ONE ) + 'TravelNotes'; |
366 | this.#errorMessage = ''; |
367 | } |
368 | |
369 | |
370 | |
371 | |
372 | |
373 | async loadApp ( ) { |
374 | |
375 | |
376 | window.TaN = theTravelNotes; |
377 | |
378 | |
379 | this.#readURL ( ); |
380 | |
381 | |
382 | if ( ! await this.#loadConfig ( ) ) { |
383 | document.body.textContent = 'Not possible to load the TravelNotesConfig.json file. '; |
384 | return; |
385 | } |
386 | |
387 | |
388 | this.#language = this.#language || theConfig.travelNotes.language || 'fr'; |
389 | |
390 | |
391 | theErrorsUI.createUI ( ); |
392 | |
393 | |
394 | if ( await this.#loadJsonFiles ( ) ) { |
395 | this.#loadTravelNotes ( ); |
396 | } |
397 | |
398 | } |
399 | } |
400 | |
401 | export default AppLoader; |
402 | |
403 | |
404 | |