From b3d502ba7829f00e9b2b0ca0e37e33f3e9b3718b Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 2 Nov 2016 18:16:50 +0100 Subject: [PATCH] Allow registration of content bundles in GitHubHint (#3094) * Clear woner & error on success * WIP buttons * Selection bar * Sanitize GitHub urls * Complete hint registration * button-row icons * PR comments, url check & validation TODO * PR comments, TODO for validation to show intent --- .../githubhint/Application/application.css | 21 ++ .../githubhint/Application/application.js | 216 ++++++++++++++---- 2 files changed, 192 insertions(+), 45 deletions(-) diff --git a/js/src/dapps/githubhint/Application/application.css b/js/src/dapps/githubhint/Application/application.css index d149d883d..be04ecf34 100644 --- a/js/src/dapps/githubhint/Application/application.css +++ b/js/src/dapps/githubhint/Application/application.css @@ -82,6 +82,10 @@ .capture { } +.capture+.capture { + margin-top: 0.5em; +} + .capture * { display: inline-block; padding: 0.75em; @@ -124,3 +128,20 @@ .hashOk { opacity: 0.5; } + +.typeButtons { + text-align: center; + padding: 0 0 1em 0; +} + +.typeButtons>div { + border-radius: 0 !important; + + &:first-child { + border-radius: 5px 0 0 5px !important; + } + + &:last-child { + border-radius: 0 5px 5px 0 !important; + } +} diff --git a/js/src/dapps/githubhint/Application/application.js b/js/src/dapps/githubhint/Application/application.js index 3c4ecfda8..4a25d3855 100644 --- a/js/src/dapps/githubhint/Application/application.js +++ b/js/src/dapps/githubhint/Application/application.js @@ -33,12 +33,15 @@ export default class Application extends Component { loading: true, url: '', urlError: null, + commit: '', + commitError: null, contentHash: '', contentHashError: null, contentHashOwner: null, registerBusy: false, registerError: null, - registerState: '' + registerState: '', + registerType: 'file' } componentDidMount () { @@ -65,7 +68,7 @@ export default class Application extends Component { } renderPage () { - const { fromAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner } = this.state; + const { fromAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner, commit, commitError, registerType, repo, repoError } = this.state; let hashClass = null; if (contentHashError) { @@ -74,22 +77,60 @@ export default class Application extends Component { hashClass = styles.hashOk; } + let valueInputs = null; + if (registerType === 'content') { + valueInputs = [ +
+ +
, +
+ +
+ ]; + } else { + valueInputs = ( +
+ +
+ ); + } + return (
+
+ + +
Provide a valid URL to register. The content information can be used in other contracts that allows for reverse lookups, e.g. image registries, dapp registries, etc.
-
- -
+ { valueInputs }
{ contentHashError || contentHash }
@@ -101,7 +142,7 @@ export default class Application extends Component { } renderButtons () { - const { accounts, fromAddress, url, urlError, contentHashError, contentHashOwner } = this.state; + const { accounts, fromAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state; const account = accounts[fromAddress]; return ( @@ -114,7 +155,7 @@ export default class Application extends Component {
+ disabled={ (contentHashError && contentHashOwner !== fromAddress) || urlError || repoError || commitError }>register url
); } @@ -147,53 +188,86 @@ export default class Application extends Component { ); } - onClickContentHash = () => { - this.setState({ fileHash: false, commit: '' }); + onClickTypeNormal = () => { + const { url } = this.state; + + this.setState({ registerType: 'file', commitError: null, repoError: null }, () => { + this.onChangeUrl({ target: { value: url } }); + }); } - onClickFileHash = () => { - this.setState({ fileHash: true, commit: 0 }); + onClickTypeContent = () => { + const { repo, commit } = this.state; + + this.setState({ registerType: 'content', urlError: null }, () => { + this.onChangeRepo({ target: { value: repo } }); + this.onChangeCommit({ target: { value: commit } }); + }); + } + + onChangeCommit = (event) => { + const commit = event.target.value; + const commitError = null; + + // TODO: field validation + + this.setState({ commit, commitError, contentHashError: 'hash lookup in progress' }, () => { + const { repo } = this.state; + this.lookupHash(`https://codeload.github.com/${repo}/zip/${commit}`); + }); + } + + onChangeRepo = (event) => { + let repo = event.target.value; + const repoError = null; + + // TODO: field validation + if (!repoError) { + repo = repo.replace('https://github.com/', ''); + } + + this.setState({ repo, repoError, contentHashError: 'hash lookup in progress' }, () => { + const { commit } = this.state; + this.lookupHash(`https://codeload.github.com/${repo}/zip/${commit}`); + }); } onChangeUrl = (event) => { - const url = event.target.value; - let urlError = null; + let url = event.target.value; + const urlError = null; - if (url && url.length) { - const re = /^https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}/g; // eslint-disable-line - urlError = re.test(url) - ? null - : 'not matching rexex'; + // TODO: field validation + if (!urlError) { + const parts = url.split('/'); + + if (parts[2] === 'github.com' || parts[2] === 'raw.githubusercontent.com') { + url = `https://raw.githubusercontent.com/${parts.slice(3).join('/')}`.replace('/blob/', '/'); + } } this.setState({ url, urlError, contentHashError: 'hash lookup in progress' }, () => { - this.lookupHash(); + this.lookupHash(url); }); } onClickRegister = () => { - const { url, urlError, contentHash, contentHashError, contentHashOwner, fromAddress, instance } = this.state; + const { commit, commitError, contentHashError, contentHashOwner, fromAddress, url, urlError, registerType, repo, repoError } = this.state; - if ((!!contentHashError && contentHashOwner !== fromAddress) || !!urlError || url.length === 0) { + // TODO: No errors are currently set, validation to be expanded and added for each + // field (query is fast to pick up the issues, so not burning atm) + if ((contentHashError && contentHashOwner !== fromAddress) || repoError || urlError || commitError) { return; } - this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' }); + if (registerType === 'file') { + this.registerUrl(url); + } else { + this.registerContent(repo, commit); + } + } - const values = [contentHash, url]; - const options = { from: fromAddress }; - - instance - .hintURL.estimateGas(options, values) - .then((gas) => { - this.setState({ registerState: 'Gas estimated, Posting transaction to the network' }); - - const gasPassed = gas.mul(1.2); - options.gas = gasPassed.toFixed(0); - console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`); - - return instance.hintURL.postTransaction(options, values); - }) + trackRequest (promise) { + return promise .then((signerRequestId) => { this.setState({ signerRequestId, registerState: 'Transaction posted, Waiting for transaction authorization' }); @@ -211,7 +285,7 @@ export default class Application extends Component { }); }) .then((txReceipt) => { - this.setState({ txReceipt, registerBusy: false, registerState: 'Network confirmed, Received transaction receipt', url: '', contentHash: '' }); + this.setState({ txReceipt, registerBusy: false, registerState: 'Network confirmed, Received transaction receipt', url: '', commit: '', commitError: null, contentHash: '', contentHashOwner: null, contentHashError: null }); }) .catch((error) => { console.error('onSend', error); @@ -219,6 +293,52 @@ export default class Application extends Component { }); } + registerContent (repo, commit) { + const { contentHash, fromAddress, instance } = this.state; + + this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' }); + + const values = [contentHash, repo, commit]; + const options = { from: fromAddress }; + + this.trackRequest( + instance + .hint.estimateGas(options, values) + .then((gas) => { + this.setState({ registerState: 'Gas estimated, Posting transaction to the network' }); + + const gasPassed = gas.mul(1.2); + options.gas = gasPassed.toFixed(0); + console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`); + + return instance.hint.postTransaction(options, values); + }) + ); + } + + registerUrl (url) { + const { contentHash, fromAddress, instance } = this.state; + + this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' }); + + const values = [contentHash, url]; + const options = { from: fromAddress }; + + this.trackRequest( + instance + .hintURL.estimateGas(options, values) + .then((gas) => { + this.setState({ registerState: 'Gas estimated, Posting transaction to the network' }); + + const gasPassed = gas.mul(1.2); + options.gas = gasPassed.toFixed(0); + console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`); + + return instance.hintURL.postTransaction(options, values); + }) + ); + } + onSelectFromAddress = () => { const { accounts, fromAddress } = this.state; const addresses = Object.keys(accounts); @@ -238,8 +358,14 @@ export default class Application extends Component { this.setState({ fromAddress: addresses[index] }); } - lookupHash () { - const { url, instance } = this.state; + lookupHash (url) { + const { instance } = this.state; + + if (!url || !url.length) { + return; + } + + console.log(`lookupHash ${url}`); api.ethcore .hashContent(url)