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

geosolutions-it / MapStore2 / 5278726914

pending completion
5278726914

push

github

web-flow
[Backport c047-2023.01.xx] Fix #9025 & #9193 WMS caching with custom scales (#9222)

* #9025 WMS caching with custom scales (projection resolutions strategy) (#9168)

* Fix #9025 WMS caching with custom scales (custom resolutions strategy from WMTS) (#9184)

---------

Co-authored-by: Lorenzo Natali <lorenzo.natali@geosolutionsgroup.com>

* Fix #9025 Available tile grids popup always reports mismatch in geostories and dashboards (#9196)

* Fix #9193 Add a cache options checks/info also for default WMS tile grid (#9195)

* Fix #9193 failing test (#9207)

* #9025 add caching options to wms background settings (#9213)

* fix failing tests

---------

Co-authored-by: Lorenzo Natali <lorenzo.natali@geosolutionsgroup.com>

27722 of 42099 branches covered (65.85%)

133 of 133 new or added lines in 12 files covered. (100.0%)

34374 of 43748 relevant lines covered (78.57%)

42.93 hits per line

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

44.06
/web/client/components/background/BackgroundDialog.jsx
1
/**
2
 * Copyright 2019, 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
import React from 'react';
9
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
10
import './css/backgrounddialog.css';
11

12
import { Editor as WYSIWYGEditor } from 'react-draft-wysiwyg';
13
import { EditorState, ContentState, convertToRaw } from 'draft-js';
14
import draftToHtml from 'draftjs-to-html';
15
import htmlToDraft from 'html-to-draftjs';
16
import localizedProps from '../misc/enhancers/localizedProps';
17
import PropTypes from 'prop-types';
18
import Select from 'react-select';
19
import assign from 'object-assign';
20
import uuidv1 from 'uuid/v1';
21
import {pick, omit, get, keys, isNumber, isBoolean} from 'lodash';
22
import Message from '../I18N/Message';
23
import ResizableModal from '../misc/ResizableModal';
24
import {Form, FormGroup, ControlLabel, FormControl, Glyphicon} from 'react-bootstrap';
25
import ButtonRB from '../misc/Button';
26
import Thumbnail from '../maps/forms/Thumbnail';
27
import WMSCacheOptions from '../TOC/fragments/settings/WMSCacheOptions';
28
import { ServerTypes } from '../../utils/LayersUtils';
29
import {getMessageById} from '../../utils/LocaleUtils';
30
import tooltip from '../misc/enhancers/tooltip';
31
const Button = tooltip(ButtonRB);
1✔
32
const Editor = localizedProps("placeholder")(WYSIWYGEditor);
1✔
33

34

35
export default class BackgroundDialog extends React.Component {
36
    static propTypes = {
1✔
37
        loading: PropTypes.bool,
38
        editing: PropTypes.bool,
39
        layer: PropTypes.object,
40
        capabilities: PropTypes.object,
41
        onAdd: PropTypes.func,
42
        onClose: PropTypes.func,
43
        source: PropTypes.string,
44
        onSave: PropTypes.func,
45
        addParameters: PropTypes.func,
46
        updateThumbnail: PropTypes.func,
47
        thumbURL: PropTypes.string,
48
        title: PropTypes.string,
49
        format: PropTypes.string,
50
        credits: PropTypes.object,
51
        style: PropTypes.string,
52
        thumbnail: PropTypes.object,
53
        additionalParameters: PropTypes.object,
54
        addParameter: PropTypes.func,
55
        defaultFormat: PropTypes.string,
56
        formatOptions: PropTypes.array,
57
        parameterTypeOptions: PropTypes.array,
58
        booleanOptions: PropTypes.array,
59
        projection: PropTypes.string,
60
        disableTileGrids: PropTypes.bool
61
    };
62

63
    static contextTypes = {
1✔
64
        messages: PropTypes.object
65
    };
66

67
    static defaultProps = {
1✔
68
        updateThumbnail: () => {},
69
        onClose: () => {},
70
        onSave: () => {},
71
        addParameters: () => {},
72
        addParameter: () => {},
73
        loading: false,
74
        editing: false,
75
        layer: {},
76
        capabilities: {},
77
        title: '',
78
        thumbnail: {},
79
        additionalParameters: {},
80
        formatOptions: [{
81
            label: 'image/png',
82
            value: 'image/png'
83
        }, {
84
            label: 'image/png8',
85
            value: 'image/png8'
86
        }, {
87
            label: 'image/jpeg',
88
            value: 'image/jpeg'
89
        }, {
90
            label: 'image/vnd.jpeg-png',
91
            value: 'image/vnd.jpeg-png'
92
        }, {
93
            label: 'image/gif',
94
            value: 'image/gif'
95
        }],
96
        parameterTypeOptions: [{
97
            label: "backgroundDialog.string",
98
            value: 'string'
99
        }, {
100
            label: "backgroundDialog.number",
101
            value: 'number'
102
        }, {
103
            label: "backgroundDialog.boolean",
104
            value: 'boolean'
105
        }],
106
        booleanOptions: [{
107
            label: 'True',
108
            value: true
109
        }, {
110
            label: 'False',
111
            value: false
112
        }]
113
    };
114

115
    constructor(props) {
116
        super(props);
3✔
117
        const pickedProps = pick(this.props, 'title', 'format', 'style', 'thumbnail', 'credits');
3✔
118
        const creditsTitleHtml = pickedProps?.credits?.title || '';
3✔
119
        const contentBlock = htmlToDraft(creditsTitleHtml);
3✔
120
        const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
3✔
121
        const editorState = EditorState.createWithContent(contentState);
3✔
122
        const newState = assign({}, pickedProps, {additionalParameters: this.assignParameters(this.props.additionalParameters), editorState});
3✔
123
        this.state = newState;
3✔
124
    }
125

126
    state = {title: '', format: 'image/png', thumbnail: {}, additionalParameters: [], credits: {}};
3✔
127

128
    onEditorStateChange(editorState) {
129
        this.setState({
×
130
            ...this.state,
131
            editorState
132
        });
133
    }
134

135
    renderStyleSelector() {
136
        return this.props.capabilities ? (
1!
137
            <FormGroup>
138
                <ControlLabel><Message msgId="layerProperties.style"/></ControlLabel>
139
                <Select
140
                    onChange={event => this.setState({style: event ? event.value : undefined})}
×
141
                    clearable
142
                    value={this.state.style}
143
                    options={(this.props.capabilities.availableStyles || []).map(({name}) => ({label: name, value: name}))}/>
✔
144
            </FormGroup>
145
        ) : null;
146
    }
147

148
    renderThumbnailErrors() {
149
        const errorMessages = {
5✔
150
            "FORMAT": <Message msgId="map.errorFormat" />,
151
            "SIZE": <Message msgId="map.errorSize" />
152
        };
153
        return this.state.thumbnailErrors && this.state.thumbnailErrors.length > 0 ? (
5!
154
            <div className="dropzone-errorBox alert-danger">
155
                <p><Message msgId="map.error"/></p>
156
                {(this.state.thumbnailErrors.map(err =>
157
                    <div id={"error" + err} key={"error" + err} className={"error" + err}>
×
158
                        {errorMessages[err]}
159
                    </div>
160
                ))}
161
            </div>
162
        ) : null;
163
    }
164
    renderSpecificTypeForm() {
165
        if (this.props.layer.type === "wms") {
5✔
166
            return (<React.Fragment>
1✔
167
                <FormGroup controlId="formControlsSelect">
168
                    <ControlLabel><Message msgId="layerProperties.format.title" /></ControlLabel>
169
                    <Select
170
                        onChange={event => this.setState({ format: event && event.value })}
×
171
                        value={this.state.format || this.props.defaultFormat}
2✔
172
                        clearable
173
                        options={this.props.formatOptions}
174
                    />
175
                </FormGroup>
176
                {this.renderStyleSelector()}
177
                {this.props.layer?.serverType !== ServerTypes.NO_VENDOR && <FormGroup>
2✔
178
                    <WMSCacheOptions
179
                        layer={{ ...this.props.layer, ...this.state }}
180
                        projection={this.props.projection}
181
                        onChange={value => this.setState(value)}
×
182
                        disableTileGrids={this.props.disableTileGrids}
183
                    />
184
                </FormGroup>}
185
                <Button>
186
                    <div style={{ display: 'flex', alignItems: 'center' }}>
187
                        <ControlLabel style={{ flex: 1 }}><Message msgId="backgroundDialog.additionalParameters" /></ControlLabel>
188
                        <Button
189
                            className="square-button-md"
190
                            tooltipId="backgroundDialog.addAdditionalParameterTooltip"
191
                            style={{ borderColor: 'transparent' }}
192
                            onClick={() => {
193
                                const cnt = Math.max(...(this.state.additionalParameters.length > 0 ?
×
194
                                    this.state.additionalParameters.map(p => p.id) : [-1])) + 1;
×
195
                                this.setState({
×
196
                                    additionalParameters:
197
                                        [...this.state.additionalParameters, { id: cnt, type: 'string', param: '', val: '' }]
198
                                });
199
                            }}>
200
                            <Glyphicon glyph="plus" />
201
                        </Button>
202
                    </div>
203
                    {this.state.additionalParameters.map((val) => (<div key={'val:' + val.id} style={{ display: 'flex', marginTop: 8 }}>
×
204
                        <div style={{ display: 'flex', flex: 1, marginRight: 8 }}>
205
                            <FormControl
206
                                style={{ width: '50%', marginRight: 8, minWidth: 0 }}
207
                                placeholder={getMessageById(this.context.messages, "backgroundDialog.parameter")}
208
                                value={val.param}
209
                                onChange={e => this.addAdditionalParameter(e.target.value, 'param', val.id, val.type)} />
×
210
                            {val.type === 'boolean' ?
×
211
                                <div style={{ width: '50%' }}>
212
                                    <Select
213
                                        onChange={e => this.addAdditionalParameter(e.value, 'val', val.id, val.type)}
×
214
                                        clearable={false}
215
                                        value={val.val}
216
                                        options={this.props.booleanOptions} />
217
                                </div> :
218
                                <FormControl
219
                                    style={{ width: '50%', minWidth: 0 }}
220
                                    placeholder={getMessageById(this.context.messages, "backgroundDialog.value")}
221
                                    value={val.val.toString()}
222
                                    onChange={e => this.addAdditionalParameter(e.target.value, 'val', val.id, val.type)} />}
×
223
                        </div>
224
                        <Select
225
                            style={{ flex: 1, width: 90 }}
226
                            onChange={event => this.addAdditionalParameter(val.val, 'val', val.id, event.value)}
×
227
                            clearable={false}
228
                            value={val.type}
229
                            options={this.props.parameterTypeOptions.map(({ label, ...other }) => ({
×
230
                                ...other,
231
                                label: getMessageById(this.context.messages, label)
232
                            }))} />
233
                        <Button
234
                            onClick={() => this.setState({
×
235
                                additionalParameters: this.state.additionalParameters.filter((aa) => val.id !== aa.id)
×
236
                            })}
237
                            tooltipId="backgroundDialog.removeAdditionalParameterTooltip"
238
                            className="square-button-md"
239
                            style={{ borderColor: 'transparent' }}>
240
                            <Glyphicon glyph="trash" />
241
                        </Button>
242
                    </div>))}
243
                </Button>
244
            </React.Fragment>);
245
        }
246
        if (this.props.layer.type === "wmts") {
4✔
247
            return (
2✔
248
                <React.Fragment>
249
                    <FormGroup controlId="formControlsSelect">
250
                        <ControlLabel><Message msgId="backgroundDialog.editAttribution" /></ControlLabel>
251
                        <Editor
252
                            editorState={this.state.editorState}
253
                            editorClassName="ms2 form-control"
254
                            toolbarClassName="bg-dialog-attribution-toolbar"
255
                            onEditorStateChange={this.onEditorStateChange.bind(this)}
256
                            placeholder="backgroundDialog.editAttributionPlaceholder"
257
                            toolbar={{
258
                                options: ['inline', 'blockType', 'link', 'remove'],
259
                                inline: {
260
                                    options: ['bold', 'italic', 'underline', 'strikethrough']
261
                                },
262
                                blockType: {
263
                                    inDropDown: true,
264
                                    options: ['Normal', 'H5']
265
                                },
266
                                link: {
267
                                    inDropDown: true,
268
                                    options: ['link', 'unlink']
269
                                }
270
                            }}
271
                        />
272
                    </FormGroup>
273
                </React.Fragment>
274
            );
275
        }
276
        return null;
2✔
277
    }
278

279
    render() {
280
        return (<ResizableModal
5✔
281
            fitContent
282
            title={<Message msgId={this.props.editing ? 'backgroundDialog.editTitle' : 'backgroundDialog.addTitle'}/>}
5!
283
            show
284
            fade
285
            clickOutEnabled={false}
286
            bodyClassName="ms-flex modal-properties-container background-dialog"
287
            loading={this.props.loading}
288
            onClose={() => { this.props.onClose(); this.resetParameters(); }}
×
289
            buttons={this.props.loading ? [] : [
5!
290
                {
291
                    text: <Message msgId={this.props.editing ? 'save' : 'backgroundDialog.add'}/>,
5!
292
                    bsStyle: 'primary',
293
                    onClick: () => {
294
                        const backgroundId = this.props.editing ? this.props.layer.id : uuidv1();
2!
295
                        const curThumbURL = this.props.layer.thumbURL || '';
2✔
296
                        const format = this.state.format || this.props.defaultFormat;
2✔
297
                        const creditsTitle = draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()));
2✔
298
                        this.props.updateThumbnail(this.state.thumbnail.data, backgroundId);
2✔
299
                        this.props.onSave(assign({}, this.props.layer, omit(this.state, 'thumbnail'), this.props.editing ? {} : {id: backgroundId},
2!
300
                            {
301
                                params: omit(
302
                                    this.state.additionalParameters.reduce((accum, p) => assign(accum, {[p.param]: p.val}), {}),
×
303
                                    ['source', 'title']
304
                                ),
305
                                format,
306
                                credits: {
307
                                    ...this.state.credits,
308
                                    title: creditsTitle
309
                                },
310
                                group: 'background'
311
                            }, !curThumbURL && !this.state.thumbnail.data ? {} : {thumbURL: this.state.thumbnail.url}));
6!
312
                        this.resetParameters();
2✔
313
                    }
314
                }
315
            ]}>
316
            {<Form style={{width: '100%'}}>
317
                {this.renderThumbnailErrors()}
318
                <Thumbnail
319
                    onUpdate={(data, url) => this.setState({thumbnail: {data, url}})}
×
320
                    onError={(errors) => this.setState({thumbnailErrors: errors})}
×
321
                    message={<Message msgId="backgroundDialog.thumbnailMessage"/>}
322
                    suggestion=""
323
                    map={{
324
                        newThumbnail: get(this.state.thumbnail, 'url') || "NODATA"
10✔
325
                    }}
326
                />
327
                <FormGroup>
328
                    <ControlLabel><Message msgId="layerProperties.title"/></ControlLabel>
329
                    <FormControl
330
                        value={this.state.title}
331
                        placeholder={getMessageById(this.context.messages, "backgroundDialog.titlePlaceholder")}
332
                        onChange={event => this.setState({title: event.target.value})}/>
×
333
                </FormGroup>
334
                {this.renderSpecificTypeForm()}
335
            </Form>}
336
        </ResizableModal>);
337
    }
338
    // assign the additional parameters from the layers (state) to the modal component state
339
    assignParameters = (parameters) =>
3✔
340
        keys(parameters).map((key, index) => {
3✔
341
            const value = parameters[key];
×
342
            const type = isNumber(value) ? 'number' : isBoolean(value) ? 'boolean' : 'string';
×
343
            return {
×
344
                id: index,
345
                param: key,
346
                type,
347
                val: type === 'string' ? value ? value.toString() : "" : value
×
348
            };
349
        });
350
    addAdditionalParameter = (event, key, id, type)=> {
3✔
351
        this.setState({
×
352
            additionalParameters:
353
            this.state.additionalParameters.map(v => {
354
                if (v.id === id) {
×
355
                    let modifiedKey;
356
                    if (key === 'val') {
×
357
                        switch (type) {
×
358
                        case 'number':
359
                            modifiedKey = Number(event);
×
360
                            if (!modifiedKey || isNaN(modifiedKey)) {
×
361
                                modifiedKey = 0;
×
362
                            }
363
                            break;
×
364
                        case 'boolean':
365
                            modifiedKey = isBoolean(event) ? event : true;
×
366
                            break;
×
367
                        default:
368
                            modifiedKey = event ? event.toString() : '';
×
369
                            break;
×
370
                        }
371
                    } else {
372
                        modifiedKey = event;
×
373
                    }
374
                    return assign({}, v, {[key]: modifiedKey, type});
×
375
                }
376
                return v;
×
377
            })
378
        });
379
    }
380
    resetParameters = () => this.setState({additionalParameters: []});
3✔
381
}
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