@ -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 > < s p a n > S w a r m < / s p a n >
< / d i v >
/ * *
* 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 < / l a b e l >
< / d i v >
< select onchange = "${e => this.selectedContract = e.value}" id = "compiledContracts" class = "custom-select" >
$ { contractList . map ( ( name ) => yo ` <option value=" ${ name } "> ${ name } </option> ` ) }
< / s e l e c t >
< / h e a d e r >
< 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 < / s p a n >
< / b u t t o n >
< button class = "btn btn-secondary btn-block" title = "Display Contract Details" onclick = "${this.details}" >
Compilation Details
< / b u t t o n >
<!-- Copy to Clipboard -- >
< div class = "${css.contractHelperButtons}" >
< div title = "Display Contract Details" class = "${css.details}" onclick = $ { details } > Details < / d i v >
< div title = "Copy ABI to clipboard" class = "${css.copyButton}" onclick = $ { copyABI } >
< i class = "${css.copyIcon} fa fa-clipboard" aria - hidden = "true" > < / i > A B I
< / d i v >
< div title = "Copy Bytecode to clipboard" class = "${css.copyButton} ${css.bytecodeButton}" onclick = $ { copyBytecode } >
< i class = "${css.copyIcon} fa fa-clipboard" aria - hidden = "true" > < / i > B y t e c o d e
< span class = "${css.copyToClipboard}" > Copy to Clipboard : < / s p a n >
< 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 < / s p a n >
< / b u t t o n >
< 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 < / s p a n >
< / b u t t o n >
< / d i v >
< / d i v >
< / d i v > `
self . _view . el = yo `
< div class = "${css.compileTabView}" id = "compileTabView" >
$ { this . compilerContainer . render ( ) }
$ { self . _view . contractEl }
$ { self . _view . errorContainerHead }
$ { self . _view . errorContainer }
< / d i v > `
< / a r t i c l e >
< / s e c t i o n > `
: yo ` <article class=" ${ css . compilerArticle } ">
< span class = "alert alert-warning" role = "alert" > No Contract Compiled Yet < / s p a n >
< / a r t i c l e > `
}
// 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 } < / s p a n > ` )
} else {
var result = yo ` <div> ${ uploaded . map ( ( value ) => {
return yo ` <div><b> ${ value . filename } </b> : <pre> ${ value . output . url } </pre></div> `
} ) } < / d i v > `
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 } < / d i v >
$ { this . insertValue ( contractProperties , propertyName ) }
< / d i v > ` )
} )
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 } < / d i v >
$ { insertValue ( contractProperties , propertyName ) }
< / d i v > ` )
} )
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 } < / s p a n > ` )
} else {
var result = yo ` <div> ${ uploaded . map ( ( value ) => {
return yo ` <div><b> ${ value . filename } </b> : <pre> ${ value . output . url } </pre></div> `
} ) } < / d i v > `
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 }
< / d i v > `
return this . _view . el
}
}