initial logical separation between the UI and the logic of the universal-dapp

pull/1/head
Iuri Matias 7 years ago
parent ad7edf216c
commit 93c62d9e92
  1. 5
      src/app.js
  2. 336
      src/universal-dapp-ui.js
  3. 338
      src/universal-dapp.js

@ -7,6 +7,7 @@ var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager var EventManager = remixLib.EventManager
var UniversalDApp = require('./universal-dapp.js') var UniversalDApp = require('./universal-dapp.js')
var UniversalDAppUI = require('./universal-dapp-ui.js')
var Remixd = require('./lib/remixd') var Remixd = require('./lib/remixd')
var OffsetToLineColumnConverter = require('./lib/offsetToLineColumnConverter') var OffsetToLineColumnConverter = require('./lib/offsetToLineColumnConverter')
@ -262,6 +263,10 @@ function run () {
}, },
opt: { removable: false, removable_instances: true } opt: { removable: false, removable_instances: true }
}) })
var udappUI = new UniversalDAppUI(udapp)
console.dir(udappUI)
udapp.reset({}, transactionContextAPI) udapp.reset({}, transactionContextAPI)
udapp.event.register('debugRequested', this, function (txResult) { udapp.event.register('debugRequested', this, function (txResult) {
startdebugging(txResult.transactionHash) startdebugging(txResult.transactionHash)

@ -0,0 +1,336 @@
/* global */
'use strict'
var $ = require('jquery')
var ethJSUtil = require('ethereumjs-util')
var remixLib = require('remix-lib')
var yo = require('yo-yo')
var txFormat = require('./app/execution/txFormat')
var txHelper = require('./app/execution/txHelper')
var txExecution = require('./app/execution/txExecution')
var helper = require('./lib/helper')
var executionContext = require('./execution-context')
var copyToClipboard = require('./app/ui/copy-to-clipboard')
// -------------- styling ----------------------
var csjs = require('csjs-inject')
var styleGuide = remixLib.ui.themeChooser
var styles = styleGuide.chooser()
var css = csjs`
.instanceTitleContainer {
display: flex;
align-items: center;
}
.title {
${styles.rightPanel.runTab.titlebox_RunTab}
display: flex;
justify-content: end;
align-items: center;
font-size: 11px;
height: 30px;
width: 97%;
overflow: hidden;
word-break: break-word;
line-height: initial;
overflow: visible;
}
.titleLine {
display: flex;
align-items: baseline;
}
.titleText {
margin-right: 1em;
word-break: break-word;
min-width: 230px;
}
.title .copy {
color: ${styles.rightPanel.runTab.icon_AltColor_Instance_CopyToClipboard};
}
.instance {
${styles.rightPanel.runTab.box_Instance};
margin-bottom: 10px;
padding: 10px 15px 15px 15px;
}
.instance .title:before {
content: "\\25BE";
margin-right: 5%;
}
.instance.hidesub .title:before {
content: "\\25B8";
margin-right: 5%;
}
.instance.hidesub > * {
display: none;
}
.instance.hidesub .title {
display: flex;
}
.instance.hidesub .udappClose {
display: flex;
}
.buttonsContainer {
margin-top: 2%;
display: flex;
overflow: hidden;
}
.contractActions {
display: flex;
}
.instanceButton {}
.closeIcon {
font-size: 12px;
cursor: pointer;
}
.udappClose {
display: flex;
justify-content: flex-end;
}
.contractProperty {
overflow: auto;
margin-bottom: 0.4em;
}
.contractProperty.hasArgs input {
width: 75%;
padding: .36em;
}
.contractProperty button {
${styles.rightPanel.runTab.button_Create}
min-width: 100px;
width: 100px;
font-size: 10px;
margin:0;
word-break: inherit;
}
.contractProperty button:disabled {
cursor: not-allowed;
background-color: white;
border-color: lightgray;
}
.contractProperty.constant button {
${styles.rightPanel.runTab.button_Constant}
min-width: 100px;
width: 100px;
font-size: 10px;
margin:0;
word-break: inherit;
outline: none;
width: inherit;
}
.contractProperty input {
display: none;
}
.contractProperty > .value {
box-sizing: border-box;
float: left;
align-self: center;
color: ${styles.appProperties.mainText_Color};
margin-left: 4px;
}
.hasArgs input {
display: block;
border: 1px solid #dddddd;
padding: .36em;
border-left: none;
padding: 8px 8px 8px 10px;
font-size: 10px;
height: 25px;
}
.hasArgs button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 0;
}
`
/*
trigger debugRequested
*/
function UniversalDAppUI (udapp, opts = {}) {
var self = this
this.udapp = udapp
self.el = yo`<div class=${css.udapp}></div>`
}
UniversalDAppUI.prototype.reset = function (contracts, transactionContextAPI) {
this.el.innerHTML = ''
}
// TODO: has both UI and Model
UniversalDAppUI.prototype.renderInstance = function (contract, address, contractName) {
var abi = txHelper.sortAbiFunction(contract.abi)
return this.renderInstanceFromABI(abi, address, contractName)
}
// TODO this function was named before "appendChild".
// this will render an instance: contract name, contract address, and all the public functions
// basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed
// this returns a DOM element
UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName) {
var self = this
function remove () { instance.remove() }
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex')
var instance = yo`<div class="instance ${css.instance}" id="instance${address}"></div>`
var context = executionContext.isVM() ? 'memory' : 'blockchain'
var shortAddress = helper.shortenAddress(address)
var title = yo`<div class="${css.title}" onclick=${toggleClass}>
<div class="${css.titleText}"> ${contractName} at ${shortAddress} (${context}) </div>
${copyToClipboard(() => address)}
</div>`
if (self.removable_instances) {
var close = yo`<div class="${css.udappClose}" onclick=${remove}><i class="${css.closeIcon} fa fa-close" aria-hidden="true"></i></div>`
instance.append(close)
}
function toggleClass () {
$(instance).toggleClass(`${css.hidesub}`)
}
instance.appendChild(title)
// Add the fallback function
var fallback = txHelper.getFallbackInterface(contractABI)
if (fallback) {
instance.appendChild(this.getCallButton({
funABI: fallback,
address: address,
contractAbi: contractABI,
contractName: contractName
}))
}
$.each(contractABI, (i, funABI) => {
if (funABI.type !== 'function') {
return
}
// @todo getData cannot be used with overloaded functions
instance.appendChild(this.getCallButton({
funABI: funABI,
address: address,
contractAbi: contractABI,
contractName: contractName
}))
})
return instance
}
// TODO this is used by renderInstance when a new instance is displayed.
// this returns a DOM element.
UniversalDAppUI.prototype.getCallButton = function (args) {
var self = this
// args.funABI, args.address [fun only]
// args.contractName [constr only]
var lookupOnly = args.funABI.constant
var inputs = ''
if (args.funABI.inputs) {
inputs = txHelper.inputParametersDeclarationToString(args.funABI.inputs)
}
var inputField = yo`<input></input>`
inputField.setAttribute('placeholder', inputs)
inputField.setAttribute('title', inputs)
var outputOverride = yo`<div class=${css.value}></div>`
var title
if (args.funABI.name) {
title = args.funABI.name
} else {
title = '(fallback)'
}
var button = yo`<button onclick=${clickButton} class="${css.instanceButton}"></button>`
button.classList.add(css.call)
button.setAttribute('title', title)
button.innerHTML = title
function clickButton () {
call(true)
}
function call (isUserAction) {
var logMsg
if (isUserAction) {
if (!args.funABI.constant) {
logMsg = `transact to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
} else {
logMsg = `call to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
}
}
txFormat.buildData(args.contractName, args.contractAbi, self.contracts, false, args.funABI, inputField.value, self, (error, data) => {
if (!error) {
if (isUserAction) {
if (!args.funABI.constant) {
self._api.logMessage(`${logMsg} pending ... `)
} else {
self._api.logMessage(`${logMsg}`)
}
}
txExecution.callFunction(args.address, data, args.funABI, self, (error, txResult) => {
if (!error) {
var isVM = executionContext.isVM()
if (isVM) {
var vmError = txExecution.checkVMError(txResult)
if (vmError.error) {
self._api.logMessage(`${logMsg} errored: ${vmError.message} `)
return
}
}
if (lookupOnly) {
var decoded = txFormat.decodeResponseToTreeView(executionContext.isVM() ? txResult.result.vm.return : ethJSUtil.toBuffer(txResult.result), args.funABI)
outputOverride.innerHTML = ''
outputOverride.appendChild(decoded)
}
} else {
self._api.logMessage(`${logMsg} errored: ${error} `)
}
})
} else {
self._api.logMessage(`${logMsg} errored: ${error} `)
}
}, (msg) => {
self._api.logMessage(msg)
})
}
var contractProperty = yo`<div class="${css.contractProperty} ${css.buttonsContainer}"></div>`
var contractActions = yo`<div class="${css.contractActions}" ></div>`
contractProperty.appendChild(contractActions)
contractActions.appendChild(button)
if (inputs.length) {
contractActions.appendChild(inputField)
}
if (lookupOnly) {
contractProperty.appendChild(outputOverride)
}
if (lookupOnly) {
contractProperty.classList.add(css.constant)
button.setAttribute('title', (title + ' - call'))
}
if (args.funABI.inputs && args.funABI.inputs.length > 0) {
contractProperty.classList.add(css.hasArgs)
}
if (args.funABI.payable === true) {
contractProperty.classList.add(css.payable)
button.setAttribute('title', (title + ' - transact (payable)'))
}
if (!lookupOnly && args.funABI.payable === false) {
button.setAttribute('title', (title + ' - transact (not payable)'))
}
return contractProperty
}
module.exports = UniversalDAppUI

@ -1,165 +1,30 @@
/* global */ /* global */
'use strict' 'use strict'
var $ = require('jquery')
var ethJSUtil = require('ethereumjs-util') var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN var BN = ethJSUtil.BN
var remixLib = require('remix-lib') var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager var EventManager = remixLib.EventManager
var crypto = require('crypto') var crypto = require('crypto')
var TxRunner = require('./app/execution/txRunner') var TxRunner = require('./app/execution/txRunner')
var yo = require('yo-yo') // var txFormat = require('./app/execution/txFormat')
var txFormat = require('./app/execution/txFormat') // var txHelper = require('./app/execution/txHelper')
var txHelper = require('./app/execution/txHelper') // var txExecution = require('./app/execution/txExecution')
var txExecution = require('./app/execution/txExecution') // var helper = require('./lib/helper')
var helper = require('./lib/helper')
var executionContext = require('./execution-context') var executionContext = require('./execution-context')
var copyToClipboard = require('./app/ui/copy-to-clipboard')
var modalCustom = require('./app/ui/modal-dialog-custom') var modalCustom = require('./app/ui/modal-dialog-custom')
// -------------- styling ----------------------
var csjs = require('csjs-inject')
var styleGuide = remixLib.ui.themeChooser
var styles = styleGuide.chooser()
var css = csjs`
.instanceTitleContainer {
display: flex;
align-items: center;
}
.title {
${styles.rightPanel.runTab.titlebox_RunTab}
display: flex;
justify-content: end;
align-items: center;
font-size: 11px;
height: 30px;
width: 97%;
overflow: hidden;
word-break: break-word;
line-height: initial;
overflow: visible;
}
.titleLine {
display: flex;
align-items: baseline;
}
.titleText {
margin-right: 1em;
word-break: break-word;
min-width: 230px;
}
.title .copy {
color: ${styles.rightPanel.runTab.icon_AltColor_Instance_CopyToClipboard};
}
.instance {
${styles.rightPanel.runTab.box_Instance};
margin-bottom: 10px;
padding: 10px 15px 15px 15px;
}
.instance .title:before {
content: "\\25BE";
margin-right: 5%;
}
.instance.hidesub .title:before {
content: "\\25B8";
margin-right: 5%;
}
.instance.hidesub > * {
display: none;
}
.instance.hidesub .title {
display: flex;
}
.instance.hidesub .udappClose {
display: flex;
}
.buttonsContainer {
margin-top: 2%;
display: flex;
overflow: hidden;
}
.contractActions {
display: flex;
}
.instanceButton {}
.closeIcon {
font-size: 12px;
cursor: pointer;
}
.udappClose {
display: flex;
justify-content: flex-end;
}
.contractProperty {
overflow: auto;
margin-bottom: 0.4em;
}
.contractProperty.hasArgs input {
width: 75%;
padding: .36em;
}
.contractProperty button {
${styles.rightPanel.runTab.button_Create}
min-width: 100px;
width: 100px;
font-size: 10px;
margin:0;
word-break: inherit;
}
.contractProperty button:disabled {
cursor: not-allowed;
background-color: white;
border-color: lightgray;
}
.contractProperty.constant button {
${styles.rightPanel.runTab.button_Constant}
min-width: 100px;
width: 100px;
font-size: 10px;
margin:0;
word-break: inherit;
outline: none;
width: inherit;
}
.contractProperty input {
display: none;
}
.contractProperty > .value {
box-sizing: border-box;
float: left;
align-self: center;
color: ${styles.appProperties.mainText_Color};
margin-left: 4px;
}
.hasArgs input {
display: block;
border: 1px solid #dddddd;
padding: .36em;
border-left: none;
padding: 8px 8px 8px 10px;
font-size: 10px;
height: 25px;
}
.hasArgs button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 0;
}
`
/* /*
trigger debugRequested trigger debugRequested
*/ */
function UniversalDApp (opts = {}) { function UniversalDAppModel (opts = {}) {
this.event = new EventManager() this.event = new EventManager()
var self = this var self = this
self._api = opts.api self._api = opts.api
self.removable = opts.opt.removable self.removable = opts.opt.removable
self.removable_instances = opts.opt.removable_instances self.removable_instances = opts.opt.removable_instances
self.el = yo`<div class=${css.udapp}></div>` self.personalMode = opts.opt.personalMode || false
self.contracts self.contracts
self.transactionContextAPI self.transactionContextAPI
executionContext.event.register('contextChanged', this, function (context) { executionContext.event.register('contextChanged', this, function (context) {
@ -168,8 +33,7 @@ function UniversalDApp (opts = {}) {
self.txRunner = new TxRunner({}, opts.api) self.txRunner = new TxRunner({}, opts.api)
} }
UniversalDApp.prototype.reset = function (contracts, transactionContextAPI) { UniversalDAppModel.prototype.reset = function (contracts, transactionContextAPI) {
this.el.innerHTML = ''
this.contracts = contracts this.contracts = contracts
if (transactionContextAPI) { if (transactionContextAPI) {
this.transactionContextAPI = transactionContextAPI this.transactionContextAPI = transactionContextAPI
@ -186,7 +50,7 @@ UniversalDApp.prototype.reset = function (contracts, transactionContextAPI) {
this.txRunner = new TxRunner(this.accounts, this._api) this.txRunner = new TxRunner(this.accounts, this._api)
} }
UniversalDApp.prototype.newAccount = function (password, cb) { UniversalDAppModel.prototype.newAccount = function (password, cb) {
if (!executionContext.isVM()) { if (!executionContext.isVM()) {
if (!this._api.personalMode()) { if (!this._api.personalMode()) {
return cb('Not running in personal mode') return cb('Not running in personal mode')
@ -208,7 +72,7 @@ UniversalDApp.prototype.newAccount = function (password, cb) {
} }
} }
UniversalDApp.prototype._addAccount = function (privateKey, balance) { UniversalDAppModel.prototype._addAccount = function (privateKey, balance) {
var self = this var self = this
if (!executionContext.isVM()) { if (!executionContext.isVM()) {
@ -225,7 +89,7 @@ UniversalDApp.prototype._addAccount = function (privateKey, balance) {
} }
} }
UniversalDApp.prototype.getAccounts = function (cb) { UniversalDAppModel.prototype.getAccounts = function (cb) {
var self = this var self = this
if (!executionContext.isVM()) { if (!executionContext.isVM()) {
@ -245,7 +109,7 @@ UniversalDApp.prototype.getAccounts = function (cb) {
} }
} }
UniversalDApp.prototype.getBalance = function (address, cb) { UniversalDAppModel.prototype.getBalance = function (address, cb) {
var self = this var self = this
address = ethJSUtil.stripHexPrefix(address) address = ethJSUtil.stripHexPrefix(address)
@ -273,181 +137,7 @@ UniversalDApp.prototype.getBalance = function (address, cb) {
} }
} }
UniversalDApp.prototype.renderInstance = function (contract, address, contractName) { UniversalDAppModel.prototype.pendingTransactions = function () {
var abi = txHelper.sortAbiFunction(contract.abi)
return this.renderInstanceFromABI(abi, address, contractName)
}
// TODO this function was named before "appendChild".
// this will render an instance: contract name, contract address, and all the public functions
// basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed
// this returns a DOM element
UniversalDApp.prototype.renderInstanceFromABI = function (contractABI, address, contractName) {
var self = this
function remove () { instance.remove() }
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex')
var instance = yo`<div class="instance ${css.instance}" id="instance${address}"></div>`
var context = executionContext.isVM() ? 'memory' : 'blockchain'
var shortAddress = helper.shortenAddress(address)
var title = yo`<div class="${css.title}" onclick=${toggleClass}>
<div class="${css.titleText}"> ${contractName} at ${shortAddress} (${context}) </div>
${copyToClipboard(() => address)}
</div>`
if (self.removable_instances) {
var close = yo`<div class="${css.udappClose}" onclick=${remove}><i class="${css.closeIcon} fa fa-close" aria-hidden="true"></i></div>`
instance.append(close)
}
function toggleClass () {
$(instance).toggleClass(`${css.hidesub}`)
}
instance.appendChild(title)
// Add the fallback function
var fallback = txHelper.getFallbackInterface(contractABI)
if (fallback) {
instance.appendChild(this.getCallButton({
funABI: fallback,
address: address,
contractAbi: contractABI,
contractName: contractName
}))
}
$.each(contractABI, (i, funABI) => {
if (funABI.type !== 'function') {
return
}
// @todo getData cannot be used with overloaded functions
instance.appendChild(this.getCallButton({
funABI: funABI,
address: address,
contractAbi: contractABI,
contractName: contractName
}))
})
return instance
}
// TODO this is used by renderInstance when a new instance is displayed.
// this returns a DOM element.
UniversalDApp.prototype.getCallButton = function (args) {
var self = this
// args.funABI, args.address [fun only]
// args.contractName [constr only]
var lookupOnly = args.funABI.constant
var inputs = ''
if (args.funABI.inputs) {
inputs = txHelper.inputParametersDeclarationToString(args.funABI.inputs)
}
var inputField = yo`<input></input>`
inputField.setAttribute('placeholder', inputs)
inputField.setAttribute('title', inputs)
var outputOverride = yo`<div class=${css.value}></div>`
var title
if (args.funABI.name) {
title = args.funABI.name
} else {
title = '(fallback)'
}
var button = yo`<button onclick=${clickButton} class="${css.instanceButton}"></button>`
button.classList.add(css.call)
button.setAttribute('title', title)
button.innerHTML = title
function clickButton () {
call(true)
}
function call (isUserAction) {
var logMsg
if (isUserAction) {
if (!args.funABI.constant) {
logMsg = `transact to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
} else {
logMsg = `call to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
}
}
txFormat.buildData(args.contractName, args.contractAbi, self.contracts, false, args.funABI, inputField.value, self, (error, data) => {
if (!error) {
if (isUserAction) {
if (!args.funABI.constant) {
self._api.logMessage(`${logMsg} pending ... `)
} else {
self._api.logMessage(`${logMsg}`)
}
}
txExecution.callFunction(args.address, data, args.funABI, self, (error, txResult) => {
if (!error) {
var isVM = executionContext.isVM()
if (isVM) {
var vmError = txExecution.checkVMError(txResult)
if (vmError.error) {
self._api.logMessage(`${logMsg} errored: ${vmError.message} `)
return
}
}
if (lookupOnly) {
var decoded = txFormat.decodeResponseToTreeView(executionContext.isVM() ? txResult.result.vm.return : ethJSUtil.toBuffer(txResult.result), args.funABI)
outputOverride.innerHTML = ''
outputOverride.appendChild(decoded)
}
} else {
self._api.logMessage(`${logMsg} errored: ${error} `)
}
})
} else {
self._api.logMessage(`${logMsg} errored: ${error} `)
}
}, (msg) => {
self._api.logMessage(msg)
})
}
var contractProperty = yo`<div class="${css.contractProperty} ${css.buttonsContainer}"></div>`
var contractActions = yo`<div class="${css.contractActions}" ></div>`
contractProperty.appendChild(contractActions)
contractActions.appendChild(button)
if (inputs.length) {
contractActions.appendChild(inputField)
}
if (lookupOnly) {
contractProperty.appendChild(outputOverride)
}
if (lookupOnly) {
contractProperty.classList.add(css.constant)
button.setAttribute('title', (title + ' - call'))
}
if (args.funABI.inputs && args.funABI.inputs.length > 0) {
contractProperty.classList.add(css.hasArgs)
}
if (args.funABI.payable === true) {
contractProperty.classList.add(css.payable)
button.setAttribute('title', (title + ' - transact (payable)'))
}
if (!lookupOnly && args.funABI.payable === false) {
button.setAttribute('title', (title + ' - transact (not payable)'))
}
return contractProperty
}
UniversalDApp.prototype.pendingTransactions = function () {
return this.txRunner.pendingTxs return this.txRunner.pendingTxs
} }
@ -461,7 +151,7 @@ function execute (pipeline, env, callback) {
next(null, env) next(null, env)
} }
UniversalDApp.prototype.runTx = function (args, cb) { UniversalDAppModel.prototype.runTx = function (args, cb) {
var self = this var self = this
var tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: args.from, value: args.value } var tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: args.from, value: args.value }
var payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName } // contains decoded parameters var payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName } // contains decoded parameters
@ -546,4 +236,4 @@ function runTransaction (env, next) {
}) })
} }
module.exports = UniversalDApp module.exports = UniversalDAppModel

Loading…
Cancel
Save