Commit 794af3aa authored by Mohammad Nakhaee's avatar Mohammad Nakhaee
Browse files

file preview (#873)

parent 639abab0
......@@ -36,10 +36,9 @@ import RecognizedFileIcon from '@material-ui/icons/InsertChartOutlinedTwoTone'
import Dropzone from 'react-dropzone'
import Download from '../entry/Download'
import Quantity from '../Quantity'
import FilePreview from './FilePreview'
import FilePreview, {viewers} from './FilePreview'
import { archiveAdaptorFactory, useBrowserAdaptorContext } from './ArchiveBrowser'
import { createMetainfo } from './metainfo'
import H5Web from '../visualization/H5Web'
import NorthLaunchButton from '../north/NorthLaunchButton'
import { useTools } from '../north/NorthPage'
import { EntryButton } from '../nav/Routes'
......@@ -364,8 +363,8 @@ export class RawFileAdaptor extends Adaptor {
archive: this.data.archive
}
return archiveAdaptorFactory(childContext, this.data.archive)
} else if (key === 'h5web') {
return new H5WebAdaptor(this.context, this.uploadId, this.path)
} else if (key === 'preview') {
return new FilePreviewAdaptor(this.context, this.uploadId, this.path, this.data)
}
}
render() {
......@@ -375,15 +374,16 @@ export class RawFileAdaptor extends Adaptor {
}
}
class H5WebAdaptor extends Adaptor {
constructor(context, uploadId, path) {
class FilePreviewAdaptor extends Adaptor {
constructor(context, uploadId, path, data) {
super(context)
this.uploadId = uploadId
this.path = path
this.data = data
}
render() {
return <Box width="80vw" height="100%">
<H5Web upload_id={this.uploadId} filename={this.path}/>
return <Box minWidth={'300px'} maxWidth={'1200px'} width="100%">
<FilePreview uploadId={this.uploadId} path={this.path} size={this.data?.size}/>
</Box>
}
}
......@@ -447,6 +447,9 @@ function RawFileContent({uploadId, path, data, editable}) {
niceSize = `${data.size} bytes`
}
const fileExtension = path.split('.').pop().toLowerCase()
const isExtensionSupported = viewers.map(viewer => viewer.fileExtensions).flat().includes(fileExtension)
return (
<Content
key={path}
......@@ -517,10 +520,9 @@ function RawFileContent({uploadId, path, data, editable}) {
<NorthLaunchButton uploadId={uploadId} path={path} tools={applicableNorthTools} />
</Compartment>
)}
<Compartment title="preview">
<FilePreview uploadId={uploadId} path={path} size={data.size}/>
</Compartment>
{isExtensionSupported && <Item itemKey="preview">
{'preview'}
</Item>}
</Content>)
}
RawFileContent.propTypes = {
......
......@@ -32,16 +32,25 @@ const fileBrowserTree = {
'deepdir/subdir1/subdir2': {cb: checkDirectoryLane},
'deepdir/subdir1/subdir2/dir special chars ~!?*\\()[]{}<>,.;:\'"`&@#$%=|': {cb: checkDirectoryLane},
'deepdir/subdir1/subdir2/dir special chars ~!?*\\()[]{}<>,.;:\'"`&@#$%=|/f.txt': {cb: checkFileLane},
'deepdir/subdir1/subdir2/dir special chars ~!?*\\()[]{}<>,.;:\'"`&@#$%=|/f.txt/preview': {cb: () => {}},
'filetypes': {cb: checkDirectoryLane},
'filetypes/image.png': {cb: checkFileLane},
'filetypes/image.png/preview': {cb: () => {}},
'filetypes/not an image.png': {cb: checkFileLane},
'filetypes/not an image.png/preview': {cb: () => {}},
'filetypes/doc.json': {cb: checkFileLane},
'filetypes/doc.json/preview': {cb: () => {}},
'test_entry': {cb: checkDirectoryLane},
'test_entry/1.aux': {cb: checkFileLane},
'test_entry/1.aux/preview': {cb: () => {}},
'test_entry/2.aux': {cb: checkFileLane},
'test_entry/2.aux/preview': {cb: () => {}},
'test_entry/3.aux': {cb: checkFileLane},
'test_entry/3.aux/preview': {cb: () => {}},
'test_entry/4.aux': {cb: checkFileLane},
'test_entry/vasp.xml': {cb: checkFileLane, extra: {entryId: 'dft_bulk', parserName: 'parsers/vasp'}}
'test_entry/4.aux/preview': {cb: () => {}},
'test_entry/vasp.xml': {cb: checkFileLane, extra: {entryId: 'dft_bulk', parserName: 'parsers/vasp'}},
'test_entry/vasp.xml/preview': {cb: () => {}}
}
afterEach(() => closeAPI())
......@@ -61,32 +70,49 @@ async function testBrowseAround(editable) {
// Navigate around
await navigateTo(`deepdir/subdir1/subdir2/${dirSpecialChars}/f.txt`, browserConfig)
await within(getLane(5)).findByText('content of f.txt') // auto-previewing .txt files with text viewer
await within(getLane(5)).findByText(/preview/i) // auto-previewing .txt files with text viewer
// Navigate to preview
await navigateTo(`deepdir/subdir1/subdir2/${dirSpecialChars}/f.txt/preview`, browserConfig)
await within(getLane(6)).findByText('content of f.txt') // auto-previewing .txt files with text viewer
// Check file types
// image: ok -> just check if there is an img tag
await navigateTo('filetypes/image.png', browserConfig)
expect(within(getLane(2)).getByRole('img')).toBeVisible()
await within(getLane(2)).findByText(/preview/i) // auto-previewing .txt files with text viewer
await navigateTo('filetypes/image.png/preview', browserConfig)
expect(within(getLane(3)).getByRole('img')).toBeVisible()
// image: bad
await navigateTo('filetypes/not an image.png', browserConfig)
await within(getLane(2)).findByText(/preview/i) // auto-previewing .txt files with text viewer
// Simulate a load error on the image (images are not actually loaded during testing, for efficiency reasons)
fireEvent.error(within(getLane(2)).getByRole('img'))
await within(getLane(2)).findByText('Failed to open with image viewer. Bad file format?')
expect(within(getLane(2)).getByButtonText('Open with text viewer')).toBeEnabled()
userEvent.click(within(getLane(2)).getByButtonText('Open with text viewer'))
await within(getLane(2)).findByText('this is not an image!') // text content of the file
await navigateTo('filetypes/not an image.png/preview', browserConfig)
fireEvent.error(within(getLane(3)).getByRole('img'))
await within(getLane(3)).findByText('Failed to open with image viewer. Bad file format?')
expect(within(getLane(3)).getByButtonText('Open with text viewer')).toBeEnabled()
userEvent.click(within(getLane(3)).getByButtonText('Open with text viewer'))
await within(getLane(3)).findByText('this is not an image!') // text content of the file
// json
await navigateTo('filetypes/doc.json', browserConfig)
await within(getLane(2)).findByText('root')
await within(getLane(2)).findByText(/"this is a json string value"/)
await within(getLane(2)).findByText(/preview/i)
// json preview
await navigateTo('filetypes/doc.json/preview', browserConfig)
await within(getLane(3)).findByText('root')
await within(getLane(3)).findByText(/"this is a json string value"/)
// Check folder with an entry
await navigateTo('test_entry/1.aux', browserConfig)
await navigateTo('test_entry/vasp.xml', browserConfig)
await within(getLane(2)).findByText(/GGA_X_PBE/) // This text should occur in the file preview
await within(getLane(2)).findByText(/preview/i)
await navigateTo('test_entry/vasp.xml/preview', browserConfig)
await within(getLane(3)).findByText(/GGA_X_PBE/) // This text should occur in the file preview
}
test.each([
......@@ -147,7 +173,10 @@ test('starting in entry dir', async () => {
await navigateTo('1.aux', browserConfig)
await navigateTo('vasp.xml', browserConfig)
await within(getLane(1)).findByText(/GGA_X_PBE/) // This text should occur in the file preview
await within(getLane(1)).findByText(/preview/i) // This text should occur in the file preview
await navigateTo('vasp.xml/preview', browserConfig)
await within(getLane(2)).findByText(/GGA_X_PBE/) // This text should occur in the file preview
})
test('delete files', async () => {
......
......@@ -24,10 +24,10 @@ import { Document, Page, pdfjs } from 'react-pdf'
import InfiniteScroll from 'react-infinite-scroller'
import { useApi } from '../api'
import { apiBase } from '../../config'
import { Item } from './Browser'
import { parseCifStructures } from 'crystcif-parse'
import Structure from '../visualization/Structure'
import { isWaitingForUpdateTestId } from '../../utils'
import H5Web from '../visualization/H5Web'
pdfjs.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`
......@@ -47,7 +47,7 @@ const useFilePreviewStyles = makeStyles(theme => ({
/* Viewer definitions */
const viewerText = {
name: 'text',
fileExtensions: ['txt', 'yaml', 'yml', 'csv', 'xml'],
fileExtensions: ['txt', 'yaml', 'yml', 'csv', 'xml', 'dat', 'cdf'],
maxSizePreview: 1e10, // Effectively infinite
maxSizeAutoPreview: 1e10, // Effectively infinite
width: 700,
......@@ -71,6 +71,7 @@ const viewerJSON = {
fileExtensions: ['json'],
maxSizeAutoPreview: 10e6,
requiresLoadedData: true,
width: 'fit-content',
render: ({classes, data}) => {
if (typeof data.current === 'string') {
data.current = JSON.parse(data.current)
......@@ -101,9 +102,8 @@ const viewerHDF5 = {
name: 'hdf5',
fileExtensions: ['hdf5', 'hd5', 'nxs'],
maxSizeAutoPreview: 10e6,
render: () => {
return <Item itemKey="h5web"><Typography>H5Web</Typography></Item>
}
width: 'fit-content',
render: ({uploadId, path}) => <H5Web upload_id={uploadId} filename={path}/>
}
const viewerCif = {
name: 'cif',
......@@ -130,7 +130,7 @@ const viewerCif = {
)
}
}
const viewers = [viewerText, viewerImg, viewerJSON, viewerPDF, viewerHDF5, viewerCif]
export const viewers = [viewerText, viewerImg, viewerJSON, viewerPDF, viewerHDF5, viewerCif]
const FilePreview = React.memo(({uploadId, path, size}) => {
const classes = useFilePreviewStyles()
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment