diff --git a/geonode_mapstore_client/client/js/actions/gnresource.js b/geonode_mapstore_client/client/js/actions/gnresource.js
index c0ef88d8c6..d56191bc4e 100644
--- a/geonode_mapstore_client/client/js/actions/gnresource.js
+++ b/geonode_mapstore_client/client/js/actions/gnresource.js
@@ -42,6 +42,8 @@ export const SET_MAP_VIEWER_LINKED_RESOURCE = 'GEONODE:SET_MAP_VIEWER_LINKED_RES
export const MANAGE_LINKED_RESOURCE = 'GEONODE:MANAGE_LINKED_RESOURCE';
export const SET_DEFAULT_VIEWER_PLUGINS = 'GEONODE:SET_DEFAULT_VIEWER_PLUGINS';
export const SET_SELECTED_LAYER = 'GEONODE:SET_SELECTED_LAYER';
+export const UPDATE_LAYER_DATASET = 'GEONODE:UPDATE_LAYER_DATASET';
+export const SET_SELECTED_LAYER_DATASET = 'GEONODE:SET_SELECTED_LAYER_DATASET';
/**
* Actions for GeoNode resource
@@ -398,3 +400,23 @@ export function setSelectedLayer(layer) {
layer
};
}
+
+/**
+ * Update layer dataset
+ */
+export function updateLayerDataset(layer) {
+ return {
+ type: UPDATE_LAYER_DATASET,
+ layer
+ };
+}
+
+/**
+ * Get layer dataset
+ */
+export function setLayerDataset(layerId) {
+ return {
+ type: SET_SELECTED_LAYER_DATASET,
+ layerId
+ };
+}
diff --git a/geonode_mapstore_client/client/js/apps/gn-catalogue.js b/geonode_mapstore_client/client/js/apps/gn-catalogue.js
index bce72e3513..4ee9d289b0 100644
--- a/geonode_mapstore_client/client/js/apps/gn-catalogue.js
+++ b/geonode_mapstore_client/client/js/apps/gn-catalogue.js
@@ -64,6 +64,7 @@ import {
import { CATALOGUE_ROUTES, appRouteComponentTypes } from '@js/utils/AppRoutesUtils';
import { updateGeoNodeSettings } from '@js/actions/gnsettings';
import {
+ gnFetchMissingLayerData,
gnCheckSelectedDatasetPermissions,
gnSetDatasetsPermissions,
// to make the current layout work we need this epic
@@ -140,6 +141,7 @@ getEndpoints()
const appEpics = cleanEpics({
...standardEpics,
...configEpics,
+ gnFetchMissingLayerData,
gnCheckSelectedDatasetPermissions,
gnSetDatasetsPermissions,
...pluginsDefinition.epics,
diff --git a/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsPanel.jsx b/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsPanel.jsx
index d7fedc93b5..aa86d83ee0 100644
--- a/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsPanel.jsx
+++ b/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsPanel.jsx
@@ -38,7 +38,7 @@ const EditTitle = ({ title, onEdit, disabled }) => {
setTextValue(evt.target.value);
onEdit(evt.target.value);
}}
- value={textValue}
+ value={onEdit ? textValue : title}
disabled={disabled}
/>
);
diff --git a/geonode_mapstore_client/client/js/epics/index.js b/geonode_mapstore_client/client/js/epics/index.js
index 3d86091a85..9e34081f65 100644
--- a/geonode_mapstore_client/client/js/epics/index.js
+++ b/geonode_mapstore_client/client/js/epics/index.js
@@ -14,12 +14,13 @@ import Rx from "rxjs";
import { setEditPermissionStyleEditor, INIT_STYLE_SERVICE } from "@mapstore/framework/actions/styleeditor";
import { getSelectedLayer, layersSelector } from "@mapstore/framework/selectors/layers";
import { getConfigProp } from "@mapstore/framework/utils/ConfigUtils";
-import { getDatasetByName, getDatasetsByName, getResourceByTypeAndByPk } from '@js/api/geonode/v2';
+import { getDatasetByName, getDatasetsByName, getDatasetByPk } from '@js/api/geonode/v2';
import { MAP_CONFIG_LOADED } from '@mapstore/framework/actions/config';
import { setPermission } from '@mapstore/framework/actions/featuregrid';
import { SELECT_NODE, updateNode, ADD_LAYER } from '@mapstore/framework/actions/layers';
-import { setSelectedDatasetPermissions, setSelectedLayer } from '@js/actions/gnresource';
+import { setSelectedDatasetPermissions, setSelectedLayer, updateLayerDataset, setLayerDataset } from '@js/actions/gnresource';
import { updateMapLayoutEpic as msUpdateMapLayoutEpic } from '@mapstore/framework/epics/maplayout';
+import isEmpty from 'lodash/isEmpty';
// We need to include missing epics. The plugins that normally include this epic is not used.
@@ -56,6 +57,37 @@ export const gnCheckSelectedDatasetPermissions = (action$, { getState } = {}) =>
);
});
+/**
+ * Fetches missing values for selected layers
+ */
+export const gnFetchMissingLayerData = (action$, { getState } = {}) =>
+ action$.ofType(SELECT_NODE)
+ .filter(({ nodeType }) => nodeType && nodeType === "layer")
+ .switchMap(() => {
+ const state = getState() || {};
+ const layer = getSelectedLayer(state);
+ const layerResourceId = layer?.extendedParams?.pk;
+ const layerResourceDataset = state.gnresource.data?.maplayers?.find(mapLayer => mapLayer.dataset?.pk === parseInt(layerResourceId, 10))?.dataset;
+ return layerResourceDataset
+ ? isEmpty(layerResourceDataset?.linkedResources)
+ ? Rx.Observable.defer(() =>
+ getDatasetByPk(layerResourceId)
+ .then((layerDataset) => layerDataset)
+ .catch(() => [])
+ ).switchMap((layerDataset) =>
+ Rx.Observable.of(
+ updateLayerDataset(layerDataset),
+ setLayerDataset(layerResourceId)
+ )
+ )
+ : Rx.Observable.of(
+ setLayerDataset(layerResourceId)
+ )
+ : Rx.Observable.of(
+ setLayerDataset()
+ )
+ });
+
/**
* Checks the permissions for layers when a map is loaded and when a new layer is added
diff --git a/geonode_mapstore_client/client/js/plugins/LayerDetailViewer.jsx b/geonode_mapstore_client/client/js/plugins/LayerDetailViewer.jsx
index 247f88f5c3..8cca68e8a2 100644
--- a/geonode_mapstore_client/client/js/plugins/LayerDetailViewer.jsx
+++ b/geonode_mapstore_client/client/js/plugins/LayerDetailViewer.jsx
@@ -12,102 +12,65 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { getConfigProp } from '@mapstore/framework/utils/ConfigUtils';
import DetailsPanel from '@js/components/DetailsPanel';
-import { userSelector } from '@mapstore/framework/selectors/security';
-import {
- editTitleResource,
- editAbstractResource,
- editThumbnailResource,
- setFavoriteResource,
- setMapThumbnail,
- setResourceThumbnail,
- enableMapThumbnailViewer,
- downloadResource
-} from '@js/actions/gnresource';
-import { processingDownload } from '@js/selectors/resourceservice';
+import { enableMapThumbnailViewer } from '@js/actions/gnresource';
import FaIcon from '@js/components/FaIcon/FaIcon';
import controls from '@mapstore/framework/reducers/controls';
import { setControlProperty } from '@mapstore/framework/actions/controls';
import gnresource from '@js/reducers/gnresource';
-import gnsearch from '@js/reducers/gnsearch';
-import {
- canEditResource,
- getResourceId,
- isThumbnailChanged,
- updatingThumbnailResource
-} from '@js/selectors/resource';
-import {
- getUpdatedLayer
-} from '@mapstore/framework/selectors/styleeditor';
+import { getSelectedLayerDataset } from '@js/selectors/resource';
import GNButton from '@js/components/Button';
import useDetectClickOut from '@js/hooks/useDetectClickOut';
import OverlayContainer from '@js/components/OverlayContainer';
import { withRouter } from 'react-router';
import { hashLocationToHref } from '@js/utils/SearchUtils';
-import Message from '@mapstore/framework/components/I18N/Message';
import { mapSelector } from '@mapstore/framework/selectors/map';
-import { resourceHasPermission } from '@js/utils/ResourceUtils';
import { parsePluginConfigExpressions } from '@js/utils/MenuUtils';
import tooltip from '@mapstore/framework/components/misc/enhancers/tooltip';
-import layerDetailViewerEpics from '@js/epics/layerdetailviewer';
+import tabComponents from '@js/plugins/detailviewer/tabComponents';
const Button = tooltip(GNButton);
const ConnectedDetailsPanel = connect(
createSelector([
- state => state?.gnresource?.layerDataset || null,
+ state => state?.gnresource?.selectedLayerDataset || null,
state => state?.gnresource?.loading || false,
- state => state?.gnresource?.layerDataset?.favorite || false,
- state => state?.gnsave?.savingThumbnailMap || false,
- isThumbnailChanged,
- updatingThumbnailResource,
mapSelector,
- state => state?.gnresource?.showMapThumbnail || false,
- processingDownload
- ], (resource, loading, favorite, savingThumbnailMap, thumbnailChanged, resourceThumbnailUpdating, mapData, showMapThumbnail, downloading) => ({
+ state => state?.gnresource?.showMapThumbnail || false
+ ], (resource, loading, mapData, showMapThumbnail) => ({
resource,
loading,
- savingThumbnailMap,
- favorite: favorite,
- isThumbnailChanged: thumbnailChanged,
- resourceThumbnailUpdating,
initialBbox: mapData?.bbox,
enableMapViewer: showMapThumbnail,
- downloading,
- canDownload: resourceHasPermission(resource, 'download_resourcebase'),
- resourceId: resource.pk
+ resourceId: resource?.pk,
+ tabComponents
})),
{
closePanel: setControlProperty.bind(null, 'rightOverlay', 'enabled', false),
- onFavorite: setFavoriteResource,
- onMapThumbnail: setMapThumbnail,
- onResourceThumbnail: setResourceThumbnail,
- onClose: enableMapThumbnailViewer,
- onAction: downloadResource
+ onClose: enableMapThumbnailViewer
}
)(DetailsPanel);
-const ButtonViewer = ({ onClick, layer, size, status, showMessage }) => {
- const layerResourceId = layer?.extendedParams?.pk;
+const ButtonViewer = ({ onClick, layer, size, status }) => {
+ const layerResourceId = layer?.pk;
const handleClickButton = () => {
onClick();
};
-
return layerResourceId && status === 'LAYER' ? (
}
>
- {!showMessage ? : }
+
) : null;
};
const ConnectedButton = connect(
- createSelector([getUpdatedLayer], (layer) => ({
- layer: layer
+ createSelector([
+ getSelectedLayerDataset
+ ], (layer) => ({
+ layer
})),
{
onClick: setControlProperty.bind(
@@ -122,11 +85,6 @@ const ConnectedButton = connect(
function LayerDetailViewer({
location,
enabled,
- onEditResource,
- onEditAbstractResource,
- onEditThumbnail,
- canEdit,
- user,
onClose,
monitoredState,
queryPathname = '/',
@@ -134,17 +92,6 @@ function LayerDetailViewer({
}) {
const parsedConfig = parsePluginConfigExpressions(monitoredState, { tabs });
- const handleTitleValue = (val) => {
- onEditResource(val);
- };
-
- const handleAbstractValue = (val) => {
- onEditAbstractResource(val);
- };
- const handleEditThumbnail = (val) => {
- onEditThumbnail(val, true);
- };
-
const node = useDetectClickOut({
disabled: !enabled,
onClickOut: () => {
@@ -166,11 +113,11 @@ function LayerDetailViewer({
className="gn-overlay-wrapper"
>
{}}
+ activeEditMode={false}
+ enableFavorite={false}
formatHref={handleFormatHref}
tabs={parsedConfig.tabs}
pathname={queryPathname}
@@ -184,24 +131,16 @@ const LayerDetailViewerPlugin = connect(
[
(state) =>
state?.controls?.rightOverlay?.enabled === 'LayerDetailViewer',
- canEditResource,
- getUpdatedLayer,
- getResourceId,
- userSelector,
+ getSelectedLayerDataset,
state => getMonitoredState(state, getConfigProp('monitorState'))
],
- (enabled, canEdit, layer, user, monitoredState) => ({
+ (enabled, layer, monitoredState) => ({
enabled,
- canEdit,
layer,
- user,
monitoredState
})
),
{
- onEditResource: editTitleResource,
- onEditAbstractResource: editAbstractResource,
- onEditThumbnail: editThumbnailResource,
onClose: setControlProperty.bind(null, 'rightOverlay', 'enabled', false)
}
)(withRouter(LayerDetailViewer));
@@ -211,13 +150,12 @@ export default createPlugin('LayerDetailViewer', {
containers: {
TOC: {
target: 'toolbar',
+ name: 'LayerDetailViewerButton',
Component: ConnectedButton
}
},
- epics: layerDetailViewerEpics,
reducers: {
gnresource,
- gnsearch,
controls
}
-});
+});
\ No newline at end of file
diff --git a/geonode_mapstore_client/client/js/plugins/detailviewer/tabComponents.js b/geonode_mapstore_client/client/js/plugins/detailviewer/tabComponents.js
new file mode 100644
index 0000000000..0343b7fcfe
--- /dev/null
+++ b/geonode_mapstore_client/client/js/plugins/detailviewer/tabComponents.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2024, 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 { connect } from 'react-redux';
+import DetailsLocations from '@js/components/DetailsPanel/DetailsLocations';
+import DetailsAssets from '@js/components/DetailsPanel/DetailsAssets';
+import DetailsAttributeTable from '@js/components/DetailsPanel/DetailsAttributeTable';
+import DetailsLinkedResources from '@js/components/DetailsPanel/DetailsLinkedResources';
+import DetailsSettings from '@js/components/DetailsPanel/DetailsSettings';
+import { updateResourceProperties } from '@js/actions/gnresource';
+
+const tabComponents = {
+ 'attribute-table': DetailsAttributeTable,
+ 'linked-resources': DetailsLinkedResources,
+ 'locations': DetailsLocations,
+ 'assets': DetailsAssets,
+ 'settings': connect(() => ({}), { onChange: updateResourceProperties })(DetailsSettings)
+};
+
+export default tabComponents;
\ No newline at end of file
diff --git a/geonode_mapstore_client/client/js/plugins/index.js b/geonode_mapstore_client/client/js/plugins/index.js
index 969674687b..ffebecae4d 100644
--- a/geonode_mapstore_client/client/js/plugins/index.js
+++ b/geonode_mapstore_client/client/js/plugins/index.js
@@ -376,6 +376,10 @@ export const plugins = {
'VisualStyleEditor',
() => import(/* webpackChunkName: 'plugins/visual-style-editor-plugin' */ '@js/plugins/VisualStyleEditor')
),
+ LayerDetailViewerPlugin: toModulePlugin(
+ 'LayerDetailViewer',
+ () => import(/* webpackChunkName: 'plugins/detail-viewer-plugin' */ '@js/plugins/LayerDetailViewer')
+ ),
LegendPlugin: toModulePlugin(
'Legend',
() => import(/* webpackChunkName: 'plugins/legend-plugin' */ '@js/plugins/Legend')
@@ -436,10 +440,6 @@ export const plugins = {
'Settings',
() => import(/* webpackChunkName: 'plugins/settings' */ '@mapstore/framework/plugins/Settings')
),
- LayerDetailViewerPlugin: toModulePlugin(
- 'LayerDetailViewer',
- () => import(/* webpackChunkName: 'plugins/detail-viewer-plugin' */ '@js/plugins/LayerDetailViewer')
- ),
DataCiteDownloadPlugin: toModulePlugin(
'DataCiteDownload',
() => import('@js/plugins/downloads/DataCiteDownload')
diff --git a/geonode_mapstore_client/client/js/reducers/gnresource.js b/geonode_mapstore_client/client/js/reducers/gnresource.js
index cb1d71a2b0..29f020425d 100644
--- a/geonode_mapstore_client/client/js/reducers/gnresource.js
+++ b/geonode_mapstore_client/client/js/reducers/gnresource.js
@@ -37,7 +37,9 @@ import {
SET_RESOURCE_PATH_PARAMETERS,
SET_MAP_VIEWER_LINKED_RESOURCE,
SET_DEFAULT_VIEWER_PLUGINS,
- SET_SELECTED_LAYER
+ SET_SELECTED_LAYER,
+ UPDATE_LAYER_DATASET,
+ SET_SELECTED_LAYER_DATASET
} from '@js/actions/gnresource';
import {
cleanCompactPermissions,
@@ -279,6 +281,38 @@ function gnresource(state = defaultState, action) {
...state,
selectedLayer: action.layer
};
+ case SET_SELECTED_LAYER_DATASET:
+ return {
+ ...state,
+ selectedLayerDataset: state.data?.maplayers?.find(layer => layer.dataset?.pk === parseInt(action.layerId, 10))?.dataset
+ };
+ case UPDATE_LAYER_DATASET:
+ const { pk, ...newData } = action.layer;
+ let linkedResources = action.layer.linked_resources ?? {};
+ if (!isEmpty(linkedResources)) {
+ const linkedTo = linkedResources.linked_to ?? [];
+ const linkedBy = linkedResources.linked_by ?? [];
+ linkedResources = isEmpty(linkedTo) && isEmpty(linkedBy) ? {} : ({ linkedTo, linkedBy });
+ }
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ maplayers: state.data?.maplayers?.map(layer => {
+ if (layer.dataset?.pk === parseInt(pk, 10)) {
+ return {
+ ...layer,
+ dataset: {
+ ...layer.dataset,
+ ...newData,
+ linkedResources
+ }
+ };
+ }
+ return layer;
+ })
+ }
+ };
default:
return state;
}
diff --git a/geonode_mapstore_client/client/js/selectors/resource.js b/geonode_mapstore_client/client/js/selectors/resource.js
index 9fbb411306..a034cbd45c 100644
--- a/geonode_mapstore_client/client/js/selectors/resource.js
+++ b/geonode_mapstore_client/client/js/selectors/resource.js
@@ -92,6 +92,14 @@ export const getLayerResourceData = (state) => {
return state?.gnresource?.layerDataset;
};
+export const getSelectedLayer = (state) => {
+ return state?.gnresource?.selectedLayer;
+};
+
+export const getSelectedLayerDataset = (state) => {
+ return state?.gnresource?.selectedLayerDataset;
+};
+
export const getCompactPermissions = (state) => {
const compactPermissions = state?.gnresource?.compactPermissions || {};
return compactPermissions;
diff --git a/geonode_mapstore_client/static/mapstore/configs/localConfig.json b/geonode_mapstore_client/static/mapstore/configs/localConfig.json
index 17df102923..8e0fef2847 100644
--- a/geonode_mapstore_client/static/mapstore/configs/localConfig.json
+++ b/geonode_mapstore_client/static/mapstore/configs/localConfig.json
@@ -89,8 +89,8 @@
"path": "gnresource.viewerLinkedResource"
},
{
- "name": "selectedLayer",
- "path": "gnresource.selectedLayer"
+ "name": "gnResourceSelectedLayerDataset",
+ "path": "gnresource.selectedLayerDataset"
}
],
"projectionDefs": [],
@@ -2795,6 +2795,151 @@
"cfg": {
"wrap": true
}
+ },
+ {
+ "mandatory": true,
+ "name": "LayerDetailViewer",
+ "cfg": {
+ "containerPosition": "rightOverlay",
+ "tabs": [
+ {
+ "type": "tab",
+ "id": "info",
+ "labelId": "gnviewer.info",
+ "items": [
+ {
+ "type": "text",
+ "labelId": "gnviewer.title",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'title')}"
+ },
+ {
+ "type": "link",
+ "labelId": "gnviewer.owner",
+ "href": "{'/people/profile/' + context.get(state('gnResourceSelectedLayerDataset'), 'owner.username')}",
+ "value": "{context.getUserResourceName(context.get(state('gnResourceSelectedLayerDataset'), 'owner'))}",
+ "disableIf": "{!context.get(state('gnResourceSelectedLayerDataset'), 'owner.username')}"
+ },
+ {
+ "type": "date",
+ "format": "YYYY-MM-DD HH:mm",
+ "labelId": "{'gnviewer.'+context.get(state('gnResourceSelectedLayerDataset'), 'date_type')}",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'date')}"
+ },
+ {
+ "type": "date",
+ "format": "YYYY-MM-DD HH:mm",
+ "labelId": "gnviewer.created",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'created')}"
+ },
+ {
+ "type": "date",
+ "format": "YYYY-MM-DD HH:mm",
+ "labelId": "gnviewer.lastModified",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'last_updated')}"
+ },
+ {
+ "type": "query",
+ "labelId": "gnviewer.resourceType",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'resource_type')}",
+ "pathname": "/",
+ "query": {
+ "f": "{context.get(state('gnResourceSelectedLayerDataset'), 'resource_type')}"
+ }
+ },
+ {
+ "type": "{context.isDocumentExternalSource(state('gnResourceSelectedLayerDataset')) ? 'link' : 'text'}",
+ "labelId": "gnviewer.sourceType",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'sourcetype', '').toLowerCase()}",
+ "href": "{context.get(state('gnResourceSelectedLayerDataset'), 'href')}"
+ },
+ {
+ "type": "query",
+ "labelId": "gnviewer.category",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'category.gn_description')}",
+ "pathname": "/",
+ "query": {
+ "filter{category.identifier}": "{context.get(state('gnResourceSelectedLayerDataset'), 'category.identifier')}"
+ }
+ },
+ {
+ "type": "link",
+ "labelId": "gnviewer.pointOfContact",
+ "value": "{context.getUserResourceNames(context.get(state('gnResourceSelectedLayerDataset'), 'poc'))}",
+ "disableIf": "{!context.get(state('gnResourceSelectedLayerDataset'), 'poc')}"
+ },
+ {
+ "type": "query",
+ "labelId": "gnviewer.keywords",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'keywords')}",
+ "valueKey": "name",
+ "pathname": "/",
+ "queryTemplate": {
+ "filter{keywords.slug.in}": "${slug}"
+ }
+ },
+ {
+ "type": "query",
+ "labelId": "gnviewer.regions",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'regions')}",
+ "valueKey": "code",
+ "pathname": "/",
+ "queryTemplate": {
+ "filter{regions.code.in}": "${code}"
+ }
+ },
+ {
+ "type": "text",
+ "labelId": "gnviewer.attribution",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'attribution')}"
+ },
+ {
+ "type": "text",
+ "labelId": "gnviewer.language",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'language')}"
+ },
+ {
+ "type": "html",
+ "labelId": "gnviewer.supplementalInformation",
+ "value": "{context.get(state('gnResourceSelectedLayerDataset'), 'supplemental_information')}"
+ },
+ {
+ "type": "date",
+ "format": "YYYY-MM-DD HH:mm",
+ "labelId": "gnviewer.temporalExtent",
+ "value": {
+ "start": "{context.get(state('gnResourceSelectedLayerDataset'), 'temporal_extent_start')}",
+ "end": "{context.get(state('gnResourceSelectedLayerDataset'), 'temporal_extent_end')}"
+ }
+ },
+ {
+ "type": "link",
+ "style": "label",
+ "labelId": "gnviewer.viewFullMetadata",
+ "href": "{context.getMetadataDetailUrl(state('gnResourceSelectedLayerDataset'))}",
+ "disableIf": "{!context.getMetadataDetailUrl(state('gnResourceSelectedLayerDataset'))}"
+ }
+ ]
+ },
+ {
+ "type": "locations",
+ "id": "locations",
+ "labelId": "gnviewer.locations",
+ "items": "{({extent: context.get(state('gnResourceSelectedLayerDataset'), 'extent')})}"
+ },
+ {
+ "type": "linked-resources",
+ "id": "related",
+ "labelId": "gnviewer.linkedResources.label",
+ "items": "{context.get(state('gnResourceSelectedLayerDataset'), 'linkedResources')}"
+ },
+ {
+ "type": "assets",
+ "id": "assets",
+ "labelId": "gnviewer.assets",
+ "items": "{context.get(state('gnResourceSelectedLayerDataset'), 'assets')}"
+ }
+ ]
+ }
}
],
"geostory_viewer": [