From 4f57b0488225491586a16314269df67dd830a34b Mon Sep 17 00:00:00 2001 From: allyoucanmap Date: Thu, 1 Aug 2024 18:24:02 +0200 Subject: [PATCH 1/2] Review homepage catalog --- .../js/components/FiltersMenu/FiltersMenu.jsx | 37 +-- .../client/js/plugins/ResourcesGrid.jsx | 260 +++++++++--------- .../client/js/utils/AppRoutesUtils.js | 4 - .../js/utils/__tests__/AppRoutesUtils-test.js | 4 - .../client/themes/geonode/less/_base.less | 3 - .../themes/geonode/less/_card-grid.less | 2 +- .../themes/geonode/less/_home-container.less | 1 + .../client/themes/geonode/less/_menu.less | 3 + .../themes/geonode/less/_resources-grid.less | 5 + .../snippets/brand_navbar.html | 23 ++ geonode_mapstore_client/templates/index.html | 11 +- 11 files changed, 180 insertions(+), 173 deletions(-) diff --git a/geonode_mapstore_client/client/js/components/FiltersMenu/FiltersMenu.jsx b/geonode_mapstore_client/client/js/components/FiltersMenu/FiltersMenu.jsx index 459988e72b..9d86c4544a 100644 --- a/geonode_mapstore_client/client/js/components/FiltersMenu/FiltersMenu.jsx +++ b/geonode_mapstore_client/client/js/components/FiltersMenu/FiltersMenu.jsx @@ -32,7 +32,8 @@ const FiltersMenu = forwardRef(({ loading, hideCardLayoutButton, cardLayoutStyle, - setCardLayoutStyle + setCardLayoutStyle, + disableFilters }, ref) => { const { isMobile } = getConfigProp('geoNodeSettings'); @@ -50,22 +51,24 @@ const FiltersMenu = forwardRef(({
- {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..911fb7b755 100644 --- a/geonode_mapstore_client/client/js/plugins/ResourcesGrid.jsx +++ b/geonode_mapstore_client/client/js/plugins/ResourcesGrid.jsx @@ -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,24 +527,21 @@ 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 = [], onGetFacets, facets, filters, - setFilters, - ...props + setFilters }, context) { const [_cardLayoutStyleState, setCardLayoutStyle] = useLocalStorage('layoutCardsStyle', defaultCardLayoutStyle); @@ -539,41 +596,11 @@ function ResourcesGrid({ 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 +608,7 @@ function ResourcesGrid({ onSearch({ ...omit(query, ['page']), ...newParams - }, getUpdatedPathName(pathname)); + }, pathname ?? location.pathname); } function handleClear() { @@ -604,13 +631,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 +653,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 @@ -885,7 +874,6 @@ const ResourcesGridPlugin = connect( { onSearch: searchResources, onInit: setSearchConfig, - onReplaceLocation: replace, onGetFacets: getFacetItems, setFilters: setFiltersAction } 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..7086939dc1 100644 --- a/geonode_mapstore_client/templates/index.html +++ b/geonode_mapstore_client/templates/index.html @@ -58,26 +58,21 @@ - + {% block ms_scripts %} {% endblock %}