diff --git a/.babelrc b/.babelrc
index c4a69430c..8403e6b9b 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,13 +1,12 @@
{
- "presets": ["es2015", "stage-0", "react"],
+ "presets": ["@babel/env", "@babel/stage-0", "@babel/react"],
"plugins": [
- "transform-runtime"
+ "@babel/transform-runtime"
],
"env": {
"development": {
- "presets": ["react-hmre"],
"plugins": [
- "transform-react-display-name"
+ "@babel/transform-react-display-name"
]
}
}
diff --git a/.gitignore b/.gitignore
index 1ffdf9e16..a8e078d49 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
+package-lock.json
+yarn.lock.json
node_modules/
.module-cache
cache/
npm-debug.log
-dist/*.*
\ No newline at end of file
+dist/*.*
diff --git a/README.md b/README.md
index d326e71b9..7980e5a32 100644
--- a/README.md
+++ b/README.md
@@ -3,21 +3,15 @@
[](http://ipn.io)
[](http://ipfs.io/)
[](http://webchat.freenode.net/?channels=%23ipfs)
-[](https://david-dm.org/ipfs/webui)
-[](https://circleci.com/gh/ipfs/webui)
-[](https://travis-ci.org/ipfs/webui)
-[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fipfs%2Fwebui?ref=badge_shield)
+[](https://david-dm.org/ipfs/webui)
+[](https://circleci.com/gh/ipfs/webui)
+[](https://travis-ci.org/ipfs/webui)
+[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fipfs%2Fwebui?ref=badge_shield)
-> The web interface for [IPFS](https://ipfs.io/)
-
-IPFS Webui is a web interface for IPFS, the Interplanetary File System. With the interface, you can check on your node info, network addresses, see connections on a globe visually, see your files, look at your config and logs without needing to touch the CLI, and more. This interface uses the [js-ipfs-api](//github.com/ipfs/js-ipfs-api) for all of its heavy lifting.
-
-# Status
+> IPFS Webui is a web interface for [IPFS](https://ipfs.io), the Interplanetary File System. With the interface, you can check on your node info, network addresses, see connections on a globe visually, see your files, look at your config and logs without needing to touch the CLI, and more. This interface uses the [js-ipfs-api](//github.com/ipfs/js-ipfs-api) for all of its heavy lifting.
The webui is a **work-in-progress**. Follow the [development](#development) processes below to check it out.
-Otherwise, if you're curious about IPFS, head over to [ipfs/ipfs](//github.com/ipfs/ipfs), or to the [golang](//github.com/ipfs/go-ipfs) or [nodejs](//github.com/ipfs/js-ipfs) implementations. The [website](https://ipfs.io) also has a host of resources on how to get started.
-
# Usage
## Config your IPFS Daemon
@@ -65,7 +59,7 @@ It might be a good idea to copy the `.ipfs/config` file somewhere with a useful
# Development
-Make sure [node.js](https://nodejs.org/) version 4+ and [npm](https://docs.npmjs.com/) version 3+ are installed and in your path.
+Make sure [node.js](https://nodejs.org/) version 6 and [npm](https://docs.npmjs.com/) version 3+ are installed and in your path.
# Contribute
diff --git a/circle.yml b/circle.yml
index 0eb26a0c3..005ec9ad0 100644
--- a/circle.yml
+++ b/circle.yml
@@ -2,12 +2,6 @@ machine:
node:
version: stable
-dependencies:
- override:
- # Ensure latest npm to avoid https://github.com/npm/npm/issues/10026
- - npm install -g npm
- - npm install
-
test:
override:
- npm run lint
diff --git a/package.json b/package.json
index ea4bbf2fa..3573befe8 100644
--- a/package.json
+++ b/package.json
@@ -14,95 +14,95 @@
"dependencies": {
"bl": "^1.2.1",
"bootstrap": "^3.3.7",
- "create-react-class": "^15.6.0",
- "d3": "^4.9.1",
- "fbjs": "^0.8.12",
+ "create-react-class": "^15.6.2",
+ "d3": "^4.11.0",
+ "fbjs": "^0.8.16",
"font-awesome": "^4.7.0",
- "i18next": "^8.4.2",
- "i18next-browser-languagedetector": "^2.0.0",
+ "i18next": "^10.0.7",
+ "i18next-browser-languagedetector": "^2.1.0",
"i18next-localstorage-cache": "^1.1.1",
"i18next-sprintf-postprocessor": "^0.2.2",
- "i18next-xhr-backend": "^1.4.2",
- "ipfs-api": "^14.0.4",
- "ipfs-geoip": "^2.2.0",
+ "i18next-xhr-backend": "^1.5.0",
+ "ipfs-api": "^17.0.1",
+ "ipfs-geoip": "^2.3.0",
"is-binary": "^0.1.0",
"lodash-es": "^4.17.4",
- "multiaddr": "^2.3.0",
- "prettysize": "^0.1.0",
- "prop-types": "^15.5.10",
- "react": "^15.6.1",
- "react-bootstrap": "^0.31.0",
- "react-contextmenu": "^2.6.3",
- "react-dnd": "^2.4.0",
- "react-dnd-html5-backend": "^2.4.1",
- "react-dom": "^15.6.1",
- "react-html5video": "^2.2.0",
- "react-redux": "^5.0.5",
- "react-redux-toastr": "^6.2.4",
- "react-router": "3.0.2",
- "react-router-bootstrap": "0.23.1",
- "react-router-redux": "^4.0.8",
- "react-syntax-highlighter": "^5.6.2",
- "readable-stream": "1.1.14",
- "redux": "^3.7.0",
- "redux-saga": "^0.15.4",
- "safe-buffer": "^5.1.1",
- "three": "^0.86.0"
+ "multiaddr": "^3.0.1",
+ "prettysize": "^1.1.0",
+ "prop-types": "^15.6.0",
+ "react": "^16.1.1",
+ "react-bootstrap": "^0.31.5",
+ "react-contextmenu": "^2.8.0",
+ "react-dnd": "^2.5.4",
+ "react-dnd-html5-backend": "^2.5.4",
+ "react-dom": "^16.1.1",
+ "react-html5video": "^2.5.1",
+ "react-redux": "^5.0.6",
+ "react-redux-toastr": "^7.1.6",
+ "react-router": "^4.2.0",
+ "react-router-bootstrap": "^0.24.4r",
+ "react-router-dom": "^4.2.2",
+ "react-router-redux": "^5.0.0-alpha.8",
+ "react-syntax-highlighter": "^6.0.3",
+ "readable-stream": "^2.3.3",
+ "redux": "^3.7.2",
+ "redux-saga": "^0.16.0",
+ "three": "^0.88.0"
},
"devDependencies": {
- "autoprefixer": "^7.1.1",
- "babel": "^6.23.0",
- "babel-core": "^6.25.0",
- "babel-eslint": "^7.2.3",
- "babel-loader": "^7.1.0",
- "babel-plugin-transform-react-display-name": "^6.25.0",
- "babel-plugin-transform-runtime": "^6.23.0",
- "babel-polyfill": "^6.23.0",
- "babel-preset-es2015": "^6.24.1",
- "babel-preset-react": "^6.24.1",
- "babel-preset-react-hmre": "^1.1.1",
- "babel-preset-stage-0": "^6.24.1",
- "babel-runtime": "^6.23.0",
- "chai": "^4.0.2",
- "chai-enzyme": "^0.7.1",
- "cheerio": "^1.0.0-rc.1",
+ "@babel/cli": "^7.0.0-beta.32",
+ "@babel/core": "^7.0.0-beta.32",
+ "@babel/plugin-transform-react-display-name": "^7.0.0-beta.32",
+ "@babel/plugin-transform-runtime": "^7.0.0-beta.32",
+ "@babel/polyfill": "^7.0.0-beta.32",
+ "@babel/preset-env": "^7.0.0-beta.32",
+ "@babel/preset-react": "^7.0.0-beta.32",
+ "@babel/preset-stage-0": "^7.0.0-beta.32",
+ "@babel/runtime": "^7.0.0-beta.32",
+ "autoprefixer": "^7.1.6",
+ "babel-eslint": "^8.0.2",
+ "babel-loader": "^8.0.0-beta.0",
+ "chai": "^4.1.2",
+ "chai-enzyme": "^1.0.0-beta.0",
+ "cheerio": "^1.0.0-rc.2",
"classnames": "^2.2.5",
- "css-loader": "^0.28.4",
- "debug": "^2.6.8",
- "enzyme": "^2.9.1",
- "eslint": "^4.1.0",
+ "css-loader": "^0.28.7",
+ "debug": "^3.1.0",
+ "enzyme": "^3.2.0",
+ "enzyme-adapter-react-16": "^1.1.0",
+ "eslint": "^4.11.0",
"eslint-config-standard": "^10.2.1",
"eslint-config-standard-react": "^5.0.0",
- "eslint-plugin-import": "^2.6.0",
- "eslint-plugin-node": "^5.1.0",
- "eslint-plugin-promise": "^3.5.0",
- "eslint-plugin-react": "^7.1.0",
+ "eslint-plugin-import": "^2.8.0",
+ "eslint-plugin-node": "^5.2.1",
+ "eslint-plugin-promise": "^3.6.0",
+ "eslint-plugin-react": "^7.5.1",
"eslint-plugin-standard": "^3.0.1",
"exports-loader": "^0.6.4",
- "file-loader": "^0.11.2",
- "hjs-webpack": "^9.1.0",
+ "file-loader": "^1.1.5",
+ "hjs-webpack": "^9.2.0",
"https-browserify": "^1.0.0",
"imports-loader": "^0.7.1",
- "json-loader": "^0.5.4",
- "karma": "^1.7.0",
+ "json-loader": "^0.5.7",
+ "karma": "^1.7.1",
"karma-chrome-launcher": "^2.2.0",
"karma-firefox-launcher": "^1.0.1",
"karma-mocha": "^1.3.0",
"karma-sourcemap-loader": "^0.3.7",
- "karma-webpack": "^2.0.3",
- "less": "^2.7.2",
- "less-loader": "^4.0.4",
- "mocha": "^3.4.2",
- "postcss-loader": "^2.0.6",
+ "karma-webpack": "^2.0.6",
+ "less": "^2.7.3",
+ "less-loader": "^4.0.5",
+ "mocha": "^4.0.1",
+ "postcss-loader": "^2.0.8",
"pre-commit": "^1.2.2",
- "react-addons-test-utils": "^15.6.0",
- "react-test-renderer": "^15.6.1",
+ "react-addons-test-utils": "^15.6.2",
+ "react-test-renderer": "^16.1.1",
"redux-logger": "^3.0.6",
- "standard": "^10.0.2",
+ "standard": "^10.0.3",
"stream-http": "^2.7.2",
- "style-loader": "^0.18.2",
- "url-loader": "^0.5.9",
- "webpack": "^3.0.0"
+ "style-loader": "^0.19.0",
+ "url-loader": "^0.6.2",
+ "webpack": "^3.8.1"
},
"pre-commit": [
"lint"
diff --git a/src/app/css/app.less b/src/app/css/app.less
index f9a36a2f6..ee51a59e6 100644
--- a/src/app/css/app.less
+++ b/src/app/css/app.less
@@ -7,6 +7,5 @@
@import "./typography";
@import './files';
-@import './files-preview';
@import './context-menu';
@import './preview';
diff --git a/src/app/css/files-preview.less b/src/app/css/files-preview.less
deleted file mode 100644
index d75a07bf2..000000000
--- a/src/app/css/files-preview.less
+++ /dev/null
@@ -1,38 +0,0 @@
-.files-preview {
- position: absolute;
- top: 0;
- background: white;
- height: 100%;
- width: 100%;
- min-height: 80vh;
-
- .files-preview-header {
- display: flex;
- flex: 1 0 auto;
- height: 36px;
-
- h3 {
- flex: 1;
- font-size: 16px;
- margin-top: 0;
- }
-
- a.close {
- flex: 0 1 auto;
- font-size: 20px;
-
- &:hover {
- cursor: pointer;
- }
- }
- }
-
- .files-preview-area {
- padding: 18px;
-
- img {
- width: 100%;
- overflow: auto;
- }
- }
-}
diff --git a/src/app/css/files.less b/src/app/css/files.less
index af6784bff..293460a63 100644
--- a/src/app/css/files.less
+++ b/src/app/css/files.less
@@ -1,4 +1,4 @@
-.files-explorer {
+.files {
user-select: none;
.files-drop {
@@ -19,6 +19,13 @@
}
}
+ .files-preview-area {
+ img {
+ width: 100%;
+ overflow: auto;
+ }
+ }
+
.files-tree {
display: flex;
flex: 1 0 auto;
diff --git a/src/app/js/actions/files.js b/src/app/js/actions/files.js
index dac81b6e9..bd8b79c87 100644
--- a/src/app/js/actions/files.js
+++ b/src/app/js/actions/files.js
@@ -3,6 +3,7 @@ import {createRequestTypes, action} from './utils'
export const FILES_LIST = createRequestTypes('FILES_LIST')
export const FILES_MKDIR = createRequestTypes('FILES_MKDIR')
export const FILES_RMDIR = createRequestTypes('FILES_RMDIR')
+export const FILES_MVDIR = createRequestTypes('FILES_MVDIR')
export const FILES_CREATE_FILES = createRequestTypes('FILES_CREATE_FILES')
export const FILES = {
@@ -16,7 +17,8 @@ export const FILES = {
SELECT_FILE: 'SELECT_FILE',
DESELECT_FILE: 'DESELECT_FILE',
DESELECT_ALL_FILE: 'DESELECT_ALL_FILE',
- CREATE_FILES: 'CREATE_FILES'
+ CREATE_FILES: 'CREATE_FILES',
+ MV_DIR: 'FILES_MV_DIR'
}
export const filesList = {
@@ -37,6 +39,12 @@ export const filesRmDir = {
failure: (error) => action(FILES_RMDIR.FAILURE, {error})
}
+export const filesMvDir = {
+ request: () => action(FILES_MVDIR.REQUEST),
+ success: () => action(FILES_MVDIR.SUCCESS),
+ failure: (error) => action(FILES_MVDIR.FAILURE, {error})
+}
+
export const createFiles = {
request: () => action(FILES_CREATE_FILES.REQUEST),
success: () => action(FILES_CREATE_FILES.SUCCESS),
@@ -53,6 +61,7 @@ export const filesRmTmpDir = () => action(FILES.RM_TMP_DIR)
export const filesSetTmpDirName = (name) => action(FILES.SET_TMP_DIR_NAME, {name})
export const filesCreateDir = () => action(FILES.CREATE_DIR)
export const filesRemoveDir = () => action(FILES.REMOVE_DIR)
+export const filesMoveDir = (from, to) => action(FILES.MV_DIR, {from, to})
export const filesSelect = (file) => action(FILES.SELECT_FILE, {file})
export const filesDeselect = (file) => action(FILES.DESELECT_FILE, {file})
diff --git a/src/app/js/components/files/action-bar.js b/src/app/js/components/files/action-bar.js
index 8f08d9f58..2e2266742 100644
--- a/src/app/js/components/files/action-bar.js
+++ b/src/app/js/components/files/action-bar.js
@@ -1,11 +1,35 @@
-import React, {Component, PropTypes} from 'react'
-import {isEmpty} from 'lodash-es'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import {isEmpty, map} from 'lodash-es'
+import {readAsBuffer} from '../../utils/files'
import Icon from '../../views/icon'
class ActionBar extends Component {
+ _onUploadClick = event => {
+ this.fileInput.click()
+ }
+
+ _onFilesChange = event => {
+ const rawFiles = this.fileInput.files
+ const { onCreateFiles } = this.props
+
+ Promise
+ .all(map(rawFiles, readAsBuffer))
+ .then((files) => {
+ onCreateFiles(files)
+ })
+
+ this.fileInput.value = null
+ }
+
render () {
- const {selectedFiles, onRemoveDir, onCreateDir} = this.props
+ const {
+ selectedFiles,
+ onRemoveDir,
+ onMoveDir,
+ onCreateDir
+ } = this.props
let fileActions
if (!isEmpty(selectedFiles)) {
@@ -13,14 +37,20 @@ class ActionBar extends Component {
const plural = length > 1 ? 's' : ''
const count = `${length} file${plural}`
- fileActions = (
+ fileActions = ([
+
,
- )
+ ])
}
return (
@@ -31,6 +61,18 @@ class ActionBar extends Component {
Create Folder
+
+ { this.fileInput = input }}
+ onChange={this._onFilesChange} />
{fileActions}
)
@@ -40,6 +82,8 @@ class ActionBar extends Component {
ActionBar.propTypes = {
onCreateDir: PropTypes.func.isRequired,
onRemoveDir: PropTypes.func.isRequired,
+ onMoveDir: PropTypes.func.isRequired,
+ onCreateFiles: PropTypes.func.isRequired,
selectedFiles: PropTypes.array.isRequired
}
diff --git a/src/app/js/components/files/breadcrumbs.js b/src/app/js/components/files/breadcrumbs.js
index c6b5e4999..8612e5865 100644
--- a/src/app/js/components/files/breadcrumbs.js
+++ b/src/app/js/components/files/breadcrumbs.js
@@ -1,11 +1,15 @@
-import React, {Component, PropTypes} from 'react'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
import {chain, isEmpty, compact} from 'lodash-es'
+import {join} from 'path'
import Icon from '../../views/icon'
class Breadcrumb extends Component {
_onClick = (event) => {
- this.props.onClick(this.props.path)
+ const {history, path} = this.props
+
+ history.push(join('/files/explorer/', path))
}
render () {
@@ -21,39 +25,48 @@ class Breadcrumb extends Component {
Breadcrumb.propTypes = {
path: PropTypes.string.isRequired,
- onClick: PropTypes.func.isRequired,
+ history: PropTypes.shape({
+ push: PropTypes.func.isRequired
+ }),
text: PropTypes.string.isRequired
}
class Breadcrumbs extends Component {
render () {
- const {root} = this.props
- const parts = {}
+ const {root, history} = this.props
+ const parts = []
const partsList = compact(root.split('/'))
+
partsList.map((part, i) => {
if (i === partsList.length - 1) {
- parts[part] = null
+ parts[i] = {
+ name: part,
+ path: null
+ }
} else {
- parts[part] = '/' + partsList.slice(0, i + 1).join('/')
+ parts[i] = {
+ name: part,
+ path: '/' + partsList.slice(0, i + 1).join('/')
+ }
}
})
const breadcrumbs = chain(parts)
- .map((root, part) => {
- if (!root) {
+ .map((info, index) => {
+ if (!info.path) {
return [
,
- {part}
+ {info.name}
]
}
return [
- ,
+ ,
+ key={`${info.path}-1`}
+ path={info.path}
+ history={history}
+ text={info.name} />
]
})
.flatten()
@@ -68,7 +81,7 @@ class Breadcrumbs extends Component {
)
}
@@ -83,7 +96,9 @@ class Breadcrumbs extends Component {
Breadcrumbs.propTypes = {
root: PropTypes.string,
- setRoot: PropTypes.func.isRequired
+ history: PropTypes.shape({
+ push: PropTypes.func.isRequired
+ })
}
Breadcrumbs.defaultProps = {
diff --git a/src/app/js/components/files/context-menu.js b/src/app/js/components/files/context-menu.js
index ec6d7a6cb..1a5ef093e 100644
--- a/src/app/js/components/files/context-menu.js
+++ b/src/app/js/components/files/context-menu.js
@@ -1,14 +1,18 @@
-import React, {Component, PropTypes} from 'react'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
import {ContextMenu, MenuItem} from 'react-contextmenu'
class FilesContextMenu extends Component {
render () {
- const {selectedFiles, onRemoveDir} = this.props
+ const {selectedFiles, onRemoveDir, onMoveDir} = this.props
return (
)
}
@@ -16,7 +20,8 @@ class FilesContextMenu extends Component {
FilesContextMenu.propTypes = {
selectedFiles: PropTypes.array.isRequired,
- onRemoveDir: PropTypes.func
+ onRemoveDir: PropTypes.func,
+ onMoveDir: PropTypes.func.isRequired
}
export default FilesContextMenu
diff --git a/src/app/js/components/files/tree.js b/src/app/js/components/files/tree.js
index b0e1b1400..d58e0b9bf 100644
--- a/src/app/js/components/files/tree.js
+++ b/src/app/js/components/files/tree.js
@@ -1,32 +1,16 @@
-import React, {PropTypes, Component} from 'react'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
import {isEmpty, includes, map} from 'lodash-es'
import {join} from 'path'
+import {readAsBuffer} from '../../utils/files'
import {DropTarget} from 'react-dnd'
import {NativeTypes} from 'react-dnd-html5-backend'
import classnames from 'classnames'
-import {Buffer} from 'safe-buffer'
import RowInput from './tree/row-input'
import Row from './tree/row'
import FilesContextMenu from './context-menu'
-function readAsBuffer (file) {
- return new Promise((resolve, reject) => {
- const reader = new window.FileReader()
- reader.onload = (event) => {
- resolve({
- content: new Buffer(reader.result),
- name: file.name
- })
- }
- reader.onerror = (event) => {
- reject(reader.error)
- }
-
- reader.readAsArrayBuffer(file)
- })
-}
-
const fileTarget = {
drop (props, monitor) {
Promise
@@ -100,7 +84,8 @@ class Tree extends Component {
+ onRemoveDir={this.props.onRemoveDir}
+ onMoveDir={this.props.onMoveDir} />
)
}
@@ -122,6 +107,7 @@ Tree.propTypes = {
onCancelCreateDir: PropTypes.func.isRequired,
onCreateFiles: PropTypes.func.isRequired,
onRemoveDir: PropTypes.func.isRequired,
+ onMoveDir: PropTypes.func.isRequired,
// react-dnd
connectDropTarget: PropTypes.func.isRequired,
isOver: PropTypes.bool.isRequired,
diff --git a/src/app/js/components/files/tree/row-input.js b/src/app/js/components/files/tree/row-input.js
index e3b7bacbf..304ada0eb 100644
--- a/src/app/js/components/files/tree/row-input.js
+++ b/src/app/js/components/files/tree/row-input.js
@@ -1,4 +1,5 @@
-import React, {Component, PropTypes} from 'react'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import Icon from '../../../views/icon'
diff --git a/src/app/js/components/files/tree/row.js b/src/app/js/components/files/tree/row.js
index 51f011995..2742b6d63 100644
--- a/src/app/js/components/files/tree/row.js
+++ b/src/app/js/components/files/tree/row.js
@@ -1,4 +1,5 @@
-import React, {PropTypes, Component} from 'react'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
import pretty from 'prettysize'
import classnames from 'classnames'
import {ContextMenuTrigger} from 'react-contextmenu'
@@ -15,7 +16,11 @@ function renderType (type) {
class Row extends Component {
_onClick = (event) => {
event.preventDefault()
- this.props.onClick(this.props.file, event.shiftKey)
+ this.props.onClick(
+ this.props.file,
+ event.shiftKey,
+ event.ctrlKey || event.metaKey
+ )
}
_onContextMenu = (event) => {
diff --git a/src/app/js/components/not-found.js b/src/app/js/components/not-found.js
index 0c979d094..a689ff657 100644
--- a/src/app/js/components/not-found.js
+++ b/src/app/js/components/not-found.js
@@ -1,7 +1,7 @@
import React from 'react'
import {Row, Col} from 'react-bootstrap'
import i18n from '../utils/i18n.js'
-import {Link} from 'react-router'
+import {Link} from 'react-router-dom'
export default function NotFound () {
return (
diff --git a/src/app/js/components/preview.js b/src/app/js/components/preview.js
index fdc2cb292..ebba961b9 100644
--- a/src/app/js/components/preview.js
+++ b/src/app/js/components/preview.js
@@ -1,14 +1,12 @@
-import React, {Component, PropTypes} from 'react'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
import {findKey, includes} from 'lodash-es'
import Highlight from 'react-syntax-highlighter'
import isBinary from 'is-binary'
import Video from 'react-html5video'
import {toastr} from 'react-redux-toastr'
-
import 'react-html5video/dist/styles.css'
-
import languages from '../constants/languages'
-import shouldPureComponentUpdate from '../utils/pure'
const loading = (Loading
)
@@ -120,10 +118,6 @@ function render (name, stats, gatewayUrl, read, content) {
}
class Preview extends Component {
- shouldComponentUpdate () {
- shouldPureComponentUpdate()
- }
-
render () {
return (
diff --git a/src/app/js/containers/app.js b/src/app/js/containers/app.js
index 5c83e8c92..b7b24f3a7 100644
--- a/src/app/js/containers/app.js
+++ b/src/app/js/containers/app.js
@@ -1,9 +1,10 @@
-import React, {Component, PropTypes} from 'react'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import ReduxToastr, {toastr} from 'react-redux-toastr'
import {DragDropContext} from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
-import {Link} from 'react-router'
+import {Link, withRouter} from 'react-router-dom'
import i18n from '../utils/i18n.js'
import {parse} from '../utils/path'
import {errors} from '../actions'
@@ -22,6 +23,10 @@ class App extends Component {
}
}
+ goToHashOrPath = () => {
+ this.context.router.history.push(`/objects${parse(this.refs.dagPath.value).urlify()}`)
+ }
+
render () {
const {children} = this.props
@@ -49,10 +54,10 @@ class App extends Component {
@@ -118,6 +123,6 @@ function mapStateToProps (state) {
}
}
-export default DragDropContext(HTML5Backend)(connect(mapStateToProps, {
+export default withRouter(DragDropContext(HTML5Backend)(connect(mapStateToProps, {
resetErrorMessage: errors.resetErrorMessage
-})(App))
+})(App)))
diff --git a/src/app/js/containers/files-explorer.js b/src/app/js/containers/files-explorer.js
index b6b3d6997..c8c9741d2 100644
--- a/src/app/js/containers/files-explorer.js
+++ b/src/app/js/containers/files-explorer.js
@@ -1,18 +1,111 @@
-import React, {PropTypes, Component} from 'react'
-import {Row, Col} from 'react-bootstrap'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
import {connect} from 'react-redux'
-import {join} from 'path'
+import {join, dirname, basename} from 'path'
import {includes} from 'lodash-es'
import {toastr} from 'react-redux-toastr'
-
-import {files, router} from '../actions'
+import {withRouter} from 'react-router'
+import {pages, files} from '../actions'
import Tree from './../components/files/tree'
import ActionBar from './../components/files/action-bar'
import Breadcrumbs from './../components/files/breadcrumbs'
class FilesExplorer extends Component {
- _onRowClick = (file, shiftKey) => {
+ componentWillMount () {
+ this.props.load()
+ }
+
+ componentDidMount () {
+ const path = this.props.match.params[0]
+ const {setRoot} = this.props
+
+ if (path) {
+ setRoot(join('/', path))
+ } else {
+ setRoot('/')
+ }
+
+ document.addEventListener('keydown', this._onKeyDown)
+ }
+
+ componentWillReceiveProps (nextProps) {
+ const locationChanged = nextProps.location !== this.props.location
+ const {setRoot} = this.props
+
+ if (locationChanged) {
+ setRoot(join('/', nextProps.match.params[0]))
+ }
+ }
+
+ componentWillUnmount () {
+ document.removeEventListener('keydown', this._onKeyDown)
+ this.props.leave()
+ }
+
+ _onKeyDown = (event) => {
+ const {deselectAll} = this.props
+
+ switch (event.which) {
+ case 27:
+ // Escape
+ event.preventDefault()
+ deselectAll()
+ break
+ case 46:
+ // Delete
+ event.preventDefault()
+ this._onRemoveDir()
+ break
+ case 113:
+ // F2
+ event.preventDefault()
+ this._onMoveDir()
+ break
+ }
+ }
+
+ selectRangeFromCurrent = (toFile) => {
+ const {
+ selected,
+ list
+ } = this.props
+
+ const selectedName = basename(selected[0])
+
+ let first = 0
+ let last = list.indexOf(toFile)
+
+ let i = list.findIndex((el, index) => {
+ return el.Name === selectedName
+ })
+
+ if (last > i) {
+ first = i + 1
+ } else {
+ first = last
+ last = i - 1
+ }
+
+ this.selectAllBetween(first, last)
+ }
+
+ selectAllBetween = (first, last) => {
+ const {
+ root,
+ list,
+ select
+ } = this.props
+
+ list.filter((el, index) => {
+ return index >= first && index <= last
+ }).forEach(el => {
+ const filePath = join(root, el.Name)
+ select(filePath)
+ })
+ }
+
+ _onRowClick = (file, shiftKey, ctrlKey) => {
const {
root,
selected,
@@ -20,13 +113,16 @@ class FilesExplorer extends Component {
deselect,
deselectAll
} = this.props
+
const filePath = join(root, file.Name)
const currentlySelected = includes(selected, filePath)
if (currentlySelected) {
deselect(filePath)
- } else if (shiftKey && !currentlySelected) {
+ } else if (ctrlKey && !currentlySelected) {
select(filePath)
+ } else if (shiftKey && !currentlySelected && selected.length === 1) {
+ this.selectRangeFromCurrent(file)
} else {
deselectAll()
select(filePath)
@@ -54,18 +150,14 @@ class FilesExplorer extends Component {
}
_onRowDoubleClick = (file, shiftKey) => {
- const {root, deselectAll, setRoot, push} = this.props
+ const {root, deselectAll, history} = this.props
const filePath = join(root, file.Name)
deselectAll()
+
if (file.Type === 'directory') {
- setRoot(filePath)
+ history.push(join('/files/explorer', filePath))
} else {
- push({
- pathname: '/files/preview',
- query: {
- name: filePath
- }
- })
+ history.push(join('/files/preview', filePath))
}
}
@@ -93,45 +185,49 @@ class FilesExplorer extends Component {
})
}
+ _onMoveDir = () => {
+ const {selected, moveDir} = this.props
+ const count = selected.length
+
+ if (count !== 1) {
+ return
+ }
+
+ const oldName = selected[0]
+ // TODO: prettier prompt
+ const newName = join(dirname(oldName), window.prompt('Insert the name name:'))
+ moveDir(oldName, newName)
+ }
+
render () {
- const {list, root, setRoot, tmpDir, selected} = this.props
+ const {list, root, tmpDir, selected, history} = this.props
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
)
}
@@ -145,6 +241,10 @@ FilesExplorer.propTypes = {
root: PropTypes.string.isRequired,
name: PropTypes.string
}),
+ location: PropTypes.object.isRequired,
+ match: PropTypes.shape({
+ params: PropTypes.object.isRequired
+ }),
selected: PropTypes.array.isRequired,
// actions
setRoot: PropTypes.func.isRequired,
@@ -152,30 +252,36 @@ FilesExplorer.propTypes = {
setTmpDirName: PropTypes.func.isRequired,
createDir: PropTypes.func.isRequired,
removeDir: PropTypes.func.isRequired,
+ moveDir: PropTypes.func.isRequired,
rmTmpDir: PropTypes.func.isRequired,
select: PropTypes.func.isRequired,
deselect: PropTypes.func.isRequired,
deselectAll: PropTypes.func.isRequired,
createFiles: PropTypes.func.isRequired,
- push: PropTypes.func.isRequired
+ history: PropTypes.object.isRequired,
+ load: PropTypes.func.isRequired,
+ leave: PropTypes.func.isRequired
}
-function mapStateToProps (state) {
+function mapStateToProps (state, ownProps) {
const {files} = state
- return files
+ return {
+ ...files
+ }
}
-export default connect(mapStateToProps, {
+export default withRouter(connect(mapStateToProps, {
setRoot: files.filesSetRoot,
createTmpDir: files.filesCreateTmpDir,
setTmpDirName: files.filesSetTmpDirName,
createDir: files.filesCreateDir,
removeDir: files.filesRemoveDir,
+ moveDir: files.filesMoveDir,
rmTmpDir: files.filesRmTmpDir,
select: files.filesSelect,
deselect: files.filesDeselect,
deselectAll: files.filesDeselectAll,
createFiles: files.filesCreateFiles,
- push: router.push
-})(FilesExplorer)
+ ...pages.files
+})(FilesExplorer))
diff --git a/src/app/js/containers/files-preview.js b/src/app/js/containers/files-preview.js
index 095b5de30..d697d244e 100644
--- a/src/app/js/containers/files-preview.js
+++ b/src/app/js/containers/files-preview.js
@@ -1,11 +1,12 @@
-import React, {Component, PropTypes} from 'react'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
import {connect} from 'react-redux'
+import {join} from 'path'
import multiaddr from 'multiaddr'
-import {pages, router, preview} from '../actions'
-import Icon from '../views/icon'
+import {pages, preview} from '../actions'
import Preview from '../components/preview'
-import shouldPureComponentUpdate from '../utils/pure'
+import Breadcrumbs from './../components/files/breadcrumbs'
function getGatewayUrl (config) {
if (!config.Addresses) {
@@ -29,16 +30,6 @@ function getGatewayUrl (config) {
}
class FilesPreview extends Component {
- _onClose = (event) => {
- event.preventDefault()
-
- this.props.goBack()
- }
-
- shouldComponentUpdate () {
- shouldPureComponentUpdate()
- }
-
componentWillMount () {
this.props.load()
}
@@ -48,19 +39,17 @@ class FilesPreview extends Component {
}
render () {
- const {name, read} = this.props
+ const {name, read, history} = this.props
const content = this.props.preview ? this.props.preview.content : null
const stats = this.props.preview ? this.props.preview.stats : {}
const gatewayUrl = getGatewayUrl(this.props.config)
return (
-
-
+
+
-
- {this.props.children}
+
+
+
+
+
)
}
}
-Files.propTypes = {
- load: PropTypes.func.isRequired,
- leave: PropTypes.func.isRequired,
- children: PropTypes.node
-}
-
-export default connect(null, {...pages.files})(Files)
+export default Files
diff --git a/src/app/js/containers/root.js b/src/app/js/containers/root.js
index d248ff569..ba9f15931 100644
--- a/src/app/js/containers/root.js
+++ b/src/app/js/containers/root.js
@@ -1,6 +1,7 @@
-import React, {Component, PropTypes} from 'react'
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
import {Provider} from 'react-redux'
-import {Router} from 'react-router'
+import {ConnectedRouter} from 'react-router-redux'
window.React = React
@@ -10,11 +11,9 @@ class Root extends Component {
return (
-
-
- {routes}
-
-
+
+ {routes}
+
)
}
diff --git a/src/app/js/include/globe.js b/src/app/js/include/globe.js
index b9bb2073d..db702f67b 100644
--- a/src/app/js/include/globe.js
+++ b/src/app/js/include/globe.js
@@ -118,7 +118,7 @@ export default function (container, opts) {
shader = Shaders['earth']
uniforms = THREE.UniformsUtils.clone(shader.uniforms)
- uniforms['texture'].value = THREE.ImageUtils.loadTexture(imgDir + 'world.jpg')
+ uniforms['texture'].value = new THREE.TextureLoader().load(imgDir + 'world.jpg')
material = new THREE.ShaderMaterial({
uniforms: uniforms,
diff --git a/src/app/js/index.js b/src/app/js/index.js
index 5fd66e129..da0dd1489 100644
--- a/src/app/js/index.js
+++ b/src/app/js/index.js
@@ -1,9 +1,9 @@
-import 'babel-polyfill'
+import '@babel/polyfill'
import React from 'react'
import {render} from 'react-dom'
-import {hashHistory} from 'react-router'
-import {syncHistoryWithStore} from 'react-router-redux'
+
+import createHistory from 'history/createHashHistory'
import routes from './routes'
import Root from './containers/root'
@@ -11,7 +11,7 @@ import configureStore from './store/configure-store'
import '../css/app.less'
const store = configureStore()
-const history = syncHistoryWithStore(hashHistory, store)
+const history = createHistory()
window.requestAnimationFrame(() => {
render(
diff --git a/src/app/js/pages/config.js b/src/app/js/pages/config.js
index a09dc6efb..95cb0adce 100644
--- a/src/app/js/pages/config.js
+++ b/src/app/js/pages/config.js
@@ -6,7 +6,7 @@ import {withIpfs} from '../components/ipfs'
class Config extends Component {
constructor (props) {
- super(constructor)
+ super(props)
this.state = { config: null }
}
diff --git a/src/app/js/pages/logs.js b/src/app/js/pages/logs.js
index 7d233c15d..d0f4244a7 100644
--- a/src/app/js/pages/logs.js
+++ b/src/app/js/pages/logs.js
@@ -1,4 +1,5 @@
-import React, { Component, PropTypes } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import i18n from '../utils/i18n.js'
import { Row, Col } from 'react-bootstrap'
diff --git a/src/app/js/pages/objects.js b/src/app/js/pages/objects.js
index 7afd81559..e50ddef75 100644
--- a/src/app/js/pages/objects.js
+++ b/src/app/js/pages/objects.js
@@ -1,22 +1,23 @@
import React from 'react'
import PropTypes from 'prop-types'
import ObjectView from '../views/object'
-import {parse} from '../utils/path.js'
-import i18n from '../utils/i18n.js'
+import {parse} from '../utils/path'
+import {join} from 'path'
+import i18n from '../utils/i18n'
import {Row, Col, Button} from 'react-bootstrap'
import {withIpfs} from '../components/ipfs'
class Objects extends React.Component {
constructor (props) {
super(props)
- const {params, ipfs} = props
- this.state = this._getStateFromRoute(params, ipfs)
+ const {match, ipfs} = props
+ this.state = this._getStateFromRoute(match.params, ipfs)
}
_getStateFromRoute = (params, ipfs) => {
let state = {}
- if (params.path) {
- const path = parse(params.path)
+ if (params[0]) {
+ const path = parse(join('/', params[0]))
state.path = path
this._getObject(ipfs, state.path)
state.pathInput = path.toString()
@@ -26,8 +27,8 @@ class Objects extends React.Component {
}
_updateState = () => {
- const {params, ipfs} = this.props
- this.setState(this._getStateFromRoute(params, ipfs))
+ const {match, ipfs} = this.props
+ this.setState(this._getStateFromRoute(match.params, ipfs))
};
_getObject = (ipfs, path) => {
@@ -49,17 +50,16 @@ class Objects extends React.Component {
if (event.which && event.which !== 13) {
return
}
- const params = this.props.params
+ const params = this.props.match.params
params.path = parse(this.state.pathInput).urlify()
- const route = ['/objects']
+ let route = '/objects'
if (params.path) {
- route.push(params.path)
+ route += params.path
}
- const r = route.join('/')
- if ('#' + r !== window.location.hash) {
- this.context.router.push(r)
+ if ('#' + route !== window.location.hash) {
+ this.context.router.history.push(route)
}
}
@@ -96,7 +96,7 @@ class Objects extends React.Component {
- {i18n.t('Enter hash or path')}
+ {i18n.t('Enter a hash')}
this.setState({pathInput: event.target.value.trim()})}
onKeyPress={this._update}
value={this.state.pathInput}
- placeholder={i18n.t('Enter hash or path: /ipfs/QmBpath...')} />
+ placeholder={i18n.t('Enter a hash: QmS4ustL54u...')} />