-
Notifications
You must be signed in to change notification settings - Fork 495
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add files page #669
feat: add files page #669
Changes from all commits
6b5c046
81a7171
b71169e
338ef51
9b81b00
5acc5d6
c7b8854
12508ac
af62ed4
4b6fd6b
72bcab7
88dbb95
9d74d90
2e1ce94
87331b3
136deee
3837533
7648cd2
6dd84ce
b8eb6a1
bfca078
3371e3c
5d9514a
566605d
a2fe297
6344b9d
28878bf
1251464
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { createAsyncResourceBundle, createSelector } from 'redux-bundler' | ||
import { join } from '../lib/path' | ||
|
||
const bundle = createAsyncResourceBundle({ | ||
name: 'files', | ||
actionBaseType: 'FILES', | ||
getPromise: (args) => { | ||
const {store, getIpfs} = args | ||
let path = store.selectRouteParams().path | ||
|
||
if (!path) { | ||
store.doUpdateHash('/files/') | ||
return Promise.resolve() | ||
} | ||
|
||
// FIX: dirs with actually encoded names | ||
// will get decoded... it shouldn't happen | ||
path = decodeURIComponent(path) | ||
|
||
return getIpfs().files.stat(path) | ||
.then(stats => { | ||
if (stats.type === 'directory') { | ||
return getIpfs().files.ls(path, {l: true}).then((res) => { | ||
// FIX: open PR on js-ipfs-api | ||
if (res) { | ||
res = res.map(file => { | ||
file.type = file.type === 0 ? 'file' : 'directory' | ||
return file | ||
}) | ||
} | ||
|
||
return { | ||
path: path, | ||
type: 'directory', | ||
files: res | ||
} | ||
}) | ||
} else { | ||
stats.name = path | ||
|
||
return { | ||
path: path, | ||
type: 'file', | ||
stats: stats, | ||
read: () => getIpfs().files.read(path) | ||
} | ||
} | ||
}) | ||
}, | ||
staleAfter: 100, | ||
checkIfOnline: false | ||
}) | ||
|
||
bundle.reactFilesFetch = createSelector( | ||
'selectFilesShouldUpdate', | ||
'selectIpfsReady', | ||
'selectRouteInfo', | ||
'selectFiles', | ||
(shouldUpdate, ipfsReady, {url, params}, files) => { | ||
if (shouldUpdate && ipfsReady && url.startsWith('/files')) { | ||
if (!files || files.path !== params.path) { | ||
return { actionCreator: 'doFetchFiles' } | ||
} | ||
} | ||
} | ||
) | ||
|
||
bundle.doFilesDelete = (files) => ({dispatch, getIpfs, store}) => { | ||
dispatch({ type: 'FILES_DELETE_STARTED' }) | ||
|
||
const promises = files.map(file => getIpfs().files.rm(file, { recursive: true })) | ||
Promise.all(promises) | ||
.then(() => { | ||
store.doFetchFiles() | ||
dispatch({ type: 'FILES_DELETE_FINISHED' }) | ||
}) | ||
.catch((error) => { | ||
dispatch({ type: 'FILES_DELETE_ERRORED', payload: error }) | ||
}) | ||
} | ||
|
||
function runAndFetch ({ dispatch, getIpfs, store }, type, action, args) { | ||
dispatch({ type: `${type}_STARTED` }) | ||
|
||
return getIpfs().files[action](...args) | ||
.then(() => { | ||
store.doFetchFiles() | ||
dispatch({ type: `${type}_FINISHED` }) | ||
}) | ||
.catch((error) => { | ||
dispatch({ type: `${type}_ERRORED`, payload: error }) | ||
}) | ||
} | ||
|
||
bundle.doFilesRename = (from, to) => (args) => { | ||
runAndFetch(args, 'FILES_RENAME', 'mv', [[from, to]]) | ||
} | ||
|
||
bundle.doFilesCopy = (from, to) => (args) => { | ||
runAndFetch(args, 'FILES_RENAME', 'cp', [[from, to]]) | ||
} | ||
|
||
bundle.doFilesMakeDir = (path) => (args) => { | ||
runAndFetch(args, 'FILES_MKDIR', 'mkdir', [path, { parents: true }]) | ||
} | ||
|
||
function readAsBuffer (file) { | ||
return new Promise((resolve, reject) => { | ||
const reader = new window.FileReader() | ||
reader.onload = (event) => { | ||
resolve({ | ||
content: Buffer.from(reader.result), | ||
name: file.name | ||
}) | ||
} | ||
reader.onerror = (event) => { | ||
reject(reader.error) | ||
} | ||
|
||
reader.readAsArrayBuffer(file) | ||
}) | ||
} | ||
|
||
bundle.doFilesWrite = (root, files) => ({dispatch, getIpfs, store}) => { | ||
dispatch({ type: 'FILES_WRITE_STARTED' }) | ||
|
||
let promises = [] | ||
|
||
for (const file of files) { | ||
promises.push(readAsBuffer(file)) | ||
} | ||
|
||
return Promise.all(promises).then(files => { | ||
return Promise.all(files.map((file) => { | ||
const target = join(root, file.name) | ||
return getIpfs().files.write(target, file.content, { create: true }) | ||
})) | ||
}).then(() => { | ||
store.doFetchFiles() | ||
dispatch({ type: 'FILES_WRITE_FINISHED' }) | ||
}).catch((error) => { | ||
dispatch({ type: 'FILES_WRITE_ERRORED', payload: error }) | ||
}) | ||
} | ||
|
||
export default bundle |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,106 @@ | ||
import React from 'react' | ||
export default () => <h1 data-id='title'>Files</h1> | ||
import PropTypes from 'prop-types' | ||
import { connect } from 'redux-bundler-react' | ||
import Breadcrumbs from './breadcrumbs/Breadcrumbs' | ||
import FilesList from './files-list/FilesList' | ||
import FilePreview from './file-preview/FilePreview' | ||
import FileInput from './file-input/FileInput' | ||
|
||
const action = (name) => { | ||
return (...args) => { | ||
console.log(name, args) | ||
} | ||
} | ||
|
||
const empty = ( | ||
<div> | ||
<h2>It seems a bit lonely here :(</h2> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @akrych when you get time, could you create some ideas for a "you have no files yet, why not add some" page for the files view? It could use some of the space to introduce some IPFS or at least some of the issues around "they are only on your machine until a peer fetches them from you", and "this is p2p. this is not a cloud" |
||
</div> | ||
) | ||
|
||
class FilesPage extends React.Component { | ||
static propTypes = { | ||
files: PropTypes.object | ||
} | ||
|
||
state = { | ||
clipboard: [], | ||
copy: false | ||
} | ||
|
||
onLinkClick = (link) => { | ||
const {doUpdateHash} = this.props | ||
doUpdateHash(`/files${link}`) | ||
} | ||
|
||
onInspect = (hash) => { | ||
const {doUpdateHash} = this.props | ||
doUpdateHash(`/explore/ipfs/${hash}`) | ||
} | ||
|
||
onRename = ([path]) => { | ||
const oldName = path.split('/').pop() | ||
const newName = window.prompt('Insert the new name:') | ||
|
||
if (newName) { | ||
this.props.doFilesRename(path, path.replace(oldName, newName)) | ||
} | ||
} | ||
|
||
onFilesUpload = (files) => { | ||
this.props.doFilesWrite(this.props.files.path, files) | ||
} | ||
|
||
render () { | ||
const {files} = this.props | ||
|
||
if (!files) { | ||
return <div> | ||
<h1 data-id='title'>Files</h1> | ||
</div> | ||
} | ||
|
||
let body | ||
if (files.type === 'directory') { | ||
if (files.files.length === 0) { | ||
body = empty | ||
} else { | ||
body = <FilesList | ||
maxWidth='calc(100% - 240px)' | ||
root={files.path} | ||
files={files.files} | ||
onShare={action('Share')} | ||
onInspect={this.onInspect} | ||
onRename={this.onRename} | ||
onDownload={action('Download')} | ||
onDelete={this.props.doFilesDelete} | ||
onNavigate={this.onLinkClick} | ||
onCancelUpload={action('Cancel Upload')} | ||
/> | ||
} | ||
} else { | ||
body = <FilePreview {...files} gatewayUrl={this.props.gatewayUrl} /> | ||
} | ||
|
||
return ( | ||
<div> | ||
<div className='flex items-center justify-between mb4'> | ||
<Breadcrumbs path={files.path} onClick={this.onLinkClick} /> | ||
<FileInput upload={this.onFilesUpload} /> | ||
</div> | ||
{body} | ||
<h1 data-id='title'>Files</h1> | ||
</div> | ||
) | ||
} | ||
} | ||
|
||
export default connect( | ||
'doUpdateHash', | ||
'doFilesDelete', | ||
'doFilesRename', | ||
'doFilesWrite', | ||
'selectFiles', | ||
'selectGatewayUrl', | ||
FilesPage | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice ✨