diff --git a/geonode_mapstore_client/client/MapStore2 b/geonode_mapstore_client/client/MapStore2
index 277de13970..82ac01e300 160000
--- a/geonode_mapstore_client/client/MapStore2
+++ b/geonode_mapstore_client/client/MapStore2
@@ -1 +1 @@
-Subproject commit 277de13970b9f3c61465f7a0c859eca9848f6eae
+Subproject commit 82ac01e30011d6b60f9ee003e2221aa433537d2b
diff --git a/geonode_mapstore_client/client/js/api/geonode/v2/facets.js b/geonode_mapstore_client/client/js/api/geonode/v2/facets.js
deleted file mode 100644
index ceb8111b2a..0000000000
--- a/geonode_mapstore_client/client/js/api/geonode/v2/facets.js
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright 2025, GeoSolutions Sas.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import { castArray, isEmpty } from 'lodash';
-import axios from '@mapstore/framework/libs/ajax';
-import {
- paramsSerializer
-} from '@js/utils/APIUtils';
-import {
- FACETS,
- getEndpointUrl,
- getQueryParams
-} from './constants';
-import { parseIcon } from '@js/utils/SearchUtils';
-import { addFilters, getFilterByField } from '@mapstore/framework/utils/ResourcesFiltersUtils';
-import { getCustomMenuFilters } from '@js/selectors/config';
-
-const parseTopicsItems = (items = [], { facet, style }) => {
- return items.map((item) => {
- const value = String(item.key);
- return {
- type: "filter",
- // TODO remove when api send isLocalized for all facets response
- ...(item.is_localized ? { labelId: item.label } : { label: item.label }),
- count: item.count ?? 0,
- filterKey: facet.filter,
- filterValue: value,
- value,
- style,
- facetName: facet.name,
- icon: parseIcon(item.fa_class),
- image: item.image
- };
- });
-};
-
-const applyFacetToFields = (fields, facets = [], { customFilters }) => {
- return fields.map((field) => {
- if (field.facet) {
- const filteredFacetsByType = facets
- .filter(f => f.type === field.facet)
- .filter(f => field.include
- ? field.include?.includes(f.name)
- : field.exclude
- ? !field.exclude?.includes(f.name)
- : true);
- if (!filteredFacetsByType.length) {
- return null;
- }
-
- return filteredFacetsByType.map((facet) => {
- const facetConfig = facet.config || {};
- const style = facetConfig.style || field.style;
- const type = facetConfig.type || field.type;
- const order = facetConfig.order || field.order;
- const isLocalized = !!facet.is_localized;
- const label = facet.label;
- return {
- id: facet.name,
- name: facet.name,
- type,
- style,
- order,
- facet: field.facet,
- ...(isLocalized ? { labelId: label } : { label }),
- key: facet.filter,
- loadItems: ({ params, config }) => {
- const { q, ...updatedParams } = getQueryParams(params, customFilters);
- return axios.get(getEndpointUrl(FACETS, `/${facet.name}`), {
- ...config,
- params: {
- ...(q && { topic_contains: q }),
- ...updatedParams
- },
- ...paramsSerializer()
- })
- .then(({ data }) => {
- const topics = data?.topics ?? {};
- const pageSize = topics?.page_size;
- const page = Number(topics.page);
- const total = topics?.total;
- const isNextPageAvailable = (Math.ceil(Number(total) / Number(pageSize)) - (page + 1)) !== 0;
- const items = parseTopicsItems(topics.items, { facet, style });
- const filterField = { key: facet.filter, style, name: facet.name };
- // if the items are empty and items are selected
- // we should still see them with count equal 0
- // to allow user to deselect the filter
- // TODO: review if possible to move this control in accordion
- const facetQuery = updatedParams[facet.filter];
- if (!isEmpty(facetQuery) && items.length === 0) {
-
- const appliedFilters = castArray(facetQuery)
- .map((val) => getFilterByField(filterField, val))
- .filter(Boolean)
- .map(appliedFilter => ({ ...appliedFilter, count: 0 }));
- // store all filters information
- addFilters(filterField, appliedFilters);
-
- return {
- isNextPageAvailable,
- items: appliedFilters
- };
- }
-
- // store all filters information
- addFilters(filterField, items);
-
- return {
- isNextPageAvailable,
- items
- };
- });
- }
- };
- });
- }
- if (field.items) {
- return {
- ...field,
- items: applyFacetToFields(field.items, facets, { customFilters })
- };
- }
- return field;
- }).flat().filter(val => val).sort((a, b) => a.order - b.order);
-};
-
-let facetsCache;
-
-const findFacetField = (facet, fields) => {
- return fields.filter((field) => field.facet && field.id === facet.name
- ? true
- : field.items
- ? findFacetField(facet, field.items)
- : false).flat()[0];
-};
-
-const updateFacets = (fields, facets = [], query = {}) => {
- const queryFacets = facets.filter(facet => query[facet.filter]);
- if (queryFacets.length) {
- return axios.all(queryFacets.map((queryFacet) => {
- const field = findFacetField(queryFacet, fields);
- if (!field) {
- return Promise.resolve({});
- }
- const keys = castArray(query[queryFacet.filter]);
- const style = queryFacet?.config?.style || field.style;
- return keys.map((key) => {
- const { q, ...params } = query;
- return axios.get(getEndpointUrl(FACETS, `/${queryFacet.name}`), {
- params: {
- ...params,
- ...(q && { topic_contains: q }),
- include_topics: true,
- key
- },
- ...paramsSerializer()
- })
- .then(({ data } = {}) => {
- const filterField = { key: queryFacet.filter, style, name: queryFacet.name };
- const topics = data?.topics ?? {};
- const items = parseTopicsItems(topics.items, { facet: queryFacet, style });
- // store all filters information
- addFilters(filterField, items);
- return {};
- })
- .catch(() => ({}));
- });
- }).flat());
- }
- return Promise.resolve({});
-};
-
-export const getFacetItems = ({
- fields,
- query,
- monitoredState
-}) => {
- const customFilters = getCustomMenuFilters(monitoredState);
- const updatedParams = getQueryParams(query, customFilters);
- return (
- !facetsCache
- ? axios.get(getEndpointUrl(FACETS), { params: { include_config: true }})
- .then(({ data } = {}) => {
- facetsCache = data?.facets;
- return { fields: applyFacetToFields(fields, facetsCache, { customFilters }) };
- })
- .catch(() => ({ fields: applyFacetToFields(fields, [], { customFilters }) }))
- : Promise.resolve({ fields: applyFacetToFields(fields, facetsCache, { customFilters }) })
- ).then((payload) => {
- // update information of the current selected facet filters
- return updateFacets(payload.fields, facetsCache, updatedParams).then(() => payload);
- });
-};
diff --git a/geonode_mapstore_client/client/js/api/geonode/v2/index.js b/geonode_mapstore_client/client/js/api/geonode/v2/index.js
index 0c8a8355d5..0e8e02fc82 100644
--- a/geonode_mapstore_client/client/js/api/geonode/v2/index.js
+++ b/geonode_mapstore_client/client/js/api/geonode/v2/index.js
@@ -134,39 +134,6 @@ export const getMaps = ({
});
};
-export const getDatasets = ({
- q,
- pageSize = 20,
- page = 1,
- sort
-}) => {
- return axios
- .get(
- getEndpointUrl(RESOURCES), {
- // axios will format query params array to `key[]=value1&key[]=value2`
- params: {
- 'filter{resource_type.in}': 'dataset',
- 'filter{metadata_only}': false,
- ...(q && {
- search: q,
- search_index: getResourcesSearchIndex()
- }),
- ...(sort && { sort: isArray(sort) ? sort : [ sort ]}),
- page,
- page_size: pageSize,
- api_preset: API_PRESET.CATALOGS
- },
- ...paramsSerializer()
- })
- .then(({ data }) => {
- return {
- totalCount: data.total,
- isNextPageAvailable: !!data.links.next,
- resources: (data.resources || [])
- };
- });
-};
-
export const getDocuments = ({
q,
pageSize = 10,
@@ -843,7 +810,6 @@ export default {
deleteResource,
copyResource,
downloadResource,
- getDatasets,
deleteExecutionRequest,
getResourceByTypeAndByPk,
createDataset,
diff --git a/geonode_mapstore_client/client/js/epics/__tests__/gnresource-test.js b/geonode_mapstore_client/client/js/epics/__tests__/gnresource-test.js
index 8c56571d16..c199c0f304 100644
--- a/geonode_mapstore_client/client/js/epics/__tests__/gnresource-test.js
+++ b/geonode_mapstore_client/client/js/epics/__tests__/gnresource-test.js
@@ -29,6 +29,7 @@ import {
} from '@js/actions/gnresource';
import { clickOnMap } from '@mapstore/framework/actions/map';
import { SET_CONTROL_PROPERTY } from '@mapstore/framework/actions/controls';
+import { CATALOG_CLOSE } from '@mapstore/framework/actions/catalog';
import {
SHOW_NOTIFICATION
} from '@mapstore/framework/actions/notifications';
@@ -194,7 +195,7 @@ describe('gnresource epics', () => {
requests: ["something"]
},
controls: {
- datasetsCatalog: {
+ metadataexplorer: {
enabled: true
}
}
@@ -206,9 +207,7 @@ describe('gnresource epics', () => {
(actions) => {
try {
expect(actions.length).toBe(1);
- expect(actions[0].type).toBe(SET_CONTROL_PROPERTY);
- expect(actions[0].control).toBe("datasetsCatalog");
- expect(actions[0].value).toBe(false);
+ expect(actions[0].type).toBe(CATALOG_CLOSE);
} catch (e) {
done(e);
}
diff --git a/geonode_mapstore_client/client/js/epics/datasetscatalog.js b/geonode_mapstore_client/client/js/epics/datasetscatalog.js
deleted file mode 100644
index 3f24a6b06d..0000000000
--- a/geonode_mapstore_client/client/js/epics/datasetscatalog.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2021, GeoSolutions Sas.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import { SET_CONTROL_PROPERTY } from '@mapstore/framework/actions/controls';
-import { updateMapLayout, UPDATE_MAP_LAYOUT } from '@mapstore/framework/actions/maplayout';
-import { mapLayoutSelector, boundingSidebarRectSelector } from '@mapstore/framework/selectors/maplayout';
-import { getConfigProp } from "@mapstore/framework/utils/ConfigUtils";
-import { LayoutSections } from "@js/utils/LayoutUtils";
-
-/**
-* @module epics/datasetcatalog
-*/
-
-/**
- * Override the layout to get the correct right offset when the data catalog is open
- */
-export const gnUpdateDatasetsCatalogMapLayout = (action$, store) =>
- action$.ofType(UPDATE_MAP_LAYOUT, SET_CONTROL_PROPERTY)
- .filter(() => store.getState()?.controls?.datasetsCatalog?.enabled)
- .filter(({ source }) => {
- return source !== LayoutSections.PANEL;
- })
- .map(({ layout }) => {
- const mapLayout = getConfigProp('mapLayout') || { left: { sm: 300, md: 500, lg: 600 }, right: { md: 658 }, bottom: { sm: 30 } };
- const boundingSidebarRect = boundingSidebarRectSelector(store.getState());
- const left = !!store.getState()?.controls?.drawer?.enabled ? mapLayout.left.sm : null;
- const action = updateMapLayout({
- ...mapLayoutSelector(store.getState()),
- ...layout,
- right: mapLayout.right.md,
- ...(left && {left}),
- boundingMapRect: {
- ...(layout?.boundingMapRect || {}),
- right: mapLayout.right.md,
- ...(left && {left})
- },
- boundingSidebarRect: {
- ...boundingSidebarRect,
- ...layout?.boundingSidebarRect
- }
- });
- return { ...action, source: LayoutSections.PANEL }; // add an argument to avoid infinite loop.
- });
-
-export default {
- gnUpdateDatasetsCatalogMapLayout
-};
diff --git a/geonode_mapstore_client/client/js/epics/gnresource.js b/geonode_mapstore_client/client/js/epics/gnresource.js
index 239a3489a6..dd6dbaaf19 100644
--- a/geonode_mapstore_client/client/js/epics/gnresource.js
+++ b/geonode_mapstore_client/client/js/epics/gnresource.js
@@ -138,7 +138,7 @@ import { REDUCERS_LOADED } from '@mapstore/framework/actions/storemanager';
import { wrapStartStop } from '@mapstore/framework/observables/epics';
import { parseDevHostname } from '@js/utils/APIUtils';
import { ProcessTypes } from '@js/utils/ResourceServiceUtils';
-import { catalogClose } from '@mapstore/framework/actions/catalog';
+import { catalogClose, addLayerAndDescribe } from '@mapstore/framework/actions/catalog';
import { VisualizationModes } from '@mapstore/framework/utils/MapTypeUtils';
import { forceUpdateMapLayout } from '@mapstore/framework/actions/maplayout';
import { getShowDetails } from '@mapstore/framework/plugins/ResourcesCatalog/selectors/resources';
@@ -206,6 +206,27 @@ const resourceTypes = {
const extent = newLayer?.bbox?.bounds && [minx, miny, maxx, maxy ];
const hasNoGeometry = gnLayer?.subtype === 'tabular';
const hasDownloadPermission = gnLayer?.perms?.includes('download_resourcebase');
+ const isNewLoad = !options?.isSamePreviousResource;
+
+ // page-specific layer actions shared between the two paths
+ const pageSpecificActions = [
+ selectNode(newLayer.id, 'layer', false),
+ ...((hasNoGeometry || page === 'dataset_edit_data_viewer')
+ ? [
+ browseData(newLayer),
+ ...(hasDownloadPermission ? [] : [setDatasetEditPermissionsError('gnviewer.noEditPermissions')])
+ ]
+ : []),
+ ...(page === 'dataset_edit_layer_settings'
+ ? [
+ showSettings(newLayer.id, "layers", {opacity: newLayer.opacity ?? 1}),
+ setControlProperty("layersettings", "activeTab", query.tab ?? "general"),
+ updateAdditionalLayer(newLayer.id, STYLE_OWNER_NAME, 'override', {}),
+ resizeMap()
+ ]
+ : [])
+ ];
+
return Observable.concat(
Observable.of(
configureMap({
@@ -215,13 +236,11 @@ const resourceTypes = {
...mapLayerData?.mapConfig?.map,
zoom: 20, // start zoomed in to mitigate initial tile blurring before the deferred fit lands
visualizationMode: ['3dtiles'].includes(subtype) ? VisualizationModes._3D : VisualizationModes._2D,
+ // on same-resource navigation the layer is already described;
+ // on new loads it is added via addLayerAndDescribe below
layers: [
...mapConfig.map.layers,
- {
- ...newLayer,
- isDataset: true,
- _v_: Date.now()
- }
+ ...(!isNewLoad ? [{ ...newLayer, isDataset: true, _v_: Date.now() }] : [])
]
}
}),
@@ -231,23 +250,15 @@ const resourceTypes = {
setProjectionsConfig(mapLayerData?.mapConfig?.crsSelector),
setControlProperty('toolbar', 'expanded', false),
forceUpdateMapLayout(),
- selectNode(newLayer.id, 'layer', false),
- ...(!options?.isSamePreviousResource ? [setResource({...gnLayer, hasNoGeometry})] : []),
+ // on new loads: configureMap (= MAP_CONFIG_LOADED) has already updated
+ // the layers state, so addLayerAndDescribe safely appends + describes
+ ...(isNewLoad
+ ? [addLayerAndDescribe({ ...newLayer, isDataset: true, _v_: Date.now() }, { zoomToLayer: false })]
+ : []
+ ),
+ ...pageSpecificActions,
+ ...(isNewLoad ? [setResource({...gnLayer, hasNoGeometry})] : []),
setResourceId(pk),
- ...((hasNoGeometry || page === 'dataset_edit_data_viewer')
- ? [
- browseData(newLayer),
- ...(hasDownloadPermission ? [] : [setDatasetEditPermissionsError('gnviewer.noEditPermissions')])
- ]
- : []),
- ...(page === 'dataset_edit_layer_settings'
- ? [
- showSettings(newLayer.id, "layers", {opacity: newLayer.opacity ?? 1}),
- setControlProperty("layersettings", "activeTab", query.tab ?? "general"),
- updateAdditionalLayer(newLayer.id, STYLE_OWNER_NAME, 'override', {}),
- resizeMap()
- ]
- : []),
...(newLayer?.bboxError
? [warningNotification({ title: "gnviewer.invalidBbox", message: "gnviewer.invalidBboxMsg" })]
: []),
@@ -338,13 +349,13 @@ const resourceTypes = {
? VisualizationModes._3D
: VisualizationModes._2D
}),
- layers: [
- ...(mapConfig?.map?.layers || []),
- newLayer
- ]
+ // configureMap (= MAP_CONFIG_LOADED) updates the layers state
+ // synchronously, so addLayerAndDescribe below safely appends + describes
+ layers: [...(mapConfig?.map?.layers || [])]
}
}
: mapConfig),
+ ...(newLayer ? [addLayerAndDescribe(newLayer, { zoomToLayer: false })] : []),
setControlProperty('toolbar', 'expanded', false),
// unblock the Viewer route so the Map plugin actually mounts;
// see fitBoundsAfterMapReady comment.
@@ -651,7 +662,7 @@ export const closeInfoPanelOnMapClick = (action$, store) => action$.ofType(CLICK
.switchMap(() => Observable.of(setControlProperty('rightOverlay', 'enabled', false)));
-// Check which control is enabled between annotations, datasetsCatalog and documentsCatalog
+// Check which control is enabled between annotations, metadataexplorer and documentsCatalog
const oneOfTheOther = (control) => {
if (control === 'rightOverlay') return null;
@@ -659,10 +670,10 @@ const oneOfTheOther = (control) => {
if (control === 'annotations') {
return {
control,
- alternates: ['datasetsCatalog', 'documentsCatalog']
+ alternates: ['metadataexplorer', 'documentsCatalog']
};
}
- if (control === 'datasetsCatalog') {
+ if (control === 'metadataexplorer') {
return {
control,
alternates: ['annotations', 'documentsCatalog']
@@ -671,7 +682,7 @@ const oneOfTheOther = (control) => {
if (control === 'documentsCatalog') {
return {
control,
- alternates: ['annotations', 'datasetsCatalog']
+ alternates: ['annotations', 'metadataexplorer']
};
}
@@ -693,16 +704,12 @@ export const closeOpenPanels = (action$, store) => action$.ofType(SET_CONTROL_PR
if (isMapInfoOpen(state)) {
setActions.push(purgeMapInfoResults(), closeIdentify());
}
- const isDatasetCatalogPanelOpen = get(state, "controls.datasetsCatalog.enabled");
const isDocumentsCatalogPanelOpen = get(state, "controls.documentsCatalog.enabled");
const isCatalogOpen = get(state, "controls.metadataexplorer.enabled");
const isVisualStyleEditorOpen = get(state, "controls.visualStyleEditor.enabled");
- if ((isDatasetCatalogPanelOpen || isDocumentsCatalogPanelOpen || isVisualStyleEditorOpen) && isCatalogOpen) {
+ if (isVisualStyleEditorOpen && isCatalogOpen) {
setActions.push(catalogClose());
}
- if (isDatasetCatalogPanelOpen && isVisualStyleEditorOpen) {
- setActions.push(setControlProperty('datasetsCatalog', 'enabled', false));
- }
if (isDocumentsCatalogPanelOpen && isVisualStyleEditorOpen) {
setActions.push(setControlProperty('documentsCatalog', 'enabled', false));
}
@@ -730,21 +737,21 @@ export const closeOpenPanels = (action$, store) => action$.ofType(SET_CONTROL_PR
});
/**
- * Close dataset and documents panels on map info panel open
+ * Close catalog and documents panels on map info panel open
*/
export const closeDatasetCatalogPanel = (action$, store) => action$.ofType(NEW_MAPINFO_REQUEST)
.filter(() => {
const state = store.getState();
return isMapInfoOpen(state) &&
- (get(state, "controls.datasetsCatalog.enabled") ||
+ (get(state, "controls.metadataexplorer.enabled") ||
get(state, "controls.documentsCatalog.enabled"));
})
.switchMap(() => {
const state = store.getState();
const actions = [];
- if (get(state, "controls.datasetsCatalog.enabled")) {
- actions.push(setControlProperty('datasetsCatalog', 'enabled', false));
+ if (get(state, "controls.metadataexplorer.enabled")) {
+ actions.push(catalogClose());
}
if (get(state, "controls.documentsCatalog.enabled")) {
diff --git a/geonode_mapstore_client/client/js/observables/persistence/index.js b/geonode_mapstore_client/client/js/observables/persistence/index.js
index 4c02820e46..cf63bad678 100644
--- a/geonode_mapstore_client/client/js/observables/persistence/index.js
+++ b/geonode_mapstore_client/client/js/observables/persistence/index.js
@@ -9,7 +9,7 @@
import { Observable } from 'rxjs';
import { addApi, setApi } from '@mapstore/framework/api/persistence';
import { getMaps, getMapByPk, getGeoAppByPk, getResources as gnGetResources } from '@js/api/geonode/v2';
-import { getFacetItems } from '@js/api/geonode/v2/facets';
+import { getFacetItems } from '@mapstore/framework/api/GeoNode';
import { parseCatalogResource, ResourceTypes } from '@js/utils/ResourceUtils';
import { getCustomMenuFilters } from '@js/selectors/config';
diff --git a/geonode_mapstore_client/client/js/plugins/DatasetsCatalog.jsx b/geonode_mapstore_client/client/js/plugins/DatasetsCatalog.jsx
deleted file mode 100644
index b72aaf673d..0000000000
--- a/geonode_mapstore_client/client/js/plugins/DatasetsCatalog.jsx
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2021, GeoSolutions Sas.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { createPlugin } from '@mapstore/framework/utils/PluginsUtils';
-import { connect } from 'react-redux';
-import { createSelector } from 'reselect';
-import Message from '@mapstore/framework/components/I18N/Message';
-import Button from '@mapstore/framework/components/layout/Button';
-import { getDatasets, getDatasetByPk, getResourceByPk } from '@js/api/geonode/v2';
-import { resourceToLayerConfig, isDefaultDatasetSubtype } from '@js/utils/ResourceUtils';
-import { addLayer } from '@mapstore/framework/actions/layers';
-import { zoomToExtent } from '@mapstore/framework/actions/map';
-import { setControlProperty } from '@mapstore/framework/actions/controls';
-import datasetscatalogEpics from '@js/epics/datasetscatalog';
-import { mapLayoutValuesSelector } from '@mapstore/framework/selectors/maplayout';
-import ResourcesCompactCatalog from '@js/components/ResourcesCompactCatalog';
-import useIsMounted from "@js/hooks/useIsMounted";
-
-function DatasetsCatalog({
- onAdd,
- onZoomTo,
- ...props
-}) {
- const isMounted = useIsMounted();
- const [loading, setLoading] = useState(false);
-
- function handleSelectResource(entry) {
- setLoading(true);
- (isDefaultDatasetSubtype(entry.subtype)
- ? getDatasetByPk(entry.pk)
- : getResourceByPk(entry.pk))
- .then((dataset) => {
- const layer = resourceToLayerConfig(dataset);
- onAdd(layer);
- const { minx, miny, maxx, maxy } = layer?.bbox?.bounds || {};
- const extent = layer?.bbox?.bounds && [minx, miny, maxx, maxy];
- if (extent) {
- onZoomTo(extent, layer?.bbox?.crs);
- }
- })
- .finally(() => {
- isMounted(() => setLoading(false));
- });
- }
-
- return ();
-}
-
-DatasetsCatalog.propTypes = {
- request: PropTypes.func,
- responseToEntries: PropTypes.func,
- pageSize: PropTypes.number,
- onAdd: PropTypes.func,
- placeholderId: PropTypes.string,
- onClose: PropTypes.func,
- onZoomTo: PropTypes.func
-};
-
-DatasetsCatalog.defaultProps = {
- request: getDatasets,
- responseToEntries: res => res.resources,
- pageSize: 10,
- onAdd: () => { },
- placeholderId: 'gnviewer.datasetsCatalogFilterPlaceholder',
- titleId: 'gnviewer.datasetsCatalogTitle',
- noResultId: 'gnviewer.datasetsCatalogEntriesNoResults',
- onZoomTo: () => { },
- onClose: () => { }
-};
-
-function DatasetsCatalogPlugin({ enabled, ...props }) {
- return enabled ? : null;
-}
-
-const ConnectedDatasetsCatalogPlugin = connect(
- createSelector([
- state => mapLayoutValuesSelector(state, { height: true }),
- state => state?.controls?.datasetsCatalog?.enabled
- ], (style, enabled) => ({
- style,
- enabled
- })), {
- onAdd: addLayer,
- onClose: setControlProperty.bind(null, 'datasetsCatalog', 'enabled', false),
- onZoomTo: zoomToExtent
- }
-)(DatasetsCatalogPlugin);
-
-const DatasetsCatalogButton = ({
- onClick,
- size,
- variant
-}) => {
-
- const handleClickButton = () => {
- onClick();
- };
-
- return (
-
- );
-};
-
-const ConnectedDatasetsCatalogButton = connect(
- createSelector([], () => ({})),
- {
- onClick: setControlProperty.bind(null, 'datasetsCatalog', 'enabled', true)
- }
-)((DatasetsCatalogButton));
-
-export default createPlugin('DatasetsCatalog', {
- component: ConnectedDatasetsCatalogPlugin,
- containers: {
- ActionNavbar: {
- name: 'DatasetsCatalog',
- Component: ConnectedDatasetsCatalogButton
- }
- },
- epics: datasetscatalogEpics,
- reducers: {}
-});
diff --git a/geonode_mapstore_client/client/js/plugins/MapViewersCatalog.jsx b/geonode_mapstore_client/client/js/plugins/MapViewersCatalog.jsx
index 1b5b28048f..79e2477cf9 100644
--- a/geonode_mapstore_client/client/js/plugins/MapViewersCatalog.jsx
+++ b/geonode_mapstore_client/client/js/plugins/MapViewersCatalog.jsx
@@ -18,7 +18,6 @@ import Button from '@mapstore/framework/components/layout/Button';
import { getGeoApps } from '@js/api/geonode/v2';
import { getDefaultPluginsConfig } from '@js/api/geonode/config';
import { setControlProperty } from '@mapstore/framework/actions/controls';
-import datasetscatalogEpics from '@js/epics/datasetscatalog';
import contextcreatorEpics from '@js/epics/contextcreator';
import ResourcesCompactCatalog from '@js/components/ResourcesCompactCatalog';
import ResizableModal from '@mapstore/framework/components/misc/ResizableModal';
@@ -35,7 +34,7 @@ function MapViewersCatalogPlugin({
match,
resourcesParams,
location,
- defaultViewerPlugins = ['Annotations', 'TOC', 'BackgroundSelector', 'Identify', 'QueryPanel', 'Measure', 'Print', 'MousePosition', 'Search', 'ScaleBox', 'GlobeViewSwitcher', 'ZoomAll', 'ZoomIn', 'ZoomOut', 'Timeline', 'MetadataExplorer', 'Widgets'],
+ defaultViewerPlugins = ['Annotations', 'TOC', 'BackgroundSelector', 'Identify', 'QueryPanel', 'Measure', 'Print', 'MousePosition', 'Search', 'ScaleBox', 'GlobeViewSwitcher', 'ZoomAll', 'ZoomIn', 'ZoomOut', 'Timeline', 'Catalog', 'Widgets'],
onReplaceLocation,
onSetMapViewer,
onManageLinkedResource,
@@ -200,7 +199,6 @@ export default createPlugin('MapViewersCatalog', {
}
},
epics: {
- ...datasetscatalogEpics,
...contextcreatorEpics
},
reducers: {}
diff --git a/geonode_mapstore_client/client/js/plugins/index.js b/geonode_mapstore_client/client/js/plugins/index.js
index 34cbed2810..072cbc301b 100644
--- a/geonode_mapstore_client/client/js/plugins/index.js
+++ b/geonode_mapstore_client/client/js/plugins/index.js
@@ -22,9 +22,9 @@ import Isochrone from "@mapstore/framework/plugins/Isochrone";
import Itinerary from "@mapstore/framework/plugins/Itinerary";
import SecurityPopup from "@mapstore/framework/plugins/SecurityPopup";
import BackgroundSelector from '@mapstore/framework/plugins/BackgroundSelector';
-import MetadataExplorer from '@mapstore/framework/plugins/MetadataExplorer';
import CameraPosition from '@mapstore/framework/plugins/CameraPosition';
import CRSSelector from '@mapstore/framework/plugins/CRSSelector';
+import CatalogPlugin from '@mapstore/framework/plugins/Catalog';
import OperationPlugin from '@js/plugins/Operation';
import ExecutionTrackerPlugin from '@js/plugins/ExecutionTracker';
@@ -94,7 +94,6 @@ export const plugins = {
ItineraryPlugin: Itinerary,
SecurityPopupPlugin: SecurityPopup,
BackgroundSelectorPlugin: BackgroundSelector,
- MetadataExplorerPlugin: MetadataExplorer,
CameraPositionPlugin: CameraPosition,
CRSSelectorPlugin: CRSSelector,
LayerDownloadPlugin: toModulePlugin(
@@ -443,10 +442,7 @@ export const plugins = {
'DocumentsCatalog',
() => import(/* webpackChunkName: 'plugins/documents-catalog' */ '@js/plugins/DocumentsCatalog')
),
- DatasetsCatalogPlugin: toModulePlugin(
- 'DatasetsCatalog',
- () => import(/* webpackChunkName: 'plugins/dataset-catalog' */ '@js/plugins/DatasetsCatalog')
- ),
+ CatalogPlugin,
SyncPlugin: toModulePlugin(
'Sync',
() => import(/* webpackChunkName: 'plugins/sync-plugin' */ '@js/plugins/Sync')
diff --git a/geonode_mapstore_client/client/js/utils/LocaleUtils.js b/geonode_mapstore_client/client/js/utils/LocaleUtils.js
deleted file mode 100644
index 8321f1f67d..0000000000
--- a/geonode_mapstore_client/client/js/utils/LocaleUtils.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2026, GeoSolutions Sas.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-
-/**
-* @module utils/LocaleUtils
-*/
-
-/**
- * Normalizes a locale code to a standard format (language-region).
- * If the locale code has a region, it returns the language-region combination.
- * Otherwise, it maximizes the locale to include region information.
- *
- * @param {string} code - The locale code to normalize (e.g., 'en', 'en-US', 'it')
- * @returns {string} The normalized locale code in language-region format, or an empty string if normalization fails
- *
- * @example
- * longLocale('en'); // Returns 'en-US' (or similar based on system locale)
- * longLocale('en-GB'); // Returns 'en-GB'
- * longLocale('invalid code'); // Returns ''
- *
- * shortLocale('en-US'); // Returns 'en'
- * shortLocale('it-IT'); // Returns 'it'
- * shortLocale('invalid code'); // Returns ''
- */
-
-/**
- * return the normalized locale code in language-region format, 5 chars length e.g. 'en-US', 'it-IT'
- * @param {*} code
- * @returns {string} The normalized locale code in language-region format, or an empty string if normalization fails
- */
-export function longLocale(code) {
- if (!code) return '';
- try {
- const loc = new Intl.Locale(code);
- if (loc.region) return `${loc.language}-${loc.region}`;
- const maximized = loc.maximize();
- return `${maximized.language}-${maximized.region}`;
- } catch {
- return '';
- }
-}
-
-/**
- * return the language component of a locale code, or an empty string if shortening fails, length is 2 chars e.g. 'en', 'it'
- * @param {string} code - The locale code to shorten (e.g., 'en-US', 'it-IT')
- * @returns {string} The language component of the locale code, or an empty string if shortening fails
- */
-export function shortLocale(code) {
- if (!code) return '';
- try {
- const loc = new Intl.Locale(code);
- return loc.language;
- } catch {
- return '';
- }
-}
diff --git a/geonode_mapstore_client/client/js/utils/ResourceUtils.js b/geonode_mapstore_client/client/js/utils/ResourceUtils.js
index ed26abd109..9e91dcc415 100644
--- a/geonode_mapstore_client/client/js/utils/ResourceUtils.js
+++ b/geonode_mapstore_client/client/js/utils/ResourceUtils.js
@@ -6,64 +6,23 @@
* LICENSE file in the root directory of this source tree.
*/
-import uuid from 'uuid';
import url from 'url';
import { isEmpty, uniqBy, omit, orderBy, isString, isObject } from 'lodash';
-import { isImageServerUrl } from '@mapstore/framework/utils/ArcGISUtils';
import { getConfigProp, convertFromLegacy, normalizeConfig } from '@mapstore/framework/utils/ConfigUtils';
-import { excludeGoogleBackground, extractTileMatrixFromSources, ServerTypes } from '@mapstore/framework/utils/LayersUtils';
+import { excludeGoogleBackground, extractTileMatrixFromSources } from '@mapstore/framework/utils/LayersUtils';
+import { SOURCE_TYPES, FEATURE_INFO_FORMAT, GXP_PTYPES, ResourceTypes, isDefaultDatasetSubtype, getDimensions, resourceToLayers, resourceToLayerConfig } from '@mapstore/framework/utils/GeoNodeUtils';
import { getGeoNodeLocalConfig, parseDevHostname } from '@js/utils/APIUtils';
import { ProcessTypes, ProcessStatus } from '@js/utils/ResourceServiceUtils';
import { determineResourceType } from '@js/utils/FileUtils';
-import { createDefaultStyle } from '@mapstore/framework/utils/StyleUtils';
-import { getSupportedLocales } from '@mapstore/framework/utils/LocaleUtils';
-import { shortLocale } from '@js/utils/LocaleUtils';
+export { SOURCE_TYPES, FEATURE_INFO_FORMAT, GXP_PTYPES, ResourceTypes, isDefaultDatasetSubtype, getDimensions, resourceToLayers, resourceToLayerConfig };
/**
* @module utils/ResourceUtils
*/
-function getExtentFromResource({ extent }) {
- if (isEmpty(extent?.coords)) {
- return null;
- }
- const [minx, miny, maxx, maxy] = extent.coords;
-
- // if the extent is greater than the max extent of the WGS84 return null
- const WGS84_MAX_EXTENT = [-180, -90, 180, 90];
- if (minx < WGS84_MAX_EXTENT[0] || miny < WGS84_MAX_EXTENT[1] || maxx > WGS84_MAX_EXTENT[2] || maxy > WGS84_MAX_EXTENT[3]) {
- return {
- crs: 'EPSG:4326',
- bounds: {
- minx: WGS84_MAX_EXTENT[0],
- miny: WGS84_MAX_EXTENT[1],
- maxx: WGS84_MAX_EXTENT[2],
- maxy: WGS84_MAX_EXTENT[3]
- }
- };
- }
- const bbox = {
- crs: 'EPSG:4326',
- bounds: { minx, miny, maxx, maxy }
- };
- return bbox;
-}
-
-export const GXP_PTYPES = {
- 'AUTO': 'gxp_wmscsource',
- 'OWS': 'gxp_wmscsource',
- 'WMS': 'gxp_wmscsource',
- 'WFS': 'gxp_wmscsource',
- 'WCS': 'gxp_wmscsource',
- 'REST_MAP': 'gxp_arcrestsource',
- 'REST_IMG': 'gxp_arcrestsource',
- 'HGL': 'gxp_hglsource',
- 'GN_WMS': 'gxp_geonodecataloguesource'
-};
-
const RESOURCE_PUBLISHING_PROPERTIES_BASE = {
'is_published': {
labelId: 'gnviewer.publishResource',
@@ -128,233 +87,6 @@ export const TIME_PRECISION_STEPS = ['years', 'months', 'days', 'hours', 'minute
// Formats that support styling in GeoServer.
export const STYLE_SUPPORTED_LAYER_TYPES = ['vector', 'raster', 'vector_time'];
-export const isDefaultDatasetSubtype = (subtype) => !subtype || ['vector', 'raster', 'remote', 'vector_time'].includes(subtype);
-
-export const FEATURE_INFO_FORMAT = 'TEMPLATE';
-
-export const SOURCE_TYPES = {
- LOCAL: 'LOCAL',
- REMOTE: 'REMOTE'
-};
-
-const datasetAttributeSetToFields = ({ attribute_set: attributeSet = [] }) => {
- return attributeSet
- .filter(({ attribute_type: type }) => !type.includes('gml:'))
- .map(({
- attribute,
- attribute_label: alias,
- attribute_type: type
- }) => {
- return {
- name: attribute,
- alias: alias,
- type: type
- };
- });
-};
-
-export const getDimensions = ({links, has_time: hasTime} = {}) => {
- const { url: wmsUrl } = links?.find(({ link_type: linkType }) => linkType === 'OGC:WMS') || {};
- const { url: wmtsUrl } = links?.find(({ link_type: linkType }) => linkType === 'OGC:WMTS') || {};
- const dimensions = [
- ...(hasTime ? [{
- name: 'time',
- source: {
- type: 'multidim-extension',
- url: wmtsUrl || (wmsUrl || '').split('/geoserver/')[0] + '/geoserver/gwc/service/wmts'
- }
- }] : [])
- ];
- return dimensions;
-};
-
-
-const getLocalizedValue = (resource, key, locale = '') => {
- if (resource[`${key}_${locale}`]) {
- return resource[`${key}_${locale}`];
- }
- const languageCode = shortLocale(locale);
- if (resource[`${key}_${languageCode}`]) {
- return resource[`${key}_${languageCode}`];
- }
- return null;
-};
-
-export const getLocalizedValues = (resource, key, defaultValue) => {
- const supportedLocales = getSupportedLocales() || {};
- const translations = Object.values(supportedLocales)
- .map(({ code }) => {
- const value = getLocalizedValue(resource, key, code);
- return value ? [code, value] : null;
- })
- .filter(value => value !== null);
-
- if (translations.length) {
- return {
- ...Object.fromEntries(translations),
- 'default': defaultValue
- };
- }
- return defaultValue;
-};
-
-
-/**
-* convert resource layer configuration to a mapstore layer object
-* @param {object} resource geonode layer resource
-* @return {object}
-*/
-export const resourceToLayerConfig = (resource) => {
-
- const {
- alternate,
- attribute_set: attributeSet = [],
- links = [],
- featureinfo_custom_template: template,
- title: defaultTitle,
- perms,
- pk,
- default_style: defaultStyle,
- ptype,
- subtype,
- sourcetype,
- data
- } = resource;
-
- const layerSettings = data?.layerSettings ?? data;
-
- const title = getLocalizedValues(resource, 'title', defaultTitle);
-
-
- const bbox = getExtentFromResource(resource);
- const defaultStyleParams = defaultStyle && {
- defaultStyle: {
- title: defaultStyle.sld_title,
- name: defaultStyle.workspace ? `${defaultStyle.workspace}:${defaultStyle.name}` : defaultStyle.name
- }
- };
-
- const extendedParams = {
- pk,
- alternate
- };
-
- if (subtype === '3dtiles') {
- const { url: tilesetUrl } = links.find(({ extension }) => (extension === '3dtiles')) || {};
- return {
- id: uuid(),
- type: '3dtiles',
- title,
- url: parseDevHostname(tilesetUrl || ''),
- ...(bbox && { bbox }),
- visibility: true,
- ...layerSettings,
- extendedParams
- };
- }
- if (subtype === 'cog') {
- const { url: cogUrl } = links.find(({ extension }) => (extension === 'cog')) || {};
- return {
- perms,
- id: uuid(),
- type: 'cog',
- title,
- sources: [{
- url: parseDevHostname(cogUrl || '')
- }],
- ...(bbox && { bbox }),
- visibility: true,
- ...layerSettings,
- extendedParams
- };
- }
- if (subtype === 'flatgeobuf') {
- const defaultGeomType = 'GeometryCollection';
- const geometryType = attributeSet.find(attr => attr.attribute === 'geometryType')?.attribute_type || defaultGeomType;
-
- const { url: fgbUrl } = links.find(({ extension }) => (extension === 'flatgeobuf')) || {};
- return {
- perms,
- id: uuid(),
- type: 'flatgeobuf',
- title,
- style: createDefaultStyle({ geometryType }),
- url: parseDevHostname(fgbUrl || ''),
- ...(bbox && { bbox }),
- visibility: true,
- ...layerSettings,
- extendedParams
- };
- }
- switch (ptype) {
- case GXP_PTYPES.REST_MAP:
- case GXP_PTYPES.REST_IMG: {
- const { url: arcgisUrl } = links.find(({ mime, link_type: linkType }) => (mime === 'text/html' && linkType === 'image')) || {};
- return {
- perms,
- id: uuid(),
- pk,
- type: 'arcgis',
- ...(isImageServerUrl(arcgisUrl)
- ? { queryable: false }
- : { name: alternate.replace('remoteWorkspace:', '') }),
- url: arcgisUrl,
- ...(bbox && { bbox }),
- title,
- visibility: true,
- ...layerSettings,
- extendedParams
- };
- }
- default:
- const { url: wfsUrl } = links.find(({ link_type: linkType }) => linkType === 'OGC:WFS') || {};
- const { url: wmsUrl } = links.find(({ link_type: linkType }) => linkType === 'OGC:WMS') || {};
-
- const dimensions = getDimensions(resource);
-
- const params = wmsUrl && url.parse(wmsUrl, true).query;
- const {
- defaultLayerFormat = 'image/png',
- defaultTileSize = 512
- } = getConfigProp('geoNodeSettings') || {};
- const fields = datasetAttributeSetToFields(resource);
- return {
- perms,
- id: uuid(),
- pk,
- type: 'wms',
- name: alternate,
- url: wmsUrl || '',
- format: defaultLayerFormat,
- ...(wfsUrl && {
- search: {
- type: 'wfs',
- url: wfsUrl
- }
- }),
- ...(bbox ? { bbox } : { bboxError: true }),
- ...(template && {
- featureInfo: {
- format: FEATURE_INFO_FORMAT,
- template
- }
- }),
- style: defaultStyleParams?.defaultStyle?.name || '',
- title,
- tileSize: defaultTileSize,
- visibility: true,
- ...(params && { params }),
- ...(dimensions.length > 0 && ({ dimensions })),
- ...(fields && { fields }),
- ...(sourcetype === SOURCE_TYPES.REMOTE && !wmsUrl.includes('/geoserver/') && {
- serverType: ServerTypes.NO_VENDOR
- }),
- ...layerSettings,
- extendedParams
- };
- }
-};
-
function updateUrlQueryParameter(requestUrl = '', query) {
const parsedUrl = url.parse(requestUrl, true);
return url.format({
@@ -465,15 +197,6 @@ export const resourceHasPermission = (resource, perm) => {
};
-export const ResourceTypes = {
- DATASET: 'dataset',
- MAP: 'map',
- DOCUMENT: 'document',
- GEOSTORY: 'geostory',
- DASHBOARD: 'dashboard',
- VIEWER: 'mapviewer'
-};
-
export const isDocumentExternalSource = (resource) => {
return resource && resource.resource_type === ResourceTypes.DOCUMENT && resource.sourcetype === SOURCE_TYPES.REMOTE;
};
@@ -789,8 +512,21 @@ export function toMapStoreMapConfig(resource, baseConfig) {
.filter(mLayer => !layers.find(layer => layer.id !== undefined && mLayer?.extra_params?.msId === layer.id))
.map(mLayer => resourceToLayerConfig(mLayer?.dataset));
+ const { catalogueServices = {}, catalogueSelectedService = '' } = getConfigProp('geoNodeSettings') || {};
+ const existingServices = data?.catalogServices?.services || {};
+ const missingServices = Object.fromEntries(
+ Object.entries(catalogueServices).filter(([key]) => !existingServices[key])
+ );
+ const catalogServices = Object.keys(missingServices).length > 0
+ ? {
+ services: { ...missingServices, ...existingServices },
+ selectedService: data?.catalogServices?.selectedService || catalogueSelectedService
+ }
+ : data?.catalogServices;
+
return {
...data,
+ ...(catalogServices && { catalogServices }),
map: {
...data?.map,
layers: [
@@ -1077,28 +813,6 @@ export const parseCatalogResource = (resource, user) => {
};
};
-export const resourceToLayers = (resource) => {
- if (resource?.resource_type === ResourceTypes.DATASET) {
- return [{...resourceToLayerConfig(resource), isDataset: true}];
- }
- if (resource.maplayers && resource?.resource_type === ResourceTypes.MAP) {
- return resource.maplayers
- .map(maplayer => {
- maplayer.dataset ? resourceToLayerConfig(maplayer.dataset) : null;
- if (maplayer.dataset) {
- const layer = resourceToLayerConfig(maplayer.dataset);
- return {
- ...layer,
- style: maplayer.current_style
- };
- }
- return null;
- })
- .filter(value => value);
- }
- return [];
-};
-
export const canManageResourcePublishing = (resource) => {
const { perms } = resource || {};
const settingsPerms = ['feature_resourcebase', 'change_resourcebase', 'publish_resourcebase'];
diff --git a/geonode_mapstore_client/client/js/utils/__tests__/LocaleUtils-test.js b/geonode_mapstore_client/client/js/utils/__tests__/LocaleUtils-test.js
deleted file mode 100644
index 8279d12f68..0000000000
--- a/geonode_mapstore_client/client/js/utils/__tests__/LocaleUtils-test.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2026, GeoSolutions Sas.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import expect from 'expect';
-import {
- longLocale,
- shortLocale
-} from '@js/utils/LocaleUtils';
-
-describe('LocaleUtils', () => {
- it('longLocale should return language-region format', () => {
- expect(longLocale('en')).toMatch(/en-[A-Z]{2}/);
- expect(longLocale('en-GB')).toBe('en-GB');
- expect(longLocale('invalid code')).toBe('');
- });
- it('shortLocale should return the language component of a locale code', () => {
- expect(shortLocale('en-US')).toBe('en');
- expect(shortLocale('it-IT')).toBe('it');
- expect(shortLocale('invalid code')).toBe('');
- });
-});
diff --git a/geonode_mapstore_client/client/js/utils/__tests__/ResourceUtils-test.js b/geonode_mapstore_client/client/js/utils/__tests__/ResourceUtils-test.js
index 39cd3e1da9..2029d317c9 100644
--- a/geonode_mapstore_client/client/js/utils/__tests__/ResourceUtils-test.js
+++ b/geonode_mapstore_client/client/js/utils/__tests__/ResourceUtils-test.js
@@ -11,7 +11,6 @@ import expect from 'expect';
import get from 'lodash/get';
import set from 'lodash/set';
import {
- resourceToLayerConfig,
getResourcePermissions,
permissionsCompactToLists,
availableResourceTypes,
@@ -33,7 +32,6 @@ import {
getCataloguePath,
getResourceWithLinkedResources,
getResourceAdditionalProperties,
- getDimensions,
canManageResourcePublishing,
canManageResourceOptions,
canManageResourceSettings,
@@ -42,63 +40,7 @@ import {
canEditMap
} from '../ResourceUtils';
-import {setSupportedLocales} from '@mapstore/framework/utils/LocaleUtils';
-
describe('Test Resource Utils', () => {
- it('should keep the wms params from the url if available', () => {
- const newLayer = resourceToLayerConfig({
- alternate: 'geonode:layer_name',
- links: [{
- extension: 'html',
- link_type: 'OGC:WMS',
- name: 'OGC WMS Service',
- mime: 'text/html',
- url: 'http://localhost:8080/geoserver/wms?map=name&map_resolution=91'
- }],
- title: 'Layer title',
- perms: [],
- pk: 1
- });
- expect(newLayer.params).toEqual({ map: 'name', map_resolution: '91' });
- });
- it('test resourceToLayerConfig with layer settings of the dataset', () => {
- const newLayer = resourceToLayerConfig({
- alternate: 'geonode:layer_name',
- links: [{
- extension: 'html',
- link_type: 'OGC:WMS',
- name: 'OGC WMS Service',
- mime: 'text/html',
- url: 'http://localhost:8080/geoserver/wms?map=name&map_resolution=91'
- }],
- title: 'Layer title',
- perms: [],
- pk: 1,
- data: {opacity: 0.8}
- });
- expect(newLayer.opacity).toBe(0.8);
- });
-
- it('should parse arcgis dataset', () => {
- const newLayer = resourceToLayerConfig({
- alternate: 'remoteWorkspace:1',
- title: 'Layer title',
- perms: [],
- links: [{
- extension: 'html',
- link_type: 'image',
- mime: 'text/html',
- name: 'ArcGIS REST ImageServer',
- url: 'http://localhost:8080/MapServer'
- }],
- pk: 1,
- ptype: 'gxp_arcrestsource'
- });
- expect(newLayer.type).toBe('arcgis');
- expect(newLayer.name).toBe('1');
- expect(newLayer.url).toBe('http://localhost:8080/MapServer');
- });
-
it('should getViewedResourcePermissions', () => {
const data = [{
name: "testType",
@@ -1162,54 +1104,6 @@ describe('Test Resource Utils', () => {
}))
.toEqual({pk: 1, links: [{}], assets: [{_showEmptyState: true}]});
});
- describe('getDimensions', () => {
- it('should return empty array if no links and has_time is false', () => {
- const result = getDimensions();
- expect(result).toEqual([]);
- });
-
- it('should return dimensions with time if has_time is true and WMTS link is present', () => {
- const links = [{ link_type: 'OGC:WMTS', url: 'http://example.com/wmts' }];
- const result = getDimensions({ links, has_time: true });
- expect(result).toEqual([{
- name: 'time',
- source: {
- type: 'multidim-extension',
- url: 'http://example.com/wmts'
- }
- }]);
- });
-
- it('should return dimensions with time if has_time is true and only WMS link is present', () => {
- const links = [{ link_type: 'OGC:WMS', url: 'http://example.com/geoserver/wms' }];
- const result = getDimensions({ links, has_time: true });
- expect(result).toEqual([{
- name: 'time',
- source: {
- type: 'multidim-extension',
- url: 'http://example.com/geoserver/gwc/service/wmts'
- }
- }]);
- });
-
- it('should return empty array if has_time is false', () => {
- const links = [{ link_type: 'OGC:WMTS', url: 'http://example.com/wmts' }];
- const result = getDimensions({ links, has_time: false });
- expect(result).toEqual([]);
- });
-
- it('should return default url if no matching link types are found', () => {
- const links = [{ link_type: 'OGC:OTHER', url: 'http://example.com/other' }];
- const result = getDimensions({ links, has_time: true });
- expect(result).toEqual([{
- name: 'time',
- source: {
- type: 'multidim-extension',
- url: '/geoserver/gwc/service/wmts'
- }
- }]);
- });
- });
it('canManageResourcePublishing', () => {
expect(canManageResourcePublishing({ perms: ['publish_resourcebase'] })).toBeTruthy();
@@ -1341,124 +1235,8 @@ describe('Test Resource Utils', () => {
const result = canEditMap(gnresourceState, { isNewCheck: true, resourceTypes: [ResourceTypes.MAP, ResourceTypes.DATASET] });
expect(result).toBeTruthy();
});
- it('dataset with title in multilanguage', () => {
- let supportedLocales = {
- "en": {
- code: "en-US",
- description: "English"
- },
- "it": {
- code: "it-IT",
- description: "Italiano"
- },
- "fr": {
- code: "fr-FR",
- description: "Français"
- }
- };
- setSupportedLocales(supportedLocales);
- const newLayer = resourceToLayerConfig({
- alternate: 'geonode:layer_numtilangue',
- title: 'Default title',
- title_en: 'Layer title',
- title_it: 'Titolo del layer',
- title_fr: 'Titre de la couche',
- perms: [],
- pk: 1
- });
- expect(newLayer.title).toEqual({
- 'en-US': 'Layer title',
- 'it-IT': 'Titolo del layer',
- 'fr-FR': 'Titre de la couche',
- 'default': 'Default title'
- });
- });
});
describe('alternate is propagated through extendedParams', () => {
- // parseDevHostname references __DEVTOOLS__ (a webpack DefinePlugin global
- // not declared in the karma test config); the 3dtiles/cog/flatgeobuf
- // branches call it, so define it here to avoid ReferenceError
- let prevDevtools;
- before(() => {
- prevDevtools = window.__DEVTOOLS__;
- window.__DEVTOOLS__ = false;
- });
- after(() => {
- window.__DEVTOOLS__ = prevDevtools;
- });
-
- it('resourceToLayerConfig (default WMS) includes alternate in extendedParams', () => {
- const newLayer = resourceToLayerConfig({
- alternate: 'geonode:layer_name',
- links: [{
- extension: 'html',
- link_type: 'OGC:WMS',
- name: 'OGC WMS Service',
- mime: 'text/html',
- url: '/geoserver/wms'
- }],
- title: 'Layer title',
- perms: [],
- pk: 1
- });
- expect(newLayer.extendedParams).toEqual({ pk: 1, alternate: 'geonode:layer_name' });
- });
-
- it('resourceToLayerConfig (3dtiles) includes alternate in extendedParams', () => {
- const newLayer = resourceToLayerConfig({
- alternate: 'geonode:tileset',
- subtype: '3dtiles',
- links: [{ extension: '3dtiles', url: '/tileset.json' }],
- title: 'Tileset',
- perms: [],
- pk: 2
- });
- expect(newLayer.extendedParams).toEqual({ pk: 2, alternate: 'geonode:tileset' });
- });
-
- it('resourceToLayerConfig (cog) includes alternate in extendedParams', () => {
- const newLayer = resourceToLayerConfig({
- alternate: 'geonode:cog_layer',
- subtype: 'cog',
- links: [{ extension: 'cog', url: '/raster.tif' }],
- title: 'COG',
- perms: [],
- pk: 3
- });
- expect(newLayer.extendedParams).toEqual({ pk: 3, alternate: 'geonode:cog_layer' });
- });
-
- it('resourceToLayerConfig (flatgeobuf) includes alternate in extendedParams', () => {
- const newLayer = resourceToLayerConfig({
- alternate: 'geonode:fgb_layer',
- subtype: 'flatgeobuf',
- attribute_set: [],
- links: [{ extension: 'flatgeobuf', url: '/data.fgb' }],
- title: 'FGB',
- perms: [],
- pk: 4
- });
- expect(newLayer.extendedParams).toEqual({ pk: 4, alternate: 'geonode:fgb_layer' });
- });
-
- it('resourceToLayerConfig (arcgis) includes alternate in extendedParams', () => {
- const newLayer = resourceToLayerConfig({
- alternate: 'remoteWorkspace:1',
- title: 'Layer title',
- perms: [],
- links: [{
- extension: 'html',
- link_type: 'image',
- mime: 'text/html',
- name: 'ArcGIS REST ImageServer',
- url: '/MapServer'
- }],
- pk: 5,
- ptype: 'gxp_arcrestsource'
- });
- expect(newLayer.extendedParams).toEqual({ pk: 5, alternate: 'remoteWorkspace:1' });
- });
-
it('getGeoNodeMapLayers prefers extendedParams.alternate over layer.name', () => {
const data = {
map: {
diff --git a/geonode_mapstore_client/context_processors.py b/geonode_mapstore_client/context_processors.py
index 6430f0e070..528b38300f 100644
--- a/geonode_mapstore_client/context_processors.py
+++ b/geonode_mapstore_client/context_processors.py
@@ -17,17 +17,27 @@
def resource_urls(request):
"""Global values to pass to templates"""
+ SITE_URL = (getattr(settings, "SITEURL", "") or "").rstrip("/")
+ default_catalogue_selected_service = "GeoNode"
+ default_catalogue_services = {
+ "GeoNode": {
+ "type": "geonode",
+ "url": SITE_URL,
+ "autoload": True,
+ "title": "GeoNode"
+ }
+ }
defaults = dict(GEOAPPS=["GeoStory", "GeoDashboard", "MapViewer"])
defaults["GEONODE_SETTINGS"] = {
"MAP_BASELAYERS": getattr(settings, "MAPSTORE_BASELAYERS", []),
"MAP_BASELAYERS_SOURCES": getattr(settings, "MAPSTORE_BASELAYERS_SOURCES", {}),
- "CATALOGUE_SERVICES": getattr(settings, "MAPSTORE_CATALOGUE_SERVICES", {}),
+ "CATALOGUE_SERVICES": getattr(settings, "MAPSTORE_CATALOGUE_SERVICES", default_catalogue_services),
"CATALOGUE_SELECTED_SERVICE": getattr(
- settings, "MAPSTORE_CATALOGUE_SELECTED_SERVICE", None
+ settings, "MAPSTORE_CATALOGUE_SELECTED_SERVICE", default_catalogue_selected_service
),
- "DASHBOARD_CATALOGUE_SERVICES": getattr(settings, "MAPSTORE_DASHBOARD_CATALOGUE_SERVICES", {}),
+ "DASHBOARD_CATALOGUE_SERVICES": getattr(settings, "MAPSTORE_DASHBOARD_CATALOGUE_SERVICES", default_catalogue_services),
"DASHBOARD_CATALOGUE_SELECTED_SERVICE": getattr(
- settings, "MAPSTORE_DASHBOARD_CATALOGUE_SELECTED_SERVICE", None
+ settings, "MAPSTORE_DASHBOARD_CATALOGUE_SELECTED_SERVICE", default_catalogue_selected_service
),
"CREATE_LAYER": getattr(settings, "CREATE_LAYER", False),
"DEFAULT_MAP_CENTER_X": getattr(settings, "DEFAULT_MAP_CENTER_X", 0),
@@ -55,7 +65,7 @@ def resource_urls(request):
"PROJECTION_DEFS_ENDPOINT": getattr(
settings,
"MAPSTORE_PROJECTION_DEFS_ENDPOINT",
- (getattr(settings, "SITEURL", "") or "").rstrip("/") + "/geoserver",
+ SITE_URL + "/geoserver",
),
"PLUGINS_CONFIG_PATCH_RULES": getattr(
settings, "MAPSTORE_PLUGINS_CONFIG_PATCH_RULES", []
diff --git a/geonode_mapstore_client/static/mapstore/configs/localConfig.json b/geonode_mapstore_client/static/mapstore/configs/localConfig.json
index e5907a0faa..e21c333e8d 100644
--- a/geonode_mapstore_client/static/mapstore/configs/localConfig.json
+++ b/geonode_mapstore_client/static/mapstore/configs/localConfig.json
@@ -1959,15 +1959,6 @@
"type": "divider",
"disableIf": "{not canEditResource(state('isNewResource'), state('gnResourceData'))}"
},
- {
- "type": "plugin",
- "name": "DatasetsCatalog",
- "disableIf": "{not canEditResource(state('isNewResource'), state('gnResourceData'))}"
- },
- {
- "type": "divider",
- "disableIf": "{not canEditResource(state('isNewResource'), state('gnResourceData'))}"
- },
{
"type": "plugin",
"name": "DocumentsCatalog",
@@ -2441,10 +2432,6 @@
"symbolsPath": "/static/mapstore/symbols/"
}
},
- {
- "mandatory": true,
- "name": "DatasetsCatalog"
- },
{
"mandatory": true,
"name": "DocumentsCatalog"
@@ -2459,20 +2446,21 @@
"name": "SidebarMenu"
},
{
- "name": "MetadataExplorer",
+ "name": "Catalog",
"cfg": {
- "wrap": true,
+ "hideIdentifier": true,
"serviceTypes": [
- { "name": "csw", "label": "CSW" },
- { "name": "wms", "label": "WMS" },
- { "name": "wmts", "label": "WMTS" },
- { "name": "tms", "label": "TMS", "allowedProviders": ["OpenStreetMap", "OpenSeaMap", "Stamen"] },
- { "name": "wfs", "label": "WFS" },
- { "name": "3dtiles", "label": "3D Tiles" },
- { "name": "model", "label": "IFC Model" },
- { "name": "arcgis", "label": "ArcGIS" },
+ { "name": "csw", "label": "CSW" },
+ { "name": "wms", "label": "WMS" },
+ { "name": "wmts", "label": "WMTS" },
+ { "name": "tms", "label": "TMS", "allowedProviders": ["OpenStreetMap", "OpenSeaMap", "Stamen"] },
+ { "name": "wfs", "label": "WFS" },
+ { "name": "3dtiles", "label": "3D Tiles" },
+ { "name": "model", "label": "IFC Model" },
+ { "name": "arcgis", "label": "ArcGIS" },
{ "name": "cog", "label": "COG" },
- { "name": "flatgeobuf", "label": "FlatGeobuf" }
+ { "name": "flatgeobuf", "label": "FlatGeobuf" },
+ { "name": "geonode", "label": "GeoNode" }
]
}
},
diff --git a/geonode_mapstore_client/static/mapstore/configs/pluginsConfig.json b/geonode_mapstore_client/static/mapstore/configs/pluginsConfig.json
index 17f05f571b..89a4e51bd5 100644
--- a/geonode_mapstore_client/static/mapstore/configs/pluginsConfig.json
+++ b/geonode_mapstore_client/static/mapstore/configs/pluginsConfig.json
@@ -547,7 +547,7 @@
}
},
{
- "name": "MetadataExplorer",
+ "name": "Catalog",
"glyph": "folder-open",
"title": "plugins.MetadataExplorer.title",
"description": "plugins.MetadataExplorer.description",
@@ -555,17 +555,19 @@
"SidebarMenu"
],
"defaultConfig": {
- "wrap": true,
+ "hideIdentifier": true,
"serviceTypes": [
- { "name": "csw", "label": "CSW" },
- { "name": "wms", "label": "WMS" },
- { "name": "wmts", "label": "WMTS" },
- { "name": "tms", "label": "TMS", "allowedProviders": ["OpenStreetMap", "OpenSeaMap", "Stamen"] },
- { "name": "wfs", "label": "WFS" },
- { "name": "3dtiles", "label": "3D Tiles" },
- { "name": "model", "label": "IFC Model" },
- { "name": "arcgis", "label": "ArcGIS" },
- { "name": "cog", "label": "COG" }
+ { "name": "csw", "label": "CSW" },
+ { "name": "wms", "label": "WMS" },
+ { "name": "wmts", "label": "WMTS" },
+ { "name": "tms", "label": "TMS", "allowedProviders": ["OpenStreetMap", "OpenSeaMap", "Stamen"] },
+ { "name": "wfs", "label": "WFS" },
+ { "name": "3dtiles", "label": "3D Tiles" },
+ { "name": "model", "label": "IFC Model" },
+ { "name": "arcgis", "label": "ArcGIS" },
+ { "name": "cog", "label": "COG" },
+ { "name": "flatgeobuf", "label": "FlatGeobuf" },
+ { "name": "geonode", "label": "GeoNode" }
]
}
},
diff --git a/tutorials/01-settings-variables.md b/tutorials/01-settings-variables.md
index 2f839f5040..180d0d38b4 100644
--- a/tutorials/01-settings-variables.md
+++ b/tutorials/01-settings-variables.md
@@ -5,12 +5,20 @@ name | description | default value
--- | --- | ---
MAPSTORE_BASELAYERS | list of base layer used in map and dataset viewers | []
MAPSTORE_BASELAYERS_SOURCES | this object defines tilematrix sets for wmts base layers | {}
+MAPSTORE_CATALOGUE_SERVICES | catalog services available in the map viewer (keys are service names, values are service config objects with `type`, `url`, `title`, and optional `autoload`) | `{"GeoNode": {"type": "geonode", "url": SITEURL, "autoload": true, "title": "GeoNode"}}`
+MAPSTORE_CATALOGUE_SELECTED_SERVICE | key of the catalog service selected by default in the map viewer | `"GeoNode"`
+MAPSTORE_DASHBOARD_CATALOGUE_SERVICES | catalog services available in the dashboard viewer (same structure as `MAPSTORE_CATALOGUE_SERVICES`) | same as `MAPSTORE_CATALOGUE_SERVICES` default
+MAPSTORE_DASHBOARD_CATALOGUE_SELECTED_SERVICE | key of the catalog service selected by default in the dashboard viewer | `"GeoNode"`
+CREATE_LAYER | enables the create layer feature in the map viewer toolbar | False
DEFAULT_MAP_CENTER_X | initial x center position of new map | 0
DEFAULT_MAP_CENTER_Y | initial y center position of new map | 0
DEFAULT_MAP_CRS | crs used by the map and dataset viewers | EPSG:3857
DEFAULT_MAP_ZOOM | initial zoom of new map | 0
DEFAULT_TILE_SIZE | tiles size used by map and dataset viewers by default | 512
DEFAULT_LAYER_FORMAT | tiles format used by map and dataset viewers by default | 'image/png'
+THUMBNAIL_SIZE | default size of resource thumbnails | `{"width": 500, "height": 200}`
+MAPSTORE_TRANSLATIONS_PATH | list of paths where the client looks for translation files | `["/static/mapstore/ms-translations", "/static/mapstore/gn-translations"]`
+MAPSTORE_PROJECTION_DEFS | list of custom projection definitions to register in the client | []
MAPSTORE_PROJECTION_DEFS_ENDPOINT | base URL of a GeoServer instance. Enables the remote projection search feature | SITEURL + '/geoserver' (embedded GeoServer)