mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 12:23:35 -05:00
Handle PDF preview on toggle between split and full-width views (#5470)
* Only hide the compile logs pane when toggled off * Handle PDF preview on toggle between split and full-width views GitOrigin-RevId: 9ceca8a06a22abfa78f245e1ae5d24af98215906
This commit is contained in:
parent
90befc1fdd
commit
f7ef2532e0
22 changed files with 500 additions and 642 deletions
|
@ -18,13 +18,10 @@ div.full-size(
|
|||
include ./editor-no-symbol-palette
|
||||
|
||||
.ui-layout-east
|
||||
// The pdf-preview component needs to always be rendered, even when the editor is in "full-width" mode and it's not visible.
|
||||
// It doesn't recompile while hidden, due to the ui.pdfHidden flag, but maintains its state for when it's shown again.
|
||||
if showNewPdfPreview
|
||||
div(ng-show="ui.pdfLayout == 'sideBySide'")
|
||||
div(ng-if="ui.pdfLayout == 'sideBySide'")
|
||||
if showNewPdfPreview
|
||||
pdf-preview()
|
||||
else
|
||||
div(ng-if="ui.pdfLayout == 'sideBySide'")
|
||||
else
|
||||
include ./pdf
|
||||
|
||||
.ui-layout-resizer-controls.synctex-controls(
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import Icon from '../../../shared/components/icon'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { memo } from 'react'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
function PdfClearCacheButton() {
|
||||
const { compiling, clearCache, clearingCache } = usePdfPreviewContext()
|
||||
const { compiling, clearCache, clearingCache } = useCompileContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
|
@ -8,9 +8,9 @@ import {
|
|||
import Icon from '../../../shared/components/icon'
|
||||
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { memo } from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
|
||||
|
||||
|
@ -23,11 +23,11 @@ function PdfCompileButton() {
|
|||
setAutoCompile,
|
||||
setDraft,
|
||||
setStopOnValidationError,
|
||||
stopOnValidationError,
|
||||
startCompile,
|
||||
stopCompile,
|
||||
stopOnValidationError,
|
||||
recompileFromScratch,
|
||||
} = usePdfPreviewContext()
|
||||
} = useCompileContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@ import { Button, Dropdown } from 'react-bootstrap'
|
|||
import Icon from '../../../shared/components/icon'
|
||||
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
|
||||
import PdfFileList from './pdf-file-list'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { memo } from 'react'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
function PdfDownloadButton() {
|
||||
const { compiling, pdfDownloadUrl, fileList } = usePdfPreviewContext()
|
||||
const { compiling, pdfDownloadUrl, fileList } = useCompileContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ import { Dropdown } from 'react-bootstrap'
|
|||
import PdfFileList from './pdf-file-list'
|
||||
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
|
||||
import { memo } from 'react'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
function PdfDownloadFilesButton() {
|
||||
const { compiling, fileList } = usePdfPreviewContext()
|
||||
const { compiling, fileList } = useCompileContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Button, OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { useLayoutContext } from '../../../shared/context/layout-context'
|
||||
|
||||
function PdfExpandButton() {
|
||||
const { pdfLayout, switchLayout } = usePdfPreviewContext()
|
||||
const { pdfLayout, switchLayout } = useLayoutContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { memo, useCallback } from 'react'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { sendMBOnce } from '../../../infrastructure/event-tracking'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
function PdfHybridCodeCheckButton() {
|
||||
const { codeCheckFailed, error, setShowLogs } = usePdfPreviewContext()
|
||||
const { codeCheckFailed, error, setShowLogs } = useCompileContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { Button, OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { memo } from 'react'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
function PdfHybridDownloadButton() {
|
||||
const { pdfDownloadUrl } = usePdfPreviewContext()
|
||||
const { pdfDownloadUrl } = useCompileContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button, Label, OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { sendMBOnce } from '../../../infrastructure/event-tracking'
|
||||
import Icon from '../../../shared/components/icon'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
function PdfHybridLogsButton() {
|
||||
const { error, logEntries, setShowLogs, showLogs } = usePdfPreviewContext()
|
||||
const { error, logEntries, setShowLogs, showLogs } = useCompileContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
|
@ -19,10 +19,6 @@ function PdfJsViewer({ url }) {
|
|||
`pdf-viewer-scale:${projectId}`,
|
||||
'page-width'
|
||||
)
|
||||
const [, setScrollTop] = usePersistedState(
|
||||
`pdf-viewer-scroll-top:${projectId}`,
|
||||
0
|
||||
)
|
||||
|
||||
// state values shared with Angular scope (highlights => editor, position => synctex buttons
|
||||
const [highlights] = useScopeValue('pdf.highlights')
|
||||
|
@ -63,23 +59,10 @@ function PdfJsViewer({ url }) {
|
|||
}
|
||||
}, [pdfJsWrapper, url])
|
||||
|
||||
useEffect(() => {
|
||||
if (pdfJsWrapper) {
|
||||
// listen for 'pdf:scroll-to-position' events
|
||||
const eventListener = event => {
|
||||
pdfJsWrapper.container.scrollTop = event.data.position
|
||||
}
|
||||
|
||||
window.addEventListener('pdf:scroll-to-position', eventListener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('pdf:scroll-to-position', eventListener)
|
||||
}
|
||||
}
|
||||
}, [pdfJsWrapper])
|
||||
|
||||
// listen for scroll events
|
||||
useEffect(() => {
|
||||
let storePositionTimer
|
||||
|
||||
if (initialised && pdfJsWrapper) {
|
||||
// store the scroll position in localStorage, for the synctex button
|
||||
const storePosition = debounce(pdfViewer => {
|
||||
|
@ -87,33 +70,30 @@ function PdfJsViewer({ url }) {
|
|||
try {
|
||||
setPosition(pdfViewer.currentPosition)
|
||||
} catch (error) {
|
||||
// TODO
|
||||
console.error(error)
|
||||
// TODO: investigate handling missing offsetParent in jsdom
|
||||
// console.error(error)
|
||||
}
|
||||
}, 500)
|
||||
|
||||
// store the scroll position in localStorage, for use when reloading
|
||||
const storeScrollTop = debounce(pdfViewer => {
|
||||
// set position for "sync to code" button
|
||||
setScrollTop(pdfViewer.container.scrollTop)
|
||||
}, 500)
|
||||
|
||||
storePosition(pdfJsWrapper)
|
||||
storePositionTimer = window.setTimeout(() => {
|
||||
storePosition(pdfJsWrapper)
|
||||
}, 100)
|
||||
|
||||
const scrollListener = () => {
|
||||
storeScrollTop(pdfJsWrapper)
|
||||
storePosition(pdfJsWrapper)
|
||||
}
|
||||
|
||||
pdfJsWrapper.container.addEventListener('scroll', scrollListener)
|
||||
|
||||
return () => {
|
||||
storePosition.cancel()
|
||||
storeScrollTop.cancel()
|
||||
pdfJsWrapper.container.removeEventListener('scroll', scrollListener)
|
||||
if (storePositionTimer) {
|
||||
window.clearTimeout(storePositionTimer)
|
||||
}
|
||||
storePosition.cancel()
|
||||
}
|
||||
}
|
||||
}, [setPosition, setScrollTop, pdfJsWrapper, initialised])
|
||||
}, [setPosition, pdfJsWrapper, initialised])
|
||||
|
||||
// listen for double-click events
|
||||
useEffect(() => {
|
||||
|
@ -147,14 +127,14 @@ function PdfJsViewer({ url }) {
|
|||
})
|
||||
|
||||
// restore the scroll position
|
||||
setScrollTop(scrollTop => {
|
||||
if (scrollTop > 0) {
|
||||
pdfJsWrapper.container.scrollTop = scrollTop
|
||||
setPosition(position => {
|
||||
if (position) {
|
||||
pdfJsWrapper.currentPosition = position
|
||||
}
|
||||
return scrollTop
|
||||
return position
|
||||
})
|
||||
}
|
||||
}, [initialised, setScale, setScrollTop, pdfJsWrapper])
|
||||
}, [initialised, setScale, setPosition, pdfJsWrapper])
|
||||
|
||||
// transmit scale value to the viewer when it changes
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { memo, useCallback, useMemo } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { PdfLogsButtonContent } from './pdf-logs-button-content'
|
||||
import { sendMBOnce } from '../../../infrastructure/event-tracking'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
function PdfLogsButton() {
|
||||
const {
|
||||
|
@ -11,7 +11,7 @@ function PdfLogsButton() {
|
|||
logEntries,
|
||||
showLogs,
|
||||
setShowLogs,
|
||||
} = usePdfPreviewContext()
|
||||
} = useCompileContext()
|
||||
|
||||
const buttonStyle = useMemo(() => {
|
||||
if (showLogs) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import PreviewLogsPaneEntry from '../../preview/components/preview-logs-pane-entry'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { memo } from 'react'
|
||||
import classnames from 'classnames'
|
||||
import PdfValidationIssue from './pdf-validation-issue'
|
||||
import TimeoutUpgradePrompt from './timeout-upgrade-prompt'
|
||||
import PdfPreviewError from './pdf-preview-error'
|
||||
|
@ -12,6 +12,7 @@ import withErrorBoundary from '../../../infrastructure/error-boundary'
|
|||
import ErrorBoundaryFallback from './error-boundary-fallback'
|
||||
import PdfCodeCheckFailedNotice from '../../preview/components/pdf-code-check-failed-notice'
|
||||
import PdfLogsPaneInfoNotice from '../../preview/components/pdf-logs-pane-info-notice'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
function PdfLogsViewer() {
|
||||
const {
|
||||
|
@ -20,12 +21,13 @@ function PdfLogsViewer() {
|
|||
logEntries,
|
||||
rawLog,
|
||||
validationIssues,
|
||||
} = usePdfPreviewContext()
|
||||
showLogs,
|
||||
} = useCompileContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="logs-pane">
|
||||
<div className={classnames('logs-pane', { hidden: !showLogs })}>
|
||||
<div className="logs-pane-content">
|
||||
<PdfLogsPaneInfoNotice />
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { memo, Suspense } from 'react'
|
||||
import PdfLogsViewer from './pdf-logs-viewer'
|
||||
import PdfViewer from './pdf-viewer'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import LoadingSpinner from '../../../shared/components/loading-spinner'
|
||||
import PdfHybridPreviewToolbar from './pdf-preview-hybrid-toolbar'
|
||||
import PdfPreviewToolbar from './pdf-preview-toolbar'
|
||||
|
@ -11,8 +10,6 @@ const newPreviewToolbar = new URLSearchParams(window.location.search).has(
|
|||
)
|
||||
|
||||
function PdfPreviewPane() {
|
||||
const { showLogs } = usePdfPreviewContext()
|
||||
|
||||
return (
|
||||
<div className="pdf full-size">
|
||||
{newPreviewToolbar ? <PdfPreviewToolbar /> : <PdfHybridPreviewToolbar />}
|
||||
|
@ -21,7 +18,7 @@ function PdfPreviewPane() {
|
|||
<PdfViewer />
|
||||
</div>
|
||||
</Suspense>
|
||||
{showLogs && <PdfLogsViewer />}
|
||||
<PdfLogsViewer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import PdfPreviewProvider from '../contexts/pdf-preview-context'
|
||||
import PdfPreviewPane from './pdf-preview-pane'
|
||||
import { memo } from 'react'
|
||||
import withErrorBoundary from '../../../infrastructure/error-boundary'
|
||||
import ErrorBoundaryFallback from './error-boundary-fallback'
|
||||
|
||||
function PdfPreview() {
|
||||
return (
|
||||
<PdfPreviewProvider>
|
||||
<PdfPreviewPane />
|
||||
</PdfPreviewProvider>
|
||||
)
|
||||
return <PdfPreviewPane />
|
||||
}
|
||||
|
||||
export default withErrorBoundary(memo(PdfPreview), () => (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import useScopeValue from '../../../shared/hooks/use-scope-value'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { lazy, memo, useEffect } from 'react'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
const PdfJsViewer = lazy(() =>
|
||||
import(/* webpackChunkName: "pdf-js-viewer" */ './pdf-js-viewer')
|
||||
|
@ -19,7 +19,7 @@ function PdfViewer() {
|
|||
}
|
||||
}, [setPdfViewer])
|
||||
|
||||
const { pdfUrl } = usePdfPreviewContext()
|
||||
const { pdfUrl } = useCompileContext()
|
||||
|
||||
if (!pdfUrl) {
|
||||
return null
|
||||
|
|
|
@ -1,494 +0,0 @@
|
|||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import useScopeValue from '../../../shared/hooks/use-scope-value'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import usePersistedState from '../../../shared/hooks/use-persisted-state'
|
||||
import {
|
||||
buildLogEntryAnnotations,
|
||||
handleOutputFiles,
|
||||
} from '../util/output-files'
|
||||
import {
|
||||
send,
|
||||
sendMB,
|
||||
sendMBSampled,
|
||||
} from '../../../infrastructure/event-tracking'
|
||||
import { useEditorContext } from '../../../shared/context/editor-context'
|
||||
import useAbortController from '../../../shared/hooks/use-abort-controller'
|
||||
import DocumentCompiler from '../util/compiler'
|
||||
import { useIdeContext } from '../../../shared/context/ide-context'
|
||||
import { useLayoutContext } from '../../../shared/context/layout-context'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
export const PdfPreviewContext = createContext(undefined)
|
||||
|
||||
PdfPreviewProvider.propTypes = {
|
||||
children: PropTypes.any,
|
||||
}
|
||||
|
||||
export default function PdfPreviewProvider({ children }) {
|
||||
const ide = useIdeContext()
|
||||
|
||||
const { pdfHidden, pdfLayout, setPdfLayout, setView } = useLayoutContext()
|
||||
|
||||
const project = useProjectContext()
|
||||
|
||||
const projectId = project._id
|
||||
|
||||
const { hasPremiumCompile, isProjectOwner } = useEditorContext()
|
||||
|
||||
const {
|
||||
logEntries,
|
||||
pdfDownloadUrl,
|
||||
pdfUrl,
|
||||
setClsiServerId,
|
||||
setLogEntries,
|
||||
setLogEntryAnnotations,
|
||||
setPdfDownloadUrl,
|
||||
setPdfUrl,
|
||||
setUncompiled,
|
||||
uncompiled,
|
||||
} = useCompileContext()
|
||||
|
||||
// whether a compile is in progress
|
||||
const [compiling, setCompiling] = useState(false)
|
||||
|
||||
// data received in response to a compile request
|
||||
const [data, setData] = useState()
|
||||
|
||||
// whether the project has been compiled yet
|
||||
const [compiledOnce, setCompiledOnce] = useState(false)
|
||||
|
||||
// whether the cache is being cleared
|
||||
const [clearingCache, setClearingCache] = useState(false)
|
||||
|
||||
// whether the logs should be visible
|
||||
const [showLogs, setShowLogs] = useState(false)
|
||||
|
||||
// an error that occurred
|
||||
const [error, setError] = useState()
|
||||
|
||||
// the list of files that can be downloaded
|
||||
const [fileList, setFileList] = useState()
|
||||
|
||||
// the raw contents of the log file
|
||||
const [rawLog, setRawLog] = useState()
|
||||
|
||||
// validation issues from CLSI
|
||||
const [validationIssues, setValidationIssues] = useState()
|
||||
|
||||
// whether autocompile is switched on
|
||||
const [autoCompile, _setAutoCompile] = usePersistedState(
|
||||
`autocompile_enabled:${projectId}`,
|
||||
false,
|
||||
true
|
||||
)
|
||||
|
||||
// whether the compile should run in draft mode
|
||||
const [draft, setDraft] = usePersistedState(`draft:${projectId}`, false, true)
|
||||
|
||||
// whether compiling should be prevented if there are linting errors
|
||||
const [stopOnValidationError, setStopOnValidationError] = usePersistedState(
|
||||
`stop_on_validation_error:${projectId}`,
|
||||
true,
|
||||
true
|
||||
)
|
||||
|
||||
// the Document currently open in the editor
|
||||
const [currentDoc] = useScopeValue('editor.sharejs_doc')
|
||||
|
||||
// whether the editor linter found errors
|
||||
const [hasLintingError, setHasLintingError] = useScopeValue('hasLintingError')
|
||||
|
||||
// whether syntax validation is enabled globally
|
||||
const [syntaxValidation] = useScopeValue('settings.syntaxValidation')
|
||||
|
||||
// the timestamp that a doc was last changed or saved
|
||||
const [changedAt, setChangedAt] = useState(0)
|
||||
|
||||
const { signal } = useAbortController()
|
||||
|
||||
// the document compiler
|
||||
const [compiler] = useState(() => {
|
||||
return new DocumentCompiler({
|
||||
project,
|
||||
setChangedAt,
|
||||
setCompiling,
|
||||
setData,
|
||||
setError,
|
||||
signal,
|
||||
})
|
||||
})
|
||||
|
||||
// clean up the compiler on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
compiler.destroy()
|
||||
}
|
||||
}, [compiler])
|
||||
|
||||
// keep currentDoc in sync with the compiler
|
||||
useEffect(() => {
|
||||
compiler.currentDoc = currentDoc
|
||||
}, [compiler, currentDoc])
|
||||
|
||||
// keep draft setting in sync with the compiler
|
||||
useEffect(() => {
|
||||
compiler.draft = draft
|
||||
}, [compiler, draft])
|
||||
|
||||
// pass the "uncompiled" value up into the scope for use outside this context provider
|
||||
useEffect(() => {
|
||||
setUncompiled(changedAt > 0)
|
||||
}, [setUncompiled, changedAt])
|
||||
|
||||
// record changes to the autocompile setting
|
||||
const setAutoCompile = useCallback(
|
||||
value => {
|
||||
_setAutoCompile(value)
|
||||
sendMB('autocompile-setting-changed', { value })
|
||||
},
|
||||
[_setAutoCompile]
|
||||
)
|
||||
|
||||
// always compile the PDF once after opening the project, after the doc has loaded
|
||||
useEffect(() => {
|
||||
if (!compiledOnce && currentDoc) {
|
||||
setCompiledOnce(true)
|
||||
compiler.compile({ isAutoCompileOnLoad: true })
|
||||
}
|
||||
}, [compiledOnce, currentDoc, compiler])
|
||||
|
||||
// handle the data returned from a compile request
|
||||
// note: this should _only_ run when `data` changes,
|
||||
// the other dependencies must all be static
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
if (data.clsiServerId) {
|
||||
setClsiServerId(data.clsiServerId) // set in scope, for PdfSynctexController
|
||||
compiler.clsiServerId = data.clsiServerId
|
||||
}
|
||||
|
||||
if (data.compileGroup) {
|
||||
compiler.compileGroup = data.compileGroup
|
||||
}
|
||||
|
||||
if (data.outputFiles) {
|
||||
handleOutputFiles(projectId, data).then(result => {
|
||||
setLogEntryAnnotations(
|
||||
buildLogEntryAnnotations(result.logEntries.all, ide.fileTreeManager)
|
||||
)
|
||||
setLogEntries(result.logEntries)
|
||||
setFileList(result.fileList)
|
||||
setPdfDownloadUrl(result.pdfDownloadUrl)
|
||||
setPdfUrl(result.pdfUrl)
|
||||
setRawLog(result.log)
|
||||
|
||||
// sample compile stats for real users
|
||||
if (!window.user.alphaProgram && data.status === 'success') {
|
||||
sendMBSampled(
|
||||
'compile-result',
|
||||
{
|
||||
errors: result.logEntries.errors.length,
|
||||
warnings: result.logEntries.warnings.length,
|
||||
typesetting: result.logEntries.typesetting.length,
|
||||
newPdfPreview: true, // TODO: is this useful?
|
||||
},
|
||||
0.01
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
switch (data.status) {
|
||||
case 'success':
|
||||
setError(undefined)
|
||||
setShowLogs(false)
|
||||
break
|
||||
|
||||
case 'clsi-maintenance':
|
||||
case 'compile-in-progress':
|
||||
case 'exited':
|
||||
case 'failure':
|
||||
case 'project-too-large':
|
||||
case 'terminated':
|
||||
case 'too-recently-compiled':
|
||||
setError(data.status)
|
||||
break
|
||||
|
||||
case 'timedout':
|
||||
setError('timedout')
|
||||
|
||||
if (!hasPremiumCompile && isProjectOwner) {
|
||||
send(
|
||||
'subscription-funnel',
|
||||
'editor-click-feature',
|
||||
'compile-timeout'
|
||||
)
|
||||
sendMB('paywall-prompt', {
|
||||
'paywall-type': 'compile-timeout',
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
case 'autocompile-backoff':
|
||||
if (!data.options.isAutoCompileOnLoad) {
|
||||
setError('autocompile-disabled')
|
||||
setAutoCompile(false)
|
||||
sendMB('autocompile-rate-limited', { hasPremiumCompile })
|
||||
}
|
||||
break
|
||||
|
||||
case 'unavailable':
|
||||
setError('clsi-unavailable')
|
||||
break
|
||||
|
||||
case 'validation-problems':
|
||||
setError('validation-problems')
|
||||
setValidationIssues(data.validationProblems)
|
||||
break
|
||||
|
||||
default:
|
||||
setError('error')
|
||||
break
|
||||
}
|
||||
}
|
||||
}, [
|
||||
compiler,
|
||||
data,
|
||||
ide,
|
||||
hasPremiumCompile,
|
||||
isProjectOwner,
|
||||
projectId,
|
||||
setAutoCompile,
|
||||
setClsiServerId,
|
||||
setLogEntries,
|
||||
setLogEntryAnnotations,
|
||||
setPdfDownloadUrl,
|
||||
setPdfUrl,
|
||||
])
|
||||
|
||||
// switch to logs if there's an error
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
setShowLogs(true)
|
||||
}
|
||||
}, [error])
|
||||
|
||||
// recompile on key press
|
||||
useEffect(() => {
|
||||
const listener = event => {
|
||||
compiler.compile(event.detail)
|
||||
}
|
||||
|
||||
window.addEventListener('pdf:recompile', listener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('pdf:recompile', listener)
|
||||
}
|
||||
}, [compiler])
|
||||
|
||||
// whether there has been an autocompile linting error, if syntax validation is switched on
|
||||
const autoCompileLintingError = Boolean(
|
||||
autoCompile && syntaxValidation && hasLintingError
|
||||
)
|
||||
|
||||
const codeCheckFailed = stopOnValidationError && autoCompileLintingError
|
||||
|
||||
// show that the project has pending changes
|
||||
const hasChanges = Boolean(
|
||||
autoCompile && uncompiled && compiledOnce && !codeCheckFailed
|
||||
)
|
||||
|
||||
// the project is available for auto-compiling
|
||||
const canAutoCompile = Boolean(
|
||||
autoCompile && !compiling && !pdfHidden && !codeCheckFailed
|
||||
)
|
||||
|
||||
// call the debounced autocompile function if the project is available for auto-compiling and it has changed
|
||||
useEffect(() => {
|
||||
if (canAutoCompile && changedAt > 0) {
|
||||
compiler.debouncedAutoCompile()
|
||||
} else {
|
||||
compiler.debouncedAutoCompile.cancel()
|
||||
}
|
||||
}, [compiler, canAutoCompile, changedAt])
|
||||
|
||||
// cancel debounced recompile on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
compiler.debouncedAutoCompile.cancel()
|
||||
}
|
||||
}, [compiler])
|
||||
|
||||
// record doc changes when notified by the editor
|
||||
useEffect(() => {
|
||||
const listener = () => {
|
||||
setChangedAt(Date.now())
|
||||
}
|
||||
|
||||
window.addEventListener('doc:changed', listener)
|
||||
window.addEventListener('doc:saved', listener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('doc:changed', listener)
|
||||
window.removeEventListener('doc:saved', listener)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// start a compile manually
|
||||
const startCompile = useCallback(() => {
|
||||
compiler.compile()
|
||||
}, [compiler])
|
||||
|
||||
// stop a compile manually
|
||||
const stopCompile = useCallback(() => {
|
||||
compiler.stopCompile()
|
||||
}, [compiler])
|
||||
|
||||
// clear the compile cache
|
||||
const clearCache = useCallback(() => {
|
||||
setClearingCache(true)
|
||||
|
||||
return compiler.clearCache().finally(() => {
|
||||
setClearingCache(false)
|
||||
})
|
||||
}, [compiler])
|
||||
|
||||
// clear the cache then run a compile, triggered by a menu item
|
||||
const recompileFromScratch = useCallback(() => {
|
||||
clearCache().then(() => {
|
||||
compiler.compile()
|
||||
})
|
||||
}, [clearCache, compiler])
|
||||
|
||||
// switch to either side-by-side or flat (full-width) layout
|
||||
// TODO: move this into LayoutContext?
|
||||
const switchLayout = useCallback(() => {
|
||||
setPdfLayout(layout => {
|
||||
const newLayout = layout === 'sideBySide' ? 'flat' : 'sideBySide'
|
||||
setView(newLayout === 'sideBySide' ? 'editor' : 'pdf')
|
||||
setPdfLayout(newLayout)
|
||||
window.localStorage.setItem('pdf.layout', newLayout)
|
||||
})
|
||||
}, [setPdfLayout, setView])
|
||||
|
||||
// the context value, memoized to minimize re-rendering
|
||||
const value = useMemo(() => {
|
||||
return {
|
||||
autoCompile,
|
||||
codeCheckFailed,
|
||||
clearCache,
|
||||
clearingCache,
|
||||
compiling,
|
||||
draft,
|
||||
error,
|
||||
fileList,
|
||||
hasChanges,
|
||||
hasLintingError,
|
||||
logEntries,
|
||||
pdfDownloadUrl,
|
||||
pdfLayout,
|
||||
pdfUrl,
|
||||
rawLog,
|
||||
recompileFromScratch,
|
||||
setAutoCompile,
|
||||
setDraft,
|
||||
setHasLintingError, // only for stories
|
||||
setShowLogs,
|
||||
setStopOnValidationError,
|
||||
showLogs,
|
||||
startCompile,
|
||||
stopCompile,
|
||||
stopOnValidationError,
|
||||
switchLayout,
|
||||
uncompiled,
|
||||
validationIssues,
|
||||
}
|
||||
}, [
|
||||
autoCompile,
|
||||
codeCheckFailed,
|
||||
clearCache,
|
||||
clearingCache,
|
||||
compiling,
|
||||
draft,
|
||||
error,
|
||||
fileList,
|
||||
hasChanges,
|
||||
hasLintingError,
|
||||
logEntries,
|
||||
pdfDownloadUrl,
|
||||
pdfLayout,
|
||||
pdfUrl,
|
||||
rawLog,
|
||||
recompileFromScratch,
|
||||
setAutoCompile,
|
||||
setDraft,
|
||||
setHasLintingError, // only for stories
|
||||
setStopOnValidationError,
|
||||
showLogs,
|
||||
startCompile,
|
||||
stopCompile,
|
||||
stopOnValidationError,
|
||||
switchLayout,
|
||||
uncompiled,
|
||||
validationIssues,
|
||||
])
|
||||
|
||||
return (
|
||||
<PdfPreviewContext.Provider value={value}>
|
||||
{children}
|
||||
</PdfPreviewContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
PdfPreviewContext.Provider.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
autoCompile: PropTypes.bool.isRequired,
|
||||
clearCache: PropTypes.func.isRequired,
|
||||
clearingCache: PropTypes.bool.isRequired,
|
||||
codeCheckFailed: PropTypes.bool.isRequired,
|
||||
compiling: PropTypes.bool.isRequired,
|
||||
draft: PropTypes.bool.isRequired,
|
||||
error: PropTypes.string,
|
||||
fileList: PropTypes.object,
|
||||
hasChanges: PropTypes.bool.isRequired,
|
||||
hasLintingError: PropTypes.bool,
|
||||
logEntries: PropTypes.object,
|
||||
pdfDownloadUrl: PropTypes.string,
|
||||
pdfLayout: PropTypes.string,
|
||||
pdfUrl: PropTypes.string,
|
||||
rawLog: PropTypes.string,
|
||||
recompileFromScratch: PropTypes.func.isRequired,
|
||||
setAutoCompile: PropTypes.func.isRequired,
|
||||
setDraft: PropTypes.func.isRequired,
|
||||
setHasLintingError: PropTypes.func.isRequired, // only for storybook
|
||||
setShowLogs: PropTypes.func.isRequired,
|
||||
setStopOnValidationError: PropTypes.func.isRequired,
|
||||
showLogs: PropTypes.bool.isRequired,
|
||||
startCompile: PropTypes.func.isRequired,
|
||||
stopCompile: PropTypes.func.isRequired,
|
||||
stopOnValidationError: PropTypes.bool.isRequired,
|
||||
switchLayout: PropTypes.func.isRequired,
|
||||
uncompiled: PropTypes.bool,
|
||||
validationIssues: PropTypes.object,
|
||||
}),
|
||||
}
|
||||
|
||||
export function usePdfPreviewContext() {
|
||||
const context = useContext(PdfPreviewContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'usePdfPreviewContext is only available inside PdfPreviewProvider'
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
|
@ -45,10 +45,6 @@ export default class DocumentCompiler {
|
|||
)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.debouncedAutoCompile.cancel()
|
||||
}
|
||||
|
||||
// The main "compile" function.
|
||||
// Call this directly to run a compile now, otherwise call debouncedAutoCompile.
|
||||
async compile(options = {}) {
|
||||
|
|
|
@ -151,6 +151,23 @@ export default class PDFJSWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
set currentPosition(position) {
|
||||
const destArray = [
|
||||
null,
|
||||
{
|
||||
name: 'XYZ', // 'XYZ' = scroll to the given coordinates
|
||||
},
|
||||
position.offset.left,
|
||||
position.offset.top,
|
||||
null,
|
||||
]
|
||||
|
||||
this.viewer.scrollPageIntoView({
|
||||
pageNumber: position.page + 1,
|
||||
destArray,
|
||||
})
|
||||
}
|
||||
|
||||
abortDocumentLoading() {
|
||||
this.loadDocumentTask = undefined
|
||||
}
|
||||
|
|
|
@ -1,27 +1,72 @@
|
|||
import { createContext, useContext, useMemo } from 'react'
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import useScopeValue from '../hooks/use-scope-value'
|
||||
import usePersistedState from '../hooks/use-persisted-state'
|
||||
import useAbortController from '../hooks/use-abort-controller'
|
||||
import DocumentCompiler from '../../features/pdf-preview/util/compiler'
|
||||
import {
|
||||
send,
|
||||
sendMB,
|
||||
sendMBSampled,
|
||||
} from '../../infrastructure/event-tracking'
|
||||
import {
|
||||
buildLogEntryAnnotations,
|
||||
handleOutputFiles,
|
||||
} from '../../features/pdf-preview/util/output-files'
|
||||
import { useIdeContext } from './ide-context'
|
||||
import { useProjectContext } from './project-context'
|
||||
import { useEditorContext } from './editor-context'
|
||||
|
||||
export const CompileContext = createContext()
|
||||
|
||||
CompileContext.Provider.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
autoCompile: PropTypes.bool.isRequired,
|
||||
clearingCache: PropTypes.bool.isRequired,
|
||||
clsiServerId: PropTypes.string,
|
||||
codeCheckFailed: PropTypes.bool.isRequired,
|
||||
compiling: PropTypes.bool.isRequired,
|
||||
draft: PropTypes.bool.isRequired,
|
||||
error: PropTypes.string,
|
||||
fileList: PropTypes.object,
|
||||
hasChanges: PropTypes.bool.isRequired,
|
||||
hasLintingError: PropTypes.bool,
|
||||
logEntries: PropTypes.object,
|
||||
logEntryAnnotations: PropTypes.object,
|
||||
pdfDownloadUrl: PropTypes.string,
|
||||
pdfUrl: PropTypes.string,
|
||||
setClsiServerId: PropTypes.func.isRequired,
|
||||
setLogEntries: PropTypes.func.isRequired,
|
||||
setLogEntryAnnotations: PropTypes.func.isRequired,
|
||||
setPdfDownloadUrl: PropTypes.func.isRequired,
|
||||
setPdfUrl: PropTypes.func.isRequired,
|
||||
setUncompiled: PropTypes.func.isRequired,
|
||||
rawLog: PropTypes.string,
|
||||
setAutoCompile: PropTypes.func.isRequired,
|
||||
setDraft: PropTypes.func.isRequired,
|
||||
setHasLintingError: PropTypes.func.isRequired, // only for storybook
|
||||
setShowLogs: PropTypes.func.isRequired,
|
||||
setStopOnValidationError: PropTypes.func.isRequired,
|
||||
showLogs: PropTypes.bool.isRequired,
|
||||
stopOnValidationError: PropTypes.bool.isRequired,
|
||||
uncompiled: PropTypes.bool,
|
||||
validationIssues: PropTypes.object,
|
||||
}),
|
||||
}
|
||||
|
||||
export function CompileProvider({ children }) {
|
||||
const ide = useIdeContext()
|
||||
|
||||
const { hasPremiumCompile, isProjectOwner } = useEditorContext()
|
||||
|
||||
const project = useProjectContext()
|
||||
|
||||
const projectId = project._id
|
||||
|
||||
// whether a compile is in progress
|
||||
const [compiling, setCompiling] = useState(false)
|
||||
|
||||
// the log entries parsed from the compile output log
|
||||
const [logEntries, setLogEntries] = useScopeValue('pdf.logEntries')
|
||||
|
||||
|
@ -42,34 +87,368 @@ export function CompileProvider({ children }) {
|
|||
// the id of the CLSI server which ran the compile
|
||||
const [clsiServerId, setClsiServerId] = useScopeValue('pdf.clsiServerId')
|
||||
|
||||
// data received in response to a compile request
|
||||
const [data, setData] = useState()
|
||||
|
||||
// whether the project has been compiled yet
|
||||
const [compiledOnce, setCompiledOnce] = useState(false)
|
||||
|
||||
// whether the cache is being cleared
|
||||
const [clearingCache, setClearingCache] = useState(false)
|
||||
|
||||
// whether the logs should be visible
|
||||
const [showLogs, setShowLogs] = useState(false)
|
||||
|
||||
// an error that occurred
|
||||
const [error, setError] = useState()
|
||||
|
||||
// the list of files that can be downloaded
|
||||
const [fileList, setFileList] = useState()
|
||||
|
||||
// the raw contents of the log file
|
||||
const [rawLog, setRawLog] = useState()
|
||||
|
||||
// validation issues from CLSI
|
||||
const [validationIssues, setValidationIssues] = useState()
|
||||
|
||||
// whether autocompile is switched on
|
||||
const [autoCompile, _setAutoCompile] = usePersistedState(
|
||||
`autocompile_enabled:${projectId}`,
|
||||
false,
|
||||
true
|
||||
)
|
||||
|
||||
// whether the compile should run in draft mode
|
||||
const [draft, setDraft] = usePersistedState(`draft:${projectId}`, false, true)
|
||||
|
||||
// whether compiling should be prevented if there are linting errors
|
||||
const [stopOnValidationError, setStopOnValidationError] = usePersistedState(
|
||||
`stop_on_validation_error:${projectId}`,
|
||||
true,
|
||||
true
|
||||
)
|
||||
|
||||
// the Document currently open in the editor
|
||||
const [currentDoc] = useScopeValue('editor.sharejs_doc')
|
||||
|
||||
// whether the editor linter found errors
|
||||
const [hasLintingError, setHasLintingError] = useScopeValue('hasLintingError')
|
||||
|
||||
// whether syntax validation is enabled globally
|
||||
const [syntaxValidation] = useScopeValue('settings.syntaxValidation')
|
||||
|
||||
// the timestamp that a doc was last changed or saved
|
||||
const [changedAt, setChangedAt] = useState(0)
|
||||
|
||||
const { signal } = useAbortController()
|
||||
|
||||
// the document compiler
|
||||
const [compiler] = useState(() => {
|
||||
return new DocumentCompiler({
|
||||
project,
|
||||
setChangedAt,
|
||||
setCompiling,
|
||||
setData,
|
||||
setError,
|
||||
signal,
|
||||
})
|
||||
})
|
||||
|
||||
// keep currentDoc in sync with the compiler
|
||||
useEffect(() => {
|
||||
compiler.currentDoc = currentDoc
|
||||
}, [compiler, currentDoc])
|
||||
|
||||
// keep draft setting in sync with the compiler
|
||||
useEffect(() => {
|
||||
compiler.draft = draft
|
||||
}, [compiler, draft])
|
||||
|
||||
// pass the "uncompiled" value up into the scope for use outside this context provider
|
||||
useEffect(() => {
|
||||
setUncompiled(changedAt > 0)
|
||||
}, [setUncompiled, changedAt])
|
||||
|
||||
// record changes to the autocompile setting
|
||||
const setAutoCompile = useCallback(
|
||||
value => {
|
||||
_setAutoCompile(value)
|
||||
sendMB('autocompile-setting-changed', { value })
|
||||
},
|
||||
[_setAutoCompile]
|
||||
)
|
||||
|
||||
// always compile the PDF once after opening the project, after the doc has loaded
|
||||
useEffect(() => {
|
||||
if (!compiledOnce && currentDoc) {
|
||||
setCompiledOnce(true)
|
||||
compiler.compile({ isAutoCompileOnLoad: true })
|
||||
}
|
||||
}, [compiledOnce, currentDoc, compiler])
|
||||
|
||||
// handle the data returned from a compile request
|
||||
// note: this should _only_ run when `data` changes,
|
||||
// the other dependencies must all be static
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
if (data.clsiServerId) {
|
||||
setClsiServerId(data.clsiServerId) // set in scope, for PdfSynctexController
|
||||
compiler.clsiServerId = data.clsiServerId
|
||||
}
|
||||
|
||||
if (data.compileGroup) {
|
||||
compiler.compileGroup = data.compileGroup
|
||||
}
|
||||
|
||||
if (data.outputFiles) {
|
||||
handleOutputFiles(projectId, data).then(result => {
|
||||
setLogEntryAnnotations(
|
||||
buildLogEntryAnnotations(result.logEntries.all, ide.fileTreeManager)
|
||||
)
|
||||
setLogEntries(result.logEntries)
|
||||
setFileList(result.fileList)
|
||||
setPdfDownloadUrl(result.pdfDownloadUrl)
|
||||
setPdfUrl(result.pdfUrl)
|
||||
setRawLog(result.log)
|
||||
|
||||
// sample compile stats for real users
|
||||
if (!window.user.alphaProgram && data.status === 'success') {
|
||||
sendMBSampled(
|
||||
'compile-result',
|
||||
{
|
||||
errors: result.logEntries.errors.length,
|
||||
warnings: result.logEntries.warnings.length,
|
||||
typesetting: result.logEntries.typesetting.length,
|
||||
newPdfPreview: true, // TODO: is this useful?
|
||||
},
|
||||
0.01
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
switch (data.status) {
|
||||
case 'success':
|
||||
setError(undefined)
|
||||
setShowLogs(false)
|
||||
break
|
||||
|
||||
case 'clsi-maintenance':
|
||||
case 'compile-in-progress':
|
||||
case 'exited':
|
||||
case 'failure':
|
||||
case 'project-too-large':
|
||||
case 'rate-limited':
|
||||
case 'terminated':
|
||||
case 'too-recently-compiled':
|
||||
setError(data.status)
|
||||
break
|
||||
|
||||
case 'timedout':
|
||||
setError('timedout')
|
||||
|
||||
if (!hasPremiumCompile && isProjectOwner) {
|
||||
send(
|
||||
'subscription-funnel',
|
||||
'editor-click-feature',
|
||||
'compile-timeout'
|
||||
)
|
||||
sendMB('paywall-prompt', {
|
||||
'paywall-type': 'compile-timeout',
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
case 'autocompile-backoff':
|
||||
if (!data.options.isAutoCompileOnLoad) {
|
||||
setError('autocompile-disabled')
|
||||
setAutoCompile(false)
|
||||
sendMB('autocompile-rate-limited', { hasPremiumCompile })
|
||||
}
|
||||
break
|
||||
|
||||
case 'unavailable':
|
||||
setError('clsi-unavailable')
|
||||
break
|
||||
|
||||
case 'validation-problems':
|
||||
setError('validation-problems')
|
||||
setValidationIssues(data.validationProblems)
|
||||
break
|
||||
|
||||
default:
|
||||
setError('error')
|
||||
break
|
||||
}
|
||||
}
|
||||
}, [
|
||||
compiler,
|
||||
data,
|
||||
ide,
|
||||
hasPremiumCompile,
|
||||
isProjectOwner,
|
||||
projectId,
|
||||
setAutoCompile,
|
||||
setClsiServerId,
|
||||
setLogEntries,
|
||||
setLogEntryAnnotations,
|
||||
setPdfDownloadUrl,
|
||||
setPdfUrl,
|
||||
])
|
||||
|
||||
// switch to logs if there's an error
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
setShowLogs(true)
|
||||
}
|
||||
}, [error])
|
||||
|
||||
// recompile on key press
|
||||
useEffect(() => {
|
||||
const listener = event => {
|
||||
compiler.compile(event.detail)
|
||||
}
|
||||
|
||||
window.addEventListener('pdf:recompile', listener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('pdf:recompile', listener)
|
||||
}
|
||||
}, [compiler])
|
||||
|
||||
// whether there has been an autocompile linting error, if syntax validation is switched on
|
||||
const autoCompileLintingError = Boolean(
|
||||
autoCompile && syntaxValidation && hasLintingError
|
||||
)
|
||||
|
||||
const codeCheckFailed = stopOnValidationError && autoCompileLintingError
|
||||
|
||||
// show that the project has pending changes
|
||||
const hasChanges = Boolean(
|
||||
autoCompile && uncompiled && compiledOnce && !codeCheckFailed
|
||||
)
|
||||
|
||||
// the project is available for auto-compiling
|
||||
const canAutoCompile = Boolean(autoCompile && !compiling && !codeCheckFailed)
|
||||
|
||||
// call the debounced autocompile function if the project is available for auto-compiling and it has changed
|
||||
useEffect(() => {
|
||||
if (canAutoCompile && changedAt > 0) {
|
||||
compiler.debouncedAutoCompile()
|
||||
} else {
|
||||
compiler.debouncedAutoCompile.cancel()
|
||||
}
|
||||
}, [compiler, canAutoCompile, changedAt])
|
||||
|
||||
// cancel debounced recompile on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
compiler.debouncedAutoCompile.cancel()
|
||||
}
|
||||
}, [compiler])
|
||||
|
||||
// record doc changes when notified by the editor
|
||||
useEffect(() => {
|
||||
const listener = event => {
|
||||
setChangedAt(Date.now())
|
||||
}
|
||||
|
||||
window.addEventListener('doc:changed', listener)
|
||||
window.addEventListener('doc:saved', listener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('doc:changed', listener)
|
||||
window.removeEventListener('doc:saved', listener)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// start a compile manually
|
||||
const startCompile = useCallback(() => {
|
||||
compiler.compile()
|
||||
}, [compiler])
|
||||
|
||||
// stop a compile manually
|
||||
const stopCompile = useCallback(() => {
|
||||
compiler.stopCompile()
|
||||
}, [compiler])
|
||||
|
||||
// clear the compile cache
|
||||
const clearCache = useCallback(() => {
|
||||
setClearingCache(true)
|
||||
|
||||
return compiler.clearCache().finally(() => {
|
||||
setClearingCache(false)
|
||||
})
|
||||
}, [compiler, setClearingCache])
|
||||
|
||||
// clear the cache then run a compile, triggered by a menu item
|
||||
const recompileFromScratch = useCallback(() => {
|
||||
clearCache().then(() => {
|
||||
compiler.compile()
|
||||
})
|
||||
}, [clearCache, compiler])
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
autoCompile,
|
||||
clearCache,
|
||||
clearingCache,
|
||||
clsiServerId,
|
||||
codeCheckFailed,
|
||||
compiling,
|
||||
draft,
|
||||
error,
|
||||
fileList,
|
||||
hasChanges,
|
||||
hasLintingError,
|
||||
logEntries,
|
||||
logEntryAnnotations,
|
||||
pdfDownloadUrl,
|
||||
pdfUrl,
|
||||
setClsiServerId,
|
||||
setLogEntries,
|
||||
setLogEntryAnnotations,
|
||||
setPdfDownloadUrl,
|
||||
setPdfUrl,
|
||||
setUncompiled,
|
||||
rawLog,
|
||||
recompileFromScratch,
|
||||
setAutoCompile,
|
||||
setClearingCache,
|
||||
setCompiling,
|
||||
setDraft,
|
||||
setHasLintingError, // only for stories
|
||||
setShowLogs,
|
||||
setStopOnValidationError,
|
||||
showLogs,
|
||||
startCompile,
|
||||
stopCompile,
|
||||
stopOnValidationError,
|
||||
uncompiled,
|
||||
validationIssues,
|
||||
}),
|
||||
[
|
||||
autoCompile,
|
||||
clearCache,
|
||||
clearingCache,
|
||||
clsiServerId,
|
||||
codeCheckFailed,
|
||||
compiling,
|
||||
draft,
|
||||
error,
|
||||
fileList,
|
||||
hasChanges,
|
||||
hasLintingError,
|
||||
logEntries,
|
||||
logEntryAnnotations,
|
||||
pdfDownloadUrl,
|
||||
pdfUrl,
|
||||
setClsiServerId,
|
||||
setLogEntries,
|
||||
setLogEntryAnnotations,
|
||||
setPdfDownloadUrl,
|
||||
setPdfUrl,
|
||||
setUncompiled,
|
||||
rawLog,
|
||||
recompileFromScratch,
|
||||
setAutoCompile,
|
||||
setDraft,
|
||||
setHasLintingError,
|
||||
setStopOnValidationError,
|
||||
showLogs,
|
||||
startCompile,
|
||||
stopCompile,
|
||||
stopOnValidationError,
|
||||
uncompiled,
|
||||
validationIssues,
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { createContext, useContext, useCallback, useMemo } from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import useScopeValue from '../hooks/use-scope-value'
|
||||
import { useIdeContext } from './ide-context'
|
||||
import localStorage from '../../infrastructure/local-storage'
|
||||
|
||||
export const LayoutContext = createContext()
|
||||
|
||||
|
@ -15,7 +16,7 @@ LayoutContext.Provider.propTypes = {
|
|||
setReviewPanelOpen: PropTypes.func.isRequired,
|
||||
leftMenuShown: PropTypes.bool,
|
||||
setLeftMenuShown: PropTypes.func.isRequired,
|
||||
pdfLayout: PropTypes.oneOf(['sideBySide', 'flat', 'split']).isRequired,
|
||||
pdfLayout: PropTypes.oneOf(['sideBySide', 'flat']).isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
|
@ -53,14 +54,20 @@ export function LayoutProvider({ children }) {
|
|||
// whether to display the editor and preview side-by-side or full-width ("flat")
|
||||
const [pdfLayout, setPdfLayout] = useScopeValue('ui.pdfLayout')
|
||||
|
||||
// whether the PDF preview pane is hidden
|
||||
const [pdfHidden] = useScopeValue('ui.pdfHidden')
|
||||
// switch to either side-by-side or flat (full-width) layout
|
||||
const switchLayout = useCallback(() => {
|
||||
setPdfLayout(layout => {
|
||||
const newLayout = layout === 'sideBySide' ? 'flat' : 'sideBySide'
|
||||
setView(newLayout === 'sideBySide' ? 'editor' : 'pdf')
|
||||
setPdfLayout(newLayout)
|
||||
localStorage.setItem('pdf.layout', newLayout)
|
||||
})
|
||||
}, [setPdfLayout, setView])
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
chatIsOpen,
|
||||
leftMenuShown,
|
||||
pdfHidden,
|
||||
pdfLayout,
|
||||
reviewPanelOpen,
|
||||
setChatIsOpen,
|
||||
|
@ -68,12 +75,12 @@ export function LayoutProvider({ children }) {
|
|||
setPdfLayout,
|
||||
setReviewPanelOpen,
|
||||
setView,
|
||||
switchLayout,
|
||||
view,
|
||||
}),
|
||||
[
|
||||
chatIsOpen,
|
||||
leftMenuShown,
|
||||
pdfHidden,
|
||||
pdfLayout,
|
||||
reviewPanelOpen,
|
||||
setChatIsOpen,
|
||||
|
@ -81,6 +88,7 @@ export function LayoutProvider({ children }) {
|
|||
setPdfLayout,
|
||||
setReviewPanelOpen,
|
||||
setView,
|
||||
switchLayout,
|
||||
view,
|
||||
]
|
||||
)
|
||||
|
|
|
@ -17,11 +17,11 @@ export function ContextRoot({ children, ide, settings }) {
|
|||
<UserProvider>
|
||||
<ProjectProvider>
|
||||
<EditorProvider settings={settings}>
|
||||
<CompileProvider>
|
||||
<LayoutProvider>
|
||||
<LayoutProvider>
|
||||
<CompileProvider>
|
||||
<ChatProvider>{children}</ChatProvider>
|
||||
</LayoutProvider>
|
||||
</CompileProvider>
|
||||
</CompileProvider>
|
||||
</LayoutProvider>
|
||||
</EditorProvider>
|
||||
</ProjectProvider>
|
||||
</UserProvider>
|
||||
|
|
|
@ -3,9 +3,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
|||
import useFetchMock from './hooks/use-fetch-mock'
|
||||
import { setupContext } from './fixtures/context'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import PdfPreviewProvider, {
|
||||
usePdfPreviewContext,
|
||||
} from '../js/features/pdf-preview/contexts/pdf-preview-context'
|
||||
import PdfPreviewPane from '../js/features/pdf-preview/components/pdf-preview-pane'
|
||||
import PdfPreview from '../js/features/pdf-preview/components/pdf-preview'
|
||||
import PdfPreviewToolbar from '../js/features/pdf-preview/components/pdf-preview-toolbar'
|
||||
|
@ -15,6 +12,7 @@ import PdfLogsViewer from '../js/features/pdf-preview/components/pdf-logs-viewer
|
|||
import examplePdf from './fixtures/storybook-example.pdf'
|
||||
import PdfPreviewError from '../js/features/pdf-preview/components/pdf-preview-error'
|
||||
import PdfPreviewHybridToolbar from '../js/features/pdf-preview/components/pdf-preview-hybrid-toolbar'
|
||||
import { useCompileContext } from '../js/shared/context/compile-context'
|
||||
|
||||
setupContext()
|
||||
|
||||
|
@ -230,7 +228,7 @@ export const Interactive = () => {
|
|||
}, [])
|
||||
|
||||
const Inner = () => {
|
||||
const context = usePdfPreviewContext()
|
||||
const context = useCompileContext()
|
||||
|
||||
const { setHasLintingError } = context
|
||||
|
||||
|
@ -369,10 +367,8 @@ export const Interactive = () => {
|
|||
|
||||
return withContextRoot(
|
||||
<div className="pdf-viewer">
|
||||
<PdfPreviewProvider>
|
||||
<PdfPreviewPane />
|
||||
<Inner />
|
||||
</PdfPreviewProvider>
|
||||
<PdfPreviewPane />
|
||||
<Inner />
|
||||
</div>,
|
||||
scope
|
||||
)
|
||||
|
@ -406,7 +402,7 @@ export const CompileError = () => {
|
|||
})
|
||||
|
||||
const Inner = () => {
|
||||
const { startCompile } = usePdfPreviewContext()
|
||||
const { startCompile } = useCompileContext()
|
||||
|
||||
const handleStatusChange = useCallback(
|
||||
event => {
|
||||
|
@ -441,10 +437,10 @@ export const CompileError = () => {
|
|||
}
|
||||
|
||||
return withContextRoot(
|
||||
<PdfPreviewProvider>
|
||||
<>
|
||||
<PdfPreviewPane />
|
||||
<Inner />
|
||||
</PdfPreviewProvider>,
|
||||
</>,
|
||||
scope
|
||||
)
|
||||
}
|
||||
|
@ -470,7 +466,7 @@ const compileErrors = [
|
|||
|
||||
export const DisplayError = () => {
|
||||
return withContextRoot(
|
||||
<PdfPreviewProvider>
|
||||
<>
|
||||
{compileErrors.map(error => (
|
||||
<div
|
||||
key={error}
|
||||
|
@ -480,7 +476,7 @@ export const DisplayError = () => {
|
|||
<PdfPreviewError error={error} />
|
||||
</div>
|
||||
))}
|
||||
</PdfPreviewProvider>,
|
||||
</>,
|
||||
scope
|
||||
)
|
||||
}
|
||||
|
@ -489,11 +485,9 @@ export const Toolbar = () => {
|
|||
useFetchMock(fetchMock => mockCompile(fetchMock, 500))
|
||||
|
||||
return withContextRoot(
|
||||
<PdfPreviewProvider>
|
||||
<div className="pdf">
|
||||
<PdfPreviewToolbar />
|
||||
</div>
|
||||
</PdfPreviewProvider>,
|
||||
<div className="pdf">
|
||||
<PdfPreviewToolbar />
|
||||
</div>,
|
||||
scope
|
||||
)
|
||||
}
|
||||
|
@ -505,11 +499,9 @@ export const HybridToolbar = () => {
|
|||
})
|
||||
|
||||
return withContextRoot(
|
||||
<PdfPreviewProvider>
|
||||
<div className="pdf">
|
||||
<PdfPreviewHybridToolbar />
|
||||
</div>
|
||||
</PdfPreviewProvider>,
|
||||
<div className="pdf">
|
||||
<PdfPreviewHybridToolbar />
|
||||
</div>,
|
||||
scope
|
||||
)
|
||||
}
|
||||
|
@ -530,21 +522,15 @@ export const FileList = () => {
|
|||
|
||||
export const Logs = () => {
|
||||
useFetchMock(fetchMock => {
|
||||
mockCompile(fetchMock, 0)
|
||||
mockCompileError(fetchMock, 400, 0)
|
||||
mockBuildFile(fetchMock)
|
||||
mockClearCache(fetchMock)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
dispatchProjectJoined()
|
||||
}, [])
|
||||
|
||||
return withContextRoot(
|
||||
<PdfPreviewProvider>
|
||||
<div className="pdf">
|
||||
<PdfLogsViewer />
|
||||
</div>
|
||||
</PdfPreviewProvider>,
|
||||
<div className="pdf">
|
||||
<PdfLogsViewer />
|
||||
</div>,
|
||||
scope
|
||||
)
|
||||
}
|
||||
|
@ -577,10 +563,5 @@ export const ValidationIssues = () => {
|
|||
dispatchProjectJoined()
|
||||
}, [])
|
||||
|
||||
return withContextRoot(
|
||||
<PdfPreviewProvider>
|
||||
<PdfPreviewPane />
|
||||
</PdfPreviewProvider>,
|
||||
scope
|
||||
)
|
||||
return withContextRoot(<PdfPreviewPane />, scope)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue