Merge branch 'master' into jg-signer-api-queries-2

This commit is contained in:
Jaco Greeff 2016-11-22 17:31:27 +01:00
commit 59f4252011
57 changed files with 3106 additions and 135 deletions

2
Cargo.lock generated
View File

@ -1263,7 +1263,7 @@ dependencies = [
[[package]]
name = "parity-ui-precompiled"
version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#f46188126257e03c775e76a3cea82b5f70549400"
source = "git+https://github.com/ethcore/js-precompiled.git#f8dba85e17d32860d4070fb444fa5cf13bcc6b3c"
dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@ -1,6 +1,6 @@
{
"name": "parity.js",
"version": "0.2.61",
"version": "0.2.63",
"main": "release/index.js",
"jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>",
@ -102,6 +102,7 @@
"postcss-nested": "^1.0.0",
"postcss-simple-vars": "^3.0.0",
"raw-loader": "^0.5.1",
"react-addons-perf": "~15.3.2",
"react-addons-test-utils": "~15.3.2",
"react-copy-to-clipboard": "^4.2.3",
"react-dom": "~15.3.2",

17
js/src/dapps/dappreg.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
<title>Dapp Registry</title>
</head>
<body>
<div id="container"></div>
<script src="vendor.js"></script>
<script src="commons.js"></script>
<script src="/parity-utils/parity.js"></script>
<script src="dappreg.js"></script>
</body>
</html>

35
js/src/dapps/dappreg.js Normal file
View File

@ -0,0 +1,35 @@
// 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 <http://www.gnu.org/licenses/>.
import React from 'react';
import ReactDOM from 'react-dom';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { useStrict } from 'mobx';
injectTapEventPlugin();
useStrict(true);
import Application from './dappreg/Application';
import '../../assets/fonts/Roboto/font.css';
import '../../assets/fonts/RobotoMono/font.css';
import './style.css';
import './dappreg.html';
ReactDOM.render(
<Application />,
document.querySelector('#container')
);

View File

@ -0,0 +1,58 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.body {
color: #333;
background: #eee;
padding: 4.5em 0;
text-align: center;
}
.apps {
background: #fff;
border-radius: 0.5em;
margin: 0 auto;
max-width: 980px;
padding: 1.5em;
text-align: left;
}
.footer {
font-size: 0.75em;
margin: 1em;
padding: 1.5em;
text-align: center;
}
.header {
background: #44e;
border-radius: 0 0 0.25em 0.25em;
color: #fff;
left: 0;
padding: 1em;
position: fixed;
right: 0;
top: 0;
z-index: 25;
}
.loading {
text-align: center;
padding-top: 5em;
font-size: 2em;
color: #999;
}

View File

@ -0,0 +1,64 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import DappsStore from '../dappsStore';
import ButtonBar from '../ButtonBar';
import Dapp from '../Dapp';
import ModalDelete from '../ModalDelete';
import ModalRegister from '../ModalRegister';
import ModalUpdate from '../ModalUpdate';
import SelectDapp from '../SelectDapp';
import Warning from '../Warning';
import styles from './application.css';
@observer
export default class Application extends Component {
dappsStore = DappsStore.instance();
render () {
if (this.dappsStore.isLoading) {
return (
<div className={ styles.loading }>
Loading application
</div>
);
}
return (
<div className={ styles.body }>
<div className={ styles.header }>
DAPP REGISTRY, a global view of distributed applications available on the network. Putting the puzzle together.
</div>
<div className={ styles.apps }>
<SelectDapp />
<ButtonBar />
<Dapp />
</div>
<div className={ styles.footer }>
{ this.dappsStore.count } applications registered, { this.dappsStore.ownedCount } owned by user
</div>
<Warning />
<ModalDelete />
<ModalRegister />
<ModalUpdate />
</div>
);
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './application';

View File

@ -0,0 +1,38 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.button {
background: #44e;
border: none;
border-radius: 0.25em;
color: #fff;
cursor: pointer;
font-size: 1em;
margin: 1em 0.375em;
opacity: 0.85;
padding: 0.75em 2em;
&[disabled] {
opacity: 0.5;
cursor: default;
background: #aaa;
}
&[data-warning="true"] {
background: #e44;
}
}

View File

@ -0,0 +1,52 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import styles from './button.css';
export default class Button extends Component {
static propTypes = {
className: PropTypes.string,
disabled: PropTypes.bool,
label: PropTypes.string.isRequired,
warning: PropTypes.bool,
onClick: PropTypes.func.isRequired
}
render () {
const { className, disabled, label, warning } = this.props;
const classes = `${styles.button} ${className}`;
return (
<button
className={ classes }
data-warning={ warning }
disabled={ disabled }
onClick={ this.onClick }>
{ label }
</button>
);
}
onClick = (event) => {
if (this.props.disabled) {
return;
}
this.props.onClick(event);
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './button';

View File

@ -0,0 +1,21 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.buttonbar {
text-align: center;
margin: 1em 0 0 0;
}

View File

@ -0,0 +1,101 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import DappsStore from '../dappsStore';
import ModalStore from '../modalStore';
import Button from '../Button';
import styles from './buttonBar.css';
@observer
export default class ButtonBar extends Component {
dappsStore = DappsStore.instance();
modalStore = ModalStore.instance();
render () {
let buttons = [];
if (this.dappsStore.isEditing || this.dappsStore.isNew) {
buttons = [
<Button
key='cancel'
label='Cancel'
warning
onClick={ this.onCancelClick } />,
<Button
key='save'
label={ this.dappsStore.isNew ? 'Register' : 'Update' }
disabled={ !this.dappsStore.canSave }
onClick={ this.onSaveClick } />
];
} else {
buttons = [
<Button
key='delete'
label='Delete'
warning
disabled={ !this.dappsStore.currentApp.isOwner && !this.dappsStore.isContractOwner }
onClick={ this.onDeleteClick } />,
<Button
key='edit'
label='Edit'
disabled={ !this.dappsStore.currentApp.isOwner }
onClick={ this.onEditClick } />,
<Button
key='new'
label='New'
onClick={ this.onNewClick } />
];
}
return (
<div className={ styles.buttonbar }>
{ buttons }
</div>
);
}
onCancelClick = () => {
if (this.dappsStore.isEditing) {
this.dappsStore.setEditing(false);
} else {
this.dappsStore.setNew(false);
}
}
onDeleteClick = () => {
this.modalStore.showDelete();
}
onEditClick = () => {
this.dappsStore.setEditing(true);
}
onNewClick = () => {
this.dappsStore.setNew(true);
}
onSaveClick = () => {
if (this.dappsStore.isEditing) {
this.modalStore.showUpdate();
} else {
this.modalStore.showRegister();
}
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './buttonBar';

View File

@ -0,0 +1,19 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.app {
}

View File

@ -0,0 +1,162 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { api } from '../parity';
import DappsStore from '../dappsStore';
import Input from '../Input';
import SelectAccount from '../SelectAccount';
import styles from './dapp.css';
@observer
export default class Dapp extends Component {
dappsStore = DappsStore.instance();
render () {
const app = this.dappsStore.isNew || this.dappsStore.isEditing
? this.dappsStore.wipApp
: this.dappsStore.currentApp;
return (
<div className={ styles.app }>
{ this.dappsStore.isNew ? this.renderOwnerSelect(app) : this.renderOwnerStatic(app) }
{ this.renderInputs(app) }
</div>
);
}
renderInputs (app) {
if (this.dappsStore.isNew) {
return null;
}
return [
this.renderHashInput(app, 'image', 'Image hash, as generated by Githubhint', true),
this.renderHashInput(app, 'manifest', 'Manifest hash, as generated by Githubhint'),
this.renderHashInput(app, 'content', 'Content hash, as generated by Githubhint')
];
}
renderOwnerSelect (app) {
const overlayImage = (
<img
className={ styles.overlayImage }
src={ api.util.createIdentityImg(this.dappsStore.currentAccount.address, 4) } />
);
return (
<Input
hint={ this.dappsStore.currentAccount.address }
label='Owner, select the application owner and editor'
overlay={ overlayImage }>
<SelectAccount />
</Input>
);
}
renderOwnerStatic (app) {
const overlayImage = (
<img
className={ styles.overlayImage }
src={ api.util.createIdentityImg(app.owner, 4) } />
);
return (
<Input
hint={ app.owner }
label='Owner, the application owner and editor'
overlay={ overlayImage }>
<input value={ app.ownerName } readOnly />
</Input>
);
}
renderHashInput (app, type, label, withImage = false) {
const onChange = (event) => this.onChangeHash(event, type);
const hash = app[`${type}Hash`];
let overlayImage = null;
if (withImage && hash) {
overlayImage = (
<img
className={ styles.overlayImage }
src={ `/api/content/${hash.substr(2)}` } />
);
}
return (
<Input
hint={ app[`${type}Error`] || app[`${type}Url`] || '...' }
label={ label }
key={ `${type}Edit` }
overlay={ overlayImage }>
<input
value={ app[`${type}Hash`] || '' }
data-dirty={ app[`${type}Changed`] }
data-error={ !!app[`${type}Error`] }
readOnly={ !this.dappsStore.isEditing && !this.dappsStore.isNew }
onChange={ onChange } />
</Input>
);
}
onChangeHash (event, type) {
if (!this.dappsStore.isNew && !this.dappsStore.isEditing) {
return;
}
const hash = event.target.value;
let changed = false;
let url = null;
if (this.dappsStore.isNew) {
if (hash && hash.length) {
changed = true;
}
} else {
if (this.dappsStore.currentApp[`${type}Hash`] !== hash) {
changed = true;
} else {
url = this.dappsStore.currentApp[`${type}Url`];
}
}
this.dappsStore.editWip({
[`${type}Changed`]: changed,
[`${type}Error`]: null,
[`${type}Hash`]: hash,
[`${type}Url`]: changed ? 'Resolving url from hash' : url
});
if (changed) {
if (hash.length) {
this.dappsStore
.lookupHash(hash)
.then((url) => {
this.dappsStore.editWip({
[`${type}Error`]: url ? null : 'Unable to resolve url',
[`${type}Url`]: url
});
});
} else {
this.dappsStore.editWip({ [`${type}Url`]: null });
}
}
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './dapp';

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './input';

View File

@ -0,0 +1,92 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.input {
position: relative;
input, select {
background: rgba(255, 255, 255, 0.85);
border: 4px solid rgba(223, 223, 223, 0.85);
border-radius: 0.25em;
box-sizing: border-box;
color: #333;
font-size: 1em;
margin: 0.25em 0 0.25em 0;
padding: 0.5em 0.5em 1.5em 0.5em;
width: 100%;
}
input {
padding-bottom: 1.5em;
&[data-dirty="true"] {
background: rgba(255, 255, 203, 0.85);
border-color: rgba(203, 203, 151, 0.85);
}
&[data-error="true"] {
background: rgba(255, 223, 223, 0.85) !important;
border-color: rgba(223, 191, 191, 0.85) !important;
}
&[readonly] {
background: rgba(239, 239, 239, 0.85);
border-color: rgba(223, 223, 223, 0.85);
}
}
label {
color: #888;
display: block;
font-size: 0.75em;
margin-top: 1.5em;
}
select {
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
height: 58px;
&[disabled] {
background: rgba(239, 239, 239, 0.85);
border-color: rgba(223, 223, 223, 0.85);
}
}
.hint {
color: #888;
display: block;
font-size: 0.75em;
position: absolute;
right: 52px;
text-align: right;
top: 52px;
}
.overlay {
right: 10px;
position: absolute;
top: 30px;
img {
border-radius: 50%;
height: 32px;
width: 32px;
}
}
}

View File

@ -0,0 +1,47 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import styles from './input.css';
export default class Input extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
hint: PropTypes.string,
label: PropTypes.string.isRequired,
overlay: PropTypes.node
}
render () {
const { children, hint, label, overlay } = this.props;
return (
<div className={ styles.input }>
<label>
{ label }
</label>
{ children }
<div className={ styles.hint }>
{ hint }
</div>
<div className={ styles.overlay }>
{ overlay }
</div>
</div>
);
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './modal';

View File

@ -0,0 +1,116 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.modal {
.body {
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
text-align: center;
z-index: 50;
.dialog {
background: #fff;
border-radius: 0 0 0.25em 0.25em;
margin: 0 auto;
max-width: 840px;
text-align: left;
.content {
line-height: 1.5em;
padding: 2em;
text-align: center;
.section {
margin: 0;
padding: 0;
&.error {
color: #f44;
}
}
.section+.section {
margin-top: 1em;
}
}
.footer {
padding: 0.5em 1.625em;
text-align: right;
}
.header {
background: #44e;
color: #fff;
opacity: 0.85;
padding: 1em;
&.error {
background: #e44;
}
}
}
}
.overlay {
background: rgba(204, 204, 204, 0.7);
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 49;
}
}
.account {
div {
display: inline-block;
vertical-align: top;
}
img {
border-radius: 50%;
margin-right: 0.5em;
}
}
.hint {
display: block !important;
color: #888;
font-size: 0.75em;
margin-top: -0.5em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.center {
text-align: center;
}
.heading {
color: #888;
font-size: 0.75em;
}
.light {
color: #888;
}

View File

@ -0,0 +1,66 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import styles from './modal.css';
export default class Modal extends Component {
static propTypes = {
buttons: PropTypes.node,
children: PropTypes.node,
error: PropTypes.object,
header: PropTypes.string
}
render () {
const { children, buttons, error, header } = this.props;
return (
<div className={ styles.modal }>
<div className={ styles.overlay } />
<div className={ styles.body }>
<div className={ styles.dialog }>
<div className={ `${styles.header} ${error ? styles.error : ''}` }>
{ header }
</div>
<div className={ styles.content }>
{ error ? this.renderError() : children }
</div>
<div className={ styles.footer }>
{ buttons }
</div>
</div>
</div>
</div>
);
}
renderError () {
const { error } = this.props;
return (
<div>
<div className={ styles.section }>
Your operation failed to complete sucessfully. The following error was returned:
</div>
<div className={ `${styles.section} ${styles.error}` }>
{ error.toString() }
</div>
</div>
);
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './modalDelete';

View File

@ -0,0 +1,159 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { api } from '../parity';
import DappsStore from '../dappsStore';
import ModalStore from '../modalStore';
import Button from '../Button';
import Modal from '../Modal';
import styles from '../Modal/modal.css';
const HEADERS = [
'Error During Deletion',
'Confirm Application Deletion',
'Waiting for Signer Confirmation',
'Waiting for Transaction Receipt',
'Deletion Completed'
];
const STEP_ERROR = 0;
const STEP_CONFIRM = 1;
const STEP_SIGNER = 2;
const STEP_TXRECEIPT = 3;
const STEP_DONE = 4;
@observer
export default class ModalDelete extends Component {
dappsStore = DappsStore.instance();
modalStore = ModalStore.instance();
render () {
if (!this.modalStore.showingDelete) {
return null;
}
return (
<Modal
buttons={ this.renderButtons() }
error={ this.modalStore.errorDelete }
header={ HEADERS[this.modalStore.stepDelete] }>
{ this.renderStep() }
</Modal>
);
}
renderButtons () {
switch (this.modalStore.stepDelete) {
case STEP_ERROR:
case STEP_DONE:
return [
<Button
key='close'
label='Close'
onClick={ this.onClickClose } />
];
case STEP_CONFIRM:
return [
<Button
key='cancel'
label='No, Cancel'
onClick={ this.onClickClose } />,
<Button
key='delete'
label='Yes, Delete'
warning
onClick={ this.onClickYes } />
];
default:
return null;
}
}
renderStep () {
switch (this.modalStore.stepDelete) {
case STEP_CONFIRM:
return this.renderStepConfirm();
case STEP_SIGNER:
return this.renderStepWait('Waiting for transaction confirmation in the Parity secure signer');
case STEP_TXRECEIPT:
return this.renderStepWait('Waiting for the transaction receipt from the network');
case STEP_DONE:
return this.renderStepCompleted();
default:
return null;
}
}
renderStepCompleted () {
return (
<div>
<div className={ styles.section }>
Your application has been removed from the registry.
</div>
</div>
);
}
renderStepConfirm () {
return (
<div>
<div className={ styles.section }>
You are about to remove a distributed application from the registry, the details of this application is given below. Removal does not return any fees, however the application will not be available to users anymore.
</div>
<div className={ styles.section }>
<div className={ styles.heading }>
Owner account
</div>
<div className={ styles.account }>
<img src={ api.util.createIdentityImg(this.dappsStore.currentApp.owner, 3) } />
<div>{ this.dappsStore.currentApp.ownerName }</div>
<div className={ styles.address }>{ this.dappsStore.currentApp.owner }</div>
</div>
</div>
<div className={ styles.section }>
<div className={ styles.heading }>
Application identifier
</div>
<div>
{ this.dappsStore.currentApp.id }
</div>
</div>
</div>
);
}
renderStepWait (waitingFor) {
return (
<div>
<div className={ styles.section }>
{ waitingFor }
</div>
</div>
);
}
onClickClose = () => {
this.modalStore.hideDelete();
}
onClickYes = () => {
this.modalStore.doDelete();
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './modalRegister';

View File

@ -0,0 +1,159 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { api } from '../parity';
import DappsStore from '../dappsStore';
import ModalStore from '../modalStore';
import Button from '../Button';
import Modal from '../Modal';
import styles from '../Modal/modal.css';
const HEADERS = [
'Error During Registration',
'Confirm Application Registration',
'Waiting for Signer Confirmation',
'Waiting for Transaction Receipt',
'Registration Completed'
];
const STEP_ERROR = 0;
const STEP_CONFIRM = 1;
const STEP_SIGNER = 2;
const STEP_TXRECEIPT = 3;
const STEP_DONE = 4;
@observer
export default class ModalRegister extends Component {
dappsStore = DappsStore.instance();
modalStore = ModalStore.instance();
render () {
if (!this.modalStore.showingRegister) {
return null;
}
return (
<Modal
buttons={ this.renderButtons() }
error={ this.modalStore.errorRegister }
header={ HEADERS[this.modalStore.stepRegister] }>
{ this.renderStep() }
</Modal>
);
}
renderButtons () {
switch (this.modalStore.stepRegister) {
case STEP_ERROR:
case STEP_DONE:
return [
<Button
key='close'
label='Close'
onClick={ this.onClickClose } />
];
case STEP_CONFIRM:
return [
<Button
key='cancel'
label='No, Cancel'
onClick={ this.onClickClose } />,
<Button
key='register'
label='Yes, Register'
warning
onClick={ this.onClickConfirmYes } />
];
default:
return null;
}
}
renderStep () {
switch (this.modalStore.stepRegister) {
case STEP_CONFIRM:
return this.renderStepConfirm();
case STEP_SIGNER:
return this.renderStepWait('Waiting for transaction confirmation in the Parity secure signer');
case STEP_TXRECEIPT:
return this.renderStepWait('Waiting for the transaction receipt from the network');
case STEP_DONE:
return this.renderStepCompleted();
default:
return null;
}
}
renderStepCompleted () {
return (
<div>
<div className={ styles.section }>
Your application has been registered in the registry.
</div>
</div>
);
}
renderStepConfirm () {
return (
<div>
<div className={ styles.section }>
You are about to register a new distributed application on the network, the details of this application is given below. This will require a non-refundable fee of { api.util.fromWei(this.dappsStore.fee).toFormat(3) }<small>ETH</small>.
</div>
<div className={ styles.section }>
<div className={ styles.heading }>
Selected owner account
</div>
<div className={ styles.account }>
<img src={ api.util.createIdentityImg(this.dappsStore.currentAccount.address, 3) } />
<div>{ this.dappsStore.currentAccount.name }</div>
<div className={ styles.hint }>{ this.dappsStore.currentAccount.address }</div>
</div>
</div>
<div className={ styles.section }>
<div className={ styles.heading }>
Unique assigned application identifier
</div>
<div>
{ this.dappsStore.wipApp.id }
</div>
</div>
</div>
);
}
renderStepWait (waitingFor) {
return (
<div>
<div className={ styles.section }>
{ waitingFor }
</div>
</div>
);
}
onClickClose = () => {
this.modalStore.hideRegister();
}
onClickConfirmYes = () => {
this.modalStore.doRegister();
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './modalUpdate';

View File

@ -0,0 +1,169 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import DappsStore from '../dappsStore';
import ModalStore from '../modalStore';
import Button from '../Button';
import Modal from '../Modal';
import styles from '../Modal/modal.css';
const HEADERS = [
'Error During Update',
'Confirm Application Update',
'Waiting for Signer Confirmation',
'Waiting for Transaction Receipt',
'Update Completed'
];
const STEP_ERROR = 0;
const STEP_CONFIRM = 1;
const STEP_SIGNER = 2;
const STEP_TXRECEIPT = 3;
const STEP_DONE = 4;
@observer
export default class ModalUpdate extends Component {
dappsStore = DappsStore.instance();
modalStore = ModalStore.instance();
render () {
if (!this.modalStore.showingUpdate) {
return null;
}
return (
<Modal
buttons={ this.renderButtons() }
error={ this.modalStore.errorUpdate }
header={ HEADERS[this.modalStore.stepUpdate] }>
{ this.renderStep() }
</Modal>
);
}
renderButtons () {
switch (this.modalStore.stepUpdate) {
case STEP_ERROR:
case STEP_DONE:
return [
<Button
key='close'
label='Close'
onClick={ this.onClickClose } />
];
case STEP_CONFIRM:
return [
<Button
key='cancel'
label='No, Cancel'
onClick={ this.onClickClose } />,
<Button
key='delete'
label='Yes, Update'
warning
onClick={ this.onClickYes } />
];
default:
return null;
}
}
renderStep () {
switch (this.modalStore.stepUpdate) {
case STEP_CONFIRM:
return this.renderStepConfirm();
case STEP_SIGNER:
return this.renderStepWait('Waiting for transaction confirmation in the Parity secure signer');
case STEP_TXRECEIPT:
return this.renderStepWait('Waiting for the transaction receipt from the network');
case STEP_DONE:
return this.renderStepCompleted();
default:
return null;
}
}
renderStepCompleted () {
return (
<div>
<div className={ styles.section }>
Your application metadata has been updated in the registry.
</div>
</div>
);
}
renderStepConfirm () {
return (
<div>
<div className={ styles.section }>
You are about to update the application details in the registry, the details of these updates are given below. Please note that each update will generate a seperate transaction.
</div>
<div className={ styles.section }>
<div className={ styles.heading }>
Application identifier
</div>
<div>
{ this.dappsStore.wipApp.id }
</div>
</div>
{ this.renderChanges() }
</div>
);
}
renderChanges () {
return ['content', 'image', 'manifest']
.filter((type) => this.dappsStore.wipApp[`${type}Changed`])
.map((type) => {
return (
<div className={ styles.section } key={ `${type}Update` }>
<div className={ styles.heading }>
Updates to { type } hash
</div>
<div>
<div>{ this.dappsStore.wipApp[`${type}Hash`] || '(removed)' }</div>
<div className={ styles.hint }>
{ this.dappsStore.wipApp[`${type}Url`] || 'current url to be removed from registry' }
</div>
</div>
</div>
);
});
}
renderStepWait (waitingFor) {
return (
<div>
<div className={ styles.section }>
{ waitingFor }
</div>
</div>
);
}
onClickClose = () => {
this.modalStore.hideUpdate();
}
onClickYes = () => {
this.modalStore.doUpdate();
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './selectAccount';

View File

@ -0,0 +1,49 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import DappsStore from '../dappsStore';
@observer
export default class SelectAccount extends Component {
dappsStore = DappsStore.instance();
render () {
return (
<select
value={ this.dappsStore.currentAccount.address }
onChange={ this.onSelect }>
{ this.renderOptions() }
</select>
);
}
renderOptions () {
return this.dappsStore.accounts.map((account) => {
return (
<option value={ account.address } key={ account.address }>
{ account.name }
</option>
);
});
}
onSelect = (event) => {
this.dappsStore.setCurrentAccount(event.target.value);
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './selectDapp';

View File

@ -0,0 +1,76 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import DappsStore from '../dappsStore';
import Input from '../Input';
@observer
export default class SelectDapp extends Component {
dappsStore = DappsStore.instance();
render () {
if (this.dappsStore.isNew) {
return (
<Input
hint='...'
label='Application Id, the unique assigned identifier'>
<input value={ this.dappsStore.wipApp.id } readOnly />
</Input>
);
}
let overlayImg = null;
if (this.dappsStore.currentApp.imageHash) {
overlayImg = (
<img src={ `/api/content/${this.dappsStore.currentApp.imageHash.substr(2)}` } />
);
}
return (
<Input
hint={ this.dappsStore.currentApp.id }
label='Application, the actual application details to show below'
overlay={ overlayImg }>
<select
disabled={ this.dappsStore.isEditing }
value={ this.dappsStore.currentApp.id }
onChange={ this.onSelect }>
{ this.renderOptions() }
</select>
</Input>
);
}
renderOptions () {
return this.dappsStore.apps.map((app) => {
return (
<option
value={ app.id }
key={ app.id }>
{ app.name }
</option>
);
});
}
onSelect = (event) => {
this.dappsStore.setCurrentApp(event.target.value);
}
}

View File

@ -0,0 +1,17 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './warning';

View File

@ -0,0 +1,36 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.warning {
background: #f44;
border-top-right-radius: 0.25em;
bottom: 0;
color: #fff;
cursor: pointer;
font-size: 0.75em;
left: 0;
line-height: 1.5em;
opacity: 1;
padding: 1.5em;
position: fixed;
max-width: 540px;
z-index: 100;
div+div {
margin-top: 1.5em;
}
}

View File

@ -0,0 +1,51 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { api } from '../parity';
import DappsStore from '../dappsStore';
import ModalStore from '../modalStore';
import styles from './warning.css';
@observer
export default class Warning extends Component {
dappsStore = DappsStore.instance();
modalStore = ModalStore.instance();
render () {
if (!this.modalStore.showingWarning) {
return null;
}
return (
<div className={ styles.warning } onClick={ this.onClose }>
<div>
WARNING: Registering a dapp is for developers only. Please ensure you understand the steps needed to develop and deploy applications, should you wish to use this dapp for anything apart from queries.
</div>
<div>
A non-refundable fee of { api.util.fromWei(this.dappsStore.fee).toFormat(3) }<small>ETH</small> is required for any registration.
</div>
</div>
);
}
onClose = () => {
this.modalStore.hideWarning();
}
}

View File

@ -0,0 +1,482 @@
// 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 <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { action, computed, observable, transaction } from 'mobx';
import * as abis from '../../contracts/abi';
import builtins from '../../views/Dapps/builtin.json';
import { api } from './parity';
let instance = null;
export default class DappsStore {
@observable accounts = [];
@observable addresses = [];
@observable apps = [];
@observable contractOwner = null;
@observable currentAccount = null;
@observable currentApp = null;
@observable count = 0;
@observable fee = new BigNumber(0);
@observable isContractOwner = false;
@observable isEditing = false;
@observable isLoading = true;
@observable isNew = false;
@observable wipApp = null;
_startTime = Date.now();
constructor () {
this._loadDapps();
}
static instance () {
if (!instance) {
instance = new DappsStore();
}
return instance;
}
@computed get canSave () {
const app = this.wipApp;
const hasError = app.contentError || app.imageError || app.manifestError;
const isDirty = this.isNew || app.contentChanged || app.imageChanged || app.manifestChanged;
const isEditMode = this.isEditing || this.isNew;
return isEditMode && isDirty && !hasError;
}
@computed get isCurrentEditable () {
return !!this.accounts.find((account) => account.address === this.currentApp.owner);
}
@computed get ownedCount () {
return (this.apps.filter((app) => app.isOwner) || []).length;
}
@action copyToWip = () => {
let wipApp;
if (this.isNew) {
wipApp = {
id: api.util.sha3(`${this._startTime}_${Date.now()}`),
contentHash: null,
contentUrl: null,
imageHash: null,
imageUrl: null,
manifestHash: null,
manifestUrl: null
};
} else {
const app = this.currentApp;
wipApp = {
id: app.id,
contentHash: app.contentHash,
contentUrl: app.contentUrl,
imageHash: app.imageHash,
imageUrl: app.imageUrl,
manifestHash: app.manifestHash,
manifestUrl: app.manifestUrl,
owner: app.owner,
ownerName: app.ownerName
};
}
this.wipApp = Object.assign(wipApp, {
contentChanged: false,
contentError: null,
imageChanged: false,
imageError: null,
manifestChanged: false,
manifestError: null
});
return this.wipApp;
}
@action editWip = (details) => {
if (this.isNew || this.isEditing) {
transaction(() => {
Object
.keys(details)
.forEach((key) => {
this.wipApp[key] = details[key];
});
});
}
return this.wipApp;
}
@action sortApps = (apps = this.apps) => {
transaction(() => {
const ownApps = apps
.filter((app) => app.isOwner)
.sort((a, b) => a.name.localeCompare(b.name));
const otherApps = apps
.filter((app) => !app.isOwner)
.sort((a, b) => a.name.localeCompare(b.name));
this.apps = ownApps.concat(otherApps);
this.currentApp = this.apps[0];
});
}
@action setApps = (apps) => {
this.sortApps(apps.filter((app) => {
const bnid = new BigNumber(app.id);
return bnid.gt(0);
}));
return this.apps;
}
@action _addApp = (app) => {
transaction(() => {
this.setApps(this.apps.concat([app]));
this.setCurrentApp(app.id);
});
}
@action addApp = (appId, account) => {
this
._loadDapp({
id: appId,
isOwner: true,
name: `- ${appId}`,
owner: account.address,
ownerName: account.name
})
.then(this._addApp);
}
@action refreshApp = (appId) => {
this._loadDapp(this.apps.find((app) => app.id === appId));
}
@action removeApp = (appId) => {
this.setApps(this.apps.filter((app) => app.id !== appId));
}
@action setAppInfo = (app, info) => {
transaction(() => {
Object.keys(info).forEach((key) => {
app[key] = info[key];
});
});
return app;
}
@action setAccounts = (accountsInfo) => {
transaction(() => {
this.addresses = Object
.keys(accountsInfo)
.map((address) => {
const account = accountsInfo[address];
account.address = address;
return account;
});
this.accounts = this.addresses.filter((account) => account.uuid);
this.currentAccount = this.accounts[0];
});
return this.accounts;
}
@action setContractOwner = (contractOwner) => {
transaction(() => {
this.contractOwner = contractOwner;
this.isContractOwner = !!this.accounts.find((account) => account.address === contractOwner);
});
return contractOwner;
}
@action setCurrentApp = (id) => {
this.currentApp = this.apps.find((app) => app.id === id);
return this.currentApp;
}
@action setCurrentAccount = (address) => {
this.currentAccount = this.accounts.find((account) => account.address === address);
return this.currentAccount;
}
@action setCount = (count) => {
this.count = count;
return count;
}
@action setEditing = (mode) => {
transaction(() => {
this.isEditing = mode;
this.copyToWip();
});
return mode;
}
@action setFee = (fee) => {
this.fee = fee;
return fee;
}
@action setLoading = (loading) => {
this.isLoading = loading;
return loading;
}
@action setNew = (mode) => {
transaction(() => {
this.isNew = mode;
this.copyToWip();
});
return mode;
}
lookupHash (hash) {
return this._retrieveUrl(hash);
}
_getCount () {
return this._instanceReg
.count.call()
.then((count) => {
this.setCount(count.toNumber());
})
.catch((error) => {
console.error('Store:getCount', error);
});
}
_getFee () {
return this._instanceReg
.fee.call()
.then(this.setFee)
.catch((error) => {
console.error('Store:getFee', error);
});
}
_getOwner () {
return this._instanceReg
.owner.call()
.then(this.setContractOwner)
.catch((error) => {
console.error('Store:getOwner', error);
});
}
_loadDapps () {
return this._loadRegistry()
.then(() => Promise.all([
this._attachContracts(),
this._loadAccounts()
]))
.then(() => Promise.all([
this._getCount(),
this._getFee(),
this._getOwner()
]))
.then(() => {
const promises = [];
for (let index = 0; index < this.count; index++) {
promises.push(this._instanceReg.at.call({}, [index]));
}
return Promise.all(promises);
})
.then((appsInfo) => {
return Promise.all(
this
.setApps(appsInfo.map(([appId, owner]) => {
const isOwner = !!this.accounts.find((account) => account.address === owner);
const account = this.addresses.find((account) => account.address === owner);
const id = api.util.bytesToHex(appId);
return {
id,
owner,
ownerName: account ? account.name : owner,
isOwner,
name: `- ${id}`
};
}))
.map(this._loadDapp)
);
})
.then(() => {
this.sortApps();
this.setLoading(this.count === 0);
})
.catch((error) => {
console.error('Store:loadDapps', error);
});
}
_loadDapp = (app) => {
return Promise
.all([
this._loadMeta(app.id, 'CONTENT'),
this._loadMeta(app.id, 'IMG'),
this._loadMeta(app.id, 'MANIFEST')
])
.then(([contentHash, imageHash, manifestHash]) => {
return Promise
.all([
this._retrieveUrl(contentHash),
this._retrieveUrl(imageHash),
this._retrieveUrl(manifestHash)
])
.then(([contentUrl, imageUrl, manifestUrl]) => {
return this
._loadManifest(app.id, manifestHash)
.then((manifest) => {
this.setAppInfo(app, {
manifest,
manifestHash,
manifestUrl,
contentHash,
contentUrl,
imageHash,
imageUrl,
name: (manifest && manifest.name) || `- ${app.id}`
});
return app;
});
});
})
.catch((error) => {
console.error('Store:loadDapp', error);
});
}
_loadMeta (appId, key) {
return this._instanceReg
.meta.call({}, [appId, key])
.then((meta) => {
const hash = api.util.bytesToHex(meta);
const bnhash = new BigNumber(hash);
return bnhash.gt(0)
? hash
: null;
})
.catch((error) => {
console.error('Store:loadMeta', error);
return null;
});
}
_loadManifest (appId, manifestHash) {
const builtin = builtins.find((app) => app.id === appId);
if (builtin) {
return Promise.resolve(builtin);
} else if (!manifestHash) {
return Promise.resolve(null);
}
return fetch(`/api/content/${manifestHash.substr(2)}/`, { redirect: 'follow', mode: 'cors' })
.then((response) => {
return response.ok
? response.json()
: null;
})
.catch((error) => {
console.error('Store:loadManifest', error);
return null;
});
}
_retrieveUrl (urlHash) {
if (!urlHash) {
return Promise.resolve(null);
}
return this._instanceGhh
.entries.call({}, [urlHash])
.then(([repo, _commit, owner]) => {
const bnowner = new BigNumber(owner);
if (bnowner.eq(0)) {
return null;
}
const commit = api.util.bytesToHex(_commit);
const bncommit = new BigNumber(commit);
if (bncommit.eq(0)) {
return repo;
} else {
return `https://codeload.github.com/${repo}/zip/${commit.substr(2)}`;
}
})
.catch((error) => {
console.error('Store:retriveUrl', error);
return null;
});
}
_loadAccounts () {
return api.parity
.accounts()
.then(this.setAccounts)
.catch((error) => {
console.error('Store:loadAccounts', error);
});
}
_loadRegistry () {
return api.parity
.registryAddress()
.then((registryAddress) => {
console.log(`the registry was found at ${registryAddress}`);
this._registry = api.newContract(abis.registry, registryAddress).instance;
})
.catch((error) => {
console.error('Store:loadRegistry', error);
});
}
_attachContracts () {
return Promise
.all([
this._registry.getAddress.call({}, [api.util.sha3('dappreg'), 'A']),
this._registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A'])
])
.then(([dappregAddress, ghhAddress]) => {
console.log(`dappreg was found at ${dappregAddress}`);
this._contractReg = api.newContract(abis.dappreg, dappregAddress);
this._instanceReg = this._contractReg.instance;
console.log(`githubhint was found at ${ghhAddress}`);
this._contractGhh = api.newContract(abis.githubhint, ghhAddress);
this._instanceGhh = this._contractGhh.instance;
})
.catch((error) => {
console.error('Store:attachContract', error);
});
}
}

View File

@ -0,0 +1,266 @@
// 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 <http://www.gnu.org/licenses/>.
import { action, observable, transaction } from 'mobx';
import { trackRequest } from './parity';
import DappsStore from './dappsStore';
let instance = null;
export default class ModalStore {
@observable errorDelete = null;
@observable errorRegister = null;
@observable errorUpdate = null;
@observable stepDelete = 0;
@observable stepRegister = 0;
@observable stepUpdate = 0;
@observable showingDelete = false;
@observable showingRegister = false;
@observable showingUpdate = false;
@observable showingWarning = true;
_dappsStore = DappsStore.instance();
static instance () {
if (!instance) {
instance = new ModalStore();
}
return instance;
}
@action setDeleteError (error) {
transaction(() => {
this.setDeleteStep(0);
this.errorDelete = error;
});
}
@action setDeleteStep (step) {
this.stepDelete = step;
}
@action showDelete () {
transaction(() => {
this.setDeleteStep(1);
this.errorDelete = null;
this.showingDelete = true;
});
}
@action hideDelete () {
this.showingDelete = false;
}
@action setRegisterError (error) {
transaction(() => {
this.setRegisterStep(0);
this.errorRegister = error;
});
}
@action setRegisterStep (step) {
this.stepRegister = step;
}
@action showRegister () {
transaction(() => {
this.setRegisterStep(1);
this.errorRegister = null;
this.showingRegister = true;
});
}
@action hideRegister () {
transaction(() => {
this._dappsStore.setEditing(false);
this._dappsStore.setNew(false);
this.showingRegister = false;
});
}
@action setUpdateError (error) {
transaction(() => {
this.setUpdateStep(0);
this.errorUpdate = error;
});
}
@action setUpdateStep (step) {
this.stepUpdate = step;
}
@action showUpdate () {
transaction(() => {
this.setUpdateStep(1);
this.errorUpdate = null;
this.showingUpdate = true;
});
}
@action hideUpdate () {
transaction(() => {
this._dappsStore.setEditing(false);
this._dappsStore.setNew(false);
this.showingUpdate = false;
});
}
@action hideWarning () {
this.showingWarning = false;
}
doDelete () {
this.setDeleteStep(2);
const appId = this._dappsStore.currentApp.id;
const values = [appId];
const options = {
from: this._dappsStore.currentApp.isOwner ? this._dappsStore.currentApp.owner : this._dappsStore.contractOwner
};
console.log('ModalStore:doDelete', `performing deletion for ${appId} from ${options.from}`);
this._dappsStore._instanceReg
.unregister.estimateGas(options, values)
.then((gas) => {
const newGas = gas.mul(1.2);
console.log('ModalStore:doDelete', `gas estimated as ${gas.toFormat(0)}, setting to ${newGas.toFormat(0)}`);
options.gas = newGas.toFixed(0);
const request = this._dappsStore._instanceReg.unregister.postTransaction(options, values);
const statusCallback = (error, status) => {
if (error) {
} else if (status.signerRequestId) {
} else if (status.transactionHash) {
this.setDeleteStep(3);
} else if (status.transactionReceipt) {
this.setDeleteStep(4);
this._dappsStore.removeApp(appId);
}
};
return trackRequest(request, statusCallback);
})
.catch((error) => {
console.error('ModalStore:doDelete', error);
this.setDeleteError(error);
});
}
doRegister () {
this.setRegisterStep(2);
const appId = this._dappsStore.wipApp.id;
const values = [appId];
const options = {
from: this._dappsStore.currentAccount.address,
value: this._dappsStore.fee
};
console.log('ModalStore:doRegister', `performing registration for ${appId} from ${this._dappsStore.currentAccount.address}`);
this._dappsStore._instanceReg
.register.estimateGas(options, values)
.then((gas) => {
const newGas = gas.mul(1.2);
console.log('ModalStore:doRegister', `gas estimated as ${gas.toFormat(0)}, setting to ${newGas.toFormat(0)}`);
options.gas = newGas.toFixed(0);
const request = this._dappsStore._instanceReg.register.postTransaction(options, values);
const statusCallback = (error, status) => {
if (error) {
} else if (status.signerRequestId) {
} else if (status.transactionHash) {
this.setRegisterStep(3);
} else if (status.transactionReceipt) {
this.setRegisterStep(4);
this._dappsStore.addApp(appId, this._dappsStore.currentAccount);
}
};
return trackRequest(request, statusCallback);
})
.catch((error) => {
console.error('ModalStore:doRegister', error);
this.setRegisterError(error);
});
}
doUpdate () {
this.setUpdateStep(2);
const appId = this._dappsStore.wipApp.id;
const options = {
from: this._dappsStore.wipApp.owner
};
const types = {
'content': 'CONTENT',
'image': 'IMG',
'manifest': 'MANIFEST'
};
const values = Object
.keys(types)
.filter((type) => this._dappsStore.wipApp[`${type}Changed`])
.map((type) => {
return [appId, types[type], this._dappsStore.wipApp[`${type}Hash`] || '0x0'];
});
console.log('ModalStore:doUpdate', `performing updates for ${appId} from ${options.from}`);
Promise
.all(values.map((value) => this._dappsStore._instanceReg.setMeta.estimateGas(options, value)))
.then((gas) => {
const newGas = gas.map((gas) => gas.mul(1.2));
gas.forEach((gas, index) => {
console.log('ModalStore:doUpdate', `${values[index][1]} gas estimated as ${gas.toFormat(0)}, setting to ${newGas[index].toFormat(0)}`);
});
const statusCallback = (error, status) => {
if (error) {
} else if (status.signerRequestId) {
} else if (status.transactionHash) {
this.setUpdateStep(3);
} else if (status.transactionReceipt) {
this.setUpdateStep(4);
}
};
return Promise.all(
newGas.map((gas, index) => {
return trackRequest(
this._dappsStore._instanceReg.setMeta.postTransaction(
Object.assign(options, { gas: gas.toFixed(0) }), values[index]
), statusCallback
);
})
);
})
.then(() => {
this._dappsStore.refreshApp(appId);
})
.catch((error) => {
console.error('ModalStore:doUpdate', error);
this.setUpdateError(error);
});
}
}

View File

@ -0,0 +1,53 @@
// 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 <http://www.gnu.org/licenses/>.
const { api } = window.parity;
function trackRequest (requestPromise, statusCallback) {
return requestPromise
.then((signerRequestId) => {
console.log('trackRequest', `posted to signer with requestId ${signerRequestId.toString()}`);
statusCallback(null, { signerRequestId });
return api.pollMethod('parity_checkRequest', signerRequestId);
})
.then((transactionHash) => {
console.log('trackRequest', `received transaction hash ${transactionHash}`);
statusCallback(null, { transactionHash });
return api.pollMethod('eth_getTransactionReceipt', transactionHash, (receipt) => {
if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) {
return false;
}
return true;
});
})
.then((transactionReceipt) => {
console.log('trackRequest', 'received transaction receipt', transactionReceipt);
statusCallback(null, { transactionReceipt });
})
.catch((error) => {
console.error('trackRequest', error);
statusCallback(error);
throw error;
});
}
export {
api,
trackRequest
};

View File

@ -46,6 +46,12 @@ import './index.html';
injectTapEventPlugin();
if (process.env.NODE_ENV === 'development') {
// Expose the React Performance Tools on the`window` object
const Perf = require('react-addons-perf');
window.Perf = Perf;
}
const AUTH_HASH = '#/auth?';
const parityUrl = process.env.PARITY_URL ||
(

View File

@ -36,6 +36,7 @@ export default class DetailsStep extends Component {
onFuncChange: PropTypes.func,
values: PropTypes.array.isRequired,
valuesError: PropTypes.array.isRequired,
warning: PropTypes.string,
onValueChange: PropTypes.func.isRequired
}
@ -44,6 +45,7 @@ export default class DetailsStep extends Component {
return (
<Form>
{ this.renderWarning() }
<AddressSelect
label='from account'
hint='the account to transact with'
@ -178,6 +180,20 @@ export default class DetailsStep extends Component {
});
}
renderWarning () {
const { warning } = this.props;
if (!warning) {
return null;
}
return (
<div className={ styles.warning }>
{ warning }
</div>
);
}
onFuncChange = (event, index, signature) => {
const { contract, onFuncChange } = this.props;

View File

@ -33,3 +33,12 @@
.txhash {
word-break: break-all;
}
.warning {
border-radius: 0.5em;
background: #f80;
color: white;
font-size: 0.75em;
padding: 0.75em;
text-align: center;
}

View File

@ -15,17 +15,21 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
import { MAX_GAS_ESTIMATION } from '../../util/constants';
import { validateAddress, validateUint } from '../../util/validation';
import DetailsStep from './DetailsStep';
import ERRORS from '../Transfer/errors';
import { ERROR_CODES } from '../../api/transport/error';
export default class ExecuteContract extends Component {
class ExecuteContract extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
@ -36,6 +40,7 @@ export default class ExecuteContract extends Component {
fromAddress: PropTypes.string,
accounts: PropTypes.object,
contract: PropTypes.object,
gasLimit: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
onFromAddressChange: PropTypes.func.isRequired
}
@ -46,6 +51,8 @@ export default class ExecuteContract extends Component {
fromAddressError: null,
func: null,
funcError: null,
gas: null,
gasLimitError: null,
values: [],
valuesError: [],
step: 0,
@ -64,6 +71,12 @@ export default class ExecuteContract extends Component {
this.onFuncChange(null, functions[0]);
}
componentWillReceiveProps (newProps) {
if (newProps.fromAddress !== this.props.fromAddress) {
this.estimateGas(newProps.fromAddress);
}
}
render () {
const { sending } = this.state;
@ -119,7 +132,7 @@ export default class ExecuteContract extends Component {
renderStep () {
const { onFromAddressChange } = this.props;
const { step, busyState, txhash, rejected } = this.state;
const { step, busyState, gasLimitError, txhash, rejected } = this.state;
if (rejected) {
return (
@ -135,6 +148,7 @@ export default class ExecuteContract extends Component {
<DetailsStep
{ ...this.props }
{ ...this.state }
warning={ gasLimitError }
onAmountChange={ this.onAmountChange }
onFromAddressChange={ onFromAddressChange }
onFuncChange={ this.onFuncChange }
@ -156,7 +170,7 @@ export default class ExecuteContract extends Component {
}
onAmountChange = (amount) => {
this.setState({ amount });
this.setState({ amount }, this.estimateGas);
}
onFuncChange = (event, func) => {
@ -182,7 +196,7 @@ export default class ExecuteContract extends Component {
this.setState({
func,
values
});
}, this.estimateGas);
}
onValueChange = (event, index, _value) => {
@ -211,14 +225,55 @@ export default class ExecuteContract extends Component {
this.setState({
values: [].concat(values),
valuesError: [].concat(valuesError)
}, () => {
if (!valueError) {
this.estimateGas();
}
});
}
estimateGas = (_fromAddress) => {
const { api } = this.context;
const { fromAddress, gasLimit } = this.props;
const { amount, func, values } = this.state;
const options = {
gas: MAX_GAS_ESTIMATION,
from: _fromAddress || fromAddress,
value: api.util.toWei(amount || 0)
};
if (!func) {
return;
}
func
.estimateGas(options, values)
.then((gasEst) => {
const gas = gasEst.mul(1.2);
let gasLimitError = null;
if (gas.gte(MAX_GAS_ESTIMATION)) {
gasLimitError = ERRORS.gasException;
} else if (gas.gt(gasLimit)) {
gasLimitError = ERRORS.gasBlockLimit;
}
this.setState({
gas,
gasLimitError
});
})
.catch((error) => {
console.warn('estimateGas', error);
});
}
postTransaction = () => {
const { api, store } = this.context;
const { fromAddress } = this.props;
const { amount, func, values } = this.state;
const options = {
gas: MAX_GAS_ESTIMATION,
from: fromAddress,
value: api.util.toWei(amount || 0)
};
@ -237,13 +292,13 @@ export default class ExecuteContract extends Component {
return api
.pollMethod('parity_checkRequest', requestId)
.catch((e) => {
if (e.code === ERROR_CODES.REQUEST_REJECTED) {
.catch((error) => {
if (error.code === ERROR_CODES.REQUEST_REJECTED) {
this.setState({ rejected: true });
return false;
}
throw e;
throw error;
});
})
.then((txhash) => {
@ -255,3 +310,18 @@ export default class ExecuteContract extends Component {
});
}
}
function mapStateToProps (state) {
const { gasLimit } = state.nodeStatus;
return { gasLimit };
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ExecuteContract);

View File

@ -19,7 +19,9 @@ const ERRORS = {
invalidAddress: 'the supplied address is an invalid network address',
invalidAmount: 'the supplied amount should be a valid positive number',
invalidDecimals: 'the supplied amount exceeds the allowed decimals',
largeAmount: 'the transaction total is higher than the available balance'
largeAmount: 'the transaction total is higher than the available balance',
gasException: 'the transaction will throw an exception with the current values',
gasBlockLimit: 'the transaction execution will exceed the block gas limit'
};
export default ERRORS;

View File

@ -14,6 +14,7 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.info {
line-height: 1.618em;
width: 100%;
@ -151,3 +152,13 @@
.gasPriceDesc {
font-size: 0.9em;
}
.warning {
border-radius: 0.5em;
background: #f80;
color: white;
font-size: 0.75em;
margin-bottom: 1em;
padding: 0.75em;
text-align: center;
}

View File

@ -16,12 +16,15 @@
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '../../util/constants';
import Details from './Details';
import Extras from './Extras';
@ -30,8 +33,6 @@ import styles from './transfer.css';
import { ERROR_CODES } from '../../api/transport/error';
const DEFAULT_GAS = '21000';
const DEFAULT_GASPRICE = '20000000000';
const TITLES = {
transfer: 'transfer details',
sending: 'sending',
@ -42,7 +43,7 @@ const TITLES = {
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete];
export default class Transfer extends Component {
class Transfer extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
@ -52,6 +53,7 @@ export default class Transfer extends Component {
account: PropTypes.object,
balance: PropTypes.object,
balances: PropTypes.object,
gasLimit: PropTypes.object.isRequired,
images: PropTypes.object.isRequired,
onClose: PropTypes.func
}
@ -64,6 +66,7 @@ export default class Transfer extends Component {
gas: DEFAULT_GAS,
gasEst: '0',
gasError: null,
gasLimitError: null,
gasPrice: DEFAULT_GASPRICE,
gasPriceHistogram: {},
gasPriceError: null,
@ -103,6 +106,7 @@ export default class Transfer extends Component {
visible
scroll
>
{ this.renderWarning() }
{ this.renderPage() }
</Modal>
);
@ -264,6 +268,20 @@ export default class Transfer extends Component {
}
}
renderWarning () {
const { gasLimitError } = this.state;
if (!gasLimitError) {
return null;
}
return (
<div className={ styles.warning }>
{ gasLimitError }
</div>
);
}
isValid () {
const detailsValid = !this.state.recipientError && !this.state.valueError && !this.state.totalError;
const extrasValid = !this.state.gasError && !this.state.gasPriceError && !this.state.totalError;
@ -519,6 +537,7 @@ export default class Transfer extends Component {
return token.contract.instance.transfer
.estimateGas({
gas: MAX_GAS_ESTIMATION,
from: account.address,
to: token.address
}, [
@ -532,6 +551,7 @@ export default class Transfer extends Component {
const { account } = this.props;
const { data, recipient, value } = this.state;
const options = {
gas: MAX_GAS_ESTIMATION,
from: account.address,
to: recipient,
value: api.util.toWei(value || 0)
@ -552,19 +572,29 @@ export default class Transfer extends Component {
return;
}
const { gasLimit } = this.props;
(this.state.isEth
? this._estimateGasEth()
: this._estimateGasToken()
).then((_value) => {
let gas = _value;
).then((gasEst) => {
let gas = gasEst;
let gasLimitError = null;
if (gas.gt(DEFAULT_GAS)) {
gas = gas.mul(1.2);
}
if (gas.gte(MAX_GAS_ESTIMATION)) {
gasLimitError = ERRORS.gasException;
} else if (gas.gt(gasLimit)) {
gasLimitError = ERRORS.gasBlockLimit;
}
this.setState({
gas: gas.toFixed(0),
gasEst: _value.toFormat()
gasEst: gasEst.toFormat(),
gasLimitError
}, this.recalculate);
})
.catch((error) => {
@ -649,3 +679,18 @@ export default class Transfer extends Component {
store.dispatch({ type: 'newError', error });
}
}
function mapStateToProps (state) {
const { gasLimit } = state.nodeStatus;
return { gasLimit };
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Transfer);

View File

@ -16,26 +16,17 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class ParityBackground extends Component {
static contextTypes = {
muiTheme: PropTypes.object.isRequired
}
static propTypes = {
style: PropTypes.object.isRequired,
children: PropTypes.node,
className: PropTypes.string,
gradient: PropTypes.string,
seed: PropTypes.any,
settings: PropTypes.object.isRequired,
onClick: PropTypes.func
}
};
render () {
const { muiTheme } = this.context;
const { children, className, gradient, seed, settings, onClick } = this.props;
const style = muiTheme.parity.getBackgroundStyle(gradient, seed || settings.backgroundSeed);
const { children, className, style, onClick } = this.props;
return (
<div
@ -48,17 +39,29 @@ class ParityBackground extends Component {
}
}
function mapStateToProps (state) {
const { settings } = state;
function mapStateToProps (_, initProps) {
const { gradient, seed, muiTheme } = initProps;
return { settings };
}
let _seed = seed;
let _props = { style: muiTheme.parity.getBackgroundStyle(gradient, seed) };
function mapDispatchToProps (dispatch) {
return bindActionCreators({}, dispatch);
return (state, props) => {
const { backgroundSeed } = state.settings;
const { seed } = props;
const newSeed = seed || backgroundSeed;
if (newSeed === _seed) {
return _props;
}
_seed = newSeed;
_props = { style: muiTheme.parity.getBackgroundStyle(gradient, newSeed) };
return _props;
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps
)(ParityBackground);

26
js/src/util/constants.js Normal file
View File

@ -0,0 +1,26 @@
// 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 <http://www.gnu.org/licenses/>.
const DEFAULT_GAS = '21000';
const DEFAULT_GASPRICE = '20000000000';
const MAX_GAS_ESTIMATION = '50000000';
export {
DEFAULT_GAS,
DEFAULT_GASPRICE,
MAX_GAS_ESTIMATION
};

View File

@ -16,6 +16,7 @@
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { isEqual } from 'lodash';
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '../../../ui';
@ -30,7 +31,6 @@ export default class Summary extends Component {
link: PropTypes.string,
name: PropTypes.string,
noLink: PropTypes.bool,
children: PropTypes.node,
handleAddSearchToken: PropTypes.func
};
@ -42,8 +42,42 @@ export default class Summary extends Component {
name: 'Unnamed'
};
shouldComponentUpdate (nextProps) {
const prev = {
link: this.props.link, name: this.props.name,
noLink: this.props.noLink,
meta: this.props.account.meta, address: this.props.account.address
};
const next = {
link: nextProps.link, name: nextProps.name,
noLink: nextProps.noLink,
meta: nextProps.account.meta, address: nextProps.account.address
};
if (!isEqual(next, prev)) {
return true;
}
const prevTokens = this.props.balance.tokens || [];
const nextTokens = nextProps.balance.tokens || [];
if (prevTokens.length !== nextTokens.length) {
return true;
}
const prevValues = prevTokens.map((t) => t.value.toNumber());
const nextValues = nextTokens.map((t) => t.value.toNumber());
if (!isEqual(prevValues, nextValues)) {
return true;
}
return false;
}
render () {
const { account, children, handleAddSearchToken } = this.props;
const { account, handleAddSearchToken } = this.props;
const { tags } = account.meta;
if (!account) {
@ -71,7 +105,6 @@ export default class Summary extends Component {
byline={ addressComponent } />
{ this.renderBalance() }
{ children }
</Container>
);
}

View File

@ -30,3 +30,25 @@
right: 1em;
top: 4em;
}
.loadings {
display: flex;
flex-wrap: wrap;
.loading {
flex: 0 1 50%;
width: 50%;
height: 150px;
display: flex;
padding: 0.25em;
box-sizing: border-box;
> div {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.8);
}
}
}

View File

@ -42,33 +42,63 @@ class Accounts extends Component {
newDialog: false,
sortOrder: '',
searchValues: [],
searchTokens: []
searchTokens: [],
show: false
}
componentWillMount () {
window.setTimeout(() => {
this.setState({ show: true });
}, 100);
}
render () {
const { accounts, hasAccounts, balances } = this.props;
const { searchValues, sortOrder } = this.state;
return (
<div className={ styles.accounts }>
{ this.renderNewDialog() }
{ this.renderActionbar() }
<Page>
<List
search={ searchValues }
accounts={ accounts }
balances={ balances }
empty={ !hasAccounts }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken } />
<Tooltip
className={ styles.accountTooltip }
text='your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account' />
</Page>
{ this.state.show ? this.renderAccounts() : this.renderLoading() }
</div>
);
}
renderLoading () {
const { accounts } = this.props;
const loadings = ((accounts && Object.keys(accounts)) || []).map((_, idx) => (
<div key={ idx } className={ styles.loading }>
<div></div>
</div>
));
return (
<div className={ styles.loadings }>
{ loadings }
</div>
);
}
renderAccounts () {
const { accounts, hasAccounts, balances } = this.props;
const { searchValues, sortOrder } = this.state;
return (
<Page>
<List
search={ searchValues }
accounts={ accounts }
balances={ balances }
empty={ !hasAccounts }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken } />
<Tooltip
className={ styles.accountTooltip }
text='your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account' />
</Page>
);
}
renderSearchButton () {
const onChange = (searchTokens, searchValues) => {
this.setState({ searchTokens, searchValues });

View File

@ -22,6 +22,10 @@ import { Errors, ParityBackground, Tooltips } from '../../../ui';
import styles from '../application.css';
export default class Container extends Component {
static contextTypes = {
muiTheme: PropTypes.object.isRequired
};
static propTypes = {
children: PropTypes.node.isRequired,
showFirstRun: PropTypes.bool,
@ -30,9 +34,10 @@ export default class Container extends Component {
render () {
const { children, showFirstRun, onCloseFirstRun } = this.props;
const { muiTheme } = this.context;
return (
<ParityBackground className={ styles.container }>
<ParityBackground className={ styles.container } muiTheme={ muiTheme }>
<FirstRun
visible={ showFirstRun }
onClose={ onCloseFirstRun } />

View File

@ -23,6 +23,11 @@
.tabs {
width: 100%;
position: relative;
display: flex;
& > * {
flex: 1;
}
}
.tabs button,
@ -38,6 +43,7 @@
button.tabactive,
button.tabactive:hover {
color: white !important;
background: rgba(0, 0, 0, 0.25) !important;
border-radius: 4px 4px 0 0;
}

View File

@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
import { Tabs, Tab } from 'material-ui/Tabs';
import { Tab as MUITab } from 'material-ui/Tabs';
import { Badge, Tooltip } from '../../../ui';
@ -33,20 +33,138 @@ const TABMAP = {
deploy: 'contract'
};
class Tab extends Component {
static propTypes = {
active: PropTypes.bool,
view: PropTypes.object,
children: PropTypes.node,
pendings: PropTypes.number,
onChange: PropTypes.func
};
shouldComponentUpdate (nextProps) {
return nextProps.active !== this.props.active ||
(nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings);
}
render () {
const { active, view, children } = this.props;
const label = this.getLabel(view);
return (
<MUITab
className={ active ? styles.tabactive : '' }
selected={ active }
icon={ view.icon }
label={ label }
onClick={ this.handleClick }
>
{ children }
</MUITab>
);
}
getLabel (view) {
const { label } = view;
if (view.id === 'signer') {
return this.renderSignerLabel(label);
}
if (view.id === 'status') {
return this.renderStatusLabel(label);
}
return this.renderLabel(label);
}
renderLabel (name, bubble) {
return (
<div className={ styles.label }>
{ name }
{ bubble }
</div>
);
}
renderSignerLabel (label) {
const { pendings } = this.props;
if (pendings) {
const bubble = (
<Badge
color='red'
className={ styles.labelBubble }
value={ pendings } />
);
return this.renderLabel(label, bubble);
}
return this.renderLabel(label);
}
renderStatusLabel (label) {
// const { isTest, netChain } = this.props;
// const bubble = (
// <Badge
// color={ isTest ? 'red' : 'default' }
// className={ styles.labelBubble }
// value={ isTest ? 'TEST' : netChain } />
// );
return this.renderLabel(label, null);
}
handleClick = () => {
const { onChange, view } = this.props;
onChange(view);
}
}
class TabBar extends Component {
static contextTypes = {
router: PropTypes.object.isRequired
}
};
static propTypes = {
views: PropTypes.array.isRequired,
hash: PropTypes.string.isRequired,
pending: PropTypes.array,
isTest: PropTypes.bool,
netChain: PropTypes.string,
settings: PropTypes.object.isRequired
}
netChain: PropTypes.string
};
static defaultProps = {
pending: []
};
state = {
activeRoute: '/accounts'
activeViewId: ''
};
setActiveView (props = this.props) {
const { hash, views } = props;
const view = views.find((view) => view.value === hash);
this.setState({ activeViewId: view.id });
}
componentWillMount () {
this.setActiveView();
}
componentWillReceiveProps (nextProps) {
if (nextProps.hash !== this.props.hash) {
this.setActiveView(nextProps);
}
}
shouldComponentUpdate (nextProps, nextState) {
return (nextProps.hash !== this.props.hash) ||
(nextProps.pending.length !== this.props.pending.length) ||
(nextState.activeViewId !== this.state.activeViewId);
}
render () {
@ -81,100 +199,64 @@ class TabBar extends Component {
}
renderTabs () {
const { settings } = this.props;
const windowHash = (window.location.hash || '').split('?')[0].split('/')[1];
const hash = TABMAP[windowHash] || windowHash;
const { views, pending } = this.props;
const { activeViewId } = this.state;
const items = Object.keys(settings.views)
.filter((id) => settings.views[id].fixed || settings.views[id].active)
.map((id) => {
const view = settings.views[id];
let label = this.renderLabel(view.label);
let body = null;
const items = views
.map((view, index) => {
const body = (view.id === 'accounts')
? (
<Tooltip className={ styles.tabbarTooltip } text='navigate between the different parts and views of the application, switching between an account view, token view and distributed application view' />
)
: null;
if (id === 'accounts') {
body = (
<Tooltip className={ styles.tabbarTooltip } text='navigate between the different parts and views of the application, switching between an account view, token view and distributed application view' />
);
} else if (id === 'signer') {
label = this.renderSignerLabel(label);
} else if (id === 'status') {
label = this.renderStatusLabel(label);
}
const active = activeViewId === view.id;
return (
<Tab
className={ hash === view.value ? styles.tabactive : '' }
value={ view.value }
icon={ view.icon }
key={ id }
label={ label }
onActive={ this.onActivate(view.route) }>
active={ active }
view={ view }
onChange={ this.onChange }
key={ index }
pendings={ pending.length }
>
{ body }
</Tab>
);
});
return (
<Tabs
<div
className={ styles.tabs }
value={ hash }>
onChange={ this.onChange }>
{ items }
</Tabs>
);
}
renderLabel = (name, bubble) => {
return (
<div className={ styles.label }>
{ name }
{ bubble }
</div>
);
}
renderSignerLabel = (label) => {
const { pending } = this.props;
let bubble = null;
if (pending && pending.length) {
bubble = (
<Badge
color='red'
className={ styles.labelBubble }
value={ pending.length } />
);
}
return this.renderLabel(label, bubble);
}
renderStatusLabel = (label) => {
// const { isTest, netChain } = this.props;
// const bubble = (
// <Badge
// color={ isTest ? 'red' : 'default' }
// className={ styles.labelBubble }
// value={ isTest ? 'TEST' : netChain } />
// );
return this.renderLabel(label, null);
}
onActivate = (activeRoute) => {
onChange = (view) => {
const { router } = this.context;
return (event) => {
router.push(activeRoute);
this.setState({ activeRoute });
};
router.push(view.route);
this.setState({ activeViewId: view.id });
}
}
function mapStateToProps (state) {
const { settings } = state;
const { views } = state.settings;
return { settings };
const filteredViews = Object
.keys(views)
.filter((id) => views[id].fixed || views[id].active)
.map((id) => ({
...views[id],
id
}));
const windowHash = (window.location.hash || '').split('?')[0].split('/')[1];
const hash = TABMAP[windowHash] || windowHash;
return { views: filteredViews, hash };
}
function mapDispatchToProps (dispatch) {

View File

@ -52,6 +52,7 @@
"description": "Have a peak on internals of transaction queue of your node.",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0",
"visible": true,
"secure": true
}
]

View File

@ -28,6 +28,10 @@ import imagesEthcoreBlock from '../../../assets/images/parity-logo-white-no-text
import styles from './parityBar.css';
class ParityBar extends Component {
static contextTypes = {
muiTheme: PropTypes.object.isRequired
};
static propTypes = {
pending: PropTypes.array,
dapp: PropTypes.bool
@ -62,6 +66,7 @@ class ParityBar extends Component {
renderBar () {
const { dapp } = this.props;
const { muiTheme } = this.context;
if (!dapp) {
return null;
@ -75,7 +80,7 @@ class ParityBar extends Component {
return (
<div className={ styles.bar }>
<ParityBackground className={ styles.corner }>
<ParityBackground className={ styles.corner } muiTheme={ muiTheme }>
<div className={ styles.cornercolor }>
<Link to='/apps'>
<Button
@ -95,9 +100,11 @@ class ParityBar extends Component {
}
renderExpanded () {
const { muiTheme } = this.context;
return (
<div className={ styles.overlay }>
<ParityBackground className={ styles.expanded }>
<ParityBackground className={ styles.expanded } muiTheme={ muiTheme }>
<div className={ styles.header }>
<div className={ styles.title }>
<ContainerTitle title='Parity Signer: Pending' />

View File

@ -81,6 +81,7 @@ class Background extends Component {
renderBackgrounds () {
const { settings } = this.props;
const { seeds } = this.state;
const { muiTheme } = this.context;
return seeds.map((seed, index) => {
return (
@ -89,7 +90,9 @@ class Background extends Component {
<ParityBackground
className={ settings.backgroundSeed === seed ? styles.seedactive : styles.seed }
seed={ seed }
onClick={ this.onSelect(seed) } />
onClick={ this.onSelect(seed) }
muiTheme={ muiTheme }
/>
</div>
</div>
);

View File

@ -37,6 +37,7 @@ module.exports = {
entry: {
// dapps
'basiccoin': ['./dapps/basiccoin.js'],
'dappreg': ['./dapps/dappreg.js'],
'githubhint': ['./dapps/githubhint.js'],
'registry': ['./dapps/registry.js'],
'signaturereg': ['./dapps/signaturereg.js'],