mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-16 03:46:21 +00:00
Merge pull request #9784 from overleaf/jel-project-tools-copy
[web] Add copy option to project tools dropdown GitOrigin-RevId: 028ba62ed858376e472a5e4e5520079cd5f60ec5
This commit is contained in:
parent
f2748c13d7
commit
f68d0d1e5f
5 changed files with 118 additions and 3 deletions
|
@ -354,6 +354,7 @@
|
|||
"logs_and_output_files": "",
|
||||
"looks_like_youre_at": "",
|
||||
"main_file_not_found": "",
|
||||
"make_a_copy": "",
|
||||
"make_email_primary_description": "",
|
||||
"make_primary": "",
|
||||
"make_private": "",
|
||||
|
|
|
@ -2,6 +2,7 @@ import { memo } from 'react'
|
|||
import { Dropdown } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ControlledDropdown from '../../../../../../shared/components/controlled-dropdown'
|
||||
import CopyProjectMenuItem from '../menu-items/copy-project-menu-item'
|
||||
import RenameProjectMenuItem from '../menu-items/rename-project-menu-item'
|
||||
|
||||
function ProjectToolsMoreDropdownButton() {
|
||||
|
@ -11,6 +12,7 @@ function ProjectToolsMoreDropdownButton() {
|
|||
<Dropdown.Toggle>{t('more')}</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
<RenameProjectMenuItem />
|
||||
<CopyProjectMenuItem />
|
||||
</Dropdown.Menu>
|
||||
</ControlledDropdown>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import { memo, useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { MenuItem } from 'react-bootstrap'
|
||||
import CloneProjectModal from '../../../../../clone-project-modal/components/clone-project-modal'
|
||||
import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
|
||||
import { useProjectListContext } from '../../../../context/project-list-context'
|
||||
import * as eventTracking from '../../../../../../infrastructure/event-tracking'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
|
||||
function CopyProjectMenuItem() {
|
||||
const {
|
||||
addClonedProjectToViewData,
|
||||
updateProjectViewData,
|
||||
selectedProjects,
|
||||
} = useProjectListContext()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const isMounted = useIsMounted()
|
||||
|
||||
const handleOpenModal = useCallback(() => {
|
||||
setShowModal(true)
|
||||
}, [])
|
||||
|
||||
const handleCloseModal = useCallback(() => {
|
||||
if (isMounted.current) {
|
||||
setShowModal(false)
|
||||
}
|
||||
}, [isMounted])
|
||||
|
||||
const handleAfterCloned = (clonedProject: Project) => {
|
||||
const project = selectedProjects[0]
|
||||
eventTracking.send(
|
||||
'project-list-page-interaction',
|
||||
'project action',
|
||||
'Clone'
|
||||
)
|
||||
addClonedProjectToViewData(clonedProject)
|
||||
updateProjectViewData({ ...project, selected: false })
|
||||
setShowModal(false)
|
||||
}
|
||||
|
||||
if (selectedProjects.length !== 1) return null
|
||||
|
||||
if (selectedProjects[0].archived || selectedProjects[0].trashed) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<CloneProjectModal
|
||||
show={showModal}
|
||||
handleHide={handleCloseModal}
|
||||
handleAfterCloned={handleAfterCloned}
|
||||
projectId={selectedProjects[0].id}
|
||||
projectName={selectedProjects[0].name}
|
||||
/>
|
||||
<MenuItem onClick={handleOpenModal}>{t('make_a_copy')}</MenuItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(CopyProjectMenuItem)
|
|
@ -1904,5 +1904,6 @@
|
|||
"overleaf_labs": "Overleaf Labs",
|
||||
"show_x_more": "Show __x__ more",
|
||||
"show_x_more_projects": "Show __x__ more projects",
|
||||
"showing_x_out_of_n_projects": "Showing __x__ out of __n__ projects."
|
||||
"showing_x_out_of_n_projects": "Showing __x__ out of __n__ projects.",
|
||||
"make_a_copy": "Make a copy"
|
||||
}
|
||||
|
|
|
@ -341,7 +341,7 @@ describe('<ProjectListRoot />', function () {
|
|||
.null
|
||||
})
|
||||
|
||||
it('opens the rename modal, validates name, and can rename the project', async function () {
|
||||
it('opens the rename modal, and can rename the project, and view updated', async function () {
|
||||
fetchMock.post(`express:/project/:id/rename`, {
|
||||
status: 200,
|
||||
})
|
||||
|
@ -396,6 +396,54 @@ describe('<ProjectListRoot />', function () {
|
|||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||
expect(allCheckboxesChecked.length).to.equal(0)
|
||||
})
|
||||
|
||||
it('opens the copy modal, can copy the project, and view updated', async function () {
|
||||
const tableRows = screen.getAllByRole('row')
|
||||
const linkForProjectToCopy = within(tableRows[1]).getByRole('link')
|
||||
const projectNameToCopy = linkForProjectToCopy.textContent || '' // needed for type checking
|
||||
screen.findByText(projectNameToCopy) // make sure not just empty string
|
||||
const copiedProjectName = `${projectNameToCopy} (Copy)`
|
||||
fetchMock.post(`express:/project/:id/clone`, {
|
||||
status: 200,
|
||||
body: {
|
||||
name: copiedProjectName,
|
||||
lastUpdated: new Date(),
|
||||
project_id: userId,
|
||||
owner_ref: userId,
|
||||
owner,
|
||||
id: '6328e14abec0df019fce0be5',
|
||||
lastUpdatedBy: owner,
|
||||
accessLevel: 'owner',
|
||||
source: 'owner',
|
||||
trashed: false,
|
||||
archived: false,
|
||||
},
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
const moreDropdown =
|
||||
within(actionsToolbar).getByText<HTMLElement>('More')
|
||||
fireEvent.click(moreDropdown)
|
||||
})
|
||||
|
||||
const copyButton =
|
||||
within(actionsToolbar).getByText<HTMLInputElement>('Make a copy')
|
||||
fireEvent.click(copyButton)
|
||||
|
||||
// confirm in modal
|
||||
const copyConfirmButton = document.querySelector(
|
||||
'button[type="submit"]'
|
||||
) as HTMLElement
|
||||
fireEvent.click(copyConfirmButton)
|
||||
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
|
||||
expect(sendSpy).to.be.calledOnce
|
||||
expect(sendSpy).calledWith('project-list-page-interaction')
|
||||
|
||||
screen.findByText(copiedProjectName)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -443,7 +491,6 @@ describe('<ProjectListRoot />', function () {
|
|||
fireEvent.click(copyButton)
|
||||
|
||||
// confirm in modal
|
||||
// const copyConfirmButton = screen.getByText('Copy')
|
||||
const copyConfirmButton = document.querySelector(
|
||||
'button[type="submit"]'
|
||||
) as HTMLElement
|
||||
|
@ -452,6 +499,9 @@ describe('<ProjectListRoot />', function () {
|
|||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
|
||||
expect(sendSpy).to.be.calledOnce
|
||||
expect(sendSpy).calledWith('project-list-page-interaction')
|
||||
|
||||
expect(screen.queryByText(copiedProjectName)).to.be.null
|
||||
|
||||
const yourProjectFilter = screen.getAllByText('Your Projects')[0]
|
||||
|
|
Loading…
Add table
Reference in a new issue