compile-tab: function based subcomponent

pull/1/head
Grandschtroumpf 6 years ago committed by yann300
parent 0b65202678
commit 0b2f8be36f
  1. 390
      src/app/tabs/compile-tab.js

@ -20,9 +20,8 @@ const CompilerContainer = require('./compileTab/compilerContainer.js')
class CompileTab { class CompileTab {
constructor (registry) { constructor (registry) {
const self = this this.event = new EventEmitter()
self.event = new EventEmitter() this._view = {
self._view = {
el: null, el: null,
warnCompilationSlow: null, warnCompilationSlow: null,
errorContainer: null, errorContainer: null,
@ -30,10 +29,10 @@ class CompileTab {
contractNames: null, contractNames: null,
contractEl: null contractEl: null
} }
self.queryParams = new QueryParams() this.queryParams = new QueryParams()
// dependencies // dependencies
self._deps = { this._deps = {
editor: registry.get('editor').api, editor: registry.get('editor').api,
config: registry.get('config').api, config: registry.get('config').api,
renderer: registry.get('renderer').api, renderer: registry.get('renderer').api,
@ -42,52 +41,62 @@ class CompileTab {
fileProviders: registry.get('fileproviders').api, fileProviders: registry.get('fileproviders').api,
pluginManager: registry.get('pluginmanager').api pluginManager: registry.get('pluginmanager').api
} }
self.data = { this.data = {
contractsDetails: {} contractsDetails: {}
} }
this.compileTabLogic = new CompileTabLogic(self.queryParams, self._deps.fileManager, self._deps.editor, self._deps.config, self._deps.fileProviders) this.compileTabLogic = new CompileTabLogic(this.queryParams, this._deps.fileManager, this._deps.editor, this._deps.config, this._deps.fileProviders)
this.compiler = this.compileTabLogic.compiler this.compiler = this.compileTabLogic.compiler
this.compileTabLogic.init() this.compileTabLogic.init()
this.compilerContainer = new CompilerContainer(self.compileTabLogic, self._deps.editor, self._deps.config, self.queryParams) this.compilerContainer = new CompilerContainer(
this.compileTabLogic,
this._deps.editor,
this._deps.config,
this.queryParams
)
this.listenToEvents() this.listenToEvents()
} }
listenToEvents () { /************
const self = this * EVENTS
*/
self.compiler.event.register('compilationStarted', () => { listenToEvents () {
if (self._view.errorContainer) { this.compiler.event.register('compilationStarted', () => {
self._view.errorContainer.innerHTML = '' if (this._view.errorContainer) {
self._view.errorContainerHead.innerHTML = '' this._view.errorContainer.innerHTML = ''
this._view.errorContainerHead.innerHTML = ''
} }
}) })
self.compiler.event.register('compilationFinished', (success, data, source) => {
this._deps.fileManager.event.register('currentFileChanged', (name) => {
this.compilerContainer.currentFile = name
})
this.compiler.event.register('compilationFinished', (success, data, source) => {
if (success) { if (success) {
// forwarding the event to the appManager infra // forwarding the event to the appManager infra
self.event.emit('compilationFinished', source.target, source, self.data.selectedVersion, data) this.event.emit('compilationFinished', source.target, source, this.data.selectedVersion, data)
} // Store the contracts
// reset the contractMetadata list (used by the publish action) this.data.contractsDetails = {}
self.data.contractsDetails = {} this.compiler.visitContracts((contract) => {
// refill the dropdown list this.data.contractsDetails[contract.name] = parseContracts(
self._view.contractNames.innerHTML = '' contract.name,
if (success) { contract.object,
// TODO consider using compile tab as a proper module instead of just forwarding event this.compiler.getSource(contract.file)
self._view.contractNames.removeAttribute('disabled') )
self.compiler.visitContracts(contract => {
self.data.contractsDetails[contract.name] = parseContracts(contract.name, contract.object, self.compiler.getSource(contract.file))
var contractName = yo`<option>${contract.name}</option>`
self._view.contractNames.appendChild(contractName)
}) })
} else {
self._view.contractNames.setAttribute('disabled', true)
} }
var error = false // Update contract Selection
const contractMap = this.compiler.getContracts()
const contractSelection = this.contractSelection(Object.keys(contractMap) || [])
yo.update(this._view.contractSelection, contractSelection)
let error = false
if (data['error']) { if (data['error']) {
error = true error = true
self._deps.renderer.error(data['error'].formattedMessage, self._view.errorContainer, {type: data['error'].severity || 'error'}) this._deps.renderer.error(data['error'].formattedMessage, this._view.errorContainer, {type: data['error'].severity || 'error'})
if (data['error'].mode === 'panic') { if (data['error'].mode === 'panic') {
return modalDialogCustom.alert(yo`<div><i class="fa fa-exclamation-circle ${css.panicError}" aria-hidden="true"></i> return modalDialogCustom.alert(yo`<div><i class="fa fa-exclamation-circle ${css.panicError}" aria-hidden="true"></i>
The compiler returned with the following internal error: <br> <b>${data['error'].formattedMessage}.<br> The compiler returned with the following internal error: <br> <b>${data['error'].formattedMessage}.<br>
@ -99,18 +108,18 @@ class CompileTab {
if (data.errors && data.errors.length) { if (data.errors && data.errors.length) {
error = true error = true
data.errors.forEach((err) => { data.errors.forEach((err) => {
if (self._deps.config.get('hideWarnings')) { if (this._deps.config.get('hideWarnings')) {
if (err.severity !== 'warning') { if (err.severity !== 'warning') {
self._deps.renderer.error(err.formattedMessage, self._view.errorContainer, {type: err.severity}) this._deps.renderer.error(err.formattedMessage, this._view.errorContainer, {type: err.severity})
} }
} else { } else {
self._deps.renderer.error(err.formattedMessage, self._view.errorContainer, {type: err.severity}) this._deps.renderer.error(err.formattedMessage, this._view.errorContainer, {type: err.severity})
} }
}) })
} }
if (!error && data.contracts) { if (!error && data.contracts) {
self.compiler.visitContracts((contract) => { this.compiler.visitContracts((contract) => {
self._deps.renderer.error(contract.name, self._view.errorContainer, {type: 'success'}) this._deps.renderer.error(contract.name, this._view.errorContainer, {type: 'success'})
}) })
} }
}) })
@ -120,7 +129,7 @@ class CompileTab {
// ctrl+s or command+s // ctrl+s or command+s
if ((e.metaKey || e.ctrlKey) && e.keyCode === 83) { if ((e.metaKey || e.ctrlKey) && e.keyCode === 83) {
e.preventDefault() e.preventDefault()
self.compileTabLogic.runCompiler() this.compileTabLogic.runCompiler()
} }
}) })
} }
@ -136,38 +145,96 @@ class CompileTab {
} }
} }
render () { /*********
const self = this * SUB-COMPONENTS
if (self._view.el) return self._view.el */
self._view.errorContainer = yo`<div class='error'></div>` /**
self._view.errorContainerHead = yo`<div class='error'></div>` * Section to select the compiled contract
self._view.contractNames = yo`<select class="${css.contractNames}" disabled></select>` * @param {string[]} contractList Names of the compiled contracts
self._view.contractEl = yo` */
<div class="${css.container}"> contractSelection(contractList = []) {
<div class="${css.contractContainer}"> return contractList.length !== 0
${self._view.contractNames} ? yo`<section class="${css.container}">
<div title="Publish on Swarm" class="${css.publish}" onclick=${publish}> <!-- Select Compiler Version -->
<i class="${css.copyIcon} fa fa-upload" aria-hidden="true"></i><span>Swarm</span> <header class="navbar navbar-light bg-light input-group mb-3 ${css.compilerArticle}">
</div> <div class="input-group-prepend">
<label class="input-group-text" for="compiledContracts">Contract</label>
</div> </div>
<select onchange="${e => this.selectedContract = e.value}" id="compiledContracts" class="custom-select">
${contractList.map((name) => yo`<option value="${name}">${name}</option>`)}
</select>
</header>
<article class="${css.compilerArticle}">
<button class="btn btn-primary btn-block" title="Publish on Swarm" onclick="${this.publish}">
<i class="${css.copyIcon} fa fa-upload" aria-hidden="true"></i>
<span>Publish ${this.selectedContract} on Swarm</span>
</button>
<button class="btn btn-secondary btn-block" title="Display Contract Details" onclick="${this.details}">
Compilation Details
</button>
<!-- Copy to Clipboard -->
<div class="${css.contractHelperButtons}"> <div class="${css.contractHelperButtons}">
<div title="Display Contract Details" class="${css.details}" onclick=${details}>Details</div> <span class="${css.copyToClipboard}">Copy to Clipboard : </span>
<div title="Copy ABI to clipboard" class="${css.copyButton}" onclick=${copyABI}> <div class="btn-group" role="group" aria-label="Copy to clipboard">
<i class="${css.copyIcon} fa fa-clipboard" aria-hidden="true"></i> ABI <button class="btn btn-secondary" title="Copy ABI to clipboard" onclick="${this.copyABI}">
</div> <i class="${css.copyIcon} fa fa-clipboard" aria-hidden="true"></i>
<div title="Copy Bytecode to clipboard" class="${css.copyButton} ${css.bytecodeButton}" onclick=${copyBytecode}> <span>ABI</span>
<i class="${css.copyIcon} fa fa-clipboard" aria-hidden="true"></i> Bytecode </button>
<button class="btn btn-secondary" title="Copy Bytecode to clipboard" onclick="${this.copyBytecode}">
<i class="${css.copyIcon} fa fa-clipboard" aria-hidden="true"></i>
<span>Bytecode</span>
</button>
</div> </div>
</div> </div>
</div>` </article>
self._view.el = yo` </section>`
<div class="${css.compileTabView}" id="compileTabView"> : yo`<article class="${css.compilerArticle}">
${this.compilerContainer.render()} <span class="alert alert-warning" role="alert">No Contract Compiled Yet</span>
${self._view.contractEl} </article>`
${self._view.errorContainerHead} }
${self._view.errorContainer}
</div>` // TODO : Add success alert when compilation succeed
contractCompiledSuccess() {
return yo``
}
// TODO : Add error alert when compilation failed
contractCompiledError() {
return yo``
}
/************
* METHODS
*/
publish() {
const selectContractNames = this._view.contractNames
if (selectContractNames.children.length > 0 && selectContractNames.selectedIndex >= 0) {
var contract = this.data.contractsDetails[selectContractNames.children[selectContractNames.selectedIndex].innerHTML]
if (contract.metadata === undefined || contract.metadata.length === 0) {
modalDialogCustom.alert('This contract may be abstract, may not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.')
} else {
publishOnSwarm(contract, this._deps.fileManager, function (err, uploaded) {
if (err) {
try {
err = JSON.stringify(err)
} catch (e) {}
modalDialogCustom.alert(yo`<span>Failed to publish metadata file to swarm, please check the Swarm gateways is available ( swarm-gateways.net ).<br />
${err}</span>`)
} else {
var result = yo`<div>${uploaded.map((value) => {
return yo`<div><b>${value.filename}</b> : <pre>${value.output.url}</pre></div>`
})}</div>`
modalDialogCustom.alert(yo`<span>Metadata published successfully.<br> <pre>${result}</pre> </span>`)
}
}, (item) => { // triggered each time there's a new verified publish (means hash correspond)
this._deps.swarmfileProvider.addReadOnly(item.hash, item.content)
})
}
}
}
details() {
const help = { const help = {
'Assembly': 'Assembly opcodes describing the contract including corresponding solidity source code', 'Assembly': 'Assembly opcodes describing the contract including corresponding solidity source code',
'Opcodes': 'Assembly opcodes describing the contract', 'Opcodes': 'Assembly opcodes describing the contract',
@ -182,115 +249,104 @@ class CompileTab {
'swarmLocation': 'Swarm url where all metadata information can be found (contract needs to be published first)', 'swarmLocation': 'Swarm url where all metadata information can be found (contract needs to be published first)',
'web3Deploy': 'Copy/paste this code to any JavaScript/Web3 console to deploy this contract' 'web3Deploy': 'Copy/paste this code to any JavaScript/Web3 console to deploy this contract'
} }
function getContractProperty (property) { if (!this.selectedContract) throw new Error('No contract compiled yet')
const select = self._view.contractNames const contractProperties = this.data.contractsDetails[this.selectedContract]
if (select.children.length > 0 && select.selectedIndex >= 0) { const log = yo`<div class="${css.detailsJSON}"></div>`
const contractName = select.children[select.selectedIndex].innerHTML Object.keys(contractProperties).map(propertyName => {
const contractProperties = self.data.contractsDetails[contractName] const copyDetails = yo`<span class="${css.copyDetails}">${copyToClipboard(() => contractProperties[propertyName])}</span>`
return contractProperties[property] || null const questionMark = yo`<span class="${css.questionMark}"><i title="${help[propertyName]}" class="fa fa-question-circle" aria-hidden="true"></i></span>`
} log.appendChild(yo`<div class=${css.log}>
} <div class="${css.key}">${propertyName} ${copyDetails} ${questionMark}</div>
function copyContractProperty (property) { ${this.insertValue(contractProperties, propertyName)}
let content = getContractProperty(property) </div>`)
if (!content) { })
addTooltip('No content available for ' + property) modalDialog(this.selectedContract, log, { label: '' }, { label: 'Close' })
return }
}
try {
if (typeof content !== 'string') {
content = JSON.stringify(content, null, '\t')
}
} catch (e) {}
copy(content) insertValue (details, propertyName) {
addTooltip('Copied value to clipboard') var node
} if (propertyName === 'web3Deploy' || propertyName === 'name' || propertyName === 'Assembly') {
function copyABI () { node = yo`<pre>${details[propertyName]}</pre>`
copyContractProperty('abi') } else if (propertyName === 'abi' || propertyName === 'metadata') {
} const treeView = new TreeView({
function copyBytecode () { extractData: function (item, parent, key) {
copyContractProperty('bytecode') var ret = {}
} if (item instanceof Array) {
function details () { ret.children = item.map((item, index) => ({ key: index, value: item }))
const select = self._view.contractNames ret.self = ''
if (select.children.length > 0 && select.selectedIndex >= 0) { } else if (item instanceof Object) {
const contractName = select.children[select.selectedIndex].innerHTML ret.children = Object.keys(item).map((key) => ({key: key, value: item[key]}))
const contractProperties = self.data.contractsDetails[contractName] ret.self = ''
const log = yo`<div class="${css.detailsJSON}"></div>` } else {
Object.keys(contractProperties).map(propertyName => { ret.self = item
const copyDetails = yo`<span class="${css.copyDetails}">${copyToClipboard(() => contractProperties[propertyName])}</span>` ret.children = []
const questionMark = yo`<span class="${css.questionMark}"><i title="${help[propertyName]}" class="fa fa-question-circle" aria-hidden="true"></i></span>`
log.appendChild(yo`<div class=${css.log}>
<div class="${css.key}">${propertyName} ${copyDetails} ${questionMark}</div>
${insertValue(contractProperties, propertyName)}
</div>`)
})
modalDialog(contractName, log, { label: '' }, { label: 'Close' })
}
}
function insertValue (details, propertyName) {
var node
if (propertyName === 'web3Deploy' || propertyName === 'name' || propertyName === 'Assembly') {
node = yo`<pre>${details[propertyName]}</pre>`
} else if (propertyName === 'abi' || propertyName === 'metadata') {
const treeView = new TreeView({
extractData: function (item, parent, key) {
var ret = {}
if (item instanceof Array) {
ret.children = item.map((item, index) => ({ key: index, value: item }))
ret.self = ''
} else if (item instanceof Object) {
ret.children = Object.keys(item).map((key) => ({key: key, value: item[key]}))
ret.self = ''
} else {
ret.self = item
ret.children = []
}
return ret
}
})
if (details[propertyName] !== '') {
try {
node = yo`<div>${treeView.render(typeof details[propertyName] === 'object' ? details[propertyName] : JSON.parse(details[propertyName]))}</div>` // catch in case the parsing fails.
} catch (e) {
node = yo`<div>Unable to display "${propertyName}": ${e.message}</div>`
} }
} else { return ret
node = yo`<div> - </div>` }
})
if (details[propertyName] !== '') {
try {
node = yo`<div>${treeView.render(typeof details[propertyName] === 'object' ? details[propertyName] : JSON.parse(details[propertyName]))}</div>` // catch in case the parsing fails.
} catch (e) {
node = yo`<div>Unable to display "${propertyName}": ${e.message}</div>`
} }
} else { } else {
node = yo`<div>${JSON.stringify(details[propertyName], null, 4)}</div>` node = yo`<div> - </div>`
} }
return yo`<pre class="${css.value}">${node || ''}</pre>` } else {
node = yo`<div>${JSON.stringify(details[propertyName], null, 4)}</div>`
} }
function publish () { return yo`<pre class="${css.value}">${node || ''}</pre>`
const selectContractNames = self._view.contractNames }
if (selectContractNames.children.length > 0 && selectContractNames.selectedIndex >= 0) {
var contract = self.data.contractsDetails[selectContractNames.children[selectContractNames.selectedIndex].innerHTML] getContractProperty (property) {
if (contract.metadata === undefined || contract.metadata.length === 0) { if (!this.selectedContract) throw new Error('No contract compiled yet')
modalDialogCustom.alert('This contract may be abstract, may not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.') const contractProperties = this.data.contractsDetails[this.selectedContract]
} else { return contractProperties[property] || null
publishOnSwarm(contract, self._deps.fileManager, function (err, uploaded) { }
if (err) {
try { copyContractProperty (property) {
err = JSON.stringify(err) let content = getContractProperty(property)
} catch (e) {} if (!content) {
modalDialogCustom.alert(yo`<span>Failed to publish metadata file to swarm, please check the Swarm gateways is available ( swarm-gateways.net ).<br /> addTooltip('No content available for ' + property)
${err}</span>`) return
} else {
var result = yo`<div>${uploaded.map((value) => {
return yo`<div><b>${value.filename}</b> : <pre>${value.output.url}</pre></div>`
})}</div>`
modalDialogCustom.alert(yo`<span>Metadata published successfully.<br> <pre>${result}</pre> </span>`)
}
}, function (item) { // triggered each time there's a new verified publish (means hash correspond)
self._deps.swarmfileProvider.addReadOnly(item.hash, item.content)
})
}
}
} }
return self._view.el
try {
if (typeof content !== 'string') {
content = JSON.stringify(content, null, '\t')
}
} catch (e) {}
copy(content)
addTooltip('Copied value to clipboard')
}
copyABI () {
this.copyContractProperty('abi')
}
copyBytecode () {
this.copyContractProperty('bytecode')
}
render () {
if (this._view.el) return this._view.el
this._view.errorContainer = yo`<div class="alert alert-danger"></div>`
this._view.errorContainerHead = yo`<div class="alert alert-danger"></div>`
this._view.contractSelection = this.contractSelection()
this._view.compilerContainer = this.compilerContainer.render()
this.compilerContainer.currentFile = this._deps.fileManager.currentFile()
this._view.el = yo`
<div id="compileTabView">
${this._view.compilerContainer}
${this._view.contractSelection}
${this._view.errorContainerHead}
${this._view.errorContainer}
</div>`
return this._view.el
} }
} }

Loading…
Cancel
Save