diff --git a/js/package.json b/js/package.json
index a035ae526..842a81e6f 100644
--- a/js/package.json
+++ b/js/package.json
@@ -46,7 +46,7 @@
"devDependencies": {
"babel-cli": "^6.10.1",
"babel-core": "^6.10.4",
- "babel-eslint": "^6.1.2",
+ "babel-eslint": "^7.1.0",
"babel-loader": "^6.2.3",
"babel-plugin-lodash": "^3.2.2",
"babel-plugin-transform-class-properties": "^6.11.5",
@@ -128,6 +128,9 @@
"marked": "^0.3.6",
"material-ui": "^0.16.1",
"material-ui-chip-input": "^0.8.0",
+ "mobx": "^2.6.1",
+ "mobx-react": "^3.5.8",
+ "mobx-react-devtools": "^4.2.9",
"moment": "^2.14.1",
"qs": "^6.3.0",
"react": "^15.2.1",
diff --git a/js/src/views/Dapp/dapp.js b/js/src/views/Dapp/dapp.js
index 7f5f36d1d..3e5b206ad 100644
--- a/js/src/views/Dapp/dapp.js
+++ b/js/src/views/Dapp/dapp.js
@@ -15,12 +15,13 @@
// along with Parity. If not, see .
import React, { Component, PropTypes } from 'react';
+import { observer } from 'mobx-react';
-import Contracts from '../../contracts';
-import { fetchAvailable } from '../Dapps/registry';
+import DappsStore from '../Dapps/dappsStore';
import styles from './dapp.css';
+@observer
export default class Dapp extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
@@ -30,17 +31,12 @@ export default class Dapp extends Component {
params: PropTypes.object
};
- state = {
- app: null
- }
-
- componentWillMount () {
- this.lookup();
- }
+ store = new DappsStore(this.context.api);
render () {
- const { app } = this.state;
const { dappsUrl } = this.context.api;
+ const { id } = this.props.params;
+ const app = this.store.apps.find((app) => app.id === id);
if (!app) {
return null;
@@ -76,30 +72,4 @@ export default class Dapp extends Component {
);
}
-
- lookup () {
- const { api } = this.context;
- const { id } = this.props.params;
- const { dappReg } = Contracts.get();
-
- fetchAvailable(api)
- .then((available) => {
- return available.find((app) => app.id === id);
- })
- .then((app) => {
- if (app.type !== 'network') {
- return app;
- }
-
- return dappReg
- .getContent(app.id)
- .then((contentHash) => {
- app.contentHash = api.util.bytesToHex(contentHash).substr(2);
- return app;
- });
- })
- .then((app) => {
- this.setState({ app });
- });
- }
}
diff --git a/js/src/views/Dapps/AddDapps/AddDapps.js b/js/src/views/Dapps/AddDapps/AddDapps.js
index 208a65004..38bb64792 100644
--- a/js/src/views/Dapps/AddDapps/AddDapps.js
+++ b/js/src/views/Dapps/AddDapps/AddDapps.js
@@ -15,6 +15,7 @@
// along with Parity. If not, see .
import React, { Component, PropTypes } from 'react';
+import { observer } from 'mobx-react';
import DoneIcon from 'material-ui/svg-icons/action/done';
import { List, ListItem } from 'material-ui/List';
import Checkbox from 'material-ui/Checkbox';
@@ -23,57 +24,67 @@ import { Modal, Button } from '../../../ui';
import styles from './AddDapps.css';
+@observer
export default class AddDapps extends Component {
static propTypes = {
- available: PropTypes.array.isRequired,
- hidden: PropTypes.array.isRequired,
- open: PropTypes.bool.isRequired,
- onHideApp: PropTypes.func.isRequired,
- onShowApp: PropTypes.func.isRequired,
- onClose: PropTypes.func.isRequired
+ store: PropTypes.object.isRequired
};
render () {
- const { onClose, open, available } = this.props;
+ const { store } = this.props;
+
+ if (!store.modalOpen) {
+ return null;
+ }
return (
} />
+ }
+ />
] }
- visible={ open }
+ visible
scroll>
- { available.map(this.renderApp) }
+ { store.apps.map(this.renderApp) }
);
}
renderApp = (app) => {
- const { hidden, onHideApp, onShowApp } = this.props;
- const isHidden = hidden.includes(app.id);
- const description = (
-
- { app.description }
-
- );
+ const { store } = this.props;
+ const isHidden = store.hidden.includes(app.id);
const onCheck = () => {
if (isHidden) {
- onShowApp(app.id);
+ store.showApp(app.id);
} else {
- onHideApp(app.id);
+ store.hideApp(app.id);
}
};
return (
}
+ leftCheckbox={
+
+ }
primaryText={ app.name }
- secondaryText={ description } />
+ secondaryText={
+
+ { app.description }
+
+ }
+ />
);
}
}
diff --git a/js/src/views/Dapps/Summary/summary.js b/js/src/views/Dapps/Summary/summary.js
index 1140bb7b9..a1c9f48b6 100644
--- a/js/src/views/Dapps/Summary/summary.js
+++ b/js/src/views/Dapps/Summary/summary.js
@@ -40,10 +40,10 @@ export default class Summary extends Component {
}
let image =
;
- if (app.image) {
- image = ;
- } else if (app.iconUrl) {
+ if (app.type === 'local') {
image = ;
+ } else {
+ image = ;
}
return (
@@ -52,9 +52,16 @@ export default class Summary extends Component {
{ app.name } }
- byline={ app.description } />
- { app.author }, v{ app.version }
+ title={
+
+ { app.name }
+
+ }
+ byline={ app.description }
+ />
+
+ { app.author }, v{ app.version }
+
{ this.props.children }
diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js
index 4c1b7baab..708c52cb5 100644
--- a/js/src/views/Dapps/dapps.js
+++ b/js/src/views/Dapps/dapps.js
@@ -15,60 +15,46 @@
// along with Parity. If not, see .
import React, { Component, PropTypes } from 'react';
+import { observer } from 'mobx-react';
-import Contracts from '../../contracts';
-import { hashToImageUrl } from '../../redux/util';
import { Actionbar, Page } from '../../ui';
import FlatButton from 'material-ui/FlatButton';
import EyeIcon from 'material-ui/svg-icons/image/remove-red-eye';
-import { fetchAvailable } from './registry';
-import { readHiddenApps, writeHiddenApps } from './hidden';
+import DappsStore from './dappsStore';
import AddDapps from './AddDapps';
import Summary from './Summary';
import styles from './dapps.css';
+@observer
export default class Dapps extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
- state = {
- available: [],
- hidden: [],
- modalOpen: false
- }
-
- componentDidMount () {
- this.loadAvailableApps();
- }
+ store = new DappsStore(this.context.api);
render () {
- const { available, hidden, modalOpen } = this.state;
- const apps = available.filter((app) => !hidden.includes(app.id));
-
return (
-
+
} onClick={ this.openModal } />
+
}
+ onClick={ this.store.openModal }
+ />
] }
/>
- { apps.map(this.renderApp) }
+ { this.store.visible.map(this.renderApp) }
@@ -76,10 +62,6 @@ export default class Dapps extends Component {
}
renderApp = (app) => {
- if (!app.name) {
- return null;
- }
-
return (
);
}
-
- onHideApp = (id) => {
- const { hidden } = this.state;
- const newHidden = hidden.concat(id);
-
- this.setState({ hidden: newHidden });
- writeHiddenApps(newHidden);
- }
-
- onShowApp = (id) => {
- const { hidden } = this.state;
- const newHidden = hidden.filter((_id) => _id !== id);
-
- this.setState({ hidden: newHidden });
- writeHiddenApps(newHidden);
- }
-
- openModal = () => {
- this.setState({ modalOpen: true });
- };
-
- closeModal = () => {
- this.setState({ modalOpen: false });
- };
-
- loadAvailableApps () {
- const { api } = this.context;
-
- fetchAvailable(api)
- .then((available) => {
- this.setState({
- available,
- hidden: readHiddenApps()
- });
-
- this.loadContent();
- });
- }
-
- loadContent () {
- const { api } = this.context;
- const { available } = this.state;
- const { dappReg } = Contracts.get();
-
- return Promise
- .all(available.map((app) => dappReg.getImage(app.id)))
- .then((images) => {
- const _available = images
- .map(hashToImageUrl)
- .map((image, index) => Object.assign({}, available[index], { image }));
-
- this.setState({ available: _available });
- const _networkApps = _available.filter((app) => app.network);
-
- return Promise
- .all(_networkApps.map((app) => dappReg.getContent(app.id)))
- .then((content) => {
- const networkApps = content.map((_contentHash, index) => {
- const networkApp = _networkApps[index];
- const contentHash = api.util.bytesToHex(_contentHash).substr(2);
- const app = _available.find((_app) => _app.id === networkApp.id);
-
- console.log(`found content for ${app.id} at ${contentHash}`);
- return Object.assign({}, app, { contentHash });
- });
-
- this.setState({
- available: _available.map((app) => {
- return Object.assign({}, networkApps.find((napp) => app.id === napp.id) || app);
- })
- });
- });
- })
- .catch((error) => {
- console.warn('loadImages', error);
- });
- }
}
diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js
new file mode 100644
index 000000000..6064ad91b
--- /dev/null
+++ b/js/src/views/Dapps/dappsStore.js
@@ -0,0 +1,222 @@
+// Copyright 2015, 2016 Ethcore (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see
.
+
+import BigNumber from 'bignumber.js';
+import { action, computed, observable } from 'mobx';
+
+import Contracts from '../../contracts';
+import { hashToImageUrl } from '../../redux/util';
+
+const builtinApps = [
+ {
+ id: '0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f',
+ url: 'basiccoin',
+ name: 'Token Deployment',
+ description: 'Deploy new basic tokens that you are able to send around',
+ author: 'Parity Team
',
+ version: '1.0.0'
+ },
+ {
+ id: '0xd1adaede68d344519025e2ff574650cd99d3830fe6d274c7a7843cdc00e17938',
+ url: 'registry',
+ name: 'Registry',
+ description: 'A global registry of addresses on the network',
+ author: 'Parity Team ',
+ version: '1.0.0'
+ },
+ {
+ id: '0x0a8048117e51e964628d0f2d26342b3cd915248b59bcce2721e1d05f5cfa2208',
+ url: 'tokenreg',
+ name: 'Token Registry',
+ description: 'A registry of transactable tokens on the network',
+ author: 'Parity Team ',
+ version: '1.0.0'
+ },
+ {
+ id: '0xf49089046f53f5d2e5f3513c1c32f5ff57d986e46309a42d2b249070e4e72c46',
+ url: 'signaturereg',
+ name: 'Method Registry',
+ description: 'A registry of method signatures for lookups on transactions',
+ author: 'Parity Team ',
+ version: '1.0.0'
+ },
+ {
+ id: '0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75',
+ url: 'githubhint',
+ name: 'GitHub Hint',
+ description: 'A mapping of GitHub URLs to hashes for use in contracts as references',
+ author: 'Parity Team ',
+ version: '1.0.0'
+ }
+];
+
+// TODO: This is just since we are moving gavcoin to its own repo, for a proper network solution
+// we need to pull the network apps from the dapp registry. (Builtins & local apps unaffected)
+// TODO: Manifest needs to be pulled from the content as well, however since the content may or may
+// not be available locally (and refreshes work for index, and will give a 503), we are putting it
+// in here. This part needs to be cleaned up.
+const networkApps = [
+ {
+ id: '0xd798a48831b4ccdbc71de206a1d6a4aa73546c7b6f59c22a47452af414dc64d6',
+ name: 'GAVcoin',
+ description: 'Manage your GAVcoins, the hottest new property in crypto',
+ author: 'Gavin Wood',
+ version: '1.0.0'
+ }
+];
+
+export default class DappsStore {
+ @observable apps = [];
+ @observable hidden = [];
+ @observable modalOpen = false;
+
+ constructor (api) {
+ this._api = api;
+
+ this._readHiddenApps();
+ this._fetch();
+ }
+
+ @computed get visible () {
+ return this.apps.filter((app) => !this.hidden.includes(app.id));
+ }
+
+ @action openModal = () => {
+ this.modalOpen = true;
+ }
+
+ @action closeModal = () => {
+ this.modalOpen = false;
+ }
+
+ @action hideApp = (id) => {
+ this.hidden = this.hidden.concat(id);
+ this._writeHiddenApps();
+ }
+
+ @action showApp = (id) => {
+ this.hidden = this.hidden.filter((_id) => _id !== id);
+ this._writeHiddenApps();
+ }
+
+ _getHost (api) {
+ return process.env.NODE_ENV === 'production'
+ ? this._api.dappsUrl
+ : '';
+ }
+
+ @action _fetch () {
+ const { dappReg } = Contracts.get();
+
+ return fetch(`${this._getHost()}/api/apps`)
+ .then((response) => {
+ return response.ok
+ ? response.json()
+ : [];
+ })
+ .catch((error) => {
+ console.warn('DappsStore:fetch', error);
+ return [];
+ })
+ .then((_localApps) => {
+ const localApps = _localApps
+ .filter((app) => !['ui'].includes(app.id))
+ .map((app) => {
+ app.type = 'local';
+ return app;
+ });
+
+ return this._api.parity
+ .registryAddress()
+ .then((registryAddress) => {
+ if (new BigNumber(registryAddress).eq(0)) {
+ return [];
+ }
+
+ const _builtinApps = builtinApps
+ .map((app) => {
+ app.type = 'builtin';
+ return app;
+ });
+
+ return networkApps
+ .map((app) => {
+ app.type = 'network';
+ return app;
+ })
+ .concat(_builtinApps);
+ })
+ .then((registryApps) => {
+ return registryApps
+ .concat(localApps)
+ .sort((a, b) => (a.name || '').localeCompare(b.name || ''));
+ });
+ })
+ .then((apps) => {
+ return Promise
+ .all([
+ Promise.all(apps.map((app) => dappReg.getImage(app.id))),
+ Promise.all(apps.map((app) => dappReg.getContent(app.id)))
+ ])
+ .then(([images, content]) => {
+ this.apps = apps.map((app, index) => {
+ return Object.assign(app, {
+ image: hashToImageUrl(images[index]),
+ contentHash: this._api.util.bytesToHex(content[index]).substr(2)
+ });
+ });
+ });
+ })
+ .catch((error) => {
+ console.warn('DappsStore:fetch', error);
+ });
+ }
+
+ _manifest (app, contentHash) {
+ fetch(`${this.getHost()}/${contentHash}/manifest.json`)
+ .then((response) => {
+ return response.ok
+ ? response.json()
+ : {};
+ })
+ .then((manifest) => {
+ Object.keys.forEach((key) => {
+ app[key] = manifest[key];
+ });
+
+ return app;
+ })
+ .catch((error) => {
+ console.warn('DappsStore:manifest', error);
+ });
+ }
+
+ @action _readHiddenApps () {
+ const stored = localStorage.getItem('hiddenApps');
+
+ if (stored) {
+ try {
+ this.hidden = JSON.parse(stored);
+ } catch (error) {
+ console.warn('DappsStore:readHiddenApps', error);
+ }
+ }
+ }
+
+ _writeHiddenApps () {
+ localStorage.setItem('hiddenApps', JSON.stringify(this.hidden));
+ }
+}
diff --git a/js/src/views/Dapps/hidden.js b/js/src/views/Dapps/hidden.js
deleted file mode 100644
index fa5458bac..000000000
--- a/js/src/views/Dapps/hidden.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2015, 2016 Ethcore (UK) Ltd.
-// This file is part of Parity.
-
-// Parity is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Parity is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Parity. If not, see .
-
-const defaultHidden = [];
-
-export function readHiddenApps () {
- const stored = localStorage.getItem('hiddenApps');
-
- if (stored) {
- try {
- return JSON.parse(stored);
- } catch (error) {
- console.warn('readHiddenApps', error);
- }
- }
-
- return defaultHidden;
-}
-
-export function writeHiddenApps (hidden) {
- localStorage.setItem('hiddenApps', JSON.stringify(hidden));
-}
diff --git a/js/src/views/Dapps/registry.js b/js/src/views/Dapps/registry.js
deleted file mode 100644
index 5502bd3ff..000000000
--- a/js/src/views/Dapps/registry.js
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2015, 2016 Ethcore (UK) Ltd.
-// This file is part of Parity.
-
-// Parity is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Parity is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Parity. If not, see .
-
-import BigNumber from 'bignumber.js';
-
-const builtinApps = [
- {
- id: '0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f',
- url: 'basiccoin',
- name: 'Token Deployment',
- description: 'Deploy new basic tokens that you are able to send around',
- author: 'Parity Team ',
- version: '1.0.0'
- },
- {
- id: '0xd1adaede68d344519025e2ff574650cd99d3830fe6d274c7a7843cdc00e17938',
- url: 'registry',
- name: 'Registry',
- description: 'A global registry of addresses on the network',
- author: 'Parity Team ',
- version: '1.0.0'
- },
- {
- id: '0x0a8048117e51e964628d0f2d26342b3cd915248b59bcce2721e1d05f5cfa2208',
- url: 'tokenreg',
- name: 'Token Registry',
- description: 'A registry of transactable tokens on the network',
- author: 'Parity Team ',
- version: '1.0.0'
- },
- {
- id: '0xf49089046f53f5d2e5f3513c1c32f5ff57d986e46309a42d2b249070e4e72c46',
- url: 'signaturereg',
- name: 'Method Registry',
- description: 'A registry of method signatures for lookups on transactions',
- author: 'Parity Team ',
- version: '1.0.0'
- },
- {
- id: '0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75',
- url: 'githubhint',
- name: 'GitHub Hint',
- description: 'A mapping of GitHub URLs to hashes for use in contracts as references',
- author: 'Parity Team ',
- version: '1.0.0',
- secure: true
- }
-];
-
-// TODO: This is just since we are moving gavcoin to its own repo, for a proper network solution
-// we need to pull the network apps from the dapp registry. (Builtins & local apps unaffected)
-// TODO: Manifest needs to be pulled from the content as well, however since the content may or may
-// not be available locally (and refreshes work for index, and will give a 503), we are putting it
-// in here. This part needs to be cleaned up.
-const networkApps = [
- {
- id: '0xd798a48831b4ccdbc71de206a1d6a4aa73546c7b6f59c22a47452af414dc64d6',
- name: 'GAVcoin',
- description: 'Manage your GAVcoins, the hottest new property in crypto',
- author: 'Gavin Wood',
- version: '1.0.0'
- }
-];
-
-function getHost (api) {
- return process.env.NODE_ENV === 'production'
- ? api.dappsUrl
- : '';
-}
-
-export function fetchAvailable (api) {
- return fetch(`${getHost(api)}/api/apps`)
- .then((response) => {
- return response.ok
- ? response.json()
- : [];
- })
- .catch((error) => {
- console.warn('fetchAvailable', error);
- return [];
- })
- .then((_localApps) => {
- const localApps = _localApps
- .filter((app) => !['ui'].includes(app.id))
- .map((app) => {
- app.type = 'local';
- return app;
- });
-
- return api.parity
- .registryAddress()
- .then((registryAddress) => {
- if (new BigNumber(registryAddress).eq(0)) {
- return [];
- }
-
- const _builtinApps = builtinApps
- .map((app) => {
- app.type = 'builtin';
- return app;
- });
-
- return networkApps
- .map((app) => {
- app.type = 'network';
- return app;
- })
- .concat(_builtinApps);
- })
- .then((registryApps) => {
- return registryApps
- .concat(localApps)
- .sort((a, b) => (a.name || '').localeCompare(b.name || ''));
- });
- })
- .catch((error) => {
- console.warn('fetchAvailable', error);
- });
-}
-
-export function fetchManifest (api, app, contentHash) {
- return fetch(`${getHost(api)}/${contentHash}/manifest.json`)
- .then((response) => {
- return response.ok
- ? response.json()
- : {};
- })
- .then((manifest) => {
- Object.keys.forEach((key) => {
- app[key] = manifest[key];
- });
-
- return app;
- })
- .catch((error) => {
- console.warn('fetchManifest', error);
- });
-}