Merge branch 'master' into ng-accounts-backup

This commit is contained in:
Nicolas Gotchac
2016-11-14 20:31:10 +01:00
614 changed files with 20356 additions and 7948 deletions

45
js/src/ui/Actionbar/Import/import.css vendored Normal file
View File

@@ -0,0 +1,45 @@
/* 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/>.
*/
.importZone {
width: 100%;
height: 200px;
border-width: 2px;
border-color: #666;
border-style: dashed;
border-radius: 10px;
background-color: rgba(50, 50, 50, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
transition: all 0.5s ease;
&:hover {
cursor: pointer;
border-radius: 0;
background-color: rgba(50, 50, 50, 0.5);
}
}
.desc {
margin-top: 0;
color: #ccc;
}

View File

@@ -0,0 +1,198 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import Dropzone from 'react-dropzone';
import FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
import ContentClear from 'material-ui/svg-icons/content/clear';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import { Modal, Button } from '../../';
import styles from './import.css';
const initialState = {
step: 0,
show: false,
validate: false,
validationBody: null,
content: ''
};
export default class ActionbarImport extends Component {
static propTypes = {
onConfirm: PropTypes.func.isRequired,
renderValidation: PropTypes.func,
className: PropTypes.string,
title: PropTypes.string
};
static defaultProps = {
title: 'Import from a file'
};
state = Object.assign({}, initialState);
render () {
const { className } = this.props;
return (
<div>
<Button
className={ className }
icon={ <FileUploadIcon /> }
label='import'
onClick={ this.onOpenModal }
/>
{ this.renderModal() }
</div>
);
}
renderModal () {
const { title, renderValidation } = this.props;
const { show, step } = this.state;
if (!show) {
return null;
}
const hasSteps = typeof renderValidation === 'function';
const steps = hasSteps ? [ 'select a file', 'validate' ] : null;
return (
<Modal
actions={ this.renderActions() }
title={ title }
steps={ steps }
current={ step }
visible
>
{ this.renderBody() }
</Modal>
);
}
renderActions () {
const { validate } = this.state;
const cancelBtn = (
<Button
key='cancel'
label='Cancel'
icon={ <ContentClear /> }
onClick={ this.onCloseModal }
/>
);
if (validate) {
const confirmBtn = (
<Button
key='confirm'
label='Confirm'
icon={ <ActionDoneAll /> }
onClick={ this.onConfirm }
/>
);
return [ cancelBtn, confirmBtn ];
}
return [ cancelBtn ];
}
renderBody () {
const { validate } = this.state;
if (validate) {
return this.renderValidation();
}
return this.renderFileSelect();
}
renderFileSelect () {
return (
<div>
<Dropzone
onDrop={ this.onDrop }
multiple={ false }
className={ styles.importZone }
>
<div>Drop a file here, or click to select a file to upload.</div>
</Dropzone>
</div>
);
}
renderValidation () {
const { validationBody } = this.state;
return (
<div>
<p className={ styles.desc }>
Confirm that this is what was intended to import.
</p>
<div>
{ validationBody }
</div>
</div>
);
}
onDrop = (files) => {
const { renderValidation } = this.props;
const file = files[0];
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target.result;
if (typeof renderValidation !== 'function') {
this.props.onConfirm(content);
return this.onCloseModal();
}
this.setState({
step: 1,
validate: true,
validationBody: renderValidation(content),
content
});
};
reader.readAsText(file);
}
onConfirm = () => {
const { content } = this.state;
this.props.onConfirm(content);
return this.onCloseModal();
}
onOpenModal = () => {
this.setState({ show: true });
}
onCloseModal = () => {
this.setState(initialState);
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './import';

View File

@@ -41,11 +41,3 @@
width: 0;
height: 0;
}
.chip > svg {
width: 1.2rem !important;
height: 1.2rem !important;
margin: initial !important;
margin-right: 4px !important;
padding: 4px 0 !important;
}

View File

@@ -15,14 +15,9 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { Chip } from 'material-ui';
import { blue300 } from 'material-ui/styles/colors';
// import ChipInput from 'material-ui-chip-input';
import ChipInput from 'material-ui-chip-input/src/ChipInput';
import ActionSearch from 'material-ui/svg-icons/action/search';
import { uniq } from 'lodash';
import { Button } from '../../';
import { Button, InputChip } from '../../';
import styles from './search.css';
@@ -74,28 +69,16 @@ export default class ActionbarSearch extends Component {
className={ styles.searchcontainer }
key='searchAccount'>
<div className={ inputContainerClasses.join(' ') }>
<ChipInput
clearOnBlur={ false }
<InputChip
className={ styles.input }
chipRenderer={ this.chipRenderer }
hintText='Enter search input...'
ref='searchInput'
value={ tokens }
hint='Enter search input...'
tokens={ tokens }
onBlur={ this.handleSearchBlur }
onRequestAdd={ this.handleTokenAdd }
onRequestDelete={ this.handleTokenDelete }
onUpdateInput={ this.handleInputChange }
hintStyle={ {
bottom: 16,
left: 2,
transition: 'none'
} }
inputStyle={ {
marginBottom: 18
} }
textFieldStyle={ {
height: 42
} }
onInputChange={ this.handleInputChange }
onTokensChange={ this.handleTokensChange }
addOnBlur
/>
</div>
@@ -108,67 +91,13 @@ export default class ActionbarSearch extends Component {
);
}
chipRenderer = (state, key) => {
const { value, isFocused, isDisabled, handleClick, handleRequestDelete } = state;
return (
<Chip
key={ key }
className={ styles.chip }
style={ {
margin: '8px 8px 0 0',
float: 'left',
pointerEvents: isDisabled ? 'none' : undefined,
alignItems: 'center'
} }
labelStyle={ {
paddingRight: 6,
fontSize: '0.9rem',
lineHeight: 'initial'
} }
backgroundColor={ isFocused ? blue300 : 'rgba(0, 0, 0, 0.73)' }
onTouchTap={ handleClick }
onRequestDelete={ handleRequestDelete }
>
{ value }
</Chip>
);
handleTokensChange = (tokens) => {
this.handleSearchChange(tokens);
}
handleTokenAdd = (value) => {
const { tokens } = this.props;
const newSearchTokens = uniq([].concat(tokens, value));
this.handleSearchChange(newSearchTokens);
}
handleTokenDelete = (value) => {
const { tokens } = this.props;
const newSearchTokens = []
.concat(tokens)
.filter(v => v !== value);
this.handleSearchChange(newSearchTokens);
this.refs.searchInput.focus();
}
handleInputChange = (value) => {
const splitTokens = value.split(/[\s,;]/);
const inputValue = (splitTokens.length <= 1)
? value
: splitTokens.slice(-1)[0].trim();
this.refs.searchInput.setState({ inputValue });
handleInputChange = (inputValue) => {
this.setState({ inputValue }, () => {
if (splitTokens.length > 1) {
const tokensToAdd = splitTokens.slice(0, -1);
tokensToAdd.forEach(token => this.handleTokenAdd(token));
} else {
this.handleSearchChange();
}
this.handleSearchChange();
});
}
@@ -177,12 +106,10 @@ export default class ActionbarSearch extends Component {
const { inputValue } = this.state;
const newSearchTokens = []
.concat(searchTokens || tokens)
.filter(v => v.length > 0);
.concat(searchTokens || tokens);
const newSearchValues = []
.concat(searchTokens || tokens, inputValue)
.filter(v => v.length > 0);
.concat(searchTokens || tokens, inputValue);
onChange(newSearchTokens, newSearchValues);
}
@@ -209,19 +136,15 @@ export default class ActionbarSearch extends Component {
}
handleOpenSearch = (showSearch, force) => {
if (this.state.stateChanging && !force) return false;
if (this.state.stateChanging && !force) {
return false;
}
this.setState({
showSearch: showSearch,
stateChanging: true
});
if (showSearch) {
this.refs.searchInput.focus();
} else {
this.refs.searchInput.getInputNode().blur();
}
const timeoutId = window.setTimeout(() => {
this.setState({ stateChanging: false });
}, 450);

View File

@@ -27,14 +27,23 @@ import styles from './sort.css';
export default class ActionbarSort extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
order: PropTypes.string
order: PropTypes.string,
showDefault: PropTypes.bool,
metas: PropTypes.array
};
static defaultProps = {
metas: [],
showDefault: true
}
state = {
menuOpen: false
}
render () {
const { showDefault } = this.props;
return (
<IconMenu
iconButtonElement={
@@ -50,14 +59,56 @@ export default class ActionbarSort extends Component {
onItemTouchTap={ this.handleSortChange }
targetOrigin={ { horizontal: 'right', vertical: 'top' } }
anchorOrigin={ { horizontal: 'right', vertical: 'top' } }
>
<MenuItem value='' primaryText='Default' />
<MenuItem value='tags' primaryText='Sort by tags' />
<MenuItem value='name' primaryText='Sort by name' />
touchTapCloseDelay={ 0 }
>
{
showDefault
? this.renderMenuItem('', 'Default')
: null
}
{ this.renderMenuItem('tags', 'Sort by tags') }
{ this.renderMenuItem('name', 'Sort by name') }
{ this.renderMenuItem('eth', 'Sort by ETH') }
{ this.renderSortByMetas() }
</IconMenu>
);
}
renderSortByMetas () {
const { metas } = this.props;
return metas
.map((meta, index) => {
return this
.renderMenuItem(meta.key, `Sort by ${meta.label}`, index);
});
}
renderMenuItem (value, label, key = null) {
const { order } = this.props;
const props = {};
if (key !== null) {
props.key = key;
}
const checked = order === value;
return (
<MenuItem
checked={ checked }
value={ value }
primaryText={ label }
innerDivStyle={ {
paddingLeft: checked ? 50 : 16
} }
{ ...props }
/>
);
}
handleSortChange = (event, child) => {
const order = child.props.value;
this.props.onChange(order);

View File

@@ -32,19 +32,25 @@
border-radius: 16px;
margin: 0.75em 0.5em 0 0;
max-height: 24px;
max-width: 100%;
display: flex;
align-items: center;
}
.balance img {
display: inline-block;
height: 32px;
margin: -4px 1em 0 0;
width: 32px;
}
.balance div {
display: inline-block;
/*font-family: 'Roboto Mono', monospace;*/
line-height: 24px;
margin: 0 1em 0 0;
vertical-align: top;
.balanceValue {
margin: 0 0.5em 0 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.balanceTag {
font-size: 0.85em;
padding-right: 0.75rem;
}

View File

@@ -44,10 +44,31 @@ class Balance extends Component {
.filter((balance) => new BigNumber(balance.value).gt(0))
.map((balance) => {
const token = balance.token;
const value = token.format
? new BigNumber(balance.value).div(new BigNumber(token.format)).toFormat(3)
: api.util.fromWei(balance.value).toFormat(3);
const imagesrc = token.image || images[token.address] || unknownImage;
let value;
if (token.format) {
const bnf = new BigNumber(token.format);
let decimals = 0;
if (bnf.gte(1000)) {
decimals = 3;
} else if (bnf.gte(100)) {
decimals = 2;
} else if (bnf.gte(10)) {
decimals = 1;
}
value = new BigNumber(balance.value).div(bnf).toFormat(decimals);
} else {
value = api.util.fromWei(balance.value).toFormat(3);
}
let imagesrc = token.image;
if (!imagesrc) {
imagesrc = images[token.address]
? `${api.dappsUrl}${images[token.address]}`
: unknownImage;
}
return (
<div
@@ -56,7 +77,10 @@ class Balance extends Component {
<img
src={ imagesrc }
alt={ token.name } />
<div>{ value }<small> { token.tag }</small></div>
<div className={ styles.balanceValue }>
<span title={ value }> { value } </span>
</div>
<div className={ styles.balanceTag }> { token.tag } </div>
</div>
);
});

View File

@@ -0,0 +1,22 @@
/* 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/>.
*/
.blockNumber {
}
.syncStatus {
}

View File

@@ -0,0 +1,90 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import styles from './blockStatus.css';
class BlockStatus extends Component {
static propTypes = {
blockNumber: PropTypes.object,
syncing: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.object
])
}
render () {
const { blockNumber, syncing } = this.props;
if (!blockNumber) {
return null;
}
if (!syncing) {
return (
<div className={ styles.blockNumber }>
{ blockNumber.toFormat() } best block
</div>
);
}
if (!syncing.warpChunksAmount.eq(syncing.warpChunksProcessed)) {
return (
<div className={ styles.syncStatus }>
{ syncing.warpChunksProcessed.mul(100).div(syncing.warpChunksAmount).toFormat(2) }% warp restore
</div>
);
}
let warpStatus = null;
if (syncing.blockGap) {
const [first, last] = syncing.blockGap;
warpStatus = (
<span>, { first.mul(100).div(last).toFormat(2) }% historic</span>
);
}
return (
<div className={ styles.syncStatus }>
<span>{ syncing.currentBlock.toFormat() }/{ syncing.highestBlock.toFormat() } syncing</span>
{ warpStatus }
</div>
);
}
}
function mapStateToProps (state) {
const { blockNumber, syncing } = state.nodeStatus;
return {
blockNumber,
syncing
};
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(BlockStatus);

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './blockStatus';

View File

@@ -19,6 +19,7 @@ import { FlatButton } from 'material-ui';
export default class Button extends Component {
static propTypes = {
backgroundColor: PropTypes.string,
className: PropTypes.string,
disabled: PropTypes.bool,
icon: PropTypes.node,
@@ -26,19 +27,25 @@ export default class Button extends Component {
React.PropTypes.string,
React.PropTypes.object
]),
onClick: PropTypes.func
onClick: PropTypes.func,
primary: PropTypes.bool
}
static defaultProps = {
primary: true
}
render () {
const { className, disabled, icon, label, onClick } = this.props;
const { className, backgroundColor, disabled, icon, label, primary, onClick } = this.props;
return (
<FlatButton
className={ className }
backgroundColor={ backgroundColor }
disabled={ disabled }
icon={ icon }
label={ label }
primary
primary={ primary }
onTouchTap={ onClick } />
);
}

View File

@@ -15,7 +15,20 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.byline {
overflow: hidden;
position: relative;
line-height: 1.2em;
max-height: 2.4em;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
color: #aaa;
* {
color: #aaa !important;
}
}
.title {

View File

@@ -24,7 +24,9 @@ export default class Title extends Component {
title: PropTypes.oneOfType([
PropTypes.string, PropTypes.node
]),
byline: PropTypes.string
byline: PropTypes.oneOfType([
PropTypes.string, PropTypes.node
])
}
state = {
@@ -32,15 +34,23 @@ export default class Title extends Component {
}
render () {
const { className } = this.props;
const { className, title, byline } = this.props;
const byLine = typeof byline === 'string'
? (
<span title={ byline }>
{ byline }
</span>
)
: byline;
return (
<div className={ className }>
<h3 className={ styles.title }>
{ this.props.title }
{ title }
</h3>
<div className={ styles.byline }>
{ this.props.byline }
{ byLine }
</div>
</div>
);

View File

@@ -17,7 +17,8 @@
.container {
flex: 1;
padding: 0em;
background: rgba(0, 0, 0, 0.8) !important;
background: rgba(0, 0, 0, 0.8);
height: 100%;
}
.compact,

View File

@@ -0,0 +1,24 @@
/* 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/>.
*/
.wrapper {
display: inline-block;
}
.data {
font-family: monospace;
}

View File

@@ -0,0 +1,93 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { IconButton } from 'material-ui';
import Snackbar from 'material-ui/Snackbar';
import Clipboard from 'react-copy-to-clipboard';
import CopyIcon from 'material-ui/svg-icons/content/content-copy';
import Theme from '../Theme';
import { darkBlack } from 'material-ui/styles/colors';
const { textColor, disabledTextColor } = Theme.flatButton;
import styles from './copyToClipboard.css';
export default class CopyToClipboard extends Component {
static propTypes = {
data: PropTypes.string.isRequired,
onCopy: PropTypes.func,
size: PropTypes.number, // in px
cooldown: PropTypes.number // in ms
};
static defaultProps = {
className: '',
onCopy: () => {},
size: 16,
cooldown: 1000
};
state = {
copied: false,
timeout: null
};
componentWillUnmount () {
const { timeoutId } = this.state;
if (timeoutId) {
window.clearTimeout(timeoutId);
}
}
render () {
const { data, size } = this.props;
const { copied } = this.state;
return (
<Clipboard onCopy={ this.onCopy } text={ data }>
<div className={ styles.wrapper }>
<Snackbar
open={ copied }
message={
<div>copied <code className={ styles.data }>{ data }</code> to clipboard</div>
}
autoHideDuration={ 2000 }
bodyStyle={ { backgroundColor: darkBlack } }
/>
<IconButton
disableTouchRipple
style={ { width: size, height: size, padding: '0' } }
iconStyle={ { width: size, height: size } }
>
<CopyIcon color={ copied ? disabledTextColor : textColor } />
</IconButton>
</div>
</Clipboard>
);
}
onCopy = () => {
const { cooldown, onCopy } = this.props;
this.setState({
copied: true,
timeout: setTimeout(() => {
this.setState({ copied: false, timeout: null });
}, cooldown)
});
onCopy();
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './copyToClipboard';

103
js/src/ui/Editor/editor.js Normal file
View File

@@ -0,0 +1,103 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PropTypes, Component } from 'react';
import 'brace';
import AceEditor from 'react-ace';
import { noop } from 'lodash';
import 'brace/theme/solarized_dark';
import 'brace/mode/json';
import './mode-solidity';
export default class Editor extends Component {
static propTypes = {
className: PropTypes.string,
value: PropTypes.string,
mode: PropTypes.string,
maxLines: PropTypes.number,
annotations: PropTypes.array,
onExecute: PropTypes.func,
onChange: PropTypes.func,
readOnly: PropTypes.bool
};
static defaultProps = {
className: '',
value: '',
mode: 'javascript',
annotations: [],
onExecute: noop,
onChange: noop,
readOnly: false
};
componentWillMount () {
this.name = `PARITY_EDITOR_${Math.round(Math.random() * 99999)}`;
}
render () {
const { className, annotations, value, readOnly, mode, maxLines } = this.props;
const commands = [
{
name: 'execut',
bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
exec: this.handleExecute
}
];
const max = (maxLines !== undefined)
? maxLines
: (readOnly ? value.split('\n').length + 1 : null);
return (
<AceEditor
mode={ mode }
theme='solarized_dark'
width='100%'
ref='brace'
style={ { flex: 1 } }
onChange={ this.handleOnChange }
name={ this.name }
editorProps={ { $blockScrolling: Infinity } }
setOptions={ {
useWorker: false,
fontFamily: 'monospace',
fontSize: '0.9em'
} }
maxLines={ max }
enableBasicAutocompletion={ !readOnly }
showPrintMargin={ false }
annotations={ annotations }
value={ value }
commands={ commands }
readOnly={ readOnly }
className={ className }
/>
);
}
handleExecute = () => {
this.props.onExecute();
}
handleOnChange = (value) => {
this.props.onChange(value);
}
}

17
js/src/ui/Editor/index.js Normal file
View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './editor';

View File

@@ -0,0 +1,994 @@
// 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/>.
/**
* This file has been taken from `ethereum/browser-solidity`
*
* @see: https://raw.githubusercontent.com/ethereum/browser-solidity/master/src/mode-solidity.js
*/
/* eslint-disable */
var ace = window.ace;
ace.define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../lib/oop");
var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules;
var DocCommentHighlightRules = function() {
this.$rules = {
"start" : [ {
token : "comment.doc.tag",
regex : "@[\\w\\d_]+" // TODO: fix email addresses
},
DocCommentHighlightRules.getTagRule(),
{
defaultToken : "comment.doc",
caseInsensitive: true
}]
};
};
oop.inherits(DocCommentHighlightRules, TextHighlightRules);
DocCommentHighlightRules.getTagRule = function(start) {
return {
token : "comment.doc.tag.storage.type",
regex : "\\b(?:TODO|FIXME|XXX|HACK)\\b"
};
};
DocCommentHighlightRules.getStartRule = function(start) {
return {
token : "comment.doc", // doc comment
regex : "\\/\\*(?=\\*)",
next : start
};
};
DocCommentHighlightRules.getEndRule = function (start) {
return {
token : "comment.doc", // closing comment
regex : "\\*\\/",
next : start
};
};
exports.DocCommentHighlightRules = DocCommentHighlightRules;
});
ace.define("ace/mode/javascript_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../lib/oop");
var DocCommentHighlightRules = acequire("./doc_comment_highlight_rules").DocCommentHighlightRules;
var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules;
var JavaScriptHighlightRules = function(options) {
var intTypes = 'bytes|int|uint';
for (var width = 8; width <= 256; width += 8)
intTypes += '|bytes' + (width / 8) + '|uint' + width + '|int' + width;
var keywordMapper = this.createKeywordMapper({
"variable.language":
"this|bool|string|byte|bytes|bytes0|address|" + intTypes,
"keyword":
"contract|library|constant|event|modifier|" +
"struct|mapping|enum|break|continue|delete|else|for|function|" +
"if|new|return|returns|var|while|using|" +
"private|public|external|internal|storage|memory",
"storage.type":
"constant|var|function",
"constant.language.boolean": "true|false"
}, "identifier");
var kwBeforeRe = "case|do|else|finally|in|instanceof|return|throw|try|typeof|yield|void";
var identifierRe = "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*\\b";
var escapedRe = "\\\\(?:x[0-9a-fA-F]{2}|" + // hex
"u[0-9a-fA-F]{4}|" + // unicode
"[0-2][0-7]{0,2}|" + // oct
"3[0-6][0-7]?|" + // oct
"37[0-7]?|" + // oct
"[4-7][0-7]?|" + //oct
".)";
this.$rules = {
"no_regex" : [
{
token : "comment",
regex : "\\/\\/",
next : "line_comment"
},
DocCommentHighlightRules.getStartRule("doc-start"),
{
token : "comment", // multi line comment
regex : /\/\*/,
next : "comment"
}, {
token : "string",
regex : "'(?=.)",
next : "qstring"
}, {
token : "string",
regex : '"(?=.)',
next : "qqstring"
}, {
token : "constant.numeric", // hex
regex : /0[xX][0-9a-fA-F]+\b/
}, {
token : "constant.numeric", // float
regex : /[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/
}, {
token : [
"storage.type", "punctuation.operator", "support.function",
"punctuation.operator", "entity.name.function", "text","keyword.operator"
],
regex : "(" + identifierRe + ")(\\.)(prototype)(\\.)(" + identifierRe +")(\\s*)(=)",
next: "function_arguments"
}, {
token : [
"storage.type", "punctuation.operator", "entity.name.function", "text",
"keyword.operator", "text", "storage.type", "text", "paren.lparen"
],
regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"entity.name.function", "text", "keyword.operator", "text", "storage.type",
"text", "paren.lparen"
],
regex : "(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"storage.type", "punctuation.operator", "entity.name.function", "text",
"keyword.operator", "text",
"storage.type", "text", "entity.name.function", "text", "paren.lparen"
],
regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s+)(\\w+)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"storage.type", "text", "entity.name.function", "text", "paren.lparen"
],
regex : "(function)(\\s+)(" + identifierRe + ")(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"entity.name.function", "text", "punctuation.operator",
"text", "storage.type", "text", "paren.lparen"
],
regex : "(" + identifierRe + ")(\\s*)(:)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"text", "text", "storage.type", "text", "paren.lparen"
],
regex : "(:)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : "keyword",
regex : "(?:" + kwBeforeRe + ")\\b",
next : "start"
}, {
token : ["punctuation.operator", "support.function"],
regex : /(\.)(s(?:h(?:ift|ow(?:Mod(?:elessDialog|alDialog)|Help))|croll(?:X|By(?:Pages|Lines)?|Y|To)?|t(?:op|rike)|i(?:n|zeToContent|debar|gnText)|ort|u(?:p|b(?:str(?:ing)?)?)|pli(?:ce|t)|e(?:nd|t(?:Re(?:sizable|questHeader)|M(?:i(?:nutes|lliseconds)|onth)|Seconds|Ho(?:tKeys|urs)|Year|Cursor|Time(?:out)?|Interval|ZOptions|Date|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Date|FullYear)|FullYear|Active)|arch)|qrt|lice|avePreferences|mall)|h(?:ome|andleEvent)|navigate|c(?:har(?:CodeAt|At)|o(?:s|n(?:cat|textual|firm)|mpile)|eil|lear(?:Timeout|Interval)?|a(?:ptureEvents|ll)|reate(?:StyleSheet|Popup|EventObject))|t(?:o(?:GMTString|S(?:tring|ource)|U(?:TCString|pperCase)|Lo(?:caleString|werCase))|est|a(?:n|int(?:Enabled)?))|i(?:s(?:NaN|Finite)|ndexOf|talics)|d(?:isableExternalCapture|ump|etachEvent)|u(?:n(?:shift|taint|escape|watch)|pdateCommands)|j(?:oin|avaEnabled)|p(?:o(?:p|w)|ush|lugins.refresh|a(?:ddings|rse(?:Int|Float)?)|r(?:int|ompt|eference))|e(?:scape|nableExternalCapture|val|lementFromPoint|x(?:p|ec(?:Script|Command)?))|valueOf|UTC|queryCommand(?:State|Indeterm|Enabled|Value)|f(?:i(?:nd|le(?:ModifiedDate|Size|CreatedDate|UpdatedDate)|xed)|o(?:nt(?:size|color)|rward)|loor|romCharCode)|watch|l(?:ink|o(?:ad|g)|astIndexOf)|a(?:sin|nchor|cos|t(?:tachEvent|ob|an(?:2)?)|pply|lert|b(?:s|ort))|r(?:ou(?:nd|teEvents)|e(?:size(?:By|To)|calc|turnValue|place|verse|l(?:oad|ease(?:Capture|Events)))|andom)|g(?:o|et(?:ResponseHeader|M(?:i(?:nutes|lliseconds)|onth)|Se(?:conds|lection)|Hours|Year|Time(?:zoneOffset)?|Da(?:y|te)|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Da(?:y|te)|FullYear)|FullYear|A(?:ttention|llResponseHeaders)))|m(?:in|ove(?:B(?:y|elow)|To(?:Absolute)?|Above)|ergeAttributes|a(?:tch|rgins|x))|b(?:toa|ig|o(?:ld|rderWidths)|link|ack))\b(?=\()/
}, {
token : ["punctuation.operator", "support.function.dom"],
regex : /(\.)(s(?:ub(?:stringData|mit)|plitText|e(?:t(?:NamedItem|Attribute(?:Node)?)|lect))|has(?:ChildNodes|Feature)|namedItem|c(?:l(?:ick|o(?:se|neNode))|reate(?:C(?:omment|DATASection|aption)|T(?:Head|extNode|Foot)|DocumentFragment|ProcessingInstruction|E(?:ntityReference|lement)|Attribute))|tabIndex|i(?:nsert(?:Row|Before|Cell|Data)|tem)|open|delete(?:Row|C(?:ell|aption)|T(?:Head|Foot)|Data)|focus|write(?:ln)?|a(?:dd|ppend(?:Child|Data))|re(?:set|place(?:Child|Data)|move(?:NamedItem|Child|Attribute(?:Node)?)?)|get(?:NamedItem|Element(?:sBy(?:Name|TagName)|ById)|Attribute(?:Node)?)|blur)\b(?=\()/
}, {
token : ["punctuation.operator", "support.constant"],
regex : /(\.)(s(?:ystemLanguage|cr(?:ipts|ollbars|een(?:X|Y|Top|Left))|t(?:yle(?:Sheets)?|atus(?:Text|bar)?)|ibling(?:Below|Above)|ource|uffixes|e(?:curity(?:Policy)?|l(?:ection|f)))|h(?:istory|ost(?:name)?|as(?:h|Focus))|y|X(?:MLDocument|SLDocument)|n(?:ext|ame(?:space(?:s|URI)|Prop))|M(?:IN_VALUE|AX_VALUE)|c(?:haracterSet|o(?:n(?:structor|trollers)|okieEnabled|lorDepth|mp(?:onents|lete))|urrent|puClass|l(?:i(?:p(?:boardData)?|entInformation)|osed|asses)|alle(?:e|r)|rypto)|t(?:o(?:olbar|p)|ext(?:Transform|Indent|Decoration|Align)|ags)|SQRT(?:1_2|2)|i(?:n(?:ner(?:Height|Width)|put)|ds|gnoreCase)|zIndex|o(?:scpu|n(?:readystatechange|Line)|uter(?:Height|Width)|p(?:sProfile|ener)|ffscreenBuffering)|NEGATIVE_INFINITY|d(?:i(?:splay|alog(?:Height|Top|Width|Left|Arguments)|rectories)|e(?:scription|fault(?:Status|Ch(?:ecked|arset)|View)))|u(?:ser(?:Profile|Language|Agent)|n(?:iqueID|defined)|pdateInterval)|_content|p(?:ixelDepth|ort|ersonalbar|kcs11|l(?:ugins|atform)|a(?:thname|dding(?:Right|Bottom|Top|Left)|rent(?:Window|Layer)?|ge(?:X(?:Offset)?|Y(?:Offset)?))|r(?:o(?:to(?:col|type)|duct(?:Sub)?|mpter)|e(?:vious|fix)))|e(?:n(?:coding|abledPlugin)|x(?:ternal|pando)|mbeds)|v(?:isibility|endor(?:Sub)?|Linkcolor)|URLUnencoded|P(?:I|OSITIVE_INFINITY)|f(?:ilename|o(?:nt(?:Size|Family|Weight)|rmName)|rame(?:s|Element)|gColor)|E|whiteSpace|l(?:i(?:stStyleType|n(?:eHeight|kColor))|o(?:ca(?:tion(?:bar)?|lName)|wsrc)|e(?:ngth|ft(?:Context)?)|a(?:st(?:M(?:odified|atch)|Index|Paren)|yer(?:s|X)|nguage))|a(?:pp(?:MinorVersion|Name|Co(?:deName|re)|Version)|vail(?:Height|Top|Width|Left)|ll|r(?:ity|guments)|Linkcolor|bove)|r(?:ight(?:Context)?|e(?:sponse(?:XML|Text)|adyState))|global|x|m(?:imeTypes|ultiline|enubar|argin(?:Right|Bottom|Top|Left))|L(?:N(?:10|2)|OG(?:10E|2E))|b(?:o(?:ttom|rder(?:Width|RightWidth|BottomWidth|Style|Color|TopWidth|LeftWidth))|ufferDepth|elow|ackground(?:Color|Image)))\b/
}, {
token : ["support.constant"],
regex : /that\b/
}, {
token : ["storage.type", "punctuation.operator", "support.function.firebug"],
regex : /(console)(\.)(warn|info|log|error|time|trace|timeEnd|assert)\b/
}, {
token : keywordMapper,
regex : identifierRe
}, {
token : "keyword.operator",
regex : /--|\*\*|\+\+|===|==|=|!=|!==|=>|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\|\||\?\:|[!$%&*+\-~\/^]=?/,
next : "start"
}, {
token : "punctuation.operator",
regex : /[?:,;.]/,
next : "start"
}, {
token : "paren.lparen",
regex : /[\[({]/,
next : "start"
}, {
token : "paren.rparen",
regex : /[\])}]/
}, {
token: "comment",
regex: /^#!.*$/
}
],
"start": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
token : "comment", // multi line comment
regex : "\\/\\*",
next : "comment_regex_allowed"
}, {
token : "comment",
regex : "\\/\\/",
next : "line_comment_regex_allowed"
}, {
token: "string.regexp",
regex: "\\/",
next: "regex"
}, {
token : "text",
regex : "\\s+|^$",
next : "start"
}, {
token: "empty",
regex: "",
next: "no_regex"
}
],
"regex": [
{
token: "regexp.keyword.operator",
regex: "\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"
}, {
token: "string.regexp",
regex: "/[sxngimy]*",
next: "no_regex"
}, {
token : "invalid",
regex: /\{\d+\b,?\d*\}[+*]|[+*$^?][+*]|[$^][?]|\?{3,}/
}, {
token : "constant.language.escape",
regex: /\(\?[:=!]|\)|\{\d+\b,?\d*\}|[+*]\?|[()$^+*?.]/
}, {
token : "constant.language.delimiter",
regex: /\|/
}, {
token: "constant.language.escape",
regex: /\[\^?/,
next: "regex_character_class"
}, {
token: "empty",
regex: "$",
next: "no_regex"
}, {
defaultToken: "string.regexp"
}
],
"regex_character_class": [
{
token: "regexp.charclass.keyword.operator",
regex: "\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"
}, {
token: "constant.language.escape",
regex: "]",
next: "regex"
}, {
token: "constant.language.escape",
regex: "-"
}, {
token: "empty",
regex: "$",
next: "no_regex"
}, {
defaultToken: "string.regexp.charachterclass"
}
],
"function_arguments": [
{
token: "variable.parameter",
regex: identifierRe
}, {
token: "punctuation.operator",
regex: "[, ]+"
}, {
token: "punctuation.operator",
regex: "$"
}, {
token: "empty",
regex: "",
next: "no_regex"
}
],
"comment_regex_allowed" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "\\*\\/", next : "start"},
{defaultToken : "comment", caseInsensitive: true}
],
"comment" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "\\*\\/", next : "no_regex"},
{defaultToken : "comment", caseInsensitive: true}
],
"line_comment_regex_allowed" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "$|^", next : "start"},
{defaultToken : "comment", caseInsensitive: true}
],
"line_comment" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "$|^", next : "no_regex"},
{defaultToken : "comment", caseInsensitive: true}
],
"qqstring" : [
{
token : "constant.language.escape",
regex : escapedRe
}, {
token : "string",
regex : "\\\\$",
next : "qqstring"
}, {
token : "string",
regex : '"|$',
next : "no_regex"
}, {
defaultToken: "string"
}
],
"qstring" : [
{
token : "constant.language.escape",
regex : escapedRe
}, {
token : "string",
regex : "\\\\$",
next : "qstring"
}, {
token : "string",
regex : "'|$",
next : "no_regex"
}, {
defaultToken: "string"
}
]
};
if (!options || !options.noES6) {
this.$rules.no_regex.unshift({
regex: "[{}]", onMatch: function(val, state, stack) {
this.next = val == "{" ? this.nextState : "";
if (val == "{" && stack.length) {
stack.unshift("start", state);
return "paren";
}
if (val == "}" && stack.length) {
stack.shift();
this.next = stack.shift();
if (this.next.indexOf("string") != -1)
return "paren.quasi.end";
}
return val == "{" ? "paren.lparen" : "paren.rparen";
},
nextState: "start"
}, {
token : "string.quasi.start",
regex : /`/,
push : [{
token : "constant.language.escape",
regex : escapedRe
}, {
token : "paren.quasi.start",
regex : /\${/,
push : "start"
}, {
token : "string.quasi.end",
regex : /`/,
next : "pop"
}, {
defaultToken: "string.quasi"
}]
});
}
this.embedRules(DocCommentHighlightRules, "doc-",
[ DocCommentHighlightRules.getEndRule("no_regex") ]);
this.normalizeRules();
};
oop.inherits(JavaScriptHighlightRules, TextHighlightRules);
exports.JavaScriptHighlightRules = JavaScriptHighlightRules;
});
ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"], function(acequire, exports, module) {
"use strict";
var Range = acequire("../range").Range;
var MatchingBraceOutdent = function() {};
(function() {
this.checkOutdent = function(line, input) {
if (! /^\s+$/.test(line))
return false;
return /^\s*\}/.test(input);
};
this.autoOutdent = function(doc, row) {
var line = doc.getLine(row);
var match = line.match(/^(\s*\})/);
if (!match) return 0;
var column = match[1].length;
var openBracePos = doc.findMatchingBracket({row: row, column: column});
if (!openBracePos || openBracePos.row == row) return 0;
var indent = this.$getIndent(doc.getLine(openBracePos.row));
doc.replace(new Range(row, 0, row, column-1), indent);
};
this.$getIndent = function(line) {
return line.match(/^\s*/)[0];
};
}).call(MatchingBraceOutdent.prototype);
exports.MatchingBraceOutdent = MatchingBraceOutdent;
});
ace.define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../../lib/oop");
var Behaviour = acequire("../behaviour").Behaviour;
var TokenIterator = acequire("../../token_iterator").TokenIterator;
var lang = acequire("../../lib/lang");
var SAFE_INSERT_IN_TOKENS =
["text", "paren.rparen", "punctuation.operator"];
var SAFE_INSERT_BEFORE_TOKENS =
["text", "paren.rparen", "punctuation.operator", "comment"];
var context;
var contextCache = {};
var initContext = function(editor) {
var id = -1;
if (editor.multiSelect) {
id = editor.selection.index;
if (contextCache.rangeCount != editor.multiSelect.rangeCount)
contextCache = {rangeCount: editor.multiSelect.rangeCount};
}
if (contextCache[id])
return context = contextCache[id];
context = contextCache[id] = {
autoInsertedBrackets: 0,
autoInsertedRow: -1,
autoInsertedLineEnd: "",
maybeInsertedBrackets: 0,
maybeInsertedRow: -1,
maybeInsertedLineStart: "",
maybeInsertedLineEnd: ""
};
};
var CstyleBehaviour = function() {
this.add("braces", "insertion", function(state, action, editor, session, text) {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
if (text == '{') {
initContext(editor);
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && selected !== "{" && editor.getWrapBehavioursEnabled()) {
return {
text: '{' + selected + '}',
selection: false
};
} else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
if (/[\]\}\)]/.test(line[cursor.column]) || editor.inMultiSelectMode) {
CstyleBehaviour.recordAutoInsert(editor, session, "}");
return {
text: '{}',
selection: [1, 1]
};
} else {
CstyleBehaviour.recordMaybeInsert(editor, session, "{");
return {
text: '{',
selection: [1, 1]
};
}
}
} else if (text == '}') {
initContext(editor);
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == '}') {
var matching = session.$findOpeningBracket('}', {column: cursor.column + 1, row: cursor.row});
if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
CstyleBehaviour.popAutoInsertedClosing();
return {
text: '',
selection: [1, 1]
};
}
}
} else if (text == "\n" || text == "\r\n") {
initContext(editor);
var closing = "";
if (CstyleBehaviour.isMaybeInsertedClosing(cursor, line)) {
closing = lang.stringRepeat("}", context.maybeInsertedBrackets);
CstyleBehaviour.clearMaybeInsertedClosing();
}
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar === '}') {
var openBracePos = session.findMatchingBracket({row: cursor.row, column: cursor.column+1}, '}');
if (!openBracePos)
return null;
var next_indent = this.$getIndent(session.getLine(openBracePos.row));
} else if (closing) {
var next_indent = this.$getIndent(line);
} else {
CstyleBehaviour.clearMaybeInsertedClosing();
return;
}
var indent = next_indent + session.getTabString();
return {
text: '\n' + indent + '\n' + next_indent + closing,
selection: [1, indent.length, 1, indent.length]
};
} else {
CstyleBehaviour.clearMaybeInsertedClosing();
}
});
this.add("braces", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && selected == '{') {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.end.column, range.end.column + 1);
if (rightChar == '}') {
range.end.column++;
return range;
} else {
context.maybeInsertedBrackets--;
}
}
});
this.add("parens", "insertion", function(state, action, editor, session, text) {
if (text == '(') {
initContext(editor);
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && editor.getWrapBehavioursEnabled()) {
return {
text: '(' + selected + ')',
selection: false
};
} else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
CstyleBehaviour.recordAutoInsert(editor, session, ")");
return {
text: '()',
selection: [1, 1]
};
}
} else if (text == ')') {
initContext(editor);
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == ')') {
var matching = session.$findOpeningBracket(')', {column: cursor.column + 1, row: cursor.row});
if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
CstyleBehaviour.popAutoInsertedClosing();
return {
text: '',
selection: [1, 1]
};
}
}
}
});
this.add("parens", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && selected == '(') {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
if (rightChar == ')') {
range.end.column++;
return range;
}
}
});
this.add("brackets", "insertion", function(state, action, editor, session, text) {
if (text == '[') {
initContext(editor);
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && editor.getWrapBehavioursEnabled()) {
return {
text: '[' + selected + ']',
selection: false
};
} else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
CstyleBehaviour.recordAutoInsert(editor, session, "]");
return {
text: '[]',
selection: [1, 1]
};
}
} else if (text == ']') {
initContext(editor);
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == ']') {
var matching = session.$findOpeningBracket(']', {column: cursor.column + 1, row: cursor.row});
if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
CstyleBehaviour.popAutoInsertedClosing();
return {
text: '',
selection: [1, 1]
};
}
}
}
});
this.add("brackets", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && selected == '[') {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
if (rightChar == ']') {
range.end.column++;
return range;
}
}
});
this.add("string_dquotes", "insertion", function(state, action, editor, session, text) {
if (text == '"' || text == "'") {
initContext(editor);
var quote = text;
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && selected !== "'" && selected != '"' && editor.getWrapBehavioursEnabled()) {
return {
text: quote + selected + quote,
selection: false
};
} else {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
var leftChar = line.substring(cursor.column-1, cursor.column);
if (leftChar == '\\') {
return null;
}
var tokens = session.getTokens(selection.start.row);
var col = 0, token;
var quotepos = -1; // Track whether we're inside an open quote.
for (var x = 0; x < tokens.length; x++) {
token = tokens[x];
if (token.type == "string") {
quotepos = -1;
} else if (quotepos < 0) {
quotepos = token.value.indexOf(quote);
}
if ((token.value.length + col) > selection.start.column) {
break;
}
col += tokens[x].value.length;
}
if (!token || (quotepos < 0 && token.type !== "comment" && (token.type !== "string" || ((selection.start.column !== token.value.length+col-1) && token.value.lastIndexOf(quote) === token.value.length-1)))) {
if (!CstyleBehaviour.isSaneInsertion(editor, session))
return;
return {
text: quote + quote,
selection: [1,1]
};
} else if (token && token.type === "string") {
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == quote) {
return {
text: '',
selection: [1, 1]
};
}
}
}
}
});
this.add("string_dquotes", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && (selected == '"' || selected == "'")) {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
if (rightChar == selected) {
range.end.column++;
return range;
}
}
});
};
CstyleBehaviour.isSaneInsertion = function(editor, session) {
var cursor = editor.getCursorPosition();
var iterator = new TokenIterator(session, cursor.row, cursor.column);
if (!this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) {
var iterator2 = new TokenIterator(session, cursor.row, cursor.column + 1);
if (!this.$matchTokenType(iterator2.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS))
return false;
}
iterator.stepForward();
return iterator.getCurrentTokenRow() !== cursor.row ||
this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_BEFORE_TOKENS);
};
CstyleBehaviour.$matchTokenType = function(token, types) {
return types.indexOf(token.type || token) > -1;
};
CstyleBehaviour.recordAutoInsert = function(editor, session, bracket) {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
if (!this.isAutoInsertedClosing(cursor, line, context.autoInsertedLineEnd[0]))
context.autoInsertedBrackets = 0;
context.autoInsertedRow = cursor.row;
context.autoInsertedLineEnd = bracket + line.substr(cursor.column);
context.autoInsertedBrackets++;
};
CstyleBehaviour.recordMaybeInsert = function(editor, session, bracket) {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
if (!this.isMaybeInsertedClosing(cursor, line))
context.maybeInsertedBrackets = 0;
context.maybeInsertedRow = cursor.row;
context.maybeInsertedLineStart = line.substr(0, cursor.column) + bracket;
context.maybeInsertedLineEnd = line.substr(cursor.column);
context.maybeInsertedBrackets++;
};
CstyleBehaviour.isAutoInsertedClosing = function(cursor, line, bracket) {
return context.autoInsertedBrackets > 0 &&
cursor.row === context.autoInsertedRow &&
bracket === context.autoInsertedLineEnd[0] &&
line.substr(cursor.column) === context.autoInsertedLineEnd;
};
CstyleBehaviour.isMaybeInsertedClosing = function(cursor, line) {
return context.maybeInsertedBrackets > 0 &&
cursor.row === context.maybeInsertedRow &&
line.substr(cursor.column) === context.maybeInsertedLineEnd &&
line.substr(0, cursor.column) == context.maybeInsertedLineStart;
};
CstyleBehaviour.popAutoInsertedClosing = function() {
context.autoInsertedLineEnd = context.autoInsertedLineEnd.substr(1);
context.autoInsertedBrackets--;
};
CstyleBehaviour.clearMaybeInsertedClosing = function() {
if (context) {
context.maybeInsertedBrackets = 0;
context.maybeInsertedRow = -1;
}
};
oop.inherits(CstyleBehaviour, Behaviour);
exports.CstyleBehaviour = CstyleBehaviour;
});
ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../../lib/oop");
var Range = acequire("../../range").Range;
var BaseFoldMode = acequire("./fold_mode").FoldMode;
var FoldMode = exports.FoldMode = function(commentRegex) {
if (commentRegex) {
this.foldingStartMarker = new RegExp(
this.foldingStartMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.start)
);
this.foldingStopMarker = new RegExp(
this.foldingStopMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.end)
);
}
};
oop.inherits(FoldMode, BaseFoldMode);
(function() {
this.foldingStartMarker = /(\{|\[)[^\}\]]*$|^\s*(\/\*)/;
this.foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/;
this.getFoldWidgetRange = function(session, foldStyle, row, forceMultiline) {
var line = session.getLine(row);
var match = line.match(this.foldingStartMarker);
if (match) {
var i = match.index;
if (match[1])
return this.openingBracketBlock(session, match[1], row, i);
var range = session.getCommentFoldRange(row, i + match[0].length, 1);
if (range && !range.isMultiLine()) {
if (forceMultiline) {
range = this.getSectionRange(session, row);
} else if (foldStyle != "all")
range = null;
}
return range;
}
if (foldStyle === "markbegin")
return;
var match = line.match(this.foldingStopMarker);
if (match) {
var i = match.index + match[0].length;
if (match[1])
return this.closingBracketBlock(session, match[1], row, i);
return session.getCommentFoldRange(row, i, -1);
}
};
this.getSectionRange = function(session, row) {
var line = session.getLine(row);
var startIndent = line.search(/\S/);
var startRow = row;
var startColumn = line.length;
row = row + 1;
var endRow = row;
var maxRow = session.getLength();
while (++row < maxRow) {
line = session.getLine(row);
var indent = line.search(/\S/);
if (indent === -1)
continue;
if (startIndent > indent)
break;
var subRange = this.getFoldWidgetRange(session, "all", row);
if (subRange) {
if (subRange.start.row <= startRow) {
break;
} else if (subRange.isMultiLine()) {
row = subRange.end.row;
} else if (startIndent == indent) {
break;
}
}
endRow = row;
}
return new Range(startRow, startColumn, endRow, session.getLine(endRow).length);
};
}).call(FoldMode.prototype);
});
ace.define("ace/mode/javascript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/javascript_highlight_rules","ace/mode/matching_brace_outdent","ace/range","ace/worker/worker_client","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../lib/oop");
var TextMode = acequire("./text").Mode;
var JavaScriptHighlightRules = acequire("./javascript_highlight_rules").JavaScriptHighlightRules;
var MatchingBraceOutdent = acequire("./matching_brace_outdent").MatchingBraceOutdent;
var Range = acequire("../range").Range;
var WorkerClient = acequire("../worker/worker_client").WorkerClient;
var CstyleBehaviour = acequire("./behaviour/cstyle").CstyleBehaviour;
var CStyleFoldMode = acequire("./folding/cstyle").FoldMode;
var Mode = function() {
this.HighlightRules = JavaScriptHighlightRules;
this.$outdent = new MatchingBraceOutdent();
this.$behaviour = new CstyleBehaviour();
this.foldingRules = new CStyleFoldMode();
};
oop.inherits(Mode, TextMode);
(function() {
this.lineCommentStart = "//";
this.blockComment = {start: "/*", end: "*/"};
this.getNextLineIndent = function(state, line, tab) {
var indent = this.$getIndent(line);
var tokenizedLine = this.getTokenizer().getLineTokens(line, state);
var tokens = tokenizedLine.tokens;
var endState = tokenizedLine.state;
if (tokens.length && tokens[tokens.length-1].type == "comment") {
return indent;
}
if (state == "start" || state == "no_regex") {
var match = line.match(/^.*(?:\bcase\b.*\:|[\{\(\[])\s*$/);
if (match) {
indent += tab;
}
} else if (state == "doc-start") {
if (endState == "start" || endState == "no_regex") {
return "";
}
var match = line.match(/^\s*(\/?)\*/);
if (match) {
if (match[1]) {
indent += " ";
}
indent += "* ";
}
}
return indent;
};
this.checkOutdent = function(state, line, input) {
return this.$outdent.checkOutdent(line, input);
};
this.autoOutdent = function(state, doc, row) {
this.$outdent.autoOutdent(doc, row);
};
// this.createWorker = function(session) {
// var worker = new WorkerClient(["ace"], "ace/mode/javascript_worker", "JavaScriptWorker");
// worker.attachToDocument(session.getDocument());
//
// worker.on("jslint", function(results) {
// session.setAnnotations(results.data);
// });
//
// worker.on("terminate", function() {
// session.clearAnnotations();
// });
//
// return worker;
// };
this.$id = "ace/mode/javascript";
}).call(Mode.prototype);
exports.Mode = Mode;
});

View File

@@ -15,26 +15,24 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.account {
max-height: 36px;
padding: 4px 0 0 0;
line-height: 32px;
margin-top: 11px;
}
.details {
.name {
height: 32px;
line-height: 32px;
display: inline-block;
vertical-align: top;
text-transform: uppercase;
padding: 0 0 0 1em;
}
.image {
display: inline-block;
max-height: 32px;
}
.name {
line-height: 32px;
vertical-align: top;
text-transform: uppercase;
height: 32px;
width: 32px;
margin: 0;
z-index: 10;
}
.icon {
@@ -50,3 +48,8 @@
.container {
position: relative;
}
.menuItem {
min-height: 0 !important;
line-height: inherit !important;
}

View File

@@ -16,6 +16,7 @@
import React, { Component, PropTypes } from 'react';
import { MenuItem } from 'material-ui';
import { isEqual } from 'lodash';
import AutoComplete from '../AutoComplete';
import IdentityIcon from '../../IdentityIcon';
@@ -24,57 +25,82 @@ import IdentityName from '../../IdentityName';
import styles from './addressSelect.css';
export default class AddressSelect extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
static propTypes = {
disabled: PropTypes.bool,
accounts: PropTypes.object,
contacts: PropTypes.object,
contracts: PropTypes.object,
label: PropTypes.string,
hint: PropTypes.string,
error: PropTypes.string,
value: PropTypes.string,
tokens: PropTypes.object,
onChange: PropTypes.func.isRequired
onChange: PropTypes.func.isRequired,
allowInput: PropTypes.bool
}
state = {
entries: {},
addresses: [],
value: ''
}
entriesFromProps (props = this.props) {
const { accounts, contacts, contracts } = props;
const entries = Object.assign({}, accounts || {}, contacts || {}, contracts || {});
return entries;
}
componentWillMount () {
const { accounts, contacts, value } = this.props;
const entries = Object.assign({}, accounts || {}, contacts || {});
this.setState({ entries, value });
const { value } = this.props;
const entries = this.entriesFromProps();
const addresses = Object.keys(entries).sort();
this.setState({ entries, addresses, value });
}
componentWillReceiveProps (newProps) {
const { accounts, contacts } = newProps;
const entries = Object.assign({}, accounts || {}, contacts || {});
this.setState({ entries });
const entries = this.entriesFromProps();
const addresses = Object.keys(entries).sort();
if (!isEqual(addresses, this.state.addresses)) {
this.setState({ entries, addresses });
}
if (newProps.value !== this.props.value) {
this.setState({ value: newProps.value });
}
}
render () {
const { disabled, error, hint, label } = this.props;
const { entries } = this.state;
const value = this.getSearchText();
const { allowInput, disabled, error, hint, label } = this.props;
const { entries, value } = this.state;
const searchText = this.getSearchText();
const icon = this.renderIdentityIcon(value);
return (
<div className={ styles.container }>
<AutoComplete
className={ (error || !value) ? '' : styles.paddedInput }
className={ !icon ? '' : styles.paddedInput }
disabled={ disabled }
label={ label }
hint={ hint ? `search for ${hint}` : 'search for an address' }
error={ error }
onChange={ this.onChange }
value={ value }
onBlur={ this.onBlur }
onUpdateInput={ allowInput && this.onUpdateInput }
value={ searchText }
filter={ this.handleFilter }
entries={ entries }
entry={ this.getEntry() || {} }
renderItem={ this.renderItem }
/>
{ this.renderIdentityIcon(value) }
{ icon }
</div>
);
}
@@ -82,7 +108,7 @@ export default class AddressSelect extends Component {
renderIdentityIcon (inputValue) {
const { error, value } = this.props;
if (error || !inputValue) {
if (error || !inputValue || value.length !== 42) {
return null;
}
@@ -96,29 +122,28 @@ export default class AddressSelect extends Component {
renderItem = (entry) => {
return {
text: entry.address,
value: this.renderSelectEntry(entry)
text: entry.name && entry.name.toUpperCase() || entry.address,
value: this.renderSelectEntry(entry),
address: entry.address
};
}
renderSelectEntry = (entry) => {
const item = (
<div className={ styles.account }>
<div className={ styles.image }>
<IdentityIcon
inline center
address={ entry.address } />
</div>
<div className={ styles.details }>
<div className={ styles.name }>
<IdentityName address={ entry.address } />
</div>
</div>
<IdentityIcon
className={ styles.image }
inline center
address={ entry.address } />
<IdentityName
className={ styles.name }
address={ entry.address } />
</div>
);
return (
<MenuItem
className={ styles.menuItem }
key={ entry.address }
value={ entry.address }
label={ item }>
@@ -129,32 +154,48 @@ export default class AddressSelect extends Component {
getSearchText () {
const entry = this.getEntry();
if (!entry) return '';
const { value } = this.state;
return entry.name ? entry.name.toUpperCase() : '';
return entry && entry.name
? entry.name.toUpperCase()
: value;
}
getEntry () {
const { value } = this.props;
if (!value) return '';
const { entries } = this.state;
return entries[value];
const { entries, value } = this.state;
return value ? entries[value] : null;
}
handleFilter = (searchText, address) => {
handleFilter = (searchText, name, item) => {
const { address } = item;
const entry = this.state.entries[address];
const lowCaseSearch = searchText.toLowerCase();
return [ entry.name, entry.address ]
return [entry.name, entry.address]
.some(text => text.toLowerCase().indexOf(lowCaseSearch) !== -1);
}
onChange = (entry, empty) => {
const { allowInput } = this.props;
const { value } = this.state;
const address = entry && entry.address
? entry.address
: (empty ? '' : this.state.value);
: ((empty && !allowInput) ? '' : value);
this.props.onChange(null, address);
}
onUpdateInput = (query, choices) => {
const { api } = this.context;
const address = query.trim();
if (!/^0x/.test(address) && api.util.isAddressValid(`0x${address}`)) {
const checksumed = api.util.toChecksumAddress(`0x${address}`);
return this.props.onChange(null, checksumed);
}
this.props.onChange(null, address);
};
}

View File

@@ -15,12 +15,14 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import keycode from 'keycode';
import { MenuItem, AutoComplete as MUIAutoComplete } from 'material-ui';
import { PopoverAnimationVertical } from 'material-ui/Popover';
export default class AutoComplete extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
onUpdateInput: PropTypes.func,
disabled: PropTypes.bool,
label: PropTypes.string,
hint: PropTypes.string,
@@ -39,11 +41,12 @@ export default class AutoComplete extends Component {
state = {
lastChangedValue: undefined,
entry: null,
open: false
open: false,
fakeBlur: false
}
render () {
const { disabled, error, hint, label, value, className, filter } = this.props;
const { disabled, error, hint, label, value, className, filter, onUpdateInput } = this.props;
const { open } = this.state;
return (
@@ -54,11 +57,11 @@ export default class AutoComplete extends Component {
hintText={ hint }
errorText={ error }
onNewRequest={ this.onChange }
onUpdateInput={ onUpdateInput }
searchText={ value }
onFocus={ this.onFocus }
onBlur={ this.onBlur }
animation={ PopoverAnimationVertical }
filter={ filter }
popoverProps={ { open } }
openOnFocus
@@ -66,6 +69,9 @@ export default class AutoComplete extends Component {
fullWidth
floatingLabelFixed
dataSource={ this.getDataSource() }
menuProps={ { maxHeight: 400 } }
ref='muiAutocomplete'
onKeyDown={ this.onKeyDown }
/>
);
}
@@ -90,6 +96,30 @@ export default class AutoComplete extends Component {
}));
}
onKeyDown = (event) => {
const { muiAutocomplete } = this.refs;
switch (keycode(event)) {
case 'down':
const { menu } = muiAutocomplete.refs;
menu.handleKeyDown(event);
this.setState({ fakeBlur: true });
break;
case 'enter':
case 'tab':
event.preventDefault();
event.stopPropagation();
event.which = 'useless';
const e = new CustomEvent('down');
e.which = 40;
muiAutocomplete.handleKeyDown(e);
break;
}
}
onChange = (item, idx) => {
if (idx === -1) {
return;
@@ -107,12 +137,23 @@ export default class AutoComplete extends Component {
this.setState({ entry, open: false });
}
onBlur = () => {
window.setTimeout(() => {
const { entry } = this.state;
onBlur = (event) => {
const { onUpdateInput } = this.props;
this.handleOnChange(entry);
}, 100);
// TODO: Handle blur gracefully where we use onUpdateInput (currently replaces
// input where text is allowed with the last selected value from the dropdown)
if (!onUpdateInput) {
window.setTimeout(() => {
const { entry, fakeBlur } = this.state;
if (fakeBlur) {
this.setState({ fakeBlur: false });
return;
}
this.handleOnChange(entry);
}, 200);
}
}
onFocus = () => {
@@ -131,5 +172,4 @@ export default class AutoComplete extends Component {
this.props.onChange(value, empty);
}
}
}

View File

@@ -14,18 +14,14 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.inputselect {
.container {
display: flex;
flex-direction: row;
align-items: flex-end;
position: relative;
}
.inputselect svg {
padding-right: 84px;
}
.toggle {
position: absolute !important;
top: 38px;
right: 0;
display: inline-block !important;
width: auto !important;
.copy {
margin-right: 0.5em;
}

View File

@@ -15,12 +15,21 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { TextField } from 'material-ui';
import CopyToClipboard from '../../CopyToClipboard';
import styles from './input.css';
// TODO: duplicated in Select
const UNDERLINE_DISABLED = {
borderColor: 'rgba(255, 255, 255, 0.298039)' // 'transparent' // 'rgba(255, 255, 255, 0.298039)'
borderBottom: 'dotted 2px',
borderColor: 'rgba(255, 255, 255, 0.125)' // 'transparent' // 'rgba(255, 255, 255, 0.298039)'
};
const UNDERLINE_READONLY = {
...UNDERLINE_DISABLED,
cursor: 'text'
};
const UNDERLINE_NORMAL = {
@@ -34,6 +43,12 @@ export default class Input extends Component {
children: PropTypes.node,
className: PropTypes.string,
disabled: PropTypes.bool,
readOnly: PropTypes.bool,
allowCopy: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool
]),
floatCopy: PropTypes.bool,
error: PropTypes.string,
hint: PropTypes.string,
label: PropTypes.string,
@@ -45,13 +60,18 @@ export default class Input extends Component {
rows: PropTypes.number,
type: PropTypes.string,
submitOnBlur: PropTypes.bool,
hideUnderline: PropTypes.bool,
value: PropTypes.oneOfType([
PropTypes.number, PropTypes.string
])
}
};
static defaultProps = {
submitOnBlur: true
submitOnBlur: true,
readOnly: false,
allowCopy: false,
hideUnderline: false,
floatCopy: false
}
state = {
@@ -66,31 +86,92 @@ export default class Input extends Component {
render () {
const { value } = this.state;
const { children, className, disabled, error, label, hint, multiLine, rows, type } = this.props;
const { children, className, hideUnderline, disabled, error, label, hint, multiLine, rows, type } = this.props;
const readOnly = this.props.readOnly || disabled;
const inputStyle = { overflow: 'hidden' };
const textFieldStyle = {};
if (readOnly) {
inputStyle.cursor = 'text';
}
if (hideUnderline && !hint) {
textFieldStyle.height = 'initial';
}
return (
<TextField
autoComplete='off'
className={ className }
disabled={ disabled }
errorText={ error }
floatingLabelFixed
floatingLabelText={ label }
fullWidth
hintText={ hint }
multiLine={ multiLine }
name={ NAME_ID }
id={ NAME_ID }
rows={ rows }
type={ type || 'text' }
underlineDisabledStyle={ UNDERLINE_DISABLED }
underlineStyle={ UNDERLINE_NORMAL }
value={ value }
onBlur={ this.onBlur }
onChange={ this.onChange }
onKeyDown={ this.onKeyDown }>
{ children }
</TextField>
<div className={ styles.container }>
{ this.renderCopyButton() }
<TextField
autoComplete='off'
className={ className }
style={ textFieldStyle }
readOnly={ readOnly }
errorText={ error }
floatingLabelFixed
floatingLabelText={ label }
fullWidth
hintText={ hint }
multiLine={ multiLine }
name={ NAME_ID }
id={ NAME_ID }
rows={ rows }
type={ type || 'text' }
underlineDisabledStyle={ UNDERLINE_DISABLED }
underlineStyle={ readOnly ? UNDERLINE_READONLY : UNDERLINE_NORMAL }
underlineFocusStyle={ readOnly ? { display: 'none' } : null }
underlineShow={ !hideUnderline }
value={ value }
onBlur={ this.onBlur }
onChange={ this.onChange }
onKeyDown={ this.onKeyDown }
inputStyle={ inputStyle }
>
{ children }
</TextField>
</div>
);
}
renderCopyButton () {
const { allowCopy, hideUnderline, label, hint, floatCopy } = this.props;
const { value } = this.state;
if (!allowCopy) {
return null;
}
const style = {
marginBottom: 13
};
const text = typeof allowCopy === 'string'
? allowCopy
: value;
if (hideUnderline && !label) {
style.marginBottom = 2;
} else if (label && !hint) {
style.marginBottom = 4;
} else if (label && hint) {
style.marginBottom = 10;
}
if (floatCopy) {
style.position = 'absolute';
style.left = -24;
style.bottom = style.marginBottom;
style.marginBottom = 0;
}
return (
<div className={ styles.copy } style={ style }>
<CopyToClipboard data={ text } />
</div>
);
}
@@ -130,8 +211,6 @@ export default class Input extends Component {
}
setValue (value) {
this.setState({
value
});
this.setState({ value });
}
}

View File

@@ -26,7 +26,16 @@
padding-left: 0 !important;
}
.icon {
.icon,
.iconDisabled {
position: absolute;
top: 35px;
}
.icon {
left: 0;
}
.iconDisabled {
left: 24px;
}

View File

@@ -20,6 +20,7 @@ import { bindActionCreators } from 'redux';
import Input from '../Input';
import IdentityIcon from '../../IdentityIcon';
import util from '../../../api/util';
import styles from './inputAddress.css';
@@ -38,44 +39,17 @@ class InputAddress extends Component {
onSubmit: PropTypes.func
};
state = {
isEmpty: false
}
componentWillMount () {
const { value, text, accountsInfo, tokens } = this.props;
const account = accountsInfo[value] || tokens[value];
const hasAccount = account && (!account.meta || !account.meta.deleted);
const inputValue = text && hasAccount ? account.name : value;
const isEmpty = (!inputValue || inputValue.length === 0);
this.setState({ isEmpty });
}
componentWillReceiveProps (newProps) {
const { value, text } = newProps;
if (value === this.props.value && text === this.props.text) {
return;
}
const inputValue = text || value;
const isEmpty = (!inputValue || inputValue.length === 0);
this.setState({ isEmpty });
}
render () {
const { className, disabled, error, label, hint, value, text, onSubmit, accountsInfo, tokens } = this.props;
const { isEmpty } = this.state;
const classes = [ className ];
classes.push(isEmpty ? styles.inputEmpty : styles.input);
const account = accountsInfo[value] || tokens[value];
const hasAccount = account && (!account.meta || !account.meta.deleted);
const icon = this.renderIcon();
const classes = [ className ];
classes.push(!icon ? styles.inputEmpty : styles.input);
return (
<div className={ styles.container }>
<Input
@@ -86,21 +60,23 @@ class InputAddress extends Component {
error={ error }
value={ text && hasAccount ? account.name : value }
onChange={ this.handleInputChange }
onSubmit={ onSubmit } />
{ this.renderIcon() }
onSubmit={ onSubmit }
allowCopy={ disabled ? value : false }
/>
{ icon }
</div>
);
}
renderIcon () {
const { value } = this.props;
const { value, disabled } = this.props;
if (!value || !value.length) {
if (!value || !value.length || !util.isAddressValid(value)) {
return null;
}
return (
<div className={ styles.icon }>
<div className={ disabled ? styles.iconDisabled : styles.icon }>
<IdentityIcon
inline center
address={ value } />
@@ -112,6 +88,11 @@ class InputAddress extends Component {
const isEmpty = (value.length === 0);
this.setState({ isEmpty });
if (!/^0x/.test(value) && util.isAddressValid(`0x${value}`)) {
return this.props.onChange(event, `0x${value}`);
}
this.props.onChange(event, value);
}
}

View File

@@ -17,103 +17,46 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Toggle } from 'material-ui';
import AddressSelect from '../AddressSelect';
import InputAddress from '../InputAddress';
import styles from './inputAddressSelect.css';
class InputAddressSelect extends Component {
static propTypes = {
accounts: PropTypes.object,
contacts: PropTypes.object,
disabled: PropTypes.bool,
editing: PropTypes.bool,
accounts: PropTypes.object.isRequired,
contacts: PropTypes.object.isRequired,
contracts: PropTypes.object.isRequired,
error: PropTypes.string,
label: PropTypes.string,
hint: PropTypes.string,
value: PropTypes.string,
tokens: PropTypes.object,
onChange: PropTypes.func
};
state = {
editing: this.props.editing || false,
entries: []
}
render () {
const { editing } = this.state;
return (
<div className={ styles.inputselect }>
{ editing ? this.renderInput() : this.renderSelect() }
<Toggle
className={ styles.toggle }
label='Edit'
labelPosition='right'
toggled={ editing }
onToggle={ this.onToggle } />
</div>
);
}
renderInput () {
const { disabled, error, hint, label, value, tokens } = this.props;
return (
<InputAddress
disabled={ disabled }
error={ error }
hint={ hint }
label={ label }
value={ value }
tokens={ tokens }
onChange={ this.onChangeInput } />
);
}
renderSelect () {
const { accounts, contacts, disabled, error, hint, label, value, tokens } = this.props;
const { accounts, contacts, contracts, label, hint, error, value, onChange } = this.props;
return (
<AddressSelect
allowInput
accounts={ accounts }
contacts={ contacts }
disabled={ disabled }
contracts={ contracts }
error={ error }
label={ label }
hint={ hint }
error={ error }
value={ value }
tokens={ tokens }
onChange={ this.onChangeSelect } />
onChange={ onChange } />
);
}
onToggle = () => {
const { editing } = this.state;
this.setState({
editing: !editing
});
}
onChangeInput = (event, value) => {
this.props.onChange(event, value);
}
onChangeSelect = (event, value) => {
this.props.onChange(event, value);
}
}
function mapStateToProps (state) {
const { accounts, contacts } = state.personal;
const { accounts, contacts, contracts } = state.personal;
return {
accounts,
contacts
contacts,
contracts
};
}

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './inputChip';

View File

@@ -0,0 +1,26 @@
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.chip {
& > svg {
width: 1rem !important;
height: 1rem !important;
margin: initial !important;
margin-right: 4px !important;
padding: 2px 0 !important;
}
}

View File

@@ -0,0 +1,198 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { Chip } from 'material-ui';
import ChipInput from 'material-ui-chip-input';
import { blue300 } from 'material-ui/styles/colors';
import { uniq } from 'lodash';
import styles from './inputChip.css';
export default class InputChip extends Component {
static propTypes = {
tokens: PropTypes.array.isRequired,
className: PropTypes.string,
hint: PropTypes.string,
label: PropTypes.string,
onTokensChange: PropTypes.func,
onInputChange: PropTypes.func,
onBlur: PropTypes.func,
addOnBlur: PropTypes.bool,
clearOnBlur: PropTypes.bool
}
static defaultProps = {
clearOnBlur: false,
addOnBlur: false
}
state = {
focused: false
}
render () {
const { clearOnBlur, className, hint, label, tokens } = this.props;
const { focused } = this.state;
const classes = `${className}`;
const textFieldStyle = {
height: 55
};
if (!focused) {
textFieldStyle.width = 0;
}
return (
<ChipInput
className={ classes }
ref='chipInput'
value={ tokens }
clearOnBlur={ clearOnBlur }
floatingLabelText={ label }
hintText={ hint }
chipRenderer={ this.chipRenderer }
onFocus={ this.handleFocus }
onBlur={ this.handleBlur }
onRequestAdd={ this.handleTokenAdd }
onRequestDelete={ this.handleTokenDelete }
onUpdateInput={ this.handleInputChange }
floatingLabelFixed
fullWidth
hintStyle={ {
bottom: 13,
left: 0,
transition: 'none'
} }
inputStyle={ {
marginBottom: 18,
width: 'initial'
} }
textFieldStyle={ textFieldStyle }
underlineStyle={ {
borderWidth: 2
} }
/>
);
}
chipRenderer = (state, key) => {
const { value, isFocused, isDisabled, handleClick, handleRequestDelete } = state;
return (
<Chip
key={ key }
className={ styles.chip }
style={ {
margin: '15px 8px 0 0',
float: 'left',
pointerEvents: isDisabled ? 'none' : undefined,
alignItems: 'center'
} }
labelStyle={ {
paddingRight: 6,
fontSize: '0.9rem',
lineHeight: 'initial'
} }
backgroundColor={ isFocused ? blue300 : 'rgba(50, 50, 50, 0.73)' }
onTouchTap={ handleClick }
onRequestDelete={ handleRequestDelete }
>
{ value }
</Chip>
);
}
handleFocus = () => {
this.setState({ focused: true });
}
handleBlur = () => {
const { onBlur, addOnBlur } = this.props;
this.setState({ focused: false });
if (addOnBlur) {
const { inputValue } = this.refs.chipInput.state;
this.handleTokenAdd(inputValue);
}
if (typeof onBlur === 'function') {
onBlur();
}
}
handleTokenAdd = (value) => {
const { tokens, onInputChange } = this.props;
const newTokens = uniq([].concat(tokens, value));
this.handleTokensChange(newTokens);
if (value === this.refs.chipInput.state.inputValue) {
if (typeof onInputChange === 'function') {
onInputChange('');
}
this.refs.chipInput.setState({ inputValue: '' });
}
}
handleTokenDelete = (value) => {
const { tokens } = this.props;
const newTokens = uniq([]
.concat(tokens)
.filter(v => v !== value));
this.handleTokensChange(newTokens);
this.refs.chipInput.focus();
}
handleInputChange = (value) => {
const { onInputChange } = this.props;
const splitTokens = value.split(/[\s,;]/);
const inputValue = (splitTokens.length <= 1)
? value
: splitTokens.slice(-1)[0].trim();
this.refs.chipInput.setState({ inputValue });
if (splitTokens.length > 1) {
const tokensToAdd = splitTokens.slice(0, -1);
tokensToAdd.forEach(token => this.handleTokenAdd(token));
}
if (typeof onInputChange === 'function') {
onInputChange(inputValue);
}
}
handleTokensChange = (tokens) => {
const { onTokensChange } = this.props;
onTokensChange(tokens.filter(token => token && token.length > 0));
}
}

View File

@@ -19,6 +19,7 @@ import FormWrap from './FormWrap';
import Input from './Input';
import InputAddress from './InputAddress';
import InputAddressSelect from './InputAddressSelect';
import InputChip from './InputChip';
import InputInline from './InputInline';
import Select from './Select';
@@ -29,6 +30,7 @@ export {
Input,
InputAddress,
InputAddressSelect,
InputChip,
InputInline,
Select
};

View File

@@ -59,10 +59,9 @@ class IdentityIcon extends Component {
updateIcon (_address, images) {
const { api } = this.context;
const { button, inline, tiny } = this.props;
const iconsrc = images[_address];
if (iconsrc) {
this.setState({ iconsrc });
if (images[_address]) {
this.setState({ iconsrc: `${api.dappsUrl}${images[_address]}` });
return;
}

View File

@@ -19,12 +19,11 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Contracts from '../../contracts';
import IdentityIcon from '../IdentityIcon';
import IdentityName from '../IdentityName';
import { Input, InputAddress } from '../Form';
import { fetchBytecode, fetchMethod } from '../../redux/providers/blockchainActions';
import styles from './methodDecoding.css';
const CONTRACT_CREATE = '0x60606040';
@@ -41,12 +40,7 @@ class MethodDecoding extends Component {
address: PropTypes.string.isRequired,
tokens: PropTypes.object,
transaction: PropTypes.object,
historic: PropTypes.bool,
fetchBytecode: PropTypes.func,
fetchMethod: PropTypes.func,
bytecodes: PropTypes.object,
methods: PropTypes.object
historic: PropTypes.bool
}
state = {
@@ -63,58 +57,7 @@ class MethodDecoding extends Component {
}
componentWillMount () {
const { transaction } = this.props;
this.lookup(transaction);
}
componentDidMount () {
this.setMethod(this.props);
}
componentWillReceiveProps (newProps) {
const { transaction } = this.props;
this.setMethod(newProps);
if (newProps.transaction.hash !== transaction.hash) {
this.lookup(transaction);
return;
}
}
setMethod (props) {
const { bytecodes, methods } = props;
const { contractAddress, methodSignature, methodParams } = this.state;
if (contractAddress && bytecodes[contractAddress]) {
const bytecode = bytecodes[contractAddress];
if (bytecode && bytecode !== '0x') {
this.setState({ isContract: true });
}
}
if (methodSignature && methods[methodSignature]) {
const method = methods[methodSignature];
const { api } = this.context;
let methodInputs = null;
let methodName = null;
if (method && method.length) {
const abi = api.util.methodToAbi(method);
methodName = abi.name;
methodInputs = api.util
.decodeMethodInput(abi, methodParams)
.map((value, index) => {
const type = abi.inputs[index].type;
return { type, value };
});
}
this.setState({ method, methodName, methodInputs });
}
this.lookup();
}
render () {
@@ -139,7 +82,7 @@ class MethodDecoding extends Component {
return (
<div className={ styles.gasDetails }>
{ historic ? 'Used' : 'Will use' } <span className={ styles.highlight }>{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)</span> for a total transaction cost of <span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
{ historic ? 'Provided' : 'Provides' } <span className={ styles.highlight }>{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)</span> for a total transaction value of <span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
</div>
);
}
@@ -268,7 +211,8 @@ class MethodDecoding extends Component {
default:
return (
<Input
disabled
readOnly
allowCopy
key={ index }
className={ styles.input }
value={ this.renderValue(input.value) }
@@ -320,7 +264,9 @@ class MethodDecoding extends Component {
);
}
lookup (transaction) {
lookup () {
const { transaction } = this.props;
if (!transaction) {
return;
}
@@ -346,26 +292,51 @@ class MethodDecoding extends Component {
return;
}
const { fetchBytecode, fetchMethod } = this.props;
Promise
.all([
api.eth.getCode(contractAddress),
Contracts.get().signatureReg.lookup(signature)
])
.then(([bytecode, method]) => {
let methodInputs = null;
let methodName = null;
fetchBytecode(contractAddress);
fetchMethod(signature);
if (method && method.length) {
const { methodParams } = this.state;
const abi = api.util.methodToAbi(method);
methodName = abi.name;
methodInputs = api.util
.decodeMethodInput(abi, methodParams)
.map((value, index) => {
const type = abi.inputs[index].type;
return { type, value };
});
}
this.setState({
method,
methodName,
methodInputs,
bytecode,
isContract: bytecode && bytecode !== '0x'
});
})
.catch((error) => {
console.warn('lookup', error);
});
}
}
function mapStateToProps (state) {
const { tokens } = state.balances;
const { bytecodes, methods } = state.blockchain;
return {
tokens, bytecodes, methods
};
return { tokens };
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
fetchBytecode, fetchMethod
}, dispatch);
return bindActionCreators({}, dispatch);
}
export default connect(

View File

@@ -22,16 +22,17 @@ import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
const lightTheme = getMuiTheme(lightBaseTheme);
const muiTheme = getMuiTheme(darkBaseTheme);
muiTheme.stepper.textColor = '#eee';
muiTheme.stepper.disabledTextColor = '#777';
muiTheme.inkBar.backgroundColor = 'transparent';
muiTheme.paper.backgroundColor = 'rgba(0, 0, 0, 0.95)';
muiTheme.raisedButton.primaryTextColor = 'white';
muiTheme.snackbar.backgroundColor = 'rgba(255, 30, 30, 0.9)';
muiTheme.snackbar.textColor = 'rgba(255, 255, 255, 0.75)';
muiTheme.stepper.textColor = '#eee';
muiTheme.stepper.disabledTextColor = '#777';
muiTheme.tabs = lightTheme.tabs;
muiTheme.tabs.backgroundColor = 'transparent';
muiTheme.tabs.selectedTextColor = 'white';
muiTheme.tabs.textColor = 'rgba(255, 255, 255, 0.5)'; // 'rgb(0, 151, 167)';
muiTheme.tabs.textColor = 'rgba(255, 255, 255, 0.5)';
muiTheme.textField.floatingLabelColor = 'rgba(255, 255, 255, 0.5)';
muiTheme.textField.hintColor = 'rgba(255, 255, 255, 0.5)';
muiTheme.textField.disabledTextColor = muiTheme.textField.textColor;

View File

@@ -25,7 +25,7 @@
left: 0;
bottom: 0;
right: 0;
background: transparent;
background: rgba(0, 0, 0, 0.25);
z-index: 499;
}

View File

@@ -23,6 +23,10 @@ import { nextTooltip } from './actions';
import styles from './tooltips.css';
class Tooltips extends Component {
static contextTypes = {
router: PropTypes.object.isRequired
};
static propTypes = {
currentId: PropTypes.number,
closed: PropTypes.bool,
@@ -33,6 +37,22 @@ class Tooltips extends Component {
const { onNextTooltip } = this.props;
onNextTooltip();
this.redirect();
}
componentWillReceiveProps (nextProps) {
if (nextProps.currentId !== this.props.currentId) {
this.redirect(nextProps);
}
}
redirect (props = this.props) {
const { currentId } = props;
if (currentId !== undefined && currentId !== -1) {
const viewLink = '/accounts/';
this.context.router.push(viewLink);
}
}
render () {

View File

@@ -23,6 +23,7 @@
.hash {
padding-top: 1em;
word-break: break-all;
}
.confirm {
@@ -31,11 +32,13 @@
}
.progressbar {
margin: 0.5em !important;
margin: 0.5em 0 !important;
width: 30% !important;
min-width: 220px;
display: inline-block !important;
height: 0.75em !important;
}
.progressinfo {
text-align: center;
}

View File

@@ -19,6 +19,7 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { LinearProgress } from 'material-ui';
import { txLink } from '../../3rdparty/etherscan/links';
import styles from './txHash.css';
@@ -29,7 +30,8 @@ class TxHash extends Component {
static propTypes = {
hash: PropTypes.string.isRequired,
isTest: PropTypes.bool
isTest: PropTypes.bool,
summary: PropTypes.bool
}
state = {
@@ -54,16 +56,22 @@ class TxHash extends Component {
}
render () {
const { hash, isTest } = this.props;
const link = `https://${isTest ? 'testnet.' : ''}etherscan.io/tx/${hash}`;
const { hash, isTest, summary } = this.props;
let header = null;
return (
<div className={ styles.details }>
if (!summary) {
header = (
<div className={ styles.header }>
The transaction has been posted to the network with a transaction hash of
</div>
);
}
return (
<div className={ styles.details }>
{ header }
<div className={ styles.hash }>
<a href={ link } target='_blank'>{ hash }</a>
<a href={ txLink(hash, isTest) } target='_blank'>{ hash }</a>
</div>
{ this.renderConfirmations() }
</div>

View File

@@ -16,16 +16,20 @@
import Actionbar from './Actionbar';
import ActionbarExport from './Actionbar/Export';
import ActionbarImport from './Actionbar/Import';
import ActionbarSearch from './Actionbar/Search';
import ActionbarSort from './Actionbar/Sort';
import Badge from './Badge';
import Balance from './Balance';
import BlockStatus from './BlockStatus';
import Button from './Button';
import ConfirmDialog from './ConfirmDialog';
import Container, { Title as ContainerTitle } from './Container';
import ContextProvider from './ContextProvider';
import CopyToClipboard from './CopyToClipboard';
import Editor from './Editor';
import Errors from './Errors';
import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputInline, Select } from './Form';
import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select } from './Form';
import IdentityIcon from './IdentityIcon';
import IdentityName from './IdentityName';
import MethodDecoding from './MethodDecoding';
@@ -41,22 +45,27 @@ import TxHash from './TxHash';
export {
Actionbar,
ActionbarExport,
ActionbarImport,
ActionbarSearch,
ActionbarSort,
AddressSelect,
Badge,
Balance,
BlockStatus,
Button,
ConfirmDialog,
Container,
ContainerTitle,
ContextProvider,
CopyToClipboard,
Editor,
Errors,
Form,
FormWrap,
Input,
InputAddress,
InputAddressSelect,
InputChip,
InputInline,
Select,
IdentityIcon,