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

geosolutions-it / MapStore2 / 15896067708

26 Jun 2025 07:47AM UTC coverage: 76.906% (-0.07%) from 76.979%
15896067708

push

github

web-flow
fix FE unit tests failure due to change in PR 11236 (#11245)

For issue #10839

31189 of 48586 branches covered (64.19%)

38806 of 50459 relevant lines covered (76.91%)

36.45 hits per line

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

30.86
/web/client/components/mapcontrols/scale/ScaleBox.jsx
1
/**
2
 * Copyright 2015, 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 { isEqual } from 'lodash';
10
import PropTypes from 'prop-types';
11
import React from 'react';
12
import { ControlLabel, Form, FormControl, FormGroup } from 'react-bootstrap';
13
import {Creatable} from 'react-select';
14
import { getExactZoomFromResolution, getGoogleMercatorScales, getResolutions } from '../../../utils/MapUtils';
15
import localizedProps from '../../misc/enhancers/localizedProps';
16
import Message from '../../I18N/Message';
17
const ReactSelectCreatable = localizedProps(['placeholder', 'noResultsText'])(Creatable);
1✔
18

19
class ScaleBox extends React.Component {
20
    static propTypes = {
1✔
21
        id: PropTypes.string,
22
        style: PropTypes.object,
23
        scales: PropTypes.array,
24
        currentZoomLvl: PropTypes.number,
25
        minZoom: PropTypes.number,
26
        onChange: PropTypes.func,
27
        readOnly: PropTypes.bool,
28
        label: PropTypes.oneOfType([PropTypes.func, PropTypes.string, PropTypes.object]),
29
        template: PropTypes.func,
30
        useRawInput: PropTypes.bool,
31
        disableScaleLockingParms: PropTypes.object
32
    };
33

34
    static defaultProps = {
1✔
35
        id: 'mapstore-scalebox',
36
        scales: getGoogleMercatorScales(0, 28),
37
        currentZoomLvl: 0,
38
        minZoom: 0,
39
        scale: 0,
40
        onChange() {},
41
        readOnly: false,
42
        template: scale => scale < 1
261!
43
            ? Math.round(1 / scale) + " : 1"
44
            : "1 : " + Math.round(scale),
45
        useRawInput: false,
46
        disableScaleLockingParms: {}
47
    };
48
    constructor(props) {
49
        super(props);
16✔
50
        this.state = {
16✔
51
            scales: this.props.disableScaleLockingParms?.resolutions?.length ?
16!
52
                this.getScalesFromResolutions(this.props.disableScaleLockingParms?.resolutions) :
53
                this.props.scales.map((scale, idx) => ({
309✔
54
                    value: scale,
55
                    zoom: idx
56
                }))
57
        };
58
    }
59

60
    shouldComponentUpdate(nextProps) {
61
        return !isEqual(nextProps, this.props);
4✔
62
    }
63
    onComboChange = (event) => {
16✔
64
        let selectedZoomLvl = parseInt(event.nativeEvent.target.value, 10);
1✔
65
        this.props.onChange(selectedZoomLvl, this.props.scales[selectedZoomLvl]);
1✔
66
    };
67

68
    getOptions = () => {
16✔
69
        return this.state.scales.map((item) => {
17✔
70
            return (
260✔
71
                <option value={item.zoom} key={item.zoom}>{this.props.template(item.value, item.zoom)}</option>
72
            );
73
        }).filter((element, index) => index >= this.props.minZoom);
260✔
74
    };
75
    // for print in case editScale = true
76
    updateScales(scales, newValue) {
77
        const scalesValues = scales.map(scale => scale.value);
×
78

79
        const isMissing = newValue && scalesValues.indexOf(newValue.value) === -1;
×
80

81
        return isMissing
×
82
            ? [ {value: newValue.value, zoom: newValue.zoom}, ...scales ].sort((a, b) => (a.value > b.value ? -1 : 1))
×
83
            : scales;
84
    }
85
    handleUpdateScales = (newScaleValue) => {
16✔
86
        this.setState((prevState) => ({
×
87
            scales: this.updateScales(prevState.scales, newScaleValue)
88
        }));
89
    };
90
    getResolutionByScale(scale, resolutions) {
91
        const { disableScaleLockingParms } = this.props;
×
92
        let correspScales = this.props.scales.map(sc => sc * disableScaleLockingParms.ratio);
×
93
        const firstRes = resolutions[0];
×
94
        const firstScale = correspScales[0];
×
95
        const corresEnteredScale = scale * disableScaleLockingParms?.ratio || 1;
×
96
        const correspondentResolution = corresEnteredScale * firstRes / firstScale;
×
97
        return correspondentResolution;
×
98
    }
99

100
    getZoomLevelByResolution(resolution) {
101
        const resolutions = this.props.disableScaleLockingParms?.resolutions || getResolutions(this.props.disableScaleLockingParms?.projection);
×
102
        const corresZoom = getExactZoomFromResolution(resolution, resolutions) ?? 0;
×
103
        return parseFloat(corresZoom.toFixed(2));
×
104
    }
105
    getScalesFromResolutions(resolutions) {
106
        const { disableScaleLockingParms } = this.props;
×
107

108
        // Calculate the corresponding scales based on the ratio
109
        let correspScales = this.props.scales.map(sc => sc * disableScaleLockingParms.ratio).map((sc, idx) => ({value: sc, zoom: idx}));
×
110

111
        // Get the first resolution and the first corresponding scale
112
        const firstRes = resolutions[0];
×
113
        const firstScale = correspScales[0].value;
×
114

115
        // Calculate the corresponding scales for each resolution
116
        const correspondentScales = resolutions.map(res => {
×
117
            return res * firstScale / firstRes;
×
118
        });
119
        return correspondentScales.map(sc => sc / disableScaleLockingParms.ratio).map((sc, idx) => ({value: sc, zoom: this.getZoomLevelByResolution(resolutions[idx])}));
×
120
    }
121
    handleChangeEditableScaleList(option) {
122
        const {disableScaleLockingParms} = this.props;
×
123

124
        const newValue = option?.value && parseFloat(option.value);
×
125
        const newScale = option?.scale && parseFloat(option.scale) || newValue;
×
126
        const newLabel = option?.label && parseFloat(option.label);
×
127
        const newZoom = option?.zoom && parseFloat(option.zoom);
×
128

129
        const baseResolutions  = disableScaleLockingParms?.resolutions || getResolutions(this.props.disableScaleLockingParms?.projection);
×
130
        const currentResolutions = [...baseResolutions];
×
131
        const correspondentResolution = this.getResolutionByScale(newScale, baseResolutions );
×
132
        // 1. Validate if newResolution is a valid number greater than 0
133
        if (typeof correspondentResolution === 'number' && correspondentResolution > 0) {
×
134
            // 2. Check if the resolution already exists in the array
135
            const resolutionSet = new Set(baseResolutions );
×
136
            if (!resolutionSet.has(correspondentResolution)) {
×
137
                // 3. Add the new resolution to the array
138
                currentResolutions .push(correspondentResolution);
×
139
                // 4. Sort the array to maintain order
140
                currentResolutions .sort((a, b) => b - a); // Sorts numerically in ascending order
×
141
            }
142
        }
143
        const corresZoom = getExactZoomFromResolution(correspondentResolution, currentResolutions) ?? 0;
×
144
        const roundedZoom = parseFloat(corresZoom.toFixed(2));
×
145
        // new created option
146
        if (newScale === newLabel && newScale && !newZoom) {
×
147
            this.handleUpdateScales({
×
148
                value: newScale, zoom: roundedZoom
149
            });
150
            this.props.onChange(roundedZoom, newScale, correspondentResolution, currentResolutions);
×
151
            // add this resolution to the resolutions list of map viewer
152
            return;
×
153
        }
154
        const scaleLevelToSet = isNaN(newScale) ? undefined : newScale;
×
155
        const zoomLevelToSet = isNaN(newZoom) ? undefined : newZoom;
×
156
        const selectedZoomLvl = this.state.scales.find( sc => sc.value === scaleLevelToSet) ||
×
157
                            this.props.scales[this.props.currentZoomLvl];
158
        this.props.onChange(zoomLevelToSet, selectedZoomLvl?.value, correspondentResolution, currentResolutions);
×
159
    }
160
    render() {
161
        let control = null;
19✔
162
        let currentZoomLvl = Math.round(this.props.currentZoomLvl);
19✔
163
        const {disableScaleLockingParms} = this.props;
19✔
164
        if (this.props.readOnly) {
19✔
165
            control =
2✔
166
                <label>{this.props.template(this.props.scales[currentZoomLvl], currentZoomLvl)}</label>
167
            ;
168
        } else if (this.props.useRawInput) {
17!
169
            control =
×
170
                (<select label={this.props.label} onChange={this.onComboChange} bsSize="small" value={currentZoomLvl || ""}>
×
171
                    {this.getOptions()}
172
                </select>)
173
            ;
174
        } else if (disableScaleLockingParms?.editScale) {
17!
175
            const {scales} = this.state;
×
176
            currentZoomLvl = disableScaleLockingParms?.resolution ? this.getZoomLevelByResolution(disableScaleLockingParms?.resolution) : Math.round(this.props.currentZoomLvl) > (scales.length - 1) ? (scales.length - 1) : Math.round(this.props.currentZoomLvl);
×
177

178
            control =
×
179
                (<Form inline><FormGroup bsSize="small">
180
                    <ReactSelectCreatable
181
                        clearable={false}
182
                        id="scaleBox"
183
                        className="scale-box-create-select"
184
                        value={currentZoomLvl}
185
                        options={scales.map((item) => ({scale: item.value, zoom: item.zoom, value: item.zoom, label: this.props.template(item.value, item.zoom)}))}
×
186
                        promptTextCreator={(label) => {
187
                            return <Message msgId={"print.createScaleOption"} msgParams={{ label }}/>;
×
188

189
                        }}
190
                        isValidNewOption={(option) => {
191
                            if (option.label) {
×
192
                                const newValue = parseFloat(option.label);
×
193
                                return !isNaN(newValue) && newValue > Math.min(...(this.props.scales || [])) && newValue < Math.max(...(this.props.scales || []));
×
194
                            }
195
                            return false;
×
196
                        }}
197
                        onChange={(op) => this.handleChangeEditableScaleList(op)}
×
198
                    />
199
                </FormGroup></Form>)
200
            ;
201
        } else {
202
            control =
17✔
203
                (<Form inline><FormGroup bsSize="small">
204
                    <ControlLabel htmlFor="scaleBox">{this.props.label}</ControlLabel>
205
                    <FormControl id="scaleBox" componentClass="select" onChange={this.onComboChange} value={currentZoomLvl || ""}>
25✔
206
                        {this.getOptions()}
207
                    </FormControl>
208
                </FormGroup></Form>)
209
            ;
210
        }
211
        return (
19✔
212

213
            <div id={this.props.id} style={this.props.style}>
214
                {control}
215
            </div>
216
        );
217
    }
218
}
219

220
export default ScaleBox;
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