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

geosolutions-it / MapStore2 / 15422327504

03 Jun 2025 04:08PM UTC coverage: 76.952% (-0.04%) from 76.993%
15422327504

Pull #11024

github

web-flow
Merge 2ddc9a6d7 into 2dbe8dab2
Pull Request #11024: Update User Guide - Upload image on Text Widget

31021 of 48282 branches covered (64.25%)

38629 of 50199 relevant lines covered (76.95%)

36.23 hits per line

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

76.67
/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 { 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
import usePluginItems from '../hooks/usePluginItems';
90
import { setProtectedServices, setShowModalStatus } from '../actions/security';
91

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

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

133

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

157

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

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

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

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

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

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

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

272

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

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

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

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

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

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