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