registry dapp: cleanup, support reverse entries (#3933)
* style fixes ✨ * registry dapp: show reverse events * registry dapp: actions & reducers for isTestnet * registry dapp: make Hash & Address components * registry dapp: code style ✨ * registry dapp: bugfixes 🐛 * registry dapp: postTx helper * registry dapp: refactor reducers * registry dapp: use react-redux * registry dapp: actions & reducers for reverse lookup * registry dapp: reverse lookup component * registry dapp: connect Address to redux * registry dapp: de-DRY recordTypeSelect In preparation for the next commit. * registry dapp: support reverse lookup * registry dapp: render reverse events * registry dapp: show tx sender, add key prop * registry dapp: link accounts to etherscan as well * registry dapp: address style grumbles 💄 * registry dapp: address style grumbles 💄
This commit is contained in:
parent
1ffc6ac58c
commit
002e8b00d4
@ -15,22 +15,26 @@
|
||||
// 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 IconMenu from 'material-ui/IconMenu';
|
||||
import IconButton from 'material-ui/IconButton/IconButton';
|
||||
import AccountIcon from 'material-ui/svg-icons/action/account-circle';
|
||||
import MenuItem from 'material-ui/MenuItem';
|
||||
|
||||
import IdentityIcon from '../IdentityIcon';
|
||||
import renderAddress from '../ui/address';
|
||||
import Address from '../ui/address';
|
||||
|
||||
import { select } from './actions';
|
||||
import styles from './accounts.css';
|
||||
|
||||
export default class Accounts extends Component {
|
||||
class Accounts extends Component {
|
||||
|
||||
static propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
all: PropTypes.object.isRequired,
|
||||
selected: PropTypes.object
|
||||
selected: PropTypes.object,
|
||||
|
||||
select: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -41,8 +45,17 @@ export default class Accounts extends Component {
|
||||
const accountsButton = (
|
||||
<IconButton className={ styles.button }>
|
||||
{ selected
|
||||
? (<IdentityIcon className={ styles.icon } address={ selected.address } />)
|
||||
: (<AccountIcon className={ styles.icon } color='white' />)
|
||||
? (
|
||||
<IdentityIcon
|
||||
className={ styles.icon }
|
||||
address={ selected.address }
|
||||
/>
|
||||
) : (
|
||||
<AccountIcon
|
||||
className={ styles.icon }
|
||||
color='white'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</IconButton>);
|
||||
|
||||
@ -61,20 +74,27 @@ export default class Accounts extends Component {
|
||||
}
|
||||
|
||||
renderAccount = (account) => {
|
||||
const { all, selected } = this.props;
|
||||
const { selected } = this.props;
|
||||
const isSelected = selected && selected.address === account.address;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={ account.address } value={ account.address }
|
||||
checked={ isSelected } insetChildren={ !isSelected }
|
||||
key={ account.address }
|
||||
value={ account.address }
|
||||
checked={ isSelected }
|
||||
insetChildren={ !isSelected }
|
||||
>
|
||||
{ renderAddress(account.address, all, {}) }
|
||||
<Address address={ account.address } />
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
onAccountSelect = (e, address) => {
|
||||
this.props.actions.select(address);
|
||||
this.props.select(address);
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => state.accounts;
|
||||
const mapDispatchToProps = (dispatch) => bindActionCreators({ select }, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Accounts);
|
||||
|
@ -30,6 +30,7 @@ import Events from '../Events';
|
||||
import Lookup from '../Lookup';
|
||||
import Names from '../Names';
|
||||
import Records from '../Records';
|
||||
import Reverse from '../Reverse';
|
||||
|
||||
export default class Application extends Component {
|
||||
static childContextTypes = {
|
||||
@ -42,26 +43,14 @@ export default class Application extends Component {
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
accounts: PropTypes.object.isRequired,
|
||||
contacts: PropTypes.object.isRequired,
|
||||
contract: nullableProptype(PropTypes.object.isRequired),
|
||||
fee: nullableProptype(PropTypes.object.isRequired),
|
||||
lookup: PropTypes.object.isRequired,
|
||||
events: PropTypes.object.isRequired,
|
||||
names: PropTypes.object.isRequired,
|
||||
records: PropTypes.object.isRequired
|
||||
contract: nullableProptype(PropTypes.object).isRequired,
|
||||
fee: nullableProptype(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
render () {
|
||||
const { api } = window.parity;
|
||||
const {
|
||||
actions,
|
||||
accounts, contacts,
|
||||
contract, fee,
|
||||
lookup,
|
||||
events
|
||||
} = this.props;
|
||||
const { contract, fee } = this.props;
|
||||
let warning = null;
|
||||
|
||||
return (
|
||||
@ -69,13 +58,13 @@ export default class Application extends Component {
|
||||
{ warning }
|
||||
<div className={ styles.header }>
|
||||
<h1>RΞgistry</h1>
|
||||
<Accounts { ...accounts } actions={ actions.accounts } />
|
||||
<Accounts />
|
||||
</div>
|
||||
{ contract && fee ? (
|
||||
<div>
|
||||
<Lookup { ...lookup } accounts={ accounts.all } contacts={ contacts } actions={ actions.lookup } />
|
||||
<Lookup />
|
||||
{ this.renderActions() }
|
||||
<Events { ...events } accounts={ accounts.all } contacts={ contacts } actions={ actions.events } />
|
||||
<Events />
|
||||
<div className={ styles.warning }>
|
||||
WARNING: The name registry is experimental. Please ensure that you understand the risks, benefits & consequences of registering a name before doing so. A non-refundable fee of { api.util.fromWei(fee).toFormat(3) }<small>ETH</small> is required for all registrations.
|
||||
</div>
|
||||
@ -88,15 +77,7 @@ export default class Application extends Component {
|
||||
}
|
||||
|
||||
renderActions () {
|
||||
const {
|
||||
actions,
|
||||
accounts,
|
||||
fee,
|
||||
names,
|
||||
records
|
||||
} = this.props;
|
||||
|
||||
const hasAccount = !!accounts.selected;
|
||||
const hasAccount = !!this.props.accounts.selected;
|
||||
|
||||
if (!hasAccount) {
|
||||
return (
|
||||
@ -111,8 +92,9 @@ export default class Application extends Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Names { ...names } fee={ fee } actions={ actions.names } />
|
||||
<Records { ...records } actions={ actions.records } />
|
||||
<Names />
|
||||
<Records />
|
||||
<Reverse />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ class Container extends Component {
|
||||
|
||||
componentDidMount () {
|
||||
Promise.all([
|
||||
this.props.actions.fetchIsTestnet(),
|
||||
this.props.actions.addresses.fetch(),
|
||||
this.props.actions.fetchContract()
|
||||
]).then(() => {
|
||||
|
@ -42,8 +42,11 @@ export const subscribe = (name, from = 0, to = 'pending') =>
|
||||
}
|
||||
|
||||
events.forEach((e) => {
|
||||
api.eth.getBlockByNumber(e.blockNumber)
|
||||
.then((block) => {
|
||||
Promise.all([
|
||||
api.eth.getBlockByNumber(e.blockNumber),
|
||||
api.eth.getTransactionByHash(e.transactionHash)
|
||||
])
|
||||
.then(([block, tx]) => {
|
||||
const data = {
|
||||
type: name,
|
||||
key: '' + e.transactionHash + e.logIndex,
|
||||
@ -51,6 +54,8 @@ export const subscribe = (name, from = 0, to = 'pending') =>
|
||||
block: e.blockNumber,
|
||||
index: e.logIndex,
|
||||
transaction: e.transactionHash,
|
||||
from: tx.from,
|
||||
to: tx.to,
|
||||
parameters: e.params,
|
||||
timestamp: block.timestamp
|
||||
};
|
||||
|
@ -49,3 +49,9 @@
|
||||
.eventsList td {
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
@ -15,13 +15,17 @@
|
||||
// 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 { Card, CardHeader, CardActions, CardText } from 'material-ui/Card';
|
||||
import Toggle from 'material-ui/Toggle';
|
||||
import moment from 'moment';
|
||||
|
||||
import { bytesToHex } from '../parity';
|
||||
import renderHash from '../ui/hash';
|
||||
import renderAddress from '../ui/address';
|
||||
import Hash from '../ui/hash';
|
||||
import Address from '../ui/address';
|
||||
|
||||
import { subscribe, unsubscribe } from './actions';
|
||||
import styles from './events.css';
|
||||
|
||||
const inlineButton = {
|
||||
@ -42,21 +46,31 @@ const renderStatus = (timestamp, isPending) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderEvent = (classNames, verb) => (e, accounts, contacts) => {
|
||||
const renderEvent = (classNames, verb) => (e) => {
|
||||
const classes = e.state === 'pending'
|
||||
? classNames + ' ' + styles.pending : classNames;
|
||||
|
||||
return (
|
||||
<tr key={ e.key } className={ classes }>
|
||||
<td>{ renderAddress(e.parameters.owner.value, accounts, contacts) }</td>
|
||||
<td><abbr title={ e.transaction }>{ verb }</abbr></td>
|
||||
<td><code>{ renderHash(bytesToHex(e.parameters.name.value)) }</code></td>
|
||||
<td>{ renderStatus(e.timestamp, e.state === 'pending') }</td>
|
||||
<td>
|
||||
<Address address={ e.parameters.owner.value } />
|
||||
</td>
|
||||
<td>
|
||||
<abbr title={ e.transaction }>{ verb }</abbr>
|
||||
</td>
|
||||
<td>
|
||||
<code>
|
||||
<Hash hash={ bytesToHex(e.parameters.name.value) } />
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
{ renderStatus(e.timestamp, e.state === 'pending') }
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
const renderDataChanged = (e, accounts, contacts) => {
|
||||
const renderDataChanged = (e) => {
|
||||
let classNames = styles.dataChanged;
|
||||
if (e.state === 'pending') {
|
||||
classNames += ' ' + styles.pending;
|
||||
@ -64,12 +78,61 @@ const renderDataChanged = (e, accounts, contacts) => {
|
||||
|
||||
return (
|
||||
<tr key={ e.key } className={ classNames }>
|
||||
<td>{ renderAddress(e.parameters.owner.value, accounts, contacts) }</td>
|
||||
<td><abbr title={ e.transaction }>updated</abbr></td>
|
||||
<td>
|
||||
key <code>{ new Buffer(e.parameters.plainKey.value).toString('utf8') }</code> of <code>{ renderHash(bytesToHex(e.parameters.name.value)) }</code>
|
||||
<Address address={ e.parameters.owner.value } />
|
||||
</td>
|
||||
<td>
|
||||
<abbr title={ e.transaction }>updated</abbr>
|
||||
</td>
|
||||
<td>
|
||||
{ 'key ' }
|
||||
<code>
|
||||
{ new Buffer(e.parameters.plainKey.value).toString('utf8') }
|
||||
</code>
|
||||
{ 'of ' }
|
||||
<code>
|
||||
<Hash hash={ bytesToHex(e.parameters.name.value) } />
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
{ renderStatus(e.timestamp, e.state === 'pending') }
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
const renderReverse = (e) => {
|
||||
const verb = ({
|
||||
ReverseProposed: 'proposed',
|
||||
ReverseConfirmed: 'confirmed',
|
||||
ReverseRemoved: 'removed'
|
||||
})[e.type];
|
||||
|
||||
if (!verb) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const classes = [ styles.reverse ];
|
||||
if (e.state === 'pending') {
|
||||
classes.push(styles.pending);
|
||||
}
|
||||
|
||||
// TODO: `name` is an indexed param, cannot display as plain text
|
||||
return (
|
||||
<tr key={ e.key } className={ classes.join(' ') }>
|
||||
<td>
|
||||
<Address address={ e.from } />
|
||||
</td>
|
||||
<td>{ verb }</td>
|
||||
<td>
|
||||
{ 'name ' }
|
||||
<code key='name'>{ bytesToHex(e.parameters.name.value) }</code>
|
||||
{ ' for ' }
|
||||
<Address key='reverse' address={ e.parameters.reverse.value } />
|
||||
</td>
|
||||
<td>
|
||||
{ renderStatus(e.timestamp, e.state === 'pending') }
|
||||
</td>
|
||||
<td>{ renderStatus(e.timestamp, e.state === 'pending') }</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
@ -77,22 +140,25 @@ const renderDataChanged = (e, accounts, contacts) => {
|
||||
const eventTypes = {
|
||||
Reserved: renderEvent(styles.reserved, 'reserved'),
|
||||
Dropped: renderEvent(styles.dropped, 'dropped'),
|
||||
DataChanged: renderDataChanged
|
||||
DataChanged: renderDataChanged,
|
||||
ReverseProposed: renderReverse,
|
||||
ReverseConfirmed: renderReverse,
|
||||
ReverseRemoved: renderReverse
|
||||
};
|
||||
|
||||
export default class Events extends Component {
|
||||
class Events extends Component {
|
||||
|
||||
static propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
subscriptions: PropTypes.object.isRequired,
|
||||
pending: PropTypes.object.isRequired,
|
||||
events: PropTypes.array.isRequired,
|
||||
accounts: PropTypes.object.isRequired,
|
||||
contacts: PropTypes.object.isRequired
|
||||
pending: PropTypes.object.isRequired,
|
||||
subscriptions: PropTypes.object.isRequired,
|
||||
|
||||
subscribe: PropTypes.func.isRequired,
|
||||
unsubscribe: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { subscriptions, pending, accounts, contacts } = this.props;
|
||||
const { subscriptions, pending } = this.props;
|
||||
|
||||
const eventsObject = this.props.events
|
||||
.filter((e) => eventTypes[e.type])
|
||||
@ -122,7 +188,16 @@ export default class Events extends Component {
|
||||
|
||||
return evB.timestamp - evA.timestamp;
|
||||
})
|
||||
.map((e) => eventTypes[e.type](e, accounts, contacts));
|
||||
.map((e) => eventTypes[e.type](e));
|
||||
|
||||
const reverseToggled =
|
||||
subscriptions.ReverseProposed !== null &&
|
||||
subscriptions.ReverseConfirmed !== null &&
|
||||
subscriptions.ReverseRemoved !== null;
|
||||
const reverseDisabled =
|
||||
pending.ReverseProposed ||
|
||||
pending.ReverseConfirmed ||
|
||||
pending.ReverseRemoved;
|
||||
|
||||
return (
|
||||
<Card className={ styles.events }>
|
||||
@ -149,6 +224,13 @@ export default class Events extends Component {
|
||||
onToggle={ this.onDataChangedToggle }
|
||||
style={ inlineButton }
|
||||
/>
|
||||
<Toggle
|
||||
label='Reverse Lookup'
|
||||
toggled={ reverseToggled }
|
||||
disabled={ reverseDisabled }
|
||||
onToggle={ this.onReverseToggle }
|
||||
style={ inlineButton }
|
||||
/>
|
||||
</CardActions>
|
||||
<CardText>
|
||||
<table className={ styles.eventsList }>
|
||||
@ -162,33 +244,59 @@ export default class Events extends Component {
|
||||
}
|
||||
|
||||
onReservedToggle = (e, isToggled) => {
|
||||
const { pending, subscriptions, actions } = this.props;
|
||||
const { pending, subscriptions, subscribe, unsubscribe } = this.props;
|
||||
|
||||
if (!pending.Reserved) {
|
||||
if (isToggled && subscriptions.Reserved === null) {
|
||||
actions.subscribe('Reserved');
|
||||
subscribe('Reserved');
|
||||
} else if (!isToggled && subscriptions.Reserved !== null) {
|
||||
actions.unsubscribe('Reserved');
|
||||
unsubscribe('Reserved');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onDroppedToggle = (e, isToggled) => {
|
||||
const { pending, subscriptions, actions } = this.props;
|
||||
const { pending, subscriptions, subscribe, unsubscribe } = this.props;
|
||||
|
||||
if (!pending.Dropped) {
|
||||
if (isToggled && subscriptions.Dropped === null) {
|
||||
actions.subscribe('Dropped');
|
||||
subscribe('Dropped');
|
||||
} else if (!isToggled && subscriptions.Dropped !== null) {
|
||||
actions.unsubscribe('Dropped');
|
||||
unsubscribe('Dropped');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onDataChangedToggle = (e, isToggled) => {
|
||||
const { pending, subscriptions, actions } = this.props;
|
||||
const { pending, subscriptions, subscribe, unsubscribe } = this.props;
|
||||
|
||||
if (!pending.DataChanged) {
|
||||
if (isToggled && subscriptions.DataChanged === null) {
|
||||
actions.subscribe('DataChanged');
|
||||
subscribe('DataChanged');
|
||||
} else if (!isToggled && subscriptions.DataChanged !== null) {
|
||||
actions.unsubscribe('DataChanged');
|
||||
unsubscribe('DataChanged');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onReverseToggle = (e, isToggled) => {
|
||||
const { pending, subscriptions, subscribe, unsubscribe } = this.props;
|
||||
|
||||
for (let e of ['ReverseProposed', 'ReverseConfirmed', 'ReverseRemoved']) {
|
||||
if (pending[e]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isToggled && subscriptions[e] === null) {
|
||||
subscribe(e);
|
||||
} else if (!isToggled && subscriptions[e] !== null) {
|
||||
unsubscribe(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => state.events;
|
||||
const mapDispatchToProps = (dispatch) => bindActionCreators({ subscribe, unsubscribe }, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Events);
|
||||
|
@ -18,12 +18,18 @@ const initialState = {
|
||||
subscriptions: {
|
||||
Reserved: null,
|
||||
Dropped: null,
|
||||
DataChanged: null
|
||||
DataChanged: null,
|
||||
ReverseProposed: null,
|
||||
ReverseConfirmed: null,
|
||||
ReverseRemoved: null
|
||||
},
|
||||
pending: {
|
||||
Reserved: false,
|
||||
Dropped: false,
|
||||
DataChanged: false
|
||||
DataChanged: false,
|
||||
ReverseProposed: false,
|
||||
ReverseConfirmed: false,
|
||||
ReverseRemoved: false
|
||||
},
|
||||
events: []
|
||||
};
|
||||
|
@ -18,15 +18,15 @@ import { sha3 } from '../parity.js';
|
||||
|
||||
export const clear = () => ({ type: 'lookup clear' });
|
||||
|
||||
export const start = (name, key) => ({ type: 'lookup start', name, key });
|
||||
export const lookupStart = (name, key) => ({ type: 'lookup start', name, key });
|
||||
export const reverseLookupStart = (address) => ({ type: 'reverseLookup start', address });
|
||||
|
||||
export const success = (address) => ({ type: 'lookup success', result: address });
|
||||
export const success = (action, result) => ({ type: `${action} success`, result: result });
|
||||
|
||||
export const fail = () => ({ type: 'lookup error' });
|
||||
export const fail = (action) => ({ type: `${action} error` });
|
||||
|
||||
export const lookup = (name, key) => (dispatch, getState) => {
|
||||
const { contract } = getState();
|
||||
|
||||
if (!contract) {
|
||||
return;
|
||||
}
|
||||
@ -35,16 +35,37 @@ export const lookup = (name, key) => (dispatch, getState) => {
|
||||
.find((f) => f.name === 'getAddress');
|
||||
|
||||
name = name.toLowerCase();
|
||||
dispatch(start(name, key));
|
||||
getAddress.call({}, [sha3(name), key])
|
||||
.then((address) => dispatch(success(address)))
|
||||
dispatch(lookupStart(name, key));
|
||||
|
||||
getAddress.call({}, [ sha3(name), key ])
|
||||
.then((address) => dispatch(success('lookup', address)))
|
||||
.catch((err) => {
|
||||
console.error(`could not lookup ${key} for ${name}`);
|
||||
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
|
||||
dispatch(fail());
|
||||
dispatch(fail('lookup'));
|
||||
});
|
||||
};
|
||||
|
||||
export const reverseLookup = (address) => (dispatch, getState) => {
|
||||
const { contract } = getState();
|
||||
if (!contract) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reverse = contract.functions
|
||||
.find((f) => f.name === 'reverse');
|
||||
|
||||
dispatch(reverseLookupStart(address));
|
||||
|
||||
reverse.call({}, [ address ])
|
||||
.then((address) => dispatch(success('reverseLookup', address)))
|
||||
.catch((err) => {
|
||||
console.error(`could not lookup reverse for ${address}`);
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
dispatch(fail('reverseLookup'));
|
||||
});
|
||||
};
|
||||
|
@ -20,9 +20,7 @@
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
margin-left: 1em;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
@ -15,50 +15,65 @@
|
||||
// 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 { Card, CardHeader, CardText } from 'material-ui/Card';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import DropDownMenu from 'material-ui/DropDownMenu';
|
||||
import MenuItem from 'material-ui/MenuItem';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
import SearchIcon from 'material-ui/svg-icons/action/search';
|
||||
|
||||
import { nullableProptype } from '~/util/proptypes';
|
||||
|
||||
import renderAddress from '../ui/address.js';
|
||||
import Address from '../ui/address.js';
|
||||
import renderImage from '../ui/image.js';
|
||||
|
||||
import recordTypeSelect from '../ui/record-type-select.js';
|
||||
import { clear, lookup, reverseLookup } from './actions';
|
||||
import styles from './lookup.css';
|
||||
|
||||
export default class Lookup extends Component {
|
||||
class Lookup extends Component {
|
||||
|
||||
static propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
result: nullableProptype(PropTypes.string.isRequired),
|
||||
accounts: PropTypes.object.isRequired,
|
||||
contacts: PropTypes.object.isRequired
|
||||
|
||||
clear: PropTypes.func.isRequired,
|
||||
lookup: PropTypes.func.isRequired,
|
||||
reverseLookup: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
state = { name: '', type: 'A' };
|
||||
state = {
|
||||
input: '', type: 'A'
|
||||
};
|
||||
|
||||
render () {
|
||||
const name = this.state.name || this.props.name;
|
||||
const type = this.state.type || this.props.type;
|
||||
const { result, accounts, contacts } = this.props;
|
||||
const { input, type } = this.state;
|
||||
const { result } = this.props;
|
||||
|
||||
let output = '';
|
||||
if (result) {
|
||||
if (type === 'A') {
|
||||
output = (<code>{ renderAddress(result, accounts, contacts, false) }</code>);
|
||||
output = (
|
||||
<code>
|
||||
<Address
|
||||
address={ result }
|
||||
shortenHash={ false }
|
||||
/>
|
||||
</code>
|
||||
);
|
||||
} else if (type === 'IMG') {
|
||||
output = renderImage(result);
|
||||
} else if (type === 'CONTENT') {
|
||||
output = (<div>
|
||||
<code>{ result }</code>
|
||||
<p>This is most likely just the hash of the content you are looking for</p>
|
||||
</div>);
|
||||
output = (
|
||||
<div>
|
||||
<code>{ result }</code>
|
||||
<p>Keep in mind that this is most likely the hash of the content you are looking for.</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
output = (<code>{ result }</code>);
|
||||
output = (
|
||||
<code>{ result }</code>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,14 +82,20 @@ export default class Lookup extends Component {
|
||||
<CardHeader title={ 'Query the Registry' } />
|
||||
<div className={ styles.box }>
|
||||
<TextField
|
||||
className={ styles.spacing }
|
||||
hintText='name'
|
||||
value={ name }
|
||||
onChange={ this.onNameChange }
|
||||
hintText={ type === 'reverse' ? 'address' : 'name' }
|
||||
value={ input }
|
||||
onChange={ this.onInputChange }
|
||||
/>
|
||||
{ recordTypeSelect(type, this.onTypeChange, styles.spacing) }
|
||||
<DropDownMenu
|
||||
value={ type }
|
||||
onChange={ this.onTypeChange }
|
||||
>
|
||||
<MenuItem value='A' primaryText='A – Ethereum address' />
|
||||
<MenuItem value='IMG' primaryText='IMG – hash of a picture in the blockchain' />
|
||||
<MenuItem value='CONTENT' primaryText='CONTENT – hash of a data in the blockchain' />
|
||||
<MenuItem value='reverse' primaryText='reverse – find a name for an address' />
|
||||
</DropDownMenu>
|
||||
<RaisedButton
|
||||
className={ styles.spacing }
|
||||
label='Lookup'
|
||||
primary
|
||||
icon={ <SearchIcon /> }
|
||||
@ -86,14 +107,30 @@ export default class Lookup extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
onNameChange = (e) => {
|
||||
this.setState({ name: e.target.value });
|
||||
onInputChange = (e) => {
|
||||
this.setState({ input: e.target.value });
|
||||
};
|
||||
|
||||
onTypeChange = (e, i, type) => {
|
||||
this.setState({ type });
|
||||
this.props.actions.clear();
|
||||
this.props.clear();
|
||||
};
|
||||
|
||||
onLookupClick = () => {
|
||||
this.props.actions.lookup(this.state.name, this.state.type);
|
||||
const { input, type } = this.state;
|
||||
|
||||
if (type === 'reverse') {
|
||||
this.props.reverseLookup(input);
|
||||
} else {
|
||||
this.props.lookup(input, type);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => state.lookup;
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
clear, lookup, reverseLookup
|
||||
}, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Lookup);
|
||||
|
@ -14,39 +14,34 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { isStage } from '../util/actions';
|
||||
|
||||
const initialState = {
|
||||
pending: false,
|
||||
name: '', type: '',
|
||||
result: null
|
||||
};
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
if (action.type === 'lookup clear') {
|
||||
return { ...state, result: null };
|
||||
const { type } = action;
|
||||
|
||||
if (type.slice(0, 7) !== 'lookup ' && type.slice(0, 14) !== 'reverseLookup ') {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (action.type === 'lookup start') {
|
||||
return {
|
||||
pending: true,
|
||||
name: action.name, type: action.entry,
|
||||
result: null
|
||||
};
|
||||
if (isStage('clear', action)) {
|
||||
return { pending: state.pending, result: null };
|
||||
}
|
||||
|
||||
if (action.type === 'lookup error') {
|
||||
return {
|
||||
pending: false,
|
||||
name: initialState.name, type: initialState.type,
|
||||
result: null
|
||||
};
|
||||
if (isStage('start', action)) {
|
||||
return { pending: true, result: null };
|
||||
}
|
||||
|
||||
if (action.type === 'lookup success') {
|
||||
return {
|
||||
pending: false,
|
||||
name: initialState.name, type: initialState.type,
|
||||
result: action.result
|
||||
};
|
||||
if (isStage('error', action)) {
|
||||
return { pending: false, result: null };
|
||||
}
|
||||
|
||||
if (isStage('success', action)) {
|
||||
return { pending: false, result: action.result };
|
||||
}
|
||||
|
||||
return state;
|
||||
|
@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { sha3, api } from '../parity.js';
|
||||
import postTx from '../util/post-tx';
|
||||
|
||||
const alreadyQueued = (queue, action, name) =>
|
||||
!!queue.find((entry) => entry.action === action && entry.name === name);
|
||||
@ -30,42 +31,32 @@ export const reserve = (name) => (dispatch, getState) => {
|
||||
const account = state.accounts.selected;
|
||||
const contract = state.contract;
|
||||
const fee = state.fee;
|
||||
|
||||
if (!contract || !account) {
|
||||
return;
|
||||
}
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
if (alreadyQueued(state.names.queue, 'reserve', name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reserve = contract.functions.find((f) => f.name === 'reserve');
|
||||
|
||||
name = name.toLowerCase();
|
||||
dispatch(reserveStart(name));
|
||||
|
||||
const options = {
|
||||
from: account.address,
|
||||
value: fee
|
||||
};
|
||||
const values = [ sha3(name) ];
|
||||
const values = [
|
||||
sha3(name)
|
||||
];
|
||||
|
||||
dispatch(reserveStart(name));
|
||||
|
||||
reserve.estimateGas(options, values)
|
||||
.then((gas) => {
|
||||
options.gas = gas.mul(1.2).toFixed(0);
|
||||
return reserve.postTransaction(options, values);
|
||||
})
|
||||
.then((requestId) => {
|
||||
return api.pollMethod('parity_checkRequest', requestId);
|
||||
})
|
||||
.then((txhash) => {
|
||||
postTx(api, reserve, options, values)
|
||||
.then((txHash) => {
|
||||
dispatch(reserveSuccess(name));
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.type === 'REQUEST_REJECTED') {
|
||||
return dispatch(reserveFail(name));
|
||||
}
|
||||
|
||||
console.error(`could not reserve ${name}`);
|
||||
|
||||
if (err) {
|
||||
@ -86,38 +77,31 @@ export const drop = (name) => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const account = state.accounts.selected;
|
||||
const contract = state.contract;
|
||||
|
||||
if (!contract || !account) {
|
||||
return;
|
||||
}
|
||||
|
||||
name = name.toLowerCase();
|
||||
if (alreadyQueued(state.names.queue, 'drop', name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const drop = contract.functions.find((f) => f.name === 'drop');
|
||||
|
||||
name = name.toLowerCase();
|
||||
const options = { from: account.address };
|
||||
const values = [ sha3(name) ];
|
||||
|
||||
dispatch(dropStart(name));
|
||||
drop.estimateGas(options, values)
|
||||
.then((gas) => {
|
||||
options.gas = gas.mul(1.2).toFixed(0);
|
||||
return drop.postTransaction(options, values);
|
||||
})
|
||||
.then((requestId) => {
|
||||
return api.pollMethod('parity_checkRequest', requestId);
|
||||
})
|
||||
|
||||
const options = {
|
||||
from: account.address
|
||||
};
|
||||
const values = [
|
||||
sha3(name)
|
||||
];
|
||||
|
||||
postTx(api, drop, options, values)
|
||||
.then((txhash) => {
|
||||
dispatch(dropSuccess(name));
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.type === 'REQUEST_REJECTED') {
|
||||
dispatch(reserveFail(name));
|
||||
}
|
||||
|
||||
console.error(`could not drop ${name}`);
|
||||
|
||||
if (err) {
|
||||
|
@ -20,7 +20,8 @@
|
||||
}
|
||||
|
||||
.box {
|
||||
margin: 0 1em 1em 1em;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
|
@ -15,6 +15,8 @@
|
||||
// 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 { Card, CardHeader, CardText } from 'material-ui/Card';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import DropDownMenu from 'material-ui/DropDownMenu';
|
||||
@ -24,6 +26,7 @@ import CheckIcon from 'material-ui/svg-icons/navigation/check';
|
||||
|
||||
import { fromWei } from '../parity.js';
|
||||
|
||||
import { reserve, drop } from './actions';
|
||||
import styles from './names.css';
|
||||
|
||||
const useSignerText = (<p>Use the <a href='/#/signer' className={ styles.link } target='_blank'>Signer</a> to authenticate the following changes.</p>);
|
||||
@ -72,13 +75,15 @@ const renderQueue = (queue) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default class Names extends Component {
|
||||
class Names extends Component {
|
||||
|
||||
static propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
fee: PropTypes.object.isRequired,
|
||||
pending: PropTypes.bool.isRequired,
|
||||
queue: PropTypes.array.isRequired
|
||||
queue: PropTypes.array.isRequired,
|
||||
|
||||
reserve: PropTypes.func.isRequired,
|
||||
drop: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
state = {
|
||||
@ -117,27 +122,29 @@ export default class Names extends Component {
|
||||
: (<p className={ styles.noSpacing }>To drop a name, you have to be the owner.</p>)
|
||||
)
|
||||
}
|
||||
<TextField
|
||||
hintText='name'
|
||||
value={ name }
|
||||
onChange={ this.onNameChange }
|
||||
/>
|
||||
<DropDownMenu
|
||||
disabled={ pending }
|
||||
value={ action }
|
||||
onChange={ this.onActionChange }
|
||||
>
|
||||
<MenuItem value='reserve' primaryText='reserve this name' />
|
||||
<MenuItem value='drop' primaryText='drop this name' />
|
||||
</DropDownMenu>
|
||||
<RaisedButton
|
||||
disabled={ pending }
|
||||
className={ styles.spacing }
|
||||
label={ action === 'reserve' ? 'Reserve' : 'Drop' }
|
||||
primary
|
||||
icon={ <CheckIcon /> }
|
||||
onTouchTap={ this.onSubmitClick }
|
||||
/>
|
||||
<div className={ styles.box }>
|
||||
<TextField
|
||||
hintText='name'
|
||||
value={ name }
|
||||
onChange={ this.onNameChange }
|
||||
/>
|
||||
<DropDownMenu
|
||||
disabled={ pending }
|
||||
value={ action }
|
||||
onChange={ this.onActionChange }
|
||||
>
|
||||
<MenuItem value='reserve' primaryText='reserve this name' />
|
||||
<MenuItem value='drop' primaryText='drop this name' />
|
||||
</DropDownMenu>
|
||||
<RaisedButton
|
||||
disabled={ pending }
|
||||
className={ styles.spacing }
|
||||
label={ action === 'reserve' ? 'Reserve' : 'Drop' }
|
||||
primary
|
||||
icon={ <CheckIcon /> }
|
||||
onTouchTap={ this.onSubmitClick }
|
||||
/>
|
||||
</div>
|
||||
{ queue.length > 0
|
||||
? (<div>{ useSignerText }{ renderQueue(queue) }</div>)
|
||||
: null
|
||||
@ -156,9 +163,14 @@ export default class Names extends Component {
|
||||
onSubmitClick = () => {
|
||||
const { action, name } = this.state;
|
||||
if (action === 'reserve') {
|
||||
this.props.actions.reserve(name);
|
||||
this.props.reserve(name);
|
||||
} else if (action === 'drop') {
|
||||
this.props.actions.drop(name);
|
||||
this.props.drop(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({ ...state.names, fee: state.fee });
|
||||
const mapDispatchToProps = (dispatch) => bindActionCreators({ reserve, drop }, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Names);
|
||||
|
@ -14,36 +14,38 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { isAction, isStage, addToQueue, removeFromQueue } from '../util/actions';
|
||||
|
||||
const initialState = {
|
||||
pending: false,
|
||||
queue: []
|
||||
};
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
if (action.type === 'names reserve start') {
|
||||
return { ...state, pending: true };
|
||||
}
|
||||
if (action.type === 'names reserve success') {
|
||||
return {
|
||||
...state, pending: false,
|
||||
queue: state.queue.concat({ action: 'reserve', name: action.name })
|
||||
};
|
||||
}
|
||||
if (action.type === 'names reserve fail') {
|
||||
return { ...state, pending: false };
|
||||
}
|
||||
|
||||
if (action.type === 'names drop start') {
|
||||
return { ...state, pending: true };
|
||||
}
|
||||
if (action.type === 'names drop success') {
|
||||
return {
|
||||
...state, pending: false,
|
||||
queue: state.queue.concat({ action: 'drop', name: action.name })
|
||||
};
|
||||
}
|
||||
if (action.type === 'names drop fail') {
|
||||
return { ...state, pending: false };
|
||||
if (isAction('names', 'reserve', action)) {
|
||||
if (isStage('start', action)) {
|
||||
return {
|
||||
...state, pending: true,
|
||||
queue: addToQueue(state.queue, 'reserve', action.name)
|
||||
};
|
||||
} else if (isStage('success', action) || isStage('fail', action)) {
|
||||
return {
|
||||
...state, pending: false,
|
||||
queue: removeFromQueue(state.queue, 'reserve', action.name)
|
||||
};
|
||||
}
|
||||
} else if (isAction('names', 'drop', action)) {
|
||||
if (isStage('start', action)) {
|
||||
return {
|
||||
...state, pending: true,
|
||||
queue: addToQueue(state.queue, 'drop', action.name)
|
||||
};
|
||||
} else if (isStage('success', action) || isStage('fail', action)) {
|
||||
return {
|
||||
...state, pending: false,
|
||||
queue: removeFromQueue(state.queue, 'drop', action.name)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
|
@ -1,4 +1,21 @@
|
||||
import { sha3 } from '../parity.js';
|
||||
// Copyright 2015, 2016 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 { sha3, api } from '../parity.js';
|
||||
import postTx from '../util/post-tx';
|
||||
|
||||
export const start = (name, key, value) => ({ type: 'records update start', name, key, value });
|
||||
|
||||
@ -10,25 +27,28 @@ export const update = (name, key, value) => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const account = state.accounts.selected;
|
||||
const contract = state.contract;
|
||||
|
||||
if (!contract || !account) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fnName = key === 'A' ? 'setAddress' : 'set';
|
||||
const fn = contract.functions.find((f) => f.name === fnName);
|
||||
|
||||
name = name.toLowerCase();
|
||||
const options = { from: account.address };
|
||||
const values = [ sha3(name), key, value ];
|
||||
|
||||
const fnName = key === 'A' ? 'setAddress' : 'set';
|
||||
const setAddress = contract.functions.find((f) => f.name === fnName);
|
||||
|
||||
dispatch(start(name, key, value));
|
||||
fn.estimateGas(options, values)
|
||||
.then((gas) => {
|
||||
options.gas = gas.mul(1.2).toFixed(0);
|
||||
return fn.postTransaction(options, values);
|
||||
})
|
||||
.then((data) => {
|
||||
|
||||
const options = {
|
||||
from: account.address
|
||||
};
|
||||
const values = [
|
||||
sha3(name),
|
||||
key,
|
||||
value
|
||||
];
|
||||
|
||||
postTx(api, setAddress, options, values)
|
||||
.then((txHash) => {
|
||||
dispatch(success());
|
||||
}).catch((err) => {
|
||||
console.error(`could not update ${key} record of ${name}`);
|
||||
|
@ -23,6 +23,16 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
@ -15,22 +15,27 @@
|
||||
// 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 { Card, CardHeader, CardText } from 'material-ui/Card';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import DropDownMenu from 'material-ui/DropDownMenu';
|
||||
import MenuItem from 'material-ui/MenuItem';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
import SaveIcon from 'material-ui/svg-icons/content/save';
|
||||
|
||||
import recordTypeSelect from '../ui/record-type-select.js';
|
||||
import { update } from './actions';
|
||||
import styles from './records.css';
|
||||
|
||||
export default class Records extends Component {
|
||||
class Records extends Component {
|
||||
|
||||
static propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
pending: PropTypes.bool.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired
|
||||
value: PropTypes.string.isRequired,
|
||||
|
||||
update: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
state = { name: '', type: 'A', value: '' };
|
||||
@ -48,28 +53,36 @@ export default class Records extends Component {
|
||||
<p className={ styles.noSpacing }>
|
||||
You can only modify entries of names that you previously registered.
|
||||
</p>
|
||||
|
||||
<TextField
|
||||
className={ styles.spacing }
|
||||
hintText='name'
|
||||
value={ name }
|
||||
onChange={ this.onNameChange }
|
||||
/>
|
||||
{ recordTypeSelect(type, this.onTypeChange, styles.spacing) }
|
||||
<TextField
|
||||
className={ styles.spacing }
|
||||
hintText='value'
|
||||
value={ value }
|
||||
onChange={ this.onValueChange }
|
||||
/>
|
||||
<RaisedButton
|
||||
disabled={ pending }
|
||||
className={ styles.spacing }
|
||||
label='Save'
|
||||
primary
|
||||
icon={ <SaveIcon /> }
|
||||
onTouchTap={ this.onSaveClick }
|
||||
/>
|
||||
<div className={ styles.box }>
|
||||
<TextField
|
||||
hintText='name'
|
||||
value={ name }
|
||||
onChange={ this.onNameChange }
|
||||
/>
|
||||
<DropDownMenu
|
||||
value={ type }
|
||||
onChange={ this.onTypeChange }
|
||||
>
|
||||
<MenuItem value='A' primaryText='A – Ethereum address' />
|
||||
<MenuItem value='IMG' primaryText='IMG – hash of a picture in the blockchain' />
|
||||
<MenuItem value='CONTENT' primaryText='CONTENT – hash of a data in the blockchain' />
|
||||
</DropDownMenu>
|
||||
<TextField
|
||||
hintText='value'
|
||||
value={ value }
|
||||
onChange={ this.onValueChange }
|
||||
/>
|
||||
<div className={ styles.button }>
|
||||
<RaisedButton
|
||||
disabled={ pending }
|
||||
className={ styles.spacing }
|
||||
label='Save'
|
||||
primary
|
||||
icon={ <SaveIcon /> }
|
||||
onTouchTap={ this.onSaveClick }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardText>
|
||||
</Card>
|
||||
);
|
||||
@ -86,6 +99,11 @@ export default class Records extends Component {
|
||||
};
|
||||
onSaveClick = () => {
|
||||
const { name, type, value } = this.state;
|
||||
this.props.actions.update(name, type, value);
|
||||
this.props.update(name, type, value);
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => state.records;
|
||||
const mapDispatchToProps = (dispatch) => bindActionCreators({ update }, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Records);
|
||||
|
@ -1,21 +1,39 @@
|
||||
// Copyright 2015, 2016 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 { isAction, isStage } from '../util/actions';
|
||||
|
||||
const initialState = {
|
||||
pending: false,
|
||||
name: '', type: '', value: ''
|
||||
};
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
if (action.type === 'records update start') {
|
||||
return {
|
||||
...state,
|
||||
pending: true,
|
||||
name: action.name, type: action.entry, value: action.value
|
||||
};
|
||||
if (!isAction('records', 'update', action)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (action.type === 'records update error' || action.type === 'records update success') {
|
||||
if (isStage('start', action)) {
|
||||
return {
|
||||
...state,
|
||||
pending: false,
|
||||
...state, pending: true,
|
||||
name: action.name, type: action.entry, value: action.value
|
||||
};
|
||||
} else if (isStage('success', action) || isStage('fail', action)) {
|
||||
return {
|
||||
...state, pending: false,
|
||||
name: initialState.name, type: initialState.type, value: initialState.value
|
||||
};
|
||||
}
|
||||
|
92
js/src/dapps/registry/Reverse/actions.js
Normal file
92
js/src/dapps/registry/Reverse/actions.js
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { api } from '../parity.js';
|
||||
import postTx from '../util/post-tx';
|
||||
|
||||
export const start = (action, name, address) => ({ type: `reverse ${action} start`, name, address });
|
||||
|
||||
export const success = (action) => ({ type: `reverse ${action} success` });
|
||||
|
||||
export const fail = (action) => ({ type: `reverse ${action} error` });
|
||||
|
||||
export const propose = (name, address) => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const account = state.accounts.selected;
|
||||
const contract = state.contract;
|
||||
if (!contract || !account) {
|
||||
return;
|
||||
}
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
const proposeReverse = contract.functions.find((f) => f.name === 'proposeReverse');
|
||||
|
||||
dispatch(start('propose', name, address));
|
||||
|
||||
const options = {
|
||||
from: account.address
|
||||
};
|
||||
const values = [
|
||||
name,
|
||||
address
|
||||
];
|
||||
|
||||
postTx(api, proposeReverse, options, values)
|
||||
.then((txHash) => {
|
||||
dispatch(success('propose'));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`could not propose reverse ${name} for address ${address}`);
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
dispatch(fail('propose'));
|
||||
});
|
||||
};
|
||||
|
||||
export const confirm = (name) => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const account = state.accounts.selected;
|
||||
const contract = state.contract;
|
||||
if (!contract || !account) {
|
||||
return;
|
||||
}
|
||||
name = name.toLowerCase();
|
||||
|
||||
const confirmReverse = contract.functions.find((f) => f.name === 'confirmReverse');
|
||||
|
||||
dispatch(start('confirm', name));
|
||||
|
||||
const options = {
|
||||
from: account.address
|
||||
};
|
||||
const values = [
|
||||
name
|
||||
];
|
||||
|
||||
postTx(api, confirmReverse, options, values)
|
||||
.then((txHash) => {
|
||||
dispatch(success('confirm'));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`could not confirm reverse ${name}`);
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
dispatch(fail('confirm'));
|
||||
});
|
||||
};
|
1
js/src/dapps/registry/Reverse/index.js
Normal file
1
js/src/dapps/registry/Reverse/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './reverse';
|
68
js/src/dapps/registry/Reverse/reducers.js
Normal file
68
js/src/dapps/registry/Reverse/reducers.js
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright 2015, 2016 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 { isAction, isStage } from '../util/actions';
|
||||
|
||||
const initialState = {
|
||||
pending: false,
|
||||
queue: []
|
||||
};
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
if (isAction('reverse', 'propose', action)) {
|
||||
if (isStage('start', action)) {
|
||||
return {
|
||||
...state, pending: true,
|
||||
queue: state.queue.concat({
|
||||
action: 'propose',
|
||||
name: action.name,
|
||||
address: action.address
|
||||
})
|
||||
};
|
||||
} else if (isStage('success', action) || isStage('fail', action)) {
|
||||
return {
|
||||
...state, pending: false,
|
||||
queue: state.queue.filter((e) =>
|
||||
e.action === 'propose' &&
|
||||
e.name === action.name &&
|
||||
e.address === action.address
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (isAction('reverse', 'confirm', action)) {
|
||||
if (isStage('start', action)) {
|
||||
return {
|
||||
...state, pending: true,
|
||||
queue: state.queue.concat({
|
||||
action: 'confirm',
|
||||
name: action.name
|
||||
})
|
||||
};
|
||||
} else if (isStage('success', action) || isStage('fail', action)) {
|
||||
return {
|
||||
...state, pending: false,
|
||||
queue: state.queue.filter((e) =>
|
||||
e.action === 'confirm' &&
|
||||
e.name === action.name
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
39
js/src/dapps/registry/Reverse/reverse.css
Normal file
39
js/src/dapps/registry/Reverse/reverse.css
Normal file
@ -0,0 +1,39 @@
|
||||
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.reverse {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.noSpacing {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
136
js/src/dapps/registry/Reverse/reverse.js
Normal file
136
js/src/dapps/registry/Reverse/reverse.js
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright 2015, 2016 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 {
|
||||
Card, CardHeader, CardText, TextField, DropDownMenu, MenuItem, RaisedButton, AddIcon, CheckIcon
|
||||
} from 'material-ui';
|
||||
|
||||
import { propose, confirm } from './actions';
|
||||
import styles from './reverse.css';
|
||||
|
||||
class Reverse extends Component {
|
||||
static propTypes = {
|
||||
pending: PropTypes.bool.isRequired,
|
||||
queue: PropTypes.array.isRequired,
|
||||
|
||||
propose: PropTypes.func.isRequired,
|
||||
confirm: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
state = {
|
||||
action: 'propose',
|
||||
name: '',
|
||||
address: ''
|
||||
};
|
||||
|
||||
render () {
|
||||
const { pending } = this.props;
|
||||
const { action, address, name } = this.state;
|
||||
|
||||
const explanation = action === 'propose'
|
||||
? (
|
||||
<p className={ styles.noSpacing }>
|
||||
To propose a reverse entry for <code>foo</code>, you have to be the owner of it.
|
||||
</p>
|
||||
) : (
|
||||
<p className={ styles.noSpacing }>
|
||||
To confirm a proposal, send the transaction from the account that the name has been proposed for.
|
||||
</p>
|
||||
);
|
||||
|
||||
let addressInput = null;
|
||||
if (action === 'propose') {
|
||||
addressInput = (
|
||||
<TextField
|
||||
className={ styles.spacing }
|
||||
hintText='address'
|
||||
value={ address }
|
||||
onChange={ this.onAddressChange }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={ styles.reverse }>
|
||||
<CardHeader title={ 'Manage Reverse Names' } />
|
||||
<CardText>
|
||||
<p className={ styles.noSpacing }>
|
||||
<strong>
|
||||
To make others to find the name of an address using the registry, you can propose & confirm reverse entries.
|
||||
</strong>
|
||||
</p>
|
||||
{ explanation }
|
||||
<div className={ styles.box }>
|
||||
<DropDownMenu
|
||||
disabled={ pending }
|
||||
value={ action }
|
||||
onChange={ this.onActionChange }
|
||||
>
|
||||
<MenuItem value='propose' primaryText='propose a reverse entry' />
|
||||
<MenuItem value='confirm' primaryText='confirm a reverse entry' />
|
||||
</DropDownMenu>
|
||||
{ addressInput }
|
||||
<TextField
|
||||
className={ styles.spacing }
|
||||
hintText='name'
|
||||
value={ name }
|
||||
onChange={ this.onNameChange }
|
||||
/>
|
||||
<div className={ styles.button }>
|
||||
<RaisedButton
|
||||
disabled={ pending }
|
||||
label={ action === 'propose' ? 'Propose' : 'Confirm' }
|
||||
primary
|
||||
icon={ action === 'propose' ? <AddIcon /> : <CheckIcon /> }
|
||||
onTouchTap={ this.onSubmitClick }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardText>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
onNameChange = (e) => {
|
||||
this.setState({ name: e.target.value });
|
||||
};
|
||||
|
||||
onAddressChange = (e) => {
|
||||
this.setState({ address: e.target.value });
|
||||
};
|
||||
|
||||
onActionChange = (e, i, action) => {
|
||||
this.setState({ action });
|
||||
};
|
||||
|
||||
onSubmitClick = () => {
|
||||
const { action, name, address } = this.state;
|
||||
|
||||
if (action === 'propose') {
|
||||
this.props.propose(name, address);
|
||||
} else if (action === 'confirm') {
|
||||
this.props.confirm(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => state.reverse;
|
||||
const mapDispatchToProps = (dispatch) => bindActionCreators({ propose, confirm }, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Reverse);
|
@ -23,63 +23,76 @@ import * as lookup from './Lookup/actions.js';
|
||||
import * as events from './Events/actions.js';
|
||||
import * as names from './Names/actions.js';
|
||||
import * as records from './Records/actions.js';
|
||||
import * as reverse from './Reverse/actions.js';
|
||||
|
||||
export { addresses, accounts, lookup, events, names, records };
|
||||
export { addresses, accounts, lookup, events, names, records, reverse };
|
||||
|
||||
export const setIsTestnet = (isTestnet) => ({ type: 'set isTestnet', isTestnet });
|
||||
|
||||
export const fetchIsTestnet = () => (dispatch) =>
|
||||
api.net.version()
|
||||
.then((netVersion) => {
|
||||
dispatch(setIsTestnet(
|
||||
netVersion === '2' || // morden
|
||||
netVersion === '3' // ropsten
|
||||
));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('could not check if testnet');
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
});
|
||||
|
||||
export const setContract = (contract) => ({ type: 'set contract', contract });
|
||||
|
||||
export const fetchContract = () => (dispatch) =>
|
||||
api.parity.registryAddress()
|
||||
.then((address) => {
|
||||
const contract = api.newContract(registryAbi, address);
|
||||
dispatch(setContract(contract));
|
||||
dispatch(fetchFee());
|
||||
dispatch(fetchOwner());
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('could not fetch contract');
|
||||
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
});
|
||||
.then((address) => {
|
||||
const contract = api.newContract(registryAbi, address);
|
||||
dispatch(setContract(contract));
|
||||
dispatch(fetchFee());
|
||||
dispatch(fetchOwner());
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('could not fetch contract');
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
});
|
||||
|
||||
export const setFee = (fee) => ({ type: 'set fee', fee });
|
||||
|
||||
const fetchFee = () => (dispatch, getState) => {
|
||||
const { contract } = getState();
|
||||
|
||||
if (!contract) {
|
||||
return;
|
||||
}
|
||||
|
||||
contract.instance.fee.call()
|
||||
.then((fee) => dispatch(setFee(fee)))
|
||||
.catch((err) => {
|
||||
console.error('could not fetch fee');
|
||||
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
});
|
||||
.then((fee) => dispatch(setFee(fee)))
|
||||
.catch((err) => {
|
||||
console.error('could not fetch fee');
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const setOwner = (owner) => ({ type: 'set owner', owner });
|
||||
|
||||
export const fetchOwner = () => (dispatch, getState) => {
|
||||
const { contract } = getState();
|
||||
|
||||
if (!contract) {
|
||||
return;
|
||||
}
|
||||
|
||||
contract.instance.owner.call()
|
||||
.then((owner) => dispatch(setOwner(owner)))
|
||||
.catch((err) => {
|
||||
console.error('could not fetch owner');
|
||||
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
});
|
||||
.then((owner) => dispatch(setOwner(owner)))
|
||||
.catch((err) => {
|
||||
console.error('could not fetch owner');
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -20,6 +20,10 @@ import lookupReducer from './Lookup/reducers.js';
|
||||
import eventsReducer from './Events/reducers.js';
|
||||
import namesReducer from './Names/reducers.js';
|
||||
import recordsReducer from './Records/reducers.js';
|
||||
import reverseReducer from './Reverse/reducers.js';
|
||||
|
||||
const isTestnetReducer = (state = null, action) =>
|
||||
action.type === 'set isTestnet' ? action.isTestnet : state;
|
||||
|
||||
const contractReducer = (state = null, action) =>
|
||||
action.type === 'set contract' ? action.contract : state;
|
||||
@ -31,6 +35,7 @@ const ownerReducer = (state = null, action) =>
|
||||
action.type === 'set owner' ? action.owner : state;
|
||||
|
||||
const initialState = {
|
||||
isTestnet: isTestnetReducer(undefined, { type: '' }),
|
||||
accounts: accountsReducer(undefined, { type: '' }),
|
||||
contacts: contactsReducer(undefined, { type: '' }),
|
||||
contract: contractReducer(undefined, { type: '' }),
|
||||
@ -39,10 +44,12 @@ const initialState = {
|
||||
lookup: lookupReducer(undefined, { type: '' }),
|
||||
events: eventsReducer(undefined, { type: '' }),
|
||||
names: namesReducer(undefined, { type: '' }),
|
||||
records: recordsReducer(undefined, { type: '' })
|
||||
records: recordsReducer(undefined, { type: '' }),
|
||||
reverse: reverseReducer(undefined, { type: '' })
|
||||
};
|
||||
|
||||
export default (state = initialState, action) => ({
|
||||
isTestnet: isTestnetReducer(state.isTestnet, action),
|
||||
accounts: accountsReducer(state.accounts, action),
|
||||
contacts: contactsReducer(state.contacts, action),
|
||||
contract: contractReducer(state.contract, action),
|
||||
@ -51,5 +58,6 @@ export default (state = initialState, action) => ({
|
||||
lookup: lookupReducer(state.lookup, action),
|
||||
events: eventsReducer(state.events, action),
|
||||
names: namesReducer(state.names, action),
|
||||
records: recordsReducer(state.records, action)
|
||||
records: recordsReducer(state.records, action),
|
||||
reverse: reverseReducer(state.reverse, action)
|
||||
});
|
||||
|
41
js/src/dapps/registry/ui/address.css
Normal file
41
js/src/dapps/registry/ui/address.css
Normal file
@ -0,0 +1,41 @@
|
||||
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.align {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
& abbr {
|
||||
text-decoration: inherit;
|
||||
}
|
||||
}
|
@ -14,34 +14,85 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import renderHash from './hash';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Hash from './hash';
|
||||
import etherscanUrl from '../util/etherscan-url';
|
||||
import IdentityIcon from '../IdentityIcon';
|
||||
|
||||
const container = {
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'middle',
|
||||
height: '24px'
|
||||
};
|
||||
const align = {
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
lineHeight: '24px'
|
||||
};
|
||||
import styles from './address.css';
|
||||
|
||||
export default (address, accounts, contacts, shortenHash = true) => {
|
||||
let caption;
|
||||
if (accounts[address]) {
|
||||
caption = (<abbr title={ address } style={ align }>{ accounts[address].name || address }</abbr>);
|
||||
} else if (contacts[address]) {
|
||||
caption = (<abbr title={ address } style={ align }>{ contacts[address].name || address }</abbr>);
|
||||
} else {
|
||||
caption = (<code style={ align }>{ shortenHash ? renderHash(address) : address }</code>);
|
||||
class Address extends Component {
|
||||
static propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
accounts: PropTypes.object.isRequired,
|
||||
contacts: PropTypes.object.isRequired,
|
||||
isTestnet: PropTypes.bool.isRequired,
|
||||
key: PropTypes.string,
|
||||
shortenHash: PropTypes.bool
|
||||
}
|
||||
return (
|
||||
<div style={ container }>
|
||||
<IdentityIcon address={ address } style={ align } />
|
||||
{ caption }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
key: 'address',
|
||||
shortenHash: true
|
||||
}
|
||||
|
||||
render () {
|
||||
const { address, accounts, contacts, isTestnet, key, shortenHash } = this.props;
|
||||
|
||||
let caption;
|
||||
if (accounts[address] || contacts[address]) {
|
||||
const name = (accounts[address] || contacts[address] || {}).name;
|
||||
caption = (
|
||||
<a
|
||||
className={ styles.link }
|
||||
href={ etherscanUrl(address, isTestnet) }
|
||||
target='_blank'
|
||||
>
|
||||
<abbr
|
||||
title={ address }
|
||||
className={ styles.align }
|
||||
>
|
||||
{ name || address }
|
||||
</abbr>
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
caption = (
|
||||
<code className={ styles.align }>
|
||||
{ shortenHash ? (
|
||||
<Hash
|
||||
hash={ address }
|
||||
linked
|
||||
/>
|
||||
) : address }
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={ key }
|
||||
className={ styles.container }
|
||||
>
|
||||
<IdentityIcon
|
||||
address={ address }
|
||||
className={ styles.align }
|
||||
/>
|
||||
{ caption }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
// mapStateToProps
|
||||
(state) => ({
|
||||
accounts: state.accounts.all,
|
||||
contacts: state.contacts,
|
||||
isTestnet: state.isTestnet
|
||||
}),
|
||||
// mapDispatchToProps
|
||||
null
|
||||
)(Address);
|
||||
|
25
js/src/dapps/registry/ui/hash.css
Normal file
25
js/src/dapps/registry/ui/hash.css
Normal file
@ -0,0 +1,25 @@
|
||||
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
@ -14,11 +14,53 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
export default (hash) => {
|
||||
const shortened = hash.length > (2 + 9 + 9)
|
||||
? hash.substr(2, 9) + '...' + hash.slice(-9)
|
||||
: hash.slice(2);
|
||||
return (<abbr title={ hash }>{ shortened }</abbr>);
|
||||
};
|
||||
import etherscanUrl from '../util/etherscan-url';
|
||||
|
||||
import styles from './hash.css';
|
||||
|
||||
const leading0x = /^0x/;
|
||||
|
||||
class Hash extends Component {
|
||||
static propTypes = {
|
||||
hash: PropTypes.string.isRequired,
|
||||
isTestnet: PropTypes.bool.isRequired,
|
||||
linked: PropTypes.bool
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
linked: false
|
||||
}
|
||||
|
||||
render () {
|
||||
const { hash, isTestnet, linked } = this.props;
|
||||
|
||||
let shortened = hash.toLowerCase().replace(leading0x, '');
|
||||
shortened = shortened.length > (6 + 6)
|
||||
? shortened.substr(0, 6) + '...' + shortened.slice(-6)
|
||||
: shortened;
|
||||
|
||||
if (linked) {
|
||||
return (
|
||||
<a
|
||||
className={ styles.link }
|
||||
href={ etherscanUrl(hash, isTestnet) }
|
||||
target='_blank'
|
||||
>
|
||||
<abbr title={ hash }>{ shortened }</abbr>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (<abbr title={ hash }>{ shortened }</abbr>);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => ({ // mapStateToProps
|
||||
isTestnet: state.isTestnet
|
||||
}),
|
||||
null // mapDispatchToProps
|
||||
)(Hash);
|
||||
|
@ -14,14 +14,18 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import DropDownMenu from 'material-ui/DropDownMenu';
|
||||
import MenuItem from 'material-ui/MenuItem';
|
||||
export const isAction = (ns, type, action) => {
|
||||
return action.type.slice(0, ns.length + 1 + type.length) === `${ns} ${type}`;
|
||||
};
|
||||
|
||||
export default (value, onSelect, className = '') => (
|
||||
<DropDownMenu className={ className } value={ value } onChange={ onSelect }>
|
||||
<MenuItem value='A' primaryText='A – Ethereum address' />
|
||||
<MenuItem value='IMG' primaryText='IMG – hash of a picture in the blockchain' />
|
||||
<MenuItem value='CONTENT' primaryText='CONTENT – hash of a data in the blockchain' />
|
||||
</DropDownMenu>
|
||||
);
|
||||
export const isStage = (stage, action) => {
|
||||
return action.type.slice(-1 - stage.length) === ` ${stage}`;
|
||||
};
|
||||
|
||||
export const addToQueue = (queue, action, name) => {
|
||||
return queue.concat({ action, name });
|
||||
};
|
||||
|
||||
export const removeFromQueue = (queue, action, name) => {
|
||||
return queue.filter((e) => e.action === action && e.name === name);
|
||||
};
|
26
js/src/dapps/registry/util/etherscan-url.js
Normal file
26
js/src/dapps/registry/util/etherscan-url.js
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2015, 2016 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 leading0x = /^0x/;
|
||||
|
||||
const etherscanUrl = (hash, isTestnet) => {
|
||||
hash = hash.toLowerCase().replace(leading0x, '');
|
||||
const type = hash.length === 40 ? 'address' : 'tx';
|
||||
|
||||
return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/${type}/0x${hash}`;
|
||||
};
|
||||
|
||||
export default etherscanUrl;
|
36
js/src/dapps/registry/util/post-tx.js
Normal file
36
js/src/dapps/registry/util/post-tx.js
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2015, 2016 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 postTx = (api, method, opt = {}, values = []) => {
|
||||
opt = Object.assign({}, opt);
|
||||
|
||||
return method.estimateGas(opt, values)
|
||||
.then((gas) => {
|
||||
opt.gas = gas.mul(1.2).toFixed(0);
|
||||
return method.postTransaction(opt, values);
|
||||
})
|
||||
.then((reqId) => {
|
||||
return api.pollMethod('parity_checkRequest', reqId);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.type === 'REQUEST_REJECTED') {
|
||||
throw new Error('The request has been rejected.');
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
export default postTx;
|
Loading…
Reference in New Issue
Block a user