Merge pull request #9567 from overleaf/ab-react-dash-keep-selected-filter

[web] Remember selected dashboard filter on page reload

GitOrigin-RevId: c5abbd4e74ed48c127ae5e60f69a0bd98fbfc876
This commit is contained in:
Davinder Singh 2022-09-15 10:58:27 +01:00 committed by Copybot
parent 0c5cdc2023
commit 3bacd89399
3 changed files with 66 additions and 31 deletions

View file

@ -15,13 +15,13 @@ function SidebarFilter({ filter, text }: SidebarFilterProps) {
const { const {
filter: activeFilter, filter: activeFilter,
selectFilter, selectFilter,
selectedTag, selectedTagId,
} = useProjectListContext() } = useProjectListContext()
return ( return (
<li <li
className={ className={
selectedTag === undefined && filter === activeFilter ? 'active' : '' selectedTagId === undefined && filter === activeFilter ? 'active' : ''
} }
> >
<Button onClick={() => selectFilter(filter)}>{text}</Button> <Button onClick={() => selectFilter(filter)}>{text}</Button>

View file

@ -5,7 +5,10 @@ import { useTranslation } from 'react-i18next'
import { Tag } from '../../../../../../app/src/Features/Tags/types' import { Tag } from '../../../../../../app/src/Features/Tags/types'
import ColorManager from '../../../../ide/colors/ColorManager' import ColorManager from '../../../../ide/colors/ColorManager'
import Icon from '../../../../shared/components/icon' import Icon from '../../../../shared/components/icon'
import { useProjectListContext } from '../../context/project-list-context' import {
UNCATEGORIZED_KEY,
useProjectListContext,
} from '../../context/project-list-context'
import CreateTagModal from './create-tag-modal' import CreateTagModal from './create-tag-modal'
import DeleteTagModal from './delete-tag-modal' import DeleteTagModal from './delete-tag-modal'
import RenameTagModal from './rename-tag-modal' import RenameTagModal from './rename-tag-modal'
@ -15,7 +18,7 @@ export default function TagsList() {
const { const {
tags, tags,
untaggedProjectsCount, untaggedProjectsCount,
selectedTag, selectedTagId,
selectTag, selectTag,
addTag, addTag,
renameTag, renameTag,
@ -98,7 +101,7 @@ export default function TagsList() {
{_.sortBy(tags, ['name']).map((tag, index) => { {_.sortBy(tags, ['name']).map((tag, index) => {
return ( return (
<li <li
className={`tag ${selectedTag === tag._id ? 'active' : ''}`} className={`tag ${selectedTagId === tag._id ? 'active' : ''}`}
key={index} key={index}
> >
<Button <Button
@ -113,7 +116,7 @@ export default function TagsList() {
}} }}
> >
<Icon <Icon
type={selectedTag === tag._id ? 'folder-open' : 'folder'} type={selectedTagId === tag._id ? 'folder-open' : 'folder'}
/> />
</span> </span>
<span className="name"> <span className="name">
@ -153,8 +156,15 @@ export default function TagsList() {
</li> </li>
) )
})} })}
<li className={`tag untagged ${selectedTag === null ? 'active' : ''}`}> <li
<Button className="tag-name" onClick={() => selectTag(null)}> className={`tag untagged ${
selectedTagId === UNCATEGORIZED_KEY ? 'active' : ''
}`}
>
<Button
className="tag-name"
onClick={() => selectTag(UNCATEGORIZED_KEY)}
>
<span className="name">{t('uncategorized')}</span> <span className="name">{t('uncategorized')}</span>
<span className="subdued"> ({untaggedProjectsCount})</span> <span className="subdued"> ({untaggedProjectsCount})</span>
</Button> </Button>

View file

@ -14,6 +14,7 @@ import {
Project, Project,
Sort, Sort,
} from '../../../../../types/project/dashboard/api' } from '../../../../../types/project/dashboard/api'
import usePersistedState from '../../../shared/hooks/use-persisted-state'
import getMeta from '../../../utils/meta' import getMeta from '../../../utils/meta'
import useAsync from '../../../shared/hooks/use-async' import useAsync from '../../../shared/hooks/use-async'
import { getProjects } from '../util/api' import { getProjects } from '../util/api'
@ -46,6 +47,8 @@ const filters: FilterMap = {
}, },
} }
export const UNCATEGORIZED_KEY = 'uncategorized'
type ProjectListContextValue = { type ProjectListContextValue = {
visibleProjects: Project[] visibleProjects: Project[]
setVisibleProjects: React.Dispatch<React.SetStateAction<Project[]>> setVisibleProjects: React.Dispatch<React.SetStateAction<Project[]>>
@ -59,8 +62,8 @@ type ProjectListContextValue = {
untaggedProjectsCount: number untaggedProjectsCount: number
filter: Filter filter: Filter
selectFilter: (filter: Filter) => void selectFilter: (filter: Filter) => void
selectedTag?: string | null selectedTagId?: string | undefined
selectTag: (tagName: string | null) => void selectTag: (tagId: string) => void
addTag: (tag: Tag) => void addTag: (tag: Tag) => void
renameTag: (tagId: string, newTagName: string) => void renameTag: (tagId: string, newTagName: string) => void
deleteTag: (tagId: string) => void deleteTag: (tagId: string) => void
@ -86,9 +89,14 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
by: 'lastUpdated', by: 'lastUpdated',
order: 'desc', order: 'desc',
}) })
const [filter, setFilter] = useState<Filter>('all') const [filter, setFilter] = usePersistedState<Filter>(
const [selectedTag, setSelectedTag] = useState<string | null>() 'project-list-filter',
const [tags, setTags] = useState<Tag[]>([]) 'all'
)
const [selectedTagId, setSelectedTagId] = usePersistedState<
string | undefined
>('project-list-selected-tag-id', undefined)
const [tags, setTags] = useState<Tag[]>(getMeta('ol-tags', []) as Tag[])
const [searchText, setSearchText] = useState('') const [searchText, setSearchText] = useState('')
const { const {
isLoading: loading, isLoading: loading,
@ -98,8 +106,6 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
} = useAsync<GetProjectsResponseBody>() } = useAsync<GetProjectsResponseBody>()
const isLoading = isIdle ? true : loading const isLoading = isIdle ? true : loading
useEffect(() => setTags(getMeta('ol-tags', []) as Tag[]), [])
useEffect(() => { useEffect(() => {
setLoadProgress(40) setLoadProgress(40)
runAsync(getProjects({ by: 'lastUpdated', order: 'desc' })) runAsync(getProjects({ by: 'lastUpdated', order: 'desc' }))
@ -122,8 +128,8 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
) )
} }
if (selectedTag !== undefined) { if (selectedTagId !== undefined) {
if (selectedTag === null) { if (selectedTagId === UNCATEGORIZED_KEY) {
const taggedProjectIds = _.uniq( const taggedProjectIds = _.uniq(
_.flatten(tags.map(tag => tag.project_ids)) _.flatten(tags.map(tag => tag.project_ids))
) )
@ -134,17 +140,30 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
!taggedProjectIds.includes(project.id) !taggedProjectIds.includes(project.id)
) )
} else { } else {
const tag = _.find(tags, tag => tag._id === selectedTag) const tag = _.find(tags, tag => tag._id === selectedTagId)
if (tag) {
filteredProjects = filteredProjects.filter(project => filteredProjects = filteredProjects.filter(project =>
tag?.project_ids?.includes(project.id) tag?.project_ids?.includes(project.id)
) )
} else {
setFilter('all')
setSelectedTagId(undefined)
}
} }
} else { } else {
filteredProjects = _.filter(filteredProjects, filters[filter]) filteredProjects = _.filter(filteredProjects, filters[filter])
} }
setVisibleProjects(filteredProjects) setVisibleProjects(filteredProjects)
}, [loadedProjects, tags, filter, selectedTag, searchText]) }, [
loadedProjects,
tags,
filter,
setFilter,
selectedTagId,
setSelectedTagId,
searchText,
])
const untaggedProjectsCount = useMemo(() => { const untaggedProjectsCount = useMemo(() => {
const taggedProjectIds = _.uniq(_.flatten(tags.map(tag => tag.project_ids))) const taggedProjectIds = _.uniq(_.flatten(tags.map(tag => tag.project_ids)))
@ -156,14 +175,20 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
).length ).length
}, [tags, loadedProjects]) }, [tags, loadedProjects])
const selectFilter = useCallback((filter: Filter) => { const selectFilter = useCallback(
(filter: Filter) => {
setFilter(filter) setFilter(filter)
setSelectedTag(undefined) setSelectedTagId(undefined)
}, []) },
[setFilter, setSelectedTagId]
)
const selectTag = useCallback((tagId: string | null) => { const selectTag = useCallback(
setSelectedTag(tagId) (tagId: string) => {
}, []) setSelectedTagId(tagId)
},
[setSelectedTagId]
)
const addTag = useCallback((tag: Tag) => { const addTag = useCallback((tag: Tag) => {
setTags(tags => _.uniqBy(_.concat(tags, [tag]), '_id')) setTags(tags => _.uniqBy(_.concat(tags, [tag]), '_id'))
@ -219,7 +244,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
isLoading, isLoading,
loadProgress, loadProgress,
renameTag, renameTag,
selectedTag, selectedTagId,
selectFilter, selectFilter,
selectTag, selectTag,
setSearchText, setSearchText,
@ -241,7 +266,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
isLoading, isLoading,
loadProgress, loadProgress,
renameTag, renameTag,
selectedTag, selectedTagId,
selectFilter, selectFilter,
selectTag, selectTag,
setSearchText, setSearchText,