mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
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:
parent
0c5cdc2023
commit
3bacd89399
3 changed files with 66 additions and 31 deletions
|
@ -15,13 +15,13 @@ function SidebarFilter({ filter, text }: SidebarFilterProps) {
|
|||
const {
|
||||
filter: activeFilter,
|
||||
selectFilter,
|
||||
selectedTag,
|
||||
selectedTagId,
|
||||
} = useProjectListContext()
|
||||
|
||||
return (
|
||||
<li
|
||||
className={
|
||||
selectedTag === undefined && filter === activeFilter ? 'active' : ''
|
||||
selectedTagId === undefined && filter === activeFilter ? 'active' : ''
|
||||
}
|
||||
>
|
||||
<Button onClick={() => selectFilter(filter)}>{text}</Button>
|
||||
|
|
|
@ -5,7 +5,10 @@ import { useTranslation } from 'react-i18next'
|
|||
import { Tag } from '../../../../../../app/src/Features/Tags/types'
|
||||
import ColorManager from '../../../../ide/colors/ColorManager'
|
||||
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 DeleteTagModal from './delete-tag-modal'
|
||||
import RenameTagModal from './rename-tag-modal'
|
||||
|
@ -15,7 +18,7 @@ export default function TagsList() {
|
|||
const {
|
||||
tags,
|
||||
untaggedProjectsCount,
|
||||
selectedTag,
|
||||
selectedTagId,
|
||||
selectTag,
|
||||
addTag,
|
||||
renameTag,
|
||||
|
@ -98,7 +101,7 @@ export default function TagsList() {
|
|||
{_.sortBy(tags, ['name']).map((tag, index) => {
|
||||
return (
|
||||
<li
|
||||
className={`tag ${selectedTag === tag._id ? 'active' : ''}`}
|
||||
className={`tag ${selectedTagId === tag._id ? 'active' : ''}`}
|
||||
key={index}
|
||||
>
|
||||
<Button
|
||||
|
@ -113,7 +116,7 @@ export default function TagsList() {
|
|||
}}
|
||||
>
|
||||
<Icon
|
||||
type={selectedTag === tag._id ? 'folder-open' : 'folder'}
|
||||
type={selectedTagId === tag._id ? 'folder-open' : 'folder'}
|
||||
/>
|
||||
</span>
|
||||
<span className="name">
|
||||
|
@ -153,8 +156,15 @@ export default function TagsList() {
|
|||
</li>
|
||||
)
|
||||
})}
|
||||
<li className={`tag untagged ${selectedTag === null ? 'active' : ''}`}>
|
||||
<Button className="tag-name" onClick={() => selectTag(null)}>
|
||||
<li
|
||||
className={`tag untagged ${
|
||||
selectedTagId === UNCATEGORIZED_KEY ? 'active' : ''
|
||||
}`}
|
||||
>
|
||||
<Button
|
||||
className="tag-name"
|
||||
onClick={() => selectTag(UNCATEGORIZED_KEY)}
|
||||
>
|
||||
<span className="name">{t('uncategorized')}</span>
|
||||
<span className="subdued"> ({untaggedProjectsCount})</span>
|
||||
</Button>
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
Project,
|
||||
Sort,
|
||||
} from '../../../../../types/project/dashboard/api'
|
||||
import usePersistedState from '../../../shared/hooks/use-persisted-state'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import useAsync from '../../../shared/hooks/use-async'
|
||||
import { getProjects } from '../util/api'
|
||||
|
@ -46,6 +47,8 @@ const filters: FilterMap = {
|
|||
},
|
||||
}
|
||||
|
||||
export const UNCATEGORIZED_KEY = 'uncategorized'
|
||||
|
||||
type ProjectListContextValue = {
|
||||
visibleProjects: Project[]
|
||||
setVisibleProjects: React.Dispatch<React.SetStateAction<Project[]>>
|
||||
|
@ -59,8 +62,8 @@ type ProjectListContextValue = {
|
|||
untaggedProjectsCount: number
|
||||
filter: Filter
|
||||
selectFilter: (filter: Filter) => void
|
||||
selectedTag?: string | null
|
||||
selectTag: (tagName: string | null) => void
|
||||
selectedTagId?: string | undefined
|
||||
selectTag: (tagId: string) => void
|
||||
addTag: (tag: Tag) => void
|
||||
renameTag: (tagId: string, newTagName: string) => void
|
||||
deleteTag: (tagId: string) => void
|
||||
|
@ -86,9 +89,14 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
by: 'lastUpdated',
|
||||
order: 'desc',
|
||||
})
|
||||
const [filter, setFilter] = useState<Filter>('all')
|
||||
const [selectedTag, setSelectedTag] = useState<string | null>()
|
||||
const [tags, setTags] = useState<Tag[]>([])
|
||||
const [filter, setFilter] = usePersistedState<Filter>(
|
||||
'project-list-filter',
|
||||
'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 {
|
||||
isLoading: loading,
|
||||
|
@ -98,8 +106,6 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
} = useAsync<GetProjectsResponseBody>()
|
||||
const isLoading = isIdle ? true : loading
|
||||
|
||||
useEffect(() => setTags(getMeta('ol-tags', []) as Tag[]), [])
|
||||
|
||||
useEffect(() => {
|
||||
setLoadProgress(40)
|
||||
runAsync(getProjects({ by: 'lastUpdated', order: 'desc' }))
|
||||
|
@ -122,8 +128,8 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
)
|
||||
}
|
||||
|
||||
if (selectedTag !== undefined) {
|
||||
if (selectedTag === null) {
|
||||
if (selectedTagId !== undefined) {
|
||||
if (selectedTagId === UNCATEGORIZED_KEY) {
|
||||
const taggedProjectIds = _.uniq(
|
||||
_.flatten(tags.map(tag => tag.project_ids))
|
||||
)
|
||||
|
@ -134,17 +140,30 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
!taggedProjectIds.includes(project.id)
|
||||
)
|
||||
} else {
|
||||
const tag = _.find(tags, tag => tag._id === selectedTag)
|
||||
const tag = _.find(tags, tag => tag._id === selectedTagId)
|
||||
if (tag) {
|
||||
filteredProjects = filteredProjects.filter(project =>
|
||||
tag?.project_ids?.includes(project.id)
|
||||
)
|
||||
} else {
|
||||
setFilter('all')
|
||||
setSelectedTagId(undefined)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filteredProjects = _.filter(filteredProjects, filters[filter])
|
||||
}
|
||||
|
||||
setVisibleProjects(filteredProjects)
|
||||
}, [loadedProjects, tags, filter, selectedTag, searchText])
|
||||
}, [
|
||||
loadedProjects,
|
||||
tags,
|
||||
filter,
|
||||
setFilter,
|
||||
selectedTagId,
|
||||
setSelectedTagId,
|
||||
searchText,
|
||||
])
|
||||
|
||||
const untaggedProjectsCount = useMemo(() => {
|
||||
const taggedProjectIds = _.uniq(_.flatten(tags.map(tag => tag.project_ids)))
|
||||
|
@ -156,14 +175,20 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
).length
|
||||
}, [tags, loadedProjects])
|
||||
|
||||
const selectFilter = useCallback((filter: Filter) => {
|
||||
const selectFilter = useCallback(
|
||||
(filter: Filter) => {
|
||||
setFilter(filter)
|
||||
setSelectedTag(undefined)
|
||||
}, [])
|
||||
setSelectedTagId(undefined)
|
||||
},
|
||||
[setFilter, setSelectedTagId]
|
||||
)
|
||||
|
||||
const selectTag = useCallback((tagId: string | null) => {
|
||||
setSelectedTag(tagId)
|
||||
}, [])
|
||||
const selectTag = useCallback(
|
||||
(tagId: string) => {
|
||||
setSelectedTagId(tagId)
|
||||
},
|
||||
[setSelectedTagId]
|
||||
)
|
||||
|
||||
const addTag = useCallback((tag: Tag) => {
|
||||
setTags(tags => _.uniqBy(_.concat(tags, [tag]), '_id'))
|
||||
|
@ -219,7 +244,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
isLoading,
|
||||
loadProgress,
|
||||
renameTag,
|
||||
selectedTag,
|
||||
selectedTagId,
|
||||
selectFilter,
|
||||
selectTag,
|
||||
setSearchText,
|
||||
|
@ -241,7 +266,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
isLoading,
|
||||
loadProgress,
|
||||
renameTag,
|
||||
selectedTag,
|
||||
selectedTagId,
|
||||
selectFilter,
|
||||
selectTag,
|
||||
setSearchText,
|
||||
|
|
Loading…
Reference in a new issue