Răsfoiți Sursa

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

[web] Remember selected dashboard filter on page reload

GitOrigin-RevId: c5abbd4e74ed48c127ae5e60f69a0bd98fbfc876
Davinder Singh 4 ani în urmă
părinte
comite
3bacd89399

+ 2 - 2
services/web/frontend/js/features/project-list/components/sidebar/sidebar-filters.tsx

@@ -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>

+ 16 - 6
services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx

@@ -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>

+ 48 - 23
services/web/frontend/js/features/project-list/context/project-list-context.tsx

@@ -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)
-        filteredProjects = filteredProjects.filter(project =>
-          tag?.project_ids?.includes(project.id)
-        )
+        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) => {
-    setFilter(filter)
-    setSelectedTag(undefined)
-  }, [])
+  const selectFilter = useCallback(
+    (filter: Filter) => {
+      setFilter(filter)
+      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,