- {totalFilters > 0 ?
}
- >
- {isMobile ?
:
}
- :
}
- {' '}
+ {!disableFilters && <>
+ {totalFilters > 0 ?
}
+ >
+ {isMobile ?
:
}
+ :
}
+ {' '}
+ >}
{loading ?
:
}
diff --git a/geonode_mapstore_client/client/js/plugins/ResourcesGrid.jsx b/geonode_mapstore_client/client/js/plugins/ResourcesGrid.jsx
index fd6a96a9fe..9abfbb7db5 100644
--- a/geonode_mapstore_client/client/js/plugins/ResourcesGrid.jsx
+++ b/geonode_mapstore_client/client/js/plugins/ResourcesGrid.jsx
@@ -30,8 +30,8 @@ import {
import { withResizeDetector } from 'react-resize-detector';
import { userSelector } from '@mapstore/framework/selectors/security';
import ConnectedCardGrid from '@js/plugins/resourcesgrid/ConnectedCardGrid';
-import { getTotalResources, getFacetsItems } from '@js/selectors/search';
-import { searchResources, setSearchConfig, getFacetItems, setFilters as setFiltersAction } from '@js/actions/gnsearch';
+import { getTotalResources, getFacetsItems, getShowFilterForm } from '@js/selectors/search';
+import { searchResources, setSearchConfig, getFacetItems, setFilters as setFiltersAction, showFilterForm as showFilterFormAction } from '@js/actions/gnsearch';
import gnsearch from '@js/reducers/gnsearch';
import gnresource from '@js/reducers/gnresource';
@@ -43,12 +43,11 @@ import resourceServiceEpics from '@js/epics/resourceservice';
import favoriteEpics from '@js/epics/favorite';
import DetailsPanel from '@js/components/DetailsPanel';
import { processingDownload } from '@js/selectors/resourceservice';
-import { resourceHasPermission, getCataloguePath } from '@js/utils/ResourceUtils';
+import { resourceHasPermission } from '@js/utils/ResourceUtils';
import {downloadResource, setFavoriteResource} from '@js/actions/gnresource';
import FiltersForm from '@js/components/FiltersForm';
import usePluginItems from '@js/hooks/usePluginItems';
import { ProcessTypes } from '@js/utils/ResourceServiceUtils';
-import { replace } from 'connected-react-router';
import FaIcon from '@js/components/FaIcon';
import Button from '@js/components/Button';
import useLocalStorage from '@js/hooks/useLocalStorage';
@@ -117,40 +116,102 @@ function PaginationCustom({
);
}
-const removeMenuHighlight = () => {
- // Remove previous higlighted menu
- const menuHiglighted = document.querySelector('#gn-topbar .highlight-menu');
- menuHiglighted?.classList.remove('highlight-menu');
-};
const getCatalogPage = (pathname) => {
- const {params: {page} = {}} = matchPath(pathname, { path: "/:page", exact: true }) ?? {};
+ const { params: {page} = {} } = matchPath(pathname, { path: "/:page", exact: true }) ?? {};
return page;
};
const withPageConfig = (Component) => {
return (props) => {
- useEffect(() => {
- // highlight topbar menu item based on catalog page
- const page = getCatalogPage(props.location.pathname);
-
- if (page) {
- removeMenuHighlight();
-
- const topbarMenu = document.querySelector(`#gn-topbar #${page}`);
- topbarMenu?.classList.add('highlight-menu');
- } else {
- removeMenuHighlight();
- }
- }, [props.location.pathname]);
-
const mergePropsWithPageConfigs = () => {
const pageName = getCatalogPage(props.location.pathname, props);
return {...props, ...props?.[`${pageName}Page`]};
};
-
return
;
};
};
+const useResourceGridLayout = ({
+ headerNodeSelector,
+ navbarNodeSelector,
+ footerNodeSelector,
+ containerSelector,
+ showFilterForm,
+ showDetail,
+ width,
+ height,
+ panel
+}) => {
+ const [stickyTop, setStickyTop] = useState(0);
+ const [stickyBottom, setStickyBottom] = useState(0);
+ useEffect(() => {
+ if (!panel) {
+ const header = headerNodeSelector ? document.querySelector(headerNodeSelector) : null;
+ const navbar = navbarNodeSelector ? document.querySelector(navbarNodeSelector) : null;
+ const footer = footerNodeSelector ? document.querySelector(footerNodeSelector) : null;
+ const { height: headerHeight = 0 } = header?.getBoundingClientRect() || {};
+ const { height: navbarHeight = 0 } = navbar?.getBoundingClientRect() || {};
+ const { height: footerHeight = 0 } = footer?.getBoundingClientRect() || {};
+ setStickyTop(headerHeight + navbarHeight);
+ setStickyBottom(footerHeight);
+ }
+ }, [width, height, panel]);
+ const detailNode = useRef();
+ const filterFormNode = useRef();
+ const { width: filterFormNodeWidth = 0 } = filterFormNode?.current?.getBoundingClientRect() || {};
+ const { width: detailNodeWidth = 0 } = detailNode?.current?.getBoundingClientRect() || {};
+ const filterFormWidth = showFilterForm ? filterFormNodeWidth : 0;
+ const detailWidth = showDetail ? detailNodeWidth : 0;
+ const panelsWidth = filterFormWidth + detailWidth;
+ const container = containerSelector ? document.querySelector(containerSelector) : null;
+ const { height: containerHeight } = container?.getBoundingClientRect() || {};
+ useEffect(() => {
+ if (container && !panel) {
+ container.style.width = `calc(100% - ${panelsWidth}px)`;
+ container.style.marginLeft = `${filterFormWidth}px`;
+ }
+ }, [container, panelsWidth, filterFormWidth, panel]);
+ const filterMenuNode = useRef();
+ const footerPaginationNode = useRef();
+ const [top, setTop] = useState();
+ const [bottom, setBottom] = useState();
+ const fullPageScroll = !container && !panel;
+ useEffect(() => {
+ let onScroll;
+ // reset top and bottom
+ setTop();
+ setBottom();
+ if (fullPageScroll) {
+ onScroll = () => {
+ const { top: filterMenuTop = 0 } = filterMenuNode?.current?.getBoundingClientRect() || {};
+ const footerPaginationRect = footerPaginationNode?.current?.getBoundingClientRect() || {};
+ setTop(filterMenuTop);
+ setBottom(window.innerHeight - footerPaginationRect.y - footerPaginationRect.height);
+ };
+ onScroll();
+ document.addEventListener('scroll', onScroll);
+ }
+ return () => {
+ if (onScroll) {
+ document.removeEventListener('scroll', onScroll);
+ }
+ };
+ }, [width, height, fullPageScroll]);
+ return {
+ container,
+ detailNode,
+ filterFormNode,
+ filterMenuNode,
+ footerPaginationNode,
+ stickyTop,
+ stickyBottom,
+ containerHeight,
+ panelsWidth,
+ filterFormWidth,
+ top: top === undefined ? stickyTop : top,
+ bottom: bottom === undefined ? stickyBottom : bottom
+ };
+};
+
/**
* @module ResourcesGrid
*/
@@ -174,7 +235,6 @@ const withPageConfig = (Component) => {
* @prop {boolean} pagination Provides a config to allow for pagination
* @prop {boolean} disableDetailPanel Provides a config to allow resource details to be viewed when selected.
* @prop {boolean} disableFilters Provides a config to enable/disable filtering of resources
- * @prop {string} filterPagePath sets path for filters page when filter button is clicked
* @prop {array} resourceCardActionsOrder order in which `cfg.items` will be rendered
* @prop {boolean} enableGeoNodeCardsMenuItems Provides a config to allow for card menu items to be enabled/disabled.
* @prop {boolean} panel when enabled, the component render the list of resources, filters and details preview inside a panel
@@ -467,16 +527,14 @@ function ResourcesGrid({
footerNodeSelector = '.gn-footer',
containerSelector = '',
scrollContainerSelector = '',
- pagination,
+ pagination = true,
disableDetailPanel,
disableFilters,
- filterPagePath = '/catalogue/#/search/filter',
resourceCardActionsOrder = [
ProcessTypes.DELETE_RESOURCE,
ProcessTypes.COPY_RESOURCE,
'downloadResource'
],
- onReplaceLocation,
error,
enableGeoNodeCardsMenuItems,
detailsTabs = [],
@@ -484,9 +542,13 @@ function ResourcesGrid({
facets,
filters,
setFilters,
- ...props
+ showFilterForm: showFilterFormProp,
+ setShowFilterForm
}, context) {
+ const showDetail = !isEmpty(resource);
+ const showFilterForm = showFilterFormProp && !showDetail;
+
const [_cardLayoutStyleState, setCardLayoutStyle] = useLocalStorage('layoutCardsStyle', defaultCardLayoutStyle);
const cardLayoutStyleState = cardLayoutStyle || _cardLayoutStyleState; // Force style when `cardLayoutStyle` is configured
@@ -534,46 +596,12 @@ function ResourcesGrid({
excludeQueryKeys: []
});
- const [_showFilterForm, setShowFilterForm] = useState(false);
- const showDetail = !isEmpty(resource);
- const showFilterForm = _showFilterForm && !showDetail;
-
const handleShowFilterForm = (show) => {
- if (show && disableFilters) {
- simulateAClick(getCataloguePath(filterPagePath));
- } else {
- if (!isEmpty(resource)) {
- const href = closeDetailPanelHref();
- simulateAClick(href);
- }
- setShowFilterForm(show);
+ if (!isEmpty(resource)) {
+ const href = closeDetailPanelHref();
+ simulateAClick(href);
}
- };
-
- const isCatalogPage = (pathname) => {
- const isConfigPresent = !!props?.[`${pathname.replace('/', '')}Page`];
-
- // to be a catalog page it should have configuration
- return getCatalogPage(pathname) && isConfigPresent;
- };
-
- const getMatchPath = () => {
- const pathname = location.pathname;
- const matchedPath = [
- '/search',
- '/search/filter',
- '/detail/:pk',
- '/detail/:resourceType/:pk',
- '/:page'
- ].find((path) => matchPath(pathname, { path, exact: true }));
- return matchedPath;
- };
-
- const getUpdatedPathName = (pathname) => {
- if (isEmpty(pathname)) {
- return isCatalogPage(location.pathname) ? location.pathname : '/';
- }
- return pathname;
+ setShowFilterForm(show);
};
function handleUpdate(newParams, pathname) {
@@ -581,7 +609,7 @@ function ResourcesGrid({
onSearch({
...omit(query, ['page']),
...newParams
- }, getUpdatedPathName(pathname));
+ }, pathname ?? location.pathname);
}
function handleClear() {
@@ -604,13 +632,8 @@ function ResourcesGrid({
}, [cardLayoutStyle]);
useEffect(() => {
- let pathname = location.pathname;
- const initialize = (pathname === '/'
- || !isEmpty(getMatchPath())
- || isCatalogPage(pathname)) && init;
-
- if (initialize) {
- pathname = getUpdatedPathName();
+ if (init) {
+ const pathname = location.pathname;
onInit({
defaultQuery,
pageSize,
@@ -631,70 +654,33 @@ function ResourcesGrid({
}
}, [init, isPaginated, location.pathname]);
- const [top, setTop] = useState(0);
- const [bottom, setBottom] = useState(0);
- useEffect(() => {
- if (!panel) {
- const header = headerNodeSelector ? document.querySelector(headerNodeSelector) : null;
- const navbar = navbarNodeSelector ? document.querySelector(navbarNodeSelector) : null;
- const footer = footerNodeSelector ? document.querySelector(footerNodeSelector) : null;
- const { height: headerHeight = 0 } = header?.getBoundingClientRect() || {};
- const { height: navbarHeight = 0 } = navbar?.getBoundingClientRect() || {};
- const { height: footerHeight = 0 } = footer?.getBoundingClientRect() || {};
- setTop(headerHeight + navbarHeight);
- setBottom(footerHeight);
- }
- }, [width, height, panel]);
-
const { query } = url.parse(location.search, true);
const queryFilters = getQueryFilters(query);
- const detailNode = useRef();
- const filterFormNode = useRef();
- const { width: filterFormNodeWidth = 0 } = filterFormNode?.current?.getBoundingClientRect() || {};
- const { width: detailNodeWidth = 0 } = detailNode?.current?.getBoundingClientRect() || {};
- const filterFormWidth = showFilterForm ? filterFormNodeWidth : 0;
- const detailWidth = showDetail ? detailNodeWidth : 0;
- const panelsWidth = filterFormWidth + detailWidth;
- const container = containerSelector ? document.querySelector(containerSelector) : null;
- const { height: containerHeight } = container?.getBoundingClientRect() || {};
- useEffect(() => {
- if (container && !panel) {
- container.style.width = `calc(100% - ${panelsWidth}px)`;
- container.style.marginLeft = `${filterFormWidth}px`;
- }
- }, [container, panelsWidth, filterFormWidth, panel]);
- useEffect(() => {
- if (!panel) {
- const pathname = location.pathname;
- const matchedPath = getMatchPath();
- if (matchedPath) {
- const options = matchPath(pathname, { path: matchedPath, exact: true });
- !isCatalogPage(location.pathname) && onReplaceLocation('' + (location.search || ''));
- switch (options.path) {
- case '/search':
- case '/detail/:pk': {
- break;
- }
- case '/search/filter': {
- handleShowFilterForm(true);
- break;
- }
- case '/detail/:resourceType/:pk': {
- const { query: locationQuery } = url.parse(location.search, true);
- const search = url.format({ query: {
- ...locationQuery,
- d: `${options?.params?.pk};${options?.params?.resourceType}`
- }});
- simulateAClick('#' + (search || ''));
- break;
- }
- default:
- break;
- }
- }
- }
- }, [location.pathname, panel]);
+ const {
+ container,
+ detailNode,
+ filterFormNode,
+ filterMenuNode,
+ footerPaginationNode,
+ stickyTop,
+ stickyBottom,
+ containerHeight,
+ panelsWidth,
+ filterFormWidth,
+ top,
+ bottom
+ } = useResourceGridLayout({
+ headerNodeSelector,
+ navbarNodeSelector,
+ footerNodeSelector,
+ containerSelector,
+ showFilterForm,
+ showDetail,
+ width,
+ height,
+ panel
+ });
const filterForm = !disableFilters && (
}
footer={
{error
@@ -869,8 +859,9 @@ const ResourcesGridPlugin = connect(
state => getMonitoredState(state, getConfigProp('monitorState')),
state => state?.gnsearch?.error,
getFacetsItems,
- state => state?.gnsearch?.filters
- ], (params, user, totalResources, loading, location, resource, monitoredState, error, facets, filters) => ({
+ state => state?.gnsearch?.filters,
+ getShowFilterForm
+ ], (params, user, totalResources, loading, location, resource, monitoredState, error, facets, filters, showFilterForm) => ({
params,
user,
totalResources,
@@ -880,14 +871,15 @@ const ResourcesGridPlugin = connect(
monitoredState,
error,
facets,
- filters
+ filters,
+ showFilterForm
})),
{
onSearch: searchResources,
onInit: setSearchConfig,
- onReplaceLocation: replace,
onGetFacets: getFacetItems,
- setFilters: setFiltersAction
+ setFilters: setFiltersAction,
+ setShowFilterForm: showFilterFormAction
}
)(withResizeDetector(withPageConfig(ResourcesGrid)));
diff --git a/geonode_mapstore_client/client/js/reducers/gnsearch.js b/geonode_mapstore_client/client/js/reducers/gnsearch.js
index 112fbf968a..db818c4196 100644
--- a/geonode_mapstore_client/client/js/reducers/gnsearch.js
+++ b/geonode_mapstore_client/client/js/reducers/gnsearch.js
@@ -18,7 +18,8 @@ import {
INCREASE_TOTAL_COUNT,
SET_SEARCH_CONFIG,
SET_FACET_ITEMS,
- SET_FILTERS
+ SET_FILTERS,
+ SHOW_FILTER_FORM
} from '@js/actions/gnsearch';
import { UPDATE_SINGLE_RESOURCE } from '@js/actions/gnresource';
@@ -132,6 +133,11 @@ function gnsearch(state = defaultState, action) {
...state,
filters: {...state.filters, ...action.filters}
};
+ case SHOW_FILTER_FORM:
+ return {
+ ...state,
+ showFilterForm: !!action.show
+ };
default:
return state;
}
diff --git a/geonode_mapstore_client/client/js/selectors/search.js b/geonode_mapstore_client/client/js/selectors/search.js
index db3aab14fe..b35428be9c 100644
--- a/geonode_mapstore_client/client/js/selectors/search.js
+++ b/geonode_mapstore_client/client/js/selectors/search.js
@@ -42,3 +42,4 @@ export const getTotalResources = (state) => {
};
export const getFacetsItems = state => state?.gnsearch?.facetItems;
+export const getShowFilterForm = state => state?.gnsearch?.showFilterForm;
diff --git a/geonode_mapstore_client/client/js/utils/AppRoutesUtils.js b/geonode_mapstore_client/client/js/utils/AppRoutesUtils.js
index 5a0c47caa4..5d9d8b6dd2 100644
--- a/geonode_mapstore_client/client/js/utils/AppRoutesUtils.js
+++ b/geonode_mapstore_client/client/js/utils/AppRoutesUtils.js
@@ -186,10 +186,6 @@ export const CATALOGUE_ROUTES = [
name: 'catalogue',
path: [
'/',
- '/search/',
- '/search/filter',
- '/detail/:pk',
- '/detail/:ctype/:pk',
'/:page'
],
component: appRouteComponentTypes.CATALOGUE
diff --git a/geonode_mapstore_client/client/js/utils/__tests__/AppRoutesUtils-test.js b/geonode_mapstore_client/client/js/utils/__tests__/AppRoutesUtils-test.js
index c1359ca585..50e71ea165 100644
--- a/geonode_mapstore_client/client/js/utils/__tests__/AppRoutesUtils-test.js
+++ b/geonode_mapstore_client/client/js/utils/__tests__/AppRoutesUtils-test.js
@@ -103,10 +103,6 @@ describe('Test App Routes Utils', () => {
expect(mapViewerRoute.shouldNotRequestResources).toEqual(true);
expect(catalogueRoute.path).toEqual([
'/',
- '/search/',
- '/search/filter',
- '/detail/:pk',
- '/detail/:ctype/:pk',
'/:page'
]);
expect(catalogueRoute.name).toEqual('catalogue');
diff --git a/geonode_mapstore_client/client/themes/geonode/less/_base.less b/geonode_mapstore_client/client/themes/geonode/less/_base.less
index ded5f3dfaa..3251adb128 100644
--- a/geonode_mapstore_client/client/themes/geonode/less/_base.less
+++ b/geonode_mapstore_client/client/themes/geonode/less/_base.less
@@ -167,9 +167,6 @@ body {
z-index: 0;
overflow: hidden;
}
- .gn-card-grid {
- padding-top: 0;
- }
.gn-filters-menu {
top: 0 !important;
margin-bottom: 0;
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 36eec773f8..8e0cba91bb 100644
--- a/geonode_mapstore_client/client/themes/geonode/less/_card-grid.less
+++ b/geonode_mapstore_client/client/themes/geonode/less/_card-grid.less
@@ -50,7 +50,6 @@
// **************
.gn-card-grid {
- padding-top: 20px;
.gn-card-grid-container {
position: relative;
max-width: @gn-page-max-width;
@@ -74,6 +73,7 @@
max-width: @gn-page-max-width;
margin: auto;
width: 100%;
+ align-content: flex-start;
li {
padding: 0;
margin: 0;
diff --git a/geonode_mapstore_client/client/themes/geonode/less/_home-container.less b/geonode_mapstore_client/client/themes/geonode/less/_home-container.less
index 6a5560b75f..7663c6e196 100644
--- a/geonode_mapstore_client/client/themes/geonode/less/_home-container.less
+++ b/geonode_mapstore_client/client/themes/geonode/less/_home-container.less
@@ -77,4 +77,5 @@
#gn-home-resources-grid {
position: relative;
+ padding-bottom: 2rem;
}
diff --git a/geonode_mapstore_client/client/themes/geonode/less/_menu.less b/geonode_mapstore_client/client/themes/geonode/less/_menu.less
index 1168b9b26c..9778fb3855 100644
--- a/geonode_mapstore_client/client/themes/geonode/less/_menu.less
+++ b/geonode_mapstore_client/client/themes/geonode/less/_menu.less
@@ -259,6 +259,9 @@ nav.hide-navigation#gn-topbar {
position: sticky;
top: 0px;
z-index: 10;
+ .gn-menu-container {
+ padding-top: 0.35rem;
+ }
}
.gn-action-navbar-title {
diff --git a/geonode_mapstore_client/client/themes/geonode/less/_resources-grid.less b/geonode_mapstore_client/client/themes/geonode/less/_resources-grid.less
index 1bdad57f97..8a7da31334 100644
--- a/geonode_mapstore_client/client/themes/geonode/less/_resources-grid.less
+++ b/geonode_mapstore_client/client/themes/geonode/less/_resources-grid.less
@@ -131,6 +131,11 @@
max-width: 100%;
}
}
+ &:not(.gn-panel) {
+ .gn-card-list {
+ min-height: 100vh;
+ }
+ }
.gn-card-grid-container {
.gn-menu {
.gn-menu-container {
diff --git a/geonode_mapstore_client/templates/geonode-mapstore-client/snippets/brand_navbar.html b/geonode_mapstore_client/templates/geonode-mapstore-client/snippets/brand_navbar.html
index 34e2cfb13b..dabfd90e47 100644
--- a/geonode_mapstore_client/templates/geonode-mapstore-client/snippets/brand_navbar.html
+++ b/geonode_mapstore_client/templates/geonode-mapstore-client/snippets/brand_navbar.html
@@ -81,6 +81,29 @@
}
window.addEventListener('DOMContentLoaded', manageUrlChange);
window.addEventListener('hashchange', manageUrlChange, false);
+
+ window.addEventListener('mapstore:ready', function(event) {
+ const msAPI = event.detail;
+ msAPI.onAction('@@router/LOCATION_CHANGE', (action) => {
+ const hashPathname = action?.payload?.location?.pathname || '';
+ const parts = hashPathname.split('/');
+ const page = parts[parts.length - 1];
+ // Remove previous highlighted menu
+ const menuHighlighted = document.querySelector('#gn-topbar .highlight-menu');
+ if (menuHighlighted) {
+ menuHighlighted.classList.remove('highlight-menu');
+ }
+ // add new highlight
+ // exclude number
+ if (page && isNaN(parseFloat(page))) {
+ const topBarMenu = document.querySelector(`#gn-topbar #${page}`);
+ if (topBarMenu) {
+ topBarMenu.classList.add('highlight-menu');
+ }
+ }
+ });
+ });
+
})();
{% endblock extra_script %}
\ No newline at end of file
diff --git a/geonode_mapstore_client/templates/index.html b/geonode_mapstore_client/templates/index.html
index 18ba072ca0..6542675f41 100644
--- a/geonode_mapstore_client/templates/index.html
+++ b/geonode_mapstore_client/templates/index.html
@@ -57,7 +57,7 @@
{% endblock %}
-
+
{% block ms_scripts %}
{% endblock %}