Merge branch 'master' into fix-verticalIcons-badge

pull/1861/head
bunsenstraat 3 years ago committed by GitHub
commit 86189437e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      .eslintrc.json
  2. 14
      apps/remix-ide-e2e/nightwatch.ts
  3. 14
      apps/remix-ide-e2e/src/commands/addAtAddressInstance.ts
  4. 3
      apps/remix-ide-e2e/src/commands/clickInstance.ts
  5. 6
      apps/remix-ide-e2e/src/commands/createContract.ts
  6. 15
      apps/remix-ide-e2e/src/commands/modalFooterCancelClick.ts
  7. 11
      apps/remix-ide-e2e/src/commands/modalFooterOKClick.ts
  8. 2
      apps/remix-ide-e2e/src/commands/selectContract.ts
  9. 5
      apps/remix-ide-e2e/src/commands/sendLowLevelTx.ts
  10. 13
      apps/remix-ide-e2e/src/commands/signMessage.ts
  11. 9
      apps/remix-ide-e2e/src/commands/testConstantFunction.ts
  12. 1
      apps/remix-ide-e2e/src/commands/validateValueInput.ts
  13. 3
      apps/remix-ide-e2e/src/commands/verifyCallReturnValue.ts
  14. 1
      apps/remix-ide-e2e/src/helpers/init.ts
  15. 15
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  16. 12
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts
  17. 3
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  18. 38
      apps/remix-ide-e2e/src/tests/gist.test.ts
  19. 7
      apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts
  20. 10
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  21. 33
      apps/remix-ide-e2e/src/tests/recorder.test.ts
  22. 18
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  23. 1
      apps/remix-ide-e2e/src/tests/specialFunctions.test.ts
  24. 6
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  25. 27
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  26. 2
      apps/remix-ide-e2e/src/tests/txListener.test.ts
  27. 6
      apps/remix-ide-e2e/src/types/index.d.ts
  28. 2
      apps/remix-ide/.eslintrc
  29. 153
      apps/remix-ide/best-practices.md
  30. 4
      apps/remix-ide/docs/code_contribution_guide.md
  31. 149
      apps/remix-ide/src/app.js
  32. 31
      apps/remix-ide/src/app/components/hidden-panel.js
  33. 37
      apps/remix-ide/src/app/components/hidden-panel.tsx
  34. 38
      apps/remix-ide/src/app/components/main-panel.js
  35. 57
      apps/remix-ide/src/app/components/main-panel.tsx
  36. 111
      apps/remix-ide/src/app/components/panel.js
  37. 63
      apps/remix-ide/src/app/components/panel.ts
  38. 156
      apps/remix-ide/src/app/components/side-panel.js
  39. 95
      apps/remix-ide/src/app/components/side-panel.tsx
  40. 5
      apps/remix-ide/src/app/components/vertical-icons.js
  41. 194
      apps/remix-ide/src/app/editor/contextView.js
  42. 2
      apps/remix-ide/src/app/files/fileManager.ts
  43. 94
      apps/remix-ide/src/app/panels/layout.ts
  44. 204
      apps/remix-ide/src/app/panels/main-view.js
  45. 18
      apps/remix-ide/src/app/panels/tab-proxy.js
  46. 10
      apps/remix-ide/src/app/panels/terminal.js
  47. 53
      apps/remix-ide/src/app/plugins/test.ts
  48. 5
      apps/remix-ide/src/app/tabs/analysis-tab.js
  49. 82
      apps/remix-ide/src/app/tabs/hardhat-provider.js
  50. 128
      apps/remix-ide/src/app/tabs/hardhat-provider.tsx
  51. 423
      apps/remix-ide/src/app/tabs/runTab/contractDropdown.js
  52. 108
      apps/remix-ide/src/app/tabs/runTab/model/dropdownlogic.js
  53. 160
      apps/remix-ide/src/app/tabs/runTab/recorder.js
  54. 449
      apps/remix-ide/src/app/tabs/runTab/settings.js
  55. 225
      apps/remix-ide/src/app/tabs/styles/run-tab-styles.js
  56. 3
      apps/remix-ide/src/app/tabs/test-tab.js
  57. 22
      apps/remix-ide/src/app/udapp/make-udapp.js
  58. 235
      apps/remix-ide/src/app/udapp/run-tab.js
  59. 212
      apps/remix-ide/src/app/ui/TreeView.js
  60. 212
      apps/remix-ide/src/app/ui/auto-complete-popup.js
  61. 70
      apps/remix-ide/src/app/ui/card.js
  62. 142
      apps/remix-ide/src/app/ui/confirmDialog.js
  63. 83
      apps/remix-ide/src/app/ui/contextMenu.js
  64. 39
      apps/remix-ide/src/app/ui/copy-to-clipboard.js
  65. 131
      apps/remix-ide/src/app/ui/draggableContent.js
  66. 128
      apps/remix-ide/src/app/ui/dropdown.js
  67. 3
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  68. 228
      apps/remix-ide/src/app/ui/multiParamManager.js
  69. 153
      apps/remix-ide/src/app/ui/renderer.js
  70. 109
      apps/remix-ide/src/app/ui/sendTxCallbacks.js
  71. 52
      apps/remix-ide/src/app/ui/styles/renderer-styles.js
  72. 8
      apps/remix-ide/src/app/ui/svgLogo.js
  73. 590
      apps/remix-ide/src/app/ui/txLogger.js
  74. 272
      apps/remix-ide/src/app/ui/universal-dapp-ui.js
  75. 17
      apps/remix-ide/src/blockchain/blockchain.js
  76. 13
      apps/remix-ide/src/blockchain/helper.ts
  77. 34
      apps/remix-ide/src/framingService.js
  78. 135
      apps/remix-ide/src/lib/cmdInterpreterAPI.js
  79. 74
      apps/remix-ide/src/lib/gist-handler.js
  80. 88
      apps/remix-ide/src/lib/panels-resize.js
  81. 121
      apps/remix-ide/src/lib/publishOnIpfs.js
  82. 109
      apps/remix-ide/src/lib/publishOnSwarm.js
  83. 145
      apps/remix-ide/src/lib/remixd.js
  84. 64
      apps/remix-ide/src/migrateFileSystem.js
  85. 47
      apps/remix-ide/src/publishToStorage.js
  86. 8
      apps/remix-ide/src/remixAppManager.js
  87. 285
      apps/remix-ide/src/universal-dapp-styles.js
  88. 16
      apps/remix-ide/test/compiler-test.js
  89. 52
      apps/remix-ide/test/gist-handler-test.js
  90. 5
      apps/remix-ide/test/index.js
  91. 23
      apps/remix-ide/test/query-params-test.js
  92. 1
      apps/remix-ide/tsconfig.json
  93. 153
      best-practices.md
  94. 1
      libs/remix-core-plugin/src/index.ts
  95. 2
      libs/remix-core-plugin/src/lib/compiler-fetch-and-compile.ts
  96. 138
      libs/remix-core-plugin/src/lib/gist-handler.ts
  97. 1
      libs/remix-ui/app/src/index.ts
  98. 3
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.css
  99. 19
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx
  100. 16
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -24,12 +24,21 @@
{ {
"files": ["*.ts", "*.tsx"], "files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nrwl/nx/typescript"], "extends": ["plugin:@nrwl/nx/typescript"],
"rules": {} "rules": {
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-empty-function": "off",
"eslint-disable-next-line no-empty": "off",
"no-empty": "off"
}
}, },
{ {
"files": ["*.js", "*.jsx"], "files": ["*.js", "*.jsx"],
"extends": ["plugin:@nrwl/nx/javascript"], "extends": ["plugin:@nrwl/nx/javascript"],
"rules": {} "rules": {}
} }
] ],
"globals": {
"JSX": true
}
} }

@ -68,7 +68,13 @@ module.exports = {
desiredCapabilities: { desiredCapabilities: {
browserName: 'firefox', browserName: 'firefox',
javascriptEnabled: true, javascriptEnabled: true,
acceptSslCerts: true acceptSslCerts: true,
'moz:firefoxOptions': {
args: [
'-width=2560',
'-height=1440'
]
}
} }
}, },
@ -78,7 +84,11 @@ module.exports = {
javascriptEnabled: true, javascriptEnabled: true,
acceptSslCerts: true, acceptSslCerts: true,
'moz:firefoxOptions': { 'moz:firefoxOptions': {
args: ['-headless'] args: [
'-headless',
'-width=2560',
'-height=1440'
]
} }
} }
} }

@ -2,9 +2,9 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
class addAtAddressInstance extends EventEmitter { class addAtAddressInstance extends EventEmitter {
command (this: NightwatchBrowser, address: string, isValidFormat: boolean, isValidChecksum: boolean): NightwatchBrowser { command (this: NightwatchBrowser, address: string, isValidFormat: boolean, isValidChecksum: boolean, isAbi = true): NightwatchBrowser {
this.api.perform((done: VoidFunction) => { this.api.perform((done: VoidFunction) => {
addInstance(this.api, address, isValidFormat, isValidChecksum, () => { addInstance(this.api, address, isValidFormat, isValidChecksum, isAbi, () => {
done() done()
this.emit('complete') this.emit('complete')
}) })
@ -13,15 +13,19 @@ class addAtAddressInstance extends EventEmitter {
} }
} }
function addInstance (browser: NightwatchBrowser, address: string, isValidFormat: boolean, isValidChecksum: boolean, callback: VoidFunction) { function addInstance (browser: NightwatchBrowser, address: string, isValidFormat: boolean, isValidChecksum: boolean, isAbi: boolean, callback: VoidFunction) {
browser.clickLaunchIcon('udapp').clearValue('.ataddressinput').setValue('.ataddressinput', address, function () { browser.clickLaunchIcon('udapp').clearValue('.ataddressinput').setValue('.ataddressinput', address, function () {
if (!isValidFormat || !isValidChecksum) browser.assert.elementPresent('button[id^="runAndDeployAtAdressButton"]:disabled') if (!isValidFormat || !isValidChecksum) browser.assert.elementPresent('button[id^="runAndDeployAtAdressButton"]:disabled')
else { else if (isAbi) {
browser.click('button[id^="runAndDeployAtAdressButton"]') browser.click('button[id^="runAndDeployAtAdressButton"]')
.waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]')
.execute(function () { .execute(function () {
const modal = document.querySelector('#modal-footer-ok') as HTMLElement const modal = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any
modal.click() modal.click()
}) })
} else {
browser.click('button[id^="runAndDeployAtAdressButton"]')
} }
callback() callback()
}) })

@ -3,8 +3,7 @@ import EventEmitter from 'events'
class ClickInstance extends EventEmitter { class ClickInstance extends EventEmitter {
command (this: NightwatchBrowser, index: number): NightwatchBrowser { command (this: NightwatchBrowser, index: number): NightwatchBrowser {
index = index + 2 const selector = `[data-id="universalDappUiTitleExpander${index}"]`
const selector = '.instance:nth-of-type(' + index + ') > div > button'
this.api.waitForElementPresent(selector).waitForElementContainsText(selector, '', 60000).scrollAndClick(selector).perform(() => { this.emit('complete') }) this.api.waitForElementPresent(selector).waitForElementContainsText(selector, '', 60000).scrollAndClick(selector).perform(() => { this.emit('complete') })
return this return this

@ -15,12 +15,12 @@ class CreateContract extends EventEmitter {
function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) { function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) {
if (inputParams) { if (inputParams) {
browser.setValue('div[class^="contractActionsContainerSingle"] input', inputParams, function () { browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () {
browser.click('#runTabView button[class^="instanceButton"]').pause(500).perform(function () { callback() }) browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() })
}) })
} else { } else {
browser browser
.click('#runTabView button[class^="instanceButton"]') .click('.udapp_contractActionsContainerSingle > button')
.pause(500) .pause(500)
.perform(function () { callback() }) .perform(function () { callback() })
} }

@ -1,14 +1,15 @@
import { NightwatchBrowser } from 'nightwatch' import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
class ModalFooterOKClick extends EventEmitter { class ModalFooterCancelClick extends EventEmitter {
command (this: NightwatchBrowser): NightwatchBrowser { command (this: NightwatchBrowser, id?: string): NightwatchBrowser {
this.api.waitForElementVisible('#modal-footer-cancel').perform((client, done) => { const clientId = id ? `*[data-id="${id}-modal-footer-cancel-react"]` : '#modal-footer-cancel'
this.api.execute(function () { this.api.waitForElementVisible(clientId).perform((client, done) => {
const elem = document.querySelector('#modal-footer-cancel') as HTMLElement this.api.execute(function (clientId) {
const elem = document.querySelector(clientId) as HTMLElement
elem.click() elem.click()
}, [], () => { }, [clientId], () => {
done() done()
this.emit('complete') this.emit('complete')
}) })
@ -17,4 +18,4 @@ class ModalFooterOKClick extends EventEmitter {
} }
} }
module.exports = ModalFooterOKClick module.exports = ModalFooterCancelClick

@ -2,13 +2,14 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
class ModalFooterOKClick extends EventEmitter { class ModalFooterOKClick extends EventEmitter {
command (this: NightwatchBrowser): NightwatchBrowser { command (this: NightwatchBrowser, id?: string): NightwatchBrowser {
this.api.waitForElementVisible('#modal-footer-ok').perform((client, done) => { const clientId = id ? `*[data-id="${id}-modal-footer-ok-react"]` : '#modal-footer-ok'
this.api.execute(function () { this.api.waitForElementVisible(clientId).perform((client, done) => {
const elem = document.querySelector('#modal-footer-ok') as HTMLElement this.api.execute(function (clientId) {
const elem = document.querySelector(clientId) as HTMLElement
elem.click() elem.click()
}, [], () => { }, [clientId], () => {
done() done()
this.emit('complete') this.emit('complete')
}) })

@ -1,7 +1,7 @@
import { NightwatchBrowser } from 'nightwatch' import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
const selector = '#runTabView select[class^="contractNames"]' const selector = '.udapp_contractNames'
class SelectContract extends EventEmitter { class SelectContract extends EventEmitter {
command (this: NightwatchBrowser, contractName: string): NightwatchBrowser { command (this: NightwatchBrowser, contractName: string): NightwatchBrowser {

@ -6,10 +6,11 @@ class sendLowLevelTx extends EventEmitter {
console.log('low level transact to ', address, value, callData) console.log('low level transact to ', address, value, callData)
this.api.waitForElementVisible(`#instance${address} #deployAndRunLLTxSendTransaction`, 1000) this.api.waitForElementVisible(`#instance${address} #deployAndRunLLTxSendTransaction`, 1000)
.clearValue(`#instance${address} #deployAndRunLLTxCalldata`) .clearValue(`#instance${address} #deployAndRunLLTxCalldata`)
.setValue(`#instance${address} #deployAndRunLLTxCalldata`, callData) .sendKeys(`#instance${address} #deployAndRunLLTxCalldata`, ['_', this.api.Keys.BACK_SPACE, callData])
.waitForElementVisible('#value') .waitForElementVisible('#value')
.clearValue('#value') .clearValue('#value')
.setValue('#value', value) .sendKeys('#value', ['1', this.api.Keys.BACK_SPACE, value])
.pause(2000)
.scrollAndClick(`#instance${address} #deployAndRunLLTxSendTransaction`) .scrollAndClick(`#instance${address} #deployAndRunLLTxSendTransaction`)
.perform(() => { .perform(() => {
this.emit('complete') this.emit('complete')

@ -20,9 +20,12 @@ function signMsg (browser: NightwatchBrowser, msg: string, cb: (hash: { value: s
browser browser
.waitForElementPresent('i[id="remixRunSignMsg"]') .waitForElementPresent('i[id="remixRunSignMsg"]')
.click('i[id="remixRunSignMsg"]') .click('i[id="remixRunSignMsg"]')
.waitForElementVisible('textarea[id="prompt_text"]') .waitForElementVisible('*[data-id="signMessageTextarea"]', 120000)
.setValue('textarea[id="prompt_text"]', msg, () => { .click('*[data-id="signMessageTextarea"]')
browser.modalFooterOKClick().perform( .setValue('*[data-id="signMessageTextarea"]', msg)
.waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.perform(
(client, done) => { (client, done) => {
browser.waitForElementVisible('span[id="remixRunSignMsgHash"]').getText('span[id="remixRunSignMsgHash"]', (v) => { hash = v; done() }) browser.waitForElementVisible('span[id="remixRunSignMsgHash"]').getText('span[id="remixRunSignMsgHash"]', (v) => { hash = v; done() })
} }
@ -32,13 +35,13 @@ function signMsg (browser: NightwatchBrowser, msg: string, cb: (hash: { value: s
browser.waitForElementVisible('span[id="remixRunSignMsgSignature"]').getText('span[id="remixRunSignMsgSignature"]', (v) => { signature = v; done() }) browser.waitForElementVisible('span[id="remixRunSignMsgSignature"]').getText('span[id="remixRunSignMsgSignature"]', (v) => { signature = v; done() })
} }
) )
.modalFooterOKClick() .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.perform( .perform(
() => { () => {
cb(hash, signature) cb(hash, signature)
} }
) )
})
} }
module.exports = SelectContract module.exports = SelectContract

@ -20,16 +20,17 @@ function testConstantFunction (browser: NightwatchBrowser, address: string, fnFu
document.querySelector('#runTabView').scrollTop = document.querySelector('#runTabView').scrollHeight document.querySelector('#runTabView').scrollTop = document.querySelector('#runTabView').scrollHeight
}, [], function () { }, [], function () {
if (expectedInput) { if (expectedInput) {
client.setValue('#runTabView input[title="' + expectedInput.types + '"]', expectedInput.values) client.waitForElementPresent('#runTabView input[title="' + expectedInput.types + '"]')
.setValue('#runTabView input[title="' + expectedInput.types + '"]', expectedInput.values)
} }
done() done()
}) })
}) })
.click('.instance button[title="' + fnFullName + '"]') .click('.instance button[title="' + fnFullName + '"]')
.pause(1000) .pause(1000)
.waitForElementPresent('#instance' + address + ' div[class^="contractActionsContainer"] div[class^="value"]') .waitForElementPresent('#instance' + address + ' .udapp_contractActionsContainer .udapp_value')
.scrollInto('#instance' + address + ' div[class^="contractActionsContainer"] div[class^="value"]') .scrollInto('#instance' + address + ' .udapp_contractActionsContainer .udapp_value')
.assert.containsText('#instance' + address + ' div[class^="contractActionsContainer"] div[class^="value"]', expectedOutput).perform(() => { .assert.containsText('#instance' + address + ' .udapp_contractActionsContainer .udapp_value', expectedOutput).perform(() => {
cb() cb()
}) })
} }

@ -6,6 +6,7 @@ class ValidateValueInput extends EventEmitter {
const browser = this.api const browser = this.api
browser.perform((done) => { browser.perform((done) => {
browser.clearValue(selector) browser.clearValue(selector)
.pause(2000)
.setValue(selector, valueTosSet) .setValue(selector, valueTosSet)
.execute(function (selector) { .execute(function (selector) {
const elem = document.querySelector(selector) as HTMLInputElement const elem = document.querySelector(selector) as HTMLInputElement

@ -15,7 +15,7 @@ class VerifyCallReturnValue extends EventEmitter {
function verifyCallReturnValue (browser: NightwatchBrowser, address: string, checks: string[], done: VoidFunction) { function verifyCallReturnValue (browser: NightwatchBrowser, address: string, checks: string[], done: VoidFunction) {
browser.execute(function (address: string) { browser.execute(function (address: string) {
const nodes = document.querySelectorAll('#instance' + address + ' div[class^="contractActionsContainer"] div[class^="value"]') as NodeListOf<HTMLElement> const nodes = document.querySelectorAll('#instance' + address + ' [data-id="udapp_value"]') as NodeListOf<HTMLElement>
const ret = [] const ret = []
for (let k = 0; k < nodes.length; k++) { for (let k = 0; k < nodes.length; k++) {
const text = nodes[k].innerText ? nodes[k].innerText : nodes[k].textContent const text = nodes[k].innerText ? nodes[k].innerText : nodes[k].textContent
@ -23,7 +23,6 @@ function verifyCallReturnValue (browser: NightwatchBrowser, address: string, che
} }
return ret return ret
}, [address], function (result) { }, [address], function (result) {
console.log('verifyCallReturnValue', result)
for (const k in checks) { for (const k in checks) {
browser.assert.equal(result.value[k].trim(), checks[k].trim()) browser.assert.equal(result.value[k].trim(), checks[k].trim())
} }

@ -9,6 +9,7 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
.switchBrowserTab(0) .switchBrowserTab(0)
.waitForElementVisible('[id="remixTourSkipbtn"]') .waitForElementVisible('[id="remixTourSkipbtn"]')
.click('[id="remixTourSkipbtn"]') .click('[id="remixTourSkipbtn"]')
.maximizeWindow()
.fullscreenWindow(() => { .fullscreenWindow(() => {
if (preloadPlugins) { if (preloadPlugins) {
initModules(browser, () => { initModules(browser, () => {

@ -25,7 +25,7 @@ module.exports = {
.setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]') .setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]')
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.click('*[data-id="universalDappUiTitleExpander"]') .clickInstance(0)
.clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' }) .clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' })
.testFunction('last', .testFunction('last',
{ {
@ -70,7 +70,7 @@ module.exports = {
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true) .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true)
.pause(500) .pause(500)
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.click('*[data-id="universalDappUiTitleExpander"]') .clickInstance(0)
.clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' }) .clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' })
.testFunction('last', .testFunction('last',
{ {
@ -84,17 +84,24 @@ module.exports = {
.openFile('Untitled.sol') .openFile('Untitled.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]') .click('*[data-id="settingsWeb3Mode"]')
.modalFooterOKClick() .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]')
.execute(function () {
const modal = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any
modal.click()
})
.pause(5000)
.execute(function () { .execute(function () {
const env: any = document.getElementById('selectExEnvOptions') const env: any = document.getElementById('selectExEnvOptions')
return env.value return env.value
}, [], function (result) { }, [], function (result) {
console.log({ result })
browser.assert.ok(result.value === 'web3', 'Web3 Provider not selected') browser.assert.ok(result.value === 'web3', 'Web3 Provider not selected')
}) })
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.pause(2000) .pause(2000)
.clearValue('input[placeholder="bytes32[] proposalNames"]')
.setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]') .setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]')
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.clickInstance(0) .clickInstance(0)

@ -33,7 +33,7 @@ module.exports = {
.setValue('input[placeholder="uint8 _numProposals"]', '2') .setValue('input[placeholder="uint8 _numProposals"]', '2')
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.click('*[data-id="universalDappUiTitleExpander"]') .clickInstance(0)
.clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' }) .clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' })
.testFunction('last', .testFunction('last',
{ {
@ -65,7 +65,7 @@ module.exports = {
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true) .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true)
.pause(500) .pause(500)
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.click('*[data-id="universalDappUiTitleExpander"]') .clickInstance(0)
.clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' }) .clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' })
.testFunction('last', .testFunction('last',
{ {
@ -79,10 +79,16 @@ module.exports = {
.openFile('Untitled.sol') .openFile('Untitled.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]') .click('*[data-id="settingsWeb3Mode"]')
.modalFooterOKClick() .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]')
.execute(function () {
const modal = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any
modal.click()
})
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.pause(2000) .pause(2000)
.clearValue('input[placeholder="uint8 _numProposals"]')
.setValue('input[placeholder="uint8 _numProposals"]', '2') .setValue('input[placeholder="uint8 _numProposals"]', '2')
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.clickInstance(0) .clickInstance(0)

@ -25,8 +25,7 @@ module.exports = {
'Should debug failing transaction #group1': function (browser: NightwatchBrowser) { 'Should debug failing transaction #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindudapp"]') browser.waitForElementVisible('*[data-id="verticalIconsKindudapp"]')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="universalDappUiTitleExpander"]') .clickInstance(0)
.click('*[data-id="universalDappUiTitleExpander"]')
.scrollAndClick('*[title="string name, uint256 goal"]') .scrollAndClick('*[title="string name, uint256 goal"]')
.setValue('*[title="string name, uint256 goal"]', '"toast", 999') .setValue('*[title="string name, uint256 goal"]', '"toast", 999')
.click('*[data-id="createProject - transact (not payable)"]') .click('*[data-id="createProject - transact (not payable)"]')

@ -76,29 +76,34 @@ module.exports = {
.waitForElementVisible('button[data-id="landingPageImportFromGistButton"]') .waitForElementVisible('button[data-id="landingPageImportFromGistButton"]')
.pause(1000) .pause(1000)
.scrollAndClick('button[data-id="landingPageImportFromGistButton"]') .scrollAndClick('button[data-id="landingPageImportFromGistButton"]')
.waitForElementVisible('*[data-id="modalDialogModalTitle"]') .waitForElementVisible('*[data-id="gisthandlerModalDialogModalTitle-react"]')
.assert.containsText('*[data-id="modalDialogModalTitle"]', 'Load a Gist') .assert.containsText('*[data-id="gisthandlerModalDialogModalTitle-react"]', 'Load a Gist')
.waitForElementVisible('*[data-id="modalDialogModalBody"]') .waitForElementVisible('*[data-id="gisthandlerModalDialogModalBody-react"]')
.assert.containsText('*[data-id="modalDialogModalBody"]', 'Enter the ID of the Gist or URL you would like to load.') .assert.containsText('*[data-id="gisthandlerModalDialogModalBody-react"]', 'Enter the ID of the Gist or URL you would like to load.')
.waitForElementVisible('*[data-id="modalDialogCustomPromptText"]') .waitForElementVisible('*[data-id="modalDialogCustomPromp"]')
.modalFooterCancelClick() .modalFooterCancelClick('gisthandler')
}, },
'Display Error Message For Invalid Gist ID': function (browser: NightwatchBrowser) { 'Display Error Message For Invalid Gist ID': function (browser: NightwatchBrowser) {
browser browser
.pause(1000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.scrollAndClick('*[data-id="landingPageImportFromGistButton"]') .scrollAndClick('*[data-id="landingPageImportFromGistButton"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptText"]') .waitForElementVisible('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', testData.invalidGistId) .execute(() => {
.modalFooterOKClick() (document.querySelector('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
.waitForElementVisible('*[data-id="modalDialogModalBody"]') }, [], () => {})
.assert.containsText('*[data-id="modalDialogModalBody"]', 'Not Found') .setValue('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]', testData.invalidGistId)
.modalFooterOKClick() .modalFooterOKClick('gisthandler')
.waitForElementVisible('*[data-id="gisthandlerModalDialogModalBody-react"]')
.assert.containsText('*[data-id="gisthandlerModalDialogModalBody-react"]', 'Not Found')
.modalFooterOKClick('gisthandler')
}, },
'Display Error Message For Missing Gist Token When Publishing': function (browser: NightwatchBrowser) { 'Display Error Message For Missing Gist Token When Publishing': function (browser: NightwatchBrowser) {
browser browser
.pause(1000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('settings') .clickLaunchIcon('settings')
.waitForElementVisible('[data-id="settingsTabRemoveGistToken"]') .waitForElementVisible('[data-id="settingsTabRemoveGistToken"]')
@ -129,9 +134,12 @@ module.exports = {
.click('[data-id="settingsTabSaveGistToken"]') .click('[data-id="settingsTabSaveGistToken"]')
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.scrollAndClick('*[data-id="landingPageImportFromGistButton"]') .scrollAndClick('*[data-id="landingPageImportFromGistButton"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptText"]') .waitForElementVisible('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', testData.validGistId) .execute(function () {
.modalFooterOKClick() (document.querySelector('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
})
.setValue('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]', testData.validGistId)
.modalFooterOKClick('gisthandler')
.openFile(`gist-${testData.validGistId}/README.txt`) .openFile(`gist-${testData.validGistId}/README.txt`)
.waitForElementVisible(`div[title='default_workspace/gist-${testData.validGistId}/README.txt']`) .waitForElementVisible(`div[title='default_workspace/gist-${testData.validGistId}/README.txt']`)
.assert.containsText(`div[title='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt') .assert.containsText(`div[title='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt')

@ -27,8 +27,7 @@ module.exports = {
console.log('testAutoDeployLib ' + address) console.log('testAutoDeployLib ' + address)
addressRef = address addressRef = address
}) })
.waitForElementPresent('.instance:nth-of-type(2)') .clickInstance(0)
.click('.instance:nth-of-type(2) > div > button')
.perform((done) => { .perform((done) => {
browser.testConstantFunction(addressRef, 'get - call', null, '0:\nuint256: 45').perform(() => { browser.testConstantFunction(addressRef, 'get - call', null, '0:\nuint256: 45').perform(() => {
done() done()
@ -37,7 +36,6 @@ module.exports = {
}, },
'Test Manual Deploy Lib': function (browser: NightwatchBrowser) { 'Test Manual Deploy Lib': function (browser: NightwatchBrowser) {
console.log('testManualDeployLib')
browser.click('*[data-id="deployAndRunClearInstances"]') browser.click('*[data-id="deployAndRunClearInstances"]')
.pause(5000) .pause(5000)
.clickLaunchIcon('settings') .clickLaunchIcon('settings')
@ -104,8 +102,7 @@ function checkDeployShouldSucceed (browser: NightwatchBrowser, address: string,
.getAddressAtPosition(1, (address) => { .getAddressAtPosition(1, (address) => {
addressRef = address addressRef = address
}) })
.waitForElementPresent('.instance:nth-of-type(3)') .clickInstance(1)
.click('.instance:nth-of-type(3) > div > button')
.perform(() => { .perform(() => {
browser browser
.testConstantFunction(addressRef, 'get - call', null, '0:\nuint256: 45') .testConstantFunction(addressRef, 'get - call', null, '0:\nuint256: 45')

@ -60,12 +60,14 @@ module.exports = {
.click('*[data-id="contractDropdownIpfsCheckbox"]') .click('*[data-id="contractDropdownIpfsCheckbox"]')
.waitForElementVisible('*[data-id="Deploy - transact (not payable)"]') .waitForElementVisible('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.pause(8000) .pause(5000)
.getModalBody((value, done) => { .waitForElementVisible('[data-id="udappModalDialogModalBody-react"]')
.getText('[data-id="udappModalDialogModalBody-react"]', (result) => {
const value = typeof result.value === 'string' ? result.value : null
if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed') if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
done()
}) })
.modalFooterOKClick() .modalFooterOKClick('udapp')
}, },
'Should remember choice after page refresh': function (browser: NightwatchBrowser) { 'Should remember choice after page refresh': function (browser: NightwatchBrowser) {

@ -17,16 +17,14 @@ module.exports = {
.pause(5000) .pause(5000)
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('div[class^="cardContainer"] i[class^="arrow"]') .click('[data-id="udapp_arrow"]')
.click('#runTabView .runtransaction') .click('[data-id="runtransaction"]')
.waitForElementPresent('.instance:nth-of-type(2)') .clickInstance(0)
.click('.instance:nth-of-type(2) > div > button') .clickInstance(1)
.waitForElementPresent('.instance:nth-of-type(3)')
.click('.instance:nth-of-type(3) > div > button')
.clickFunction('getInt - call') .clickFunction('getInt - call')
.clickFunction('getAddress - call') .clickFunction('getAddress - call')
.clickFunction('getFromLib - call') .clickFunction('getFromLib - call')
.waitForElementPresent('div[class^="contractActionsContainer"] div[class^="value"] ul') .waitForElementPresent('[data-id="udapp_value"]')
.getAddressAtPosition(1, (address) => { .getAddressAtPosition(1, (address) => {
console.log('Test Recorder ' + address) console.log('Test Recorder ' + address)
addressRef = address addressRef = address
@ -39,11 +37,15 @@ module.exports = {
.testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder']) .testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.createContract('12') .createContract('12')
.waitForElementPresent('.instance:nth-of-type(2)') .clickInstance(0)
.click('.instance:nth-of-type(2) > div > button')
.clickFunction('set - transact (not payable)', { types: 'uint256 _p', values: '34' }) .clickFunction('set - transact (not payable)', { types: 'uint256 _p', values: '34' })
.click('i.savetransaction') .click('i.savetransaction')
.modalFooterOKClick() .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.execute(function () {
const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any
modalOk.click()
})
.getEditorValue(function (result) { .getEditorValue(function (result) {
const parsed = JSON.parse(result) const parsed = JSON.parse(result)
browser.assert.equal(JSON.stringify(parsed.transactions[0].record.parameters), JSON.stringify(scenario.transactions[0].record.parameters)) browser.assert.equal(JSON.stringify(parsed.transactions[0].record.parameters), JSON.stringify(scenario.transactions[0].record.parameters))
@ -73,11 +75,16 @@ module.exports = {
.pause(1000) .pause(1000)
.createContract('') .createContract('')
.click('i.savetransaction') .click('i.savetransaction')
.modalFooterOKClick() .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.execute(function () {
const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any
modalOk.click()
})
.click('*[data-id="deployAndRunClearInstances"]') // clear udapp .click('*[data-id="deployAndRunClearInstances"]') // clear udapp
.click('*[data-id="terminalClearConsole"]') // clear terminal .click('*[data-id="terminalClearConsole"]') // clear terminal
.click('#runTabView .runtransaction') .click('[data-id="runtransaction"]')
.clickInstance(1) .clickInstance(2)
.pause(1000) .pause(1000)
.clickFunction('set2 - transact (not payable)', { types: 'uint256 _po', values: '10' }) .clickFunction('set2 - transact (not payable)', { types: 'uint256 _po', values: '10' })
.testFunction('last', .testFunction('last',

@ -36,16 +36,19 @@ module.exports = {
.pause(2000) .pause(2000)
.click('*[data-id="settingsRemixRunSignMsg"]') .click('*[data-id="settingsRemixRunSignMsg"]')
.pause(2000) .pause(2000)
.waitForElementVisible('*[data-id="modalDialogCustomPromptText"]', 120000) .waitForElementVisible('*[data-id="signMessageTextarea"]', 120000)
.setValue('*[data-id="modalDialogCustomPromptText"]', 'Remix is cool!') .click('*[data-id="signMessageTextarea"]')
.setValue('*[data-id="signMessageTextarea"]', 'Remix is cool!')
.assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgHash"]') .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgHash"]')
.assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgSignature"]') .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgSignature"]')
.pause(2000) .pause(2000)
.modalFooterOKClick() .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]', 12000) .click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementVisible('*[data-id="udappNotifyModalDialogModalBody-react"]', 12000)
.assert.elementPresent('*[data-id="settingsRemixRunSignMsgHash"]') .assert.elementPresent('*[data-id="settingsRemixRunSignMsgHash"]')
.assert.elementPresent('*[data-id="settingsRemixRunSignMsgSignature"]') .assert.elementPresent('*[data-id="settingsRemixRunSignMsgSignature"]')
.modalFooterOKClick() .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
}, },
'Should deploy contract on JavascriptVM #group3': function (browser: NightwatchBrowser) { 'Should deploy contract on JavascriptVM #group3': function (browser: NightwatchBrowser) {
@ -64,8 +67,7 @@ module.exports = {
'Should run low level interaction (fallback function) #group3': function (browser: NightwatchBrowser) { 'Should run low level interaction (fallback function) #group3': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.waitForElementPresent('*[data-id="universalDappUiTitleExpander"]') .clickInstance(0)
.click('*[data-id="universalDappUiTitleExpander"]')
.waitForElementPresent('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]') .waitForElementPresent('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]')
.click('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]') .click('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]')
.pause(5000) .pause(5000)
@ -181,7 +183,7 @@ module.exports = {
.useCss().switchBrowserTab(0) .useCss().switchBrowserTab(0)
.refresh() .refresh()
.clickLaunchIcon('pluginManager') // load debugger and source verification .clickLaunchIcon('pluginManager') // load debugger and source verification
// .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_source-verification"] button') // .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_sourcify"] button')
// debugger already activated .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button') // debugger already activated .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="settingsSelectEnvOptions"]') .waitForElementPresent('*[data-id="settingsSelectEnvOptions"]')

@ -173,6 +173,7 @@ module.exports = {
.waitForElementVisible('#value') .waitForElementVisible('#value')
.clearValue('#value') .clearValue('#value')
.setValue('#value', '0') .setValue('#value', '0')
.pause(2000)
.createContract('') .createContract('')
.clickInstance(1) .clickInstance(1)
.pause(1000) .pause(1000)

@ -51,7 +51,7 @@ module.exports = {
.click('*[data-id="terminalClearConsole"]') // clear the terminal .click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]') .click('*[data-id="settingsWeb3Mode"]')
.modalFooterOKClick() .modalFooterOKClick('udappNotify')
.executeScript('web3.eth.getAccounts()') .executeScript('web3.eth.getAccounts()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content .waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content
.waitForElementContainsText('*[data-id="terminalJournal"]', '"]', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', '"]', 60000)
@ -109,10 +109,10 @@ module.exports = {
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Contract Address:', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'Contract Address:', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deployment successful.', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'Deployment successful.', 60000)
.addAtAddressInstance('0xd9145CCE52D386f254917e481eB44e9943F39138', true, true) .addAtAddressInstance('0xd9145CCE52D386f254917e481eB44e9943F39138', true, true, false)
.click('*[data-id="terminalClearConsole"]') // clear the terminal .click('*[data-id="terminalClearConsole"]') // clear the terminal
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.click('*[data-id="universalDappUiTitleExpander"]') .clickInstance(0)
.clickFunction('changeOwner - transact (not payable)', { types: 'address newOwner', values: '0xd9145CCE52D386f254917e481eB44e9943F39138' }) // execute the "changeOwner" function .clickFunction('changeOwner - transact (not payable)', { types: 'address newOwner', values: '0xd9145CCE52D386f254917e481eB44e9943F39138' }) // execute the "changeOwner" function
.waitForElementContainsText('*[data-id="terminalJournal"]', 'previousOwner', 60000) // check that the script is logging the event .waitForElementContainsText('*[data-id="terminalJournal"]', 'previousOwner', 60000) // check that the script is logging the event
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', 60000) // check that the script is logging the event .waitForElementContainsText('*[data-id="terminalJournal"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', 60000) // check that the script is logging the event

@ -15,11 +15,8 @@ module.exports = {
browser.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['TestContract']) browser.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['TestContract'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('#runTabView button[class^="instanceButton"]') .click('.udapp_contractActionsContainerSingle > button')
.waitForElementPresent('.instance:nth-of-type(2)') .clickInstance(0)
.click('.instance:nth-of-type(2) > div > button')
.click('#runTabView .instance div[class^="title"]')
.click('#runTabView .instance div[class^="title"]')
.clickFunction('f - transact (not payable)') .clickFunction('f - transact (not payable)')
.testFunction('last', .testFunction('last',
{ {
@ -45,9 +42,8 @@ module.exports = {
'Test Complex Return Values #group1': function (browser: NightwatchBrowser) { 'Test Complex Return Values #group1': function (browser: NightwatchBrowser) {
browser.testContracts('returnValues.sol', sources[1]['returnValues.sol'], ['testReturnValues']) browser.testContracts('returnValues.sol', sources[1]['returnValues.sol'], ['testReturnValues'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('#runTabView button[class^="instanceButton"]') .click('.udapp_contractActionsContainerSingle > button')
.waitForElementPresent('.instance:nth-of-type(2)') .clickInstance(0)
.click('.instance:nth-of-type(2) > div > button')
.clickFunction('retunValues1 - transact (not payable)') .clickFunction('retunValues1 - transact (not payable)')
.testFunction('last', .testFunction('last',
{ {
@ -90,9 +86,8 @@ module.exports = {
'Test Complex Input Values #group2': function (browser: NightwatchBrowser) { 'Test Complex Input Values #group2': function (browser: NightwatchBrowser) {
browser.testContracts('inputValues.sol', sources[2]['inputValues.sol'], ['test']) browser.testContracts('inputValues.sol', sources[2]['inputValues.sol'], ['test'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('#runTabView button[class^="instanceButton"]') .click('.udapp_contractActionsContainerSingle > button')
.waitForElementPresent('.instance:nth-of-type(2)') .clickInstance(0)
.click('.instance:nth-of-type(2) > div > button')
.clickFunction('inputValue1 - transact (not payable)', { types: 'uint256 _u, int256 _i, string _str', values: '"2343242", "-4324324", "string _ string _ string _ string _ string _ string _ string _ string _ string _ string _"' }) .clickFunction('inputValue1 - transact (not payable)', { types: 'uint256 _u, int256 _i, string _str', values: '"2343242", "-4324324", "string _ string _ string _ string _ string _ string _ string _ string _ string _ string _"' })
.testFunction('last', .testFunction('last',
{ {
@ -136,8 +131,8 @@ module.exports = {
browser.testContracts('eventFunctionInput.sol', sources[3]['eventFunctionInput.sol'], ['C']) browser.testContracts('eventFunctionInput.sol', sources[3]['eventFunctionInput.sol'], ['C'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('#runTabView button[class^="instanceButton"]') .click('.udapp_contractActionsContainerSingle > button')
.waitForElementPresent('.instance:nth-of-type(2)') .clickInstance(0)
.click('*[data-id="deployAndRunClearInstances"]') .click('*[data-id="deployAndRunClearInstances"]')
}, },
@ -145,7 +140,7 @@ module.exports = {
browser.testContracts('customError.sol', sources[4]['customError.sol'], ['C']) browser.testContracts('customError.sol', sources[4]['customError.sol'], ['C'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('#runTabView button[class^="instanceButton"]') .click('.udapp_contractActionsContainerSingle > button')
.clickInstance(0) .clickInstance(0)
.clickFunction('g - transact (not payable)') .clickFunction('g - transact (not payable)')
.pause(5000) .pause(5000)
@ -168,7 +163,7 @@ module.exports = {
.clearTransactions() .clearTransactions()
.click('*[data-id="settingsVMLondonMode"]') // switch to London fork .click('*[data-id="settingsVMLondonMode"]') // switch to London fork
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('#runTabView button[class^="instanceButton"]') .click('.udapp_contractActionsContainerSingle > button')
.clickInstance(0) .clickInstance(0)
.clickFunction('g - transact (not payable)') .clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:') .journalLastChildIncludes('Error provided by the contract:')
@ -186,7 +181,7 @@ module.exports = {
'Should Compile and Deploy a contract which define a custom error in a library, the error should be logged in the terminal #group3': function (browser: NightwatchBrowser) { 'Should Compile and Deploy a contract which define a custom error in a library, the error should be logged in the terminal #group3': function (browser: NightwatchBrowser) {
browser.testContracts('customErrorLib.sol', sources[5]['customErrorLib.sol'], ['D']) browser.testContracts('customErrorLib.sol', sources[5]['customErrorLib.sol'], ['D'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('#runTabView button[class^="instanceButton"]') .click('.udapp_contractActionsContainerSingle > button')
.clickInstance(1) .clickInstance(1)
.clickFunction('h - transact (not payable)') .clickFunction('h - transact (not payable)')
.pause(5000) .pause(5000)

@ -26,7 +26,7 @@ module.exports = {
.setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]') .setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]')
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.click('*[data-id="universalDappUiTitleExpander"]') .clickInstance(0)
.clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' }) .clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' })
.testFunction('last', .testFunction('last',
{ {

@ -17,8 +17,8 @@ declare module 'nightwatch' {
testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser, testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser,
goToVMTraceStep(step: number, incr?: number): NightwatchBrowser, goToVMTraceStep(step: number, incr?: number): NightwatchBrowser,
checkVariableDebug(id: string, debugValue: NightwatchCheckVariableDebugValue): NightwatchBrowser, checkVariableDebug(id: string, debugValue: NightwatchCheckVariableDebugValue): NightwatchBrowser,
addAtAddressInstance(address: string, isValidFormat: boolean, isValidChecksum: boolean): NightwatchBrowser, addAtAddressInstance(address: string, isValidFormat: boolean, isValidChecksum: boolean, isAbi?: boolean): NightwatchBrowser,
modalFooterOKClick(): NightwatchBrowser, modalFooterOKClick(id?: string): NightwatchBrowser,
clickInstance(index: number): NightwatchBrowser, clickInstance(index: number): NightwatchBrowser,
journalLastChildIncludes(val: string): NightwatchBrowser, journalLastChildIncludes(val: string): NightwatchBrowser,
executeScript(script: string): NightwatchBrowser, executeScript(script: string): NightwatchBrowser,
@ -32,7 +32,7 @@ declare module 'nightwatch' {
scrollToLine(line: number): NightwatchBrowser, scrollToLine(line: number): NightwatchBrowser,
waitForElementContainsText(id: string, value: string, timeout?: number): NightwatchBrowser, waitForElementContainsText(id: string, value: string, timeout?: number): NightwatchBrowser,
getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser, getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser,
modalFooterCancelClick(): NightwatchBrowser, modalFooterCancelClick(id?: string): NightwatchBrowser,
selectContract(contractName: string): NightwatchBrowser, selectContract(contractName: string): NightwatchBrowser,
createContract(inputParams: string): NightwatchBrowser, createContract(inputParams: string): NightwatchBrowser,
getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser, getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser,

@ -3,7 +3,7 @@
"browser": true, "browser": true,
"es6": true "es6": true
}, },
"extends": "../../.eslintrc", "extends": "../../.eslintrc.json",
"globals": { "globals": {
"Atomics": "readonly", "Atomics": "readonly",
"SharedArrayBuffer": "readonly" "SharedArrayBuffer": "readonly"

@ -1,153 +0,0 @@
# Current "Best Practice" Conventions
- Please use [JS Standard Style](https://standardjs.com/) as a coding style guide.
- `ES6 class` rather than ES5 to create class.
- CSS declaration using `csjs-inject`.
- CSS files:
 **if** the CSS section of an UI component is too important, CSS declarations should be put in a different file and in a different folder.
The folder should be named `styles` and the file should be named with the extension `-styles.css`.
 e.g: `file-explorer.js` being an UI component `file-explorer-styles.css` is created in the `styles` folder right under `file-explorer.js`
**if** the CSS section of an UI component is rather limited it is preferable to put it in the corresponding JS file.
- HTML declaration using `yo-yo`.
- A module trigger events using `event` property:
 `self.event = new EventManager()`.
Events can then be triggered:
 `self.event.trigger('eventName', [param1, param2])`
- `self._view` is the HTML view renderered by `yo-yo` in the `render` function.
- `render()` this function should be called at the first rendering (make sure that the returned node element is put on the DOM), and should *not* by called again from outside the component.
- `update()` call this function to update the DOM when the state of the component has changed (this function must be called after the initial call to `render()`).
- for all functions / properties, prefixing by underscore (`_`) means the scope is private, and they should **not** be accessed not changed from outside the component.
- constructor arguments: There is no fixed rule whether it is preferrable to use multiples arguments or a single option *{}* argument (or both).
We recommend:
- use a specific slot for **obligatory** arguments and/or for complex arguments (meaning not boolean, not string, etc...).
- put arguments in an option *{}* for non critical and for optionnal arguments.
- if a component has more than 4/5 parameters, it is recommended to find a way to group some in one or more *opt* arguments.
- look them up, discuss them, update them.
   
## Module Definition (example)
```js
// user-card.js
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var EventManager = require('remix-lib').EventManager
var css = csjs`
.userCard {
position : relative;
box-sizing : border-box;
display : flex;
flex-direction : column;
align-items : center;
border : 1px solid black;
width : 400px;
padding : 50px;
}
.clearFunds { background-color: lightblue; }
`
class UserCard {
constructor (api, events, opts = {}) {
var self = this
self.event = new EventManager()
self.opts = opts
self._api = api
self._consumedEvents = events
self._view = undefined
events.funds.register('fundsChanged', function (amount) {
if (amount < self.state._funds) self.state.totalSpend += self.state._funds - amount
self.state._funds = amount
self.render()
})
self.event.trigger('eventName', [param1, param2])
}
render () {
var self = this
var view = yo`
<div class=${css.userCard}>
<h1> @${self.state._nickname} </h1>
<h2> Welcome, ${self.state.title || ''} ${self.state.name || 'anonymous'} ${self.state.surname} </h2>
<ul> <li> User Funds: $${self.state._funds} </li> </ul>
<ul> <li> Spent Funds: $${self.state.totalSpend} </li> </ul>
<button class=${css.clearFunds} onclick=${e=>self._spendAll.call(self, e)}> spend all funds </button>
</div>
`
if (!self._view) {
self._view = view
}
return self._view
}
update () {
yo.update(this._view, this.render())
}
setNickname (name) {
this._nickname = name
}
getNickname () {
var self = this
return `@${self.state._nickname}`
}
getFullName () {
var self = this
return `${self.state.title} ${self.state.name} ${self.state.surname}`
}
_spendAll (event) {
var self = this
self._appAPI.clearUserFunds()
}
_constraint (msg) { throw new Error(msg) }
}
module.exports = UserCard
```
## Module Usage (example)
```js
/*****************************************************************************/
// 1. SETUP CONTEXT
var EventManager = require('remix-lib').EventManager
var funds = { event: new EventManager() }
var userfunds = 15
function getUserFunds () { return userfunds }
function clearUserFunds () {
var spent = userfunds
userfunds = 0
console.log(`all funds of ${usercard.getFullName()} were spent.`)
funds.event.trigger('fundsChanged', [userfunds])
return spent
}
setInterval(function () {
userfunds++
funds.event.trigger('fundsChanged', [userfunds])
}, 100)
/*****************************************************************************/
// 2. EXAMPLE USAGE
var UserCard = require('./user-card')
var usercard = new UserCard(
{ getUserFunds, clearUserFunds },
{ funds: funds.event },
{
title: 'Dr.',
name: 'John',
surname: 'Doe',
nickname: 'johndoe99'
})
var el = usercard.render()
document.body.appendChild(el)
setTimeout(function () {
userCard.setNickname('new name')
usercard.update()
}, 5000)
```

@ -5,7 +5,7 @@ Remix is an open source tool and we encourage anyone to help us improve our tool
You can do that by opening issues, giving feedback or by contributing a pull request You can do that by opening issues, giving feedback or by contributing a pull request
to our codebase. to our codebase.
The Remix application is built with JavaScript and it doesn't use any framework. We only The Remix application is built with JavaScript and React.
rely on selected set of npm modules, like `yo-yo`, `csjs-inject` and others. Check out the `package.json` files in the Remix submodules to learn more about the stack. Check out the `package.json` files in the Remix submodules to learn more about the stack.
To learn more, please visit our [GitHub page](https://github.com/ethereum/remix-ide). To learn more, please visit our [GitHub page](https://github.com/ethereum/remix-ide).

@ -2,7 +2,6 @@
import { RunTab, makeUdapp } from './app/udapp' import { RunTab, makeUdapp } from './app/udapp'
import { RemixEngine } from './remixEngine' import { RemixEngine } from './remixEngine'
import { RemixAppManager } from './remixAppManager' import { RemixAppManager } from './remixAppManager'
import { MainView } from './app/panels/main-view'
import { ThemeModule } from './app/tabs/theme-module' import { ThemeModule } from './app/tabs/theme-module'
import { NetworkModule } from './app/tabs/network-module' import { NetworkModule } from './app/tabs/network-module'
import { Web3ProviderModule } from './app/tabs/web3-provider' import { Web3ProviderModule } from './app/tabs/web3-provider'
@ -11,17 +10,17 @@ import { HiddenPanel } from './app/components/hidden-panel'
import { VerticalIcons } from './app/components/vertical-icons' import { VerticalIcons } from './app/components/vertical-icons'
import { LandingPage } from './app/ui/landing-page/landing-page' import { LandingPage } from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel' import { MainPanel } from './app/components/main-panel'
import { FramingService } from './framingService'
import { ModalPluginTester } from './app/plugins/test'
import { WalkthroughService } from './walkthroughService' import { WalkthroughService } from './walkthroughService'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, EditorContextListener } from '@remix-project/core-plugin' import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, EditorContextListener, GistHandler } from '@remix-project/core-plugin'
import migrateFileSystem from './migrateFileSystem'
import Registry from './app/state/registry' import Registry from './app/state/registry'
import { ConfigPlugin } from './app/plugins/config' import { ConfigPlugin } from './app/plugins/config'
import { Layout } from './app/panels/layout'
import { ModalPlugin } from './app/plugins/modal' import { ModalPlugin } from './app/plugins/modal'
import { Blockchain } from './blockchain/blockchain.js'
import { HardhatProvider } from './app/tabs/hardhat-provider'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -30,7 +29,6 @@ const remixLib = require('@remix-project/remix-lib')
const QueryParams = require('./lib/query-params') const QueryParams = require('./lib/query-params')
const Storage = remixLib.Storage const Storage = remixLib.Storage
const RemixDProvider = require('./app/files/remixDProvider') const RemixDProvider = require('./app/files/remixDProvider')
const HardhatProvider = require('./app/tabs/hardhat-provider')
const Config = require('./config') const Config = require('./config')
const FileManager = require('./app/files/fileManager') const FileManager = require('./app/files/fileManager')
@ -39,8 +37,6 @@ const DGitProvider = require('./app/files/dgitProvider')
const WorkspaceFileProvider = require('./app/files/workspaceFileProvider') const WorkspaceFileProvider = require('./app/files/workspaceFileProvider')
const toolTip = require('./app/ui/tooltip') const toolTip = require('./app/ui/tooltip')
const Blockchain = require('./blockchain/blockchain.js')
const PluginManagerComponent = require('./app/components/plugin-manager-component') const PluginManagerComponent = require('./app/components/plugin-manager-component')
const CompileTab = require('./app/tabs/compile-tab') const CompileTab = require('./app/tabs/compile-tab')
@ -51,6 +47,7 @@ const TestTab = require('./app/tabs/test-tab')
const FilePanel = require('./app/panels/file-panel') const FilePanel = require('./app/panels/file-panel')
const Editor = require('./app/editor/editor') const Editor = require('./app/editor/editor')
const Terminal = require('./app/panels/terminal') const Terminal = require('./app/panels/terminal')
const { TabProxy } = require('./app/panels/tab-proxy.js')
class AppComponent { class AppComponent {
constructor () { constructor () {
@ -68,15 +65,27 @@ class AppComponent {
// load file system // load file system
self._components.filesProviders = {} self._components.filesProviders = {}
self._components.filesProviders.browser = new FileProvider('browser') self._components.filesProviders.browser = new FileProvider('browser')
Registry.getInstance().put({ api: self._components.filesProviders.browser, name: 'fileproviders/browser' }) Registry.getInstance().put({
self._components.filesProviders.localhost = new RemixDProvider(self.appManager) api: self._components.filesProviders.browser,
Registry.getInstance().put({ api: self._components.filesProviders.localhost, name: 'fileproviders/localhost' }) name: 'fileproviders/browser'
})
self._components.filesProviders.localhost = new RemixDProvider(
self.appManager
)
Registry.getInstance().put({
api: self._components.filesProviders.localhost,
name: 'fileproviders/localhost'
})
self._components.filesProviders.workspace = new WorkspaceFileProvider() self._components.filesProviders.workspace = new WorkspaceFileProvider()
Registry.getInstance().put({ api: self._components.filesProviders.workspace, name: 'fileproviders/workspace' }) Registry.getInstance().put({
api: self._components.filesProviders.workspace,
Registry.getInstance().put({ api: self._components.filesProviders, name: 'fileproviders' }) name: 'fileproviders/workspace'
})
migrateFileSystem(self._components.filesProviders.browser) Registry.getInstance().put({
api: self._components.filesProviders,
name: 'fileproviders'
})
} }
async run () { async run () {
@ -84,6 +93,7 @@ class AppComponent {
// APP_MANAGER // APP_MANAGER
const appManager = self.appManager const appManager = self.appManager
const pluginLoader = self.appManager.pluginLoader const pluginLoader = self.appManager.pluginLoader
self.panels = {}
self.workspace = pluginLoader.get() self.workspace = pluginLoader.get()
self.engine = new RemixEngine() self.engine = new RemixEngine()
self.engine.register(appManager) self.engine.register(appManager)
@ -93,8 +103,15 @@ class AppComponent {
'remix-beta.ethereum.org': 25, 'remix-beta.ethereum.org': 25,
'remix.ethereum.org': 23 'remix.ethereum.org': 23
} }
self.showMatamo = (matomoDomains[window.location.hostname] && !Registry.getInstance().get('config').api.exists('settings/matomo-analytics')) self.showMatamo =
self.walkthroughService = new WalkthroughService(appManager, self.showMatamo) matomoDomains[window.location.hostname] &&
!Registry.getInstance()
.get('config')
.api.exists('settings/matomo-analytics')
self.walkthroughService = new WalkthroughService(
appManager,
self.showMatamo
)
const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080'] const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080']
// workaround for Electron support // workaround for Electron support
@ -106,6 +123,8 @@ class AppComponent {
} }
// SERVICES // SERVICES
// ----------------- gist service ---------------------------------
self.gistHandler = new GistHandler()
// ----------------- theme service --------------------------------- // ----------------- theme service ---------------------------------
self.themeModule = new ThemeModule() self.themeModule = new ThemeModule()
Registry.getInstance().put({ api: self.themeModule, name: 'themeModule' }) Registry.getInstance().put({ api: self.themeModule, name: 'themeModule' })
@ -113,7 +132,9 @@ class AppComponent {
// ----------------- editor service ---------------------------- // ----------------- editor service ----------------------------
const editor = new Editor() // wrapper around ace editor const editor = new Editor() // wrapper around ace editor
Registry.getInstance().put({ api: editor, name: 'editor' }) Registry.getInstance().put({ api: editor, name: 'editor' })
editor.event.register('requiringToSaveCurrentfile', () => fileManager.saveCurrentFile()) editor.event.register('requiringToSaveCurrentfile', () =>
fileManager.saveCurrentFile()
)
// ----------------- fileManager service ---------------------------- // ----------------- fileManager service ----------------------------
const fileManager = new FileManager(editor, appManager) const fileManager = new FileManager(editor, appManager)
@ -130,7 +151,10 @@ class AppComponent {
const compilerMetadataGenerator = new CompilerMetadata() const compilerMetadataGenerator = new CompilerMetadata()
// ----------------- compilation result service (can keep track of compilation results) ---------------------------- // ----------------- compilation result service (can keep track of compilation results) ----------------------------
const compilersArtefacts = new CompilerArtefacts() // store all the compilation results (key represent a compiler name) const compilersArtefacts = new CompilerArtefacts() // store all the compilation results (key represent a compiler name)
Registry.getInstance().put({ api: compilersArtefacts, name: 'compilersartefacts' }) Registry.getInstance().put({
api: compilersArtefacts,
name: 'compilersartefacts'
})
// service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it // service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it
const fetchAndCompile = new FetchAndCompile() const fetchAndCompile = new FetchAndCompile()
@ -141,19 +165,22 @@ class AppComponent {
const hardhatProvider = new HardhatProvider(blockchain) const hardhatProvider = new HardhatProvider(blockchain)
// ----------------- convert offset to line/column service ----------- // ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter() const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({ api: offsetToLineColumnConverter, name: 'offsettolinecolumnconverter' }) Registry.getInstance().put({
api: offsetToLineColumnConverter,
name: 'offsettolinecolumnconverter'
})
// -------------------Terminal---------------------------------------- // -------------------Terminal----------------------------------------
makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl)) makeUdapp(blockchain, compilersArtefacts, domEl => terminal.logHtml(domEl))
const terminal = new Terminal( const terminal = new Terminal(
{ appManager, blockchain }, { appManager, blockchain },
{ {
getPosition: (event) => { getPosition: event => {
const limitUp = 36 const limitUp = 36
const limitDown = 20 const limitDown = 20
const height = window.innerHeight const height = window.innerHeight
let newpos = (event.pageY < limitUp) ? limitUp : event.pageY let newpos = event.pageY < limitUp ? limitUp : event.pageY
newpos = (newpos < height - limitDown) ? newpos : height - limitDown newpos = newpos < height - limitDown ? newpos : height - limitDown
return height - newpos return height - newpos
} }
} }
@ -163,9 +190,12 @@ class AppComponent {
self.modal = new ModalPlugin() self.modal = new ModalPlugin()
const configPlugin = new ConfigPlugin() const configPlugin = new ConfigPlugin()
self.layout = new Layout()
self.engine.register([ self.engine.register([
self.layout,
self.modal, self.modal,
self.gistHandler,
configPlugin, configPlugin,
blockchain, blockchain,
contentImport, contentImport,
@ -187,22 +217,27 @@ class AppComponent {
// LAYOUT & SYSTEM VIEWS // LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel() const appPanel = new MainPanel()
self.mainview = new MainView(contextualListener, editor, appPanel, fileManager, appManager, terminal)
Registry.getInstance().put({ api: self.mainview, name: 'mainview' }) Registry.getInstance().put({ api: self.mainview, name: 'mainview' })
const tabProxy = new TabProxy(fileManager, editor)
self.engine.register([ self.engine.register([appPanel, tabProxy])
appPanel,
self.mainview.tabProxy
])
// those views depend on app_manager // those views depend on app_manager
self.menuicons = new VerticalIcons(appManager) self.menuicons = new VerticalIcons(appManager)
self.sidePanel = new SidePanel(appManager, self.menuicons) self.sidePanel = new SidePanel(appManager, self.menuicons)
self.hiddenPanel = new HiddenPanel() self.hiddenPanel = new HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent(appManager, self.engine) const pluginManagerComponent = new PluginManagerComponent(
appManager,
self.engine
)
const filePanel = new FilePanel(appManager) const filePanel = new FilePanel(appManager)
const landingPage = new LandingPage(appManager, self.menuicons, fileManager, filePanel, contentImport) const landingPage = new LandingPage(
appManager,
self.menuicons,
fileManager,
filePanel,
contentImport
)
self.settings = new SettingsTab( self.settings = new SettingsTab(
Registry.getInstance().get('config').api, Registry.getInstance().get('config').api,
editor, editor,
@ -220,7 +255,10 @@ class AppComponent {
]) ])
// CONTENT VIEWS & DEFAULT PLUGINS // CONTENT VIEWS & DEFAULT PLUGINS
const compileTab = new CompileTab(Registry.getInstance().get('config').api, Registry.getInstance().get('filemanager').api) const compileTab = new CompileTab(
Registry.getInstance().get('config').api,
Registry.getInstance().get('filemanager').api
)
const run = new RunTab( const run = new RunTab(
blockchain, blockchain,
Registry.getInstance().get('config').api, Registry.getInstance().get('config').api,
@ -229,7 +267,6 @@ class AppComponent {
filePanel, filePanel,
Registry.getInstance().get('compilersartefacts').api, Registry.getInstance().get('compilersartefacts').api,
networkModule, networkModule,
self.mainview,
Registry.getInstance().get('fileproviders/browser').api Registry.getInstance().get('fileproviders/browser').api
) )
const analysis = new AnalysisTab() const analysis = new AnalysisTab()
@ -243,9 +280,7 @@ class AppComponent {
contentImport contentImport
) )
const testplugin = new ModalPluginTester()
self.engine.register([ self.engine.register([
testplugin,
compileTab, compileTab,
run, run,
debug, debug,
@ -256,6 +291,13 @@ class AppComponent {
filePanel.hardhatHandle, filePanel.hardhatHandle,
filePanel.slitherHandle filePanel.slitherHandle
]) ])
self.layout.panels = {
tabs: { plugin: tabProxy, active: true },
editor: { plugin: editor, active: true },
main: { plugin: appPanel, active: false },
terminal: { plugin: terminal, active: true, minimized: false }
}
} }
async activate () { async activate () {
@ -270,9 +312,9 @@ class AppComponent {
try { try {
self.engine.register(await self.appManager.registeredPlugins()) self.engine.register(await self.appManager.registeredPlugins())
} catch (e) { } catch (e) {
console.log('couldn\'t register iframe plugins', e.message) console.log("couldn't register iframe plugins", e.message)
} }
await self.appManager.activatePlugin(['layout'])
await self.appManager.activatePlugin(['modal']) await self.appManager.activatePlugin(['modal'])
await self.appManager.activatePlugin(['editor']) await self.appManager.activatePlugin(['editor'])
await self.appManager.activatePlugin(['theme', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter']) await self.appManager.activatePlugin(['theme', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
@ -280,23 +322,30 @@ class AppComponent {
await self.appManager.activatePlugin(['sidePanel']) // activating host plugin separately await self.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await self.appManager.activatePlugin(['home']) await self.appManager.activatePlugin(['home'])
await self.appManager.activatePlugin(['settings', 'config']) await self.appManager.activatePlugin(['settings', 'config'])
await self.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport']) await self.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await self.appManager.activatePlugin(['settings']) await self.appManager.activatePlugin(['settings'])
await self.appManager.activatePlugin(['walkthrough']) await self.appManager.activatePlugin(['walkthrough'])
await self.appManager.activatePlugin(['testerplugin'])
self.appManager.on('filePanel', 'workspaceInitializationCompleted', async () => { self.appManager.on(
'filePanel',
'workspaceInitializationCompleted',
async () => {
await self.appManager.registerContextMenuItems() await self.appManager.registerContextMenuItems()
}) }
)
await self.appManager.activatePlugin(['filePanel']) await self.appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation // Set workspace after initial activation
self.appManager.on('editor', 'editorMounted', () => { self.appManager.on('editor', 'editorMounted', () => {
if (Array.isArray(self.workspace)) { if (Array.isArray(self.workspace)) {
self.appManager.activatePlugin(self.workspace).then(async () => { self.appManager
.activatePlugin(self.workspace)
.then(async () => {
try { try {
if (params.deactivate) { if (params.deactivate) {
await self.appManager.deactivatePlugin(params.deactivate.split(',')) await self.appManager.deactivatePlugin(
params.deactivate.split(',')
)
} }
} catch (e) { } catch (e) {
console.log(e) console.log(e)
@ -306,7 +355,10 @@ class AppComponent {
self.menuicons.select('solidity') self.menuicons.select('solidity')
} else { } else {
// If plugins are loaded from the URL params, we focus on the last one. // If plugins are loaded from the URL params, we focus on the last one.
if (self.appManager.pluginLoader.current === 'queryParams' && self.workspace.length > 0) self.menuicons.select(self.workspace[self.workspace.length - 1]) if (
self.appManager.pluginLoader.current === 'queryParams' &&
self.workspace.length > 0
) { self.menuicons.select(self.workspace[self.workspace.length - 1]) }
} }
if (params.call) { if (params.call) {
@ -317,16 +369,13 @@ class AppComponent {
self.appManager.call(...callDetails).catch(console.error) self.appManager.call(...callDetails).catch(console.error)
} }
} }
}).catch(console.error) })
.catch(console.error)
} }
}) })
// activate solidity plugin // activate solidity plugin
self.appManager.activatePlugin(['solidity', 'udapp']) self.appManager.activatePlugin(['solidity', 'udapp'])
// Load and start the service who manager layout and frame // Load and start the service who manager layout and frame
const framingService = new FramingService(self.sidePanel, self.menuicons, self.mainview, null)
if (params.embed) framingService.embed()
framingService.start(params)
} }
} }

@ -1,31 +0,0 @@
import { AbstractPanel } from './panel'
import * as packageJson from '../../../../../package.json'
const csjs = require('csjs-inject')
const yo = require('yo-yo')
const css = csjs`
.pluginsContainer {
display: none;
}
`
const profile = {
name: 'hiddenPanel',
displayName: 'Hidden Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView']
}
export class HiddenPanel extends AbstractPanel {
constructor () {
super(profile)
}
render () {
return yo`
<div class=${css.pluginsContainer}>
${this.view}
</div>`
}
}

@ -0,0 +1,37 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import ReactDOM from 'react-dom' // eslint-disable-line
import { AbstractPanel } from './panel'
import * as packageJson from '../../../../../package.json'
import { RemixPluginPanel } from '@remix-ui/panel'
const profile = {
name: 'hiddenPanel',
displayName: 'Hidden Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView']
}
export class HiddenPanel extends AbstractPanel {
el: HTMLElement
constructor () {
super(profile)
this.el = document.createElement('div')
this.el.setAttribute('class', 'pluginsContainer')
}
addView (profile: any, view: any): void {
super.removeView(profile)
super.addView(profile, view)
this.renderComponent()
}
render () {
return this.el
}
renderComponent () {
ReactDOM.render(<RemixPluginPanel header={<></>} plugins={this.plugins}/>, this.el)
}
}

@ -1,38 +0,0 @@
import { AbstractPanel } from './panel'
import * as packageJson from '../../../../../package.json'
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const css = csjs`
.pluginsContainer {
height: 100%;
display: flex;
overflow-y: hidden;
}
`
const profile = {
name: 'mainPanel',
displayName: 'Main Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView']
}
export class MainPanel extends AbstractPanel {
constructor () {
super(profile)
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
}
render () {
return yo`
<div class=${css.pluginsContainer} data-id="mainPanelPluginsContainer" id='mainPanelPluginsContainer-id'>
${this.view}
</div>`
}
}

@ -0,0 +1,57 @@
import React from 'react' // eslint-disable-line
import { AbstractPanel } from './panel'
import ReactDOM from 'react-dom' // eslint-disable-line
import { RemixPluginPanel } from '@remix-ui/panel'
import packageJson from '../../../../../package.json'
const profile = {
name: 'mainPanel',
displayName: 'Main Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView', 'showContent']
}
export class MainPanel extends AbstractPanel {
element: HTMLDivElement
constructor (config) {
super(profile)
this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
this.element.setAttribute('style', 'height: 100%; width: 100%;')
// this.config = config
}
onActivation () {
this.renderComponent()
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
this.renderComponent()
}
addView (profile, view) {
super.addView(profile, view)
this.renderComponent()
}
removeView (profile) {
super.removeView(profile)
this.renderComponent()
}
async showContent (name) {
super.showContent(name)
this.renderComponent()
}
render () {
return this.element
}
renderComponent () {
ReactDOM.render(<RemixPluginPanel header={<></>} plugins={this.plugins}/>, this.element)
}
}

@ -1,111 +0,0 @@
import { EventEmitter } from 'events'
import { HostPlugin } from '@remixproject/engine-web'
const csjs = require('csjs-inject')
const yo = require('yo-yo')
const css = csjs`
.plugins {
height: 100%;
}
.plugItIn {
display : none;
height : 100%;
}
.plugItIn > div {
overflow-y : auto;
overflow-x : hidden;
height : 100%;
width : 100%;
}
.plugItIn.active {
display : block;
}
.pluginsContainer {
height : 100%;
overflow-y : hidden;
}
`
/** Abstract class used for hosting the view of a plugin */
export class AbstractPanel extends HostPlugin {
constructor (profile) {
super(profile)
this.events = new EventEmitter()
this.contents = {}
this.active = undefined
// View where the plugin HTMLElement leaves
this.view = yo`<div id="plugins" class="${css.plugins}"></div>`
}
/**
* Add the plugin to the panel
* @param {String} name the name of the plugin
* @param {HTMLElement} content the HTMLContent of the plugin
*/
add (view, name) {
if (this.contents[name]) throw new Error(`Plugin ${name} already rendered`)
view.style.height = '100%'
view.style.width = '100%'
view.style.border = '0'
const isIframe = view.tagName === 'IFRAME'
view.style.display = isIframe ? 'none' : 'block'
const loading = isIframe ? yo`
<div class="d-flex justify-content-center align-items-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
` : ''
this.contents[name] = yo`<div class="${css.plugItIn}" >${view}${loading}</div>`
if (view.tagName === 'IFRAME') {
view.addEventListener('load', () => {
if (this.contents[name].contains(loading)) {
this.contents[name].removeChild(loading)
}
view.style.display = 'block'
})
}
this.contents[name].style.display = 'none'
this.view.appendChild(this.contents[name])
}
addView (profile, view) {
this.add(view, profile.name)
}
removeView (profile) {
this.remove(profile.name)
}
/**
* Remove a plugin from the panel
* @param {String} name The name of the plugin to remove
*/
remove (name) {
const el = this.contents[name]
delete this.contents[name]
if (el) el.parentElement.removeChild(el)
if (name === this.active) this.active = undefined
}
/**
* Display the content of this specific plugin
* @param {String} name The name of the plugin to display the content
*/
showContent (name) {
if (!this.contents[name]) throw new Error(`Plugin ${name} is not yet activated`)
// hiding the current view and display the `moduleName`
if (this.active) {
this.contents[this.active].style.display = 'none'
}
this.contents[name].style.display = 'flex'
this.active = name
}
focus (name) {
this.showContent(name)
}
}

@ -0,0 +1,63 @@
import React from 'react' // eslint-disable-line
import { EventEmitter } from 'events'
import { HostPlugin } from '@remixproject/engine-web' // eslint-disable-line
import { PluginRecord } from 'libs/remix-ui/panel/src/lib/types'
const EventManager = require('../../lib/events')
export class AbstractPanel extends HostPlugin {
events: EventEmitter
event: any
public plugins: Record<string, PluginRecord> = {}
constructor (profile) {
super(profile)
this.events = new EventEmitter()
this.event = new EventManager()
}
currentFocus (): string {
return Object.values(this.plugins).find(plugin => {
return plugin.active
}).profile.name
}
addView (profile, view) {
if (this.plugins[profile.name]) throw new Error(`Plugin ${profile.name} already rendered`)
this.plugins[profile.name] = {
profile: profile,
view: view,
active: false,
class: 'plugItIn active'
}
}
removeView (profile) {
this.emit('pluginDisabled', profile.name)
this.call('menuicons', 'unlinkContent', profile)
this.remove(profile.name)
}
/**
* Remove a plugin from the panel
* @param {String} name The name of the plugin to remove
*/
remove (name) {
delete this.plugins[name]
}
/**
* Display the content of this specific plugin
* @param {String} name The name of the plugin to display the content
*/
showContent (name) {
if (!this.plugins[name]) throw new Error(`Plugin ${name} is not yet activated`)
Object.values(this.plugins).forEach(plugin => {
plugin.active = false
})
this.plugins[name].active = true
}
focus (name) {
this.showContent(name)
}
}

@ -1,156 +0,0 @@
import { AbstractPanel } from './panel'
import * as packageJson from '../../../../../package.json'
const csjs = require('csjs-inject')
const yo = require('yo-yo')
const css = csjs`
.panel {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
flex: auto;
}
.swapitTitle {
margin: 0;
text-transform: uppercase;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.swapitTitle i{
padding-left: 6px;
font-size: 14px;
}
.swapitHeader {
display: flex;
align-items: center;
padding: 16px 24px 15px;
justify-content: space-between;
}
.icons i {
height: 80%;
cursor: pointer;
}
.pluginsContainer {
height: 100%;
overflow-y: auto;
}
.titleInfo {
padding-left: 10px;
}
.versionBadge {
background-color: var(--light);
padding: 0 7px;
font-weight: bolder;
margin-left: 5px;
text-transform: lowercase;
cursor: default;
}
`
const sidePanel = {
name: 'sidePanel',
displayName: 'Side Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView']
}
// TODO merge with vertical-icons.js
export class SidePanel extends AbstractPanel {
constructor (appManager, verticalIcons) {
super(sidePanel)
this.appManager = appManager
this.header = yo`<header></header>`
this.renderHeader()
this.verticalIcons = verticalIcons
// Toggle content
verticalIcons.events.on('toggleContent', (name) => {
if (!this.contents[name]) return
if (this.active === name) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggle', name)
this.events.emit('toggle', name)
return
}
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
// Force opening
verticalIcons.events.on('showContent', (name) => {
if (!this.contents[name]) return
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
}
removeView (profile) {
super.removeView(profile)
this.emit('pluginDisabled', profile.name)
this.verticalIcons.unlinkContent(profile)
}
addView (profile, view) {
super.addView(profile, view)
this.verticalIcons.linkContent(profile)
}
/**
* Display content and update the header
* @param {String} name The name of the plugin to display
*/
async showContent (name) {
super.showContent(name)
this.renderHeader()
this.emit('focusChanged', name)
}
/** The header of the side panel */
async renderHeader () {
let name = ' - '
let docLink = ''
let versionWarning
if (this.active) {
const profile = await this.appManager.getProfile(this.active)
name = profile.displayName ? profile.displayName : profile.name
docLink = profile.documentation ? yo`<a href="${profile.documentation}" class="${css.titleInfo}" title="link to documentation" target="_blank"><i aria-hidden="true" class="fas fa-book"></i></a>` : ''
if (profile.version && profile.version.match(/\b(\w*alpha\w*)\b/g)) {
versionWarning = yo`<small title="Version Alpha" class="badge-light ${css.versionBadge}">alpha</small>`
}
// Beta
if (profile.version && profile.version.match(/\b(\w*beta\w*)\b/g)) {
versionWarning = yo`<small title="Version Beta" class="badge-light ${css.versionBadge}">beta</small>`
}
}
const header = yo`
<header class="${css.swapitHeader}">
<h6 class="${css.swapitTitle}" data-id="sidePanelSwapitTitle">${name}</h6>
${docLink}
${versionWarning}
</header>
`
yo.update(this.header, header)
}
render () {
return yo`
<section class="${css.panel} plugin-manager">
${this.header}
<div class="${css.pluginsContainer}">
${this.view}
</div>
</section>`
}
}

@ -0,0 +1,95 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import ReactDOM from 'react-dom'
import { AbstractPanel } from './panel'
import { RemixPluginPanel } from '@remix-ui/panel'
import packageJson from '../../../../../package.json'
import { RemixAppManager } from '../../remixAppManager'
import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel'
import RemixUIPanelHeader from 'libs/remix-ui/panel/src/lib/plugins/panel-header'
// const csjs = require('csjs-inject')
const sidePanel = {
name: 'sidePanel',
displayName: 'Side Panel',
description: '',
version: packageJson.version,
methods: ['addView', 'removeView']
}
// TODO merge with vertical-icons.js
export class SidePanel extends AbstractPanel {
appManager: RemixAppManager
sideelement: any
verticalIcons: VerticalIcons;
constructor (appManager: RemixAppManager, verticalIcons: VerticalIcons) {
super(sidePanel)
this.appManager = appManager
this.sideelement = document.createElement('section')
this.sideelement.setAttribute('class', 'panel plugin-manager')
this.verticalIcons = verticalIcons
// Toggle content
verticalIcons.events.on('toggleContent', (name) => {
if (!this.plugins[name]) return
if (this.plugins[name].active) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggle', name)
this.events.emit('toggle', name)
return
}
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
// Force opening
verticalIcons.events.on('showContent', (name) => {
if (!this.plugins[name]) return
this.showContent(name)
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showing', name)
this.events.emit('showing', name)
})
}
onActivation () {
this.renderComponent()
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
}
removeView (profile) {
super.removeView(profile)
this.emit('pluginDisabled', profile.name)
this.call('menuicons', 'unlinkContent', profile)
this.renderComponent()
}
addView (profile, view) {
super.addView(profile, view)
this.verticalIcons.linkContent(profile)
this.renderComponent()
}
/**
* Display content and update the header
* @param {String} name The name of the plugin to display
*/
async showContent (name) {
super.showContent(name)
this.emit('focusChanged', name)
this.renderComponent()
}
render () {
return this.sideelement
}
renderComponent () {
ReactDOM.render(<RemixPluginPanel header={<RemixUIPanelHeader plugins={this.plugins}></RemixUIPanelHeader>} plugins={this.plugins}/>, this.sideelement)
}
}

@ -1,12 +1,9 @@
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
// eslint-disable-next-line no-unused-vars
import { basicLogo } from '../ui/svgLogo'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel' import { RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel'
import Registry from '../state/registry' import Registry from '../state/registry'
// var helper = require('../../lib/helper')
const { Plugin } = require('@remixproject/engine') const { Plugin } = require('@remixproject/engine')
const EventEmitter = require('events') const EventEmitter = require('events')
@ -15,7 +12,7 @@ const profile = {
displayName: 'Vertical Icons', displayName: 'Vertical Icons',
description: '', description: '',
version: packageJson.version, version: packageJson.version,
methods: ['select'] methods: ['select', 'unlinkContent']
} }
// TODO merge with side-panel.js. VerticalIcons should not be a plugin // TODO merge with side-panel.js. VerticalIcons should not be a plugin

@ -1,194 +0,0 @@
'use strict'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
import Registry from '../state/registry'
const yo = require('yo-yo')
const css = require('./styles/contextView-styles')
/*
Display information about the current focused code:
- if it's a reference, display information about the declaration
- jump to the declaration
- number of references
- rename declaration/references
*/
class ContextView {
constructor (opts) {
this._components = {}
this._components.registry = Registry.getInstance()
this.contextualListener = opts.contextualListener
this.editor = opts.editor
this._deps = {
compilersArtefacts: this._components.registry.get('compilersartefacts').api,
offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api,
config: this._components.registry.get('config').api,
fileManager: this._components.registry.get('filemanager').api
}
this._view = null
this._nodes = null
this._current = null
this.sourceMappingDecoder = sourceMappingDecoder
this.previousElement = null
this.contextualListener.event.register('contextChanged', nodes => {
this.show()
this._nodes = nodes
this.update()
})
this.contextualListener.event.register('stopHighlighting', () => {
})
}
render () {
const view = yo`
<div class="${css.contextview} ${css.contextviewcontainer} bg-light text-dark border-0">
<div class=${css.container}>
${this._renderTarget()}
</div>
</div>`
if (!this._view) {
this._view = view
}
return view
}
hide () {
if (this._view) {
this._view.style.display = 'none'
}
}
show () {
if (this._view) {
this._view.style.display = 'block'
}
}
update () {
if (this._view) {
yo.update(this._view, this.render())
}
}
_renderTarget () {
let last
const previous = this._current
if (this._nodes && this._nodes.length) {
last = this._nodes[this._nodes.length - 1]
if (isDefinition(last)) {
this._current = last
} else {
const target = this.contextualListener.declarationOf(last)
if (target) {
this._current = target
} else {
this._current = null
}
}
}
if (!this._current || !previous || previous.id !== this._current.id || (this.previousElement && !this.previousElement.children.length)) {
this.previousElement = this._render(this._current, last)
}
return this.previousElement
}
_jumpToInternal (position) {
const jumpToLine = (lineColumn) => {
if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) {
this.editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1)
}
}
const lastCompilationResult = this._deps.compilersArtefacts.__last
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
const lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(
position,
position.file,
lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts())
const filename = lastCompilationResult.getSourceName(position.file)
// TODO: refactor with rendererAPI.errorClick
if (filename !== this._deps.config.get('currentFile')) {
const provider = this._deps.fileManager.fileProviderOf(filename)
if (provider) {
provider.exists(filename).then(exist => {
this._deps.fileManager.open(filename)
jumpToLine(lineColumn)
}).catch(error => {
if (error) return console.log(error)
})
}
} else {
jumpToLine(lineColumn)
}
}
}
_render (node, nodeAtCursorPosition) {
if (!node) return yo`<div></div>`
let references = this.contextualListener.referencesOf(node)
const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType
references = `${references ? references.length : '0'} reference(s)`
let ref = 0
const nodes = this.contextualListener.getActiveHighlights()
for (const k in nodes) {
if (nodeAtCursorPosition.id === nodes[k].nodeId) {
ref = k
break
}
}
// JUMP BETWEEN REFERENCES
const jump = (e) => {
e.target.dataset.action === 'next' ? ref++ : ref--
if (ref < 0) ref = nodes.length - 1
if (ref >= nodes.length) ref = 0
this._jumpToInternal(nodes[ref].position)
}
const jumpTo = () => {
if (node && node.src) {
const position = this.sourceMappingDecoder.decode(node.src)
if (position) {
this._jumpToInternal(position)
}
}
}
const showGasEstimation = () => {
if (node.nodeType === 'FunctionDefinition') {
const result = this.contextualListener.gasEstimation(node)
const executionCost = ' Execution cost: ' + result.executionCost + ' gas'
const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas'
const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}`
return yo`
<div class=${css.gasEstimation}>
<i class="fas fa-gas-pump ${css.gasStationIcon}" title='Gas estimation'></i>
<span>${estimatedGas}</span>
</div>
`
}
}
return yo`
<div class=${css.line}>${showGasEstimation()}
<div title=${type} class=${css.type}>${type}</div>
<div title=${node.name} class=${css.name}>${node.name}</div>
<i class="fas fa-share ${css.jump}" aria-hidden="true" onclick=${jumpTo}></i>
<span class=${css.referencesnb}>${references}</span>
<i data-action='previous' class="fas fa-chevron-up ${css.jump}" aria-hidden="true" onclick=${jump}></i>
<i data-action='next' class="fas fa-chevron-down ${css.jump}" aria-hidden="true" onclick=${jump}></i>
</div>
`
}
}
function isDefinition (node) {
return node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
}
module.exports = ContextView

@ -22,7 +22,7 @@ const profile = {
icon: 'assets/img/fileManager.webp', icon: 'assets/img/fileManager.webp',
permission: true, permission: true,
version: packageJson.version, version: packageJson.version,
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile'], methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles'],
kind: 'file-system' kind: 'file-system'
} }
const errorMsg = { const errorMsg = {

@ -0,0 +1,94 @@
import { Plugin } from '@remixproject/engine'
import { Profile } from '@remixproject/plugin-utils'
import { EventEmitter } from 'events'
import QueryParams from '../../lib/query-params'
const profile: Profile = {
name: 'layout',
description: 'layout',
methods: ['minimize']
}
interface panelState {
active: boolean
plugin: Plugin
minimized: boolean
}
interface panels {
tabs: panelState
editor: panelState
main: panelState
terminal: panelState
}
export class Layout extends Plugin {
event: any
panels: panels
constructor () {
super(profile)
this.event = new EventEmitter()
}
async onActivation (): Promise<void> {
this.on('fileManager', 'currentFileChanged', () => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'openFile', () => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'switchApp', (name: string) => {
this.call('mainPanel', 'showContent', name)
this.panels.editor.active = false
this.panels.main.active = true
this.event.emit('change', null)
})
this.on('tabs', 'closeApp', (name: string) => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'tabCountChanged', async count => {
if (!count) await this.call('manager', 'activatePlugin', 'home')
})
this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) {
case 'filePanel':
this.call('menuicons', 'select', 'filePanel')
break
}
})
document.addEventListener('keypress', e => {
if (e.shiftKey && e.ctrlKey) {
if (e.code === 'KeyF') {
// Ctrl+Shift+F
this.call('menuicons', 'select', 'filePanel')
} else if (e.code === 'KeyA') {
// Ctrl+Shift+A
this.call('menuicons', 'select', 'pluginManager')
} else if (e.code === 'KeyS') {
// Ctrl+Shift+S
this.call('menuicons', 'select', 'settings')
}
e.preventDefault()
}
})
const queryParams = new QueryParams()
const params = queryParams.get()
if (params.minimizeterminal || params.embed) {
this.panels.terminal.minimized = true
this.event.emit('change', null)
}
if (params.minimizesidepanel || params.embed) {
this.event.emit('minimizesidepanel')
}
}
minimize (name: string, minimized:boolean): void {
this.panels[name].minimized = minimized
this.event.emit('change', null)
}
}

@ -1,204 +0,0 @@
import Registry from '../state/registry'
var yo = require('yo-yo')
var EventManager = require('../../lib/events')
var { TabProxy } = require('./tab-proxy.js')
var csjs = require('csjs-inject')
var css = csjs`
.mainview {
display : flex;
flex-direction : column;
height : 100%;
width : 100%;
}
`
// @todo(#650) Extract this into two classes: MainPanel (TabsProxy + Iframe/Editor) & BottomPanel (Terminal)
export class MainView {
constructor (contextualListener, editor, mainPanel, fileManager, appManager, terminal) {
var self = this
self.event = new EventManager()
self._view = {}
self._components = {}
self._components.registry = Registry.getInstance()
self.contextualListener = contextualListener
self.editor = editor
self.fileManager = fileManager
self.mainPanel = mainPanel
self.txListener = Registry.getInstance().get('txlistener').api
self._components.terminal = terminal
this.appManager = appManager
this.init()
}
showApp (name) {
this.fileManager.unselectCurrentFile()
this.mainPanel.showContent(name)
this._view.editor.style.display = 'none'
this._view.mainPanel.style.display = 'block'
}
getAppPanel () {
return this.mainPanel
}
init () {
var self = this
self._deps = {
config: self._components.registry.get('config').api,
fileManager: self._components.registry.get('filemanager').api
}
self.tabProxy = new TabProxy(self.fileManager, self.editor)
/*
We listen here on event from the tab component to display / hide the editor and mainpanel
depending on the content that should be displayed
*/
self.fileManager.events.on('currentFileChanged', (file) => {
// we check upstream for "fileChanged"
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
})
self.tabProxy.event.on('openFile', (file) => {
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
})
self.tabProxy.event.on('closeFile', (file) => {
})
self.tabProxy.event.on('switchApp', self.showApp.bind(self))
self.tabProxy.event.on('closeApp', (name) => {
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
})
self.tabProxy.event.on('tabCountChanged', (count) => {
if (!count) this.editor.displayEmptyReadOnlySession()
})
self.data = {
_layout: {
top: {
offset: self._terminalTopOffset(),
show: true
}
}
}
self._components.terminal.event.register('resize', delta => self._adjustLayout('top', delta))
if (self.txListener) {
self._components.terminal.event.register('listenOnNetWork', (listenOnNetWork) => {
self.txListener.setListenOnNetwork(listenOnNetWork)
})
}
}
_terminalTopOffset () {
return this._deps.config.get('terminal-top-offset') || 150
}
_adjustLayout (direction, delta) {
var limitUp = 0
var limitDown = 32
var containerHeight = window.innerHeight - limitUp // - menu bar containerHeight
var self = this
var layout = self.data._layout[direction]
if (layout) {
if (delta === undefined) {
layout.show = !layout.show
if (layout.show) delta = layout.offset
else delta = 0
} else {
layout.show = true
self._deps.config.set(`terminal-${direction}-offset`, delta)
layout.offset = delta
}
}
var tmp = delta - limitDown
delta = tmp > 0 ? tmp : 0
if (direction === 'top') {
var mainPanelHeight = containerHeight - delta
mainPanelHeight = mainPanelHeight < 0 ? 0 : mainPanelHeight
self._view.editor.style.height = `${mainPanelHeight}px`
self._view.mainPanel.style.height = `${mainPanelHeight}px`
self._view.terminal.style.height = `${delta}px` // - menu bar height
self.editor.resize((document.querySelector('#editorWrap') || {}).checked)
self._components.terminal.scroll2bottom()
}
}
minimizeTerminal () {
this._adjustLayout('top')
}
showTerminal (offset) {
this._adjustLayout('top', offset || this._terminalTopOffset())
}
getTerminal () {
return this._components.terminal
}
getEditor () {
var self = this
return self.editor
}
refresh () {
var self = this
self._view.tabs.onmouseenter()
}
log (data = {}) {
var self = this
var command = self._components.terminal.commands[data.type]
if (typeof command === 'function') command(data.value)
}
logMessage (msg) {
var self = this
self.log({ type: 'log', value: msg })
}
logHtmlMessage (msg) {
var self = this
self.log({ type: 'html', value: msg })
}
render () {
var self = this
if (self._view.mainview) return self._view.mainview
self._view.editor = self.editor.render()
self._view.editor.style.display = 'none'
self._view.mainPanel = self.mainPanel.render()
self._view.terminal = self._components.terminal.render()
self._view.mainview = yo`
<div class=${css.mainview}>
${self.tabProxy.renderTabsbar()}
${self._view.editor}
${self._view.mainPanel}
<div class="${css.contextview} contextview"></div>
${self._view.terminal}
</div>
`
// INIT
self._adjustLayout('top', self.data._layout.top.offset)
document.addEventListener('keydown', (e) => {
if (e.altKey && e.keyCode === 84) self.tabProxy.switchNextTab() // alt + t
})
return self._view.mainview
}
registerCommand (name, command, opts) {
var self = this
return self._components.terminal.registerCommand(name, command, opts)
}
updateTerminalFilter (filter) {
this._components.terminal.updateJournal(filter)
}
}

@ -22,6 +22,7 @@ export class TabProxy extends Plugin {
this._view = {} this._view = {}
this._handlers = {} this._handlers = {}
this.loadedTabs = [] this.loadedTabs = []
this.el = document.createElement('div')
} }
onActivation () { onActivation () {
@ -72,10 +73,12 @@ export class TabProxy extends Plugin {
this.addTab(workspacePath, '', () => { this.addTab(workspacePath, '', () => {
this.fileManager.open(file) this.fileManager.open(file)
this.event.emit('openFile', file) this.event.emit('openFile', file)
this.emit('openFile', file)
}, },
() => { () => {
this.fileManager.closeFile(file) this.fileManager.closeFile(file)
this.event.emit('closeFile', file) this.event.emit('closeFile', file)
this.emit('closeFile', file)
}) })
this.tabsApi.activateTab(workspacePath) this.tabsApi.activateTab(workspacePath)
} else { } else {
@ -88,10 +91,12 @@ export class TabProxy extends Plugin {
this.addTab(path, '', () => { this.addTab(path, '', () => {
this.fileManager.open(file) this.fileManager.open(file)
this.event.emit('openFile', file) this.event.emit('openFile', file)
this.emit('openFile', file)
}, },
() => { () => {
this.fileManager.closeFile(file) this.fileManager.closeFile(file)
this.event.emit('closeFile', file) this.event.emit('closeFile', file)
this.emit('closeFile', file)
}) })
this.tabsApi.activateTab(path) this.tabsApi.activateTab(path)
} }
@ -132,9 +137,9 @@ export class TabProxy extends Plugin {
this.addTab( this.addTab(
name, name,
displayName, displayName,
() => this.event.emit('switchApp', name), () => this.emit('switchApp', name),
() => { () => {
this.event.emit('closeApp', name) this.emit('closeApp', name)
this.call('manager', 'deactivatePlugin', name) this.call('manager', 'deactivatePlugin', name)
}, },
icon icon
@ -149,7 +154,7 @@ export class TabProxy extends Plugin {
} }
focus (name) { focus (name) {
this.event.emit('switchApp', name) this.emit('switchApp', name)
this.tabsApi.activateTab(name) this.tabsApi.activateTab(name)
} }
@ -199,6 +204,7 @@ export class TabProxy extends Plugin {
() => { () => {
this.fileManager.closeFile(newName) this.fileManager.closeFile(newName)
this.event.emit('closeFile', newName) this.event.emit('closeFile', newName)
this.emit('closeFile', newName)
}) })
this.removeTab(oldName) this.removeTab(oldName)
} }
@ -285,7 +291,7 @@ export class TabProxy extends Plugin {
if (this.loadedTabs[index]) { if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name const name = this.loadedTabs[index].name
if (this._handlers[name]) this._handlers[name].switchTo() if (this._handlers[name]) this._handlers[name].switchTo()
this.event.emit('tabCountChanged', this.loadedTabs.length) this.emit('tabCountChanged', this.loadedTabs.length)
} }
} }
@ -293,7 +299,7 @@ export class TabProxy extends Plugin {
if (this.loadedTabs[index]) { if (this.loadedTabs[index]) {
const name = this.loadedTabs[index].name const name = this.loadedTabs[index].name
if (this._handlers[name]) this._handlers[name].close() if (this._handlers[name]) this._handlers[name].close()
this.event.emit('tabCountChanged', this.loadedTabs.length) this.emit('tabCountChanged', this.loadedTabs.length)
} }
} }
@ -308,8 +314,6 @@ export class TabProxy extends Plugin {
} }
renderTabsbar () { renderTabsbar () {
this.el = document.createElement('div')
this.renderComponent()
return this.el return this.el
} }
} }

@ -8,13 +8,8 @@ import Registry from '../state/registry'
const vm = require('vm') const vm = require('vm')
const EventManager = require('../../lib/events') const EventManager = require('../../lib/events')
const CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI')
const AutoCompletePopup = require('../ui/auto-complete-popup')
import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line
const GistHandler = require('../../lib/gist-handler')
const KONSOLES = [] const KONSOLES = []
function register (api) { KONSOLES.push(api) } function register (api) { KONSOLES.push(api) }
@ -22,7 +17,7 @@ function register (api) { KONSOLES.push(api) }
const profile = { const profile = {
displayName: 'Terminal', displayName: 'Terminal',
name: 'terminal', name: 'terminal',
methods: ['log'], methods: ['log', 'logHtml'],
events: [], events: [],
description: ' - ', description: ' - ',
version: packageJson.version version: packageJson.version
@ -32,7 +27,6 @@ class Terminal extends Plugin {
constructor (opts, api) { constructor (opts, api) {
super(profile) super(profile)
this.fileImport = new CompilerImports() this.fileImport = new CompilerImports()
this.gistHandler = new GistHandler()
this.event = new EventManager() this.event = new EventManager()
this.globalRegistry = Registry.getInstance() this.globalRegistry = Registry.getInstance()
this.element = document.createElement('div') this.element = document.createElement('div')
@ -68,8 +62,6 @@ class Terminal extends Plugin {
} }
this._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null } this._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null }
this._components = {} this._components = {}
this._components.cmdInterpreter = new CommandInterpreterAPI(this, this.blockchain)
this._components.autoCompletePopup = new AutoCompletePopup(this._opts)
this._commands = {} this._commands = {}
this.commands = {} this.commands = {}
this._JOURNAL = [] this._JOURNAL = []

@ -1,53 +0,0 @@
import { Plugin } from '@remixproject/engine'
import { Profile } from '@remixproject/plugin-utils'
import { AlertModal } from 'libs/remix-ui/app/src/lib/remix-app/interface'
import { ModalTypes } from 'libs/remix-ui/app/src/lib/remix-app/types'
import { AppModal } from '../../../../../libs/remix-ui/app/src'
const profile:Profile = {
name: 'testerplugin',
displayName: 'testerplugin',
description: 'testerplugin',
methods: []
}
export class ModalPluginTester extends Plugin {
constructor () {
super(profile)
}
handleMessage (message: any): void {
console.log(message)
}
onActivation (): void {
// just a modal
let mod:AppModal = {
id: 'modal1',
title: 'test',
message: 'test',
okFn: this.handleMessage,
okLabel: 'yes',
cancelFn: null,
cancelLabel: 'no'
}
// this.call('modal', 'modal', mod)
// modal with callback
mod = { ...mod, message: 'gist url', modalType: ModalTypes.prompt, defaultValue: 'prompting' }
// this.call('modal', 'modal', mod)
// modal with password
mod = { ...mod, message: 'enter password to give me eth', modalType: ModalTypes.password, defaultValue: 'pass' }
// this.call('modal', 'modal', mod)
const al:AlertModal = {
id: 'myalert',
message: 'alert message'
}
// this.call('modal', 'alert', al)
// set toaster
// this.call('modal', 'toast', 'toast message')
}
}

@ -5,7 +5,6 @@ import { EventEmitter } from 'events'
import {RemixUiStaticAnalyser} from '@remix-ui/static-analyser' // eslint-disable-line import {RemixUiStaticAnalyser} from '@remix-ui/static-analyser' // eslint-disable-line
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry' import Registry from '../state/registry'
var Renderer = require('../ui/renderer')
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
@ -30,9 +29,7 @@ class AnalysisTab extends ViewPlugin {
this.registry = Registry.getInstance() this.registry = Registry.getInstance()
this.element = document.createElement('div') this.element = document.createElement('div')
this.element.setAttribute('id', 'staticAnalyserView') this.element.setAttribute('id', 'staticAnalyserView')
this._components = { this._components = {}
renderer: new Renderer(this)
}
this._components.registry = this.registry this._components.registry = this.registry
this._deps = { this._deps = {
offsetToLineColumnConverter: this.registry.get( offsetToLineColumnConverter: this.registry.get(

@ -1,82 +0,0 @@
import * as packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine'
import Web3 from 'web3'
const yo = require('yo-yo')
const modalDialogCustom = require('../ui/modal-dialog-custom')
const profile = {
name: 'hardhat-provider',
displayName: 'Hardhat Provider',
kind: 'provider',
description: 'Hardhat provider',
methods: ['sendAsync'],
version: packageJson.version
}
export default class HardhatProvider extends Plugin {
constructor (blockchain) {
super(profile)
this.provider = null
this.blocked = false // used to block any call when trying to recover after a failed connection.
this.blockchain = blockchain
}
onDeactivation () {
this.provider = null
this.blocked = false
}
hardhatProviderDialogBody () {
return yo`
<div class="">
Note: To run Hardhat network node on your system, go to hardhat project folder and run command:
<div class="border p-1">npx hardhat node</div>
<br>
For more info, visit: <a href="https://hardhat.org/getting-started/#connecting-a-wallet-or-dapp-to-hardhat-network" target="_blank">Hardhat Documentation</a>
<br><br>
Hardhat JSON-RPC Endpoint
</div>
`
}
sendAsync (data) {
return new Promise((resolve, reject) => {
if (this.blocked) return reject(new Error('provider unable to connect'))
// If provider is not set, allow to open modal only when provider is trying to connect
if (!this.provider) {
modalDialogCustom.prompt('Hardhat node request', this.hardhatProviderDialogBody(), 'http://127.0.0.1:8545', (target) => {
this.provider = new Web3.providers.HttpProvider(target)
this.sendAsyncInternal(data, resolve, reject)
}, () => {
this.sendAsyncInternal(data, resolve, reject)
})
} else {
this.sendAsyncInternal(data, resolve, reject)
}
})
}
sendAsyncInternal (data, resolve, reject) {
if (this.provider) {
// Check the case where current environment is VM on UI and it still sends RPC requests
// This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!'
if (this.blockchain.getProvider() !== 'Hardhat Provider' && data.method !== 'net_listening') return reject(new Error('Environment Updated !!'))
this.provider[this.provider.sendAsync ? 'sendAsync' : 'send'](data, async (error, message) => {
if (error) {
this.blocked = true
modalDialogCustom.alert('Hardhat Provider', `Error while connecting to the hardhat provider: ${error.message}`)
await this.call('udapp', 'setEnvironmentMode', { context: 'vm', fork: 'london' })
this.provider = null
setTimeout(_ => { this.blocked = false }, 1000) // we wait 1 second for letting remix to switch to vm
return reject(error)
}
resolve(message)
})
} else {
const result = data.method === 'net_listening' ? 'canceled' : []
resolve({ jsonrpc: '2.0', result: result, id: data.id })
}
}
}
module.exports = HardhatProvider

@ -0,0 +1,128 @@
import * as packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine'
import { AppModal, AlertModal, ModalTypes } from '@remix-ui/app'
import React from 'react' // eslint-disable-line
import { Blockchain } from '../../blockchain/blockchain'
import { ethers } from 'ethers'
const profile = {
name: 'hardhat-provider',
displayName: 'Hardhat Provider',
kind: 'provider',
description: 'Hardhat provider',
methods: ['sendAsync'],
version: packageJson.version
}
type JsonDataRequest = {
id: number,
jsonrpc: string // version
method: string,
params: Array<any>,
}
type JsonDataResult = {
id: number,
jsonrpc: string // version
result: any
}
type RejectRequest = (error: Error) => void
type SuccessRequest = (data: JsonDataResult) => void
export class HardhatProvider extends Plugin {
provider: ethers.providers.JsonRpcProvider
blocked: boolean
blockchain: Blockchain
target: String
constructor (blockchain) {
super(profile)
this.provider = null
this.blocked = false // used to block any call when trying to recover after a failed connection.
this.blockchain = blockchain
}
onDeactivation () {
this.provider = null
this.blocked = false
}
hardhatProviderDialogBody (): JSX.Element {
return (<div> Note: To run Hardhat network node on your system, go to hardhat project folder and run command:
<div className="border p-1">npx hardhat node</div>
For more info, visit: <a href="https://hardhat.org/getting-started/#connecting-a-wallet-or-dapp-to-hardhat-network" target="_blank">Hardhat Documentation</a>
Hardhat JSON-RPC Endpoint
</div>)
}
sendAsync (data: JsonDataRequest): Promise<any> {
return new Promise(async (resolve, reject) => {
if (this.blocked) return reject(new Error('provider unable to connect'))
// If provider is not set, allow to open modal only when provider is trying to connect
if (!this.provider) {
let value: string
try {
value = await ((): Promise<string> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
id: 'harrhatprovider',
title: 'Hardhat node request',
message: this.hardhatProviderDialogBody(),
modalType: ModalTypes.prompt,
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: (value: string) => {
setTimeout(() => resolve(value), 0)
},
cancelFn: () => {
setTimeout(() => reject(new Error('Canceled')), 0)
},
hideFn: () => {
setTimeout(() => reject(new Error('Hide')), 0)
},
defaultValue: 'http://127.0.0.1:8545'
}
this.call('modal', 'modal', modalContent)
})
})()
} catch (e) {
// the modal has been canceled/hide
return
}
this.provider = new ethers.providers.JsonRpcProvider(value)
this.sendAsyncInternal(data, resolve, reject)
} else {
this.sendAsyncInternal(data, resolve, reject)
}
})
}
private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise<void> {
if (this.provider) {
// Check the case where current environment is VM on UI and it still sends RPC requests
// This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!'
if (this.blockchain.getProvider() !== 'Hardhat Provider' && data.method !== 'net_listening') return reject(new Error('Environment Updated !!'))
try {
const result = await this.provider.send(data.method, data.params)
resolve({ jsonrpc: '2.0', result, id: data.id })
} catch (error) {
this.blocked = true
const modalContent: AlertModal = {
id: 'harrhatprovider',
title: 'Hardhat Provider',
message: `Error while connecting to the hardhat provider: ${error.message}`,
}
this.call('modal', 'alert', modalContent)
await this.call('udapp', 'setEnvironmentMode', { context: 'vm', fork: 'london' })
this.provider = null
setTimeout(_ => { this.blocked = false }, 1000) // we wait 1 second for letting remix to switch to vm
reject(error)
}
} else {
const result = data.method === 'net_listening' ? 'canceled' : []
resolve({ jsonrpc: '2.0', result: result, id: data.id })
}
}
}

@ -1,423 +0,0 @@
import publishToStorage from '../../../publishToStorage'
const yo = require('yo-yo')
const ethJSUtil = require('ethereumjs-util')
const css = require('../styles/run-tab-styles')
const modalDialogCustom = require('../../ui/modal-dialog-custom')
const remixLib = require('@remix-project/remix-lib')
const EventManager = remixLib.EventManager
const confirmDialog = require('../../ui/confirmDialog')
const modalDialog = require('../../ui/modaldialog')
const MultiParamManager = require('../../ui/multiParamManager')
const helper = require('../../../lib/helper')
const addTooltip = require('../../ui/tooltip')
const _paq = window._paq = window._paq || []
class ContractDropdownUI {
constructor (blockchain, dropdownLogic, logCallback, runView) {
this.blockchain = blockchain
this.dropdownLogic = dropdownLogic
this.logCallback = logCallback
this.runView = runView
this.event = new EventManager()
this.listenToEvents()
this.ipfsCheckedState = false
this.exEnvironment = blockchain.getProvider()
this.listenToContextChange()
this.loadType = 'other'
}
listenToEvents () {
this.dropdownLogic.event.register('newlyCompiled', (success, data, source, compiler, compilerFullName, file) => {
if (!this.selectContractNames) return
this.selectContractNames.innerHTML = ''
if (success) {
this.dropdownLogic.getCompiledContracts(compiler, compilerFullName).forEach((contract) => {
this.selectContractNames.appendChild(yo`<option value="${contract.name}" compiler="${compilerFullName}">${contract.name} - ${contract.file}</option>`)
})
}
this.enableAtAddress(success)
this.enableContractNames(success)
this.setInputParamsPlaceHolder()
if (success) {
this.compFails.style.display = 'none'
} else {
this.compFails.style.display = 'block'
}
})
}
listenToContextChange () {
this.blockchain.event.register('networkStatus', ({ error, network }) => {
if (error) {
console.log('can\'t detect network')
return
}
this.exEnvironment = this.blockchain.getProvider()
this.networkName = network.name
this.networkId = network.id
const savedConfig = window.localStorage.getItem(`ipfs/${this.exEnvironment}/${this.networkName}`)
// check if an already selected option exist else use default workflow
if (savedConfig !== null) {
this.setCheckedState(savedConfig)
} else {
this.setCheckedState(this.networkName === 'Main')
}
})
}
setCheckedState (value) {
value = value === 'true' ? true : value === 'false' ? false : value
this.ipfsCheckedState = value
if (this.ipfsCheckbox) this.ipfsCheckbox.checked = value
}
toggleCheckedState () {
if (this.exEnvironment === 'vm') this.networkName = 'VM'
this.ipfsCheckedState = !this.ipfsCheckedState
window.localStorage.setItem(`ipfs/${this.exEnvironment}/${this.networkName}`, this.ipfsCheckedState)
}
enableContractNames (enable) {
if (enable) {
if (this.selectContractNames.value === '') return
this.selectContractNames.removeAttribute('disabled')
this.selectContractNames.setAttribute('title', 'Select contract for Deploy or At Address.')
} else {
this.selectContractNames.setAttribute('disabled', true)
if (this.loadType === 'sol') {
this.selectContractNames.setAttribute('title', '⚠ Select and compile *.sol file to deploy or access a contract.')
} else {
this.selectContractNames.setAttribute('title', '⚠ Selected *.abi file allows accessing contracts, select and compile *.sol file to deploy and access one.')
}
}
}
enableAtAddress (enable) {
if (enable) {
const address = this.atAddressButtonInput.value
if (!address || !ethJSUtil.isValidAddress(address)) {
this.enableAtAddress(false)
return
}
this.atAddress.removeAttribute('disabled')
this.atAddress.setAttribute('title', 'Interact with the given contract.')
} else {
this.atAddress.setAttribute('disabled', true)
if (this.atAddressButtonInput.value === '') {
this.atAddress.setAttribute('title', '⚠ Compile *.sol file or select *.abi file & then enter the address of deployed contract.')
} else {
this.atAddress.setAttribute('title', '⚠ Compile *.sol file or select *.abi file.')
}
}
}
render () {
this.compFails = yo`<i title="No contract compiled yet or compilation failed. Please check the compile tab for more information." class="m-2 ml-3 fas fa-times-circle ${css.errorIcon}" ></i>`
this.atAddress = yo`<button class="${css.atAddress} btn btn-sm btn-info" id="runAndDeployAtAdressButton" onclick=${this.loadFromAddress.bind(this)}>At Address</button>`
this.atAddressButtonInput = yo`<input class="${css.input} ${css.ataddressinput} ataddressinput form-control" placeholder="Load contract from Address" title="address of contract" oninput=${this.atAddressChanged.bind(this)} />`
this.selectContractNames = yo`<select class="${css.contractNames} custom-select" disabled title="Please compile *.sol file to deploy or access a contract"></select>`
this.abiLabel = yo`<span class="py-1">ABI file selected</span>`
if (this.exEnvironment === 'vm') this.networkName = 'VM'
this.enableAtAddress(false)
this.abiLabel.style.display = 'none'
const savedConfig = window.localStorage.getItem(`ipfs/${this.exEnvironment}/${this.networkName}`)
this.ipfsCheckedState = savedConfig === 'true' ? true : false // eslint-disable-line
this.ipfsCheckbox = yo`
<input
id="deployAndRunPublishToIPFS"
data-id="contractDropdownIpfsCheckbox"
class="form-check-input custom-control-input"
type="checkbox"
onchange=${() => this.toggleCheckedState()}
>
`
if (this.ipfsCheckedState) this.ipfsCheckbox.checked = true
this.deployCheckBox = yo`
<div class="d-flex py-1 align-items-center custom-control custom-checkbox">
${this.ipfsCheckbox}
<label
for="deployAndRunPublishToIPFS"
data-id="contractDropdownIpfsCheckboxLabel"
class="m-0 form-check-label custom-control-label ${css.checkboxAlign}"
title="Publishing the source code and metadata to IPFS facilitates source code verification using Sourcify and will greatly foster contract adoption (auditing, debugging, calling it, etc...)"
>
Publish to IPFS
</label>
</div>
`
this.createPanel = yo`<div class="${css.deployDropdown}"></div>`
this.orLabel = yo`<div class="${css.orLabel} mt-2">or</div>`
const contractNamesContainer = yo`
<div class="${css.container}" data-id="contractDropdownContainer">
<label class="${css.settingsLabel}">Contract</label>
<div class="${css.subcontainer}">
${this.selectContractNames} ${this.compFails}
${this.abiLabel}
</div>
<div>
${this.createPanel}
${this.orLabel}
<div class="${css.button} ${css.atAddressSect}">
${this.atAddress}
${this.atAddressButtonInput}
</div>
</div>
</div>
`
this.selectContractNames.addEventListener('change', this.setInputParamsPlaceHolder.bind(this))
this.setInputParamsPlaceHolder()
if (!this.contractNamesContainer) {
this.contractNamesContainer = contractNamesContainer
}
return contractNamesContainer
}
atAddressChanged (event) {
if (!this.atAddressButtonInput.value) {
this.enableAtAddress(false)
} else {
if ((this.selectContractNames && !this.selectContractNames.getAttribute('disabled') && this.loadType === 'sol') ||
this.loadType === 'abi') {
this.enableAtAddress(true)
} else {
this.enableAtAddress(false)
}
}
}
changeCurrentFile (currentFile) {
if (!this.selectContractNames) return
if (/.(.abi)$/.exec(currentFile)) {
this.createPanel.style.display = 'none'
this.orLabel.style.display = 'none'
this.compFails.style.display = 'none'
this.loadType = 'abi'
this.contractNamesContainer.style.display = 'block'
this.abiLabel.style.display = 'block'
this.abiLabel.innerHTML = currentFile
this.selectContractNames.style.display = 'none'
this.enableContractNames(true)
this.enableAtAddress(true)
} else if (/.(.sol)$/.exec(currentFile) ||
/.(.vy)$/.exec(currentFile) || // vyper
/.(.lex)$/.exec(currentFile) || // lexon
/.(.contract)$/.exec(currentFile)) {
this.createPanel.style.display = 'block'
this.orLabel.style.display = 'block'
this.contractNamesContainer.style.display = 'block'
this.loadType = 'sol'
this.selectContractNames.style.display = 'block'
this.abiLabel.style.display = 'none'
if (this.selectContractNames.value === '') this.enableAtAddress(false)
} else {
this.loadType = 'other'
this.createPanel.style.display = 'block'
this.orLabel.style.display = 'block'
this.contractNamesContainer.style.display = 'block'
this.selectContractNames.style.display = 'block'
this.abiLabel.style.display = 'none'
if (this.selectContractNames.value === '') this.enableAtAddress(false)
}
}
setInputParamsPlaceHolder () {
this.createPanel.innerHTML = ''
if (this.selectContractNames.selectedIndex < 0 || this.selectContractNames.children.length <= 0) {
this.createPanel.innerHTML = 'No compiled contracts'
return
}
const selectedContract = this.getSelectedContract()
const clickCallback = async (valArray, inputsValues) => {
var selectedContract = this.getSelectedContract()
this.createInstance(selectedContract, inputsValues)
}
const createConstructorInstance = new MultiParamManager(
0,
selectedContract.getConstructorInterface(),
clickCallback,
selectedContract.getConstructorInputs(),
'Deploy',
selectedContract.bytecodeObject,
true
)
this.createPanel.appendChild(createConstructorInstance.render())
this.createPanel.appendChild(this.deployCheckBox)
}
getSelectedContract () {
var contract = this.selectContractNames.children[this.selectContractNames.selectedIndex]
var contractName = contract.getAttribute('value')
var compilerAtributeName = contract.getAttribute('compiler')
return this.dropdownLogic.getSelectedContract(contractName, compilerAtributeName)
}
async createInstance (selectedContract, args) {
if (selectedContract.bytecodeObject.length === 0) {
return modalDialogCustom.alert('This contract may be abstract, not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.')
}
var continueCb = (error, continueTxExecution, cancelCb) => {
if (error) {
var msg = typeof error !== 'string' ? error.message : error
modalDialog('Gas estimation failed', yo`<div>Gas estimation errored with the following message (see below).
The transaction execution will likely fail. Do you want to force sending? <br>
${msg}
</div>`,
{
label: 'Send Transaction',
fn: () => {
continueTxExecution()
}
}, {
label: 'Cancel Transaction',
fn: () => {
cancelCb()
}
})
} else {
continueTxExecution()
}
}
const self = this
var promptCb = (okCb, cancelCb) => {
modalDialogCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
}
var statusCb = (msg) => {
return this.logCallback(msg)
}
var finalCb = (error, contractObject, address) => {
self.event.trigger('clearInstance')
if (error) {
return this.logCallback(error)
}
self.event.trigger('newContractInstanceAdded', [contractObject, address, contractObject.name])
const data = self.runView.compilersArtefacts.getCompilerAbstract(contractObject.contract.file)
self.runView.compilersArtefacts.addResolvedContract(helper.addressToString(address), data)
if (self.ipfsCheckedState) {
_paq.push(['trackEvent', 'udapp', 'DeployAndPublish', this.networkName + '_' + this.networkId])
publishToStorage('ipfs', self.runView.fileProvider, self.runView.fileManager, selectedContract)
} else {
_paq.push(['trackEvent', 'udapp', 'DeployOnly', this.networkName + '_' + this.networkId])
}
}
let contractMetadata
try {
contractMetadata = await this.runView.call('compilerMetadata', 'deployMetadataOf', selectedContract.name, selectedContract.contract.file)
} catch (error) {
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
}
const compilerContracts = this.dropdownLogic.getCompilerContracts()
const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog)
if (selectedContract.isOverSizeLimit()) {
return modalDialog('Contract code size over limit', yo`<div>Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails. <br>
More info: <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md" target="_blank">eip-170</a>
</div>`,
{
label: 'Force Send',
fn: () => {
this.deployContract(selectedContract, args, contractMetadata, compilerContracts, { continueCb, promptCb, statusCb, finalCb }, confirmationCb)
}
}, {
label: 'Cancel',
fn: () => {
this.logCallback(`creation of ${selectedContract.name} canceled by user.`)
}
})
}
this.deployContract(selectedContract, args, contractMetadata, compilerContracts, { continueCb, promptCb, statusCb, finalCb }, confirmationCb)
}
deployContract (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) {
_paq.push(['trackEvent', 'udapp', 'DeployContractTo', this.networkName + '_' + this.networkId])
const { statusCb } = callbacks
if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) {
return this.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb)
}
if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`)
this.blockchain.deployContractWithLibrary(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb)
}
getConfirmationCb (modalDialog, confirmDialog) {
// this code is the same as in recorder.js. TODO need to be refactored out
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
if (network.name !== 'Main') {
return continueTxExecution(null)
}
const amount = this.blockchain.fromWei(tx.value, true, 'ether')
const content = confirmDialog(tx, network, amount, gasEstimation, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain))
modalDialog('Confirm transaction', content,
{
label: 'Confirm',
fn: () => {
this.blockchain.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
// TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) {
cancelCb('Given transaction fee is not correct')
} else {
continueTxExecution(content.txFee)
}
}
}, {
label: 'Cancel',
fn: () => {
return cancelCb('Transaction canceled by user.')
}
}
)
}
return confirmationCb
}
loadFromAddress () {
this.event.trigger('clearInstance')
let address = this.atAddressButtonInput.value
if (!ethJSUtil.isValidChecksumAddress(address)) {
addTooltip(yo`
<span>
It seems you are not using a checksumed address.
<br>A checksummed address is an address that contains uppercase letters, as specified in <a href="https://eips.ethereum.org/EIPS/eip-55" target="_blank">EIP-55</a>.
<br>Checksummed addresses are meant to help prevent users from sending transactions to the wrong address.
</span>`)
address = ethJSUtil.toChecksumAddress(address)
}
this.dropdownLogic.loadContractFromAddress(address,
(cb) => {
modalDialogCustom.confirm('At Address', `Do you really want to interact with ${address} using the current ABI definition?`, cb)
},
(error, loadType, abi) => {
if (error) {
return modalDialogCustom.alert(error)
}
if (loadType === 'abi') {
return this.event.trigger('newContractABIAdded', [abi, address])
}
var selectedContract = this.getSelectedContract()
this.event.trigger('newContractInstanceAdded', [selectedContract.object, address, this.selectContractNames.value])
}
)
}
}
module.exports = ContractDropdownUI

@ -1,108 +0,0 @@
import { CompilerAbstract } from '@remix-project/remix-solidity'
const remixLib = require('@remix-project/remix-lib')
const txHelper = remixLib.execution.txHelper
const EventManager = remixLib.EventManager
const _paq = window._paq = window._paq || []
class DropdownLogic {
constructor (compilersArtefacts, config, editor, runView) {
this.compilersArtefacts = compilersArtefacts
this.config = config
this.editor = editor
this.runView = runView
this.event = new EventManager()
this.listenToCompilationEvents()
}
// TODO: can be moved up; the event in contractDropdown will have to refactored a method instead
listenToCompilationEvents () {
const broadcastCompilationResult = (file, source, languageVersion, data) => {
// TODO check whether the tab is configured
const compiler = new CompilerAbstract(languageVersion, data, source)
this.compilersArtefacts[languageVersion] = compiler
this.compilersArtefacts.__last = compiler
this.event.trigger('newlyCompiled', [true, data, source, compiler, languageVersion, file])
}
this.runView.on('solidity', 'compilationFinished', (file, source, languageVersion, data) =>
broadcastCompilationResult(file, source, languageVersion, data)
)
this.runView.on('vyper', 'compilationFinished', (file, source, languageVersion, data) =>
broadcastCompilationResult(file, source, languageVersion, data)
)
this.runView.on('lexon', 'compilationFinished', (file, source, languageVersion, data) =>
broadcastCompilationResult(file, source, languageVersion, data)
)
this.runView.on('yulp', 'compilationFinished', (file, source, languageVersion, data) =>
broadcastCompilationResult(file, source, languageVersion, data)
)
this.runView.on('optimism-compiler', 'compilationFinished', (file, source, languageVersion, data) =>
broadcastCompilationResult(file, source, languageVersion, data)
)
}
loadContractFromAddress (address, confirmCb, cb) {
if (/.(.abi)$/.exec(this.config.get('currentFile'))) {
confirmCb(() => {
var abi
try {
abi = JSON.parse(this.editor.currentContent())
} catch (e) {
return cb('Failed to parse the current file as JSON ABI.')
}
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithABI'])
cb(null, 'abi', abi)
})
} else {
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithArtifacts'])
cb(null, 'instance')
}
}
getCompiledContracts (compiler, compilerFullName) {
var contracts = []
compiler.visitContracts((contract) => {
contracts.push(contract)
})
return contracts
}
getSelectedContract (contractName, compilerAtributeName) {
if (!contractName) return null
var compiler = this.compilersArtefacts[compilerAtributeName]
if (!compiler) return null
var contract = compiler.getContract(contractName)
return {
name: contractName,
contract: contract,
compiler: compiler,
abi: contract.object.abi,
bytecodeObject: contract.object.evm.bytecode.object,
bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences,
object: contract.object,
deployedBytecode: contract.object.evm.deployedBytecode,
getConstructorInterface: () => {
return txHelper.getConstructorInterface(contract.object.abi)
},
getConstructorInputs: () => {
var constructorInteface = txHelper.getConstructorInterface(contract.object.abi)
return txHelper.inputParametersDeclarationToString(constructorInteface.inputs)
},
isOverSizeLimit: () => {
var deployedBytecode = contract.object.evm.deployedBytecode
return (deployedBytecode && deployedBytecode.object.length / 2 > 24576)
},
metadata: contract.object.metadata
}
}
getCompilerContracts () {
return this.compilersArtefacts.__last.getData().contracts
}
}
module.exports = DropdownLogic

@ -1,160 +0,0 @@
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../../package.json'
var yo = require('yo-yo')
var remixLib = require('@remix-project/remix-lib')
var EventManager = remixLib.EventManager
var csjs = require('csjs-inject')
var css = require('../styles/run-tab-styles')
var modalDialogCustom = require('../../ui/modal-dialog-custom')
var modalDialog = require('../../ui/modaldialog')
var confirmDialog = require('../../ui/confirmDialog')
var helper = require('../../../lib/helper.js')
const profile = {
name: 'recorder',
methods: ['runScenario'],
version: packageJson.version
}
class RecorderUI extends Plugin {
constructor (blockchain, fileManager, recorder, logCallBack, config) {
super(profile)
this.fileManager = fileManager
this.blockchain = blockchain
this.recorder = recorder
this.logCallBack = logCallBack
this.config = config
this.event = new EventManager()
}
render () {
var css2 = csjs`
.container {}
.runTxs {}
.recorder {}
`
this.runButton = yo`<i class="fas fa-play runtransaction ${css2.runTxs} ${css.icon}" title="Run Transactions" aria-hidden="true"></i>`
this.recordButton = yo`
<i class="fas fa-save savetransaction ${css2.recorder} ${css.icon}"
onclick=${this.triggerRecordButton.bind(this)} title="Save Transactions" aria-hidden="true">
</i>`
this.runButton.onclick = () => {
const file = this.config.get('currentFile')
if (!file) return modalDialogCustom.alert('A scenario file has to be selected')
this.runScenario(file)
}
}
runScenario (file) {
if (!file) return modalDialogCustom.alert('Unable to run scenerio, no specified scenario file')
var continueCb = (error, continueTxExecution, cancelCb) => {
if (error) {
var msg = typeof error !== 'string' ? error.message : error
modalDialog('Gas estimation failed', yo`<div>Gas estimation errored with the following message (see below).
The transaction execution will likely fail. Do you want to force sending? <br>
${msg}
</div>`,
{
label: 'Send Transaction',
fn: () => {
continueTxExecution()
}
}, {
label: 'Cancel Transaction',
fn: () => {
cancelCb()
}
})
} else {
continueTxExecution()
}
}
var promptCb = (okCb, cancelCb) => {
modalDialogCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
}
var alertCb = (msg) => {
modalDialogCustom.alert(msg)
}
const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog)
this.fileManager.readFile(file).then((json) => {
// TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily
this.recorder.runScenario(json, continueCb, promptCb, alertCb, confirmationCb, this.logCallBack, (error, abi, address, contractName) => {
if (error) {
return modalDialogCustom.alert(error)
}
this.event.trigger('newScenario', [abi, address, contractName])
})
}).catch((error) => modalDialogCustom.alert(error))
}
getConfirmationCb (modalDialog, confirmDialog) {
// this code is the same as in contractDropdown.js. TODO need to be refactored out
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
if (network.name !== 'Main') {
return continueTxExecution(null)
}
const amount = this.blockchain.fromWei(tx.value, true, 'ether')
const content = confirmDialog(tx, network, amount, gasEstimation, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain))
modalDialog('Confirm transaction', content,
{
label: 'Confirm',
fn: () => {
this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
// TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) {
cancelCb('Given transaction fee is not correct')
} else {
continueTxExecution(content.txFee)
}
}
}, {
label: 'Cancel',
fn: () => {
return cancelCb('Transaction canceled by user.')
}
}
)
}
return confirmationCb
}
triggerRecordButton () {
this.saveScenario(
(path, cb) => {
modalDialogCustom.prompt('Save transactions as scenario', 'Transactions will be saved in a file under ' + path, 'scenario.json', cb)
},
(error) => {
if (error) return modalDialogCustom.alert(error)
}
)
}
saveScenario (promptCb, cb) {
var txJSON = JSON.stringify(this.recorder.getAll(), null, 2)
var path = this.fileManager.currentPath()
promptCb(path, input => {
var fileProvider = this.fileManager.fileProviderOf(path)
if (!fileProvider) return
var newFile = path + '/' + input
helper.createNonClashingName(newFile, fileProvider, (error, newFile) => {
if (error) return cb('Failed to create file. ' + newFile + ' ' + error)
if (!fileProvider.set(newFile, txJSON)) return cb('Failed to create file ' + newFile)
this.fileManager.open(newFile)
})
})
}
}
module.exports = RecorderUI

@ -1,449 +0,0 @@
import { BN } from 'ethereumjs-util'
import Registry from '../../state/registry'
const $ = require('jquery')
const yo = require('yo-yo')
const remixLib = require('@remix-project/remix-lib')
const EventManager = remixLib.EventManager
const css = require('../styles/run-tab-styles')
const copyToClipboard = require('../../ui/copy-to-clipboard')
const modalDialogCustom = require('../../ui/modal-dialog-custom')
const addTooltip = require('../../ui/tooltip')
const helper = require('../../../lib/helper.js')
class SettingsUI {
constructor (blockchain, networkModule) {
this.blockchain = blockchain
this.event = new EventManager()
this._components = {}
this.blockchain.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => {
if (!lookupOnly) this.el.querySelector('#value').value = 0
if (error) return
this.updateAccountBalances()
})
this._components = {
registry: Registry.getInstance(),
networkModule: networkModule
}
this._components.registry = Registry.getInstance()
this._deps = {
config: this._components.registry.get('config').api
}
this._deps.config.events.on('settings/personal-mode_changed', this.onPersonalChange.bind(this))
setInterval(() => {
this.updateAccountBalances()
}, 1000)
this.accountListCallId = 0
this.loadedAccounts = {}
}
updateAccountBalances () {
if (!this.el) return
var accounts = $(this.el.querySelector('#txorigin')).children('option')
accounts.each((index, account) => {
this.blockchain.getBalanceInEther(account.value, (err, balance) => {
if (err) return
const updated = helper.shortenAddress(account.value, balance)
if (updated !== account.innerText) { // check if the balance has been updated and update UI accordingly.
account.innerText = updated
}
})
})
}
validateInputKey (e) {
// preventing not numeric keys
// preventing 000 case
if (!helper.isNumeric(e.key) ||
(e.key === '0' && !parseInt(this.el.querySelector('#value').value) && this.el.querySelector('#value').value.length > 0)) {
e.preventDefault()
e.stopImmediatePropagation()
}
}
validateValue () {
const valueEl = this.el.querySelector('#value')
if (!valueEl.value) {
// assign 0 if given value is
// - empty
valueEl.value = 0
return
}
let v
try {
v = new BN(valueEl.value, 10)
valueEl.value = v.toString(10)
} catch (e) {
// assign 0 if given value is
// - not valid (for ex 4345-54)
// - contains only '0's (for ex 0000) copy past or edit
valueEl.value = 0
}
// if giveen value is negative(possible with copy-pasting) set to 0
if (v.lt(0)) valueEl.value = 0
}
render () {
this.netUI = yo`<span class="${css.network} badge badge-secondary"></span>`
var environmentEl = yo`
<div class="${css.crow}">
<label id="selectExEnv" class="${css.settingsLabel}">
Environment
</label>
<div class="${css.environment}">
<select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" class="form-control ${css.select} custom-select">
<option id="vm-mode-london" data-id="settingsVMLondonMode"
title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm-london" name="executionContext" fork="london"> JavaScript VM (London)
</option>
<option id="vm-mode-berlin" data-id="settingsVMBerlinMode"
title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm-berlin" name="executionContext" fork="berlin" > JavaScript VM (Berlin)
</option>
<option id="injected-mode" data-id="settingsInjectedMode"
title="Execution environment has been provided by Metamask or similar provider."
value="injected" name="executionContext"> Injected Web3
</option>
<option id="web3-mode" data-id="settingsWeb3Mode"
title="Execution environment connects to node at localhost (or via IPC if available), transactions will be sent to the network and can cause loss of money or worse!
If this page is served via https and you access your node via http, it might not work. In this case, try cloning the repository and serving it via http."
value="web3" name="executionContext"> Web3 Provider
</option>
</select>
<a href="https://remix-ide.readthedocs.io/en/latest/run.html#run-setup" target="_blank"><i class="${css.infoDeployAction} ml-2 fas fa-info" title="check out docs to setup Environment"></i></a>
</div>
</div>
`
const networkEl = yo`
<div class="${css.crow}">
<div class="${css.settingsLabel}">
</div>
<div class="${css.environment}" data-id="settingsNetworkEnv">
${this.netUI}
</div>
</div>
`
const accountEl = yo`
<div class="${css.crow}">
<label class="${css.settingsLabel}">
Account
<span id="remixRunPlusWraper" title="Create a new account" onload=${this.updatePlusButton.bind(this)}>
<i id="remixRunPlus" class="fas fa-plus-circle ${css.icon}" aria-hidden="true" onclick=${this.newAccount.bind(this)}"></i>
</span>
</label>
<div class="${css.account}">
<select data-id="runTabSelectAccount" name="txorigin" class="form-control ${css.select} custom-select pr-4" id="txorigin"></select>
<div style="margin-left: -5px;">${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)}</div>
<i id="remixRunSignMsg" data-id="settingsRemixRunSignMsg" class="mx-1 fas fa-edit ${css.icon}" aria-hidden="true" onclick=${this.signMessage.bind(this)} title="Sign a message using this account key"></i>
</div>
</div>
`
const gasPriceEl = yo`
<div class="${css.crow}">
<label class="${css.settingsLabel}">Gas limit</label>
<input type="number" class="form-control ${css.gasNval} ${css.col2}" id="gasLimit" value="3000000">
</div>
`
const valueEl = yo`
<div class="${css.crow}">
<label class="${css.settingsLabel}" data-id="remixDRValueLabel">Value</label>
<div class="${css.gasValueContainer}">
<input
type="number"
min="0"
pattern="^[0-9]"
step="1"
class="form-control ${css.gasNval} ${css.col2}"
id="value"
data-id="dandrValue"
value="0"
title="Enter the value and choose the unit"
onkeypress=${(e) => this.validateInputKey(e)}
onchange=${() => this.validateValue()}
>
<select name="unit" class="form-control p-1 ${css.gasNvalUnit} ${css.col2_2} custom-select" id="unit">
<option data-unit="wei">Wei</option>
<option data-unit="gwei">Gwei</option>
<option data-unit="finney">Finney</option>
<option data-unit="ether">Ether</option>
</select>
</div>
</div>
`
const el = yo`
<div class="${css.settings}">
${environmentEl}
${networkEl}
${accountEl}
${gasPriceEl}
${valueEl}
</div>
`
var selectExEnv = environmentEl.querySelector('#selectExEnvOptions')
this.setDropdown(selectExEnv)
this.blockchain.event.register('contextChanged', (context, silent) => {
this.setFinalContext()
})
this.blockchain.event.register('networkStatus', ({ error, network }) => {
if (error) {
this.netUI.innerHTML = 'can\'t detect network '
return
}
const networkProvider = this._components.networkModule.getNetworkProvider.bind(this._components.networkModule)
this.netUI.innerHTML = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : ''
})
setInterval(() => {
this.fillAccountsList()
}, 1000)
this.el = el
this.fillAccountsList()
return el
}
setDropdown (selectExEnv) {
this.selectExEnv = selectExEnv
const addProvider = (network) => {
selectExEnv.appendChild(yo`<option
title="provider name: ${network.name}"
value="${network.name}"
name="executionContext"
>
${network.name}
</option>`)
addTooltip(yo`<span><b>${network.name}</b> provider added</span>`)
}
const removeProvider = (name) => {
var env = selectExEnv.querySelector(`option[value="${name}"]`)
if (env) {
selectExEnv.removeChild(env)
addTooltip(yo`<span><b>${name}</b> provider removed</span>`)
}
}
this.blockchain.event.register('addProvider', provider => addProvider(provider))
this.blockchain.event.register('removeProvider', name => removeProvider(name))
selectExEnv.addEventListener('change', (event) => {
const provider = selectExEnv.options[selectExEnv.selectedIndex]
const fork = provider.getAttribute('fork') // can be undefined if connected to an external source (web3 provider / injected)
let context = provider.value
context = context.startsWith('vm') ? 'vm' : context // context has to be 'vm', 'web3' or 'injected'
this.setExecutionContext({ context, fork })
})
selectExEnv.value = this._getProviderDropdownValue()
}
setExecutionContext (context) {
this.blockchain.changeExecutionContext(context, () => {
modalDialogCustom.prompt('External node request', this.web3ProviderDialogBody(), 'http://127.0.0.1:8545', (target) => {
this.blockchain.setProviderFromEndpoint(target, context, (alertMsg) => {
if (alertMsg) addTooltip(alertMsg)
this.setFinalContext()
})
}, this.setFinalContext.bind(this))
}, (alertMsg) => {
addTooltip(alertMsg)
}, this.setFinalContext.bind(this))
}
web3ProviderDialogBody () {
const thePath = '<path/to/local/folder/for/test/chain>'
return yo`
<div class="">
Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see <a href="https://geth.ethereum.org/docs/rpc/server" target="_blank">Geth Docs on rpc server</a>)
<div class="border p-1">geth --http --http.corsdomain https://remix.ethereum.org</div>
<br>
To run Remix & a local Geth test node, use this command: (see <a href="https://geth.ethereum.org/getting-started/dev-mode" target="_blank">Geth Docs on Dev mode</a>)
<div class="border p-1">geth --http --http.corsdomain="${window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir ${thePath} --dev console</div>
<br>
<br>
<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b>
<br>
<br>For more info: <a href="https://remix-ide.readthedocs.io/en/latest/run.html#more-about-web3-provider" target="_blank">Remix Docs on Web3 Provider</a>
<br>
<br>
Web3 Provider Endpoint
</div>
`
}
/**
* generate a value used by the env dropdown list.
* @return {String} - can return 'vm-berlin, 'vm-london', 'injected' or 'web3'
*/
_getProviderDropdownValue () {
const provider = this.blockchain.getProvider()
const fork = this.blockchain.getCurrentFork()
return provider === 'vm' ? provider + '-' + fork : provider
}
setFinalContext () {
// set the final context. Cause it is possible that this is not the one we've originaly selected
this.selectExEnv.value = this._getProviderDropdownValue()
this.event.trigger('clearInstance', [])
this.updatePlusButton()
}
updatePlusButton () {
// enable/disable + button
const plusBtn = document.getElementById('remixRunPlus')
const plusTitle = document.getElementById('remixRunPlusWraper')
switch (this.selectExEnv.value) {
case 'injected':
plusBtn.classList.add(css.disableMouseEvents)
plusTitle.title = "Unfortunately it's not possible to create an account using injected web3. Please create the account directly from your provider (i.e metamask or other of the same type)."
break
case 'vm':
plusBtn.classList.remove(css.disableMouseEvents)
plusTitle.title = 'Create a new account'
break
case 'web3':
this.onPersonalChange()
break
default: {
plusBtn.classList.add(css.disableMouseEvents)
plusTitle.title = `Unfortunately it's not possible to create an account using an external wallet (${this.selectExEnv.value}).`
}
}
}
onPersonalChange () {
const plusBtn = document.getElementById('remixRunPlus')
const plusTitle = document.getElementById('remixRunPlusWraper')
if (!this._deps.config.get('settings/personal-mode')) {
plusBtn.classList.add(css.disableMouseEvents)
plusTitle.title = 'Creating an account is possible only in Personal mode. Please go to Settings to enable it.'
} else {
plusBtn.classList.remove(css.disableMouseEvents)
plusTitle.title = 'Create a new account'
}
}
newAccount () {
this.blockchain.newAccount(
'',
(cb) => {
modalDialogCustom.promptPassphraseCreation((error, passphrase) => {
if (error) {
return modalDialogCustom.alert(error)
}
cb(passphrase)
}, () => {})
},
(error, address) => {
if (error) {
return addTooltip('Cannot create an account: ' + error)
}
addTooltip(`account ${address} created`)
}
)
}
getSelectedAccount () {
return this.el.querySelector('#txorigin').selectedOptions[0].value
}
getEnvironment () {
return this.blockchain.getProvider()
}
signMessage () {
this.blockchain.getAccounts((err, accounts) => {
if (err) {
return addTooltip(`Cannot get account list: ${err}`)
}
var signMessageDialog = { title: 'Sign a message', text: 'Enter a message to sign', inputvalue: 'Message to sign' }
var $txOrigin = this.el.querySelector('#txorigin')
if (!$txOrigin.selectedOptions[0] && (this.blockchain.isInjectedWeb3() || this.blockchain.isWeb3Provider())) {
return addTooltip('Account list is empty, please make sure the current provider is properly connected to remix')
}
var account = $txOrigin.selectedOptions[0].value
var promptCb = (passphrase) => {
const modal = modalDialogCustom.promptMulti(signMessageDialog, (message) => {
this.blockchain.signMessage(message, account, passphrase, (err, msgHash, signedData) => {
if (err) {
return addTooltip(err)
}
modal.hide()
modalDialogCustom.alert(yo`
<div>
<b>hash:</b><br>
<span id="remixRunSignMsgHash" data-id="settingsRemixRunSignMsgHash">${msgHash}</span>
<br><b>signature:</b><br>
<span id="remixRunSignMsgSignature" data-id="settingsRemixRunSignMsgSignature">${signedData}</span>
</div>
`)
})
}, false)
}
if (this.blockchain.isWeb3Provider()) {
return modalDialogCustom.promptPassphrase(
'Passphrase to sign a message',
'Enter your passphrase for this account to sign the message',
'',
promptCb,
false
)
}
promptCb()
})
}
// TODO: unclear what's the goal of accountListCallId, feels like it can be simplified
async fillAccountsList () {
this.accountListCallId++
const callid = this.accountListCallId
const txOrigin = this.el.querySelector('#txorigin')
let accounts = []
try {
accounts = await this.blockchain.getAccounts()
} catch (e) {
addTooltip(`Cannot get account list: ${e}`)
}
if (!accounts) accounts = []
if (this.accountListCallId > callid) return
this.accountListCallId++
for (const loadedaddress in this.loadedAccounts) {
if (accounts.indexOf(loadedaddress) === -1) {
txOrigin.removeChild(txOrigin.querySelector('option[value="' + loadedaddress + '"]'))
delete this.loadedAccounts[loadedaddress]
}
}
for (const i in accounts) {
const address = accounts[i]
if (!this.loadedAccounts[address]) {
txOrigin.appendChild(yo`<option value="${address}" >${address}</option>`)
this.loadedAccounts[address] = 1
}
}
txOrigin.setAttribute('value', accounts[0])
}
}
module.exports = SettingsUI

@ -1,225 +0,0 @@
var csjs = require('csjs-inject')
var css = csjs`
.runTabView {
display: flex;
flex-direction: column;
}
.runTabView::-webkit-scrollbar {
display: none;
}
.settings {
padding: 0 24px 16px;
}
.crow {
display: block;
margin-top: 8px;
}
.col1 {
width: 30%;
float: left;
align-self: center;
}
.settingsLabel {
font-size: 11px;
margin-bottom: 4px;
text-transform: uppercase;
}
.environment {
display: flex;
align-items: center;
position: relative;
width: 100%;
}
.environment a {
margin-left: 7px;
}
.account {
display: flex;
align-items: center;
}
.account i {
margin-left: 12px;
}
.col2 {
border-radius: 3px;
}
.col2_1 {
width: 164px;
min-width: 164px;
}
.col2_2 {
}
.select {
font-weight: normal;
width: 100%;
overflow: hidden;
}
.instanceContainer {
display: flex;
flex-direction: column;
margin-bottom: 2%;
border: none;
text-align: center;
padding: 0 14px 16px;
}
.pendingTxsContainer {
display: flex;
flex-direction: column;
margin-top: 2%;
border: none;
text-align: center;
}
.container {
padding: 0 24px 16px;
}
.recorderDescription {
margin: 0 15px 15px 0;
}
.contractNames {
width: 100%;
border: 1px solid
}
.subcontainer {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 8px;
}
.subcontainer i {
width: 16px;
display: flex;
justify-content: center;
margin-left: 1px;
}
.button button{
flex: none;
}
.button {
display: flex;
align-items: center;
margin-top: 13px;
}
.transaction {
}
.atAddress {
margin: 0;
min-width: 100px;
width: 100px;
height: 100%;
word-break: inherit;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 0;
}
.atAddressSect {
margin-top: 8px;
height: 32px;
}
.atAddressSect input {
height: 32px;
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
}
.ataddressinput {
padding: .25rem;
}
.create {
}
.input {
font-size: 10px !important;
}
.noInstancesText {
font-style: italic;
text-align: left;
padding-left: 15px;
}
.pendingTxsText {
font-style: italic;
display: flex;
justify-content: space-evenly;
align-items: center;
flex-wrap: wrap;
}
.item {
margin-right: 1em;
display: flex;
align-items: center;
}
.pendingContainer {
display: flex;
align-items: baseline;
}
.pending {
height: 25px;
text-align: center;
padding-left: 10px;
border-radius: 3px;
margin-left: 5px;
}
.disableMouseEvents {
pointer-events: none;
}
.icon {
cursor: pointer;
font-size: 12px;
cursor: pointer;
margin-left: 5px;
}
.icon:hover {
font-size: 12px;
color: var(--warning);
}
.errorIcon {
color: var(--warning);
margin-left: 15px;
}
.failDesc {
color: var(--warning);
padding-left: 10px;
display: inline;
}
.network {
margin-left: 8px;
pointer-events: none;
}
.networkItem {
margin-right: 5px;
}
.transactionActions {
display: flex;
justify-content: space-evenly;
width: 145px;
}
.orLabel {
text-align: center;
text-transform: uppercase;
}
.infoDeployAction {
margin-left: 1px;
font-size: 13px;
color: var(--info);
}
.gasValueContainer {
flex-direction: row;
display: flex;
}
.gasNval {
width: 55%;
font-size: 0.8rem;
}
.gasNvalUnit {
width: 41%;
margin-left: 10px;
font-size: 0.8rem;
}
.deployDropdown {
text-align: center;
text-transform: uppercase;
}
.checkboxAlign {
padding-top: 2px;
}
`
module.exports = css

@ -8,8 +8,6 @@ import { ViewPlugin } from '@remixproject/engine-web'
import helper from '../../lib/helper' import helper from '../../lib/helper'
import { canUseWorker, urlFromVersion } from '@remix-project/remix-solidity' import { canUseWorker, urlFromVersion } from '@remix-project/remix-solidity'
// var tooltip = require('../ui/tooltip')
var Renderer = require('../ui/renderer')
var { UnitTestRunner, assertLibCode } = require('@remix-project/remix-tests') var { UnitTestRunner, assertLibCode } = require('@remix-project/remix-tests')
const profile = { const profile = {
@ -31,7 +29,6 @@ module.exports = class TestTab extends ViewPlugin {
this.fileManager = fileManager this.fileManager = fileManager
this.filePanel = filePanel this.filePanel = filePanel
this.appManager = appManager this.appManager = appManager
this.renderer = new Renderer(this)
this.testRunner = new UnitTestRunner() this.testRunner = new UnitTestRunner()
this.testTabLogic = new TestTabLogic(this.fileManager, helper) this.testTabLogic = new TestTabLogic(this.fileManager, helper)
this.offsetToLineColumnConverter = offsetToLineColumnConverter this.offsetToLineColumnConverter = offsetToLineColumnConverter

@ -1,31 +1,9 @@
import Registry from '../state/registry' import Registry from '../state/registry'
var remixLib = require('@remix-project/remix-lib') var remixLib = require('@remix-project/remix-lib')
var yo = require('yo-yo')
var EventsDecoder = remixLib.execution.EventsDecoder var EventsDecoder = remixLib.execution.EventsDecoder
const transactionDetailsLinks = {
Main: 'https://www.etherscan.io/tx/',
Rinkeby: 'https://rinkeby.etherscan.io/tx/',
Ropsten: 'https://ropsten.etherscan.io/tx/',
Kovan: 'https://kovan.etherscan.io/tx/',
Goerli: 'https://goerli.etherscan.io/tx/'
}
function txDetailsLink (network, hash) {
if (transactionDetailsLinks[network]) {
return transactionDetailsLinks[network] + hash
}
}
export function makeUdapp (blockchain, compilersArtefacts, logHtmlCallback) { export function makeUdapp (blockchain, compilersArtefacts, logHtmlCallback) {
// ----------------- UniversalDApp -----------------
// TODO: to remove when possible
blockchain.event.register('transactionBroadcasted', (txhash, networkName) => {
var txLink = txDetailsLink(networkName, txhash)
if (txLink && logHtmlCallback) logHtmlCallback(yo`<a href="${txLink}" target="_blank">${txLink}</a>`)
})
// ----------------- Tx listener ----------------- // ----------------- Tx listener -----------------
const _transactionReceipts = {} const _transactionReceipts = {}
const transactionReceiptResolver = (tx, cb) => { const transactionReceiptResolver = (tx, cb) => {

@ -1,24 +1,13 @@
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { RunTabUI } from '@remix-ui/run-tab'
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
const $ = require('jquery')
const yo = require('yo-yo')
const ethJSUtil = require('ethereumjs-util')
const Web3 = require('web3')
const EventManager = require('../../lib/events') const EventManager = require('../../lib/events')
const Card = require('../ui/card')
const css = require('../tabs/styles/run-tab-styles')
const SettingsUI = require('../tabs/runTab/settings.js')
const Recorder = require('../tabs/runTab/model/recorder.js') const Recorder = require('../tabs/runTab/model/recorder.js')
const RecorderUI = require('../tabs/runTab/recorder.js')
const DropdownLogic = require('../tabs/runTab/model/dropdownlogic.js')
const ContractDropdownUI = require('../tabs/runTab/contractDropdown.js')
const toaster = require('../ui/tooltip')
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
const UniversalDAppUI = require('../ui/universal-dapp-ui')
const profile = { const profile = {
name: 'udapp', name: 'udapp',
displayName: 'Deploy & run transactions', displayName: 'Deploy & run transactions',
@ -34,19 +23,25 @@ const profile = {
} }
export class RunTab extends ViewPlugin { export class RunTab extends ViewPlugin {
constructor (blockchain, config, fileManager, editor, filePanel, compilersArtefacts, networkModule, mainView, fileProvider) { constructor (blockchain, config, fileManager, editor, filePanel, compilersArtefacts, networkModule, fileProvider) {
super(profile) super(profile)
this.event = new EventManager() this.event = new EventManager()
this.config = config this.config = config
this.blockchain = blockchain this.blockchain = blockchain
this.fileManager = fileManager this.fileManager = fileManager
this.editor = editor this.editor = editor
this.logCallback = (msg) => { mainView.getTerminal().logHtml(yo`<pre>${msg}</pre>`) }
this.filePanel = filePanel this.filePanel = filePanel
this.compilersArtefacts = compilersArtefacts this.compilersArtefacts = compilersArtefacts
this.networkModule = networkModule this.networkModule = networkModule
this.fileProvider = fileProvider this.fileProvider = fileProvider
this.recorder = new Recorder(blockchain)
this.REACT_API = {}
this.setupEvents() this.setupEvents()
this.el = document.createElement('div')
}
onActivation () {
this.renderComponent()
} }
setupEvents () { setupEvents () {
@ -57,33 +52,19 @@ export class RunTab extends ViewPlugin {
getSettings () { getSettings () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.container) reject(new Error('UI not ready'))
else {
resolve({ resolve({
selectedAccount: this.settingsUI.getSelectedAccount(), selectedAccount: this.REACT_API.accounts.selectedAccount,
selectedEnvMode: this.blockchain.getProvider(), selectedEnvMode: this.REACT_API.selectExEnv,
networkEnvironment: this.container.querySelector('*[data-id="settingsNetworkEnv"]').textContent networkEnvironment: this.REACT_API.networkName
} })
)
}
}) })
} }
async setEnvironmentMode (env) { async setEnvironmentMode (env) {
const canCall = await this.askUserPermission('setEnvironmentMode', 'change the environment used') const canCall = await this.askUserPermission('setEnvironmentMode', 'change the environment used')
if (canCall) { if (canCall) {
toaster(yo` env = typeof env === 'string' ? { context: env } : env
<div> this.emit('setEnvironmentModeReducer', env, this.currentRequest.from)
<i class="fas fa-exclamation-triangle text-danger mr-1"></i>
<span>
${this.currentRequest.from}
<span class="font-weight-bold text-warning">
is changing your environment to
</span> ${env}
</span>
</div>
`, '', { time: 3000 })
this.settingsUI.setExecutionContext(env)
} }
} }
@ -104,183 +85,25 @@ export class RunTab extends ViewPlugin {
return this.blockchain.pendingTransactionsCount() return this.blockchain.pendingTransactionsCount()
} }
renderContainer () { render () {
this.container = yo`<div class="${css.runTabView} run-tab" id="runTabView" data-id="runTabView"></div>` return this.el
var el = yo`
<div class="list-group list-group-flush">
${this.settingsUI.render()}
${this.contractDropdownUI.render()}
${this.recorderCard.render()}
${this.instanceContainer}
</div>
`
this.container.appendChild(el)
return this.container
}
renderInstanceContainer () {
this.instanceContainer = yo`<div class="${css.instanceContainer} border-0 list-group-item"></div>`
const instanceContainerTitle = yo`
<div class="d-flex justify-content-between align-items-center pl-2 ml-1 mb-2"
title="Autogenerated generic user interfaces for interaction with deployed contracts">
Deployed Contracts
<i class="mr-2 ${css.icon} far fa-trash-alt" data-id="deployAndRunClearInstances" onclick=${() => this.event.trigger('clearInstance', [])}
title="Clear instances list and reset recorder" aria-hidden="true">
</i>
</div>`
this.noInstancesText = yo`
<span class="mx-2 mt-3 alert alert-warning" data-id="deployAndRunNoInstanceText" role="alert">
Currently you have no contract instances to interact with.
</span>`
this.event.register('clearInstance', () => {
this.instanceContainer.innerHTML = '' // clear the instances list
this.instanceContainer.appendChild(instanceContainerTitle)
this.instanceContainer.appendChild(this.noInstancesText)
})
this.instanceContainer.appendChild(instanceContainerTitle)
this.instanceContainer.appendChild(this.noInstancesText)
}
renderSettings () {
this.settingsUI = new SettingsUI(this.blockchain, this.networkModule)
this.settingsUI.event.register('clearInstance', () => {
this.event.trigger('clearInstance', [])
})
}
renderDropdown (udappUI, fileManager, compilersArtefacts, config, editor, logCallback) {
const dropdownLogic = new DropdownLogic(compilersArtefacts, config, editor, this)
this.contractDropdownUI = new ContractDropdownUI(this.blockchain, dropdownLogic, logCallback, this)
fileManager.events.on('currentFileChanged', this.contractDropdownUI.changeCurrentFile.bind(this.contractDropdownUI))
this.contractDropdownUI.event.register('clearInstance', () => {
const noInstancesText = this.noInstancesText
if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) }
})
this.contractDropdownUI.event.register('newContractABIAdded', (abi, address) => {
this.instanceContainer.appendChild(udappUI.renderInstanceFromABI(abi, address, '<at address>'))
})
this.contractDropdownUI.event.register('newContractInstanceAdded', (contractObject, address, value) => {
this.instanceContainer.appendChild(udappUI.renderInstance(contractObject, address, value))
})
} }
renderRecorder (udappUI, fileManager, config, logCallback) { renderComponent () {
this.recorderCount = yo`<span>0</span>` ReactDOM.render(
<RunTabUI plugin={this} />
const recorder = new Recorder(this.blockchain) , this.el)
recorder.event.register('recorderCountChange', (count) => {
this.recorderCount.innerText = count
})
this.event.register('clearInstance', recorder.clearAll.bind(recorder))
this.recorderInterface = new RecorderUI(this.blockchain, fileManager, recorder, logCallback, config)
this.recorderInterface.event.register('newScenario', (abi, address, contractName) => {
var noInstancesText = this.noInstancesText
if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) }
this.instanceContainer.appendChild(udappUI.renderInstanceFromABI(abi, address, contractName))
})
this.recorderInterface.render()
} }
renderRecorderCard () { onReady (api) {
const collapsedView = yo` this.REACT_API = api
<div class="d-flex flex-column">
<div class="ml-2 badge badge-pill badge-primary" title="The number of recorded transactions">${this.recorderCount}</div>
</div>`
const expandedView = yo`
<div class="d-flex flex-column">
<div class="${css.recorderDescription} mt-2">
All transactions (deployed contracts and function executions) in this environment can be saved and replayed in
another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3.
</div>
<div class="${css.transactionActions}">
${this.recorderInterface.recordButton}
${this.recorderInterface.runButton}
</div>
</div>
</div>`
this.recorderCard = new Card({}, {}, { title: 'Transactions recorded', collapsedView: collapsedView })
this.recorderCard.event.register('expandCollapseCard', (arrow, body, status) => {
body.innerHTML = ''
status.innerHTML = ''
if (arrow === 'down') {
status.appendChild(collapsedView)
body.appendChild(expandedView)
} else if (arrow === 'up') {
status.appendChild(collapsedView)
}
})
} }
render () { writeFile (fileName, content) {
this.udappUI = new UniversalDAppUI(this.blockchain, this.logCallback) return this.call('fileManager', 'writeFile', fileName, content)
this.blockchain.resetAndInit(this.config, {
getAddress: (cb) => {
cb(null, $('#txorigin').val())
},
getValue: (cb) => {
try {
const number = document.querySelector('#value').value
const select = document.getElementById('unit')
const index = select.selectedIndex
const selectedUnit = select.querySelectorAll('option')[index].dataset.unit
let unit = 'ether' // default
if (['ether', 'finney', 'gwei', 'wei'].indexOf(selectedUnit) >= 0) {
unit = selectedUnit
}
cb(null, Web3.utils.toWei(number, unit))
} catch (e) {
cb(e)
} }
},
getGasLimit: (cb) => {
try {
cb(null, '0x' + new ethJSUtil.BN($('#gasLimit').val(), 10).toString(16))
} catch (e) {
cb(e.message)
}
}
})
this.renderInstanceContainer()
this.renderSettings()
this.renderDropdown(this.udappUI, this.fileManager, this.compilersArtefacts, this.config, this.editor, this.logCallback)
this.renderRecorder(this.udappUI, this.fileManager, this.config, this.logCallback)
this.renderRecorderCard()
const addPluginProvider = (profile) => { readFile (fileName) {
if (profile.kind === 'provider') { return this.call('fileManager', 'readFile', fileName)
((profile, app) => {
const web3Provider = {
async sendAsync (payload, callback) {
try {
const result = await app.call(profile.name, 'sendAsync', payload)
callback(null, result)
} catch (e) {
callback(e)
}
}
}
app.blockchain.addProvider({ name: profile.displayName, provider: web3Provider })
})(profile, this)
}
}
const removePluginProvider = (profile) => {
if (profile.kind === 'provider') this.blockchain.removeProvider(profile.displayName)
}
this.on('manager', 'pluginActivated', addPluginProvider.bind(this))
this.on('manager', 'pluginDeactivated', removePluginProvider.bind(this))
return this.renderContainer()
} }
} }

@ -1,212 +0,0 @@
'use strict'
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var css = csjs`
.li_tv {
list-style-type: none;
-webkit-margin-before: 0px;
-webkit-margin-after: 0px;
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
-webkit-padding-start: 0px;
}
.ul_tv {
list-style-type: none;
-webkit-margin-before: 0px;
-webkit-margin-after: 0px;
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
-webkit-padding-start: 0px;
}
.caret_tv {
width: 10px;
flex-shrink: 0;
padding-right: 5px;
}
.label_item {
word-break: break-all;
}
.label_key {
min-width: max-content;
max-width: 80%;
word-break: break-word;
}
.label_value {
min-width: 10%;
}
.cursor_pointer {
cursor: pointer;
}
`
var EventManager = require('../../lib/events')
/**
* TreeView
* - extendable by specifying custom `extractData` and `formatSelf` function
* - trigger `nodeClick` and `leafClick`
*/
class TreeView {
constructor (opts) {
this.event = new EventManager()
this.extractData = opts.extractData || this.extractDataDefault
this.formatSelf = opts.formatSelf || this.formatSelfDefault
this.loadMore = opts.loadMore
this.view = null
this.expandPath = []
}
render (json, expand) {
var view = this.renderProperties(json, expand)
if (!this.view) {
this.view = view
}
return view
}
update (json) {
if (this.view) {
yo.update(this.view, this.render(json))
}
}
renderObject (item, parent, key, expand, keyPath) {
var data = this.extractData(item, parent, key)
var children = (data.children || []).map((child, index) => {
return this.renderObject(child.value, data, child.key, expand, keyPath + '/' + child.key)
})
return this.formatData(key, data, children, expand, keyPath)
}
renderProperties (json, expand, key) {
key = key || ''
var children = Object.keys(json).map((innerkey) => {
return this.renderObject(json[innerkey], json, innerkey, expand, innerkey)
})
return yo`<ul key=${key} data-id="treeViewUl${key}" class="${css.ul_tv} ml-0 px-2">${children}</ul>`
}
formatData (key, data, children, expand, keyPath) {
var self = this
var li = yo`<li key=${keyPath} data-id="treeViewLi${keyPath}" class=${css.li_tv}></li>`
var caret = yo`<div class="px-1 fas fa-caret-right caret ${css.caret_tv}"></div>`
var label = yo`
<div key=${keyPath} data-id="treeViewDiv${keyPath}" class="d-flex flex-row align-items-center">
${caret}
<span class="w-100">${self.formatSelf(key, data, li)}</span>
</div>`
const expanded = self.expandPath.includes(keyPath)
li.appendChild(label)
if (data.children) {
var list = yo`<ul key=${keyPath} data-id="treeViewUlList${keyPath}" class="pl-2 ${css.ul_tv}">${children}</ul>`
list.style.display = expanded ? 'block' : 'none'
caret.className = list.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}`
caret.setAttribute('data-id', `treeViewToggle${keyPath}`)
label.onclick = function () {
self.expand(keyPath)
if (self.isExpanded(keyPath)) {
if (!self.expandPath.includes(keyPath)) self.expandPath.push(keyPath)
} else {
self.expandPath = self.expandPath.filter(path => !path.startsWith(keyPath))
}
}
label.oncontextmenu = function (event) {
self.event.trigger('nodeRightClick', [keyPath, data, label, event])
}
li.appendChild(list)
if (data.hasNext) {
list.appendChild(yo`<li><span class="w-100 text-primary ${css.cursor_pointer}" data-id="treeViewLoadMore" onclick="${() => self.loadMore(data.cursor)}">Load more</span></li>`)
}
} else {
caret.style.visibility = 'hidden'
label.oncontextmenu = function (event) {
self.event.trigger('leafRightClick', [keyPath, data, label, event])
}
label.onclick = function (event) {
self.event.trigger('leafClick', [keyPath, data, label, event])
}
}
return li
}
isExpanded (path) {
var current = this.nodeAt(path)
if (current) {
return current.style.display !== 'none'
}
return false
}
expand (path) {
var caret = this.caretAt(path)
var node = this.nodeAt(path)
if (node) {
node.style.display = node.style.display === 'none' ? 'block' : 'none'
caret.className = node.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}`
this.event.trigger('nodeClick', [path, node])
}
}
caretAt (path) {
var label = this.labelAt(path)
if (label) {
return label.querySelector('.caret')
}
}
itemAt (path) {
return this.view.querySelector(`li[key="${path}"]`)
}
labelAt (path) {
return this.view.querySelector(`div[key="${path}"]`)
}
nodeAt (path) {
return this.view.querySelector(`ul[key="${path}"]`)
}
updateNodeFromJSON (path, jsonTree, expand) {
var newTree = this.renderProperties(jsonTree, expand, path)
var current = this.nodeAt(path)
if (current && current.parentElement) {
current.parentElement.replaceChild(newTree, current)
}
}
formatSelfDefault (key, data) {
return yo`
<div class="d-flex mt-2 flex-row ${css.label_item}">
<label class="small font-weight-bold pr-1 ${css.label_key}">${key}:</label>
<label class="m-0 ${css.label_value}">${data.self}</label>
</div>
`
}
extractDataDefault (item, parent, key) {
var ret = {}
if (item instanceof Array) {
ret.children = item.map((item, index) => {
return { key: index, value: item }
})
ret.self = 'Array'
ret.isNode = true
ret.isLeaf = false
} else if (item instanceof Object) {
ret.children = Object.keys(item).map((key) => {
return { key: key, value: item[key] }
})
ret.self = 'Object'
ret.isNode = true
ret.isLeaf = false
} else {
ret.self = item
ret.children = null
ret.isNode = false
ret.isLeaf = true
}
return ret
}
}
module.exports = TreeView

@ -1,212 +0,0 @@
var yo = require('yo-yo')
var remixLib = require('@remix-project/remix-lib')
var EventManager = remixLib.EventManager
var Commands = require('../../lib/commands')
// -------------- styling ----------------------
var css = require('./styles/auto-complete-popup-styles')
/* USAGE:
var autoCompletePopup = new AutoCompletePopup({
options: []
})
autoCompletePopup.event.register('handleSelect', function (input) { })
autoCompletePopup.event.register('updateList', function () { })
*/
class AutoCompletePopup {
constructor (opts = {}) {
var self = this
self.event = new EventManager()
self.isOpen = false
self.opts = opts
self.data = {
_options: []
}
self._components = {}
self._view = null
self._startingElement = 0
self._elementsToShow = 4
self._selectedElement = 0
this.extraCommands = []
}
render () {
var self = this
const autoComplete = yo`
<div class="${css.popup} alert alert-secondary">
<div>
${self.data._options.map((item, index) => {
return yo`
<div data-id="autoCompletePopUpAutoCompleteItem" class="${css.autoCompleteItem} ${css.listHandlerHide} item ${self._selectedElement === index ? 'border border-primary' : ''}">
<div value=${index} onclick=${(event) => { self.handleSelect(event.srcElement.innerText) }}>
${getKeyOf(item)}
</div>
<div>
${getValueOf(item)}
</div>
</div>
`
})}
</div>
<div class="${css.listHandlerHide}">
<div class="${css.pageNumberAlignment}">Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(self.data._options.length / self._elementsToShow)}</div>
</div>
</div>
`
function setUpPopUp (autoComplete) {
handleOpenPopup(autoComplete)
handleListSize(autoComplete)
}
function handleOpenPopup (autoComplete) {
autoComplete.style.display = self.data._options.length > 0 ? 'block' : 'none'
}
function handleListSize (autoComplete) {
if (self.data._options.length >= self._startingElement) {
for (let i = self._startingElement; i < (self._elementsToShow + self._startingElement); i++) {
const el = autoComplete.querySelectorAll('.item')[i]
if (el) {
el.classList.remove(css.listHandlerHide)
el.classList.add(css.listHandlerShow)
}
}
}
}
setUpPopUp(autoComplete)
if (!this._view) this._view = autoComplete
return autoComplete
}
handleSelect (text) {
this.removeAutoComplete()
this.event.trigger('handleSelect', [text])
}
moveUp () {
if (this._selectedElement === 0) return
this._selectedElement--
this._startingElement = this._selectedElement > 0 ? this._selectedElement - 1 : 0
this.event.trigger('updateList')
yo.update(this._view, this.render())
}
moveDown () {
if (this.data._options.length <= this._selectedElement + 1) return
this._selectedElement++
this._startingElement = this._selectedElement - 1
this.event.trigger('updateList')
yo.update(this._view, this.render())
}
handleAutoComplete (event, inputString) {
if (this.isOpen && (event.which === 27 || event.which === 8 || event.which === 46)) {
// backspace or any key that should remove the autocompletion
this.removeAutoComplete()
return true
}
if (this.isOpen && (event.which === 13 || event.which === 9)) {
// enter and tab (validate completion)
event.preventDefault()
if (this.data._options[this._selectedElement]) {
this.handleSelect(getKeyOf(this.data._options[this._selectedElement]))
}
this.removeAutoComplete()
return true
}
if (this.isOpen && event.which === 38) {
// move up
event.preventDefault()
this.isOpen = true
this.moveUp()
return true
}
if (this.isOpen && event.which === 40) {
// move down
event.preventDefault()
this.isOpen = true
this.moveDown()
return true
}
if (event.which === 13 || event.which === 9) {
// enter || tab and autocompletion is off, just returning false
return false
}
const textList = inputString.split(' ')
const autoCompleteInput = textList.length > 1 ? textList[textList.length - 1] : textList[0]
if (inputString.length >= 2) {
// more than 2 letters, start completion
this.data._options = []
Commands.allPrograms.forEach(item => {
const program = getKeyOf(item)
if (program.substring(0, program.length - 1).includes(autoCompleteInput.trim())) {
this.data._options.push(item)
} else if (autoCompleteInput.trim().includes(program) || (program === autoCompleteInput.trim())) {
Commands.allCommands.forEach(item => {
const command = getKeyOf(item)
if (command.includes(autoCompleteInput.trim())) {
this.data._options.push(item)
}
})
}
})
this.extraCommands.forEach(item => {
const command = getKeyOf(item)
if (command.includes(autoCompleteInput.trim())) {
this.data._options.push(item)
}
})
if (this.data._options.length === 1 && event.which === 9) {
// if only one option and tab is pressed, we resolve it
event.preventDefault()
textList.pop()
textList.push(getKeyOf(this.data._options[0]))
this.handleSelect(`${textList}`.replace(/,/g, ' '))
this.removeAutoComplete()
return
}
if (this.data._options.length) this.isOpen = true
yo.update(this._view, this.render())
return true
}
return false
}
removeAutoComplete () {
if (!this.isOpen) return
this._view.style.display = 'none'
this.isOpen = false
this.data._options = []
this._startingElement = 0
this._selectedElement = 0
yo.update(this._view, this.render())
}
extendAutocompletion () {
// TODO: this is not using the appManager interface. Terminal should be put as module
this.opts.appManager.event.on('activate', async (profile) => {
if (!profile.methods) return
profile.methods.forEach((method) => {
const key = `remix.call('${profile.name}', '${method}')`
const keyValue = {}
keyValue[key] = `call ${profile.name} - ${method}`
if (this.extraCommands.includes(keyValue)) return
this.extraCommands.push(keyValue)
})
})
}
}
function getKeyOf (item) {
return Object.keys(item)[0]
}
function getValueOf (item) {
return Object.values(item)[0]
}
module.exports = AutoCompletePopup

@ -1,70 +0,0 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var EventManager = require('../../lib/events')
module.exports = class Card {
constructor (api, events, opts) {
const self = this
self._api = api
self._events = events
self._opts = opts
self._view = {}
self.event = new EventManager()
}
render () {
const self = this
if (self._view.el) return self._view.el
self._view.cardBody = yo`<div></div>`
self._view.arrow = yo`<i class="${css.arrow} fas fa-angle-down" onclick="${() => trigger(this)}"></i>`
self._view.expandCollapseButton = yo`
<div>${self._view.arrow}</div>`
self._view.statusBar = yo`<div>${self._opts.collapsedView}</div>`
self._view.cardHeader = yo`
<div class="d-flex justify-content-between align-items-center" onclick=${() => trigger(self._view.arrow)}>
<div class="pr-1 d-flex flex-row">
<div>${self._opts.title}</div>
${self._view.statusBar}
</div>
<div>${self._view.expandCollapseButton}</div>
</div>`
function trigger (el) {
var body = self._view.cardBody
var status = self._view.statusBar
if (el.classList) {
el.classList.toggle('fa-angle-up')
var arrow = el.classList.toggle('fa-angle-down') ? 'up' : 'down'
self.event.trigger('expandCollapseCard', [arrow, body, status])
}
}
// HTML
self._view.el = yo`
<div class="${css.cardContainer} list-group-item border-0">
${self._view.cardHeader}
${self._view.cardBody}
</div>`
return self._view.el
}
}
const css = csjs`
.cardContainer {
padding : 0 24px 16px;
margin : 0;
background : none;
}
.arrow {
font-weight : bold;
cursor : pointer;
font-size : 14px;
}
.arrow:hover {
}
`

@ -1,142 +0,0 @@
var yo = require('yo-yo')
var csjs = require('csjs-inject')
const copyToClipboard = require('./copy-to-clipboard')
const Web3 = require('web3')
var css = csjs`
#confirmsetting {
z-index: 1;
}
.txInfoBox {
}
.wrapword {
white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
`
function confirmDialog (tx, network, amount, gasEstimation, newGasPriceCb, initialParamsCb) {
const onGasPriceChange = function () {
var gasPrice = el.querySelector('#gasprice').value
newGasPriceCb(gasPrice, (txFeeText, priceStatus) => {
el.querySelector('#txfee').innerHTML = txFeeText
el.gasPriceStatus = priceStatus
el.txFee = { gasPrice }
})
}
const onMaxFeeChange = function () {
var maxFee = el.querySelector('#maxfee').value
var confirmBtn = document.querySelector('#modal-footer-ok')
var maxPriorityFee = el.querySelector('#maxpriorityfee').value
if (parseInt(network.lastBlock.baseFeePerGas, 16) > Web3.utils.toWei(maxFee, 'Gwei')) {
el.querySelector('#txfee').innerHTML = 'Transaction is invalid. Max fee should not be less than Base fee'
el.gasPriceStatus = false
confirmBtn.hidden = true
return
} else {
el.gasPriceStatus = true
confirmBtn.hidden = false
}
newGasPriceCb(maxFee, (txFeeText, priceStatus) => {
el.querySelector('#txfee').innerHTML = txFeeText
if (priceStatus) {
confirmBtn.hidden = false
} else {
confirmBtn.hidden = true
}
el.gasPriceStatus = priceStatus
el.txFee = { maxFee, maxPriorityFee, baseFeePerGas: network.lastBlock.baseFeePerGas }
})
}
const el = yo`
<div>
<div class="text-dark">You are about to create a transaction on ${network.name} Network. Confirm the details to send the info to your provider.
<br>The provider for many users is MetaMask. The provider will ask you to sign the transaction before it is sent to ${network.name} Network.
</div>
<div class="mt-3 ${css.txInfoBox}">
<div>
<span class="text-dark mr-2">From:</span>
<span>${tx.from}</span>
</div>
<div>
<span class="text-dark mr-2">To:</span>
<span>${tx.to ? tx.to : '(Contract Creation)'}</span>
</div>
<div class="d-flex align-items-center">
<span class="text-dark mr-2">Data:</span>
<pre class="${css.wrapword} mb-0">${tx.data && tx.data.length > 50 ? tx.data.substring(0, 49) + '...' : tx.data} ${copyToClipboard(() => { return tx.data })}</pre>
</div>
<div class="mb-3">
<span class="text-dark mr-2">Amount:</span>
<span>${amount} Ether</span>
</div>
<div>
<span class="text-dark mr-2">Gas estimation:</span>
<span>${gasEstimation}</span>
</div>
<div>
<span class="text-dark mr-2">Gas limit:</span>
<span>${tx.gas}</span>
</div>
${
network.lastBlock.baseFeePerGas ? yo`
<div class="align-items-center my-1" title="Represents the part of the tx fee that goes to the miner.">
<div class='d-flex'>
<span class="text-dark mr-2 text-nowrap">Max Priority fee:</span>
<input class="form-control mr-1 text-right" style='height: 1.2rem; width: 6rem;' value="1" id='maxpriorityfee' />
<span title="visit https://ethgasstation.info for current gas price info.">Gwei</span>
</div>
</div>
<div class="align-items-center my-1" title="Represents the maximum amount of fee that you will pay for this transaction. The minimun needs to be set to base fee.">
<div class='d-flex'>
<span class="text-dark mr-2 text-nowrap">Max fee (Not less than base fee ${Web3.utils.fromWei(Web3.utils.toBN(parseInt(network.lastBlock.baseFeePerGas, 16)), 'Gwei')} Gwei):</span>
<input class="form-control mr-1 text-right" style='height: 1.2rem; width: 6rem;' id='maxfee' oninput=${onMaxFeeChange} />
<span>Gwei</span>
<span class="text-dark ml-2"></span>
</div>
</div>`
: yo`<div class="d-flex align-items-center my-1">
<span class="text-dark mr-2 text-nowrap">Gas price:</span>
<input class="form-control mr-1 text-right" style='width: 40px; height: 28px;' id='gasprice' oninput=${onGasPriceChange} />
<span>Gwei (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> for current gas price info.)</span>
</div>`
}
<div class="mb-3">
<span class="text-dark mr-2">Max transaction fee:</span>
<span class="text-warning" id='txfee'></span>
</div>
</div>
<div class="d-flex py-1 align-items-center custom-control custom-checkbox ${css.checkbox}">
<input class="form-check-input custom-control-input" id="confirmsetting" type="checkbox">
<label class="m-0 form-check-label custom-control-label" for="confirmsetting">Do not show this warning again.</label>
</div>
</div>
`
initialParamsCb((txFeeText, gasPriceValue, gasPriceStatus) => {
if (txFeeText) {
el.querySelector('#txfee').innerHTML = txFeeText
}
if (el.querySelector('#gasprice') && gasPriceValue) {
el.querySelector('#gasprice').value = gasPriceValue
onGasPriceChange()
}
if (el.querySelector('#maxfee') && network && network.lastBlock && network.lastBlock.baseFeePerGas) {
el.querySelector('#maxfee').value = Web3.utils.fromWei(Web3.utils.toBN(parseInt(network.lastBlock.baseFeePerGas, 16)), 'Gwei')
onMaxFeeChange()
}
if (gasPriceStatus !== undefined) {
el.gasPriceStatus = gasPriceStatus
}
})
return el
}
module.exports = confirmDialog

@ -1,83 +0,0 @@
var yo = require('yo-yo')
// -------------- copyToClipboard ----------------------
var csjs = require('csjs-inject')
var css = csjs`
.container
{
display: none;
position: fixed;
border-radius: 2px;
z-index: 1000;
box-shadow: 0 0 4px var(--dark);
}
.liitem
{
padding: 2px;
padding-left: 6px;
cursor: pointer;
color: var(--text-dark);
background-color: var(--light);
}
.liitem:hover
{
background-color: var(--secondary);
}
#menuitems
{
list-style: none;
margin: 0px;
}
`
module.exports = (event, items, linkItems) => {
event.preventDefault()
function hide (event, force) {
if (container && container.parentElement && (force || (event.target !== container))) {
container.parentElement.removeChild(container)
}
window.removeEventListener('click', hide)
}
const menu = Object.keys(items).map((item, index) => {
const current = yo`<li id="menuitem${item.toLowerCase()}" class=${css.liitem}>${item}</li>`
current.onclick = () => { hide(null, true); items[item]() }
return current
})
let menuForLinks = yo``
if (linkItems) {
menuForLinks = Object.keys(linkItems).map((item, index) => {
const current = yo`<li id="menuitem${item.toLowerCase()}" class=${css.liitem}><a href=${linkItems[item]} target="_blank">${item}</a></li>`
current.onclick = () => { hide(null, true) }
return current
})
}
const container = yo`
<div id="menuItemsContainer" class="p-1 ${css.container} bg-light shadow border">
<ul id='menuitems'>${menu} ${menuForLinks}</ul>
</div>
`
container.style.left = event.pageX + 'px'
container.style.top = event.pageY + 'px'
container.style.display = 'block'
document.querySelector('body').appendChild(container)
const menuItemsContainer = document.getElementById('menuItemsContainer')
const boundary = menuItemsContainer.getBoundingClientRect()
if (boundary.bottom > (window.innerHeight || document.documentElement.clientHeight)) {
menuItemsContainer.style.position = 'absolute'
menuItemsContainer.style.bottom = '10px'
menuItemsContainer.style.top = null
}
setTimeout(() => {
window.addEventListener('click', hide)
}, 500)
return { hide }
}

@ -1,39 +0,0 @@
var yo = require('yo-yo')
// -------------- copyToClipboard ----------------------
const copy = require('copy-to-clipboard')
var addTooltip = require('./tooltip')
// -------------- styling ----------------------
var csjs = require('csjs-inject')
var css = csjs`
.copyIcon {
margin-left: 5px;
cursor: pointer;
}
`
module.exports = function copyToClipboard (getContent, tip = 'Copy value to clipboard', icon = 'fa-copy') {
var copyIcon = yo`<i title="${tip}" class="${css.copyIcon} far ${icon} p-2" data-id="copyToClipboardCopyIcon" aria-hidden="true"></i>`
copyIcon.onclick = (event) => {
event.stopPropagation()
var copiableContent
try {
copiableContent = getContent()
} catch (e) {
addTooltip(e.message)
return
}
if (copiableContent) { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory
try {
if (typeof copiableContent !== 'string') {
copiableContent = JSON.stringify(copiableContent, null, '\t')
}
} catch (e) {}
copy(copiableContent)
addTooltip('Copied value to clipboard.')
} else {
addTooltip('Cannot copy empty content!')
}
}
return copyIcon
}

@ -1,131 +0,0 @@
'use strict'
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var css = csjs`
.containerDraggableModal {
position: absolute;
z-index: 1000;
text-align: center;
width: 500px;
height: 500px;
overflow-y: hidden;
}
.headerDraggableModal {
cursor: move;
z-index: 10;
text-overflow: ellipsis;
overflow-x: hidden;
}
.modalActions {
float: right;
}
.modalAction {
padding-right: 1px;
padding-left: 1px;
cursor: pointer;
}
`
module.exports =
class DraggableContent {
constructor (closeCb) {
this.closeCb = closeCb
this.isMaximised = false
}
render (title, url, content) {
this.content = content
var el = yo`
<div class=${css.containerDraggableModal}>
<div>
<div class="${css.headerDraggableModal} title" title=${title}><span title="${title}" >${title}</span><span title="${url}" > - ${url}</span>
<div class=${css.modalActions}>
<i onclick=${() => { this.minimize() }} class="fas fa-window-minimize ${css.modalAction}"></i>
<i onclick=${() => { this.maximise() }} class="fas fa-window-maximize ${css.modalAction}"></i>
<i onclick=${() => { this.close() }} class="fas fa-window-close-o ${css.modalAction}"></i>
</div>
</div>
</div>
${content}
</div> `
dragElement(el)
el.style.top = '20%'
el.style.left = '50%'
this.el = el
return el
}
setTitle (title) {
this.el.querySelector('.title span').innerHTML = title
}
minimize () {
this.isMaximised = false
this.content.style.display = 'none'
this.el.style.height = 'inherit'
this.el.style.width = '150px'
this.el.querySelector('.title').style.width = '146px'
}
maximise () {
this.content.style.display = 'block'
var body = document.querySelector('body')
this.el.style.height = this.isMaximised ? '500px' : body.offsetHeight + 'px'
this.el.style.width = this.isMaximised ? '500px' : body.offsetWidth + 'px'
this.isMaximised = !this.isMaximised
this.el.style.top = this.isMaximised ? '0%' : '20%'
this.el.style.left = this.isMaximised ? '0%' : '50%'
this.el.querySelector('.title').style.width = 'inherit'
}
close () {
if (this.closeCb) this.closeCb()
if (this.el.parentElement) {
this.el.parentElement.removeChild(this.el)
}
}
}
function dragElement (elmnt) {
var pos1 = 0
var pos2 = 0
var pos3 = 0
var pos4 = 0
elmnt.querySelector('.title').onmousedown = dragMouseDown
function dragMouseDown (e) {
e = e || window.event
if (e.button !== 0) return
e.preventDefault()
// get the mouse cursor position at startup:
pos3 = e.clientX
pos4 = e.clientY
document.onmouseup = closeDragElement
// call a function whenever the cursor moves:
document.onmousemove = elementDrag
}
function elementDrag (e) {
e = e || window.event
e.preventDefault()
// calculate the new cursor position:
pos1 = pos3 - e.clientX
pos2 = pos4 - e.clientY
pos3 = e.clientX
pos4 = e.clientY
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + 'px'
elmnt.style.left = (elmnt.offsetLeft - pos1) + 'px'
}
function closeDragElement () {
/* stop moving when mouse button is released: */
document.onmouseup = null
document.onmousemove = null
}
}

@ -1,128 +0,0 @@
var yo = require('yo-yo')
var EventManager = require('../../lib/events')
// -------------- styling ----------------------
var css = require('./styles/dropdown-styles')
/* USAGE:
var dropdown = new Dropdown({
options: [
'knownTransaction',
'unknownTransaction',
'log',
'info',
'error'
],
defaults: ['knownTransaction']
})
dropdown.event.register('deselect', function (label) { })
dropdown.event.register('select', function (label) { })
*/
class Dropdown {
constructor (opts = {}) {
var self = this
self.event = new EventManager()
self.data = {
_options: opts.options || [],
_dependencies: opts.dependencies || [],
selected: opts.defaults || [],
_elements: []
}
self._view = {}
self._api = opts.api
self._events = opts.events
}
render () {
var self = this
if (self._view.el) return self._view.el
self._view.selected = yo`
<div class=${css.selectbox}>
<span class=${css.selected}> [${self.data.selected.length}] ${self.data.selected.join(', ')}</span>
<i class="${css.icon} fas fa-caret-down"></i>
</div>
`
self._view.el = yo`
<div name="dropdown" class="${css.dropdown} form-control form-control-sm" onclick=${show}>
${self._view.selected}
<div class="${css.options} bg-light" style="display: none;}">
${self.data._options.map(label => {
const index = self.data._elements.length
var input = yo`
<input
data-idx=${index}
onchange=${emit}
type="${index === 2 ? 'checkbox' : 'radio'}"
id="${label}"
/>
`
if (self.data.selected.indexOf(label) !== -1) {
input.checked = true
self.event.trigger('select', [label])
}
self.data._elements.push(input)
return yo`
<div class=${css.option}>
${input}
<label class="text-dark" for="${label}">${label}</label>
</div>
`
})}
</div>
</div>
`
return self._view.el
function emit (event) {
var input = event.currentTarget
var label = input.nextSibling.innerText
if (input.checked) {
self.data.selected.push(label)
if (event.type === 'change') {
self.event.trigger('select', [label])
updateDependencies(label)
}
} else {
var idx = self.data.selected.indexOf(label)
self.data.selected.splice(idx, 1)
if (event.type === 'change') {
self.event.trigger('deselect', [label])
updateDependencies(label)
}
}
self._view.selected.children[0].innerText = `[${self.data.selected.length}] ${self.data.selected.join(', ')}`
}
function updateDependencies (changed) {
if (self.data._dependencies[changed]) {
for (var dep in self.data._dependencies[changed]) {
var label = self.data._dependencies[changed][dep]
var el = self.data._elements[self.data._options.indexOf(label)]
el.checked = !el.checked
emit({ currentTarget: el, type: 'changeDependencies' })
}
}
}
function show (event) {
event.stopPropagation()
var options = event.currentTarget.children[1]
var parent = event.target.parentElement
var isOption = parent === options || parent.parentElement === options
if (isOption) return
if (options.style.display === 'none') {
options.style.display = ''
document.body.addEventListener('click', handler)
} else {
options.style.display = 'none'
document.body.removeEventListener('click', handler)
}
function handler (event) {
options.style.display = 'none'
document.body.removeEventListener('click', handler)
}
}
}
}
module.exports = Dropdown

@ -5,8 +5,6 @@ import * as packageJson from '../../../../../../package.json'
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import { RemixUiHomeTab } from '@remix-ui/home-tab' // eslint-disable-line import { RemixUiHomeTab } from '@remix-ui/home-tab' // eslint-disable-line
const GistHandler = require('../../../lib/gist-handler')
const profile = { const profile = {
name: 'home', name: 'home',
displayName: 'Home', displayName: 'Home',
@ -26,7 +24,6 @@ export class LandingPage extends ViewPlugin {
this.contentImport = contentImport this.contentImport = contentImport
this.appManager = appManager this.appManager = appManager
this.verticalIcons = verticalIcons this.verticalIcons = verticalIcons
this.gistHandler = new GistHandler()
this.el = document.createElement('div') this.el = document.createElement('div')
this.el.setAttribute('id', 'landingPageHomeContainer') this.el.setAttribute('id', 'landingPageHomeContainer')
this.el.setAttribute('class', 'remixui_homeContainer justify-content-between bg-light d-flex') this.el.setAttribute('class', 'remixui_homeContainer justify-content-between bg-light d-flex')

@ -1,228 +0,0 @@
'use strict'
var yo = require('yo-yo')
var css = require('../../universal-dapp-styles')
var copyToClipboard = require('./copy-to-clipboard')
var remixLib = require('@remix-project/remix-lib')
var txFormat = remixLib.execution.txFormat
class MultiParamManager {
/**
*
* @param {bool} lookupOnly
* @param {Object} funABI
* @param {Function} clickMultiCallBack
* @param {string} inputs
* @param {string} title
* @param {string} evmBC
*
*/
constructor (lookupOnly, funABI, clickCallBack, inputs, title, evmBC, isDeploy) {
this.lookupOnly = lookupOnly
this.funABI = funABI
this.clickCallBack = clickCallBack
this.inputs = inputs
this.title = title
this.evmBC = evmBC
this.basicInputField = null
this.multiFields = null
this.isDeploy = isDeploy
}
switchMethodViewOn () {
this.contractActionsContainerSingle.style.display = 'none'
this.contractActionsContainerMulti.style.display = 'flex'
this.makeMultiVal()
}
switchMethodViewOff () {
this.contractActionsContainerSingle.style.display = 'flex'
this.contractActionsContainerMulti.style.display = 'none'
var multiValString = this.getMultiValsString()
if (multiValString) this.basicInputField.value = multiValString
}
getValue (item, index) {
var valStr = item.value.join('')
return valStr
}
getMultiValsString () {
var valArray = this.multiFields.querySelectorAll('input')
var ret = ''
var valArrayTest = []
for (var j = 0; j < valArray.length; j++) {
if (ret !== '') ret += ','
var elVal = valArray[j].value
valArrayTest.push(elVal)
elVal = elVal.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number
elVal = elVal.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string
try {
JSON.parse(elVal)
} catch (e) {
elVal = '"' + elVal + '"'
}
ret += elVal
}
var valStringTest = valArrayTest.join('')
if (valStringTest) {
return ret
} else {
return ''
}
}
emptyInputs () {
var valArray = this.multiFields.querySelectorAll('input')
for (var k = 0; k < valArray.length; k++) {
valArray[k].value = ''
}
this.basicInputField.value = ''
}
makeMultiVal () {
var inputString = this.basicInputField.value
if (inputString) {
inputString = inputString.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number
inputString = inputString.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string
var inputJSON = JSON.parse('[' + inputString + ']')
var multiInputs = this.multiFields.querySelectorAll('input')
for (var k = 0; k < multiInputs.length; k++) {
if (inputJSON[k]) {
multiInputs[k].value = JSON.stringify(inputJSON[k])
}
}
}
}
createMultiFields () {
if (this.funABI.inputs) {
return yo`<div>
${this.funABI.inputs.map(function (inp) {
return yo`<div class="${css.multiArg}"><label for="${inp.name}"> ${inp.name}: </label><input class="form-control" placeholder="${inp.type}" title="${inp.name}" data-id="multiParamManagerInput${inp.name}"></div>`
})}
</div>`
}
}
render () {
var title
if (this.title) {
title = this.title
} else if (this.funABI.name) {
title = this.funABI.name
} else {
title = this.funABI.type === 'receive' ? '(receive)' : '(fallback)'
}
this.basicInputField = yo`<input class="form-control" data-id="multiParamManagerBasicInputField"></input>`
this.basicInputField.setAttribute('placeholder', this.inputs)
this.basicInputField.setAttribute('title', this.inputs)
this.basicInputField.setAttribute('data-id', this.inputs)
var onClick = () => {
this.clickCallBack(this.funABI.inputs, this.basicInputField.value)
}
const width = this.isDeploy ? '' : 'w-50'
const funcButton = yo`<button onclick=${() => onClick()} class="${css.instanceButton} ${width} btn btn-sm" data-id="multiParamManagerFuncButton">${title}</button>`
this.contractActionsContainerSingle = yo`
<div class="${css.contractActionsContainerSingle} pt-2">
${funcButton}
${this.basicInputField}
<i class="fas fa-angle-down ${css.methCaret}" onclick=${() => this.switchMethodViewOn()} title=${title} ></i>
</div>`
this.multiFields = this.createMultiFields()
var multiOnClick = () => {
var valsString = this.getMultiValsString()
if (valsString) {
this.clickCallBack(this.funABI.inputs, valsString)
} else {
this.clickCallBack(this.funABI.inputs, '')
}
}
var expandedButton = yo`<button onclick=${() => { multiOnClick() }} class="${css.instanceButton}" data-id="multiParamManagerExpandedButton"></button>`
this.contractActionsContainerMulti = yo`<div class="${css.contractActionsContainerMulti}" >
<div class="${css.contractActionsContainerMultiInner} text-dark" >
<div onclick=${() => { this.switchMethodViewOff() }} class="${css.multiHeader}">
<div class="${css.multiTitle} run-instance-multi-title">${title}</div>
<i class='fas fa-angle-up ${css.methCaret}'></i>
</div>
${this.multiFields}
<div class="${css.group} ${css.multiArg}" >
${copyToClipboard(
() => {
var multiString = this.getMultiValsString()
var multiJSON = JSON.parse('[' + multiString + ']')
var encodeObj
if (this.evmBC) {
encodeObj = txFormat.encodeData(this.funABI, multiJSON, this.evmBC)
} else {
encodeObj = txFormat.encodeData(this.funABI, multiJSON)
}
if (encodeObj.error) {
throw new Error(encodeObj.error)
} else {
return encodeObj.data
}
}, 'Encode values of input fields & copy to clipboard', 'fa-clipboard')}
${expandedButton}
</div>
</div>
</div>`
var contractProperty = yo`
<div class="${css.contractProperty}">
${this.contractActionsContainerSingle} ${this.contractActionsContainerMulti}
</div>
`
if (this.lookupOnly) {
// call. stateMutability is either pure or view
expandedButton.setAttribute('title', (title + ' - call'))
expandedButton.innerHTML = 'call'
expandedButton.classList.add('btn-info')
expandedButton.setAttribute('data-id', (title + ' - call'))
funcButton.setAttribute('title', (title + ' - call'))
funcButton.classList.add('btn-info')
funcButton.setAttribute('data-id', (title + ' - call'))
} else if (this.funABI.stateMutability === 'payable' || this.funABI.payable) {
// transact. stateMutability = payable
expandedButton.setAttribute('title', (title + ' - transact (payable)'))
expandedButton.innerHTML = 'transact'
expandedButton.classList.add('btn-danger')
expandedButton.setAttribute('data-id', (title + ' - transact (payable)'))
funcButton.setAttribute('title', (title + ' - transact (payable)'))
funcButton.classList.add('btn-danger')
funcButton.setAttribute('data-id', (title + ' - transact (payable)'))
} else {
// transact. stateMutability = nonpayable
expandedButton.setAttribute('title', (title + ' - transact (not payable)'))
expandedButton.innerHTML = 'transact'
expandedButton.classList.add('btn-warning')
expandedButton.setAttribute('data-id', (title + ' - transact (not payable)'))
funcButton.classList.add('btn-warning')
funcButton.setAttribute('title', (title + ' - transact (not payable)'))
funcButton.setAttribute('data-id', (title + ' - transact (not payable)'))
}
if (this.funABI.inputs && this.funABI.inputs.length > 0) {
contractProperty.classList.add(css.hasArgs)
} else if (this.funABI.type === 'fallback' || this.funABI.type === 'receive') {
contractProperty.classList.add(css.hasArgs)
this.basicInputField.setAttribute('title', `'(${this.funABI.type}')`) // probably should pass name instead
this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden'
this.basicInputField.setAttribute('data-id', `'(${this.funABI.type}')`)
} else {
this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden'
this.basicInputField.style.visibility = 'hidden'
}
return contractProperty
}
}
module.exports = MultiParamManager

@ -1,153 +0,0 @@
'use strict'
var $ = require('jquery')
var yo = require('yo-yo')
const { default: Registry } = require('../state/registry')
var css = require('./styles/renderer-styles')
/**
* After refactor, the renderer is only used to render error/warning
* TODO: This don't need to be an object anymore. Simplify and just export the renderError function.
*
*/
function Renderer (service) {
const self = this
self.service = service
self._components = {}
self._components.registry = Registry.getInstance()
// dependencies
self._deps = {
fileManager: self._components.registry.get('filemanager').api,
config: self._components.registry.get('config').api
}
if (document && document.head) {
document.head.appendChild(css)
}
}
Renderer.prototype._error = function (file, error) {
const self = this
if (file === self._deps.config.get('currentFile')) {
self.service.call('editor', 'addAnnotation', error, file)
}
}
Renderer.prototype._errorClick = function (errFile, errLine, errCol) {
const self = this
const editor = self._components.registry.get('editor').api
if (errFile !== self._deps.config.get('currentFile')) {
// TODO: refactor with this._components.contextView.jumpTo
var provider = self._deps.fileManager.fileProviderOf(errFile)
if (provider) {
provider.exists(errFile).then(exist => {
self._deps.fileManager.open(errFile)
editor.gotoLine(errLine, errCol)
}).catch(error => {
if (error) return console.log(error)
})
}
} else {
editor.gotoLine(errLine, errCol)
}
}
function getPositionDetails (msg) {
const result = {}
// To handle some compiler warning without location like SPDX license warning etc
if (!msg.includes(':')) return { errLine: -1, errCol: -1, errFile: msg }
// extract line / column
let position = msg.match(/^(.*?):([0-9]*?):([0-9]*?)?/)
result.errLine = position ? parseInt(position[2]) - 1 : -1
result.errCol = position ? parseInt(position[3]) : -1
// extract file
position = msg.match(/^(https:.*?|http:.*?|.*?):/)
result.errFile = position ? position[1] : ''
return result
}
/**
* format msg like error or warning,
*
* @param {String or DOMElement} message
* @param {DOMElement} container
* @param {Object} options {
* useSpan,
* noAnnotations,
* click:(Function),
* type:(
* warning,
* error
* ),
* errFile,
* errLine,
* errCol
* }
*/
Renderer.prototype.error = function (message, container, opt) {
if (!message) return
if (container === undefined) return
opt = opt || {}
var text
if (typeof message === 'string') {
text = message
message = yo`<span>${message}</span>`
} else if (message.innerText) {
text = message.innerText
}
// ^ e.g:
// browser/gm.sol: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.6.12
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0/contracts/introspection/IERC1820Registry.sol:3:1: ParserError: Source file requires different compiler version (current compiler is 0.7.4+commit.3f05b770.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version
let position = getPositionDetails(text)
// For compiler version 0.8.0 and upcoming versions, errors and warning will be reported in a different way
// Above method regex will return type of error as 'errFile'
// Comparison of 'errFile' with passed error type will ensure the reporter type
if (!position.errFile || (opt.errorType && opt.errorType === position.errFile)) {
// Updated error reported includes '-->' before file details
const errorDetails = text.split('-->')
// errorDetails[1] will have file details
if (errorDetails.length > 1) position = getPositionDetails(errorDetails[1])
}
opt.errLine = position.errLine
opt.errCol = position.errCol
opt.errFile = position.errFile.trim()
if (!opt.noAnnotations && opt.errFile) {
this._error(opt.errFile, {
row: opt.errLine,
column: opt.errCol,
text: text,
type: opt.type
})
}
var $pre = $(opt.useSpan ? yo`<span></span>` : yo`<pre></pre>`).html(message)
const classList = opt.type === 'error' ? 'alert alert-danger' : 'alert alert-warning'
var $error = $(yo`<div class="sol ${opt.type} ${classList}" data-id="${opt.errFile}"><div class="close" data-id="renderer"><i class="fas fa-times"></i></div></div>`).prepend($pre)
$(container).append($error)
$error.click((ev) => {
if (opt.click) {
opt.click(message)
} else if (opt.errFile !== undefined && opt.errLine !== undefined && opt.errCol !== undefined) {
this._errorClick(opt.errFile, opt.errLine, opt.errCol)
}
})
$error.find('.close').click(function (ev) {
ev.preventDefault()
$error.remove()
return false
})
}
module.exports = Renderer

@ -1,109 +0,0 @@
const yo = require('yo-yo')
const remixLib = require('@remix-project/remix-lib')
const confirmDialog = require('./confirmDialog')
const modalCustom = require('./modal-dialog-custom')
const modalDialog = require('./modaldialog')
const typeConversion = remixLib.execution.typeConversion
const Web3 = require('web3')
module.exports = {
getCallBacksWithContext: (udappUI, blockchain) => {
const callbacks = {}
callbacks.confirmationCb = confirmationCb
callbacks.continueCb = continueCb
callbacks.promptCb = promptCb
callbacks.udappUI = udappUI
callbacks.blockchain = blockchain
return callbacks
}
}
const continueCb = function (error, continueTxExecution, cancelCb) {
if (error) {
const msg = typeof error !== 'string' ? error.message : error
modalDialog(
'Gas estimation failed',
yo`
<div>Gas estimation errored with the following message (see below).
The transaction execution will likely fail. Do you want to force sending? <br>${msg}</div>
`,
{
label: 'Send Transaction',
fn: () => continueTxExecution()
},
{
label: 'Cancel Transaction',
fn: () => cancelCb()
}
)
} else {
continueTxExecution()
}
}
const promptCb = function (okCb, cancelCb) {
modalCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
}
const confirmationCb = function (network, tx, gasEstimation, continueTxExecution, cancelCb) {
const self = this
if (network.name !== 'Main') {
return continueTxExecution(null)
}
var amount = Web3.utils.fromWei(typeConversion.toInt(tx.value), 'ether')
var content = confirmDialog(tx, network, amount, gasEstimation,
(gasPrice, cb) => {
let txFeeText, priceStatus
// TODO: this try catch feels like an anti pattern, can/should be
// removed, but for now keeping the original logic
try {
var fee = Web3.utils.toBN(tx.gas).mul(Web3.utils.toBN(Web3.utils.toWei(gasPrice.toString(10), 'gwei')))
txFeeText = ' ' + Web3.utils.fromWei(fee.toString(10), 'ether') + ' Ether'
priceStatus = true
} catch (e) {
txFeeText = ' Please fix this issue before sending any transaction. ' + e.message
priceStatus = false
}
cb(txFeeText, priceStatus)
},
(cb) => {
self.blockchain.web3().eth.getGasPrice((error, gasPrice) => {
const warnMessage = ' Please fix this issue before sending any transaction. '
if (error) {
return cb('Unable to retrieve the current network gas price.' + warnMessage + error)
}
try {
var gasPriceValue = Web3.utils.fromWei(gasPrice.toString(10), 'gwei')
cb(null, gasPriceValue)
} catch (e) {
cb(warnMessage + e.message, null, false)
}
})
}
)
modalDialog(
'Confirm transaction',
content,
{
label: 'Confirm',
fn: () => {
self.blockchain.config.setUnpersistedProperty(
'doNotShowTransactionConfirmationAgain',
content.querySelector('input#confirmsetting').checked
)
// TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) {
cancelCb('Given transaction fee is not correct')
} else {
continueTxExecution(content.txFee)
}
}
},
{
label: 'Cancel',
fn: () => {
return cancelCb('Transaction canceled by user.')
}
}
)
}

@ -1,52 +0,0 @@
var yo = require('yo-yo')
var css = yo`<style>
.sol.success,
.sol.error,
.sol.warning {
white-space: pre-line;
word-wrap: break-word;
cursor: pointer;
position: relative;
margin: 0.5em 0 1em 0;
border-radius: 5px;
line-height: 20px;
padding: 8px 15px;
}
.sol.success pre,
.sol.error pre,
.sol.warning pre {
white-space: pre-line;
overflow-y: hidden;
background-color: transparent;
margin: 0;
font-size: 12px;
border: 0 none;
padding: 0;
border-radius: 0;
}
.sol.success .close,
.sol.error .close,
.sol.warning .close {
white-space: pre-line;
font-weight: bold;
position: absolute;
color: hsl(0, 0%, 0%); /* black in style-guide.js */
top: 0;
right: 0;
padding: 0.5em;
}
.sol.error {
}
.sol.warning {
}
.sol.success {
/* background-color: // styles.rightPanel.message_Success_BackgroundColor; */
}</style>`
module.exports = css

@ -1,8 +0,0 @@
import yo from 'yo-yo'
export function basicLogo () {
return yo`<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z"/>
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z"/>
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z"/>
</svg>`
}

@ -1,590 +0,0 @@
'use strict'
var yo = require('yo-yo')
var copyToClipboard = require('./copy-to-clipboard')
// -------------- styling ----------------------
var csjs = require('csjs-inject')
var remixLib = require('@remix-project/remix-lib')
var EventManager = require('../../lib/events')
var helper = require('../../lib/helper')
var modalDialog = require('./modal-dialog-custom')
const { default: Registry } = require('../state/registry')
var typeConversion = remixLib.execution.typeConversion
var css = csjs`
.log {
display: flex;
cursor: pointer;
align-items: center;
cursor: pointer;
}
.log:hover {
opacity: 0.8;
}
.arrow {
color: var(--text-info);
font-size: 20px;
cursor: pointer;
display: flex;
margin-left: 10px;
}
.arrow:hover {
color: var(--secondary);
}
.txLog {
}
.txStatus {
display: flex;
font-size: 20px;
margin-right: 20px;
float: left;
}
.succeeded {
color: var(--success);
}
.failed {
color: var(--danger);
}
.notavailable {
}
.call {
font-size: 7px;
border-radius: 50%;
min-width: 20px;
min-height: 20px;
display: flex;
justify-content: center;
align-items: center;
color: var(--text-info);
text-transform: uppercase;
font-weight: bold;
}
.txItem {
color: var(--text-info);
margin-right: 5px;
float: left;
}
.txItemTitle {
font-weight: bold;
}
.tx {
color: var(--text-info);
font-weight: bold;
float: left;
margin-right: 10px;
}
.txTable,
.tr,
.td {
border-collapse: collapse;
font-size: 10px;
color: var(--text-info);
border: 1px solid var(--text-info);
}
#txTable {
margin-top: 1%;
margin-bottom: 5%;
align-self: center;
width: 85%;
}
.tr, .td {
padding: 4px;
vertical-align: baseline;
}
.td:first-child {
min-width: 30%;
width: 30%;
align-items: baseline;
font-weight: bold;
}
.tableTitle {
width: 25%;
}
.buttons {
display: flex;
margin-left: auto;
}
.debug {
white-space: nowrap;
}
.debug:hover {
opacity: 0.8;
}`
/**
* This just export a function that register to `newTransaction` and forward them to the logger.
*
*/
class TxLogger {
constructor (terminal, blockchain) {
this.event = new EventManager()
this.seen = {}
function filterTx (value, query) {
if (value.length) {
return helper.find(value, query)
}
return false
}
this.eventsDecoder = Registry.getInstance().get('eventsDecoder').api
this.txListener = Registry.getInstance().get('txlistener').api
this.terminal = terminal
// dependencies
this._deps = {
compilersArtefacts: Registry.getInstance().get('compilersartefacts').api
}
this.logKnownTX = this.terminal.registerCommand('knownTransaction', (args, cmds, append) => {
var data = args[0]
var el
if (data.tx.isCall) {
el = renderCall(this, data)
} else {
el = renderKnownTransaction(this, data, blockchain)
}
this.seen[data.tx.hash] = el
append(el)
}, { activate: true, filterFn: filterTx })
this.logUnknownTX = this.terminal.registerCommand('unknownTransaction', (args, cmds, append) => {
// triggered for transaction AND call
var data = args[0]
var el = renderUnknownTransaction(this, data, blockchain)
append(el)
}, { activate: false, filterFn: filterTx })
this.logEmptyBlock = this.terminal.registerCommand('emptyBlock', (args, cmds, append) => {
var data = args[0]
var el = renderEmptyBlock(this, data)
append(el)
}, { activate: true })
this.txListener.event.register('newBlock', (block) => {
if (!block.transactions || (block.transactions && !block.transactions.length)) {
this.logEmptyBlock({ block: block })
}
})
this.txListener.event.register('newTransaction', (tx, receipt) => {
log(this, tx, receipt)
})
this.txListener.event.register('newCall', (tx) => {
log(this, tx, null)
})
this.terminal.updateJournal({ type: 'select', value: 'unknownTransaction' })
this.terminal.updateJournal({ type: 'select', value: 'knownTransaction' })
}
}
function debug (e, data, self) {
e.stopPropagation()
if (data.tx.isCall && data.tx.envMode !== 'vm') {
modalDialog.alert('Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.')
} else {
self.event.trigger('debuggingRequested', [data.tx.hash])
}
}
function log (self, tx, receipt) {
var resolvedTransaction = self.txListener.resolvedTransaction(tx.hash)
if (resolvedTransaction) {
var compiledContracts = null
if (self._deps.compilersArtefacts.__last) {
compiledContracts = self._deps.compilersArtefacts.__last.getContracts()
}
self.eventsDecoder.parseLogs(tx, resolvedTransaction.contractName, compiledContracts, (error, logs) => {
if (!error) {
self.logKnownTX({ tx: tx, receipt: receipt, resolvedData: resolvedTransaction, logs: logs })
}
})
} else {
// contract unknown - just displaying raw tx.
self.logUnknownTX({ tx: tx, receipt: receipt })
}
}
function renderKnownTransaction (self, data, blockchain) {
var from = data.tx.from
var to = data.resolvedData.contractName + '.' + data.resolvedData.fn
var obj = { from, to }
var txType = 'knownTx'
var tx = yo`
<span id="tx${data.tx.hash}" data-id="txLogger${data.tx.hash}">
<div class="${css.log}" onclick=${e => txDetails(e, tx, data, obj)}>
${checkTxStatus(data.receipt, txType)}
${context(self, { from, to, data }, blockchain)}
<div class=${css.buttons}>
<button
class="${css.debug} btn btn-primary btn-sm"
data-shared="txLoggerDebugButton"
data-id="txLoggerDebugButton${data.tx.hash}"
onclick=${(e) => debug(e, data, self)}
>
Debug
</div>
</div>
<i class="${css.arrow} fas fa-angle-down"></i>
</div>
</span>
`
return tx
}
function renderCall (self, data) {
var to = data.resolvedData.contractName + '.' + data.resolvedData.fn
var from = data.tx.from ? data.tx.from : ' - '
var input = data.tx.input ? helper.shortenHexData(data.tx.input) : ''
var obj = { from, to }
var txType = 'call'
var tx = yo`
<span id="tx${data.tx.hash}">
<div class="${css.log}" onclick=${e => txDetails(e, tx, data, obj)}>
${checkTxStatus(data.tx, txType)}
<span class=${css.txLog}>
<span class=${css.tx}>[call]</span>
<div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>data:</span> ${input}</div>
</span>
<div class=${css.buttons}>
<div class="${css.debug} btn btn-primary btn-sm" onclick=${(e) => debug(e, data, self)}>Debug</div>
</div>
<i class="${css.arrow} fas fa-angle-down"></i>
</div>
</span>
`
return tx
}
function renderUnknownTransaction (self, data, blockchain) {
var from = data.tx.from
var to = data.tx.to
var obj = { from, to }
var txType = 'unknown' + (data.tx.isCall ? 'Call' : 'Tx')
var tx = yo`
<span id="tx${data.tx.hash}">
<div class="${css.log}" onclick=${e => txDetails(e, tx, data, obj)}>
${checkTxStatus(data.receipt || data.tx, txType)}
${context(self, { from, to, data }, blockchain)}
<div class=${css.buttons}>
<div class="${css.debug} btn btn-primary btn-sm" onclick=${(e) => debug(e, data, self)}>Debug</div>
</div>
<i class="${css.arrow} fas fa-angle-down"></i>
</div>
</span>
`
return tx
}
function renderEmptyBlock (self, data) {
return yo`
<span class=${css.txLog}>
<span class='${css.tx}'><div class=${css.txItem}>[<span class=${css.txItemTitle}>block:${data.block.number} - </span> 0 transactions]</span></span>
</span>`
}
function checkTxStatus (tx, type) {
if (tx.status === '0x1' || tx.status === true) {
return yo`<i class="${css.txStatus} ${css.succeeded} fas fa-check-circle"></i>`
}
if (type === 'call' || type === 'unknownCall') {
return yo`<i class="${css.txStatus} ${css.call}">call</i>`
} else if (tx.status === '0x0' || tx.status === false) {
return yo`<i class="${css.txStatus} ${css.failed} fas fa-times-circle"></i>`
} else {
return yo`<i class="${css.txStatus} ${css.notavailable} fas fa-circle-thin" title='Status not available' ></i>`
}
}
function context (self, opts, blockchain) {
var data = opts.data || ''
var from = opts.from ? helper.shortenHexData(opts.from) : ''
var to = opts.to
if (data.tx.to) to = to + ' ' + helper.shortenHexData(data.tx.to)
var val = data.tx.value
var hash = data.tx.hash ? helper.shortenHexData(data.tx.hash) : ''
var input = data.tx.input ? helper.shortenHexData(data.tx.input) : ''
var logs = data.logs && data.logs.decoded && data.logs.decoded.length ? data.logs.decoded.length : 0
var block = data.receipt ? data.receipt.blockNumber : data.tx.blockNumber || ''
var i = data.receipt ? data.receipt.transactionIndex : data.tx.transactionIndex
var value = val ? typeConversion.toInt(val) : 0
if (blockchain.getProvider() === 'vm') {
return yo`
<div>
<span class=${css.txLog}>
<span class=${css.tx}>[vm]</span>
<div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>value:</span> ${value} wei</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>data:</span> ${input}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>logs:</span> ${logs}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>hash:</span> ${hash}</div>
</span>
</div>`
} else if (blockchain.getProvider() !== 'vm' && data.resolvedData) {
return yo`
<div>
<span class=${css.txLog}>
<span class='${css.tx}'>[block:${block} txIndex:${i}]</span>
<div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>value:</span> ${value} wei</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>data:</span> ${input}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>logs:</span> ${logs}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>hash:</span> ${hash}</div>
</span>
</div>`
} else {
to = helper.shortenHexData(to)
hash = helper.shortenHexData(data.tx.blockHash)
return yo`
<div>
<span class=${css.txLog}>
<span class='${css.tx}'>[block:${block} txIndex:${i}]</span>
<div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div>
<div class=${css.txItem}><span class=${css.txItemTitle}>value:</span> ${value} wei</div>
</span>
</div>`
}
}
module.exports = TxLogger
// helpers
function isDescendant (parent, child) {
var node = child.parentNode
while (node != null) {
if (node === parent) {
return true
}
node = node.parentNode
}
return false
}
function txDetails (e, tx, data, obj) {
const from = obj.from
const to = obj.to
const arrowUp = yo`<i class="${css.arrow} fas fa-angle-up"></i>`
const arrowDown = yo`<i class="${css.arrow} fas fa-angle-down"></i>`
let blockElement = e.target
while (true) { // get the parent block element
if (blockElement.className.startsWith('block')) break
else if (blockElement.parentElement) {
blockElement = blockElement.parentElement
} else break
}
const tables = blockElement.querySelectorAll(`#${tx.id} [class^="txTable"]`)
const logs = blockElement.querySelectorAll(`#${tx.id} [class^='log']`)
const arrows = blockElement.querySelectorAll(`#${tx.id} [class^='arrow']`)
let table = [...tables].filter((t) => isDescendant(tx, t))[0]
const log = [...logs].filter((t) => isDescendant(tx, t))[0]
const arrow = [...arrows].filter((t) => isDescendant(tx, t))[0]
if (table && table.parentNode) {
tx.removeChild(table)
log.removeChild(arrow)
log.appendChild(arrowDown)
} else {
log.removeChild(arrow)
log.appendChild(arrowUp)
table = createTable({
hash: data.tx.hash,
status: data.receipt ? data.receipt.status : null,
isCall: data.tx.isCall,
contractAddress: data.tx.contractAddress,
data: data.tx,
from,
to,
gas: data.tx.gas,
input: data.tx.input,
'decoded input': data.resolvedData && data.resolvedData.params ? JSON.stringify(typeConversion.stringify(data.resolvedData.params), null, '\t') : ' - ',
'decoded output': data.resolvedData && data.resolvedData.decodedReturnValue ? JSON.stringify(typeConversion.stringify(data.resolvedData.decodedReturnValue), null, '\t') : ' - ',
logs: data.logs,
val: data.tx.value,
transactionCost: data.tx.transactionCost,
executionCost: data.tx.executionCost
})
tx.appendChild(table)
}
}
function createTable (opts) {
var table = yo`<table class="${css.txTable}" id="txTable" data-id="txLoggerTable${opts.hash}"></table>`
if (!opts.isCall) {
var msg = ''
if (opts.status !== undefined && opts.status !== null) {
if (opts.status === '0x0' || opts.status === false) {
msg = ' Transaction mined but execution failed'
} else if (opts.status === '0x1' || opts.status === true) {
msg = ' Transaction mined and execution succeed'
}
} else {
msg = ' Status not available at the moment'
}
table.appendChild(yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> status </td>
<td class="${css.td}" data-id="txLoggerTableStatus${opts.hash}" data-shared="pair_${opts.hash}">${opts.status}${msg}</td>
</tr>`)
}
var transactionHash = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> transaction hash </td>
<td class="${css.td}" data-id="txLoggerTableHash${opts.hash}" data-shared="pair_${opts.hash}">${opts.hash}
${copyToClipboard(() => opts.hash)}
</td>
</tr>
`
table.appendChild(transactionHash)
var contractAddress = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> contract address </td>
<td class="${css.td}" data-id="txLoggerTableContractAddress${opts.hash}" data-shared="pair_${opts.hash}">${opts.contractAddress}
${copyToClipboard(() => opts.contractAddress)}
</td>
</tr>
`
if (opts.contractAddress) table.appendChild(contractAddress)
var from = yo`
<tr class="${css.tr}">
<td class="${css.td} ${css.tableTitle}" data-shared="key_${opts.hash}"> from </td>
<td class="${css.td}" data-id="txLoggerTableFrom${opts.hash}" data-shared="pair_${opts.hash}">${opts.from}
${copyToClipboard(() => opts.from)}
</td>
</tr>
`
if (opts.from) table.appendChild(from)
var toHash
var data = opts.data // opts.data = data.tx
if (data.to) {
toHash = opts.to + ' ' + data.to
} else {
toHash = opts.to
}
var to = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> to </td>
<td class="${css.td}" data-id="txLoggerTableTo${opts.hash}" data-shared="pair_${opts.hash}">${toHash}
${copyToClipboard(() => data.to ? data.to : toHash)}
</td>
</tr>
`
if (opts.to) table.appendChild(to)
var gas = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> gas </td>
<td class="${css.td}" data-id="txLoggerTableGas${opts.hash}" data-shared="pair_${opts.hash}">${opts.gas} gas
${copyToClipboard(() => opts.gas)}
</td>
</tr>
`
if (opts.gas) table.appendChild(gas)
var callWarning = ''
if (opts.isCall) {
callWarning = '(Cost only applies when called by a contract)'
}
if (opts.transactionCost) {
table.appendChild(yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> transaction cost </td>
<td class="${css.td}" data-id="txLoggerTableTransactionCost${opts.hash}" data-shared="pair_${opts.hash}">${opts.transactionCost} gas ${callWarning}
${copyToClipboard(() => opts.transactionCost)}
</td>
</tr>`)
}
if (opts.executionCost) {
table.appendChild(yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> execution cost </td>
<td class="${css.td}" data-id="txLoggerTableExecutionHash${opts.hash}" data-shared="pair_${opts.hash}">${opts.executionCost} gas ${callWarning}
${copyToClipboard(() => opts.executionCost)}
</td>
</tr>`)
}
var hash = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> hash </td>
<td class="${css.td}" data-id="txLoggerTableHash${opts.hash}" data-shared="pair_${opts.hash}">${opts.hash}
${copyToClipboard(() => opts.hash)}
</td>
</tr>
`
if (opts.hash) table.appendChild(hash)
var input = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> input </td>
<td class="${css.td}" data-id="txLoggerTableInput${opts.hash}" data-shared="pair_${opts.hash}">${helper.shortenHexData(opts.input)}
${copyToClipboard(() => opts.input)}
</td>
</tr>
`
if (opts.input) table.appendChild(input)
if (opts['decoded input']) {
var inputDecoded = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> decoded input </td>
<td class="${css.td}" data-id="txLoggerTableDecodedInput${opts.hash}" data-shared="pair_${opts.hash}">${opts['decoded input']}
${copyToClipboard(() => opts['decoded input'])}
</td>
</tr>`
table.appendChild(inputDecoded)
}
if (opts['decoded output']) {
var outputDecoded = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> decoded output </td>
<td class="${css.td}" id="decodedoutput" data-id="txLoggerTableDecodedOutput${opts.hash}" data-shared="pair_${opts.hash}">${opts['decoded output']}
${copyToClipboard(() => opts['decoded output'])}
</td>
</tr>`
table.appendChild(outputDecoded)
}
var stringified = ' - '
if (opts.logs && opts.logs.decoded) {
stringified = typeConversion.stringify(opts.logs.decoded)
}
var logs = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> logs </td>
<td class="${css.td}" id="logs" data-id="txLoggerTableLogs${opts.hash}" data-shared="pair_${opts.hash}">
${JSON.stringify(stringified, null, '\t')}
${copyToClipboard(() => JSON.stringify(stringified, null, '\t'))}
${copyToClipboard(() => JSON.stringify(opts.logs.raw || '0'))}
</td>
</tr>
`
if (opts.logs) table.appendChild(logs)
var val = opts.val != null ? typeConversion.toInt(opts.val) : 0
val = yo`
<tr class="${css.tr}">
<td class="${css.td}" data-shared="key_${opts.hash}"> value </td>
<td class="${css.td}" data-id="txLoggerTableValue${opts.hash}" data-shared="pair_${opts.hash}">${val} wei
${copyToClipboard(() => `${val} wei`)}
</td>
</tr>
`
if (opts.val) table.appendChild(val)
return table
}

@ -1,272 +0,0 @@
/* global */
'use strict'
var $ = require('jquery')
var yo = require('yo-yo')
var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN
var helper = require('../../lib/helper')
var copyToClipboard = require('./copy-to-clipboard')
var css = require('../../universal-dapp-styles')
var MultiParamManager = require('./multiParamManager')
var remixLib = require('@remix-project/remix-lib')
var txFormat = remixLib.execution.txFormat
const txHelper = remixLib.execution.txHelper
var TreeView = require('./TreeView')
var txCallBacks = require('./sendTxCallbacks')
const _paq = window._paq = window._paq || []
function UniversalDAppUI (blockchain, logCallback) {
this.blockchain = blockchain
this.logCallback = logCallback
this.compilerData = { contractsDetails: {} }
}
function decodeResponseToTreeView (response, fnabi) {
var treeView = new TreeView({
extractData: (item, parent, key) => {
var ret = {}
if (BN.isBN(item)) {
ret.self = item.toString(10)
ret.children = []
} else {
ret = treeView.extractDataDefault(item, parent, key)
}
return ret
}
})
return treeView.render(txFormat.decodeResponse(response, fnabi))
}
UniversalDAppUI.prototype.renderInstance = function (contract, address, contractName) {
var noInstances = document.querySelector('[data-id="deployAndRunNoInstanceText"]')
if (noInstances) {
noInstances.parentNode.removeChild(noInstances)
}
const abi = txHelper.sortAbiFunction(contract.abi)
return this.renderInstanceFromABI(abi, address, contractName, contract)
}
// 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, contract) {
const self = this
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex')
address = ethJSUtil.toChecksumAddress(address)
var instance = yo`<div class="instance run-instance border-dark ${css.instance} ${css.hidesub}" id="instance${address}" data-shared="universalDappUiInstance"></div>`
const context = this.blockchain.context()
var shortAddress = helper.shortenAddress(address)
var title = yo`
<div class="${css.title} alert alert-secondary">
<button data-id="universalDappUiTitleExpander" class="btn ${css.titleExpander}" onclick="${(e) => { toggleClass(e) }}">
<i class="fas fa-angle-right" aria-hidden="true"></i>
</button>
<div class="input-group ${css.nameNbuts}">
<div class="${css.titleText} input-group-prepend">
<span class="input-group-text ${css.spanTitleText}">
${contractName} at ${shortAddress} (${context})
</span>
</div>
<div class="btn-group">
<button class="btn p-1 btn-secondary">${copyToClipboard(() => address)}</button>
</div>
</div>
</div>
`
var close = yo`
<button
class="${css.udappClose} mr-1 p-1 btn btn-secondary align-items-center"
data-id="universalDappUiUdappClose"
onclick=${remove}
title="Remove from the list"
>
<i class="${css.closeIcon} fas fa-times" aria-hidden="true"></i>
</button>`
title.querySelector('.btn-group').appendChild(close)
var contractActionsWrapper = yo`
<div class="${css.cActionsWrapper}" data-id="universalDappUiContractActionWrapper">
</div>
`
function remove () {
instance.remove()
// @TODO perhaps add a callack here to warn the caller that the instance has been removed
}
function toggleClass (e) {
$(instance).toggleClass(`${css.hidesub} bg-light`)
// e.currentTarget.querySelector('i')
e.currentTarget.querySelector('i').classList.toggle('fa-angle-right')
e.currentTarget.querySelector('i').classList.toggle('fa-angle-down')
}
instance.appendChild(title)
instance.appendChild(contractActionsWrapper)
$.each(contractABI, (i, funABI) => {
if (funABI.type !== 'function') {
return
}
// @todo getData cannot be used with overloaded functions
contractActionsWrapper.appendChild(this.getCallButton({
funABI: funABI,
address: address,
contractABI: contractABI,
contractName: contractName,
contract
}))
})
const calldataInput = yo`
<input id="deployAndRunLLTxCalldata" class="${css.calldataInput} form-control" title="The Calldata to send to fallback function of the contract.">
`
const llIError = yo`
<label id="deployAndRunLLTxError" class="text-danger my-2"></label>
`
// constract LLInteractions elements
const lowLevelInteracions = yo`
<div class="d-flex flex-column">
<div class="d-flex flex-row justify-content-between mt-2">
<div class="py-2 border-top d-flex justify-content-start flex-grow-1">
Low level interactions
</div>
<a
href="https://solidity.readthedocs.io/en/v0.6.2/contracts.html#receive-ether-function"
title="check out docs for using 'receive'/'fallback'"
target="_blank"
>
<i aria-hidden="true" class="fas fa-info my-2 mr-1"></i>
</a>
</div>
<div class="d-flex flex-column align-items-start">
<label class="">CALLDATA</label>
<div class="d-flex justify-content-end w-100 align-items-center">
${calldataInput}
<button id="deployAndRunLLTxSendTransaction" data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction" class="${css.instanceButton} p-0 w-50 btn border-warning text-warning" title="Send data to contract." onclick=${() => sendData()}>Transact</button>
</div>
</div>
<div>
${llIError}
</div>
</div>
`
function sendData () {
function setLLIError (text) {
llIError.innerText = text
}
setLLIError('')
const fallback = txHelper.getFallbackInterface(contractABI)
const receive = txHelper.getReceiveInterface(contractABI)
const args = {
funABI: fallback || receive,
address: address,
contractName: contractName,
contractABI: contractABI
}
const amount = document.querySelector('#value').value
if (amount !== '0') {
// check for numeric and receive/fallback
if (!helper.isNumeric(amount)) {
return setLLIError('Value to send should be a number')
} else if (!receive && !(fallback && fallback.stateMutability === 'payable')) {
return setLLIError("In order to receive Ether transfer the contract should have either 'receive' or payable 'fallback' function")
}
}
let calldata = calldataInput.value
if (calldata) {
if (calldata.length < 4 && helper.is0XPrefixed(calldata)) {
return setLLIError('The calldata should be a valid hexadecimal value with size of at least one byte.')
} else {
if (helper.is0XPrefixed(calldata)) {
calldata = calldata.substr(2, calldata.length)
}
if (!helper.isHexadecimal(calldata)) {
return setLLIError('The calldata should be a valid hexadecimal value.')
}
}
if (!fallback) {
return setLLIError("'Fallback' function is not defined")
}
}
if (!receive && !fallback) return setLLIError('Both \'receive\' and \'fallback\' functions are not defined')
// we have to put the right function ABI:
// if receive is defined and that there is no calldata => receive function is called
// if fallback is defined => fallback function is called
if (receive && !calldata) args.funABI = receive
else if (fallback) args.funABI = fallback
if (!args.funABI) return setLLIError('Please define a \'Fallback\' function to send calldata and a either \'Receive\' or payable \'Fallback\' to send ethers')
self.runTransaction(false, args, null, calldataInput.value, null)
}
contractActionsWrapper.appendChild(lowLevelInteracions)
return instance
}
// TODO this is used by renderInstance when a new instance is displayed.
// this returns a DOM element.
UniversalDAppUI.prototype.getCallButton = function (args) {
const self = this
var outputOverride = yo`<div class=${css.value}></div>` // show return value
const isConstant = args.funABI.constant !== undefined ? args.funABI.constant : false
const lookupOnly = args.funABI.stateMutability === 'view' || args.funABI.stateMutability === 'pure' || isConstant
const multiParamManager = new MultiParamManager(
lookupOnly,
args.funABI,
(valArray, inputsValues) => self.runTransaction(lookupOnly, args, valArray, inputsValues, outputOverride),
self.blockchain.getInputs(args.funABI)
)
const contractActionsContainer = yo`<div class="${css.contractActionsContainer}" >${multiParamManager.render()}</div>`
contractActionsContainer.appendChild(outputOverride)
return contractActionsContainer
}
UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, inputsValues, outputOverride) {
const functionName = args.funABI.type === 'function' ? args.funABI.name : `(${args.funABI.type})`
const logMsg = `${lookupOnly ? 'call' : 'transact'} to ${args.contractName}.${functionName}`
const callbacksInContext = txCallBacks.getCallBacksWithContext(this, this.blockchain)
const outputCb = (returnValue) => {
if (outputOverride) {
const decoded = decodeResponseToTreeView(returnValue, args.funABI)
outputOverride.innerHTML = ''
outputOverride.appendChild(decoded)
}
}
let callinfo = ''
if (lookupOnly) callinfo = 'call'
else if (args.funABI.type === 'fallback' || args.funABI.type === 'receive') callinfo = 'lowLevelInteracions'
else callinfo = 'transact'
_paq.push(['trackEvent', 'udapp', callinfo, this.blockchain.getCurrentNetworkStatus().network.name])
const params = args.funABI.type !== 'fallback' ? inputsValues : ''
this.blockchain.runOrCallContractMethod(
args.contractName,
args.contractABI,
args.funABI,
args.contract,
inputsValues,
args.address,
params,
lookupOnly,
logMsg,
this.logCallback,
outputCb,
callbacksInContext.confirmationCb.bind(callbacksInContext),
callbacksInContext.continueCb.bind(callbacksInContext),
callbacksInContext.promptCb.bind(callbacksInContext))
}
module.exports = UniversalDAppUI

@ -1,3 +1,5 @@
import React from 'react' // eslint-disable-line
import Web3 from 'web3' import Web3 from 'web3'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import { toBuffer, addHexPrefix } from 'ethereumjs-util' import { toBuffer, addHexPrefix } from 'ethereumjs-util'
@ -9,6 +11,7 @@ import VMProvider from './providers/vm.js'
import InjectedProvider from './providers/injected.js' import InjectedProvider from './providers/injected.js'
import NodeProvider from './providers/node.js' import NodeProvider from './providers/node.js'
import { execution, EventManager, helpers } from '@remix-project/remix-lib' import { execution, EventManager, helpers } from '@remix-project/remix-lib'
import { etherScanLink } from './helper'
const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution
const { txResultHelper: resultToRemixTx } = helpers const { txResultHelper: resultToRemixTx } = helpers
const packageJson = require('../../../../package.json') const packageJson = require('../../../../package.json')
@ -23,7 +26,7 @@ const profile = {
version: packageJson.version version: packageJson.version
} }
class Blockchain extends Plugin { export class Blockchain extends Plugin {
// NOTE: the config object will need to be refactored out in remix-lib // NOTE: the config object will need to be refactored out in remix-lib
constructor (config) { constructor (config) {
super(profile) super(profile)
@ -344,13 +347,17 @@ class Blockchain extends Plugin {
} }
}, _ => this.executionContext.web3(), _ => this.executionContext.currentblockGasLimit()) }, _ => this.executionContext.web3(), _ => this.executionContext.currentblockGasLimit())
this.txRunner = new TxRunner(web3Runner, { runAsync: true }) web3Runner.event.register('transactionBroadcasted', (txhash) => {
this.txRunner.event.register('transactionBroadcasted', (txhash) => {
this.executionContext.detectNetwork((error, network) => { this.executionContext.detectNetwork((error, network) => {
if (error || !network) return if (error || !network) return
this.event.trigger('transactionBroadcasted', [txhash, network.name]) if (network.name === 'VM') return
this.call('terminal', 'logHtml',
(<a href={etherScanLink(network.name, txhash)} target="_blank">
open in etherscan
</a>))
}) })
}) })
this.txRunner = new TxRunner(web3Runner, { runAsync: true })
} }
/** /**
@ -545,5 +552,3 @@ class Blockchain extends Plugin {
}) })
} }
} }
module.exports = Blockchain

@ -0,0 +1,13 @@
const transactionDetailsLinks = {
Main: 'https://www.etherscan.io/tx/',
Rinkeby: 'https://rinkeby.etherscan.io/tx/',
Ropsten: 'https://ropsten.etherscan.io/tx/',
Kovan: 'https://kovan.etherscan.io/tx/',
Goerli: 'https://goerli.etherscan.io/tx/'
}
export function etherScanLink (network: string, hash: string): string {
if (transactionDetailsLinks[network]) {
return transactionDetailsLinks[network] + hash
}
}

@ -1,34 +0,0 @@
export class FramingService {
constructor (sidePanel, verticalIcons, mainView, resizeFeature) {
this.sidePanel = sidePanel
this.verticalIcons = verticalIcons
this.mainPanel = mainView.getAppPanel()
this.mainView = mainView
this.resizeFeature = resizeFeature
}
start (params) {
this.verticalIcons.select('filePanel')
document.addEventListener('keypress', (e) => {
if (e.shiftKey && e.ctrlKey) {
if (e.code === 'KeyF') { // Ctrl+Shift+F
this.verticalIcons.select('filePanel')
} else if (e.code === 'KeyA') { // Ctrl+Shift+A
this.verticalIcons.select('pluginManager')
} else if (e.code === 'KeyS') { // Ctrl+Shift+S
this.verticalIcons.select('settings')
}
e.preventDefault()
}
})
if (params.minimizeterminal) this.mainView.minimizeTerminal()
if (params.minimizesidepanel) this.resizeFeature.hidePanel()
}
embed () {
this.mainView.minimizeTerminal()
this.resizeFeature.hidePanel()
}
}

@ -1,135 +0,0 @@
'use strict'
import { CompilerImports } from '@remix-project/core-plugin'
import Registry from '../app/state/registry'
var yo = require('yo-yo')
var async = require('async')
var EventManager = require('../lib/events')
var toolTip = require('../app/ui/tooltip')
var GistHandler = require('./gist-handler')
class CmdInterpreterAPI {
constructor (terminal, blockchain) {
const self = this
self.event = new EventManager()
self.blockchain = blockchain
self._components = {}
self._components.registry = Registry.getInstance()
self._components.terminal = terminal
self._components.fileImport = new CompilerImports()
self._components.gistHandler = new GistHandler()
self._deps = {
fileManager: self._components.registry.get('filemanager').api,
editor: self._components.registry.get('editor').api,
compilersArtefacts: self._components.registry.get('compilersartefacts').api,
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api
}
self.commandHelp = {
'remix.loadgist(id)': 'Load a gist in the file explorer.',
'remix.loadurl(url)': 'Load the given url in the file explorer. The url can be of type github, swarm, ipfs or raw http',
'remix.execute(filepath)': 'Run the script specified by file path. If filepath is empty, script currently displayed in the editor is executed.',
'remix.exeCurrent()': 'Run the script currently displayed in the editor',
'remix.help()': 'Display this help message'
}
}
log () { arguments[0] != null ? this._components.terminal.commands.html(arguments[0]) : this._components.terminal.commands.html(arguments[1]) }
loadgist (id, cb) {
const self = this
self._components.gistHandler.loadFromGist({ gist: id }, this._deps.fileManager)
if (cb) cb()
}
loadurl (url, cb) {
const self = this
self._components.fileImport.import(url,
(loadingMsg) => { toolTip(loadingMsg) },
(err, content, cleanUrl, type, url) => {
if (err) {
toolTip(`Unable to load ${url}: ${err}`)
if (cb) cb(err)
} else {
self._deps.fileManager.writeFile(type + '/' + cleanUrl, content)
try {
content = JSON.parse(content)
async.eachOfSeries(content.sources, (value, file, callbackSource) => {
var url = value.urls[0] // @TODO retrieve all other contents ?
self._components.fileImport.import(url,
(loadingMsg) => { toolTip(loadingMsg) },
async (error, content, cleanUrl, type, url) => {
if (error) {
toolTip(`Cannot retrieve the content of ${url}: ${error}`)
return callbackSource(`Cannot retrieve the content of ${url}: ${error}`)
} else {
try {
await self._deps.fileManager.writeFile(type + '/' + cleanUrl, content)
callbackSource()
} catch (e) {
callbackSource(e.message)
}
}
})
}, (error) => {
if (cb) cb(error)
})
} catch (e) {}
if (cb) cb()
}
})
}
exeCurrent (cb) {
return this.execute(undefined, cb)
}
execute (file, cb) {
const self = this
function _execute (content, cb) {
if (!content) {
toolTip('no content to execute')
if (cb) cb()
return
}
self._components.terminal.commands.script(content)
}
if (typeof file === 'undefined') {
var content = self._deps.editor.currentContent()
_execute(content, cb)
return
}
var provider = self._deps.fileManager.fileProviderOf(file)
if (!provider) {
toolTip(`provider for path ${file} not found`)
if (cb) cb()
return
}
provider.get(file, (error, content) => {
if (error) {
toolTip(error)
if (cb) cb()
return
}
_execute(content, cb)
})
}
help (cb) {
const self = this
var help = yo`<div></div>`
for (var k in self.commandHelp) {
help.appendChild(yo`<div>${k}: ${self.commandHelp[k]}</div>`)
help.appendChild(yo`<br>`)
}
self._components.terminal.commands.html(help)
if (cb) cb()
return ''
}
}
module.exports = CmdInterpreterAPI

@ -1,74 +0,0 @@
'use strict'
var modalDialogCustom = require('../app/ui/modal-dialog-custom')
var request = require('request')
// Allowing window to be overriden for testing
function GistHandler (_window) {
if (_window !== undefined) {
modalDialogCustom = _window
}
this.handleLoad = function (params, cb) {
if (!cb) cb = () => {}
var loadingFromGist = false
var gistId
if (params.gist === '') {
loadingFromGist = true
modalDialogCustom.prompt('Load a Gist', 'Enter the ID of the Gist or URL you would like to load.', null, (target) => {
if (target !== '') {
gistId = getGistId(target)
if (gistId) {
cb(gistId)
} else {
modalDialogCustom.alert('Gist load error', 'Error while loading gist. Please provide a valid Gist ID or URL.')
}
}
})
return loadingFromGist
} else {
gistId = params.gist
loadingFromGist = !!gistId
}
if (loadingFromGist) {
cb(gistId)
}
return loadingFromGist
}
function getGistId (str) {
var idr = /[0-9A-Fa-f]{8,}/
var match = idr.exec(str)
return match ? match[0] : null
}
this.loadFromGist = (params, fileManager) => {
const self = this
return self.handleLoad(params, function (gistId) {
request.get({
url: `https://api.github.com/gists/${gistId}`,
json: true
}, async (error, response, data = {}) => {
if (error || !data.files) {
modalDialogCustom.alert('Gist load error', error || data.message)
return
}
const obj = {}
Object.keys(data.files).forEach((element) => {
const path = element.replace(/\.\.\./g, '/')
obj['/' + 'gist-' + gistId + '/' + path] = data.files[element]
})
fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => {
if (!errorLoadingFile) {
const provider = fileManager.getProvider('workspace')
provider.lastLoadedGistId = gistId
} else {
modalDialogCustom.alert('Gist load error', errorLoadingFile.message || errorLoadingFile)
}
})
})
})
}
}
module.exports = GistHandler

@ -1,88 +0,0 @@
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const css = csjs`
.dragbar {
width : 2px;
height : 100%;
cursor : col-resize;
z-index : 999;
}
.ghostbar {
width : 3px;
background-color : var(--primary);
opacity : 0.5;
position : absolute;
cursor : col-resize;
z-index : 9999;
top : 0;
bottom : 0;
}
`
export default class PanelsResize {
constructor (panel) {
this.panel = panel
const string = panel.style.minWidth
this.minWidth = string.length > 2 ? parseInt(string.substring(0, string.length - 2)) : 0
}
render () {
this.ghostbar = yo`<div class=${css.ghostbar}></div>`
const mousedown = (event) => {
event.preventDefault()
if (event.which === 1) {
moveGhostbar(event)
document.body.appendChild(this.ghostbar)
document.addEventListener('mousemove', moveGhostbar)
document.addEventListener('mouseup', removeGhostbar)
document.addEventListener('keydown', cancelGhostbar)
}
}
const cancelGhostbar = (event) => {
if (event.keyCode === 27) {
document.body.removeChild(this.ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
}
}
const moveGhostbar = (event) => {
this.ghostbar.style.left = event.x + 'px'
}
const removeGhostbar = (event) => {
document.body.removeChild(this.ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
this.setPosition(event)
}
return yo`<div onmousedown=${mousedown} class=${css.dragbar}></div>`
}
calculatePanelWidth (event) {
return event.x - this.panel.offsetLeft
}
setPosition (event) {
const panelWidth = this.calculatePanelWidth(event)
// close the panel if the width is less than a minWidth
if (panelWidth > this.minWidth - 10 || this.panel.style.display === 'none') {
this.panel.style.width = panelWidth + 'px'
this.showPanel()
} else this.hidePanel()
}
hidePanel () {
this.panel.style.display = 'none'
}
showPanel () {
this.panel.style.display = 'flex'
}
}

@ -1,121 +0,0 @@
'use strict'
const async = require('async')
const IpfsClient = require('ipfs-mini')
const ipfsNodes = [
new IpfsClient({ host: 'ipfs.remixproject.org', port: 443, protocol: 'https' }),
new IpfsClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' }),
new IpfsClient({ host: '127.0.0.1', port: 5001, protocol: 'http' })
]
module.exports = (contract, fileManager, cb, ipfsVerifiedPublishCallBack) => {
// gather list of files to publish
var sources = []
var metadata
try {
metadata = JSON.parse(contract.metadata)
} catch (e) {
return cb(e)
}
if (metadata === undefined) {
return cb('No metadata')
}
async.eachSeries(Object.keys(metadata.sources), function (fileName, cb) {
// find hash
let hash = null
try {
// we try extract the hash defined in the metadata.json
// in order to check if the hash that we get after publishing is the same as the one located in metadata.json
// if it's not the same, we throw "hash mismatch between solidity bytecode and uploaded content"
// if we don't find the hash in the metadata.json, the check is not done.
//
// TODO: refactor this with publishOnSwarm
if (metadata.sources[fileName].urls) {
metadata.sources[fileName].urls.forEach(url => {
if (url.includes('ipfs')) hash = url.match('dweb:/ipfs/(.+)')[1]
})
}
} catch (e) {
return cb('Error while extracting the hash from metadata.json')
}
fileManager.fileProviderOf(fileName).get(fileName, (error, content) => {
if (error) {
console.log(error)
} else {
sources.push({
content: content,
hash: hash,
filename: fileName
})
}
cb()
})
}, function (error) {
if (error) {
cb(error)
} else {
// publish the list of sources in order, fail if any failed
var uploaded = []
async.eachSeries(sources, function (item, cb) {
ipfsVerifiedPublish(item.content, item.hash, (error, result) => {
try {
item.hash = result.url.match('dweb:/ipfs/(.+)')[1]
} catch (e) {
item.hash = '<Metadata inconsistency> - ' + item.fileName
}
if (!error && ipfsVerifiedPublishCallBack) ipfsVerifiedPublishCallBack(item)
item.output = result
uploaded.push(item)
cb(error)
})
}, () => {
const metadataContent = JSON.stringify(metadata)
ipfsVerifiedPublish(metadataContent, '', (error, result) => {
try {
contract.metadataHash = result.url.match('dweb:/ipfs/(.+)')[1]
} catch (e) {
contract.metadataHash = '<Metadata inconsistency> - metadata.json'
}
if (!error && ipfsVerifiedPublishCallBack) {
ipfsVerifiedPublishCallBack({
content: metadataContent,
hash: contract.metadataHash
})
}
uploaded.push({
content: contract.metadata,
hash: contract.metadataHash,
filename: 'metadata.json',
output: result
})
cb(error, uploaded)
})
})
}
})
}
async function ipfsVerifiedPublish (content, expectedHash, cb) {
try {
const results = await severalGatewaysPush(content)
if (expectedHash && results !== expectedHash) {
cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + results, hash: results })
} else {
cb(null, { message: 'ok', url: 'dweb:/ipfs/' + results, hash: results })
}
} catch (error) {
cb(error)
}
}
function severalGatewaysPush (content) {
const invert = p => new Promise((resolve, reject) => p.then(reject).catch(resolve)) // Invert res and rej
const promises = ipfsNodes.map((node) => invert(node.add(content)))
return invert(Promise.all(promises))
}

@ -1,109 +0,0 @@
'use strict'
var async = require('async')
var swarmgw = require('swarmgw')()
module.exports = (contract, fileManager, cb, swarmVerifiedPublishCallBack) => {
// gather list of files to publish
var sources = []
var metadata
try {
metadata = JSON.parse(contract.metadata)
} catch (e) {
return cb(e)
}
if (metadata === undefined) {
return cb('No metadata')
}
async.eachSeries(Object.keys(metadata.sources), function (fileName, cb) {
// find hash
let hash = null
try {
// we try extract the hash defined in the metadata.json
// in order to check if the hash that we get after publishing is the same as the one located in metadata.json
// if it's not the same, we throw "hash mismatch between solidity bytecode and uploaded content"
// if we don't find the hash in the metadata.json, the check is not done.
//
// TODO: refactor this with publishOnIpfs
if (metadata.sources[fileName].urls) {
metadata.sources[fileName].urls.forEach(url => {
if (url.includes('bzz')) hash = url.match('(bzzr|bzz-raw)://(.+)')[1]
})
}
} catch (e) {
return cb('Error while extracting the hash from metadata.json')
}
fileManager.fileProviderOf(fileName).get(fileName, (error, content) => {
if (error) {
console.log(error)
} else {
sources.push({
content: content,
hash: hash,
filename: fileName
})
}
cb()
})
}, function (error) {
if (error) {
cb(error)
} else {
// publish the list of sources in order, fail if any failed
var uploaded = []
async.eachSeries(sources, function (item, cb) {
swarmVerifiedPublish(item.content, item.hash, (error, result) => {
try {
item.hash = result.url.match('bzz-raw://(.+)')[1]
} catch (e) {
item.hash = '<Metadata inconsistency> - ' + item.fileName
}
if (!error && swarmVerifiedPublishCallBack) swarmVerifiedPublishCallBack(item)
item.output = result
uploaded.push(item)
// TODO this is a fix cause Solidity metadata does not contain the right swarm hash (poc 0.3)
metadata.sources[item.filename].urls[0] = result.url
cb(error)
})
}, () => {
const metadataContent = JSON.stringify(metadata)
swarmVerifiedPublish(metadataContent, '', (error, result) => {
try {
contract.metadataHash = result.url.match('bzz-raw://(.+)')[1]
} catch (e) {
contract.metadataHash = '<Metadata inconsistency> - metadata.json'
}
if (!error && swarmVerifiedPublishCallBack) {
swarmVerifiedPublishCallBack({
content: metadataContent,
hash: contract.metadataHash
})
}
uploaded.push({
content: contract.metadata,
hash: contract.metadataHash,
filename: 'metadata.json',
output: result
})
cb(error, uploaded)
})
})
}
})
}
function swarmVerifiedPublish (content, expectedHash, cb) {
swarmgw.put(content, function (err, ret) {
if (err) {
cb(err)
} else if (expectedHash && ret !== expectedHash) {
cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + ret, hash: ret })
} else {
cb(null, { message: 'ok', url: 'bzz-raw://' + ret, hash: ret })
}
})
}

@ -1,145 +0,0 @@
'use strict'
var EventManager = require('../lib/events')
var modalDialog = require('../app/ui/modaldialog')
var yo = require('yo-yo')
class Remixd {
constructor (port) {
this.event = new EventManager()
this.port = port
this.callbacks = {}
this.callid = 0
this.socket = null
this.connected = false
this.receiveResponse()
}
online () {
return this.socket !== null
}
close () {
if (this.socket) {
this.socket.close()
this.socket = null
}
}
start (cb) {
if (this.socket) {
try {
this.socket.close()
} catch (e) {}
}
this.event.trigger('connecting', [])
this.socket = new WebSocket('ws://localhost:' + this.port, 'echo-protocol') // eslint-disable-line
this.socket.addEventListener('open', (event) => {
this.connected = true
this.event.trigger('connected', [event])
cb()
})
this.socket.addEventListener('message', (event) => {
var data = JSON.parse(event.data)
if (data.type === 'reply') {
if (this.callbacks[data.id]) {
this.callbacks[data.id](data.error, data.result)
delete this.callbacks[data.id]
}
this.event.trigger('replied', [data])
} else if (data.type === 'notification') {
this.event.trigger('notified', [data])
} else if (data.type === 'system') {
if (data.error) {
this.event.trigger('system', [{
error: data.error
}])
}
}
})
this.socket.addEventListener('error', (event) => {
this.errored(event)
cb(event)
})
this.socket.addEventListener('close', (event) => {
if (event.wasClean) {
this.connected = false
this.event.trigger('closed', [event])
} else {
this.errored(event)
}
this.socket = null
})
}
async receiveResponse (requestId) {
return new Promise((resolve, reject) => {
this.event.register('replied', (data) => {
if (data.id === requestId) {
if (data.error) reject(data.error)
else resolve(data.result)
}
})
})
}
errored (event) {
function remixdDialog () {
return yo`<div>Connection to Remixd closed. Localhost connection not available anymore.</div>`
}
if (this.connected) {
modalDialog('Lost connection to Remixd!', remixdDialog(), {}, { label: '' })
}
this.connected = false
this.socket = null
this.event.trigger('errored', [event])
}
call (service, fn, args, callback) {
return new Promise((resolve, reject) => {
this.ensureSocket((error) => {
if (error) {
callback && typeof callback === 'function' && callback(error)
reject(error)
return
}
if (this.socket && this.socket.readyState === this.socket.OPEN) {
var data = this.format(service, fn, args)
this.callbacks[data.id] = callback
this.socket.send(JSON.stringify(data))
resolve(data.id)
} else {
callback && typeof callback === 'function' && callback('Socket not ready. state:' + this.socket.readyState)
reject(new Error('Socket not ready. state:' + this.socket.readyState))
}
})
})
}
ensureSocket (cb) {
if (this.socket) return cb(null, this.socket)
this.start((error) => {
if (error) {
cb(error)
} else {
cb(null, this.socket)
}
})
}
format (service, fn, args) {
var data = {
id: this.callid,
service: service,
fn: fn,
args: args
}
this.callid++
return data
}
}
module.exports = Remixd

@ -1,64 +0,0 @@
import { Storage } from '@remix-project/remix-lib'
import { joinPath } from './lib/helper'
import yo from 'yo-yo'
const modalDialogCustom = require('./app/ui/modal-dialog-custom')
/*
Migrating the files to the BrowserFS storage instead or raw localstorage
*/
export default (fileProvider) => {
const fileStorage = new Storage('sol:')
const flag = 'status'
const fileStorageBrowserFS = new Storage('remix_browserFS_migration:')
if (fileStorageBrowserFS.get(flag) === 'done') return
fileStorage.keys().forEach((path) => {
if (path !== '.remix.config') {
const content = fileStorage.get(path)
fileProvider.set(path, content)
// TODO https://github.com/ethereum/remix-ide/issues/2377
// fileStorage.remove(path) we don't want to remove it as we are still supporting the old version
}
})
fileStorageBrowserFS.set(flag, 'done')
}
export async function migrateToWorkspace (fileManager, filePanel) {
const browserProvider = fileManager.getProvider('browser')
const workspaceProvider = fileManager.getProvider('workspace')
const files = await browserProvider.copyFolderToJson('/')
if (Object.keys(files).length === 0) {
// we don't have any root file, only .workspaces
// don't need to create a workspace
throw new Error('No file to migrate')
}
if (Object.keys(files).length === 1 && files['/.workspaces']) {
// we don't have any root file, only .workspaces
// don't need to create a workspace
throw new Error('No file to migrate')
}
const workspaceName = 'workspace_migrated_' + Date.now()
await filePanel.processCreateWorkspace(workspaceName)
filePanel.getWorkspaces() // refresh list
const workspacePath = joinPath('browser', workspaceProvider.workspacesPath, workspaceName)
await populateWorkspace(workspacePath, files, browserProvider)
return workspaceName
}
const populateWorkspace = async (workspace, json, browserProvider) => {
for (const item in json) {
const isFolder = json[item].content === undefined
if (isFolder && item === '/.workspaces') continue // we don't want to replicate this one.
if (isFolder) {
browserProvider.createDir(joinPath(workspace, item))
await populateWorkspace(workspace, json[item].children, browserProvider)
} else {
await browserProvider.set(joinPath(workspace, item), json[item].content, (err) => {
if (err && err.message) {
modalDialogCustom.alert(yo`<div>There was an error migrating your files:${err.message} <div>Please use the ‘Download all Files' action, clear the local storage and re-import your files manually or use the 'Restore files' action.</div></div>`)
}
})
}
}
}

@ -1,47 +0,0 @@
const yo = require('yo-yo')
const publishOnSwarm = require('./lib/publishOnSwarm')
const publishOnIpfs = require('./lib/publishOnIpfs')
const modalDialogCustom = require('./app/ui/modal-dialog-custom')
export default function publish (storage, fileProvider, fileManager, contract) {
if (contract) {
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 {
if (storage === 'swarm') {
publishOnSwarm(contract, fileManager, function (err, uploaded) {
if (err) {
try {
err = JSON.stringify(err)
} catch (e) {}
console.log(`Failed to publish metadata file to swarm, please check the Swarm gateways is available ( swarm-gateways.net ) ${err}`)
} else {
var result = yo`<div>${uploaded.map((value) => {
return yo`<div><b>${value.filename}</b> : <pre>${value.output.url}</pre></div>`
})}</div>`
modalDialogCustom.alert(`Published ${contract.name}'s Metadata`, yo`<span>Metadata of "${contract.name.toLowerCase()}" was published successfully.<br> <pre>${result}</pre> </span>`)
}
}, (item) => { // triggered each time there's a new verified publish (means hash correspond)
fileProvider.addExternal('swarm/' + item.hash, item.content)
})
} else {
publishOnIpfs(contract, fileManager, function (err, uploaded) {
if (err) {
try {
err = JSON.stringify(err)
} catch (e) {}
modalDialogCustom.alert(yo`<span>Failed to publish metadata file to ${storage}, please check the ${storage} gateways is available.<br />
${err}</span>`)
} else {
var result = yo`<div>${uploaded.map((value) => {
return yo`<div><b>${value.filename}</b> : <pre>${value.output.url.replace('dweb:/ipfs/', 'ipfs://')}</pre></div>`
})}</div>`
modalDialogCustom.alert(`Published ${contract.name}'s Metadata`, yo`<span>Metadata of "${contract.name.toLowerCase()}" was published successfully.<br> <pre>${result}</pre> </span>`)
}
}, (item) => { // triggered each time there's a new verified publish (means hash correspond)
fileProvider.addExternal('ipfs/' + item.hash, item.content)
})
}
}
}
}

@ -1,20 +1,20 @@
/* global localStorage, fetch */ /* global localStorage, fetch */
import { PluginManager } from '@remixproject/engine' import { PluginManager } from '@remixproject/engine'
import { IframePlugin } from '@remixproject/engine-web'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import QueryParams from './lib/query-params' import QueryParams from './lib/query-params'
import { PermissionHandler } from './app/ui/persmission-handler' import { PermissionHandler } from './app/ui/persmission-handler'
import { IframePlugin } from '@remixproject/engine-web'
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
const requiredModules = [ // services + layout views + system views const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic'] 'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout', 'modal']
const dependentModules = ['git', 'hardhat', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd) const dependentModules = ['git', 'hardhat', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd)
export function isNative (name) { export function isNative (name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting'] const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'modal']
return nativePlugins.includes(name) || requiredModules.includes(name) return nativePlugins.includes(name) || requiredModules.includes(name)
} }
@ -78,6 +78,7 @@ export class RemixAppManager extends PluginManager {
onPluginActivated (plugin) { onPluginActivated (plugin) {
this.pluginLoader.set(plugin, this.actives) this.pluginLoader.set(plugin, this.actives)
this.event.emit('activate', plugin) this.event.emit('activate', plugin)
this.emit('activate', plugin)
if (!requiredModules.includes(plugin.name)) _paq.push(['trackEvent', 'pluginManager', 'activate', plugin.name]) if (!requiredModules.includes(plugin.name)) _paq.push(['trackEvent', 'pluginManager', 'activate', plugin.name])
} }
@ -131,6 +132,7 @@ export class RemixAppManager extends PluginManager {
} }
return plugins.map(plugin => { return plugins.map(plugin => {
return new IframePlugin(plugin) return new IframePlugin(plugin)
// return new IframeReactPlugin(plugin)
}) })
} }

@ -1,285 +0,0 @@
const csjs = require('csjs-inject')
var css = csjs`
.instanceTitleContainer {
display: flex;
align-items: center;
}
.calldataInput{
height: 32px;
}
.title {
display: flex;
justify-content: space-between;
font-size: 11px;
width: 100%;
overflow: hidden;
word-break: break-word;
line-height: initial;
overflow: visible;
padding: 0 0 8px;
margin: 0;
background: none;
border: none;
}
.title button {
background: none;
border: none;
}
.titleLine {
display: flex;
align-items: baseline;
}
.titleText {
word-break: break-word;
width: 100%;
border: none;
overflow: hidden;
}
.spanTitleText {
line-height: 12px;
padding: 0;
font-size: 11px;
width:100%;
border: none;
background: none;
text-transform: uppercase;
overflow: hidden;
}
.inputGroupText {
width: 100%;
}
.title .copy {
color: var(--primary);
}
.titleExpander {
padding: 5px 7px;
}
.nameNbuts {
display: contents;
flex-wrap: nowrap;
width: 100%;
}
.instance {
display: block;
flex-direction: column;
margin-bottom: 12px;
background: none;
border-radius: 2px;
}
.instance.hidesub {
border-bottom: 1px solid;
}
.instance.hidesub .title {
display: flex;
}
.instance.hidesub .udappClose {
display: flex;
}
.instance.hidesub > * {
display: none;
}
.methCaret {
min-width: 12px;
width: 12px;
margin-left: 4px;
cursor: pointer;
font-size: 16px;
line-height: 0.6;
vertical-align: middle;
padding: 0;
}
.cActionsWrapper {
border-top-left-radius: 0;
border-bottom-left-radius: 0.25rem;
border-top-rightt-radius: 0;
border-bottom-right-radius: 0.25rem;
padding: 8px 10px 7px;
}
.group:after {
content: "";
display: table;
clear: both;
}
.buttonsContainer {
margin-top: 2%;
display: flex;
overflow: hidden;
}
.instanceButton {
height: 32px;
border-radius: 3px;
white-space: nowrap;
font-size: 11px;
overflow: hidden;
text-overflow: ellipsis;
}
.closeIcon {
font-size: 12px;
cursor: pointer;
margin-left: 5px;
}
.udappClose {
display: flex;
justify-content: flex-end;
}
.contractProperty {
width:100%;
}
.contractProperty.hasArgs input {
padding: .36em;
border-radius: 5px;
}
.contractProperty .contractActionsContainerSingle input{
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.contractProperty button {
min-width: 100px;
width: 100px;
margin:0;
word-break: inherit;
}
.contractProperty button:disabled {
cursor: not-allowed;
background-color: white;
border-color: lightgray;
}
.contractProperty.constant button {
min-width: 100px;
width: 100px;
margin:0;
word-break: inherit;
outline: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.contractProperty > .value {
box-sizing: border-box;
float: left;
align-self: center;
margin-left: 4px;
}
.contractActionsContainer {
width: 100%;
margin-bottom: 8px;
}
.contractActionsContainerSingle {
display: flex;
width: 100%;
}
.contractActionsContainerSingle i {
line-height: 2;
}
.contractActionsContainerMulti {
display:none;
width: 100%;
}
.contractActionsContainerMultiInner {
width: 100%;
padding: 16px 8px 16px 14px;
border-radius: 3px;
margin-bottom: 8px;
}
.multiHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
text-align: left;
font-size: 10px;
font-weight: bold;
}
.contractActionsContainerMultiInner .multiTitle {
padding-left: 10px;
}
.contractProperty .multiTitle {
padding: 0;
line-height: 16px;
display: inline-block;
font-size: 12px;
font-weight: bold;
cursor: default;
}
.contractProperty .contractActionsContainerMultiInner .multiArg label{
text-align: right;
}
.multiHeader .methCaret {
float: right;
margin-right: 0;
}
.contractProperty.constant .multiTitle {
display: inline-block;
width: 90%;
/* font-size: 10px; */
height: 25px;
padding-left: 20px;
font-weight: bold;
line-height: 25px;
cursor: default;
}
.multiArg {
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: 4px;
}
.multiArg input{
padding: 5px;
}
.multiArg label {
width: auto;
padding: 0;
margin: 0 4px 0 0;
font-size: 10px;
line-height: 12px;
text-align: right;
word-break: initial;
}
.multiArg button {
max-width: 100px;
border-radius: 3px;
border-width: 1px;
width: inherit;
}
.multiHeader button {
display: inline-block;
width: 94%;
}
.hasArgs .multiArg input {
border-left: 1px solid #dddddd;
width: 67%;
}
.hasArgs input {
display: block;
height: 32px;
border: 1px solid #dddddd;
padding: .36em;
border-left: none;
padding: 8px 8px 8px 10px;
font-size: 10px !important;
}
.hasArgs button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 11px;
}
.hasArgs .contractActionsContainerMulti button {
border-radius: 3px;
}
.contractActionsContainerMultiInner .multiArg i {
padding-right: 10px;
}
.hideWarningsContainer {
display: flex;
align-items: center;
margin-left: 2%
}
`
module.exports = css

@ -1,16 +0,0 @@
'use strict'
var test = require('tape')
var Compiler = require('@remix-project/remix-solidity').Compiler
test('compiler.compile smoke', function (t) {
t.plan(1)
var noop = function () {}
var fakeImport = function (url, cb) { cb('Not implemented') }
var compiler = new Compiler(fakeImport)
compiler.compileJSON = noop
compiler.compile({ 'test': '' }, 'test')
t.ok(compiler)
})

@ -1,52 +0,0 @@
'use strict'
var modalDialogCustom
if (typeof window !== 'undefined') {
modalDialogCustom = require('../app/ui/modal-dialog-custom')
}
// ^ this class can be load in a non browser context when running node unit testing.
// should not load UI in that case
// Allowing window to be overriden for testing
function GistHandler (_window) {
if (_window !== undefined) {
modalDialogCustom = _window
}
this.handleLoad = function (params, cb) {
if (!cb) cb = () => {}
var loadingFromGist = false
var gistId
if (params['gist'] === '') {
loadingFromGist = true
modalDialogCustom.prompt(
'Load a Gist',
'Enter the URL or ID of the Gist you would like to load.',
null,
target => {
if (target !== '') {
gistId = getGistId(target)
if (gistId) {
cb(gistId)
}
}
}
)
return loadingFromGist
} else {
gistId = params['gist']
loadingFromGist = !!gistId
}
if (loadingFromGist) {
cb(gistId)
}
return loadingFromGist
}
function getGistId (str) {
var idr = /[0-9A-Fa-f]{8,}/
var match = idr.exec(str)
return match ? match[0] : null
}
}
module.exports = GistHandler

@ -1,5 +0,0 @@
'use strict'
require('./compiler-test')
require('./gist-handler-test')
require('./query-params-test')

@ -1,23 +0,0 @@
'use strict'
var test = require('tape')
var QueryParams = require('../src/lib/query-params')
test('queryParams.get', function (t) {
t.plan(2)
var fakeWindow = {location: {hash: '#wat=sup&foo=bar', search: ''}}
var params = new QueryParams(fakeWindow).get()
t.equal(params.wat, 'sup')
t.equal(params.foo, 'bar')
})
test('queryParams.update', function (t) {
t.plan(1)
var fakeWindow = {location: {hash: '#wat=sup', search: ''}}
var qp = new QueryParams(fakeWindow)
qp.update({foo: 'bar'})
t.equal(fakeWindow.location.hash, '#wat=sup&foo=bar')
})

@ -4,6 +4,7 @@
"jsx": "react", "jsx": "react",
"allowJs": true, "allowJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"types": ["node", "jest"], "types": ["node", "jest"],
"module": "es6", "module": "es6",

@ -1,153 +0,0 @@
# Current "Best Practice" Conventions
- Coding style is defined in [.eslintrc](https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/.eslintrc).
- `ES6 class` rather than ES5 to create class.
- CSS declaration using `csjs-inject`.
- CSS files:
 **if** the CSS section of an UI component is too important, CSS declarations should be put in a different file and in a different folder.
The folder should be named `styles` and the file should be named with the extension `-styles.css`.
 e.g: `file-explorer.js` being an UI component `file-explorer-styles.css` is created in the `styles` folder right under `file-explorer.js`
**if** the CSS section of an UI component is rather limited it is preferable to put it in the corresponding JS file.
- HTML declaration using `yo-yo`.
- A module trigger events using `event` property:
 `self.event = new EventManager()`.
Events can then be triggered:
 `self.event.trigger('eventName', [param1, param2])`
- `self._view` is the HTML view renderered by `yo-yo` in the `render` function.
- `render()` this function should be called at the first rendering (make sure that the returned node element is put on the DOM), and should *not* by called again from outside the component.
- `update()` call this function to update the DOM when the state of the component has changed (this function must be called after the initial call to `render()`).
- for all functions / properties, prefixing by underscore (`_`) means the scope is private, and they should **not** be accessed not changed from outside the component.
- constructor arguments: There is no fixed rule whether it is preferrable to use multiples arguments or a single option *{}* argument (or both).
We recommend:
- use a specific slot for **obligatory** arguments and/or for complex arguments (meaning not boolean, not string, etc...).
- put arguments in an option *{}* for non critical and for optionnal arguments.
- if a component has more than 4/5 parameters, it is recommended to find a way to group some in one or more *opt* arguments.
- look them up, discuss them, update them.
   
## Module Definition (example)
```js
// user-card.js
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var EventManager = require('remix-lib').EventManager
var css = csjs`
.userCard {
position : relative;
box-sizing : border-box;
display : flex;
flex-direction : column;
align-items : center;
border : 1px solid black;
width : 400px;
padding : 50px;
}
.clearFunds { background-color: lightblue; }
`
class UserCard {
constructor (api, events, opts = {}) {
var self = this
self.event = new EventManager()
self.opts = opts
self._api = api
self._consumedEvents = events
self._view = undefined
events.funds.register('fundsChanged', function (amount) {
if (amount < self.state._funds) self.state.totalSpend += self.state._funds - amount
self.state._funds = amount
self.render()
})
self.event.trigger('eventName', [param1, param2])
}
render () {
var self = this
var view = yo`
<div class=${css.userCard}>
<h1> @${self.state._nickname} </h1>
<h2> Welcome, ${self.state.title || ''} ${self.state.name || 'anonymous'} ${self.state.surname} </h2>
<ul> <li> User Funds: $${self.state._funds} </li> </ul>
<ul> <li> Spent Funds: $${self.state.totalSpend} </li> </ul>
<button class=${css.clearFunds} onclick=${e=>self._spendAll.call(self, e)}> spend all funds </button>
</div>
`
if (!self._view) {
self._view = view
}
return self._view
}
update () {
yo.update(this._view, this.render())
}
setNickname (name) {
this._nickname = name
}
getNickname () {
var self = this
return `@${self.state._nickname}`
}
getFullName () {
var self = this
return `${self.state.title} ${self.state.name} ${self.state.surname}`
}
_spendAll (event) {
var self = this
self._appAPI.clearUserFunds()
}
_constraint (msg) { throw new Error(msg) }
}
module.exports = UserCard
```
## Module Usage (example)
```js
/*****************************************************************************/
// 1. SETUP CONTEXT
var EventManager = require('remix-lib').EventManager
var funds = { event: new EventManager() }
var userfunds = 15
function getUserFunds () { return userfunds }
function clearUserFunds () {
var spent = userfunds
userfunds = 0
console.log(`all funds of ${usercard.getFullName()} were spent.`)
funds.event.trigger('fundsChanged', [userfunds])
return spent
}
setInterval(function () {
userfunds++
funds.event.trigger('fundsChanged', [userfunds])
}, 100)
/*****************************************************************************/
// 2. EXAMPLE USAGE
var UserCard = require('./user-card')
var usercard = new UserCard(
{ getUserFunds, clearUserFunds },
{ funds: funds.event },
{
title: 'Dr.',
name: 'John',
surname: 'Doe',
nickname: 'johndoe99'
})
var el = usercard.render()
document.body.appendChild(el)
setTimeout(function () {
userCard.setNickname('new name')
usercard.update()
}, 5000)
```

@ -4,3 +4,4 @@ export { FetchAndCompile } from './lib/compiler-fetch-and-compile'
export { CompilerImports } from './lib/compiler-content-imports' export { CompilerImports } from './lib/compiler-content-imports'
export { CompilerArtefacts } from './lib/compiler-artefacts' export { CompilerArtefacts } from './lib/compiler-artefacts'
export { EditorContextListener } from './lib/editor-context-listener' export { EditorContextListener } from './lib/editor-context-listener'
export { GistHandler } from './lib/gist-handler'

@ -69,7 +69,7 @@ export class FetchAndCompile extends Plugin {
let data let data
try { try {
data = await this.call('source-verification', 'fetchByNetwork', contractAddress, network.id) data = await this.call('sourcify', 'fetchByNetwork', contractAddress, network.id)
} catch (e) { } catch (e) {
setTimeout(_ => this.emit('notFound', contractAddress), 0) // plugin framework returns a time out error although it actually didn't find the source... setTimeout(_ => this.emit('notFound', contractAddress), 0) // plugin framework returns a time out error although it actually didn't find the source...
this.unresolvedAddresses.push(contractAddress) this.unresolvedAddresses.push(contractAddress)

@ -0,0 +1,138 @@
/* global fetch */
'use strict'
import { Plugin } from '@remixproject/engine'
interface StringByString {
[key: string]: string;
}
const profile = {
name: 'gistHandler',
methods: ['load'],
events: [],
version: '0.0.1'
}
export class GistHandler extends Plugin {
constructor () {
super(profile)
}
async handleLoad (gistId: String | null, cb: Function) {
if (!cb) cb = () => {}
var loadingFromGist = false
if (!gistId) {
loadingFromGist = true
let value
try {
value = await (() => {
return new Promise((resolve, reject) => {
const modalContent = {
id: 'gisthandler',
title: 'Load a Gist',
message: 'Enter the ID of the Gist or URL you would like to load.',
modalType: 'prompt',
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: (value) => {
setTimeout(() => resolve(value), 0)
},
cancelFn: () => {
setTimeout(() => reject(new Error('Canceled')), 0)
},
hideFn: () => {
setTimeout(() => reject(new Error('Hide')), 0)
}
}
this.call('modal', 'modal', modalContent)
})
})()
} catch (e) {
// the modal has been canceled
return
}
if (value !== '') {
gistId = getGistId(value)
if (gistId) {
cb(gistId)
} else {
const modalContent = {
id: 'gisthandler',
title: 'Gist load error',
message: 'Error while loading gist. Please provide a valid Gist ID or URL.'
}
this.call('modal', 'alert', modalContent)
}
} else {
const modalContent = {
id: 'gisthandlerEmpty',
title: 'Gist load error',
message: 'Error while loading gist. Id cannot be empty.'
}
this.call('modal', 'alert', modalContent)
}
return loadingFromGist
} else {
loadingFromGist = !!gistId
}
if (loadingFromGist) {
cb(gistId)
}
return loadingFromGist
}
load (gistId: String | null) {
const self = this
return self.handleLoad(gistId, async (gistId: String | null) => {
let data: any
try {
data = await (await fetch(`https://api.github.com/gists/${gistId}`)).json() as any
if (!data.files) {
const modalContent = {
id: 'gisthandler',
title: 'Gist load error',
message: data.message,
modalType: 'alert',
okLabel: 'OK'
}
await this.call('modal', 'modal', modalContent)
return
}
} catch (e: any) {
const modalContent = {
id: 'gisthandler',
title: 'Gist load error',
message: e.message
}
await this.call('modal', 'alert', modalContent)
return
}
const obj: StringByString = {}
Object.keys(data.files).forEach((element) => {
const path = element.replace(/\.\.\./g, '/')
obj['/' + 'gist-' + gistId + '/' + path] = data.files[element]
})
this.call('fileManager', 'setBatchFiles', obj, 'workspace', true, async (errorSavingFiles: any) => {
if (errorSavingFiles) {
const modalContent = {
id: 'gisthandler',
title: 'Gist load error',
message: errorSavingFiles.message || errorSavingFiles
}
this.call('modal', 'alert', modalContent)
}
})
})
}
}
const getGistId = (str) => {
var idr = /[0-9A-Fa-f]{8,}/
var match = idr.exec(str)
return match ? match[0] : null
}

@ -3,3 +3,4 @@ export { dispatchModalContext } from './lib/remix-app/context/context'
export { ModalProvider } from './lib/remix-app/context/provider' export { ModalProvider } from './lib/remix-app/context/provider'
export { AppModal } from './lib/remix-app/interface/index' export { AppModal } from './lib/remix-app/interface/index'
export { AlertModal } from './lib/remix-app/interface/index' export { AlertModal } from './lib/remix-app/interface/index'
export { ModalTypes } from './lib/remix-app/types/index'

@ -20,7 +20,8 @@
z-index: 9998; z-index: 9998;
} }
.dragbar:hover, .dragbar.ondrag{ .dragbar:hover,
.dragbar.ondrag {
background-color: var(--secondary); background-color: var(--secondary);
cursor: col-resize; cursor: col-resize;
} }

@ -16,16 +16,21 @@ const DragBar = (props: IRemixDragBarUi) => {
const nodeRef = React.useRef(null) // fix for strictmode const nodeRef = React.useRef(null) // fix for strictmode
useEffect(() => { useEffect(() => {
// arbitrary time out to wait the the UI to be completely done setDragBarPosX(offset + (props.hidden ? 0 : props.refObject.current.offsetWidth))
setTimeout(() => { }, [props.hidden, offset])
const handleResize = () => {
setOffSet(props.refObject.current.offsetLeft) setOffSet(props.refObject.current.offsetLeft)
setDragBarPosX(offset + props.refObject.current.offsetWidth) setDragBarPosX(props.refObject.current.offsetLeft + props.refObject.current.offsetWidth)
}, 1000) }
}, [])
useEffect(() => { useEffect(() => {
setDragBarPosX(offset + (props.hidden ? 0 : props.refObject.current.offsetWidth)) window.addEventListener('resize', handleResize)
}, [props.hidden, offset]) // TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() =>
handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize)
}, [])
function stopDrag (e: MouseEvent, data: any) { function stopDrag (e: MouseEvent, data: any) {
setDragState(false) setDragState(false)

@ -1,5 +1,6 @@
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import './style/remix-app.css' import './style/remix-app.css'
import { RemixUIMainPanel } from '@remix-ui/panel'
import RemixSplashScreen from './components/splashscreen' import RemixSplashScreen from './components/splashscreen'
import MatomoDialog from './components/modals/matomo' import MatomoDialog from './components/modals/matomo'
import OriginWarning from './components/modals/origin-warning' import OriginWarning from './components/modals/origin-warning'
@ -62,6 +63,13 @@ const RemixApp = (props: IRemixAppUi) => {
props.app.sidePanel.events.on('showing', () => { props.app.sidePanel.events.on('showing', () => {
setHideSidePanel(false) setHideSidePanel(false)
}) })
props.app.layout.event.on('minimizesidepanel', () => {
// the 'showing' event always fires from sidepanel, so delay this a bit
setTimeout(() => {
setHideSidePanel(true)
}, 1000)
})
} }
const components = { const components = {
@ -75,7 +83,8 @@ const RemixApp = (props: IRemixAppUi) => {
settings: props.app.settings, settings: props.app.settings,
showMatamo: props.app.showMatamo, showMatamo: props.app.showMatamo,
appManager: props.app.appManager, appManager: props.app.appManager,
modal: props.app.modal modal: props.app.modal,
layout: props.app.layout
} }
return ( return (
@ -88,8 +97,9 @@ const RemixApp = (props: IRemixAppUi) => {
{components.iconPanel} {components.iconPanel}
{components.sidePanel} {components.sidePanel}
<DragBar minWidth={250} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar> <DragBar minWidth={250} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar>
{components.mainPanel} <div id="main-panel" data-id="remixIdeMainPanel" className='mainpanel'>
<RemixUIMainPanel></RemixUIMainPanel>
</div>
</div> </div>
{components.hiddenPanel} {components.hiddenPanel}
<AppDialogs></AppDialogs> <AppDialogs></AppDialogs>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save