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

geosolutions-it / MapStore2 / 19331738526

13 Nov 2025 12:35PM UTC coverage: 76.903% (-0.02%) from 76.922%
19331738526

Pull #11681

github

web-flow
Merge e2a7a6a09 into ee68b3be2
Pull Request #11681: [Backport 2025.02.xx] - Fix #11613 and #11614 issues on mapview's TOC (#11656)

31990 of 49707 branches covered (64.36%)

27 of 50 new or added lines in 3 files covered. (54.0%)

4 existing lines in 3 files now uncovered.

39762 of 51704 relevant lines covered (76.9%)

37.64 hits per line

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

13.64
/web/client/components/mapviews/MapViewSettings.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 from 'react';
10
import { Alert } from 'react-bootstrap';
11
import DescriptionSection from './settings/DescriptionSection';
12
import PositionsSection from './settings/PositionsSection';
13
import AnimationSection from './settings/AnimationSection';
14
import MaskSection from './settings/MaskSection';
15
import GlobeTranslucencySection from './settings/GlobeTranslucencySection';
16
import LayersSection from './settings/LayersSection';
17
import { getResourceFromLayer } from '../../api/MapViews';
18
import { ViewSettingsTypes } from '../../utils/MapViewsUtils';
19
import Message from '../I18N/Message';
20
import useBatchedUpdates from '../../hooks/useBatchedUpdates';
21

22
const sections = {
1✔
23
    [ViewSettingsTypes.DESCRIPTION]: DescriptionSection,
24
    [ViewSettingsTypes.POSITION]: PositionsSection,
25
    [ViewSettingsTypes.ANIMATION]: AnimationSection,
26
    [ViewSettingsTypes.MASK]: MaskSection,
27
    [ViewSettingsTypes.GLOBE_TRANSLUCENCY]: GlobeTranslucencySection,
28
    [ViewSettingsTypes.LAYERS_OPTIONS]: LayersSection
29
};
30

31
function ViewSettings({
32
    view,
33
    api,
34
    layers = [],
2✔
35
    groups = [],
2✔
36
    onChange,
37
    onUpdateResource = () => { },
2✔
38
    onCaptureView,
39
    locale,
40
    resources = [],
2✔
41
    expandedSections = {},
2✔
42
    onExpandSection = () => {},
2✔
43
    showClipGeometries,
44
    onShowClipGeometries = () => {}
2✔
45
}) {
46

47
    const availableVectorLayers = layers
2✔
48
        .filter(({ type, features }) => {
49
            if (type === 'wfs') {
×
50
                return true;
×
51
            }
52
            if (type === 'vector') {
×
53
                return !!features?.find(({ geometry }) => ['Polygon', 'MultiPolygon'].includes(geometry?.type));
×
54
            }
55
            return false;
×
56
        });
57

58
    /**
59
     * Custom batching logic for layers and groups.
60
     * Accumulates changes in an object with separate keys for layers and groups,
61
     * then applies them all at once to prevent race conditions.
62
     */
63
    const [batchedUpdate] = useBatchedUpdates(
2✔
64
        (changes) => {
NEW
65
            const updatedView = { ...view };
×
NEW
66
            const { layers: layerChanges = {}, groups: groupChanges = {} } = changes;
×
67

68
            // Apply layer changes
NEW
69
            if (Object.keys(layerChanges).length > 0) {
×
NEW
70
                const updatedLayers = [...(view?.layers || [])];
×
NEW
71
                Object.entries(layerChanges).forEach(([layerId, layerOptions]) => {
×
NEW
72
                    const layerIndex = updatedLayers.findIndex(layer => layer.id === layerId);
×
NEW
73
                    if (layerIndex >= 0) {
×
NEW
74
                        updatedLayers[layerIndex] = { ...updatedLayers[layerIndex], ...layerOptions };
×
75
                    } else {
NEW
76
                        updatedLayers.push({ id: layerId, ...layerOptions });
×
77
                    }
78
                });
NEW
79
                updatedView.layers = updatedLayers;
×
80
            }
81

82
            // Apply group changes
NEW
83
            if (Object.keys(groupChanges).length > 0) {
×
NEW
84
                const updatedGroups = [...(view?.groups || [])];
×
NEW
85
                Object.entries(groupChanges).forEach(([groupId, groupOptions]) => {
×
NEW
86
                    const groupIndex = updatedGroups.findIndex(group => group.id === groupId);
×
NEW
87
                    if (groupIndex >= 0) {
×
NEW
88
                        updatedGroups[groupIndex] = { ...updatedGroups[groupIndex], ...groupOptions };
×
89
                    } else {
NEW
90
                        updatedGroups.push({ id: groupId, ...groupOptions });
×
91
                    }
92
                });
NEW
93
                updatedView.groups = updatedGroups;
×
94
            }
95

NEW
96
            onChange(updatedView);
×
97
        },
98
        {
99
            delay: 0,
100
            reducer: (accumulated, type, id, options) => {
NEW
101
                const current = accumulated || { layers: {}, groups: {} };
×
NEW
102
                return {
×
103
                    layers: type === 'layers' ? { ...current.layers, [id]: { ...current.layers[id], ...options } } : current.layers,
×
104
                    groups: type === 'groups' ? { ...current.groups, [id]: { ...current.groups[id], ...options } } : current.groups
×
105
                };
106
            }
107
        }
108
    );
109

110
    function handleChange(options) {
111
        onChange({ ...view, ...options });
×
112
    }
113

114
    /**
115
     * Handles layer changes with batching to prevent race conditions.
116
     * Multiple calls are batched and flushed together.
117
     */
118
    function handleChangeLayer(layerId, options) {
NEW
119
        batchedUpdate('layers', layerId, options);
×
120
    }
121

122
    function handleResetLayer(layerId) {
123
        const viewLayers = view?.layers?.filter(vLayer => vLayer.id !== layerId);
×
124
        onChange({
×
125
            ...view,
126
            layers: viewLayers
127
        });
128
    }
129

130
    /**
131
     * Handles group changes with batching to prevent race conditions.
132
     * Multiple calls are batched and flushed together.
133
     */
134
    function handleChangeGroup(groupId, options) {
NEW
135
        batchedUpdate('groups', groupId, options);
×
136
    }
137

138
    function handleResetGroup(groupId) {
139
        const viewGroups = view?.groups?.filter(vGroup => vGroup.id !== groupId);
×
140
        onChange({
×
141
            ...view,
142
            groups: viewGroups
143
        });
144
    }
145

146
    function updateLayerRequest({ layer, inverse = false, offset = 0 } = {}) {
×
147
        return getResourceFromLayer({
×
148
            layer,
149
            inverse,
150
            offset,
151
            resources
152
        }).then(({ updated, ...resource }) => {
153
            if (updated) {
×
154
                onUpdateResource(resource.id, resource.data);
×
155
            }
156
            return resource.id;
×
157
        });
158
    }
159

160
    return (
2✔
161
        <div className="ms-map-views-settings">
162
            <form>
163
                {api?.options?.settings?.map((key) => {
164
                    const SectionComponent = sections[key];
6✔
165
                    return (
6✔
166
                        <SectionComponent
167
                            key={key}
168
                            isTerrainAvailable={!(api?.options?.unsupportedLayers || []).includes('terrain')}
12✔
169
                            isClippingAvailable={!(api?.options?.unsupportedLayers || []).includes('3dtiles')}
12✔
170
                            computeViewCoordinates={api.computeViewCoordinates}
171
                            view={view}
172
                            expandedSections={expandedSections}
173
                            onExpandSection={onExpandSection}
174
                            onChange={handleChange}
175
                            resources={resources}
176
                            layers={layers.filter(({ type }) => !(api?.options?.unsupportedLayers || []).includes(type))}
×
177
                            groups={groups}
178
                            vectorLayers={availableVectorLayers}
179
                            updateLayerRequest={updateLayerRequest}
180
                            locale={locale}
181
                            onChangeLayer={handleChangeLayer}
182
                            onResetLayer={handleResetLayer}
183
                            onChangeGroup={handleChangeGroup}
184
                            onResetGroup={handleResetGroup}
185
                            showClipGeometries={showClipGeometries}
186
                            onShowClipGeometries={onShowClipGeometries}
187
                            onCaptureView={onCaptureView}
188
                        />
189
                    );
190
                })}
191
            </form>
192
            {(view?.globeTranslucency?.enabled && view?.mask?.enabled) &&
2!
193
                <Alert bsStyle="warning" style={{ bottom: 0, position: 'sticky', margin: 0, zIndex: 30 }}>
194
                    <Message msgId="mapViews.maskWithGlobeTranslucencyWarning" />
195
                </Alert>}
196
        </div>
197
    );
198
}
199

200
export default ViewSettings;
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