Ui 2 shell (#5510)
* Split application into ~/shell * reset.css back to index
This commit is contained in:
48
js/src/shell/Application/Container/container.js
Normal file
48
js/src/shell/Application/Container/container.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// 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 { FirstRun, UpgradeParity } from '~/modals';
|
||||
import { Errors, Tooltips } from '~/ui';
|
||||
|
||||
import styles from '../application.css';
|
||||
|
||||
export default class Container extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
onCloseFirstRun: PropTypes.func,
|
||||
showFirstRun: PropTypes.bool,
|
||||
upgradeStore: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
render () {
|
||||
const { children, onCloseFirstRun, showFirstRun, upgradeStore } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<FirstRun
|
||||
onClose={ onCloseFirstRun }
|
||||
visible={ showFirstRun }
|
||||
/>
|
||||
<Tooltips />
|
||||
<UpgradeParity upgradeStore={ upgradeStore } />
|
||||
<Errors />
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
17
js/src/shell/Application/Container/index.js
Normal file
17
js/src/shell/Application/Container/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 './container';
|
||||
38
js/src/shell/Application/DappContainer/dappContainer.js
Normal file
38
js/src/shell/Application/DappContainer/dappContainer.js
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/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import { Errors } from '~/ui';
|
||||
|
||||
import styles from '../application.css';
|
||||
|
||||
export default class DappContainer extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
render () {
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<Errors />
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
17
js/src/shell/Application/DappContainer/index.js
Normal file
17
js/src/shell/Application/DappContainer/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 './dappContainer';
|
||||
52
js/src/shell/Application/Extension/extension.css
Normal file
52
js/src/shell/Application/Extension/extension.css
Normal file
@@ -0,0 +1,52 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
.body {
|
||||
background: #f80;
|
||||
color: white;
|
||||
opacity: 1;
|
||||
max-width: 500px;
|
||||
padding: 1em 4em 1em 2em;
|
||||
position: fixed;
|
||||
right: 1.5em;
|
||||
top: 1.5em;
|
||||
z-index: 1000;
|
||||
|
||||
.button {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: white !important;
|
||||
|
||||
svg {
|
||||
fill: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonrow {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.close {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 1em;
|
||||
}
|
||||
}
|
||||
74
js/src/shell/Application/Extension/extension.js
Normal file
74
js/src/shell/Application/Extension/extension.js
Normal file
@@ -0,0 +1,74 @@
|
||||
// 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 } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Button } from '~/ui';
|
||||
import { CloseIcon, CheckIcon } from '~/ui/Icons';
|
||||
|
||||
import Store from './store';
|
||||
import styles from './extension.css';
|
||||
|
||||
@observer
|
||||
export default class Extension extends Component {
|
||||
store = Store.get();
|
||||
|
||||
render () {
|
||||
const { showWarning } = this.store;
|
||||
|
||||
if (!showWarning) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.body }>
|
||||
<CloseIcon
|
||||
className={ styles.close }
|
||||
onClick={ this.onClose }
|
||||
/>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='extension.intro'
|
||||
defaultMessage='Parity now has an extension available for Chrome that allows safe browsing of Ethereum-enabled distributed applications. It is highly recommended that you install this extension to further enhance your Parity experience.'
|
||||
/>
|
||||
</p>
|
||||
<p className={ styles.buttonrow }>
|
||||
<Button
|
||||
className={ styles.button }
|
||||
icon={ <CheckIcon /> }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='extension.install'
|
||||
defaultMessage='Install the extension now'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onInstallClick }
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.store.snoozeWarning();
|
||||
}
|
||||
|
||||
onInstallClick = () => {
|
||||
this.store.installExtension();
|
||||
}
|
||||
}
|
||||
17
js/src/shell/Application/Extension/index.js
Normal file
17
js/src/shell/Application/Extension/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 './extension';
|
||||
116
js/src/shell/Application/Extension/store.js
Normal file
116
js/src/shell/Application/Extension/store.js
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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/>.
|
||||
|
||||
/* global chrome */
|
||||
|
||||
import { action, computed, observable } from 'mobx';
|
||||
|
||||
import store from 'store';
|
||||
import browser from 'useragent.js/lib/browser';
|
||||
|
||||
import { DOMAIN } from '~/util/constants';
|
||||
|
||||
const A_DAY = 24 * 60 * 60 * 1000;
|
||||
const NEXT_DISPLAY = '_parity::extensionWarning::nextDisplay';
|
||||
|
||||
// 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig';
|
||||
const EXTENSION_PAGE = 'https://chrome.google.com/webstore/detail/himekenlppkgeaoeddcliojfddemadig';
|
||||
|
||||
let instance;
|
||||
|
||||
export default class Store {
|
||||
@observable hasExtension = false;
|
||||
@observable isInstalling = false;
|
||||
@observable nextDisplay = 0;
|
||||
@observable shouldInstall = false;
|
||||
|
||||
constructor () {
|
||||
this.nextDisplay = store.get(NEXT_DISPLAY) || 0;
|
||||
this.testInstall();
|
||||
}
|
||||
|
||||
@computed get showWarning () {
|
||||
return !this.isInstalling && this.shouldInstall && (Date.now() > this.nextDisplay);
|
||||
}
|
||||
|
||||
@action setExtensionActive = () => {
|
||||
this.hasExtension = true;
|
||||
}
|
||||
|
||||
@action setInstalling = (isInstalling) => {
|
||||
this.isInstalling = isInstalling;
|
||||
}
|
||||
|
||||
@action snoozeWarning = (sleep = A_DAY) => {
|
||||
this.nextDisplay = Date.now() + sleep;
|
||||
store.set(NEXT_DISPLAY, this.nextDisplay);
|
||||
}
|
||||
|
||||
@action testInstall = () => {
|
||||
this.shouldInstall = this.readStatus();
|
||||
}
|
||||
|
||||
readStatus = () => {
|
||||
const hasExtension = Symbol.for('parity.extension') in window;
|
||||
const ua = browser.analyze(navigator.userAgent || '');
|
||||
|
||||
if (hasExtension) {
|
||||
this.setExtensionActive();
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ua || {}).name.toLowerCase() === 'chrome';
|
||||
}
|
||||
|
||||
installExtension = () => {
|
||||
this.setInstalling(true);
|
||||
|
||||
if (window.location.hostname.toString().endsWith(DOMAIN)) {
|
||||
return this.inlineInstall()
|
||||
.catch((error) => {
|
||||
console.warn('Unable to perform direct install', error);
|
||||
window.open(EXTENSION_PAGE, '_blank');
|
||||
});
|
||||
}
|
||||
|
||||
window.open(EXTENSION_PAGE, '_blank');
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
inlineInstall = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const link = document.createElement('link');
|
||||
|
||||
link.setAttribute('rel', 'chrome-webstore-item');
|
||||
link.setAttribute('href', EXTENSION_PAGE);
|
||||
document.querySelector('head').appendChild(link);
|
||||
|
||||
if (chrome && chrome.webstore && chrome.webstore.install) {
|
||||
chrome.webstore.install(EXTENSION_PAGE, resolve, reject);
|
||||
} else {
|
||||
reject(new Error('Direct installation failed.'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static get () {
|
||||
if (!instance) {
|
||||
instance = new Store();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
21
js/src/shell/Application/FrameError/frameError.css
Normal file
21
js/src/shell/Application/FrameError/frameError.css
Normal file
@@ -0,0 +1,21 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
.error {
|
||||
padding: 2em;
|
||||
background: red;
|
||||
color: white;
|
||||
}
|
||||
33
js/src/shell/Application/FrameError/frameError.js
Normal file
33
js/src/shell/Application/FrameError/frameError.js
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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 { FormattedMessage } from 'react-intl';
|
||||
|
||||
import styles from './frameError.css';
|
||||
|
||||
export default class FrameError extends Component {
|
||||
render () {
|
||||
return (
|
||||
<div className={ styles.error }>
|
||||
<FormattedMessage
|
||||
id='application.frame.error'
|
||||
defaultMessage='ERROR: This application cannot and should not be loaded in an embedded iFrame'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
17
js/src/shell/Application/FrameError/index.js
Normal file
17
js/src/shell/Application/FrameError/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 './frameError';
|
||||
17
js/src/shell/Application/Requests/index.js
Normal file
17
js/src/shell/Application/Requests/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 './requests';
|
||||
126
js/src/shell/Application/Requests/requests.css
Normal file
126
js/src/shell/Application/Requests/requests.css
Normal file
@@ -0,0 +1,126 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
$baseColor: 18;
|
||||
$baseOpacity: 0.95;
|
||||
$borderColor: rgba($baseColor, $baseColor, $baseColor, 0.25);
|
||||
|
||||
.requests {
|
||||
align-items: flex-end;
|
||||
bottom: 2em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
right: 0.175em;
|
||||
width: 24em;
|
||||
z-index: 750;
|
||||
|
||||
* {
|
||||
font-size: 0.85rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.request {
|
||||
animation-fill-mode: forwards;
|
||||
animation-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
|
||||
background-color: rgba($baseColor, $baseColor, $baseColor, $baseOpacity);
|
||||
border: 4px solid $borderColor;
|
||||
cursor: pointer;
|
||||
margin-top: 0.25em;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
|
||||
&.hide {
|
||||
animation-duration: 0.5s;
|
||||
animation-name: fadeout;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 0.5em;
|
||||
|
||||
&.error {
|
||||
background-color: rgba(200, 40, 40, 0.95);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 1em 1em 0.5em 1em;
|
||||
}
|
||||
|
||||
&:hover .container {
|
||||
background-color: rgba($baseColor, $baseColor, $baseColor, 1);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeout {
|
||||
from {
|
||||
display: block;
|
||||
height: inherit;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
49% {
|
||||
display: block;
|
||||
height: inherit;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
98% {
|
||||
display: block;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
display: none;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.identity {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-right: 1em;
|
||||
|
||||
.icon {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.inline {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.fill {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.hash {
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
243
js/src/shell/Application/Requests/requests.js
Normal file
243
js/src/shell/Application/Requests/requests.js
Normal file
@@ -0,0 +1,243 @@
|
||||
// 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 { LinearProgress } from 'material-ui';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { hideRequest } from '~/redux/providers/requestsActions';
|
||||
import { MethodDecoding, IdentityIcon, ScrollableText, ShortenedHash } from '~/ui';
|
||||
|
||||
import styles from './requests.css';
|
||||
|
||||
const ERROR_STATE = 'ERROR_STATE';
|
||||
const DONE_STATE = 'DONE_STATE';
|
||||
const WAITING_STATE = 'WAITING_STATE';
|
||||
|
||||
class Requests extends Component {
|
||||
static propTypes = {
|
||||
requests: PropTypes.object.isRequired,
|
||||
onHideRequest: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
extras: {}
|
||||
};
|
||||
|
||||
render () {
|
||||
const { requests } = this.props;
|
||||
const { extras } = this.state;
|
||||
|
||||
return (
|
||||
<div className={ styles.requests }>
|
||||
{
|
||||
Object
|
||||
.values(requests)
|
||||
.map((request) => this.renderRequest(request, extras[request.requestId]))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderRequest (request, extras = {}) {
|
||||
const { show, transaction } = request;
|
||||
const state = this.getTransactionState(request);
|
||||
const displayedTransaction = { ...transaction };
|
||||
|
||||
// Don't show gas and gasPrice
|
||||
delete displayedTransaction.gas;
|
||||
delete displayedTransaction.gasPrice;
|
||||
|
||||
const requestClasses = [ styles.request ];
|
||||
const statusClasses = [ styles.status ];
|
||||
const requestStyle = {};
|
||||
|
||||
const handleHideRequest = () => {
|
||||
this.handleHideRequest(request.requestId);
|
||||
};
|
||||
|
||||
if (state.type === ERROR_STATE) {
|
||||
statusClasses.push(styles.error);
|
||||
}
|
||||
|
||||
if (!show) {
|
||||
requestClasses.push(styles.hide);
|
||||
}
|
||||
|
||||
// Set the Request height (for animation) if found
|
||||
if (extras.height) {
|
||||
requestStyle.height = extras.height;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ requestClasses.join(' ') }
|
||||
key={ request.requestId }
|
||||
ref={ `request_${request.requestId}` }
|
||||
onClick={ handleHideRequest }
|
||||
style={ requestStyle }
|
||||
>
|
||||
<div className={ statusClasses.join(' ') }>
|
||||
{ this.renderStatus(request) }
|
||||
</div>
|
||||
{
|
||||
state.type === ERROR_STATE
|
||||
? null
|
||||
: (
|
||||
<LinearProgress
|
||||
max={ 6 }
|
||||
mode={ state.type === WAITING_STATE ? 'indeterminate' : 'determinate' }
|
||||
value={ state.type === DONE_STATE ? request.blockHeight.toNumber() : 6 }
|
||||
/>
|
||||
)
|
||||
}
|
||||
<div className={ styles.container }>
|
||||
<div
|
||||
className={ styles.identity }
|
||||
title={ transaction.from }
|
||||
>
|
||||
<IdentityIcon
|
||||
address={ transaction.from }
|
||||
inline
|
||||
center
|
||||
className={ styles.icon }
|
||||
/>
|
||||
</div>
|
||||
<MethodDecoding
|
||||
address={ transaction.from }
|
||||
compact
|
||||
historic={ state.type === DONE_STATE }
|
||||
transaction={ displayedTransaction }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderStatus (request) {
|
||||
const { error, transactionHash, transactionReceipt } = request;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div
|
||||
className={ styles.inline }
|
||||
title={ error.message }
|
||||
>
|
||||
<FormattedMessage
|
||||
id='requests.status.error'
|
||||
defaultMessage='An error occured:'
|
||||
/>
|
||||
<div className={ styles.fill }>
|
||||
<ScrollableText
|
||||
text={ error.text || error.message || error.toString() }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (transactionReceipt) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='requests.status.transactionMined'
|
||||
defaultMessage='Transaction mined at block #{blockNumber} ({blockHeight} blocks ago)'
|
||||
values={ {
|
||||
blockHeight: request.blockHeight.toNumber(),
|
||||
blockNumber: transactionReceipt.blockNumber.toFormat()
|
||||
} }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (transactionHash) {
|
||||
return (
|
||||
<div className={ styles.inline }>
|
||||
<FormattedMessage
|
||||
id='requests.status.transactionSent'
|
||||
defaultMessage='Transaction sent to network with hash'
|
||||
/>
|
||||
<div className={ [ styles.fill, styles.hash ].join(' ') }>
|
||||
<ShortenedHash data={ transactionHash } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='requests.status.waitingForSigner'
|
||||
defaultMessage='Waiting for authorization in the Parity Signer'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
getTransactionState (request) {
|
||||
const { error, transactionReceipt } = request;
|
||||
|
||||
if (error) {
|
||||
return { type: ERROR_STATE };
|
||||
}
|
||||
|
||||
if (transactionReceipt) {
|
||||
return { type: DONE_STATE };
|
||||
}
|
||||
|
||||
return { type: WAITING_STATE };
|
||||
}
|
||||
|
||||
handleHideRequest = (requestId) => {
|
||||
const requestElement = ReactDOM.findDOMNode(this.refs[`request_${requestId}`]);
|
||||
|
||||
// Try to get the request element height, to have a nice transition effect
|
||||
if (requestElement) {
|
||||
const { height } = requestElement.getBoundingClientRect();
|
||||
const prevExtras = this.state.extras;
|
||||
const nextExtras = {
|
||||
...prevExtras,
|
||||
[ requestId ]: {
|
||||
...prevExtras[requestId],
|
||||
height
|
||||
}
|
||||
};
|
||||
|
||||
return this.setState({ extras: nextExtras }, () => {
|
||||
return this.props.onHideRequest(requestId);
|
||||
});
|
||||
}
|
||||
|
||||
return this.props.onHideRequest(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { requests } = state;
|
||||
|
||||
return { requests };
|
||||
};
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({
|
||||
onHideRequest: hideRequest
|
||||
}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Requests);
|
||||
118
js/src/shell/Application/Requests/savedRequests.js
Normal file
118
js/src/shell/Application/Requests/savedRequests.js
Normal file
@@ -0,0 +1,118 @@
|
||||
// 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 store from 'store';
|
||||
|
||||
import { ERROR_CODES } from '@parity/api/transport/error';
|
||||
|
||||
export const LS_REQUESTS_KEY = '_parity::requests';
|
||||
|
||||
export default class SavedRequests {
|
||||
network = null;
|
||||
|
||||
/**
|
||||
* Load the network version, and then the related requests
|
||||
*/
|
||||
load (api) {
|
||||
return api.net.version()
|
||||
.then((network) => {
|
||||
this.network = network;
|
||||
return this.loadRequests(api);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the requests of the current network
|
||||
*/
|
||||
loadRequests (api) {
|
||||
const requests = this._get();
|
||||
const promises = Object.values(requests).map((request) => {
|
||||
const { requestId, transactionHash } = request;
|
||||
|
||||
// The request hasn't been signed yet
|
||||
if (transactionHash) {
|
||||
return request;
|
||||
}
|
||||
|
||||
return this._requestExists(api, requestId)
|
||||
.then((exists) => {
|
||||
if (!exists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return request;
|
||||
})
|
||||
.catch(() => {
|
||||
this.remove(requestId);
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises).then((requests) => requests.filter((request) => request));
|
||||
}
|
||||
|
||||
save (requestId, requestData) {
|
||||
const requests = this._get();
|
||||
|
||||
requests[requestId] = {
|
||||
...(requests[requestId] || {}),
|
||||
...requestData
|
||||
};
|
||||
|
||||
this._set(requests);
|
||||
}
|
||||
|
||||
remove (requestId) {
|
||||
const requests = this._get();
|
||||
|
||||
delete requests[requestId];
|
||||
this._set(requests);
|
||||
}
|
||||
|
||||
_get () {
|
||||
const allRequests = store.get(LS_REQUESTS_KEY) || {};
|
||||
|
||||
return allRequests[this.network] || {};
|
||||
}
|
||||
|
||||
_set (requests = {}) {
|
||||
const allRequests = store.get(LS_REQUESTS_KEY) || {};
|
||||
|
||||
if (Object.keys(requests).length > 0) {
|
||||
allRequests[this.network] = requests;
|
||||
} else {
|
||||
delete allRequests[this.network];
|
||||
}
|
||||
|
||||
return store.set(LS_REQUESTS_KEY, allRequests);
|
||||
}
|
||||
|
||||
_requestExists (api, requestId) {
|
||||
return api.parity
|
||||
.checkRequest(requestId)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
if (error.code === ERROR_CODES.REQUEST_NOT_FOUND) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
||||
105
js/src/shell/Application/Requests/savedRequests.spec.js
Normal file
105
js/src/shell/Application/Requests/savedRequests.spec.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// 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 sinon from 'sinon';
|
||||
import store from 'store';
|
||||
|
||||
import SavedRequests, { LS_REQUESTS_KEY } from './savedRequests';
|
||||
|
||||
const NETWORK_ID = 42;
|
||||
const DEFAULT_REQUEST = {
|
||||
requestId: '0x1',
|
||||
transaction: {}
|
||||
};
|
||||
|
||||
const api = createApi(NETWORK_ID);
|
||||
const api2 = createApi(1);
|
||||
const savedRequests = new SavedRequests();
|
||||
|
||||
function createApi (networkVersion) {
|
||||
return {
|
||||
parity: {
|
||||
checkRequest: sinon.stub().resolves()
|
||||
},
|
||||
net: {
|
||||
version: sinon.stub().resolves(networkVersion)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe('views/Application/Requests/savedRequests', () => {
|
||||
beforeEach((done) => {
|
||||
store.set(LS_REQUESTS_KEY, {
|
||||
[NETWORK_ID]: {
|
||||
[DEFAULT_REQUEST.requestId]: DEFAULT_REQUEST
|
||||
}
|
||||
});
|
||||
|
||||
savedRequests.load(api)
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.set(LS_REQUESTS_KEY, {});
|
||||
});
|
||||
|
||||
it('gets requests from local storage', () => {
|
||||
const requests = savedRequests._get();
|
||||
|
||||
expect(requests[DEFAULT_REQUEST.requestId]).to.deep.equal(DEFAULT_REQUEST);
|
||||
});
|
||||
|
||||
it('sets requests to local storage', () => {
|
||||
savedRequests._set({});
|
||||
|
||||
const requests = savedRequests._get();
|
||||
|
||||
expect(requests).to.deep.equal({});
|
||||
});
|
||||
|
||||
it('removes requests', () => {
|
||||
savedRequests.remove(DEFAULT_REQUEST.requestId);
|
||||
|
||||
const requests = savedRequests._get();
|
||||
|
||||
expect(requests).to.deep.equal({});
|
||||
});
|
||||
|
||||
it('saves new requests', () => {
|
||||
savedRequests.save(DEFAULT_REQUEST.requestId, { extraData: true });
|
||||
|
||||
const requests = savedRequests._get();
|
||||
|
||||
expect(requests[DEFAULT_REQUEST.requestId]).to.deep.equal({
|
||||
...DEFAULT_REQUEST,
|
||||
extraData: true
|
||||
});
|
||||
});
|
||||
|
||||
it('loads requests', () => {
|
||||
return savedRequests.load(api)
|
||||
.then((requests) => {
|
||||
expect(requests[0]).to.deep.equal(DEFAULT_REQUEST);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads requests from the right network', () => {
|
||||
return savedRequests.load(api2)
|
||||
.then((requests) => {
|
||||
expect(requests).to.deep.equal([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
17
js/src/shell/Application/Snackbar/index.js
Normal file
17
js/src/shell/Application/Snackbar/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 './snackbar';
|
||||
76
js/src/shell/Application/Snackbar/snackbar.js
Normal file
76
js/src/shell/Application/Snackbar/snackbar.js
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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 { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { Snackbar as SnackbarMUI } from 'material-ui';
|
||||
import { darkBlack, grey800 } from 'material-ui/styles/colors';
|
||||
|
||||
import { closeSnackbar } from '~/redux/providers/snackbarActions';
|
||||
|
||||
const bodyStyle = {
|
||||
backgroundColor: darkBlack,
|
||||
borderStyle: 'solid',
|
||||
borderColor: grey800,
|
||||
borderWidth: '1px 1px 0 1px'
|
||||
};
|
||||
|
||||
class Snackbar extends Component {
|
||||
static propTypes = {
|
||||
closeSnackbar: PropTypes.func.isRequired,
|
||||
|
||||
open: PropTypes.bool,
|
||||
cooldown: PropTypes.number,
|
||||
message: PropTypes.any
|
||||
};
|
||||
|
||||
render () {
|
||||
const { open, message, cooldown } = this.props;
|
||||
|
||||
return (
|
||||
<SnackbarMUI
|
||||
open={ open }
|
||||
message={ message }
|
||||
autoHideDuration={ cooldown }
|
||||
bodyStyle={ bodyStyle }
|
||||
onRequestClose={ this.handleClose }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
this.props.closeSnackbar();
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { open, message, cooldown } = state.snackbar;
|
||||
|
||||
return { open, message, cooldown };
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({
|
||||
closeSnackbar
|
||||
}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Snackbar);
|
||||
17
js/src/shell/Application/Status/index.js
Normal file
17
js/src/shell/Application/Status/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 './status';
|
||||
65
js/src/shell/Application/Status/status.css
Normal file
65
js/src/shell/Application/Status/status.css
Normal file
@@ -0,0 +1,65 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
.status {
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
bottom: 0;
|
||||
color: #ccc;
|
||||
display: flex;
|
||||
font-size: 0.75em;
|
||||
left: 0;
|
||||
padding: .4em .5em;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.netinfo {
|
||||
color: #ddd;
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
text-align: right;
|
||||
|
||||
div {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.network {
|
||||
border-radius: 0.4em;
|
||||
line-height: 1.2;
|
||||
padding: 0.25em 0.5em;
|
||||
text-transform: uppercase;
|
||||
|
||||
&.live {
|
||||
background: rgb(0, 136, 0);
|
||||
}
|
||||
|
||||
&.test {
|
||||
background: rgb(136, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.upgrade {
|
||||
div {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
152
js/src/shell/Application/Status/status.js
Normal file
152
js/src/shell/Application/Status/status.js
Normal file
@@ -0,0 +1,152 @@
|
||||
// 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 { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { BlockStatus } from '~/ui';
|
||||
|
||||
import styles from './status.css';
|
||||
|
||||
class Status extends Component {
|
||||
static propTypes = {
|
||||
clientVersion: PropTypes.string,
|
||||
isTest: PropTypes.bool,
|
||||
netChain: PropTypes.string,
|
||||
netPeers: PropTypes.object,
|
||||
upgradeStore: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { clientVersion, isTest, netChain, netPeers } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ styles.status }>
|
||||
<div className={ styles.version }>
|
||||
{ clientVersion }
|
||||
</div>
|
||||
<div className={ styles.upgrade }>
|
||||
{ this.renderConsensus() }
|
||||
{ this.renderUpgradeButton() }
|
||||
</div>
|
||||
<div className={ styles.netinfo }>
|
||||
<BlockStatus />
|
||||
<div className={ `${styles.network} ${styles[isTest ? 'test' : 'live']}` }>
|
||||
{ netChain }
|
||||
</div>
|
||||
<div className={ styles.peers }>
|
||||
{ netPeers.active.toFormat() }/{ netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderConsensus () {
|
||||
const { upgradeStore } = this.props;
|
||||
|
||||
if (!upgradeStore || !upgradeStore.consensusCapability) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (upgradeStore.consensusCapability === 'capable') {
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='application.status.consensus.capable'
|
||||
defaultMessage='Capable'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (upgradeStore.consensusCapability.capableUntil) {
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='application.status.consensus.capableUntil'
|
||||
defaultMessage='Capable until #{blockNumber}'
|
||||
values={ {
|
||||
blockNumber: upgradeStore.consensusCapability.capableUntil
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (upgradeStore.consensusCapability.incapableSince) {
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='application.status.consensus.incapableSince'
|
||||
defaultMessage='Incapable since #{blockNumber}'
|
||||
values={ {
|
||||
blockNumber: upgradeStore.consensusCapability.incapableSince
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='application.status.consensus.unknown'
|
||||
defaultMessage='Unknown capability'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderUpgradeButton () {
|
||||
const { upgradeStore } = this.props;
|
||||
|
||||
if (!upgradeStore.available) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<a
|
||||
href='javascript:void(0)'
|
||||
onClick={ upgradeStore.openModal }
|
||||
>
|
||||
<FormattedMessage
|
||||
id='application.status.upgrade'
|
||||
defaultMessage='Upgrade'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { clientVersion, netPeers, netChain, isTest } = state.nodeStatus;
|
||||
|
||||
return {
|
||||
clientVersion,
|
||||
netPeers,
|
||||
netChain,
|
||||
isTest
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(Status);
|
||||
26
js/src/shell/Application/application.css
Normal file
26
js/src/shell/Application/application.css
Normal file
@@ -0,0 +1,26 @@
|
||||
/* 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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-bottom: 1.25em;
|
||||
}
|
||||
134
js/src/shell/Application/application.js
Normal file
134
js/src/shell/Application/application.js
Normal file
@@ -0,0 +1,134 @@
|
||||
// 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 { connect } from 'react-redux';
|
||||
|
||||
import UpgradeStore from '~/modals/UpgradeParity/store';
|
||||
|
||||
import Connection from '../Connection';
|
||||
import ParityBar from '../ParityBar';
|
||||
|
||||
import Snackbar from './Snackbar';
|
||||
import Container from './Container';
|
||||
import DappContainer from './DappContainer';
|
||||
import Extension from './Extension';
|
||||
import FrameError from './FrameError';
|
||||
import Status from './Status';
|
||||
import Store from './store';
|
||||
import Requests from './Requests';
|
||||
|
||||
import styles from './application.css';
|
||||
|
||||
const inFrame = window.parent !== window && window.parent.frames.length !== 0;
|
||||
|
||||
@observer
|
||||
class Application extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired,
|
||||
background: PropTypes.string
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
blockNumber: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
pending: PropTypes.array
|
||||
}
|
||||
|
||||
store = new Store(this.context.api);
|
||||
upgradeStore = UpgradeStore.get(this.context.api);
|
||||
|
||||
render () {
|
||||
const [root] = (window.location.hash || '').replace('#/', '').split('/');
|
||||
const isMinimized = root === 'app' || root === 'web';
|
||||
|
||||
if (process.env.NODE_ENV !== 'production' && root === 'playground') {
|
||||
return (
|
||||
<div>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (inFrame) {
|
||||
return (
|
||||
<FrameError />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
isMinimized
|
||||
? this.renderMinimized()
|
||||
: this.renderApp()
|
||||
}
|
||||
<Connection />
|
||||
<Requests />
|
||||
<ParityBar dapp={ isMinimized } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderApp () {
|
||||
const { blockNumber, children } = this.props;
|
||||
|
||||
return (
|
||||
<Container
|
||||
upgradeStore={ this.upgradeStore }
|
||||
onCloseFirstRun={ this.store.closeFirstrun }
|
||||
showFirstRun={ this.store.firstrunVisible }
|
||||
>
|
||||
<div className={ styles.content }>
|
||||
{ children }
|
||||
</div>
|
||||
{
|
||||
blockNumber
|
||||
? <Status upgradeStore={ this.upgradeStore } />
|
||||
: null
|
||||
}
|
||||
<Extension />
|
||||
<Snackbar />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
renderMinimized () {
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<DappContainer>
|
||||
{ children }
|
||||
</DappContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { blockNumber } = state.nodeStatus;
|
||||
const { hasAccounts } = state.personal;
|
||||
|
||||
return {
|
||||
blockNumber,
|
||||
hasAccounts
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(Application);
|
||||
17
js/src/shell/Application/index.js
Normal file
17
js/src/shell/Application/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 './application';
|
||||
84
js/src/shell/Application/store.js
Normal file
84
js/src/shell/Application/store.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 { action, observable } from 'mobx';
|
||||
import store from 'store';
|
||||
|
||||
const OLD_LS_FIRST_RUN_KEY = 'showFirstRun';
|
||||
const LS_FIRST_RUN_KEY = '_parity::showFirstRun';
|
||||
|
||||
export default class Store {
|
||||
@observable firstrunVisible = false;
|
||||
|
||||
constructor (api) {
|
||||
// Migrate the old key to the new one
|
||||
this._migrateStore();
|
||||
|
||||
this._api = api;
|
||||
// Show the first run if it hasn't been shown before
|
||||
// (thus an undefined value)
|
||||
this.firstrunVisible = store.get(LS_FIRST_RUN_KEY) === undefined;
|
||||
|
||||
this._checkAccounts();
|
||||
}
|
||||
|
||||
@action closeFirstrun = () => {
|
||||
this.toggleFirstrun(false);
|
||||
}
|
||||
|
||||
@action toggleFirstrun = (visible = false) => {
|
||||
this.firstrunVisible = visible;
|
||||
|
||||
// There's no need to write to storage that the
|
||||
// First Run should be visible
|
||||
if (!visible) {
|
||||
store.set(LS_FIRST_RUN_KEY, !!visible);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate the old LocalStorage ket format
|
||||
* to the new one
|
||||
*/
|
||||
_migrateStore () {
|
||||
const oldValue = store.get(OLD_LS_FIRST_RUN_KEY);
|
||||
const newValue = store.get(LS_FIRST_RUN_KEY);
|
||||
|
||||
if (newValue === undefined && oldValue !== undefined) {
|
||||
store.set(LS_FIRST_RUN_KEY, oldValue);
|
||||
store.remove(OLD_LS_FIRST_RUN_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAccounts () {
|
||||
return Promise
|
||||
.all([
|
||||
this._api.parity.listVaults(),
|
||||
this._api.parity.allAccountsInfo()
|
||||
])
|
||||
.then(([ vaults, info ]) => {
|
||||
const accounts = Object.keys(info).filter((address) => info[address].uuid);
|
||||
// Has accounts if any vaults or accounts
|
||||
const hasAccounts = (accounts && accounts.length > 0) || (vaults && vaults.length > 0);
|
||||
|
||||
// Show First Run if no accounts and no vaults
|
||||
this.toggleFirstrun(this.firstrunVisible || !hasAccounts);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('checkAccounts', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
114
js/src/shell/Connection/connection.css
Normal file
114
js/src/shell/Connection/connection.css
Normal file
@@ -0,0 +1,114 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
z-index: 20000
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 20001
|
||||
}
|
||||
|
||||
.body {
|
||||
margin: 0 auto;
|
||||
padding: 2em 4em;
|
||||
text-align: center;
|
||||
max-width: 40em;
|
||||
background: rgba(25, 25, 25, 0.75);
|
||||
color: rgb(208, 208, 208);
|
||||
box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 45px, rgba(0, 0, 0, 0.22) 0px 10px 18px
|
||||
}
|
||||
|
||||
.header {
|
||||
fontSize: 1.25em
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-top: 2em;
|
||||
line-height: 1.618em
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 0.75em;
|
||||
padding: 0 4em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.btnrow {
|
||||
text-align: right;
|
||||
padding: 0 4em;
|
||||
}
|
||||
|
||||
.icons {
|
||||
}
|
||||
|
||||
.icon,
|
||||
.iconSmall {
|
||||
display: inline-block;
|
||||
padding: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.iconName {
|
||||
}
|
||||
|
||||
.icon .svg {
|
||||
width: 6em !important;
|
||||
height: 6em !important;
|
||||
}
|
||||
|
||||
.iconSmall .svg {
|
||||
width: 3em !important;
|
||||
height: 3em !important;
|
||||
}
|
||||
|
||||
.console {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
font-size: 16px;
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
fill: rgb(0, 200, 0);
|
||||
}
|
||||
50% {
|
||||
fill: rgb(150, 200, 150);
|
||||
}
|
||||
100% {
|
||||
fill: rgb(0, 200, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.pulse {
|
||||
fill: red;
|
||||
animation-name: pulse;
|
||||
animation-duration: 1.5s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
199
js/src/shell/Connection/connection.js
Normal file
199
js/src/shell/Connection/connection.js
Normal file
@@ -0,0 +1,199 @@
|
||||
// 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 { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Input } from '~/ui';
|
||||
import { CompareIcon, ComputerIcon, DashboardIcon, VpnIcon } from '~/ui/Icons';
|
||||
|
||||
import styles from './connection.css';
|
||||
|
||||
class Connection extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
isConnected: PropTypes.bool,
|
||||
isConnecting: PropTypes.bool,
|
||||
needsToken: PropTypes.bool
|
||||
}
|
||||
|
||||
state = {
|
||||
loading: false,
|
||||
token: '',
|
||||
validToken: false
|
||||
}
|
||||
|
||||
render () {
|
||||
const { isConnecting, isConnected, needsToken } = this.props;
|
||||
|
||||
if (!isConnecting && isConnected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={ styles.overlay } />
|
||||
<div className={ styles.modal }>
|
||||
<div className={ styles.body }>
|
||||
<div className={ styles.icons }>
|
||||
<div className={ styles.icon }>
|
||||
<ComputerIcon className={ styles.svg } />
|
||||
</div>
|
||||
<div className={ styles.iconSmall }>
|
||||
<CompareIcon className={ `${styles.svg} ${styles.pulse}` } />
|
||||
</div>
|
||||
<div className={ styles.icon }>
|
||||
{
|
||||
needsToken
|
||||
? <VpnIcon className={ styles.svg } />
|
||||
: <DashboardIcon className={ styles.svg } />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
needsToken
|
||||
? this.renderSigner()
|
||||
: this.renderPing()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSigner () {
|
||||
const { loading, token, validToken } = this.state;
|
||||
const { isConnecting, needsToken } = this.props;
|
||||
|
||||
if (needsToken && !isConnecting) {
|
||||
return (
|
||||
<div className={ styles.info }>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='connection.noConnection'
|
||||
defaultMessage='Unable to make a connection to the Parity Secure API. To update your secure token or to generate a new one, run {newToken} and paste the generated token into the space below.'
|
||||
values={ {
|
||||
newToken: <span className={ styles.console }>parity signer new-token</span>
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.form }>
|
||||
<Input
|
||||
disabled={ loading }
|
||||
error={
|
||||
validToken || (!token || !token.length)
|
||||
? null
|
||||
: (
|
||||
<FormattedMessage
|
||||
id='connection.invalidToken'
|
||||
defaultMessage='invalid signer token'
|
||||
/>
|
||||
)
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='connection.token.hint'
|
||||
defaultMessage='a generated token from Parity'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='connection.token.label'
|
||||
defaultMessage='secure token'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onChangeToken }
|
||||
value={ token }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.info }>
|
||||
<FormattedMessage
|
||||
id='connection.connectingAPI'
|
||||
defaultMessage='Connecting to the Parity Secure API.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPing () {
|
||||
return (
|
||||
<div className={ styles.info }>
|
||||
<FormattedMessage
|
||||
id='connection.connectingNode'
|
||||
defaultMessage='Connecting to the Parity Node. If this informational message persists, please ensure that your Parity node is running and reachable on the network.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
validateToken = (_token) => {
|
||||
const token = _token.trim();
|
||||
const validToken = /^[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}$/.test(token);
|
||||
|
||||
return {
|
||||
token,
|
||||
validToken
|
||||
};
|
||||
}
|
||||
|
||||
onChangeToken = (event, _token) => {
|
||||
const { token, validToken } = this.validateToken(_token || event.target.value);
|
||||
|
||||
this.setState({ token, validToken }, () => {
|
||||
validToken && this.setToken();
|
||||
});
|
||||
}
|
||||
|
||||
setToken = () => {
|
||||
const { api } = this.context;
|
||||
const { token } = this.state;
|
||||
|
||||
this.setState({ loading: true });
|
||||
|
||||
return api
|
||||
.updateToken(token, 0)
|
||||
.then((isValid) => {
|
||||
this.setState({
|
||||
loading: isValid || false,
|
||||
validToken: isValid
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { isConnected, isConnecting, needsToken } = state.nodeStatus;
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
isConnecting,
|
||||
needsToken
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(Connection);
|
||||
156
js/src/shell/Connection/connection.spec.js
Normal file
156
js/src/shell/Connection/connection.spec.js
Normal file
@@ -0,0 +1,156 @@
|
||||
// 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 { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Connection from './';
|
||||
|
||||
let api;
|
||||
let component;
|
||||
let instance;
|
||||
|
||||
function createApi () {
|
||||
return {
|
||||
updateToken: sinon.stub().resolves()
|
||||
};
|
||||
}
|
||||
|
||||
function createRedux (isConnected = true, isConnecting = false, needsToken = false) {
|
||||
return {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
nodeStatus: {
|
||||
isConnected,
|
||||
isConnecting,
|
||||
needsToken
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function render (store) {
|
||||
api = createApi();
|
||||
component = shallow(
|
||||
<Connection />,
|
||||
{ context: { store: store || createRedux() } }
|
||||
).find('Connection').shallow({ context: { api } });
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Connection', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
it('does not render when connected', () => {
|
||||
expect(render(createRedux(true)).find('div')).to.have.length(0);
|
||||
});
|
||||
|
||||
describe('renderPing', () => {
|
||||
it('renders the connecting to node message', () => {
|
||||
render();
|
||||
const ping = shallow(instance.renderPing());
|
||||
|
||||
expect(ping.find('FormattedMessage').props().id).to.equal('connection.connectingNode');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderSigner', () => {
|
||||
it('renders the connecting to api message when isConnecting === true', () => {
|
||||
render(createRedux(false, true));
|
||||
const signer = shallow(instance.renderSigner());
|
||||
|
||||
expect(signer.find('FormattedMessage').props().id).to.equal('connection.connectingAPI');
|
||||
});
|
||||
|
||||
it('renders token input when needsToken == true & isConnecting === false', () => {
|
||||
render(createRedux(false, false, true));
|
||||
const signer = shallow(instance.renderSigner());
|
||||
|
||||
expect(signer.find('FormattedMessage').first().props().id).to.equal('connection.noConnection');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateToken', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('trims whitespace from passed tokens', () => {
|
||||
expect(instance.validateToken(' \t test ing\t ').token).to.equal('test ing');
|
||||
});
|
||||
|
||||
it('validates 4-4-4-4 format', () => {
|
||||
expect(instance.validateToken('1234-5678-90ab-cdef').validToken).to.be.true;
|
||||
});
|
||||
|
||||
it('validates 4-4-4-4 format (with trimmable whitespace)', () => {
|
||||
expect(instance.validateToken(' \t 1234-5678-90ab-cdef \t ').validToken).to.be.true;
|
||||
});
|
||||
|
||||
it('validates 4444 format', () => {
|
||||
expect(instance.validateToken('1234567890abcdef').validToken).to.be.true;
|
||||
});
|
||||
|
||||
it('validates 4444 format (with trimmable whitespace)', () => {
|
||||
expect(instance.validateToken(' \t 1234567890abcdef \t ').validToken).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChangeToken', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
sinon.spy(instance, 'setToken');
|
||||
sinon.spy(instance, 'validateToken');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.setToken.restore();
|
||||
instance.validateToken.restore();
|
||||
});
|
||||
|
||||
it('validates tokens passed', () => {
|
||||
instance.onChangeToken({ target: { value: 'testing' } });
|
||||
expect(instance.validateToken).to.have.been.calledWith('testing');
|
||||
});
|
||||
|
||||
it('sets the token on the api when valid', () => {
|
||||
instance.onChangeToken({ target: { value: '1234-5678-90ab-cdef' } });
|
||||
expect(instance.setToken).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('setToken', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('calls the api.updateToken', () => {
|
||||
component.setState({ token: 'testing' });
|
||||
|
||||
return instance.setToken().then(() => {
|
||||
expect(api.updateToken).to.have.been.calledWith('testing');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
17
js/src/shell/Connection/index.js
Normal file
17
js/src/shell/Connection/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 './connection';
|
||||
43
js/src/shell/Dapp/dapp.css
Normal file
43
js/src/shell/Dapp/dapp.css
Normal file
@@ -0,0 +1,43 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
.frame {
|
||||
background: white;
|
||||
border: 0;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.full {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: white;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
padding: 5em;
|
||||
font-size: 2em;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
139
js/src/shell/Dapp/dapp.js
Normal file
139
js/src/shell/Dapp/dapp.js
Normal file
@@ -0,0 +1,139 @@
|
||||
// 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 { observer } from 'mobx-react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import DappsStore from '../Dapps/dappsStore';
|
||||
|
||||
import styles from './dapp.css';
|
||||
|
||||
@observer
|
||||
export default class Dapp extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object
|
||||
};
|
||||
|
||||
state = {
|
||||
app: null,
|
||||
loading: true
|
||||
};
|
||||
|
||||
store = DappsStore.get(this.context.api);
|
||||
|
||||
componentWillMount () {
|
||||
const { id } = this.props.params;
|
||||
|
||||
this.loadApp(id);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.id !== this.props.params.id) {
|
||||
this.loadApp(nextProps.params.id);
|
||||
}
|
||||
}
|
||||
|
||||
loadApp (id) {
|
||||
this.setState({ loading: true });
|
||||
|
||||
this.store
|
||||
.loadApp(id)
|
||||
.then((app) => {
|
||||
this.setState({ loading: false, app });
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({ loading: false });
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { dappsUrl } = this.context.api;
|
||||
const { params } = this.props;
|
||||
const { app, loading } = this.state;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className={ styles.full }>
|
||||
<div className={ styles.text }>
|
||||
<FormattedMessage
|
||||
id='dapp.loading'
|
||||
defaultMessage='Loading'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!app) {
|
||||
return (
|
||||
<div className={ styles.full }>
|
||||
<div className={ styles.text }>
|
||||
<FormattedMessage
|
||||
id='dapp.unavailable'
|
||||
defaultMessage='The dapp cannot be reached'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let src = null;
|
||||
|
||||
switch (app.type) {
|
||||
case 'local':
|
||||
src = `${dappsUrl}/${app.id}/`;
|
||||
break;
|
||||
case 'network':
|
||||
src = `${dappsUrl}/${app.contentHash}/`;
|
||||
break;
|
||||
default:
|
||||
let dapphost = process.env.DAPPS_URL || (
|
||||
process.env.NODE_ENV === 'production' && !app.secure
|
||||
? `${dappsUrl}/ui`
|
||||
: ''
|
||||
);
|
||||
|
||||
if (dapphost === '/') {
|
||||
dapphost = '';
|
||||
}
|
||||
|
||||
src = `${dapphost}/${app.url}.html`;
|
||||
break;
|
||||
}
|
||||
|
||||
let hash = '';
|
||||
|
||||
if (params.details) {
|
||||
hash = `#/${params.details}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<iframe
|
||||
className={ styles.frame }
|
||||
frameBorder={ 0 }
|
||||
name={ name }
|
||||
sandbox='allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation'
|
||||
scrolling='auto'
|
||||
src={ `${src}${hash}` }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
17
js/src/shell/Dapp/index.js
Normal file
17
js/src/shell/Dapp/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 './dapp';
|
||||
220
js/src/shell/Dapps/dappStore.spec.js
Normal file
220
js/src/shell/Dapps/dappStore.spec.js
Normal file
@@ -0,0 +1,220 @@
|
||||
// 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 BigNumber from 'bignumber.js';
|
||||
import sinon from 'sinon';
|
||||
import localStore from 'store';
|
||||
|
||||
import Contracts from '~/contracts';
|
||||
|
||||
import Store, { LS_KEY_DISPLAY } from './dappsStore';
|
||||
|
||||
const APPID_DAPPREG = '0x7bbc4f1a27628781b96213e781a1b8eec6982c1db8fac739af6e4c5a55862c03';
|
||||
const APPID_GHH = '0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75';
|
||||
const APPID_LOCALTX = '0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d';
|
||||
const APPID_TOKENDEPLOY = '0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f';
|
||||
const FETCH_OK = {
|
||||
ok: true,
|
||||
status: 200
|
||||
};
|
||||
|
||||
let globalContractsGet;
|
||||
let globalFetch;
|
||||
|
||||
function stubGlobals () {
|
||||
globalContractsGet = Contracts.get;
|
||||
globalFetch = global.fetch;
|
||||
|
||||
Contracts.get = () => {
|
||||
return {
|
||||
dappReg: {
|
||||
at: sinon.stub().resolves([[0, 1, 2, 3], 'appOwner']),
|
||||
count: sinon.stub().resolves(new BigNumber(1)),
|
||||
getContract: sinon.stub().resolves({}),
|
||||
getContent: sinon.stub().resolves([0, 1, 2, 3]),
|
||||
getImage: sinon.stub().resolves([0, 1, 2, 3]),
|
||||
getManifest: sinon.stub().resolves([0, 1, 2, 3])
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
global.fetch = (url) => {
|
||||
switch (url) {
|
||||
case '/api/apps':
|
||||
return Promise.resolve(Object.assign({}, FETCH_OK, {
|
||||
json: sinon.stub().resolves([]) // TODO: Local stubs in here
|
||||
}));
|
||||
|
||||
default:
|
||||
console.log('Unknown fetch stub endpoint', url);
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function restoreGlobals () {
|
||||
Contracts.get = globalContractsGet;
|
||||
global.fetch = globalFetch;
|
||||
}
|
||||
|
||||
let api;
|
||||
let store;
|
||||
|
||||
function create () {
|
||||
api = {};
|
||||
store = new Store(api);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
describe('views/Dapps/DappStore', () => {
|
||||
beforeEach(() => {
|
||||
stubGlobals();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
restoreGlobals();
|
||||
});
|
||||
|
||||
describe('@action', () => {
|
||||
const defaultViews = {
|
||||
[APPID_TOKENDEPLOY]: { visible: false },
|
||||
[APPID_DAPPREG]: { visible: true }
|
||||
};
|
||||
|
||||
describe('setDisplayApps', () => {
|
||||
beforeEach(() => {
|
||||
create();
|
||||
store.setDisplayApps(defaultViews);
|
||||
});
|
||||
|
||||
it('sets from empty start', () => {
|
||||
expect(store.displayApps).to.deep.equal(defaultViews);
|
||||
});
|
||||
|
||||
it('overrides single keys, keeping existing', () => {
|
||||
store.setDisplayApps({ [APPID_TOKENDEPLOY]: { visible: true } });
|
||||
|
||||
expect(store.displayApps).to.deep.equal(
|
||||
Object.assign({}, defaultViews, { [APPID_TOKENDEPLOY]: { visible: true } })
|
||||
);
|
||||
});
|
||||
|
||||
it('extends with new keys, keeping existing', () => {
|
||||
store.setDisplayApps({ 'test': { visible: true } });
|
||||
|
||||
expect(store.displayApps).to.deep.equal(
|
||||
Object.assign({}, defaultViews, { 'test': { visible: true } })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideApp/showApp', () => {
|
||||
beforeEach(() => {
|
||||
localStore.set(LS_KEY_DISPLAY, defaultViews);
|
||||
|
||||
create().readDisplayApps();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStore.set(LS_KEY_DISPLAY, {});
|
||||
});
|
||||
|
||||
it('disables visibility', () => {
|
||||
store.hideApp(APPID_DAPPREG);
|
||||
|
||||
expect(store.displayApps[APPID_DAPPREG].visible).to.be.false;
|
||||
expect(localStore.get(LS_KEY_DISPLAY)).to.deep.equal(
|
||||
Object.assign({}, defaultViews, { [APPID_DAPPREG]: { visible: false } })
|
||||
);
|
||||
});
|
||||
|
||||
it('enables visibility', () => {
|
||||
store.showApp(APPID_TOKENDEPLOY);
|
||||
|
||||
expect(store.displayApps[APPID_TOKENDEPLOY].visible).to.be.true;
|
||||
expect(localStore.get(LS_KEY_DISPLAY)).to.deep.equal(
|
||||
Object.assign({}, defaultViews, { [APPID_TOKENDEPLOY]: { visible: true } })
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps visibility state', () => {
|
||||
store.hideApp(APPID_TOKENDEPLOY);
|
||||
store.showApp(APPID_DAPPREG);
|
||||
|
||||
expect(store.displayApps[APPID_TOKENDEPLOY].visible).to.be.false;
|
||||
expect(store.displayApps[APPID_DAPPREG].visible).to.be.true;
|
||||
expect(localStore.get(LS_KEY_DISPLAY)).to.deep.equal(defaultViews);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readDisplayApps/writeDisplayApps', () => {
|
||||
beforeEach(() => {
|
||||
localStore.set(LS_KEY_DISPLAY, defaultViews);
|
||||
|
||||
create().readDisplayApps();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStore.set(LS_KEY_DISPLAY, {});
|
||||
});
|
||||
|
||||
it('loads visibility from storage', () => {
|
||||
expect(store.displayApps).to.deep.equal(defaultViews);
|
||||
});
|
||||
|
||||
it('saves visibility to storage', () => {
|
||||
store.setDisplayApps({ [APPID_TOKENDEPLOY]: { visible: true } });
|
||||
store.writeDisplayApps();
|
||||
|
||||
expect(localStore.get(LS_KEY_DISPLAY)).to.deep.equal(
|
||||
Object.assign({}, defaultViews, { [APPID_TOKENDEPLOY]: { visible: true } })
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('saved views', () => {
|
||||
beforeEach(() => {
|
||||
localStore.set(LS_KEY_DISPLAY, {
|
||||
[APPID_TOKENDEPLOY]: { visible: false },
|
||||
[APPID_DAPPREG]: { visible: true }
|
||||
});
|
||||
|
||||
return create().loadAllApps();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStore.set(LS_KEY_DISPLAY, {});
|
||||
});
|
||||
|
||||
it('disables based on saved keys', () => {
|
||||
expect(store.displayApps[APPID_TOKENDEPLOY].visible).to.be.false;
|
||||
});
|
||||
|
||||
it('enables based on saved keys', () => {
|
||||
expect(store.displayApps[APPID_DAPPREG].visible).to.be.true;
|
||||
});
|
||||
|
||||
it('keeps non-sepcified disabled keys', () => {
|
||||
expect(store.displayApps[APPID_GHH].visible).to.be.false;
|
||||
});
|
||||
|
||||
it('keeps non-specified enabled keys', () => {
|
||||
expect(store.displayApps[APPID_LOCALTX].visible).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
27
js/src/shell/Dapps/dapps.css
Normal file
27
js/src/shell/Dapps/dapps.css
Normal file
@@ -0,0 +1,27 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
.overlay {
|
||||
line-height: 1.5em;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
max-width: 980px;
|
||||
|
||||
&>div:first-child {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
}
|
||||
176
js/src/shell/Dapps/dapps.js
Normal file
176
js/src/shell/Dapps/dapps.js
Normal file
@@ -0,0 +1,176 @@
|
||||
// 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 { omitBy } from 'lodash';
|
||||
import { Checkbox } from 'material-ui';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { DappPermissions, DappsVisible } from '~/modals';
|
||||
import PermissionStore from '~/modals/DappPermissions/store';
|
||||
import { Actionbar, Button, DappCard, Page, SectionList } from '~/ui';
|
||||
import { LockedIcon, VisibleIcon } from '~/ui/Icons';
|
||||
|
||||
import DappsStore from './dappsStore';
|
||||
|
||||
import styles from './dapps.css';
|
||||
|
||||
@observer
|
||||
class Dapps extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
store = DappsStore.get(this.context.api);
|
||||
permissionStore = new PermissionStore(this.context.api);
|
||||
|
||||
componentWillMount () {
|
||||
this.store.loadAllApps();
|
||||
}
|
||||
|
||||
render () {
|
||||
let externalOverlay = null;
|
||||
|
||||
if (this.store.externalOverlayVisible) {
|
||||
externalOverlay = (
|
||||
<div className={ styles.overlay }>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='dapps.external.warning'
|
||||
defaultMessage='Applications made available on the network by 3rd-party authors are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each before interacting.'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
className={ styles.accept }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='dapps.external.accept'
|
||||
defaultMessage='I understand that these applications are not affiliated with Parity'
|
||||
/>
|
||||
}
|
||||
checked={ false }
|
||||
onCheck={ this.onClickAcceptExternal }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DappPermissions permissionStore={ this.permissionStore } />
|
||||
<DappsVisible store={ this.store } />
|
||||
<Actionbar
|
||||
className={ styles.toolbar }
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='dapps.label'
|
||||
defaultMessage='Decentralized Applications'
|
||||
/>
|
||||
}
|
||||
buttons={ [
|
||||
<Button
|
||||
icon={ <VisibleIcon /> }
|
||||
key='edit'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='dapps.button.edit'
|
||||
defaultMessage='edit'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.openModal }
|
||||
/>,
|
||||
<Button
|
||||
icon={ <LockedIcon /> }
|
||||
key='permissions'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='dapps.button.permissions'
|
||||
defaultMessage='permissions'
|
||||
/>
|
||||
}
|
||||
onClick={ this.openPermissionsModal }
|
||||
/>
|
||||
] }
|
||||
/>
|
||||
<Page>
|
||||
<div>{ this.renderList(this.store.visibleViews) }</div>
|
||||
<div>{ this.renderList(this.store.visibleLocal) }</div>
|
||||
<div>{ this.renderList(this.store.visibleBuiltin) }</div>
|
||||
<div>{ this.renderList(this.store.visibleNetwork, externalOverlay) }</div>
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderList (items, overlay) {
|
||||
return (
|
||||
<SectionList
|
||||
items={ items }
|
||||
overlay={ overlay }
|
||||
renderItem={ this.renderApp }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderApp = (app) => {
|
||||
return (
|
||||
<DappCard
|
||||
app={ app }
|
||||
key={ app.id }
|
||||
showLink
|
||||
showTags
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onClickAcceptExternal = () => {
|
||||
this.store.closeExternalOverlay();
|
||||
}
|
||||
|
||||
openPermissionsModal = () => {
|
||||
const { accounts } = this.props;
|
||||
|
||||
this.permissionStore.openModal(accounts);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { accounts } = state.personal;
|
||||
|
||||
/**
|
||||
* Do not show the Wallet Accounts in the Dapps
|
||||
* Permissions Modal. This will come in v1.6, but
|
||||
* for now it would break dApps using Web3...
|
||||
*/
|
||||
const _accounts = omitBy(accounts, (account) => account.wallet);
|
||||
|
||||
return {
|
||||
accounts: _accounts
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(Dapps);
|
||||
302
js/src/shell/Dapps/dappsStore.js
Normal file
302
js/src/shell/Dapps/dappsStore.js
Normal file
@@ -0,0 +1,302 @@
|
||||
// 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 EventEmitter from 'eventemitter3';
|
||||
import { action, computed, observable, transaction } from 'mobx';
|
||||
import store from 'store';
|
||||
|
||||
import Contracts from '~/contracts';
|
||||
import {
|
||||
fetchBuiltinApps, fetchLocalApps,
|
||||
fetchRegistryAppIds, fetchRegistryApp,
|
||||
subscribeToChanges
|
||||
} from '~/util/dapps';
|
||||
|
||||
const LS_KEY_DISPLAY = 'displayApps';
|
||||
const LS_KEY_EXTERNAL_ACCEPT = 'acceptExternal';
|
||||
const BUILTIN_APPS_KEY = 'BUILTIN_APPS_KEY';
|
||||
|
||||
let instance = null;
|
||||
|
||||
export default class DappsStore extends EventEmitter {
|
||||
@observable apps = [];
|
||||
@observable displayApps = {};
|
||||
@observable modalOpen = false;
|
||||
@observable externalOverlayVisible = true;
|
||||
|
||||
_api = null;
|
||||
_subscriptions = {};
|
||||
_cachedApps = {};
|
||||
_manifests = {};
|
||||
_registryAppsIds = null;
|
||||
|
||||
constructor (api) {
|
||||
super();
|
||||
|
||||
this._api = api;
|
||||
|
||||
this.readDisplayApps();
|
||||
this.loadExternalOverlay();
|
||||
this.subscribeToChanges();
|
||||
}
|
||||
|
||||
static get (api) {
|
||||
if (!instance) {
|
||||
instance = new DappsStore(api);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@computed get sortedBuiltin () {
|
||||
return this.apps.filter((app) => app.type === 'builtin');
|
||||
}
|
||||
|
||||
@computed get sortedLocal () {
|
||||
return this.apps.filter((app) => app.type === 'local');
|
||||
}
|
||||
|
||||
@computed get sortedNetwork () {
|
||||
return this.apps.filter((app) => app.type === 'network');
|
||||
}
|
||||
|
||||
@computed get visibleApps () {
|
||||
return this.apps.filter((app) => this.displayApps[app.id] && this.displayApps[app.id].visible);
|
||||
}
|
||||
|
||||
@computed get visibleBuiltin () {
|
||||
return this.visibleApps.filter((app) => !app.noselect && app.type === 'builtin');
|
||||
}
|
||||
|
||||
@computed get visibleLocal () {
|
||||
return this.visibleApps.filter((app) => app.type === 'local');
|
||||
}
|
||||
|
||||
@computed get visibleNetwork () {
|
||||
return this.visibleApps.filter((app) => app.type === 'network');
|
||||
}
|
||||
|
||||
@computed get visibleViews () {
|
||||
return this.visibleApps.filter((app) => !app.noselect && app.type === 'view');
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find the app from the local (local or builtin)
|
||||
* apps, else fetch from the node
|
||||
*/
|
||||
loadApp (id) {
|
||||
const { dappReg } = Contracts.get(this._api);
|
||||
|
||||
return this
|
||||
.loadLocalApps()
|
||||
.then(() => {
|
||||
const app = this.apps.find((app) => app.id === id);
|
||||
|
||||
if (app) {
|
||||
return app;
|
||||
}
|
||||
|
||||
return this.fetchRegistryApp(dappReg, id, true);
|
||||
})
|
||||
.then((app) => {
|
||||
this.emit('loaded', app);
|
||||
return app;
|
||||
});
|
||||
}
|
||||
|
||||
loadLocalApps () {
|
||||
return Promise
|
||||
.all([
|
||||
this.fetchBuiltinApps().then((apps) => this.addApps(apps)),
|
||||
this.fetchLocalApps().then((apps) => this.addApps(apps))
|
||||
]);
|
||||
}
|
||||
|
||||
loadAllApps () {
|
||||
const { dappReg } = Contracts.get(this._api);
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
this.loadLocalApps(),
|
||||
this.fetchRegistryApps(dappReg).then((apps) => this.addApps(apps))
|
||||
])
|
||||
.then(this.writeDisplayApps);
|
||||
}
|
||||
|
||||
subscribeToChanges () {
|
||||
const { dappReg } = Contracts.get(this._api);
|
||||
|
||||
// Unsubscribe from previous subscriptions, if any
|
||||
if (this._subscriptions.block) {
|
||||
this._api.unsubscribe(this._subscriptions.block);
|
||||
}
|
||||
|
||||
if (this._subscriptions.filter) {
|
||||
this._api.eth.uninstallFilter(this._subscriptions.filter);
|
||||
}
|
||||
|
||||
// Subscribe to dapps reg changes
|
||||
subscribeToChanges(this._api, dappReg, (appIds) => {
|
||||
const updates = appIds.map((appId) => {
|
||||
return this.fetchRegistryApp(dappReg, appId, true);
|
||||
});
|
||||
|
||||
Promise
|
||||
.all(updates)
|
||||
.then((apps) => {
|
||||
this.addApps(apps);
|
||||
});
|
||||
}).then((subscriptions) => {
|
||||
this._subscriptions = subscriptions;
|
||||
});
|
||||
}
|
||||
|
||||
fetchBuiltinApps (force = false) {
|
||||
if (!force && this._cachedApps[BUILTIN_APPS_KEY] !== undefined) {
|
||||
return Promise.resolve(this._cachedApps[BUILTIN_APPS_KEY]);
|
||||
}
|
||||
|
||||
this._cachedApps[BUILTIN_APPS_KEY] = fetchBuiltinApps(this._api)
|
||||
.then((apps) => {
|
||||
this._cachedApps[BUILTIN_APPS_KEY] = apps;
|
||||
return apps;
|
||||
});
|
||||
|
||||
return Promise.resolve(this._cachedApps[BUILTIN_APPS_KEY]);
|
||||
}
|
||||
|
||||
fetchLocalApps () {
|
||||
return fetchLocalApps(this._api);
|
||||
}
|
||||
|
||||
fetchRegistryAppIds (force = false) {
|
||||
if (!force && this._registryAppsIds) {
|
||||
return Promise.resolve(this._registryAppsIds);
|
||||
}
|
||||
|
||||
this._registryAppsIds = fetchRegistryAppIds(this._api)
|
||||
.then((appIds) => {
|
||||
this._registryAppsIds = appIds;
|
||||
return this._registryAppsIds;
|
||||
});
|
||||
|
||||
return Promise.resolve(this._registryAppsIds);
|
||||
}
|
||||
|
||||
fetchRegistryApp (dappReg, appId, force = false) {
|
||||
if (!force && this._cachedApps[appId] !== undefined) {
|
||||
return Promise.resolve(this._cachedApps[appId]);
|
||||
}
|
||||
|
||||
this._cachedApps[appId] = fetchRegistryApp(this._api, dappReg, appId)
|
||||
.then((dapp) => {
|
||||
this._cachedApps[appId] = dapp;
|
||||
return dapp;
|
||||
});
|
||||
|
||||
return Promise.resolve(this._cachedApps[appId]);
|
||||
}
|
||||
|
||||
fetchRegistryApps (dappReg) {
|
||||
return this
|
||||
.fetchRegistryAppIds()
|
||||
.then((appIds) => {
|
||||
const promises = appIds.map((appId) => {
|
||||
// Fetch the Dapp and display it ASAP
|
||||
return this
|
||||
.fetchRegistryApp(dappReg, appId)
|
||||
.then((app) => {
|
||||
if (app) {
|
||||
this.addApps([ app ]);
|
||||
}
|
||||
|
||||
return app;
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
@action openModal = () => {
|
||||
this.modalOpen = true;
|
||||
}
|
||||
|
||||
@action closeModal = () => {
|
||||
this.modalOpen = false;
|
||||
}
|
||||
|
||||
@action closeExternalOverlay = () => {
|
||||
this.externalOverlayVisible = false;
|
||||
store.set(LS_KEY_EXTERNAL_ACCEPT, true);
|
||||
}
|
||||
|
||||
@action loadExternalOverlay () {
|
||||
this.externalOverlayVisible = !(store.get(LS_KEY_EXTERNAL_ACCEPT) || false);
|
||||
}
|
||||
|
||||
@action hideApp = (id) => {
|
||||
this.setDisplayApps({ [id]: { visible: false } });
|
||||
this.writeDisplayApps();
|
||||
}
|
||||
|
||||
@action showApp = (id) => {
|
||||
this.setDisplayApps({ [id]: { visible: true } });
|
||||
this.writeDisplayApps();
|
||||
}
|
||||
|
||||
@action readDisplayApps = () => {
|
||||
this.displayApps = store.get(LS_KEY_DISPLAY) || {};
|
||||
}
|
||||
|
||||
@action writeDisplayApps = () => {
|
||||
store.set(LS_KEY_DISPLAY, this.displayApps);
|
||||
}
|
||||
|
||||
@action setDisplayApps = (displayApps) => {
|
||||
this.displayApps = Object.assign({}, this.displayApps, displayApps);
|
||||
};
|
||||
|
||||
@action addApps = (_apps = []) => {
|
||||
transaction(() => {
|
||||
const apps = _apps.filter((app) => app);
|
||||
|
||||
// Get new apps IDs if available
|
||||
const newAppsIds = apps
|
||||
.map((app) => app.id)
|
||||
.filter((id) => id);
|
||||
|
||||
this.apps = this.apps
|
||||
.filter((app) => !app.id || !newAppsIds.includes(app.id))
|
||||
.concat(apps || [])
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const visibility = {};
|
||||
|
||||
apps.forEach((app) => {
|
||||
if (!this.displayApps[app.id]) {
|
||||
visibility[app.id] = { visible: app.visible };
|
||||
}
|
||||
});
|
||||
|
||||
this.setDisplayApps(visibility);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
LS_KEY_DISPLAY
|
||||
};
|
||||
17
js/src/shell/Dapps/index.js
Normal file
17
js/src/shell/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';
|
||||
129
js/src/shell/ParityBar/accountStore.js
Normal file
129
js/src/shell/ParityBar/accountStore.js
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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';
|
||||
|
||||
export default class AccountStore {
|
||||
@observable accounts = [];
|
||||
@observable defaultAccount = null;
|
||||
@observable isLoading = false;
|
||||
|
||||
constructor (api) {
|
||||
this._api = api;
|
||||
|
||||
this.loadDefaultAccount()
|
||||
.then(() => this.loadAccounts());
|
||||
|
||||
this.subscribeDefaultAccount();
|
||||
}
|
||||
|
||||
@action setAccounts = (accounts) => {
|
||||
this.accounts = accounts;
|
||||
}
|
||||
|
||||
@action setDefaultAccount = (defaultAccount) => {
|
||||
transaction(() => {
|
||||
this.accounts = this.accounts.map((account) => {
|
||||
account.checked = account.address === defaultAccount;
|
||||
|
||||
return account;
|
||||
});
|
||||
|
||||
this.defaultAccount = defaultAccount;
|
||||
});
|
||||
}
|
||||
|
||||
@action setLoading = (isLoading) => {
|
||||
this.isLoading = isLoading;
|
||||
}
|
||||
|
||||
makeDefaultAccount = (defaultAddress) => {
|
||||
this.setDefaultAccount(defaultAddress);
|
||||
|
||||
return this._api.parity
|
||||
.setNewDappsDefaultAddress(defaultAddress)
|
||||
.catch((error) => {
|
||||
console.warn('makeDefaultAccount', error);
|
||||
});
|
||||
}
|
||||
|
||||
loadDefaultAccount () {
|
||||
return this._api.parity
|
||||
.defaultAccount()
|
||||
.then((address) => this.setDefaultAccount(address));
|
||||
}
|
||||
|
||||
loadAccounts () {
|
||||
this.setLoading(true);
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
this._api.parity.getNewDappsAddresses(),
|
||||
this._api.parity.allAccountsInfo()
|
||||
])
|
||||
.then(([whitelist, allAccounts]) => {
|
||||
transaction(() => {
|
||||
const accounts = Object
|
||||
.keys(allAccounts)
|
||||
.filter((address) => {
|
||||
const account = allAccounts[address];
|
||||
const isAccount = account.uuid;
|
||||
const isExternal = account.meta && (account.meta.external || account.meta.hardware);
|
||||
const isWhitelisted = !whitelist || whitelist.includes(address);
|
||||
|
||||
return (isAccount || isExternal) && isWhitelisted;
|
||||
})
|
||||
.map((address) => {
|
||||
return {
|
||||
...allAccounts[address],
|
||||
checked: address === this.defaultAccount,
|
||||
address
|
||||
};
|
||||
});
|
||||
|
||||
this.setLoading(false);
|
||||
this.setAccounts(accounts);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setLoading(false);
|
||||
console.warn('loadAccounts', error);
|
||||
});
|
||||
}
|
||||
|
||||
subscribeDefaultAccount () {
|
||||
const promiseDefaultAccount = this._api.subscribe('parity_defaultAccount', (error, defaultAccount) => {
|
||||
if (!error) {
|
||||
this.setDefaultAccount(defaultAccount);
|
||||
}
|
||||
});
|
||||
|
||||
const promiseEthAccounts = this._api.subscribe('eth_accounts', (error) => {
|
||||
if (!error) {
|
||||
this.loadAccounts();
|
||||
}
|
||||
});
|
||||
|
||||
const promiseAccountsInfo = this._api
|
||||
.subscribe('parity_allAccountsInfo', (error, accountsInfo) => {
|
||||
if (!error) {
|
||||
this.loadAccounts();
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all([ promiseDefaultAccount, promiseEthAccounts, promiseAccountsInfo ]);
|
||||
}
|
||||
}
|
||||
112
js/src/shell/ParityBar/accountStore.spec.js
Normal file
112
js/src/shell/ParityBar/accountStore.spec.js
Normal file
@@ -0,0 +1,112 @@
|
||||
// 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 sinon from 'sinon';
|
||||
|
||||
import AccountStore from './accountStore';
|
||||
|
||||
import { ACCOUNT_DEFAULT, ACCOUNT_NEW, createApi } from './parityBar.test.js';
|
||||
|
||||
let api;
|
||||
let store;
|
||||
|
||||
function create () {
|
||||
api = createApi();
|
||||
store = new AccountStore(api);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
describe('views/ParityBar/AccountStore', () => {
|
||||
beforeEach(() => {
|
||||
create();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('subscribes to defaultAccount', () => {
|
||||
expect(api.subscribe).to.have.been.calledWith('parity_defaultAccount');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@action', () => {
|
||||
describe('setAccounts', () => {
|
||||
it('sets the accounts', () => {
|
||||
store.setAccounts('testing');
|
||||
expect(store.accounts).to.equal('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDefaultAccount', () => {
|
||||
it('sets the default account', () => {
|
||||
store.setDefaultAccount('testing');
|
||||
expect(store.defaultAccount).to.equal('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setLoading', () => {
|
||||
it('sets the loading status', () => {
|
||||
store.setLoading('testing');
|
||||
expect(store.isLoading).to.equal('testing');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
describe('loadAccounts', () => {
|
||||
beforeEach(() => {
|
||||
sinon.spy(store, 'setAccounts');
|
||||
|
||||
return store.loadAccounts();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.setAccounts.restore();
|
||||
});
|
||||
|
||||
it('calls into parity_getNewDappsAddresses', () => {
|
||||
expect(api.parity.getNewDappsAddresses).to.have.been.called;
|
||||
});
|
||||
|
||||
it('calls into parity_allAccountsInfo', () => {
|
||||
expect(api.parity.allAccountsInfo).to.have.been.called;
|
||||
});
|
||||
|
||||
it('sets the accounts', () => {
|
||||
expect(store.setAccounts).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadDefaultAccount', () => {
|
||||
beforeEach(() => {
|
||||
return store.loadDefaultAccount();
|
||||
});
|
||||
|
||||
it('load and set the default account', () => {
|
||||
expect(store.defaultAccount).to.equal(ACCOUNT_DEFAULT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeDefaultAccount', () => {
|
||||
beforeEach(() => {
|
||||
return store.makeDefaultAccount(ACCOUNT_NEW);
|
||||
});
|
||||
|
||||
it('calls into parity_setNewDappsDefaultAddress', () => {
|
||||
expect(api.parity.setNewDappsDefaultAddress).to.have.been.calledWith(ACCOUNT_NEW);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
17
js/src/shell/ParityBar/index.js
Normal file
17
js/src/shell/ParityBar/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 './parityBar';
|
||||
267
js/src/shell/ParityBar/parityBar.css
Normal file
267
js/src/shell/ParityBar/parityBar.css
Normal file
@@ -0,0 +1,267 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
$overlayZ: 10000;
|
||||
$modalZ: 10001;
|
||||
|
||||
.account {
|
||||
width: 100%;
|
||||
|
||||
.selected,
|
||||
.unselected {
|
||||
margin: 0.125em 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.unselected {
|
||||
background: rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: rgba(255, 255, 255, 0.35) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
z-index: $overlayZ;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.bar,
|
||||
.expanded {
|
||||
position: fixed;
|
||||
font-size: 16px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
z-index: $modalZ;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.bar {
|
||||
vertical-align: middle;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
&.moving {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
&:hover {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.parityBg {
|
||||
position: fixed;
|
||||
transition-property: left, top, right, bottom;
|
||||
transition-duration: 0.25s;
|
||||
transition-timing-function: ease;
|
||||
|
||||
&.moving {
|
||||
transition-duration: 0.05s;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.accountsSection {
|
||||
padding: 0 1em;
|
||||
width: 920px;
|
||||
}
|
||||
|
||||
.expanded {
|
||||
border-radius: 4px 4px 0 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 30vh;
|
||||
max-height: 80vh;
|
||||
max-width: calc(100vw - 2em);
|
||||
width: 960px;
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
max-width: calc(100vw - 2em);
|
||||
}
|
||||
}
|
||||
|
||||
.corner {
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.cornercolor {
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
padding: 0.5em 1em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.link {
|
||||
white-space: nowrap;
|
||||
border: none;
|
||||
outline: none !important;
|
||||
color: white !important;
|
||||
display: inline-block;
|
||||
|
||||
img,
|
||||
svg {
|
||||
height: 24px !important;
|
||||
width: 24px !important;
|
||||
margin: 2px 0.5em 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.link+.link {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.button,
|
||||
.iconButton,
|
||||
.parityButton {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.parityButton img {
|
||||
width: auto !important;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.button svg {
|
||||
fill: white !important;
|
||||
}
|
||||
|
||||
.iconButton {
|
||||
min-width: 2em !important;
|
||||
|
||||
img {
|
||||
margin: 6px 0.5em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.labelText {
|
||||
text-transform: uppercase;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.labelBubble {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 2em;
|
||||
padding: 0.5em 1em;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
margin-bottom: 0;
|
||||
|
||||
&::after {
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.header,
|
||||
.corner {
|
||||
button {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.title {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.actions {
|
||||
float: right;
|
||||
margin-top: -2px;
|
||||
|
||||
div {
|
||||
margin-left: 1em;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.parityIcon,
|
||||
.signerIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
vertical-align: middle;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.moveIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
.dragButton {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-left: 0.5em;
|
||||
background-color: white;
|
||||
opacity: 0.25;
|
||||
border-radius: 50%;
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.1s;
|
||||
transition-timing-function: ease-in-out;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.moving {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
709
js/src/shell/ParityBar/parityBar.js
Normal file
709
js/src/shell/ParityBar/parityBar.js
Normal file
@@ -0,0 +1,709 @@
|
||||
// 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 { throttle } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import store from 'store';
|
||||
|
||||
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
|
||||
import { AccountCard, Badge, Button, ContainerTitle, IdentityIcon, SelectionList } from '~/ui';
|
||||
import { CancelIcon, FingerprintIcon } from '~/ui/Icons';
|
||||
|
||||
import DappsStore from '~/shell/Dapps/dappsStore';
|
||||
import Signer from '~/shell/Signer/Embedded';
|
||||
|
||||
import AccountStore from './accountStore';
|
||||
import styles from './parityBar.css';
|
||||
|
||||
const LS_STORE_KEY = '_parity::parityBar';
|
||||
const DEFAULT_POSITION = { right: '1em', bottom: 0 };
|
||||
const DISPLAY_ACCOUNTS = 'accounts';
|
||||
const DISPLAY_SIGNER = 'signer';
|
||||
|
||||
@observer
|
||||
class ParityBar extends Component {
|
||||
app = null;
|
||||
measures = null;
|
||||
moving = false;
|
||||
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
dapp: PropTypes.bool,
|
||||
externalLink: PropTypes.string,
|
||||
pending: PropTypes.array
|
||||
};
|
||||
|
||||
state = {
|
||||
displayType: DISPLAY_SIGNER,
|
||||
moving: false,
|
||||
opened: false,
|
||||
position: DEFAULT_POSITION
|
||||
};
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.debouncedMouseMove = throttle(
|
||||
this._onMouseMove,
|
||||
40,
|
||||
{ leading: true, trailing: true }
|
||||
);
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
const { api } = this.context;
|
||||
|
||||
this.accountStore = new AccountStore(api);
|
||||
|
||||
// Hook to the dapp loaded event to position the
|
||||
// Parity Bar accordingly
|
||||
const dappsStore = DappsStore.get(api);
|
||||
|
||||
dappsStore
|
||||
.on('loaded', (app) => {
|
||||
this.app = app;
|
||||
this.loadPosition();
|
||||
});
|
||||
|
||||
if (this.props.dapp) {
|
||||
this.loadPosition();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const count = this.props.pending.length;
|
||||
const newCount = nextProps.pending.length;
|
||||
|
||||
// Replace to default position when leaving a dapp
|
||||
if (this.props.dapp && !nextProps.dapp) {
|
||||
this.loadPosition(true);
|
||||
}
|
||||
|
||||
// Load position when dapp loads
|
||||
if (!this.props.dapp && nextProps.dapp) {
|
||||
this.loadPosition();
|
||||
}
|
||||
|
||||
if (count === newCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count < newCount) {
|
||||
this.setOpened(true, DISPLAY_SIGNER);
|
||||
} else if (newCount === 0 && count === 1) {
|
||||
this.setOpened(false);
|
||||
}
|
||||
}
|
||||
|
||||
setOpened (opened, displayType = DISPLAY_SIGNER) {
|
||||
this.setState({ displayType, opened });
|
||||
this.dispatchOpenEvent(opened);
|
||||
}
|
||||
|
||||
dispatchOpenEvent (opened) {
|
||||
if (!this.bar) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire up custom even to support having parity bar iframed.
|
||||
const event = new CustomEvent('parity.bar.visibility', {
|
||||
bubbles: true,
|
||||
detail: { opened }
|
||||
});
|
||||
|
||||
this.bar.dispatchEvent(event);
|
||||
}
|
||||
|
||||
onRef = (element) => {
|
||||
this.bar = element;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { moving, opened, position } = this.state;
|
||||
|
||||
const containerClassNames = opened
|
||||
? [ styles.overlay ]
|
||||
: [ styles.bar ];
|
||||
|
||||
if (!opened && moving) {
|
||||
containerClassNames.push(styles.moving);
|
||||
}
|
||||
|
||||
const parityBgClassNames = [
|
||||
opened
|
||||
? styles.expanded
|
||||
: styles.corner,
|
||||
styles.parityBg
|
||||
];
|
||||
|
||||
if (moving) {
|
||||
parityBgClassNames.push(styles.moving);
|
||||
}
|
||||
|
||||
const parityBgStyle = {
|
||||
...position
|
||||
};
|
||||
|
||||
// Open the Signer at one of the four corners
|
||||
// of the screen
|
||||
if (opened) {
|
||||
// Set at top or bottom of the screen
|
||||
if (position.top !== undefined) {
|
||||
parityBgStyle.top = 0;
|
||||
} else {
|
||||
parityBgStyle.bottom = 0;
|
||||
}
|
||||
|
||||
// Set at left or right of the screen
|
||||
if (position.left !== undefined) {
|
||||
parityBgStyle.left = '1em';
|
||||
} else {
|
||||
parityBgStyle.right = '1em';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ containerClassNames.join(' ') }
|
||||
onMouseEnter={ this.onMouseEnter }
|
||||
onMouseLeave={ this.onMouseLeave }
|
||||
onMouseMove={ this.onMouseMove }
|
||||
onMouseUp={ this.onMouseUp }
|
||||
ref={ this.onRef }
|
||||
>
|
||||
<div
|
||||
className={ parityBgClassNames.join(' ') }
|
||||
ref='container'
|
||||
style={ parityBgStyle }
|
||||
>
|
||||
{
|
||||
opened
|
||||
? this.renderExpanded()
|
||||
: this.renderBar()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBar () {
|
||||
const { dapp } = this.props;
|
||||
|
||||
if (!dapp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.cornercolor }>
|
||||
<Button
|
||||
className={ styles.iconButton }
|
||||
icon={
|
||||
<IdentityIcon
|
||||
address={ this.accountStore.defaultAccount }
|
||||
button
|
||||
center
|
||||
inline
|
||||
/>
|
||||
}
|
||||
onClick={ this.toggleAccountsDisplay }
|
||||
/>
|
||||
{
|
||||
this.renderLink(
|
||||
<Button
|
||||
className={ styles.parityButton }
|
||||
icon={
|
||||
<img
|
||||
className={ styles.parityIcon }
|
||||
src={ imagesEthcoreBlock }
|
||||
/>
|
||||
}
|
||||
label={
|
||||
this.renderLabel(
|
||||
<FormattedMessage
|
||||
id='parityBar.label.parity'
|
||||
defaultMessage='Parity'
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
className={ styles.button }
|
||||
icon={ <FingerprintIcon /> }
|
||||
label={ this.renderSignerLabel() }
|
||||
onClick={ this.toggleSignerDisplay }
|
||||
/>
|
||||
{ this.renderDrag() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderDrag () {
|
||||
const dragButtonClasses = [ styles.dragButton ];
|
||||
|
||||
if (this.state.moving) {
|
||||
dragButtonClasses.push(styles.moving);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ styles.moveIcon }
|
||||
onMouseDown={ this.onMouseDown }
|
||||
>
|
||||
<div
|
||||
className={ dragButtonClasses.join(' ') }
|
||||
ref='dragButton'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLink (button) {
|
||||
const { externalLink } = this.props;
|
||||
|
||||
if (!externalLink) {
|
||||
return (
|
||||
<Link to='/apps'>
|
||||
{ button }
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href={ externalLink }
|
||||
target='_parent'
|
||||
>
|
||||
{ button }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderExpanded () {
|
||||
const { externalLink } = this.props;
|
||||
const { displayType } = this.state;
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<div className={ styles.header }>
|
||||
<div className={ styles.title }>
|
||||
<ContainerTitle
|
||||
title={
|
||||
displayType === DISPLAY_ACCOUNTS
|
||||
? (
|
||||
<FormattedMessage
|
||||
id='parityBar.title.accounts'
|
||||
defaultMessage='Default Account'
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<FormattedMessage
|
||||
id='parityBar.title.signer'
|
||||
defaultMessage='Parity Signer: Pending'
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.actions }>
|
||||
<Button
|
||||
icon={ <CancelIcon /> }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='parityBar.button.close'
|
||||
defaultMessage='Close'
|
||||
/>
|
||||
}
|
||||
onClick={ this.toggleSignerDisplay }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={ styles.content }>
|
||||
{
|
||||
displayType === DISPLAY_ACCOUNTS
|
||||
? (
|
||||
<SelectionList
|
||||
className={ styles.accountsSection }
|
||||
items={ this.accountStore.accounts }
|
||||
noStretch
|
||||
onSelectClick={ this.onMakeDefault }
|
||||
renderItem={ this.renderAccount }
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Signer externalLink={ externalLink } />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onMakeDefault = (account) => {
|
||||
this.toggleAccountsDisplay();
|
||||
|
||||
return this.accountStore
|
||||
.makeDefaultAccount(account.address)
|
||||
.then(() => this.accountStore.loadAccounts());
|
||||
}
|
||||
|
||||
renderAccount = (account) => {
|
||||
return (
|
||||
<AccountCard
|
||||
account={ account }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderLabel (name, bubble) {
|
||||
return (
|
||||
<div className={ styles.label }>
|
||||
<div className={ styles.labelText }>
|
||||
{ name }
|
||||
</div>
|
||||
{ bubble }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSignerLabel () {
|
||||
const { pending } = this.props;
|
||||
let bubble = null;
|
||||
|
||||
if (pending && pending.length) {
|
||||
bubble = (
|
||||
<Badge
|
||||
color='red'
|
||||
className={ styles.labelBubble }
|
||||
value={ pending.length }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return this.renderLabel(
|
||||
<FormattedMessage
|
||||
id='parityBar.label.signer'
|
||||
defaultMessage='Signer'
|
||||
/>,
|
||||
bubble
|
||||
);
|
||||
}
|
||||
|
||||
getHorizontal (x) {
|
||||
const { page, button, container } = this.measures;
|
||||
|
||||
const left = x - button.offset.left;
|
||||
const centerX = left + container.width / 2;
|
||||
|
||||
// left part of the screen
|
||||
if (centerX < page.width / 2) {
|
||||
return { left: Math.max(0, left) };
|
||||
}
|
||||
|
||||
const right = page.width - x - button.offset.right;
|
||||
|
||||
return { right: Math.max(0, right) };
|
||||
}
|
||||
|
||||
getVertical (y) {
|
||||
const STICKY_SIZE = 75;
|
||||
const { page, button, container } = this.measures;
|
||||
|
||||
const top = y - button.offset.top;
|
||||
const centerY = top + container.height / 2;
|
||||
|
||||
// top part of the screen
|
||||
if (centerY < page.height / 2) {
|
||||
// Add Sticky edges
|
||||
const stickyTop = top < STICKY_SIZE
|
||||
? 0
|
||||
: top;
|
||||
|
||||
return { top: Math.max(0, stickyTop) };
|
||||
}
|
||||
|
||||
const bottom = page.height - y - button.offset.bottom;
|
||||
// Add Sticky edges
|
||||
const stickyBottom = bottom < STICKY_SIZE
|
||||
? 0
|
||||
: bottom;
|
||||
|
||||
return { bottom: Math.max(0, stickyBottom) };
|
||||
}
|
||||
|
||||
getPosition (x, y) {
|
||||
if (!this.moving || !this.measures) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const horizontal = this.getHorizontal(x);
|
||||
const vertical = this.getVertical(y);
|
||||
|
||||
const position = {
|
||||
...horizontal,
|
||||
...vertical
|
||||
};
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
onMouseDown = () => {
|
||||
// Dispatch an open event in case in an iframe (get full w and h)
|
||||
this.dispatchOpenEvent(true);
|
||||
|
||||
window.setTimeout(() => {
|
||||
const containerElt = ReactDOM.findDOMNode(this.refs.container);
|
||||
const dragButtonElt = ReactDOM.findDOMNode(this.refs.dragButton);
|
||||
|
||||
if (!containerElt || !dragButtonElt) {
|
||||
console.warn(containerElt ? 'drag button' : 'container', 'not found...');
|
||||
return;
|
||||
}
|
||||
|
||||
const bodyRect = document.body.getBoundingClientRect();
|
||||
const containerRect = containerElt.getBoundingClientRect();
|
||||
const buttonRect = dragButtonElt.getBoundingClientRect();
|
||||
|
||||
const buttonOffset = {
|
||||
top: (buttonRect.top + buttonRect.height / 2) - containerRect.top,
|
||||
left: (buttonRect.left + buttonRect.width / 2) - containerRect.left
|
||||
};
|
||||
|
||||
buttonOffset.bottom = containerRect.height - buttonOffset.top;
|
||||
buttonOffset.right = containerRect.width - buttonOffset.left;
|
||||
|
||||
const button = {
|
||||
offset: buttonOffset,
|
||||
height: buttonRect.height,
|
||||
width: buttonRect.width
|
||||
};
|
||||
|
||||
const container = {
|
||||
height: containerRect.height,
|
||||
width: containerRect.width
|
||||
};
|
||||
|
||||
const page = {
|
||||
height: bodyRect.height,
|
||||
width: bodyRect.width
|
||||
};
|
||||
|
||||
this.moving = true;
|
||||
this.measures = {
|
||||
button,
|
||||
container,
|
||||
page
|
||||
};
|
||||
|
||||
this.setMovingState(true);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
onMouseEnter = (event) => {
|
||||
if (!this.moving) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { buttons } = event;
|
||||
|
||||
// If no left-click, stop move
|
||||
if (buttons !== 1) {
|
||||
this.onMouseUp(event);
|
||||
}
|
||||
}
|
||||
|
||||
onMouseLeave = (event) => {
|
||||
if (!this.moving) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
onMouseMove = (event) => {
|
||||
const { pageX, pageY } = event;
|
||||
|
||||
// this._onMouseMove({ pageX, pageY });
|
||||
this.debouncedMouseMove({ pageX, pageY });
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
_onMouseMove = (event) => {
|
||||
if (!this.moving) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { pageX, pageY } = event;
|
||||
const position = this.getPosition(pageX, pageY);
|
||||
|
||||
this.setState({ position });
|
||||
}
|
||||
|
||||
onMouseUp = (event) => {
|
||||
if (!this.moving) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { pageX, pageY } = event;
|
||||
const position = this.getPosition(pageX, pageY);
|
||||
|
||||
// Stick to bottom or top
|
||||
if (position.top !== undefined) {
|
||||
position.top = 0;
|
||||
} else {
|
||||
position.bottom = 0;
|
||||
}
|
||||
|
||||
// Stick to bottom or top
|
||||
if (position.left !== undefined) {
|
||||
position.left = '1em';
|
||||
} else {
|
||||
position.right = '1em';
|
||||
}
|
||||
|
||||
this.moving = false;
|
||||
this.setMovingState(false, { position });
|
||||
this.savePosition(position);
|
||||
}
|
||||
|
||||
toggleAccountsDisplay = () => {
|
||||
const { opened } = this.state;
|
||||
|
||||
this.setOpened(!opened, DISPLAY_ACCOUNTS);
|
||||
}
|
||||
|
||||
toggleSignerDisplay = () => {
|
||||
const { opened } = this.state;
|
||||
|
||||
this.setOpened(!opened, DISPLAY_SIGNER);
|
||||
}
|
||||
|
||||
get config () {
|
||||
const config = store.get(LS_STORE_KEY);
|
||||
|
||||
if (typeof config === 'string') {
|
||||
try {
|
||||
return JSON.parse(config);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return config || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the config key for the current view.
|
||||
* If inside a dapp, should be the dapp id.
|
||||
* Otherwise, try to get the current hostname.
|
||||
*/
|
||||
getConfigKey () {
|
||||
const { app } = this;
|
||||
|
||||
if (app && app.id) {
|
||||
return app.id;
|
||||
}
|
||||
|
||||
return window.location.hostname;
|
||||
}
|
||||
|
||||
loadPosition (loadDefault = false) {
|
||||
if (loadDefault) {
|
||||
return this.setState({ position: DEFAULT_POSITION });
|
||||
}
|
||||
|
||||
const { app, config } = this;
|
||||
const configKey = this.getConfigKey();
|
||||
|
||||
if (config[configKey]) {
|
||||
return this.setState({ position: config[configKey] });
|
||||
}
|
||||
|
||||
if (app && app.position) {
|
||||
const position = this.stringToPosition(app.position);
|
||||
|
||||
return this.setState({ position });
|
||||
}
|
||||
|
||||
return this.setState({ position: DEFAULT_POSITION });
|
||||
}
|
||||
|
||||
savePosition (position) {
|
||||
const { config } = this;
|
||||
const configKey = this.getConfigKey();
|
||||
|
||||
config[configKey] = position;
|
||||
store.set(LS_STORE_KEY, config);
|
||||
}
|
||||
|
||||
stringToPosition (value) {
|
||||
switch (value) {
|
||||
case 'top-left':
|
||||
return {
|
||||
left: '1em',
|
||||
top: 0
|
||||
};
|
||||
|
||||
case 'top-right':
|
||||
return {
|
||||
right: '1em',
|
||||
top: 0
|
||||
};
|
||||
|
||||
case 'bottom-left':
|
||||
return {
|
||||
bottom: 0,
|
||||
left: '1em'
|
||||
};
|
||||
|
||||
case 'bottom-right':
|
||||
default:
|
||||
return DEFAULT_POSITION;
|
||||
}
|
||||
}
|
||||
|
||||
setMovingState (moving, extras = {}) {
|
||||
this.setState({ moving, ...extras });
|
||||
|
||||
// Trigger an open event if it's moving
|
||||
this.dispatchOpenEvent(moving);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { pending } = state.signer;
|
||||
|
||||
return {
|
||||
pending
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(ParityBar);
|
||||
170
js/src/shell/ParityBar/parityBar.spec.js
Normal file
170
js/src/shell/ParityBar/parityBar.spec.js
Normal file
@@ -0,0 +1,170 @@
|
||||
// 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 { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import ParityBar from './';
|
||||
|
||||
import { createApi } from './parityBar.test.js';
|
||||
|
||||
let api;
|
||||
let component;
|
||||
let instance;
|
||||
let store;
|
||||
|
||||
function createRedux (state = {}) {
|
||||
store = {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => Object.assign({
|
||||
balances: {
|
||||
balances: {}
|
||||
},
|
||||
signer: {
|
||||
pending: []
|
||||
}
|
||||
}, state)
|
||||
};
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
function render (props = {}, state = {}) {
|
||||
api = createApi();
|
||||
component = shallow(
|
||||
<ParityBar { ...props } />,
|
||||
{
|
||||
context: {
|
||||
store: createRedux(state)
|
||||
}
|
||||
}
|
||||
).find('ParityBar').shallow({ context: { api } });
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/ParityBar', () => {
|
||||
beforeEach(() => {
|
||||
render({ dapp: true });
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
describe('renderBar', () => {
|
||||
let bar;
|
||||
|
||||
beforeEach(() => {
|
||||
bar = shallow(instance.renderBar());
|
||||
});
|
||||
|
||||
it('renders nothing when not overlaying a dapp', () => {
|
||||
render({ dapp: false });
|
||||
expect(instance.renderBar()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders when overlaying a dapp', () => {
|
||||
expect(bar.find('div')).not.to.have.length(0);
|
||||
});
|
||||
|
||||
it('renders the Account selector button', () => {
|
||||
const icon = bar.find('Button').first().props().icon;
|
||||
|
||||
expect(icon.type.displayName).to.equal('Connect(IdentityIcon)');
|
||||
});
|
||||
|
||||
it('renders the Parity button', () => {
|
||||
const label = shallow(bar.find('Button').at(1).props().label);
|
||||
|
||||
expect(label.find('FormattedMessage').props().id).to.equal('parityBar.label.parity');
|
||||
});
|
||||
|
||||
it('renders the Signer button', () => {
|
||||
const label = shallow(bar.find('Button').last().props().label);
|
||||
|
||||
expect(label.find('FormattedMessage').props().id).to.equal('parityBar.label.signer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderExpanded', () => {
|
||||
let expanded;
|
||||
|
||||
beforeEach(() => {
|
||||
expanded = shallow(instance.renderExpanded());
|
||||
});
|
||||
|
||||
it('includes the Signer', () => {
|
||||
expect(expanded.find('Connect(Embedded)')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderLabel', () => {
|
||||
it('renders the label name', () => {
|
||||
expect(shallow(instance.renderLabel('testing', null)).text()).to.equal('testing');
|
||||
});
|
||||
|
||||
it('renders name and bubble', () => {
|
||||
expect(shallow(instance.renderLabel('testing', '(bubble)')).text()).to.equal('testing(bubble)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderSignerLabel', () => {
|
||||
let label;
|
||||
|
||||
beforeEach(() => {
|
||||
label = shallow(instance.renderSignerLabel());
|
||||
});
|
||||
|
||||
it('renders the signer label', () => {
|
||||
expect(label.find('FormattedMessage').props().id).to.equal('parityBar.label.signer');
|
||||
});
|
||||
|
||||
it('does not render a badge when no pending requests', () => {
|
||||
expect(label.find('Badge')).to.have.length(0);
|
||||
});
|
||||
|
||||
it('renders a badge when pending requests', () => {
|
||||
render({}, { signer: { pending: ['123', '456'] } });
|
||||
expect(shallow(instance.renderSignerLabel()).find('Badge').props().value).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('opened state', () => {
|
||||
beforeEach(() => {
|
||||
sinon.spy(instance, 'renderBar');
|
||||
sinon.spy(instance, 'renderExpanded');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.renderBar.restore();
|
||||
instance.renderExpanded.restore();
|
||||
});
|
||||
|
||||
it('renders the bar on with opened === false', () => {
|
||||
expect(component.find('Link[to="/apps"]')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders expanded with opened === true', () => {
|
||||
expect(instance.renderExpanded).not.to.have.been.called;
|
||||
instance.setState({ opened: true });
|
||||
expect(instance.renderExpanded).to.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
56
js/src/shell/ParityBar/parityBar.test.js
Normal file
56
js/src/shell/ParityBar/parityBar.test.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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 sinon from 'sinon';
|
||||
|
||||
const ACCOUNT_DEFAULT = '0x2345678901';
|
||||
const ACCOUNT_FIRST = '0x1234567890';
|
||||
const ACCOUNT_NEW = '0x0987654321';
|
||||
const ACCOUNTS = {
|
||||
[ACCOUNT_FIRST]: { uuid: 123 },
|
||||
[ACCOUNT_DEFAULT]: { uuid: 234 },
|
||||
'0x3456789012': {},
|
||||
[ACCOUNT_NEW]: { uuid: 456 }
|
||||
};
|
||||
|
||||
function createApi () {
|
||||
const api = {
|
||||
subscribe: (params, callback) => {
|
||||
callback(null, ACCOUNT_DEFAULT);
|
||||
|
||||
return Promise.resolve(1);
|
||||
},
|
||||
parity: {
|
||||
defaultAccount: sinon.stub().resolves(ACCOUNT_DEFAULT),
|
||||
allAccountsInfo: sinon.stub().resolves(ACCOUNTS),
|
||||
getNewDappsAddresses: sinon.stub().resolves(null),
|
||||
setNewDappsAddresses: sinon.stub().resolves(true),
|
||||
setNewDappsDefaultAddress: sinon.stub().resolves(true)
|
||||
}
|
||||
};
|
||||
|
||||
sinon.spy(api, 'subscribe');
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
export {
|
||||
ACCOUNT_DEFAULT,
|
||||
ACCOUNT_FIRST,
|
||||
ACCOUNT_NEW,
|
||||
ACCOUNTS,
|
||||
createApi
|
||||
};
|
||||
38
js/src/shell/Signer/Embedded/embedded.css
Normal file
38
js/src/shell/Signer/Embedded/embedded.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/>.
|
||||
*/
|
||||
|
||||
@import '../_layout.css';
|
||||
|
||||
.info {
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
.none {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.request {
|
||||
&:nth-child(even) {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
.signer {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
width: $embedWidth;
|
||||
}
|
||||
135
js/src/shell/Signer/Embedded/embedded.js
Normal file
135
js/src/shell/Signer/Embedded/embedded.js
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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 BigNumber from 'bignumber.js';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import Store from '../store';
|
||||
import * as RequestsActions from '~/redux/providers/signerActions';
|
||||
import { Container } from '~/ui';
|
||||
|
||||
import RequestPending from '../components/RequestPending';
|
||||
|
||||
import styles from './embedded.css';
|
||||
|
||||
class Embedded extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
startConfirmRequest: PropTypes.func.isRequired,
|
||||
startRejectRequest: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
externalLink: PropTypes.string,
|
||||
gasLimit: PropTypes.object.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
signer: PropTypes.shape({
|
||||
finished: PropTypes.array.isRequired,
|
||||
pending: PropTypes.array.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
store = new Store(this.context.api, false, this.props.externalLink);
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Container style={ { background: 'transparent' } }>
|
||||
<div className={ styles.signer }>
|
||||
{ this.renderPendingRequests() }
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
renderPendingRequests () {
|
||||
const { signer } = this.props;
|
||||
const { pending } = signer;
|
||||
|
||||
if (!pending.length) {
|
||||
return (
|
||||
<div className={ styles.none }>
|
||||
<FormattedMessage
|
||||
id='signer.embedded.noPending'
|
||||
defaultMessage='There are currently no pending requests awaiting your confirmation'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const items = pending.sort(this._sortRequests).map(this.renderPending);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ items }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPending = (data, index) => {
|
||||
const { actions, gasLimit, netVersion } = this.props;
|
||||
const { date, id, isSending, payload, origin } = data;
|
||||
|
||||
return (
|
||||
<RequestPending
|
||||
className={ styles.request }
|
||||
date={ date }
|
||||
focus={ index === 0 }
|
||||
gasLimit={ gasLimit }
|
||||
id={ id }
|
||||
isSending={ isSending }
|
||||
netVersion={ netVersion }
|
||||
key={ id }
|
||||
onConfirm={ actions.startConfirmRequest }
|
||||
onReject={ actions.startRejectRequest }
|
||||
origin={ origin }
|
||||
payload={ payload }
|
||||
signerStore={ this.store }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_sortRequests = (a, b) => {
|
||||
return new BigNumber(a.id).cmp(b.id);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { gasLimit, netVersion } = state.nodeStatus;
|
||||
const { actions, signer } = state;
|
||||
|
||||
return {
|
||||
actions,
|
||||
gasLimit,
|
||||
netVersion,
|
||||
signer
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(RequestsActions, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Embedded);
|
||||
17
js/src/shell/Signer/Embedded/index.js
Normal file
17
js/src/shell/Signer/Embedded/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 './embedded';
|
||||
24
js/src/shell/Signer/_layout.css
Normal file
24
js/src/shell/Signer/_layout.css
Normal file
@@ -0,0 +1,24 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
$pendingHeight: 190px;
|
||||
$finishedHeight: 120px;
|
||||
|
||||
$embedWidth: 920px;
|
||||
$statusWidth: 270px;
|
||||
|
||||
$accountPadding: 75px;
|
||||
@@ -0,0 +1,20 @@
|
||||
/* 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 {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// 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 { connect } from 'react-redux';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import styles from './accountLink.css';
|
||||
|
||||
class AccountLink extends Component {
|
||||
static propTypes = {
|
||||
accountAddresses: PropTypes.array.isRequired,
|
||||
address: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
externalLink: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
state = {
|
||||
link: null
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
const { address, externalLink } = this.props;
|
||||
|
||||
this.updateLink(address, externalLink);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const { address, externalLink } = nextProps;
|
||||
|
||||
this.updateLink(address, externalLink);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, address, className, externalLink } = this.props;
|
||||
|
||||
if (externalLink) {
|
||||
return (
|
||||
<a
|
||||
href={ this.state.link }
|
||||
target='_blank'
|
||||
className={ `${styles.container} ${className}` }
|
||||
>
|
||||
{ children || address }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={ `${styles.container} ${className}` }
|
||||
to={ this.state.link }
|
||||
>
|
||||
{ children || address }
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
updateLink (address, externalLink) {
|
||||
const { accountAddresses } = this.props;
|
||||
const isAccount = accountAddresses.includes(address);
|
||||
|
||||
let link = isAccount
|
||||
? `/accounts/${address}`
|
||||
: `/addresses/${address}`;
|
||||
|
||||
if (externalLink) {
|
||||
const path = externalLink.replace(/\/+$/, '');
|
||||
|
||||
link = `${path}/#${link}`;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
link
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (initState) {
|
||||
const { accounts } = initState.personal;
|
||||
const accountAddresses = Object.keys(accounts);
|
||||
|
||||
return () => {
|
||||
return { accountAddresses };
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(AccountLink);
|
||||
17
js/src/shell/Signer/components/Account/AccountLink/index.js
Normal file
17
js/src/shell/Signer/components/Account/AccountLink/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 './accountLink';
|
||||
50
js/src/shell/Signer/components/Account/account.css
Normal file
50
js/src/shell/Signer/components/Account/account.css
Normal file
@@ -0,0 +1,50 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
.acc {
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.acc > * {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.address {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.acc img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
vertical-align: middle;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.name {
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
vertical-align: middle;
|
||||
text-transform: uppercase;
|
||||
|
||||
span {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
128
js/src/shell/Signer/components/Account/account.js
Normal file
128
js/src/shell/Signer/components/Account/account.js
Normal file
@@ -0,0 +1,128 @@
|
||||
// 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 { IdentityIcon, IdentityName } from '~/ui';
|
||||
import AccountLink from './AccountLink';
|
||||
|
||||
import styles from './account.css';
|
||||
|
||||
export default class Account extends Component {
|
||||
static propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
externalLink: PropTypes.string.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
balance: PropTypes.object // eth BigNumber, not required since it mght take time to fetch
|
||||
};
|
||||
|
||||
state = {
|
||||
balanceDisplay: '?'
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
this.updateBalanceDisplay(this.props.balance);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.balance === this.props.balance) {
|
||||
return;
|
||||
}
|
||||
this.updateBalanceDisplay(nextProps.balance);
|
||||
}
|
||||
|
||||
updateBalanceDisplay (balance) {
|
||||
this.setState({
|
||||
balanceDisplay: balance ? balance.div(1e18).toFormat(3) : '?'
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { address, className, disabled, externalLink, netVersion } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ `${styles.acc} ${className}` }>
|
||||
<AccountLink
|
||||
address={ address }
|
||||
externalLink={ externalLink }
|
||||
netVersion={ netVersion }
|
||||
>
|
||||
<IdentityIcon
|
||||
center
|
||||
disabled={ disabled }
|
||||
address={ address }
|
||||
/>
|
||||
</AccountLink>
|
||||
{ this.renderName() }
|
||||
{ this.renderBalance() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBalance () {
|
||||
const { balanceDisplay } = this.state;
|
||||
|
||||
return (
|
||||
<span> <strong>{ balanceDisplay }</strong> <small>ETH</small></span>
|
||||
);
|
||||
}
|
||||
|
||||
renderName () {
|
||||
const { address, externalLink, netVersion } = this.props;
|
||||
const name = <IdentityName address={ address } empty />;
|
||||
|
||||
if (!name) {
|
||||
return (
|
||||
<AccountLink
|
||||
address={ address }
|
||||
externalLink={ externalLink }
|
||||
netVersion={ netVersion }
|
||||
>
|
||||
[{ this.shortAddress(address) }]
|
||||
</AccountLink>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AccountLink
|
||||
address={ address }
|
||||
externalLink={ externalLink }
|
||||
netVersion={ netVersion }
|
||||
>
|
||||
<span>
|
||||
<span className={ styles.name }>{ name }</span>
|
||||
<span className={ styles.address }>[{ this.tinyAddress(address) }]</span>
|
||||
</span>
|
||||
</AccountLink>
|
||||
);
|
||||
}
|
||||
|
||||
tinyAddress () {
|
||||
const { address } = this.props;
|
||||
const len = address.length;
|
||||
|
||||
return address.slice(2, 4) + '..' + address.slice(len - 2);
|
||||
}
|
||||
|
||||
shortAddress () {
|
||||
const { address } = this.props;
|
||||
const len = address.length;
|
||||
|
||||
return address.slice(2, 8) + '..' + address.slice(len - 7);
|
||||
}
|
||||
}
|
||||
17
js/src/shell/Signer/components/Account/index.js
Normal file
17
js/src/shell/Signer/components/Account/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 './account';
|
||||
183
js/src/shell/Signer/components/DecryptRequest/decryptRequest.js
Normal file
183
js/src/shell/Signer/components/DecryptRequest/decryptRequest.js
Normal file
@@ -0,0 +1,183 @@
|
||||
// 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 { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Account from '../Account';
|
||||
import TransactionPendingForm from '../TransactionPendingForm';
|
||||
import RequestOrigin from '../RequestOrigin';
|
||||
|
||||
import styles from '../SignRequest/signRequest.css';
|
||||
|
||||
@observer
|
||||
class DecryptRequest extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
address: PropTypes.string.isRequired,
|
||||
data: PropTypes.string.isRequired,
|
||||
id: PropTypes.object.isRequired,
|
||||
isFinished: PropTypes.bool.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
signerStore: PropTypes.object.isRequired,
|
||||
|
||||
className: PropTypes.string,
|
||||
focus: PropTypes.bool,
|
||||
isSending: PropTypes.bool,
|
||||
onConfirm: PropTypes.func,
|
||||
onReject: PropTypes.func,
|
||||
origin: PropTypes.any,
|
||||
status: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focus: false,
|
||||
origin: {
|
||||
type: 'unknown',
|
||||
details: ''
|
||||
}
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
const { address, signerStore } = this.props;
|
||||
|
||||
signerStore.fetchBalance(address);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ `${styles.container} ${className}` }>
|
||||
{ this.renderDetails() }
|
||||
{ this.renderActions() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderDetails () {
|
||||
const { api } = this.context;
|
||||
const { address, data, netVersion, origin, signerStore } = this.props;
|
||||
const { balances, externalLink } = signerStore;
|
||||
|
||||
const balance = balances[address];
|
||||
|
||||
if (!balance) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.signDetails }>
|
||||
<div className={ styles.address }>
|
||||
<Account
|
||||
address={ address }
|
||||
balance={ balance }
|
||||
className={ styles.account }
|
||||
externalLink={ externalLink }
|
||||
netVersion={ netVersion }
|
||||
/>
|
||||
<RequestOrigin origin={ origin } />
|
||||
</div>
|
||||
<div className={ styles.info } title={ api.util.sha3(data) }>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='signer.decryptRequest.request'
|
||||
defaultMessage='A request to decrypt data using your account:'
|
||||
/>
|
||||
</p>
|
||||
|
||||
<div className={ styles.signData }>
|
||||
<p>{ data }</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderActions () {
|
||||
const { accounts, address, focus, isFinished, status } = this.props;
|
||||
const account = accounts[address];
|
||||
|
||||
if (isFinished) {
|
||||
if (status === 'confirmed') {
|
||||
return (
|
||||
<div className={ styles.actions }>
|
||||
<span className={ styles.isConfirmed }>
|
||||
<FormattedMessage
|
||||
id='signer.decryptRequest.state.confirmed'
|
||||
defaultMessage='Confirmed'
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.actions }>
|
||||
<span className={ styles.isRejected }>
|
||||
<FormattedMessage
|
||||
id='signer.decryptRequest.state.rejected'
|
||||
defaultMessage='Rejected'
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TransactionPendingForm
|
||||
account={ account }
|
||||
address={ address }
|
||||
focus={ focus }
|
||||
isSending={ this.props.isSending }
|
||||
netVersion={ this.props.netVersion }
|
||||
onConfirm={ this.onConfirm }
|
||||
onReject={ this.onReject }
|
||||
className={ styles.actions }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onConfirm = (data) => {
|
||||
const { id } = this.props;
|
||||
const { password } = data;
|
||||
|
||||
this.props.onConfirm({ id, password });
|
||||
}
|
||||
|
||||
onReject = () => {
|
||||
this.props.onReject(this.props.id);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { accounts } = state.personal;
|
||||
|
||||
return {
|
||||
accounts
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(DecryptRequest);
|
||||
17
js/src/shell/Signer/components/DecryptRequest/index.js
Normal file
17
js/src/shell/Signer/components/DecryptRequest/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 './decryptRequest';
|
||||
17
js/src/shell/Signer/components/RequestOrigin/index.js
Normal file
17
js/src/shell/Signer/components/RequestOrigin/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 './requestOrigin';
|
||||
@@ -0,0 +1,43 @@
|
||||
/* 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 {
|
||||
text-align: left;
|
||||
margin: 3em .5em;
|
||||
opacity: 0.6;
|
||||
font-size: 0.8em;
|
||||
|
||||
.unknown {
|
||||
color: #e44;
|
||||
}
|
||||
|
||||
.url {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.hash {
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.hash, .url {
|
||||
margin-bottom: -.2em;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
163
js/src/shell/Signer/components/RequestOrigin/requestOrigin.js
Normal file
163
js/src/shell/Signer/components/RequestOrigin/requestOrigin.js
Normal file
@@ -0,0 +1,163 @@
|
||||
// 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 { FormattedMessage } from 'react-intl';
|
||||
|
||||
import IdentityIcon from '~/ui/IdentityIcon';
|
||||
|
||||
import styles from './requestOrigin.css';
|
||||
|
||||
export default class RequestOrigin extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
origin: PropTypes.shape({
|
||||
type: PropTypes.oneOf(['unknown', 'dapp', 'rpc', 'ipc', 'signer']),
|
||||
details: PropTypes.string.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
render () {
|
||||
const { origin } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
Requested { this.renderOrigin(origin) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderOrigin (origin) {
|
||||
if (origin.type === 'unknown') {
|
||||
return (
|
||||
<span className={ styles.unknown }>
|
||||
<FormattedMessage
|
||||
id='signer.requestOrigin.unknownInterface'
|
||||
defaultMessage='via unknown interface'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (origin.type === 'dapp') {
|
||||
return (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='signer.requestOrigin.dapp'
|
||||
defaultMessage='by a dapp at {url}'
|
||||
values={ {
|
||||
url: (
|
||||
<span className={ styles.url }>
|
||||
{
|
||||
origin.details || (
|
||||
<FormattedMessage
|
||||
id='signer.requestOrigin.unknownUrl'
|
||||
defaultMessage='unknown URL'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
)
|
||||
} }
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (origin.type === 'rpc') {
|
||||
return (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='signer.requestOrigin.rpc'
|
||||
defaultMessage='via RPC {rpc}'
|
||||
values={ {
|
||||
url: (
|
||||
<span className={ styles.url }>
|
||||
({
|
||||
origin.details || (
|
||||
<FormattedMessage
|
||||
id='signer.requestOrigin.unknownRpc'
|
||||
defaultMessage='unidentified'
|
||||
/>
|
||||
)
|
||||
})
|
||||
</span>
|
||||
)
|
||||
} }
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (origin.type === 'ipc') {
|
||||
return (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='signer.requestOrigin.ipc'
|
||||
defaultMessage='via IPC session'
|
||||
/>
|
||||
<span
|
||||
className={ styles.hash }
|
||||
title={ origin.details }
|
||||
>
|
||||
<IdentityIcon
|
||||
address={ origin.details }
|
||||
tiny
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (origin.type === 'signer') {
|
||||
return this.renderSigner(origin.details);
|
||||
}
|
||||
}
|
||||
|
||||
renderSigner (session) {
|
||||
if (session.substr(2) === this.context.api.transport.sessionHash) {
|
||||
return (
|
||||
<span title={ session }>
|
||||
<FormattedMessage
|
||||
id='signer.requestOrigin.signerCurrent'
|
||||
defaultMessage='via current tab'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='signer.requestOrigin.signerUI'
|
||||
defaultMessage='via UI session'
|
||||
/>
|
||||
<span
|
||||
className={ styles.hash }
|
||||
title={ `UI Session id: ${session}` }
|
||||
>
|
||||
<IdentityIcon
|
||||
address={ session }
|
||||
tiny
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import RequestOrigin from './';
|
||||
|
||||
const context = {
|
||||
context: {
|
||||
api: {
|
||||
transport: {
|
||||
sessionHash: '1234'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('views/Signer/components/RequestOrigin', () => {
|
||||
it('renders unknown', () => {
|
||||
expect(shallow(
|
||||
<RequestOrigin origin={ { type: 'unknown', details: '' } } />,
|
||||
context
|
||||
).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.unknownInterface');
|
||||
});
|
||||
|
||||
it('renders dapps', () => {
|
||||
expect(shallow(
|
||||
<RequestOrigin origin={ { type: 'dapp', details: 'http://parity.io' } } />,
|
||||
context
|
||||
).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.dapp');
|
||||
});
|
||||
|
||||
it('renders rpc', () => {
|
||||
expect(shallow(
|
||||
<RequestOrigin origin={ { type: 'rpc', details: '' } } />,
|
||||
context
|
||||
).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.rpc');
|
||||
});
|
||||
|
||||
it('renders ipc', () => {
|
||||
expect(shallow(
|
||||
<RequestOrigin origin={ { type: 'ipc', details: '0x1234' } } />,
|
||||
context
|
||||
).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.ipc');
|
||||
});
|
||||
|
||||
it('renders signer', () => {
|
||||
expect(shallow(
|
||||
<RequestOrigin origin={ { type: 'signer', details: '0x12345' } } />,
|
||||
context
|
||||
).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.signerUI');
|
||||
|
||||
expect(shallow(
|
||||
<RequestOrigin origin={ { type: 'signer', details: '0x1234' } } />,
|
||||
context
|
||||
).find('FormattedMessage').props().id).to.equal('signer.requestOrigin.signerCurrent');
|
||||
});
|
||||
});
|
||||
17
js/src/shell/Signer/components/RequestPending/index.js
Normal file
17
js/src/shell/Signer/components/RequestPending/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 './requestPending';
|
||||
125
js/src/shell/Signer/components/RequestPending/requestPending.js
Normal file
125
js/src/shell/Signer/components/RequestPending/requestPending.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// 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 DecryptRequest from '../DecryptRequest';
|
||||
import SignRequest from '../SignRequest';
|
||||
import TransactionPending from '../TransactionPending';
|
||||
|
||||
export default class RequestPending extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
date: PropTypes.instanceOf(Date).isRequired,
|
||||
focus: PropTypes.bool,
|
||||
gasLimit: PropTypes.object.isRequired,
|
||||
id: PropTypes.object.isRequired,
|
||||
isSending: PropTypes.bool.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onReject: PropTypes.func.isRequired,
|
||||
origin: PropTypes.object.isRequired,
|
||||
payload: PropTypes.oneOfType([
|
||||
PropTypes.shape({ decrypt: PropTypes.object.isRequired }),
|
||||
PropTypes.shape({ sendTransaction: PropTypes.object.isRequired }),
|
||||
PropTypes.shape({ sign: PropTypes.object.isRequired }),
|
||||
PropTypes.shape({ signTransaction: PropTypes.object.isRequired })
|
||||
]).isRequired,
|
||||
signerStore: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focus: false,
|
||||
isSending: false
|
||||
};
|
||||
|
||||
render () {
|
||||
const { className, date, focus, gasLimit, id, isSending, netVersion, onReject, payload, signerStore, origin } = this.props;
|
||||
|
||||
if (payload.sign) {
|
||||
const { sign } = payload;
|
||||
|
||||
return (
|
||||
<SignRequest
|
||||
address={ sign.address }
|
||||
className={ className }
|
||||
focus={ focus }
|
||||
data={ sign.data }
|
||||
id={ id }
|
||||
isFinished={ false }
|
||||
isSending={ isSending }
|
||||
netVersion={ netVersion }
|
||||
onConfirm={ this.onConfirm }
|
||||
onReject={ onReject }
|
||||
origin={ origin }
|
||||
signerStore={ signerStore }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (payload.decrypt) {
|
||||
const { decrypt } = payload;
|
||||
|
||||
return (
|
||||
<DecryptRequest
|
||||
address={ decrypt.address }
|
||||
className={ className }
|
||||
focus={ focus }
|
||||
data={ decrypt.msg }
|
||||
id={ id }
|
||||
isFinished={ false }
|
||||
isSending={ isSending }
|
||||
netVersion={ netVersion }
|
||||
onConfirm={ this.onConfirm }
|
||||
onReject={ onReject }
|
||||
origin={ origin }
|
||||
signerStore={ signerStore }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const transaction = payload.sendTransaction || payload.signTransaction;
|
||||
|
||||
if (transaction) {
|
||||
return (
|
||||
<TransactionPending
|
||||
className={ className }
|
||||
date={ date }
|
||||
focus={ focus }
|
||||
gasLimit={ gasLimit }
|
||||
id={ id }
|
||||
isSending={ isSending }
|
||||
netVersion={ netVersion }
|
||||
onConfirm={ this.onConfirm }
|
||||
onReject={ onReject }
|
||||
origin={ origin }
|
||||
signerStore={ signerStore }
|
||||
transaction={ transaction }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
console.error('RequestPending: Unknown payload', payload);
|
||||
return null;
|
||||
}
|
||||
|
||||
onConfirm = (data) => {
|
||||
const { onConfirm, payload } = this.props;
|
||||
|
||||
data.payload = payload;
|
||||
onConfirm(data);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// 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 BigNumber from 'bignumber.js';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import RequestPending from './';
|
||||
|
||||
const ADDRESS = '0x1234567890123456789012345678901234567890';
|
||||
const TRANSACTION = {
|
||||
from: ADDRESS,
|
||||
gas: new BigNumber(21000),
|
||||
gasPrice: new BigNumber(20000000),
|
||||
value: new BigNumber(1)
|
||||
};
|
||||
const PAYLOAD_SENDTX = {
|
||||
sendTransaction: TRANSACTION
|
||||
};
|
||||
const PAYLOAD_SIGN = {
|
||||
sign: {
|
||||
address: ADDRESS,
|
||||
data: 'testing'
|
||||
}
|
||||
};
|
||||
const PAYLOAD_SIGNTX = {
|
||||
signTransaction: TRANSACTION
|
||||
};
|
||||
|
||||
let component;
|
||||
let onConfirm;
|
||||
let onReject;
|
||||
|
||||
function render (payload) {
|
||||
onConfirm = sinon.stub();
|
||||
onReject = sinon.stub();
|
||||
|
||||
component = shallow(
|
||||
<RequestPending
|
||||
date={ new Date() }
|
||||
gasLimit={ new BigNumber(100000) }
|
||||
id={ new BigNumber(123) }
|
||||
isSending={ false }
|
||||
netVersion='42'
|
||||
onConfirm={ onConfirm }
|
||||
onReject={ onReject }
|
||||
origin={ {} }
|
||||
payload={ payload }
|
||||
store={ {} }
|
||||
/>
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Signer/RequestPending', () => {
|
||||
describe('sendTransaction', () => {
|
||||
beforeEach(() => {
|
||||
render(PAYLOAD_SENDTX);
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders TransactionPending component', () => {
|
||||
expect(component.find('Connect(TransactionPending)')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sign', () => {
|
||||
beforeEach(() => {
|
||||
render(PAYLOAD_SIGN);
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders SignRequest component', () => {
|
||||
expect(component.find('Connect(SignRequest)')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('signTransaction', () => {
|
||||
beforeEach(() => {
|
||||
render(PAYLOAD_SIGNTX);
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders TransactionPending component', () => {
|
||||
expect(component.find('Connect(TransactionPending)')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
17
js/src/shell/Signer/components/SignRequest/index.js
Normal file
17
js/src/shell/Signer/components/SignRequest/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 './signRequest';
|
||||
91
js/src/shell/Signer/components/SignRequest/signRequest.css
Normal file
91
js/src/shell/Signer/components/SignRequest/signRequest.css
Normal file
@@ -0,0 +1,91 @@
|
||||
/* 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 '../../_layout.css';
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
padding: 1.5em 1em 1.5em 0;
|
||||
}
|
||||
|
||||
.actions, .signDetails {
|
||||
vertical-align: middle;
|
||||
min-height: $pendingHeight;
|
||||
}
|
||||
|
||||
.signData {
|
||||
border: 0.25em solid red;
|
||||
margin-left: 2em;
|
||||
padding: 0.5em;
|
||||
overflow: auto;
|
||||
max-height: 6em;
|
||||
max-width: calc(100% - 2em);
|
||||
}
|
||||
|
||||
.signData > p {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.signDetails {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.account img {
|
||||
display: inline-block;
|
||||
height: 50px;
|
||||
margin: 5px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.address, .info {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.address {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #E53935;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.info p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* TODO [todr] copy&paste from transactions */
|
||||
.isConfirmed {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.isRejected {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.txHash {
|
||||
display: block;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: inline-block;
|
||||
min-height: $finishedHeight;
|
||||
}
|
||||
224
js/src/shell/Signer/components/SignRequest/signRequest.js
Normal file
224
js/src/shell/Signer/components/SignRequest/signRequest.js
Normal file
@@ -0,0 +1,224 @@
|
||||
// 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 { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Account from '../Account';
|
||||
import TransactionPendingForm from '../TransactionPendingForm';
|
||||
import RequestOrigin from '../RequestOrigin';
|
||||
|
||||
import styles from './signRequest.css';
|
||||
|
||||
function isAscii (data) {
|
||||
for (var i = 2; i < data.length; i += 2) {
|
||||
let n = parseInt(data.substr(i, 2), 16);
|
||||
|
||||
if (n < 32 || n >= 128) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@observer
|
||||
class SignRequest extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
address: PropTypes.string.isRequired,
|
||||
data: PropTypes.string.isRequired,
|
||||
id: PropTypes.object.isRequired,
|
||||
isFinished: PropTypes.bool.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
signerStore: PropTypes.object.isRequired,
|
||||
|
||||
className: PropTypes.string,
|
||||
focus: PropTypes.bool,
|
||||
isSending: PropTypes.bool,
|
||||
onConfirm: PropTypes.func,
|
||||
onReject: PropTypes.func,
|
||||
origin: PropTypes.any,
|
||||
status: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focus: false,
|
||||
origin: {
|
||||
type: 'unknown',
|
||||
details: ''
|
||||
}
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
const { address, signerStore } = this.props;
|
||||
|
||||
signerStore.fetchBalance(address);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ `${styles.container} ${className}` }>
|
||||
{ this.renderDetails() }
|
||||
{ this.renderActions() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderAsciiDetails (ascii) {
|
||||
return (
|
||||
<div className={ styles.signData }>
|
||||
<p>{ascii}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBinaryDetails (data) {
|
||||
return (
|
||||
<div className={ styles.signData }>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='signer.signRequest.unknownBinary'
|
||||
defaultMessage='(Unknown binary data)'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderDetails () {
|
||||
const { api } = this.context;
|
||||
const { address, data, netVersion, origin, signerStore } = this.props;
|
||||
const { balances, externalLink } = signerStore;
|
||||
|
||||
const balance = balances[address];
|
||||
|
||||
if (!balance) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.signDetails }>
|
||||
<div className={ styles.address }>
|
||||
<Account
|
||||
address={ address }
|
||||
balance={ balance }
|
||||
className={ styles.account }
|
||||
externalLink={ externalLink }
|
||||
netVersion={ netVersion }
|
||||
/>
|
||||
<RequestOrigin origin={ origin } />
|
||||
</div>
|
||||
<div className={ styles.info } title={ api.util.sha3(data) }>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='signer.signRequest.request'
|
||||
defaultMessage='A request to sign data using your account:'
|
||||
/>
|
||||
</p>
|
||||
{
|
||||
isAscii(data)
|
||||
? this.renderAsciiDetails(api.util.hexToAscii(data))
|
||||
: this.renderBinaryDetails(data)
|
||||
}
|
||||
<p>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id='signer.signRequest.warning'
|
||||
defaultMessage='WARNING: This consequences of doing this may be grave. Confirm the request only if you are sure.'
|
||||
/>
|
||||
</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderActions () {
|
||||
const { accounts, address, focus, isFinished, status } = this.props;
|
||||
const account = accounts[address];
|
||||
|
||||
if (isFinished) {
|
||||
if (status === 'confirmed') {
|
||||
return (
|
||||
<div className={ styles.actions }>
|
||||
<span className={ styles.isConfirmed }>
|
||||
<FormattedMessage
|
||||
id='signer.signRequest.state.confirmed'
|
||||
defaultMessage='Confirmed'
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.actions }>
|
||||
<span className={ styles.isRejected }>
|
||||
<FormattedMessage
|
||||
id='signer.signRequest.state.rejected'
|
||||
defaultMessage='Rejected'
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TransactionPendingForm
|
||||
account={ account }
|
||||
address={ address }
|
||||
focus={ focus }
|
||||
isSending={ this.props.isSending }
|
||||
netVersion={ this.props.netVersion }
|
||||
onConfirm={ this.onConfirm }
|
||||
onReject={ this.onReject }
|
||||
className={ styles.actions }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onConfirm = (data) => {
|
||||
const { id } = this.props;
|
||||
const { password } = data;
|
||||
|
||||
this.props.onConfirm({ id, password });
|
||||
}
|
||||
|
||||
onReject = () => {
|
||||
this.props.onReject(this.props.id);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { accounts } = state.personal;
|
||||
|
||||
return {
|
||||
accounts
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(SignRequest);
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import SignRequest from './';
|
||||
|
||||
let component;
|
||||
let reduxStore;
|
||||
let signerStore;
|
||||
|
||||
function createSignerStore () {
|
||||
return {
|
||||
balances: {},
|
||||
fetchBalance: sinon.stub()
|
||||
};
|
||||
}
|
||||
|
||||
function createReduxStore () {
|
||||
return {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
personal: {
|
||||
accounts: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function render () {
|
||||
reduxStore = createReduxStore();
|
||||
signerStore = createSignerStore();
|
||||
|
||||
component = shallow(
|
||||
<SignRequest signerStore={ signerStore } />,
|
||||
{
|
||||
context: {
|
||||
store: reduxStore
|
||||
}
|
||||
}
|
||||
).find('SignRequest').shallow();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Signer/components/SignRequest', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
});
|
||||
@@ -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 './transactionMainDetails';
|
||||
@@ -0,0 +1,80 @@
|
||||
/* 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 '../../_layout.css';
|
||||
|
||||
.account {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.contractIcon {
|
||||
background: #eee;
|
||||
width: 50px !important;
|
||||
height: 50px !important;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
padding: 13px;
|
||||
}
|
||||
|
||||
.editButtonRow {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.from {
|
||||
display: inline-block;
|
||||
width: 40%;
|
||||
vertical-align: top;
|
||||
|
||||
.account {
|
||||
img {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.method {
|
||||
display: inline-block;
|
||||
width: 60%;
|
||||
vertical-align: top;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.tx {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin: 0 -75px;
|
||||
width: 150px;
|
||||
top: -20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.total {
|
||||
font-size: 0.6em;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.transaction {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// 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 { FormattedMessage } from 'react-intl';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
||||
import { Button, MethodDecoding } from '~/ui';
|
||||
import { GasIcon } from '~/ui/Icons';
|
||||
|
||||
import * as tUtil from '../util/transaction';
|
||||
import Account from '../Account';
|
||||
import RequestOrigin from '../RequestOrigin';
|
||||
|
||||
import styles from './transactionMainDetails.css';
|
||||
|
||||
export default class TransactionMainDetails extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
disabled: PropTypes.bool,
|
||||
externalLink: PropTypes.string.isRequired,
|
||||
from: PropTypes.string.isRequired,
|
||||
fromBalance: PropTypes.object,
|
||||
gasStore: PropTypes.object,
|
||||
id: PropTypes.object.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
origin: PropTypes.any,
|
||||
totalValue: PropTypes.object.isRequired,
|
||||
transaction: PropTypes.object.isRequired,
|
||||
value: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
origin: {
|
||||
type: 'unknown',
|
||||
details: ''
|
||||
}
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
const { totalValue, value } = this.props;
|
||||
|
||||
this.updateDisplayValues(value, totalValue);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const { totalValue, value } = nextProps;
|
||||
|
||||
this.updateDisplayValues(value, totalValue);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, disabled, externalLink, from, fromBalance, gasStore, netVersion, transaction, origin } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ styles.transaction }>
|
||||
<div className={ styles.from }>
|
||||
<div className={ styles.account }>
|
||||
<Account
|
||||
address={ from }
|
||||
balance={ fromBalance }
|
||||
disabled={ disabled }
|
||||
externalLink={ externalLink }
|
||||
netVersion={ netVersion }
|
||||
/>
|
||||
</div>
|
||||
<RequestOrigin origin={ origin } />
|
||||
</div>
|
||||
<div className={ styles.method }>
|
||||
<MethodDecoding
|
||||
address={ from }
|
||||
historic={ false }
|
||||
transaction={
|
||||
gasStore
|
||||
? gasStore.overrideTransaction(transaction)
|
||||
: transaction
|
||||
}
|
||||
/>
|
||||
{ this.renderEditTx() }
|
||||
</div>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderEditTx () {
|
||||
const { gasStore } = this.props;
|
||||
|
||||
if (!gasStore) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.editButtonRow }>
|
||||
<Button
|
||||
icon={ <GasIcon /> }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='signer.mainDetails.editTx'
|
||||
defaultMessage='Edit conditions/gas/gasPrice'
|
||||
/>
|
||||
}
|
||||
onClick={ this.toggleGasEditor }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTotalValue () {
|
||||
const { id } = this.props;
|
||||
const { feeEth, totalValueDisplay, totalValueDisplayWei } = this.state;
|
||||
const labelId = `totalValue${id}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={ styles.total }
|
||||
data-effect='solid'
|
||||
data-for={ labelId }
|
||||
data-place='bottom'
|
||||
data-tip
|
||||
>
|
||||
{ totalValueDisplay } <small>ETH</small>
|
||||
</div>
|
||||
<ReactTooltip id={ labelId }>
|
||||
<FormattedMessage
|
||||
id='signer.mainDetails.tooltips.total1'
|
||||
defaultMessage='The value of the transaction including the mining fee is {total} {type}.'
|
||||
values={ {
|
||||
total: <strong>{ totalValueDisplayWei }</strong>,
|
||||
type: <small>WEI</small>
|
||||
} }
|
||||
/>
|
||||
<br />
|
||||
<FormattedMessage
|
||||
id='signer.mainDetails.tooltips.total2'
|
||||
defaultMessage='(This includes a mining fee of {fee} {token})'
|
||||
values={ {
|
||||
fee: <strong>{ feeEth }</strong>,
|
||||
token: <small>ETH</small>
|
||||
} }
|
||||
/>
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderValue () {
|
||||
const { id } = this.props;
|
||||
const { valueDisplay, valueDisplayWei } = this.state;
|
||||
const labelId = `value${id}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
data-effect='solid'
|
||||
data-for={ labelId }
|
||||
data-tip
|
||||
>
|
||||
<strong>{ valueDisplay } </strong>
|
||||
<small>ETH</small>
|
||||
</div>
|
||||
<ReactTooltip id={ labelId }>
|
||||
<FormattedMessage
|
||||
id='signer.mainDetails.tooltips.value1'
|
||||
defaultMessage='The value of the transaction.'
|
||||
/>
|
||||
<br />
|
||||
<strong>{ valueDisplayWei }</strong> <small>WEI</small>
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
updateDisplayValues (value, totalValue) {
|
||||
this.setState({
|
||||
feeEth: tUtil.calcFeeInEth(totalValue, value),
|
||||
totalValueDisplay: tUtil.getTotalValueDisplay(totalValue),
|
||||
totalValueDisplayWei: tUtil.getTotalValueDisplayWei(totalValue),
|
||||
valueDisplay: tUtil.getValueDisplay(value),
|
||||
valueDisplayWei: tUtil.getValueDisplayWei(value)
|
||||
});
|
||||
}
|
||||
|
||||
toggleGasEditor = () => {
|
||||
this.props.gasStore.setEditing(true);
|
||||
}
|
||||
}
|
||||
17
js/src/shell/Signer/components/TransactionPending/index.js
Normal file
17
js/src/shell/Signer/components/TransactionPending/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 './transactionPending';
|
||||
@@ -0,0 +1,27 @@
|
||||
/* 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 '../../_layout.css';
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
padding: 1.5em 1em 1.5em 0;
|
||||
|
||||
& > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// 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 { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import HardwareStore from '~/mobx/hardwareStore';
|
||||
import { Button, GasPriceEditor } from '~/ui';
|
||||
|
||||
import TransactionMainDetails from '../TransactionMainDetails';
|
||||
import TransactionPendingForm from '../TransactionPendingForm';
|
||||
|
||||
import styles from './transactionPending.css';
|
||||
|
||||
import * as tUtil from '../util/transaction';
|
||||
|
||||
@observer
|
||||
class TransactionPending extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
className: PropTypes.string,
|
||||
date: PropTypes.instanceOf(Date).isRequired,
|
||||
focus: PropTypes.bool,
|
||||
gasLimit: PropTypes.object,
|
||||
id: PropTypes.object.isRequired,
|
||||
isSending: PropTypes.bool.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
nonce: PropTypes.number,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onReject: PropTypes.func.isRequired,
|
||||
origin: PropTypes.any,
|
||||
signerStore: PropTypes.object.isRequired,
|
||||
transaction: PropTypes.shape({
|
||||
condition: PropTypes.object,
|
||||
data: PropTypes.string,
|
||||
from: PropTypes.string.isRequired,
|
||||
gas: PropTypes.object.isRequired,
|
||||
gasPrice: PropTypes.object.isRequired,
|
||||
to: PropTypes.string,
|
||||
value: PropTypes.object.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focus: false,
|
||||
origin: {
|
||||
type: 'unknown',
|
||||
details: ''
|
||||
}
|
||||
};
|
||||
|
||||
gasStore = new GasPriceEditor.Store(this.context.api, {
|
||||
condition: this.props.transaction.condition,
|
||||
gas: this.props.transaction.gas.toFixed(),
|
||||
gasLimit: this.props.gasLimit,
|
||||
gasPrice: this.props.transaction.gasPrice.toFixed()
|
||||
});
|
||||
|
||||
hardwareStore = HardwareStore.get(this.context.api);
|
||||
|
||||
componentWillMount () {
|
||||
const { signerStore, transaction } = this.props;
|
||||
const { from, gas, gasPrice, to, value } = transaction;
|
||||
|
||||
const fee = tUtil.getFee(gas, gasPrice); // BigNumber object
|
||||
const gasPriceEthmDisplay = tUtil.getEthmFromWeiDisplay(gasPrice);
|
||||
const gasToDisplay = tUtil.getGasDisplay(gas);
|
||||
const totalValue = tUtil.getTotalValue(fee, value);
|
||||
|
||||
this.setState({ gasPriceEthmDisplay, totalValue, gasToDisplay });
|
||||
this.gasStore.setEthValue(value);
|
||||
signerStore.fetchBalances([from, to]);
|
||||
}
|
||||
|
||||
render () {
|
||||
return this.gasStore.isEditing
|
||||
? this.renderTxEditor()
|
||||
: this.renderTransaction();
|
||||
}
|
||||
|
||||
renderTransaction () {
|
||||
const { accounts, className, focus, id, isSending, netVersion, origin, signerStore, transaction } = this.props;
|
||||
const { totalValue } = this.state;
|
||||
const { balances, externalLink } = signerStore;
|
||||
const { from, value } = transaction;
|
||||
const fromBalance = balances[from];
|
||||
const account = accounts[from] || {};
|
||||
const disabled = account.hardware && !this.hardwareStore.isConnected(from);
|
||||
|
||||
return (
|
||||
<div className={ `${styles.container} ${className}` }>
|
||||
<TransactionMainDetails
|
||||
className={ styles.transactionDetails }
|
||||
disabled={ disabled }
|
||||
externalLink={ externalLink }
|
||||
from={ from }
|
||||
fromBalance={ fromBalance }
|
||||
gasStore={ this.gasStore }
|
||||
id={ id }
|
||||
netVersion={ netVersion }
|
||||
origin={ origin }
|
||||
totalValue={ totalValue }
|
||||
transaction={ transaction }
|
||||
value={ value }
|
||||
/>
|
||||
<TransactionPendingForm
|
||||
account={ account }
|
||||
address={ from }
|
||||
disabled={ disabled }
|
||||
focus={ focus }
|
||||
gasStore={ this.gasStore }
|
||||
isSending={ isSending }
|
||||
netVersion={ netVersion }
|
||||
onConfirm={ this.onConfirm }
|
||||
onReject={ this.onReject }
|
||||
transaction={ transaction }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTxEditor () {
|
||||
const { className } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ `${styles.container} ${className}` }>
|
||||
<GasPriceEditor store={ this.gasStore }>
|
||||
<Button
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='signer.txPending.buttons.viewToggle'
|
||||
defaultMessage='view transaction'
|
||||
/>
|
||||
}
|
||||
onClick={ this.toggleGasEditor }
|
||||
/>
|
||||
</GasPriceEditor>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onConfirm = (data) => {
|
||||
const { id, transaction } = this.props;
|
||||
const { password, txSigned, wallet } = data;
|
||||
const { condition, gas, gasPrice } = this.gasStore.overrideTransaction(transaction);
|
||||
|
||||
const options = {
|
||||
gas,
|
||||
gasPrice,
|
||||
id,
|
||||
password,
|
||||
txSigned,
|
||||
wallet
|
||||
};
|
||||
|
||||
if (condition && (condition.block || condition.time)) {
|
||||
options.condition = condition;
|
||||
}
|
||||
|
||||
this.props.onConfirm(options);
|
||||
}
|
||||
|
||||
onReject = () => {
|
||||
this.props.onReject(this.props.id);
|
||||
}
|
||||
|
||||
toggleGasEditor = () => {
|
||||
this.gasStore.setEditing(false);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { accounts } = state.personal;
|
||||
|
||||
return {
|
||||
accounts
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(TransactionPending);
|
||||
@@ -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 './transactionPendingFormConfirm';
|
||||
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
.confirmForm {
|
||||
margin-top: -2em;
|
||||
}
|
||||
|
||||
.confirmButton {
|
||||
display: block !important;
|
||||
margin-bottom: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.signerIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
vertical-align: middle;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.passwordHint {
|
||||
font-size: 0.75em;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
.passwordHint span {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.fileInput input {
|
||||
top: 22px;
|
||||
}
|
||||
|
||||
.qr {
|
||||
margin-bottom: 0.5em;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
// 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 RaisedButton from 'material-ui/RaisedButton';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
||||
import { Form, Input, IdentityIcon, QrCode, QrScan } from '~/ui';
|
||||
import { generateTxQr } from '~/util/qrscan';
|
||||
|
||||
import styles from './transactionPendingFormConfirm.css';
|
||||
|
||||
const QR_VISIBLE = 1;
|
||||
const QR_SCAN = 2;
|
||||
const QR_COMPLETED = 3;
|
||||
|
||||
export default class TransactionPendingFormConfirm extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
account: PropTypes.object,
|
||||
address: PropTypes.string.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
focus: PropTypes.bool,
|
||||
gasStore: PropTypes.object.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
isSending: PropTypes.bool.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
transaction: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
account: {},
|
||||
focus: false
|
||||
};
|
||||
|
||||
id = Math.random(); // for tooltip
|
||||
|
||||
state = {
|
||||
password: '',
|
||||
qrState: QR_VISIBLE,
|
||||
qr: {},
|
||||
wallet: null,
|
||||
walletError: null
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.readNonce();
|
||||
this.subscribeNonce();
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.unsubscribeNonce();
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (!this.props.focus && nextProps.focus) {
|
||||
this.focus(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Properly focus on the input element when needed.
|
||||
* This might be fixed some day in MaterialUI with
|
||||
* an autoFocus prop.
|
||||
*
|
||||
* @see https://github.com/callemall/material-ui/issues/5632
|
||||
*/
|
||||
focus (props = this.props) {
|
||||
if (props.focus) {
|
||||
const textNode = ReactDOM.findDOMNode(this.refs.input);
|
||||
|
||||
if (!textNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputNode = textNode.querySelector('input');
|
||||
|
||||
inputNode && inputNode.focus();
|
||||
}
|
||||
}
|
||||
|
||||
getPasswordHint () {
|
||||
const { account } = this.props;
|
||||
const accountHint = account.meta && account.meta.passwordHint;
|
||||
|
||||
if (accountHint) {
|
||||
return accountHint;
|
||||
}
|
||||
|
||||
const { wallet } = this.state;
|
||||
const walletHint = wallet && wallet.meta && wallet.meta.passwordHint;
|
||||
|
||||
return walletHint || null;
|
||||
}
|
||||
|
||||
// TODO: Now that we have 3 types, it would make sense splitting each into their own
|
||||
// sub-module and having the consistent bits combined (e.g. i18n, layouts)
|
||||
render () {
|
||||
const { account, address, disabled, isSending } = this.props;
|
||||
const { wallet, walletError } = this.state;
|
||||
const isAccount = account.external || account.hardware || account.uuid;
|
||||
const isWalletOk = isAccount || (walletError === null && wallet !== null);
|
||||
const confirmText = this.renderConfirmButton();
|
||||
const confirmButton = confirmText
|
||||
? (
|
||||
<div
|
||||
data-effect='solid'
|
||||
data-for={ `transactionConfirmForm${this.id}` }
|
||||
data-place='bottom'
|
||||
data-tip
|
||||
>
|
||||
<RaisedButton
|
||||
className={ styles.confirmButton }
|
||||
disabled={ disabled || isSending || !isWalletOk }
|
||||
fullWidth
|
||||
icon={
|
||||
<IdentityIcon
|
||||
address={ address }
|
||||
button
|
||||
className={ styles.signerIcon }
|
||||
/>
|
||||
}
|
||||
label={ confirmText }
|
||||
onTouchTap={ this.onConfirm }
|
||||
primary
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className={ styles.confirmForm }>
|
||||
<Form>
|
||||
{ this.renderKeyInput() }
|
||||
{ this.renderQrCode() }
|
||||
{ this.renderQrScanner() }
|
||||
{ this.renderPassword() }
|
||||
{ this.renderHint() }
|
||||
{ confirmButton }
|
||||
{ this.renderTooltip() }
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderConfirmButton () {
|
||||
const { account, isSending } = this.props;
|
||||
const { qrState } = this.state;
|
||||
|
||||
if (account.external) {
|
||||
switch (qrState) {
|
||||
case QR_VISIBLE:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.buttons.scanSigned'
|
||||
defaultMessage='Scan Signed QR'
|
||||
/>
|
||||
);
|
||||
|
||||
case QR_SCAN:
|
||||
case QR_COMPLETED:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return isSending
|
||||
? (
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.buttons.confirmBusy'
|
||||
defaultMessage='Confirming...'
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.buttons.confirmRequest'
|
||||
defaultMessage='Confirm Request'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderPassword () {
|
||||
const { account } = this.props;
|
||||
const { password } = this.state;
|
||||
|
||||
if (account.hardware || account.external) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isAccount = account.uuid;
|
||||
|
||||
return (
|
||||
<Input
|
||||
hint={
|
||||
isAccount
|
||||
? (
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.password.unlock.hint'
|
||||
defaultMessage='unlock the account'
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.password.decrypt.hint'
|
||||
defaultMessage='decrypt the key'
|
||||
/>
|
||||
)
|
||||
}
|
||||
label={
|
||||
isAccount
|
||||
? (
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.password.unlock.label'
|
||||
defaultMessage='Account Password'
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.password.decrypt.label'
|
||||
defaultMessage='Key Password'
|
||||
/>
|
||||
)
|
||||
}
|
||||
onChange={ this.onModifyPassword }
|
||||
onKeyDown={ this.onKeyDown }
|
||||
ref='input'
|
||||
type='password'
|
||||
value={ password }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderHint () {
|
||||
const { account, disabled, isSending } = this.props;
|
||||
const { qrState } = this.state;
|
||||
|
||||
if (account.external) {
|
||||
switch (qrState) {
|
||||
case QR_VISIBLE:
|
||||
return (
|
||||
<div className={ styles.passwordHint }>
|
||||
<FormattedMessage
|
||||
id='signer.sending.external.scanTx'
|
||||
defaultMessage='Please scan the transaction QR on your external device'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
case QR_SCAN:
|
||||
return (
|
||||
<div className={ styles.passwordHint }>
|
||||
<FormattedMessage
|
||||
id='signer.sending.external.scanSigned'
|
||||
defaultMessage='Scan the QR code of the signed transaction from your external device'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
case QR_COMPLETED:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (account.hardware) {
|
||||
if (isSending) {
|
||||
return (
|
||||
<div className={ styles.passwordHint }>
|
||||
<FormattedMessage
|
||||
id='signer.sending.hardware.confirm'
|
||||
defaultMessage='Please confirm the transaction on your attached hardware device'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (disabled) {
|
||||
return (
|
||||
<div className={ styles.passwordHint }>
|
||||
<FormattedMessage
|
||||
id='signer.sending.hardware.connect'
|
||||
defaultMessage='Please attach your hardware device before confirming the transaction'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const passwordHint = this.getPasswordHint();
|
||||
|
||||
if (!passwordHint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.passwordHint }>
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.passwordHint'
|
||||
defaultMessage='(hint) {passwordHint}'
|
||||
values={ {
|
||||
passwordHint
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Split into sub-scomponent
|
||||
renderQrCode () {
|
||||
const { account } = this.props;
|
||||
const { qrState, qr } = this.state;
|
||||
|
||||
if (!account.external || qrState !== QR_VISIBLE || !qr.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<QrCode
|
||||
className={ styles.qr }
|
||||
value={ qr.value }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Split into sub-scomponent
|
||||
renderQrScanner () {
|
||||
const { account } = this.props;
|
||||
const { qrState } = this.state;
|
||||
|
||||
if (!account.external || qrState !== QR_SCAN) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<QrScan
|
||||
className={ styles.camera }
|
||||
onScan={ this.onScanTx }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderKeyInput () {
|
||||
const { account } = this.props;
|
||||
const { walletError } = this.state;
|
||||
|
||||
if (account.uuid || account.wallet || account.hardware || account.external) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
className={ styles.fileInput }
|
||||
error={ walletError }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.selectKey.hint'
|
||||
defaultMessage='The keyfile to use for this account'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.selectKey.label'
|
||||
defaultMessage='Select Local Key'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onKeySelect }
|
||||
type='file'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderTooltip () {
|
||||
const { account } = this.props;
|
||||
|
||||
if (this.state.password.length || account.hardware || account.external) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactTooltip id={ `transactionConfirmForm${this.id}` }>
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.tooltips.password'
|
||||
defaultMessage='Please provide a password for this account'
|
||||
/>
|
||||
</ReactTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
onScanTx = (signature) => {
|
||||
const { chainId, rlp, tx } = this.state.qr;
|
||||
|
||||
if (signature && signature.substr(0, 2) !== '0x') {
|
||||
signature = `0x${signature}`;
|
||||
}
|
||||
|
||||
this.setState({ qrState: QR_COMPLETED });
|
||||
|
||||
this.props.onConfirm({
|
||||
txSigned: {
|
||||
chainId,
|
||||
rlp,
|
||||
signature,
|
||||
tx
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onKeySelect = (event) => {
|
||||
// Check that file have been selected
|
||||
if (event.target.files.length === 0) {
|
||||
return this.setState({
|
||||
wallet: null,
|
||||
walletError: null
|
||||
});
|
||||
}
|
||||
|
||||
const fileReader = new FileReader();
|
||||
|
||||
fileReader.onload = (e) => {
|
||||
try {
|
||||
const wallet = JSON.parse(e.target.result);
|
||||
|
||||
try {
|
||||
if (wallet && typeof wallet.meta === 'string') {
|
||||
wallet.meta = JSON.parse(wallet.meta);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
this.setState({
|
||||
wallet,
|
||||
walletError: null
|
||||
});
|
||||
} catch (error) {
|
||||
this.setState({
|
||||
wallet: null,
|
||||
walletError: (
|
||||
<FormattedMessage
|
||||
id='signer.txPendingConfirm.errors.invalidWallet'
|
||||
defaultMessage='Given wallet file is invalid.'
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fileReader.readAsText(event.target.files[0]);
|
||||
}
|
||||
|
||||
onModifyPassword = (event) => {
|
||||
const password = event.target.value;
|
||||
|
||||
this.setState({
|
||||
password
|
||||
});
|
||||
}
|
||||
|
||||
onConfirm = () => {
|
||||
const { account } = this.props;
|
||||
const { password, qrState, wallet } = this.state;
|
||||
|
||||
if (account.external && qrState === QR_VISIBLE) {
|
||||
return this.setState({ qrState: QR_SCAN });
|
||||
}
|
||||
|
||||
this.props.onConfirm({
|
||||
password,
|
||||
wallet
|
||||
});
|
||||
}
|
||||
|
||||
generateTxQr = () => {
|
||||
const { api } = this.context;
|
||||
const { netVersion, gasStore, transaction } = this.props;
|
||||
|
||||
generateTxQr(api, netVersion, gasStore, transaction).then((qr) => {
|
||||
this.setState({ qr });
|
||||
});
|
||||
}
|
||||
|
||||
onKeyDown = (event) => {
|
||||
const codeName = keycode(event);
|
||||
|
||||
if (codeName !== 'enter') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onConfirm();
|
||||
}
|
||||
|
||||
// FIXME: Sadly the API subscription channels currently does not allow for specific values,
|
||||
// rather it can only do general queries where parameters are not specified. Hence we are
|
||||
// polling for the nonce here. Since we are moving to node-based subscriptions on the API layer,
|
||||
// this can be optimised when the subscription mechanism is reworked to conform.
|
||||
subscribeNonce () {
|
||||
const nonceTimerId = setInterval(this.readNonce, 1000);
|
||||
|
||||
this.setState({ nonceTimerId });
|
||||
}
|
||||
|
||||
unsubscribeNonce () {
|
||||
const { nonceTimerId } = this.state;
|
||||
|
||||
if (!nonceTimerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(nonceTimerId);
|
||||
}
|
||||
|
||||
readNonce = () => {
|
||||
const { api } = this.context;
|
||||
const { account } = this.props;
|
||||
|
||||
if (!account || !account.external || !api.transport.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
return api.parity
|
||||
.nextNonce(account.address)
|
||||
.then((nonce) => {
|
||||
const { qr } = this.state;
|
||||
|
||||
if (!qr.nonce || !nonce.eq(qr.nonce)) {
|
||||
this.generateTxQr();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// 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 { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import TransactionPendingFormConfirm from './';
|
||||
|
||||
const ADDR_NORMAL = '0x0123456789012345678901234567890123456789';
|
||||
const ADDR_WALLET = '0x1234567890123456789012345678901234567890';
|
||||
const ADDR_HARDWARE = '0x2345678901234567890123456789012345678901';
|
||||
const ADDR_SIGN = '0x3456789012345678901234567890123456789012';
|
||||
const ACCOUNTS = {
|
||||
[ADDR_NORMAL]: {
|
||||
address: ADDR_NORMAL,
|
||||
uuid: ADDR_NORMAL
|
||||
},
|
||||
[ADDR_WALLET]: {
|
||||
address: ADDR_WALLET,
|
||||
wallet: true
|
||||
},
|
||||
[ADDR_HARDWARE]: {
|
||||
address: ADDR_HARDWARE,
|
||||
hardware: true
|
||||
}
|
||||
};
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let onConfirm;
|
||||
|
||||
function render (address) {
|
||||
onConfirm = sinon.stub();
|
||||
|
||||
component = shallow(
|
||||
<TransactionPendingFormConfirm
|
||||
account={ ACCOUNTS[address] }
|
||||
address={ address }
|
||||
onConfirm={ onConfirm }
|
||||
isSending={ false }
|
||||
/>
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Signer/TransactionPendingFormConfirm', () => {
|
||||
describe('normal accounts', () => {
|
||||
beforeEach(() => {
|
||||
render(ADDR_NORMAL);
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('does not render the key input', () => {
|
||||
expect(instance.renderKeyInput()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders the password', () => {
|
||||
expect(instance.renderPassword()).not.to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
describe('hardware accounts', () => {
|
||||
beforeEach(() => {
|
||||
render(ADDR_HARDWARE);
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('does not render the key input', () => {
|
||||
expect(instance.renderKeyInput()).to.be.null;
|
||||
});
|
||||
|
||||
it('does not render the password', () => {
|
||||
expect(instance.renderPassword()).to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
describe('wallet accounts', () => {
|
||||
beforeEach(() => {
|
||||
render(ADDR_WALLET);
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('does not render the key input', () => {
|
||||
expect(instance.renderKeyInput()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders the password', () => {
|
||||
expect(instance.renderPassword()).not.to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
describe('signing accounts', () => {
|
||||
beforeEach(() => {
|
||||
render(ADDR_SIGN);
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders the key input', () => {
|
||||
expect(instance.renderKeyInput()).not.to.be.null;
|
||||
});
|
||||
|
||||
it('renders the password', () => {
|
||||
expect(instance.renderPassword()).not.to.be.null;
|
||||
});
|
||||
|
||||
it('renders the hint', () => {
|
||||
expect(instance.renderHint()).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 './transactionPendingFormReject';
|
||||
@@ -0,0 +1,26 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
/* the rejection button itself, once .reject has been pressed */
|
||||
.rejectButton {
|
||||
display: block !important;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.rejectText {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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 { FormattedMessage } from 'react-intl';
|
||||
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
|
||||
import styles from './transactionPendingFormReject.css';
|
||||
|
||||
export default class TransactionPendingFormReject extends Component {
|
||||
static propTypes = {
|
||||
onReject: PropTypes.func.isRequired,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
render () {
|
||||
const { onReject } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={ styles.rejectText }>
|
||||
<FormattedMessage
|
||||
id='signer.txPendingReject.info'
|
||||
defaultMessage='Are you sure you want to reject request?'
|
||||
/>
|
||||
<br />
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id='signer.txPendingReject.undone'
|
||||
defaultMessage='This cannot be undone'
|
||||
/>
|
||||
</strong>
|
||||
</div>
|
||||
<RaisedButton
|
||||
onTouchTap={ onReject }
|
||||
className={ styles.rejectButton }
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='signer.txPendingReject.buttons.reject'
|
||||
defaultMessage='Reject Request'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 './transactionPendingForm';
|
||||
@@ -0,0 +1,44 @@
|
||||
/* 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 '../../_layout.css';
|
||||
|
||||
.container {
|
||||
box-sizing: border-box;
|
||||
padding: 1em 0 0 2em;
|
||||
flex: 0 0 $statusWidth;
|
||||
}
|
||||
|
||||
.rejectToggle {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
color: #00e;
|
||||
opacity: .7;
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.rejectToggle:hover {
|
||||
opacity: 1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.rejectToggle svg {
|
||||
position: relative;
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
top: 3px;
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// 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 { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { PrevIcon } from '~/ui/Icons';
|
||||
|
||||
import TransactionPendingFormConfirm from './TransactionPendingFormConfirm';
|
||||
import TransactionPendingFormReject from './TransactionPendingFormReject';
|
||||
import styles from './transactionPendingForm.css';
|
||||
|
||||
export default class TransactionPendingForm extends Component {
|
||||
static propTypes = {
|
||||
account: PropTypes.object,
|
||||
address: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
focus: PropTypes.bool,
|
||||
gasStore: PropTypes.object.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
isSending: PropTypes.bool.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onReject: PropTypes.func.isRequired,
|
||||
transaction: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
account: {},
|
||||
focus: false
|
||||
};
|
||||
|
||||
state = {
|
||||
isRejectOpen: false
|
||||
};
|
||||
|
||||
render () {
|
||||
const { className } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ `${styles.container} ${className}` }>
|
||||
{ this.renderForm() }
|
||||
{ this.renderRejectToggle() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderForm () {
|
||||
const { account, address, disabled, focus, gasStore, isSending, netVersion, onConfirm, onReject, transaction } = this.props;
|
||||
|
||||
if (this.state.isRejectOpen) {
|
||||
return (
|
||||
<TransactionPendingFormReject onReject={ onReject } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TransactionPendingFormConfirm
|
||||
address={ address }
|
||||
account={ account }
|
||||
disabled={ disabled }
|
||||
focus={ focus }
|
||||
gasStore={ gasStore }
|
||||
netVersion={ netVersion }
|
||||
isSending={ isSending }
|
||||
onConfirm={ onConfirm }
|
||||
transaction={ transaction }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderRejectToggle () {
|
||||
const { isRejectOpen } = this.state;
|
||||
let html;
|
||||
|
||||
if (!isRejectOpen) {
|
||||
html = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='signer.txPendingForm.reject'
|
||||
defaultMessage='reject request'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
html = (
|
||||
<span>
|
||||
<PrevIcon />
|
||||
<FormattedMessage
|
||||
id='signer.txPendingForm.changedMind'
|
||||
defaultMessage="I've changed my mind"
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
className={ styles.rejectToggle }
|
||||
onClick={ this.onToggleReject }
|
||||
>
|
||||
{ html }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
onToggleReject = () => {
|
||||
const { isRejectOpen } = this.state;
|
||||
|
||||
this.setState({
|
||||
isRejectOpen: !isRejectOpen
|
||||
});
|
||||
}
|
||||
}
|
||||
17
js/src/shell/Signer/components/TxHashLink/index.js
Normal file
17
js/src/shell/Signer/components/TxHashLink/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 './txHashLink';
|
||||
42
js/src/shell/Signer/components/TxHashLink/txHashLink.js
Normal file
42
js/src/shell/Signer/components/TxHashLink/txHashLink.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// 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 { txLink } from '~/3rdparty/etherscan/links';
|
||||
|
||||
export default class TxHashLink extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
txHash: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, className, netVersion, txHash } = this.props;
|
||||
|
||||
return (
|
||||
<a
|
||||
className={ className }
|
||||
href={ txLink(txHash, false, netVersion) }
|
||||
target='_blank'
|
||||
>
|
||||
{ children || txHash }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
38
js/src/shell/Signer/components/util/logger.js
Normal file
38
js/src/shell/Signer/components/util/logger.js
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/>.
|
||||
|
||||
const isLogging = process.env.LOGGING;
|
||||
|
||||
export default logger();
|
||||
|
||||
function logger () {
|
||||
return isLogging ? devLogger() : prodLogger();
|
||||
}
|
||||
|
||||
function prodLogger () {
|
||||
return {
|
||||
log: noop,
|
||||
info: noop,
|
||||
error: noop,
|
||||
warn: noop
|
||||
};
|
||||
}
|
||||
|
||||
function devLogger () {
|
||||
return console;
|
||||
}
|
||||
|
||||
function noop () {}
|
||||
21
js/src/shell/Signer/components/util/react.js
vendored
Normal file
21
js/src/shell/Signer/components/util/react.js
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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 { isValidElement } from 'react';
|
||||
|
||||
export function isReactComponent (componentOrElem) {
|
||||
return isValidElement(componentOrElem) && typeof componentOrElem.type === 'function';
|
||||
}
|
||||
115
js/src/shell/Signer/components/util/transaction.js
Normal file
115
js/src/shell/Signer/components/util/transaction.js
Normal file
@@ -0,0 +1,115 @@
|
||||
// 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 BigNumber from 'bignumber.js';
|
||||
|
||||
const WEI_TO_ETH_MULTIPLIER = 0.000000000000000001;
|
||||
const WEI_TO_SZABU_MULTIPLIER = 0.000000000001;
|
||||
|
||||
export const getShortData = _getShortData;
|
||||
// calculations
|
||||
export const getFee = _getFee;
|
||||
export const calcFeeInEth = _calcFeeInEth;
|
||||
export const getTotalValue = _getTotalValue;
|
||||
// displays
|
||||
export const getSzaboFromWeiDisplay = _getSzaboFromWeiDisplay;
|
||||
export const getValueDisplay = _getValueDisplay;
|
||||
export const getValueDisplayWei = _getValueDisplayWei;
|
||||
export const getTotalValueDisplay = _getTotalValueDisplay;
|
||||
export const getTotalValueDisplayWei = _getTotalValueDisplayWei;
|
||||
export const getEthmFromWeiDisplay = _getEthmFromWeiDisplay;
|
||||
export const getGasDisplay = _getGasDisplay;
|
||||
|
||||
function _getShortData (data) {
|
||||
if (data.length <= 3) {
|
||||
return data;
|
||||
}
|
||||
return data.substr(0, 3) + '...';
|
||||
}
|
||||
|
||||
/*
|
||||
* @param {hex string} gas
|
||||
* @param {wei hex string} gasPrice
|
||||
* @return {BigNumber} fee in wei
|
||||
*/
|
||||
function _getFee (gas, gasPrice) {
|
||||
gas = new BigNumber(gas);
|
||||
gasPrice = new BigNumber(gasPrice);
|
||||
return gasPrice.times(gas);
|
||||
}
|
||||
|
||||
function _calcFeeInEth (totalValue, value) {
|
||||
let fee = new BigNumber(totalValue).sub(new BigNumber(value));
|
||||
|
||||
return fee.times(WEI_TO_ETH_MULTIPLIER).toFormat(7);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param {wei BigNumber} fee
|
||||
* @param {wei hex string} value
|
||||
* @return {BigNumber} total value in wei
|
||||
*/
|
||||
function _getTotalValue (fee, value) {
|
||||
value = new BigNumber(value);
|
||||
return fee.plus(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param {wei hex string} gasPrice
|
||||
* @return {string} szabo gas price with unit [szabo] i.e. 21,423 [szabo]
|
||||
*/
|
||||
function _getSzaboFromWeiDisplay (gasPrice) {
|
||||
gasPrice = new BigNumber(gasPrice);
|
||||
return gasPrice.times(WEI_TO_SZABU_MULTIPLIER).toPrecision(5);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param {wei hex string} value
|
||||
* @return {string} value in WEI nicely formatted
|
||||
*/
|
||||
function _getValueDisplay (value) {
|
||||
value = new BigNumber(value);
|
||||
return value.times(WEI_TO_ETH_MULTIPLIER).toFormat(5);
|
||||
}
|
||||
|
||||
function _getValueDisplayWei (value) {
|
||||
value = new BigNumber(value);
|
||||
return value.toFormat(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param {wei hex string} totalValue
|
||||
* @return {string} total value (including fee) with units i.e. 1.32 [eth]
|
||||
*/
|
||||
function _getTotalValueDisplay (totalValue) {
|
||||
totalValue = new BigNumber(totalValue);
|
||||
return totalValue.times(WEI_TO_ETH_MULTIPLIER).toFormat(5);
|
||||
}
|
||||
|
||||
function _getTotalValueDisplayWei (totalValue) {
|
||||
totalValue = new BigNumber(totalValue);
|
||||
return totalValue.toFormat(0);
|
||||
}
|
||||
|
||||
function _getEthmFromWeiDisplay (weiHexString) {
|
||||
const value = new BigNumber(weiHexString);
|
||||
|
||||
return value.times(WEI_TO_ETH_MULTIPLIER).times(1e7).toFixed(5);
|
||||
}
|
||||
|
||||
function _getGasDisplay (gas) {
|
||||
return new BigNumber(gas).times(1e-7).toFormat(4);
|
||||
}
|
||||
79
js/src/shell/Signer/components/util/transaction.spec.js
Normal file
79
js/src/shell/Signer/components/util/transaction.spec.js
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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 BigNumber from 'bignumber.js';
|
||||
import { getShortData, getFee, getTotalValue } from './transaction';
|
||||
|
||||
describe('views/Signer/components/util/transaction', () => {
|
||||
describe('getEstimatedMiningTime', () => {
|
||||
it('should return estimated mining time', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getShortData', () => {
|
||||
it('should return short data', () => {
|
||||
// given
|
||||
const data = '0xh87dY78';
|
||||
|
||||
// when
|
||||
const res = getShortData(data);
|
||||
|
||||
// then
|
||||
expect(res).to.equal('0xh...');
|
||||
});
|
||||
|
||||
it('should return data as is', () => {
|
||||
// given
|
||||
const data = '0x0';
|
||||
|
||||
// when
|
||||
const shortData = getShortData(data);
|
||||
|
||||
// then
|
||||
expect(shortData).to.equal('0x0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFee', () => {
|
||||
it('should return wei BigNumber object equals to gas * gasPrice', () => {
|
||||
// given
|
||||
const gas = '0x76c0'; // 30400
|
||||
const gasPrice = '0x9184e72a000'; // 10000000000000 wei
|
||||
|
||||
// when
|
||||
const fee = getFee(gas, gasPrice);
|
||||
|
||||
// then
|
||||
expect(fee).to.be.an.instanceOf(BigNumber);
|
||||
expect(fee.toString()).to.be.equal('304000000000000000'); // converting to string due to https://github.com/MikeMcl/bignumber.js/issues/11
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTotalValue', () => {
|
||||
it('should return wei BigNumber totalValue equals to value + fee', () => {
|
||||
// given
|
||||
const fee = new BigNumber(304000000000000000); // wei
|
||||
const value = '0x9184e72a'; // 2441406250 wei
|
||||
|
||||
// when
|
||||
const totalValue = getTotalValue(fee, value);
|
||||
|
||||
// then
|
||||
expect(totalValue).to.be.an.instanceOf(BigNumber);
|
||||
expect(totalValue.toString()).to.be.equal('304000002441406250'); // converting to string due to https://github.com/MikeMcl/bignumber.js/issues/11
|
||||
});
|
||||
});
|
||||
});
|
||||
35
js/src/shell/Signer/components/util/util.js
Normal file
35
js/src/shell/Signer/components/util/util.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// 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 function toPromise (fn) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fn((err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function identity (x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
export function capitalize (str) {
|
||||
return str[0].toUpperCase() + str.slice(1).toLowerCase();
|
||||
}
|
||||
107
js/src/shell/Signer/store.js
Normal file
107
js/src/shell/Signer/store.js
Normal file
@@ -0,0 +1,107 @@
|
||||
// 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 { isEqual } from 'lodash';
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
export default class SignerStore {
|
||||
@observable balances = {};
|
||||
@observable localHashes = [];
|
||||
|
||||
externalLink = '';
|
||||
|
||||
constructor (api, withLocalTransactions = false, externalLink = '') {
|
||||
this._api = api;
|
||||
this._timeoutId = 0;
|
||||
this.externalLink = externalLink;
|
||||
|
||||
if (withLocalTransactions) {
|
||||
this.fetchLocalTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
@action setBalance = (address, balance) => {
|
||||
this.setBalances({ [address]: balance });
|
||||
}
|
||||
|
||||
@action setBalances = (balances) => {
|
||||
this.balances = Object.assign({}, this.balances, balances);
|
||||
}
|
||||
|
||||
@action setLocalHashes = (localHashes = []) => {
|
||||
// Use slice to make sure they are both Arrays (MobX uses Objects for Observable Arrays)
|
||||
if (!isEqual(localHashes.slice(), this.localHashes.slice())) {
|
||||
this.localHashes = localHashes;
|
||||
}
|
||||
}
|
||||
|
||||
@action unsubscribe () {
|
||||
if (this._timeoutId) {
|
||||
clearTimeout(this._timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
fetchBalance (address) {
|
||||
this._api.eth
|
||||
.getBalance(address)
|
||||
.then((balance) => {
|
||||
this.setBalance(address, balance);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('Store:fetchBalance', error);
|
||||
});
|
||||
}
|
||||
|
||||
fetchBalances (_addresses) {
|
||||
const addresses = _addresses.filter((address) => address) || [];
|
||||
|
||||
if (!addresses.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
Promise
|
||||
.all(addresses.map((address) => this._api.eth.getBalance(address)))
|
||||
.then((_balances) => {
|
||||
this.setBalances(
|
||||
addresses.reduce((balances, address, index) => {
|
||||
balances[address] = _balances[index];
|
||||
return balances;
|
||||
}, {})
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('Store:fetchBalances', error);
|
||||
});
|
||||
}
|
||||
|
||||
fetchLocalTransactions = () => {
|
||||
const nextTimeout = () => {
|
||||
this._timeoutId = setTimeout(this.fetchLocalTransactions, 1500);
|
||||
};
|
||||
|
||||
this._api.parity
|
||||
.localTransactions()
|
||||
.then((localTransactions) => {
|
||||
const keys = Object
|
||||
.keys(localTransactions)
|
||||
.filter((key) => localTransactions[key].status !== 'canceled');
|
||||
|
||||
this.setLocalHashes(keys);
|
||||
})
|
||||
.then(nextTimeout)
|
||||
.catch(nextTimeout);
|
||||
}
|
||||
}
|
||||
19
js/src/shell/Signer/utils/extension.js
Normal file
19
js/src/shell/Signer/utils/extension.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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 const isExtension = () => {
|
||||
return window.location.protocol.indexOf('chrome-extension:') > -1;
|
||||
};
|
||||
19
js/src/shell/Signer/utils/utils.js
Normal file
19
js/src/shell/Signer/utils/utils.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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 function identity (x) {
|
||||
return x;
|
||||
}
|
||||
108
js/src/shell/embed.js
Normal file
108
js/src/shell/embed.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// 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 'babel-polyfill';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import es6Promise from 'es6-promise';
|
||||
es6Promise.polyfill();
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
|
||||
import SecureApi from '~/secureApi';
|
||||
import ContractInstances from '~/contracts';
|
||||
|
||||
import { initStore } from '~/redux';
|
||||
import ContextProvider from '~/ui/ContextProvider';
|
||||
import muiTheme from '~/ui/Theme';
|
||||
|
||||
import { patchApi } from '~/util/tx';
|
||||
|
||||
import '~/environment';
|
||||
|
||||
import '~/../assets/fonts/Roboto/font.css';
|
||||
import '~/../assets/fonts/RobotoMono/font.css';
|
||||
|
||||
injectTapEventPlugin();
|
||||
|
||||
import ParityBar from '~/shell/ParityBar';
|
||||
|
||||
// Test transport (std transport should be provided as global object)
|
||||
class FakeTransport {
|
||||
constructor () {
|
||||
console.warn('Secure Transport not provided. Falling back to FakeTransport');
|
||||
}
|
||||
|
||||
execute (method, ...params) {
|
||||
console.log('Calling', method, params);
|
||||
return Promise.reject('not connected');
|
||||
}
|
||||
|
||||
on () {
|
||||
}
|
||||
}
|
||||
|
||||
class FrameSecureApi extends SecureApi {
|
||||
constructor (transport) {
|
||||
super('', null, () => {
|
||||
return transport;
|
||||
});
|
||||
}
|
||||
|
||||
connect () {
|
||||
// Do nothing - this API does not need connecting
|
||||
this.emit('connecting');
|
||||
// Fire connected event with some delay.
|
||||
setTimeout(() => {
|
||||
this.emit('connected');
|
||||
});
|
||||
}
|
||||
|
||||
needsToken () {
|
||||
return false;
|
||||
}
|
||||
|
||||
isConnecting () {
|
||||
return false;
|
||||
}
|
||||
|
||||
isConnected () {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const api = new FrameSecureApi(window.secureTransport || new FakeTransport());
|
||||
|
||||
patchApi(api);
|
||||
ContractInstances.get(api);
|
||||
|
||||
const store = initStore(api, null, true);
|
||||
|
||||
window.secureApi = api;
|
||||
|
||||
ReactDOM.render(
|
||||
<ContextProvider
|
||||
api={ api }
|
||||
muiTheme={ muiTheme }
|
||||
store={ store }
|
||||
>
|
||||
<ParityBar dapp externalLink={ 'http://127.0.0.1:8180' } />
|
||||
</ContextProvider>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
38
js/src/shell/index.ejs
Normal file
38
js/src/shell/index.ejs
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<style>
|
||||
html {
|
||||
background: white;
|
||||
background-repeat: round;
|
||||
}
|
||||
|
||||
html, body, #container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding-top: 5em;
|
||||
font-size: 2em;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div class="loading">Loading</div>
|
||||
</div>
|
||||
<script src="vendor.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
103
js/src/shell/index.js
Normal file
103
js/src/shell/index.js
Normal file
@@ -0,0 +1,103 @@
|
||||
// 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 'babel-polyfill';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import es6Promise from 'es6-promise';
|
||||
es6Promise.polyfill();
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
import { IndexRoute, Redirect, Route, Router, hashHistory } from 'react-router';
|
||||
import qs from 'querystring';
|
||||
|
||||
import SecureApi from '~/secureApi';
|
||||
import ContractInstances from '~/contracts';
|
||||
|
||||
import { initStore } from '~/redux';
|
||||
import ContextProvider from '~/ui/ContextProvider';
|
||||
import muiTheme from '~/ui/Theme';
|
||||
import { patchApi } from '~/util/tx';
|
||||
|
||||
import Application from './Application';
|
||||
import Dapp from './Dapp';
|
||||
import Dapps from './Dapps';
|
||||
|
||||
import styles from '~/reset.css';
|
||||
|
||||
import '~/environment';
|
||||
|
||||
import '~/../assets/fonts/Roboto/font.css';
|
||||
import '~/../assets/fonts/RobotoMono/font.css';
|
||||
|
||||
injectTapEventPlugin();
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Expose the React Performance Tools on the`window` object
|
||||
const Perf = require('react-addons-perf');
|
||||
|
||||
window.Perf = Perf;
|
||||
}
|
||||
|
||||
const AUTH_HASH = '#/auth?';
|
||||
const parityUrl = process.env.PARITY_URL || window.location.host;
|
||||
const urlScheme = window.location.href.match(/^https/) ? 'wss://' : 'ws://';
|
||||
|
||||
let token = null;
|
||||
|
||||
if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {
|
||||
token = qs.parse(window.location.hash.substr(AUTH_HASH.length)).token;
|
||||
}
|
||||
|
||||
const api = new SecureApi(`${urlScheme}${parityUrl}`, token);
|
||||
|
||||
patchApi(api);
|
||||
ContractInstances.get(api);
|
||||
|
||||
const store = initStore(api, hashHistory);
|
||||
|
||||
window.secureApi = api;
|
||||
|
||||
import HistoryStore from '~/mobx/historyStore';
|
||||
import builtinDapps from '~/config/dappsBuiltin.json';
|
||||
import viewsDapps from '~/config/dappsViews.json';
|
||||
|
||||
const dapps = [].concat(viewsDapps, builtinDapps);
|
||||
|
||||
const dappsHistory = HistoryStore.get('dapps');
|
||||
|
||||
function onEnterDapp ({ params }) {
|
||||
if (!dapps[params.id] || !dapps[params.id].skipHistory) {
|
||||
dappsHistory.add(params.id);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<ContextProvider api={ api } muiTheme={ muiTheme } store={ store }>
|
||||
<Router className={ styles.reset } history={ hashHistory }>
|
||||
<Route path='/' component={ Application }>
|
||||
<Redirect from='/auth' to='/' />
|
||||
<Route path='/:id' component={ Dapp } onEnter={ onEnterDapp } />
|
||||
<Route path='/:id/:details' component={ Dapp } onEnter={ onEnterDapp } />
|
||||
<IndexRoute component={ Dapps } />
|
||||
</Route>
|
||||
</Router>
|
||||
</ContextProvider>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
19
js/src/shell/package.json
Normal file
19
js/src/shell/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@parity/shell",
|
||||
"description": "Parity UI shell",
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"author": "Parity Team <admin@parity.io>",
|
||||
"maintainers": [],
|
||||
"contributors": [],
|
||||
"license": "GPL-3.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/paritytech/parity.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"scripts": {},
|
||||
"devDependencies": {},
|
||||
"dependencies": {},
|
||||
"peerDependencies": {}
|
||||
}
|
||||
Reference in New Issue
Block a user