1
0
Fork 0
mirror of https://github.com/overleaf/overleaf.git synced 2025-04-17 06:17:40 +00:00

Merge pull request from overleaf/jel-download-dropdown-menu

Add output download dropdown

GitOrigin-RevId: 1f97f0d681679268d28796fbc8251ffbc6e98433
This commit is contained in:
Jessica Lawshe 2020-10-20 07:44:32 -05:00 committed by Copybot
parent f8a8c9bbd6
commit 6fe36b6acb
9 changed files with 289 additions and 2 deletions
services/web
app/views/project/editor
frontend
locales
test/frontend/features/preview/components

View file

@ -16,6 +16,8 @@ div.full-size.pdf(ng-controller="PdfController")
on-set-draft-mode="setDraftMode"
on-set-syntax-check="setSyntaxCheck"
on-toggle-logs="toggleLogs"
output-files="pdf.outputFiles"
pdf-download-url="pdf.downloadUrl"
show-logs="shouldShowLogs"
)
else

View file

@ -1,4 +1,6 @@
[
"download_pdf",
"download_file",
"n_warnings",
"n_warnings_plural",
"n_errors",
@ -16,6 +18,7 @@
"auto_compile",
"on",
"off",
"other_output_files",
"compile_mode",
"normal",
"fast",
@ -25,6 +28,7 @@
"loading",
"no_messages",
"send_first_message",
"toggle_output_files_list",
"your_message",
"your_project_has_errors",
"view_warnings",

View file

@ -0,0 +1,83 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Dropdown, MenuItem } from 'react-bootstrap'
import { useTranslation, Trans } from 'react-i18next'
import Icon from '../../../shared/components/icon'
export const topFileTypes = ['bbl', 'gls', 'ind']
function PreviewDownloadButton({ isCompiling, outputFiles, pdfDownloadUrl }) {
let topFiles = []
let otherFiles = []
const { t } = useTranslation()
if (outputFiles) {
topFiles = outputFiles.filter(file => {
if (topFileTypes.includes(file.type)) {
return file
}
})
otherFiles = outputFiles.filter(file => {
if (!topFileTypes.includes(file.type)) {
if (file.type === 'pdf' && file.main === true) return
return file
}
})
}
return (
<Dropdown id="download-dropdown" disabled={isCompiling}>
<a
className="btn btn-xs btn-info"
disabled={isCompiling || !pdfDownloadUrl}
download
href={pdfDownloadUrl || '#'}
>
<Icon type="download" modifier="fw" /> {t('download_pdf')}
</a>
<Dropdown.Toggle
className="btn btn-xs btn-info dropdown-toggle"
aria-label={t('toggle_output_files_list')}
/>
<Dropdown.Menu id="download-dropdown-list">
<FileList list={topFiles} listType="main" />
{otherFiles.length > 0 && (
<>
<MenuItem divider />
<MenuItem header>{t('other_output_files')}</MenuItem>
<FileList list={otherFiles} listType="other" />
</>
)}
</Dropdown.Menu>
</Dropdown>
)
}
function FileList({ listType, list }) {
return list.map((file, index) => {
return (
<MenuItem download href={file.url} key={`${listType}-${index}`}>
<Trans
i18nKey="download_file"
components={[<strong />]}
values={{ type: file.fileName }}
/>
</MenuItem>
)
})
}
PreviewDownloadButton.propTypes = {
isCompiling: PropTypes.bool.isRequired,
outputFiles: PropTypes.array,
pdfDownloadUrl: PropTypes.string
}
FileList.propTypes = {
list: PropTypes.array.isRequired,
listType: PropTypes.string.isRequired
}
export default PreviewDownloadButton

View file

@ -13,6 +13,8 @@ function PreviewPane({
onSetDraftMode,
onSetSyntaxCheck,
onToggleLogs,
outputFiles,
pdfDownloadUrl,
showLogs
}) {
const { t } = useTranslation()
@ -43,6 +45,8 @@ function PreviewPane({
onSetDraftMode={onSetDraftMode}
onSetSyntaxCheck={onSetSyntaxCheck}
onToggleLogs={onToggleLogs}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
<span aria-live="polite" className="sr-only">
{nErrors && !compilerState.isCompiling
@ -76,6 +80,8 @@ PreviewPane.propTypes = {
onSetDraftMode: PropTypes.func.isRequired,
onSetSyntaxCheck: PropTypes.func.isRequired,
onToggleLogs: PropTypes.func.isRequired,
outputFiles: PropTypes.array,
pdfDownloadUrl: PropTypes.string,
showLogs: PropTypes.bool.isRequired
}

View file

@ -1,5 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import PreviewDownloadButton from './preview-download-button'
import PreviewRecompileButton from './preview-recompile-button'
import PreviewLogsToggleButton from './preview-logs-toggle-button'
@ -13,6 +14,8 @@ function PreviewToolbar({
onSetDraftMode,
onSetSyntaxCheck,
onToggleLogs,
outputFiles,
pdfDownloadUrl,
showLogs
}) {
return (
@ -27,6 +30,11 @@ function PreviewToolbar({
onSetSyntaxCheck={onSetSyntaxCheck}
onClearCache={onClearCache}
/>
<PreviewDownloadButton
isCompiling={compilerState.isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
</div>
<div className="toolbar-pdf-right">
<PreviewLogsToggleButton
@ -59,7 +67,9 @@ PreviewToolbar.propTypes = {
onSetAutoCompile: PropTypes.func.isRequired,
onSetDraftMode: PropTypes.func.isRequired,
onSetSyntaxCheck: PropTypes.func.isRequired,
onToggleLogs: PropTypes.func.isRequired
onToggleLogs: PropTypes.func.isRequired,
pdfDownloadUrl: PropTypes.string,
outputFiles: PropTypes.array
}
export default PreviewToolbar

View file

@ -508,7 +508,9 @@ App.controller('PdfController', function(
url:
`/project/${$scope.project_id}/output/${file.path}` +
createQueryString(qs),
main: !!isOutputFile
main: !!isOutputFile,
fileName: file.path,
type: file.type
})
}
}

View file

@ -460,3 +460,10 @@
display: block;
}
}
#download-dropdown-list {
max-height: calc(
~'100vh - ' @toolbar-small-height ~' - ' @toolbar-height ~' - ' @margin-md
);
overflow-y: auto;
}

View file

@ -1,4 +1,6 @@
{
"other_output_files": "Other output files",
"toggle_output_files_list": "Toggle output files list",
"n_warnings": "__count__ warning",
"n_warnings_plural": "__count__ warnings",
"n_errors": "__count__ error",
@ -1106,6 +1108,7 @@
"remove_collaborator": "Remove collaborator",
"add_to_folders": "Add to folders",
"download_zip_file": "Download .zip File",
"download_file": "Download <0>__type__</0> file",
"price": "Price",
"close": "Close",
"keybindings": "Keybindings",

View file

@ -0,0 +1,170 @@
import React from 'react'
import { expect } from 'chai'
import { screen, render } from '@testing-library/react'
import PreviewDownloadButton, {
topFileTypes
} from '../../../../../frontend/js/features/preview/components/preview-download-button'
describe('<PreviewDownloadButton />', function() {
const projectId = 'projectId123'
const pdfDownloadUrl = `/download/project/${projectId}/build/17523aaafdf-1ad9063af140f004/output/output.pdf?compileGroup=priority&popupDownload=true`
function makeFile(fileName, main) {
return {
fileName,
url: `/project/${projectId}/output/${fileName}`,
type: fileName.split('.').pop(),
main: main || false
}
}
it('should disable the button and dropdown toggle when compiling', function() {
const isCompiling = true
const outputFiles = undefined
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={undefined}
/>
)
expect(screen.getByText('Download PDF').getAttribute('disabled')).to.exist
const buttons = screen.getAllByRole('button')
expect(buttons.length).to.equal(1) // the dropdown toggle
expect(buttons[0].getAttribute('disabled')).to.exist
expect(buttons[0].getAttribute('aria-label')).to.equal(
'Toggle output files list'
)
})
it('should disable the PDF button when there is no PDF', function() {
const isCompiling = false
const outputFiles = []
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={undefined}
/>
)
expect(screen.getByText('Download PDF').getAttribute('disabled')).to.exist
})
it('should enable the PDF button when there is a main PDF', function() {
const isCompiling = false
const outputFiles = []
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
expect(screen.getByText('Download PDF').getAttribute('href')).to.equal(
pdfDownloadUrl
)
expect(screen.getByText('Download PDF').getAttribute('disabled')).to.not
.exist
})
it('should enable the dropdown when not compiling', function() {
const isCompiling = false
const outputFiles = []
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
const buttons = screen.getAllByRole('button')
expect(buttons[0]).to.exist
expect(buttons[0].getAttribute('disabled')).to.not.exist
})
it('should list all output files and group them', function() {
const isCompiling = false
const outputFiles = [
makeFile('output.ind'),
makeFile('output.log'),
makeFile('output.pdf', true),
makeFile('alt.pdf'),
makeFile('output.stderr'),
makeFile('output.stdout'),
makeFile('output.aux'),
makeFile('output.bbl'),
makeFile('output.blg')
]
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
const menuItems = screen.getAllByRole('menuitem')
expect(menuItems.length).to.equal(outputFiles.length - 1) // main PDF is listed separately
const fileTypes = outputFiles.map(file => {
return file.type
})
menuItems.forEach((item, index) => {
// check displayed text
const itemTextParts = item.textContent.split(' ')
expect(itemTextParts[0]).to.equal('Download')
const fileType = itemTextParts[1].split('.').pop()
expect(fileTypes).to.include(fileType)
expect(itemTextParts[2]).to.equal('file')
})
// check grouped correctly
expect(topFileTypes).to.exist
expect(topFileTypes.length).to.be.above(0)
const outputTopFileTypes = outputFiles
.filter(file => {
if (topFileTypes.includes(file.type)) return file.type
})
.map(file => file.type)
const topMenuItems = menuItems.slice(0, outputTopFileTypes.length)
topMenuItems.forEach(item => {
const fileType = item.textContent
.split('.')
.pop()
.replace(' file', '')
expect(topFileTypes.includes(fileType)).to.be.true
})
})
it('should list all files when there are duplicate types', function() {
const isCompiling = false
const pdfFile = makeFile('output.pdf', true)
const bblFile = makeFile('output.bbl')
const outputFiles = [Object.assign({}, { ...bblFile }), bblFile, pdfFile]
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
const bblMenuItems = screen.getAllByText((content, element) => {
return (
content !== '' && element.textContent === 'Download output.bbl file'
)
})
expect(bblMenuItems.length).to.equal(2)
})
it('should list the non-main PDF in the dropdown', function() {
const isCompiling = false
const pdfFile = makeFile('output.pdf', true)
const pdfAltFile = makeFile('alt.pdf')
const outputFiles = [pdfFile, pdfAltFile]
render(
<PreviewDownloadButton
isCompiling={isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
/>
)
screen.getAllByRole('menuitem', { name: 'Download alt.pdf file' })
})
})