Merge branch 'master' into ui-2

# Conflicts:
#	js/src/shared/redux/middleware.js
#	js/src/shared/redux/providers/registry/middleware.js
#	js/src/shell/Application/application.js
#	js/src/ui/Actionbar/actionbar.js
#	js/src/ui/Button/button.js
#	js/src/ui/Form/AddressSelect/addressSelect.js
#	js/src/ui/Form/Input/input.js
#	js/src/ui/MethodDecoding/methodDecoding.js
This commit is contained in:
Jaco Greeff 2017-05-10 17:29:07 +02:00
commit 566b6a1967
19 changed files with 493 additions and 129 deletions

2
Cargo.lock generated
View File

@ -1781,7 +1781,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/paritytech/js-precompiled.git#cff0aec1877a4b75f51de3facee9fe439a41a90d" source = "git+https://github.com/paritytech/js-precompiled.git#eef7f1ac0dfc44527ef1925968235c5e69add959"
dependencies = [ dependencies = [
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "1.7.70", "version": "1.7.73",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",

View File

@ -37,8 +37,8 @@ export default function (api, browserHistory, forEmbed = false) {
]; ];
if (!forEmbed) { if (!forEmbed) {
const certifications = new CertificationsMiddleware().toMiddleware(api); const certifications = new CertificationsMiddleware(api).toMiddleware();
const registry = new RegistryMiddleware(api); const registry = new RegistryMiddleware(api).toMiddleware();
middleware.push(certifications, registry); middleware.push(certifications, registry);
} }

View File

@ -241,7 +241,7 @@ export default class Balances {
// If syncing, only retrieve balances once every // If syncing, only retrieve balances once every
// few seconds // few seconds
if (syncing) { if (syncing || syncing === null) {
this.shortThrottledFetch.cancel(); this.shortThrottledFetch.cancel();
this.longThrottledFetch(skipNotifications); this.longThrottledFetch(skipNotifications);

View File

@ -58,10 +58,14 @@ const updatableFilter = (api, onFilter) => {
}; };
export default class CertificationsMiddleware { export default class CertificationsMiddleware {
toMiddleware (api) { constructor (api) {
const badgeReg = Contracts.get(api).badgeReg; this._api = api;
}
const contract = new Contract(api, CertifierABI); toMiddleware () {
const badgeReg = Contracts.get(this._api).badgeReg;
const contract = new Contract(this._api, CertifierABI);
const Confirmed = contract.events.find((e) => e.name === 'Confirmed'); const Confirmed = contract.events.find((e) => e.name === 'Confirmed');
const Revoked = contract.events.find((e) => e.name === 'Revoked'); const Revoked = contract.events.find((e) => e.name === 'Revoked');
@ -73,12 +77,12 @@ export default class CertificationsMiddleware {
let badgeRegFilter = null; let badgeRegFilter = null;
let fetchCertifiersPromise = null; let fetchCertifiersPromise = null;
const updateFilter = updatableFilter(api, (filterId) => { const updateFilter = updatableFilter(this._api, (filterId) => {
filterChanged = true; filterChanged = true;
filter = filterId; filter = filterId;
}); });
const badgeRegUpdateFilter = updatableFilter(api, (filterId) => { const badgeRegUpdateFilter = updatableFilter(this._api, (filterId) => {
filterChanged = true; filterChanged = true;
badgeRegFilter = filterId; badgeRegFilter = filterId;
}); });
@ -96,7 +100,7 @@ export default class CertificationsMiddleware {
.then(() => { .then(() => {
shortFetchChanges(); shortFetchChanges();
api.subscribe('eth_blockNumber', (err) => { this._api.subscribe('eth_blockNumber', (err) => {
if (err) { if (err) {
return; return;
} }
@ -141,12 +145,12 @@ export default class CertificationsMiddleware {
filterChanged = false; filterChanged = false;
api.eth[method](badgeRegFilter) this._api.eth[method](badgeRegFilter)
.then(onBadgeRegLogs) .then(onBadgeRegLogs)
.catch((err) => { .catch((err) => {
console.error('Failed to fetch badge reg events:', err); console.error('Failed to fetch badge reg events:', err);
}) })
.then(() => api.eth[method](filter)) .then(() => this._api.eth[method](filter))
.then(onLogs) .then(onLogs)
.catch((err) => { .catch((err) => {
console.error('Failed to fetch new certifier events:', err); console.error('Failed to fetch new certifier events:', err);

View File

@ -15,131 +15,150 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import store from 'store'; import lsstore from 'store';
import Contracts from '@parity/shared/contracts'; import Contracts from '~/shared/contracts';
import registryABI from '@parity/shared/contracts/abi/registry.json'; import registryABI from '~/shared/contracts/abi/registry.json';
import subscribeToEvents from '@parity/shared/util/subscribe-to-events'; import subscribeToEvents from '~/shared/util/subscribe-to-events';
import { setReverse } from './actions'; import { setReverse, startCachingReverses } from './actions';
const STORE_KEY = '_parity::reverses'; const STORE_KEY = '_parity::reverses';
const read = (chain) => { export default class RegistryMiddleware {
const reverses = store.get(`${STORE_KEY}::${chain}::data`); contract;
const lastBlock = store.get(`${STORE_KEY}::${chain}::lastBlock`); interval;
store;
subscription;
timeout;
if (!reverses || !lastBlock) { addressesToCheck = {};
return null;
constructor (api) {
this._api = api;
} }
return { reverses, lastBlock };
};
const write = debounce((getChain, getReverses, getLastBlock) => { toMiddleware () {
const chain = getChain(); return (store) => {
const reverses = getReverses(); this.store = store;
const lastBlock = getLastBlock();
store.set(`${STORE_KEY}::${chain}::data`, reverses); return (next) => (action) => {
store.set(`${STORE_KEY}::${chain}::lastBlock`, lastBlock); switch (action.type) {
}, 20000); case 'initAll':
next(action);
store.dispatch(startCachingReverses());
break;
export default (api) => (store) => { case 'startCachingReverses':
let contract; this.cacheReverses();
let subscription; break;
let timeout;
let interval;
let addressesToCheck = {}; case 'stopCachingReverses':
if (this.subscription) {
this.subscription.unsubscribe();
}
if (this.interval) {
clearInterval(this.interval);
}
if (this.timeout) {
clearTimeout(this.timeout);
}
const onLog = (log) => { this.write.flush();
switch (log.event) { break;
case 'ReverseConfirmed':
addressesToCheck[log.params.reverse.value] = true;
break; case 'setReverse':
case 'ReverseRemoved': this.write(
delete addressesToCheck[log.params.reverse.value]; () => store.getState().nodeStatus.netChain,
() => store.getState().registry.reverse,
() => +store.getState().nodeStatus.blockNumber
);
next(action);
break;
break; default:
next(action);
}
};
};
}
cacheReverses () {
const { registry } = Contracts.get();
const cached = this.read(this.store.getState().nodeStatus.netChain);
if (cached) {
Object
.entries(cached.reverses)
.forEach(([ address, reverse ]) => this.store.dispatch(setReverse(address, reverse)));
} }
};
const checkReverses = () => { registry.getInstance()
.then((instance) => this._api.newContract(registryABI, instance.address))
.then((_contract) => {
this.contract = _contract;
this.subscription = subscribeToEvents(this.contract, [
'ReverseConfirmed', 'ReverseRemoved'
], {
from: cached ? cached.lastBlock : 0
});
this.subscription.on('log', this.onLog);
this.timeout = setTimeout(this.checkReverses, 10000);
this.interval = setInterval(this.checkReverses, 20000);
})
.catch((err) => {
console.error('Failed to start caching reverses:', err);
throw err;
});
}
checkReverses = () => {
Object Object
.keys(addressesToCheck) .keys(this.addressesToCheck)
.forEach((address) => { .forEach((address) => {
contract this.contract
.instance .instance
.reverse .reverse
.call({}, [ address ]) .call({}, [ address ])
.then((reverse) => { .then((reverse) => {
store.dispatch(setReverse(address, reverse)); this.store.dispatch(setReverse(address, reverse));
}); });
}); });
addressesToCheck = {}; this.addressesToCheck = {};
}; };
return (next) => (action) => { onLog = (log) => {
switch (action.type) { switch (log.event) {
case 'startCachingReverses': case 'ReverseConfirmed':
const { registry } = Contracts.get(api); this.addressesToCheck[log.params.reverse.value] = true;
const cached = read(store.getState().nodeStatus.netChain);
if (cached) {
Object
.entries(cached.reverses)
.forEach(([ address, reverse ]) => store.dispatch(setReverse(address, reverse)));
}
registry.getInstance()
.then((instance) => api.newContract(registryABI, instance.address))
.then((_contract) => {
contract = _contract;
subscription = subscribeToEvents(_contract, [
'ReverseConfirmed', 'ReverseRemoved'
], {
from: cached ? cached.lastBlock : 0
});
subscription.on('log', onLog);
timeout = setTimeout(checkReverses, 10000);
interval = setInterval(checkReverses, 20000);
})
.catch((err) => {
console.error('Failed to start caching reverses:', err);
throw err;
});
break; break;
case 'ReverseRemoved':
delete this.addressesToCheck[log.params.reverse.value];
case 'stopCachingReverses':
if (subscription) {
subscription.unsubscribe();
}
if (interval) {
clearInterval(interval);
}
if (timeout) {
clearTimeout(timeout);
}
write.flush();
break; break;
case 'setReverse':
write(
() => store.getState().nodeStatus.netChain,
() => store.getState().registry.reverse,
() => +store.getState().nodeStatus.blockNumber
);
next(action);
break;
default:
next(action);
} }
}; };
};
read = (chain) => {
const reverses = lsstore.get(`${STORE_KEY}::${chain}::data`);
const lastBlock = lsstore.get(`${STORE_KEY}::${chain}::lastBlock`);
if (!reverses || !lastBlock) {
return null;
}
return { reverses, lastBlock };
};
write = debounce((getChain, getReverses, getLastBlock) => {
const chain = getChain();
const reverses = getReverses();
const lastBlock = getLastBlock();
lsstore.set(`${STORE_KEY}::${chain}::data`, reverses);
lsstore.set(`${STORE_KEY}::${chain}::lastBlock`, lastBlock);
}, 20000);
}

View File

@ -33,7 +33,7 @@ const initialState = {
netVersion: '0', netVersion: '0',
nodeKind: null, nodeKind: null,
nodeKindFull: null, nodeKindFull: null,
syncing: true, syncing: null,
isConnected: false, isConnected: false,
isConnecting: false, isConnecting: false,
isTest: undefined, isTest: undefined,

View File

@ -0,0 +1,44 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/**
* Convert a String or a FormattedMessage
* element into a string
*
* @param {Object} context - The React `context`
* @param {String|Object} value - A String or a FormattedMessage
* element
* @return {String}
*/
export function toString (context, value) {
if (!context.intl) {
console.warn(`remember to add:
static contextTypes = {
intl: React.PropTypes.object.isRequired
};
to your component`);
return value;
}
const textValue = typeof value !== 'string' && (value && value.props)
? context.intl.formatMessage(
value.props,
value.props.values || {}
)
: value || '';
return textValue;
}

View File

@ -30,11 +30,13 @@ import Snackbar from '../Snackbar';
import Status from '../Status'; import Status from '../Status';
import UpgradeParity from '../UpgradeParity'; import UpgradeParity from '../UpgradeParity';
import UpgradeStore from '../UpgradeParity/store'; import UpgradeStore from '../UpgradeParity/store';
import SyncWarning, { showSyncWarning } from '../SyncWarning';
import Store from './store'; import Store from './store';
import styles from './application.css'; import styles from './application.css';
const inFrame = window.parent !== window && window.parent.frames.length !== 0; const inFrame = window.parent !== window && window.parent.frames.length !== 0;
const doShowSyncWarning = showSyncWarning();
@observer @observer
class Application extends Component { class Application extends Component {
@ -74,6 +76,11 @@ class Application extends Component {
? this.renderMinimized() ? this.renderMinimized()
: this.renderApp() : this.renderApp()
} }
{
doShowSyncWarning
? <SyncWarning />
: null
}
<Connection /> <Connection />
<Requests /> <Requests />
<ParityBar dapp={ isMinimized } /> <ParityBar dapp={ isMinimized } />

View File

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

View File

@ -0,0 +1,60 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation; either version 3 of the License; or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful;
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not; see <http://www.gnu.org/licenses/>.
*/
.overlay {
background: rgba(255, 255, 255, 0.75);
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 10000;
}
.modal {
align-items: flex-start;
bottom: 0;
display: flex;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 10001;
}
.body {
background: rgba(25, 25, 25, 0.75);
box-shadow: rgba(0, 0, 0, 0.25) 0 14px 45px, rgba(0, 0, 0, 0.22) 0 10px 18px;
color: rgb(208, 208, 208);
display: flex;
flex-direction: column;
margin: 0 auto;
max-width: 20em;
padding: 2em 4em;
text-align: center;
}
.button {
margin-top: 1em;
}
.body,
.button {
> * {
margin: 0.5em 0;
}
}

View File

@ -0,0 +1,130 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { Checkbox } from 'material-ui';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import store from 'store';
import { Button } from '~/ui';
import styles from './syncWarning.css';
const LS_DONT_SHOW_AGAIN = '_parity::syncWarning::dontShowAgain';
export const showSyncWarning = () => {
const dontShowAgain = store.get(LS_DONT_SHOW_AGAIN);
if (dontShowAgain === undefined || dontShowAgain === null) {
return true;
}
return !dontShowAgain;
};
class SyncWarning extends Component {
static propTypes = {
isSyncing: PropTypes.bool
};
state = {
dontShowAgain: false,
show: true
};
render () {
const { isSyncing } = this.props;
const { dontShowAgain, show } = this.state;
if (!isSyncing || isSyncing === null || !show) {
return null;
}
return (
<div>
<div className={ styles.overlay } />
<div className={ styles.modal }>
<div className={ styles.body }>
<FormattedMessage
id='syncWarning.message.line1'
defaultMessage={ `
Your Parity node is still syncing to the chain.
` }
/>
<FormattedMessage
id='syncWarning.message.line2'
defaultMessage={ `
Some of the shown information might be out-of-date.
` }
/>
<div className={ styles.button }>
<Checkbox
label={
<FormattedMessage
id='syncWarning.dontShowAgain.label'
defaultMessage='Do not show this warning again'
/>
}
checked={ dontShowAgain }
onCheck={ this.handleCheck }
/>
<Button
label={
<FormattedMessage
id='syncWarning.understandBtn.label'
defaultMessage='I understand'
/>
}
onClick={ this.handleAgreeClick }
/>
</div>
</div>
</div>
</div>
);
}
handleCheck = () => {
this.setState({ dontShowAgain: !this.state.dontShowAgain });
}
handleAgreeClick = () => {
if (this.state.dontShowAgain) {
store.set(LS_DONT_SHOW_AGAIN, true);
}
this.setState({ show: false });
}
}
function mapStateToProps (state) {
const { syncing } = state.nodeStatus;
// syncing could be an Object, false, or null
const isSyncing = syncing
? true
: syncing;
return {
isSyncing
};
}
export default connect(
mapStateToProps,
null
)(SyncWarning);

View File

@ -0,0 +1,57 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { shallow } from 'enzyme';
import React from 'react';
import SyncWarning from './';
let component;
function createRedux (syncing = null) {
return {
getState: () => {
return {
nodeStatus: {
syncing
}
};
}
};
}
function render (store) {
component = shallow(
<SyncWarning />,
{ context: { store: store || createRedux() } }
).find('SyncWarning').shallow();
return component;
}
describe('shell/SyncWarning', () => {
it('renders defaults', () => {
expect(render()).to.be.ok;
});
it('does render when syncing', () => {
expect(render(createRedux({})).find('div')).to.have.length.gte(1);
});
it('does not render when not syncing', () => {
expect(render(createRedux(false)).find('div')).to.have.length(0);
});
});

View File

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

View File

@ -24,6 +24,7 @@ import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline';
import apiutil from '@parity/api/util'; import apiutil from '@parity/api/util';
import { nodeOrStringProptype } from '@parity/shared/util/proptypes'; import { nodeOrStringProptype } from '@parity/shared/util/proptypes';
import { toString } from '@parity/shared/util/messages';
import { validateAddress } from '@parity/shared/util/validation'; import { validateAddress } from '@parity/shared/util/validation';
import AccountCard from '~/ui/AccountCard'; import AccountCard from '~/ui/AccountCard';
@ -186,12 +187,7 @@ class AddressSelect extends Component {
} }
const id = `addressSelect_${++currentId}`; const id = `addressSelect_${++currentId}`;
const ilHint = typeof hint === 'string' || !(hint && hint.props) const ilHint = toString(this.context, hint);
? (hint || '')
: this.context.intl.formatMessage(
hint.props,
hint.props.values || {}
);
return ( return (
<Portal <Portal

View File

@ -20,6 +20,7 @@ import { noop } from 'lodash';
import keycode from 'keycode'; import keycode from 'keycode';
import { nodeOrStringProptype } from '@parity/shared/util/proptypes'; import { nodeOrStringProptype } from '@parity/shared/util/proptypes';
import { toString } from '@parity/shared/util/messages';
import CopyToClipboard from '~/ui/CopyToClipboard'; import CopyToClipboard from '~/ui/CopyToClipboard';
@ -149,12 +150,7 @@ export default class Input extends Component {
? UNDERLINE_FOCUSED ? UNDERLINE_FOCUSED
: readOnly && typeof focused !== 'boolean' ? { display: 'none' } : null; : readOnly && typeof focused !== 'boolean' ? { display: 'none' } : null;
const textValue = typeof value !== 'string' && (value && value.props) const textValue = toString(this.context, value);
? this.context.intl.formatMessage(
value.props,
value.props.values || {}
)
: value;
return ( return (
<div className={ styles.container } style={ style }> <div className={ styles.container } style={ style }>

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import moment from 'moment'; import moment from 'moment';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
@ -177,10 +178,12 @@ class MethodDecoding extends Component {
return null; return null;
} }
if (condition.block && condition.block.gt(0)) { const blockCondition = new BigNumber(condition.block || 0);
if (blockCondition.gt(0)) {
const blockNumber = ( const blockNumber = (
<span className={ styles.highlight }> <span className={ styles.highlight }>
#{ condition.block.toFormat(0) } #{ blockCondition.toFormat(0) }
</span> </span>
); );

View File

@ -347,12 +347,13 @@ class ContractDevelop extends Component {
} }
onClick={ this.store.handleCompile } onClick={ this.store.handleCompile }
primary={ false } primary={ false }
disabled={ compiling } disabled={ compiling || this.store.isPristine }
/> />
{ {
contract contract
? ( ? (
<Button <Button
disabled={ compiling || !this.store.isPristine }
icon={ <SendIcon /> } icon={ <SendIcon /> }
label={ label={
<FormattedMessage <FormattedMessage

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { action, observable, transaction } from 'mobx'; import { action, computed, observable, transaction } from 'mobx';
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import store from 'store'; import store from 'store';
@ -140,7 +140,7 @@ export default class ContractDevelopStore {
this.worker = worker; this.worker = worker;
return Promise.all([ return Promise.all([
this.fetchSolidityVersions(), this.fetchSolidityVersions().then(() => this.handleCompile()),
this.reloadContracts(undefined, undefined, false) this.reloadContracts(undefined, undefined, false)
]); ]);
} }
@ -316,6 +316,18 @@ export default class ContractDevelopStore {
}); });
} }
@computed get isPristine () {
return this.getHash() === this.lastCompilation.hash;
}
getHash () {
const build = this.builds[this.selectedBuild];
const version = build.longVersion;
const sourcecode = this.sourcecode.replace(/\s+/g, ' ');
return sha3(JSON.stringify({ version, sourcecode, optimize: this.optimize }));
}
@action handleCompile = () => { @action handleCompile = () => {
transaction(() => { transaction(() => {
this.compiled = false; this.compiled = false;
@ -324,9 +336,7 @@ export default class ContractDevelopStore {
}); });
const build = this.builds[this.selectedBuild]; const build = this.builds[this.selectedBuild];
const version = build.longVersion; const hash = this.getHash();
const sourcecode = this.sourcecode.replace(/\s+/g, ' ');
const hash = sha3(JSON.stringify({ version, sourcecode, optimize: this.optimize }));
let promise = Promise.resolve(null); let promise = Promise.resolve(null);