Newer
Older
import React from 'react'
import { withStyles } from '@material-ui/core/styles'
vkovtun
committed
import autoBind from 'auto-bind'
import Toolbar from '@src/js/components/database/data-browser/Toolbar.jsx'
import GridView from '@src/js/components/database/data-browser/GridView.jsx'
import Grid from '@src/js/components/common/grid/Grid.jsx'
import GridFilterOptions from '@src/js/components/common/grid/GridFilterOptions.js'
import AppController from '@src/js/components/AppController.js'
import ItemIcon from '@src/js/components/database/data-browser/ItemIcon.jsx'
import InfoPanel from '@src/js/components/database/data-browser/InfoPanel.jsx'
import DataBrowserController from '@src/js/components/database/data-browser/DataBrowserController.js'
import messages from '@src/js/common/messages.js'
import InfoBar from '@src/js/components/database/data-browser/InfoBar.jsx'
import LoadingDialog from '@src/js/components/common/loading/LoadingDialog.jsx'
import ErrorDialog from '@src/js/components/common/error/ErrorDialog.jsx'
columnFlexContainer: {
flexDirection: 'column',
display: 'flex',
height: 'calc(100vh - ' + theme.spacing(21) + 'px)'
boundary: {
padding: theme.spacing(1),
borderColor: theme.palette.border.secondary,
backgroundColor: theme.palette.background.paper
'&>*': {
flex: '0 0 auto',
padding: theme.spacing(1),
borderWidth: '1px',
borderStyle: 'solid',
borderColor: theme.palette.border.secondary,
backgroundColor: theme.palette.background.paper
},
},
overflowY: 'auto',
paddingTop: 0,
paddingBottom: 0
},
content: {
flex: '1 1 100%',
},
nameCell: {
display: 'flex',
alignItems: 'center',
'&>span': {
flex: 1,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
},
const configuration =
[
extensions: ['wav', 'mp3', 'aac', 'ogg', 'oga', 'flac', 'm4a', 'wma',
'opus', 'flac', 'aiff', 'weba']
},
{
extensions: ['txt', 'rtf', 'html', 'htm', 'epub', 'md', 'tex', 'pages',
'numbers', 'key', 'mobi', 'indd', 'csv', 'tsv',
'odt', 'ods', 'odp', 'otp', 'odm', 'ott', 'ots', 'odf', 'odft']
},
{
extensions: ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm',
'mpeg', 'mpg', 'mpe', 'vob', 'm4v', 'ogv']
},
{
extensions: ['tif', 'tiff', 'gif', 'jpg', 'jpeg', 'png', 'bmp', 'svg',
'webp', 'psd', 'raw', 'heif', 'heic', 'odc', 'otc', 'odg', 'otg',
'odi', 'oti']
},
{
icon: 'file-archive',
extensions: ['zip', 'rar', '7z', 'tar', 'gz', 'bz', 'bz2', 'xz', 'iso',
'zipx', 'cab', 'arj', 'lz', 'lzma', 'z', 'tgz', 'ace', 'dmg']
},
{
icon: 'file-code',
extensions: ['xml', 'js', 'html', 'css', 'c', 'cpp', 'h', 'cs', 'php',
'rb', 'swift', 'go', 'rs', 'ts', 'json', 'sh', 'bat', 'sql', 'yaml',
'yml', 'jsx', 'tsx', 'pl', 'scala', 'kt']
},
// Fine-grained file formats
{
icon: 'file-pdf',
extensions: ['pdf']
},
{
icon: 'file-word',
extensions: ['doc', 'dot', 'docx']
},
{
icon: 'file-excel',
extensions: ['xls', 'xlsx']
},
{
icon: 'file-powerpoint',
extensions: ['ppt', 'pptx']
]
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
const mimeTypeMap = {
'.7z': 'application/x-7z-compressed',
'.aac': 'audio/aac',
'.ace': 'application/x-ace-compressed',
'.aiff': 'audio/aiff',
'.arj': 'application/x-arj',
'.avi': 'video/x-msvideo',
'.bmp': 'image/bmp',
'.bz': 'application/x-bzip',
'.bz2': 'application/x-bzip2',
'.bat': 'application/x-msdownload',
'.c': 'text/x-c',
'.cab': 'application/vnd.ms-cab-compressed',
'.cpp': 'text/x-c',
'.cs': 'text/x-csharp',
'.css': 'text/css',
'.csv': 'text/csv',
'.dmg': 'application/x-apple-diskimage',
'.doc': 'application/msword',
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.dot': 'application/msword',
'.epub': 'application/epub+zip',
'.flac': 'audio/flac',
'.flv': 'video/x-flv',
'.gif': 'image/gif',
'.go': 'text/plain',
'.gz': 'application/gzip',
'.h': 'text/x-c',
'.heic': 'image/heic',
'.heif': 'image/heif',
'.htm': 'text/html',
'.html': 'text/html',
'.indd': 'application/octet-stream',
'.iso': 'application/x-iso9660-image',
'.jpeg': 'image/jpeg',
'.jpg': 'image/jpeg',
'.js': 'application/javascript',
'.json': 'application/json',
'.jsx': 'text/jsx',
'.key': 'application/vnd.apple.keynote',
'.kt': 'text/plain',
'.lz': 'application/octet-stream',
'.lzma': 'application/x-lzma',
'.m4a': 'audio/mp4',
'.m4v': 'video/x-m4v',
'.md': 'text/markdown',
'.mkv': 'video/x-matroska',
'.mobi': 'application/x-mobipocket-ebook',
'.mov': 'video/quicktime',
'.mp3': 'audio/mpeg',
'.mp4': 'video/mp4',
'.mpe': 'video/mpeg',
'.mpeg': 'video/mpeg',
'.mpg': 'video/mpeg',
'.oga': 'audio/ogg',
'.ogg': 'audio/ogg',
'.ogv': 'video/ogg',
'.pdf': 'application/pdf',
'.odc': 'application/vnd.oasis.opendocument.chart',
'.odg': 'application/vnd.oasis.opendocument.graphics',
'.odf': 'application/vnd.oasis.opendocument.formula',
'.odft': 'application/vnd.oasis.opendocument.formula-template',
'.odi': 'application/vnd.oasis.opendocument.image',
'.odm': 'application/vnd.oasis.opendocument.text-master',
'.odt': 'application/vnd.oasis.opendocument.text',
'.odp': 'application/vnd.oasis.opendocument.presentation',
'.ods': 'application/vnd.oasis.opendocument.spreadsheet',
'.otc': 'application/vnd.oasis.opendocument.chart-template',
'.otg': 'application/vnd.oasis.opendocument.graphics-template',
'.oth': 'application/vnd.oasis.opendocument.text-web',
'.oti': 'application/vnd.oasis.opendocument.image-template',
'.otp': 'application/vnd.oasis.opendocument.presentation-template',
'.ots': 'application/vnd.oasis.opendocument.spreadsheet-template',
'.ott': 'application/vnd.oasis.opendocument.text-template',
'.opus': 'audio/opus',
'.pages': 'application/vnd.apple.pages',
'.php': 'application/x-httpd-php',
'.pl': 'application/x-perl',
'.png': 'image/png',
'.pot': 'application/vnd.ms-powerpoint',
'.pps': 'application/vnd.ms-powerpoint',
'.ppt': 'application/vnd.ms-powerpoint',
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'.psd': 'image/vnd.adobe.photoshop',
'.rar': 'application/x-rar-compressed',
'.raw': 'image/x-panasonic-raw',
'.rb': 'text/x-ruby',
'.rs': 'application/rls-services+xml',
'.rtf': 'application/rtf',
'.scala': 'text/x-scala',
'.sh': 'application/x-sh',
'.svg': 'image/svg+xml',
'.sql': 'application/x-sql',
'.swift': 'text/x-swift',
'.tar': 'application/x-tar',
'.tex': 'application/x-tex',
'.tgz': 'application/gzip',
'.tif': 'image/tiff',
'.tiff': 'image/tiff',
'.ts': 'text/typescript',
'.tsv': 'text/tab-separated-values',
'.tsx': 'text/tsx',
'.txt': 'text/plain',
'.vob': 'video/x-ms-vob',
'.wav': 'audio/wav',
'.weba': 'audio/webm',
'.webm': 'video/webm',
'.webp': 'image/webp',
'.wma': 'audio/x-ms-wma',
'.wmv': 'video/x-ms-wmv',
'.xls': 'application/vnd.ms-excel',
'.xlt': 'application/vnd.ms-excel',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.xml': 'application/xml',
'.xz': 'application/x-xz',
'.yaml': 'application/yaml',
'.yml': 'application/yaml',
'.z': 'application/x-compress',
'.zip': 'application/zip',
'.zipx': 'application/zip'
}
class DataBrowser extends React.Component {
constructor(props, context) {
super(props, context)
vkovtun
committed
autoBind(this)
const { sessionToken, controller, id } = this.props
this.controller = controller || new DataBrowserController(id)
this.state = {
viewType: props.viewType,
multiselectedFiles: new Set([]),
showInfo: false,
totalSpace: -1,
loading: false,
vkovtun
committed
handleViewTypeChange(viewType) {
this.setState({ viewType })
}
handleClick(file) {
// TODO: implement
}
const { directory, path } = file
if (directory) {
} else {
await this.downloadFile(file)
}
}
this.setState({ selectedFile: selectedRow && selectedRow.data })
handleMultiselect(selectedRow) {
this.setState({
multiselectedFiles: new Set(
Object.values(selectedRow).map(value => value.data)
)
async handleDownload() {
const { multiselectedFiles } = this.state
const files = multiselectedFiles.values()
const file = files.next().value
if (multiselectedFiles.size > 1 || file.directory) {
// ZIP download
await this.downloadFiles()
} else {
// Single file download
await this.downloadFile(file)
}
}
async downloadFiles() {
const { multiselectedFiles } = this.state
const { id } = this.props
const zipBlob = await this.prepareZipBlob(multiselectedFiles)
this.downloadBlob(zipBlob, id)
this.zip = new JSZip()
}
async prepareZipBlob(files) {
for (let file of files) {
if (!file.directory) {
const dataArray = await this.controller.download(file)
this.zip.file(
file.path,
new Blob(dataArray, { type: this.inferMimeType(file.path) })
)
} else {
this.zip.folder(file.path)
const nestedFiles = await this.controller.listFiles(file.path)
await this.prepareZipBlob(nestedFiles)
return await this.zip.generateAsync({ type: 'blob' })
async downloadFile(file) {
vkovtun
committed
try {
this.setState({ loading: true, progress: 0 })
const blob = await this.fileToBlob(file)
this.downloadBlob(blob, file.name)
} finally {
this.setState({ loading: false, progress: 0 })
}
}
updateProgress(progress) {
this.setState({ progress })
}
downloadBlob(blob, fileName) {
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = fileName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
async fileToBlob(file) {
vkovtun
committed
const dataArray = await this.controller.download(file)
return new Blob(dataArray, { type: this.inferMimeType(file.path) })
async onError(error) {
await AppController.getInstance().errorChange(error)
}
this.setState({ showInfo: !this.state.showInfo })
handleGridControllerRef(gridController) {
this.controller.gridController = gridController
}
async handlePathChange(path) {
await this.setPath(path)
}
if (this.state.path !== path + '/') {
this.setState({ path: path + '/' })
this.controller.setPath(path + '/')
await this.controller.gridController.load()
}
timeToString(time) {
return new Date(time).toLocaleString()
}
sizeToString(bytes) {
if (!bytes) {
return null
}
if (typeof bytes == 'string') {
bytes = parseInt(bytes)
}
let size
let unit
const kbytes = bytes / 1024.0
const mbytes = kbytes / 1024.0
const gbytes = mbytes / 1024.0
if (gbytes > 1.0) {
size = gbytes
unit = 'GB'
} else if (mbytes > 1.0) {
size = mbytes
unit = 'MB'
} else if (kbytes > 1.0) {
size = kbytes
unit = 'kB'
unit = 'bytes'
return size.toFixed(1) + '\xa0' + unit
fetchSpaceStatus() {
this.controller.free().then(space => {
this.setState({ freeSpace: space.free, totalSpace: space.total })
})
inferMimeType(fileName) {
const extension = fileName.slice(fileName.lastIndexOf('.')).toLowerCase()
return mimeTypeMap[extension] || 'application/octet-stream'
}
this.fetchSpaceStatus()
openErrorDialog(errorMessage) {
this.setState({ errorMessage })
}
closeErrorDialog() {
this.setState({ errorMessage: null })
}
const { classes, sessionToken, id } = this.props
const {
viewType,
files,
selectedFile,
multiselectedFiles,
showInfo,
totalSpace,
loading,
} = this.state
return ([
<div
className={[classes.boundary, classes.columnFlexContainer].join(' ')}
>
viewType={viewType}
onViewTypeChange={this.handleViewTypeChange}
onShowInfoChange={this.handleShowInfoChange}
onDownload={this.handleDownload}
owner={id}
<InfoBar
path={path}
onPathChange={this.handlePathChange}
free={freeSpace}
total={totalSpace}
<div
className={[
classes.flexContainer,
classes.boundary,
classes.content
].join(' ')}
>
controllerRef={this.handleGridControllerRef}
filterModes={[GridFilterOptions.COLUMN_FILTERS]}
header='Files'
classes={{ container: classes.grid }}
label: messages.get(messages.NAME),
<div className={classes.nameCell}>
<ItemIcon
file={row}
classes={{ icon: classes.icon }}
configuration={configuration}
/>
<span>{row.name}</span>
</div>
name: 'type',
label: messages.get(messages.TYPE),
getValue: ({ row }) => (row.directory ? 'Directory' : 'File')
label: messages.get(messages.SIZE),
getValue: ({ row }) => this.sizeToString(row.size)
{
name: 'created',
label: messages.get(messages.CREATED),
sortable: true,
getValue: ({ row }) => row.creationTime,
renderValue: ({ row }) => this.timeToString(row.creationTime)
},
label: messages.get(messages.MODIFIED),
sortable: true,
getValue: ({ row }) => row.lastModifiedTime,
renderValue: ({ row }) =>
this.timeToString(row.lastModifiedTime)
},
{
name: 'accessed',
label: messages.get(messages.ACCESSED),
sortable: true,
getValue: ({ row }) => row.lastAccessTime,
renderValue: ({ row }) =>
this.timeToString(row.lastAccessTime)
exportable={false}
selectable={true}
multiselectable={true}
loadSettings={null}
showHeaders={true}
onSettingsChange={null}
onError={this.onError}
onSelectedRowChange={this.handleSelect}
onMultiselectedRowsChange={this.handleMultiselect}
onRowDoubleClick={this.handleRowDoubleClick}
exportXLS={null}
/>
)}
{viewType === 'grid' && (
<GridView
clickable={true}
selectable={true}
multiselectable={true}
onClick={this.handleClick}
onSelect={this.handleSelect}
onMultiselect={this.handleMultiselect}
configuration={configuration}
files={files}
selectedFile={selectedFile}
multiselectedFiles={multiselectedFiles}
<InfoPanel
selectedFile={selectedFile}
configuration={configuration}
/>
</div>,
<LoadingDialog key='data-browser-loaging-dialog' variant='determinate'
value={progress} loading={loading} />,
<ErrorDialog open={!!errorMessage} error={errorMessage}
onClose={this.closeErrorDialog} />
])