Merge pull request #3242 from overleaf/jel-add-recompile-from-scratch

Add recompile from scratch option

GitOrigin-RevId: 59836df9049e307acb11824058024919409ea4a4
This commit is contained in:
Jessica Lawshe 2020-10-20 07:44:19 -05:00 committed by Copybot
parent 4690b2ec86
commit f8a8c9bbd6
8 changed files with 191 additions and 10 deletions

View file

@ -3,11 +3,13 @@ div.full-size.pdf(ng-controller="PdfController")
preview-pane(
compiler-state=`{
isAutoCompileOn: autocompile_enabled,
isClearingCache: pdf.clearingCache,
isCompiling: pdf.compiling,
isDraftModeOn: draft,
isSyntaxCheckOn: stop_on_validation_error,
logEntries: pdf.logEntries ? pdf.logEntries : {}
}`
on-clear-cache="clearCache"
on-recompile="recompile"
on-run-syntax-check-now="runSyntaxCheckNow"
on-set-auto-compile="setAutoCompile"
@ -379,6 +381,7 @@ script(type='text/ng-template', id='clearCacheModalTemplate')
.modal-body
p #{translate("clear_cache_explanation")}
p #{translate("clear_cache_is_safe")}
.alert.alert-danger(ng-if="state.error") #{translate("generic_something_went_wrong")}.
.modal-footer
button.btn.btn-default(
ng-click="cancel()"

View file

@ -29,5 +29,8 @@
"your_project_has_errors",
"view_warnings",
"view_logs",
"view_pdf"
"view_pdf",
"recompile_from_scratch",
"run_syntax_check_now",
"toggle_compile_options_menu"
]

View file

@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'
function PreviewPane({
compilerState,
onClearCache,
onRecompile,
onRunSyntaxCheckNow,
onSetAutoCompile,
@ -35,6 +36,7 @@ function PreviewPane({
compilerState={compilerState}
logsState={{ nErrors, nWarnings, nLogEntries }}
showLogs={showLogs}
onClearCache={onClearCache}
onRecompile={onRecompile}
onRunSyntaxCheckNow={onRunSyntaxCheckNow}
onSetAutoCompile={onSetAutoCompile}
@ -67,6 +69,7 @@ PreviewPane.propTypes = {
isSyntaxCheckOn: PropTypes.bool.isRequired,
logEntries: PropTypes.object.isRequired
}),
onClearCache: PropTypes.func.isRequired,
onRecompile: PropTypes.func.isRequired,
onRunSyntaxCheckNow: PropTypes.func.isRequired,
onSetAutoCompile: PropTypes.func.isRequired,

View file

@ -7,10 +7,12 @@ import Icon from '../../../shared/components/icon'
function PreviewRecompileButton({
compilerState: {
isAutoCompileOn,
isClearingCache,
isCompiling,
isDraftModeOn,
isSyntaxCheckOn
},
onClearCache,
onRecompile,
onRunSyntaxCheckNow,
onSetAutoCompile,
@ -19,6 +21,16 @@ function PreviewRecompileButton({
}) {
const { t } = useTranslation()
function handleRecompileFromScratch() {
onClearCache()
.then(() => {
onRecompile()
})
.catch(error => {
console.error(error)
})
}
function handleSelectAutoCompileOn() {
onSetAutoCompile(true)
}
@ -47,7 +59,7 @@ function PreviewRecompileButton({
<Dropdown id="pdf-recompile-dropdown" className="btn-recompile-group">
<button className="btn btn-recompile" onClick={onRecompile}>
<Icon type="refresh" spin={isCompiling} />
{isCompiling ? (
{isCompiling || isClearingCache ? (
<span className="btn-recompile-label">
{t('compiling')}
&hellip;
@ -56,7 +68,10 @@ function PreviewRecompileButton({
<span className="btn-recompile-label">{t('recompile')}</span>
)}
</button>
<Dropdown.Toggle className="btn btn-recompile" />
<Dropdown.Toggle
aria-label={t('toggle_compile_options_menu')}
className="btn btn-recompile"
/>
<Dropdown.Menu>
<MenuItem header>{t('auto_compile')}</MenuItem>
<MenuItem onSelect={handleSelectAutoCompileOn}>
@ -89,6 +104,14 @@ function PreviewRecompileButton({
<Icon type="" modifier="fw" />
{t('run_syntax_check_now')}
</MenuItem>
<MenuItem divider />
<MenuItem
onSelect={handleRecompileFromScratch}
disabled={isCompiling || isClearingCache}
aria-disabled={!!(isCompiling || isClearingCache)}
>
{t('recompile_from_scratch')}
</MenuItem>
</Dropdown.Menu>
</Dropdown>
)
@ -97,11 +120,13 @@ function PreviewRecompileButton({
PreviewRecompileButton.propTypes = {
compilerState: PropTypes.shape({
isAutoCompileOn: PropTypes.bool.isRequired,
isClearingCache: PropTypes.bool.isRequired,
isCompiling: PropTypes.bool.isRequired,
isDraftModeOn: PropTypes.bool.isRequired,
isSyntaxCheckOn: PropTypes.bool.isRequired,
logEntries: PropTypes.object.isRequired
}),
onClearCache: PropTypes.func.isRequired,
onRecompile: PropTypes.func.isRequired,
onRunSyntaxCheckNow: PropTypes.func.isRequired,
onSetAutoCompile: PropTypes.func.isRequired,

View file

@ -6,6 +6,7 @@ import PreviewLogsToggleButton from './preview-logs-toggle-button'
function PreviewToolbar({
compilerState,
logsState,
onClearCache,
onRecompile,
onRunSyntaxCheckNow,
onSetAutoCompile,
@ -24,6 +25,7 @@ function PreviewToolbar({
onSetAutoCompile={onSetAutoCompile}
onSetDraftMode={onSetDraftMode}
onSetSyntaxCheck={onSetSyntaxCheck}
onClearCache={onClearCache}
/>
</div>
<div className="toolbar-pdf-right">
@ -51,6 +53,7 @@ PreviewToolbar.propTypes = {
nLogEntries: PropTypes.number.isRequired
}),
showLogs: PropTypes.bool.isRequired,
onClearCache: PropTypes.func.isRequired,
onRecompile: PropTypes.func.isRequired,
onRunSyntaxCheckNow: PropTypes.func.isRequired,
onSetAutoCompile: PropTypes.func.isRequired,

View file

@ -20,12 +20,14 @@ App.controller('PdfController', function(
$modal,
synctex,
eventTracking,
localStorage
localStorage,
$q
) {
let autoCompile = true
// pdf.view = uncompiled | pdf | errors
$scope.pdf.view = $scope.pdf.url ? 'pdf' : 'uncompiled'
$scope.pdf.clearingCache = false
$scope.shouldShowLogs = false
$scope.wikiEnabled = window.wikiEnabled
@ -796,7 +798,10 @@ App.controller('PdfController', function(
}
$scope.clearCache = function() {
return $http({
$scope.pdf.clearingCache = true
const deferred = $q.defer()
$http({
url: `/project/${$scope.project_id}/output`,
method: 'DELETE',
params: {
@ -806,6 +811,20 @@ App.controller('PdfController', function(
'X-Csrf-Token': window.csrfToken
}
})
.then(function(response) {
$scope.pdf.clearingCache = false
return deferred.resolve()
})
.catch(function(response) {
console.error(response)
const error = response.data
$scope.pdf.clearingCache = false
$scope.pdf.renderingError = false
$scope.pdf.error = true
$scope.pdf.view = 'errors'
return deferred.reject(error)
})
return deferred.promise
}
$scope.toggleLogs = function() {
@ -1056,14 +1075,20 @@ App.controller('PdfLogEntryController', function($scope, ide, eventTracking) {
})
App.controller('ClearCacheModalController', function($scope, $modalInstance) {
$scope.state = { inflight: false }
$scope.state = { error: false, inflight: false }
$scope.clear = function() {
$scope.state.inflight = true
$scope.clearCache().then(function() {
$scope
.clearCache()
.then(function() {
$scope.state.inflight = false
$modalInstance.close()
})
.catch(function() {
$scope.state.error = true
$scope.state.inflight = false
})
}
$scope.cancel = () => $modalInstance.dismiss('cancel')

View file

@ -3,6 +3,7 @@
"n_warnings_plural": "__count__ warnings",
"n_errors": "__count__ error",
"n_errors_plural": "__count__ errors",
"toggle_compile_options_menu": "Toggle compile options menu",
"view_pdf": "View PDF",
"your_project_has_errors": "Your project has errors",
"view_warnings": "View warnings",

View file

@ -0,0 +1,118 @@
import React from 'react'
import sinon from 'sinon'
import { screen, render, fireEvent } from '@testing-library/react'
import PreviewRecompileButton from '../../../../../frontend/js/features/preview/components/preview-recompile-button'
const { expect } = require('chai')
describe('<PreviewRecompileButton />', function() {
let onRecompile, onClearCache
beforeEach(function() {
onRecompile = sinon.stub().resolves()
onClearCache = sinon.stub().resolves()
})
it('renders all items', function() {
renderPreviewRecompileButton()
const menuItems = screen.getAllByRole('menuitem')
expect(menuItems.length).to.equal(8)
expect(menuItems.map(item => item.textContent)).to.deep.equal([
'On',
'Off',
'Normal',
'Fast [draft]',
'Check syntax before compile',
"Don't check syntax",
'Run syntax check now',
'Recompile from scratch'
])
const menuHeadingItems = screen.getAllByRole('heading')
expect(menuHeadingItems.length).to.equal(3)
expect(menuHeadingItems.map(item => item.textContent)).to.deep.equal([
'Auto Compile',
'Compile Mode',
'Syntax Checks'
])
})
describe('Recompile from scratch', function() {
describe('click', function() {
it('should call onClearCache and onRecompile', async function() {
renderPreviewRecompileButton()
const button = screen.getByRole('menuitem', {
name: 'Recompile from scratch'
})
await fireEvent.click(button)
expect(onClearCache).to.have.been.calledOnce
expect(onRecompile).to.have.been.calledOnce
})
})
describe('processing', function() {
it('shows processing view and disable menuItem when clearing cache', function() {
renderPreviewRecompileButton({ isClearingCache: true })
screen.getByRole('button', { name: 'Compiling …' })
expect(
screen
.getByRole('menuitem', {
name: 'Recompile from scratch'
})
.getAttribute('aria-disabled')
).to.equal('true')
expect(
screen
.getByRole('menuitem', {
name: 'Recompile from scratch'
})
.closest('li')
.getAttribute('class')
).to.equal('disabled')
})
it('shows processing view and disable menuItem when recompiling', function() {
renderPreviewRecompileButton({ isCompiling: true })
screen.getByRole('button', { name: 'Compiling …' })
expect(
screen
.getByRole('menuitem', {
name: 'Recompile from scratch'
})
.getAttribute('aria-disabled')
).to.equal('true')
expect(
screen
.getByRole('menuitem', {
name: 'Recompile from scratch'
})
.closest('li')
.getAttribute('class')
).to.equal('disabled')
})
})
})
function renderPreviewRecompileButton(compilerState = {}) {
render(
<PreviewRecompileButton
compilerState={{
isAutoCompileOn: true,
isClearingCache: false,
isCompiling: false,
isDraftModeOn: false,
isSyntaxCheckOn: false,
...compilerState
}}
onRecompile={onRecompile}
onRunSyntaxCheckNow={() => {}}
onSetAutoCompile={() => {}}
onSetDraftMode={() => {}}
onSetSyntaxCheck={() => {}}
onClearCache={onClearCache}
/>
)
}
})