Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds layer info button to map view #22

Merged
merged 1 commit into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 26 additions & 0 deletions geonode_mapstore_client/client/js/actions/gnresource.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

export const RESOURCE_LOADING = 'GEONODE:RESOURCE_LOADING';
export const SET_RESOURCE = 'GEONODE:SET_RESOURCE';
export const SET_LAYER_RESOURCE = 'GEONODE:SET_LAYER_RESOURCE';
export const RESOURCE_ERROR = 'GEONODE:RESOURCE_ERROR';
export const UPDATE_RESOURCE_PROPERTIES = 'GEONODE:UPDATE_RESOURCE_PROPERTIES';
export const UPDATE_LAYER_RESOURCE_PROPERTIES = 'GEONODE:UPDATE_LAYER_RESOURCE_PROPERTIES';
export const SET_RESOURCE_TYPE = 'GEONODE:SET_RESOURCE_TYPE';
export const SET_NEW_RESOURCE = 'GEONODE:SET_NEW_RESOURCE';
export const SET_RESOURCE_ID = 'GEONODE:SET_RESOURCE_ID';
Expand Down Expand Up @@ -64,6 +66,19 @@ export function setResource(data, pending) {
};
}

/**
* Restores resource from the state
* @param {object} data resource data object
* @param {object} pending declare if the request is still pending
*/
export function setLayerResource(data, pending) {
return {
type: SET_LAYER_RESOURCE,
data,
pending
};
}

/**
* Set the resource in the state
* @param {object} data resource data object
Expand Down Expand Up @@ -153,6 +168,17 @@ export function updateResourceProperties(properties) {
};
}

/**
* Update resource properties
* @param {object} properties resource properties to override
*/
export function updateLayerResourceProperties(properties) {
return {
type: UPDATE_LAYER_RESOURCE_PROPERTIES,
properties
};
}

/**
* Set the current resource as new
*/
Expand Down
28 changes: 21 additions & 7 deletions geonode_mapstore_client/client/js/epics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ 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 } from '@js/api/geonode/v2';
import { getDatasetByName, getDatasetsByName, getResourceByTypeAndByPk } 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 } from '@js/actions/gnresource';
import { setSelectedDatasetPermissions, setLayerResource } from '@js/actions/gnresource';
import { updateMapLayoutEpic as msUpdateMapLayoutEpic } from '@mapstore/framework/epics/maplayout';

// We need to include missing epics. The plugins that normally include this epic is not used.
Expand All @@ -37,15 +37,29 @@ export const gnCheckSelectedDatasetPermissions = (action$, { getState } = {}) =>
.switchMap(() => {
const state = getState() || {};
const layer = getSelectedLayer(state);
const layerResourceId = layer?.extendedParams?.pk;
const permissions = layer?.perms || [];
const canEditStyles = permissions.includes("change_dataset_style");
const canEdit = permissions.includes("change_dataset_data");
return layer
? Rx.Observable.of(
setPermission({canEdit}),
setEditPermissionStyleEditor(canEditStyles),
setSelectedDatasetPermissions(permissions)
)
? layerResourceId
? Rx.Observable.defer(() =>
getResourceByTypeAndByPk('dataset', layerResourceId)
.then((layerDataset) => layerDataset)
.catch(() => [])
).switchMap((layerDataset) =>
Rx.Observable.of(
setLayerResource(layerDataset),
setPermission({canEdit}),
setEditPermissionStyleEditor(canEditStyles),
setSelectedDatasetPermissions(permissions)
)
)
: Rx.Observable.of(
setPermission({canEdit}),
setEditPermissionStyleEditor(canEditStyles),
setSelectedDatasetPermissions(permissions)
)
: Rx.Observable.of(
setPermission({canEdit: false}),
setEditPermissionStyleEditor(false),
Expand Down
41 changes: 41 additions & 0 deletions geonode_mapstore_client/client/js/epics/layerdetailviewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2023, 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 { Observable } from 'rxjs';
import isEmpty from 'lodash/isEmpty';
import { getLinkedResourcesByPk } from '@js/api/geonode/v2';
import {
updateLayerResourceProperties,
SET_LAYER_RESOURCE
} from '@js/actions/gnresource';
import { getLayerResourceData } from '@js/selectors/resource';

/**
* Get linked resources for layer
*/
export const gnGetLayerLinkedResources = (action$, store) =>
action$.ofType(SET_LAYER_RESOURCE)
.filter((action) =>
!action.pending && action.data?.pk && isEmpty(getLayerResourceData(store.getState())?.linkedResources)
)
.switchMap((action) =>
Observable.defer(() =>
getLinkedResourcesByPk(action.data.pk)
.then((linkedResources) => linkedResources)
.catch(() => [])
).switchMap((linkedResources) =>
Observable.of(
updateLayerResourceProperties({linkedResources})
)
)
);


export default {
gnGetLayerLinkedResources
};
223 changes: 223 additions & 0 deletions geonode_mapstore_client/client/js/plugins/LayerDetailViewer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
* 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 from 'react';
import { createPlugin, getMonitoredState } from '@mapstore/framework/utils/PluginsUtils';
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 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 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';

const Button = tooltip(GNButton);

const ConnectedDetailsPanel = connect(
createSelector([
state => state?.gnresource?.layerDataset || 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) => ({
resource,
loading,
savingThumbnailMap,
favorite: favorite,
isThumbnailChanged: thumbnailChanged,
resourceThumbnailUpdating,
initialBbox: mapData?.bbox,
enableMapViewer: showMapThumbnail,
downloading,
canDownload: resourceHasPermission(resource, 'download_resourcebase'),
resourceId: resource.pk
})),
{
closePanel: setControlProperty.bind(null, 'rightOverlay', 'enabled', false),
onFavorite: setFavoriteResource,
onMapThumbnail: setMapThumbnail,
onResourceThumbnail: setResourceThumbnail,
onClose: enableMapThumbnailViewer,
onAction: downloadResource
}
)(DetailsPanel);

const ButtonViewer = ({ onClick, layer, size, status, showMessage }) => {
const layerResourceId = layer?.extendedParams?.pk;
const handleClickButton = () => {
onClick();
};

return layerResourceId && status === 'LAYER' ? (
<Button
variant="primary"
className="square-button-md"
size={size}
onClick={handleClickButton}
tooltipId={<Message msgId={`gnviewer.info`} />}
>
{!showMessage ? <FaIcon name="info-circle" /> : <Message msgId="gnviewer.editInfo"/>}
</Button>
) : null;
};

const ConnectedButton = connect(
createSelector([getUpdatedLayer], (layer) => ({
layer: layer
})),
{
onClick: setControlProperty.bind(
null,
'rightOverlay',
'enabled',
'LayerDetailViewer'
)
}
)((ButtonViewer));

function LayerDetailViewer({
location,
enabled,
onEditResource,
onEditAbstractResource,
onEditThumbnail,
canEdit,
user,
onClose,
monitoredState,
queryPathname = '/',
tabs = []
}) {
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: () => {
onClose();
}
});

const handleFormatHref = (options) => {
return hashLocationToHref({
location,
...options
});
};

return (
<OverlayContainer
enabled={enabled}
ref={node}
className="gn-overlay-wrapper"
>
<ConnectedDetailsPanel
editTitle={handleTitleValue}
editAbstract={handleAbstractValue}
editThumbnail={handleEditThumbnail}
activeEditMode={enabled && canEdit}
enableFavorite={!!user}
formatHref={handleFormatHref}
tabs={parsedConfig.tabs}
pathname={queryPathname}
/>
</OverlayContainer>
);
}

const LayerDetailViewerPlugin = connect(
createSelector(
[
(state) =>
state?.controls?.rightOverlay?.enabled === 'LayerDetailViewer',
canEditResource,
getUpdatedLayer,
getResourceId,
userSelector,
state => getMonitoredState(state, getConfigProp('monitorState'))
],
(enabled, canEdit, layer, user, monitoredState) => ({
enabled,
canEdit,
layer,
user,
monitoredState
})
),
{
onEditResource: editTitleResource,
onEditAbstractResource: editAbstractResource,
onEditThumbnail: editThumbnailResource,
onClose: setControlProperty.bind(null, 'rightOverlay', 'enabled', false)
}
)(withRouter(LayerDetailViewer));

export default createPlugin('LayerDetailViewer', {
component: LayerDetailViewerPlugin,
containers: {
TOC: {
target: 'toolbar',
Component: ConnectedButton
}
},
epics: layerDetailViewerEpics,
reducers: {
gnresource,
gnsearch,
controls
}
});
4 changes: 4 additions & 0 deletions geonode_mapstore_client/client/js/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,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')
Expand Down