From c0e52e61bb4808406e77a8320c736a00ba88681a Mon Sep 17 00:00:00 2001 From: stefano bovio Date: Fri, 22 Oct 2021 15:48:54 +0200 Subject: [PATCH] optimize request while routing (#545) --- .../client/js/api/geonode/config/index.js | 26 +- .../client/js/epics/gnresource.js | 39 ++- .../client/js/epics/gnsearch.js | 12 +- .../client/js/hooks/useLazyPlugins.js | 59 ++-- .../client/js/routes/Viewer.jsx | 47 +-- .../static/mapstore/configs/localConfig.json | 274 ++++++++++++++++-- .../client/themes/geonode/less/_base.less | 1 + 7 files changed, 350 insertions(+), 108 deletions(-) diff --git a/geonode_mapstore_client/client/js/api/geonode/config/index.js b/geonode_mapstore_client/client/js/api/geonode/config/index.js index b2957e93c7..590c6fa01f 100644 --- a/geonode_mapstore_client/client/js/api/geonode/config/index.js +++ b/geonode_mapstore_client/client/js/api/geonode/config/index.js @@ -8,14 +8,32 @@ import axios from '@mapstore/framework/libs/ajax'; +let cache = {}; + export const getNewMapConfiguration = (newMapUrl = '/static/mapstore/configs/map.json') => { - return axios.get(newMapUrl) - .then(({ data }) => window.overrideNewMapConfig ? window.overrideNewMapConfig(data) : data); + return cache.newMapConfig + ? new Promise((resolve) => resolve(cache.newMapConfig)) + : axios.get(newMapUrl).then(({ data }) => { + cache.newMapConfig = data; + return data; + }) + .then((newMapConfig) => window.overrideNewMapConfig + ? window.overrideNewMapConfig(newMapConfig) + : newMapConfig + ); }; export const getNewGeoStoryConfig = (newGeoStoryUrl = '/static/mapstore/configs/geostory.json') => { - return axios.get(newGeoStoryUrl) - .then(({ data }) => window.overrideNewGeoStoryConfig ? window.overrideNewGeoStoryConfig(data) : data); + return cache.newGeoStoryConfig + ? new Promise((resolve) => resolve(cache.newGeoStoryConfig)) + : axios.get(newGeoStoryUrl).then(({ data }) => { + cache.newGeoStoryConfig = data; + return data; + }) + .then((newGeoStoryConfig) => window.overrideNewGeoStoryConfig + ? window.overrideNewGeoStoryConfig(newGeoStoryConfig) + : newGeoStoryConfig + ); }; export default { diff --git a/geonode_mapstore_client/client/js/epics/gnresource.js b/geonode_mapstore_client/client/js/epics/gnresource.js index 0f75d22d12..e6da0cc642 100644 --- a/geonode_mapstore_client/client/js/epics/gnresource.js +++ b/geonode_mapstore_client/client/js/epics/gnresource.js @@ -60,7 +60,10 @@ import { resourceToLayerConfig, ResourceTypes } from '@js/utils/ResourceUtils'; -import { canAddResource } from '@js/selectors/resource'; +import { + canAddResource, + getResourceData +} from '@js/selectors/resource'; import { updateAdditionalLayer } from '@mapstore/framework/actions/additionallayers'; import { STYLE_OWNER_NAME } from '@mapstore/framework/utils/StyleEditorUtils'; import StylesAPI from '@mapstore/framework/api/geoserver/Styles'; @@ -74,7 +77,9 @@ const resourceTypes = { return Observable.defer(() => axios.all([ getNewMapConfiguration(), - getDatasetByPk(pk) + options?.isSamePreviousResource + ? new Promise(resolve => resolve(options.resourceData)) + : getDatasetByPk(pk) ]) .then((response) => { const [mapConfig, gnLayer] = response; @@ -229,9 +234,9 @@ const resourceTypes = { }; // collect all the reset action needed before changing a viewer -const getResetActions = () => [ +const getResetActions = (isSameResource) => [ resetControls(), - resetResourceState(), + ...(!isSameResource ? [ resetResourceState() ] : []), setControlProperty('rightOverlay', 'enabled', false) ]; @@ -301,19 +306,25 @@ export const gnViewerRequestResourceConfig = (action$, store) => ); } const styleService = styleServiceSelector(state); + const resourceData = getResourceData(state); + const isSamePreviousResource = !resourceData?.['@ms-detail'] && resourceData?.pk === action.pk; return Observable.concat( Observable.of( - ...getResetActions(), + ...getResetActions(isSamePreviousResource), loadingResourceConfig(true), setResourceType(action.resourceType) ), - Observable.defer(() => getCompactPermissionsByPk(action.pk)) - .switchMap((compactPermissions) => { - return Observable.of(setResourceCompactPermissions(compactPermissions)); - }) - .catch(() => { - return Observable.empty(); - }), + ...(!isSamePreviousResource + ? [ + Observable.defer(() => getCompactPermissionsByPk(action.pk)) + .switchMap((compactPermissions) => { + return Observable.of(setResourceCompactPermissions(compactPermissions)); + }) + .catch(() => { + return Observable.empty(); + }) + ] + : []), ...(styleService?.baseUrl ? [Observable.defer(() => updateStyleService({ styleService @@ -327,7 +338,9 @@ export const gnViewerRequestResourceConfig = (action$, store) => // set the pending changes as the new data fro maps, dashboards and geostories // if undefined the returned data will be used data: pendingChanges?.data, - styleService: styleServiceSelector(state) + styleService: styleServiceSelector(state), + isSamePreviousResource, + resourceData }), Observable.of( ...(pendingChanges?.resource ? [updateResourceProperties(pendingChanges.resource)] : []), diff --git a/geonode_mapstore_client/client/js/epics/gnsearch.js b/geonode_mapstore_client/client/js/epics/gnsearch.js index ce02a25230..20c8aca750 100644 --- a/geonode_mapstore_client/client/js/epics/gnsearch.js +++ b/geonode_mapstore_client/client/js/epics/gnsearch.js @@ -214,7 +214,11 @@ export const gnsSelectResourceEpic = (action$, store) => pk === action.pk && action.ctype === resourceType); return Observable.defer(() => getResourceByPk(action.pk)) .switchMap((resource) => { - return Observable.of(setResource(resource)); + return Observable.of(setResource({ + ...resource, + /* store information related to detail */ + '@ms-detail': true + })); }) .catch((error) => { return Observable.of(resourceError(error.data || error.message)); @@ -222,7 +226,11 @@ export const gnsSelectResourceEpic = (action$, store) => .startWith( // preload the resource if available ...(selectedResource - ? [ setResource(selectedResource) ] + ? [ setResource({ + ...selectedResource, + /* store information related to detail */ + '@ms-detail': true + }) ] : [ resourceLoading() ]) ); }); diff --git a/geonode_mapstore_client/client/js/hooks/useLazyPlugins.js b/geonode_mapstore_client/client/js/hooks/useLazyPlugins.js index fd365bb9cd..670a468091 100644 --- a/geonode_mapstore_client/client/js/hooks/useLazyPlugins.js +++ b/geonode_mapstore_client/client/js/hooks/useLazyPlugins.js @@ -8,7 +8,7 @@ import { useEffect, useState } from 'react'; import isEmpty from 'lodash/isEmpty'; -import { getPlugins } from '@mapstore/framework/utils/PluginsUtils'; +import { getPlugins, createPlugin } from '@mapstore/framework/utils/PluginsUtils'; import { augmentStore } from '@mapstore/framework/utils/StateUtils'; import join from 'lodash/join'; @@ -24,6 +24,7 @@ function filterRemoved(registry, removed = []) { }, {}); } +let storedPlugins = {}; const pluginsCache = {}; const epicsCache = {}; const reducersCache = {}; @@ -43,18 +44,20 @@ function useLazyPlugins({ const pluginsString = join(pluginsKeys, ','); useEffect(() => { - if (!pluginsCache[pluginsString]) { + const filteredPluginsKeys = pluginsKeys + .filter((pluginName) => !pluginsCache[pluginName]); + if (filteredPluginsKeys.length > 0) { setPending(true); - Promise.all( - pluginsKeys.map(pluginName => { + const loadPlugins = filteredPluginsKeys + .map(pluginName => { return pluginsEntries[pluginName]().then((mod) => { const impl = mod.default; return impl; }); - }) - ) + }); + Promise.all(loadPlugins) .then((impls) => { - const { reducers, epics } = pluginsKeys.reduce((acc, pluginName, idx) => { + const { reducers, epics } = filteredPluginsKeys.reduce((acc, pluginName, idx) => { const impl = impls[idx]; return { reducers: { @@ -104,37 +107,19 @@ function useLazyPlugins({ epics: filterOutExistingEpics }); } - - return pluginsKeys.map((pluginName, idx) => { - const { loadPlugin, enabler, ...impl } = impls[idx]; - const pluginDef = { - [pluginName]: { - [pluginName]: { - ...impl.containers, - ...(enabler && { enabler }), - loadPlugin: loadPlugin - ? (resolve) => loadPlugin((component) => { - resolve({ ...impl, component }); - }) - : (resolve) => { - resolve(impl); - } - } - } - }; - return { plugin: pluginDef }; + return getPlugins({ + ...filterRemoved(impls.map(impl => createPlugin(impl.name, impl)), removed) }); }) - .then((loaded) => { - pluginsCache[pluginsString] = getPlugins( - { - ...filterRemoved( - loaded.reduce((previous, current) => ({ ...previous, ...current.plugin }), {}), - removed - ) - } - ); - setPlugins(pluginsCache[pluginsString]); + .then((newPlugins) => { + Object.keys(newPlugins).forEach(pluginName => { + pluginsCache[pluginName] = true; + }); + storedPlugins = { + ...storedPlugins, + ...newPlugins + }; + setPlugins(storedPlugins); setPending(false); }) .catch(() => { @@ -142,7 +127,7 @@ function useLazyPlugins({ setPending(false); }); } else { - setPlugins(pluginsCache[pluginsString]); + setPlugins(storedPlugins); } }, [ pluginsString ]); diff --git a/geonode_mapstore_client/client/js/routes/Viewer.jsx b/geonode_mapstore_client/client/js/routes/Viewer.jsx index 1589061e2b..b7e615835a 100644 --- a/geonode_mapstore_client/client/js/routes/Viewer.jsx +++ b/geonode_mapstore_client/client/js/routes/Viewer.jsx @@ -6,12 +6,12 @@ * LICENSE file in the root directory of this source tree. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; import url from 'url'; import isArray from 'lodash/isArray'; -import { createSelector } from 'reselect'; import BorderLayout from '@mapstore/framework/components/layout/BorderLayout'; import { getMonitoredState } from '@mapstore/framework/utils/PluginsUtils'; import { getConfigProp } from '@mapstore/framework/utils/ConfigUtils'; @@ -20,17 +20,24 @@ import useLazyPlugins from '@js/hooks/useLazyPlugins'; import { requestResourceConfig, requestNewResourceConfig } from '@js/actions/gnresource'; import MetaTags from '@js/components/MetaTags'; import MainErrorView from '@js/components/MainErrorView'; +import { createShallowSelector } from '@mapstore/framework/utils/ReselectUtils'; const urlQuery = url.parse(window.location.href, true).query; -const ConnectedPluginsContainer = connect((state) => ({ - mode: urlQuery.mode || (urlQuery.mobile || state.browser && state.browser.mobile ? 'mobile' : 'desktop'), - monitoredState: getMonitoredState(state, getConfigProp('monitorState')), - pluginsState: { - ...state.controls - } -}))(PluginsContainer); +const ConnectedPluginsContainer = connect( + createShallowSelector( + state => urlQuery.mode || (urlQuery.mobile || state.browser && state.browser.mobile ? 'mobile' : 'desktop'), + state => getMonitoredState(state, getConfigProp('monitorState')), + state => state?.controls, + (mode, monitoredState, controls) => ({ + mode, + monitoredState, + pluginsState: controls + }) + ) +)(PluginsContainer); +const DEFAULT_PLUGINS_CONFIG = []; function ViewerRoute({ name, pluginsConfig: propPluginsConfig, @@ -51,15 +58,15 @@ function ViewerRoute({ const { pk } = match.params || {}; const pluginsConfig = isArray(propPluginsConfig) ? propPluginsConfig - : propPluginsConfig && propPluginsConfig[name] || []; + : propPluginsConfig && propPluginsConfig[name] || DEFAULT_PLUGINS_CONFIG; - const [loading, setLoading] = useState(true); - const { plugins: loadedPlugins } = useLazyPlugins({ + + const { plugins: loadedPlugins, pending } = useLazyPlugins({ pluginsEntries: lazyPlugins, pluginsConfig }); useEffect(() => { - if (!loading && pk !== undefined) { + if (!pending && pk !== undefined) { if (pk === 'new') { onCreate(resourceType); } else { @@ -68,30 +75,30 @@ function ViewerRoute({ }); } } - }, [loading, pk]); + }, [pending, pk]); + const parsedPlugins = useMemo(() => ({ ...loadedPlugins, ...plugins }), [loadedPlugins]); const Loader = loaderComponent; return ( <> - {resource && } - setLoading(false)} - /> - {( loading || loadingConfig ) && Loader && } + />} + {( loadingConfig || pending ) && Loader && } {configError && } ); diff --git a/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json b/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json index fd17203691..04181ae7be 100644 --- a/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json +++ b/geonode_mapstore_client/client/static/mapstore/configs/localConfig.json @@ -85,6 +85,10 @@ { "name": "isNewResource", "path": "gnresource.isNew" + }, + { + "name": "printEnabled", + "path": "print.capabilities" } ], "projectionDefs": [], @@ -455,7 +459,9 @@ "name": "Map", "cfg": { "shouldLoadFont": false, - "tools": [], + "tools": [ + "popup" + ], "mapOptions": { "openlayers": { "attribution": { @@ -472,11 +478,7 @@ { "name": "Identify", "cfg": { - "showFullscreen": false, - "dock": true, - "position": "right", - "size": 0.4, - "fluid": true, + "showInMapPopup": true, "viewerOptions": { "container": "{context.ReactSwipe}" } @@ -1060,17 +1062,94 @@ "cfg": { "disablePluginIf": "{!state('selectedLayerPermissions').includes('download_resourcebase')}", "formats": [ - {"name": "application/json", "label": "GeoJSON", "type": "vector", "validServices": ["wps"]}, - {"name": "application/arcgrid", "label": "ArcGrid", "type": "raster", "validServices": ["wps"]}, - {"name": "image/tiff", "label": "TIFF", "type": "raster", "validServices": ["wps"]}, - {"name": "image/png", "label": "PNG", "type": "raster", "validServices": ["wps"]}, - {"name": "image/jpeg", "label": "JPEG", "type": "raster", "validServices": ["wps"]}, - {"name": "application/wfs-collection-1.0", "label": "wfs-collection-1.0", "type": "vector", "validServices": ["wps"]}, - {"name": "application/wfs-collection-1.1", "label": "wfs-collection-1.1", "type": "vector", "validServices": ["wps"]}, - {"name": "application/zip", "label": "Shapefile", "type": "vector", "validServices": ["wps"]}, - {"name": "text/csv", "label": "CSV", "type": "vector", "validServices": ["wps"]}, - {"name": "application/geopackage+sqlite3", "label": "GeoPackage", "type": "vector", "validServices": ["wps"]}, - {"name": "application/geopackage+sqlite3", "label": "GeoPackage", "type": "raster", "validServices": ["wps"]} + { + "name": "application/json", + "label": "GeoJSON", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "application/arcgrid", + "label": "ArcGrid", + "type": "raster", + "validServices": [ + "wps" + ] + }, + { + "name": "image/tiff", + "label": "TIFF", + "type": "raster", + "validServices": [ + "wps" + ] + }, + { + "name": "image/png", + "label": "PNG", + "type": "raster", + "validServices": [ + "wps" + ] + }, + { + "name": "image/jpeg", + "label": "JPEG", + "type": "raster", + "validServices": [ + "wps" + ] + }, + { + "name": "application/wfs-collection-1.0", + "label": "wfs-collection-1.0", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "application/wfs-collection-1.1", + "label": "wfs-collection-1.1", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "application/zip", + "label": "Shapefile", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "text/csv", + "label": "CSV", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "application/geopackage+sqlite3", + "label": "GeoPackage", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "application/geopackage+sqlite3", + "label": "GeoPackage", + "type": "raster", + "validServices": [ + "wps" + ] + } ] } }, @@ -1188,6 +1267,10 @@ }, { "name": "FeatureEditor", + "cfg": { + "hideCloseButton": true, + "hideLayerTitle": true + }, "override": { "ViewerLayout": { "priority": 1 @@ -1245,6 +1328,54 @@ } } }, + { + "name": "BackgroundSelector", + "override": { + "ViewerLayout": { + "priority": 1 + } + } + }, + { + "name": "Toolbar", + "id": "NavigationBar", + "cfg": { + "id": "navigationBar", + "layout": "horizontal" + }, + "override": { + "ViewerLayout": { + "priority": 1 + } + } + }, + { + "name": "MapLoading", + "override": { + "Toolbar": { + "alwaysVisible": true + } + } + }, + { + "name": "ZoomIn", + "override": { + "Toolbar": { + "alwaysVisible": true + } + } + }, + { + "name": "ZoomOut", + "override": { + "Toolbar": { + "alwaysVisible": true + } + } + }, + { + "name": "ScaleBox" + }, { "name": "MapFooter", "override": { @@ -1343,7 +1474,7 @@ { "name": "Identify", "cfg": { - "showInMapPopup":true, + "showInMapPopup": true, "viewerOptions": { "container": "{context.ReactSwipe}" } @@ -1506,7 +1637,6 @@ "name": "FullScreen" } ] - } }, { @@ -1676,17 +1806,94 @@ "cfg": { "disablePluginIf": "{!state('selectedLayerPermissions').includes('download_resourcebase')}", "formats": [ - {"name": "application/json", "label": "GeoJSON", "type": "vector", "validServices": ["wps"]}, - {"name": "application/arcgrid", "label": "ArcGrid", "type": "raster", "validServices": ["wps"]}, - {"name": "image/tiff", "label": "TIFF", "type": "raster", "validServices": ["wps"]}, - {"name": "image/png", "label": "PNG", "type": "raster", "validServices": ["wps"]}, - {"name": "image/jpeg", "label": "JPEG", "type": "raster", "validServices": ["wps"]}, - {"name": "application/wfs-collection-1.0", "label": "wfs-collection-1.0", "type": "vector", "validServices": ["wps"]}, - {"name": "application/wfs-collection-1.1", "label": "wfs-collection-1.1", "type": "vector", "validServices": ["wps"]}, - {"name": "application/zip", "label": "Shapefile", "type": "vector", "validServices": ["wps"]}, - {"name": "text/csv", "label": "CSV", "type": "vector", "validServices": ["wps"]}, - {"name": "application/geopackage+sqlite3", "label": "GeoPackage", "type": "vector", "validServices": ["wps"]}, - {"name": "application/geopackage+sqlite3", "label": "GeoPackage", "type": "raster", "validServices": ["wps"]} + { + "name": "application/json", + "label": "GeoJSON", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "application/arcgrid", + "label": "ArcGrid", + "type": "raster", + "validServices": [ + "wps" + ] + }, + { + "name": "image/tiff", + "label": "TIFF", + "type": "raster", + "validServices": [ + "wps" + ] + }, + { + "name": "image/png", + "label": "PNG", + "type": "raster", + "validServices": [ + "wps" + ] + }, + { + "name": "image/jpeg", + "label": "JPEG", + "type": "raster", + "validServices": [ + "wps" + ] + }, + { + "name": "application/wfs-collection-1.0", + "label": "wfs-collection-1.0", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "application/wfs-collection-1.1", + "label": "wfs-collection-1.1", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "application/zip", + "label": "Shapefile", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "text/csv", + "label": "CSV", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "application/geopackage+sqlite3", + "label": "GeoPackage", + "type": "vector", + "validServices": [ + "wps" + ] + }, + { + "name": "application/geopackage+sqlite3", + "label": "GeoPackage", + "type": "raster", + "validServices": [ + "wps" + ] + } ] } }, @@ -1898,7 +2105,10 @@ "name": "ViewerLayout", "cfg": { "header": { - "order": [ "ActionNavbar", "GeoStoryNavigation" ] + "order": [ + "ActionNavbar", + "GeoStoryNavigation" + ] } } }, @@ -2318,4 +2528,4 @@ } ] } -} +} \ No newline at end of file diff --git a/geonode_mapstore_client/client/themes/geonode/less/_base.less b/geonode_mapstore_client/client/themes/geonode/less/_base.less index 1572812d4a..539a8bc664 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/_base.less +++ b/geonode_mapstore_client/client/themes/geonode/less/_base.less @@ -137,6 +137,7 @@ } } +.page-dataset-viewer, .page-map-viewer { .gn-viewer-layout-body { // needed to compute the position of ma toolbar and background selector