diff --git a/projects/mercury/src/constants.js b/projects/mercury/src/constants.js index b0cbada34..4d2b9e43e 100644 --- a/projects/mercury/src/constants.js +++ b/projects/mercury/src/constants.js @@ -22,6 +22,8 @@ export const DATE_FORMAT = 'dd-MM-yyyy'; // The maximum number of items in a list in the right panel, for performance reasons. // If you change this, also change it in 'MetadataService.java' export const MAX_LIST_LENGTH = 100; +// Max length of URL in the browser +export const MAX_URL_LENGTH = 2000; // Metadata schemas export const SHACL_NS = 'http://www.w3.org/ns/shacl#'; diff --git a/projects/mercury/src/metadata/views/MetadataView.js b/projects/mercury/src/metadata/views/MetadataView.js index 5bf908525..da519ad0e 100644 --- a/projects/mercury/src/metadata/views/MetadataView.js +++ b/projects/mercury/src/metadata/views/MetadataView.js @@ -3,9 +3,11 @@ import _ from 'lodash'; import {useHistory} from 'react-router-dom'; import {Button, Grid, Typography} from '@mui/material'; import withStyles from '@mui/styles/withStyles'; -import {Assignment, Close} from '@mui/icons-material'; +import {Assignment, Close, ContentCopy} from '@mui/icons-material'; import queryString from 'query-string'; +import qs from 'qs'; +import {SnackbarProvider, useSnackbar} from 'notistack'; import styles from './MetadataView.styles'; import type {MetadataViewFacet, MetadataViewFilter, MetadataViewOptions, ValueType} from './MetadataViewAPI'; import BreadCrumbs from '../../common/components/BreadCrumbs'; @@ -34,6 +36,7 @@ import usePageTitleUpdater from '../../common/hooks/UsePageTitleUpdater'; import MetadataViewFacetsContext from './MetadataViewFacetsContext'; import {accessLevelForCollection} from '../../collections/collectionUtils'; import InternalMetadataSourceContext from '../metadata-sources/InternalMetadataSourceContext'; +import {MAX_URL_LENGTH} from '../../constants'; type ContextualMetadataViewProperties = { classes: any @@ -70,6 +73,7 @@ export const MetadataView = (props: MetadataViewProperties) => { const {collections} = useContext(CollectionsContext); const {toggle, selected} = useSingleSelection(); + const {enqueueSnackbar} = useSnackbar(); const [filterCandidates, setFilterCandidates] = useState([]); const [textFiltersObject, setTextFiltersObject] = useState({}); @@ -199,7 +203,9 @@ export const MetadataView = (props: MetadataViewProperties) => { }; const areFacetFiltersNonEmpty = useMemo( - () => filters && filters.some(filter => facetsEx.some(facet => facet.name === filter.field)), + () => + filters && + filters.some(filter => facetsEx.some(facet => facet.name.toLowerCase() === filter.field.toLowerCase())), [filters, facetsEx] ); const areTextFiltersNonEmpty = useMemo( @@ -218,6 +224,48 @@ export const MetadataView = (props: MetadataViewProperties) => { return `${window.location.host}${pathPrefix}?${prefilteringQueryString}`; }; + const copyFiltersUrl = () => { + const queryParams = filters.reduce((acc, filter) => { + acc[filter.field.toLowerCase()] = filter.values.join(','); + return acc; + }, {}); + const queryStringFilters = queryString.stringify(queryParams); + const url = `${window.location.protocol}//${window.location.host}/metadata-views?view=${currentView.name}&${queryStringFilters}`; + + if (url.length > MAX_URL_LENGTH) { + enqueueSnackbar('Failed to copy metadata view filters URL to clipboard: URL too long'); + return; + } + navigator.clipboard + .writeText(url) + .then(() => enqueueSnackbar('Metadata view filters URL copied to clipboard')) + .catch(() => enqueueSnackbar('Failed to copy metadata view filters URL to clipboard')); + }; + + useEffect(() => { + const queryStringFilters = qs.parse(window.location.search, {ignoreQueryPrefix: true}); + if (queryStringFilters && Object.keys(queryStringFilters).length > 0) { + const idTextFilter = queryStringFilters[currentViewIdColumn.name.toLowerCase()]; + if (idTextFilter && (!areTextFiltersNonEmpty || !textFiltersObject.keys.includes(currentViewIdColumn))) { + setTextFiltersObject({...textFiltersObject, [currentViewIdColumn.name]: idTextFilter}); + } + if (!areFacetFiltersNonEmpty) { + const facetNames = facets.map(f => f.name.toLowerCase()); + const newFilters = Object.keys(queryStringFilters) + .filter(k => facetNames.includes(k.toLowerCase())) + .reduce((arr, key) => { + arr.push({ + field: key, + values: queryStringFilters[key].split(',') + }); + return arr; + }, []); + updateFilters(newFilters); + } + } + // eslint-disable-next-line + }, []); + return ( { {(areFacetFiltersNonEmpty || areTextFiltersNonEmpty) && ( - + + @@ -348,19 +399,21 @@ export const ContextualMetadataView = (props: ContextualMetadataViewProperties) }; return ( - + + + ); }; diff --git a/projects/mercury/src/metadata/views/MetadataView.styles.js b/projects/mercury/src/metadata/views/MetadataView.styles.js index 52f96d12a..da55f9c43 100644 --- a/projects/mercury/src/metadata/views/MetadataView.styles.js +++ b/projects/mercury/src/metadata/views/MetadataView.styles.js @@ -29,9 +29,11 @@ const styles = theme => ({ clearAllButtonContainer: { textAlign: 'end' }, - clearAllButton: { + filterButtons: { color: theme.palette.primary.contrastText, - background: theme.palette.primary.main + background: theme.palette.primary.main, + marginLeft: 1, + marginBottom: 1 }, activeFilters: { marginBottom: 10 diff --git a/projects/mercury/src/metadata/views/MetadataViewActiveFacetFilters.js b/projects/mercury/src/metadata/views/MetadataViewActiveFacetFilters.js index fc6d8632a..8343f9014 100644 --- a/projects/mercury/src/metadata/views/MetadataViewActiveFacetFilters.js +++ b/projects/mercury/src/metadata/views/MetadataViewActiveFacetFilters.js @@ -64,7 +64,7 @@ export const MetadataViewActiveFacetFilters = (props: MetadataViewActiveFacetFil ); } return filter.values.map(valueIri => { - const value = facet.values.find(val => val.value === valueIri); + const value = facet.values.find(val => val.value.toLowerCase() === valueIri.toLowerCase()); return ( value && ( f.name === filter.field); + const facet = facets.find(f => f.name.toLowerCase() === filter.field.toLowerCase()); if (facet) { return ( diff --git a/projects/mercury/src/metadata/views/MetadataViewContext.js b/projects/mercury/src/metadata/views/MetadataViewContext.js index 7a366df34..d10470f41 100644 --- a/projects/mercury/src/metadata/views/MetadataViewContext.js +++ b/projects/mercury/src/metadata/views/MetadataViewContext.js @@ -1,4 +1,5 @@ import React from 'react'; +// import {useHistory} from 'react-router-dom'; import type {MetadataViewFilter} from './MetadataViewAPI'; import MetadataViewAPI from './MetadataViewAPI'; import useAsync from '../../common/hooks/UseAsync'; @@ -11,6 +12,7 @@ const SESSION_STORAGE_METADATA_FILTERS_KEY = 'FAIRSPACE_METADATA_FILTERS'; export const MetadataViewProvider = ({children, metadataViewAPI = MetadataViewAPI, sourceName = ''}) => { const {data = {}, error, loading, refresh} = useAsync(() => metadataViewAPI.getViews(), []); + // const history = useHistory(); const [filters: MetadataViewFilter[], setFilters] = useStateWithSessionStorage( `${SESSION_STORAGE_METADATA_FILTERS_KEY}_${sourceName}`, @@ -23,11 +25,15 @@ export const MetadataViewProvider = ({children, metadataViewAPI = MetadataViewAP const clearAllFilters = () => { setFilters([]); + + const queryParams = new URLSearchParams(window.location.search); + const viewParam = queryParams.get('view'); + window.history.replaceState(null, '', `${window.location.pathname}?${viewParam ? `view=${viewParam}` : ''}`); }; const updateFilters = (filterCandidates: MetadataViewFilter[]) => { setFilters([ - ...filters.filter(f => !filterCandidates.some(u => u.field === f.field)), + ...filters.filter(f => !filterCandidates.some(u => u.field.toLowerCase() === f.field.toLowerCase())), ...filterCandidates.filter( f => (f.values && f.values.length > 0) || diff --git a/projects/mercury/src/metadata/views/MetadataViewFacets.js b/projects/mercury/src/metadata/views/MetadataViewFacets.js index d31d08eec..a339309f1 100644 --- a/projects/mercury/src/metadata/views/MetadataViewFacets.js +++ b/projects/mercury/src/metadata/views/MetadataViewFacets.js @@ -79,7 +79,9 @@ export const MetadataViewFacets = (props: MetadataViewFacetsProperties) => { const renderSingleFacet = (facet: MetadataViewFacet) => { const facetOptions = getFilterValues(facet.type, facet); - const activeFilter = [...filterCandidates, ...filters].find(filter => filter.field === facet.name); + const activeFilter = [...filterCandidates, ...filters].find( + filter => filter.field.toLowerCase() === facet.name.toLowerCase() + ); let activeFilterValues = []; if (activeFilter) { activeFilterValues = getFilterValues(facet.type, activeFilter); diff --git a/projects/mercury/src/metadata/views/MetadataViewTable.js b/projects/mercury/src/metadata/views/MetadataViewTable.js index 3472b5074..66f557640 100644 --- a/projects/mercury/src/metadata/views/MetadataViewTable.js +++ b/projects/mercury/src/metadata/views/MetadataViewTable.js @@ -1,15 +1,14 @@ -import React, {useCallback, useEffect} from 'react'; +import React, {useCallback} from 'react'; import {Checkbox, Link, Table, TableBody, TableCell, TableHead, TableRow} from '@mui/material'; import {Check, Close} from '@mui/icons-material'; import makeStyles from '@mui/styles/makeStyles'; import {Link as RouterLink} from 'react-router-dom'; -import qs from 'qs'; import useDeepCompareEffect from 'use-deep-compare-effect'; import type {MetadataViewColumn, MetadataViewData} from './MetadataViewAPI'; import {TextualValueTypes} from './MetadataViewAPI'; import type {MetadataViewEntity, MetadataViewEntityWithLinkedFiles} from './metadataViewUtils'; import {RESOURCES_VIEW} from './metadataViewUtils'; -import {stringToBooleanValueOrNull, formatDate} from '../../common/utils/genericUtils'; +import {formatDate, stringToBooleanValueOrNull} from '../../common/utils/genericUtils'; import type {Collection} from '../../collections/CollectionAPI'; import {collectionAccessIcon} from '../../collections/collectionUtils'; import {getPathFromIri, redirectLink} from '../../file/fileUtils'; @@ -52,12 +51,12 @@ const RESOURCE_TYPE_COLUMN = `${RESOURCES_VIEW}_type`; export const MetadataViewTable = (props: MetadataViewTableProperties) => { const {columns, visibleColumnNames, loading, data, toggleRow, selected, view, idColumn, history, collections} = props; - const classes = useStyles(); const {textFiltersObject, setTextFiltersObject} = props; + const {checkboxes, setCheckboxState} = props; + const classes = useStyles(); const visibleColumns = columns.filter(column => visibleColumnNames.includes(column.name)); const dataLinkColumn = columns.find(c => c.type === 'dataLink'); const isResourcesView = view === RESOURCES_VIEW; - const {checkboxes, setCheckboxState} = props; const isCustomResourceColumn = (column: MetadataViewColumn) => isResourcesView && CUSTOM_RESOURCE_COLUMNS.includes(column.name) && column.type === 'Custom'; @@ -67,11 +66,6 @@ export const MetadataViewTable = (props: MetadataViewTableProperties) => { return col ? col.access : 'None'; }; - const getIdColumnFilterFromSearchParams = () => { - const idColumnName = idColumn.name.toLowerCase(); - return qs.parse(window.location.search, {ignoreQueryPrefix: true})[idColumnName]; - }; - const getResourceType = (row: Map) => row[RESOURCE_TYPE_COLUMN] && row[RESOURCE_TYPE_COLUMN][0] && row[RESOURCE_TYPE_COLUMN][0].value; @@ -82,15 +76,6 @@ export const MetadataViewTable = (props: MetadataViewTableProperties) => { toggleRow({label, iri, linkedFiles: linkedFiles || []}); } }; - useEffect(() => { - if (!textFiltersObject || !textFiltersObject.keys || !textFiltersObject.keys.includes(idColumn)) { - const idColumnTextFilter = getIdColumnFilterFromSearchParams(); - if (idColumnTextFilter) { - setTextFiltersObject({...textFiltersObject, [idColumn.name]: idColumnTextFilter}); - } - } - // eslint-disable-next-line - }, []); const initializeCheckboxes = useCallback(() => { if (idColumn && data && data.rows) {