Refactoring of the Dapp Registry (#4589)
* Add React Hot Loader to DappReg dapp * Updated colours * Add DappCards * Dapp Modal with manifest displayed * Add input to the Dapp Modal * WIP // Editing a Dapp * Clean-Up * Linting * CleanUp and separate dapp from dappS * Semi-working updates * Working Editing of a Dapp * OCD * Linting * Add a Dapp -- WIP * Register a new Dapp * WIP Dapps * Working update / delete / register * Better promises * Working updates for DappReg * Fully functional again ! * Generic Card Component * Dashed Register Card * Cleanups * Cleanups * Add Actions to Modal * Clean-Up * Better Close Icon * Single place for Registry version // Fetch meta-data from Registry * Fixing test * Fix saving changes in dapp reg * PR Grumbles - Part I * PR Grumble - Part I * PR Grumble - Part II * DappReg Contract owner can delete dapps
This commit is contained in:
parent
e15f60b819
commit
eebb8b87a4
12
js/assets/images/dapps/close.svg
Normal file
12
js/assets/images/dapps/close.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512 512">
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<path d="M295.516 216.494h154v78.992h-154v-78.992z" fill="#FFFFFF" />
|
||||||
|
<path d="M62.474 216.514h154.050v78.971h-154.050v-78.971z" fill="#FFFFFF" />
|
||||||
|
<path d="M216.525 295.465h79.001v154.050h-79.001v-154.050z" fill="#FFFFFF" />
|
||||||
|
<path d="M216.525 62.474h79.001v154.041h-79.001v-154.041z" fill="#FFFFFF" />
|
||||||
|
<path d="M216.525 216.514h79.001v78.971h-79.001v-78.971z" fill="#FFFFFF" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 720 B |
12
js/assets/images/dapps/plus.svg
Normal file
12
js/assets/images/dapps/plus.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512 512">
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<path d="M295.516 216.494h154v78.992h-154v-78.992z" fill="#000000" />
|
||||||
|
<path d="M62.474 216.514h154.050v78.971h-154.050v-78.971z" fill="#000000" />
|
||||||
|
<path d="M216.525 295.465h79.001v154.050h-79.001v-154.050z" fill="#000000" />
|
||||||
|
<path d="M216.525 62.474h79.001v154.041h-79.001v-154.041z" fill="#000000" />
|
||||||
|
<path d="M216.525 216.514h79.001v78.971h-79.001v-78.971z" fill="#000000" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 715 B |
@ -16,7 +16,14 @@
|
|||||||
|
|
||||||
import * as abis from './abi';
|
import * as abis from './abi';
|
||||||
|
|
||||||
|
const REGISTRY_V1_HASHES = [
|
||||||
|
'0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten
|
||||||
|
'0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead
|
||||||
|
];
|
||||||
|
|
||||||
export default class Registry {
|
export default class Registry {
|
||||||
|
_registryContract = null;
|
||||||
|
|
||||||
constructor (api) {
|
constructor (api) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
|
|
||||||
@ -43,11 +50,10 @@ export default class Registry {
|
|||||||
|
|
||||||
this._fetching = true;
|
this._fetching = true;
|
||||||
|
|
||||||
return this._api.parity
|
return this.fetchContract()
|
||||||
.registryAddress()
|
.then((contract) => {
|
||||||
.then((address) => {
|
|
||||||
this._fetching = false;
|
this._fetching = false;
|
||||||
this._instance = this._api.newContract(abis.registry, address).instance;
|
this._instance = contract.instance;
|
||||||
|
|
||||||
this._queue.forEach((queued) => {
|
this._queue.forEach((queued) => {
|
||||||
queued.resolve(this._instance);
|
queued.resolve(this._instance);
|
||||||
@ -89,6 +95,47 @@ export default class Registry {
|
|||||||
.then((contract) => contract.instance);
|
.then((contract) => contract.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchContract () {
|
||||||
|
if (this._registryContract) {
|
||||||
|
return Promise.resolve(this._registryContract);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._api.parity
|
||||||
|
.registryAddress()
|
||||||
|
.then((address) => Promise.all([ address, this._api.eth.getCode(address) ]))
|
||||||
|
.then(([ address, code ]) => {
|
||||||
|
const codeHash = this._api.util.sha3(code);
|
||||||
|
const version = REGISTRY_V1_HASHES.includes(codeHash)
|
||||||
|
? 1
|
||||||
|
: 2;
|
||||||
|
const abi = version === 1
|
||||||
|
? abis.registry
|
||||||
|
: abis.registry2;
|
||||||
|
const contract = this._api.newContract(abi, address);
|
||||||
|
|
||||||
|
// Add support for previous `set` and `get` methods
|
||||||
|
if (!contract.instance.get && contract.instance.getData) {
|
||||||
|
contract.instance.get = contract.instance.getData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contract.instance.get && !contract.instance.getData) {
|
||||||
|
contract.instance.getData = contract.instance.get;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contract.instance.set && contract.instance.setData) {
|
||||||
|
contract.instance.set = contract.instance.setData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contract.instance.set && !contract.instance.setData) {
|
||||||
|
contract.instance.setData = contract.instance.set;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`registry at ${address}, code ${codeHash}, version ${version}`);
|
||||||
|
this._registryContract = contract;
|
||||||
|
return this._registryContract;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_createGetParams (_name, key) {
|
_createGetParams (_name, key) {
|
||||||
const name = _name.toLowerCase();
|
const name = _name.toLowerCase();
|
||||||
const sha3 = this._api.util.sha3.text(name);
|
const sha3 = this._api.util.sha3.text(name);
|
||||||
|
@ -35,6 +35,9 @@ function create () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
api = {
|
api = {
|
||||||
|
eth: {
|
||||||
|
getCode: sinon.stub().resolves('0x123456')
|
||||||
|
},
|
||||||
parity: {
|
parity: {
|
||||||
registryAddress: sinon.stub().resolves('testRegistryAddress')
|
registryAddress: sinon.stub().resolves('testRegistryAddress')
|
||||||
},
|
},
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||||
|
import { AppContainer } from 'react-hot-loader';
|
||||||
|
|
||||||
injectTapEventPlugin();
|
injectTapEventPlugin();
|
||||||
|
|
||||||
@ -27,6 +28,21 @@ import '../../assets/fonts/RobotoMono/font.css';
|
|||||||
import './style.css';
|
import './style.css';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Application />,
|
<AppContainer>
|
||||||
|
<Application />
|
||||||
|
</AppContainer>,
|
||||||
document.querySelector('#container')
|
document.querySelector('#container')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('./dappreg/Application/index.js', () => {
|
||||||
|
require('./dappreg/Application/index.js');
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<AppContainer>
|
||||||
|
<Application />
|
||||||
|
</AppContainer>,
|
||||||
|
document.querySelector('#container')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -15,15 +15,17 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import '../_colors.css';
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
color: #333;
|
color: $text-color;
|
||||||
background: #eee;
|
background: $background-color;
|
||||||
padding: 4.5em 0;
|
padding: 3em 0 6em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.apps {
|
.apps {
|
||||||
background: #fff;
|
background: white;
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 980px;
|
max-width: 980px;
|
||||||
@ -39,9 +41,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
background: #44e;
|
background: $blue;
|
||||||
border-radius: 0 0 0.25em 0.25em;
|
color: white;
|
||||||
color: #fff;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -54,5 +55,5 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding-top: 5em;
|
padding-top: 5em;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
color: #999;
|
color: $loading-color;
|
||||||
}
|
}
|
||||||
|
@ -19,18 +19,14 @@ import { observer } from 'mobx-react';
|
|||||||
|
|
||||||
import DappsStore from '../dappsStore';
|
import DappsStore from '../dappsStore';
|
||||||
|
|
||||||
import ButtonBar from '../ButtonBar';
|
import Dapps from '../Dapps';
|
||||||
import Dapp from '../Dapp';
|
import Transactions from '../Transactions';
|
||||||
import ModalDelete from '../ModalDelete';
|
|
||||||
import ModalRegister from '../ModalRegister';
|
|
||||||
import ModalUpdate from '../ModalUpdate';
|
|
||||||
import SelectDapp from '../SelectDapp';
|
|
||||||
import Warning from '../Warning';
|
import Warning from '../Warning';
|
||||||
import styles from './application.css';
|
import styles from './application.css';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class Application extends Component {
|
export default class Application extends Component {
|
||||||
dappsStore = DappsStore.instance();
|
dappsStore = DappsStore.get();
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
if (this.dappsStore.isLoading) {
|
if (this.dappsStore.isLoading) {
|
||||||
@ -41,23 +37,32 @@ export default class Application extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { ownDapps, otherDapps } = this.dappsStore;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.body }>
|
<div className={ styles.body }>
|
||||||
<div className={ styles.header }>
|
<div className={ styles.header }>
|
||||||
DAPP REGISTRY, a global view of distributed applications available on the network. Putting the puzzle together.
|
DAPP REGISTRY, a global view of distributed applications available on the network. Putting the puzzle together.
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.apps }>
|
|
||||||
<SelectDapp />
|
<div>
|
||||||
<ButtonBar />
|
<Dapps
|
||||||
<Dapp />
|
dapps={ ownDapps }
|
||||||
|
own
|
||||||
|
title='My Dapps'
|
||||||
|
/>
|
||||||
|
<Dapps
|
||||||
|
dapps={ otherDapps }
|
||||||
|
title='Other Dapps'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={ styles.footer }>
|
<div className={ styles.footer }>
|
||||||
{ this.dappsStore.count } applications registered, { this.dappsStore.ownedCount } owned by user
|
{ this.dappsStore.count } applications registered, { this.dappsStore.ownedCount } owned by user
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Transactions />
|
||||||
<Warning />
|
<Warning />
|
||||||
<ModalDelete />
|
|
||||||
<ModalRegister />
|
|
||||||
<ModalUpdate />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,16 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import '../_colors.css';
|
||||||
|
@import '../_utils.css';
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background: #44e;
|
composes: bezier-transform;
|
||||||
|
|
||||||
|
background: $blue;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
color: #fff;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
margin: 1em 0.375em;
|
margin: 1em 0.375em;
|
||||||
@ -29,10 +34,14 @@
|
|||||||
&[disabled] {
|
&[disabled] {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
background: #aaa;
|
background: $disabled-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-warning="true"] {
|
&[data-warning="true"] {
|
||||||
background: #e44;
|
background: $warning-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,27 +24,29 @@ export default class Button extends Component {
|
|||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
warning: PropTypes.bool,
|
warning: PropTypes.bool,
|
||||||
onClick: PropTypes.func.isRequired
|
onClick: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { className, disabled, label, warning } = this.props;
|
const { className, disabled, label, warning } = this.props;
|
||||||
const classes = `${styles.button} ${className}`;
|
const classes = [ styles.button, className ];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={ classes }
|
className={ classes.join(' ') }
|
||||||
data-warning={ warning }
|
data-warning={ warning }
|
||||||
disabled={ disabled }
|
disabled={ disabled }
|
||||||
onClick={ this.onClick }
|
onClick={ this.handleClick }
|
||||||
>
|
>
|
||||||
{ label }
|
{ label }
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick = (event) => {
|
handleClick = (event) => {
|
||||||
if (this.props.disabled) {
|
if (this.props.disabled) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
// Copyright 2015-2017 Parity Technologies (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 || (!this.dappsStore.currentApp.isOwner && !this.dappsStore.isContractOwner) }
|
|
||||||
onClick={ this.onDeleteClick }
|
|
||||||
/>,
|
|
||||||
<Button
|
|
||||||
key='edit'
|
|
||||||
label='Edit'
|
|
||||||
disabled={ !this.dappsStore.currentApp || !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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
66
js/src/dapps/dappreg/Card/card.css
Normal file
66
js/src/dapps/dappreg/Card/card.css
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (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 '../_utils.css';
|
||||||
|
|
||||||
|
$imgSize: 6rem;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
composes: bezier-transform;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(240, 240, 240, 0.75);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
width: 10rem;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
cursor: pointer;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashed {
|
||||||
|
border: 1px dashed black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 50%;
|
||||||
|
height: $imgSize;
|
||||||
|
width: $imgSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
width: 100%;
|
||||||
|
}
|
99
js/src/dapps/dappreg/Card/card.js
Normal file
99
js/src/dapps/dappreg/Card/card.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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 keycode from 'keycode';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import styles from './card.css';
|
||||||
|
|
||||||
|
export default class Card extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.any,
|
||||||
|
dashed: PropTypes.bool,
|
||||||
|
focus: PropTypes.bool,
|
||||||
|
icon: PropTypes.object,
|
||||||
|
name: PropTypes.object,
|
||||||
|
onClick: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
dashed: false,
|
||||||
|
focus: false,
|
||||||
|
name: { value: '' }
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (nextProps.focus && !this.props.focus) {
|
||||||
|
this.handleFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { children, dashed, icon, name } = this.props;
|
||||||
|
|
||||||
|
const cardClasses = [ styles.card ];
|
||||||
|
|
||||||
|
if (dashed) {
|
||||||
|
cardClasses.push(styles.dashed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.container }>
|
||||||
|
<div
|
||||||
|
className={ cardClasses.join(' ') }
|
||||||
|
onClick={ this.handleClick }
|
||||||
|
onKeyPress={ this.handleKeyPress }
|
||||||
|
ref='card'
|
||||||
|
tabIndex={ 0 }
|
||||||
|
>
|
||||||
|
<div className={ styles.icon }>
|
||||||
|
{ icon }
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className={ styles.name }
|
||||||
|
title={ name.title || name.value }
|
||||||
|
>
|
||||||
|
{ name.value }
|
||||||
|
</span>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (event) => {
|
||||||
|
const codeName = keycode(event);
|
||||||
|
|
||||||
|
if (codeName === 'enter') {
|
||||||
|
return this.handleClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFocus = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const element = ReactDOM.findDOMNode(this.refs.card);
|
||||||
|
|
||||||
|
element && element.focus();
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = () => {
|
||||||
|
this.props.onClick();
|
||||||
|
}
|
||||||
|
}
|
@ -14,4 +14,4 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default from './dapp';
|
export default from './card';
|
84
js/src/dapps/dappreg/CreateDappCard/createDappCard.js
Normal file
84
js/src/dapps/dappreg/CreateDappCard/createDappCard.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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 DappsStore from '../dappsStore';
|
||||||
|
import Card from '../Card';
|
||||||
|
import ModalRegister from '../ModalRegister';
|
||||||
|
|
||||||
|
import PlusImage from '~/../assets/images/dapps/plus.svg';
|
||||||
|
|
||||||
|
export default class CreateDappCard extends Component {
|
||||||
|
state = {
|
||||||
|
dappId: null,
|
||||||
|
focus: false,
|
||||||
|
open: false
|
||||||
|
};
|
||||||
|
|
||||||
|
dappsStore = DappsStore.get();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { focus } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ this.renderModal() }
|
||||||
|
|
||||||
|
<Card
|
||||||
|
dashed
|
||||||
|
focus={ focus }
|
||||||
|
icon={ (<img src={ PlusImage } />) }
|
||||||
|
name={ { value: 'Register a dapp' } }
|
||||||
|
onClick={ this.handleOpen }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderModal () {
|
||||||
|
const { dappId, open } = this.state;
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalRegister
|
||||||
|
dappId={ dappId }
|
||||||
|
onClose={ this.handleClose }
|
||||||
|
onRegister={ this.handleRegister }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOpen = () => {
|
||||||
|
const dappId = this.dappsStore.createDappId();
|
||||||
|
|
||||||
|
this.setState({ focus: false, open: true, dappId });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClose = () => {
|
||||||
|
this.setState({ focus: true, open: false, dappId: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRegister = () => {
|
||||||
|
const { dappId } = this.state;
|
||||||
|
|
||||||
|
this.dappsStore.register(dappId);
|
||||||
|
this.handleClose();
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/CreateDappCard/index.js
Normal file
17
js/src/dapps/dappreg/CreateDappCard/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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 './createDappCard';
|
@ -1,174 +0,0 @@
|
|||||||
// Copyright 2015-2017 Parity Technologies (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;
|
|
||||||
|
|
||||||
if (!app) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,7 +15,8 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.buttonbar {
|
.author,
|
||||||
|
.version {
|
||||||
|
font-size: 0.75rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 1em 0 0 0;
|
|
||||||
}
|
}
|
110
js/src/dapps/dappreg/DappCard/dappCard.js
Normal file
110
js/src/dapps/dappreg/DappCard/dappCard.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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 Card from '../Card';
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
import DappModal from '../DappModal';
|
||||||
|
|
||||||
|
import styles from './dappCard.css';
|
||||||
|
|
||||||
|
export default class DappCard extends Component {
|
||||||
|
dappsStore = DappsStore.get();
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
dapp: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
focus: false,
|
||||||
|
open: false
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { dapp } = this.props;
|
||||||
|
const { focus } = this.state;
|
||||||
|
const { id, image } = dapp;
|
||||||
|
const manifest = dapp.manifest.content;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ this.renderModal() }
|
||||||
|
|
||||||
|
<Card
|
||||||
|
focus={ focus }
|
||||||
|
icon={ this.renderImage(image.url) }
|
||||||
|
name={ { title: id, value: manifest && manifest.name || id } }
|
||||||
|
onClick={ this.handleOpen }
|
||||||
|
>
|
||||||
|
{ this.renderVersion(manifest) }
|
||||||
|
{ this.renderAuthor(manifest) }
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderModal () {
|
||||||
|
const { dapp } = this.props;
|
||||||
|
const { open } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DappModal
|
||||||
|
dapp={ dapp }
|
||||||
|
onClose={ this.handleClose }
|
||||||
|
open={ open }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderImage (url) {
|
||||||
|
return (
|
||||||
|
<img src={ url } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderVersion (manifest) {
|
||||||
|
if (!manifest || !manifest.version) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={ styles.version }>
|
||||||
|
v{ manifest.version }
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAuthor (manifest) {
|
||||||
|
if (!manifest || !manifest.author) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={ styles.author }>
|
||||||
|
by { manifest && manifest.author }
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClose = () => {
|
||||||
|
this.setState({ focus: true, open: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOpen = () => {
|
||||||
|
this.setState({ focus: false, open: true });
|
||||||
|
}
|
||||||
|
}
|
@ -14,4 +14,4 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default from './buttonBar';
|
export default from './dappCard';
|
82
js/src/dapps/dappreg/DappModal/dappModal.css
Normal file
82
js/src/dapps/dappreg/DappModal/dappModal.css
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (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 '../_colors.css';
|
||||||
|
@import '../_utils.css';
|
||||||
|
|
||||||
|
$imgSize: 5rem;
|
||||||
|
|
||||||
|
.code {
|
||||||
|
color: $code-color;
|
||||||
|
font-family: 'Roboto Mono', monospace;
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
|
.codeTitle {
|
||||||
|
align-items: center;
|
||||||
|
background-color: $code-title-bg;
|
||||||
|
color: $code-title-color;
|
||||||
|
display: inline-flex;
|
||||||
|
height: 2rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeContainer {
|
||||||
|
background-color: $code-bg;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
height: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
text-align: right;
|
||||||
|
top: 0.5rem;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: $imgSize;
|
||||||
|
width: $imgSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
color: #ddd;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
423
js/src/dapps/dappreg/DappModal/dappModal.js
Normal file
423
js/src/dapps/dappreg/DappModal/dappModal.js
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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 { observer } from 'mobx-react';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import { api } from '../parity';
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
import Button from '../Button';
|
||||||
|
import Input from '../Input';
|
||||||
|
import Modal from '../Modal';
|
||||||
|
import ModalDelete from '../ModalDelete';
|
||||||
|
import ModalUpdate from '../ModalUpdate';
|
||||||
|
import SelectAccount from '../SelectAccount';
|
||||||
|
|
||||||
|
import styles from './dappModal.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class DappModal extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
dapp: PropTypes.object.isRequired,
|
||||||
|
open: PropTypes.bool.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
showDelete: false,
|
||||||
|
showUpdate: false,
|
||||||
|
updates: null,
|
||||||
|
updating: false
|
||||||
|
};
|
||||||
|
|
||||||
|
dappsStore = DappsStore.get();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { dapp, open } = this.props;
|
||||||
|
const { showDelete, showUpdate, updates } = this.state;
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
showDelete
|
||||||
|
? (
|
||||||
|
<ModalDelete
|
||||||
|
dappId={ dapp.id }
|
||||||
|
onClose={ this.handleDeleteClose }
|
||||||
|
onDelete={ this.handleDeleteConfirm }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
showUpdate
|
||||||
|
? (
|
||||||
|
<ModalUpdate
|
||||||
|
dappId={ dapp.id }
|
||||||
|
onClose={ this.handleUpdateClose }
|
||||||
|
onConfirm={ this.handleUpdateConfirm }
|
||||||
|
updates={ updates }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
header={ this.renderHeader(dapp) }
|
||||||
|
onClose={ this.handleClose }
|
||||||
|
>
|
||||||
|
{ this.renderContent(dapp) }
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent (dapp) {
|
||||||
|
const manifest = dapp.manifest.content || {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
{ this.renderInputs(dapp) }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ this.renderActions(dapp) }
|
||||||
|
|
||||||
|
<div className={ styles.code }>
|
||||||
|
<div className={ styles.codeTitle }>manifest.json</div>
|
||||||
|
<div className={ styles.codeContainer }>
|
||||||
|
<code>{ JSON.stringify(manifest, null, 2) }</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderActions (dapp) {
|
||||||
|
if (!dapp.isOwner && !dapp.isContractOwner) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dapp.isOwner && dapp.isContractOwner) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.actions }>
|
||||||
|
<Button
|
||||||
|
className={ styles.button }
|
||||||
|
label='Delete'
|
||||||
|
onClick={ this.handleDelete }
|
||||||
|
warning
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { isEditing } = dapp;
|
||||||
|
const { updating } = this.state;
|
||||||
|
|
||||||
|
if (updating) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.actions }>
|
||||||
|
<Button
|
||||||
|
className={ styles.button }
|
||||||
|
disabled
|
||||||
|
label='Updating...'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.actions }>
|
||||||
|
<Button
|
||||||
|
className={ styles.button }
|
||||||
|
disabled={ !dapp.canSave }
|
||||||
|
label='Save'
|
||||||
|
onClick={ this.handleSave }
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={ styles.button }
|
||||||
|
label='Fetch Registry'
|
||||||
|
onClick={ this.handleFetchRegistry }
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={ styles.button }
|
||||||
|
label='Cancel'
|
||||||
|
onClick={ this.handleCancel }
|
||||||
|
warning
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.actions }>
|
||||||
|
<Button
|
||||||
|
className={ styles.button }
|
||||||
|
label='Edit'
|
||||||
|
onClick={ this.handleEdit }
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={ styles.button }
|
||||||
|
label='Delete'
|
||||||
|
onClick={ this.handleDelete }
|
||||||
|
warning
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHeader (dapp) {
|
||||||
|
const { id, image } = dapp;
|
||||||
|
const manifest = dapp.manifest.content || {};
|
||||||
|
|
||||||
|
const infos = [];
|
||||||
|
|
||||||
|
if (manifest.version) {
|
||||||
|
infos.push(`v${manifest.version}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.author) {
|
||||||
|
infos.push(`by ${manifest.author}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.icon }>
|
||||||
|
<img src={ image.url } />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className={ styles.name }>
|
||||||
|
{ manifest.name || 'Unnamed' }
|
||||||
|
</div>
|
||||||
|
<div className={ styles.info }>
|
||||||
|
{ id }
|
||||||
|
</div>
|
||||||
|
<div className={ styles.info }>
|
||||||
|
{ infos.length > 0 ? infos.join(', ') : null }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInputs (dapp) {
|
||||||
|
return [
|
||||||
|
this.renderOwner(dapp),
|
||||||
|
this.renderHashInput(dapp, 'image', 'Image URL', true),
|
||||||
|
this.renderHashInput(dapp, 'manifest', 'Manifest URL'),
|
||||||
|
this.renderHashInput(dapp, 'content', 'Content URL')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOwner (dapp) {
|
||||||
|
const { isEditing } = dapp;
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
return this.renderOwnerSelect(dapp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.renderOwnerStatic(dapp);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOwnerSelect (dapp) {
|
||||||
|
const overlayImage = (
|
||||||
|
<img
|
||||||
|
className={ styles.overlayImage }
|
||||||
|
src={ api.util.createIdentityImg(this.props.dapp.wip.owner.address, 4) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
key='owner_select'
|
||||||
|
hint={ this.props.dapp.wip.owner.address }
|
||||||
|
label='Owner, select the application owner and editor'
|
||||||
|
overlay={ overlayImage }
|
||||||
|
>
|
||||||
|
<SelectAccount
|
||||||
|
onSelect={ this.handleSelectOwner }
|
||||||
|
value={ dapp.wip.owner.address }
|
||||||
|
/>
|
||||||
|
</Input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOwnerStatic (dapp) {
|
||||||
|
const overlayImage = (
|
||||||
|
<img
|
||||||
|
className={ styles.overlayImage }
|
||||||
|
src={ api.util.createIdentityImg(dapp.owner.address, 4) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
key='owner_static'
|
||||||
|
hint={ dapp.owner.address }
|
||||||
|
label='Owner, the application owner and editor'
|
||||||
|
overlay={ overlayImage }
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
readOnly
|
||||||
|
tabIndex={ -1 }
|
||||||
|
value={ dapp.owner.name || dapp.owner.address }
|
||||||
|
/>
|
||||||
|
</Input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHashInput (dapp, type, label, isImage = false) {
|
||||||
|
const handleChange = (event) => {
|
||||||
|
return this.handleChangeHash(event, type);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { isEditing, wip } = dapp;
|
||||||
|
|
||||||
|
const changed = wip && wip[type].changed;
|
||||||
|
const error = wip && wip[type].error;
|
||||||
|
|
||||||
|
const hash = dapp[type].hash;
|
||||||
|
const url = dapp[type].url;
|
||||||
|
|
||||||
|
const overlayImage = (isImage && hash)
|
||||||
|
? (
|
||||||
|
<img
|
||||||
|
className={ styles.overlayImage }
|
||||||
|
src={ `/api/content/${hash.substr(2)}` }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const wipUrl = isEditing && wip && wip[type].url;
|
||||||
|
|
||||||
|
const hint = error || (!changed && hash) || '...';
|
||||||
|
const value = typeof wipUrl !== 'string'
|
||||||
|
? url || ''
|
||||||
|
: wipUrl;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
key={ `${type}Edit` }
|
||||||
|
hint={ hint }
|
||||||
|
label={ label }
|
||||||
|
overlay={ overlayImage }
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
data-dirty={ changed }
|
||||||
|
data-error={ !!error }
|
||||||
|
onChange={ handleChange }
|
||||||
|
readOnly={ !isEditing }
|
||||||
|
tabIndex={ isEditing ? 0 : -1 }
|
||||||
|
value={ value }
|
||||||
|
/>
|
||||||
|
</Input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClose = () => {
|
||||||
|
this.handleCancel();
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelectOwner = (event) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
|
||||||
|
const changed = (this.props.dapp.owner.address !== value);
|
||||||
|
|
||||||
|
this.props.dapp.handleChange({
|
||||||
|
owner: {
|
||||||
|
address: value,
|
||||||
|
changed
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeHash = (event, type) => {
|
||||||
|
if (!this.props.dapp.isEditing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = event.target.value;
|
||||||
|
const changed = (this.props.dapp[type].url !== url);
|
||||||
|
|
||||||
|
this.props.dapp.handleChange({
|
||||||
|
[ type ]: {
|
||||||
|
error: null,
|
||||||
|
changed,
|
||||||
|
url
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFetchRegistry = () => {
|
||||||
|
this.dappsStore.fetchRegistryData(this.props.dapp);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCancel = () => {
|
||||||
|
this.props.dapp.setEditing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEdit = () => {
|
||||||
|
this.props.dapp.setEditing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDelete = () => {
|
||||||
|
this.setState({ showDelete: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteClose = () => {
|
||||||
|
this.setState({ showDelete: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteConfirm = () => {
|
||||||
|
this.dappsStore.delete(this.props.dapp);
|
||||||
|
this.handleDeleteClose();
|
||||||
|
this.handleClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSave = () => {
|
||||||
|
const updates = this.props.dapp.handleSave();
|
||||||
|
|
||||||
|
this.setState({ showUpdate: true, updates });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdateClose = () => {
|
||||||
|
this.setState({ showUpdate: false, updates: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdateConfirm = () => {
|
||||||
|
const { id, owner } = this.props.dapp;
|
||||||
|
const { updates } = this.state;
|
||||||
|
|
||||||
|
this.handleUpdateClose();
|
||||||
|
this.handleCancel();
|
||||||
|
this.setState({ updating: true });
|
||||||
|
|
||||||
|
return this.dappsStore.update(id, owner.address, updates)
|
||||||
|
.then(() => {
|
||||||
|
this.setState({ updating: false });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.setState({ updating: false });
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -14,4 +14,4 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default from './selectDapp';
|
export default from './dappModal';
|
38
js/src/dapps/dappreg/Dapps/dapps.css
Normal file
38
js/src/dapps/dappreg/Dapps/dapps.css
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.dapps {
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
}
|
73
js/src/dapps/dappreg/Dapps/dapps.js
Normal file
73
js/src/dapps/dappreg/Dapps/dapps.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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 CreateDappCard from '../CreateDappCard';
|
||||||
|
import DappCard from '../DappCard';
|
||||||
|
|
||||||
|
import styles from './dapps.css';
|
||||||
|
|
||||||
|
export default class Dapps extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
dapps: PropTypes.array.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
own: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
own: false
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { dapps, title } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.dapps }>
|
||||||
|
<h2 className={ styles.title }>{ title }</h2>
|
||||||
|
<div className={ styles.container }>
|
||||||
|
{ this.renderAddDapp() }
|
||||||
|
{ this.renderDapps(dapps) }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAddDapp () {
|
||||||
|
const { own } = this.props;
|
||||||
|
|
||||||
|
if (!own) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CreateDappCard />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDapps (dapps) {
|
||||||
|
return dapps.map((dapp) => {
|
||||||
|
const { id } = dapp;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DappCard
|
||||||
|
dapp={ dapp }
|
||||||
|
key={ id }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/Dapps/index.js
Normal file
17
js/src/dapps/dappreg/Dapps/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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 './dapps';
|
@ -30,6 +30,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.withOverlay input {
|
||||||
|
padding-right: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
padding-bottom: 1.5em;
|
padding-bottom: 1.5em;
|
||||||
|
|
||||||
|
@ -29,8 +29,14 @@ export default class Input extends Component {
|
|||||||
render () {
|
render () {
|
||||||
const { children, hint, label, overlay } = this.props;
|
const { children, hint, label, overlay } = this.props;
|
||||||
|
|
||||||
|
const inputClasses = [ styles.input ];
|
||||||
|
|
||||||
|
if (overlay) {
|
||||||
|
inputClasses.push(styles.withOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.input }>
|
<div className={ inputClasses.join(' ') }>
|
||||||
<label>
|
<label>
|
||||||
{ label }
|
{ label }
|
||||||
</label>
|
</label>
|
||||||
|
@ -15,95 +15,120 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import '../_colors.css';
|
||||||
|
@import '../_utils.css';
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
.body {
|
align-items: center;
|
||||||
|
background-color: $modal-bg;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
left: 0;
|
left: 0;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
text-align: center;
|
z-index: 150;
|
||||||
z-index: 50;
|
|
||||||
|
|
||||||
.dialog {
|
&.secondary {
|
||||||
background: #fff;
|
align-items: flex-start;
|
||||||
border-radius: 0 0 0.25em 0.25em;
|
z-index: 200;
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 840px;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
line-height: 1.5em;
|
line-height: 1.5rem;
|
||||||
padding: 2em;
|
padding: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
max-width: 840px;
|
||||||
|
width: 85vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
composes: bezier-transform;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
display: inline-block;
|
||||||
|
font-family: 'Roboto Mono', monospace;
|
||||||
|
font-size: 4rem;
|
||||||
|
opacity: 0.75;
|
||||||
|
padding: 0;
|
||||||
|
position: fixed;
|
||||||
|
right: 1rem;
|
||||||
|
top: 0.25rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
.closeIcon {
|
||||||
|
transform: rotate(135deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeIcon {
|
||||||
|
composes: bezier-transform;
|
||||||
|
|
||||||
|
height: 3rem;
|
||||||
|
width: 3rem;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
max-width: 740px;
|
||||||
|
width: 75vw;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
&.error {
|
* {
|
||||||
color: #f44;
|
overflow-x: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.section+.section {
|
.section + .section {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
padding: 0.5em 1.625em;
|
padding: 0.5em 1.625em;
|
||||||
text-align: right;
|
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 {
|
.header {
|
||||||
div {
|
background-color: $blue;
|
||||||
display: inline-block;
|
color: white;
|
||||||
vertical-align: top;
|
min-height: 3rem;
|
||||||
|
|
||||||
|
&,
|
||||||
|
& > * {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
.heading {
|
||||||
|
@ -14,53 +14,147 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import keycode from 'keycode';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import Button from '../Button';
|
||||||
|
|
||||||
import styles from './modal.css';
|
import styles from './modal.css';
|
||||||
|
import CloseImage from '~/../assets/images/dapps/close.svg';
|
||||||
|
|
||||||
export default class Modal extends Component {
|
export default class Modal extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
buttons: PropTypes.node,
|
actions: PropTypes.array,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
error: PropTypes.object,
|
header: PropTypes.node,
|
||||||
header: PropTypes.string
|
secondary: PropTypes.bool,
|
||||||
}
|
onClose: PropTypes.func.isRequired,
|
||||||
|
onConfirm: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
actions: null,
|
||||||
|
secondary: false
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, buttons, error, header } = this.props;
|
const { children, actions, header, secondary } = this.props;
|
||||||
|
|
||||||
|
const modalClasses = [ styles.modal ];
|
||||||
|
|
||||||
|
if (secondary) {
|
||||||
|
modalClasses.push(styles.secondary);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.modal }>
|
<div
|
||||||
<div className={ styles.overlay } />
|
className={ modalClasses.join(' ') }
|
||||||
<div className={ styles.body }>
|
onClick={ this.handleClose }
|
||||||
<div className={ styles.dialog }>
|
onKeyUp={ this.handleKeyPress }
|
||||||
<div className={ `${styles.header} ${error ? styles.error : ''}` }>
|
>
|
||||||
|
<div
|
||||||
|
className={ styles.dialog }
|
||||||
|
onClick={ this.stopEvent }
|
||||||
|
ref={ this.handleSetRef }
|
||||||
|
tabIndex={ open ? 0 : null }
|
||||||
|
>
|
||||||
|
<div className={ styles.header }>
|
||||||
{ header }
|
{ header }
|
||||||
|
<div
|
||||||
|
className={ styles.close }
|
||||||
|
onClick={ this.handleClose }
|
||||||
|
onKeyPress={ this.handleCloseKeyPress }
|
||||||
|
tabIndex={ open ? 0 : null }
|
||||||
|
title='close'
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className={ styles.closeIcon }
|
||||||
|
src={ CloseImage }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={ styles.content }>
|
<div className={ styles.content }>
|
||||||
{ error ? this.renderError() : children }
|
{ children }
|
||||||
</div>
|
|
||||||
<div className={ styles.footer }>
|
|
||||||
{ buttons }
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{ actions ? this.renderActions(actions) : null }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderError () {
|
renderActions (actions) {
|
||||||
const { error } = this.props;
|
return (
|
||||||
|
<div className={ styles.footer }>
|
||||||
|
{ actions.map((action) => {
|
||||||
|
let onClick = () => {};
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case 'confirm':
|
||||||
|
onClick = this.handleConfirm;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'close':
|
||||||
|
onClick = this.handleClose;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Button
|
||||||
<div className={ styles.section }>
|
key={ action.type }
|
||||||
Your operation failed to complete sucessfully. The following error was returned:
|
label={ action.label }
|
||||||
</div>
|
warning={ action.warning }
|
||||||
<div className={ `${styles.section} ${styles.error}` }>
|
onClick={ onClick }
|
||||||
{ error.toString() }
|
/>
|
||||||
</div>
|
);
|
||||||
|
}) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopEvent = (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (event) => {
|
||||||
|
const codeName = keycode(event);
|
||||||
|
|
||||||
|
if (codeName === 'esc') {
|
||||||
|
return this.handleClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCloseKeyPress = (event) => {
|
||||||
|
const codeName = keycode(event);
|
||||||
|
|
||||||
|
if (codeName === 'enter') {
|
||||||
|
return this.handleClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSetRef = (containerRef) => {
|
||||||
|
// Focus after the modal is open
|
||||||
|
setTimeout(() => {
|
||||||
|
const element = ReactDOM.findDOMNode(containerRef);
|
||||||
|
|
||||||
|
element && element.focus();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConfirm = () => {
|
||||||
|
this.props.onConfirm && this.props.onConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClose = () => {
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,150 +14,48 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component, PropTypes } 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 Modal from '../Modal';
|
||||||
|
|
||||||
import styles from '../Modal/modal.css';
|
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 {
|
export default class ModalDelete extends Component {
|
||||||
dappsStore = DappsStore.instance();
|
static propTypes = {
|
||||||
modalStore = ModalStore.instance();
|
dappId: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
onDelete: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
if (!this.modalStore.showingDelete) {
|
const { dappId, onClose, onDelete } = this.props;
|
||||||
return null;
|
const actions = [
|
||||||
}
|
{ type: 'close', label: 'No, Cancel' },
|
||||||
|
{ type: 'confirm', label: 'Yes, Delete', warning: true }
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
buttons={ this.renderButtons() }
|
actions={ actions }
|
||||||
error={ this.modalStore.errorDelete }
|
header='Confirm Application Deletion'
|
||||||
header={ HEADERS[this.modalStore.stepDelete] }
|
onClose={ onClose }
|
||||||
|
onConfirm={ onDelete }
|
||||||
|
secondary
|
||||||
>
|
>
|
||||||
{ 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 }>
|
<div className={ styles.section }>
|
||||||
Your application has been removed from the registry.
|
You are about to remove a distributed application from the registry,
|
||||||
</div>
|
the details of this application is given below. Removal does not return any fees,
|
||||||
</div>
|
however the application will not be available to users anymore.
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
||||||
<div className={ styles.section }>
|
<div className={ styles.section }>
|
||||||
<div className={ styles.heading }>
|
<div className={ styles.heading }>
|
||||||
Application identifier
|
Application identifier
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{ this.dappsStore.currentApp.id }
|
{ dappId }
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderStepWait (waitingFor) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className={ styles.section }>
|
|
||||||
{ waitingFor }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClickClose = () => {
|
|
||||||
this.modalStore.hideDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
onClickYes = () => {
|
|
||||||
this.modalStore.doDelete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,150 +14,55 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import { api } from '../parity';
|
import { api } from '../parity';
|
||||||
import DappsStore from '../dappsStore';
|
import DappsStore from '../dappsStore';
|
||||||
import ModalStore from '../modalStore';
|
|
||||||
|
|
||||||
import Button from '../Button';
|
|
||||||
import Modal from '../Modal';
|
import Modal from '../Modal';
|
||||||
|
|
||||||
import styles from '../Modal/modal.css';
|
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
|
@observer
|
||||||
export default class ModalRegister extends Component {
|
export default class ModalRegister extends Component {
|
||||||
dappsStore = DappsStore.instance();
|
static propTypes = {
|
||||||
modalStore = ModalStore.instance();
|
dappId: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
onRegister: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
dappsStore = DappsStore.get();
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
if (!this.modalStore.showingRegister) {
|
const { onClose, onRegister } = this.props;
|
||||||
return null;
|
const actions = [
|
||||||
}
|
{ type: 'close', label: 'No, Cancel' },
|
||||||
|
{ type: 'confirm', label: 'Yes, Register', warning: true }
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
buttons={ this.renderButtons() }
|
actions={ actions }
|
||||||
error={ this.modalStore.errorRegister }
|
header='Confirm Application Registration'
|
||||||
header={ HEADERS[this.modalStore.stepRegister] }
|
onClose={ onClose }
|
||||||
|
onConfirm={ onRegister }
|
||||||
|
secondary
|
||||||
>
|
>
|
||||||
{ 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 }>
|
<div className={ styles.section }>
|
||||||
Your application has been registered in the registry.
|
You are about to register a new distributed application on the network, the details of
|
||||||
</div>
|
this application is given below. This will require a non-refundable fee
|
||||||
</div>
|
of { api.util.fromWei(this.dappsStore.fee).toFormat(3) } <small>ETH</small>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
||||||
<div className={ styles.section }>
|
<div className={ styles.section }>
|
||||||
<div className={ styles.heading }>
|
<div className={ styles.heading }>
|
||||||
Unique assigned application identifier
|
Unique assigned application identifier
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{ this.dappsStore.wipApp.id }
|
{ this.props.dappId }
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderStepWait (waitingFor) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className={ styles.section }>
|
|
||||||
{ waitingFor }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClickClose = () => {
|
|
||||||
this.modalStore.hideRegister();
|
|
||||||
}
|
|
||||||
|
|
||||||
onClickConfirmYes = () => {
|
|
||||||
this.modalStore.doRegister();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,160 +14,71 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
|
||||||
|
|
||||||
import DappsStore from '../dappsStore';
|
|
||||||
import ModalStore from '../modalStore';
|
|
||||||
|
|
||||||
import Button from '../Button';
|
|
||||||
import Modal from '../Modal';
|
import Modal from '../Modal';
|
||||||
|
|
||||||
import styles from '../Modal/modal.css';
|
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 {
|
export default class ModalUpdate extends Component {
|
||||||
dappsStore = DappsStore.instance();
|
static propTypes = {
|
||||||
modalStore = ModalStore.instance();
|
dappId: PropTypes.string.isRequired,
|
||||||
|
updates: PropTypes.object.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
onConfirm: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
if (!this.modalStore.showingUpdate) {
|
const { dappId, onClose, onConfirm } = this.props;
|
||||||
return null;
|
const actions = [
|
||||||
}
|
{ type: 'close', label: 'No, Cancel' },
|
||||||
|
{ type: 'confirm', label: 'Yes, Update', warning: true }
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
buttons={ this.renderButtons() }
|
actions={ actions }
|
||||||
error={ this.modalStore.errorUpdate }
|
header='Confirm Application Update'
|
||||||
header={ HEADERS[this.modalStore.stepUpdate] }
|
onClose={ onClose }
|
||||||
|
onConfirm={ onConfirm }
|
||||||
|
secondary
|
||||||
>
|
>
|
||||||
{ 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 }>
|
<div className={ styles.section }>
|
||||||
Your application metadata has been updated in the registry.
|
You are about to update the application details in the registry,
|
||||||
</div>
|
the details of these updates are given below. Please note that each
|
||||||
</div>
|
update will generate a seperate transaction.
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
||||||
<div className={ styles.section }>
|
<div className={ styles.section }>
|
||||||
<div className={ styles.heading }>
|
<div className={ styles.heading }>
|
||||||
Application identifier
|
Application identifier
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{ this.dappsStore.wipApp.id }
|
{ dappId }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ this.renderChanges() }
|
{ this.renderChanges() }
|
||||||
</div>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderChanges () {
|
renderChanges () {
|
||||||
return ['content', 'image', 'manifest']
|
const { updates } = this.props;
|
||||||
.filter((type) => this.dappsStore.wipApp[`${type}Changed`])
|
|
||||||
|
return Object.keys(updates)
|
||||||
.map((type) => {
|
.map((type) => {
|
||||||
return (
|
return (
|
||||||
<div className={ styles.section } key={ `${type}Update` }>
|
<div
|
||||||
|
className={ styles.section }
|
||||||
|
key={ `${type}Update` }
|
||||||
|
>
|
||||||
<div className={ styles.heading }>
|
<div className={ styles.heading }>
|
||||||
Updates to { type } hash
|
Updates to { type }
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>{ this.dappsStore.wipApp[`${type}Hash`] || '(removed)' }</div>
|
<div>{ updates[type] || '(removed)' }</div>
|
||||||
<div className={ styles.hint }>
|
|
||||||
{ this.dappsStore.wipApp[`${type}Url`] || 'current url to be removed from registry' }
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderStepWait (waitingFor) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className={ styles.section }>
|
|
||||||
{ waitingFor }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClickClose = () => {
|
|
||||||
this.modalStore.hideUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
onClickYes = () => {
|
|
||||||
this.modalStore.doUpdate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,20 +14,27 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import DappsStore from '../dappsStore';
|
import DappsStore from '../dappsStore';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class SelectAccount extends Component {
|
export default class SelectAccount extends Component {
|
||||||
dappsStore = DappsStore.instance();
|
dappsStore = DappsStore.get();
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
onSelect: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
const { value } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
value={ this.dappsStore.currentAccount.address }
|
value={ value }
|
||||||
onChange={ this.onSelect }
|
onChange={ this.handleSelect }
|
||||||
>
|
>
|
||||||
{ this.renderOptions() }
|
{ this.renderOptions() }
|
||||||
</select>
|
</select>
|
||||||
@ -44,7 +51,7 @@ export default class SelectAccount extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelect = (event) => {
|
handleSelect = (event) => {
|
||||||
this.dappsStore.setCurrentAccount(event.target.value);
|
this.props.onSelect && this.props.onSelect(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
// Copyright 2015-2017 Parity Technologies (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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.dappsStore.currentApp) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
17
js/src/dapps/dappreg/Transactions/index.js
Normal file
17
js/src/dapps/dappreg/Transactions/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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 './transactions';
|
95
js/src/dapps/dappreg/Transactions/transactions.css
Normal file
95
js/src/dapps/dappreg/Transactions/transactions.css
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.container {
|
||||||
|
bottom: 4rem;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
z-index: 150;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
width: 30rem;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: 'Roboto Mono', monospace;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
position: absolute;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
background-color: rgba(40, 40, 40, 0.95);
|
||||||
|
color: white;
|
||||||
|
left: 0.5rem;
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
max-width: 20rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
background-color: rgba(215, 215, 215, 0.95);
|
||||||
|
color: black;
|
||||||
|
max-width: 5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(80, 80, 80, 0.95);
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
padding: 1rem 0.75rem;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
background-color: rgba(255, 68, 68, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pending {
|
||||||
|
background-color: rgba(243, 156, 18, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.confirmed {
|
||||||
|
background-color: rgba(39, 174, 96, 0.95);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
js/src/dapps/dappreg/Transactions/transactions.js
Normal file
136
js/src/dapps/dappreg/Transactions/transactions.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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 styles from './transactions.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class Transactions extends Component {
|
||||||
|
dappsStore = DappsStore.get();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { transactions } = this.dappsStore;
|
||||||
|
const displayedTransactions = Object.values(transactions)
|
||||||
|
.filter((tx) => !tx.hide)
|
||||||
|
.sort((txA, txB) => txB.start - txA.start);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.container }>
|
||||||
|
{ displayedTransactions.map((transaction) => this.renderTransaction(transaction)) }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTransaction (transaction) {
|
||||||
|
const { error, name, requestId, start, transactionHash, transactionReceipt } = transaction;
|
||||||
|
|
||||||
|
const date = new Date(start);
|
||||||
|
const isError = !!error;
|
||||||
|
const isPendingNetwork = transactionHash && !transactionReceipt;
|
||||||
|
const isConfirmed = !!transactionReceipt;
|
||||||
|
|
||||||
|
const transactionClasses = [ styles.content ];
|
||||||
|
const handleHideTransaction = (event) => this.handleHideTransaction(event, requestId);
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
transactionClasses.push(styles.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPendingNetwork) {
|
||||||
|
transactionClasses.push(styles.pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isConfirmed) {
|
||||||
|
transactionClasses.push(styles.confirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={ styles.transaction }
|
||||||
|
key={ requestId }
|
||||||
|
>
|
||||||
|
<div className={ styles.header }>
|
||||||
|
{
|
||||||
|
name
|
||||||
|
? (
|
||||||
|
<div
|
||||||
|
className={ styles.name }
|
||||||
|
title={ name }
|
||||||
|
>
|
||||||
|
{ name }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
className={ styles.date }
|
||||||
|
title={ date.toISOString() }
|
||||||
|
>
|
||||||
|
{ date.toLocaleTimeString() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={ transactionClasses.join(' ') }
|
||||||
|
onClick={ handleHideTransaction }
|
||||||
|
>
|
||||||
|
{ this.renderTransactionContent(transaction) }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTransactionContent (transaction) {
|
||||||
|
const { error, transactionHash, transactionReceipt } = transaction;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ error.text || error.message || error.toString() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactionReceipt) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Transaction mined at block { transactionReceipt.blockNumber.toFormat(0) }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactionHash) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Transaction sent to network with hash { transactionHash }..
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Transaction waiting to be signed...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHideTransaction = (event, requestId) => {
|
||||||
|
this.dappsStore.updateTransaction(requestId, { hide: true });
|
||||||
|
}
|
||||||
|
}
|
@ -15,11 +15,13 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import '../_colors.css';
|
||||||
|
|
||||||
.warning {
|
.warning {
|
||||||
background: #f44;
|
background: $warning-bg;
|
||||||
border-top-right-radius: 0.25em;
|
border-top-right-radius: 0.25em;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
color: #fff;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -15,37 +15,41 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
|
||||||
|
|
||||||
import { api } from '../parity';
|
import { api } from '../parity';
|
||||||
import DappsStore from '../dappsStore';
|
import DappsStore from '../dappsStore';
|
||||||
import ModalStore from '../modalStore';
|
|
||||||
|
|
||||||
import styles from './warning.css';
|
import styles from './warning.css';
|
||||||
|
|
||||||
@observer
|
|
||||||
export default class Warning extends Component {
|
export default class Warning extends Component {
|
||||||
dappsStore = DappsStore.instance();
|
dappsStore = DappsStore.get();
|
||||||
modalStore = ModalStore.instance();
|
|
||||||
|
state = {
|
||||||
|
show: true
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
if (!this.modalStore.showingWarning) {
|
if (!this.state.show) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.warning } onClick={ this.onClose }>
|
<div className={ styles.warning } onClick={ this.onClose }>
|
||||||
<div>
|
<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.
|
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>
|
||||||
<div>
|
<div>
|
||||||
A non-refundable fee of { api.util.fromWei(this.dappsStore.fee).toFormat(3) }<small>ETH</small> is required for any registration.
|
A non-refundable fee
|
||||||
|
of { api.util.fromWei(this.dappsStore.fee).toFormat(3) } <small>ETH</small> is required
|
||||||
|
for any registration.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
this.modalStore.hideWarning();
|
this.setState({ show: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
js/src/dapps/dappreg/_colors.css
Normal file
30
js/src/dapps/dappreg/_colors.css
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/* Copyright 2015-2017 Parity Technologies (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$blue: rgb(41, 128, 185);
|
||||||
|
$disabled-bg: #aaa;
|
||||||
|
$warning-bg: #e44;
|
||||||
|
$modal-bg: rgba(40, 40, 40, 0.75);
|
||||||
|
|
||||||
|
$background-color: #eee;
|
||||||
|
$text-color: #333;
|
||||||
|
$loading-color: #999;
|
||||||
|
|
||||||
|
$code-bg: #002b36;
|
||||||
|
$code-color: #b58900;
|
||||||
|
$code-title-bg: #073642;
|
||||||
|
$code-title-color: #859900;
|
@ -15,5 +15,8 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.app {
|
.bezier-transform {
|
||||||
|
transition-duration: 0.1s;
|
||||||
|
transition-property: all;
|
||||||
|
transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
|
||||||
}
|
}
|
153
js/src/dapps/dappreg/dappStore.js
Normal file
153
js/src/dapps/dappreg/dappStore.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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, computed, observable, transaction } from 'mobx';
|
||||||
|
|
||||||
|
export default class DappStore {
|
||||||
|
@observable id = null;
|
||||||
|
@observable content = null;
|
||||||
|
@observable image = null;
|
||||||
|
@observable manifest = null;
|
||||||
|
@observable owner = null;
|
||||||
|
@observable isOwner = false;
|
||||||
|
|
||||||
|
@observable isEditing = false;
|
||||||
|
@observable wip = null;
|
||||||
|
|
||||||
|
contractOwner = '';
|
||||||
|
isContractOwner = false;
|
||||||
|
|
||||||
|
constructor (data) {
|
||||||
|
const { id, content = {}, image = {}, manifest = {}, owner = {}, isOwner = false, contractOwner = '', isContractOwner = false } = data;
|
||||||
|
|
||||||
|
transaction(() => {
|
||||||
|
this.id = id;
|
||||||
|
this.content = content;
|
||||||
|
this.image = image;
|
||||||
|
this.manifest = manifest;
|
||||||
|
this.owner = owner;
|
||||||
|
this.isOwner = isOwner;
|
||||||
|
|
||||||
|
this.copyToWip();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.contractOwner = contractOwner;
|
||||||
|
this.isContractOwner = isContractOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get canSave () {
|
||||||
|
if (!this.wip) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { content, image, manifest, owner } = this.wip;
|
||||||
|
const fields = [ content, image, manifest, owner ];
|
||||||
|
|
||||||
|
const hasError = !!fields.find((field) => field.error);
|
||||||
|
const hasChanged = !!fields.find((field) => field.changed);
|
||||||
|
const isEditMode = this.isEditing;
|
||||||
|
|
||||||
|
return isEditMode && hasChanged && !hasError;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action copyToWip = () => {
|
||||||
|
const defaults = {
|
||||||
|
changed: false,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const wip = {
|
||||||
|
id: this.id,
|
||||||
|
content: {
|
||||||
|
...defaults,
|
||||||
|
url: this.content.url
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
...defaults,
|
||||||
|
url: this.image.url
|
||||||
|
},
|
||||||
|
manifest: {
|
||||||
|
...defaults,
|
||||||
|
url: this.manifest.url
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
...defaults,
|
||||||
|
address: this.owner.address
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.wip = { ...wip };
|
||||||
|
}
|
||||||
|
|
||||||
|
@action handleChange = (details) => {
|
||||||
|
if (!this.isEditing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wip = {
|
||||||
|
...this.wip,
|
||||||
|
...details
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.wip;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action handleSave = () => {
|
||||||
|
const updates = {};
|
||||||
|
|
||||||
|
if (this.wip.content.url !== this.content.url) {
|
||||||
|
updates.content = this.wip.content.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wip.image.url !== this.image.url) {
|
||||||
|
updates.image = this.wip.image.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wip.manifest.url !== this.manifest.url) {
|
||||||
|
updates.manifest = this.wip.manifest.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wip.owner.address !== this.owner.address) {
|
||||||
|
updates.owner = this.wip.owner.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setEditing = (mode) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.isEditing = mode;
|
||||||
|
this.copyToWip();
|
||||||
|
});
|
||||||
|
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
update = (updates) => {
|
||||||
|
const { image, content } = updates;
|
||||||
|
const changes = {};
|
||||||
|
|
||||||
|
if (image && image !== this.wip.image.url) {
|
||||||
|
changes.image = { url: image, changed: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content && content !== this.wip.content.url) {
|
||||||
|
changes.content = { url: content, changed: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.handleChange(changes);
|
||||||
|
}
|
||||||
|
}
|
@ -16,11 +16,15 @@
|
|||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { action, computed, observable, transaction } from 'mobx';
|
import { action, computed, observable, transaction } from 'mobx';
|
||||||
|
import { flatten } from 'lodash';
|
||||||
|
|
||||||
import * as abis from '~/contracts/abi';
|
import * as abis from '~/contracts/abi';
|
||||||
|
import Contracts from '~/contracts';
|
||||||
import builtins from '~/views/Dapps/builtin.json';
|
import builtins from '~/views/Dapps/builtin.json';
|
||||||
|
import Dapp from './dappStore.js';
|
||||||
|
import { deleteDapp, registerDapp, updateDapp } from './utils';
|
||||||
|
|
||||||
import { api } from './parity';
|
import { api, trackRequest } from './parity';
|
||||||
|
|
||||||
let instance = null;
|
let instance = null;
|
||||||
|
|
||||||
@ -28,23 +32,22 @@ export default class DappsStore {
|
|||||||
@observable accounts = [];
|
@observable accounts = [];
|
||||||
@observable apps = [];
|
@observable apps = [];
|
||||||
@observable contractOwner = null;
|
@observable contractOwner = null;
|
||||||
@observable currentAccount = null;
|
|
||||||
@observable currentApp = null;
|
|
||||||
@observable count = 0;
|
@observable count = 0;
|
||||||
@observable fee = new BigNumber(0);
|
@observable fee = new BigNumber(0);
|
||||||
@observable isContractOwner = false;
|
@observable isContractOwner = false;
|
||||||
@observable isEditing = false;
|
|
||||||
@observable isLoading = true;
|
@observable isLoading = true;
|
||||||
@observable isNew = false;
|
@observable transactions = {};
|
||||||
@observable wipApp = null;
|
|
||||||
|
|
||||||
|
_instanceGhh = null;
|
||||||
|
_instanceReg = null;
|
||||||
|
_registry = null;
|
||||||
_startTime = Date.now();
|
_startTime = Date.now();
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this._loadDapps();
|
this._loadDapps();
|
||||||
}
|
}
|
||||||
|
|
||||||
static instance () {
|
static get () {
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
instance = new DappsStore();
|
instance = new DappsStore();
|
||||||
}
|
}
|
||||||
@ -52,141 +55,69 @@ export default class DappsStore {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get canSave () {
|
createDappId () {
|
||||||
const app = this.wipApp;
|
return api.util.sha3(`${this._startTime}_${Date.now()}`);
|
||||||
|
|
||||||
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 () {
|
@computed get ownedCount () {
|
||||||
return (this.apps.filter((app) => app.isOwner) || []).length;
|
return this.ownDapps.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action copyToWip = () => {
|
@computed get ownDapps () {
|
||||||
let wipApp;
|
return this.apps.filter((app) => app.isOwner);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isNew) {
|
@computed get otherDapps () {
|
||||||
wipApp = {
|
return this.apps.filter((app) => !app.isOwner);
|
||||||
id: api.util.sha3(`${this._startTime}_${Date.now()}`),
|
}
|
||||||
contentHash: null,
|
|
||||||
contentUrl: null,
|
@action sortApps = () => {
|
||||||
imageHash: null,
|
// Sort dapps per name, then per id
|
||||||
imageUrl: null,
|
const sort = (a, b) => {
|
||||||
manifestHash: null,
|
if (a.name && b.name) {
|
||||||
manifestUrl: null
|
return a.name.localeCompare(b.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.name) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.name) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.id - b.id;
|
||||||
};
|
};
|
||||||
} 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(() => {
|
transaction(() => {
|
||||||
Object
|
const ownDapps = this.ownDapps.sort(sort);
|
||||||
.keys(details)
|
const otherDapps = this.otherDapps.sort(sort);
|
||||||
.forEach((key) => {
|
|
||||||
this.wipApp[key] = details[key];
|
this.apps = ownDapps.concat(otherDapps);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.wipApp;
|
@action setApps = (dapps) => {
|
||||||
}
|
const filteredDapps = dapps.filter((dapp) => {
|
||||||
|
return new BigNumber(dapp.id).gt(0);
|
||||||
|
});
|
||||||
|
|
||||||
@action sortApps = (apps = this.apps) => {
|
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
const ownApps = apps
|
this.apps = filteredDapps;
|
||||||
.filter((app) => app.isOwner)
|
this.sortApps();
|
||||||
.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);
|
|
||||||
|
|
||||||
if (this.apps.length) {
|
|
||||||
this.currentApp = this.apps[0];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action setApps = (apps) => {
|
@action refreshApp = (dappId) => {
|
||||||
this.sortApps(apps.filter((app) => {
|
const dapp = this.apps.find((dapp) => dapp.id === dappId);
|
||||||
const bnid = new BigNumber(app.id);
|
|
||||||
|
|
||||||
return bnid.gt(0);
|
this._loadDapp(dapp);
|
||||||
}));
|
|
||||||
|
|
||||||
return this.apps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action _addApp = (app) => {
|
@action removeApp = (dappId) => {
|
||||||
transaction(() => {
|
const dapps = this.apps.filter((dapp) => dapp.id !== dappId);
|
||||||
this.setApps(this.apps.concat([app]));
|
|
||||||
this.setCurrentApp(app.id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@action addApp = (appId, account) => {
|
this.setApps(dapps);
|
||||||
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) => {
|
@action setAccounts = (accountsInfo) => {
|
||||||
@ -199,8 +130,6 @@ export default class DappsStore {
|
|||||||
account.address = address;
|
account.address = address;
|
||||||
return account;
|
return account;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.currentAccount = this.accounts[0];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.accounts;
|
return this.accounts;
|
||||||
@ -214,30 +143,11 @@ export default class DappsStore {
|
|||||||
return 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) => {
|
@action setCount = (count) => {
|
||||||
this.count = count;
|
this.count = count;
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action setEditing = (mode) => {
|
|
||||||
transaction(() => {
|
|
||||||
this.isEditing = mode;
|
|
||||||
this.copyToWip();
|
|
||||||
});
|
|
||||||
|
|
||||||
return mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action setFee = (fee) => {
|
@action setFee = (fee) => {
|
||||||
this.fee = fee;
|
this.fee = fee;
|
||||||
return fee;
|
return fee;
|
||||||
@ -248,13 +158,106 @@ export default class DappsStore {
|
|||||||
return loading;
|
return loading;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action setNew = (mode) => {
|
@action updateTransaction = (requestId, nextData) => {
|
||||||
transaction(() => {
|
const prevTransaction = this.transactions[requestId] || { requestId };
|
||||||
this.isNew = mode;
|
const nextTransaction = {
|
||||||
this.copyToWip();
|
...prevTransaction,
|
||||||
|
hide: false,
|
||||||
|
...nextData
|
||||||
|
};
|
||||||
|
|
||||||
|
this.transactions = {
|
||||||
|
...this.transactions,
|
||||||
|
[ requestId ]: nextTransaction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchRegistryData (dapp) {
|
||||||
|
const ownerAddress = (dapp.wip && dapp.wip.owner.address) || dapp.owner.address;
|
||||||
|
|
||||||
|
this._registry.reverse
|
||||||
|
.call({}, [ ownerAddress ])
|
||||||
|
.then((name) => {
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = api.util.sha3.text(name);
|
||||||
|
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this._registry.get.call({}, [ key, 'IMG' ])
|
||||||
|
.then((bytes) => api.util.bytesToHex(bytes))
|
||||||
|
.then((hash) => this._instanceGhh.entries.call({}, [ hash ])),
|
||||||
|
this._registry.get.call({}, [ key, 'CONTENT' ])
|
||||||
|
.then((bytes) => api.util.bytesToHex(bytes))
|
||||||
|
.then((hash) => this._instanceGhh.entries.call({}, [ hash ]))
|
||||||
|
])
|
||||||
|
.then(([ imageGHH, contentGHH ]) => {
|
||||||
|
const imageUrl = imageGHH[0];
|
||||||
|
const contentUrl = contentGHH[0];
|
||||||
|
|
||||||
|
return dapp.update({
|
||||||
|
image: imageUrl,
|
||||||
|
content: contentUrl
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
register (dappId) {
|
||||||
|
const dappRegInstance = this._instanceReg;
|
||||||
|
const dappRegFee = this.fee;
|
||||||
|
|
||||||
|
return registerDapp(dappId, dappRegInstance, dappRegFee)
|
||||||
|
.then((request) => this.trackRequest(request, `Registering ${dappId}`))
|
||||||
|
.then(() => this._loadDapps());
|
||||||
|
}
|
||||||
|
|
||||||
|
delete (dapp) {
|
||||||
|
const dappRegInstance = this._instanceReg;
|
||||||
|
|
||||||
|
return deleteDapp(dapp, dappRegInstance)
|
||||||
|
.then((request) => this.trackRequest(request, `Deleting ${dapp.id}`))
|
||||||
|
.then(() => this._loadDapps());
|
||||||
|
}
|
||||||
|
|
||||||
|
update (dappId, dappOwner, updates) {
|
||||||
|
const dappRegInstance = this._instanceReg;
|
||||||
|
const ghhRegInstance = this._instanceGhh;
|
||||||
|
|
||||||
|
const promises = updateDapp(dappId, dappOwner, updates, dappRegInstance, ghhRegInstance);
|
||||||
|
const handledPromises = promises.map((promise) => {
|
||||||
|
return promise
|
||||||
|
.then((requests) => {
|
||||||
|
const requestPromises = flatten([].concat(requests))
|
||||||
|
.filter((request) => request)
|
||||||
|
.map((request) => this.trackRequest(request.id, request.name));
|
||||||
|
|
||||||
|
return Promise.all(requestPromises);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const randomRequestId = api.util.sha3(Date.now()).slice(0, 5);
|
||||||
|
|
||||||
|
this.updateTransaction(randomRequestId, { start: Date.now(), error });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return mode;
|
return Promise.all(handledPromises)
|
||||||
|
.then(() => this._loadDapps());
|
||||||
|
}
|
||||||
|
|
||||||
|
trackRequest (requestId, name) {
|
||||||
|
const statusCallback = (error, data) => {
|
||||||
|
if (error) {
|
||||||
|
return this.updateTransaction(requestId, { error });
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.updateTransaction(requestId, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateTransaction(requestId, { name, start: Date.now() });
|
||||||
|
return trackRequest(requestId, statusCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
lookupHash (hash) {
|
lookupHash (hash) {
|
||||||
@ -310,26 +313,30 @@ export default class DappsStore {
|
|||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
})
|
})
|
||||||
.then((appsInfo) => {
|
.then((dappsInfo) => {
|
||||||
return Promise.all(
|
const dapps = dappsInfo.reduce((dapps, [dappId, ownerAddress]) => {
|
||||||
this
|
const id = api.util.bytesToHex(dappId);
|
||||||
.setApps(appsInfo.map(([appId, owner]) => {
|
const owner = this.accounts.find((account) => account.address === ownerAddress);
|
||||||
const isOwner = !!this.accounts.find((account) => account.address === owner);
|
const isOwner = !!owner;
|
||||||
const account = this.accounts.find((account) => account.address === owner);
|
const dapp = {
|
||||||
const id = api.util.bytesToHex(appId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
id,
|
||||||
owner,
|
owner: owner || { address: ownerAddress },
|
||||||
ownerName: account ? account.name : owner,
|
isOwner
|
||||||
isOwner,
|
|
||||||
name: `- ${id}`
|
|
||||||
};
|
};
|
||||||
}))
|
|
||||||
.map(this._loadDapp)
|
dapps[id] = dapp;
|
||||||
);
|
return dapps;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const promises = Object.values(dapps)
|
||||||
|
// Only show dapps with id and owners
|
||||||
|
.filter((dapp) => dapp.id && dapp.owner && !/^(0x)?0*$/.test(dapp.owner.address))
|
||||||
|
.map((dapp) => this._loadDapp(dapp));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then((dapps) => {
|
||||||
|
this.setApps(dapps);
|
||||||
this.sortApps();
|
this.sortApps();
|
||||||
this.setLoading(false);
|
this.setLoading(false);
|
||||||
})
|
})
|
||||||
@ -338,12 +345,14 @@ export default class DappsStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadDapp = (app) => {
|
_loadDapp = (dappData) => {
|
||||||
|
const { id, owner, isOwner } = dappData;
|
||||||
|
|
||||||
return Promise
|
return Promise
|
||||||
.all([
|
.all([
|
||||||
this._loadMeta(app.id, 'CONTENT'),
|
this._loadMeta(id, 'CONTENT'),
|
||||||
this._loadMeta(app.id, 'IMG'),
|
this._loadMeta(id, 'IMG'),
|
||||||
this._loadMeta(app.id, 'MANIFEST')
|
this._loadMeta(id, 'MANIFEST')
|
||||||
])
|
])
|
||||||
.then(([contentHash, imageHash, manifestHash]) => {
|
.then(([contentHash, imageHash, manifestHash]) => {
|
||||||
return Promise
|
return Promise
|
||||||
@ -354,25 +363,47 @@ export default class DappsStore {
|
|||||||
])
|
])
|
||||||
.then(([contentUrl, imageUrl, manifestUrl]) => {
|
.then(([contentUrl, imageUrl, manifestUrl]) => {
|
||||||
return this
|
return this
|
||||||
._loadManifest(app.id, manifestHash)
|
._loadManifest(id, manifestHash, manifestUrl)
|
||||||
.then((manifest) => {
|
.then((manifestContent) => {
|
||||||
this.setAppInfo(app, {
|
const content = {
|
||||||
manifest,
|
hash: contentHash,
|
||||||
manifestHash,
|
url: contentUrl
|
||||||
manifestUrl,
|
};
|
||||||
contentHash,
|
|
||||||
contentUrl,
|
|
||||||
imageHash,
|
|
||||||
imageUrl,
|
|
||||||
name: (manifest && manifest.name) || `- ${app.id}`
|
|
||||||
});
|
|
||||||
|
|
||||||
return app;
|
const image = {
|
||||||
|
hash: imageHash,
|
||||||
|
url: imageUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
const manifest = {
|
||||||
|
content: manifestContent,
|
||||||
|
hash: manifestHash,
|
||||||
|
url: manifestUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
return { content, image, manifest };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Store:loadDapp', error);
|
console.error('dappsStore::loadDapp', error);
|
||||||
|
return {};
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
const { content, image, manifest } = data;
|
||||||
|
|
||||||
|
const dapp = new Dapp({
|
||||||
|
contractOwner: this.contractOwner,
|
||||||
|
isContractOwner: this.isContractOwner,
|
||||||
|
id,
|
||||||
|
content,
|
||||||
|
image,
|
||||||
|
manifest,
|
||||||
|
owner,
|
||||||
|
isOwner
|
||||||
|
});
|
||||||
|
|
||||||
|
return dapp;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,7 +429,9 @@ export default class DappsStore {
|
|||||||
|
|
||||||
if (builtin) {
|
if (builtin) {
|
||||||
return Promise.resolve(builtin);
|
return Promise.resolve(builtin);
|
||||||
} else if (!manifestHash) {
|
}
|
||||||
|
|
||||||
|
if (!manifestHash) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,11 +486,10 @@ export default class DappsStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_loadRegistry () {
|
_loadRegistry () {
|
||||||
return api.parity
|
return Contracts.create(api).registry
|
||||||
.registryAddress()
|
.fetchContract()
|
||||||
.then((registryAddress) => {
|
.then((contract) => {
|
||||||
console.log(`the registry was found at ${registryAddress}`);
|
this._registry = contract.instance;
|
||||||
this._registry = api.newContract(abis.registry, registryAddress).instance;
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Store:loadRegistry', error);
|
console.error('Store:loadRegistry', error);
|
||||||
@ -472,9 +504,11 @@ export default class DappsStore {
|
|||||||
])
|
])
|
||||||
.then(([dappregAddress, ghhAddress]) => {
|
.then(([dappregAddress, ghhAddress]) => {
|
||||||
console.log(`dappreg was found at ${dappregAddress}`);
|
console.log(`dappreg was found at ${dappregAddress}`);
|
||||||
|
console.log(`githubhint was found at ${ghhAddress}`);
|
||||||
|
|
||||||
this._contractReg = api.newContract(abis.dappreg, dappregAddress);
|
this._contractReg = api.newContract(abis.dappreg, dappregAddress);
|
||||||
this._instanceReg = this._contractReg.instance;
|
this._instanceReg = this._contractReg.instance;
|
||||||
console.log(`githubhint was found at ${ghhAddress}`);
|
|
||||||
this._contractGhh = api.newContract(abis.githubhint, ghhAddress);
|
this._contractGhh = api.newContract(abis.githubhint, ghhAddress);
|
||||||
this._instanceGhh = this._contractGhh.instance;
|
this._instanceGhh = this._contractGhh.instance;
|
||||||
})
|
})
|
||||||
|
@ -1,266 +0,0 @@
|
|||||||
// Copyright 2015-2017 Parity Technologies (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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,16 +14,10 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const { api } = window.parity;
|
const api = window.parent.secureApi;
|
||||||
|
|
||||||
function trackRequest (requestPromise, statusCallback) {
|
function trackRequest (signerRequestId, statusCallback) {
|
||||||
return requestPromise
|
return api.pollMethod('parity_checkRequest', signerRequestId)
|
||||||
.then((signerRequestId) => {
|
|
||||||
console.log('trackRequest', `posted to signer with requestId ${signerRequestId.toString()}`);
|
|
||||||
statusCallback(null, { signerRequestId });
|
|
||||||
|
|
||||||
return api.pollMethod('parity_checkRequest', signerRequestId);
|
|
||||||
})
|
|
||||||
.then((transactionHash) => {
|
.then((transactionHash) => {
|
||||||
console.log('trackRequest', `received transaction hash ${transactionHash}`);
|
console.log('trackRequest', `received transaction hash ${transactionHash}`);
|
||||||
statusCallback(null, { transactionHash });
|
statusCallback(null, { transactionHash });
|
||||||
@ -41,9 +35,7 @@ function trackRequest (requestPromise, statusCallback) {
|
|||||||
statusCallback(null, { transactionReceipt });
|
statusCallback(null, { transactionReceipt });
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('trackRequest', error);
|
|
||||||
statusCallback(error);
|
statusCallback(error);
|
||||||
throw error;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
184
js/src/dapps/dappreg/utils.js
Normal file
184
js/src/dapps/dappreg/utils.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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 { api } from './parity';
|
||||||
|
|
||||||
|
export const INVALID_URL_HASH = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
|
||||||
|
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the given URL to a content hash,
|
||||||
|
* and checks if it is already registered in GHH
|
||||||
|
*/
|
||||||
|
export const urlToHash = (api, instance, url) => {
|
||||||
|
if (!url || !url.length) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.parity
|
||||||
|
.hashContent(url)
|
||||||
|
.catch((error) => {
|
||||||
|
const message = error.text || error.message || error.toString();
|
||||||
|
|
||||||
|
throw new Error(`${message} (${url})`);
|
||||||
|
})
|
||||||
|
.then((contentHash) => {
|
||||||
|
console.log('lookupHash', url, contentHash);
|
||||||
|
|
||||||
|
if (contentHash === INVALID_URL_HASH) {
|
||||||
|
throw new Error(`"${url}" is not a valid URL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance.entries
|
||||||
|
.call({}, [contentHash])
|
||||||
|
.then(([accountSlashRepo, commit, contentHashOwner]) => {
|
||||||
|
const registered = (contentHashOwner !== ZERO_ADDRESS);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hash: contentHash,
|
||||||
|
registered
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the given URL to GithubHint
|
||||||
|
* registry contract
|
||||||
|
*/
|
||||||
|
export const registerGHH = (instance, url, hash, owner) => {
|
||||||
|
const values = [ hash, url ];
|
||||||
|
const options = {
|
||||||
|
from: owner
|
||||||
|
};
|
||||||
|
|
||||||
|
return instance
|
||||||
|
.hintURL.estimateGas(options, values)
|
||||||
|
.then((gas) => {
|
||||||
|
options.gas = gas.mul(1.2).toFixed(0);
|
||||||
|
return instance.hintURL.postTransaction(options, values);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const registerDapp = (dappId, dappRegInstance) => {
|
||||||
|
const values = [ dappId ];
|
||||||
|
const options = {};
|
||||||
|
|
||||||
|
return dappRegInstance
|
||||||
|
.fee.call({}, [])
|
||||||
|
.then((fee) => {
|
||||||
|
options.value = fee;
|
||||||
|
|
||||||
|
return dappRegInstance
|
||||||
|
.register.estimateGas(options, values)
|
||||||
|
.then((gas) => {
|
||||||
|
options.gas = gas.mul(1.2).toFixed(0);
|
||||||
|
return dappRegInstance.register.postTransaction(options, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteDapp = (dapp, dappRegInstance) => {
|
||||||
|
const { id, owner } = dapp;
|
||||||
|
|
||||||
|
const fromAddress = dapp.isOwner
|
||||||
|
? owner.address
|
||||||
|
: dapp.contractOwner;
|
||||||
|
|
||||||
|
const values = [ id ];
|
||||||
|
const options = {
|
||||||
|
from: fromAddress
|
||||||
|
};
|
||||||
|
|
||||||
|
return dappRegInstance
|
||||||
|
.unregister.estimateGas(options, values)
|
||||||
|
.then((gas) => {
|
||||||
|
options.gas = gas.mul(1.2).toFixed(0);
|
||||||
|
|
||||||
|
return dappRegInstance.unregister.postTransaction(options, values);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateDappOwner = (dappId, dappOwner, nextOwnerAddress, dappRegInstance) => {
|
||||||
|
const options = {
|
||||||
|
from: dappOwner
|
||||||
|
};
|
||||||
|
|
||||||
|
const values = [ dappId, nextOwnerAddress ];
|
||||||
|
|
||||||
|
return dappRegInstance.setDappOwner
|
||||||
|
.estimateGas(options, values)
|
||||||
|
.then((gas) => {
|
||||||
|
options.gas = gas.mul(1.2);
|
||||||
|
|
||||||
|
return dappRegInstance.setDappOwner.postTransaction(options, values);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateDapp = (dappId, dappOwner, updates, dappRegInstance, ghhRegInstance) => {
|
||||||
|
const options = {
|
||||||
|
from: dappOwner
|
||||||
|
};
|
||||||
|
|
||||||
|
const types = {
|
||||||
|
content: 'CONTENT',
|
||||||
|
image: 'IMG',
|
||||||
|
manifest: 'MANIFEST'
|
||||||
|
};
|
||||||
|
|
||||||
|
const promises = Object
|
||||||
|
.keys(updates)
|
||||||
|
.map((type) => {
|
||||||
|
const key = types[type];
|
||||||
|
const url = updates[type];
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
promise = Promise.resolve([ null, '' ]);
|
||||||
|
} else {
|
||||||
|
promise = urlToHash(api, ghhRegInstance, url)
|
||||||
|
.then((ghhResult) => {
|
||||||
|
const { hash, registered } = ghhResult;
|
||||||
|
|
||||||
|
if (!registered) {
|
||||||
|
return registerGHH(ghhRegInstance, url, hash, dappOwner)
|
||||||
|
.then((requestId) => [ { id: requestId, name: `Registering ${url}` }, hash ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ null, hash ];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise
|
||||||
|
.then(([ ghhRequest, hash ]) => {
|
||||||
|
const values = [ dappId, key, hash ];
|
||||||
|
|
||||||
|
return dappRegInstance.setMeta.estimateGas(options, values)
|
||||||
|
.then((gas) => {
|
||||||
|
options.gas = gas.mul(1.2).toFixed(0);
|
||||||
|
return dappRegInstance.setMeta.postTransaction(options, values);
|
||||||
|
})
|
||||||
|
.then((requestId) => [ ghhRequest, { id: requestId, name: `Updating ${type} of ${dappId}` } ]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updates.owner) {
|
||||||
|
promises.push(updateDappOwner(updates.owner).then((reqId) => ({ id: reqId, name: `Updating owner of ${dappId}` })));
|
||||||
|
}
|
||||||
|
|
||||||
|
return promises;
|
||||||
|
};
|
||||||
|
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { registry as registryAbi, registry2 as registryAbi2 } from '~/contracts/abi';
|
import Contracts from '~/contracts';
|
||||||
|
|
||||||
import { api } from './parity.js';
|
import { api } from './parity.js';
|
||||||
import * as addresses from './addresses/actions.js';
|
import * as addresses from './addresses/actions.js';
|
||||||
@ -27,11 +27,6 @@ import * as reverse from './Reverse/actions.js';
|
|||||||
|
|
||||||
export { addresses, accounts, lookup, events, names, records, reverse };
|
export { addresses, accounts, lookup, events, names, records, reverse };
|
||||||
|
|
||||||
const REGISTRY_V1_HASHES = [
|
|
||||||
'0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten
|
|
||||||
'0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead
|
|
||||||
];
|
|
||||||
|
|
||||||
export const setNetVersion = (netVersion) => ({ type: 'set netVersion', netVersion });
|
export const setNetVersion = (netVersion) => ({ type: 'set netVersion', netVersion });
|
||||||
|
|
||||||
export const fetchIsTestnet = () => (dispatch) =>
|
export const fetchIsTestnet = () => (dispatch) =>
|
||||||
@ -48,36 +43,18 @@ export const fetchIsTestnet = () => (dispatch) =>
|
|||||||
|
|
||||||
export const setContract = (contract) => ({ type: 'set contract', contract });
|
export const setContract = (contract) => ({ type: 'set contract', contract });
|
||||||
|
|
||||||
export const fetchContract = () => (dispatch) =>
|
export const fetchContract = () => (dispatch) => {
|
||||||
api.parity
|
return Contracts.create(api).registry
|
||||||
.registryAddress()
|
.fetchContract()
|
||||||
.then((address) => {
|
.then((contract) => {
|
||||||
return api.eth
|
|
||||||
.getCode(address)
|
|
||||||
.then((code) => {
|
|
||||||
const codeHash = api.util.sha3(code);
|
|
||||||
const isVersion1 = REGISTRY_V1_HASHES.includes(codeHash);
|
|
||||||
|
|
||||||
console.log(`registry at ${address}, code ${codeHash}, version ${isVersion1 ? 1 : 2}`);
|
|
||||||
|
|
||||||
const contract = api.newContract(
|
|
||||||
isVersion1
|
|
||||||
? registryAbi
|
|
||||||
: registryAbi2,
|
|
||||||
address
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(setContract(contract));
|
dispatch(setContract(contract));
|
||||||
dispatch(fetchFee());
|
dispatch(fetchFee());
|
||||||
dispatch(fetchOwner());
|
dispatch(fetchOwner());
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((error) => {
|
||||||
console.error('could not fetch contract');
|
console.error('could not fetch contract', error);
|
||||||
if (err) {
|
|
||||||
console.error(err.stack);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const setFee = (fee) => ({ type: 'set fee', fee });
|
export const setFee = (fee) => ({ type: 'set fee', fee });
|
||||||
|
|
||||||
|
@ -170,6 +170,10 @@ export default class WalletsUtils {
|
|||||||
* to make unnecessary calls on non-wallet accounts
|
* to make unnecessary calls on non-wallet accounts
|
||||||
*/
|
*/
|
||||||
static isWallet (api, address) {
|
static isWallet (api, address) {
|
||||||
|
if (!address) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (!_cachedWalletLookup[address]) {
|
if (!_cachedWalletLookup[address]) {
|
||||||
const walletContract = new Contract(api, WalletAbi);
|
const walletContract = new Contract(api, WalletAbi);
|
||||||
|
|
||||||
|
@ -62,7 +62,8 @@
|
|||||||
"description": "Enables the registration and content management of dapps on the network",
|
"description": "Enables the registration and content management of dapps on the network",
|
||||||
"author": "Parity Team <admin@ethcore.io>",
|
"author": "Parity Team <admin@ethcore.io>",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"visible": false
|
"visible": false,
|
||||||
|
"secure": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "0x9042323cd85c6576992d211de34b3ecc183f15e4f639aa87859882f839c374e5",
|
"id": "0x9042323cd85c6576992d211de34b3ecc183f15e4f639aa87859882f839c374e5",
|
||||||
|
Loading…
Reference in New Issue
Block a user