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

geosolutions-it / MapStore2 / 16175372106

09 Jul 2025 04:51PM UTC coverage: 76.886% (-0.003%) from 76.889%
16175372106

push

github

web-flow
#8338: Implement a terrain layer selector (#11087)

Fix #8338: Implement a terrain layer selector

---------

Co-authored-by: allyoucanmap <stefano.bovio@geosolutionsgroup.com>

31281 of 48696 branches covered (64.24%)

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

2 existing lines in 1 file now uncovered.

38845 of 50523 relevant lines covered (76.89%)

36.4 hits per line

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

77.14
/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 PropTypes from 'prop-types';
12
import React from 'react';
13
import { Glyphicon, Panel } from 'react-bootstrap';
14
import { connect } from 'react-redux';
15
import { branch, compose, defaultProps, renderComponent, withProps } from 'recompose';
16
import { createStructuredSelector } from 'reselect';
17

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

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

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

132

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

156

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

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

189
    static defaultProps = {
1✔
190
        id: "mapstore-metadata-explorer",
191
        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" }],
192
        active: false,
193
        wrap: false,
194
        modal: true,
195
        wrapWithPanel: false,
196
        panelStyle: {
197
            zIndex: 100,
198
            overflow: "hidden",
199
            height: "100%"
200
        },
201
        panelClassName: "catalog-panel",
202
        closeCatalog: () => {},
203
        onInitPlugin: () => {},
204
        closeGlyph: "1-close",
205
        zoomToLayer: true,
206

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

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

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

265
const MetadataExplorerComponentWrapper = (props, context) => {
1✔
266
    const { loadedPlugins } = context;
6✔
267
    const addonsItems = usePluginItems({ items: props.items, loadedPlugins }).filter(({ target }) => target === 'url-addon');
6✔
268
    return <MetadataExplorerComponent {...props} addonsItems={addonsItems}/>;
6✔
269
};
270

271

272
MetadataExplorerComponentWrapper.contextTypes = {
1✔
273
    loadedPlugins: PropTypes.object
274
};
275

276
const MetadataExplorerPlugin = connect(metadataExplorerSelector, {
1✔
277
    clearModal: clearModalParameters,
278
    onSearch: textSearch,
279
    onLayerAdd: addLayer,
280
    closeCatalog: catalogClose,
281
    onChangeFormat: changeCatalogFormat,
282
    onChangeServiceFormat: changeServiceFormat,
283
    onChangeUrl: changeUrl,
284
    onChangeType: changeType,
285
    onChangeTitle: changeTitle,
286
    onChangeMetadataTemplate: changeMetadataTemplate,
287
    onChangeText: changeText,
288
    onChangeServiceProperty: changeServiceProperty,
289
    onChangeSelectedService: changeSelectedService,
290
    onChangeCatalogMode: changeCatalogMode,
291
    onAddService: addService,
292
    onToggleAdvancedSettings: toggleAdvancedSettings,
293
    onToggleThumbnail: toggleThumbnail,
294
    onToggleTemplate: toggleTemplate,
295
    onDeleteService: deleteService,
296
    onError: addLayerError,
297
    // add layer action to pass to the layers
298
    onAddBackgroundProperties: addBackgroundProperties,
299
    onFocusServicesList: focusServicesList,
300
    onPropertiesChange: changeLayerProperties,
301
    onAddBackground: backgroundAdded,
302
    onFormatOptionsFetch: formatOptionsFetch,
303
    onToggle: toggleControl.bind(null, 'backgroundSelector', null),
304
    onLayerChange: setControlProperty.bind(null, 'backgroundSelector'),
305
    onStartChange: setControlProperty.bind(null, 'backgroundSelector', 'start'),
306
    setNewServiceStatus,
307
    onShowSecurityModal: setShowModalStatus,
308
    onSetProtectedServices: setProtectedServices,
309
    onInitPlugin: initPlugin
310
})(MetadataExplorerComponentWrapper);
311

312
const AddLayerButton = connect(() => ({}), {
3✔
313
    onClick: setControlProperties.bind(null, 'metadataexplorer', 'enabled', true, 'group')
314
})(({
315
    onClick,
316
    selectedNodes,
317
    status,
318
    itemComponent,
319
    statusTypes,
320
    config,
321
    ...props
322
}) => {
323
    const ItemComponent = itemComponent;
3✔
324

325
    // deprecated TOC configuration
326
    if (config.activateAddLayerButton === false) {
3!
327
        return null;
×
328
    }
329

330
    if ([statusTypes.DESELECT, statusTypes.GROUP].includes(status)) {
3!
331
        const group = selectedNodes?.[0]?.id;
3✔
332
        return (
3✔
333
            <ItemComponent
334
                {...props}
335
                glyph="add-layer"
336
                tooltipId={status === statusTypes.GROUP ? 'toc.addLayerToGroup' : 'toc.addLayer'}
3!
337
                onClick={() => onClick(group)}
×
338
            />
339
        );
340
    }
341
    return null;
×
342
});
343

344
export const BackgroundSelectorAdd = connect(
1✔
345
    createStructuredSelector({
346
        enabled: state => state.controls && state.controls.metadataexplorer && state.controls.metadataexplorer.enabled
1!
347
    }),
348
    {
349
        onAdd: addBackground
350
    }
351
)(({ source, onAdd = () => {}, itemComponent, canEdit, enabled }) => {
×
352
    const ItemComponent = itemComponent;
1✔
353
    return canEdit ? (
1!
354
        <ItemComponent
355
            disabled={!!enabled}
356
            onClick={() => {
NEW
357
                onAdd(source || 'backgroundSelector');
×
358
            }}
359
            tooltipId="backgroundSelector.addTooltip"
360
            glyph="plus"
361
        />
362
    ) : null;
363
});
364

365
/**
366
 * MetadataExplorer (Catalog) plugin. Shows the catalogs results (CSW, WMS, WMTS, TMS, WFS and COG).
367
 * Some useful flags in `localConfig.json`:
368
 * - `noCreditsFromCatalog`: avoid add credits (attribution) from catalog
369
 *
370
 * @class
371
 * @name MetadataExplorer
372
 * @memberof plugins
373
 * @prop {string} cfg.hideThumbnail shows/hides thumbnail
374
 * @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" }]`.
375
 * `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)
376
 * @prop {object} cfg.hideIdentifier shows/hides identifier
377
 * @prop {boolean} cfg.hideExpand shows/hides full description button
378
 * @prop {number} cfg.zoomToLayer enable/disable zoom to layer when added
379
 * @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))
380
 * @prop {number} [delayAutoSearch] time in ms passed after a search is triggered by filter changes, default 1000
381
 * @prop {object[]} items this property contains the items injected from the other plugins,
382
 * using the `url-addon` option in the plugin that want to inject the components.
383
 * You can select the position where to insert the components adding the `target` property.
384
 * The allowed targets are:
385
 * - `url-addon` target add an addon button in the url field of catalog form (in main viewer) in edit mode
386
 * ```javascript
387
 * const MyAddonComponent = connect(null,
388
 * {
389
 *     onSetShowModal: setShowModalStatus,
390
 *    }
391
 * )(({
392
 *    onSetShowModal, // opens a modal to enter credentials
393
 *    itemComponent // default component that provides a consistent UI (see UrlAddon in MainForm.jsx)
394
 *    }) => {
395
 *    const Component = itemComponent;
396
 *    return (<Component
397
 *        onClick={(value) => {
398
 *            onSetShowModal(true);
399
 *        }}
400
 *        btnClassName={condition ? "btn-success" : ""}
401
 *        glyph="glyph"
402
 *        tooltipId="path"
403
 *    />  );
404
 * });
405
 * createPlugin(
406
 *  'MyPlugin',
407
 *  {
408
 *      containers: {
409
 *          MetadataExplorer: {
410
 *              name: "TOOLNAME", // a name for the current tool.
411
 *              target: "url-addon", // the target where to insert the component
412
 *              Component: MyAddonComponent
413
 *          },
414
 * // ...
415
 * ```
416
 */
417
export default {
418
    MetadataExplorerPlugin: Object.assign(MetadataExplorerPlugin, {
419
        BurgerMenu: {
420
            name: 'metadataexplorer',
421
            position: 5,
422
            text: <Message msgId="catalog.title"/>,
423
            tooltip: "catalog.tooltip",
424
            icon: <Glyphicon glyph="folder-open"/>,
425
            action: setControlProperty.bind(null, "metadataexplorer", "enabled", true, true),
426
            doNotHide: true,
427
            priority: 1
428
        },
429
        BackgroundSelector: {
430
            name: 'MetadataExplorer',
431
            doNotHide: true,
432
            priority: 1,
433
            Component: BackgroundSelectorAdd,
434
            target: 'background-toolbar'
435
        },
436
        TOC: {
437
            name: 'MetadataExplorer',
438
            doNotHide: true,
439
            priority: 1,
440
            target: 'toolbar',
441
            Component: AddLayerButton,
442
            position: 2
443
        },
444
        SidebarMenu: {
445
            name: 'metadataexplorer',
446
            position: 5,
447
            text: <Message msgId="catalog.title"/>,
448
            tooltip: "catalog.tooltip",
449
            icon: <Glyphicon glyph="folder-open"/>,
450
            action: setControlProperty.bind(null, "metadataexplorer", "enabled", true, true),
451
            selector: (state) => ({
×
452
                style: { display: burgerMenuSelector(state) ? 'none' : null }
×
453
            }),
454
            toggle: true,
455
            doNotHide: true,
456
            priority: 1
457
        }
458
    }),
459
    reducers: {catalog: require('../reducers/catalog').default},
460
    epics: require("../epics/catalog").default(API)
461
};
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