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
This commit is contained in:
parent
e4c75bde4c
commit
b3d502ba78
@ -82,6 +82,10 @@
|
|||||||
.capture {
|
.capture {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.capture+.capture {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.capture * {
|
.capture * {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
@ -124,3 +128,20 @@
|
|||||||
.hashOk {
|
.hashOk {
|
||||||
opacity: 0.5;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -33,12 +33,15 @@ export default class Application extends Component {
|
|||||||
loading: true,
|
loading: true,
|
||||||
url: '',
|
url: '',
|
||||||
urlError: null,
|
urlError: null,
|
||||||
|
commit: '',
|
||||||
|
commitError: null,
|
||||||
contentHash: '',
|
contentHash: '',
|
||||||
contentHashError: null,
|
contentHashError: null,
|
||||||
contentHashOwner: null,
|
contentHashOwner: null,
|
||||||
registerBusy: false,
|
registerBusy: false,
|
||||||
registerError: null,
|
registerError: null,
|
||||||
registerState: ''
|
registerState: '',
|
||||||
|
registerType: 'file'
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@ -65,7 +68,7 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderPage () {
|
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;
|
let hashClass = null;
|
||||||
if (contentHashError) {
|
if (contentHashError) {
|
||||||
@ -74,14 +77,31 @@ export default class Application extends Component {
|
|||||||
hashClass = styles.hashOk;
|
hashClass = styles.hashOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
let valueInputs = null;
|
||||||
<div className={ styles.container }>
|
if (registerType === 'content') {
|
||||||
<div className={ styles.form }>
|
valueInputs = [
|
||||||
<div className={ styles.box }>
|
<div className={ styles.capture } key='repo'>
|
||||||
<div className={ styles.description }>
|
<input
|
||||||
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.
|
type='text'
|
||||||
|
placeholder='owner/repo'
|
||||||
|
disabled={ registerBusy }
|
||||||
|
value={ repo }
|
||||||
|
className={ repoError ? styles.error : null }
|
||||||
|
onChange={ this.onChangeRepo } />
|
||||||
|
</div>,
|
||||||
|
<div className={ styles.capture } key='hash'>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
placeholder='commit hash sha3'
|
||||||
|
disabled={ registerBusy }
|
||||||
|
value={ commit }
|
||||||
|
className={ commitError ? styles.error : null }
|
||||||
|
onChange={ this.onChangeCommit } />
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.capture }>
|
];
|
||||||
|
} else {
|
||||||
|
valueInputs = (
|
||||||
|
<div className={ styles.capture } key='url'>
|
||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
placeholder='http://domain/filename'
|
placeholder='http://domain/filename'
|
||||||
@ -90,6 +110,27 @@ export default class Application extends Component {
|
|||||||
className={ urlError ? styles.error : null }
|
className={ urlError ? styles.error : null }
|
||||||
onChange={ this.onChangeUrl } />
|
onChange={ this.onChangeUrl } />
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.container }>
|
||||||
|
<div className={ styles.form }>
|
||||||
|
<div className={ styles.typeButtons }>
|
||||||
|
<Button
|
||||||
|
disabled={ registerBusy }
|
||||||
|
invert={ registerType !== 'file' }
|
||||||
|
onClick={ this.onClickTypeNormal }>File Link</Button>
|
||||||
|
<Button
|
||||||
|
disabled={ registerBusy }
|
||||||
|
invert={ registerType !== 'content' }
|
||||||
|
onClick={ this.onClickTypeContent }>Content Bundle</Button>
|
||||||
|
</div>
|
||||||
|
<div className={ styles.box }>
|
||||||
|
<div className={ styles.description }>
|
||||||
|
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.
|
||||||
|
</div>
|
||||||
|
{ valueInputs }
|
||||||
<div className={ hashClass }>
|
<div className={ hashClass }>
|
||||||
{ contentHashError || contentHash }
|
{ contentHashError || contentHash }
|
||||||
</div>
|
</div>
|
||||||
@ -101,7 +142,7 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderButtons () {
|
renderButtons () {
|
||||||
const { accounts, fromAddress, url, urlError, contentHashError, contentHashOwner } = this.state;
|
const { accounts, fromAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state;
|
||||||
const account = accounts[fromAddress];
|
const account = accounts[fromAddress];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -114,7 +155,7 @@ export default class Application extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={ this.onClickRegister }
|
onClick={ this.onClickRegister }
|
||||||
disabled={ (!!contentHashError && contentHashOwner !== fromAddress) || !!urlError || url.length === 0 }>register url</Button>
|
disabled={ (contentHashError && contentHashOwner !== fromAddress) || urlError || repoError || commitError }>register url</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -147,53 +188,86 @@ export default class Application extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickContentHash = () => {
|
onClickTypeNormal = () => {
|
||||||
this.setState({ fileHash: false, commit: '' });
|
const { url } = this.state;
|
||||||
|
|
||||||
|
this.setState({ registerType: 'file', commitError: null, repoError: null }, () => {
|
||||||
|
this.onChangeUrl({ target: { value: url } });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickFileHash = () => {
|
onClickTypeContent = () => {
|
||||||
this.setState({ fileHash: true, commit: 0 });
|
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) => {
|
onChangeUrl = (event) => {
|
||||||
const url = event.target.value;
|
let url = event.target.value;
|
||||||
let urlError = null;
|
const urlError = null;
|
||||||
|
|
||||||
if (url && url.length) {
|
// TODO: field validation
|
||||||
const re = /^https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}/g; // eslint-disable-line
|
if (!urlError) {
|
||||||
urlError = re.test(url)
|
const parts = url.split('/');
|
||||||
? null
|
|
||||||
: 'not matching rexex';
|
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.setState({ url, urlError, contentHashError: 'hash lookup in progress' }, () => {
|
||||||
this.lookupHash();
|
this.lookupHash(url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickRegister = () => {
|
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;
|
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];
|
trackRequest (promise) {
|
||||||
const options = { from: fromAddress };
|
return promise
|
||||||
|
|
||||||
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);
|
|
||||||
})
|
|
||||||
.then((signerRequestId) => {
|
.then((signerRequestId) => {
|
||||||
this.setState({ signerRequestId, registerState: 'Transaction posted, Waiting for transaction authorization' });
|
this.setState({ signerRequestId, registerState: 'Transaction posted, Waiting for transaction authorization' });
|
||||||
|
|
||||||
@ -211,7 +285,7 @@ export default class Application extends Component {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((txReceipt) => {
|
.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) => {
|
.catch((error) => {
|
||||||
console.error('onSend', 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 = () => {
|
onSelectFromAddress = () => {
|
||||||
const { accounts, fromAddress } = this.state;
|
const { accounts, fromAddress } = this.state;
|
||||||
const addresses = Object.keys(accounts);
|
const addresses = Object.keys(accounts);
|
||||||
@ -238,8 +358,14 @@ export default class Application extends Component {
|
|||||||
this.setState({ fromAddress: addresses[index] });
|
this.setState({ fromAddress: addresses[index] });
|
||||||
}
|
}
|
||||||
|
|
||||||
lookupHash () {
|
lookupHash (url) {
|
||||||
const { url, instance } = this.state;
|
const { instance } = this.state;
|
||||||
|
|
||||||
|
if (!url || !url.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`lookupHash ${url}`);
|
||||||
|
|
||||||
api.ethcore
|
api.ethcore
|
||||||
.hashContent(url)
|
.hashContent(url)
|
||||||
|
Loading…
Reference in New Issue
Block a user