overleaf/services/web/frontend/stories/pdf-preview.stories.jsx
Antoine Clausse 4a8b79080b [storybook] Update Storybook and add a control for BS3/BS5 (#20948)
* [storybook] Update Storybook to version 8.3.5

* [storybook] Run storybook with `--no-open`. Fixes xdg-utils issue

* [storybook] Create decorator for BS3/BS5

* [storybook] Add `bsVersionDecorator` to stories

* [storybook] Fix bugs in stories

* [storybook] Fixup `useMeta` type. Use `DeepPartial`

* [storybook] Fix types

GitOrigin-RevId: 48c0f0fefb1ab2d4863ab59051b900b1908a613c
2024-10-14 11:07:40 +00:00

372 lines
8.8 KiB
JavaScript

import { useCallback, useMemo, useState } from 'react'
import useFetchMock from './hooks/use-fetch-mock'
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 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 PdfPreviewError from '../js/features/pdf-preview/components/pdf-preview-error'
import PdfPreviewHybridToolbar from '../js/features/pdf-preview/components/pdf-preview-hybrid-toolbar'
import { useDetachCompileContext as useCompileContext } from '../js/shared/context/detach-compile-context'
import {
dispatchDocChanged,
mockBuildFile,
mockClearCache,
mockCompile,
mockCompileError,
mockCompileValidationIssues,
mockEventTracking,
outputFiles,
} from './fixtures/compile'
import { cloneDeep } from 'lodash'
import { ScopeDecorator } from './decorators/scope'
import { PdfPreviewProvider } from '@/features/pdf-preview/components/pdf-preview-provider'
import { bsVersionDecorator } from '../../.storybook/utils/with-bootstrap-switcher'
export default {
title: 'Editor / PDF Preview',
component: PdfPreview,
subcomponents: {
PdfPreviewHybridToolbar,
PdfFileList,
PdfPreviewError,
},
decorators: [ScopeDecorator],
argTypes: {
...bsVersionDecorator.argTypes,
},
}
export const Interactive = () => {
useFetchMock(fetchMock => {
mockCompile(fetchMock)
mockBuildFile(fetchMock)
mockClearCache(fetchMock)
})
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 (
<div className="pdf-viewer">
<PdfPreviewPane />
<Inner />
</div>
)
}
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 (
<>
<PdfPreviewPane />
<Inner />
</>
)
}
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 = () => {
useFetchMock(fetchMock => {
mockCompile(fetchMock)
})
return (
<>
{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>
))}
</>
)
}
export const HybridToolbar = () => {
useFetchMock(fetchMock => {
mockCompile(fetchMock, 500)
mockBuildFile(fetchMock)
mockEventTracking(fetchMock)
})
return (
<div className="pdf">
<PdfPreviewHybridToolbar />
</div>
)
}
export const FileList = () => {
const fileList = useMemo(() => {
return buildFileList(cloneDeep(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 (
<div className="pdf">
<PdfPreviewProvider>
<PdfLogsViewer />
</PdfPreviewProvider>
</div>
)
}
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)
})
return <PdfPreviewPane />
}