diff --git a/src/components/orgs/OrgHome.jsx b/src/components/orgs/OrgHome.jsx
index 2b18615b2..d17d43bd6 100644
--- a/src/components/orgs/OrgHome.jsx
+++ b/src/components/orgs/OrgHome.jsx
@@ -10,6 +10,7 @@ import { OperationsContext } from '../app/LayoutContext';
import CommonTabs from '../common/CommonTabs';
import DeleteEntityDialog from '../common/DeleteEntityDialog'
import Search from '../search/Search';
+import useDefaultFiltersOpen from '../../hooks/useDefaultFiltersOpen';
import Error40X from '../errors/Error40X';
import OrgOverview from './OrgOverview';
import OrgSummary from './OrgSummary';
@@ -25,6 +26,7 @@ const OrgHome = () => {
const [members, setMembers] = React.useState([])
const [deleteOrg, setDeleteOrg] = React.useState(false)
const [tab, setTab] = React.useState(findTab)
+ const [defaultFiltersOpen, onFiltersOpenChange] = useDefaultFiltersOpen()
const { setAlert } = React.useContext(OperationsContext);
const { pins: bookmarks, canPin, togglePin, deletePin } = usePins(params?.org ? {type: 'org', id: params.org} : false)
@@ -108,7 +110,8 @@ const OrgHome = () => {
pins={bookmarks}
canPin={canPin}
onPinToggle={togglePin}
- defaultFiltersOpen={false}
+ defaultFiltersOpen={defaultFiltersOpen}
+ onFiltersOpenChange={onFiltersOpenChange}
nested
noTabs
filtersHeightToSubtract={280}
diff --git a/src/components/repos/RepoHome.jsx b/src/components/repos/RepoHome.jsx
index 17f305bd2..667c49392 100644
--- a/src/components/repos/RepoHome.jsx
+++ b/src/components/repos/RepoHome.jsx
@@ -21,6 +21,7 @@ import { WHITE } from '../../common/colors';
import { OperationsContext } from '../app/LayoutContext';
import CommonTabs from '../common/CommonTabs';
import Search from '../search/Search';
+import useDefaultFiltersOpen from '../../hooks/useDefaultFiltersOpen';
import DeleteEntityDialog from '../common/DeleteEntityDialog'
import ConceptHome from '../concepts/ConceptHome';
import MappingHome from '../mappings/MappingHome';
@@ -86,6 +87,7 @@ const RepoHome = () => {
const [showSummary, setShowSummary] = React.useState(true)
const [addReferencesOpen, setAddReferencesOpen] = React.useState(false)
const [searchReloadKey, setSearchReloadKey] = React.useState(0)
+ const [defaultFiltersOpen, onFiltersOpenChange] = useDefaultFiltersOpen()
const [expansions, setExpansions] = React.useState([])
const [expansionsLoading, setExpansionsLoading] = React.useState(false)
const [selectedExpansion, setSelectedExpansion] = React.useState(false)
@@ -591,7 +593,8 @@ const RepoHome = () => {
summary={repoSummary || repo?.summary}
resource={tab}
url={getSearchURL()}
- defaultFiltersOpen={false}
+ defaultFiltersOpen={defaultFiltersOpen}
+ onFiltersOpenChange={onFiltersOpenChange}
nested
repo={repo}
noTabs
diff --git a/src/components/search/Search.jsx b/src/components/search/Search.jsx
index 0f83bcad3..96a461e12 100644
--- a/src/components/search/Search.jsx
+++ b/src/components/search/Search.jsx
@@ -804,7 +804,10 @@ const Search = props => {
onSelect={newSelected => onSelectItem(newSelected)}
selectedToShow={showItem}
onShowItemSelect={onShowItemSelect}
- onFiltersToggle={() => setOpenFilters(!openFilters)}
+ onFiltersToggle={() => {
+ setOpenFilters(!openFilters)
+ props.onFiltersOpenChange?.(!openFilters)
+ }}
onDisplayChange={onDisplayChange}
resultContainerStyle={props.resultContainerStyle}
resultSize={props.resultSize}
diff --git a/src/components/search/SearchControls.jsx b/src/components/search/SearchControls.jsx
index 48556e1d2..d511ff45a 100644
--- a/src/components/search/SearchControls.jsx
+++ b/src/components/search/SearchControls.jsx
@@ -25,13 +25,13 @@ const SearchControls = ({ disabled, onDisplayChange, display, order, orderBy, on
{
hasSortOptions(sortConfig) &&
- } onClick={onSortClick}>
+ } onClick={onSortClick}>
{t('search.sort_by')}
}
{
showDisplayControl &&
- } onClick={onDisplayClick}>
+ } onClick={onDisplayClick}>
{t('search.display')}
}
diff --git a/src/components/search/SearchResults.jsx b/src/components/search/SearchResults.jsx
index 1810d35e4..af47fcbd1 100644
--- a/src/components/search/SearchResults.jsx
+++ b/src/components/search/SearchResults.jsx
@@ -2,10 +2,8 @@ import React from 'react';
import { useTranslation } from 'react-i18next'
import { useHistory } from 'react-router-dom';
import Box from '@mui/material/Box';
-import Badge from '@mui/material/Badge';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
-import IconButton from '@mui/material/IconButton';
import Button from '@mui/material/Button';
import FilterListIcon from '@mui/icons-material/FilterList';
import AddIcon from '@mui/icons-material/PlaylistAddOutlined';
@@ -13,7 +11,7 @@ import RepeatIcon from '@mui/icons-material/Repeat';
import CloneIcon from '@mui/icons-material/ControlPointDuplicate';
import TablePagination from '@mui/material/TablePagination';
import Skeleton from '@mui/material/Skeleton';
-import { isNumber, isNaN, flatten, values, uniqBy, compact, pick, keys } from 'lodash'
+import { isNumber, isNaN, flatten, values, uniqBy, compact, pick, keys, omit } from 'lodash'
import SearchControls from './SearchControls';
import NoResults from './NoResults';
import TableResults from './TableResults';
@@ -24,15 +22,18 @@ import CloneToSourceDialog from '../repos/CloneToSourceDialog';
import MappingIcon from '../mappings/MappingIcon';
import GAService from '../../services/GAService';
import { getSortConfig } from './sortConfig'
+import { EXCLUDE_FILTER_KEY } from './ResultConstants'
import { resolveColumns } from './columns'
import { isLoggedIn, currentUserHasAccess } from '../../common/utils';
import { createSimilarRepoHref } from '../repos/utils';
import { MAX_SEARCH_RESULT_WINDOW } from '../../common/constants';
const ResultsToolbar = props => {
+ const { t } = useTranslation()
const { numSelected, title, onFiltersToggle, disabled, isFilterable, onDisplayChange, display, order, orderBy, onOrderByChange, sortConfig, noCardDisplay, toolbarControl, appliedFilters, availableFilterFields, openFilters, bulkActions, leftControls, displayOptions, resource } = props;
- const countableFilters = availableFilterFields ? pick(appliedFilters, availableFilterFields) : appliedFilters
- const filtersCount = resource === 'references' ? flatten(values(countableFilters))?.length : flatten(values(countableFilters).map(v => values(v))).length
+ // Reference searches fetch no facets, so their filters can't be narrowed to facet fields.
+ const countableFilters = (availableFilterFields && resource !== 'references') ? pick(appliedFilters, availableFilterFields) : appliedFilters
+ const filtersCount = resource === 'references' ? flatten(values(countableFilters))?.length : flatten(values(countableFilters).map(v => values(omit(v, EXCLUDE_FILTER_KEY)))).length
return (
{
>
{
isFilterable &&
- 0 ? {backgroundColor: 'rgba(73, 69, 79, 0.12)'} : {})}} onClick={onFiltersToggle} disabled={Boolean(disabled)}>
-
-
-
-
+ } onClick={onFiltersToggle} aria-expanded={Boolean(openFilters)}>
+ {t('search.filters')}{filtersCount > 0 ? ` (${filtersCount})` : ''}
+
}
{
leftControls?.length > 0 &&
@@ -405,6 +404,7 @@ const SearchResults = props => {
numSelected={selected.length}
title={getTitle()}
onFiltersToggle={props.onFiltersToggle}
+ openFilters={props.openFilters}
disabled={props.noResults}
isFilterable={props.isFilterable}
onDisplayChange={onDisplayChange}
diff --git a/src/components/users/UserHome.jsx b/src/components/users/UserHome.jsx
index 0459d9095..78a9572f1 100644
--- a/src/components/users/UserHome.jsx
+++ b/src/components/users/UserHome.jsx
@@ -8,6 +8,7 @@ import { COLORS } from '../../common/colors'
import APIService from '../../services/APIService'
import CommonTabs from '../common/CommonTabs';
import Search from '../search/Search';
+import useDefaultFiltersOpen from '../../hooks/useDefaultFiltersOpen';
import Error40X from '../errors/Error40X';
import UserOverview from './UserOverview';
import UserSummary from './UserSummary'
@@ -19,6 +20,7 @@ const UserHome = () => {
const params = useParams()
const history = useHistory()
const [tab, setTab] = React.useState(findTab || 'overview')
+ const [defaultFiltersOpen, onFiltersOpenChange] = useDefaultFiltersOpen()
const [user, setUser] = React.useState({})
const [status, setStatus] = React.useState(false)
const { pins: bookmarks, canPin, togglePin, deletePin } = usePins(params?.user ? {type: 'user', id: params.user} : false)
@@ -122,7 +124,8 @@ const UserHome = () => {
filtersHeightToSubtract={baseHeightToDeduct}
resultContainerStyle={{height: `calc(var(--app-height) - ${baseHeightToDeduct}px - 88px)`, overflow: 'auto'}}
containerStyle={{padding: 0}}
- defaultFiltersOpen={false}
+ defaultFiltersOpen={defaultFiltersOpen}
+ onFiltersOpenChange={onFiltersOpenChange}
resultSize='medium'
excludedColumns={['owner']}
/>
diff --git a/src/hooks/useDefaultFiltersOpen.js b/src/hooks/useDefaultFiltersOpen.js
new file mode 100644
index 000000000..0148f3907
--- /dev/null
+++ b/src/hooks/useDefaultFiltersOpen.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import useMediaQuery from '@mui/material/useMediaQuery';
+
+// For pages that embed Search (repo pages, org and user repository lists): the filter
+// panel starts open on wide screens and closed on narrow ones. Once the user opens or
+// closes it, that choice is kept while the page stays mounted, including when Search
+// remounts on a tab or version change. A window resize never opens or closes it.
+// Returns [defaultFiltersOpen, onFiltersOpenChange] to pass to Search.
+const useDefaultFiltersOpen = () => {
+ // Search reads defaultFiltersOpen once, when it mounts, so the first render needs the real width.
+ const isWide = useMediaQuery(theme => theme.breakpoints.up('lg'), {noSsr: true}) // eslint-disable-line spellcheck/spell-checker
+ const [choice, setChoice] = React.useState(null)
+ return [choice === null ? isWide : choice, setChoice]
+}
+
+export default useDefaultFiltersOpen;