Merge pull request #1342 from ethereum/remixd_terminal

Terminal in React
pull/1634/head^2
Rob 3 years ago committed by GitHub
commit af5c51081b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 2
      apps/remix-ide-e2e/src/commands/addFile.ts
  3. 1
      apps/remix-ide-e2e/src/commands/executeScript.ts
  4. 7
      apps/remix-ide-e2e/src/commands/journalLastChildIncludes.ts
  5. 3
      apps/remix-ide-e2e/src/commands/testFunction.ts
  6. 1
      apps/remix-ide-e2e/src/commands/verifyContracts.ts
  7. 10
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  8. 2
      apps/remix-ide-e2e/src/tests/compiler_api.test.ts
  9. 3
      apps/remix-ide-e2e/src/tests/debugger.spec.ts
  10. 4
      apps/remix-ide-e2e/src/tests/defaultLayout.test.ts
  11. 6
      apps/remix-ide-e2e/src/tests/editor.spec.ts
  12. 8
      apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts
  13. 1
      apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts
  14. 22
      apps/remix-ide-e2e/src/tests/solidityUnittests.spec.ts
  15. 34
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  16. 13
      apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts
  17. 28
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  18. 6
      apps/remix-ide/src/app.js
  19. 2
      apps/remix-ide/src/app/components/main-panel.js
  20. 835
      apps/remix-ide/src/app/panels/terminal.js
  21. 1
      apps/remix-ide/src/lib/cmdInterpreterAPI.js
  22. 1
      apps/solidity-compiler/src/app/compiler.ts
  23. 1
      libs/remix-ui/solidity-compiler/src/lib/actions/compiler.ts
  24. 4
      libs/remix-ui/terminal/.babelrc
  25. 19
      libs/remix-ui/terminal/.eslintrc
  26. 7
      libs/remix-ui/terminal/README.md
  27. 4
      libs/remix-ui/terminal/package.json
  28. 1
      libs/remix-ui/terminal/src/index.ts
  29. 152
      libs/remix-ui/terminal/src/lib/actions/terminalAction.ts
  30. 53
      libs/remix-ui/terminal/src/lib/commands.ts
  31. 16
      libs/remix-ui/terminal/src/lib/components/ChechTxStatus.tsx
  32. 62
      libs/remix-ui/terminal/src/lib/components/Context.tsx
  33. 61
      libs/remix-ui/terminal/src/lib/components/RenderCall.tsx
  34. 56
      libs/remix-ui/terminal/src/lib/components/RenderKnownTransactions.tsx
  35. 50
      libs/remix-ui/terminal/src/lib/components/RenderUnknownTransactions.tsx
  36. 165
      libs/remix-ui/terminal/src/lib/components/Table.tsx
  37. 29
      libs/remix-ui/terminal/src/lib/custom-hooks/useKeyPress.tsx
  38. 195
      libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts
  39. 421
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.css
  40. 574
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  41. 29
      libs/remix-ui/terminal/src/lib/terminalWelcome.tsx
  42. 28
      libs/remix-ui/terminal/src/lib/types/terminalTypes.ts
  43. 42
      libs/remix-ui/terminal/src/lib/utils/utils.ts
  44. 16
      libs/remix-ui/terminal/src/lib/utils/wrapScript.ts
  45. 16
      libs/remix-ui/terminal/tsconfig.json
  46. 13
      libs/remix-ui/terminal/tsconfig.lib.json
  47. 3
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  48. 3
      nx.json
  49. 5
      package.json
  50. 5
      tsconfig.base.json
  51. 16
      workspace.json

1
.gitignore vendored

@ -30,6 +30,7 @@ soljson.js
*.launch
.settings/
*.sublime-workspace
.vscode/
# IDE - VSCode
.vscode/*

@ -17,7 +17,7 @@ function addFile (browser: NightwatchBrowser, name: string, content: NightwatchC
browser.clickLaunchIcon('udapp')
.clickLaunchIcon('filePanel')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
.click('.newFile')
.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)

@ -6,6 +6,7 @@ class ExecuteScript extends EventEmitter {
this.api
.clearEditableContent('*[data-id="terminalCliInput"]')
.click('*[data-id="terminalCli"]')
.setValue('*[data-id="terminalCliInput"]', [this.api.Keys.CONTROL, 'a', this.api.Keys.DELETE])
.sendKeys('*[data-id="terminalCliInput"]', script)
.sendKeys('*[data-id="terminalCliInput"]', this.api.Keys.ENTER)
.sendKeys('*[data-id="terminalCliInput"]', this.api.Keys.ENTER)

@ -7,11 +7,12 @@ import EventEmitter from 'events'
class JournalLastChildIncludes extends EventEmitter {
command (this: NightwatchBrowser, val: string): NightwatchBrowser {
this.api
.waitForElementVisible('*[data-id="terminalJournal"] > div:last-child', 10000)
.getText('*[data-id="terminalJournal"] > div:last-child', (result) => {
.waitForElementVisible('*[data-id="terminalJournal"]', 10000)
.pause(1000)
.getText('*[data-id="terminalJournal"]', (result) => {
console.log('JournalLastChildIncludes', result.value)
if (typeof result.value === 'string' && result.value.indexOf(val) === -1) return this.api.assert.fail(`wait for ${val} in ${result.value}`)
else this.api.assert.ok(true, `<*[data-id="terminalJournal"] > div:last-child> contains ${val}.`)
else this.api.assert.ok(true, `<*[data-id="terminalJournal"]> contains ${val}.`)
this.emit('complete')
})
return this

@ -24,8 +24,8 @@ class TestFunction extends EventEmitter {
.perform((done) => {
browser.waitForElementVisible(`[data-id="block_tx${txHash}"]`, 60000)
.click(`[data-id="block_tx${txHash}"]`)
.pause(3000)
.waitForElementVisible(`*[data-id="txLoggerTable${txHash}"]`, 60000)
.pause(10000)
// fetch and format transaction logs as key => pair object
.elements('css selector', `*[data-shared="key_${txHash}"]`, (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement) {
@ -58,7 +58,6 @@ class TestFunction extends EventEmitter {
.perform(() => {
Object.keys(expectedValue).forEach(key => {
let equal = false
try {
const receivedValue = JSON.parse(logs[key])

@ -17,6 +17,7 @@ function verifyContracts (browser: NightwatchBrowser, compiledContractNames: str
browser
.clickLaunchIcon('solidity')
.pause(opts.wait)
.pause(5000)
.waitForElementPresent('*[data-id="compiledContracts"] option', 60000)
.perform((done) => {
if (opts.version) {

@ -81,8 +81,16 @@ module.exports = {
'Deploy and use Ballot using external web3': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="settingsWeb3Mode"]')
.click('option[value="web3"]')
.pause(5000)
.modalFooterOKClick()
.execute(function () {
const env: any = document.getElementById('selectExEnvOptions')
return env.value
}, [], function (result) {
console.log({ result })
browser.assert.ok(result.value === 'web3', 'Web3 Provider not selected')
})
.clickLaunchIcon('solidity')
.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['Ballot'])
.clickLaunchIcon('udapp')

@ -21,7 +21,7 @@ module.exports = {
browser
.addFile('test_jsCompile.js', { content: jsCompile })
.executeScript('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '"languageversion": "0.6.8+commit.0bbfe453"', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '"languageversion":"0.6.8+commit.0bbfe453"', 60000)
.click('*[data-id="terminalClearConsole"]')
},

@ -187,7 +187,8 @@ module.exports = {
browser
.addFile('test_jsGetTrace.js', { content: jsGetTrace })
.executeScript('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'result { "gas": "0x575f", "return": "0x0000000000000000000000000000000000000000000000000000000000000000", "structLogs":', 60000)
.pause(1000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '{"gas":"0x575f","return":"0x0000000000000000000000000000000000000000000000000000000000000000","structLogs":', 60000)
},
'Should call the debugger api: debug': function (browser: NightwatchBrowser) {

@ -50,11 +50,11 @@ module.exports = {
'Toggles Terminal': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="terminalContainer"]')
.assert.visible('div[data-id="terminalContainerDisplay"]')
.assert.elementPresent('div[data-id="terminalContainerDisplay"]')
.click('i[data-id="terminalToggleIcon"]')
.checkElementStyle('div[data-id="terminalToggleMenu"]', 'height', '35px')
.click('i[data-id="terminalToggleIcon"]')
.assert.visible('div[data-id="terminalContainerDisplay"]')
.assert.elementPresent('div[data-id="terminalContainerDisplay"]')
},
'Switch Tabs using tabs icon': function (browser: NightwatchBrowser) {

@ -34,8 +34,8 @@ module.exports = {
browser.waitForElementVisible('*[data-id="editorInput"]')
.waitForElementVisible('*[class="ace_content"]')
.click('*[class="ace_content"]')
.editorScroll('down', 27) // scroll down to line 27 and add the error word
.sendKeys('*[class="ace_text-input"]', 'error')
.pause(2000)
.waitForElementVisible('.ace_error', 120000)
.checkAnnotations('error', 28)
.clickLaunchIcon('udapp')
@ -92,9 +92,13 @@ module.exports = {
.openFile('sourcehighlight.js')
.executeScript('remix.exeCurrent()')
.editorScroll('down', 60)
.pause(1000)
.waitForElementPresent('.highlightLine32', 60000)
.pause(1000)
.checkElementStyle('.highlightLine32', 'background-color', 'rgb(8, 108, 181)')
.pause(1000)
.waitForElementPresent('.highlightLine40', 60000)
.pause(1000)
.checkElementStyle('.highlightLine40', 'background-color', 'rgb(8, 108, 181)')
.waitForElementPresent('.highlightLine50', 60000)
.checkElementStyle('.highlightLine50', 'background-color', 'rgb(8, 108, 181)')

@ -11,6 +11,7 @@ module.exports = {
browser
.addFile('file.js', { content: executeFile })
.executeScript('remix.exeCurrent()')
.pause(1000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'file.js', 60000)
},
@ -72,7 +73,8 @@ module.exports = {
browser
.addFile('readdirFile.js', { content: executeReaddir })
.executeScript('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Test_Folder isDirectory true', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Test_Folder isDirectory', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'true', 5000)
},
'Should execute `remove` api from file manager external api': function (browser: NightwatchBrowser) {
@ -175,8 +177,8 @@ const executeMkdir = `
const executeReaddir = `
const run = async () => {
const result = await remix.call('fileManager', 'readdir', '/')
console.log('Test_Folder isDirectory ', result["Test_Folder"].isDirectory)
const output = result["Test_Folder"].isDirectory
console.log('Test_Folder isDirectory ', output)
}
run()

@ -77,6 +77,7 @@ function checkDeployShouldFail (browser: NightwatchBrowser, callback: VoidFuncti
.clickLaunchIcon('udapp')
.selectContract('test') // deploy lib
.createContract('')
.pause(2000)
.getText('div[class^="terminal"]', (value) => {
console.log('value: ', value)
})

@ -164,6 +164,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_new' })
.pause(5000)
.waitForElementPresent('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.click('*[data-id="workspacesSelect"] option[value="workspace_new"]')
// end of creating
@ -181,6 +183,7 @@ module.exports = {
.clickLaunchIcon('solidityUnitTesting')
.pause(2000)
.verify.attributeEquals('*[data-id="uiPathInput"]', 'value', 'tests')
.pause(2000)
.scrollAndClick('#runTestsTabRunAction')
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]', 60000)
@ -201,13 +204,13 @@ module.exports = {
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]', 60000)
.waitForElementContainsText('#solidityUnittestsOutput', 'tests/hhLogs_test.sol', 60000)
.assert.containsText('#journal > div:nth-child(3) > span > div', 'Before all:')
.assert.containsText('#journal > div:nth-child(3) > span > div', 'Inside beforeAll')
.assert.containsText('#journal > div:nth-child(4) > span > div', 'Check sender:')
.assert.containsText('#journal > div:nth-child(4) > span > div', 'msg.sender is 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.assert.containsText('#journal > div:nth-child(5) > span > div', 'Check int logs:')
.assert.containsText('#journal > div:nth-child(5) > span > div', '10 20')
.assert.containsText('#journal > div:nth-child(5) > span > div', 'Number is 25')
.assert.containsText('#journal > div:nth-child(2) > span', 'Before all:')
.assert.containsText('#journal > div:nth-child(2) > span', 'Inside beforeAll')
.assert.containsText('#journal > div:nth-child(3) > span', 'Check sender:')
.assert.containsText('#journal > div:nth-child(3) > span', 'msg.sender is 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.assert.containsText('#journal > div:nth-child(4) > span', 'Check int logs:')
.assert.containsText('#journal > div:nth-child(4) > span', '10 20')
.assert.containsText('#journal > div:nth-child(4) > span', 'Number is 25')
.openFile('tests/hhLogs_test.sol')
.removeFile('tests/hhLogs_test.sol', 'workspace_new')
},
@ -223,8 +226,8 @@ module.exports = {
.pause(2000)
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementContainsText('#solidityUnittestsOutput', 'tests/ballotFailedLog_test.sol', 60000)
.assert.containsText('#journal > div:nth-child(6) > span > div', 'Check winning proposal:')
.assert.containsText('#journal > div:nth-child(6) > span > div', 'Inside checkWinningProposal')
.assert.containsText('#journal > div:nth-child(5) > span', 'Check winning proposal:')
.assert.containsText('#journal > div:nth-child(5) > span', 'Inside checkWinningProposal')
.openFile('tests/ballotFailedLog_test.sol')
.removeFile('tests/ballotFailedLog_test.sol', 'workspace_new')
},
@ -298,6 +301,7 @@ module.exports = {
.scrollAndClick('[data-id="pluginManagerComponentDeactivateButtonsolidityUnitTesting"]')
.pause(2000)
.scrollAndClick('[data-id="pluginManagerComponentActivateButtonsolidityUnitTesting"]')
.pause(5000)
.clickLaunchIcon('solidityUnitTesting')
.scrollAndClick('#runTestsTabRunAction')
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)

@ -11,6 +11,7 @@ module.exports = {
browser
.waitForElementVisible('*[data-id="terminalCli"]', 10000)
.executeScript('console.log(1 + 1)')
.pause(2000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '2', 60000)
},
@ -30,24 +31,14 @@ module.exports = {
.assert.visible('*[data-id="autoCompletePopUpAutoCompleteItem"]')
},
'Should execute remix.help() command': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="terminalCli"]')
.executeScript('remix.help()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'remix.loadgist(id)', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'remix.loadurl(url)', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'remix.execute(filepath)', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'remix.exeCurrent()', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'remix.help()', 60000)
},
'Async/Await Script': function (browser: NightwatchBrowser) {
browser
.addFile('asyncAwait.js', { content: asyncAwait })
.openFile('asyncAwait.js')
.executeScript('remix.execute(\'asyncAwait.js\')')
.executeScript('remix.execute("asyncAwait.js")')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Waiting Promise', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'result - Promise Resolved', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'result - ', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Promise Resolved', 60000)
},
'Call Remix File Manager from a script': function (browser: NightwatchBrowser) {
@ -62,7 +53,7 @@ module.exports = {
'Call web3.eth.getAccounts() using JavaScript VM': function (browser: NightwatchBrowser) {
browser
.executeScript('web3.eth.getAccounts()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB", "0x617F2E2fD72FD9D5503197092aC168c91465E7f2", "0x17F6AD8Ef982297579C203069C1DbfFE4348c372", "0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C"', 80000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]', 80000)
},
'Call web3.eth.getAccounts() using Web3 Provider': function (browser: NightwatchBrowser) {
@ -72,9 +63,9 @@ module.exports = {
.click('*[data-id="settingsWeb3Mode"]')
.modalFooterOKClick()
.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)
.waitForElementContainsText('*[data-id="terminalJournal"]', '", "', 60000)
.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)
},
'Call Remix File Resolver (external URL) from a script': function (browser: NightwatchBrowser) {
@ -124,15 +115,18 @@ module.exports = {
.clickLaunchIcon('solidity')
.click('*[data-id="compilerContainerCompileBtn"]') // compile Owner
.executeScript('remix.execute(\'deployWithEthersJs.js\')')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Contract Address: 0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Contract Address:', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deployment successful.', 60000)
.addAtAddressInstance('0xd9145CCE52D386f254917e481eB44e9943F39138', true, true)
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.click('*[data-id="universalDappUiTitleExpander"]')
.clickFunction('changeOwner - transact (not payable)', { types: 'address newOwner', values: '0xd9145CCE52D386f254917e481eB44e9943F39138' }) // execute the "changeOwner" function
.waitForElementContainsText('*[data-id="terminalJournal"]', 'previousOwner0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', 60000) // check that the script is logging the event
.waitForElementContainsText('*[data-id="terminalJournal"]', 'newOwner0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
.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"]', 'newOwner', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
.end()
}
}

@ -147,12 +147,13 @@ module.exports = {
.waitForElementPresent('.instance:nth-of-type(3)')
.click('.instance:nth-of-type(3) > div > button')
.clickFunction('g - transact (not payable)')
.pause(5000)
.journalLastChildIncludes('Error provided by the contract:')
.journalLastChildIncludes('CustomError : error description')
.journalLastChildIncludes('Parameters:')
.journalLastChildIncludes('"value": "2",')
.journalLastChildIncludes('"value": "3",')
.journalLastChildIncludes('"value": "error_string_2",')
.journalLastChildIncludes('"value": "2"')
.journalLastChildIncludes('"value": "3"')
.journalLastChildIncludes('"value": "error_string_2"')
.journalLastChildIncludes('"documentation": "param1"')
.journalLastChildIncludes('"documentation": "param2"')
.journalLastChildIncludes('"documentation": "param3"')
@ -170,9 +171,9 @@ module.exports = {
.journalLastChildIncludes('Error provided by the contract:')
.journalLastChildIncludes('CustomError : error description')
.journalLastChildIncludes('Parameters:')
.journalLastChildIncludes('"value": "2",')
.journalLastChildIncludes('"value": "3",')
.journalLastChildIncludes('"value": "error_string_2",')
.journalLastChildIncludes('"value": "2"')
.journalLastChildIncludes('"value": "3"')
.journalLastChildIncludes('"value": "error_string_2"')
.journalLastChildIncludes('"documentation": "param1"')
.journalLastChildIncludes('"documentation": "param2"')
.journalLastChildIncludes('"documentation": "param3"')

@ -19,7 +19,7 @@ module.exports = {
browser
.pause(5000)
.refresh()
.pause(2000)
.pause(5000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('contract Ballot {') !== -1, 'content doesn\'t include Ballot contract')
})
@ -35,19 +35,28 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspaceCreate"]') // create workspace_name
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="workspacesModalDialogModalDialogModalFooter-react"] > span')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_name' })
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.pause(1000)
.click('span[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.pause(1000)
.addFile('test.sol', { content: 'test' })
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
.click('*[data-id="workspaceCreate"]') // create workspace_name_1
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_name_1' })
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.waitForElementVisible('span[data-id="workspacesModalDialog-modal-footer-ok-react"]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('span[data-id="workspacesModalDialog-modal-footer-ok-react"]') })
.pause(2000)
.click('span[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.pause(2000)
.click('*[data-id="workspacesSelect"] option[value="workspace_name"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
},
@ -59,10 +68,15 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextRename"]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextRename"]')['value'] = 'workspace_name_renamed' })
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.pause(2000)
.waitForElementPresent('span[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.click('span[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.pause(2000)
.click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.click('*[data-id="workspacesSelect"] option[value="workspace_name_renamed"]')
.pause(2000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
},
@ -70,8 +84,10 @@ module.exports = {
browser
.click('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.click('*[data-id="workspaceDelete"]') // delete workspace_name_1
.waitForElementVisible('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.click('*[data-id="workspacesModalDialogModalDialogModalFooter-react"] .modal-ok')
.waitForElementVisible('[data-id="workspacesModalDialogModalDialogModalFooter-react"] > span')
.pause(2000)
.click('[data-id="workspacesModalDialogModalDialogModalFooter-react"] > span')
.pause(2000)
.waitForElementNotPresent('*[data-id="workspacesSelect"] option[value="workspace_name_1"]')
.end()
},

@ -287,7 +287,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
registry.put({ api: offsetToLineColumnConverter, name: 'offsettolinecolumnconverter' })
// -------------------Terminal----------------------------------------
makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl))
const terminal = new Terminal(
{ appManager, blockchain },
{
@ -299,9 +299,9 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
newpos = (newpos < height - limitDown) ? newpos : height - limitDown
return height - newpos
}
}
},
registry
)
makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl))
const contextualListener = new ContextualListener({ editor })

@ -31,7 +31,7 @@ export class MainPanel extends AbstractPanel {
render () {
return yo`
<div class=${css.pluginsContainer} data-id="mainPanelPluginsContainer">
<div class=${css.pluginsContainer} data-id="mainPanelPluginsContainer" id='mainPanelPluginsContainer-id'>
${this.view}
</div>`
}

@ -1,29 +1,24 @@
/* global Node, requestAnimationFrame */
/* global Node, requestAnimationFrame */ // eslint-disable-line
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { RemixUiTerminal } from '@remix-ui/terminal' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import * as remixBleach from '../../lib/remixBleach'
const vm = require('vm')
const EventManager = require('../../lib/events')
var yo = require('yo-yo')
var javascriptserialize = require('javascript-serialize')
var jsbeautify = require('js-beautify')
var type = require('component-type')
var vm = require('vm')
var EventManager = require('../../lib/events')
const CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI')
const AutoCompletePopup = require('../ui/auto-complete-popup')
var CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI')
var AutoCompletePopup = require('../ui/auto-complete-popup')
var TxLogger = require('../../app/ui/txLogger')
import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line
const globalRegistry = require('../../global/registry')
const SourceHighlighter = require('../../app/editor/sourceHighlighter')
const GistHandler = require('../../lib/gist-handler')
var csjs = require('csjs-inject')
var css = require('./styles/terminal-styles')
var KONSOLES = []
const KONSOLES = []
function register (api) { KONSOLES.push(api) }
var ghostbar = yo`<div class=${css.ghostbar} bg-secondary></div>`
const profile = {
displayName: 'Terminal',
name: 'terminal',
@ -34,77 +29,72 @@ const profile = {
}
class Terminal extends Plugin {
constructor (opts, api) {
constructor (opts, api, registry) {
super(profile)
var self = this
self.event = new EventManager()
self.blockchain = opts.blockchain
self._api = api
self._opts = opts
self.data = {
this.fileImport = new CompilerImports()
this.gistHandler = new GistHandler()
this.event = new EventManager()
this.registry = registry
this.globalRegistry = globalRegistry
this.element = document.createElement('div')
this.element.setAttribute('class', 'panel')
this.element.setAttribute('id', 'terminal-view')
this.element.setAttribute('data-id', 'terminalContainer-view')
this.eventsDecoder = this.globalRegistry.get('eventsDecoder').api
this.txListener = this.globalRegistry.get('txlistener').api
this.sourceHighlighter = new SourceHighlighter()
this._deps = {
fileManager: this.registry.get('filemanager').api,
editor: this.registry.get('editor').api,
compilersArtefacts: this.registry.get('compilersartefacts').api,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api
}
this.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'
}
this.blockchain = opts.blockchain
this.vm = vm
this._api = api
this._opts = opts
this.config = registry.get('config').api
this.version = packageJson.version
this.data = {
lineLength: opts.lineLength || 80, // ????
session: [],
activeFilters: { commands: {}, input: '' },
filterFns: {}
}
self._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null }
self._components = {}
self._components.cmdInterpreter = new CommandInterpreterAPI(this, null, self.blockchain)
self._components.autoCompletePopup = new AutoCompletePopup(self._opts)
self._components.autoCompletePopup.event.register('handleSelect', function (input) {
const textList = self._view.input.innerText.split(' ')
textList.pop()
textList.push(input)
self._view.input.innerText = textList
self._view.input.focus()
self.putCursor2End(self._view.input)
})
self._commands = {}
self.commands = {}
self._JOURNAL = []
self._jobs = []
self._INDEX = {}
self._INDEX.all = []
self._INDEX.allMain = []
self._INDEX.commands = {}
self._INDEX.commandsMain = {}
self.registerCommand('html', self._blocksRenderer('html'), { activate: true })
self.registerCommand('log', self._blocksRenderer('log'), { activate: true })
self.registerCommand('info', self._blocksRenderer('info'), { activate: true })
self.registerCommand('warn', self._blocksRenderer('warn'), { activate: true })
self.registerCommand('error', self._blocksRenderer('error'), { activate: true })
self.registerCommand('script', function execute (args, scopedCommands, append) {
var script = String(args[0])
self._shell(script, scopedCommands, function (error, output) {
if (error) scopedCommands.error(error)
else if (output) scopedCommands.log(output)
this._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null }
this._components = {}
this._components.cmdInterpreter = new CommandInterpreterAPI(this, null, this.blockchain)
this._components.autoCompletePopup = new AutoCompletePopup(this._opts)
this._commands = {}
this.commands = {}
this._JOURNAL = []
this._jobs = []
this._INDEX = {}
this._INDEX.all = []
this._INDEX.allMain = []
this._INDEX.commands = {}
this._INDEX.commandsMain = {}
if (opts.shell) this._shell = opts.shell // ???
register(this)
this.event.register('debuggingRequested', async (hash) => {
// TODO should probably be in the run module
if (!await this._opts.appManager.isActive('debugger')) await this._opts.appManager.activatePlugin('debugger')
this.call('menuicons', 'select', 'debugger')
this.call('debugger', 'debug', hash)
})
}, { activate: true })
function basicFilter (value, query) { try { return value.indexOf(query) !== -1 } catch (e) { return false } }
self.registerFilter('log', basicFilter)
self.registerFilter('info', basicFilter)
self.registerFilter('warn', basicFilter)
self.registerFilter('error', basicFilter)
self.registerFilter('script', basicFilter)
if (opts.shell) self._shell = opts.shell // ???
register(self)
this.logHtmlResponse = []
this.logResponse = []
}
onActivation () {
this.on('scriptRunner', 'log', (msg) => {
this.commands.log.apply(this.commands, msg.data)
})
this.on('scriptRunner', 'info', (msg) => {
this.commands.info.apply(this.commands, msg.data)
})
this.on('scriptRunner', 'warn', (msg) => {
this.commands.warn.apply(this.commands, msg.data)
})
this.on('scriptRunner', 'error', (msg) => {
this.commands.error.apply(this.commands, msg.data)
})
this.renderComponent()
}
onDeactivation () {
@ -114,690 +104,43 @@ class Terminal extends Plugin {
this.off('scriptRunner', 'error')
}
log (message) {
var command = this.commands[message.type]
if (typeof command === 'function') {
if (typeof message.value === 'string' && message.type === 'html') {
var el = document.createElement('div')
el.innerHTML = remixBleach.sanitize(message.value, {
list: [
'a',
'b',
'p',
'em',
'strong',
'div',
'span',
'ul',
'li',
'ol',
'hr'
]
})
message.value = el
}
command(message.value)
};
}
logHtml (html) {
var command = this.commands.html
if (typeof command === 'function') command(html)
}
focus () {
if (this._view.input) this._view.input.focus()
}
render () {
var self = this
if (self._view.el) return self._view.el
self._view.journal = yo`<div id="journal" class=${css.journal} data-id="terminalJournal"></div>`
self._view.input = yo`
<span class=${css.input} onload=${() => { this.focus() }} onpaste=${paste} onkeydown=${change}></span>
`
self._view.input.setAttribute('spellcheck', 'false')
self._view.input.setAttribute('contenteditable', 'true')
self._view.input.setAttribute('id', 'terminalCliInput')
self._view.input.setAttribute('data-id', 'terminalCliInput')
self._view.input.innerText = '\n'
self._view.cli = yo`
<div id="terminalCli" data-id="terminalCli" class="${css.cli}" onclick=${focusinput}>
<span class=${css.prompt}>${'>'}</span>
${self._view.input}
</div>
`
self._view.icon = yo`
<i onmouseenter=${hover} onmouseleave=${hover} onmousedown=${minimize}
class="mx-2 ${css.toggleTerminal} fas fa-angle-double-down" data-id="terminalToggleIcon"></i>`
self._view.dragbar = yo`
<div onmousedown=${mousedown} class=${css.dragbarHorizontal}></div>`
self._view.pendingTxCount = yo`<div class="mx-2" title='Pending Transactions'>0</div>`
self._view.inputSearch = yo`<input
spellcheck="false"
type="text"
class="border ${css.filter} form-control"
id="searchInput"
onkeydown=${filter}
placeholder="Search with transaction hash or address"
data-id="terminalInputSearch">
</input>`
self._view.bar = yo`
<div class="${css.bar}">
${self._view.dragbar}
<div class="${css.menu} border-top border-dark bg-light" data-id="terminalToggleMenu">
${self._view.icon}
<div class="mx-2" id="clearConsole" data-id="terminalClearConsole" onclick=${clear}>
<i class="fas fa-ban" aria-hidden="true" title="Clear console"
onmouseenter=${hover} onmouseleave=${hover}></i>
</div>
${self._view.pendingTxCount}
<div class=${css.verticalLine}></div>
<div class="pt-1 h-80 mx-3 align-items-center ${css.listenOnNetwork} custom-control custom-checkbox">
<input
class="custom-control-input"
id="listenNetworkCheck"
onchange=${listenOnNetwork}
type="checkbox"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
>
<label
class="pt-1 form-check-label custom-control-label text-nowrap""
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
for="listenNetworkCheck"
>
listen on network
</label>
</div>
<div class=${css.search}>
<i class="fas fa-search ${css.searchIcon} bg-light" aria-hidden="true"></i>
${self._view.inputSearch}
</div>
</div>
</div>
`
self._view.term = yo`
<div class="${css.terminal_container}" tabindex="-1" data-id="terminalContainer" onscroll=${throttle(reattach, 10)} onkeydown=${focusinput}>
${self._components.autoCompletePopup.render()}
<div data-id="terminalContainerDisplay" style="
position: absolute;
height: 100%;
width: 100%;
opacity: 0.1;
z-index: -1;
"></div>
<div class=${css.terminal}>
${self._view.journal}
${self._view.cli}
</div>
</div>
`
self._view.el = yo`
<div class="${css.panel}" style="height: 180px;">
${self._view.bar}
${self._view.term}
</div>
`
setInterval(async () => {
try {
self._view.pendingTxCount.innerHTML = await self.call('udapp', 'pendingTransactionsCount')
} catch (err) {}
}, 1000)
function listenOnNetwork (ev) {
self.event.trigger('listenOnNetWork', [ev.currentTarget.checked])
}
function paste (event) {
const selection = window.getSelection()
if (!selection.rangeCount) return false
event.preventDefault()
event.stopPropagation()
var clipboard = (event.clipboardData || window.clipboardData)
var text = clipboard.getData('text/plain')
text = text.replace(/[^\x20-\xFF]/gi, '') // remove non-UTF-8 characters
var temp = document.createElement('div')
temp.innerHTML = text
var textnode = document.createTextNode(temp.textContent)
selection.getRangeAt(0).insertNode(textnode)
selection.empty()
self.scroll2bottom()
placeCaretAtEnd(event.currentTarget)
}
function placeCaretAtEnd (el) {
el.focus()
var range = document.createRange()
range.selectNodeContents(el)
range.collapse(false)
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
}
function throttle (fn, wait) {
var time = Date.now()
return function debounce () {
if ((time + wait - Date.now()) < 0) {
fn.apply(this, arguments)
time = Date.now()
}
}
}
var css2 = csjs`
.anchor {
position : static;
border-top : 2px dotted blue;
height : 10px;
this.logHtmlResponse.push(html.innerText)
this.renderComponent()
this.resetLogHtml()
}
.overlay {
position : absolute;
width : 100%;
display : flex;
align-items : center;
justify-content : center;
bottom : 0;
right : 15px;
min-height : 20px;
}
.text {
z-index : 2;
color : black;
font-weight : bold;
pointer-events : none;
}
.background {
z-index : 1;
opacity : 0.8;
background-color : #a6aeba;
cursor : pointer;
}
.ul {
padding-left : 20px;
padding-bottom : 5px;
}
`
var text = yo`<div class="${css2.overlay} ${css2.text}"></div>`
var background = yo`<div class="${css2.overlay} ${css2.background}"></div>`
var placeholder = yo`<div class=${css2.anchor}>${background}${text}</div>`
var inserted = false
window.addEventListener('resize', function (event) {
self.event.trigger('resize', [])
self.event.trigger('resize', [])
})
function focusinput (event) {
if (
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey ||
event.key === 'Down' ||
event.key === 'ArrowDown' ||
event.key === 'Up' ||
event.key === 'ArrowUp' ||
event.key === 'Left' ||
event.key === 'ArrowLeft' ||
event.key === 'Right' ||
event.key === 'ArrowRight' ||
event.key === 'Esc' ||
event.key === 'Escape'
) return
refocus()
}
function refocus () {
self._view.input.focus()
reattach({ currentTarget: self._view.term })
delete self.scroll2bottom
self.scroll2bottom()
}
function reattach (event) {
var el = event.currentTarget
var isBottomed = el.scrollHeight - el.scrollTop - el.clientHeight < 30
if (isBottomed) {
if (inserted) {
text.innerText = ''
background.onclick = undefined
if (placeholder.parentElement) self._view.journal.removeChild(placeholder)
}
inserted = false
delete self.scroll2bottom
} else {
if (!inserted) self._view.journal.appendChild(placeholder)
inserted = true
check()
if (!placeholder.nextElementSibling) {
placeholder.style.display = 'none'
} else {
placeholder.style = ''
}
self.scroll2bottom = function () {
var next = placeholder.nextElementSibling
if (next) {
placeholder.style = ''
check()
var messages = 1
while ((next = next.nextElementSibling)) messages += 1
text.innerText = `${messages} new unread log entries`
} else {
placeholder.style.display = 'none'
}
}
}
}
function check () {
var pos1 = self._view.term.offsetHeight + self._view.term.scrollTop - (self._view.el.offsetHeight * 0.15)
var pos2 = placeholder.offsetTop
if ((pos1 - pos2) > 0) {
text.style.display = 'none'
background.style.position = 'relative'
background.style.opacity = 0.3
background.style.right = 0
background.style.borderBox = 'content-box'
background.style.padding = '2px'
background.style.height = (self._view.journal.offsetHeight - (placeholder.offsetTop + placeholder.offsetHeight)) + 'px'
background.onclick = undefined
background.style.cursor = 'default'
background.style.pointerEvents = 'none'
} else {
background.style = ''
text.style = ''
background.onclick = function (event) {
placeholder.scrollIntoView()
check()
}
}
}
function hover (event) { event.currentTarget.classList.toggle(css.hover) }
function minimize (event) {
event.preventDefault()
event.stopPropagation()
if (event.button === 0) {
var classList = self._view.icon.classList
classList.toggle('fa-angle-double-down')
classList.toggle('fa-angle-double-up')
self.event.trigger('resize', [])
}
}
var filtertimeout = null
function filter (event) {
if (filtertimeout) {
clearTimeout(filtertimeout)
}
filtertimeout = setTimeout(() => {
self.updateJournal({ type: 'search', value: self._view.inputSearch.value })
self.scroll2bottom()
}, 500)
}
function clear (event) {
refocus()
self._view.journal.innerHTML = ''
}
// ----------------- resizeable ui ---------------
function mousedown (event) {
event.preventDefault()
if (event.which === 1) {
moveGhostbar(event)
document.body.appendChild(ghostbar)
document.addEventListener('mousemove', moveGhostbar)
document.addEventListener('mouseup', removeGhostbar)
document.addEventListener('keydown', cancelGhostbar)
}
}
function cancelGhostbar (event) {
if (event.keyCode === 27) {
document.body.removeChild(ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
}
}
function moveGhostbar (event) { // @NOTE HORIZONTAL ghostbar
ghostbar.style.top = self._api.getPosition(event) + 'px'
}
function removeGhostbar (event) {
if (self._view.icon.classList.contains('fa-angle-double-up')) {
self._view.icon.classList.toggle('fa-angle-double-down')
self._view.icon.classList.toggle('fa-angle-double-up')
}
document.body.removeChild(ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
self.event.trigger('resize', [self._api.getPosition(event)])
}
self._cmdHistory = []
self._cmdIndex = -1
self._cmdTemp = ''
var intro = yo`
<div><div> - Welcome to Remix ${packageJson.version} - </div><br>
<div>You can use this terminal to: </div>
<ul class=${css2.ul}>
<li>Check transactions details and start debugging.</li>
<li>Execute JavaScript scripts:
<br />
<i> - Input a script directly in the command line interface </i>
<br />
<i> - Select a Javascript file in the file explorer and then run \`remix.execute()\` or \`remix.exeCurrent()\` in the command line interface </i>
<br />
<i> - Right click on a JavaScript file in the file explorer and then click \`Run\` </i>
</li>
</ul>
<div>The following libraries are accessible:</div>
<ul class=${css2.ul}>
<li><a target="_blank" href="https://web3js.readthedocs.io/en/1.0/">web3 version 1.0.0</a></li>
<li><a target="_blank" href="https://docs.ethers.io">ethers.js</a> </li>
<li><a target="_blank" href="https://www.npmjs.com/package/swarmgw">swarmgw</a> </li>
<li>remix (run remix.help() for more info)</li>
</ul>
</div>
`
self._shell('remix.help()', self.commands, () => {})
self.commands.html(intro)
self._components.txLogger = new TxLogger(this, self.blockchain)
self._components.txLogger.event.register('debuggingRequested', async (hash) => {
// TODO should probably be in the run module
if (!await self._opts.appManager.isActive('debugger')) await self._opts.appManager.activatePlugin('debugger')
this.call('menuicons', 'select', 'debugger')
this.call('debugger', 'debug', hash)
})
return self._view.el
function wrapScript (script) {
const isKnownScript = ['remix.', 'git'].some(prefix => script.trim().startsWith(prefix))
if (isKnownScript) return script
return `
try {
const ret = ${script};
if (ret instanceof Promise) {
ret.then((result) => { console.log(result) }).catch((error) => { console.log(error) })
} else {
console.log(ret)
}
} catch (e) {
console.log(e.message)
}
`
}
function change (event) {
if (self._components.autoCompletePopup.handleAutoComplete(
event,
self._view.input.innerText)) return
if (self._view.input.innerText.length === 0) self._view.input.innerText += '\n'
if (event.which === 13) {
if (event.ctrlKey) { // <ctrl+enter>
self._view.input.innerText += '\n'
self.putCursor2End(self._view.input)
self.scroll2bottom()
self._components.autoCompletePopup.removeAutoComplete()
} else { // <enter>
self._cmdIndex = -1
self._cmdTemp = ''
event.preventDefault()
var script = self._view.input.innerText.trim()
self._view.input.innerText = '\n'
if (script.length) {
self._cmdHistory.unshift(script)
self.commands.script(wrapScript(script))
}
self._components.autoCompletePopup.removeAutoComplete()
}
} else if (event.which === 38) { // <arrowUp>
var len = self._cmdHistory.length
if (len === 0) return event.preventDefault()
if (self._cmdHistory.length - 1 > self._cmdIndex) {
self._cmdIndex++
}
self._view.input.innerText = self._cmdHistory[self._cmdIndex]
self.putCursor2End(self._view.input)
self.scroll2bottom()
} else if (event.which === 40) { // <arrowDown>
if (self._cmdIndex > -1) {
self._cmdIndex--
}
self._view.input.innerText = self._cmdIndex >= 0 ? self._cmdHistory[self._cmdIndex] : self._cmdTemp
self.putCursor2End(self._view.input)
self.scroll2bottom()
} else {
self._cmdTemp = self._view.input.innerText
}
}
resetLogHtml () {
this.logHtmlResponse = []
}
putCursor2End (editable) {
var range = document.createRange()
range.selectNode(editable)
var child = editable
var chars
while (child) {
if (child.lastChild) child = child.lastChild
else break
if (child.nodeType === Node.TEXT_NODE) {
chars = child.textContent.length
} else {
chars = child.innerHTML.length
}
log (message) {
this.logResponse.push(message)
this.renderComponent()
this.resetLog()
}
range.setEnd(child, chars)
var toStart = true
var toEnd = !toStart
range.collapse(toEnd)
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
editable.focus()
resetLog () {
this.logResponse = []
}
updateJournal (filterEvent) {
var self = this
var commands = self.data.activeFilters.commands
var value = filterEvent.value
if (filterEvent.type === 'select') {
commands[value] = true
if (!self._INDEX.commandsMain[value]) return
self._INDEX.commandsMain[value].forEach(item => {
item.root.steps.forEach(item => { self._JOURNAL[item.gidx] = item })
self._JOURNAL[item.gidx] = item
})
} else if (filterEvent.type === 'deselect') {
commands[value] = false
if (!self._INDEX.commandsMain[value]) return
self._INDEX.commandsMain[value].forEach(item => {
item.root.steps.forEach(item => { self._JOURNAL[item.gidx].hide = true })
self._JOURNAL[item.gidx].hide = true
})
} else if (filterEvent.type === 'search') {
if (value !== self.data.activeFilters.input) {
var query = self.data.activeFilters.input = value
var items = self._JOURNAL
for (var gidx = 0, len = items.length; gidx < len; gidx++) {
var item = items[gidx]
if (item && self.data.filterFns[item.cmd]) {
var show = query.length ? self.data.filterFns[item.cmd](item.args, query) : true
item.hide = !show
}
}
}
}
var df = document.createDocumentFragment()
self._JOURNAL.forEach(item => {
if (item && item.el && !item.hide) df.appendChild(item.el)
})
self._view.journal.innerHTML = ''
requestAnimationFrame(function updateDOM () {
self._view.journal.appendChild(df)
})
render () {
return this.element
}
_appendItem (item) {
var self = this
var { el, gidx } = item
self._JOURNAL[gidx] = item
if (!self._jobs.length) {
requestAnimationFrame(function updateTerminal () {
self._jobs.forEach(el => self._view.journal.appendChild(el))
self.scroll2bottom()
self._jobs = []
})
}
if (self.data.activeFilters.commands[item.cmd]) self._jobs.push(el)
renderComponent () {
ReactDOM.render(
<RemixUiTerminal
plugin = {this}
/>,
this.element
)
}
scroll2bottom () {
var self = this
setTimeout(function () {
self._view.term.scrollTop = self._view.term.scrollHeight
}, 0)
}
_blocksRenderer (mode) {
if (mode === 'html') {
return function logger (args, scopedCommands, append) {
if (args.length) append(args[0])
}
}
mode = {
log: 'text-info',
info: 'text-info',
warn: 'text-warning',
error: 'text-danger'
}[mode] // defaults
if (mode) {
const filterUndefined = (el) => el !== undefined && el !== null
return function logger (args, scopedCommands, append) {
var types = args.filter(filterUndefined).map(type)
var values = javascriptserialize.apply(null, args.filter(filterUndefined)).map(function (val, idx) {
if (typeof args[idx] === 'string') {
const el = document.createElement('div')
el.innerHTML = args[idx].replace(/(\r\n|\n|\r)/gm, '<br>')
val = el.children.length === 0 ? el.firstChild : el
}
if (types[idx] === 'element') val = jsbeautify.html(val)
return val
})
if (values.length) {
append(yo`<span class="${mode}" >${values}</span>`)
}
}
} else {
throw new Error('mode is not supported')
}
}
_scopeCommands (append) {
var self = this
var scopedCommands = {}
Object.keys(self.commands).forEach(function makeScopedCommand (cmd) {
var command = self._commands[cmd]
scopedCommands[cmd] = function _command () {
var args = [...arguments]
command(args, scopedCommands, el => append(cmd, args, blockify(el)))
}
})
return scopedCommands
}
registerFilter (commandName, filterFn) {
this.data.filterFns[commandName] = filterFn
}
registerCommand (name, command, opts) {
var self = this
name = String(name)
if (self._commands[name]) throw new Error(`command "${name}" exists already`)
if (typeof command !== 'function') throw new Error(`invalid command: ${command}`)
self._commands[name] = command
self._INDEX.commands[name] = []
self._INDEX.commandsMain[name] = []
self.commands[name] = function _command () {
var args = [...arguments]
var steps = []
var root = { steps, cmd: name }
var ITEM = { root, cmd: name }
root.gidx = self._INDEX.allMain.push(ITEM) - 1
root.idx = self._INDEX.commandsMain[name].push(ITEM) - 1
function append (cmd, params, el) {
var item
if (cmd) { // subcommand
item = { el, cmd, root }
} else { // command
item = ITEM
item.el = el
cmd = name
}
item.gidx = self._INDEX.all.push(item) - 1
item.idx = self._INDEX.commands[cmd].push(item) - 1
item.step = steps.push(item) - 1
item.args = params
self._appendItem(item)
}
var scopedCommands = self._scopeCommands(append)
command(args, scopedCommands, el => append(null, args, blockify(el)))
}
var help = typeof command.help === 'string' ? command.help : [
'// no help available for:',
`terminal.commands.${name}(...)`
].join('\n')
self.commands[name].toString = _ => { return help }
self.commands[name].help = help
self.data.activeFilters.commands[name] = opts && opts.activate
if (opts.filterFn) {
self.registerFilter(name, opts.filterFn)
}
return self.commands[name]
}
async _shell (script, scopedCommands, done) { // default shell
if (script.indexOf('remix:') === 0) {
return done(null, 'This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.')
}
var self = this
if (script.indexOf('remix.') === 0) {
// we keep the old feature. This will basically only be called when the command is querying the "remix" object.
// for all the other case, we use the Code Executor plugin
var context = domTerminalFeatures(self, scopedCommands, self.blockchain)
try {
var cmds = vm.createContext(context)
var result = vm.runInContext(script, cmds)
return done(null, result)
} catch (error) {
return done(error.message)
}
}
try {
let result
if (script.trim().startsWith('git')) {
// result = await this.call('git', 'execute', script)
} else {
result = await this.call('scriptRunner', 'execute', script)
}
if (result) self.commands.html(yo`<pre>${result}</pre>`)
done()
} catch (error) {
done(error.message || error)
}
}
}
function domTerminalFeatures (self, scopedCommands, blockchain) {
return {
remix: self._components.cmdInterpreter
}
}
function blockify (el) { return yo`<div class="px-4 ${css.block}" data-id="block_${el.getAttribute ? el.getAttribute('id') : ''}">${el}</div>` }
module.exports = Terminal

@ -93,7 +93,6 @@ class CmdInterpreterAPI {
if (cb) cb()
return
}
self._components.terminal.commands.script(content)
}

@ -30,7 +30,6 @@ const defaultCompilerParameters = {
evmVersion: null, // compiler default
language: 'Solidity'
}
export class CompilerClientApi extends CompilerApiMixin(PluginClient) implements ICompilerApi {
constructor () {
super()

@ -42,7 +42,6 @@ export const listenToEvents = (compileTabLogic: CompileTabLogic, api) => (dispat
api.onContentChanged = () => {
dispatch(setEditorMode('contentChanged'))
}
compileTabLogic.compiler.event.register('loadingCompiler', () => {
dispatch(setCompilerMode('loadingCompiler'))
})

@ -0,0 +1,4 @@
{
"presets": ["@nrwl/react/babel"],
"plugins": []
}

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

@ -0,0 +1,7 @@
# remix-ui-terminal
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-terminal` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1,4 @@
{
"name": "remix-ui-terminal",
"version": "0.0.1"
}

@ -0,0 +1 @@
export * from './lib/remix-ui-terminal'

@ -0,0 +1,152 @@
import React from 'react'
import { EMPTY_BLOCK, KNOWN_TRANSACTION, NEW_BLOCK, NEW_CALL, NEW_TRANSACTION, UNKNOWN_TRANSACTION } from '../types/terminalTypes'
export const registerCommandAction = (name: string, command, activate, dispatch: React.Dispatch<any>) => {
const commands: any = {}
const _commands: any = {}
_commands[name] = command
const data: any = {
session: [],
activeFilters: { commands: {}, input: '' },
filterFns: {}
}
const _INDEX = {
all: [],
allMain: [],
commands: {},
commandsMain: {}
}
const registerFilter = (commandName, filterFn) => {
data.filterFns[commandName] = filterFn
}
commands[name] = function () {
const args = [...arguments]
const steps = []
const root = { steps, cmd: name, gidx: 0, idx: 0 }
const ITEM = { root, cmd: name }
root.gidx = _INDEX.allMain.push(ITEM) - 1
let item
function append (cmd, params, el) {
if (cmd) { // subcommand
item = { el, cmd, root }
} else { // command
item = ITEM
item.el = el
cmd = name
}
item.gidx = _INDEX.all.push(item) - 1
item.idx = _INDEX.commands[cmd].push(item) - 1
item.step = steps.push(item) - 1
item.args = params
}
const scopedCommands = _scopeCommands(append)
command(args, scopedCommands, el => append(null, args, el))
}
const help = typeof command.help === 'string' ? command.help : [
'// no help available for:', `terminal.command.${name}`
].join('\n')
commands[name].toString = () => { return help }
commands[name].help = help
data.activeFilters.commands[name] = activate && activate.activate
if (activate.filterFn) {
registerFilter(name, activate.filterFn)
}
if (name !== (KNOWN_TRANSACTION || UNKNOWN_TRANSACTION || EMPTY_BLOCK)) {
dispatch({ type: name, payload: { commands: commands, _commands: _commands, data: data } })
}
const _scopeCommands = (append) => {
const scopedCommands = {}
Object.keys(commands).forEach(function makeScopedCommand (cmd) {
const command = _commands[cmd]
scopedCommands[cmd] = function _command () {
const args = [...arguments]
command(args, scopedCommands, el => append(cmd, args, el))
}
})
return scopedCommands
}
}
export const filterFnAction = (name: string, filterFn, dispatch: React.Dispatch<any>) => {
const data: any = {
filterFns: {}
}
data.filterFns[name] = filterFn
dispatch({ type: name, payload: { data: data } })
}
export const registerLogScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch<any>) => {
on('scriptRunner', commandName, (msg) => {
commandFn.log.apply(commandFn, msg.data)
dispatch({ type: commandName, payload: { commandFn, message: msg.data } })
})
}
export const registerInfoScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch<any>) => {
on('scriptRunner', commandName, (msg) => {
commandFn.info.apply(commandFn, msg.data)
dispatch({ type: commandName, payload: { commandFn, message: msg.data } })
})
}
export const registerWarnScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch<any>) => {
on('scriptRunner', commandName, (msg) => {
commandFn.warn.apply(commandFn, msg.data)
dispatch({ type: commandName, payload: { commandFn, message: msg.data } })
})
}
export const registerErrorScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch<any>) => {
on('scriptRunner', commandName, (msg) => {
commandFn.error.apply(commandFn, msg.data)
dispatch({ type: commandName, payload: { commandFn, message: msg.data } })
})
}
export const listenOnNetworkAction = async (event, isListening) => {
event.trigger('listenOnNetWork', [isListening])
}
export const initListeningOnNetwork = (plugins, dispatch: React.Dispatch<any>) => {
plugins.txListener.event.register(NEW_BLOCK, (block) => {
if (!block.transactions || (block.transactions && !block.transactions.length)) {
dispatch({ type: EMPTY_BLOCK, payload: { message: 0 } })
}
})
plugins.txListener.event.register(KNOWN_TRANSACTION, () => {
})
plugins.txListener.event.register(NEW_CALL, (tx, receipt) => {
log(plugins, tx, receipt, dispatch)
// log(this, tx, null)
})
plugins.txListener.event.register(NEW_TRANSACTION, (tx, receipt) => {
log(plugins, tx, receipt, dispatch)
})
const log = async (plugins, tx, receipt, dispatch: React.Dispatch<any>) => {
const resolvedTransaction = await plugins.txListener.resolvedTransaction(tx.hash)
if (resolvedTransaction) {
let compiledContracts = null
if (plugins._deps.compilersArtefacts.__last) {
compiledContracts = await plugins._deps.compilersArtefacts.__last.getContracts()
}
await plugins.eventsDecoder.parseLogs(tx, resolvedTransaction.contractName, compiledContracts, async (error, logs) => {
if (!error) {
await dispatch({ type: KNOWN_TRANSACTION, payload: { message: [{ tx: tx, receipt: receipt, resolvedData: resolvedTransaction, logs: logs }] } })
}
})
} else {
await dispatch({ type: UNKNOWN_TRANSACTION, payload: { message: [{ tx: tx, receipt: receipt }] } })
}
}
plugins.txListener.event.register('debuggingRequested', async (hash) => {
// TODO should probably be in the run module
if (!await plugins.options.appManager.isActive('debugger')) await plugins.options.appManager.activatePlugin('debugger')
plugins.call('menuicons', 'select', 'debugger')
plugins.call('debugger', 'debug', hash)
})
}

@ -0,0 +1,53 @@
export const allPrograms = [
{ ethers: 'The ethers.js library is a compact and complete JavaScript library for Ethereum.' },
{ remix: 'Ethereum IDE and tools for the web.' },
{ web3: 'The web3.js library is a collection of modules which contain specific functionality for the ethereum ecosystem.' }
// { swarmgw: 'This library can be used to upload/download files to Swarm via https://swarm-gateways.net/.' }
]
export const allCommands = [
{ '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.' },
{ '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 or ipfs.' },
// { 'swarmgw.get(url, cb)': 'Download files from Swarm via https://swarm-gateways.net/' },
// { 'swarmgw.put(content, cb)': 'Upload files to Swarm via https://swarm-gateways.net/' },
{ 'ethers.Contract': 'This API provides a graceful connection to a contract deployed on the blockchain, simplifying calling and querying its functions and handling all the binary protocol and conversion as necessarily.' },
// { 'ethers.HDNode': 'A Hierarchical Deterministic Wallet represents a large tree of private keys which can reliably be reproduced from an initial seed.' },
// { 'ethers.Interface': 'The Interface Object is a meta-class that accepts a Solidity (or compatible) Application Binary Interface (ABI) and populates functions to deal with encoding and decoding the parameters to pass in and results returned.' },
{ 'ethers.providers': 'A Provider abstracts a connection to the Ethereum blockchain, for issuing queries and sending state changing transactions.' },
// { 'ethers.SigningKey': 'The SigningKey interface provides an abstraction around the secp256k1 elliptic curve cryptography library.' },
// { 'ethers.utils': 'The utility functions exposed in both the ethers umbrella package and the ethers-utils.' },
// { 'ethers.utils.AbiCoder': 'Create a new ABI Coder object' },
// { 'ethers.utils.RLP': 'This encoding method is used internally for several aspects of Ethereum, such as encoding transactions and determining contract addresses.' },
{ 'ethers.Wallet': 'A wallet manages a private/public key pair which is used to cryptographically sign transactions and prove ownership on the Ethereum network.' },
{ 'ethers.version': 'Contains the version of the ethers container object.' },
{ 'web3.eth': 'Eth module for interacting with the Ethereum network.' },
{ 'web3.eth.accounts': 'The web3.eth.accounts contains functions to generate Ethereum accounts and sign transactions and data.' },
// TODO: need to break down the object return from abi response
// { 'web3.eth.abi': 'The web3.eth.abi functions let you de- and encode parameters to ABI (Application Binary Interface) for function calls to the EVM (Ethereum Virtual Machine).' },
{ 'web3.eth.ens': 'The web3.eth.ens functions let you interacting with ENS.' },
{ 'web3.eth.Iban': 'The web3.eth.Iban function lets convert Ethereum addresses from and to IBAN and BBAN.' },
{ 'web3.eth.net': 'Net module for interacting with network properties.' },
{ 'web3.eth.personal': 'Personal module for interacting with the Ethereum accounts.' },
{ 'web3.eth.subscribe': 'The web3.eth.subscribe function lets you subscribe to specific events in the blockchain.' },
{ 'web3.givenProvider': 'When using web3.js in an Ethereum compatible browser, it will set with the current native provider by that browser. Will return the given provider by the (browser) environment, otherwise null.' },
// { 'web3.modules': 'Contains the version of the web3 container object.' },
{ 'web3.providers': 'Contains the current available providers.' },
{ 'web3.shh': 'Shh module for interacting with the whisper protocol' },
{ 'web3.utils': 'This package provides utility functions for Ethereum dapps and other web3.js packages.' },
{ 'web3.version': 'Contains the version of the web3 container object.' },
{ 'web3.eth.clearSubscriptions();': 'Resets subscriptions.' }
// { 'web3.eth.Contract(jsonInterface[, address][, options])': 'The web3.eth.Contract object makes it easy to interact with smart contracts on the ethereum blockchain.' },
// { 'web3.eth.accounts.create([entropy]);': 'The web3.eth.accounts contains functions to generate Ethereum accounts and sign transactions and data.' },
// { 'web3.eth.getAccounts();': 'Retrieve the list of accounts' },
// { 'web3.eth.accounts.privateKeyToAccount(privateKey [, ignoreLength ]);': 'Get the account from the private key' },
// { 'web3.eth.accounts.signTransaction(tx, privateKey [, callback]);': 'Sign Transaction' },
// { 'web3.eth.accounts.recoverTransaction(rawTransaction);': 'Sign Transaction' },
// { 'web3.eth.accounts.hashMessage(message);': 'Hash message' }
]

@ -0,0 +1,16 @@
import React from 'react' // eslint-disable-line
const CheckTxStatus = ({ tx, type }) => {
if (tx.status === '0x1' || tx.status === true) {
return (<i className='txStatus succeeded fas fa-check-circle'></i>)
}
if (type === 'call' || type === 'unknownCall' || type === 'unknown') {
return (<i className='txStatus call'>call</i>)
} else if (tx.status === '0x0' || tx.status === false) {
return (<i className='txStatus failed fas fa-times-circle'></i>)
} else {
return (<i className='txStatus notavailable fas fa-circle-thin' title='Status not available' ></i>)
}
}
export default CheckTxStatus

@ -0,0 +1,62 @@
import React from 'react' // eslint-disable-line
import helper from 'apps/remix-ide/src/lib/helper'
const remixLib = require('@remix-project/remix-lib')
const typeConversion = remixLib.execution.typeConversion
const Context = ({ opts, blockchain }) => {
const data = opts.tx || ''
const from = opts.from ? helper.shortenHexData(opts.from) : ''
let to = opts.to
if (data.to) to = to + ' ' + helper.shortenHexData(data.to)
const val = data.value
let hash = data.hash ? helper.shortenHexData(data.hash) : ''
const input = data.input ? helper.shortenHexData(data.input) : ''
const logs = data.logs && data.logs.decoded && data.logs.decoded.length ? data.logs.decoded.length : 0
const block = data.receipt ? data.receipt.blockNumber : data.blockNumber || ''
const i = data.receipt ? data.transactionIndex : data.transactionIndex
const value = val ? typeConversion.toInt(val) : 0
if (blockchain.getProvider() === 'vm') {
return (
<div>
<span className='txLog_7Xiho'>
<span className='tx'>[vm]</span>
<div className='txItem'><span className='txItemTitle'>from:</span> {from}</div>
<div className='txItem'><span className='txItemTitle'>to:</span> {to}</div>
<div className='txItem'><span className='txItemTitle'>value:</span> {value} wei</div>
<div className='txItem'><span className='txItemTitle'>data:</span> {input}</div>
<div className='txItem'><span className='txItemTitle'>logs:</span> {logs}</div>
<div className='txItem'><span className='txItemTitle'>hash:</span> {hash}</div>
</span>
</div>)
} else if (blockchain.getProvider() !== 'vm' && data.resolvedData) {
return (
<div>
<span className='txLog_7Xiho'>
<span className='tx'>[block:{block} txIndex:{i}]</span>
<div className='txItem'><span className='txItemTitle'>from:</span> {from}</div>
<div className='txItem'><span className='txItemTitle'>to:</span> {to}</div>
<div className='txItem'><span className='txItemTitle'>value:</span> {value} wei</div>
<div className='txItem'><span className='txItemTitle'>data:</span> {input}</div>
<div className='txItem'><span className='txItemTitle'>logs:</span> {logs}</div>
<div className='txItem'><span className='txItemTitle'>hash:</span> {hash}</div>
</span>
</div>)
} else {
hash = helper.shortenHexData(data.blockHash)
return (
<div>
<span className='txLog'>
<span className='tx'>[block:{block} txIndex:{i}]</span>
<div className='txItem'><span className='txItemTitle'>from:</span> {from}</div>
<div className='txItem'><span className='txItemTitle'>to:</span> {to}</div>
<div className='txItem'><span className='txItemTitle'>value:</span> {value} wei</div>
<div className='txItem'><span className='txItemTitle'>data:</span> {input}</div>
<div className='txItem'><span className='txItemTitle'>logs:</span> {logs}</div>
<div className='txItem'><span className='txItemTitle'>hash:</span> {hash}</div>
</span>
</div>)
}
}
export default Context

@ -0,0 +1,61 @@
import React, { useState } from 'react' // eslint-disable-line
import helper from 'apps/remix-ide/src/lib/helper'
import CheckTxStatus from './ChechTxStatus' // eslint-disable-line
import showTable from './Table'
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
const remixLib = require('@remix-project/remix-lib')
const typeConversion = remixLib.execution.typeConversion
const RenderCall = ({ tx, resolvedData, logs, index, plugin, showTableHash, txDetails, modal }) => {
const to = resolvedData.contractName + '.' + resolvedData.fn
const from = tx.from ? tx.from : ' - '
const input = tx.input ? helper.shortenHexData(tx.input) : ''
const txType = 'call'
const debug = (event, tx) => {
event.stopPropagation()
if (tx.isCall && tx.envMode !== 'vm') {
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
} else {
plugin.event.trigger('debuggingRequested', [tx.hash])
}
}
return (
<span id={`tx${tx.hash}`} key={index}>
<div className="log" onClick={(event) => txDetails(event, tx)}>
<CheckTxStatus tx={tx} type={txType} />
<span className="txLog">
<span className="tx">[call]</span>
<div className='txItem'><span className='txItemTitle'>from:</span> {from}</div>
<div className='txItem'><span className='txItemTitle'>to:</span> {to}</div>
<div className='txItem'><span className='txItemTitle'>data:</span> {input}</div>
</span>
<div className='buttons'>
<div className="debug btn btn-primary btn-sm" onClick={(event) => debug(event, tx)}>Debug</div>
</div>
<i className="terminal_arrow fas fa-angle-down"></i>
</div>
{showTableHash.includes(tx.hash) ? showTable({
hash: tx.hash,
isCall: tx.isCall,
contractAddress: tx.contractAddress,
data: tx,
from,
to,
gas: tx.gas,
input: tx.input,
'decoded input': resolvedData && resolvedData.params ? JSON.stringify(typeConversion.stringify(resolvedData.params), null, '\t') : ' - ',
'decoded output': resolvedData && resolvedData.decodedReturnValue ? JSON.stringify(typeConversion.stringify(resolvedData.decodedReturnValue), null, '\t') : ' - ',
val: tx.value,
logs: logs,
transactionCost: tx.transactionCost,
executionCost: tx.executionCost
}, showTableHash) : null}
</span>
)
}
export default RenderCall

@ -0,0 +1,56 @@
import React, { useState } from 'react' // eslint-disable-line
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import CheckTxStatus from './ChechTxStatus' // eslint-disable-line
import Context from './Context' // eslint-disable-line
import showTable from './Table'
const remixLib = require('@remix-project/remix-lib')
const typeConversion = remixLib.execution.typeConversion
const RenderKnownTransactions = ({ tx, receipt, resolvedData, logs, index, plugin, showTableHash, txDetails, modal }) => {
const debug = (event, tx) => {
event.stopPropagation()
if (tx.isCall && tx.envMode !== 'vm') {
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
} else {
plugin.event.trigger('debuggingRequested', [tx.hash])
}
}
const from = tx.from
const to = resolvedData.contractName + '.' + resolvedData.fn
const txType = 'knownTx'
const options = { from, to, tx }
return (
<span id={`tx${tx.hash}`} key={index}>
<div className="log" onClick={(event) => txDetails(event, tx)}>
<CheckTxStatus tx={receipt} type={txType} />
<Context opts = { options } blockchain={plugin.blockchain} />
<div className='buttons'>
<div className='debug btn btn-primary btn-sm' data-shared='txLoggerDebugButton' data-id={`txLoggerDebugButton${tx.hash}`} onClick={(event) => debug(event, tx)}>Debug</div>
</div>
<i className = {`terminal_arrow fas ${(showTableHash.includes(tx.hash)) ? 'fa-angle-up' : 'fa-angle-down'}`}></i>
</div>
{showTableHash.includes(tx.hash) ? showTable({
hash: tx.hash,
status: receipt !== null ? receipt.status : null,
isCall: tx.isCall,
contractAddress: tx.contractAddress,
data: tx,
from,
to,
gas: tx.gas,
input: tx.input,
'decoded input': resolvedData && resolvedData.params ? JSON.stringify(typeConversion.stringify(resolvedData.params), null, '\t') : ' - ',
'decoded output': resolvedData && resolvedData.decodedReturnValue ? JSON.stringify(typeConversion.stringify(resolvedData.decodedReturnValue), null, '\t') : ' - ',
logs: logs,
val: tx.value,
transactionCost: tx.transactionCost,
executionCost: tx.executionCost
}, showTableHash) : null}
</span>
)
}
export default RenderKnownTransactions

@ -0,0 +1,50 @@
import React, { useState } from 'react' // eslint-disable-line
import { ModalDialog } from '@remix-ui/modal-dialog'// eslint-disable-line
import CheckTxStatus from './ChechTxStatus' // eslint-disable-line
import Context from './Context' // eslint-disable-line
import showTable from './Table'
const RenderUnKnownTransactions = ({ tx, receipt, index, plugin, showTableHash, txDetails, modal }) => {
const debug = (event, tx) => {
event.stopPropagation()
if (tx.isCall && tx.envMode !== 'vm') {
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.', 'Ok', true, () => {}, 'Cancel', () => {})
} else {
plugin.event.trigger('debuggingRequested', [tx.hash])
}
}
const from = tx.from
const to = tx.to
const txType = 'unknown' + (tx.isCall ? 'Call' : 'Tx')
const options = { from, to, tx }
return (
<span id={`tx${tx.hash}`} key={index}>
<div className="log" onClick={(event) => txDetails(event, tx)}>
<CheckTxStatus tx={receipt || tx} type={txType} />
<Context opts = { options } blockchain={plugin.blockchain} />
<div className='buttons'>
<div className='debug btn btn-primary btn-sm' data-shared='txLoggerDebugButton' data-id={`txLoggerDebugButton${tx.hash}`} onClick={(event) => debug(event, tx)}>Debug</div>
</div>
<i className = {`terminal_arrow fas ${(showTableHash.includes(tx.hash)) ? 'fa-angle-up' : 'fa-angle-down'}`}></i>
</div>
{showTableHash.includes(tx.hash) ? showTable({
hash: tx.hash,
status: receipt !== null ? receipt.status : null,
isCall: tx.isCall,
contractAddress: tx.contractAddress,
data: tx,
from,
to,
gas: tx.gas,
input: tx.input,
'decoded output': ' - ',
val: tx.value,
transactionCost: tx.transactionCost,
executionCost: tx.executionCost
}, showTableHash) : null}
</span>
)
}
export default RenderUnKnownTransactions

@ -0,0 +1,165 @@
import React, { useState } from 'react' // eslint-disable-line
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
import helper from 'apps/remix-ide/src/lib/helper'
const remixLib = require('@remix-project/remix-lib')
const typeConversion = remixLib.execution.typeConversion
const showTable = (opts, showTableHash) => {
let msg = ''
let toHash
const data = opts.data // opts.data = data.tx
if (data.to) {
toHash = opts.to + ' ' + data.to
} else {
toHash = opts.to
}
let callWarning = ''
if (opts.isCall) {
callWarning = '(Cost only applies when called by a contract)'
}
if (!opts.isCall) {
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'
}
}
let stringified = ' - '
if (opts.logs && opts.logs.decoded) {
stringified = typeConversion.stringify(opts.logs.decoded)
}
const val = opts.val != null ? typeConversion.toInt(opts.val) : 0
return (
<table className={`txTable ${showTableHash.includes(opts.hash) ? 'active' : ''}`} id='txTable' data-id={`txLoggerTable${opts.hash}`}>
<tbody>
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>status</td>
<td className='td' data-id={`txLoggerTableStatus${opts.hash}`} data-shared={`pair_${opts.hash}`}>{`${opts.status} ${msg}`}</td>
</tr>
{opts.hash ? (<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>transaction hash</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.hash}
<CopyToClipboard content={opts.hash}/>
</td>
</tr>) : null }
{
opts.contractAddress ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>contract address</td>
<td className='td' data-id={`txLoggerTableContractAddress${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.contractAddress}
<CopyToClipboard content={opts.contractAddress}/>
</td>
</tr>
) : null
}
{
opts.from ? (
<tr className='tr'>
<td className='td tableTitle' data-shared={`key_${opts.hash}`}>from</td>
<td className='td' data-id={`txLoggerTableFrom${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.from}
<CopyToClipboard content={opts.from}/>
</td>
</tr>
) : null
}
{
opts.to ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>to</td>
<td className='td' data-id={`txLoggerTableTo${opts.hash}`} data-shared={`pair_${opts.hash}`}>{toHash}
<CopyToClipboard content={data.to ? data.to : toHash}/>
</td>
</tr>
) : null
}
{
opts.gas ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>gas</td>
<td className='td' data-id={`txLoggerTableGas${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.gas} gas
<CopyToClipboard content={opts.gas}/>
</td>
</tr>
) : null
}
{
opts.transactionCost ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>transaction cost</td>
<td className='td' data-id={`txLoggerTableTransactionCost${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.transactionCost} gas {callWarning}
<CopyToClipboard content={opts.transactionCost}/>
</td>
</tr>
) : null
}
{
opts.executionCost ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>execution cost</td>
<td className='td' data-id={`txLoggerTableExecutionHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.executionCost} gas {callWarning}
<CopyToClipboard content={opts.executionCost}/>
</td>
</tr>
) : null
}
{opts.hash ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>hash</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.hash}
<CopyToClipboard content={opts.hash}/>
</td>
</tr>
) : null}
{opts.input ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>input</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{helper.shortenHexData(opts.input)}
<CopyToClipboard content={opts.input}/>
</td>
</tr>
) : null}
{opts['decoded input'] ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>decoded input</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts['decoded input'].trim()}
<CopyToClipboard content={opts['decoded input']}/>
</td>
</tr>
) : null}
{opts['decoded output'] ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>decoded output</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts['decoded output']}
<CopyToClipboard content={opts['decoded output']}/>
</td>
</tr>
) : null}
{opts.logs ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>logs</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>
{JSON.stringify(stringified, null, '\t')}
<CopyToClipboard content={JSON.stringify(stringified, null, '\t')}/>
<CopyToClipboard content={JSON.stringify(opts.logs.raw || '0')}/>
</td>
</tr>
) : null}
{opts.val ? (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}>val</td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{val} wei
<CopyToClipboard content={`${val} wei`}/>
</td>
</tr>
) : null}
</tbody>
</table>
)
}
export default showTable

@ -0,0 +1,29 @@
import React, {useEffect, useState} from "react" // eslint-disable-line
export const useKeyPress = (targetKey: string): boolean => {
// State for keeping track of whether key is pressed
const [keyPressed, setKeyPressed] = useState(false)
// If pressed key is our target key then set to true
function downHandler ({ key }): void {
if (key === targetKey) {
setKeyPressed(true)
}
}
// If released key is our target key then set to false
const upHandler = ({ key }): void => {
if (key === targetKey) {
setKeyPressed(false)
}
}
// Add event listeners
useEffect(() => {
window.addEventListener('keydown', downHandler)
window.addEventListener('keyup', upHandler)
// Remove event listeners on cleanup
return () => {
window.removeEventListener('keydown', downHandler)
window.removeEventListener('keyup', upHandler)
}
}, []) // Empty array ensures that effect is only run on mount and unmount
return keyPressed
}

@ -0,0 +1,195 @@
import { CLEAR_CONSOLE, CMD_HISTORY, EMPTY_BLOCK, ERROR, HTML, INFO, KNOWN_TRANSACTION, LISTEN_ON_NETWORK, LOG, NEW_TRANSACTION, SCRIPT, UNKNOWN_TRANSACTION, WARN } from '../types/terminalTypes'
export const initialState = {
journalBlocks: [
],
data: {
// lineLength: props.options.lineLength || 80,
session: [],
activeFilters: { commands: {}, input: '' },
filterFns: {}
},
_commandHistory: [],
_commands: {},
commands: {},
_JOURNAL: [],
_jobs: [],
_INDEX: {
},
_INDEXall: [],
_INDEXallMain: [],
_INDEXcommands: {},
_INDEXcommandsMain: {},
message: []
}
export const registerCommandReducer = (state, action) => {
switch (action.type) {
case HTML :
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, { ...action.payload.data })
}
case LOG:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, { ...action.payload.data })
}
case INFO:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, action.payload.data)
}
case WARN:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, action.payload.data)
}
case ERROR:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, action.payload.data)
}
case SCRIPT:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, action.payload.data)
}
case CLEAR_CONSOLE:
return {
...state,
...state.journalBlocks.splice(0)
}
case LISTEN_ON_NETWORK:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-info' })
}
default :
return { state }
}
}
export const registerFilterReducer = (state, action) => {
switch (action.type) {
case LOG:
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
}
case INFO:
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
}
case WARN:
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
}
case ERROR:
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
}
case SCRIPT:
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
}
default :
return { state }
}
}
export const addCommandHistoryReducer = (state, action) => {
switch (action.type) {
case CMD_HISTORY:
return {
...state,
_commandHistory: initialState._commandHistory.unshift(action.payload.script)
}
default :
return { state }
}
}
export const remixWelcomeTextReducer = (state, action) => {
switch (action.type) {
case 'welcomeText' :
return {
...state,
journalBlocks: initialState.journalBlocks.push(action.payload.welcomeText)
}
}
}
export const registerScriptRunnerReducer = (state, action) => {
switch (action.type) {
case HTML:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log' })
}
case LOG:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-info' })
}
case INFO:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-info' })
}
case WARN:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-warning' })
}
case ERROR:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-danger' })
}
case SCRIPT:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log' })
}
case KNOWN_TRANSACTION:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'knownTransaction' })
}
case UNKNOWN_TRANSACTION:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'unknownTransaction' })
}
case EMPTY_BLOCK:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'emptyBlock' })
}
case NEW_TRANSACTION:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '' })
}
}
}

@ -0,0 +1,421 @@
element.style {
height: 323px !important;
}
#terminalCliInput{
width: 95%;
background: transparent;
border: none;
font-weight: bold;
color: #a2a3b4;
border-top-style: hidden;
border-right-style: hidden;
border-left-style: hidden;
border-bottom-style: hidden;
}
#terminalCliInput:focus {
outline: none;
}
.border-primary {
border-color: #007aa6!important;
}
/* seleted option should reflect the theme color */
.selectedOptions {
background-color: #222336;
}
.panel {
position : relative;
display : flex;
flex-direction : column;
font-size : 12px;
min-height : 3em;
}
.bar {
display : flex;
z-index : 2;
}
.menu {
position : relative;
display : flex;
align-items : center;
width : 100%;
max-height : 35px;
min-height : 35px;
}
.toggleTerminal {
cursor : pointer;
}
.toggleTerminal:hover {
color : var(--secondary);
}
.terminal_container {
display : flex;
flex-direction : column;
height : 100%;
overflow-y : auto;
font-family : monospace;
margin : 0px;
background-repeat : no-repeat;
background-position : center 15%;
background-size : auto calc(75% - 1.7em);
}
.terminal {
position : relative;
display : flex;
flex-direction : column;
height : 100%;
}
.journal {
margin-top : auto;
font-family : monospace;
}
.block {
word-break : break-word;
white-space : pre-wrap;
line-height : 2ch;
padding : 1ch;
margin-top : 2ch;
}
.block > pre {
max-height : 200px;
}
.cli {
line-height : 1.7em;
font-family : monospace;
padding : .4em;
color : var(--primary);
}
.prompt {
margin-right : 0.5em;
font-family : monospace;
font-weight : bold;
font-size : 14px;
color : lightgray;
}
.input {
word-break : break-word;
outline : none;
font-family : monospace;
}
.search {
display : flex;
align-items : center;
width : 330px;
padding-left : 20px;
height : 100%;
padding-top : 1px;
padding-bottom : 1px;
}
.filter {
height : 80%;
white-space : nowrap;
overflow : hidden;
text-overflow : ellipsis;
}
.searchIcon {
width : 25px;
border-top-left-radius : 3px;
border-bottom-left-radius : 3px;
display : flex;
align-items : center;
justify-content : center;
margin-right : 5px;
}
.listen {
margin-right : 30px;
min-width : 40px;
height : 13px;
display : flex;
align-items : center;
}
.listenLabel {
min-width: 50px;
}
.verticalLine {
border-left : 1px solid var(--secondary);
height : 65%;
}
.dragbarHorizontal {
position : absolute;
top : 0;
height : 0.2em;
right : 0;
left : 0;
cursor : row-resize;
z-index : 999;
}
.console {
cursor : pointer;
}
.dragbarHorizontal:hover {
background-color: #007AA6;
border:2px solid #007AA6;
}
.listenOnNetwork {
min-height: auto;
}
.ghostbar {
position : absolute;
height : 6px;
opacity : 0.5;
cursor : row-resize;
z-index : 9999;
left : 0;
right : 0;
}
.divider-hitbox {
color: white;
cursor: row-resize;
align-self: stretch;
display: flex;
align-items: center;
padding: 0 1rem;
}
.ul {margin-left: 0; padding-left: 20px;}
.popup {
position : absolute;
text-align : left;
width : 95%;
font-family : monospace;
background-color : var(--secondary);
overflow : auto;
padding-bottom : 13px;
z-index : 80;
bottom : 1em;
border-width : 4px;
left : 2em;
}
.autoCompleteItem {
padding : 4px;
border-radius : 2px;
}
.popup a {
cursor : pointer;
}
.listHandlerShow {
display : block;
}
.listHandlerHide {
display : none;
}
.listHandlerButtonShow {
position : fixed;
width : 46%;
}
.pageNumberAlignment {
font-size : 10px;
float : right;
}
.modalContent {
position : absolute;
margin-left : 20%;
margin-bottom : 32px;
bottom : 0px;
padding : 0;
line-height : 18px;
font-size : 12px;
width : 40%;
box-shadow : 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatebottom;
-webkit-animation-duration: 0.4s;
animation-name : animatetop;
animation-duration: 0.4s
}
@-webkit-keyframes animatetop {
from {bottom: -300px; opacity: 0}
to {bottom: 0; opacity: 1}
}
@keyframes animatetop {
from {bottom: -300px; opacity: 0}
to {bottom: 0; opacity: 1}
}
/* tx logger css*/
.log {
display: flex;
cursor: pointer;
align-items: center;
cursor: pointer;
}
.log:hover {
opacity: 0.8;
}
.txStatus {
display: flex;
font-size: 20px;
margin-right: 20px;
float: left;
}
.succeeded {
color: var(--success);
}
.failed {
color: var(--danger);
}
.terminal_arrow {
color: var(--text-info);
font-size: 20px;
cursor: pointer;
display: flex;
margin-left: 10px;
}
.terminal_arrow:hover {
color: var(--secondary);
}
.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);
transition: max-height 0.3s, padding 0.3s;
}
table .active {
transition: max-height 0.6s, padding 0.6s;
}
#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;
}
/* Style the accordion section */
.accordion__section {
display: flex;
flex-direction: column;
}
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
display: flex;
align-items: center;
border: none;
outline: none;
transition: background-color 0.6s ease;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
/* .accordion:hover,
.active {
background-color: #ccc;
} */
/* Style the accordion content title */
.accordion__title {
font-family: "Open Sans", sans-serif;
font-weight: 600;
font-size: 14px;
}
/* Style the accordion chevron icon */
.accordion__icon {
margin-left: auto;
transition: transform 0.6s ease;
}
/* Style to rotate icon when state is active */
.rotate {
transform: rotate(90deg);
}
/* Style the accordion content panel. Note: hidden by default */
.accordion__content {
background-color: white;
overflow: hidden;
transition: max-height 0.6s ease;
}
/* Style the accordion content text */
.accordion__text {
font-family: "Open Sans", sans-serif;
font-weight: 400;
font-size: 14px;
padding: 18px;
}

@ -0,0 +1,574 @@
import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent } from 'react' // eslint-disable-line
import { registerCommandAction, registerLogScriptRunnerAction, registerInfoScriptRunnerAction, registerErrorScriptRunnerAction, registerWarnScriptRunnerAction, listenOnNetworkAction, initListeningOnNetwork } from './actions/terminalAction'
import { initialState, registerCommandReducer, addCommandHistoryReducer, registerScriptRunnerReducer } from './reducers/terminalReducer'
import { getKeyOf, getValueOf, Objectfilter, matched } from './utils/utils'
import {allCommands, allPrograms} from './commands' // eslint-disable-line
import TerminalWelcomeMessage from './terminalWelcome' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import './remix-ui-terminal.css'
import vm from 'vm'
import javascriptserialize from 'javascript-serialize'
import jsbeautify from 'js-beautify'
import RenderUnKnownTransactions from './components/RenderUnknownTransactions' // eslint-disable-line
import RenderCall from './components/RenderCall' // eslint-disable-line
import RenderKnownTransactions from './components/RenderKnownTransactions' // eslint-disable-line
import parse from 'html-react-parser'
import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, UNKNOWN_TRANSACTION } from './types/terminalTypes'
import { wrapScript } from './utils/wrapScript'
/* eslint-disable-next-line */
export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> {
clipboardData: DataTransfer;
}
export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const { call, _deps, on, config, event, gistHandler, logHtmlResponse, logResponse, version } = props.plugin
const [toggleDownUp, setToggleDownUp] = useState('fa-angle-double-down')
const [_cmdIndex, setCmdIndex] = useState(-1)
const [_cmdTemp, setCmdTemp] = useState('')
// dragable state
const [leftHeight, setLeftHeight] = useState<undefined | number>(undefined)
const [separatorYPosition, setSeparatorYPosition] = useState<undefined | number>(undefined)
const [dragging, setDragging] = useState(false)
const [newstate, dispatch] = useReducer(registerCommandReducer, initialState)
const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState)
const [, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState)
const [toaster, setToaster] = useState(false)
const [toastProvider, setToastProvider] = useState({ show: false, fileName: '' })
const [modalState, setModalState] = useState({
message: '',
title: '',
okLabel: '',
cancelLabel: '',
hide: true,
cancelFn: () => {},
handleHide: () => {}
})
const [clearConsole, setClearConsole] = useState(false)
const [paste, setPaste] = useState(false)
const [autoCompletState, setAutoCompleteState] = useState({
activeSuggestion: 0,
data: {
_options: []
},
_startingElement: 0,
autoCompleteSelectedItem: {},
_elementToShow: 4,
_selectedElement: 0,
filteredCommands: [],
filteredPrograms: [],
showSuggestions: false,
text: '',
userInput: '',
extraCommands: [],
commandHistoryIndex: 0
})
const [searchInput, setSearchInput] = useState('')
const [showTableHash, setShowTableHash] = useState([])
// terminal inputRef
const inputEl = useRef(null)
const messagesEndRef = useRef(null)
// terminal dragable
const leftRef = useRef(null)
const panelRef = useRef(null)
const scrollToBottom = () => {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
}
useEffect(() => {
scriptRunnerDispatch({ type: 'html', payload: { message: logHtmlResponse } })
}, [logHtmlResponse])
useEffect(() => {
scriptRunnerDispatch({ type: 'log', payload: { message: logResponse } })
}, [logResponse])
// events
useEffect(() => {
initListeningOnNetwork(props.plugin, scriptRunnerDispatch)
registerLogScriptRunnerAction(on, 'log', newstate.commands, scriptRunnerDispatch)
registerInfoScriptRunnerAction(on, 'info', newstate.commands, scriptRunnerDispatch)
registerWarnScriptRunnerAction(on, 'warn', newstate.commands, scriptRunnerDispatch)
registerErrorScriptRunnerAction(on, 'error', newstate.commands, scriptRunnerDispatch)
registerCommandAction('html', _blocksRenderer('html'), { activate: true }, dispatch)
registerCommandAction('log', _blocksRenderer('log'), { activate: true }, dispatch)
registerCommandAction('info', _blocksRenderer('info'), { activate: true }, dispatch)
registerCommandAction('warn', _blocksRenderer('warn'), { activate: true }, dispatch)
registerCommandAction('error', _blocksRenderer('error'), { activate: true }, dispatch)
registerCommandAction('script', function execute (args, scopedCommands) {
var script = String(args[0])
_shell(script, scopedCommands, function (error, output) {
if (error) scriptRunnerDispatch({ type: 'error', payload: { message: error } })
if (output) scriptRunnerDispatch({ type: 'script', payload: { message: '5' } })
})
}, { activate: true }, dispatch)
}, [autoCompletState.text])
useEffect(() => {
scrollToBottom()
}, [newstate.journalBlocks.length, logHtmlResponse.length, toaster])
function execute (file, cb) {
function _execute (content, cb) {
if (!content) {
setToaster(true)
if (cb) cb()
return
}
newstate.commands.script(content)
}
if (typeof file === 'undefined') {
const content = _deps.editor.currentContent()
_execute(content, cb)
return
}
const provider = _deps.fileManager.fileProviderOf(file)
console.log({ provider })
if (!provider) {
// toolTip(`provider for path ${file} not found`)
setToastProvider({ show: true, fileName: file })
if (cb) cb()
return
}
provider.get(file, (error, content) => {
console.log({ content })
if (error) {
// toolTip(error)
// TODO: pop up
if (cb) cb()
return
}
_execute(content, cb)
})
}
function loadgist (id, cb) {
gistHandler.loadFromGist({ gist: id }, _deps.fileManager)
if (cb) cb()
}
const _shell = async (script, scopedCommands, done) => { // default shell
if (script.indexOf('remix:') === 0) {
return done(null, 'This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.')
}
if (script.indexOf('remix.') === 0) {
// we keep the old feature. This will basically only be called when the command is querying the "remix" object.
// for all the other case, we use the Code Executor plugin
const context = { remix: { exeCurrent: (script: any) => { return execute(undefined, script) }, loadgist: (id: any) => { return loadgist(id, () => {}) }, execute: (fileName, callback) => { return execute(fileName, callback) } } }
try {
const cmds = vm.createContext(context)
const result = vm.runInContext(script, cmds) // eslint-disable-line
console.log({ result })
return done(null, result)
} catch (error) {
return done(error.message)
}
}
try {
if (script.trim().startsWith('git')) {
// await this.call('git', 'execute', script) code might be used in the future
} else {
await call('scriptRunner', 'execute', script)
}
done()
} catch (error) {
done(error.message || error)
}
}
const handleMinimizeTerminal = (e) => {
e.preventDefault()
e.stopPropagation()
if (toggleDownUp === 'fa-angle-double-down') {
setToggleDownUp('fa-angle-double-up')
event.trigger('resize', [])
} else {
const terminalTopOffset = config.get('terminal-top-offset')
event.trigger('resize', [terminalTopOffset])
setToggleDownUp('fa-angle-double-down')
}
}
const focusinput = () => {
inputEl.current.focus()
}
const handleKeyDown = (event) => {
const suggestionCount = autoCompletState.activeSuggestion
if (autoCompletState.userInput !== '' && (event.which === 27 || event.which === 8 || event.which === 46)) {
// backspace or any key that should remove the autocompletion
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false }))
}
if (autoCompletState.showSuggestions && (event.which === 13 || event.which === 9)) {
if (autoCompletState.userInput.length === 1) {
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false, userInput: Object.keys(autoCompletState.data._options[0]).toString() }))
} else {
if (autoCompletState.showSuggestions && (event.which === 13 || event.which === 9)) {
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false, userInput: autoCompletState.data._options[autoCompletState.activeSuggestion] ? Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion]).toString() : inputEl.current.value }))
} else {
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false, userInput: autoCompletState.data._options.length === 1 ? Object.keys(autoCompletState.data._options[0]).toString() : inputEl.current.value }))
}
}
}
if (event.which === 13 && !autoCompletState.showSuggestions) {
if (event.ctrlKey) { // <ctrl+enter>
// on enter, append the value in the cli input to the journal
inputEl.current.focus()
} else { // <enter>
event.preventDefault()
setCmdIndex(-1)
setCmdTemp('')
const script = autoCompletState.userInput.trim() // inputEl.current.innerText.trim()
if (script.length) {
cmdHistoryDispatch({ type: 'cmdHistory', payload: { script } })
newstate.commands.script(wrapScript(script))
}
setAutoCompleteState(prevState => ({ ...prevState, userInput: '' }))
inputEl.current.innerText = ''
inputEl.current.focus()
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false }))
}
} else if (newstate._commandHistory.length && event.which === 38 && !autoCompletState.showSuggestions && (autoCompletState.userInput === '')) {
event.preventDefault()
setAutoCompleteState(prevState => ({ ...prevState, userInput: newstate._commandHistory[0] }))
} else if (event.which === 38 && autoCompletState.showSuggestions) {
event.preventDefault()
if (autoCompletState.activeSuggestion === 0) {
return
}
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: suggestionCount - 1, userInput: Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion]).toString() }))
} else if (event.which === 38 && !autoCompletState.showSuggestions) { // <arrowUp>
if (cmdHistory.length - 1 > _cmdIndex) {
setCmdIndex(prevState => prevState++)
}
inputEl.current.innerText = cmdHistory[_cmdIndex]
inputEl.current.focus()
} else if (event.which === 40 && autoCompletState.showSuggestions) {
event.preventDefault()
if ((autoCompletState.activeSuggestion + 1) === autoCompletState.data._options.length) {
return
}
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: suggestionCount + 1, userInput: Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion + 1]).toString() }))
} else if (event.which === 40 && !autoCompletState.showSuggestions) {
if (_cmdIndex > -1) {
setCmdIndex(prevState => prevState--)
}
inputEl.current.innerText = _cmdIndex >= 0 ? cmdHistory[_cmdIndex] : _cmdTemp
inputEl.current.focus()
} else {
setCmdTemp(inputEl.current.innerText)
}
}
/* start of mouse events */
const mousedown = (event: MouseEvent) => {
setSeparatorYPosition(event.clientY)
leftRef.current.style.backgroundColor = '#007AA6'
leftRef.current.style.border = '2px solid #007AA6'
setDragging(true)
}
const onMouseMove: any = (e: MouseEvent) => {
e.preventDefault()
if (dragging && leftHeight && separatorYPosition) {
const newLeftHeight = leftHeight + separatorYPosition - e.clientY
setSeparatorYPosition(e.clientY)
setLeftHeight(newLeftHeight)
event.trigger('resize', [newLeftHeight + 32])
}
}
const onMouseUp = () => {
leftRef.current.style.backgroundColor = ''
leftRef.current.style.border = ''
setDragging(false)
}
/* end of mouse event */
useEffect(() => {
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
return () => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
}, [onMouseMove, onMouseUp])
React.useEffect(() => {
if (panelRef) {
if (!leftHeight) {
setLeftHeight(panelRef.current.offsetHeight)
return
}
panelRef.current.style.height = `${leftHeight}px`
}
}, [leftHeight, setLeftHeight, panelRef])
/* block contents that gets rendered from scriptRunner */
const _blocksRenderer = (mode) => {
if (mode === 'html') {
return function logger (args) {
if (args.length) {
return args[0]
}
}
}
mode = {
log: 'text-log',
info: 'text-info',
warn: 'text-warning',
error: 'text-danger'
}[mode] // defaults
if (mode) {
const filterUndefined = (el) => el !== undefined && el !== null
return function logger (args) {
const types = args.filter(filterUndefined).map(type => type)
const values = javascriptserialize.apply(null, args.filter(filterUndefined)).map(function (val, idx) {
if (typeof args[idx] === 'string') {
const el = document.createElement('div')
el.innerHTML = args[idx].replace(/(\r\n|\n|\r)/gm, '<br>')
val = el.children.length === 0 ? el.firstChild : el
}
if (types[idx] === 'element') val = jsbeautify.html(val)
return val
})
if (values.length) {
return values
}
}
} else {
throw new Error('mode is not supported')
}
}
/* end of block content that gets rendered from script Runner */
const handleClearConsole = () => {
setClearConsole(true)
dispatch({ type: 'clearconsole', payload: [] })
inputEl.current.focus()
}
/* start of autoComplete */
const listenOnNetwork = (e: any) => {
const isListening = e.target.checked
// setIsListeningOnNetwork(isListening)
listenOnNetworkAction(event, isListening)
}
const onChange = (event: any) => {
event.preventDefault()
const inputString = event.target.value
if (matched(allPrograms, inputString) || inputString.includes('.')) {
if (paste) {
setPaste(false)
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false, userInput: inputString }))
} else {
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: true, userInput: inputString }))
}
const textList = inputString.split('.')
if (textList.length === 1) {
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [] } }))
const result = Objectfilter(allPrograms, autoCompletState.userInput)
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: result } }))
} else {
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [] } }))
const result = Objectfilter(allCommands, autoCompletState.userInput)
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: result } }))
}
} else {
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false, userInput: inputString }))
}
}
const handleSelect = (event) => {
const suggestionCount = autoCompletState.activeSuggestion
if (event.keyCode === 38) {
if (autoCompletState.activeSuggestion === 0) {
return
}
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: suggestionCount - 1 }))
} else if (event.keyCode === 40) {
if (autoCompletState.activeSuggestion - 1 === autoCompletState.data._options.length) {
return
}
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: suggestionCount + 1 }))
}
}
const modal = (title: string, message: string, okLabel: string, hide: boolean, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => {
setModalState(prevState => ({ ...prevState, message, okLabel, okFn, cancelLabel, cancelFn, hide }))
}
const handleHideModal = () => {
setModalState(prevState => ({ ...prevState, hide: true }))
}
const txDetails = (event, tx) => {
if (showTableHash.includes(tx.hash)) {
const index = showTableHash.indexOf(tx.hash)
if (index > -1) {
setShowTableHash((prevState) => prevState.filter((x) => x !== tx.hash))
}
} else {
setShowTableHash((prevState) => ([...prevState, tx.hash]))
}
}
const handleAutoComplete = () => (
<div className='popup alert alert-secondary' style={{ display: (autoCompletState.showSuggestions && autoCompletState.userInput !== '') && autoCompletState.data._options.length > 0 ? 'block' : 'none' }}>
<div>
{autoCompletState.data._options.map((item, index) => {
return (
<div key={index} data-id="autoCompletePopUpAutoCompleteItem" className={`autoCompleteItem listHandlerShow item ${autoCompletState.data._options[autoCompletState.activeSuggestion] === item ? 'border border-primary ' : ''}`} onKeyDown={ handleSelect } >
<div>
{getKeyOf(item)}
</div>
<div>
{getValueOf(item)}
</div>
</div>
)
})}
</div>
</div>
)
/* end of autoComplete */
const handlePaste = () => {
setPaste(true)
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false }))
}
return (
<div style={{ height: '323px', flexGrow: 1 }} className='panel' ref={panelRef}>
<div className="bar">
<div className="dragbarHorizontal" onMouseDown={mousedown} ref={leftRef}></div>
<div className="menu border-top border-dark bg-light" data-id="terminalToggleMenu">
<i className={`mx-2 toggleTerminal fas ${toggleDownUp}`} data-id="terminalToggleIcon" onClick={ handleMinimizeTerminal }></i>
<div className="mx-2 console" id="clearConsole" data-id="terminalClearConsole" onClick={handleClearConsole} >
<i className="fas fa-ban" aria-hidden="true" title="Clear console"
></i>
</div>
<div className="mx-2" title='Pending Transactions'>0</div>
<div className="pt-1 h-80 mx-3 align-items-center listenOnNetwork custom-control custom-checkbox">
<input
className="custom-control-input"
id="listenNetworkCheck"
onChange={listenOnNetwork}
type="checkbox"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
/>
<label
className="pt-1 form-check-label custom-control-label text-nowrap"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
htmlFor="listenNetworkCheck"
>
listen on network
</label>
</div>
<div className="search">
<i className="fas fa-search searchIcon bg-light" aria-hidden="true"></i>
<input
onChange={(event) => setSearchInput(event.target.value.trim()) }
type="text"
className="border filter form-control"
id="searchInput"
placeholder="Search with transaction hash or address"
data-id="terminalInputSearch" />
</div>
</div>
</div>
<div tabIndex={-1} className="terminal_container" data-id="terminalContainer" >
{
handleAutoComplete()
}
<div data-id='terminalContainerDisplay' style = {{
position: 'relative',
height: '100%',
width: '100%',
opacity: '0.1',
zIndex: -1
}}></div>
<div className="terminal">
<div id='journal' className='journal' data-id='terminalJournal'>
{!clearConsole && <TerminalWelcomeMessage packageJson={version}/>}
{newstate.journalBlocks && newstate.journalBlocks.map((x, index) => {
if (x.name === EMPTY_BLOCK) {
return (
<div className="px-4 block" data-id='block' key={index}>
<span className='txLog'>
<span className='tx'><div className='txItem'>[<span className='txItemTitle'>block:{x.message} - </span> 0 {'transactions'} ] </div></span></span>
</div>
)
} else if (x.name === UNKNOWN_TRANSACTION) {
return x.message.filter(x => x.tx.hash.includes(searchInput) || x.tx.from.includes(searchInput) || (x.tx.to.includes(searchInput))).map((trans) => {
return (<div className='px-4 block' data-id={`block_tx${trans.tx.hash}`} key={index}> { <RenderUnKnownTransactions tx={trans.tx} receipt={trans.receipt} index={index} plugin={props.plugin} showTableHash={showTableHash} txDetails={txDetails} modal={modal}/>} </div>)
})
} else if (x.name === KNOWN_TRANSACTION) {
return x.message.map((trans) => {
return (<div className='px-4 block' data-id={`block_tx${trans.tx.hash}`} key={index}> { trans.tx.isCall ? <RenderCall tx={trans.tx} resolvedData={trans.resolvedData} logs={trans.logs} index={index} plugin={props.plugin} showTableHash={showTableHash} txDetails={txDetails} modal={modal}/> : (<RenderKnownTransactions tx = { trans.tx } receipt = { trans.receipt } resolvedData = { trans.resolvedData } logs = {trans.logs } index = { index } plugin = { props.plugin } showTableHash = { showTableHash } txDetails = { txDetails } modal={modal}/>) } </div>)
})
} else if (Array.isArray(x.message)) {
return x.message.map((msg, i) => {
if (typeof msg === 'object') {
return (
<div className="px-4 block" data-id="block" key={i}><span className={x.style}>{ msg.value ? parse(msg.value) : JSON.stringify(msg) } </span></div>
)
} else {
return (
<div className="px-4 block" data-id="block" key={i}><span className={x.style}>{ msg ? msg.toString().replace(/,/g, '') : msg }</span></div>
)
}
})
} else {
if (typeof x.message !== 'function') {
return (
<div className="px-4 block" data-id="block" key={index}> <span className={x.style}> {x.message}</span></div>
)
}
}
})}
<div ref={messagesEndRef} />
</div>
<div id="terminalCli" data-id="terminalCli" className="cli" onClick={focusinput}>
<span className="prompt">{'>'}</span>
<input className="input" ref={inputEl} spellCheck="false" contentEditable="true" id="terminalCliInput" data-id="terminalCliInput" onChange={(event) => onChange(event)} onKeyDown={(event) => handleKeyDown(event) } value={autoCompletState.userInput} onPaste={handlePaste}></input>
</div>
</div>
</div>
<ModalDialog
title={ modalState.title }
message={ modalState.message }
hide={ modalState.hide }
okLabel={ modalState.okLabel }
cancelLabel={ modalState.cancelLabel }
cancelFn={ modalState.cancelFn }
handleHide={ handleHideModal }
/>
{toaster && <Toaster message="no content to execute"/>}
{toastProvider.show && <Toaster message={`provider for path ${toastProvider.fileName} not found`} />}
</div>
)
}
export default RemixUiTerminal

@ -0,0 +1,29 @@
import React from 'react' // eslint-disable-line
const TerminalWelcomeMessage = ({ packageJson }) => {
return (
<div className="px-4 block" data-id="block_null">
<div> - Welcome to Remix {packageJson} - </div><br />
<div>You can use this terminal to: </div>
<ul className='ul'>
<li>Check transactions details and start debugging.</li>
<li>Execute JavaScript scripts:
<br />
<i> - Input a script directly in the command line interface </i>
<br />
<i> - Select a Javascript file in the file explorer and then run \`remix.execute()\` or \`remix.exeCurrent()\` in the command line interface </i>
<br />
<i> - Right click on a JavaScript file in the file explorer and then click \`Run\` </i>
</li>
</ul>
<div>The following libraries are accessible:</div>
<ul className='ul'>
<li><a target="_blank" href="https://web3js.readthedocs.io/en/1.0/">web3 version 1.5.2</a></li>
<li><a target="_blank" href="https://docs.ethers.io">ethers.js</a> </li>
<li>remix (run remix.help() for more info)</li>
</ul>
</div>
)
}
export default TerminalWelcomeMessage

@ -0,0 +1,28 @@
export interface ROOTS {
steps: any,
cmd: string,
gidx: number,
idx: number
}
export const KNOWN_TRANSACTION = 'knownTransaction'
export const UNKNOWN_TRANSACTION = 'unknownTransaction'
export const EMPTY_BLOCK = 'emptyBlock'
export const NEW_TRANSACTION = 'newTransaction'
export const NEW_BLOCK = 'newBlock'
export const NEW_CALL = 'newCall'
export const HTML = 'html'
export const LOG = 'log'
export const INFO = 'info'
export const WARN = 'warn'
export const ERROR = 'error'
export const SCRIPT = 'script'
export const CLEAR_CONSOLE = 'clearconsole'
export const LISTEN_ON_NETWORK = 'listenOnNetWork'
export const CMD_HISTORY = 'cmdHistory'
export interface RemixUiTerminalProps {
plugin: any
}

@ -0,0 +1,42 @@
export const getKeyOf = (item) => {
return Object.keys(item)[0]
}
export const getValueOf = (item) => {
return Object.values(item)[0]
}
export const Objectfilter = (obj: any, filterValue: any) =>
obj.filter((item: any) => Object.keys(item)[0].indexOf(filterValue) > -1)
export const matched = (arr, value) => arr.map(x => Object.keys(x).some(x => x.startsWith(value))).some(x => x === true)
const findDeep = (object, fn, found = { break: false, value: undefined }) => {
if (typeof object !== 'object' || object === null) return
for (var i in object) {
if (found.break) break
var el = object[i]
if (el && el.innerText !== undefined && el.innerText !== null) el = el.innerText
if (fn(el, i, object)) {
found.value = el
found.break = true
break
} else {
findDeep(el, fn, found)
}
}
return found.value
}
export const find = (args, query) => {
query = query.trim()
const isMatch = !!findDeep(args, function check (value) {
if (value === undefined || value === null) return false
if (typeof value === 'function') return false
if (typeof value === 'object') return false
const contains = String(value).indexOf(query.trim()) !== -1
return contains
})
return isMatch
}

@ -0,0 +1,16 @@
export const wrapScript = (script) => {
const isKnownScript = ['remix.', 'git'].some(prefix => script.trim().startsWith(prefix))
if (isKnownScript) return script
return `
try {
const ret = ${script};
if (ret instanceof Promise) {
ret.then((result) => { console.log(result) }).catch((error) => { console.log(error) })
} else {
console.log(ret)
}
} catch (e) {
console.log(e.message)
}
`
}

@ -0,0 +1,16 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -332,7 +332,7 @@ export const Workspace = (props: WorkspaceProps) => {
return (
<div className='remixui_container'>
<ModalDialog
{ state.modal.message && <ModalDialog
id='workspacesModalDialog'
title={ state.modal.title }
message={ state.modal.message }
@ -344,6 +344,7 @@ export const Workspace = (props: WorkspaceProps) => {
handleHide={ handleHideModal }>
{ (typeof state.modal.message !== 'string') && state.modal.message }
</ModalDialog>
}
<Toaster message={state.toasterMsg} />
<div className='remixui_fileexplorer' onClick={() => resetFocus(true)}>
<div>

@ -121,6 +121,9 @@
"remix-ui-renderer": {
"tags": []
},
"remix-ui-terminal": {
"tags": []
},
"solidity-compiler": {
"tags": []
},

@ -45,8 +45,8 @@
"workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph",
"help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,remix-ui-plugin-manager",
"build:libs": "nx run-many --target=build --parallel=false --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,remix-ui-plugin-manager,remix-ui-terminal",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs",
"build:e2e": "tsc -p apps/remix-ide-e2e/tsconfig.e2e.json",
@ -163,6 +163,7 @@
"file-saver": "^2.0.5",
"form-data": "^4.0.0",
"fs-extra": "^3.0.1",
"html-react-parser": "^1.3.0",
"http-server": "^0.11.1",
"intro.js": "^4.1.0",
"isbinaryfile": "^3.0.2",

@ -43,8 +43,9 @@
"@remix-project/core-plugin": ["libs/remix-core-plugin/src/index.ts"],
"@remix-ui/solidity-compiler": ["libs/remix-ui/solidity-compiler/src/index.ts"],
"@remix-ui/publish-to-storage": ["libs/remix-ui/publish-to-storage/src/index.ts"],
"@remix-ui/plugin-manager": ["libs/remix-ui/plugin-manager/src/index.ts"],
"@remix-ui/renderer": ["libs/remix-ui/renderer/src/index.ts"]
"@remix-ui/renderer": ["libs/remix-ui/renderer/src/index.ts"],
"@remix-ui/terminal": ["libs/remix-ui/terminal/src/index.ts"],
"@remix-ui/plugin-manager": ["libs/remix-ui/plugin-manager/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]

@ -778,6 +778,22 @@
}
}
},
"remix-ui-terminal": {
"root": "libs/remix-ui/terminal",
"sourceRoot": "libs/remix-ui/terminal/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/terminal/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/terminal/**/*"]
}
}
}
},
"remix-ui-plugin-manager": {
"root": "libs/remix-ui/plugin-manager",
"sourceRoot": "libs/remix-ui/plugin-manager/src",

Loading…
Cancel
Save