Kaynağa Gözat

Store selected project ids separately (#15598)

GitOrigin-RevId: 56a833fb1524362ff6617afa9be4cb7b6c1984c7
Alf Eaton 2 yıl önce
ebeveyn
işleme
49d2ca781e
14 değiştirilmiş dosya ile 114 ekleme ve 57 silme
  1. 4 2
      services/web/frontend/js/features/project-list/components/modals/rename-project-modal.tsx
  2. 4 3
      services/web/frontend/js/features/project-list/components/table/cells/action-buttons/archive-project-button.tsx
  3. 4 1
      services/web/frontend/js/features/project-list/components/table/cells/action-buttons/copy-project-button.tsx
  4. 4 3
      services/web/frontend/js/features/project-list/components/table/cells/action-buttons/trash-project-button.tsx
  5. 5 4
      services/web/frontend/js/features/project-list/components/table/cells/action-buttons/unarchive-project-button.tsx
  6. 5 3
      services/web/frontend/js/features/project-list/components/table/cells/action-buttons/untrash-project-button.tsx
  7. 25 0
      services/web/frontend/js/features/project-list/components/table/project-checkbox.tsx
  8. 5 20
      services/web/frontend/js/features/project-list/components/table/project-list-table-row.tsx
  9. 3 2
      services/web/frontend/js/features/project-list/components/table/project-tools/buttons/archive-projects-button.tsx
  10. 3 2
      services/web/frontend/js/features/project-list/components/table/project-tools/buttons/trash-projects-button.tsx
  11. 4 2
      services/web/frontend/js/features/project-list/components/table/project-tools/buttons/unarchive-projects-button.tsx
  12. 4 2
      services/web/frontend/js/features/project-list/components/table/project-tools/buttons/untrash-projects-button.tsx
  13. 3 3
      services/web/frontend/js/features/project-list/components/table/project-tools/menu-items/copy-project-menu-item.tsx
  14. 41 10
      services/web/frontend/js/features/project-list/context/project-list-context.tsx

+ 4 - 2
services/web/frontend/js/features/project-list/components/modals/rename-project-modal.tsx

@@ -34,7 +34,8 @@ function RenameProjectModal({
   const { t } = useTranslation()
   const [newProjectName, setNewProjectName] = useState(project.name)
   const { error, isError, isLoading, runAsync } = useAsync()
-  const { updateProjectViewData } = useProjectListContext()
+  const { toggleSelectedProject, updateProjectViewData } =
+    useProjectListContext()
   const newNotificationStyle = getMeta(
     'ol-newNotificationStyle',
     false
@@ -63,10 +64,10 @@ function RenameProjectModal({
 
       runAsync(renameProject(project.id, newProjectName))
         .then(() => {
+          toggleSelectedProject(project.id, false)
           updateProjectViewData({
             ...project,
             name: newProjectName,
-            selected: false,
           })
           handleCloseModal()
         })
@@ -78,6 +79,7 @@ function RenameProjectModal({
       newProjectName,
       project,
       runAsync,
+      toggleSelectedProject,
       updateProjectViewData,
     ]
   )

+ 4 - 3
services/web/frontend/js/features/project-list/components/table/cells/action-buttons/archive-project-button.tsx

@@ -17,7 +17,8 @@ function ArchiveProjectButton({
   project,
   children,
 }: ArchiveProjectButtonProps) {
-  const { updateProjectViewData } = useProjectListContext()
+  const { toggleSelectedProject, updateProjectViewData } =
+    useProjectListContext()
   const { t } = useTranslation()
   const text = t('archive')
   const [showModal, setShowModal] = useState(false)
@@ -35,13 +36,13 @@ function ArchiveProjectButton({
 
   const handleArchiveProject = useCallback(async () => {
     await archiveProject(project.id)
+    toggleSelectedProject(project.id, false)
     updateProjectViewData({
       ...project,
       archived: true,
-      selected: false,
       trashed: false,
     })
-  }, [project, updateProjectViewData])
+  }, [project, toggleSelectedProject, updateProjectViewData])
 
   if (project.archived) return null
 

+ 4 - 1
services/web/frontend/js/features/project-list/components/table/cells/action-buttons/copy-project-button.tsx

@@ -22,6 +22,7 @@ function CopyProjectButton({ project, children }: CopyButtonProps) {
   const {
     addClonedProjectToViewData,
     addProjectToTagInView,
+    toggleSelectedProject,
     updateProjectViewData,
   } = useProjectListContext()
   const { t } = useTranslation()
@@ -51,13 +52,15 @@ function CopyProjectButton({ project, children }: CopyButtonProps) {
       for (const tag of tags) {
         addProjectToTagInView(tag._id, clonedProject.project_id)
       }
-      updateProjectViewData({ ...project, selected: false })
+      toggleSelectedProject(project.id, false)
+      updateProjectViewData({ ...project })
       setShowModal(false)
     },
     [
       addClonedProjectToViewData,
       addProjectToTagInView,
       project,
+      toggleSelectedProject,
       updateProjectViewData,
     ]
   )

+ 4 - 3
services/web/frontend/js/features/project-list/components/table/cells/action-buttons/trash-project-button.tsx

@@ -14,7 +14,8 @@ type TrashProjectButtonProps = {
 }
 
 function TrashProjectButton({ project, children }: TrashProjectButtonProps) {
-  const { updateProjectViewData } = useProjectListContext()
+  const { toggleSelectedProject, updateProjectViewData } =
+    useProjectListContext()
   const { t } = useTranslation()
   const text = t('trash')
   const [showModal, setShowModal] = useState(false)
@@ -32,13 +33,13 @@ function TrashProjectButton({ project, children }: TrashProjectButtonProps) {
 
   const handleTrashProject = useCallback(async () => {
     await trashProject(project.id)
+    toggleSelectedProject(project.id, false)
     updateProjectViewData({
       ...project,
       trashed: true,
       archived: false,
-      selected: false,
     })
-  }, [project, updateProjectViewData])
+  }, [project, toggleSelectedProject, updateProjectViewData])
 
   if (project.trashed) return null
 

+ 5 - 4
services/web/frontend/js/features/project-list/components/table/cells/action-buttons/unarchive-project-button.tsx

@@ -20,13 +20,14 @@ function UnarchiveProjectButton({
 }: UnarchiveProjectButtonProps) {
   const { t } = useTranslation()
   const text = t('unarchive')
-  const { updateProjectViewData } = useProjectListContext()
+  const { toggleSelectedProject, updateProjectViewData } =
+    useProjectListContext()
 
   const handleUnarchiveProject = useCallback(async () => {
     await unarchiveProject(project.id)
-
-    updateProjectViewData({ ...project, archived: false, selected: false })
-  }, [project, updateProjectViewData])
+    toggleSelectedProject(project.id, false)
+    updateProjectViewData({ ...project, archived: false })
+  }, [project, toggleSelectedProject, updateProjectViewData])
 
   if (!project.archived) return null
 

+ 5 - 3
services/web/frontend/js/features/project-list/components/table/cells/action-buttons/untrash-project-button.tsx

@@ -20,12 +20,14 @@ function UntrashProjectButton({
 }: UntrashProjectButtonProps) {
   const { t } = useTranslation()
   const text = t('untrash')
-  const { updateProjectViewData } = useProjectListContext()
+  const { toggleSelectedProject, updateProjectViewData } =
+    useProjectListContext()
 
   const handleUntrashProject = useCallback(async () => {
     await untrashProject(project.id)
-    updateProjectViewData({ ...project, trashed: false, selected: false })
-  }, [project, updateProjectViewData])
+    toggleSelectedProject(project.id, false)
+    updateProjectViewData({ ...project, trashed: false })
+  }, [project, toggleSelectedProject, updateProjectViewData])
 
   if (!project.trashed) return null
 

+ 25 - 0
services/web/frontend/js/features/project-list/components/table/project-checkbox.tsx

@@ -0,0 +1,25 @@
+import { memo, useCallback } from 'react'
+import { useProjectListContext } from '@/features/project-list/context/project-list-context'
+
+export const ProjectCheckbox = memo<{ projectId: string }>(({ projectId }) => {
+  const { selectedProjectIds, toggleSelectedProject } = useProjectListContext()
+
+  const handleCheckboxChange = useCallback(
+    event => {
+      toggleSelectedProject(projectId, event.target.checked)
+    },
+    [projectId, toggleSelectedProject]
+  )
+
+  return (
+    <input
+      type="checkbox"
+      id={`select-project-${projectId}`}
+      checked={selectedProjectIds.has(projectId)}
+      onChange={handleCheckboxChange}
+      data-project-id={projectId}
+    />
+  )
+})
+
+ProjectCheckbox.displayName = 'ProjectCheckbox'

+ 5 - 20
services/web/frontend/js/features/project-list/components/table/project-list-table-row.tsx

@@ -1,41 +1,25 @@
-import { useCallback } from 'react'
+import { memo } from 'react'
 import { useTranslation } from 'react-i18next'
 import InlineTags from './cells/inline-tags'
 import OwnerCell from './cells/owner-cell'
 import LastUpdatedCell from './cells/last-updated-cell'
 import ActionsCell from './cells/actions-cell'
 import ActionsDropdown from '../dropdown/actions-dropdown'
-import { useProjectListContext } from '../../context/project-list-context'
 import { getOwnerName } from '../../util/project'
 import { Project } from '../../../../../../types/project/dashboard/api'
+import { ProjectCheckbox } from './project-checkbox'
 
 type ProjectListTableRowProps = {
   project: Project
 }
-export default function ProjectListTableRow({
-  project,
-}: ProjectListTableRowProps) {
+function ProjectListTableRow({ project }: ProjectListTableRowProps) {
   const { t } = useTranslation()
   const ownerName = getOwnerName(project)
-  const { updateProjectViewData } = useProjectListContext()
-
-  const handleCheckboxChange = useCallback(
-    (event: React.ChangeEvent<HTMLInputElement>) => {
-      updateProjectViewData({ ...project, selected: event.target.checked })
-    },
-    [project, updateProjectViewData]
-  )
 
   return (
     <tr>
       <td className="dash-cell-checkbox hidden-xs">
-        <input
-          type="checkbox"
-          id={`select-project-${project.id}`}
-          checked={project.selected === true}
-          onChange={handleCheckboxChange}
-          data-project-id={project.id}
-        />
+        <ProjectCheckbox projectId={project.id} />
         <label htmlFor={`select-project-${project.id}`} className="sr-only">
           {t('select_project', { project: project.name })}
         </label>
@@ -68,3 +52,4 @@ export default function ProjectListTableRow({
     </tr>
   )
 }
+export default memo(ProjectListTableRow)

+ 3 - 2
services/web/frontend/js/features/project-list/components/table/project-tools/buttons/archive-projects-button.tsx

@@ -9,7 +9,8 @@ import { archiveProject } from '../../../../util/api'
 import { Project } from '../../../../../../../../types/project/dashboard/api'
 
 function ArchiveProjectsButton() {
-  const { selectedProjects, updateProjectViewData } = useProjectListContext()
+  const { selectedProjects, toggleSelectedProject, updateProjectViewData } =
+    useProjectListContext()
   const { t } = useTranslation()
   const text = t('archive')
 
@@ -29,10 +30,10 @@ function ArchiveProjectsButton() {
   const handleArchiveProject = async (project: Project) => {
     await archiveProject(project.id)
 
+    toggleSelectedProject(project.id, false)
     updateProjectViewData({
       ...project,
       archived: true,
-      selected: false,
       trashed: false,
     })
   }

+ 3 - 2
services/web/frontend/js/features/project-list/components/table/project-tools/buttons/trash-projects-button.tsx

@@ -9,7 +9,8 @@ import { trashProject } from '../../../../util/api'
 import { Project } from '../../../../../../../../types/project/dashboard/api'
 
 function TrashProjectsButton() {
-  const { selectedProjects, updateProjectViewData } = useProjectListContext()
+  const { selectedProjects, toggleSelectedProject, updateProjectViewData } =
+    useProjectListContext()
   const { t } = useTranslation()
   const text = t('trash')
 
@@ -29,11 +30,11 @@ function TrashProjectsButton() {
   const handleTrashProject = async (project: Project) => {
     await trashProject(project.id)
 
+    toggleSelectedProject(project.id, false)
     updateProjectViewData({
       ...project,
       trashed: true,
       archived: false,
-      selected: false,
     })
   }
 

+ 4 - 2
services/web/frontend/js/features/project-list/components/table/project-tools/buttons/unarchive-projects-button.tsx

@@ -5,13 +5,15 @@ import { useProjectListContext } from '../../../../context/project-list-context'
 import { unarchiveProject } from '../../../../util/api'
 
 function UnarchiveProjectsButton() {
-  const { selectedProjects, updateProjectViewData } = useProjectListContext()
+  const { selectedProjects, toggleSelectedProject, updateProjectViewData } =
+    useProjectListContext()
   const { t } = useTranslation()
 
   const handleUnarchiveProjects = async () => {
     for (const project of selectedProjects) {
       await unarchiveProject(project.id)
-      updateProjectViewData({ ...project, archived: false, selected: false })
+      toggleSelectedProject(project.id, false)
+      updateProjectViewData({ ...project, archived: false })
     }
   }
 

+ 4 - 2
services/web/frontend/js/features/project-list/components/table/project-tools/buttons/untrash-projects-button.tsx

@@ -5,13 +5,15 @@ import { useProjectListContext } from '../../../../context/project-list-context'
 import { untrashProject } from '../../../../util/api'
 
 function UntrashProjectsButton() {
-  const { selectedProjects, updateProjectViewData } = useProjectListContext()
+  const { selectedProjects, toggleSelectedProject, updateProjectViewData } =
+    useProjectListContext()
   const { t } = useTranslation()
 
   const handleUntrashProjects = async () => {
     for (const project of selectedProjects) {
       await untrashProject(project.id)
-      updateProjectViewData({ ...project, trashed: false, selected: false })
+      toggleSelectedProject(project.id, false)
+      updateProjectViewData({ ...project, trashed: false })
     }
   }
 

+ 3 - 3
services/web/frontend/js/features/project-list/components/table/project-tools/menu-items/copy-project-menu-item.tsx

@@ -13,7 +13,7 @@ function CopyProjectMenuItem() {
   const {
     addClonedProjectToViewData,
     addProjectToTagInView,
-    updateProjectViewData,
+    toggleSelectedProject,
     selectedProjects,
   } = useProjectListContext()
   const { t } = useTranslation()
@@ -43,7 +43,7 @@ function CopyProjectMenuItem() {
       for (const tag of tags) {
         addProjectToTagInView(tag._id, clonedProject.project_id)
       }
-      updateProjectViewData({ ...project, selected: false })
+      toggleSelectedProject(project.id, false)
 
       if (isMounted.current) {
         setShowModal(false)
@@ -54,7 +54,7 @@ function CopyProjectMenuItem() {
       selectedProjects,
       addClonedProjectToViewData,
       addProjectToTagInView,
-      updateProjectViewData,
+      toggleSelectedProject,
     ]
   )
 

+ 41 - 10
services/web/frontend/js/features/project-list/context/project-list-context.tsx

@@ -96,6 +96,9 @@ export type ProjectListContextValue = {
   searchText: string
   setSearchText: React.Dispatch<React.SetStateAction<string>>
   selectedProjects: Project[]
+  selectedProjectIds: Set<string>
+  setSelectedProjectIds: React.Dispatch<React.SetStateAction<Set<string>>>
+  toggleSelectedProject: (projectId: string, selected?: boolean) => void
   hiddenProjectsCount: number
   loadMoreCount: number
   showAllProjects: () => void
@@ -262,22 +265,44 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
     setMaxVisibleProjects(maxVisibleProjects + loadMoreCount)
   }, [maxVisibleProjects, loadMoreCount])
 
+  const [selectedProjectIds, setSelectedProjectIds] = useState(
+    () => new Set<string>()
+  )
+
+  const toggleSelectedProject = useCallback(
+    (projectId: string, selected?: boolean) => {
+      setSelectedProjectIds(selectedProjectIds => {
+        if (selected === true) {
+          selectedProjectIds.add(projectId)
+        } else if (selected === false) {
+          selectedProjectIds.delete(projectId)
+        } else if (selectedProjectIds.has(projectId)) {
+          selectedProjectIds.delete(projectId)
+        } else {
+          selectedProjectIds.add(projectId)
+        }
+        return new Set([...selectedProjectIds])
+      })
+    },
+    []
+  )
+
   const selectedProjects = useMemo(() => {
-    return visibleProjects.filter(project => project.selected)
-  }, [visibleProjects])
+    return visibleProjects.filter(project => selectedProjectIds.has(project.id))
+  }, [selectedProjectIds, visibleProjects])
 
   const selectOrUnselectAllProjects = useCallback(
     checked => {
-      const visibleProjectIds = visibleProjects.map(p => p.id)
-      setLoadedProjects(loadedProjects =>
-        loadedProjects.map(p => {
-          if (visibleProjectIds.includes(p.id)) {
-            return { ...p, selected: checked }
+      setSelectedProjectIds(selectedProjectIds => {
+        for (const project of visibleProjects) {
+          if (checked) {
+            selectedProjectIds.add(project.id)
           } else {
-            return p
+            selectedProjectIds.delete(project.id)
           }
-        })
-      )
+        }
+        return new Set([...selectedProjectIds])
+      })
     },
     [visibleProjects]
   )
@@ -448,16 +473,19 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
       selectedTagId,
       selectFilter,
       selectedProjects,
+      selectedProjectIds,
       selectOrUnselectAllProjects,
       selectTag,
       searchText,
       setSearchText,
+      setSelectedProjectIds,
       setShowCustomPicker,
       setSort,
       showAllProjects,
       showCustomPicker,
       sort,
       tags,
+      toggleSelectedProject,
       totalProjectsCount,
       untaggedProjectsCount,
       updateProjectViewData,
@@ -483,17 +511,20 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
       removeProjectFromView,
       selectedTagId,
       selectFilter,
+      selectedProjectIds,
       selectedProjects,
       selectOrUnselectAllProjects,
       selectTag,
       searchText,
       setSearchText,
+      setSelectedProjectIds,
       setShowCustomPicker,
       setSort,
       showAllProjects,
       showCustomPicker,
       sort,
       tags,
+      toggleSelectedProject,
       totalProjectsCount,
       untaggedProjectsCount,
       updateProjectViewData,