From 5df0813e91893549fe4e6b7746c2ee15713af2fb Mon Sep 17 00:00:00 2001
From: David Quartey <42542676+DavidQuartz@users.noreply.github.com>
Date: Wed, 23 Feb 2022 08:20:58 +0000
Subject: [PATCH] Implement direct download action buttons (#844)
---
.../client/js/actions/gnresource.js | 17 ++++++
.../client/js/api/geonode/v2/index.js | 12 +++++
.../ActionButtons/ActionButtons.jsx | 11 ++--
.../js/components/CardGrid/CardGrid.jsx | 12 ++++-
.../components/DetailsPanel/DetailsPanel.jsx | 17 +++---
.../js/components/FeaturedList/Cards.jsx | 6 ++-
.../components/FeaturedList/FeaturedList.jsx | 6 ++-
.../components/ResourceCard/ResourceCard.jsx | 9 ++--
.../epics/__tests__/resourceservice-test.js | 28 +++++++++-
.../client/js/epics/resourceservice.js | 41 ++++++++++++---
.../client/js/plugins/DetailViewer.jsx | 17 ++++--
.../client/js/reducers/resourceservice.js | 27 ++++++++--
.../client/js/routes/Detail.jsx | 17 ++++--
.../client/js/routes/Home.jsx | 14 +++--
.../js/routes/catalogue/ConnectedCardGrid.jsx | 12 +++--
.../__tests__/resourceservice-test.js | 52 ++++++++++++++++---
.../client/js/selectors/resourceservice.js | 36 +++++++++++++
.../static/mapstore/configs/localConfig.json | 13 +++++
.../mapstore/translations/data.de-DE.json | 2 +-
.../mapstore/translations/data.en-US.json | 2 +-
.../mapstore/translations/data.es-ES.json | 2 +-
.../mapstore/translations/data.fr-FR.json | 2 +-
.../mapstore/translations/data.it-IT.json | 2 +-
.../themes/geonode/less/_details-panel.less | 4 +-
24 files changed, 298 insertions(+), 63 deletions(-)
diff --git a/geonode_mapstore_client/client/js/actions/gnresource.js b/geonode_mapstore_client/client/js/actions/gnresource.js
index 3b9a73c3aa..b81e351d72 100644
--- a/geonode_mapstore_client/client/js/actions/gnresource.js
+++ b/geonode_mapstore_client/client/js/actions/gnresource.js
@@ -31,6 +31,9 @@ export const RESET_GEO_LIMITS = 'GEONODE:RESET_GEO_LIMITS';
export const PROCESS_RESOURCES = 'GEONODE:PROCESS_RESOURCES';
export const SET_RESOURCE_THUMBNAIL = 'GEONODE_SET_RESOURCE_THUMBNAIL';
export const ENABLE_MAP_THUMBNAIL_VIEWER = 'GEONODE_ENABLE_MAP_THUMBNAIL_VIEWER';
+export const DOWNLOAD_RESOURCE = 'GEONODE_DOWNLOAD_RESOURCE';
+export const DOWNLOAD_COMPLETE = 'GEONODE_DOWNLOAD_COMPLETE';
+
/**
* Actions for GeoNode resource
@@ -280,3 +283,17 @@ export function processResources(processType, resources, redirectTo) {
redirectTo
};
}
+
+export function downloadResource(resource) {
+ return {
+ type: DOWNLOAD_RESOURCE,
+ resource
+ };
+}
+
+export function downloadComplete(resource) {
+ return {
+ type: DOWNLOAD_COMPLETE,
+ resource
+ };
+}
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 9a49d9b11d..fd11072b00 100644
--- a/geonode_mapstore_client/client/js/api/geonode/v2/index.js
+++ b/geonode_mapstore_client/client/js/api/geonode/v2/index.js
@@ -714,6 +714,17 @@ export const copyResource = (resource) => {
.then(({ data }) => data);
};
+export const downloadResource = (resource) => {
+ const url = resource.download_url || resource.href;
+ return axios.get(url, {
+ responseType: 'blob',
+ headers: {
+ 'Content_type': 'application/jsson'
+ }
+ })
+ .then(({ data, headers }) => ({output: data, headers}));
+};
+
export const getPendingUploads = () => {
return axios.get(parseDevHostname(endpoints[UPLOADS]), {
params: {
@@ -813,6 +824,7 @@ export default {
updateCompactPermissionsByPk,
deleteResource,
copyResource,
+ downloadResource,
getDatasets,
getPendingUploads,
getProcessedUploadsById,
diff --git a/geonode_mapstore_client/client/js/components/ActionButtons/ActionButtons.jsx b/geonode_mapstore_client/client/js/components/ActionButtons/ActionButtons.jsx
index e2b57151ad..3682a699f2 100644
--- a/geonode_mapstore_client/client/js/components/ActionButtons/ActionButtons.jsx
+++ b/geonode_mapstore_client/client/js/components/ActionButtons/ActionButtons.jsx
@@ -16,7 +16,8 @@ function ActionButtons({
actions,
onAction,
resource,
- buildHrefByTemplate
+ buildHrefByTemplate,
+ onDownload
}) {
return (
@@ -31,14 +32,14 @@ function ActionButtons({
{options.map((opt) => {
- if (opt.type === 'button' && actions[opt.action]) {
+ if ((opt.type === 'button' && actions[opt.action]) || opt.action === 'download') {
return (
- (opt.action !== 'copy' || resource?.is_copyable) &&
- onAction(actions[opt.action], [
+ opt.action !== 'download' ? onAction(actions[opt.action], [
resource
- ])
+ ]) : onDownload(resource)
}
>
{' '}
diff --git a/geonode_mapstore_client/client/js/components/CardGrid/CardGrid.jsx b/geonode_mapstore_client/client/js/components/CardGrid/CardGrid.jsx
index 3f0963af99..d5f0f6ad57 100644
--- a/geonode_mapstore_client/client/js/components/CardGrid/CardGrid.jsx
+++ b/geonode_mapstore_client/client/js/components/CardGrid/CardGrid.jsx
@@ -26,7 +26,9 @@ const Cards = withResizeDetector(({
buildHrefByTemplate,
options,
actions,
- onAction
+ onAction,
+ onDownload,
+ downloading
}) => {
const width = containerWidth || detectedWidth;
@@ -117,6 +119,8 @@ const Cards = withResizeDetector(({
onAction={onAction}
loading={isProcessing}
readOnly={isDeleted || isProcessing}
+ onDownload={onDownload}
+ downloading={downloading?.find((download) => download.pk === resource.pk) ? true : false}
/>
);
@@ -142,7 +146,9 @@ const CardGrid = ({
scrollContainer,
actions,
onAction,
- onControl
+ onControl,
+ onDownload,
+ downloading
}) => {
useInfiniteScroll({
@@ -178,6 +184,8 @@ const CardGrid = ({
options={cardOptions}
buildHrefByTemplate={buildHrefByTemplate}
actions={actions}
+ onDownload={onDownload}
+ downloading={downloading}
onAction={(action, payload) => {
if (action.isControlled) {
onControl(action.processType, 'value', payload);
diff --git a/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsPanel.jsx b/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsPanel.jsx
index a0a55a792b..e37fb57209 100644
--- a/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsPanel.jsx
+++ b/geonode_mapstore_client/client/js/components/DetailsPanel/DetailsPanel.jsx
@@ -185,6 +185,7 @@ function DetailsPanel({
linkHref,
sectionStyle,
loading,
+ downloading,
getTypesInfo,
editTitle,
editAbstract,
@@ -202,7 +203,9 @@ function DetailsPanel({
resourceThumbnailUpdating,
initialBbox,
enableMapViewer,
- onClose
+ onClose,
+ onAction,
+ canDownload
}) {
const detailsContainerNode = useRef();
const isMounted = useRef();
@@ -246,7 +249,8 @@ function DetailsPanel({
const embedUrl = resource?.embed_url && formatEmbedUrl(resource);
const detailUrl = resource?.pk && formatDetailUrl(resource);
const resourceCanPreviewed = resource?.pk && canPreviewed && canPreviewed(resource);
- const documentDownloadUrl = (resource?.href && resource?.href.includes('download')) ? resource?.href : undefined;
+ const downloadUrl = (resource?.href && resource?.href.includes('download')) ? resource?.href
+ : (resource?.download_url && canDownload) ? resource?.download_url : undefined;
const metadataDetailUrl = resource?.pk && getMetadataDetailUrl(resource);
const validateDataType = (data) => {
@@ -516,7 +520,7 @@ function DetailsPanel({
-
+ {!downloading ? : }
@@ -530,9 +534,9 @@ function DetailsPanel({
}
- {documentDownloadUrl &&
+ {downloadUrl &&
}
@@ -602,7 +606,8 @@ DetailsPanel.defaultProps = {
onResourceThumbnail: () => '#',
width: 696,
getTypesInfo: getResourceTypesInfo,
- isThumbnailChanged: false
+ isThumbnailChanged: false,
+ onAction: () => {}
};
export default DetailsPanel;
diff --git a/geonode_mapstore_client/client/js/components/FeaturedList/Cards.jsx b/geonode_mapstore_client/client/js/components/FeaturedList/Cards.jsx
index 58c2d63cef..89ee2ec044 100644
--- a/geonode_mapstore_client/client/js/components/FeaturedList/Cards.jsx
+++ b/geonode_mapstore_client/client/js/components/FeaturedList/Cards.jsx
@@ -21,7 +21,9 @@ const Cards = ({
options,
onResize,
actions,
- onAction
+ onAction,
+ onDownload,
+ downloading
}) => {
const width = detectedWidth || containerWidth;
const margin = 24;
@@ -93,6 +95,8 @@ const Cards = ({
featured
actions={actions}
onAction={onAction}
+ onDownload={onDownload}
+ downloading={downloading?.find((download) => download.pk === resource.pk) ? true : false}
/>
);
diff --git a/geonode_mapstore_client/client/js/components/FeaturedList/FeaturedList.jsx b/geonode_mapstore_client/client/js/components/FeaturedList/FeaturedList.jsx
index fcaae04724..1ff4a69d41 100644
--- a/geonode_mapstore_client/client/js/components/FeaturedList/FeaturedList.jsx
+++ b/geonode_mapstore_client/client/js/components/FeaturedList/FeaturedList.jsx
@@ -30,7 +30,9 @@ const FeaturedList = withResizeDetector(({
onLoad,
width,
onControl,
- onAction
+ onAction,
+ onDownload,
+ downloading
}) => {
const [count, setCount] = useState();
@@ -73,6 +75,8 @@ const FeaturedList = withResizeDetector(({
onAction(action.processType, payload, action.redirectTo);
}
}}
+ onDownload={onDownload}
+ downloading={downloading}
/>
diff --git a/geonode_mapstore_client/client/js/components/ResourceCard/ResourceCard.jsx b/geonode_mapstore_client/client/js/components/ResourceCard/ResourceCard.jsx
index 99fce6e506..8e264bcb63 100644
--- a/geonode_mapstore_client/client/js/components/ResourceCard/ResourceCard.jsx
+++ b/geonode_mapstore_client/client/js/components/ResourceCard/ResourceCard.jsx
@@ -32,7 +32,9 @@ const ResourceCard = forwardRef(({
className,
loading,
featured,
- onClick
+ onClick,
+ downloading,
+ onDownload
}, ref) => {
const res = data;
const types = getTypesInfo();
@@ -80,6 +82,7 @@ const ResourceCard = forwardRef(({
options={options}
readOnly={readOnly}
onAction={onAction}
+ onDownload={onDownload}
/>
)}
@@ -96,7 +99,7 @@ const ResourceCard = forwardRef(({
- {icon && !loading && (
+ {(icon && !loading && !downloading) && (
<>
>
)}
- {loading && }
+ {(loading || downloading) && }
{
testState
);
});
+
+ it('test gnDownloadResource', (done) => {
+ const testState = {
+ resourceservice: {}
+ };
+ const actionsCount = 1;
+ mockAxios.onGet().reply(() => [200, {output: {}, headers: { 'content-type': 'application/zip' } }]);
+ testEpic(
+ gnDownloadResource,
+ actionsCount,
+ downloadResource({ pk: 1, title: 'test resource' }),
+ (actions) => {
+ try {
+ expect(actions.map(({ type }) => type))
+ .toEqual([
+ DOWNLOAD_COMPLETE
+ ]);
+ } catch (e) {
+ done(e);
+ }
+ done();
+ },
+ testState
+ );
+ });
});
diff --git a/geonode_mapstore_client/client/js/epics/resourceservice.js b/geonode_mapstore_client/client/js/epics/resourceservice.js
index befeecfa95..6594bcd59e 100644
--- a/geonode_mapstore_client/client/js/epics/resourceservice.js
+++ b/geonode_mapstore_client/client/js/epics/resourceservice.js
@@ -8,6 +8,7 @@
import { Observable } from 'rxjs';
import axios from '@mapstore/framework/libs/ajax';
+import { saveAs } from 'file-saver';
import {
START_ASYNC_PROCESS,
@@ -23,9 +24,10 @@ import {
import { isProcessCompleted } from '@js/selectors/resourceservice';
import {
deleteResource,
- copyResource
+ copyResource,
+ downloadResource
} from '@js/api/geonode/v2';
-import { PROCESS_RESOURCES } from '@js/actions/gnresource';
+import { PROCESS_RESOURCES, DOWNLOAD_RESOURCE, downloadComplete } from '@js/actions/gnresource';
import { setControlProperty } from '@mapstore/framework/actions/controls';
import { push } from 'connected-react-router';
import {
@@ -37,7 +39,7 @@ export const gnMonitorAsyncProcesses = (action$, store) => {
.flatMap((action) => {
const { status_url: statusUrl } = action?.payload?.output || {};
if (!statusUrl || action?.payload?.error) {
- return action?.payload?.error ? Observable.of(stopAsyncProcess({ ...action.payload, completed: true }), errorNotification({ title: 'gnviewer.invalidUploadMessageError', message: 'gnviewer.cannotCloneResource' }))
+ return action?.payload?.error ? Observable.of(stopAsyncProcess({ ...action.payload, completed: true }), errorNotification({ title: 'gnviewer.invalidUploadMessageError', message: 'gnviewer.cannotPerfomAction' }))
: Observable.of(stopAsyncProcess({ ...action.payload, completed: true }));
}
return Observable
@@ -69,10 +71,10 @@ export const gnProcessResources = (action$) =>
// all the processes must be listened for this reason we should use flatMap instead of switchMap
.flatMap((action) => {
return Observable.defer(() => axios.all(
- action.resources.map(resource =>
- processAPI[action.processType](resource)
- .then(output => ({ resource, output, processType: action.processType }))
- .catch((error) => ({ resource, error: error?.data?.detail || error?.statusText || error?.message || true, processType: action.processType }))
+ action.resources.map((resource) => processAPI[action.processType](resource)
+ .then((output) => ({ resource, output, processType: action.processType })
+ )
+ .catch((error) => ({ resource, error: error?.data?.detail || error?.statusText || error?.message || true, processType: action.processType }))
)
))
.switchMap((processes) => {
@@ -88,7 +90,30 @@ export const gnProcessResources = (action$) =>
.startWith(setControlProperty(action.processType, 'loading', true));
});
+export const gnDownloadResource = (action$) =>
+ action$.ofType(DOWNLOAD_RESOURCE)
+ .switchMap((action) => {
+ const resource = action?.resource;
+ return Observable.defer(() => downloadResource(resource)
+ .then(({ output, headers }) => {
+ saveAs(new Blob([output], { type: headers?.['content-type'] }), resource.title);
+ return { resource };
+ })
+ .catch((error) => ({ resource, error: error?.data?.detail || error?.statusText || error?.message || true }))
+ )
+ .switchMap((downloaded) => {
+ const { error } = downloaded || {};
+ if (error) {
+ return Observable.of(downloadComplete({ ...downloaded.resource }), errorNotification({ title: 'gnviewer.invalidUploadMessageError', message: 'gnviewer.cannotPerfomAction' }));
+ }
+ return Observable.of(
+ downloadComplete({ ...downloaded.resource })
+ );
+ });
+ });
+
export default {
gnMonitorAsyncProcesses,
- gnProcessResources
+ gnProcessResources,
+ gnDownloadResource
};
diff --git a/geonode_mapstore_client/client/js/plugins/DetailViewer.jsx b/geonode_mapstore_client/client/js/plugins/DetailViewer.jsx
index 340cfd5b88..822d3519aa 100644
--- a/geonode_mapstore_client/client/js/plugins/DetailViewer.jsx
+++ b/geonode_mapstore_client/client/js/plugins/DetailViewer.jsx
@@ -19,8 +19,10 @@ import {
setFavoriteResource,
setMapThumbnail,
setResourceThumbnail,
- enableMapThumbnailViewer
+ 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';
@@ -40,6 +42,7 @@ import { hashLocationToHref } from '@js/utils/SearchUtils';
import Message from '@mapstore/framework/components/I18N/Message';
import { layersSelector } from '@mapstore/framework/selectors/layers';
import { mapSelector } from '@mapstore/framework/selectors/map';
+import { resourceHasPermission } from '@js/utils/ResourceUtils';
const ConnectedDetailsPanel = connect(
createSelector([
@@ -51,8 +54,9 @@ const ConnectedDetailsPanel = connect(
isThumbnailChanged,
updatingThumbnailResource,
mapSelector,
- state => state?.gnresource?.showMapThumbnail || false
- ], (resource, loading, favorite, savingThumbnailMap, layers, thumbnailChanged, resourceThumbnailUpdating, mapData, showMapThumbnail) => ({
+ state => state?.gnresource?.showMapThumbnail || false,
+ processingDownload
+ ], (resource, loading, favorite, savingThumbnailMap, layers, thumbnailChanged, resourceThumbnailUpdating, mapData, showMapThumbnail, downloading) => ({
layers: layers,
resource,
loading,
@@ -61,14 +65,17 @@ const ConnectedDetailsPanel = connect(
isThumbnailChanged: thumbnailChanged,
resourceThumbnailUpdating,
initialBbox: mapData?.bbox,
- enableMapViewer: showMapThumbnail
+ enableMapViewer: showMapThumbnail,
+ downloading,
+ canDownload: resourceHasPermission(resource, 'download_resourcebase')
})),
{
closePanel: setControlProperty.bind(null, 'rightOverlay', 'enabled', false),
onFavorite: setFavoriteResource,
onMapThumbnail: setMapThumbnail,
onResourceThumbnail: setResourceThumbnail,
- onClose: enableMapThumbnailViewer
+ onClose: enableMapThumbnailViewer,
+ onAction: downloadResource
}
)(DetailsPanel);
diff --git a/geonode_mapstore_client/client/js/reducers/resourceservice.js b/geonode_mapstore_client/client/js/reducers/resourceservice.js
index 80617a5152..b673227c1b 100644
--- a/geonode_mapstore_client/client/js/reducers/resourceservice.js
+++ b/geonode_mapstore_client/client/js/reducers/resourceservice.js
@@ -13,11 +13,14 @@ import {
} from '@js/actions/resourceservice';
import {
- PROCESS_RESOURCES
+ DOWNLOAD_RESOURCE,
+ PROCESS_RESOURCES,
+ DOWNLOAD_COMPLETE
} from '@js/actions/gnresource';
const defaultState = {
- processes: []
+ processes: [],
+ downloads: []
};
function resourceservice(state = defaultState, action) {
@@ -55,11 +58,29 @@ function resourceservice(state = defaultState, action) {
...state,
processes: state.processes.map((process) =>
process?.resource?.pk === action?.payload?.resource?.pk
- && process?.processType === action?.payload?.processType
+ && process?.processType === action?.payload?.processType
? action.payload
: process)
};
}
+ case DOWNLOAD_RESOURCE: {
+ return {
+ ...state,
+ downloads: [
+ ...state.downloads,
+ action.resource
+ ]
+ };
+ }
+ case DOWNLOAD_COMPLETE: {
+ return {
+ ...state,
+ downloads: [
+ ...state.downloads.filter((download) =>
+ (download?.resource?.pk === action?.resource?.pk))
+ ]
+ };
+ }
default:
return state;
}
diff --git a/geonode_mapstore_client/client/js/routes/Detail.jsx b/geonode_mapstore_client/client/js/routes/Detail.jsx
index 9991bd33a4..ca1c718deb 100644
--- a/geonode_mapstore_client/client/js/routes/Detail.jsx
+++ b/geonode_mapstore_client/client/js/routes/Detail.jsx
@@ -17,13 +17,14 @@ import { getParsedGeoNodeConfiguration } from "@js/selectors/config";
import { userSelector } from '@mapstore/framework/selectors/security';
import { buildHrefByTemplate } from '@js/utils/MenuUtils';
import { setControlProperty } from '@mapstore/framework/actions/controls';
+import { processingDownload } from '@js/selectors/resourceservice';
import {
searchResources,
requestResource,
loadFeaturedResources
} from '@js/actions/gnsearch';
-import { setFavoriteResource } from '@js/actions/gnresource';
+import { downloadResource, setFavoriteResource } from '@js/actions/gnresource';
import {
hashLocationToHref,
clearQueryParams,
@@ -35,6 +36,7 @@ import MetaTags from "@js/components/MetaTags";
import {
getThemeLayoutSize
} from '@js/utils/AppUtils';
+import { resourceHasPermission } from '@js/utils/ResourceUtils';
import { getTotalResources } from '@js/selectors/search';
import ConnectedCardGrid from '@js/routes/catalogue/ConnectedCardGrid';
import DeleteResource from '@js/plugins/DeleteResource';
@@ -47,13 +49,18 @@ const { NotificationsPlugin } = Notifications;
const ConnectedDetailsPanel = connect(
createSelector([
state => state?.gnresource?.loading || false,
- state => state?.gnresource?.data?.favorite || false
- ], (loading, favorite) => ({
+ state => state?.gnresource?.data?.favorite || false,
+ processingDownload,
+ state => state?.gnresource?.data || null
+ ], (loading, favorite, downloading, resource) => ({
loading,
- favorite
+ favorite,
+ downloading,
+ canDownload: resourceHasPermission(resource, 'download_resourcebase')
})),
{
- onFavorite: setFavoriteResource
+ onFavorite: setFavoriteResource,
+ onAction: downloadResource
}
)(DetailsPanel);
function Detail({
diff --git a/geonode_mapstore_client/client/js/routes/Home.jsx b/geonode_mapstore_client/client/js/routes/Home.jsx
index b2e2f9ea67..b2695f9ce9 100644
--- a/geonode_mapstore_client/client/js/routes/Home.jsx
+++ b/geonode_mapstore_client/client/js/routes/Home.jsx
@@ -37,8 +37,10 @@ import { getFeaturedResults, getTotalResources } from '@js/selectors/search';
import DeleteResource from '@js/plugins/DeleteResource';
import SaveAs from '@js/plugins/SaveAs';
import Notifications from '@mapstore/framework/plugins/Notifications';
-import { processResources } from '@js/actions/gnresource';
+import { processResources, downloadResource } from '@js/actions/gnresource';
import { setControlProperty } from '@mapstore/framework/actions/controls';
+import { featuredResourceDownload } from '@js/selectors/resourceservice';
+
const { DeleteResourcePlugin } = DeleteResource;
const { SaveAsPlugin } = SaveAs;
const { NotificationsPlugin } = Notifications;
@@ -50,13 +52,15 @@ const ConnectedFeatureList = connect(
state => state?.gnsearch?.featuredResources?.isNextPageAvailable || false,
state => state?.gnsearch?.featuredResources?.isPreviousPageAvailable || false,
state => state?.gnsearch?.featuredResources?.loading || false,
- getParsedGeoNodeConfiguration
- ], (resources, page, isNextPageAvailable, isPreviousPageAvailable, loading, { cardOptionsItemsAllowed }) => ({
- resources, page, isNextPageAvailable, isPreviousPageAvailable, loading, cardOptions: cardOptionsItemsAllowed})
+ getParsedGeoNodeConfiguration,
+ featuredResourceDownload
+ ], (resources, page, isNextPageAvailable, isPreviousPageAvailable, loading, { cardOptionsItemsAllowed }, downloading) => ({
+ resources, page, isNextPageAvailable, isPreviousPageAvailable, loading, cardOptions: cardOptionsItemsAllowed, downloading})
), {
loadFeaturedResources,
onAction: processResources,
- onControl: setControlProperty
+ onControl: setControlProperty,
+ onDownload: downloadResource
}
)(FeaturedList);
diff --git a/geonode_mapstore_client/client/js/routes/catalogue/ConnectedCardGrid.jsx b/geonode_mapstore_client/client/js/routes/catalogue/ConnectedCardGrid.jsx
index 095d7173cf..3dd7f49986 100644
--- a/geonode_mapstore_client/client/js/routes/catalogue/ConnectedCardGrid.jsx
+++ b/geonode_mapstore_client/client/js/routes/catalogue/ConnectedCardGrid.jsx
@@ -11,9 +11,10 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import CardGrid from '@js/components/CardGrid';
import { getSearchResults } from '@js/selectors/search';
-import { processResources } from '@js/actions/gnresource';
+import { downloadResource, processResources } from '@js/actions/gnresource';
import { setControlProperty } from '@mapstore/framework/actions/controls';
import { actionButtons } from '@js/utils/ResourceServiceUtils';
+import { generalResourceDownload } from '@js/selectors/resourceservice';
const CardGridWithMessageId = ({ query, user, isFirstRequest, ...props }) => {
const hasResources = props.resources?.length > 0;
@@ -32,16 +33,19 @@ const ConnectedCardGrid = connect(
getSearchResults,
state => state?.gnsearch?.loading || false,
state => state?.gnsearch?.isNextPageAvailable || false,
- state => state?.gnsearch?.isFirstRequest
- ], (resources, loading, isNextPageAvailable, isFirstRequest) => ({
+ state => state?.gnsearch?.isFirstRequest,
+ generalResourceDownload
+ ], (resources, loading, isNextPageAvailable, isFirstRequest, downloading) => ({
resources,
loading,
isNextPageAvailable,
isFirstRequest,
- actions: actionButtons
+ actions: actionButtons,
+ downloading
})),
{
onAction: processResources,
+ onDownload: downloadResource,
onControl: setControlProperty
}
)(CardGridWithMessageId);
diff --git a/geonode_mapstore_client/client/js/selectors/__tests__/resourceservice-test.js b/geonode_mapstore_client/client/js/selectors/__tests__/resourceservice-test.js
index eef723a89c..0f2025e929 100644
--- a/geonode_mapstore_client/client/js/selectors/__tests__/resourceservice-test.js
+++ b/geonode_mapstore_client/client/js/selectors/__tests__/resourceservice-test.js
@@ -7,18 +7,58 @@
*/
import expect from 'expect';
-import { getCurrentProcesses } from '../resourceservice';
+import { getCurrentProcesses, processingDownload, generalResourceDownload, featuredResourceDownload } from '../resourceservice';
-const testState = {
- resourceservice: {
- processes: [{ name: 'test process' }]
- }
-};
describe('resourceservice selector', () => {
it('test getCurrentProcesses', () => {
+ const testState = {
+ resourceservice: {
+ processes: [{ name: 'test process' }]
+ }
+ };
expect(getCurrentProcesses(testState)).toEqual([{ name: 'test process' }]);
});
+ it('test processingDownload', () => {
+ const testState = {
+ gnresource: {
+ data: {
+ pk: 1
+ }
+ },
+ resourceservice: {
+ downloads: [{ pk: 1 }]
+ }
+ };
+ expect(processingDownload(testState)).toEqual(true);
+ });
+
+ it('test featuredResourceDownload', () => {
+ const testState = {
+ resourceservice: {
+ downloads: [{ pk: 1 }]
+ },
+ gnsearch: {
+ featuredResources: {
+ resources: [{ pk: 1 }]
+ }
+ }
+ };
+ expect(featuredResourceDownload(testState)).toEqual([{ pk: 1 }]);
+ });
+
+ it('test generalResourceDownload', () => {
+ const testState = {
+ resourceservice: {
+ downloads: [{ pk: 1 }]
+ },
+ gnsearch: {
+ resources: [{ pk: 1 }]
+ }
+ };
+ expect(generalResourceDownload(testState)).toEqual([{ pk: 1 }]);
+ });
+
});
diff --git a/geonode_mapstore_client/client/js/selectors/resourceservice.js b/geonode_mapstore_client/client/js/selectors/resourceservice.js
index 16a1a9b613..2c1bc1d387 100644
--- a/geonode_mapstore_client/client/js/selectors/resourceservice.js
+++ b/geonode_mapstore_client/client/js/selectors/resourceservice.js
@@ -8,6 +8,7 @@
import { getResourceData } from '@js/selectors/resource';
import { ProcessTypes } from '@js/utils/ResourceServiceUtils';
+import { getSearchResults, getFeaturedResults } from '@js/selectors/search';
export const isProcessCompleted = (state, payload) => {
const completedProcess = state?.resourceservice?.processes?.find(process =>
@@ -18,6 +19,41 @@ export const isProcessCompleted = (state, payload) => {
return completedProcess.completed;
};
+export const processingDownload = (state) => {
+ const resource = getResourceData(state);
+ const isProcessingDownload = state?.resourceservice?.downloads?.find((download) =>
+ download?.pk === resource?.pk
+ );
+ const downloading = isProcessingDownload ? true : false;
+ return downloading;
+};
+
+export const generalResourceDownload = (state) => {
+ const generalResources = getSearchResults(state);
+ const downloads = state?.resourceservice?.downloads || [];
+ const generalDownloads = generalResources?.reduce((acc, resource) => {
+ const downloadingResources = downloads.find(download => download.pk === resource.pk);
+ if (downloadingResources) {
+ return [...acc, { ...resource }];
+ }
+ return [...acc];
+ }, []);
+ return generalDownloads;
+};
+
+export const featuredResourceDownload = (state) => {
+ const featuredResources = getFeaturedResults(state);
+ const downloads = state?.resourceservice?.downloads || [];
+ const featuredDownloads = featuredResources?.reduce((acc, resource) => {
+ const downloadingResources = downloads.find(download => download.pk === resource.pk);
+ if (downloadingResources) {
+ return [...acc, { ...resource }];
+ }
+ return [...acc];
+ }, []);
+ return featuredDownloads;
+};
+
export const getCurrentResourcePermissionsLoading = (state) => {
const resource = getResourceData(state);
const permissionsProcess = resource && state?.resourceservice?.processes?.find(process =>
diff --git a/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json b/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json
index 06d776205b..d7175fde4f 100644
--- a/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json
+++ b/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json
@@ -456,6 +456,19 @@
"value": "change_resourcebase"
}
]
+ },
+ {
+ "type": "button",
+ "action": "download",
+ "labelId": "gnviewer.download",
+ "icon": "download",
+ "authenticated": true,
+ "perms": [
+ {
+ "type": "resource",
+ "value": "download_resourcebase"
+ }
+ ]
}
]
}
diff --git a/geonode_mapstore_client/client/static/mapstore/translations/data.de-DE.json b/geonode_mapstore_client/client/static/mapstore/translations/data.de-DE.json
index bfa05516e9..e1a0e58c34 100644
--- a/geonode_mapstore_client/client/static/mapstore/translations/data.de-DE.json
+++ b/geonode_mapstore_client/client/static/mapstore/translations/data.de-DE.json
@@ -274,7 +274,7 @@
"transferInProgress": "Übertragung läuft",
"fileExceeds": "Die Dateigröße überschreitet {limit} MB. Bitte versuchen Sie es erneut mit einer kleineren Datei.",
"exceedingFileMsg": "Algunos de sus archivos exceden el límite de carga de {limit} MB. Por favor, elimínelos de la lista.",
- "cannotCloneResource": "Ressource kann nicht geklont werden."
+ "cannotPerfomAction": "Die Aktion für diese Ressource konnte nicht ausgeführt werden."
}
}
}
diff --git a/geonode_mapstore_client/client/static/mapstore/translations/data.en-US.json b/geonode_mapstore_client/client/static/mapstore/translations/data.en-US.json
index 8fcc752b8d..b9b64445d2 100644
--- a/geonode_mapstore_client/client/static/mapstore/translations/data.en-US.json
+++ b/geonode_mapstore_client/client/static/mapstore/translations/data.en-US.json
@@ -274,7 +274,7 @@
"transferInProgress": "Transfer in progress",
"fileExceeds": "File size size exceeds {limit} MB. Please try again with a smaller file.",
"exceedingFileMsg": "Some of your files exceed the upload limit of {limit} MB. Please remove them from the list.",
- "cannotCloneResource": "Resource cannot be cloned"
+ "cannotPerfomAction": "Failed to perform action on this resource."
}
}
}
diff --git a/geonode_mapstore_client/client/static/mapstore/translations/data.es-ES.json b/geonode_mapstore_client/client/static/mapstore/translations/data.es-ES.json
index 34e9c0235d..edb503eb15 100644
--- a/geonode_mapstore_client/client/static/mapstore/translations/data.es-ES.json
+++ b/geonode_mapstore_client/client/static/mapstore/translations/data.es-ES.json
@@ -272,7 +272,7 @@
"transferInProgress": "Transferencia en progreso",
"fileExceeds": "El tamaño del archivo supera los {limit} MB. Vuelva a intentarlo con un archivo más pequeño.",
"exceedingFileMsg": "Algunos de sus archivos exceden el límite de carga de {limit} MB. Por favor, elimínelos de la lista.",
- "cannotCloneResource": "El recurso no se puede clonar."
+ "cannotPerfomAction": "No se pudo realizar la acción en este recurso."
}
}
}
diff --git a/geonode_mapstore_client/client/static/mapstore/translations/data.fr-FR.json b/geonode_mapstore_client/client/static/mapstore/translations/data.fr-FR.json
index cfc3f4a289..a6f92ac8ce 100644
--- a/geonode_mapstore_client/client/static/mapstore/translations/data.fr-FR.json
+++ b/geonode_mapstore_client/client/static/mapstore/translations/data.fr-FR.json
@@ -273,7 +273,7 @@
"transferInProgress": "Transfert en cours",
"fileExceeds": "La taille du fichier dépasse {Limit} Mo. Veuillez réessayer avec un fichier plus petit.",
"exceedingFileMsg": "Certains de vos fichiers dépassent la limite de téléchargement de {limit} Mo. Veuillez les supprimer de la liste.",
- "cannotCloneResource": "La ressource ne peut pas être clonée."
+ "cannotPerfomAction": "Échec de l'exécution de l'action sur cette ressource."
}
}
}
diff --git a/geonode_mapstore_client/client/static/mapstore/translations/data.it-IT.json b/geonode_mapstore_client/client/static/mapstore/translations/data.it-IT.json
index a55660b311..4648158bd0 100644
--- a/geonode_mapstore_client/client/static/mapstore/translations/data.it-IT.json
+++ b/geonode_mapstore_client/client/static/mapstore/translations/data.it-IT.json
@@ -275,7 +275,7 @@
"transferInProgress": "Trasferimento in corso",
"fileExceeds": "La dimensione del file supera i {limit} MB. Riprova con un file più piccolo.",
"exceedingFileMsg": "Alcuni dei tuoi file superano il limite di caricamento di {limit} MB. Si prega di rimuoverli dall'elenco.",
- "cannotCloneResource": "La risorsa non può essere clonata."
+ "cannotPerfomAction": "Impossibile eseguire l'azione su questa risorsa."
}
}
}
diff --git a/geonode_mapstore_client/client/themes/geonode/less/_details-panel.less b/geonode_mapstore_client/client/themes/geonode/less/_details-panel.less
index fd5bb333bd..75c60f6c0e 100644
--- a/geonode_mapstore_client/client/themes/geonode/less/_details-panel.less
+++ b/geonode_mapstore_client/client/themes/geonode/less/_details-panel.less
@@ -306,11 +306,9 @@
}
}
- .gn-details-panel-title-icon{
- .fa{
+ .gn-details-panel-title-icon {
margin-right: 0.6rem;
margin-left: 0.2rem;
- }
}
.gn-details-panel-tools{
display: flex;