-
Notifications
You must be signed in to change notification settings - Fork 492
/
FilePreview.js
142 lines (122 loc) · 4.31 KB
/
FilePreview.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import React, { useCallback, useEffect, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import { connect } from 'redux-bundler-react'
import isBinary from 'is-binary'
import { Trans, withTranslation } from 'react-i18next'
import typeFromExt from '../type-from-ext'
import ComponentLoader from '../../loader/ComponentLoader.js'
import './FilePreview.css'
import CID from 'cids'
import { useDrag } from 'react-dnd'
import fromUint8ArrayToString from 'uint8arrays/to-string'
import Button from '../../components/button/Button'
const Preview = (props) => {
const { name, size, cid, path } = props
const [, drag] = useDrag({
item: { name, size, cid, path, type: 'FILE' }
})
const type = typeFromExt(name)
// Hack: Allows for text selection if it's a text file (bypass useDrag)
const dummyRef = useRef()
return <div className={ classNames(type !== 'pdf' && type !== 'text' && type !== 'json' && 'dib') } ref={type === 'text' ? dummyRef : drag}>
<PreviewItem {...props} type={type} />
</div>
}
const PreviewItem = ({ t, name, cid, size, type, availableGatewayUrl: gatewayUrl, read, onDownload }) => {
const [content, setContent] = useState(null)
const [hasMoreContent, setHasMoreContent] = useState(false)
const [buffer, setBuffer] = useState(null)
const loadContent = useCallback(async () => {
const readBuffer = buffer || await read()
if (!buffer) {
setBuffer(readBuffer)
}
const { value, done } = await readBuffer.next()
const previousContent = content || ''
const currentContent = previousContent + fromUint8ArrayToString(value)
setContent(currentContent)
const hasMore = !done && new TextEncoder().encode(currentContent).length < size
setHasMoreContent(hasMore)
}, [buffer, content, read, size])
useEffect(() => {
loadContent()
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[])
const src = `${gatewayUrl}/ipfs/${cid}?filename=${encodeURIComponent(name)}`
const className = 'mw-100 mt3 bg-snow-muted pa2 br2 border-box'
switch (type) {
case 'audio':
return (
// eslint-disable-next-line jsx-a11y/media-has-caption
<audio width='100%' controls>
<source src={src} />
</audio>
)
case 'pdf':
return (
<object className="FilePreviewPDF w-100" data={src} type='application/pdf'>
{t('noPDFSupport')}
<a href={src} download target='_blank' rel='noopener noreferrer' className='underline-hover navy-muted'>{t('downloadPDF')}</a>
</object>
)
case 'video':
return (
// eslint-disable-next-line jsx-a11y/media-has-caption
<video controls className={className}>
<source src={src} />
</video>
)
case 'image':
return <img className={className} alt={name} src={src} />
default: {
const cantPreview = (
<div className='mt4'>
<p className='b'>{t('cantBePreviewed')} <span role='img' aria-label='sad'>😢</span></p>
<p>
<Trans i18nKey='downloadInstead' t={t}>
Try <a href={src} download target='_blank' rel='noopener noreferrer' className='link blue' >downloading</a> it instead.
</Trans>
</p>
</div>
)
if (size > 1024 * 1024 * 4) {
return cantPreview
}
if (!content) {
return <ComponentLoader pastDelay />
}
if (isBinary(content)) {
loadContent()
return cantPreview
}
return <>
<pre className={`${className} overflow-auto monospace`}>
{content}
</pre>
{ hasMoreContent && <div className="w-100 flex items-center justify-center">
<Button onClick={ loadContent }>
{ t('loadMore')}
</Button>
<Button className="mh2" onClick={ onDownload }>
{ t('app:actions.download')}
</Button>
</div>}
</>
}
}
}
Preview.propTypes = {
name: PropTypes.string.isRequired,
hash: PropTypes.instanceOf(CID),
size: PropTypes.number.isRequired,
availableGatewayUrl: PropTypes.string.isRequired,
read: PropTypes.func.isRequired,
content: PropTypes.object,
t: PropTypes.func.isRequired,
tReady: PropTypes.bool.isRequired
}
export default connect(
'selectAvailableGatewayUrl',
withTranslation('files')(Preview)
)