From 3f817994a2b266946e8ef55c7a8c9fcf258eb5ea Mon Sep 17 00:00:00 2001 From: stefano bovio Date: Wed, 11 Aug 2021 15:44:12 +0200 Subject: [PATCH] introduces async processes for cards actions (delete) (#412) --- .../client/js/actions/gnresource.js | 10 + .../client/js/actions/resourceservice.js | 32 +++ .../client/js/api/geonode/v2/index.js | 14 +- .../client/js/apps/gn-catalogue.js | 4 + .../client/js/apps/gn-home.jsx | 7 +- .../js/components/CardGrid/CardGrid.jsx | 38 ++- .../js/components/Permissions/Permissions.jsx | 9 +- .../components/ResourceCard/ResourceCard.jsx | 53 +++-- .../client/js/epics/gnsave.js | 17 +- .../client/js/epics/resourceservice.js | 89 +++++++ .../client/js/plugins/DeleteResource.jsx | 148 ++++++++++++ .../client/js/plugins/Save.jsx | 14 +- .../client/js/plugins/Share.jsx | 13 +- .../client/js/plugins/index.js | 5 +- .../client/js/plugins/save/SaveModal.jsx | 219 +++++++++--------- .../client/js/reducers/resourceservice.js | 83 +++++++ .../client/js/routes/Detail.jsx | 3 + .../client/js/routes/Home.jsx | 8 +- .../client/js/routes/Search.jsx | 3 + .../js/routes/catalogue/ConnectedCardGrid.jsx | 25 +- .../client/js/selectors/resourceservice.js | 26 +++ .../client/js/selectors/search.js | 31 +++ .../client/js/utils/ResourceServiceUtils.js | 26 +++ .../static/mapstore/configs/localConfig.json | 88 +++++-- .../mapstore/translations/data.de-DE.json | 5 +- .../mapstore/translations/data.en-US.json | 5 +- .../mapstore/translations/data.es-ES.json | 5 +- .../mapstore/translations/data.fr-FR.json | 5 +- .../mapstore/translations/data.it-IT.json | 5 +- .../themes/geonode/less/_card-grid.less | 2 +- .../themes/geonode/less/_resource-card.less | 10 +- .../client/themes/geonode/less/_share.less | 1 + .../client/themes/geonode/less/_spinner.less | 17 ++ .../client/themes/geonode/less/ms-theme.less | 12 + 34 files changed, 850 insertions(+), 182 deletions(-) create mode 100644 geonode_mapstore_client/client/js/actions/resourceservice.js create mode 100644 geonode_mapstore_client/client/js/epics/resourceservice.js create mode 100644 geonode_mapstore_client/client/js/plugins/DeleteResource.jsx create mode 100644 geonode_mapstore_client/client/js/reducers/resourceservice.js create mode 100644 geonode_mapstore_client/client/js/selectors/resourceservice.js create mode 100644 geonode_mapstore_client/client/js/selectors/search.js create mode 100644 geonode_mapstore_client/client/js/utils/ResourceServiceUtils.js diff --git a/geonode_mapstore_client/client/js/actions/gnresource.js b/geonode_mapstore_client/client/js/actions/gnresource.js index 827980d981..adee7121b4 100644 --- a/geonode_mapstore_client/client/js/actions/gnresource.js +++ b/geonode_mapstore_client/client/js/actions/gnresource.js @@ -27,6 +27,7 @@ export const RESOURCE_CONFIG_ERROR = 'GEONODE:RESOURCE_CONFIG_ERROR'; export const SET_RESOURCE_COMPACT_PERMISSIONS = 'GEONODE:SET_RESOURCE_COMPACT_PERMISSIONS'; export const UPDATE_RESOURCE_COMPACT_PERMISSIONS = 'GEONODE:UPDATE_RESOURCE_COMPACT_PERMISSIONS'; export const RESET_GEO_LIMITS = 'GEONODE:RESET_GEO_LIMITS'; +export const PROCESS_RESOURCES = 'GEONODE:PROCESS_RESOURCES'; /** * Actions for GeoNode resource @@ -250,3 +251,12 @@ export function resetGeoLimits() { type: RESET_GEO_LIMITS }; } + +export function processResources(processType, resources, redirectTo) { + return { + type: PROCESS_RESOURCES, + processType, + resources, + redirectTo + }; +} diff --git a/geonode_mapstore_client/client/js/actions/resourceservice.js b/geonode_mapstore_client/client/js/actions/resourceservice.js new file mode 100644 index 0000000000..020958c124 --- /dev/null +++ b/geonode_mapstore_client/client/js/actions/resourceservice.js @@ -0,0 +1,32 @@ +/* + * 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. +*/ + +export const START_ASYNC_PROCESS = 'GEONODE:START_ASYNC_PROCESS'; +export const STOP_ASYNC_PROCESS = 'GEONODE:STOP_ASYNC_PROCESS'; +export const UPDATE_ASYNC_PROCESS = 'GEONODE:UPDATE_ASYNC_PROCESS'; + +export function startAsyncProcess(payload) { + return { + type: START_ASYNC_PROCESS, + payload + }; +} + +export function updateAsyncProcess(payload) { + return { + type: UPDATE_ASYNC_PROCESS, + payload + }; +} + +export function stopAsyncProcess(payload) { + return { + type: STOP_ASYNC_PROCESS, + payload + }; +} 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 464b7ce432..757684a208 100644 --- a/geonode_mapstore_client/client/js/api/geonode/v2/index.js +++ b/geonode_mapstore_client/client/js/api/geonode/v2/index.js @@ -677,6 +677,16 @@ export const updateCompactPermissionsByPk = (pk, body) => { .then(({ data }) => data); }; +export const deleteResource = (resource) => { + return axios.delete(parseDevHostname(`${endpoints[RESOURCES]}/${resource.pk}/delete`)) + .then(({ data }) => data); +}; + +export const copyResource = (resource) => { + return axios.put(parseDevHostname(`${endpoints[RESOURCES]}/${resource.pk}/copy`)) + .then(({ data }) => data); +}; + export default { getEndpoints, getResources, @@ -703,5 +713,7 @@ export default { getOwners, getKeywords, getCompactPermissionsByPk, - updateCompactPermissionsByPk + updateCompactPermissionsByPk, + deleteResource, + copyResource }; diff --git a/geonode_mapstore_client/client/js/apps/gn-catalogue.js b/geonode_mapstore_client/client/js/apps/gn-catalogue.js index 50a7fc4d6a..01d6770c11 100644 --- a/geonode_mapstore_client/client/js/apps/gn-catalogue.js +++ b/geonode_mapstore_client/client/js/apps/gn-catalogue.js @@ -43,6 +43,7 @@ import ViewerRoute from '@js/routes/Viewer'; import gnsearch from '@js/reducers/gnsearch'; import gnresource from '@js/reducers/gnresource'; +import resourceservice from '@js/reducers/resourceservice'; import gnsettings from '@js/reducers/gnsettings'; import { @@ -67,6 +68,7 @@ import { } from '@js/epics'; import gnresourceEpics from '@js/epics/gnresource'; +import resourceServiceEpics from '@js/epics/resourceservice'; import gnsearchEpics from '@js/epics/gnsearch'; import favoriteEpics from '@js/epics/favorite'; import maplayout from '@mapstore/framework/reducers/maplayout'; @@ -251,6 +253,7 @@ Promise.all([ appReducers: { ...standardReducers, gnresource, + resourceservice, gnsettings, security, maptype, @@ -276,6 +279,7 @@ Promise.all([ gnSetDatasetsPermissions, ...pluginsDefinition.epics, ...gnresourceEpics, + ...resourceServiceEpics, ...gnsearchEpics, ...favoriteEpics, updateMapLayoutEpic diff --git a/geonode_mapstore_client/client/js/apps/gn-home.jsx b/geonode_mapstore_client/client/js/apps/gn-home.jsx index 712088cdbe..2addaa7283 100644 --- a/geonode_mapstore_client/client/js/apps/gn-home.jsx +++ b/geonode_mapstore_client/client/js/apps/gn-home.jsx @@ -13,13 +13,14 @@ import { connect } from 'react-redux'; import security from '@mapstore/framework/reducers/security'; import controls from '@mapstore/framework/reducers/controls'; - import Home from '@js/routes/Home'; import gnsearch from '@js/reducers/gnsearch'; import gnresource from '@js/reducers/gnresource'; +import resourceservice from '@js/reducers/resourceservice'; import gnsearchEpics from '@js/epics/gnsearch'; import gnsaveEpics from '@js/epics/gnsave'; +import resourceServiceEpics from '@js/epics/resourceservice'; import { getConfiguration, @@ -78,12 +79,14 @@ Promise.all([ appReducers: { gnsearch, gnresource, + resourceservice, security, controls }, appEpics: { ...gnsearchEpics, - ...gnsaveEpics + ...gnsaveEpics, + ...resourceServiceEpics }, geoNodeConfiguration }); diff --git a/geonode_mapstore_client/client/js/components/CardGrid/CardGrid.jsx b/geonode_mapstore_client/client/js/components/CardGrid/CardGrid.jsx index 39b0719ab0..0320c0d503 100644 --- a/geonode_mapstore_client/client/js/components/CardGrid/CardGrid.jsx +++ b/geonode_mapstore_client/client/js/components/CardGrid/CardGrid.jsx @@ -15,6 +15,10 @@ import { withResizeDetector } from 'react-resize-detector'; import useLocalStorage from '@js/hooks/useLocalStorage'; import { hasPermissionsTo } from '@js/utils/MenuUtils'; import useInfiniteScroll from '@js/hooks/useInfiniteScroll'; +import { + ProcessTypes, + ProcessStatus +} from '@js/utils/ResourceServiceUtils'; const Cards = withResizeDetector(({ resources, @@ -23,7 +27,9 @@ const Cards = withResizeDetector(({ containerWidth, width: detectedWidth, buildHrefByTemplate, - options + options, + actions, + onAction }) => { const width = containerWidth || detectedWidth; @@ -86,7 +92,15 @@ const Cards = withResizeDetector(({ > {resources.map((resource, idx) => { // enable allowedOptions (menu cards) only for list layout - const allowedOptions = (cardLayoutStyle === 'list') ? options + const { processes, ...data } = resource; + const isProcessing = processes + ? !!processes.find(({ completed }) => !completed) + : false; + const deleteProcess = processes && processes.find(({ processType }) => processType === ProcessTypes.DELETE_RESOURCE); + const isDeleting = !!deleteProcess?.output?.status; + const isDeleted = deleteProcess?.output?.status === ProcessStatus.FINISHED; + + const allowedOptions = (cardLayoutStyle === 'list' && !isProcessing) ? options .filter((opt) => hasPermissionsTo(resource?.perms, opt?.perms, 'resource')) : []; return ( @@ -95,12 +109,17 @@ const Cards = withResizeDetector(({ style={(layoutSpace(idx))} > ); @@ -123,7 +142,10 @@ const CardGrid = ({ messageId, children, buildHrefByTemplate, - scrollContainer + scrollContainer, + actions, + onAction, + onControl }) => { useInfiniteScroll({ @@ -157,6 +179,14 @@ const CardGrid = ({ isCardActive={isCardActive} options={cardOptions} buildHrefByTemplate={buildHrefByTemplate} + actions={actions} + onAction={(action, payload) => { + if (action.isControlled) { + onControl(action.processType, 'value', payload); + } else { + onAction(action.processType, payload, action.redirectTo); + } + }} />
{loading && diff --git a/geonode_mapstore_client/client/js/components/Permissions/Permissions.jsx b/geonode_mapstore_client/client/js/components/Permissions/Permissions.jsx index c6014f2f0a..e4e2d8d4a6 100644 --- a/geonode_mapstore_client/client/js/components/Permissions/Permissions.jsx +++ b/geonode_mapstore_client/client/js/components/Permissions/Permissions.jsx @@ -21,6 +21,7 @@ import { } from '@js/utils/ResourceUtils'; import localizedProps from '@mapstore/framework/components/misc/enhancers/localizedProps'; import { getGeoLimits } from '@js/api/geonode/security'; +import Spinner from '@js/components/Spinner'; const FormControl = localizedProps('placeholder')(FormControlRB); @@ -34,7 +35,8 @@ function Permissions({ defaultGroupOptions, enableGeoLimits, requestGeoLimits = getGeoLimits, - resourceId + resourceId, + loading }) { const { entries = [], groups = [] } = permissionsCompactToLists(compactPermissions); @@ -301,6 +303,11 @@ function Permissions({
} + {loading && ( +
+ +
+ )} ); } diff --git a/geonode_mapstore_client/client/js/components/ResourceCard/ResourceCard.jsx b/geonode_mapstore_client/client/js/components/ResourceCard/ResourceCard.jsx index 8bf1cb8e50..4a470472ba 100644 --- a/geonode_mapstore_client/client/js/components/ResourceCard/ResourceCard.jsx +++ b/geonode_mapstore_client/client/js/components/ResourceCard/ResourceCard.jsx @@ -10,8 +10,14 @@ import React, { forwardRef } from 'react'; import Message from '@mapstore/framework/components/I18N/Message'; import FaIcon from '@js/components/FaIcon'; import Dropdown from '@js/components/Dropdown'; +import Spinner from '@js/components/Spinner'; import { getUserName } from '@js/utils/SearchUtils'; import { getResourceTypesInfo } from '@js/utils/ResourceUtils'; + +function ALink({ href, readOnly, children }) { + return readOnly ? children : {children}; +} + const ResourceCard = forwardRef(({ data, active, @@ -19,7 +25,12 @@ const ResourceCard = forwardRef(({ formatHref, getTypesInfo, layoutCardsStyle, - buildHrefByTemplate + buildHrefByTemplate, + readOnly, + actions, + onAction, + className, + loading }, ref) => { const res = data; @@ -29,14 +40,14 @@ const ResourceCard = forwardRef(({ return (
- + />}

- : : {getUserName(res.owner)} + })}>{getUserName(res.owner)}

- {options && options.length > 0 && 0) && @@ -92,7 +105,16 @@ const ResourceCard = forwardRef(({ {options .map((opt) => { - + if (opt.type === 'button' && actions[opt.action]) { + return ( + onAction(actions[opt.action], [res])} + > + + + ); + } const viewResourcebase = opt.perms.filter(obj => { return obj.value === "view_resourcebase"; }); @@ -102,7 +124,7 @@ const ResourceCard = forwardRef(({ key={opt.href} href={ (viewResourcebase.length > 0 ) ? formatHref({ - pathname: `/detail/${res.resource_type}/${res.pk}` + pathname: `/${res.resource_type}/${res.pk}` }) : buildHrefByTemplate(res, opt.href) } > @@ -120,7 +142,8 @@ const ResourceCard = forwardRef(({ ResourceCard.defaultProps = { links: [], theme: 'light', - getTypesInfo: getResourceTypesInfo + getTypesInfo: getResourceTypesInfo, + formatHref: () => '#' }; export default ResourceCard; diff --git a/geonode_mapstore_client/client/js/epics/gnsave.js b/geonode_mapstore_client/client/js/epics/gnsave.js index 121b6b8b8b..466512f0c2 100644 --- a/geonode_mapstore_client/client/js/epics/gnsave.js +++ b/geonode_mapstore_client/client/js/epics/gnsave.js @@ -54,18 +54,20 @@ import { getResourceName, getResourceDescription, getResourceThumbnail, - getPermissionsPayload + getPermissionsPayload, + getResourceData } from '@js/selectors/resource'; import { updateGeoLimits, deleteGeoLimits } from '@js/api/geonode/security'; - +import { startAsyncProcess } from '@js/actions/resourceservice'; import { ResourceTypes, cleanCompactPermissions } from '@js/utils/ResourceUtils'; +import { ProcessTypes } from '@js/utils/ResourceServiceUtils'; const SaveAPI = { [ResourceTypes.MAP]: (state, id, metadata, reload) => { @@ -214,11 +216,16 @@ export const gnSaveDirectContent = (action$, store) => const resourceId = mapInfo?.id || state?.gnresource?.id; // injected geostory id const { compactPermissions, geoLimits } = getPermissionsPayload(state); + const currentResource = getResourceData(state); return Observable.concat( ...(compactPermissions ? [ - Observable.defer(() => updateCompactPermissionsByPk(resourceId, cleanCompactPermissions(compactPermissions))) - .switchMap(() => { - return Observable.empty(); // TODO: manage async status + Observable.defer(() => + updateCompactPermissionsByPk(resourceId, cleanCompactPermissions(compactPermissions)) + .then(output => ({ resource: currentResource, output, processType: ProcessTypes.PERMISSIONS_RESOURCE })) + .catch((error) => ({ resource: currentResource, error: error?.data?.detail || error?.statusText || error?.message || true, processType: ProcessTypes.PERMISSIONS_RESOURCE })) + ) + .switchMap((payload) => { + return Observable.of(startAsyncProcess(payload)); }) ] : []), Observable.defer(() => axios.all([ diff --git a/geonode_mapstore_client/client/js/epics/resourceservice.js b/geonode_mapstore_client/client/js/epics/resourceservice.js new file mode 100644 index 0000000000..4f06d79054 --- /dev/null +++ b/geonode_mapstore_client/client/js/epics/resourceservice.js @@ -0,0 +1,89 @@ +/* + * 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 { Observable } from 'rxjs'; +import axios from '@mapstore/framework/libs/ajax'; + +import { + START_ASYNC_PROCESS, + startAsyncProcess, + stopAsyncProcess, + updateAsyncProcess +} from '@js/actions/resourceservice'; +import { + ProcessStatus, + ProcessInterval, + ProcessTypes +} from '@js/utils/ResourceServiceUtils'; +import { isProcessCompleted } from '@js/selectors/resourceservice'; +import { + deleteResource, + copyResource +} from '@js/api/geonode/v2'; +import { PROCESS_RESOURCES } from '@js/actions/gnresource'; +import { setControlProperty } from '@mapstore/framework/actions/controls'; +import { push } from 'connected-react-router'; + +export const gnMonitorAsyncProcesses = (action$, store) => { + return action$.ofType(START_ASYNC_PROCESS) + .flatMap((action) => { + const { status_url: statusUrl } = action?.payload?.output || {}; + if (!statusUrl || action?.payload?.error) { + return Observable.of(stopAsyncProcess({ ...action.payload, completed: true })); + } + return Observable + .interval(ProcessInterval[action?.payload?.processType] || 1000) + .switchMap(() => { + return Observable.defer(() => + axios.get(statusUrl) + .then(({ data }) => data) + .catch((error) => ({ error: error?.data?.detail || error?.statusText || error?.message || true })) + ) + .switchMap((output) => { + if (output.error || output.status === ProcessStatus.FINISHED || output.status === ProcessStatus.FAILED) { + return Observable.of(stopAsyncProcess({ ...action.payload, output, completed: true })); + } + return Observable.of(updateAsyncProcess({ ...action.payload, output })); + }); + }) + .takeWhile(() => !isProcessCompleted(store.getState(), action.payload)); + }); +}; + +const processAPI = { + [ProcessTypes.DELETE_RESOURCE]: deleteResource, + [ProcessTypes.CLONE_RESOURCE]: copyResource +}; + +export const gnProcessResources = (action$) => + action$.ofType(PROCESS_RESOURCES) + .switchMap((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 })) + ) + )) + .switchMap((processes) => { + return Observable.of( + setControlProperty(action.processType, 'loading', false), + setControlProperty(action.processType, 'value', undefined), + ...processes.map((payload) => startAsyncProcess(payload)), + ...(action.redirectTo ? [ + push(action.redirectTo) + ] : []) + ); + }) + .startWith(setControlProperty(action.processType, 'loading', true)); + }); + +export default { + gnMonitorAsyncProcesses, + gnProcessResources +}; diff --git a/geonode_mapstore_client/client/js/plugins/DeleteResource.jsx b/geonode_mapstore_client/client/js/plugins/DeleteResource.jsx new file mode 100644 index 0000000000..1e880261f5 --- /dev/null +++ b/geonode_mapstore_client/client/js/plugins/DeleteResource.jsx @@ -0,0 +1,148 @@ +/* + * 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 } from '@mapstore/framework/utils/PluginsUtils'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import Message from '@mapstore/framework/components/I18N/Message'; +import Button from '@js/components/Button'; +import ResizableModal from '@mapstore/framework/components/misc/ResizableModal'; +import Portal from '@mapstore/framework/components/misc/Portal'; +import { setControlProperty } from '@mapstore/framework/actions/controls'; +import { getResourceData } from '@js/selectors/resource'; +import { processResources } from '@js/actions/gnresource'; +import ResourceCard from '@js/components/ResourceCard'; +import { ProcessTypes } from '@js/utils/ResourceServiceUtils'; +import Loader from '@mapstore/framework/components/misc/Loader'; + +function DeleteResourcePlugin({ + enabled, + resources = [], + onClose = () => {}, + onDelete = () => {}, + redirectTo = '/', + loading +}) { + return ( + + } + show={enabled} + fitContent + clickOutEnabled={false} + modalClassName="gn-simple-dialog" + buttons={loading + ? [] + : [{ + text: , + onClick: () => onClose() + }, + { + text: , + bsStyle: 'danger', + onClick: () => onDelete(resources, redirectTo) + }] + } + onClose={loading ? null : () => onClose()} + > +
    + {resources.map((data, idx) => { + return ( +
  • + +
  • + ); + })} +
+ {loading &&
+ +
} +
+
+ ); +} + +const ConnectedDeleteResourcePlugin = connect( + createSelector([ + state => state?.controls?.[ProcessTypes.DELETE_RESOURCE]?.value, + state => state?.controls?.[ProcessTypes.DELETE_RESOURCE]?.loading + ], (resources, loading) => ({ + resources, + enabled: !!resources, + loading + })), { + onClose: setControlProperty.bind(null, ProcessTypes.DELETE_RESOURCE, 'value', undefined), + onDelete: processResources.bind(null, ProcessTypes.DELETE_RESOURCE) + } +)(DeleteResourcePlugin); + +const DeleteButton = ({ + onClick, + size, + resource +}) => { + + const handleClickButton = () => { + onClick([resource]); + }; + + return ( + + ); +}; + +const ConnectedDeleteButton = connect( + createSelector([ + getResourceData + ], (resource) => ({ + resource + })), + { + onClick: setControlProperty.bind(null, ProcessTypes.DELETE_RESOURCE, 'value') + } +)((DeleteButton)); + +export default createPlugin('DeleteResource', { + component: ConnectedDeleteResourcePlugin, + containers: { + ActionNavbar: { + name: 'DeleteResource', + Component: ConnectedDeleteButton + } + }, + epics: {}, + reducers: {} +}); diff --git a/geonode_mapstore_client/client/js/plugins/Save.jsx b/geonode_mapstore_client/client/js/plugins/Save.jsx index f98176d492..2f4aaf4fe7 100644 --- a/geonode_mapstore_client/client/js/plugins/Save.jsx +++ b/geonode_mapstore_client/client/js/plugins/Save.jsx @@ -15,6 +15,7 @@ import { Glyphicon } from 'react-bootstrap'; import { mapInfoSelector } from '@mapstore/framework/selectors/map'; import Loader from '@mapstore/framework/components/misc/Loader'; import Button from '@js/components/Button'; +import Spinner from '@js/components/Spinner'; import { isLoggedIn } from '@mapstore/framework/selectors/security'; import controls from '@mapstore/framework/reducers/controls'; import gnresource from '@js/reducers/gnresource'; @@ -25,6 +26,7 @@ import { isNewResource, canEditResource } from '@js/selectors/resource'; +import { getCurrentResourcePermissionsLoading } from '@js/selectors/resourceservice'; /** * Plugin for Save modal * @name Save @@ -72,15 +74,17 @@ function SaveButton({ enabled, onClick, variant, - size + size, + loading }) { return enabled ? : null ; @@ -92,10 +96,12 @@ const ConnectedSaveButton = connect( isNewResource, canEditResource, mapInfoSelector, - (loggedIn, isNew, canEdit, mapInfo) => ({ + getCurrentResourcePermissionsLoading, + (loggedIn, isNew, canEdit, mapInfo, permissionsLoading) => ({ // we should add permList to map pages too // currently the canEdit is located inside the map info - enabled: loggedIn && !isNew && (canEdit || mapInfo?.canEdit) + enabled: loggedIn && !isNew && (canEdit || mapInfo?.canEdit), + loading: permissionsLoading }) ), { diff --git a/geonode_mapstore_client/client/js/plugins/Share.jsx b/geonode_mapstore_client/client/js/plugins/Share.jsx index 516e8f84a4..1f7a33ba3a 100644 --- a/geonode_mapstore_client/client/js/plugins/Share.jsx +++ b/geonode_mapstore_client/client/js/plugins/Share.jsx @@ -32,6 +32,7 @@ import { getUsers, getGroups } from '@js/api/geonode/v2'; import { resourceToPermissionEntry } from '@js/utils/ResourceUtils'; import SharePageLink from '@js/plugins/share/SharePageLink'; import ShareEmbedLink from '@js/plugins/share/ShareEmbedLink'; +import { getCurrentResourcePermissionsLoading } from '@js/selectors/resourceservice'; function getShareUrl({ resourceId, @@ -109,7 +110,8 @@ function Share({ onClose, canEdit, permissionsGroupOptions, - permissionsDefaultGroupOptions + permissionsDefaultGroupOptions, + permissionsLoading }) { const shareUrl = getShareUrl({ @@ -148,6 +150,7 @@ function Share({ resourceId={resourceId} groupOptions={permissionsGroupOptions} defaultGroupOptions={permissionsDefaultGroupOptions} + loading={permissionsLoading} /> }
@@ -234,13 +237,15 @@ const SharePlugin = connect( mapInfoSelector, getCompactPermissions, layersSelector, - canEditPermissions - ], (enabled, resourceId, mapInfo, compactPermissions, layers, canEdit) => ({ + canEditPermissions, + getCurrentResourcePermissionsLoading + ], (enabled, resourceId, mapInfo, compactPermissions, layers, canEdit, permissionsLoading) => ({ enabled, resourceId: resourceId || mapInfo?.id, compactPermissions, layers, - canEdit + canEdit, + permissionsLoading })), { onClose: setControlProperty.bind(null, 'rightOverlay', 'enabled', false), diff --git a/geonode_mapstore_client/client/js/plugins/index.js b/geonode_mapstore_client/client/js/plugins/index.js index e7d81698db..0a5c39af6a 100644 --- a/geonode_mapstore_client/client/js/plugins/index.js +++ b/geonode_mapstore_client/client/js/plugins/index.js @@ -359,8 +359,11 @@ export const plugins = { } } } + ), + DeleteResourcePlugin: toLazyPlugin( + 'DeleteResource', + import(/* webpackChunkName: 'plugins/delete-resource-plugin' */ '@js/plugins/DeleteResource') ) - }; const pluginsDefinition = { diff --git a/geonode_mapstore_client/client/js/plugins/save/SaveModal.jsx b/geonode_mapstore_client/client/js/plugins/save/SaveModal.jsx index c53496eadf..733d63ad72 100644 --- a/geonode_mapstore_client/client/js/plugins/save/SaveModal.jsx +++ b/geonode_mapstore_client/client/js/plugins/save/SaveModal.jsx @@ -14,6 +14,7 @@ import Message from '@mapstore/framework/components/I18N/Message'; import { Form, FormGroup, ControlLabel, FormControl as FormControlRB, Alert } from 'react-bootstrap'; import localizedProps from '@mapstore/framework/components/misc/enhancers/localizedProps'; import Loader from '@mapstore/framework/components/misc/Loader'; +import Portal from '@mapstore/framework/components/misc/Portal'; const FormControl = localizedProps('placeholder')(FormControlRB); function SaveModal({ @@ -69,115 +70,117 @@ function SaveModal({ const isLoading = loading || saving; return ( - } - show={enabled} - fitContent - clickOutEnabled={false} - buttons={isLoading - ? [] - : [ - { - text: , - onClick: () => onClose() - }, - { - text: , - disabled: !!nameValidation, - onClick: () => onSave( - update ? contentId : undefined, - { - thumbnail, - name, - description - }, - true) - } - ]} - onClose={isLoading ? null : () => onClose()} - > - {error && -
-
} - {success && -
-
} -
- - - - - } - onUpdate={(data) => { - setThumbnail(data); - setThumbnailError(false); - }} - onError={(newThumbnailError) => { - setThumbnailError(newThumbnailError); - }} - /> - {thumbnailError &&
-
-
{thumbnailError.indexOf && thumbnailError.indexOf('FORMAT') !== -1 && }
-
{thumbnailError.indexOf && thumbnailError.indexOf('SIZE') !== -1 && }
-
} -
- - - - - { - setName(event.target.value); - setNameValidation(!event.target.value - ? 'error' - : undefined); - }} - onBlur={(event) => { - setNameValidation(!event.target.value - ? 'error' - : undefined - ); - }} - /> - - - - - - setDescription(event.target.value)} - /> - -
- {isLoading &&
+ } + show={enabled} + fitContent + clickOutEnabled={false} + buttons={isLoading + ? [] + : [ + { + text: , + onClick: () => onClose() + }, + { + text: , + disabled: !!nameValidation, + onClick: () => onSave( + update ? contentId : undefined, + { + thumbnail, + name, + description + }, + true) + } + ]} + onClose={isLoading ? null : () => onClose()} > - -
} -
+ {error && +
+
} + {success && +
+
} +
+ + + + + } + onUpdate={(data) => { + setThumbnail(data); + setThumbnailError(false); + }} + onError={(newThumbnailError) => { + setThumbnailError(newThumbnailError); + }} + /> + {thumbnailError &&
+
+
{thumbnailError.indexOf && thumbnailError.indexOf('FORMAT') !== -1 && }
+
{thumbnailError.indexOf && thumbnailError.indexOf('SIZE') !== -1 && }
+
} +
+ + + + + { + setName(event.target.value); + setNameValidation(!event.target.value + ? 'error' + : undefined); + }} + onBlur={(event) => { + setNameValidation(!event.target.value + ? 'error' + : undefined + ); + }} + /> + + + + + + setDescription(event.target.value)} + /> + +
+ {isLoading &&
+ +
} + + ); } diff --git a/geonode_mapstore_client/client/js/reducers/resourceservice.js b/geonode_mapstore_client/client/js/reducers/resourceservice.js new file mode 100644 index 0000000000..379d857d5e --- /dev/null +++ b/geonode_mapstore_client/client/js/reducers/resourceservice.js @@ -0,0 +1,83 @@ +/* + * 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 { + START_ASYNC_PROCESS, + STOP_ASYNC_PROCESS, + UPDATE_ASYNC_PROCESS +} from '@js/actions/resourceservice'; + +import { ProcessStatus } from '@js/utils/ResourceServiceUtils'; + +const LOCAL_STORAGE_PROCESSES_KEY = 'gn.reducers.resourceservice.processes'; + +function getLocalStorageProcesses() { + try { + const processes = JSON.parse(localStorage.getItem(LOCAL_STORAGE_PROCESSES_KEY)) || []; + return processes.filter(({ completed, output }) => !(completed || output?.status === ProcessStatus.FINISHED || output?.status === ProcessStatus.FAILED)); + } catch (e) { + return []; + } +} + +function setLocalStorageProcesses(reducer) { + return (state, action) => { + const newState = reducer(state, action); + try { + localStorage.setItem(LOCAL_STORAGE_PROCESSES_KEY, JSON.stringify(newState.processes)); + } catch (e) {/**/} + + return newState; + }; +} + +const defaultState = { + processes: getLocalStorageProcesses() +}; + +function resourceservice(state = defaultState, action) { + switch (action.type) { + case START_ASYNC_PROCESS: { + return { + ...state, + processes: [ + // remove previous process if same id and type + ...state.processes.filter((process) => !( + process?.resource?.pk === action?.payload?.resource?.pk + && process?.processType === action?.payload?.processType + )), + action.payload + ] + }; + } + case UPDATE_ASYNC_PROCESS: { + return { + ...state, + processes: state.processes.map((process) => + process?.resource?.pk === action?.payload?.resource?.pk + && process?.processType === action?.payload?.processType + ? action.payload + : process) + }; + } + case STOP_ASYNC_PROCESS: { + return { + ...state, + processes: state.processes.map((process) => + process?.resource?.pk === action?.payload?.resource?.pk + && process?.processType === action?.payload?.processType + ? action.payload + : process) + }; + } + default: + return state; + } +} + +export default setLocalStorageProcesses(resourceservice); diff --git a/geonode_mapstore_client/client/js/routes/Detail.jsx b/geonode_mapstore_client/client/js/routes/Detail.jsx index d39483ed76..9fb6140b3f 100644 --- a/geonode_mapstore_client/client/js/routes/Detail.jsx +++ b/geonode_mapstore_client/client/js/routes/Detail.jsx @@ -37,6 +37,8 @@ import { } from '@js/utils/AppUtils'; import ConnectedCardGrid from '@js/routes/catalogue/ConnectedCardGrid'; +import DeleteResource from '@js/plugins/DeleteResource'; +const { DeleteResourcePlugin } = DeleteResource; const ConnectedDetailsPanel = connect( createSelector([ @@ -163,6 +165,7 @@ function Detail({ formatHref={handleFormatHref} />}
+ ); } diff --git a/geonode_mapstore_client/client/js/routes/Home.jsx b/geonode_mapstore_client/client/js/routes/Home.jsx index b4748778e8..8ae6ea9cac 100644 --- a/geonode_mapstore_client/client/js/routes/Home.jsx +++ b/geonode_mapstore_client/client/js/routes/Home.jsx @@ -33,12 +33,13 @@ import { } from '@js/utils/AppUtils'; import ConnectedCardGrid from '@js/routes/catalogue/ConnectedCardGrid'; - -const DEFAULT_RESOURCES = []; +import { getFeaturedResults } from '@js/selectors/search'; +import DeleteResource from '@js/plugins/DeleteResource'; +const { DeleteResourcePlugin } = DeleteResource; const ConnectedFeatureList = connect( createSelector([ - state => state?.gnsearch?.featuredResources?.resources || DEFAULT_RESOURCES, + getFeaturedResults, state => state?.gnsearch?.featuredResources?.page || 1, state => state?.gnsearch?.featuredResources?.isNextPageAvailable || false, state => state?.gnsearch?.featuredResources?.isPreviousPageAvailable || false, @@ -142,6 +143,7 @@ function Home({ + ); } diff --git a/geonode_mapstore_client/client/js/routes/Search.jsx b/geonode_mapstore_client/client/js/routes/Search.jsx index e2f5daf4bb..00b7fde99f 100644 --- a/geonode_mapstore_client/client/js/routes/Search.jsx +++ b/geonode_mapstore_client/client/js/routes/Search.jsx @@ -40,6 +40,8 @@ import { } from '@js/utils/AppUtils'; import ConnectedCardGrid from '@js/routes/catalogue/ConnectedCardGrid'; +import DeleteResource from '@js/plugins/DeleteResource'; +const { DeleteResourcePlugin } = DeleteResource; const suggestionsRequestTypes = { resourceTypes: { @@ -221,6 +223,7 @@ function Search({ + ); } diff --git a/geonode_mapstore_client/client/js/routes/catalogue/ConnectedCardGrid.jsx b/geonode_mapstore_client/client/js/routes/catalogue/ConnectedCardGrid.jsx index ff535b3db3..239f3d6663 100644 --- a/geonode_mapstore_client/client/js/routes/catalogue/ConnectedCardGrid.jsx +++ b/geonode_mapstore_client/client/js/routes/catalogue/ConnectedCardGrid.jsx @@ -10,8 +10,10 @@ import React from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import CardGrid from '@js/components/CardGrid'; - -const DEFAULT_RESOURCES = []; +import { getSearchResults } from '@js/selectors/search'; +import { processResources } from '@js/actions/gnresource'; +import { setControlProperty } from '@mapstore/framework/actions/controls'; +import { ProcessTypes } from '@js/utils/ResourceServiceUtils'; const CardGridWithMessageId = ({ query, user, isFirstRequest, ...props }) => { const hasResources = props.resources?.length > 0; @@ -27,7 +29,7 @@ const CardGridWithMessageId = ({ query, user, isFirstRequest, ...props }) => { const ConnectedCardGrid = connect( createSelector([ - state => state?.gnsearch?.resources || DEFAULT_RESOURCES, + getSearchResults, state => state?.gnsearch?.loading || false, state => state?.gnsearch?.isNextPageAvailable || false, state => state?.gnsearch?.isFirstRequest @@ -35,8 +37,21 @@ const ConnectedCardGrid = connect( resources, loading, isNextPageAvailable, - isFirstRequest - })) + isFirstRequest, + actions: { + 'delete': { + processType: ProcessTypes.DELETE_RESOURCE, + isControlled: true + }, + 'clone': { + processType: ProcessTypes.CLONE_RESOURCE + } + } + })), + { + onAction: processResources, + onControl: setControlProperty + } )(CardGridWithMessageId); export default ConnectedCardGrid; diff --git a/geonode_mapstore_client/client/js/selectors/resourceservice.js b/geonode_mapstore_client/client/js/selectors/resourceservice.js new file mode 100644 index 0000000000..ce69c46d9c --- /dev/null +++ b/geonode_mapstore_client/client/js/selectors/resourceservice.js @@ -0,0 +1,26 @@ +/* + * 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 { getResourceData } from '@js/selectors/resource'; +import { ProcessTypes } from '@js/utils/ResourceServiceUtils'; + +export const isProcessCompleted = (state, payload) => + (state?.resourceservice?.processes?.find(process => + process?.resource?.pk === payload?.resource?.pk + && process?.processType === payload?.processType + ) || {}).completed; + +export const getCurrentResourcePermissionsLoading = (state) => { + const resource = getResourceData(state); + const permissionsProcess = resource && state?.resourceservice?.processes?.find(process => + process?.resource?.pk === resource?.pk + && process?.processType === ProcessTypes.PERMISSIONS_RESOURCE + ); + const isLoading = permissionsProcess ? !permissionsProcess?.completed : false; + return isLoading; +}; diff --git a/geonode_mapstore_client/client/js/selectors/search.js b/geonode_mapstore_client/client/js/selectors/search.js new file mode 100644 index 0000000000..6e5feb8d96 --- /dev/null +++ b/geonode_mapstore_client/client/js/selectors/search.js @@ -0,0 +1,31 @@ +/* + * 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. +*/ + +export const getSearchResults = (state) => { + const resources = state?.gnsearch?.resources || []; + const processes = state?.resourceservice?.processes || []; + return resources.map((resource) => { + const resourceProcesses = processes.filter((process) => process?.resource?.pk === resource?.pk); + if (resourceProcesses.length > 0) { + return { ...resource, processes: resourceProcesses }; + } + return resource; + }); +}; + +export const getFeaturedResults = (state) => { + const resources = state?.gnsearch?.featuredResources?.resources || []; + const processes = state?.resourceservice?.processes || []; + return resources.map((resource) => { + const resourceProcesses = processes.filter((process) => process?.resource?.pk === resource?.pk); + if (resourceProcesses.length > 0) { + return { ...resource, processes: resourceProcesses }; + } + return resource; + }); +}; diff --git a/geonode_mapstore_client/client/js/utils/ResourceServiceUtils.js b/geonode_mapstore_client/client/js/utils/ResourceServiceUtils.js new file mode 100644 index 0000000000..666dc0ec5f --- /dev/null +++ b/geonode_mapstore_client/client/js/utils/ResourceServiceUtils.js @@ -0,0 +1,26 @@ +/* + * 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. +*/ + +export const ProcessTypes = { + DELETE_RESOURCE: 'deleteResource', + CLONE_RESOURCE: 'cloneResource', + PERMISSIONS_RESOURCE: 'permissionsResource' +}; + +export const ProcessStatus = { + READY: 'ready', + FAILED: 'failed', + RUNNING: 'running', + FINISHED: 'finished' +}; + +export const ProcessInterval = { + [ProcessTypes.DELETE_RESOURCE]: 5000, + [ProcessTypes.CLONE_RESOURCE]: 1000, + [ProcessTypes.PERMISSIONS_RESOURCE]: 1000 +}; diff --git a/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json b/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json index f2b3e6a1a0..72ea6b4c3d 100644 --- a/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json +++ b/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json @@ -485,24 +485,8 @@ ] }, { - "type": "link", - "href": "${detail_url}/metadata_advanced", - "labelId": "gnhome.update", - "icon": "paste", - "authenticated": true, - "perms": [ - { - "type": "resource", - "value": "change_resourcebase" - } - ], - "allowedGroups": [ - "admin" - ] - }, - { - "type": "link", - "href": "${detail_url}/remove", + "type": "button", + "action": "delete", "labelId": "gnhome.delete", "icon": "trash", "authenticated": true, @@ -511,9 +495,6 @@ "type": "resource", "value": "delete_resourcebase" } - ], - "allowedGroups": [ - "admin" ] } ] @@ -2482,6 +2463,16 @@ { "type": "plugin", "name": "Measure" + }, + { + "type": "plugin", + "name": "DeleteResource", + "perms": [ + { + "type": "resource", + "value": "delete_resourcebase" + } + ] } ] } @@ -2508,6 +2499,9 @@ { "name": "DetailViewer" }, + { + "name": "DeleteResource" + }, { "name": "Map", "cfg": { @@ -3031,10 +3025,23 @@ { "type": "plugin", "name": "Measure" + }, + { + "type": "plugin", + "name": "DeleteResource", + "perms": [ + { + "type": "resource", + "value": "delete_resourcebase" + } + ] } ] } }, + { + "name": "DeleteResource" + }, { "name": "DetailViewer" }, @@ -3455,6 +3462,16 @@ { "type": "plugin", "name": "Share" + }, + { + "type": "plugin", + "name": "DeleteResource", + "perms": [ + { + "type": "resource", + "value": "delete_resourcebase" + } + ] } ] } @@ -3462,6 +3479,9 @@ { "name": "DetailViewer" }, + { + "name": "DeleteResource" + }, { "name": "GeoStory", "cfg": { @@ -3619,6 +3639,16 @@ { "type": "plugin", "name": "Share" + }, + { + "type": "plugin", + "name": "DeleteResource", + "perms": [ + { + "type": "resource", + "value": "delete_resourcebase" + } + ] } ] } @@ -3626,6 +3656,9 @@ { "name": "DetailViewer" }, + { + "name": "DeleteResource" + }, { "name": "Save" }, @@ -3691,6 +3724,16 @@ { "type": "plugin", "name": "Share" + }, + { + "type": "plugin", + "name": "DeleteResource", + "perms": [ + { + "type": "resource", + "value": "delete_resourcebase" + } + ] } ] } @@ -3698,6 +3741,9 @@ { "name": "DetailViewer" }, + { + "name": "DeleteResource" + }, { "name": "Save" }, 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 0026f22cd2..3f39142f99 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 @@ -159,7 +159,10 @@ "registered-members": "Mitwirkende", "permissionsEntriesNoResults": "Keine Ergebnisse...", "shareThisResource": "Diese Ressource teilen", - "embed": "Einbetten" + "embed": "Einbetten", + "deleteResourceTitle": "{count, plural, =1 {Möchten Sie diese Ressource wirklich löschen?} other {Möchten Sie diese Ressourcen wirklich löschen?}}", + "deleteResourceNo": "{count, plural, =1 {Nein, nicht löschen} other {Nein, lösche sie nicht}}", + "deleteResourceYes": "Ja, ich bin sicher" } } } 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 d614ae0c51..ebb92a6dd7 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 @@ -160,7 +160,10 @@ "registered-members": "Contributors", "permissionsEntriesNoResults": "No Results...", "shareThisResource": "Share this resource", - "embed": "Embed" + "embed": "Embed", + "deleteResourceTitle": "{count, plural, =1 {Are you sure you want to delete this resource?} other {Are you sure you want to delete these resources?}}", + "deleteResourceNo": "{count, plural, =1 {No, don't delete it} other {No, don't delete them}}", + "deleteResourceYes": "Yes, I am sure" } } } 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 94b0eec402..dace228df8 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 @@ -159,7 +159,10 @@ "registered-members": "Colaboradores", "permissionsEntriesNoResults": "Sin resultados ...", "shareThisResource": "Comparte este recurso", - "embed": "Insertar" + "embed": "Insertar", + "deleteResourceTitle": "{count, plural, =1 {¿Estás seguro de que deseas eliminar este recurso?} other {¿Está seguro de que desea eliminar estos recursos?}}", + "deleteResourceNo": "{count, plural, =1 {No, no lo borres} other {No, no los borres}}", + "deleteResourceYes": "Sí, estoy seguro" } } } 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 9c433a30bb..674c97f3ad 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 @@ -159,7 +159,10 @@ "registered-members": "Contributeurs", "permissionsEntriesNoResults": "Aucun résultat...", "shareThisResource": "Partager cette ressource", - "embed": "Intégrer" + "embed": "Intégrer", + "deleteResourceTitle": "{count, plural, =1 {Voulez-vous vraiment supprimer cette ressource?} other {Voulez-vous vraiment supprimer ces ressources?}}", + "deleteResourceNo": "{count, plural, =1 {Non, ne le supprime pas} other {Non, ne les supprime pas}}", + "deleteResourceYes": "Oui, je suis sûr" } } } 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 31ae9a242c..9d58d4ac27 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 @@ -161,7 +161,10 @@ "registered-members": "Collaboratori", "permissionsEntriesNoResults": "Nessun risultato...", "shareThisResource": "Condividi questa risorsa", - "embed": "Incorpora" + "embed": "Incorpora", + "deleteResourceTitle": "{count, plural, =1 {Sei sicuro di voler eliminare questa risorsa?} other {Sei sicuro di voler eliminare queste risorse?}}", + "deleteResourceNo": "{count, plural, =1 {No, non eliminarlo} other {No, non eliminarli}}", + "deleteResourceYes": "Sì, sono sicuro" } } } diff --git a/geonode_mapstore_client/client/themes/geonode/less/_card-grid.less b/geonode_mapstore_client/client/themes/geonode/less/_card-grid.less index 0ba0cc955d..194a7a6ac2 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/_card-grid.less +++ b/geonode_mapstore_client/client/themes/geonode/less/_card-grid.less @@ -92,7 +92,7 @@ flex: 1 1 auto; .card-img-left{ width: 160px; - height: auto; + height: 135px; object-fit: cover; } } diff --git a/geonode_mapstore_client/client/themes/geonode/less/_resource-card.less b/geonode_mapstore_client/client/themes/geonode/less/_resource-card.less index c447c9c7a6..689d713f34 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/_resource-card.less +++ b/geonode_mapstore_client/client/themes/geonode/less/_resource-card.less @@ -31,7 +31,7 @@ .outline-color-var(@theme-vars[focus-color]); } - &:hover { + &:not(.read-only):hover { .border-color-var(@theme-vars[focus-color]); } .gn-card-options { @@ -105,6 +105,9 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + .gn-spinner { + margin-right: 0.4rem; + } } .card-title { font-size: 0.875rem; @@ -141,7 +144,10 @@ outline-style: solid; .shadow(); } - &:hover { + &:not(.read-only):hover { .shadow(); } + &.deleted { + opacity: 0.4; + } } diff --git a/geonode_mapstore_client/client/themes/geonode/less/_share.less b/geonode_mapstore_client/client/themes/geonode/less/_share.less index 0db592acb5..9b4aa145ce 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/_share.less +++ b/geonode_mapstore_client/client/themes/geonode/less/_share.less @@ -82,6 +82,7 @@ border-style: solid; padding: 0.5rem; margin: 0.5rem; + position: relative; } .gn-share-permissions-list { diff --git a/geonode_mapstore_client/client/themes/geonode/less/_spinner.less b/geonode_mapstore_client/client/themes/geonode/less/_spinner.less index 3d265ac8eb..88ee89ff83 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/_spinner.less +++ b/geonode_mapstore_client/client/themes/geonode/less/_spinner.less @@ -9,6 +9,9 @@ .border-bottom-color-var(@theme-vars[loader-fade-color]); .border-left-color-var(@theme-vars[loader-color]); } + .gn-spinner-container { + .background-color-var(@theme-vars[main-bg]); + } } // ************** @@ -62,3 +65,17 @@ .get-gn-spinner(0.1em, 1em); } } + +.gn-spinner-container { + position: absolute; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + opacity: 0.7; + top: 0; + left: 0; + font-size: 2rem; +} diff --git a/geonode_mapstore_client/client/themes/geonode/less/ms-theme.less b/geonode_mapstore_client/client/themes/geonode/less/ms-theme.less index 58c1209e85..9e0fa3a346 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/ms-theme.less +++ b/geonode_mapstore_client/client/themes/geonode/less/ms-theme.less @@ -122,3 +122,15 @@ display: none; } } + +.gn-simple-dialog { + .ms-resizable-modal > .modal-content > .modal-header { + height: auto; + } + .ms-resizable-modal > .modal-content > .modal-header .modal-title .ms-title { + text-overflow: initial; + height: auto; + line-height: initial; + white-space: normal; + } +}