Merge pull request #12448 from overleaf/jpa-compile-and-download-pdf

[web] add button to project dashboard for compiling and downloading PDF

GitOrigin-RevId: c243b4a30e4720116d82d9c25bdc8be8825d6d74
This commit is contained in:
Jakob Ackermann
2023-11-29 09:03:59 +00:00
committed by Copybot
parent 7a87bf4288
commit 4636f40f03
12 changed files with 396 additions and 6 deletions
@@ -11,6 +11,8 @@ import UntrashProjectButton from '../table/cells/action-buttons/untrash-project-
import LeaveProjectButton from '../table/cells/action-buttons/leave-project-button'
import DeleteProjectButton from '../table/cells/action-buttons/delete-project-button'
import { Project } from '../../../../../../types/project/dashboard/api'
import CompileAndDownloadProjectPDFButton from '../table/cells/action-buttons/compile-and-download-project-pdf-button'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
type ActionButtonProps = {
project: Project
@@ -30,6 +32,29 @@ function CopyProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
)
}
function CompileAndDownloadProjectPDFButtonMenuItem({
project,
onClick,
}: ActionButtonProps) {
return (
<CompileAndDownloadProjectPDFButton project={project}>
{(text, pendingCompile, downloadProject) => (
<MenuItemButton
onClick={() => downloadProject(onClick)}
className="projects-action-menu-item"
>
{pendingCompile ? (
<Icon type="spinner" spin className="menu-item-button-icon" />
) : (
<Icon type="file-pdf-o" className="menu-item-button-icon" />
)}{' '}
<span className="menu-item-button-text">{text}</span>
</MenuItemButton>
)}
</CompileAndDownloadProjectPDFButton>
)
}
function DownloadProjectButtonMenuItem({
project,
onClick,
@@ -194,6 +219,12 @@ function ActionsDropdown({ project }: ActionDropdownProps) {
project={project}
onClick={handleClose}
/>
{isSplitTestEnabled('download-pdf-dashboard') && (
<CompileAndDownloadProjectPDFButtonMenuItem
project={project}
onClick={handleClose}
/>
)}
<ArchiveProjectButtonMenuItem project={project} onClick={handleClose} />
<TrashProjectButtonMenuItem project={project} onClick={handleClose} />
<UnarchiveProjectButtonMenuItem
@@ -0,0 +1,163 @@
import { useTranslation } from 'react-i18next'
import { memo, useCallback, useState } from 'react'
import { Project } from '../../../../../../../../types/project/dashboard/api'
import Icon from '../../../../../../shared/components/icon'
import Tooltip from '../../../../../../shared/components/tooltip'
import * as eventTracking from '../../../../../../infrastructure/event-tracking'
import { useLocation } from '../../../../../../shared/hooks/use-location'
import useAbortController from '../../../../../../shared/hooks/use-abort-controller'
import { postJSON } from '../../../../../../infrastructure/fetch-json'
import AccessibleModal from '../../../../../../shared/components/accessible-modal'
import { Button, Modal } from 'react-bootstrap'
import { isSmallDevice } from '../../../../../../infrastructure/event-tracking'
type CompileAndDownloadProjectPDFButtonProps = {
project: Project
children: (
text: string,
pendingDownload: boolean,
downloadProject: (fn: () => void) => void
) => React.ReactElement
}
function CompileAndDownloadProjectPDFButton({
project,
children,
}: CompileAndDownloadProjectPDFButtonProps) {
const { t } = useTranslation()
const location = useLocation()
const { signal } = useAbortController()
const [pendingCompile, setPendingCompile] = useState(false)
const downloadProject = useCallback(
onDone => {
setPendingCompile(pendingCompile => {
if (pendingCompile) return true
eventTracking.sendMB('project-list-page-interaction', {
action: 'downloadPDF',
projectId: project.id,
isSmallDevice,
})
postJSON(`/project/${project.id}/compile`, {
body: {
check: 'silent',
draft: false,
incrementalCompilesEnabled: true,
},
signal,
})
.catch(() => ({ status: 'error' }))
.then(data => {
setPendingCompile(false)
if (data.status === 'success') {
const outputFile = data.outputFiles
.filter((file: { path: string }) => file.path === 'output.pdf')
.pop()
const params = new URLSearchParams({
compileGroup: data.compileGroup,
popupDownload: 'true',
})
if (data.clsiServerId) {
params.set('clsiserverid', data.clsiServerId)
}
// Note: Triggering concurrent downloads does not work.
// Note: This is affecting the download of .zip files as well.
// When creating a dynamic `a` element with `download` attribute,
// another "actual" UI click is needed to trigger downloads.
// Forwarding the click `event` to the dynamic `a` element does
// not work either.
location.assign(
`/download/project/${project.id}/build/${outputFile.build}/output/output.pdf?${params}`
)
onDone()
} else {
setShowErrorModal(true)
}
})
return true
})
},
[project, signal, location]
)
const [showErrorModal, setShowErrorModal] = useState(false)
return (
<>
{children(
pendingCompile ? t('compiling') + '…' : t('download_pdf'),
pendingCompile,
downloadProject
)}
{showErrorModal && (
<CompileErrorModal
project={project}
handleClose={() => {
setShowErrorModal(false)
}}
/>
)}
</>
)
}
function CompileErrorModal({
project,
handleClose,
}: { project: Project } & { handleClose: () => void }) {
const { t } = useTranslation()
return (
<>
<AccessibleModal show onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>
{project.name}: {t('pdf_unavailable_for_download')}
</Modal.Title>
</Modal.Header>
<Modal.Body>{t('generic_linked_file_compile_error')}</Modal.Body>
<Modal.Footer>
<a href={`/project/${project.id}`}>
<Button bsStyle="primary">{t('open_project')}</Button>
</a>
</Modal.Footer>
</AccessibleModal>
</>
)
}
const CompileAndDownloadProjectPDFButtonTooltip = memo(
function CompileAndDownloadProjectPDFButtonTooltip({
project,
}: Pick<CompileAndDownloadProjectPDFButtonProps, 'project'>) {
return (
<CompileAndDownloadProjectPDFButton project={project}>
{(text, pendingCompile, compileAndDownloadProject) => (
<Tooltip
key={`tooltip-compile-and-download-project-${project.id}`}
id={`compile-and-download-project-${project.id}`}
description={text}
overlayProps={{ placement: 'top', trigger: ['hover', 'focus'] }}
>
<button
className="btn btn-link action-btn"
aria-label={text}
onClick={() => compileAndDownloadProject(() => {})}
>
{pendingCompile ? (
<Icon type="spinner" spin />
) : (
<Icon type="file-pdf-o" />
)}
</button>
</Tooltip>
)}
</CompileAndDownloadProjectPDFButton>
)
}
)
export default memo(CompileAndDownloadProjectPDFButton)
export { CompileAndDownloadProjectPDFButtonTooltip }
@@ -17,7 +17,7 @@ function DownloadProjectButton({
children,
}: DownloadProjectButtonProps) {
const { t } = useTranslation()
const text = t('download')
const text = t('download_zip_file')
const location = useLocation()
const downloadProject = useCallback(() => {
@@ -7,6 +7,8 @@ import { UntrashProjectButtonTooltip } from './action-buttons/untrash-project-bu
import { DownloadProjectButtonTooltip } from './action-buttons/download-project-button'
import { LeaveProjectButtonTooltip } from './action-buttons/leave-project-button'
import { DeleteProjectButtonTooltip } from './action-buttons/delete-project-button'
import { CompileAndDownloadProjectPDFButtonTooltip } from './action-buttons/compile-and-download-project-pdf-button'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
type ActionsCellProps = {
project: Project
@@ -17,6 +19,9 @@ export default function ActionsCell({ project }: ActionsCellProps) {
<>
<CopyProjectButtonTooltip project={project} />
<DownloadProjectButtonTooltip project={project} />
{isSplitTestEnabled('download-pdf-dashboard') && (
<CompileAndDownloadProjectPDFButtonTooltip project={project} />
)}
<ArchiveProjectButtonTooltip project={project} />
<TrashProjectButtonTooltip project={project} />
<UnarchiveProjectButtonTooltip project={project} />