mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-04 20:07:15 +00:00
Merge pull request #9662 from overleaf/jel-checked-dash
[web] Refactor checked projects GitOrigin-RevId: 5696cf8beb69d479d95c05273b11625c277b8761
This commit is contained in:
parent
171cd4f21c
commit
ab852d1955
8 changed files with 55 additions and 44 deletions
|
@ -22,7 +22,7 @@ const UserController = require('../User/UserController')
|
|||
|
||||
/** @typedef {import("./types").GetProjectsRequest} GetProjectsRequest */
|
||||
/** @typedef {import("./types").GetProjectsResponse} GetProjectsResponse */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Project} Project */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").ProjectApi} ProjectApi */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Filters} Filters */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Page} Page */
|
||||
/** @typedef {import("../../../../types/project/dashboard/api").Sort} Sort */
|
||||
|
@ -308,7 +308,7 @@ async function getProjectsJson(req, res) {
|
|||
* @param {Filters} filters
|
||||
* @param {Sort} sort
|
||||
* @param {Page} page
|
||||
* @returns {Promise<{totalSize: number, projects: Project[]}>}
|
||||
* @returns {Promise<{totalSize: number, projects: ProjectApi[]}>}
|
||||
* @private
|
||||
*/
|
||||
async function _getProjects(
|
||||
|
|
|
@ -17,23 +17,14 @@ export default function ProjectListTableRow({
|
|||
}: ProjectListTableRowProps) {
|
||||
const { t } = useTranslation()
|
||||
const ownerName = getOwnerName(project)
|
||||
const { selectedProjects, setSelectedProjects } = useProjectListContext()
|
||||
const { updateProjectViewData } = useProjectListContext()
|
||||
|
||||
const handleCheckboxChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const checked = event.target.checked
|
||||
setSelectedProjects(selectedProjects => {
|
||||
let projects = [...selectedProjects]
|
||||
if (checked) {
|
||||
projects.push(project)
|
||||
} else {
|
||||
const projectId = event.target.getAttribute('data-project-id')
|
||||
projects = projects.filter(p => p.id !== projectId)
|
||||
}
|
||||
return projects
|
||||
})
|
||||
project.selected = event.target.checked
|
||||
updateProjectViewData(project)
|
||||
},
|
||||
[project, setSelectedProjects]
|
||||
[project, updateProjectViewData]
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -42,7 +33,7 @@ export default function ProjectListTableRow({
|
|||
<input
|
||||
type="checkbox"
|
||||
id={`select-project-${project.id}`}
|
||||
checked={selectedProjects.includes(project)}
|
||||
checked={project.selected === true}
|
||||
onChange={handleCheckboxChange}
|
||||
data-project-id={project.id}
|
||||
/>
|
||||
|
|
|
@ -21,19 +21,19 @@ const SortByButton = withContent(SortBtn)
|
|||
|
||||
function ProjectListTable() {
|
||||
const { t } = useTranslation()
|
||||
const { visibleProjects, sort, selectedProjects, setSelectedProjects } =
|
||||
useProjectListContext()
|
||||
const {
|
||||
visibleProjects,
|
||||
sort,
|
||||
selectedProjects,
|
||||
selectOrUnselectAllProjects,
|
||||
} = useProjectListContext()
|
||||
const { handleSort } = useSort()
|
||||
|
||||
const handleAllProjectsCheckboxChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
setSelectedProjects(visibleProjects)
|
||||
} else {
|
||||
setSelectedProjects([])
|
||||
}
|
||||
selectOrUnselectAllProjects(event.target.checked)
|
||||
},
|
||||
[setSelectedProjects, visibleProjects]
|
||||
[selectOrUnselectAllProjects]
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
@ -8,8 +8,7 @@ import { useProjectListContext } from '../../../../context/project-list-context'
|
|||
import { archiveProject } from '../../../../util/api'
|
||||
|
||||
function ArchiveProjectsButton() {
|
||||
const { selectedProjects, updateProjectViewData, setSelectedProjects } =
|
||||
useProjectListContext()
|
||||
const { selectedProjects, updateProjectViewData } = useProjectListContext()
|
||||
const { t } = useTranslation()
|
||||
const text = t('archive')
|
||||
|
||||
|
@ -31,10 +30,10 @@ function ArchiveProjectsButton() {
|
|||
await archiveProject(project.id)
|
||||
// update view
|
||||
project.archived = true
|
||||
project.selected = false
|
||||
updateProjectViewData(project)
|
||||
}
|
||||
setSelectedProjects([])
|
||||
}, [selectedProjects, setSelectedProjects, updateProjectViewData])
|
||||
}, [selectedProjects, updateProjectViewData])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -6,7 +6,8 @@ import * as eventTracking from '../../../../../../infrastructure/event-tracking'
|
|||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
|
||||
function DownloadProjectsButton() {
|
||||
const { selectedProjects, setSelectedProjects } = useProjectListContext()
|
||||
const { selectedProjects, selectOrUnselectAllProjects } =
|
||||
useProjectListContext()
|
||||
const { t } = useTranslation()
|
||||
const text = t('download')
|
||||
|
||||
|
@ -23,8 +24,9 @@ function DownloadProjectsButton() {
|
|||
`/project/download/zip?project_ids=${projectIds.join(',')}`
|
||||
)
|
||||
|
||||
setSelectedProjects([])
|
||||
}, [projectIds, setSelectedProjects])
|
||||
const selected = false
|
||||
selectOrUnselectAllProjects(selected)
|
||||
}, [projectIds, selectOrUnselectAllProjects])
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
|
|
|
@ -8,8 +8,7 @@ import { useProjectListContext } from '../../../../context/project-list-context'
|
|||
import { trashProject } from '../../../../util/api'
|
||||
|
||||
function TrashProjectsButton() {
|
||||
const { selectedProjects, setSelectedProjects, updateProjectViewData } =
|
||||
useProjectListContext()
|
||||
const { selectedProjects, updateProjectViewData } = useProjectListContext()
|
||||
const { t } = useTranslation()
|
||||
const text = t('trash')
|
||||
|
||||
|
@ -32,10 +31,10 @@ function TrashProjectsButton() {
|
|||
// update view
|
||||
project.trashed = true
|
||||
project.archived = false
|
||||
project.selected = false
|
||||
updateProjectViewData(project)
|
||||
}
|
||||
setSelectedProjects([])
|
||||
}, [selectedProjects, setSelectedProjects, updateProjectViewData])
|
||||
}, [selectedProjects, updateProjectViewData])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
uniqBy,
|
||||
without,
|
||||
} from 'lodash'
|
||||
import {
|
||||
import React, {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
|
@ -64,6 +64,7 @@ export const UNCATEGORIZED_KEY = 'uncategorized'
|
|||
|
||||
type ProjectListContextValue = {
|
||||
addClonedProjectToViewData: (project: Project) => void
|
||||
selectOrUnselectAllProjects: React.Dispatch<React.SetStateAction<boolean>>
|
||||
visibleProjects: Project[]
|
||||
totalProjectsCount: number
|
||||
error: Error | null
|
||||
|
@ -86,7 +87,6 @@ type ProjectListContextValue = {
|
|||
searchText: string
|
||||
setSearchText: React.Dispatch<React.SetStateAction<string>>
|
||||
selectedProjects: Project[]
|
||||
setSelectedProjects: React.Dispatch<React.SetStateAction<Project[]>>
|
||||
hiddenProjects: Project[]
|
||||
loadMoreCount: number
|
||||
showAllProjects: () => void
|
||||
|
@ -122,7 +122,6 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
>('project-list-selected-tag-id', undefined)
|
||||
const [tags, setTags] = useState<Tag[]>(getMeta('ol-tags', []) as Tag[])
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [selectedProjects, setSelectedProjects] = useState<Project[]>([])
|
||||
|
||||
const {
|
||||
isLoading: loading,
|
||||
|
@ -240,6 +239,21 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
}
|
||||
}, [visibleProjects, hiddenProjects, loadMoreCount])
|
||||
|
||||
const selectedProjects = useMemo(() => {
|
||||
return visibleProjects.filter(project => project.selected)
|
||||
}, [visibleProjects])
|
||||
|
||||
const selectOrUnselectAllProjects = useCallback(
|
||||
checked => {
|
||||
const projects = visibleProjects.map(project => {
|
||||
project.selected = checked
|
||||
return project
|
||||
})
|
||||
setVisibleProjects(projects)
|
||||
},
|
||||
[visibleProjects]
|
||||
)
|
||||
|
||||
const untaggedProjectsCount = useMemo(() => {
|
||||
const taggedProjectIds = uniq(flatten(tags.map(tag => tag.project_ids)))
|
||||
return loadedProjects.filter(
|
||||
|
@ -254,9 +268,10 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
(filter: Filter) => {
|
||||
setFilter(filter)
|
||||
setSelectedTagId(undefined)
|
||||
setSelectedProjects([])
|
||||
const selected = false
|
||||
selectOrUnselectAllProjects(selected)
|
||||
},
|
||||
[setFilter, setSelectedTagId]
|
||||
[selectOrUnselectAllProjects, setFilter, setSelectedTagId]
|
||||
)
|
||||
|
||||
const selectTag = useCallback(
|
||||
|
@ -354,6 +369,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
() => ({
|
||||
addTag,
|
||||
addClonedProjectToViewData,
|
||||
selectOrUnselectAllProjects,
|
||||
deleteTag,
|
||||
error,
|
||||
filter,
|
||||
|
@ -371,7 +387,6 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
selectTag,
|
||||
searchText,
|
||||
setSearchText,
|
||||
setSelectedProjects,
|
||||
setSort,
|
||||
showAllProjects,
|
||||
sort,
|
||||
|
@ -384,6 +399,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
[
|
||||
addTag,
|
||||
addClonedProjectToViewData,
|
||||
selectOrUnselectAllProjects,
|
||||
deleteTag,
|
||||
error,
|
||||
filter,
|
||||
|
@ -401,7 +417,6 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
|
|||
selectTag,
|
||||
searchText,
|
||||
setSearchText,
|
||||
setSelectedProjects,
|
||||
setSort,
|
||||
showAllProjects,
|
||||
sort,
|
||||
|
|
|
@ -32,11 +32,11 @@ export type UserRef = {
|
|||
lastName: string
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
export type ProjectApi = {
|
||||
id: string
|
||||
name: string
|
||||
owner?: UserRef
|
||||
lastUpdated: string
|
||||
lastUpdated: Date
|
||||
lastUpdatedBy: UserRef | null
|
||||
archived: boolean
|
||||
trashed: boolean
|
||||
|
@ -44,6 +44,11 @@ export type Project = {
|
|||
source: 'owner' | 'invite' | 'token'
|
||||
}
|
||||
|
||||
export type Project = ProjectApi & {
|
||||
lastUpdated: string
|
||||
selected?: boolean
|
||||
}
|
||||
|
||||
export type GetProjectsResponseBody = {
|
||||
totalSize: number
|
||||
projects: Project[]
|
||||
|
|
Loading…
Reference in a new issue