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;