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

geosolutions-it / MapStore2 / 14534587011

18 Apr 2025 11:41AM UTC coverage: 76.977% (-0.02%) from 76.993%
14534587011

Pull #11037

github

web-flow
Merge f22d700f6 into 48d6a1a15
Pull Request #11037: Remove object assign pollyfills

30792 of 47937 branches covered (64.23%)

446 of 556 new or added lines in 94 files covered. (80.22%)

8 existing lines in 4 files now uncovered.

38277 of 49725 relevant lines covered (76.98%)

36.07 hits per line

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

46.67
/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 uuidv1 from 'uuid/v1';
20
import {pick, omit, get, keys, isNumber, isBoolean} from 'lodash';
21
import Message from '../I18N/Message';
22
import ResizableModal from '../misc/ResizableModal';
23
import {Form, FormGroup, ControlLabel, FormControl, Glyphicon} from 'react-bootstrap';
24
import ButtonRB from '../misc/Button';
25
import Thumbnail from '../maps/forms/Thumbnail';
26
import WMSCacheOptions from '../TOC/fragments/settings/WMSCacheOptions';
27
import { ServerTypes } from '../../utils/LayersUtils';
28
import {getMessageById} from '../../utils/LocaleUtils';
29
import tooltip from '../misc/enhancers/tooltip';
30
const Button = tooltip(ButtonRB);
1✔
31
const Editor = localizedProps("placeholder")(WYSIWYGEditor);
1✔
32

33

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

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

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

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

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

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

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

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

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