Skip to content

Commit 9b1dcb4

Browse files
ahmdthrdsuren1
andauthored
Added table of content to open layer details in map view. (#1661)
* Added table of content to open layer details in map view. Fixes issue#1660. * Updated to the newer version. * Code refactor * Fixed instability issues for info button on TOC. * Fixed linked resources for info button on TOC and code cleanup. * Avoided repeating calls to get layer dataset fields including linked resources for info button on TOC. * Fixed localConfig json file for the info button. * Reverted api changes to include linked resources, because they are already included for 'viewer_common' api_preset. * Fixed getDatasetByPk definition. * Fixed linting issues. * Fixed bug where info button was misbehaving when any maplayer had no dataset. * Fixed a bug where the title of the dataset was not changing on the details panel. --------- Co-authored-by: Suren <[email protected]>
1 parent 860eca2 commit 9b1dcb4

File tree

9 files changed

+418
-6
lines changed

9 files changed

+418
-6
lines changed

geonode_mapstore_client/client/js/actions/gnresource.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export const SET_MAP_VIEWER_LINKED_RESOURCE = 'GEONODE:SET_MAP_VIEWER_LINKED_RES
4040
export const MANAGE_LINKED_RESOURCE = 'GEONODE:MANAGE_LINKED_RESOURCE';
4141
export const SET_DEFAULT_VIEWER_PLUGINS = 'GEONODE:SET_DEFAULT_VIEWER_PLUGINS';
4242
export const SET_SELECTED_LAYER = 'GEONODE:SET_SELECTED_LAYER';
43+
export const UPDATE_LAYER_DATASET = 'GEONODE:UPDATE_LAYER_DATASET';
44+
export const SET_SELECTED_LAYER_DATASET = 'GEONODE:SET_SELECTED_LAYER_DATASET';
4345

4446
/**
4547
* Actions for GeoNode resource
@@ -372,3 +374,23 @@ export function setSelectedLayer(layer) {
372374
layer
373375
};
374376
}
377+
378+
/**
379+
* Update layer dataset
380+
*/
381+
export function updateLayerDataset(layer) {
382+
return {
383+
type: UPDATE_LAYER_DATASET,
384+
layer
385+
};
386+
}
387+
388+
/**
389+
* Get layer dataset
390+
*/
391+
export function setLayerDataset(layerId) {
392+
return {
393+
type: SET_SELECTED_LAYER_DATASET,
394+
layerId
395+
};
396+
}

geonode_mapstore_client/client/js/apps/gn-catalogue.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
import { CATALOGUE_ROUTES, appRouteComponentTypes } from '@js/utils/AppRoutesUtils';
6565
import { updateGeoNodeSettings } from '@js/actions/gnsettings';
6666
import {
67+
gnFetchMissingLayerData,
6768
gnCheckSelectedDatasetPermissions,
6869
gnSetDatasetsPermissions,
6970
// to make the current layout work we need this epic
@@ -140,6 +141,7 @@ getEndpoints()
140141
const appEpics = cleanEpics({
141142
...standardEpics,
142143
...configEpics,
144+
gnFetchMissingLayerData,
143145
gnCheckSelectedDatasetPermissions,
144146
gnSetDatasetsPermissions,
145147
...pluginsDefinition.epics,

geonode_mapstore_client/client/js/components/DetailsPanel/DetailsPanel.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const EditTitle = ({ title, onEdit, disabled }) => {
3838
setTextValue(evt.target.value);
3939
onEdit(evt.target.value);
4040
}}
41-
value={textValue}
41+
value={onEdit ? textValue : title}
4242
disabled={disabled}
4343
/>
4444
</div>);

geonode_mapstore_client/client/js/epics/index.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ import Rx from "rxjs";
1414
import { setEditPermissionStyleEditor, INIT_STYLE_SERVICE } from "@mapstore/framework/actions/styleeditor";
1515
import { getSelectedLayer, layersSelector } from "@mapstore/framework/selectors/layers";
1616
import { getConfigProp } from "@mapstore/framework/utils/ConfigUtils";
17-
import { getDatasetByName, getDatasetsByName } from '@js/api/geonode/v2';
17+
import { getDatasetByName, getDatasetsByName, getDatasetByPk } from '@js/api/geonode/v2';
1818
import { MAP_CONFIG_LOADED } from '@mapstore/framework/actions/config';
1919
import { setPermission } from '@mapstore/framework/actions/featuregrid';
2020
import { SELECT_NODE, updateNode, ADD_LAYER } from '@mapstore/framework/actions/layers';
21-
import { setSelectedDatasetPermissions, setSelectedLayer } from '@js/actions/gnresource';
21+
import { setSelectedDatasetPermissions, setSelectedLayer, updateLayerDataset, setLayerDataset } from '@js/actions/gnresource';
2222
import { updateMapLayoutEpic as msUpdateMapLayoutEpic } from '@mapstore/framework/epics/maplayout';
23+
import isEmpty from 'lodash/isEmpty';
2324

2425
// We need to include missing epics. The plugins that normally include this epic is not used.
2526

@@ -55,6 +56,37 @@ export const gnCheckSelectedDatasetPermissions = (action$, { getState } = {}) =>
5556
);
5657
});
5758

59+
/**
60+
* Fetches missing values for selected layers
61+
*/
62+
export const gnFetchMissingLayerData = (action$, { getState } = {}) =>
63+
action$.ofType(SELECT_NODE)
64+
.filter(({ nodeType }) => nodeType && nodeType === "layer")
65+
.switchMap(() => {
66+
const state = getState() || {};
67+
const layer = getSelectedLayer(state);
68+
const layerResourceId = layer?.extendedParams?.pk;
69+
const layerResourceDataset = state.gnresource.data?.maplayers?.find(mapLayer => mapLayer.dataset?.pk === parseInt(layerResourceId, 10))?.dataset;
70+
return layerResourceDataset
71+
? isEmpty(layerResourceDataset?.linkedResources)
72+
? Rx.Observable.defer(() =>
73+
getDatasetByPk(layerResourceId)
74+
.then((layerDataset) => layerDataset)
75+
.catch(() => [])
76+
).switchMap((layerDataset) =>
77+
Rx.Observable.of(
78+
updateLayerDataset(layerDataset),
79+
setLayerDataset(layerResourceId)
80+
)
81+
)
82+
: Rx.Observable.of(
83+
setLayerDataset(layerResourceId)
84+
)
85+
: Rx.Observable.of(
86+
setLayerDataset()
87+
)
88+
});
89+
5890

5991
/**
6092
* Checks the permissions for layers when a map is loaded and when a new layer is added
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Copyright 2021, 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 React from 'react';
10+
import { createPlugin, getMonitoredState } from '@mapstore/framework/utils/PluginsUtils';
11+
import { connect } from 'react-redux';
12+
import { createSelector } from 'reselect';
13+
import { getConfigProp } from '@mapstore/framework/utils/ConfigUtils';
14+
import DetailsPanel from '@js/components/DetailsPanel';
15+
import { enableMapThumbnailViewer } from '@js/actions/gnresource';
16+
import FaIcon from '@js/components/FaIcon/FaIcon';
17+
import controls from '@mapstore/framework/reducers/controls';
18+
import { setControlProperty } from '@mapstore/framework/actions/controls';
19+
import gnresource from '@js/reducers/gnresource';
20+
import { getSelectedLayerDataset } from '@js/selectors/resource';
21+
import GNButton from '@js/components/Button';
22+
import useDetectClickOut from '@js/hooks/useDetectClickOut';
23+
import OverlayContainer from '@js/components/OverlayContainer';
24+
import { withRouter } from 'react-router';
25+
import { hashLocationToHref } from '@js/utils/SearchUtils';
26+
import { mapSelector } from '@mapstore/framework/selectors/map';
27+
import { parsePluginConfigExpressions } from '@js/utils/MenuUtils';
28+
import tooltip from '@mapstore/framework/components/misc/enhancers/tooltip';
29+
import tabComponents from '@js/plugins/detailviewer/tabComponents';
30+
31+
const Button = tooltip(GNButton);
32+
33+
const ConnectedDetailsPanel = connect(
34+
createSelector([
35+
state => state?.gnresource?.selectedLayerDataset || null,
36+
state => state?.gnresource?.loading || false,
37+
mapSelector,
38+
state => state?.gnresource?.showMapThumbnail || false
39+
], (resource, loading, mapData, showMapThumbnail) => ({
40+
resource,
41+
loading,
42+
initialBbox: mapData?.bbox,
43+
enableMapViewer: showMapThumbnail,
44+
resourceId: resource?.pk,
45+
tabComponents
46+
})),
47+
{
48+
closePanel: setControlProperty.bind(null, 'rightOverlay', 'enabled', false),
49+
onClose: enableMapThumbnailViewer
50+
}
51+
)(DetailsPanel);
52+
53+
const ButtonViewer = ({ onClick, layer, size, status }) => {
54+
const layerResourceId = layer?.pk;
55+
const handleClickButton = () => {
56+
onClick();
57+
};
58+
return layerResourceId && status === 'LAYER' ? (
59+
<Button
60+
variant="primary"
61+
size={size}
62+
onClick={handleClickButton}
63+
>
64+
<FaIcon name={'info-circle'} />
65+
</Button>
66+
) : null;
67+
};
68+
69+
const ConnectedButton = connect(
70+
createSelector([
71+
getSelectedLayerDataset
72+
], (layer) => ({
73+
layer
74+
})),
75+
{
76+
onClick: setControlProperty.bind(
77+
null,
78+
'rightOverlay',
79+
'enabled',
80+
'LayerDetailViewer'
81+
)
82+
}
83+
)((ButtonViewer));
84+
85+
function LayerDetailViewer({
86+
location,
87+
enabled,
88+
onClose,
89+
monitoredState,
90+
queryPathname = '/',
91+
tabs = []
92+
}) {
93+
const parsedConfig = parsePluginConfigExpressions(monitoredState, { tabs });
94+
95+
const node = useDetectClickOut({
96+
disabled: !enabled,
97+
onClickOut: () => {
98+
onClose();
99+
}
100+
});
101+
102+
const handleFormatHref = (options) => {
103+
return hashLocationToHref({
104+
location,
105+
...options
106+
});
107+
};
108+
109+
return (
110+
<OverlayContainer
111+
enabled={enabled}
112+
ref={node}
113+
className="gn-overlay-wrapper"
114+
>
115+
<ConnectedDetailsPanel
116+
editTitle={null}
117+
editAbstract={null}
118+
editThumbnail={() => {}}
119+
activeEditMode={false}
120+
enableFavorite={false}
121+
formatHref={handleFormatHref}
122+
tabs={parsedConfig.tabs}
123+
pathname={queryPathname}
124+
/>
125+
</OverlayContainer>
126+
);
127+
}
128+
129+
const LayerDetailViewerPlugin = connect(
130+
createSelector(
131+
[
132+
(state) =>
133+
state?.controls?.rightOverlay?.enabled === 'LayerDetailViewer',
134+
getSelectedLayerDataset,
135+
state => getMonitoredState(state, getConfigProp('monitorState'))
136+
],
137+
(enabled, layer, monitoredState) => ({
138+
enabled,
139+
layer,
140+
monitoredState
141+
})
142+
),
143+
{
144+
onClose: setControlProperty.bind(null, 'rightOverlay', 'enabled', false)
145+
}
146+
)(withRouter(LayerDetailViewer));
147+
148+
export default createPlugin('LayerDetailViewer', {
149+
component: LayerDetailViewerPlugin,
150+
containers: {
151+
TOC: {
152+
target: 'toolbar',
153+
name: 'LayerDetailViewerButton',
154+
Component: ConnectedButton
155+
}
156+
},
157+
reducers: {
158+
gnresource,
159+
controls
160+
}
161+
});

geonode_mapstore_client/client/js/plugins/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,10 @@ export const plugins = {
378378
'VisualStyleEditor',
379379
() => import(/* webpackChunkName: 'plugins/visual-style-editor-plugin' */ '@js/plugins/VisualStyleEditor')
380380
),
381+
LayerDetailViewerPlugin: toModulePlugin(
382+
'LayerDetailViewer',
383+
() => import(/* webpackChunkName: 'plugins/detail-viewer-plugin' */ '@js/plugins/LayerDetailViewer')
384+
),
381385
LegendPlugin: toModulePlugin(
382386
'Legend',
383387
() => import(/* webpackChunkName: 'plugins/legend-plugin' */ '@js/plugins/Legend')

geonode_mapstore_client/client/js/reducers/gnresource.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ import {
3535
SET_RESOURCE_PATH_PARAMETERS,
3636
SET_MAP_VIEWER_LINKED_RESOURCE,
3737
SET_DEFAULT_VIEWER_PLUGINS,
38-
SET_SELECTED_LAYER
38+
SET_SELECTED_LAYER,
39+
UPDATE_LAYER_DATASET,
40+
SET_SELECTED_LAYER_DATASET
3941
} from '@js/actions/gnresource';
4042
import {
4143
cleanCompactPermissions,
@@ -253,6 +255,38 @@ function gnresource(state = defaultState, action) {
253255
...state,
254256
selectedLayer: action.layer
255257
};
258+
case SET_SELECTED_LAYER_DATASET:
259+
return {
260+
...state,
261+
selectedLayerDataset: state.data?.maplayers?.find(layer => layer.dataset?.pk === parseInt(action.layerId, 10))?.dataset
262+
};
263+
case UPDATE_LAYER_DATASET:
264+
const { pk, ...newData } = action.layer;
265+
let linkedResources = action.layer.linked_resources ?? {};
266+
if (!isEmpty(linkedResources)) {
267+
const linkedTo = linkedResources.linked_to ?? [];
268+
const linkedBy = linkedResources.linked_by ?? [];
269+
linkedResources = isEmpty(linkedTo) && isEmpty(linkedBy) ? {} : ({ linkedTo, linkedBy });
270+
}
271+
return {
272+
...state,
273+
data: {
274+
...state.data,
275+
maplayers: state.data?.maplayers?.map(layer => {
276+
if (layer.dataset?.pk === parseInt(pk, 10)) {
277+
return {
278+
...layer,
279+
dataset: {
280+
...layer.dataset,
281+
...newData,
282+
linkedResources
283+
}
284+
};
285+
}
286+
return layer;
287+
})
288+
}
289+
};
256290
default:
257291
return state;
258292
}

geonode_mapstore_client/client/js/selectors/resource.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ export const getResourceData = (state) => {
9191
return state?.gnresource?.data;
9292
};
9393

94+
export const getLayerResourceData = (state) => {
95+
return state?.gnresource?.layerDataset;
96+
};
97+
98+
export const getSelectedLayer = (state) => {
99+
return state?.gnresource?.selectedLayer;
100+
};
101+
102+
export const getSelectedLayerDataset = (state) => {
103+
return state?.gnresource?.selectedLayerDataset;
104+
};
105+
94106
export const getCompactPermissions = (state) => {
95107
const compactPermissions = state?.gnresource?.compactPermissions || {};
96108
return compactPermissions;

0 commit comments

Comments
 (0)