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

geosolutions-it / MapStore2 / 15180837196

22 May 2025 07:35AM UTC coverage: 76.882% (-0.05%) from 76.934%
15180837196

Pull #11130

github

web-flow
Merge 838d482a3 into 94d9822b2
Pull Request #11130: #10839: Allow printing by freely setting the scale factor

31033 of 48379 branches covered (64.15%)

40 of 84 new or added lines in 9 files covered. (47.62%)

4 existing lines in 3 files now uncovered.

38641 of 50260 relevant lines covered (76.88%)

36.34 hits per line

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

37.88
/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.scales.map((scale, idx) => ({
309✔
52
                value: scale,
53
                zoom: idx
54
            }))
55
        };
56
    }
57

58
    shouldComponentUpdate(nextProps) {
59
        return !isEqual(nextProps, this.props);
4✔
60
    }
61
    // for print in case disableScaleLocking = true
62
    updateScales(scales, newValue) {
NEW
63
        const scalesValues = scales.map(scale => scale.value);
×
64

NEW
65
        const isMissing = newValue && scalesValues.indexOf(newValue.value) === -1;
×
66

NEW
67
        return isMissing
×
NEW
68
            ? [ {value: newValue.value, zoom: newValue.zoom}, ...scales ].sort((a, b) => (a.value > b.value ? -1 : 1))
×
69
            : scales;
70
    }
71
    handleUpdateScales = (newScaleValue) => {
16✔
NEW
72
        this.setState((prevState) => ({
×
73
            scales: this.updateScales(prevState.scales, newScaleValue)
74
        }));
75
    };
76
    onComboChange = (event) => {
16✔
77
        let selectedZoomLvl = parseInt(event.nativeEvent.target.value, 10);
1✔
78
        this.props.onChange(selectedZoomLvl, this.props.scales[selectedZoomLvl]);
1✔
79
    };
80

81
    getOptions = () => {
16✔
82
        return this.state.scales.map((item) => {
17✔
83
            return (
260✔
84
                <option value={item.zoom} key={item.zoom}>{this.props.template(item.value, item.zoom)}</option>
85
            );
86
        }).filter((element, index) => index >= this.props.minZoom);
260✔
87
    };
88
    // for print in case disableScaleLocking = true
89

90
    getResolutionByScale(scale, resolutions) {
NEW
91
        const { disableScaleLockingParms } = this.props;
×
NEW
92
        let correspScales = this.props.scales.map(sc => sc * disableScaleLockingParms.ratio);
×
NEW
93
        const firstRes = resolutions[0];
×
NEW
94
        const firstScale = correspScales[0];
×
NEW
95
        const corresEnteredScale = scale * disableScaleLockingParms?.ratio || 1;
×
NEW
96
        const correspondentResolution = corresEnteredScale * firstRes / firstScale;
×
NEW
97
        return correspondentResolution;
×
98
    }
99
    getZoomLevelByResolution(resolution) {
NEW
100
        const resolutions = this.props.disableScaleLockingParms?.resolutions || getResolutions(this.props.disableScaleLockingParms?.projection);
×
NEW
101
        const corresZoom = getExactZoomFromResolution(resolution, resolutions) ?? 0;
×
NEW
102
        return parseFloat(corresZoom.toFixed(2));
×
103
    }
104
    render() {
105
        let control = null;
19✔
106
        let currentZoomLvl = Math.round(this.props.currentZoomLvl);
19✔
107
        const {disableScaleLockingParms} = this.props;
19✔
108
        if (this.props.readOnly) {
19✔
109
            control =
2✔
110
                <label>{this.props.template(this.props.scales[currentZoomLvl], currentZoomLvl)}</label>
111
            ;
112
        } else if (this.props.useRawInput) {
17!
113
            control =
×
114
                (<select label={this.props.label} onChange={this.onComboChange} bsSize="small" value={currentZoomLvl || ""}>
×
115
                    {this.getOptions()}
116
                </select>)
117
            ;
118
        } else if (disableScaleLockingParms?.disableScaleLocking) {
17!
NEW
119
            const {scales} = this.state;
×
NEW
120
            currentZoomLvl = disableScaleLockingParms?.resolution ? this.getZoomLevelByResolution(disableScaleLockingParms?.resolution) : Math.round(this.props.currentZoomLvl) > (scales.length - 1) ? (scales.length - 1) : Math.round(this.props.currentZoomLvl);
×
121

NEW
122
            control =
×
123
                (<Form inline><FormGroup bsSize="small">
124
                    <ReactSelectCreatable
125
                        id="scaleBox"
126
                        className="scale-box-create-select"
127
                        value={currentZoomLvl}
NEW
128
                        options={scales.map((item) => ({scale: item.value, zoom: item.zoom, value: item.zoom, label: this.props.template(item.value, item.zoom)}))}
×
129
                        promptTextCreator={(label) => {
NEW
130
                            return <Message msgId={"print.createScaleOption"} msgParams={{ label }}/>;
×
131
                        }}
132
                        isValidNewOption={(option) => {
NEW
133
                            if (option.label) {
×
NEW
134
                                const newValue = parseFloat(option.label);
×
NEW
135
                                return !isNaN(newValue) && newValue > 0;
×
136
                            }
NEW
137
                            return false;
×
138
                        }}
139
                        onChange={(option) => {
NEW
140
                            const newValue = option?.value && parseFloat(option.value);
×
NEW
141
                            const newScale = option?.scale && parseFloat(option.scale) || newValue;
×
NEW
142
                            const newLabel = option?.label && parseFloat(option.label);
×
NEW
143
                            const newZoom = option?.zoom && parseFloat(option.zoom);
×
NEW
144
                            const resolutions = disableScaleLockingParms?.resolutions || getResolutions(this.props.disableScaleLockingParms?.projection);
×
145

NEW
146
                            const correspondentResolution = this.getResolutionByScale(newScale, resolutions);
×
NEW
147
                            const corresZoom = getExactZoomFromResolution(correspondentResolution, resolutions) ?? 0;
×
NEW
148
                            const roundedZoom = parseFloat(corresZoom.toFixed(2));
×
149
                            // new created option
NEW
150
                            if (newScale === newLabel && newScale && !newZoom) {
×
NEW
151
                                this.handleUpdateScales({
×
152
                                    value: newScale, zoom: roundedZoom
153
                                });
NEW
154
                                this.props.onChange(roundedZoom, newScale, correspondentResolution);
×
NEW
155
                                return;
×
156
                            }
NEW
157
                            const scaleLevelToSet = isNaN(newScale) ? undefined : newScale;
×
NEW
158
                            const zoomLevelToSet = isNaN(newZoom) ? undefined : newZoom;
×
NEW
159
                            const selectedZoomLvl = this.state.scales.find( sc => sc.value === scaleLevelToSet) || this.props.scales[this.props.currentZoomLvl];
×
NEW
160
                            this.props.onChange(zoomLevelToSet, selectedZoomLvl?.value, correspondentResolution);
×
161
                        }}
162
                    />
163
                </FormGroup></Form>)
164
            ;
165
        } else {
166
            control =
17✔
167
                (<Form inline><FormGroup bsSize="small">
168
                    <ControlLabel htmlFor="scaleBox">{this.props.label}</ControlLabel>
169
                    <FormControl id="scaleBox" componentClass="select" onChange={this.onComboChange} value={currentZoomLvl || ""}>
25✔
170
                        {this.getOptions()}
171
                    </FormControl>
172
                </FormGroup></Form>)
173
            ;
174
        }
175
        return (
19✔
176

177
            <div id={this.props.id} style={this.props.style}>
178
                {control}
179
            </div>
180
        );
181
    }
182
}
183

184
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