• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 15829819958

23 Jun 2025 04:29PM UTC coverage: 76.979% (+0.03%) from 76.95%
15829819958

Pull #11183

github

web-flow
Merge 124f321fe into 7cde38ac9
Pull Request #11183: #11165: Option to deny app context for normal users

31124 of 48441 branches covered (64.25%)

14 of 16 new or added lines in 5 files covered. (87.5%)

1401 existing lines in 128 files now uncovered.

38752 of 50341 relevant lines covered (76.98%)

36.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.4
/web/client/plugins/Print.jsx
1
/*
2
 * Copyright 2016, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
 */
8

9
import './print/print.css';
10

11
import head from 'lodash/head';
12
import castArray from "lodash/castArray";
13
import isNil from "lodash/isNil";
14
import PropTypes from 'prop-types';
15
import React from 'react';
16
import { PanelGroup, Col, Glyphicon, Grid, Panel, Row } from 'react-bootstrap';
17
import { connect } from '../utils/PluginsUtils';
18
import { createSelector } from 'reselect';
19

20
import { setControlProperty, toggleControl } from '../actions/controls';
21
import { configurePrintMap, printError, printSubmit, printSubmitting, addPrintParameter } from '../actions/print';
22
import Message from '../components/I18N/Message';
23
import Dialog from '../components/misc/Dialog';
24
import printReducers from '../reducers/print';
25
import printEpics from '../epics/print';
26
import { printSpecificationSelector } from "../selectors/print";
27
import { layersSelector, rawGroupsSelector } from '../selectors/layers';
28
import { currentLocaleSelector } from '../selectors/locale';
29
import { mapSelector, scalesSelector } from '../selectors/map';
30
import { mapTypeSelector } from '../selectors/maptype';
31
import { normalizeSRS, convertDegreesToRadian } from '../utils/CoordinatesUtils';
32
import { getMessageById } from '../utils/LocaleUtils';
33
import { defaultGetZoomForExtent, getResolutions, mapUpdated, dpi2dpu, DEFAULT_SCREEN_DPI, getScales, reprojectZoom } from '../utils/MapUtils';
34
import { getDerivedLayersVisibility, isInsideResolutionsLimits } from '../utils/LayersUtils';
35
import { has, includes } from 'lodash';
36
import {additionalLayersSelector} from "../selectors/additionallayers";
37
import { MapLibraries } from '../utils/MapTypeUtils';
38
import FlexBox from '../components/layout/FlexBox';
39
import Text from '../components/layout/Text';
40
import Button from '../components/layout/Button';
41

42
/**
43
 * Print plugin. This plugin allows to print current map view. **note**: this plugin requires the  **printing module** to work.
44
 * Please look at mapstore documentation about how to add and configure the printing module in your installation.
45
 *
46
 * It also works as a container for other plugins, usable to customize the UI of the parameters dialog.
47
 *
48
 * The UI supports different targets for adding new plugins:
49
 *  - `left-panel` (controls/widgets to be added to the left column, before the accordion)
50
 *  - `left-panel-accordion` (controls/widgets to be added to the left column, as subpanels of the accordion)
51
 *  - `right-panel` (controls/widgets to be added to the right column, before the buttons bar)
52
 *  - `buttons` (controls/widgets to be added to the right column, in the buttons bar)
53
 *  - `preview-panel` (controls/widgets to be added to the printed pdf preview panel)
54
 *
55
 * In addition it is also possibile to use specific targets that override a standard widget, to replace it
56
 * with a custom one. They are (in order, from left to right and top to bottom in the UI):
57
 *  - `name` (`left-panel`, `position`: `1`)
58
 *  - `description` (`left-panel`, `position`: `2`)
59
 *  - `outputFormat` (`left-panel`, `position`: `3`)
60
 *  - `projection` (`left-panel`, `position`: `4`)
61
 *  - `layout` (`left-panel-accordion`, `position`: `1`)
62
 *  - `legend-options` (`left-panel-accordion`, `position`: `2`)
63
 *  - `resolution` (`right-panel`, `position`: `1`)
64
 *  - `map-preview` (`right-panel`, `position`: `2`)
65
 *  - `default-background-ignore` (`right-panel`, `position`: `3`)
66
 *  - `submit` (`buttons`, `position`: `1`)
67
 *  - `print-preview` (`preview-panel`, `position`: `1`)
68
 *
69
 * To remove a widget, you have to include a Null plugin with the desired target.
70
 * You can use the position to sort existing and custom items.
71
 *
72
 * Standard widgets can be configured by providing an options object as a configuration property
73
 * of this (Print) plugin. The options object of a widget is named `<widget_id>Options`
74
 * (e.g. `outputFormatOptions`).
75
 *
76
 * You can customize Print plugin by creating one custom plugin (or more) that modifies the existing
77
 * components with your own ones. You can configure this plugin in `localConfig.json` as usual.
78
 *
79
 * It delegates to a printingService the creation of the final print. The default printingService
80
 * implements a mapfish-print v2 compatible workflow. It is possible to override the printingService to
81
 * use, via a specific property (printingService).
82
 *
83
 * It is also possible to customize the payload of the spec sent to the mapfish-print engine, by
84
 * adding new transformers to the default chain.
85
 *
86
 * Each transformer is a function that can add / replace / remove fragments from the JSON payload.
87
 *
88
 * @class Print
89
 * @memberof plugins
90
 * @static
91
 *
92
 * @prop {boolean} cfg.useFixedScales if true the printing scale is constrained to the nearest scale of the ones configured
93
 * in the config.yml file, if false the current scale is used
94
 * @prop {object} cfg.overrideOptions overrides print options, this will override options created from current state of map
95
 * @prop {boolean} cfg.overrideOptions.geodetic prints in geodetic mode: in geodetic mode scale calculation is more precise on
96
 * printed maps, but the preview is not accurate
97
 * @prop {string} cfg.overrideOptions.outputFilename name of output file
98
 * @prop {object} cfg.mapPreviewOptions options for the map preview tool
99
 * @prop {string[]} cfg.ignoreLayers list of layer types to ignore in preview and when printing, default ["google", "bing"]
100
 * @prop {boolean} cfg.mapPreviewOptions.enableScalebox if true a combobox to select the printing scale is shown over the preview
101
 * this is particularly useful if useFixedScales is also true, to show the real printing scales
102
 * @prop {boolean} cfg.mapPreviewOptions.enableRefresh true by default, if false the preview is not updated if the user pans or zooms the main map
103
 * @prop {object} cfg.outputFormatOptions options for the output formats
104
 * @prop {object[]} cfg.outputFormatOptions.allowedFormats array of allowed formats, e.g. [{"name": "PDF", "value": "pdf"}]
105
 * @prop {object} cfg.projectionOptions options for the projections
106
 * @prop {string[]} cfg.excludeLayersFromLegend list of layer names e.g. ["workspace:layerName"] to exclude from printed document
107
 * @prop {object} cfg.mergeableParams object to pass to mapfish-print v2 to merge params, example here https://github.com/mapfish/mapfish-print-v2/blob/main/docs/protocol.rst#printpdf
108
 * @prop {object[]} cfg.projectionOptions.projections array of available projections, e.g. [{"name": "EPSG:3857", "value": "EPSG:3857"}]
109
 * @prop {object} cfg.overlayLayersOptions options for overlay layers
110
 * @prop {boolean} cfg.overlayLayersOptions.enabled if true a checkbox will be shown to exclude or include overlay layers to the print
111
 *
112
 * @example
113
 * // printing in geodetic mode
114
 * {
115
 *   "name": "Print",
116
 *   "cfg": {
117
 *       "overrideOptions": {
118
 *          "geodetic": true
119
 *       }
120
 *    }
121
 * }
122
 *
123
 * @example
124
 * // Using a list of fixed scales with scale selector
125
 * {
126
 *   "name": "Print",
127
 *   "cfg": {
128
 *       "ignoreLayers": ["google", "bing"],
129
 *       "useFixedScales": true,
130
 *       "mapPreviewOptions": {
131
 *          "enableScalebox": true
132
 *       }
133
 *    }
134
 * }
135
 *
136
 * @example
137
 * // restrict allowed output formats
138
 * {
139
 *   "name": "Print",
140
 *   "cfg": {
141
 *       "outputFormatOptions": {
142
 *          "allowedFormats": [{"name": "PDF", "value": "pdf"}, {"name": "PNG", "value": "png"}]
143
 *       }
144
 *    }
145
 * }
146
 *
147
 * @example
148
 * // enable custom projections for printing
149
 * "projectionDefs": [{
150
 *    "code": "EPSG:23032",
151
 *    "def": "+proj=utm +zone=32 +ellps=intl +towgs84=-87,-98,-121,0,0,0,0 +units=m +no_defs",
152
 *    "extent": [-1206118.71, 4021309.92, 1295389.0, 8051813.28],
153
 *    "worldExtent": [-9.56, 34.88, 31.59, 71.21]
154
 * }]
155
 * ...
156
 * {
157
 *   "name": "Print",
158
 *   "cfg": {
159
 *       "projectionOptions": {
160
 *          "projections": [{"name": "UTM32N", "value": "EPSG:23032"}, {"name": "EPSG:3857", "value": "EPSG:3857"}, {"name": "EPSG:4326", "value": "EPSG:4326"}]
161
 *       }
162
 *    }
163
 * }
164
 *
165
 * @example
166
 * // customize the printing UI via plugin(s)
167
 * import React from "react";
168
 * import {createPlugin} from "../../utils/PluginsUtils";
169
 * import { connect } from "react-redux";
170
 *
171
 * const MyCustomPanel = () => <div>Hello, I am a custom component</div>;
172
 *
173
 * const MyCustomLayout = ({sheet}) => <div>Hello, I am a custom layout, the sheet is {sheet}</div>;
174
 * const MyConnectedCustomLayout = connect((state) => ({sheet: state.print?.spec.sheet}))(MyCustomLayout);
175
 *
176
 * export default createPlugin('PrintCustomizations', {
177
 *     component: () => null,
178
 *     containers: {
179
 *         Print: [
180
 *             // this entry add a panel between title and description
181
 *             {
182
 *                 target: "left-panel",
183
 *                 position: 1.5,
184
 *                 component: MyCustomPanel
185
 *             },
186
 *             // this entry replaces the layout panel
187
 *             {
188
 *                 target: "layout",
189
 *                 component: MyConnectedCustomLayout,
190
 *                 title: "MyLayout"
191
 *             },
192
 *             // To remove one component, simply create a component that returns null.
193
 *             {
194
 *                 target: "map-preview",
195
 *                 component: () => null
196
 *             }
197
 *         ]
198
 *     }
199
 * });
200
 * @example
201
 * // adds a transformer to the printingService chain
202
 * import {addTransformer} from "@js/utils/PrintUtils";
203
 *
204
 * addTransformer("mytranform", (state, spec) => Promise.resolve({
205
 *      ...spec,
206
 *      custom: "some value"
207
 * }));
208
 */
209

210
function overrideItem(item, overrides = []) {
×
211
    const replacement = overrides.find(i => i.target === item.id);
698✔
212
    return replacement ?? item;
698✔
213
}
214

215
const EmptyComponent = () => {
1✔
UNCOV
216
    return null;
×
217
};
218

219
function handleRemoved(item) {
220
    return item.plugin ? item : {
698!
221
        ...item,
222
        plugin: EmptyComponent
223
    };
224
}
225

226
function mergeItems(standard, overrides) {
227
    return standard
234✔
228
        .map(item => overrideItem(item, overrides))
698✔
229
        .map(handleRemoved);
230
}
231

232
function filterLayer(layer = {}) {
×
233
    // Skip layer with error and type cog
234
    return !layer.loadingError && layer.type !== "cog";
33✔
235
}
236

237
export default {
238
    PrintPlugin: Object.assign({
239
        loadPlugin: (resolve) => {
240
            Promise.all([
17✔
241
                import('./print/index'),
242
                import('../utils/PrintUtils')
243
            ]).then(([printMod, utilsMod]) => {
244

245
                const {
246
                    standardItems
247
                } = printMod.default;
17✔
248

249
                const {
250
                    getDefaultPrintingService,
251
                    getLayoutName,
252
                    getPrintScales,
253
                    getNearestZoom
254
                } = utilsMod;
17✔
255
                class Print extends React.Component {
256
                    static propTypes = {
17✔
257
                        map: PropTypes.object,
258
                        layers: PropTypes.array,
259
                        capabilities: PropTypes.object,
260
                        printSpec: PropTypes.object,
261
                        printSpecTemplate: PropTypes.object,
262
                        withContainer: PropTypes.bool,
263
                        withPanelAsContainer: PropTypes.bool,
264
                        open: PropTypes.bool,
265
                        pdfUrl: PropTypes.string,
266
                        title: PropTypes.string,
267
                        style: PropTypes.object,
268
                        mapWidth: PropTypes.number,
269
                        mapType: PropTypes.string,
270
                        alternatives: PropTypes.array,
271
                        toggleControl: PropTypes.func,
272
                        onBeforePrint: PropTypes.func,
273
                        setPage: PropTypes.func,
274
                        onPrint: PropTypes.func,
275
                        printError: PropTypes.func,
276
                        configurePrintMap: PropTypes.func,
277
                        getLayoutName: PropTypes.func,
278
                        error: PropTypes.string,
279
                        getZoomForExtent: PropTypes.func,
280
                        minZoom: PropTypes.number,
281
                        maxZoom: PropTypes.number,
282
                        usePreview: PropTypes.bool,
283
                        mapPreviewOptions: PropTypes.object,
284
                        syncMapPreview: PropTypes.bool,
285
                        useFixedScales: PropTypes.bool,
286
                        scales: PropTypes.array,
287
                        ignoreLayers: PropTypes.array,
288
                        defaultBackground: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
289
                        closeGlyph: PropTypes.string,
290
                        submitConfig: PropTypes.object,
291
                        previewOptions: PropTypes.object,
292
                        currentLocale: PropTypes.string,
293
                        overrideOptions: PropTypes.object,
294
                        items: PropTypes.array,
295
                        excludeLayersFromLegend: PropTypes.array,
296
                        mergeableParams: PropTypes.object,
297
                        addPrintParameter: PropTypes.func,
298
                        printingService: PropTypes.object,
299
                        printMap: PropTypes.object
300
                    };
301

302
                    static contextTypes = {
17✔
303
                        messages: PropTypes.object,
304
                        plugins: PropTypes.object,
305
                        loadedPlugins: PropTypes.object
306
                    };
307

308
                    static defaultProps = {
17✔
309
                        withContainer: true,
310
                        withPanelAsContainer: false,
311
                        title: 'print.paneltitle',
312
                        toggleControl: () => {},
313
                        onBeforePrint: () => {},
314
                        setPage: () => {},
315
                        onPrint: () => {},
316
                        configurePrintMap: () => {},
317
                        printSpecTemplate: {},
318
                        excludeLayersFromLegend: [],
319
                        getLayoutName: getLayoutName,
320
                        getZoomForExtent: defaultGetZoomForExtent,
321
                        pdfUrl: null,
322
                        mapWidth: 370,
323
                        mapType: MapLibraries.OPENLAYERS,
324
                        minZoom: 1,
325
                        maxZoom: 23,
326
                        usePreview: true,
327
                        mapPreviewOptions: {
328
                            enableScalebox: false,
329
                            enableRefresh: true
330
                        },
331
                        syncMapPreview: false,      // make it false to prevent map sync
332
                        useFixedScales: false,
333
                        scales: [],
334
                        ignoreLayers: ["google", "bing"],
335
                        defaultBackground: ["osm", "wms", "empty"],
336
                        closeGlyph: "1-close",
337
                        submitConfig: {
338
                            buttonConfig: {
339
                                bsSize: "small",
340
                                bsStyle: "primary"
341
                            },
342
                            glyph: ""
343
                        },
344
                        previewOptions: {
345
                            buttonStyle: "primary"
346
                        },
347
                        style: {},
348
                        currentLocale: 'en-US',
349
                        overrideOptions: {},
350
                        items: [],
351
                        printingService: getDefaultPrintingService(),
352
                        printMap: {}
353
                    };
354
                    constructor(props) {
355
                        super(props);
17✔
356
                        // Calling configurePrintMap here to replace calling in in UNSAFE_componentWillMount
357
                        this.configurePrintMap();
17✔
358
                        this.state = {
17✔
359
                            activeAccordionPanel: 0
360
                        };
361
                    }
362

363
                    UNSAFE_componentWillReceiveProps(nextProps) {
364
                        const hasBeenOpened = nextProps.open && !this.props.open;
43✔
365
                        const mapHasChanged = this.props.open && this.props.syncMapPreview && mapUpdated(this.props.map, nextProps.map);
43!
366
                        const specHasChanged = (
367
                            nextProps.printSpec.defaultBackground !== this.props.printSpec.defaultBackground ||
43✔
368
                                nextProps.printSpec.additionalLayers !== this.props.printSpec.additionalLayers
369
                        );
370
                        if (hasBeenOpened || mapHasChanged || specHasChanged) {
43✔
371
                            this.configurePrintMap(nextProps);
1✔
372
                        }
373
                    }
374

375
                    getAlternativeBackground = (layers, defaultBackground, projection) => {
17✔
376
                        const allowedBackground = head(castArray(defaultBackground).map(type => ({
10✔
377
                            type
378
                        })).filter(l => this.isAllowed(l, projection)));
10✔
379
                        if (allowedBackground) {
5!
380
                            return head(layers.filter(l => l.type === allowedBackground.type));
5✔
381
                        }
UNCOV
382
                        return null;
×
383
                    };
384

385
                    getItems = (target) => {
17✔
386
                        const filtered = this.props.items.filter(i => !target || i.target === target);
234✔
387
                        const merged = mergeItems(standardItems[target], this.props.items)
234✔
388
                            .map(item => ({
698✔
389
                                ...item,
390
                                target
391
                            }));
392
                        return [...merged, ...filtered]
234✔
393
                            .sort((i1, i2) => (i1.position ?? 0) - (i2.position ?? 0));
488✔
394
                    };
395
                    getMapConfiguration = () => {
17✔
396
                        const map = this.props.printingService.getMapConfiguration();
66✔
397
                        return {
66✔
398
                            ...map,
399
                            layers: this.filterLayers(map.layers, this.props.useFixedScales ? map.scaleZoom : map.zoom, map.projection)
66✔
400
                        };
401
                    };
402
                    getMapSize = (layout) => {
17✔
403
                        const currentLayout = layout || this.getLayout();
58!
404
                        return {
58✔
405
                            width: this.props.mapWidth,
406
                            height: currentLayout && currentLayout.map.height / currentLayout.map.width * this.props.mapWidth || 270
116!
407
                        };
408
                    };
409
                    getPreviewResolution = (zoom, projection) => {
17✔
410
                        const dpu = dpi2dpu(DEFAULT_SCREEN_DPI, projection);
66✔
411
                        const roundZoom = Math.round(zoom);
66✔
412
                        const scale = this.props.useFixedScales
66✔
413
                            ? getPrintScales(this.props.capabilities)[roundZoom]
414
                            : this.props.scales[roundZoom];
415
                        return scale / dpu;
66✔
416
                    };
417
                    getLayout = (props) => {
17✔
418
                        const { getLayoutName: getLayoutNameProp, printSpec, capabilities } = props || this.props;
58✔
419
                        const layoutName = getLayoutNameProp(printSpec);
58✔
420
                        return head(capabilities.layouts.filter((l) => l.name === layoutName));
58✔
421
                    };
422

423
                    renderWarning = (layout) => {
17✔
424
                        if (!layout) {
58!
UNCOV
425
                            return <Row><Col xs={12}><div className="print-warning"><span><Message msgId="print.layoutWarning"/></span></div></Col></Row>;
×
426
                        }
427
                        return null;
58✔
428
                    };
429
                    renderItem = (item, opts) => {
17✔
430
                        const {validations, ...options } = opts;
704✔
431
                        const Comp = item.component ?? item.plugin;
704✔
432
                        const {style, ...other} = this.props;
704✔
433
                        const itemOptions = this.props[item.id + "Options"];
704✔
434
                        return <Comp role="body" {...other} {...item.cfg} {...options} {...itemOptions} validation={validations?.[item.id ?? item.name]}/>;
704✔
435
                    };
436
                    renderItems = (target, options) => {
17✔
437
                        return this.getItems(target)
176✔
438
                            .map(item => this.renderItem(item, options));
588✔
439
                    };
440
                    renderAccordion = (target, options) => {
17✔
441
                        const items = this.getItems(target);
58✔
442
                        return (<PanelGroup accordion activeKey={this.state.activeAccordionPanel} onSelect={(key) => {
58✔
UNCOV
443
                            this.setState({
×
444
                                activeAccordionPanel: key
445
                            });
446
                        }}>
447
                            {items.map((item, pos) => (
448
                                <Panel header={getMessageById(this.context.messages, item.cfg?.title ?? item.title ?? "")} eventKey={pos} collapsible>
116!
449
                                    {this.renderItem(item, options)}
450
                                </Panel>
451
                            ))}
452
                        </PanelGroup>);
453
                    };
454
                    renderPrintPanel = () => {
17✔
455
                        const layout = this.getLayout();
58✔
456
                        const map = this.getMapConfiguration();
58✔
457
                        const options = {
58✔
458
                            layout,
459
                            map,
460
                            layoutName: this.props.getLayoutName(this.props.printSpec),
461
                            mapSize: this.getMapSize(layout),
462
                            resolutions: getResolutions(map?.projection),
UNCOV
463
                            onRefresh: () => this.configurePrintMap(),
×
464
                            notAllowedLayers: this.isBackgroundIgnored(this.props.layers, map?.projection),
465
                            actionConfig: this.props.submitConfig,
466
                            validations: this.props.printingService.validate(),
467
                            rotation: !isNil(this.props.printSpec.rotation) ? convertDegreesToRadian(Number(this.props.printSpec.rotation)) : 0,
58!
468
                            actions: {
469
                                print: this.print,
470
                                addParameter: this.addParameter
471
                            }
472
                        };
473
                        return (
58✔
474
                            <Grid fluid role="body">
475
                                {this.renderError()}
476
                                {this.renderWarning(layout)}
477
                                <Row>
478
                                    <Col xs={12} md={6}>
479
                                        {this.renderItems("left-panel", options)}
480
                                        {this.renderAccordion("left-panel-accordion", options)}
481
                                    </Col>
482
                                    <Col xs={12} md={6} style={{textAlign: "center"}}>
483
                                        {this.renderItems("right-panel", options)}
484
                                        {this.renderItems("buttons", options)}
485
                                        {this.renderDownload()}
486
                                    </Col>
487
                                </Row>
488
                            </Grid>
489
                        );
490
                    };
491

492
                    renderDownload = () => {
17✔
493
                        if (this.props.pdfUrl && !this.props.usePreview) {
58!
UNCOV
494
                            return <iframe src={this.props.pdfUrl} style={{visibility: "hidden", display: "none"}}/>;
×
495
                        }
496
                        return null;
58✔
497
                    };
498

499
                    renderError = () => {
17✔
500
                        if (this.props.error) {
58✔
501
                            return <Row><Col xs={12}><div className="print-error"><span>{this.props.error}</span></div></Col></Row>;
2✔
502
                        }
503
                        return null;
56✔
504
                    };
505

506
                    renderPreviewPanel = () => {
17✔
507
                        return this.renderItems("preview-panel", this.props.previewOptions);
2✔
508
                    };
509

510
                    renderBody = () => {
17✔
511
                        if (this.props.pdfUrl && this.props.usePreview) {
60✔
512
                            return this.renderPreviewPanel();
2✔
513
                        }
514
                        return this.renderPrintPanel();
58✔
515
                    };
516

517
                    render() {
518
                        if ((this.props.capabilities || this.props.error) && this.props.open) {
60!
519
                            if (this.props.withContainer) {
60!
520
                                if (this.props.withPanelAsContainer) {
60!
UNCOV
521
                                    return (<Panel className="mapstore-print-panel" header={<span><span className="print-panel-title"><Message msgId="print.paneltitle"/></span><span className="print-panel-close panel-close" onClick={this.props.toggleControl}></span></span>} style={this.props.style}>
×
522
                                        {this.renderBody()}
523
                                    </Panel>);
524
                                }
525
                                return (<Dialog start={{x: 0, y: 80}} id="mapstore-print-panel" style={{ zIndex: 1990, ...this.props.style}}>
60✔
526
                                    <FlexBox role="header" centerChildrenVertically gap="sm">
527
                                        <FlexBox.Fill component={Text} ellipsis fontSize="md" className="print-panel-title _padding-lr-sm">
528
                                            <Message msgId="print.paneltitle"/>
529
                                        </FlexBox.Fill>
530
                                        <Button onClick={this.props.toggleControl} square borderTransparent className="print-panel-close">
531
                                            {this.props.closeGlyph ? <Glyphicon glyph={this.props.closeGlyph}/> : <span>×</span>}
60!
532
                                        </Button>
533
                                    </FlexBox>
534
                                    {this.renderBody()}
535
                                </Dialog>);
536
                            }
UNCOV
537
                            return this.renderBody();
×
538
                        }
UNCOV
539
                        return null;
×
540
                    }
541
                    addParameter = (name, value) => {
17✔
542
                        this.props.addPrintParameter("params." + name, value);
3✔
543
                    };
544
                    isCompatibleWithSRS = (projection, layer) => {
17✔
545
                        return projection === "EPSG:3857" || includes([
42!
546
                            "wms",
547
                            "wfs",
548
                            "vector",
549
                            "graticule",
550
                            "empty",
551
                            "arcgis"
552
                        ], layer.type) || layer.type === "wmts" && has(layer.allowedSRS, projection);
553
                    };
554
                    isAllowed = (layer, projection) => {
17✔
555
                        return this.props.ignoreLayers.indexOf(layer.type) === -1 &&
45✔
556
                            this.isCompatibleWithSRS(normalizeSRS(projection), layer);
557
                    };
558

559
                    isBackgroundIgnored = (layers, projection) => {
17✔
560
                        const background = head((layers || this.props.layers)
124!
561
                            .filter(layer => layer.group === "background" && layer.visibility && this.isAllowed(layer, projection)));
73!
562
                        return !background;
124✔
563
                    };
564
                    filterLayers = (layers, zoom, projection) => {
17✔
565
                        const resolution = this.getPreviewResolution(zoom, projection);
66✔
566

567
                        const filtered = layers.filter((layer) =>
66✔
568
                            layer.visibility &&
40✔
569
                            isInsideResolutionsLimits(layer, resolution) &&
570
                            this.isAllowed(layer, projection)
571
                        );
572
                        if (this.isBackgroundIgnored(layers, projection) && this.props.defaultBackground && this.props.printSpec.defaultBackground) {
66✔
573
                            const defaultBackground = this.getAlternativeBackground(layers, this.props.defaultBackground);
5✔
574
                            if (defaultBackground) {
5!
UNCOV
575
                                return [{
×
576
                                    ...defaultBackground,
577
                                    visibility: true
578
                                }, ...filtered];
579
                            }
580
                            return filtered;
5✔
581
                        }
582
                        return filtered;
61✔
583
                    };
584

585
                    configurePrintMap = (props) => {
17✔
586
                        const {
587
                            map: newMap,
588
                            capabilities,
589
                            configurePrintMap: configurePrintMapProp,
590
                            useFixedScales,
591
                            currentLocale,
592
                            layers,
593
                            printMap,
594
                            printSpec
595
                        } = props || this.props;
18✔
596
                        if (newMap && newMap.bbox && capabilities) {
18!
597
                            const selectedPrintProjection = (printSpec && printSpec?.params?.projection) || (printSpec && printSpec?.projection) || (printMap && printMap.projection) || 'EPSG:3857';
18!
598
                            const printSrs = normalizeSRS(selectedPrintProjection);
18✔
599
                            const mapProjection = newMap.projection;
18✔
600
                            const mapSrs = normalizeSRS(mapProjection);
18✔
601
                            const zoom = reprojectZoom(newMap.zoom, mapSrs, printSrs);
18✔
602
                            const scales = getPrintScales(capabilities);
18✔
603
                            const printMapScales = getScales(printSrs);
18✔
604
                            const scaleZoom = getNearestZoom(zoom, scales, printMapScales);
18✔
605
                            if (useFixedScales) {
18✔
606
                                const scale = scales[scaleZoom];
4✔
607
                                configurePrintMapProp(newMap.center, zoom, scaleZoom, scale,
4✔
608
                                    layers, newMap.projection, currentLocale, useFixedScales);
609
                            } else {
610
                                const scale = printMapScales[zoom];
14✔
611
                                configurePrintMapProp(newMap.center, zoom, scaleZoom, scale,
14✔
612
                                    layers, newMap.projection, currentLocale, useFixedScales);
613
                            }
614
                        }
615
                    };
616

617
                    print = () => {
17✔
618
                        this.props.setPage(0);
8✔
619
                        this.props.onBeforePrint();
8✔
620
                        this.props.printingService.print({
8✔
621
                            excludeLayersFromLegend: this.props.excludeLayersFromLegend,
622
                            mergeableParams: this.props.mergeableParams,
623
                            layers: this.getMapConfiguration()?.layers,
624
                            scales: this.props.useFixedScales ? getPrintScales(this.props.capabilities) : undefined,
8✔
625
                            bbox: this.props.map?.bbox
626
                        })
627
                            .then((spec) =>
628
                                this.props.onPrint(this.props.capabilities.createURL, { ...spec, ...this.props.overrideOptions })
6✔
629
                            )
630
                            .catch(e => {
UNCOV
631
                                this.props.printError("Error in printing:" + e.message);
×
632
                            });
633
                    };
634
                }
635

636
                const selector = createSelector([
17✔
637
                    (state) => state.controls.print && state.controls.print.enabled || state.controls.toolbar && state.controls.toolbar.active === 'print',
60!
638
                    (state) => state.print && state.print.capabilities,
60✔
639
                    printSpecificationSelector,
640
                    (state) => state.print && state.print.pdfUrl,
60✔
641
                    (state) => state.print && state.print.error,
60✔
642
                    mapSelector,
643
                    layersSelector,
644
                    additionalLayersSelector,
645
                    scalesSelector,
646
                    (state) => state.browser && (!state.browser.ie || state.browser.ie11),
60!
647
                    currentLocaleSelector,
648
                    mapTypeSelector,
649
                    (state) => state.print.map,
60✔
650
                    rawGroupsSelector
651
                ], (open, capabilities, printSpec, pdfUrl, error, map, layers, additionalLayers, scales, usePreview, currentLocale, mapType, printMap, groups) => ({
60✔
652
                    open,
653
                    capabilities,
654
                    printSpec,
655
                    pdfUrl,
656
                    error,
657
                    map,
658
                    layers: [
659
                        ...getDerivedLayersVisibility(layers, groups).filter(filterLayer),
UNCOV
660
                        ...(printSpec?.additionalLayers ? additionalLayers.map(l => l.options).filter(
×
661
                            l => {
UNCOV
662
                                const isVector = l.type === 'vector';
×
663
                                const hasFeatures = Array.isArray(l.features) && l.features.length > 0;
×
664
                                return !l.loadingError && (!isVector || (isVector && hasFeatures));
×
665
                            }
666
                        ) : [])
667
                    ],
668
                    scales,
669
                    usePreview,
670
                    currentLocale,
671
                    mapType,
672
                    printMap
673
                }));
674

675
                const PrintPlugin = connect(selector, {
17✔
676
                    toggleControl: toggleControl.bind(null, 'print', null),
677
                    onPrint: printSubmit,
678
                    printError: printError,
679
                    onBeforePrint: printSubmitting,
680
                    setPage: setControlProperty.bind(null, 'print', 'currentPage'),
681
                    configurePrintMap,
682
                    addPrintParameter
683
                })(Print);
684
                resolve(PrintPlugin);
17✔
685
            });
686
        },
UNCOV
687
        enabler: (state) => state.print && state.print.enabled || state.toolbar && state.toolbar.active === 'print'
×
688
    },
689
    {
690
        disablePluginIf: "{state('mapType') === 'cesium' || !state('printEnabled')}",
691
        Toolbar: {
692
            name: 'print',
693
            position: 7,
694
            help: <Message msgId="helptexts.print"/>,
695
            tooltip: "printbutton",
696
            icon: <Glyphicon glyph="print"/>,
697
            exclusive: true,
698
            panel: true,
699
            priority: 1
700
        },
701
        BurgerMenu: {
702
            name: 'print',
703
            position: 2,
704
            tooltip: "printToolTip",
705
            text: <Message msgId="printbutton"/>,
706
            icon: <Glyphicon glyph="print"/>,
707
            action: toggleControl.bind(null, 'print', null),
708
            priority: 3,
709
            doNotHide: true
710
        },
711
        SidebarMenu: {
712
            name: "print",
713
            position: 3,
714
            tooltip: "printbutton",
715
            text: <Message msgId="printbutton"/>,
716
            icon: <Glyphicon glyph="print"/>,
717
            action: toggleControl.bind(null, 'print', null),
718
            doNotHide: true,
719
            toggle: true,
720
            priority: 2
721
        }
722
    }),
723
    reducers: {print: printReducers},
724
    epics: {...printEpics}
725
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc