Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions src/bundles/files/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { IGNORED_FILES, ACTIONS } from './consts.js'
* @property {CID} cid
* @property {string} name
* @property {string} path
* @property {string} parentPath
* @property {boolean} pinned
* @property {boolean|void} isParent
*
Expand All @@ -41,15 +42,22 @@ import { IGNORED_FILES, ACTIONS } from './consts.js'
* @param {string} [prefix]
* @returns {FileStat}
*/
const fileFromStats = ({ cumulativeSize, type, size, cid, name, path, pinned, isParent }, prefix = '/ipfs') => ({
size: cumulativeSize || size || 0,
type: type === 'dir' ? 'directory' : type,
cid,
name: name || path.split('/').pop() || cid.toString(),
path: path || `${prefix}/${cid.toString()}`,
pinned: Boolean(pinned),
isParent
})
const fileFromStats = ({ cumulativeSize, type, size, cid, name, path, pinned, isParent }, prefix = '/ipfs') => {
const pathParts = path.split('/')
const pathFileName = pathParts.pop()
const parentPath = pathParts.join('/') || '/'
const file = {
size: cumulativeSize || size || 0,
type: type === 'dir' ? 'directory' : type,
cid,
name: name || pathFileName || cid.toString(),
path: path || `${prefix}/${cid.toString()}`,
parentPath,
pinned: Boolean(pinned),
isParent
}
return file
}

/**
* @param {IPFSService} ipfs
Expand Down Expand Up @@ -717,6 +725,7 @@ const importFiles = (ipfs, files) => {
* @param {string} options.path
* @param {boolean} [options.isRoot]
* @param {import('./utils').Sorting} options.sorting
* @returns {Promise<import('./protocol').DirectoryContent>}
*/
const dirStats = async (ipfs, cid, { path, isRoot, sorting }) => {
const entries = await all(ipfs.ls(cid)) || []
Expand Down Expand Up @@ -746,6 +755,7 @@ const dirStats = async (ipfs, cid, { path, isRoot, sorting }) => {
}

let parent = null
let parentPathForDir = '/'

if (!isRoot) {
const parentPath = dirname(path)
Expand All @@ -764,11 +774,13 @@ const dirStats = async (ipfs, cid, { path, isRoot, sorting }) => {
name: '..',
isParent: true
})
parentPathForDir = parentInfo.path
}
}

return {
path,
parentPath: parentPathForDir,
fetched: Date.now(),
type: 'directory',
cid,
Expand Down
5 changes: 4 additions & 1 deletion src/bundles/files/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type UnknownContent = {
type: 'unknown',
fetched: Time,
path: string,
parentPath: string,
cid: CID,
size: 0
}
Expand All @@ -33,6 +34,7 @@ type FileContent = {
type: 'file',
fetched: Time,
path: string,
parentPath: string,
cid: CID,
size: number,

Expand All @@ -44,10 +46,11 @@ export type DirectoryContent = {
type: 'directory',
fetched: Time,
path: string,
parentPath: string,
cid: CID,

content: FileStat[]
upper: void | FileStat,
upper: FileStat | null,
}

export type PageContent =
Expand Down
15 changes: 11 additions & 4 deletions src/files/FilesPage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { findDOMNode } from 'react-dom'
import { Helmet } from 'react-helmet'
import { connect } from 'redux-bundler-react'
Expand Down Expand Up @@ -101,6 +101,13 @@ const FilesPage = ({
doFilesBulkCidImport(raw, root)
}

const onClosePreview = useCallback(() => {
// if the parentPath is / or null then we are at the root of the files page, (preview close button shouldn't even be visible in this case)
if (files?.parentPath == null || files?.parentPath === '/') return

doUpdateHash(files?.parentPath)
}, [files?.parentPath, doUpdateHash])

const onAddByPath = (path, name) => doFilesAddPath(files.path, path, name)
/**
*
Expand Down Expand Up @@ -357,7 +364,7 @@ const FilesPage = ({

<MainView t={t} files={files} remotePins={remotePins} pendingPins={pendingPins} failedPins={failedPins} doExploreUserProvidedPath={doExploreUserProvidedPath}/>

<Preview files={files} onDownload={() => onDownload([files])} />
<Preview files={files} onDownload={() => onDownload([files])} onClose={onClosePreview} />

<InfoBoxes isRoot={filesPathInfo.isMfs && filesPathInfo.isRoot}
isCompanion={false}
Expand Down Expand Up @@ -393,9 +400,9 @@ const FilesPage = ({
)
}

const Preview = ({ files, onDownload }) => {
const Preview = ({ files, onDownload, onClose }) => {
if (files && files.type === 'file') {
return (<FilePreview {...files} onDownload={onDownload} />)
return (<FilePreview {...files} onDownload={onDownload} onClose={onClose} />)
}
return (<div/>)
}
Expand Down
123 changes: 84 additions & 39 deletions src/files/file-preview/FilePreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { CID } from 'multiformats/cid'
import { useDrag } from 'react-dnd'
import { toString as fromUint8ArrayToString } from 'uint8arrays'
import Button from '../../components/button/button.tsx'
import GlyphCancel from '../../icons/GlyphCancel.js'

const maxPlainTextPreview = 1024 * 10 // only preview small part of huge files

Expand All @@ -24,7 +25,7 @@ const Drag = ({ name, size, cid, path, children }) => {
}

const Preview = (props) => {
const { t, name, cid, size, availableGatewayUrl, publicGateway, read, onDownload } = props
const { t, name, cid, size, availableGatewayUrl, publicGateway, read, onDownload, onClose } = props
const [content, setContent] = useState(null)
const [hasMoreContent, setHasMoreContent] = useState(false)
const [buffer, setBuffer] = useState(null)
Expand Down Expand Up @@ -60,39 +61,64 @@ const Preview = (props) => {
const src = `${availableGatewayUrl}/ipfs/${cid}?filename=${encodeURIComponent(name)}`
const className = 'mw-100 mt3 bg-snow-muted pa2 br2 border-box'

// Close button header
const closeButtonHeader = onClose != null && (
<div className="flex items-center justify-between mb3 pb2 bb b--light-gray">
<div className="flex items-center">
<h2 className="ma0 f4 charcoal truncate">{name}</h2>
</div>
<GlyphCancel
onClick={onClose}
style={{ width: '44px', height: '44px', fill: '#244c5a', cursor: 'pointer' }}
/>
</div>
)

switch (type) {
case 'audio':
return (
<Drag {...props}>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<audio width='100%' controls>
<source src={safeSubresourceGwUrl(src)} />
</audio>
</Drag>
<div>
Comment thread
alikhere marked this conversation as resolved.
{closeButtonHeader}
<Drag {...props}>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<audio width='100%' controls>
<source src={safeSubresourceGwUrl(src)} />
</audio>
</Drag>
</div>
)
case 'pdf':
return (
<Drag {...props}>
<object className="FilePreviewPDF w-100" data={safeSubresourceGwUrl(src)} type='application/pdf'>
{t('noPDFSupport')}
<a href={src} download target='_blank' rel='noopener noreferrer' className='underline-hover navy-muted'>{t('downloadPDF')}</a>
</object>
</Drag>
<div>
{closeButtonHeader}
<Drag {...props}>
<object className="FilePreviewPDF w-100" data={safeSubresourceGwUrl(src)} type='application/pdf'>
{t('noPDFSupport')}
<a href={src} download target='_blank' rel='noopener noreferrer' className='underline-hover navy-muted'>{t('downloadPDF')}</a>
</object>
</Drag>
</div>
)
case 'video':
return (
<Drag {...props}>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video controls className={className}>
<source src={safeSubresourceGwUrl(src)} />
</video>
</Drag>
<div>
{closeButtonHeader}
<Drag {...props}>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video controls className={className}>
<source src={safeSubresourceGwUrl(src)} />
</video>
</Drag>
</div>
)
case 'image':
return (
<Drag {...props}>
<img className={className} alt={name} src={safeSubresourceGwUrl(src)} />
</Drag>
<div>
{closeButtonHeader}
<Drag {...props}>
<img className={className} alt={name} src={safeSubresourceGwUrl(src)} />
</Drag>
</div>
)
default: {
const srcPublic = `${publicGateway}/ipfs/${cid}?filename=${encodeURIComponent(name)}`
Expand All @@ -114,31 +140,49 @@ const Preview = (props) => {
)

if (content === null) {
return <ComponentLoader />
return (
<div>
{closeButtonHeader}
<ComponentLoader />
</div>
)
}

// a precaution to not render too much, in case we overread
if (content.length > maxPlainTextPreview) {
return cantPreview
return (
<div>
{closeButtonHeader}
{cantPreview}
</div>
)
}

if (isBinary(name, content)) {
return cantPreview
return (
<div>
{closeButtonHeader}
{cantPreview}
</div>
)
}

return <>
<pre className={`${className} overflow-auto monospace`}>
{content}
</pre>
{ hasMoreContent && <div className="w-100 flex items-center justify-center">
<p><Trans i18nKey='previewLimitReached' t={t}>This preview is limited to 10 KiB. Click the download button to access the full file.</Trans></p>
<p>
<Button className="mh2 lh-copy bn justify-center flex " onClick={ onDownload }>
{ t('app:actions.download')}
</Button>
</p>
</div>}
</>
return (
<div>
{closeButtonHeader}
<pre className={`${className} overflow-auto monospace`}>
{content}
</pre>
{ hasMoreContent && <div className="w-100 flex items-center justify-center">
<p><Trans i18nKey='previewLimitReached' t={t}>This preview is limited to 10 KiB. Click the download button to access the full file.</Trans></p>
<p>
<Button className="mh2 lh-copy bn justify-center flex " onClick={ onDownload }>
{ t('app:actions.download')}
</Button>
</p>
</div>}
</div>
)
}
}
}
Expand All @@ -151,7 +195,8 @@ Preview.propTypes = {
read: PropTypes.func.isRequired,
content: PropTypes.object,
t: PropTypes.func.isRequired,
tReady: PropTypes.bool.isRequired
tReady: PropTypes.bool.isRequired,
onClose: PropTypes.func
Comment thread
alikhere marked this conversation as resolved.
}

export default connect(
Expand Down