-
-
-
-
{i18n.t('Drag-and-drop your files here')}
-
{i18n.t('or')}
-
-
-
-
-
-
{i18n.t('Drop your file here to add it to IPFS')}
-
-
-
{i18n.t('Added')} {this.state.confirm}
-
-
+
+
-
-
-
-
-
+
+
+
+ {i18n.t('Drag-and-drop your files here')}
+
+
+ {i18n.t('or')}
+
+
+
+
+
+
+
+ {i18n.t('Drop your file here to add it to IPFS')}
+
+
+
+
+ {i18n.t('Added')} {this.state.confirm}
+
+
+
+ )
+ },
-
-
{i18n.t('Pinned Files')}
-
-
-
-
+ _renderPanel () {
+ switch (this.props.location.pathname) {
+ case '/files':
+ return (
+
+
+
+ )
+ case '/files/pinned':
+ return (
+
+
+
+ )
+ case '/files/all':
+ return (
+
+
+
+ )
+ default:
+ return ''
+ }
+ },
-
-
{i18n.t('All Local Files')}
-
-
-
-
-
-
+ render: function () {
+ return (
+
+
+
+
+
+ {this._renderTitle()}
+ {this._renderPanel()}
+
+
+
)
}
})
diff --git a/app/scripts/pages/notfound.js b/app/scripts/pages/notfound.js
index 366664161..c884db749 100644
--- a/app/scripts/pages/notfound.js
+++ b/app/scripts/pages/notfound.js
@@ -1,7 +1,8 @@
-var React = require('react')
-var Row = require('react-bootstrap').Row
-var Col = require('react-bootstrap').Col
-var i18n = require('../utils/i18n.js')
+import React from 'react'
+import {Row, Col} from 'react-bootstrap'
+import i18n from '../utils/i18n.js'
+
+import {Link} from 'react-router'
export default React.createClass({
displayName: 'NotFound',
@@ -9,11 +10,12 @@ export default React.createClass({
return (
-
{i18n.t('404 - Not Found')}
-
- {i18n.t('Go to console home')}
-
+
+
+ {i18n.t('Go to console home')}
+
+
)
diff --git a/app/scripts/pages/objects.js b/app/scripts/pages/objects.js
index 77616d2e1..af911c28b 100644
--- a/app/scripts/pages/objects.js
+++ b/app/scripts/pages/objects.js
@@ -1,5 +1,4 @@
import React from 'react'
-import Router from 'react-router'
import $ from 'jquery'
import ObjectView from '../views/object'
import {parse} from '../utils/path.js'
@@ -8,10 +7,15 @@ import {Row, Col, Button} from 'react-bootstrap'
export default React.createClass({
displayName: 'Objects',
+
+ contextTypes: {
+ router: React.PropTypes.object.isRequired
+ },
+
propTypes: {
- gateway: React.PropTypes.string
+ gateway: React.PropTypes.string,
+ params: React.PropTypes.object
},
- mixins: [ Router.State ],
componentDidMount: function () {
window.addEventListener('hashchange', this.updateState)
@@ -22,7 +26,7 @@ export default React.createClass({
},
getStateFromRoute: function () {
- var params = this.context.router.getCurrentParams()
+ const params = this.props.params
var state = {}
if (params.path) {
var path = parse(params.path)
@@ -66,9 +70,13 @@ export default React.createClass({
update: function (e) {
if (e.which && e.which !== 13) return
- var params = this.context.router.getCurrentParams()
+ const params = this.props.params
params.path = parse(this.state.pathInput).urlify()
- this.context.router.transitionTo('objects', params)
+
+ const route = ['/objects']
+ if (params.path) route.push(params.path)
+
+ this.context.router.push(route.join('/'))
},
render: function () {
@@ -82,8 +90,7 @@ export default React.createClass({
: null
// TODO add provider-view here
- var views = {
- object: (!error && this.state.object
+ var views = (!error && this.state.object
?
: null)
- }
-
- var params = this.context.router.getCurrentParams()
- var tab = params.tab
return (
@@ -117,7 +120,7 @@ export default React.createClass({
{error}
- {views[tab]}
+ {views}
)
diff --git a/app/scripts/routes.js b/app/scripts/routes.js
index 987ef9aab..bb33e65ac 100644
--- a/app/scripts/routes.js
+++ b/app/scripts/routes.js
@@ -1,5 +1,5 @@
import React from 'react'
-import {Route, DefaultRoute, NotFoundRoute, Redirect} from 'react-router'
+import {Route, IndexRoute, Redirect} from 'react-router'
import Page from './views/page'
import HomePage from './pages/home'
@@ -13,18 +13,21 @@ import LogPage from './pages/logs'
import NotFoundPage from './pages/notfound'
export default (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
diff --git a/app/scripts/views/config.js b/app/scripts/views/config.js
index 181c0add2..b67dc15bd 100644
--- a/app/scripts/views/config.js
+++ b/app/scripts/views/config.js
@@ -11,7 +11,7 @@ export default React.createClass({
getInitialState: function () {
return {
- body: JSON.stringify(this.props.config, null, ' '),
+ body: JSON.stringify(this.props.config, null, 2),
error: null,
saving: false,
saved: false
@@ -20,7 +20,7 @@ export default React.createClass({
reset: function () {
this.setState({
- body: JSON.stringify(this.props.config, null, ' '),
+ body: JSON.stringify(this.props.config, null, 2),
error: null
})
},
@@ -60,8 +60,6 @@ export default React.createClass({
saving: true
})
- console.log(t.state.body)
-
t.props.ipfs.config.replace(new Buffer(t.state.body), function (err) {
var newState = { saving: false }
if (err) {
diff --git a/app/scripts/views/filelist.js b/app/scripts/views/filelist.js
index 4570e8a56..288d3afee 100644
--- a/app/scripts/views/filelist.js
+++ b/app/scripts/views/filelist.js
@@ -51,7 +51,7 @@ export default React.createClass({
}
var gatewayPath = t.props.gateway + '/ipfs/' + file.id
- var dagPath = '#/objects/object/' + file.id
+ var dagPath = '#/objects/' + file.id
var tooltip = (
{i18n.t('Remove')}
diff --git a/app/scripts/views/icon.js b/app/scripts/views/icon.js
new file mode 100644
index 000000000..0a3da8443
--- /dev/null
+++ b/app/scripts/views/icon.js
@@ -0,0 +1,17 @@
+import React, {PropTypes} from 'react'
+
+function Icon ({glyph}) {
+ return (
+
+
+ )
+}
+
+Icon.propTypes = {
+ glyph: PropTypes.string.isRequired
+}
+
+export default Icon
diff --git a/app/scripts/views/nav-item.js b/app/scripts/views/nav-item.js
new file mode 100644
index 000000000..17a9a8b67
--- /dev/null
+++ b/app/scripts/views/nav-item.js
@@ -0,0 +1,21 @@
+import React, {PropTypes} from 'react'
+import {Link} from 'react-router'
+
+import i18n from '../utils/i18n'
+import Icon from './icon'
+
+function NavItem ({title, url, icon}) {
+ return (
+
+ {i18n.t(title)}
+
+ )
+}
+
+NavItem.propTypes = {
+ title: PropTypes.string.isRequired,
+ url: PropTypes.string.isRequired,
+ icon: PropTypes.string.isRequired
+}
+
+export default NavItem
diff --git a/app/scripts/views/nav.js b/app/scripts/views/nav.js
index ebbf015dc..99d0b349b 100644
--- a/app/scripts/views/nav.js
+++ b/app/scripts/views/nav.js
@@ -1,42 +1,35 @@
import React from 'react'
-import {Link} from 'react-router'
-import i18n from '../utils/i18n.js'
+
+import NavItem from './nav-item'
export default React.createClass({
displayName: 'Nav',
+
+ contextTypes: {
+ router: React.PropTypes.object.isRequired
+ },
+
render: function () {
return (
- -
-
- {i18n.t('Home')}
-
+
-
+
-
-
- {i18n.t('Connections')}
-
+
-
-
- {i18n.t('Files')}
-
+
-
-
- {i18n.t('DAG')}
-
+
-
-
- {i18n.t('Config')}
-
+
-
-
- {i18n.t('Logs')}
-
+
diff --git a/app/scripts/views/object.js b/app/scripts/views/object.js
index c1a373f42..1ef71741e 100644
--- a/app/scripts/views/object.js
+++ b/app/scripts/views/object.js
@@ -1,6 +1,6 @@
import React from 'react'
import {Link} from 'react-router'
-import upath from '../utils/path.js'
+import {parse} from '../utils/path.js'
import i18n from '../utils/i18n.js'
export default React.createClass({
@@ -19,7 +19,7 @@ export default React.createClass({
var t = this
var parent = this.props.path.parent()
var parentlink = parent
- ?
+ ?
{i18n.t('Parent object')}
: null
@@ -45,18 +45,18 @@ export default React.createClass({
if (link.Name) {
path = t.props.path.append(link.Name).urlify()
} else { // support un-named links
- path = upath.parse(link.Hash).urlify()
+ path = parse(link.Hash).urlify()
}
return (
-
+
{link.Name}
|
-
+
{link.Hash}
|
@@ -73,7 +73,7 @@ export default React.createClass({
var resolved = this.props.permalink
?
{i18n.t('permalink:')}
-
+
{this.props.permalink.toString()}
diff --git a/app/scripts/views/page.js b/app/scripts/views/page.js
index 33b0d534f..9d1dad1df 100644
--- a/app/scripts/views/page.js
+++ b/app/scripts/views/page.js
@@ -1,9 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom'
import Nav from './nav'
-import {RouteHandler, Link} from 'react-router'
+import {Link} from 'react-router'
import $ from 'jquery'
+
import i18n from '../utils/i18n.js'
+import {parse} from '../utils/path'
var host = window.location.hostname
var port = window.location.port || 80
@@ -18,6 +20,14 @@ var ipfsHost = window.location.host
export default React.createClass({
displayName: 'Page',
+
+ contextTypes: {
+ router: React.PropTypes.object.isRequired
+ },
+ propTypes: {
+ children: React.PropTypes.object
+ },
+
getInitialState: function () {
var t = this
@@ -50,7 +60,7 @@ export default React.createClass({
showDAG: function () {
var path = $(ReactDOM.findDOMNode(this)).find('.dag-path').val()
- window.location = '#/objects/object/' + path.replace(/\//g, '\\')
+ this.context.router.push(`/objects/${parse(path).urlify()}`)
},
update: function () {
@@ -86,7 +96,7 @@ export default React.createClass({
-
+
{i18n.t('IPFS')}
@@ -134,7 +144,9 @@ export default React.createClass({
{update}
-
+ {this.props.children && React.cloneElement(
+ this.props.children, {ipfs: ipfs, host: ipfsHost, gateway: this.state.gateway}
+ )}
diff --git a/karma.conf.js b/karma.conf.js
index 5f6a7e7c3..290e66237 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -1,4 +1,4 @@
-var webpackConfig = require('./webpack.config')
+var webpackConfig = require('./make-config')(true)
module.exports = function (config) {
config.set({
@@ -10,11 +10,11 @@ module.exports = function (config) {
frameworks: [ 'mocha' ],
files: [
- 'tests.webpack.js'
+ 'test/setup.js'
],
preprocessors: {
- 'tests.webpack.js': [ 'webpack', 'sourcemap' ]
+ 'test/setup.js': ['webpack', 'sourcemap']
},
reporters: [ 'dots' ],
diff --git a/make-config.js b/make-config.js
new file mode 100644
index 000000000..317de8be0
--- /dev/null
+++ b/make-config.js
@@ -0,0 +1,42 @@
+var createConfig = require('hjs-webpack')
+
+module.exports = function makeConfig (isDev) {
+ var config = createConfig({
+ isDev: isDev,
+ in: './app/scripts/app.js',
+ out: 'dist',
+ html: function (ctx) {
+ return ctx.defaultTemplate({
+ publicPath: ''
+ })
+ },
+ clearBeforeBuild: '!(locale|img|favicon.ico)'
+ })
+
+ // Handle js-ipfs-api
+ config.module.loaders.push({
+ test: /\.js$/,
+ include: /node_modules\/(hoek|qs|wreck|boom|ipfs-api)/,
+ loader: 'babel-loader'
+ })
+
+ config.externals = {
+ net: '{}',
+ fs: '{}',
+ tls: '{}',
+ console: '{}',
+ 'require-dir': '{}'
+ }
+
+ config.resolve = {
+ modulesDirectories: [
+ 'node_modules'
+ ],
+ alias: {
+ http: 'stream-http',
+ https: 'https-browserify'
+ }
+ }
+
+ return config
+}
diff --git a/package.json b/package.json
index dea5f1639..a04652ac9 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
"react-bootstrap": "^0.28.1",
"react-dom": "^0.14.2",
"react-localstorage": "^0.2.8",
- "react-router": "^0.13.5",
+ "react-router": "^2.0.0-rc4",
"three": "^0.73.0"
},
"devDependencies": {
@@ -28,6 +28,7 @@
"babel-core": "^6.3.21",
"babel-eslint": "^5.0.0-beta6",
"babel-loader": "^6.2.0",
+ "babel-plugin-transform-react-display-name": "^6.3.13",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-0": "^6.3.13",
@@ -43,6 +44,7 @@
"https-browserify": "0.0.1",
"ipfs-api": "^2.10.1",
"json-loader": "^0.5.4",
+ "jsx-chai": "^2.0.0",
"karma": "^0.13.15",
"karma-chrome-launcher": "^0.2.2",
"karma-firefox-launcher": "^0.1.7",
@@ -55,6 +57,7 @@
"postcss-loader": "^0.8.0",
"pre-commit": "^1.1.2",
"react-addons-test-utils": "^0.14.3",
+ "react-router-bootstrap": "^0.20.0",
"stream-http": "^2.0.2",
"style-loader": "^0.13.0",
"url-loader": "^0.5.7",
diff --git a/test/.eslintrc b/test/.eslintrc
new file mode 100644
index 000000000..7eeefc33b
--- /dev/null
+++ b/test/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "env": {
+ "mocha": true
+ }
+}
diff --git a/test/setup.js b/test/setup.js
new file mode 100644
index 000000000..9d3400782
--- /dev/null
+++ b/test/setup.js
@@ -0,0 +1,19 @@
+// This gets replaced by karma webpack with the updated files on rebuild
+var __karmaWebpackManifest__ = []
+
+// require all modules ending in '_test' from the
+// current directory and all subdirectories
+var testsContext = require.context('.', true, /\.spec\.js$/)
+
+function inManifest (path) {
+ return __karmaWebpackManifest__.indexOf(path) >= 0
+}
+
+var runnable = testsContext.keys().filter(inManifest)
+
+// Run all tests if we didn't find any changes
+if (!runnable.length) {
+ runnable = testsContext.keys()
+}
+
+runnable.forEach(testsContext)
diff --git a/test/test-helpers.js b/test/test-helpers.js
new file mode 100644
index 000000000..871a408b3
--- /dev/null
+++ b/test/test-helpers.js
@@ -0,0 +1,13 @@
+import chai, {expect} from 'chai'
+import jsxChai from 'jsx-chai'
+import {createRenderer} from 'react-addons-test-utils'
+
+chai.use(jsxChai)
+
+export function shallowRender (comp) {
+ const renderer = createRenderer()
+ renderer.render(comp)
+ return renderer.getRenderOutput()
+}
+
+export {expect}
diff --git a/test/utils/path.spec.js b/test/utils/path.spec.js
index 9201d17dd..a48bc6f95 100644
--- a/test/utils/path.spec.js
+++ b/test/utils/path.spec.js
@@ -1,5 +1,3 @@
-/* eslint-env mocha */
-
import {expect} from 'chai'
import {parse} from '../../app/scripts/utils/path'
diff --git a/test/views/config.spec.js b/test/views/config.spec.js
new file mode 100644
index 000000000..ed373a634
--- /dev/null
+++ b/test/views/config.spec.js
@@ -0,0 +1,20 @@
+import {expect, shallowRender} from '../test-helpers'
+import React from 'react'
+
+import ConfigView from '../../app/scripts/views/config'
+
+describe('ConfigView', () => {
+ it('renders the given config', () => {
+ const config = {a: true, b: {c: 'hello'}}
+ const el = shallowRender()
+
+ expect(el).to.contain(
+