• 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

79.45
/web/client/plugins/TOC/components/DefaultLayer.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, { useRef, useLayoutEffect, useState } from 'react';
10
import { castArray, find } from 'lodash';
11
import { Glyphicon } from 'react-bootstrap';
12
import { isInsideResolutionsLimits, getLayerTypeGlyph } from '../../../utils/LayersUtils';
13
import { getLayerErrorMessage } from '../utils/TOCUtils';
14
import DropNode from './DropNode';
15
import DragNode from './DragNode';
16
import { VisualizationModes } from '../../../utils/MapTypeUtils';
17
import InlineLoader from './InlineLoader';
18
import WMSLegend from './WMSLegend';
19
import ArcGISLegend from './ArcGISLegend';
20
import OpacitySlider from './OpacitySlider';
21
import VectorLegend from './VectorLegend';
22
import VisibilityCheck from './VisibilityCheck';
23
import NodeHeader from './NodeHeader';
24
import NodeTool from './NodeTool';
25
import ExpandButton from './ExpandButton';
26
import Message from '../../../components/I18N/Message';
27
import FilterNodeTool from './FilterNodeTool';
28

29
const getLayerVisibilityWarningMessageId = (node, config = {}) => {
1✔
30
    if (config.visualizationMode === VisualizationModes._2D && ['3dtiles', 'model'].includes(node.type)) {
103!
31
        return 'toc.notVisibleSwitchTo3D';
×
32
    }
33
    if (config.visualizationMode === VisualizationModes._3D && ['cog'].includes(node.type)) {
103!
34
        return 'toc.notVisibleSwitchTo2D';
×
35
    }
36
    if (config.resolution !== undefined && !isInsideResolutionsLimits(node, config.resolution)) {
103!
37
        const maxResolution = node.maxResolution || Infinity;
×
38
        return config.resolution >=  maxResolution
×
39
            ? 'toc.notVisibleZoomIn'
40
            : 'toc.notVisibleZoomOut';
41
    }
42
    if (node.loadingError === 'Warning') {
103!
43
        return 'toc.toggleLayerVisibilityWarning';
×
44
    }
45
    return '';
103✔
46
};
47

48
const NodeLegend = ({
1✔
49
    config,
50
    node,
51
    visible,
52
    onChange
53
}) => {
54

55
    if (config?.layerOptions?.hideLegend) {
141✔
56
        return null;
14✔
57
    }
58
    const layerType = node?.type;
127✔
59
    if (['wfs', 'vector'].includes(layerType)) {
127✔
60
        const hasStyle = node?.style?.format === 'geostyler' && node?.style?.body?.rules?.length > 0;
6✔
61
        return hasStyle
6✔
62
            ? (
63
                <>
64
                    <li>
65
                        {visible ? <VectorLegend
3!
66
                            style={node?.style}
67
                        /> : null}
68
                    </li>
69
                </>
70
            )
71
            : null;
72
    }
73
    if (layerType === 'wms') {
121✔
74
        return (
93✔
75
            <>
76
                <li>
77
                    {visible ? <WMSLegend
93✔
78
                        node={node}
79
                        currentZoomLvl={config?.zoom}
80
                        scales={config?.scales}
81
                        language={config?.language}
82
                        {...config?.layerOptions?.legendOptions}
83
                        onChange={onChange}
84
                    /> : null}
85
                </li>
86
            </>
87
        );
88
    }
89
    if (layerType === 'arcgis') {
28!
UNCOV
90
        return (
×
91
            <>
92
                <li>
93
                    {visible ? <ArcGISLegend
×
94
                        node={node}
95
                    /> : null}
96
                </li>
97
            </>
98
        );
99
    }
100
    return null;
28✔
101
};
102

103
const NodeContent = ({
1✔
104
    error,
105
    config,
106
    node,
107
    visible,
108
    onChange,
109
    items
110
}) => {
111
    if (error) {
142✔
112
        return null;
1✔
113
    }
114
    const contentProps = {
141✔
115
        config,
116
        node,
117
        onChange,
118
        visible
119
    };
120
    return <>
141✔
121
        {items.map(({ Component, name }) => {
122
            return (<Component key={name} {...contentProps} />);
15✔
123
        })}
124
        <NodeLegend {...contentProps} />
125
    </>;
126
};
127
/**
128
 * DefaultLayerNode renders internal part of the layer node
129
 * @prop {string} node layer node properties
130
 * @prop {string} filterText filter to apply to the layer title
131
 * @prop {function} onChange return the changes of a specific node
132
 * @prop {object} config optional configuration available for the nodes
133
 * @prop {array} nodeToolItems list of node tool component to customize specific tool available on a node, expected structure [ { name, Component } ]
134
 * @prop {array} nodeContentItems list of node content component to customize specific content available after expanding the node, expected structure [ { name, Component } ]
135
 * @prop {function} onSelect return the current selected node on click event
136
 * @prop {string} nodeType node type
137
 * @prop {object} nodeTypes constant values for node types
138
 * @prop {object} error error message object
139
 * @prop {string} visibilityWarningMessageId message id for visibility warning tool
140
 * @prop {component} sortHandler component for the sort handler
141
 * @prop {component} visibilityCheck component for the visibility check
142
 * @prop {component} nodeIcon component for the node icon
143
 */
144
const DefaultLayerNode = ({
1✔
145
    node,
146
    filterText,
147
    onChange,
148
    sortHandler,
149
    config = {},
33✔
150
    nodeToolItems = [],
×
151
    nodeContentItems = [],
×
152
    onSelect,
153
    nodeType,
154
    nodeTypes,
155
    error,
156
    visibilityWarningMessageId,
157
    visibilityCheck,
158
    nodeIcon
159
}) => {
160

161
    const contentNode = useRef();
156✔
162
    const [hasContent, setHasContent] = useState(false);
156✔
163
    useLayoutEffect(() => {
156✔
164
        setHasContent(!!contentNode?.current?.children?.length);
110✔
165
    }, [error, node, config]);
166

167
    const forceExpanded = config?.expanded !== undefined;
156✔
168
    const expanded = forceExpanded ? config?.expanded : node?.expanded;
156✔
169

170
    const componentProps = {
156✔
171
        node,
172
        onChange,
173
        nodeType,
174
        nodeTypes,
175
        config,
176
        itemComponent: NodeTool
177
    };
178

179
    const filterNode = !config?.layerOptions?.hideFilter
156✔
180
        ? [{ name: 'FilterLayer', Component: FilterNodeTool }]
181
        : [];
182

183
    return (
156✔
184
        <>
185
            <NodeHeader
186
                node={node}
187
                className={nodeType}
188
                filterText={filterText}
189
                currentLocale={config?.currentLocale}
190
                tooltipOptions={config?.layerOptions?.tooltipOptions}
191
                onClick={onSelect}
192
                showTitleTooltip={config?.showTitleTooltip}
193
                showFullTitle={config?.showFullTitle}
194
                beforeTitle={
195
                    <>
196
                        {sortHandler}
197
                        <ExpandButton
198
                            hide={!(!forceExpanded && hasContent)}
304✔
199
                            expanded={expanded}
200
                            onChange={onChange}
201
                        />
202
                        {visibilityCheck}
203
                        {nodeIcon}
204
                    </>
205
                }
206
                afterTitle={
207
                    <>
208
                        {error ? <NodeTool tooltip={<Message msgId={error.msgId} msgParams={error.msgParams} />}  glyph="exclamation-mark" /> : null}
156✔
209
                        {visibilityWarningMessageId && <NodeTool glyph="info-sign" tooltipId={visibilityWarningMessageId} />}
156!
210
                        {config?.layerOptions?.indicators ? castArray(config.layerOptions.indicators).map( indicator =>
156✔
211
                            (indicator.type === 'dimension'
2!
212
                                ? find(node?.dimensions || [], indicator.condition) : false)
2!
213
                                ? indicator.glyph && <NodeTool onClick={false} key={indicator.key} glyph={indicator.glyph} {...indicator.props} />
4✔
214
                                : null)
215
                            : null}
216
                        {[ ...filterNode, ...nodeToolItems ].filter(({ selector = () => true }) => selector(componentProps)).map(({ Component, name }) => {
113✔
217
                            return (<Component key={name} {...componentProps} />);
113✔
218
                        })}
219
                    </>
220
                }
221
            />
222
            <ul ref={contentNode} style={!expanded || !hasContent ? { display: 'none' } : {}}>
332✔
223
                <NodeContent
224
                    error={error}
225
                    config={config}
226
                    node={node}
227
                    onChange={onChange}
228
                    visible={expanded}
229
                    items={nodeContentItems}
230
                />
231
            </ul>
232
            <OpacitySlider
233
                hide={!!error || config?.hideOpacitySlider || ['3dtiles', 'model'].includes(node?.type)}
455✔
234
                opacity={node?.opacity}
235
                disabled={!node.visibility}
236
                hideTooltip={!config.showOpacityTooltip}
UNCOV
237
                onChange={opacity => onChange({ opacity })}
×
238
            />
239
        </>
240
    );
241
};
242
/**
243
 * DefaultLayer renders the layer node representation
244
 * @prop {string} node layer node properties
245
 * @prop {string} parentId id of the parent node
246
 * @prop {number} index index of the node
247
 * @prop {object} sort sorting handlers
248
 * @prop {function} sort.beginDrag begin drag event
249
 * @prop {function} sort.hover hover dragging event
250
 * @prop {function} sort.drop drop event
251
 * @prop {function} filter if false hides the component
252
 * @prop {string} filterText filter to apply to the layer title
253
 * @prop {function} replaceNodeOptions function to change the node properties (used by LayersTree)
254
 * @prop {function} onChange return the changes of a specific node
255
 * @prop {function} onContextMenu return the context menu event of a specific node
256
 * @prop {function} onSelect return the current selected node on click event
257
 * @prop {function} getNodeStyle function to create a custom style (used by LayersTree)
258
 * @prop {function} getNodeClassName function to create a custom class name (used by LayersTree)
259
 * @prop {boolean} mutuallyExclusive if true changes the visibility icon to radio button
260
 * @prop {string} nodeType type of the current node
261
 * @prop {boolean} sortable if false hides the sort handler components
262
 * @prop {array} nodeItems list of node component to customize specific nodes, expected structure [ { name, Component, selector } ]
263
 * @prop {array} nodeToolItems list of node tool component to customize specific tool available on a node, expected structure [ { name, Component } ]
264
 * @prop {object} config optional configuration available for the nodes
265
 * @prop {number} config.resolution map resolution
266
 * @prop {array} config.resolutions list of available map resolutions
267
 * @prop {array} config.scales list of available map scales
268
 * @prop {number} config.zoom zoom level of the map
269
 * @prop {string} config.visualizationMode visualization mode of the map, `2D` or `3D`
270
 * @prop {boolean} config.hideOpacitySlider hide the opacity slider
271
 * @prop {string} config.language current language code
272
 * @prop {string} config.currentLocale current language code
273
 * @prop {boolean} config.showTitleTooltip show the title tooltip
274
 * @prop {boolean} config.showOpacityTooltip show the opacity tooltip
275
 * @prop {object} config.layerOptions specific options for layer nodes
276
 * @prop {object} config.layerOptions.tooltipOptions options for layer title tooltip
277
 * @prop {boolean} config.layerOptions.hideLegend hide the legend of the layer
278
 * @prop {object} config.layerOptions.legendOptions additional options for WMS legend
279
 * @prop {boolean} config.layerOptions.hideFilter hide the filter button
280
 */
281
const DefaultLayer = ({
1✔
282
    node: nodeProp,
283
    parentId,
UNCOV
284
    connectDragPreview = cmp => cmp,
×
UNCOV
285
    connectDragSource = cmp => cmp,
×
286
    index,
287
    sort,
288
    filter = () => true,
21✔
289
    filterText,
290
    replaceNodeOptions = node => node,
23✔
291
    onChange = () => {},
21✔
292
    onContextMenu = () => {},
23✔
293
    onSelect = () => {},
23✔
294
    getNodeStyle = () => {},
23✔
295
    getNodeClassName = () => '',
20✔
296
    mutuallyExclusive,
297
    nodeType,
298
    sortable,
299
    config,
300
    nodeToolItems = [],
77✔
301
    nodeContentItems = [],
90✔
302
    nodeItems = [],
88✔
303
    nodeTypes,
304
    theme
305
}) => {
306

307
    const replacedNode = replaceNodeOptions(nodeProp, nodeType);
103✔
308
    const error = replacedNode?.error ?? getLayerErrorMessage(replacedNode);
103✔
309
    const visibilityWarningMessageId = getLayerVisibilityWarningMessageId(replacedNode, config);
103✔
310
    const node = {
103✔
311
        ...replacedNode,
312
        error,
313
        visibilityWarningMessageId
314
    };
315

316
    function handleOnChange(options) {
317
        onChange({ node: nodeProp, nodeType, parentId, options });
7✔
318
    }
319

320
    function handleOnContextMenu(event) {
UNCOV
321
        event.stopPropagation();
×
UNCOV
322
        event.preventDefault();
×
UNCOV
323
        onContextMenu({ node: nodeProp, nodeType, parentId, event });
×
324
    }
325

326
    function handleOnSelect(event) {
327
        event.stopPropagation();
1✔
328
        event.preventDefault();
1✔
329
        onSelect({ node: nodeProp, nodeType, parentId, event });
1✔
330
    }
331

332
    if (!filter(node, nodeType, parentId)) {
103✔
333
        return null;
1✔
334
    }
335
    const icon = getLayerTypeGlyph(node);
102✔
336
    const layerNodeProp = {
102✔
337
        index,
338
        parentId,
339
        theme,
340
        node,
341
        filterText,
342
        onChange: handleOnChange,
343
        mutuallyExclusive,
344
        config,
345
        nodeToolItems,
346
        nodeContentItems,
347
        onSelect: handleOnSelect,
348
        nodeType,
349
        error,
350
        visibilityWarningMessageId,
351
        nodeTypes,
352
        sortHandler:
353
            sortable ? connectDragSource(
102✔
UNCOV
354
                <div className="grab-handle" onClick={(event) => event.stopPropagation()}>
×
355
                    <Glyphicon glyph="grab-handle" />
356
                </div>
357
            ) : <div className="grab-handle disabled" />,
358
        visibilityCheck: (<VisibilityCheck
359
            hide={config?.hideVisibilityButton}
360
            mutuallyExclusive={mutuallyExclusive}
361
            value={!!node?.visibility}
362
            onChange={(visibility) => {
363
                handleOnChange({ visibility });
3✔
364
            }}
365
        />),
366
        nodeIcon: <Glyphicon className="ms-node-icon" glyph={icon} />
367
    };
368
    const style = getNodeStyle(node, nodeType);
102✔
369
    const className = getNodeClassName(node, nodeType);
102✔
370
    const filteredNodeItems = nodeItems
102✔
UNCOV
371
        .filter(({ selector = () => false }) => selector(layerNodeProp));
×
372
    return (
102✔
373
        connectDragPreview(
374
            <li
375
                className={`ms-node ms-node-layer${node?.dragging ? ' dragging' : ''}${className ? ` ${className}` : ''}`}
204!
376
                style={style}
377
                onContextMenu={handleOnContextMenu}>
378
                <DropNode
379
                    sortable={sortable}
380
                    sort={sort}
381
                    nodeType={nodeType}
382
                    index={index}
383
                    id={node.id}
384
                    parentId={parentId}
385
                >
386
                    <InlineLoader loading={node?.loading}/>
387
                    {filteredNodeItems.length
102!
388
                        ? filteredNodeItems.map(({ Component, name }) => {
UNCOV
389
                            return (
×
390
                                <Component key={name} {...layerNodeProp} defaultLayerNodeComponent={DefaultLayerNode} />
391
                            );
392
                        })
393
                        : <DefaultLayerNode
394
                            {...layerNodeProp}
395
                        />}
396
                </DropNode>
397
            </li>
398
        )
399
    );
400
};
401

402
const DraggableDefaultLayer = (props) => <DragNode {...props} component={DefaultLayer}/>;
103✔
403

404
export default DraggableDefaultLayer;
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

© 2025 Coveralls, Inc