File : main/travelNotesViewer/AppLoaderViewer.js

1
/*
2
Copyright - 2017 2023 - wwwouaiebe - Contact: https://www.ouaie.be/
3
4
This  program is free software;
5
you can redistribute it and/or modify it under the terms of the
6
GNU General Public License as published by the Free Software Foundation;
7
either version 3 of the License, or any later version.
8
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this program; if not, write to the Free Software
16
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
*/
18
/*
19
Changes:
20
    - v4.0.0:
21
        - created from v3.6.0
22
Doc reviewed 202208
23
 */
24
25
import theConfig from '../../data/Config.js';
26
import ConfigOverloader from '../../data/ConfigOverloader.js';
27
import theTravelNotesViewer from './TravelNotesViewer.js';
28
import theTravelNotesData from '../../data/TravelNotesData.js';
29
import theTranslator from '../../core/uiLib/Translator.js';
30
import theViewerLayersToolbar from '../../toolbars/viewerLayersToolbar/ViewerLayersToolbar.js';
31
import MapEditorViewer from '../../core/mapEditor/MapEditorViewer.js';
32
import ViewerKeydownEL from './ViewerKeydownEL.js';
33
import { ZERO, ONE, LAT_LNG, HTTP_STATUS_OK } from '../Constants.js';
34
35
import { LeafletMap } from '../../leaflet/LeafletImports.js';
36
37
/* ------------------------------------------------------------------------------------------------------------------------- */
38
/**
39
Loader for the app. Load all the json files needed (config, translations, map layers...) and add event listeners.
40
*/
41
/* ------------------------------------------------------------------------------------------------------------------------- */
42
43
class AppLoaderViewer {
44
45
    /**
46
    The url of the TaN file in the fil parameter of the url
47
    @type {String}
48
    */
49
50
    #travelUrl;
51
52
    /**
53
    the language in the lng parameter of the url
54
    @type {String}
55
    */
56
57
    #language;
58
59
    /**
60
    The path of the app + TravelNotes ( first part of the json file names )
61
    @type {String}
62
    */
63
64
    #originAndPath;
65
66
    /**
67
    A flag indicating when the layer toolbar must be added
68
    @type {Boolean}
69
    */
70
71
    #addLayerToolbar;
72
73
    /**
74
    The dafault zomm factor
75
    @type {Number}
76
    */
77
78
    // eslint-disable-next-line no-magic-numbers
79
    static get #VIEWER_DEFAULT_ZOOM ( ) { return 2; }
80
81
    /**
82
    The mapEditorViewer
83
    @type {MapEditorViewer}
84
    */
85
86
    static #mapEditorViewer = new MapEditorViewer ( );
87
88
    /**
89
    Loading event listeners
90
    */
91
92
    #addEventsListeners ( ) {
93
        document.addEventListener ( 'keydown', new ViewerKeydownEL ( ), true );
94
        document.addEventListener (
95
            'routeupdated',
96
            updateRouteEvent => {
97
                if ( updateRouteEvent.data ) {
98
                    AppLoaderViewer.#mapEditorViewer.addRoute (
99
                        updateRouteEvent.data.addedRouteObjId
100
                    );
101
                }
102
            },
103
            false
104
        );
105
        document.addEventListener (
106
            'noteupdated',
107
            updateNoteEvent => {
108
                if ( updateNoteEvent.data ) {
109
                    AppLoaderViewer.#mapEditorViewer.addNote (
110
                        updateNoteEvent.data.addedNoteObjId
111
                    );
112
                }
113
            },
114
            false
115
        );
116
        document.addEventListener (
117
            'zoomto',
118
            zoomToEvent => {
119
                if ( zoomToEvent.data ) {
120
                    AppLoaderViewer.#mapEditorViewer.zoomTo (
121
                        zoomToEvent.data.latLng,
122
                        zoomToEvent.data.geometry
123
                    );
124
                }
125
            },
126
            false
127
        );
128
        document.addEventListener (
129
            'layerchange',
130
            layerChangeEvent => {
131
                if ( layerChangeEvent.data ) {
132
                    AppLoaderViewer.#mapEditorViewer.setLayer ( layerChangeEvent.data.layer, layerChangeEvent.data.layer.url );
133
                }
134
            }
135
        );
136
        document.addEventListener (
137
            'geolocationpositionchanged',
138
            geoLocationPositionChangedEvent => {
139
                if ( geoLocationPositionChangedEvent.data ) {
140
                    AppLoaderViewer.#mapEditorViewer.onGeolocationPositionChanged (
141
                        geoLocationPositionChangedEvent.data.position
142
                    );
143
                }
144
            },
145
            false
146
        );
147
        document.addEventListener (
148
            'geolocationstatuschanged',
149
            geoLocationStatusChangedEvent => {
150
                if ( geoLocationStatusChangedEvent.data ) {
151
                    AppLoaderViewer.#mapEditorViewer.onGeolocationStatusChanged ( geoLocationStatusChangedEvent.data.status );
152
                }
153
            },
154
            false
155
        );
156
    }
157
158
    /**
159
    Read the url. Search a 'fil' parameter, a 'lng' parameter and a 'lay' in the url.
160
    */
161
162
    #readURL ( ) {
163
        const docURL = new URL ( window.location );
164
        let strTravelUrl = docURL.searchParams.get ( 'fil' );
165
        if ( strTravelUrl && ZERO !== strTravelUrl.length ) {
166
            try {
167
                strTravelUrl = atob ( strTravelUrl );
168
                if ( strTravelUrl.match ( /[^\w-%:./]/ ) ) {
169
                    throw new Error ( 'invalid char in the url encoded in the fil parameter' );
170
                }
171
                const travelURL = new URL ( strTravelUrl, docURL.protocol + '//' + docURL.hostname );
172
                if (
173
                    docURL.protocol && travelURL.protocol && docURL.protocol === travelURL.protocol
174
                    &&
175
                    docURL.hostname && travelURL.hostname && docURL.hostname === travelURL.hostname
176
                ) {
177
                    this.#travelUrl = encodeURI ( travelURL.href );
178
                }
179
                else {
180
                    throw new Error ( 'The distant file is not on the same site than the app' );
181
                }
182
            }
183
            catch ( err ) {
184
                if ( err instanceof Error ) {
185
                    console.error ( err );
186
                }
187
            }
188
        }
189
        const urlLng = docURL.searchParams.get ( 'lng' );
190
        if ( urlLng ) {
191
            if ( urlLng.match ( /^[A-Z,a-z]{2}$/ ) ) {
192
                this.#language = urlLng.toLowerCase ( );
193
            }
194
        }
195
        if ( '' === docURL.searchParams.get ( 'lay' ) ) {
196
            this.#addLayerToolbar = true;
197
        }
198
    }
199
200
    /**
201
    Loading the config.json file from the server
202
    */
203
204
    async #loadConfig ( ) {
205
        const configResponse = await fetch ( this.#originAndPath + 'Config.json' );
206
        if ( HTTP_STATUS_OK === configResponse.status && configResponse.ok ) {
207
            const config = await configResponse.json ( );
208
            config.travelNotes.language = this.#language || config.travelNotes.language;
209
            if ( 'wwwouaiebe.github.io' === window.location.hostname ) {
210
                config.note.haveBackground = true;
211
            }
212
            new ConfigOverloader ( ).overload ( config );
213
            return true;
214
        }
215
        return false;
216
    }
217
218
    /**
219
    Loading translations
220
    */
221
222
    async #loadTranslations ( ) {
223
        const languageResponse = await fetch ( this.#originAndPath + this.#language.toUpperCase ( ) + '.json' );
224
        if ( HTTP_STATUS_OK === languageResponse.status && languageResponse.ok ) {
225
            theTranslator.setTranslations ( await languageResponse.json ( ) );
226
            return true;
227
        }
228
        return false;
229
    }
230
231
    /**
232
    Loading map layers
233
    */
234
235
    async #loadMapLayers ( ) {
236
        const layersResponse = await fetch ( this.#originAndPath +    'Layers.json' );
237
        if ( HTTP_STATUS_OK === layersResponse.status && layersResponse.ok ) {
238
            theViewerLayersToolbar.addMapLayers ( await layersResponse.json ( ) );
239
            return true;
240
        }
241
        return false;
242
    }
243
244
    /**
245
    Loading TravelNotes
246
    */
247
248
    #loadTravelNotes ( ) {
249
250
        // mapDiv must be extensible for leaflet
251
        const mapDiv = document.createElement ( 'div' );
252
        mapDiv.id = 'TravelNotes-Map';
253
        document.body.appendChild ( mapDiv );
254
        theTravelNotesData.map = new LeafletMap ( mapDiv.id, { attributionControl : false, zoomControl : false } )
255
            .setView ( [ LAT_LNG.defaultValue, LAT_LNG.defaultValue ], AppLoaderViewer.#VIEWER_DEFAULT_ZOOM );
256
257
        theTravelNotesViewer.addReadOnlyMap ( this.#travelUrl, this.#addLayerToolbar );
258
    }
259
260
    /**
261
    The constructor
262
    */
263
264
    constructor ( ) {
265
        Object.freeze ( this );
266
        this.#originAndPath =
267
            window.location.href.substring ( ZERO, window.location.href.lastIndexOf ( '/' ) + ONE ) + 'TravelNotes';
268
        this.#addLayerToolbar = false;
269
    }
270
271
    /**
272
    Load the complete app
273
    */
274
275
    async loadApp ( ) {
276
        this.#readURL ( );
277
        if ( ! await this.#loadConfig ( ) ) {
278
            document.body.textContent = 'Not possible to load the TravelNotesConfig.json file. ';
279
            return;
280
        }
281
        this.#language = this.#language || theConfig.travelNotes.language;
282
        if ( ! await this.#loadTranslations ( ) ) {
283
            document.body.textContent =
284
                'Not possible to load the TravelNotesConfig' + this.#language.toUpperCase ( ) + '.json file. ';
285
            return;
286
        }
287
        if ( ! await this.#loadMapLayers ( ) ) {
288
            document.body.textContent = 'Not possible to load the TravelNotesLayers.json file. ';
289
            return;
290
        }
291
        this.#addEventsListeners ( );
292
        this.#loadTravelNotes ( );
293
    }
294
}
295
296
export default AppLoaderViewer;
297
298
/* --- End of file --------------------------------------------------------------------------------------------------------- */
299