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

geosolutions-it / MapStore2 / 12831531306

17 Jan 2025 03:01PM UTC coverage: 77.182% (+0.07%) from 77.115%
12831531306

Pull #10746

github

web-flow
Merge 501dbaeea into 4e4dabc03
Pull Request #10746: Fix #10739 Changing correctly resolutions limits when switching map CRS

30373 of 47156 branches covered (64.41%)

34 of 43 new or added lines in 2 files covered. (79.07%)

126 existing lines in 15 files now uncovered.

37769 of 48935 relevant lines covered (77.18%)

35.14 hits per line

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

81.4
/web/client/components/mapviews/settings/LayersSection.jsx
1
/*
2
 * Copyright 2022, 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 React, { useState } from 'react';
10
import PropTypes from 'prop-types';
11
import { FormControl as FormControlRB, FormGroup, InputGroup, Glyphicon, Checkbox, Button as ButtonRB, ButtonGroup } from 'react-bootstrap';
12
import Section from './Section';
13
import { mergeViewLayers, mergeViewGroups, pickViewLayerProperties, pickViewGroupProperties } from '../../../utils/MapViewsUtils';
14
import LayerOverridesNodeContent from './LayerOverridesNodeContent';
15
import Message from '../../I18N/Message';
16
import TOC from '../../../plugins/TOC/components/TOC';
17
import tooltip from '../../misc/enhancers/tooltip';
18
import localizedProps from '../../misc/enhancers/localizedProps';
19
import { NodeTypes } from '../../../utils/LayersUtils';
20
import { getMessageById } from '../../../utils/LocaleUtils';
21

22
const Button = tooltip(ButtonRB);
1✔
23
const FormControl = localizedProps('placeholder')(FormControlRB);
1✔
24

25
/**
26
 * ResetLayerOverrides node tool to link and unlink groups and layers to TOC
27
 * @prop {object} itemComponent default node tool component
28
 * @prop {object} node layer object
29
 * @prop {object} config optional configuration available for the nodes
30
 * @prop {string} nodeType node type
31
 * @prop {object} nodeTypes constant values for node types
32
 * @prop {function} onChange return the changes of this node
33
 */
34
function ResetLayerOverrides({
35
    itemComponent,
36
    node,
37
    config,
38
    nodeType,
39
    nodeTypes,
40
    onChange
41
}) {
42
    const ItemComponent = itemComponent;
20✔
43
    const { view } = config?.mapViews || {};
20!
44
    const changed = nodeType === nodeTypes.LAYER
20✔
45
        ? !!view?.layers?.find(layer => layer.id === node.id)
5✔
46
        : !!view?.groups?.find(group => group.id === node.id);
5✔
47
    function handleClick() {
48
        if (changed) {
4✔
49
            onChange({ resetView: true });
2✔
50
        } else {
51
            onChange(nodeType === nodeTypes.LAYER
2✔
52
                ? pickViewLayerProperties(node)
53
                : pickViewGroupProperties(node));
54
        }
55
    }
56
    return (
20✔
57
        <ItemComponent
58
            tooltipId={changed ? `mapViews.${nodeType}Unlinked` : `mapViews.${nodeType}Linked`}
20✔
59
            glyph={changed ? 'unplug' : 'plug'}
20✔
60
            onClick={handleClick}
61
        />
62
    );
63
}
64

65
ResetLayerOverrides.propTypes = {
1✔
66
    itemComponent: PropTypes.any,
67
    node: PropTypes.object,
68
    config: PropTypes.object,
69
    nodeType: PropTypes.string,
70
    nodeTypes: PropTypes.object,
71
    onChange: PropTypes.func
72
};
73
/**
74
 * LayersSection table of content for layers and groups inside a map view
75
 * @prop {object} view view configuration
76
 * @prop {object} expandedSections state of the expended section
77
 * @prop {function} onExpandSection returns the new expanded state
78
 * @prop {function} onChange returns changes on the view
79
 * @prop {array} resources list of resources available for the views
80
 * @prop {array} layers list of supported layers
81
 * @prop {array} groups list of supported groups
82
 * @prop {array} vectorLayers list of vector layers
83
 * @prop {string} locale current locale
84
 * @prop {function} onChangeLayer returns changes on a view layer
85
 * @prop {function} onResetLayer requests a reset on the selected view layer
86
 * @prop {function} onChangeGroup returns changes on a view group
87
 * @prop {function} onResetGroup requests a reset on the selected view group
88
 * @prop {boolean} showClipGeometries visibility state of clipping features
89
 * @prop {function} onShowClipGeometries return the clipping checkbox state
90
 * @prop {function} isTerrainAvailable if true shows the terrain options
91
 * @prop {function} isClippingAvailable if true shows enable clipping options
92
 */
93
function LayersSection({
94
    view,
95
    expandedSections = {},
1✔
96
    onExpandSection,
97
    onChange,
98
    resources,
99
    layers = [],
4✔
100
    groups = [],
5✔
101
    vectorLayers,
102
    updateLayerRequest,
103
    locale,
104
    onChangeLayer,
105
    onResetLayer,
106
    onChangeGroup,
107
    onResetGroup,
108
    showClipGeometries,
109
    onShowClipGeometries,
110
    isTerrainAvailable,
111
    isClippingAvailable
112
}, { messages }) {
113

114
    const [filterText, setFilterText] = useState('');
14✔
115
    const [expandedNodes, setExpandedNodes] = useState([
14✔
116
        ...groups.filter((group) => group.expanded).map(group => group.id),
9✔
117
        ...layers.filter((layer) => layer.expanded).map(layer => layer.id)
11✔
118
    ]);
119
    const mergedLayers = mergeViewLayers(layers, view);
14✔
120
    const mergedGroups = mergeViewGroups(groups, view);
14✔
121
    const tocMapViewConfig = {
14✔
122
        view,
123
        updateLayerRequest,
124
        vectorLayers,
125
        resources,
126
        locale
127
    };
128
    function applyExpandedProperty(nodes) {
129
        return nodes.map(node => ({ ...node, expanded: expandedNodes.includes(node.id) }));
28✔
130
    }
131
    function areAllNodesUnlinked() {
132
        return layers.every(layer => (view?.layers || []).some(vLayer => vLayer.id === layer.id))
14!
133
            && groups.every(group => (view?.groups || []).some(vGroup => vGroup.id === group.id));
1!
134
    }
135
    return (
14✔
136
        <Section
137
            title={<Message msgId="mapViews.layersOptions"/>}
138
            initialExpanded={expandedSections.layers}
UNCOV
139
            onExpand={(expanded) => onExpandSection({ layers: expanded })}
×
140
        >
141
            {isClippingAvailable && <div className="ms-map-views-layers-options-header">
25✔
142
                <FormGroup
143
                    controlId="map-views-show-clipping-geometries">
144
                    <Checkbox
145
                        checked={!!showClipGeometries}
UNCOV
146
                        onChange={() => onShowClipGeometries(!showClipGeometries)}
×
147
                    >
148
                        <Message msgId="mapViews.showClippingLayersGeometries"/>
149
                    </Checkbox>
150
                </FormGroup>
151
            </div>}
152
            <FormGroup style={{ marginBottom: '0.5rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
153
                <InputGroup style={{ flex: 1 }}>
154
                    <FormControl
155
                        placeholder="toc.filterPlaceholder"
156
                        value={filterText}
UNCOV
157
                        onChange={(event) => setFilterText(event?.target?.value)}
×
158
                    />
159
                    {filterText
14!
160
                        ? <InputGroup.Button>
UNCOV
161
                            <Button tooltipId="toc.clearFilter" onClick={() => setFilterText('')}><Glyphicon glyph="1-close"/></Button>
×
162
                        </InputGroup.Button>
163
                        : <InputGroup.Addon>
164
                            <Glyphicon glyph="filter"/>
165
                        </InputGroup.Addon>}
166
                </InputGroup>
167
                <ButtonGroup>
168
                    <Button
169
                        tooltipId="mapViews.linkAllNodes"
170
                        disabled={!view?.layers?.length && !view?.groups?.length}
25✔
171
                        className="square-button-md"
172
                        bsStyle="primary"
173
                        onClick={() => {
174
                            onChange({
1✔
175
                                groups: undefined,
176
                                layers: undefined
177
                            });
178
                        }}
179
                    >
180
                        <Glyphicon glyph="plug"/>
181
                    </Button>
182
                    <Button
183
                        tooltipId="mapViews.unlinkAllNodes"
184
                        className="square-button-md"
185
                        bsStyle="primary"
186
                        disabled={areAllNodesUnlinked()}
187
                        onClick={() => {
188
                            onChange({
1✔
189
                                groups: groups.map((group) => {
190
                                    const viewGroup = (view?.groups || []).find(vGroup => vGroup.id === group.id);
1!
191
                                    return pickViewGroupProperties(viewGroup || group);
1!
192
                                }),
193
                                layers: layers.map((layer) => {
194
                                    const viewLayer = (view?.layers || []).find(vLayer => vLayer.id === layer.id);
1!
195
                                    return pickViewLayerProperties(viewLayer || layer);
1✔
196
                                })
197
                            });
198
                        }}
199
                    >
200
                        <Glyphicon glyph="unplug" />
201
                    </Button>
202
                </ButtonGroup>
203
            </FormGroup>
204
            {isTerrainAvailable ? <TOC
14✔
205
                map={{
206
                    layers: [{
207
                        ...view?.terrain,
208
                        id: 'terrain',
209
                        type: 'terrain',
210
                        title: getMessageById(messages, 'mapViews.terrain')
211
                    }]
212
                }}
213
                nodeContentItems={[
214
                    { name: 'LayerOverridesNodeContent', Component: LayerOverridesNodeContent }
215
                ]}
216
                config={{
217
                    sortable: false,
218
                    hideOpacitySlider: true,
219
                    hideVisibilityButton: true,
220
                    layerOptions: {
221
                        hideFilter: true,
222
                        hideLegend: true
223
                    },
224
                    mapViews: tocMapViewConfig
225
                }}
226
                onChangeNode={(nodeId, nodeType, options) => {
UNCOV
227
                    if (nodeId === 'terrain' && nodeType === NodeTypes.LAYER) {
×
UNCOV
228
                        onChange({ terrain: { ...view?.terrain, ...options }});
×
229
                    }
230
                }}
231
            /> : null}
232
            <TOC
233
                map={{
234
                    layers: applyExpandedProperty(mergedLayers),
235
                    groups: applyExpandedProperty(mergedGroups)
236
                }}
237
                filterText={filterText}
238
                config={{
239
                    sortable: false,
240
                    layerOptions: {
241
                        hideFilter: true,
242
                        hideLegend: true
243
                    },
244
                    mapViews: tocMapViewConfig
245
                }}
246
                nodeContentItems={[
247
                    { name: 'LayerOverridesNodeContent', Component: LayerOverridesNodeContent }
248
                ]}
249
                nodeToolItems={[
250
                    { name: 'ResetLayerOverrides', Component: ResetLayerOverrides }
251
                ]}
252
                onChangeNode={(nodeId, nodeType, options) => {
253
                    if (options.expanded !== undefined) {
4!
UNCOV
254
                        return setExpandedNodes(
×
255
                            options.expanded
×
256
                                ? [...expandedNodes, nodeId]
UNCOV
257
                                : expandedNodes.filter(expandedNodeId => expandedNodeId !== nodeId));
×
258
                    }
259
                    if (options.resetView) {
4✔
260
                        return nodeType === NodeTypes.LAYER
2✔
261
                            ? onResetLayer(nodeId)
262
                            : onResetGroup(nodeId);
263
                    }
264
                    return nodeType === NodeTypes.LAYER
2✔
265
                        ? onChangeLayer(nodeId, options)
266
                        : onChangeGroup(nodeId, options);
267
                }}
268
            />
269
        </Section>
270
    );
271
}
272

273
LayersSection.propTypes = {
1✔
274
    view: PropTypes.object,
275
    expandedSections: PropTypes.object,
276
    onExpandSection: PropTypes.func,
277
    onChange: PropTypes.func,
278
    resources: PropTypes.array,
279
    layers: PropTypes.array,
280
    groups: PropTypes.array,
281
    vectorLayers: PropTypes.array,
282
    updateLayerRequest: PropTypes.func,
283
    locale: PropTypes.string,
284
    onChangeLayer: PropTypes.func,
285
    onResetLayer: PropTypes.func,
286
    onChangeGroup: PropTypes.func,
287
    onResetGroup: PropTypes.func,
288
    showClipGeometries: PropTypes.bool,
289
    onShowClipGeometries: PropTypes.func,
290
    isTerrainAvailable: PropTypes.bool,
291
    isClippingAvailable: PropTypes.bool
292
};
293

294
LayersSection.contextTypes = {
1✔
295
    messages: PropTypes.object
296
};
297

298
export default LayersSection;
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