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 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' 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' 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
 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.
 \\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 = usePdfPreviewContext()

    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 (
      
{values.boolean.map(([key, value]) => { return ( ) })}
{value ? '🟢' : '🔴'} {key}
{values.other.map(([key, value]) => { return ( ) })}
{key}
                          {JSON.stringify(value, null, 2)}
                        
) } return withContextRoot(
, 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 } = usePdfPreviewContext() const handleStatusChange = useCallback( event => { setStatus(event.target.value) window.setTimeout(() => { startCompile() }, 0) }, [startCompile] ) return (
) } return withContextRoot( , 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 => (
{error}
))}
, scope ) } export const Toolbar = () => { useFetchMock(fetchMock => mockCompile(fetchMock, 500)) return withContextRoot(
, scope ) } export const HybridToolbar = () => { useFetchMock(fetchMock => { mockCompile(fetchMock, 500) mockBuildFile(fetchMock) }) return withContextRoot(
, scope ) } export const FileList = () => { const fileList = useMemo(() => { return buildFileList(outputFiles) }, []) return (
) } export const Logs = () => { useFetchMock(fetchMock => { mockCompile(fetchMock, 0) mockBuildFile(fetchMock) mockClearCache(fetchMock) }) useEffect(() => { dispatchProjectJoined() }, []) return withContextRoot(
, 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( , scope ) }