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

geosolutions-it / MapStore2 / 16418038671

21 Jul 2025 01:16PM UTC coverage: 76.999% (-0.001%) from 77.0%
16418038671

Pull #11347

github

web-flow
Merge dba2ede17 into 8c6af9117
Pull Request #11347: [Backport c040-2025.01.xx] #8338: Implement a terrain layer selector (#11087)

31046 of 48207 branches covered (64.4%)

124 of 152 new or added lines in 10 files covered. (81.58%)

2 existing lines in 1 file now uncovered.

38431 of 49911 relevant lines covered (77.0%)

36.75 hits per line

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

73.33
/web/client/plugins/MetadataExplorer.jsx
1
/*
2
 * Copyright 2017, 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 './metadataexplorer/css/style.css';
10

11
import assign from 'object-assign';
12
import PropTypes from 'prop-types';
13
import React from 'react';
14
import { Glyphicon, Panel } from 'react-bootstrap';
15
import { connect } from 'react-redux';
16
import { branch, compose, defaultProps, renderComponent, withProps } from 'recompose';
17
import { createStructuredSelector } from 'reselect';
18

19
import { addBackground, addBackgroundProperties, backgroundAdded, clearModalParameters } from '../actions/backgroundselector';
20
import {
21
    addLayer,
22
    addLayerError,
23
    addService,
24
    catalogClose,
25
    changeCatalogFormat,
26
    changeCatalogMode,
27
    changeMetadataTemplate,
28
    changeSelectedService,
29
    changeServiceFormat,
30
    changeServiceProperty,
31
    changeText,
32
    changeTitle,
33
    changeType,
34
    changeUrl,
35
    deleteService,
36
    focusServicesList,
37
    formatOptionsFetch,
38
    textSearch,
39
    toggleAdvancedSettings,
40
    toggleTemplate,
41
    toggleThumbnail,
42
    setNewServiceStatus,
43
    initPlugin
44
} from '../actions/catalog';
45
import { setControlProperty, toggleControl, setControlProperties } from '../actions/controls';
46
import { changeLayerProperties } from '../actions/layers';
47
import API from '../api/catalog';
48
import CatalogComp from '../components/catalog/Catalog';
49
import CatalogServiceEditor from '../components/catalog/CatalogServiceEditor';
50
import Message from '../components/I18N/Message';
51
import { metadataSourceSelector, modalParamsSelector } from '../selectors/backgroundselector';
52
import {
53
    isActiveSelector,
54
    authkeyParamNameSelector,
55
    groupSelector,
56
    layerErrorSelector,
57
    loadingErrorSelector,
58
    loadingSelector,
59
    modeSelector,
60
    newServiceSelector,
61
    newServiceTypeSelector,
62
    pageSizeSelector,
63
    resultSelector,
64
    savingSelector,
65
    searchOptionsSelector,
66
    searchTextSelector,
67
    selectedServiceLayerOptionsSelector,
68
    selectedServiceSelector,
69
    selectedServiceTypeSelector,
70
    serviceListOpenSelector,
71
    servicesSelector,
72
    servicesSelectorWithBackgrounds,
73
    tileSizeOptionsSelector,
74
    formatsLoadingSelector,
75
    getSupportedFormatsSelector,
76
    getSupportedGFIFormatsSelector,
77
    getNewServiceStatusSelector,
78
    showFormatErrorSelector,
79
    canEditServiceSelector
80
} from '../selectors/catalog';
81
import { layersSelector } from '../selectors/layers';
82
import { currentLocaleSelector, currentMessagesSelector } from '../selectors/locale';
83
import {burgerMenuSelector} from "../selectors/controls";
84
import { isLocalizedLayerStylesEnabledSelector } from '../selectors/localizedLayerStyles';
85
import { projectionSelector } from '../selectors/map';
86
import { mapLayoutValuesSelector } from '../selectors/maplayout';
87
import ResponsivePanel from "../components/misc/panels/ResponsivePanel";
88
import { DEFAULT_PANEL_WIDTH } from '../utils/LayoutUtils';
89

90
export const DEFAULT_ALLOWED_PROVIDERS = ["OpenStreetMap", "OpenSeaMap", "Stamen"];
1✔
91

92
const metadataExplorerSelector = createStructuredSelector({
1✔
93
    searchOptions: searchOptionsSelector,
94
    showFormatError: showFormatErrorSelector,
95
    result: resultSelector,
96
    loadingError: loadingErrorSelector,
97
    selectedService: selectedServiceSelector,
98
    mode: modeSelector,
99
    services: servicesSelector,
100
    servicesWithBackgrounds: servicesSelectorWithBackgrounds,
101
    layerError: layerErrorSelector,
102
    active: isActiveSelector,
103
    dockStyle: state => mapLayoutValuesSelector(state, { height: true, right: true }, true),
6✔
104
    searchText: searchTextSelector,
105
    group: groupSelector,
106
    source: metadataSourceSelector,
107
    layers: layersSelector,
108
    modalParams: modalParamsSelector,
109
    authkeyParamNames: authkeyParamNameSelector,
110
    saving: savingSelector,
111
    openCatalogServiceList: serviceListOpenSelector,
112
    service: newServiceSelector,
113
    format: newServiceTypeSelector,
114
    selectedFormat: selectedServiceTypeSelector,
115
    options: searchOptionsSelector,
116
    layerOptions: selectedServiceLayerOptionsSelector,
117
    tileSizeOptions: tileSizeOptionsSelector,
118
    currentLocale: currentLocaleSelector,
119
    locales: currentMessagesSelector,
120
    pageSize: pageSizeSelector,
121
    loading: loadingSelector,
122
    crs: projectionSelector,
123
    isLocalizedLayerStylesEnabled: isLocalizedLayerStylesEnabledSelector,
124
    formatsLoading: formatsLoadingSelector,
125
    formatOptions: getSupportedFormatsSelector,
126
    infoFormatOptions: getSupportedGFIFormatsSelector,
127
    isNewServiceAdded: getNewServiceStatusSelector,
128
    canEdit: canEditServiceSelector
129
});
130

131

132
const Catalog = compose(
1✔
133
    withProps(({ result, selectedFormat, options, layerOptions, services, selectedService, locales}) => ({
×
134
        records: result && API[selectedFormat].getCatalogRecords(result, { ...options, layerOptions, service: services[selectedService] }, locales) || []
×
135
    })),
136
    defaultProps({
137
        buttonStyle: {
138
            marginBottom: "10px",
139
            marginRight: "5px"
140
        },
141
        formatOptions: [],
142
        advancedRasterStyles: {
143
            display: 'flex',
144
            alignItems: 'center',
145
            paddingTop: 15,
146
            borderTop: '1px solid #ddd'
147
        }
148
    }),
149
    branch(
150
        ({mode}) => mode === "edit",
×
151
        renderComponent(CatalogServiceEditor)
152
    )
153
)(CatalogComp);
154

155

156
class MetadataExplorerComponent extends React.Component {
157
    static propTypes = {
1✔
158
        id: PropTypes.string,
159
        source: PropTypes.string,
160
        active: PropTypes.bool,
161
        searchOnStartup: PropTypes.bool,
162
        serviceTypes: PropTypes.array,
163
        wrap: PropTypes.bool,
164
        wrapWithPanel: PropTypes.bool,
165
        panelStyle: PropTypes.object,
166
        panelClassName: PropTypes.string,
167
        closeCatalog: PropTypes.func,
168
        closeGlyph: PropTypes.string,
169
        buttonStyle: PropTypes.object,
170
        services: PropTypes.object,
171
        servicesWithBackgrounds: PropTypes.object,
172
        selectedService: PropTypes.string,
173
        style: PropTypes.object,
174
        dockProps: PropTypes.object,
175
        zoomToLayer: PropTypes.bool,
176
        isLocalizedLayerStylesEnabled: PropTypes.bool,
177

178
        // side panel properties
179
        width: PropTypes.number,
180
        dockStyle: PropTypes.object,
181
        group: PropTypes.string,
182
        onInitPlugin: PropTypes.func,
183
        editingAllowedRoles: PropTypes.array,
184
        editingAllowedGroups: PropTypes.array
185
    };
186

187
    static defaultProps = {
1✔
188
        id: "mapstore-metadata-explorer",
189
        serviceTypes: [{ name: "csw", label: "CSW" }, { name: "wms", label: "WMS" }, { name: "wmts", label: "WMTS" }, { name: "tms", label: "TMS", allowedProviders: DEFAULT_ALLOWED_PROVIDERS }, { name: "wfs", label: "WFS" }, { name: "3dtiles", label: "3D Tiles" }, {name: "model", label: "IFC Model"}, { name: "arcgis", label: "ArcGIS" }],
190
        active: false,
191
        wrap: false,
192
        modal: true,
193
        wrapWithPanel: false,
194
        panelStyle: {
195
            zIndex: 100,
196
            overflow: "hidden",
197
            height: "100%"
198
        },
199
        panelClassName: "catalog-panel",
200
        closeCatalog: () => {},
201
        onInitPlugin: () => {},
202
        closeGlyph: "1-close",
203
        zoomToLayer: true,
204

205
        // side panel properties
206
        width: DEFAULT_PANEL_WIDTH,
207
        dockProps: {
208
            dimMode: "none",
209
            fluid: false,
210
            position: "right",
211
            zIndex: 1030
212
        },
213
        dockStyle: {},
214
        group: null,
215
        services: {},
216
        servicesWithBackgrounds: {},
217
        editingAllowedRoles: ["ALL"]
218
    };
219

220
    componentDidMount() {
221
        this.props.onInitPlugin({
3✔
222
            editingAllowedRoles: this.props.editingAllowedRoles,
223
            editingAllowedGroups: this.props.editingAllowedGroups
224
        });
225
    }
226

227
    componentWillUnmount() {
228
        this.props.closeCatalog();
3✔
229
    }
230
    render() {
231
        // TODO: separate catalog props from Container props (and handlers)
232
        const layerBaseConfig = {
6✔
233
            group: this.props.group || undefined
12✔
234
        };
235
        const panel = (
236
            <Catalog
6✔
237
                layerBaseConfig={layerBaseConfig}
238
                {...this.props}
239
                services={this.props.source === 'backgroundSelector' ? this.props.servicesWithBackgrounds : this.props.services}
6!
240
            />
241
        );
242
        return (
6✔
243
            <ResponsivePanel
244
                containerStyle={this.props.dockStyle}
245
                containerId="catalog-root"
246
                containerClassName={this.props.active ? 'catalog-active' : ''}
6!
247
                open={this.props.active}
248
                size={this.props.width}
249
                position="right"
250
                title={<Message msgId="catalog.title"/>}
251
                onClose={() => this.props.closeCatalog()}
×
252
                glyph="folder-open"
253
                style={this.props.dockStyle}
254
            >
255
                <Panel id={this.props.id} style={this.props.panelStyle} className={this.props.panelClassName}>
256
                    {panel}
257
                </Panel>
258
            </ResponsivePanel>
259
        );
260
    }
261
}
262

263
const MetadataExplorerPlugin = connect(metadataExplorerSelector, {
1✔
264
    clearModal: clearModalParameters,
265
    onSearch: textSearch,
266
    onLayerAdd: addLayer,
267
    closeCatalog: catalogClose,
268
    onChangeFormat: changeCatalogFormat,
269
    onChangeServiceFormat: changeServiceFormat,
270
    onChangeUrl: changeUrl,
271
    onChangeType: changeType,
272
    onChangeTitle: changeTitle,
273
    onChangeMetadataTemplate: changeMetadataTemplate,
274
    onChangeText: changeText,
275
    onChangeServiceProperty: changeServiceProperty,
276
    onChangeSelectedService: changeSelectedService,
277
    onChangeCatalogMode: changeCatalogMode,
278
    onAddService: addService,
279
    onToggleAdvancedSettings: toggleAdvancedSettings,
280
    onToggleThumbnail: toggleThumbnail,
281
    onToggleTemplate: toggleTemplate,
282
    onDeleteService: deleteService,
283
    onError: addLayerError,
284
    // add layer action to pass to the layers
285
    onAddBackgroundProperties: addBackgroundProperties,
286
    onFocusServicesList: focusServicesList,
287
    onPropertiesChange: changeLayerProperties,
288
    onAddBackground: backgroundAdded,
289
    onFormatOptionsFetch: formatOptionsFetch,
290
    onToggle: toggleControl.bind(null, 'backgroundSelector', null),
291
    onLayerChange: setControlProperty.bind(null, 'backgroundSelector'),
292
    onStartChange: setControlProperty.bind(null, 'backgroundSelector', 'start'),
293
    setNewServiceStatus,
294
    onInitPlugin: initPlugin
295
})(MetadataExplorerComponent);
296

297
const AddLayerButton = connect(() => ({}), {
3✔
298
    onClick: setControlProperties.bind(null, 'metadataexplorer', 'enabled', true, 'group')
299
})(({
300
    onClick,
301
    selectedNodes,
302
    status,
303
    itemComponent,
304
    statusTypes,
305
    config,
306
    ...props
307
}) => {
308
    const ItemComponent = itemComponent;
3✔
309

310
    // deprecated TOC configuration
311
    if (config.activateAddLayerButton === false) {
3!
312
        return null;
×
313
    }
314

315
    if ([statusTypes.DESELECT, statusTypes.GROUP].includes(status)) {
3!
316
        const group = selectedNodes?.[0]?.id;
3✔
317
        return (
3✔
318
            <ItemComponent
319
                {...props}
320
                glyph="add-layer"
321
                tooltipId={status === statusTypes.GROUP ? 'toc.addLayerToGroup' : 'toc.addLayer'}
3!
322
                onClick={() => onClick(group)}
×
323
            />
324
        );
325
    }
326
    return null;
×
327
});
328

329
export const BackgroundSelectorAdd = connect(
1✔
330
    createStructuredSelector({
331
        enabled: state => state.controls && state.controls.metadataexplorer && state.controls.metadataexplorer.enabled
1!
332
    }),
333
    {
334
        onAdd: addBackground
335
    }
336
)(({ source, onAdd = () => {}, itemComponent, canEdit, enabled }) => {
×
337
    const ItemComponent = itemComponent;
1✔
338
    return canEdit ? (
1!
339
        <ItemComponent
340
            disabled={!!enabled}
341
            onClick={() => {
NEW
342
                onAdd(source || 'backgroundSelector');
×
343
            }}
344
            tooltipId="backgroundSelector.addTooltip"
345
            glyph="plus"
346
        />
347
    ) : null;
348
});
349

350
/**
351
 * MetadataExplorer (Catalog) plugin. Shows the catalogs results (CSW, WMS, WMTS, TMS, WFS and COG).
352
 * Some useful flags in `localConfig.json`:
353
 * - `noCreditsFromCatalog`: avoid add credits (attribution) from catalog
354
 *
355
 * @class
356
 * @name MetadataExplorer
357
 * @memberof plugins
358
 * @prop {string} cfg.hideThumbnail shows/hides thumbnail
359
 * @prop {object[]} cfg.serviceTypes Service types available to add a new catalog. default: `[{ name: "csw", label: "CSW" }, { name: "wms", label: "WMS" }, { name: "wmts", label: "WMTS" }, { name: "tms", label: "TMS", allowedProviders },{ name: "wfs", label: "WFS" }]`.
360
 * `allowedProviders` is a whitelist of tileProviders from ConfigProvider.js. you can set a global variable allowedProviders in localConfig.json to set it up globally. You can configure it to "ALL" to get all the list (at your own risk, some services could change or not be available anymore)
361
 * @prop {object} cfg.hideIdentifier shows/hides identifier
362
 * @prop {boolean} cfg.hideExpand shows/hides full description button
363
 * @prop {number} cfg.zoomToLayer enable/disable zoom to layer when added
364
 * @prop {number} cfg.autoSetVisibilityLimits if true, allows fetching and setting visibility limits of the layer from capabilities on layer add (Note: The default configuration value is applied only on new catalog service (WMS/CSW))
365
 * @prop {number} [delayAutoSearch] time in ms passed after a search is triggered by filter changes, default 1000
366
 */
367
export default {
368
    MetadataExplorerPlugin: assign(MetadataExplorerPlugin, {
369
        BurgerMenu: {
370
            name: 'metadataexplorer',
371
            position: 5,
372
            text: <Message msgId="catalog.title"/>,
373
            tooltip: "catalog.tooltip",
374
            icon: <Glyphicon glyph="folder-open"/>,
375
            action: setControlProperty.bind(null, "metadataexplorer", "enabled", true, true),
376
            doNotHide: true,
377
            priority: 1
378
        },
379
        BackgroundSelector: {
380
            name: 'MetadataExplorer',
381
            doNotHide: true,
382
            priority: 1,
383
            Component: BackgroundSelectorAdd,
384
            target: 'background-toolbar'
385
        },
386
        TOC: {
387
            name: 'MetadataExplorer',
388
            doNotHide: true,
389
            priority: 1,
390
            target: 'toolbar',
391
            Component: AddLayerButton,
392
            position: 2
393
        },
394
        SidebarMenu: {
395
            name: 'metadataexplorer',
396
            position: 5,
397
            text: <Message msgId="catalog.title"/>,
398
            tooltip: "catalog.tooltip",
399
            icon: <Glyphicon glyph="folder-open"/>,
400
            action: setControlProperty.bind(null, "metadataexplorer", "enabled", true, true),
401
            selector: (state) => ({
×
402
                style: { display: burgerMenuSelector(state) ? 'none' : null }
×
403
            }),
404
            toggle: true,
405
            doNotHide: true,
406
            priority: 1
407
        }
408
    }),
409
    reducers: {catalog: require('../reducers/catalog').default},
410
    epics: require("../epics/catalog").default(API)
411
};
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