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'; | ||||
| 
 | ||||
| const REGISTRY_V1_HASHES = [ | ||||
|   '0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten
 | ||||
|   '0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead
 | ||||
| ]; | ||||
| 
 | ||||
| export default class Registry { | ||||
|   _registryContract = null; | ||||
| 
 | ||||
|   constructor (api) { | ||||
|     this._api = api; | ||||
| 
 | ||||
| @ -43,11 +50,10 @@ export default class Registry { | ||||
| 
 | ||||
|     this._fetching = true; | ||||
| 
 | ||||
|     return this._api.parity | ||||
|       .registryAddress() | ||||
|       .then((address) => { | ||||
|     return this.fetchContract() | ||||
|       .then((contract) => { | ||||
|         this._fetching = false; | ||||
|         this._instance = this._api.newContract(abis.registry, address).instance; | ||||
|         this._instance = contract.instance; | ||||
| 
 | ||||
|         this._queue.forEach((queued) => { | ||||
|           queued.resolve(this._instance); | ||||
| @ -89,6 +95,47 @@ export default class Registry { | ||||
|       .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) { | ||||
|     const name = _name.toLowerCase(); | ||||
|     const sha3 = this._api.util.sha3.text(name); | ||||
|  | ||||
| @ -35,6 +35,9 @@ function create () { | ||||
|     } | ||||
|   }; | ||||
|   api = { | ||||
|     eth: { | ||||
|       getCode: sinon.stub().resolves('0x123456') | ||||
|     }, | ||||
|     parity: { | ||||
|       registryAddress: sinon.stub().resolves('testRegistryAddress') | ||||
|     }, | ||||
|  | ||||
| @ -17,6 +17,7 @@ | ||||
| import React from 'react'; | ||||
| import ReactDOM from 'react-dom'; | ||||
| import injectTapEventPlugin from 'react-tap-event-plugin'; | ||||
| import { AppContainer } from 'react-hot-loader'; | ||||
| 
 | ||||
| injectTapEventPlugin(); | ||||
| 
 | ||||
| @ -27,6 +28,21 @@ import '../../assets/fonts/RobotoMono/font.css'; | ||||
| import './style.css'; | ||||
| 
 | ||||
| ReactDOM.render( | ||||
|   <Application />, | ||||
|   <AppContainer> | ||||
|     <Application /> | ||||
|   </AppContainer>, | ||||
|   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/>. | ||||
| */ | ||||
| 
 | ||||
| @import '../_colors.css'; | ||||
| 
 | ||||
| .body { | ||||
|   color: #333; | ||||
|   background: #eee; | ||||
|   padding: 4.5em 0; | ||||
|   color: $text-color; | ||||
|   background: $background-color; | ||||
|   padding: 3em 0 6em; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .apps { | ||||
|   background: #fff; | ||||
|   background: white; | ||||
|   border-radius: 0.5em; | ||||
|   margin: 0 auto; | ||||
|   max-width: 980px; | ||||
| @ -39,9 +41,8 @@ | ||||
| } | ||||
| 
 | ||||
| .header { | ||||
|   background: #44e; | ||||
|   border-radius: 0 0 0.25em 0.25em; | ||||
|   color: #fff; | ||||
|   background: $blue; | ||||
|   color: white; | ||||
|   left: 0; | ||||
|   padding: 1em; | ||||
|   position: fixed; | ||||
| @ -54,5 +55,5 @@ | ||||
|   text-align: center; | ||||
|   padding-top: 5em; | ||||
|   font-size: 2em; | ||||
|   color: #999; | ||||
|   color: $loading-color; | ||||
| } | ||||
|  | ||||
| @ -19,18 +19,14 @@ import { observer } from 'mobx-react'; | ||||
| 
 | ||||
| import DappsStore from '../dappsStore'; | ||||
| 
 | ||||
| import ButtonBar from '../ButtonBar'; | ||||
| import Dapp from '../Dapp'; | ||||
| import ModalDelete from '../ModalDelete'; | ||||
| import ModalRegister from '../ModalRegister'; | ||||
| import ModalUpdate from '../ModalUpdate'; | ||||
| import SelectDapp from '../SelectDapp'; | ||||
| import Dapps from '../Dapps'; | ||||
| import Transactions from '../Transactions'; | ||||
| import Warning from '../Warning'; | ||||
| import styles from './application.css'; | ||||
| 
 | ||||
| @observer | ||||
| export default class Application extends Component { | ||||
|   dappsStore = DappsStore.instance(); | ||||
|   dappsStore = DappsStore.get(); | ||||
| 
 | ||||
|   render () { | ||||
|     if (this.dappsStore.isLoading) { | ||||
| @ -41,23 +37,32 @@ export default class Application extends Component { | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     const { ownDapps, otherDapps } = this.dappsStore; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.body }> | ||||
|         <div className={ styles.header }> | ||||
|           DAPP REGISTRY, a global view of distributed applications available on the network. Putting the puzzle together. | ||||
|         </div> | ||||
|         <div className={ styles.apps }> | ||||
|           <SelectDapp /> | ||||
|           <ButtonBar /> | ||||
|           <Dapp /> | ||||
| 
 | ||||
|         <div> | ||||
|           <Dapps | ||||
|             dapps={ ownDapps } | ||||
|             own | ||||
|             title='My Dapps' | ||||
|           /> | ||||
|           <Dapps | ||||
|             dapps={ otherDapps } | ||||
|             title='Other Dapps' | ||||
|           /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div className={ styles.footer }> | ||||
|           { this.dappsStore.count } applications registered, { this.dappsStore.ownedCount } owned by user | ||||
|         </div> | ||||
| 
 | ||||
|         <Transactions /> | ||||
|         <Warning /> | ||||
|         <ModalDelete /> | ||||
|         <ModalRegister /> | ||||
|         <ModalUpdate /> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @ -15,11 +15,16 @@ | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| @import '../_colors.css'; | ||||
| @import '../_utils.css'; | ||||
| 
 | ||||
| .button { | ||||
|   background: #44e; | ||||
|   composes: bezier-transform; | ||||
| 
 | ||||
|   background: $blue; | ||||
|   border: none; | ||||
|   border-radius: 0.25em; | ||||
|   color: #fff; | ||||
|   color: white; | ||||
|   cursor: pointer; | ||||
|   font-size: 1em; | ||||
|   margin: 1em 0.375em; | ||||
| @ -29,10 +34,14 @@ | ||||
|   &[disabled] { | ||||
|     opacity: 0.5; | ||||
|     cursor: default; | ||||
|     background: #aaa; | ||||
|     background: $disabled-bg; | ||||
|   } | ||||
| 
 | ||||
|   &[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, | ||||
|     label: PropTypes.string.isRequired, | ||||
|     warning: PropTypes.bool, | ||||
|     onClick: PropTypes.func.isRequired | ||||
|     onClick: PropTypes.func | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { className, disabled, label, warning } = this.props; | ||||
|     const classes = `${styles.button} ${className}`; | ||||
|     const classes = [ styles.button, className ]; | ||||
| 
 | ||||
|     return ( | ||||
|       <button | ||||
|         className={ classes } | ||||
|         className={ classes.join(' ') } | ||||
|         data-warning={ warning } | ||||
|         disabled={ disabled } | ||||
|         onClick={ this.onClick } | ||||
|         onClick={ this.handleClick } | ||||
|       > | ||||
|         { label } | ||||
|       </button> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   onClick = (event) => { | ||||
|   handleClick = (event) => { | ||||
|     if (this.props.disabled) { | ||||
|       event.preventDefault(); | ||||
|       event.stopPropagation(); | ||||
|       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
 | ||||
| // 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/>. | ||||
| */ | ||||
| 
 | ||||
| .buttonbar { | ||||
| .author, | ||||
| .version { | ||||
|   font-size: 0.75rem; | ||||
|   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
 | ||||
| // 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
 | ||||
| // 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%; | ||||
|   } | ||||
| 
 | ||||
|   &.withOverlay input { | ||||
|     padding-right: 3em; | ||||
|   } | ||||
| 
 | ||||
|   input { | ||||
|     padding-bottom: 1.5em; | ||||
| 
 | ||||
|  | ||||
| @ -29,8 +29,14 @@ export default class Input extends Component { | ||||
|   render () { | ||||
|     const { children, hint, label, overlay } = this.props; | ||||
| 
 | ||||
|     const inputClasses = [ styles.input ]; | ||||
| 
 | ||||
|     if (overlay) { | ||||
|       inputClasses.push(styles.withOverlay); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.input }> | ||||
|       <div className={ inputClasses.join(' ') }> | ||||
|         <label> | ||||
|           { label } | ||||
|         </label> | ||||
|  | ||||
| @ -15,95 +15,120 @@ | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| @import '../_colors.css'; | ||||
| @import '../_utils.css'; | ||||
| 
 | ||||
| .modal { | ||||
|   .body { | ||||
|     bottom: 0; | ||||
|     left: 0; | ||||
|     position: fixed; | ||||
|     right: 0; | ||||
|     top: 0; | ||||
|     text-align: center; | ||||
|     z-index: 50; | ||||
|   align-items: center; | ||||
|   background-color: $modal-bg; | ||||
|   bottom: 0; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   left: 0; | ||||
|   position: fixed; | ||||
|   right: 0; | ||||
|   top: 0; | ||||
|   z-index: 150; | ||||
| 
 | ||||
|   &.secondary { | ||||
|     align-items: flex-start; | ||||
|     z-index: 200; | ||||
| 
 | ||||
|     .content { | ||||
|       line-height: 1.5rem; | ||||
|       padding: 2rem; | ||||
|       text-align: center; | ||||
|     } | ||||
| 
 | ||||
|     .dialog { | ||||
|       background: #fff; | ||||
|       border-radius: 0 0 0.25em 0.25em; | ||||
|       margin: 0 auto; | ||||
|       max-width: 840px; | ||||
|       text-align: left; | ||||
|       width: 85vw; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|       .content { | ||||
|         line-height: 1.5em; | ||||
|         padding: 2em; | ||||
|         text-align: center; | ||||
| .close { | ||||
|   composes: bezier-transform; | ||||
| 
 | ||||
|         .section { | ||||
|           margin: 0; | ||||
|           padding: 0; | ||||
|   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; | ||||
| 
 | ||||
|           &.error { | ||||
|             color: #f44; | ||||
|           } | ||||
|         } | ||||
|   &:hover { | ||||
|     cursor: pointer; | ||||
|     opacity: 1; | ||||
| 
 | ||||
|         .section+.section { | ||||
|           margin-top: 1em; | ||||
|         } | ||||
|       } | ||||
|     .closeIcon { | ||||
|       transform: rotate(135deg); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|       .footer { | ||||
|         padding: 0.5em 1.625em; | ||||
|         text-align: right; | ||||
|       } | ||||
| .closeIcon { | ||||
|   composes: bezier-transform; | ||||
| 
 | ||||
|       .header { | ||||
|         background: #44e; | ||||
|         color: #fff; | ||||
|         opacity: 0.85; | ||||
|         padding: 1em; | ||||
|   height: 3rem; | ||||
|   width: 3rem; | ||||
|   transform: rotate(45deg); | ||||
| } | ||||
| 
 | ||||
|         &.error { | ||||
|           background: #e44; | ||||
|         } | ||||
|       } | ||||
| .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 { | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
| 
 | ||||
|     * { | ||||
|       overflow-x: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .overlay { | ||||
|     background: rgba(204, 204, 204, 0.7); | ||||
|     bottom: 0; | ||||
|     left: 0; | ||||
|     position: fixed; | ||||
|     right: 0; | ||||
|     top: 0; | ||||
|     z-index: 49; | ||||
|   .section + .section { | ||||
|     margin-top: 1em; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .account { | ||||
|   div { | ||||
|     display: inline-block; | ||||
|     vertical-align: top; | ||||
|   } | ||||
| 
 | ||||
|   img { | ||||
|     border-radius: 50%; | ||||
|     margin-right: 0.5em; | ||||
|   } | ||||
| .footer { | ||||
|   padding: 0.5em 1.625em; | ||||
|   text-align: right; | ||||
| } | ||||
| 
 | ||||
| .hint { | ||||
|   display: block !important; | ||||
|   color: #888; | ||||
|   font-size: 0.75em; | ||||
|   margin-top: -0.5em; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
|   white-space: nowrap; | ||||
| } | ||||
| .header { | ||||
|   background-color: $blue; | ||||
|   color: white; | ||||
|   min-height: 3rem; | ||||
| 
 | ||||
| .center { | ||||
|   text-align: center; | ||||
|   &, | ||||
|   & > * { | ||||
|     align-items: center; | ||||
|     display: flex; | ||||
|     flex: 0 0 auto; | ||||
|     flex-direction: row; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .heading { | ||||
|  | ||||
| @ -14,53 +14,147 @@ | ||||
| // 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 Button from '../Button'; | ||||
| 
 | ||||
| import styles from './modal.css'; | ||||
| import CloseImage from '~/../assets/images/dapps/close.svg'; | ||||
| 
 | ||||
| export default class Modal extends Component { | ||||
|   static propTypes = { | ||||
|     buttons: PropTypes.node, | ||||
|     actions: PropTypes.array, | ||||
|     children: PropTypes.node, | ||||
|     error: PropTypes.object, | ||||
|     header: PropTypes.string | ||||
|   } | ||||
|     header: PropTypes.node, | ||||
|     secondary: PropTypes.bool, | ||||
|     onClose: PropTypes.func.isRequired, | ||||
|     onConfirm: PropTypes.func | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|     actions: null, | ||||
|     secondary: false | ||||
|   }; | ||||
| 
 | ||||
|   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 ( | ||||
|       <div className={ styles.modal }> | ||||
|         <div className={ styles.overlay } /> | ||||
|         <div className={ styles.body }> | ||||
|           <div className={ styles.dialog }> | ||||
|             <div className={ `${styles.header} ${error ? styles.error : ''}` }> | ||||
|               { header } | ||||
|             </div> | ||||
|             <div className={ styles.content }> | ||||
|               { error ? this.renderError() : children } | ||||
|             </div> | ||||
|             <div className={ styles.footer }> | ||||
|               { buttons } | ||||
|       <div | ||||
|         className={ modalClasses.join(' ') } | ||||
|         onClick={ this.handleClose } | ||||
|         onKeyUp={ this.handleKeyPress } | ||||
|       > | ||||
|         <div | ||||
|           className={ styles.dialog } | ||||
|           onClick={ this.stopEvent } | ||||
|           ref={ this.handleSetRef } | ||||
|           tabIndex={ open ? 0 : null } | ||||
|         > | ||||
|           <div className={ styles.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 className={ styles.content }> | ||||
|             { children } | ||||
|           </div> | ||||
| 
 | ||||
|           { actions ? this.renderActions(actions) : null } | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderError () { | ||||
|     const { error } = this.props; | ||||
| 
 | ||||
|   renderActions (actions) { | ||||
|     return ( | ||||
|       <div> | ||||
|         <div className={ styles.section }> | ||||
|           Your operation failed to complete sucessfully. The following error was returned: | ||||
|         </div> | ||||
|         <div className={ `${styles.section} ${styles.error}` }> | ||||
|           { error.toString() } | ||||
|         </div> | ||||
|       <div className={ styles.footer }> | ||||
|         { actions.map((action) => { | ||||
|           let onClick = () => {}; | ||||
| 
 | ||||
|           switch (action.type) { | ||||
|             case 'confirm': | ||||
|               onClick = this.handleConfirm; | ||||
|               break; | ||||
| 
 | ||||
|             case 'close': | ||||
|               onClick = this.handleClose; | ||||
|               break; | ||||
|           } | ||||
| 
 | ||||
|           return ( | ||||
|             <Button | ||||
|               key={ action.type } | ||||
|               label={ action.label } | ||||
|               warning={ action.warning } | ||||
|               onClick={ onClick } | ||||
|             /> | ||||
|           ); | ||||
|         }) } | ||||
|       </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
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component } from 'react'; | ||||
| import { observer } from 'mobx-react'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { api } from '../parity'; | ||||
| import DappsStore from '../dappsStore'; | ||||
| import ModalStore from '../modalStore'; | ||||
| 
 | ||||
| import Button from '../Button'; | ||||
| import Modal from '../Modal'; | ||||
| 
 | ||||
| import styles from '../Modal/modal.css'; | ||||
| 
 | ||||
| const HEADERS = [ | ||||
|   'Error During Deletion', | ||||
|   'Confirm Application Deletion', | ||||
|   'Waiting for Signer Confirmation', | ||||
|   'Waiting for Transaction Receipt', | ||||
|   'Deletion Completed' | ||||
| ]; | ||||
| const STEP_ERROR = 0; | ||||
| const STEP_CONFIRM = 1; | ||||
| const STEP_SIGNER = 2; | ||||
| const STEP_TXRECEIPT = 3; | ||||
| const STEP_DONE = 4; | ||||
| 
 | ||||
| @observer | ||||
| export default class ModalDelete extends Component { | ||||
|   dappsStore = DappsStore.instance(); | ||||
|   modalStore = ModalStore.instance(); | ||||
|   static propTypes = { | ||||
|     dappId: PropTypes.string.isRequired, | ||||
|     onClose: PropTypes.func.isRequired, | ||||
|     onDelete: PropTypes.func.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     if (!this.modalStore.showingDelete) { | ||||
|       return null; | ||||
|     } | ||||
|     const { dappId, onClose, onDelete } = this.props; | ||||
|     const actions = [ | ||||
|       { type: 'close', label: 'No, Cancel' }, | ||||
|       { type: 'confirm', label: 'Yes, Delete', warning: true } | ||||
|     ]; | ||||
| 
 | ||||
|     return ( | ||||
|       <Modal | ||||
|         buttons={ this.renderButtons() } | ||||
|         error={ this.modalStore.errorDelete } | ||||
|         header={ HEADERS[this.modalStore.stepDelete] } | ||||
|         actions={ actions } | ||||
|         header='Confirm Application Deletion' | ||||
|         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 }> | ||||
|           Your application has been removed from the registry. | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderStepConfirm () { | ||||
|     return ( | ||||
|       <div> | ||||
|         <div className={ styles.section }> | ||||
|           You are about to remove a distributed application from the registry, the details of this application is given below. Removal does not return any fees, however the application will not be available to users anymore. | ||||
|         </div> | ||||
|         <div className={ styles.section }> | ||||
|           <div className={ styles.heading }> | ||||
|             Owner account | ||||
|           </div> | ||||
|           <div className={ styles.account }> | ||||
|             <img src={ api.util.createIdentityImg(this.dappsStore.currentApp.owner, 3) } /> | ||||
|             <div>{ this.dappsStore.currentApp.ownerName }</div> | ||||
|             <div className={ styles.address }>{ this.dappsStore.currentApp.owner }</div> | ||||
|           </div> | ||||
|           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 }> | ||||
|             Application identifier | ||||
|           </div> | ||||
|           <div> | ||||
|             { this.dappsStore.currentApp.id } | ||||
|             { dappId } | ||||
|           </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
 | ||||
| // 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 styles from '../Modal/modal.css'; | ||||
| 
 | ||||
| const HEADERS = [ | ||||
|   'Error During Registration', | ||||
|   'Confirm Application Registration', | ||||
|   'Waiting for Signer Confirmation', | ||||
|   'Waiting for Transaction Receipt', | ||||
|   'Registration Completed' | ||||
| ]; | ||||
| const STEP_ERROR = 0; | ||||
| const STEP_CONFIRM = 1; | ||||
| const STEP_SIGNER = 2; | ||||
| const STEP_TXRECEIPT = 3; | ||||
| const STEP_DONE = 4; | ||||
| 
 | ||||
| @observer | ||||
| export default class ModalRegister extends Component { | ||||
|   dappsStore = DappsStore.instance(); | ||||
|   modalStore = ModalStore.instance(); | ||||
|   static propTypes = { | ||||
|     dappId: PropTypes.string.isRequired, | ||||
|     onClose: PropTypes.func.isRequired, | ||||
|     onRegister: PropTypes.func.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   dappsStore = DappsStore.get(); | ||||
| 
 | ||||
|   render () { | ||||
|     if (!this.modalStore.showingRegister) { | ||||
|       return null; | ||||
|     } | ||||
|     const { onClose, onRegister } = this.props; | ||||
|     const actions = [ | ||||
|       { type: 'close', label: 'No, Cancel' }, | ||||
|       { type: 'confirm', label: 'Yes, Register', warning: true } | ||||
|     ]; | ||||
| 
 | ||||
|     return ( | ||||
|       <Modal | ||||
|         buttons={ this.renderButtons() } | ||||
|         error={ this.modalStore.errorRegister } | ||||
|         header={ HEADERS[this.modalStore.stepRegister] } | ||||
|         actions={ actions } | ||||
|         header='Confirm Application Registration' | ||||
|         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 }> | ||||
|           Your application has been registered in the registry. | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderStepConfirm () { | ||||
|     return ( | ||||
|       <div> | ||||
|         <div className={ styles.section }> | ||||
|           You are about to register a new distributed application on the network, the details of this application is given below. This will require a non-refundable fee of { api.util.fromWei(this.dappsStore.fee).toFormat(3) }<small>ETH</small>. | ||||
|         </div> | ||||
|         <div className={ styles.section }> | ||||
|           <div className={ styles.heading }> | ||||
|             Selected owner account | ||||
|           </div> | ||||
|           <div className={ styles.account }> | ||||
|             <img src={ api.util.createIdentityImg(this.dappsStore.currentAccount.address, 3) } /> | ||||
|             <div>{ this.dappsStore.currentAccount.name }</div> | ||||
|             <div className={ styles.hint }>{ this.dappsStore.currentAccount.address }</div> | ||||
|           </div> | ||||
|           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 }> | ||||
|             Unique assigned application identifier | ||||
|           </div> | ||||
|           <div> | ||||
|             { this.dappsStore.wipApp.id } | ||||
|             { this.props.dappId } | ||||
|           </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
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component } from 'react'; | ||||
| import { observer } from 'mobx-react'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import DappsStore from '../dappsStore'; | ||||
| import ModalStore from '../modalStore'; | ||||
| 
 | ||||
| import Button from '../Button'; | ||||
| import Modal from '../Modal'; | ||||
| 
 | ||||
| import styles from '../Modal/modal.css'; | ||||
| 
 | ||||
| const HEADERS = [ | ||||
|   'Error During Update', | ||||
|   'Confirm Application Update', | ||||
|   'Waiting for Signer Confirmation', | ||||
|   'Waiting for Transaction Receipt', | ||||
|   'Update Completed' | ||||
| ]; | ||||
| const STEP_ERROR = 0; | ||||
| const STEP_CONFIRM = 1; | ||||
| const STEP_SIGNER = 2; | ||||
| const STEP_TXRECEIPT = 3; | ||||
| const STEP_DONE = 4; | ||||
| 
 | ||||
| @observer | ||||
| export default class ModalUpdate extends Component { | ||||
|   dappsStore = DappsStore.instance(); | ||||
|   modalStore = ModalStore.instance(); | ||||
|   static propTypes = { | ||||
|     dappId: PropTypes.string.isRequired, | ||||
|     updates: PropTypes.object.isRequired, | ||||
|     onClose: PropTypes.func.isRequired, | ||||
|     onConfirm: PropTypes.func.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     if (!this.modalStore.showingUpdate) { | ||||
|       return null; | ||||
|     } | ||||
|     const { dappId, onClose, onConfirm } = this.props; | ||||
|     const actions = [ | ||||
|       { type: 'close', label: 'No, Cancel' }, | ||||
|       { type: 'confirm', label: 'Yes, Update', warning: true } | ||||
|     ]; | ||||
| 
 | ||||
|     return ( | ||||
|       <Modal | ||||
|         buttons={ this.renderButtons() } | ||||
|         error={ this.modalStore.errorUpdate } | ||||
|         header={ HEADERS[this.modalStore.stepUpdate] } | ||||
|         actions={ actions } | ||||
|         header='Confirm Application Update' | ||||
|         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 }> | ||||
|           Your application metadata has been updated in the registry. | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderStepConfirm () { | ||||
|     return ( | ||||
|       <div> | ||||
|         <div className={ styles.section }> | ||||
|           You are about to update the application details in the registry, the details of these updates are given below. Please note that each update will generate a seperate transaction. | ||||
|           You are about to update the application details in the registry, | ||||
|           the details of these updates are given below. Please note that each | ||||
|           update will generate a seperate transaction. | ||||
|         </div> | ||||
|         <div className={ styles.section }> | ||||
|           <div className={ styles.heading }> | ||||
|             Application identifier | ||||
|           </div> | ||||
|           <div> | ||||
|             { this.dappsStore.wipApp.id } | ||||
|             { dappId } | ||||
|           </div> | ||||
|         </div> | ||||
|         { this.renderChanges() } | ||||
|       </div> | ||||
|       </Modal> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderChanges () { | ||||
|     return ['content', 'image', 'manifest'] | ||||
|       .filter((type) => this.dappsStore.wipApp[`${type}Changed`]) | ||||
|     const { updates } = this.props; | ||||
| 
 | ||||
|     return Object.keys(updates) | ||||
|       .map((type) => { | ||||
|         return ( | ||||
|           <div className={ styles.section } key={ `${type}Update` }> | ||||
|           <div | ||||
|             className={ styles.section } | ||||
|             key={ `${type}Update` } | ||||
|           > | ||||
|             <div className={ styles.heading }> | ||||
|               Updates to { type } hash | ||||
|               Updates to { type } | ||||
|             </div> | ||||
|             <div> | ||||
|               <div>{ this.dappsStore.wipApp[`${type}Hash`] || '(removed)' }</div> | ||||
|               <div className={ styles.hint }> | ||||
|                 { this.dappsStore.wipApp[`${type}Url`] || 'current url to be removed from registry' } | ||||
|               </div> | ||||
|               <div>{ updates[type] || '(removed)' }</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
 | ||||
| // 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'; | ||||
| 
 | ||||
| @observer | ||||
| export default class SelectAccount extends Component { | ||||
|   dappsStore = DappsStore.instance(); | ||||
|   dappsStore = DappsStore.get(); | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     value: PropTypes.string.isRequired, | ||||
|     onSelect: PropTypes.func.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { value } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <select | ||||
|         value={ this.dappsStore.currentAccount.address } | ||||
|         onChange={ this.onSelect } | ||||
|         value={ value } | ||||
|         onChange={ this.handleSelect } | ||||
|       > | ||||
|         { this.renderOptions() } | ||||
|       </select> | ||||
| @ -44,7 +51,7 @@ export default class SelectAccount extends Component { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onSelect = (event) => { | ||||
|     this.dappsStore.setCurrentAccount(event.target.value); | ||||
|   handleSelect = (event) => { | ||||
|     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/>. | ||||
| */ | ||||
| 
 | ||||
| @import '../_colors.css'; | ||||
| 
 | ||||
| .warning { | ||||
|   background: #f44; | ||||
|   background: $warning-bg; | ||||
|   border-top-right-radius: 0.25em; | ||||
|   bottom: 0; | ||||
|   color: #fff; | ||||
|   color: white; | ||||
|   cursor: pointer; | ||||
|   font-size: 0.75em; | ||||
|   left: 0; | ||||
|  | ||||
| @ -15,37 +15,41 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component } from 'react'; | ||||
| import { observer } from 'mobx-react'; | ||||
| 
 | ||||
| import { api } from '../parity'; | ||||
| import DappsStore from '../dappsStore'; | ||||
| import ModalStore from '../modalStore'; | ||||
| 
 | ||||
| import styles from './warning.css'; | ||||
| 
 | ||||
| @observer | ||||
| export default class Warning extends Component { | ||||
|   dappsStore = DappsStore.instance(); | ||||
|   modalStore = ModalStore.instance(); | ||||
|   dappsStore = DappsStore.get(); | ||||
| 
 | ||||
|   state = { | ||||
|     show: true | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     if (!this.modalStore.showingWarning) { | ||||
|     if (!this.state.show) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.warning } onClick={ this.onClose }> | ||||
|         <div> | ||||
|           WARNING: Registering a dapp is for developers only. Please ensure you understand the steps needed to develop and deploy applications, should you wish to use this dapp for anything apart from queries. | ||||
|           WARNING: Registering a dapp is for developers only. Please ensure you understand the | ||||
|           steps needed to develop and deploy applications, should you wish to use this dapp for | ||||
|           anything apart from queries. | ||||
|         </div> | ||||
|         <div> | ||||
|           A non-refundable fee of { api.util.fromWei(this.dappsStore.fee).toFormat(3) }<small>ETH</small> is required for any registration. | ||||
|           A non-refundable fee | ||||
|           of { api.util.fromWei(this.dappsStore.fee).toFormat(3) } <small>ETH</small> is required | ||||
|           for any registration. | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   onClose = () => { | ||||
|     this.modalStore.hideWarning(); | ||||
|     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/>. | ||||
| */ | ||||
| 
 | ||||
| .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 { action, computed, observable, transaction } from 'mobx'; | ||||
| import { flatten } from 'lodash'; | ||||
| 
 | ||||
| import * as abis from '~/contracts/abi'; | ||||
| import Contracts from '~/contracts'; | ||||
| 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; | ||||
| 
 | ||||
| @ -28,23 +32,22 @@ export default class DappsStore { | ||||
|   @observable accounts = []; | ||||
|   @observable apps = []; | ||||
|   @observable contractOwner = null; | ||||
|   @observable currentAccount = null; | ||||
|   @observable currentApp = null; | ||||
|   @observable count = 0; | ||||
|   @observable fee = new BigNumber(0); | ||||
|   @observable isContractOwner = false; | ||||
|   @observable isEditing = false; | ||||
|   @observable isLoading = true; | ||||
|   @observable isNew = false; | ||||
|   @observable wipApp = null; | ||||
|   @observable transactions = {}; | ||||
| 
 | ||||
|   _instanceGhh = null; | ||||
|   _instanceReg = null; | ||||
|   _registry = null; | ||||
|   _startTime = Date.now(); | ||||
| 
 | ||||
|   constructor () { | ||||
|     this._loadDapps(); | ||||
|   } | ||||
| 
 | ||||
|   static instance () { | ||||
|   static get () { | ||||
|     if (!instance) { | ||||
|       instance = new DappsStore(); | ||||
|     } | ||||
| @ -52,141 +55,69 @@ export default class DappsStore { | ||||
|     return instance; | ||||
|   } | ||||
| 
 | ||||
|   @computed get canSave () { | ||||
|     const app = this.wipApp; | ||||
| 
 | ||||
|     const hasError = app.contentError || app.imageError || app.manifestError; | ||||
|     const isDirty = this.isNew || app.contentChanged || app.imageChanged || app.manifestChanged; | ||||
|     const isEditMode = this.isEditing || this.isNew; | ||||
| 
 | ||||
|     return isEditMode && isDirty && !hasError; | ||||
|   } | ||||
| 
 | ||||
|   @computed get isCurrentEditable () { | ||||
|     return !!this.accounts.find((account) => account.address === this.currentApp.owner); | ||||
|   createDappId () { | ||||
|     return api.util.sha3(`${this._startTime}_${Date.now()}`); | ||||
|   } | ||||
| 
 | ||||
|   @computed get ownedCount () { | ||||
|     return (this.apps.filter((app) => app.isOwner) || []).length; | ||||
|     return this.ownDapps.length; | ||||
|   } | ||||
| 
 | ||||
|   @action copyToWip = () => { | ||||
|     let wipApp; | ||||
| 
 | ||||
|     if (this.isNew) { | ||||
|       wipApp = { | ||||
|         id: api.util.sha3(`${this._startTime}_${Date.now()}`), | ||||
|         contentHash: null, | ||||
|         contentUrl: null, | ||||
|         imageHash: null, | ||||
|         imageUrl: null, | ||||
|         manifestHash: null, | ||||
|         manifestUrl: null | ||||
|       }; | ||||
|     } else { | ||||
|       const app = this.currentApp; | ||||
| 
 | ||||
|       wipApp = { | ||||
|         id: app.id, | ||||
|         contentHash: app.contentHash, | ||||
|         contentUrl: app.contentUrl, | ||||
|         imageHash: app.imageHash, | ||||
|         imageUrl: app.imageUrl, | ||||
|         manifestHash: app.manifestHash, | ||||
|         manifestUrl: app.manifestUrl, | ||||
|         owner: app.owner, | ||||
|         ownerName: app.ownerName | ||||
|       }; | ||||
|     } | ||||
| 
 | ||||
|     this.wipApp = Object.assign(wipApp, { | ||||
|       contentChanged: false, | ||||
|       contentError: null, | ||||
|       imageChanged: false, | ||||
|       imageError: null, | ||||
|       manifestChanged: false, | ||||
|       manifestError: null | ||||
|     }); | ||||
| 
 | ||||
|     return this.wipApp; | ||||
|   @computed get ownDapps () { | ||||
|     return this.apps.filter((app) => app.isOwner); | ||||
|   } | ||||
| 
 | ||||
|   @action editWip = (details) => { | ||||
|     if (this.isNew || this.isEditing) { | ||||
|       transaction(() => { | ||||
|         Object | ||||
|           .keys(details) | ||||
|           .forEach((key) => { | ||||
|             this.wipApp[key] = details[key]; | ||||
|           }); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     return this.wipApp; | ||||
|   @computed get otherDapps () { | ||||
|     return this.apps.filter((app) => !app.isOwner); | ||||
|   } | ||||
| 
 | ||||
|   @action sortApps = (apps = this.apps) => { | ||||
|     transaction(() => { | ||||
|       const ownApps = apps | ||||
|         .filter((app) => app.isOwner) | ||||
|         .sort((a, b) => a.name.localeCompare(b.name)); | ||||
|       const otherApps = apps | ||||
|         .filter((app) => !app.isOwner) | ||||
|         .sort((a, b) => a.name.localeCompare(b.name)); | ||||
| 
 | ||||
|       this.apps = ownApps.concat(otherApps); | ||||
| 
 | ||||
|       if (this.apps.length) { | ||||
|         this.currentApp = this.apps[0]; | ||||
|   @action sortApps = () => { | ||||
|     // Sort dapps per name, then per id
 | ||||
|     const sort = (a, b) => { | ||||
|       if (a.name && b.name) { | ||||
|         return a.name.localeCompare(b.name); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action setApps = (apps) => { | ||||
|     this.sortApps(apps.filter((app) => { | ||||
|       const bnid = new BigNumber(app.id); | ||||
|       if (a.name) { | ||||
|         return -1; | ||||
|       } | ||||
| 
 | ||||
|       return bnid.gt(0); | ||||
|     })); | ||||
|       if (b.name) { | ||||
|         return 1; | ||||
|       } | ||||
| 
 | ||||
|     return this.apps; | ||||
|   } | ||||
|       return a.id - b.id; | ||||
|     }; | ||||
| 
 | ||||
|   @action _addApp = (app) => { | ||||
|     transaction(() => { | ||||
|       this.setApps(this.apps.concat([app])); | ||||
|       this.setCurrentApp(app.id); | ||||
|       const ownDapps = this.ownDapps.sort(sort); | ||||
|       const otherDapps = this.otherDapps.sort(sort); | ||||
| 
 | ||||
|       this.apps = ownDapps.concat(otherDapps); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action addApp = (appId, account) => { | ||||
|     this | ||||
|       ._loadDapp({ | ||||
|         id: appId, | ||||
|         isOwner: true, | ||||
|         name: `- ${appId}`, | ||||
|         owner: account.address, | ||||
|         ownerName: account.name | ||||
|       }) | ||||
|       .then(this._addApp); | ||||
|   } | ||||
|   @action setApps = (dapps) => { | ||||
|     const filteredDapps = dapps.filter((dapp) => { | ||||
|       return new BigNumber(dapp.id).gt(0); | ||||
|     }); | ||||
| 
 | ||||
|   @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]; | ||||
|       }); | ||||
|       this.apps = filteredDapps; | ||||
|       this.sortApps(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|     return app; | ||||
|   @action refreshApp = (dappId) => { | ||||
|     const dapp = this.apps.find((dapp) => dapp.id === dappId); | ||||
| 
 | ||||
|     this._loadDapp(dapp); | ||||
|   } | ||||
| 
 | ||||
|   @action removeApp = (dappId) => { | ||||
|     const dapps = this.apps.filter((dapp) => dapp.id !== dappId); | ||||
| 
 | ||||
|     this.setApps(dapps); | ||||
|   } | ||||
| 
 | ||||
|   @action setAccounts = (accountsInfo) => { | ||||
| @ -199,8 +130,6 @@ export default class DappsStore { | ||||
|           account.address = address; | ||||
|           return account; | ||||
|         }); | ||||
| 
 | ||||
|       this.currentAccount = this.accounts[0]; | ||||
|     }); | ||||
| 
 | ||||
|     return this.accounts; | ||||
| @ -214,30 +143,11 @@ export default class DappsStore { | ||||
|     return contractOwner; | ||||
|   } | ||||
| 
 | ||||
|   @action setCurrentApp = (id) => { | ||||
|     this.currentApp = this.apps.find((app) => app.id === id); | ||||
|     return this.currentApp; | ||||
|   } | ||||
| 
 | ||||
|   @action setCurrentAccount = (address) => { | ||||
|     this.currentAccount = this.accounts.find((account) => account.address === address); | ||||
|     return this.currentAccount; | ||||
|   } | ||||
| 
 | ||||
|   @action setCount = (count) => { | ||||
|     this.count = count; | ||||
|     return count; | ||||
|   } | ||||
| 
 | ||||
|   @action setEditing = (mode) => { | ||||
|     transaction(() => { | ||||
|       this.isEditing = mode; | ||||
|       this.copyToWip(); | ||||
|     }); | ||||
| 
 | ||||
|     return mode; | ||||
|   } | ||||
| 
 | ||||
|   @action setFee = (fee) => { | ||||
|     this.fee = fee; | ||||
|     return fee; | ||||
| @ -248,13 +158,106 @@ export default class DappsStore { | ||||
|     return loading; | ||||
|   } | ||||
| 
 | ||||
|   @action setNew = (mode) => { | ||||
|     transaction(() => { | ||||
|       this.isNew = mode; | ||||
|       this.copyToWip(); | ||||
|   @action updateTransaction = (requestId, nextData) => { | ||||
|     const prevTransaction = this.transactions[requestId] || { requestId }; | ||||
|     const nextTransaction = { | ||||
|       ...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) { | ||||
| @ -310,26 +313,30 @@ export default class DappsStore { | ||||
| 
 | ||||
|         return Promise.all(promises); | ||||
|       }) | ||||
|       .then((appsInfo) => { | ||||
|         return Promise.all( | ||||
|           this | ||||
|             .setApps(appsInfo.map(([appId, owner]) => { | ||||
|               const isOwner = !!this.accounts.find((account) => account.address === owner); | ||||
|               const account = this.accounts.find((account) => account.address === owner); | ||||
|               const id = api.util.bytesToHex(appId); | ||||
|       .then((dappsInfo) => { | ||||
|         const dapps = dappsInfo.reduce((dapps, [dappId, ownerAddress]) => { | ||||
|           const id = api.util.bytesToHex(dappId); | ||||
|           const owner = this.accounts.find((account) => account.address === ownerAddress); | ||||
|           const isOwner = !!owner; | ||||
|           const dapp = { | ||||
|             id, | ||||
|             owner: owner || { address: ownerAddress }, | ||||
|             isOwner | ||||
|           }; | ||||
| 
 | ||||
|               return { | ||||
|                 id, | ||||
|                 owner, | ||||
|                 ownerName: account ? account.name : owner, | ||||
|                 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.setLoading(false); | ||||
|       }) | ||||
| @ -338,12 +345,14 @@ export default class DappsStore { | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   _loadDapp = (app) => { | ||||
|   _loadDapp = (dappData) => { | ||||
|     const { id, owner, isOwner } = dappData; | ||||
| 
 | ||||
|     return Promise | ||||
|       .all([ | ||||
|         this._loadMeta(app.id, 'CONTENT'), | ||||
|         this._loadMeta(app.id, 'IMG'), | ||||
|         this._loadMeta(app.id, 'MANIFEST') | ||||
|         this._loadMeta(id, 'CONTENT'), | ||||
|         this._loadMeta(id, 'IMG'), | ||||
|         this._loadMeta(id, 'MANIFEST') | ||||
|       ]) | ||||
|       .then(([contentHash, imageHash, manifestHash]) => { | ||||
|         return Promise | ||||
| @ -354,25 +363,47 @@ export default class DappsStore { | ||||
|           ]) | ||||
|           .then(([contentUrl, imageUrl, manifestUrl]) => { | ||||
|             return this | ||||
|               ._loadManifest(app.id, manifestHash) | ||||
|               .then((manifest) => { | ||||
|                 this.setAppInfo(app, { | ||||
|                   manifest, | ||||
|                   manifestHash, | ||||
|                   manifestUrl, | ||||
|                   contentHash, | ||||
|                   contentUrl, | ||||
|                   imageHash, | ||||
|                   imageUrl, | ||||
|                   name: (manifest && manifest.name) || `- ${app.id}` | ||||
|                 }); | ||||
|               ._loadManifest(id, manifestHash, manifestUrl) | ||||
|               .then((manifestContent) => { | ||||
|                 const content = { | ||||
|                   hash: contentHash, | ||||
|                   url: contentUrl | ||||
|                 }; | ||||
| 
 | ||||
|                 return app; | ||||
|                 const image = { | ||||
|                   hash: imageHash, | ||||
|                   url: imageUrl | ||||
|                 }; | ||||
| 
 | ||||
|                 const manifest = { | ||||
|                   content: manifestContent, | ||||
|                   hash: manifestHash, | ||||
|                   url: manifestUrl | ||||
|                 }; | ||||
| 
 | ||||
|                 return { content, image, manifest }; | ||||
|               }); | ||||
|           }); | ||||
|       }) | ||||
|       .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) { | ||||
|       return Promise.resolve(builtin); | ||||
|     } else if (!manifestHash) { | ||||
|     } | ||||
| 
 | ||||
|     if (!manifestHash) { | ||||
|       return Promise.resolve(null); | ||||
|     } | ||||
| 
 | ||||
| @ -453,11 +486,10 @@ export default class DappsStore { | ||||
|   } | ||||
| 
 | ||||
|   _loadRegistry () { | ||||
|     return api.parity | ||||
|       .registryAddress() | ||||
|       .then((registryAddress) => { | ||||
|         console.log(`the registry was found at ${registryAddress}`); | ||||
|         this._registry = api.newContract(abis.registry, registryAddress).instance; | ||||
|     return Contracts.create(api).registry | ||||
|       .fetchContract() | ||||
|       .then((contract) => { | ||||
|         this._registry = contract.instance; | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.error('Store:loadRegistry', error); | ||||
| @ -472,9 +504,11 @@ export default class DappsStore { | ||||
|       ]) | ||||
|       .then(([dappregAddress, ghhAddress]) => { | ||||
|         console.log(`dappreg was found at ${dappregAddress}`); | ||||
|         console.log(`githubhint was found at ${ghhAddress}`); | ||||
| 
 | ||||
|         this._contractReg = api.newContract(abis.dappreg, dappregAddress); | ||||
|         this._instanceReg = this._contractReg.instance; | ||||
|         console.log(`githubhint was found at ${ghhAddress}`); | ||||
| 
 | ||||
|         this._contractGhh = api.newContract(abis.githubhint, ghhAddress); | ||||
|         this._instanceGhh = this._contractGhh.instance; | ||||
|       }) | ||||
|  | ||||
| @ -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
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| const { api } = window.parity; | ||||
| const api = window.parent.secureApi; | ||||
| 
 | ||||
| function trackRequest (requestPromise, statusCallback) { | ||||
|   return requestPromise | ||||
|     .then((signerRequestId) => { | ||||
|       console.log('trackRequest', `posted to signer with requestId ${signerRequestId.toString()}`); | ||||
|       statusCallback(null, { signerRequestId }); | ||||
| 
 | ||||
|       return api.pollMethod('parity_checkRequest', signerRequestId); | ||||
|     }) | ||||
| function trackRequest (signerRequestId, statusCallback) { | ||||
|   return api.pollMethod('parity_checkRequest', signerRequestId) | ||||
|     .then((transactionHash) => { | ||||
|       console.log('trackRequest', `received transaction hash ${transactionHash}`); | ||||
|       statusCallback(null, { transactionHash }); | ||||
| @ -41,9 +35,7 @@ function trackRequest (requestPromise, statusCallback) { | ||||
|       statusCallback(null, { transactionReceipt }); | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       console.error('trackRequest', 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
 | ||||
| // 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 * 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 }; | ||||
| 
 | ||||
| const REGISTRY_V1_HASHES = [ | ||||
|   '0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten
 | ||||
|   '0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead
 | ||||
| ]; | ||||
| 
 | ||||
| export const setNetVersion = (netVersion) => ({ type: 'set netVersion', netVersion }); | ||||
| 
 | ||||
| export const fetchIsTestnet = () => (dispatch) => | ||||
| @ -48,36 +43,18 @@ export const fetchIsTestnet = () => (dispatch) => | ||||
| 
 | ||||
| export const setContract = (contract) => ({ type: 'set contract', contract }); | ||||
| 
 | ||||
| export const fetchContract = () => (dispatch) => | ||||
|   api.parity | ||||
|     .registryAddress() | ||||
|     .then((address) => { | ||||
|       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(fetchFee()); | ||||
|           dispatch(fetchOwner()); | ||||
|         }); | ||||
| export const fetchContract = () => (dispatch) => { | ||||
|   return Contracts.create(api).registry | ||||
|     .fetchContract() | ||||
|     .then((contract) => { | ||||
|       dispatch(setContract(contract)); | ||||
|       dispatch(fetchFee()); | ||||
|       dispatch(fetchOwner()); | ||||
|     }) | ||||
|     .catch((err) => { | ||||
|       console.error('could not fetch contract'); | ||||
|       if (err) { | ||||
|         console.error(err.stack); | ||||
|       } | ||||
|     .catch((error) => { | ||||
|       console.error('could not fetch contract', error); | ||||
|     }); | ||||
| }; | ||||
| 
 | ||||
| export const setFee = (fee) => ({ type: 'set fee', fee }); | ||||
| 
 | ||||
|  | ||||
| @ -170,6 +170,10 @@ export default class WalletsUtils { | ||||
|    * to make unnecessary calls on non-wallet accounts | ||||
|    */ | ||||
|   static isWallet (api, address) { | ||||
|     if (!address) { | ||||
|       return Promise.resolve(false); | ||||
|     } | ||||
| 
 | ||||
|     if (!_cachedWalletLookup[address]) { | ||||
|       const walletContract = new Contract(api, WalletAbi); | ||||
| 
 | ||||
|  | ||||
| @ -62,7 +62,8 @@ | ||||
|     "description": "Enables the registration and content management of dapps on the network", | ||||
|     "author": "Parity Team <admin@ethcore.io>", | ||||
|     "version": "1.0.0", | ||||
|     "visible": false | ||||
|     "visible": false, | ||||
|     "secure": true | ||||
|   }, | ||||
|   { | ||||
|     "id": "0x9042323cd85c6576992d211de34b3ecc183f15e4f639aa87859882f839c374e5", | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user