| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- import { withContextRoot } from './utils/with-context-root'
- 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 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'
- import PdfFileList from '../js/features/pdf-preview/components/pdf-file-list'
- import { buildFileList } from '../js/features/pdf-preview/util/file-list'
- 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()
- export default {
- title: 'PDF Preview',
- component: PdfPreview,
- subcomponents: {
- PdfPreviewToolbar,
- PdfPreviewHybridToolbar,
- PdfFileList,
- PdfPreviewError,
- },
- }
- const project = {
- _id: 'a-project',
- name: 'A Project',
- features: {},
- tokens: {},
- owner: {
- _id: 'a-user',
- email: 'stories@overleaf.com',
- },
- members: [],
- invites: [],
- }
- const scope = {
- project,
- settings: {
- syntaxValidation: true,
- },
- hasLintingError: false,
- $applyAsync: () => {},
- editor: {
- sharejs_doc: {
- doc_id: 'test-doc',
- getSnapshot: () => 'some doc content',
- },
- },
- }
- const dispatchProjectJoined = () => {
- window.dispatchEvent(new CustomEvent('project:joined', { detail: project }))
- }
- const dispatchDocChanged = () => {
- window.dispatchEvent(
- new CustomEvent('doc:changed', { detail: { doc_id: 'foo' } })
- )
- }
- const outputFiles = [
- {
- path: 'output.pdf',
- build: '123',
- url: '/build/output.pdf',
- type: 'pdf',
- },
- {
- path: 'output.bbl',
- build: '123',
- url: '/build/output.bbl',
- type: 'bbl',
- },
- {
- path: 'output.bib',
- build: '123',
- url: '/build/output.bib',
- type: 'bib',
- },
- {
- path: 'example.txt',
- build: '123',
- url: '/build/example.txt',
- type: 'txt',
- },
- {
- path: 'output.log',
- build: '123',
- url: '/build/output.log',
- type: 'log',
- },
- {
- path: 'output.blg',
- build: '123',
- url: '/build/output.blg',
- type: 'blg',
- },
- ]
- const mockCompile = (fetchMock, delay = 1000) =>
- fetchMock.post(
- 'express:/project/:projectId/compile',
- {
- body: {
- status: 'success',
- clsiServerId: 'foo',
- compileGroup: 'priority',
- pdfDownloadDomain: '',
- outputFiles,
- },
- },
- { delay }
- )
- const mockCompileError = (fetchMock, status = 'success', delay = 1000) =>
- fetchMock.post(
- 'express:/project/:projectId/compile',
- {
- body: {
- status,
- clsiServerId: 'foo',
- compileGroup: 'priority',
- },
- },
- { delay, overwriteRoutes: true }
- )
- const mockCompileValidationIssues = (
- fetchMock,
- validationProblems,
- delay = 1000
- ) =>
- fetchMock.post(
- 'express:/project/:projectId/compile',
- () => {
- return {
- body: {
- status: 'validation-problems',
- validationProblems,
- clsiServerId: 'foo',
- compileGroup: 'priority',
- },
- }
- },
- { delay, overwriteRoutes: true }
- )
- const mockClearCache = fetchMock =>
- fetchMock.delete('express:/project/:projectId/output', 204, {
- delay: 1000,
- })
- const mockBuildFile = fetchMock =>
- fetchMock.get(
- 'express:/build/:file',
- (url, options, request) => {
- const { pathname } = new URL(url, 'https://example.com')
- switch (pathname) {
- case '/build/output.blg':
- return 'This is BibTeX, Version 4.0' // FIXME
- case '/build/output.log':
- return `
- The LaTeX compiler output
- * With a lot of details
- Wrapped in an HTML <pre> element with
- preformatted text which is to be presented exactly
- as written in the HTML file
- (whitespace included™)
- The text is typically rendered using a non-proportional ("monospace") font.
- LaTeX Font Info: External font \`cmex10' loaded for size
- (Font) <7> on input line 18.
- LaTeX Font Info: External font \`cmex10' loaded for size
- (Font) <5> on input line 18.
- ! Undefined control sequence.
- <recently read> \\Zlpha
- main.tex, line 23
- `
- case '/build/output.pdf':
- return new Promise(resolve => {
- const xhr = new XMLHttpRequest()
- xhr.addEventListener('load', () => {
- resolve({
- status: 200,
- headers: {
- 'Content-Length': xhr.getResponseHeader('Content-Length'),
- 'Content-Type': xhr.getResponseHeader('Content-Type'),
- },
- body: xhr.response,
- })
- })
- xhr.open('GET', examplePdf)
- xhr.responseType = 'arraybuffer'
- xhr.send()
- })
- default:
- return 404
- }
- },
- { sendAsJson: false }
- )
- export const Interactive = () => {
- useFetchMock(fetchMock => {
- mockCompile(fetchMock)
- mockBuildFile(fetchMock)
- mockClearCache(fetchMock)
- })
- useEffect(() => {
- dispatchProjectJoined()
- }, [])
- const Inner = () => {
- const context = useCompileContext()
- const { setHasLintingError } = context
- const toggleLintingError = useCallback(() => {
- setHasLintingError(value => !value)
- }, [setHasLintingError])
- const values = useMemo(() => {
- const entries = Object.entries(context).sort((a, b) => {
- return a[0].localeCompare(b[0])
- })
- const values = { boolean: [], other: [] }
- for (const entry of entries) {
- const type = typeof entry[1]
- if (type === 'boolean') {
- values.boolean.push(entry)
- } else if (type !== 'function') {
- values.other.push(entry)
- }
- }
- return values
- }, [context])
- return (
- <div
- style={{
- padding: 20,
- background: 'white',
- float: 'left',
- zIndex: 10,
- position: 'absolute',
- top: 60,
- bottom: 60,
- right: 20,
- left: 400,
- overflow: 'hidden',
- boxShadow: '0px 2px 5px #ccc',
- borderRadius: 3,
- }}
- >
- <div
- style={{
- display: 'flex',
- fontSize: 14,
- gap: 20,
- height: '100%',
- overflow: 'hidden',
- }}
- >
- <div style={{ height: '100%', overflow: 'auto', flexShrink: 0 }}>
- <table>
- <tbody>
- {values.boolean.map(([key, value]) => {
- return (
- <tr key={key} style={{ border: '1px solid #ddd' }}>
- <td style={{ padding: 5 }}>{value ? '🟢' : '🔴'}</td>
- <th style={{ padding: 5 }}>{key}</th>
- </tr>
- )
- })}
- </tbody>
- </table>
- <div>
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- gap: 10,
- margin: '10px 0',
- }}
- >
- <Button onClick={dispatchDocChanged}>trigger doc change</Button>
- <Button onClick={toggleLintingError}>
- toggle linting error
- </Button>
- </div>
- </div>
- </div>
- <div style={{ height: '100%', overflow: 'auto' }}>
- <table
- style={{
- width: '100%',
- overflow: 'hidden',
- }}
- >
- <tbody>
- {values.other.map(([key, value]) => {
- return (
- <tr
- key={key}
- style={{
- width: '100%',
- overflow: 'hidden',
- border: '1px solid #ddd',
- }}
- >
- <th
- style={{
- verticalAlign: 'top',
- padding: 5,
- }}
- >
- {key}
- </th>
- <td
- style={{
- overflow: 'auto',
- padding: 5,
- }}
- >
- <pre
- style={{
- margin: '0 10px',
- fontSize: 10,
- }}
- >
- {JSON.stringify(value, null, 2)}
- </pre>
- </td>
- </tr>
- )
- })}
- </tbody>
- </table>
- </div>
- </div>
- </div>
- )
- }
- return withContextRoot(
- <div className="pdf-viewer">
- <PdfPreviewPane />
- <Inner />
- </div>,
- scope
- )
- }
- const compileStatuses = [
- 'autocompile-backoff',
- 'clear-cache',
- 'clsi-maintenance',
- 'compile-in-progress',
- 'exited',
- 'failure',
- 'generic',
- 'project-too-large',
- 'rate-limited',
- 'success',
- 'terminated',
- 'timedout',
- 'too-recently-compiled',
- 'unavailable',
- 'validation-problems',
- 'foo',
- ]
- export const CompileError = () => {
- const [status, setStatus] = useState('success')
- useFetchMock(fetchMock => {
- mockCompileError(fetchMock, status, 0)
- mockBuildFile(fetchMock)
- })
- const Inner = () => {
- const { startCompile } = useCompileContext()
- const handleStatusChange = useCallback(
- event => {
- setStatus(event.target.value)
- window.setTimeout(() => {
- startCompile()
- }, 0)
- },
- [startCompile]
- )
- return (
- <div
- style={{
- position: 'absolute',
- bottom: 10,
- left: 10,
- background: 'white',
- padding: 10,
- }}
- >
- <label>
- {'status: '}
- <select value={status} onInput={handleStatusChange}>
- {compileStatuses.map(status => (
- <option key={status}>{status}</option>
- ))}
- </select>
- </label>
- </div>
- )
- }
- return withContextRoot(
- <>
- <PdfPreviewPane />
- <Inner />
- </>,
- scope
- )
- }
- const compileErrors = [
- 'autocompile-backoff',
- 'clear-cache',
- 'clsi-maintenance',
- 'compile-in-progress',
- 'exited',
- 'failure',
- 'generic',
- 'project-too-large',
- 'rate-limited',
- 'success',
- 'terminated',
- 'timedout',
- 'too-recently-compiled',
- 'unavailable',
- 'validation-problems',
- 'foo',
- ]
- export const DisplayError = () => {
- return withContextRoot(
- <>
- {compileErrors.map(error => (
- <div
- key={error}
- style={{ background: '#5d6879', padding: 10, margin: 5 }}
- >
- <div style={{ fontFamily: 'monospace', color: 'white' }}>{error}</div>
- <PdfPreviewError error={error} />
- </div>
- ))}
- </>,
- scope
- )
- }
- export const Toolbar = () => {
- useFetchMock(fetchMock => mockCompile(fetchMock, 500))
- return withContextRoot(
- <div className="pdf">
- <PdfPreviewToolbar />
- </div>,
- scope
- )
- }
- export const HybridToolbar = () => {
- useFetchMock(fetchMock => {
- mockCompile(fetchMock, 500)
- mockBuildFile(fetchMock)
- })
- return withContextRoot(
- <div className="pdf">
- <PdfPreviewHybridToolbar />
- </div>,
- scope
- )
- }
- export const FileList = () => {
- const fileList = useMemo(() => {
- return buildFileList(outputFiles)
- }, [])
- return (
- <div className="dropdown open">
- <div className="dropdown-menu">
- <PdfFileList fileList={fileList} />
- </div>
- </div>
- )
- }
- export const Logs = () => {
- useFetchMock(fetchMock => {
- mockCompileError(fetchMock, 400, 0)
- mockBuildFile(fetchMock)
- mockClearCache(fetchMock)
- })
- return withContextRoot(
- <div className="pdf">
- <PdfLogsViewer />
- </div>,
- scope
- )
- }
- const validationProblems = {
- sizeCheck: {
- resources: [
- { path: 'foo/bar', kbSize: 76221 },
- { path: 'bar/baz', kbSize: 2342 },
- ],
- },
- mainFile: true,
- conflictedPaths: [
- {
- path: 'foo/bar',
- },
- {
- path: 'foo/baz',
- },
- ],
- }
- export const ValidationIssues = () => {
- useFetchMock(fetchMock => {
- mockCompileValidationIssues(fetchMock, validationProblems, 0)
- mockBuildFile(fetchMock)
- })
- useEffect(() => {
- dispatchProjectJoined()
- }, [])
- return withContextRoot(<PdfPreviewPane />, scope)
- }
|