mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #9676 from overleaf/jel-project-tools-unarchive
[web] Add unarchive button to project tools GitOrigin-RevId: d1c7c693eb13cb7473b65e311f09ffe7a7f0d88f
This commit is contained in:
parent
9b09f8426d
commit
30fd0bfc9d
5 changed files with 69 additions and 9 deletions
|
@ -0,0 +1,21 @@
|
||||||
|
import { memo } from 'react'
|
||||||
|
import { Button } from 'react-bootstrap'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||||
|
import { unarchiveProject } from '../../../../util/api'
|
||||||
|
|
||||||
|
function UnarchiveProjectsButton() {
|
||||||
|
const { selectedProjects, updateProjectViewData } = useProjectListContext()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const handleUnarchiveProjects = async () => {
|
||||||
|
for (const project of selectedProjects) {
|
||||||
|
await unarchiveProject(project.id)
|
||||||
|
updateProjectViewData({ ...project, archived: false, selected: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Button onClick={handleUnarchiveProjects}>{t('unarchive')}</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(UnarchiveProjectsButton)
|
|
@ -1,4 +1,4 @@
|
||||||
import { memo, useCallback } from 'react'
|
import { memo } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import { Button } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||||
|
@ -8,12 +8,12 @@ function UntrashProjectsButton() {
|
||||||
const { selectedProjects, updateProjectViewData } = useProjectListContext()
|
const { selectedProjects, updateProjectViewData } = useProjectListContext()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleUntrashProjects = useCallback(async () => {
|
const handleUntrashProjects = async () => {
|
||||||
for (const [, project] of Object.entries(selectedProjects)) {
|
for (const project of selectedProjects) {
|
||||||
await untrashProject(project.id)
|
await untrashProject(project.id)
|
||||||
updateProjectViewData({ ...project, trashed: false, selected: false })
|
updateProjectViewData({ ...project, trashed: false, selected: false })
|
||||||
}
|
}
|
||||||
}, [selectedProjects, updateProjectViewData])
|
}
|
||||||
|
|
||||||
return <Button onClick={handleUntrashProjects}>{t('untrash')}</Button>
|
return <Button onClick={handleUntrashProjects}>{t('untrash')}</Button>
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useProjectListContext } from '../../../context/project-list-context'
|
||||||
import ArchiveProjectsButton from './buttons/archive-projects-button'
|
import ArchiveProjectsButton from './buttons/archive-projects-button'
|
||||||
import DownloadProjectsButton from './buttons/download-projects-button'
|
import DownloadProjectsButton from './buttons/download-projects-button'
|
||||||
import TrashProjectsButton from './buttons/trash-projects-button'
|
import TrashProjectsButton from './buttons/trash-projects-button'
|
||||||
|
import UnarchiveProjectsButton from './buttons/unarchive-projects-button'
|
||||||
import UntrashProjectsButton from './buttons/untrash-projects-button'
|
import UntrashProjectsButton from './buttons/untrash-projects-button'
|
||||||
|
|
||||||
function ProjectTools() {
|
function ProjectTools() {
|
||||||
|
@ -18,6 +19,7 @@ function ProjectTools() {
|
||||||
|
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{filter === 'trashed' && <UntrashProjectsButton />}
|
{filter === 'trashed' && <UntrashProjectsButton />}
|
||||||
|
{filter === 'archived' && <UnarchiveProjectsButton />}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</ButtonToolbar>
|
</ButtonToolbar>
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,11 @@ import fetchMock from 'fetch-mock'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import ProjectListRoot from '../../../../../frontend/js/features/project-list/components/project-list-root'
|
import ProjectListRoot from '../../../../../frontend/js/features/project-list/components/project-list-root'
|
||||||
import { renderWithProjectListContext } from '../helpers/render-with-context'
|
import { renderWithProjectListContext } from '../helpers/render-with-context'
|
||||||
import { owner, makeLongProjectList } from '../fixtures/projects-data'
|
import {
|
||||||
|
owner,
|
||||||
|
archivedProjects,
|
||||||
|
makeLongProjectList,
|
||||||
|
} from '../fixtures/projects-data'
|
||||||
const { fullList, currentList, trashedList } = makeLongProjectList(40)
|
const { fullList, currentList, trashedList } = makeLongProjectList(40)
|
||||||
|
|
||||||
const userId = owner.id
|
const userId = owner.id
|
||||||
|
@ -191,10 +195,43 @@ describe('<ProjectListRoot />', function () {
|
||||||
// first one is the select all checkbox
|
// first one is the select all checkbox
|
||||||
fireEvent.click(allCheckboxes[1])
|
fireEvent.click(allCheckboxes[1])
|
||||||
project1Id = allCheckboxes[1].getAttribute('data-project-id')
|
project1Id = allCheckboxes[1].getAttribute('data-project-id')
|
||||||
|
|
||||||
|
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not show the archive button in toolbar when archive view selected', function () {
|
it('does not show the archive button in toolbar when archive view selected', function () {
|
||||||
expect(screen.queryByLabelText('Archive')).to.be.null
|
expect(screen.queryByLabelText('Archive')).to.be.null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('restores all projects when selected', async function () {
|
||||||
|
fetchMock.delete(`express:/project/:id/archive`, {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
const unarchiveButton =
|
||||||
|
within(actionsToolbar).getByText<HTMLInputElement>('Restore')
|
||||||
|
fireEvent.click(unarchiveButton)
|
||||||
|
|
||||||
|
await fetchMock.flush(true)
|
||||||
|
expect(fetchMock.done()).to.be.true
|
||||||
|
|
||||||
|
screen.getByText('No projects')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('only unarchive the selected projects', async function () {
|
||||||
|
// beforeEach selected all, so uncheck the 1st project
|
||||||
|
fireEvent.click(allCheckboxes[1])
|
||||||
|
|
||||||
|
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||||
|
expect(allCheckboxesChecked.length).to.equal(
|
||||||
|
archivedProjects.length - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
await fetchMock.flush(true)
|
||||||
|
expect(fetchMock.done()).to.be.true
|
||||||
|
|
||||||
|
expect(screen.queryByText('No projects')).to.be.null
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('trashed projects', function () {
|
describe('trashed projects', function () {
|
||||||
|
|
|
@ -132,13 +132,13 @@ export const projectsData: Array<Project> = [
|
||||||
trashedAndNotOwnedProject,
|
trashedAndNotOwnedProject,
|
||||||
]
|
]
|
||||||
|
|
||||||
export const currentProjects: Array<Project> = projectsData.filter(
|
export const archivedProjects = projectsData.filter(({ archived }) => archived)
|
||||||
|
|
||||||
|
export const currentProjects = projectsData.filter(
|
||||||
({ archived, trashed }) => !archived && !trashed
|
({ archived, trashed }) => !archived && !trashed
|
||||||
)
|
)
|
||||||
|
|
||||||
export const trashedProjects: Array<Project> = projectsData.filter(
|
export const trashedProjects = projectsData.filter(({ trashed }) => trashed)
|
||||||
({ trashed }) => trashed
|
|
||||||
)
|
|
||||||
|
|
||||||
export const makeLongProjectList = (listLength: number) => {
|
export const makeLongProjectList = (listLength: number) => {
|
||||||
const longList = [...projectsData]
|
const longList = [...projectsData]
|
||||||
|
|
Loading…
Reference in a new issue