File : printRoute/PrintPageBuilder.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 theHTMLElementsFactory from '../core/uiLib/HTMLElementsFactory.js';
26
import theTravelNotesData from '../data/TravelNotesData.js';
27
import theConfig from '../data/Config.js';
28
import theTranslator from '../core/uiLib/Translator.js';
29
import theMapLayersCollection from '../data/MapLayersCollection.js';
30
import theApiKeysManager from '../core/ApiKeysManager.js';
31
import theHTMLSanitizer from '../core/htmlSanitizer/HTMLSanitizer.js';
32
import PrintEL from './PrintEL.js';
33
import AfterPrintEL from './AfterPrintEL.js';
34
import { ZERO, TWO } from '../main/Constants.js';
35
36
import {
37
    LeafletCircleMarker,
38
    LeafletDivIcon,
39
    LeafletMap,
40
    LeafletMarker,
41
    LeafletPolyline,
42
    LeafletTileLayer
43
} from '../leaflet/LeafletImports.js';
44
45
/* ------------------------------------------------------------------------------------------------------------------------- */
46
/**
47
Build the html page for print
48
*/
49
/* ------------------------------------------------------------------------------------------------------------------------- */
50
51
class PrintPageBuilder {
52
53
    /**
54
    A reference to the PrintRouteMapOptions object containing the user choices
55
    @type {PrintRouteMapOptions}
56
    */
57
58
    #printRouteMapOptions;
59
60
    /**
61
    A reference to the printed route
62
    @type {Route}
63
    */
64
65
    #route;
66
67
    /**
68
    A reference to the views to print
69
    @type {Array.<PrintView>}
70
    */
71
72
    #printViews;
73
74
    /**
75
    The toolbar on right top of the screen
76
    @type {HTMLElement}
77
    */
78
79
    #printToolbar;
80
81
    /**
82
    The print button on the toolbar
83
    @type {HTMLElement}
84
    */
85
86
    #printButton;
87
88
    /**
89
    The cancel button on the toolbar
90
    @type {HTMLElement}
91
    */
92
93
    #cancelButton;
94
95
    /**
96
    A counter for the views, so we can gives a unique id to the views
97
    @type {Number}
98
    */
99
100
    #viewsCounter;
101
102
    /**
103
    An array with the HTML views
104
    @type {Array.<HTMLElement>}
105
    */
106
107
    #viewsDiv;
108
109
    /**
110
    A leaflet.polyline used to represent the route on the maps
111
    @type {LeafletObject}
112
    */
113
114
    #routePolyline;
115
116
    /**
117
    Event listener for the print button
118
    @type {PrintEL}
119
    */
120
121
    #printEL;
122
123
    /**
124
    Event listener for the cancel button and the document. Reset the document in the correct state
125
    @type {AfterPrintEL}
126
    */
127
128
    #afterPrintEL;
129
130
    /**
131
    Remove the print views and restore the map and user interface after printing
132
    */
133
134
    onAfterPrint ( ) {
135
136
        // removing the views
137
        this.#viewsDiv.forEach ( viewDiv => document.body.removeChild ( viewDiv ) );
138
        this.#viewsDiv.length = ZERO;
139
140
        // removing the toolbar
141
        this.#printButton.removeEventListener (    'click', this.#printEL, false );
142
        this.#cancelButton.removeEventListener ( 'click', this.#afterPrintEL, false );
143
        document.body.removeChild ( this.#printToolbar );
144
145
        // shwing the hidden HTMLElements
146
        const childrens = document.body.children;
147
        for ( let counter = 0; counter < childrens.length; counter ++ ) {
148
            childrens.item ( counter ).classList.remove ( 'TravelNotes-Hidden' );
149
        }
150
151
        // reset the map
152
        theTravelNotesData.map.invalidateSize ( false );
153
154
        // reset the document title
155
        document.title =
156
            'Travel & Notes' +
157
            ( '' === theTravelNotesData.travel.name ? '' : ' - ' + theTravelNotesData.travel.name );
158
159
        // removing event listeners
160
        window.removeEventListener ( 'afterprint', this.#afterPrintEL, true );
161
        this.#afterPrintEL = null;
162
        this.#printEL = null;
163
    }
164
165
    /**
166
    Creates a leaflet layer with the same map that the main map
167
    */
168
169
    #getMapLayer ( ) {
170
        const mapLayer = theMapLayersCollection.getMapLayer ( theTravelNotesData.travel.layerName );
171
        const url = theApiKeysManager.getUrl ( mapLayer );
172
        const leafletLayer =
173
            'wmts' === mapLayer.service.toLowerCase ( )
174
                ?
175
                new LeafletTileLayer ( url )
176
                :
177
                new ( LeafletTileLayer.WMS ) ( url, mapLayer.wmsOptions );
178
179
        leafletLayer.options.attribution = theHTMLSanitizer.sanitizeToHtmlString (
180
            ' © <a href="https://www.openstreetmap.org/copyright" target="_blank" ' +
181
            'title="OpenStreetMap contributors">OpenStreetMap contributors</a> ' +
182
            mapLayer.attribution +
183
            '| © <a href="https://github.com/wwwouaiebe" target="_blank" ' +
184
            'title="https://github.com/wwwouaiebe">Travel & Notes</a> '
185
        ).htmlString;
186
187
        return leafletLayer;
188
    }
189
190
    /**
191
    Creates markers for notes
192
    */
193
194
    #getNotesMarkers ( ) {
195
        const notesMarkers = [];
196
        this.#route.notes.forEach (
197
            note => {
198
                const icon = new LeafletDivIcon (
199
                    {
200
                        iconSize : [ note.iconWidth, note.iconHeight ],
201
                        iconAnchor : [ note.iconWidth / TWO, note.iconHeight / TWO ],
202
                        popupAnchor : [ ZERO, -note.iconHeight / TWO ],
203
                        html : note.iconContent,
204
                        className : 'TravelNotes-Map-AllNotes '
205
                    }
206
                );
207
208
                const marker = new LeafletMarker (
209
                    note.iconLatLng,
210
                    {
211
                        // eslint-disable-next-line no-magic-numbers
212
                        zIndexOffset : 100,
213
                        icon : icon,
214
                        draggable : true
215
                    }
216
                );
217
                notesMarkers.push ( marker );
218
            }
219
        );
220
        return notesMarkers;
221
    }
222
223
    /**
224
    Creates a print view
225
    @param {PrintView} printView The view to create
226
    */
227
228
    #createViewOnPage ( printView ) {
229
230
        this.#viewsCounter ++;
231
        const viewId = 'TravelNotes-RouteViewDiv' + this.#viewsCounter;
232
233
        // viewDiv is used by leaflet. We cannot seal viewDiv with theHTMLElementsFactory
234
        const viewDiv = document.createElement ( 'div' );
235
        viewDiv.className = 'TravelNotes-routeViewDiv';
236
        viewDiv.id = viewId;
237
        document.body.appendChild ( viewDiv );
238
        this.#viewsDiv.push ( viewDiv );
239
240
        // setting the size given by the user in mm
241
        viewDiv.style.width = String ( this.#printRouteMapOptions.paperWidth ) + 'mm';
242
        viewDiv.style.height = String ( this.#printRouteMapOptions.paperHeight ) + 'mm';
243
244
        // creating markers for notes
245
        const layers = this.#printRouteMapOptions.printNotes ? this.#getNotesMarkers ( ) : [];
246
247
        // adding the leaflet map layer
248
        layers.push ( this.#getMapLayer ( ) );
249
250
        // adding entry point and exit point markers
251
        layers.push (
252
            new LeafletCircleMarker (
253
                [ printView.entryPoint.lat, printView.entryPoint.lng ],
254
                theConfig.printRouteMap.entryPointMarker
255
            )
256
        );
257
        layers.push (
258
            new LeafletCircleMarker (
259
                [ printView.exitPoint.lat, printView.exitPoint.lng ],
260
                theConfig.printRouteMap.exitPointMarker
261
            )
262
        );
263
264
        // adding the route
265
        layers.push ( this.#routePolyline );
266
267
        // creating the map
268
        new LeafletMap (
269
            viewId,
270
            {
271
                attributionControl : true,
272
                zoomControl : false,
273
                center : [
274
                    ( printView.bottomLeft.lat + printView.upperRight.lat ) / TWO,
275
                    ( printView.bottomLeft.lng + printView.upperRight.lng ) / TWO
276
                ],
277
                zoom : this.#printRouteMapOptions.zoomFactor,
278
                minZoom : this.#printRouteMapOptions.zoomFactor,
279
                maxZoom : this.#printRouteMapOptions.zoomFactor,
280
                layers : layers
281
            }
282
        );
283
    }
284
285
    /**
286
    creates the toolbar with the print and cancel button
287
    */
288
289
    #createToolbar ( ) {
290
291
        // toolbar
292
        this.#printToolbar = theHTMLElementsFactory.create (
293
            'div',
294
            {
295
                id : 'TravelNotes-PrintToolbar'
296
            },
297
            document.body
298
        );
299
300
        // print button
301
        this.#printButton = theHTMLElementsFactory.create (
302
            'div',
303
            {
304
                className : 'TravelNotes-UI-Button',
305
                title : theTranslator.getText ( 'PrintPageBuilder - Print' ),
306
                textContent : '🖨️'
307
            },
308
            this.#printToolbar
309
        );
310
        this.#printButton.addEventListener ( 'click', this.#printEL, false );
311
312
        // cancel button
313
        this.#cancelButton = theHTMLElementsFactory.create (
314
            'div',
315
            {
316
                className : 'TravelNotes-UI-Button',
317
                title : theTranslator.getText ( 'PrintPageBuilder - Cancel print' ),
318
                textContent : '❌'
319
            },
320
            this.#printToolbar
321
        );
322
        this.#cancelButton.addEventListener (    'click', this.#afterPrintEL, false );
323
    }
324
325
    /**
326
    The constructor
327
    @param {Route} route A reference to the printed route
328
    @param {Array.PrintView>} printViews A reference to the views to print
329
    @param {PrintRouteMapOptions} printRouteMapOptions A reference to the PrintRouteMapOptions
330
    object containing the user choices
331
    */
332
333
    constructor ( route, printViews, printRouteMapOptions ) {
334
335
        Object.freeze ( this );
336
337
        // Saving parameters
338
        this.#route = route;
339
        this.#printViews = printViews;
340
        this.#printRouteMapOptions = printRouteMapOptions;
341
342
        this.#viewsCounter = ZERO;
343
        this.#viewsDiv = [];
344
345
        // Event listeners creation
346
        this.#printEL = new PrintEL ( );
347
        this.#afterPrintEL = new AfterPrintEL ( this );
348
    }
349
350
    /**
351
    Hide existing HTMLElements, add the toolbar, prepare the polyline and add the views to the html page
352
    */
353
354
    preparePage ( ) {
355
        if ( this.#printRouteMapOptions.firefoxBrowser ) {
356
            document.body.classList.add ( 'TravelNotes-Maps-FirefoxBrowser' );
357
        }
358
        else {
359
            document.body.classList.remove ( 'TravelNotes-Maps-FirefoxBrowser' );
360
        }
361
362
        // adding classes to the body, so all existing elements are hidden
363
364
        const childrens = document.body.children;
365
        for ( let counter = 0; counter < childrens.length; counter ++ ) {
366
            childrens.item ( counter ).classList.add ( 'TravelNotes-Hidden' );
367
        }
368
369
        // modify the document title with the travel name and route name
370
        document.title =
371
            '' === theTravelNotesData.travel.name
372
                ?
373
                'maps'
374
                :
375
                theTravelNotesData.travel.name + ' - ' + this.#route.computedName + ' - maps';
376
        this.#createToolbar ( );
377
378
        // Adding afterprint event listener to the document
379
        window.addEventListener ( 'afterprint', this.#afterPrintEL, true );
380
381
        // creating the polyline for the route
382
        // why we can create the polyline only once and we have to create markers and layers for each view?
383
        const latLngs = [];
384
        const pointsIterator = this.#route.itinerary.itineraryPoints.iterator;
385
        while ( ! pointsIterator.done ) {
386
            latLngs.push ( pointsIterator.value.latLng );
387
        }
388
389
        this.#routePolyline = new LeafletPolyline (
390
            latLngs,
391
            {
392
                color : this.#route.color,
393
                weight : this.#route.width,
394
                dashArray : this.#route.dashString
395
            }
396
        );
397
398
        // adding views
399
        this.#viewsCounter = ZERO;
400
        this.#printViews.forEach ( printView => this.#createViewOnPage ( printView ) );
401
    }
402
}
403
404
export default PrintPageBuilder;
405
406
/* --- End of file --------------------------------------------------------------------------------------------------------- */
407