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 {
constructor (registry) {
const self = this
self.event = new EventEmitter()
self._view = {
this.event = new EventEmitter()
this._view = {
el: null,
warnCompilationSlow: null,
errorContainer: null,
@ -30,10 +29,10 @@ class CompileTab {
contractNames: null,
contractEl: null
}
self.queryParams = new QueryParams()
this.queryParams = new QueryParams()
// dependencies
self._deps = {
this._deps = {
editor: registry.get('editor').api,
config: registry.get('config').api,
renderer: registry.get('renderer').api,
@ -42,52 +41,62 @@ class CompileTab {
fileProviders: registry.get('fileproviders').api,
pluginManager: registry.get('pluginmanager').api
}
self.data = {
this.data = {
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.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()
}
listenToEvents () {
const self = this
/************
* EVENTS
*/
self.compiler.event.register('compilationStarted', () => {
if (self._view.errorContainer) {
self._view.errorContainer.innerHTML = ''
self._view.errorContainerHead.innerHTML = ''
listenToEvents () {
this.compiler.event.register('compilationStarted', () => {
if (this._view.errorContainer) {
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) {
// forwarding the event to the appManager infra
self.event.emit('compilationFinished', source.target, source, self.data.selectedVersion, data)
}
// reset the contractMetadata list (used by the publish action)
self.data.contractsDetails = {}
// refill the dropdown list
self._view.contractNames.innerHTML = ''
if (success) {
// TODO consider using compile tab as a proper module instead of just forwarding event
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)
this.event.emit('compilationFinished', source.target, source, this.data.selectedVersion, data)
// Store the contracts
this.data.contractsDetails = {}
this.compiler.visitContracts((contract) => {
this.data.contractsDetails[contract.name] = parseContracts(
contract.name,
contract.object,
this.compiler.getSource(contract.file)
)
})
} 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']) {
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') {
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>
@ -99,18 +108,18 @@ class CompileTab {
if (data.errors && data.errors.length) {
error = true
data.errors.forEach((err) => {
if (self._deps.config.get('hideWarnings')) {
if (this._deps.config.get('hideWarnings')) {
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 {
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) {
self.compiler.visitContracts((contract) => {
self._deps.renderer.error(contract.name, self._view.errorContainer, {type: 'success'})
this.compiler.visitContracts((contract) => {
this._deps.renderer.error(contract.name, this._view.errorContainer, {type: 'success'})
})
}
})
@ -120,7 +129,7 @@ class CompileTab {
// ctrl+s or command+s
if ((e.metaKey || e.ctrlKey) && e.keyCode === 83) {
e.preventDefault()
self.compileTabLogic.runCompiler()
this.compileTabLogic.runCompiler()
}
})
}
@ -136,38 +145,96 @@ class CompileTab {
}
}
render () {
const self = this
if (self._view.el) return self._view.el
/*********
* SUB-COMPONENTS
*/
self._view.errorContainer = yo`<div class='error'></div>`
self._view.errorContainerHead = yo`<div class='error'></div>`
self._view.contractNames = yo`<select class="${css.contractNames}" disabled></select>`
self._view.contractEl = yo`
<div class="${css.container}">
<div class="${css.contractContainer}">
${self._view.contractNames}
<div title="Publish on Swarm" class="${css.publish}" onclick=${publish}>
<i class="${css.copyIcon} fa fa-upload" aria-hidden="true"></i><span>Swarm</span>
</div>
/**
* Section to select the compiled contract
* @param {string[]} contractList Names of the compiled contracts
*/
contractSelection(contractList = []) {
return contractList.length !== 0
? yo`<section class="${css.container}">
<!-- Select Compiler Version -->
<header class="navbar navbar-light bg-light input-group mb-3 ${css.compilerArticle}">
<div class="input-group-prepend">
<label class="input-group-text" for="compiledContracts">Contract</label>
</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 title="Display Contract Details" class="${css.details}" onclick=${details}>Details</div>
<div title="Copy ABI to clipboard" class="${css.copyButton}" onclick=${copyABI}>
<i class="${css.copyIcon} fa fa-clipboard" aria-hidden="true"></i> ABI
</div>
<div title="Copy Bytecode to clipboard" class="${css.copyButton} ${css.bytecodeButton}" onclick=${copyBytecode}>
<i class="${css.copyIcon} fa fa-clipboard" aria-hidden="true"></i> Bytecode
<span class="${css.copyToClipboard}">Copy to Clipboard : </span>
<div class="btn-group" role="group" aria-label="Copy to clipboard">
<button class="btn btn-secondary" title="Copy ABI to clipboard" onclick="${this.copyABI}">
<i class="${css.copyIcon} fa fa-clipboard" aria-hidden="true"></i>
<span>ABI</span>
</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>`
self._view.el = yo`
<div class="${css.compileTabView}" id="compileTabView">
${this.compilerContainer.render()}
${self._view.contractEl}
${self._view.errorContainerHead}
${self._view.errorContainer}
</div>`
</article>
</section>`
: yo`<article class="${css.compilerArticle}">
<span class="alert alert-warning" role="alert">No Contract Compiled Yet</span>
</article>`
}
// 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 = {
'Assembly': 'Assembly opcodes describing the contract including corresponding solidity source code',
'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)',
'web3Deploy': 'Copy/paste this code to any JavaScript/Web3 console to deploy this contract'
}
function getContractProperty (property) {
const select = self._view.contractNames
if (select.children.length > 0 && select.selectedIndex >= 0) {
const contractName = select.children[select.selectedIndex].innerHTML
const contractProperties = self.data.contractsDetails[contractName]
return contractProperties[property] || null
}
}
function copyContractProperty (property) {
let content = getContractProperty(property)
if (!content) {
addTooltip('No content available for ' + property)
return
}
try {
if (typeof content !== 'string') {
content = JSON.stringify(content, null, '\t')
}
} catch (e) {}
if (!this.selectedContract) throw new Error('No contract compiled yet')
const contractProperties = this.data.contractsDetails[this.selectedContract]
const log = yo`<div class="${css.detailsJSON}"></div>`
Object.keys(contractProperties).map(propertyName => {
const copyDetails = yo`<span class="${css.copyDetails}">${copyToClipboard(() => contractProperties[propertyName])}</span>`
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>
${this.insertValue(contractProperties, propertyName)}
</div>`)
})
modalDialog(this.selectedContract, log, { label: '' }, { label: 'Close' })
}
copy(content)
addTooltip('Copied value to clipboard')
}
function copyABI () {
copyContractProperty('abi')
}
function copyBytecode () {
copyContractProperty('bytecode')
}
function details () {
const select = self._view.contractNames
if (select.children.length > 0 && select.selectedIndex >= 0) {
const contractName = select.children[select.selectedIndex].innerHTML
const contractProperties = self.data.contractsDetails[contractName]
const log = yo`<div class="${css.detailsJSON}"></div>`
Object.keys(contractProperties).map(propertyName => {
const copyDetails = yo`<span class="${css.copyDetails}">${copyToClipboard(() => contractProperties[propertyName])}</span>`
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>`
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 = []
}
} else {
node = yo`<div> - </div>`
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 {
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 () {
const selectContractNames = self._view.contractNames
if (selectContractNames.children.length > 0 && selectContractNames.selectedIndex >= 0) {
var contract = self.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, self._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>`)
}
}, function (item) { // triggered each time there's a new verified publish (means hash correspond)
self._deps.swarmfileProvider.addReadOnly(item.hash, item.content)
})
}
}
return yo`<pre class="${css.value}">${node || ''}</pre>`
}
getContractProperty (property) {
if (!this.selectedContract) throw new Error('No contract compiled yet')
const contractProperties = this.data.contractsDetails[this.selectedContract]
return contractProperties[property] || null
}
copyContractProperty (property) {
let content = getContractProperty(property)
if (!content) {
addTooltip('No content available for ' + property)
return
}
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