diff --git a/.gitignore b/.gitignore
index d0429e2909..d35a9539a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@ soljson.js
*.launch
.settings/
*.sublime-workspace
+.vscode/
# IDE - VSCode
.vscode/*
diff --git a/apps/remix-ide-e2e/src/commands/addFile.ts b/apps/remix-ide-e2e/src/commands/addFile.ts
index aa1f8d7f6e..8fc6b1940b 100644
--- a/apps/remix-ide-e2e/src/commands/addFile.ts
+++ b/apps/remix-ide-e2e/src/commands/addFile.ts
@@ -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)
diff --git a/apps/remix-ide-e2e/src/commands/executeScript.ts b/apps/remix-ide-e2e/src/commands/executeScript.ts
index 77649a2664..dbb93e66db 100644
--- a/apps/remix-ide-e2e/src/commands/executeScript.ts
+++ b/apps/remix-ide-e2e/src/commands/executeScript.ts
@@ -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)
diff --git a/apps/remix-ide-e2e/src/commands/journalLastChildIncludes.ts b/apps/remix-ide-e2e/src/commands/journalLastChildIncludes.ts
index e99f611a79..7e63d703b6 100644
--- a/apps/remix-ide-e2e/src/commands/journalLastChildIncludes.ts
+++ b/apps/remix-ide-e2e/src/commands/journalLastChildIncludes.ts
@@ -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
diff --git a/apps/remix-ide-e2e/src/commands/testFunction.ts b/apps/remix-ide-e2e/src/commands/testFunction.ts
index e16d613718..371fec9568 100644
--- a/apps/remix-ide-e2e/src/commands/testFunction.ts
+++ b/apps/remix-ide-e2e/src/commands/testFunction.ts
@@ -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])
diff --git a/apps/remix-ide-e2e/src/commands/verifyContracts.ts b/apps/remix-ide-e2e/src/commands/verifyContracts.ts
index e7dbf4a591..aef1b06217 100644
--- a/apps/remix-ide-e2e/src/commands/verifyContracts.ts
+++ b/apps/remix-ide-e2e/src/commands/verifyContracts.ts
@@ -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) {
diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts
index 84e768f420..702de52e10 100644
--- a/apps/remix-ide-e2e/src/tests/ballot.test.ts
+++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts
@@ -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')
diff --git a/apps/remix-ide-e2e/src/tests/compiler_api.test.ts b/apps/remix-ide-e2e/src/tests/compiler_api.test.ts
index 4128a057bd..cc81e5ace4 100644
--- a/apps/remix-ide-e2e/src/tests/compiler_api.test.ts
+++ b/apps/remix-ide-e2e/src/tests/compiler_api.test.ts
@@ -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"]')
},
diff --git a/apps/remix-ide-e2e/src/tests/debugger.spec.ts b/apps/remix-ide-e2e/src/tests/debugger.spec.ts
index f069bce1a2..7104fef041 100644
--- a/apps/remix-ide-e2e/src/tests/debugger.spec.ts
+++ b/apps/remix-ide-e2e/src/tests/debugger.spec.ts
@@ -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) {
diff --git a/apps/remix-ide-e2e/src/tests/defaultLayout.test.ts b/apps/remix-ide-e2e/src/tests/defaultLayout.test.ts
index 94bc20207d..c8133c4c20 100644
--- a/apps/remix-ide-e2e/src/tests/defaultLayout.test.ts
+++ b/apps/remix-ide-e2e/src/tests/defaultLayout.test.ts
@@ -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) {
diff --git a/apps/remix-ide-e2e/src/tests/editor.spec.ts b/apps/remix-ide-e2e/src/tests/editor.spec.ts
index 83d4f23f8f..721b4c7741 100644
--- a/apps/remix-ide-e2e/src/tests/editor.spec.ts
+++ b/apps/remix-ide-e2e/src/tests/editor.spec.ts
@@ -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)')
diff --git a/apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts b/apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts
index 26a49d9beb..94d1e6378a 100644
--- a/apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts
+++ b/apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts
@@ -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()
diff --git a/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts b/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts
index 0a759aa332..9293775733 100644
--- a/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts
+++ b/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts
@@ -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)
})
diff --git a/apps/remix-ide-e2e/src/tests/solidityUnittests.spec.ts b/apps/remix-ide-e2e/src/tests/solidityUnittests.spec.ts
index fb1b5d7a93..b9dcccf13b 100644
--- a/apps/remix-ide-e2e/src/tests/solidityUnittests.spec.ts
+++ b/apps/remix-ide-e2e/src/tests/solidityUnittests.spec.ts
@@ -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)
diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts
index 0f2f0928e7..1fa68d3348 100644
--- a/apps/remix-ide-e2e/src/tests/terminal.test.ts
+++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts
@@ -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,19 +53,19 @@ 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) {
browser
- .click('*[data-id="terminalClearConsole"]') // clear the terminal
+ .click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('udapp')
.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()
}
}
diff --git a/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts b/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts
index 7dfbd968e9..55f2eadacb 100644
--- a/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts
+++ b/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts
@@ -99,7 +99,7 @@ module.exports = {
'decoded output': {
0: 'uint256: _uret 2343242',
1: 'int256: _iret -4324324',
- 2: 'string: _strret string _ string _ string _ string _ string _ string _ string _ string _ string _ string _'
+ 2: 'string: _strret string _ string _ string _ string _ string _ string _ string _ string _ string _ string _'
}
})
.pause(500)
@@ -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"')
diff --git a/apps/remix-ide-e2e/src/tests/workspace.test.ts b/apps/remix-ide-e2e/src/tests/workspace.test.ts
index b306076763..204b8e7c31 100644
--- a/apps/remix-ide-e2e/src/tests/workspace.test.ts
+++ b/apps/remix-ide-e2e/src/tests/workspace.test.ts
@@ -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()
},
diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js
index 03ecc61b55..20c2e511b6 100644
--- a/apps/remix-ide/src/app.js
+++ b/apps/remix-ide/src/app.js
@@ -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 })
diff --git a/apps/remix-ide/src/app/components/main-panel.js b/apps/remix-ide/src/app/components/main-panel.js
index 62d76cf4c3..66e6a50245 100644
--- a/apps/remix-ide/src/app/components/main-panel.js
+++ b/apps/remix-ide/src/app/components/main-panel.js
@@ -31,7 +31,7 @@ export class MainPanel extends AbstractPanel {
render () {
return yo`
-
+
${this.view}
`
}
diff --git a/apps/remix-ide/src/app/panels/terminal.js b/apps/remix-ide/src/app/panels/terminal.js
index bd474b41fb..12b95d8b06 100644
--- a/apps/remix-ide/src/app/panels/terminal.js
+++ b/apps/remix-ide/src/app/panels/terminal.js
@@ -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`
`
-
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)
+ 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)
})
- 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)
- })
- }, { 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)
+ this.logHtmlResponse.push(html.innerText)
+ this.renderComponent()
+ this.resetLogHtml()
}
- focus () {
- if (this._view.input) this._view.input.focus()
+ resetLogHtml () {
+ this.logHtmlResponse = []
}
- render () {
- var self = this
- if (self._view.el) return self._view.el
- self._view.journal = yo`
`
- self._view.input = yo`
-
{ this.focus() }} onpaste=${paste} onkeydown=${change}>
- `
- 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`
-
- ${'>'}
- ${self._view.input}
-
- `
-
- self._view.icon = yo`
-
`
- self._view.dragbar = yo`
-
`
-
- self._view.pendingTxCount = yo`
0
`
- self._view.inputSearch = yo`
- `
- self._view.bar = yo`
-
- ${self._view.dragbar}
-
-
- `
- self._view.term = yo`
-
- ${self._components.autoCompletePopup.render()}
-
-
- ${self._view.journal}
- ${self._view.cli}
-
-
- `
- self._view.el = yo`
-
- ${self._view.bar}
- ${self._view.term}
-
- `
- 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;
- }
- .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`
`
- var background = yo`
`
- var placeholder = yo`
${background}${text}
`
- 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`
-
- Welcome to Remix ${packageJson.version} -
-
You can use this terminal to:
-
- Check transactions details and start debugging.
- Execute JavaScript scripts:
-
- - Input a script directly in the command line interface
-
- - Select a Javascript file in the file explorer and then run \`remix.execute()\` or \`remix.exeCurrent()\` in the command line interface
-
- - Right click on a JavaScript file in the file explorer and then click \`Run\`
-
-
-
The following libraries are accessible:
-
-
- `
-
- 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) { //
- self._view.input.innerText += '\n'
- self.putCursor2End(self._view.input)
- self.scroll2bottom()
- self._components.autoCompletePopup.removeAutoComplete()
- } else { //
- 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) { //
- 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) { //
- 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
- }
- }
+ log (message) {
+ this.logResponse.push(message)
+ this.renderComponent()
+ this.resetLog()
}
- 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
- }
- }
-
- 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(
+ ,
+ 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, ' ')
- val = el.children.length === 0 ? el.firstChild : el
- }
- if (types[idx] === 'element') val = jsbeautify.html(val)
- return val
- })
- if (values.length) {
- append(yo`${values} `)
- }
- }
- } 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`${result} `)
- done()
- } catch (error) {
- done(error.message || error)
- }
- }
}
-function domTerminalFeatures (self, scopedCommands, blockchain) {
- return {
- remix: self._components.cmdInterpreter
- }
-}
-
-function blockify (el) { return yo`${el}
` }
-
module.exports = Terminal
diff --git a/apps/remix-ide/src/lib/cmdInterpreterAPI.js b/apps/remix-ide/src/lib/cmdInterpreterAPI.js
index 9c79cae25b..1bd834054b 100644
--- a/apps/remix-ide/src/lib/cmdInterpreterAPI.js
+++ b/apps/remix-ide/src/lib/cmdInterpreterAPI.js
@@ -93,7 +93,6 @@ class CmdInterpreterAPI {
if (cb) cb()
return
}
-
self._components.terminal.commands.script(content)
}
diff --git a/apps/solidity-compiler/src/app/app.tsx b/apps/solidity-compiler/src/app/app.tsx
index 00ce99f3d7..85e67924b8 100644
--- a/apps/solidity-compiler/src/app/app.tsx
+++ b/apps/solidity-compiler/src/app/app.tsx
@@ -6,7 +6,7 @@ import { CompilerClientApi } from './compiler'
const remix = new CompilerClientApi()
-export const App = () => {
+export const App = () => {
return (
diff --git a/apps/solidity-compiler/src/app/compiler.ts b/apps/solidity-compiler/src/app/compiler.ts
index 5a6e8eafaa..4f90fa4bd3 100644
--- a/apps/solidity-compiler/src/app/compiler.ts
+++ b/apps/solidity-compiler/src/app/compiler.ts
@@ -30,7 +30,6 @@ const defaultCompilerParameters = {
evmVersion: null, // compiler default
language: 'Solidity'
}
-
export class CompilerClientApi extends CompilerApiMixin(PluginClient) implements ICompilerApi {
constructor () {
super()
diff --git a/libs/remix-ui/solidity-compiler/src/lib/actions/compiler.ts b/libs/remix-ui/solidity-compiler/src/lib/actions/compiler.ts
index 17e58a0052..bc43ed73b1 100644
--- a/libs/remix-ui/solidity-compiler/src/lib/actions/compiler.ts
+++ b/libs/remix-ui/solidity-compiler/src/lib/actions/compiler.ts
@@ -42,7 +42,6 @@ export const listenToEvents = (compileTabLogic: CompileTabLogic, api) => (dispat
api.onContentChanged = () => {
dispatch(setEditorMode('contentChanged'))
}
-
compileTabLogic.compiler.event.register('loadingCompiler', () => {
dispatch(setCompilerMode('loadingCompiler'))
})
diff --git a/libs/remix-ui/terminal/.babelrc b/libs/remix-ui/terminal/.babelrc
new file mode 100644
index 0000000000..09d67939cc
--- /dev/null
+++ b/libs/remix-ui/terminal/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["@nrwl/react/babel"],
+ "plugins": []
+}
diff --git a/libs/remix-ui/terminal/.eslintrc b/libs/remix-ui/terminal/.eslintrc
new file mode 100644
index 0000000000..dae5c6feeb
--- /dev/null
+++ b/libs/remix-ui/terminal/.eslintrc
@@ -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"
+ }
+}
diff --git a/libs/remix-ui/terminal/README.md b/libs/remix-ui/terminal/README.md
new file mode 100644
index 0000000000..4638096417
--- /dev/null
+++ b/libs/remix-ui/terminal/README.md
@@ -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).
diff --git a/libs/remix-ui/terminal/package.json b/libs/remix-ui/terminal/package.json
new file mode 100644
index 0000000000..623cf89c25
--- /dev/null
+++ b/libs/remix-ui/terminal/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "remix-ui-terminal",
+ "version": "0.0.1"
+}
diff --git a/libs/remix-ui/terminal/src/index.ts b/libs/remix-ui/terminal/src/index.ts
new file mode 100644
index 0000000000..5b8eeca5b5
--- /dev/null
+++ b/libs/remix-ui/terminal/src/index.ts
@@ -0,0 +1 @@
+export * from './lib/remix-ui-terminal'
diff --git a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts
new file mode 100644
index 0000000000..dafe6f78cf
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts
@@ -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
) => {
+ 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) => {
+ const data: any = {
+ filterFns: {}
+ }
+ data.filterFns[name] = filterFn
+ dispatch({ type: name, payload: { data: data } })
+}
+
+export const registerLogScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch) => {
+ 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) => {
+ 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) => {
+ 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) => {
+ 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) => {
+ 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) => {
+ 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)
+ })
+}
diff --git a/libs/remix-ui/terminal/src/lib/commands.ts b/libs/remix-ui/terminal/src/lib/commands.ts
new file mode 100644
index 0000000000..cc3b5e6963
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/commands.ts
@@ -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' }
+]
diff --git a/libs/remix-ui/terminal/src/lib/components/ChechTxStatus.tsx b/libs/remix-ui/terminal/src/lib/components/ChechTxStatus.tsx
new file mode 100644
index 0000000000..005be81e02
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/components/ChechTxStatus.tsx
@@ -0,0 +1,16 @@
+import React from 'react' // eslint-disable-line
+
+const CheckTxStatus = ({ tx, type }) => {
+ if (tx.status === '0x1' || tx.status === true) {
+ return ( )
+ }
+ if (type === 'call' || type === 'unknownCall' || type === 'unknown') {
+ return (call )
+ } else if (tx.status === '0x0' || tx.status === false) {
+ return ( )
+ } else {
+ return ( )
+ }
+}
+
+export default CheckTxStatus
diff --git a/libs/remix-ui/terminal/src/lib/components/Context.tsx b/libs/remix-ui/terminal/src/lib/components/Context.tsx
new file mode 100644
index 0000000000..a354c4cab4
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/components/Context.tsx
@@ -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 (
+
+
+ [vm]
+ from: {from}
+ to: {to}
+ value: {value} wei
+ data: {input}
+ logs: {logs}
+ hash: {hash}
+
+
)
+ } else if (blockchain.getProvider() !== 'vm' && data.resolvedData) {
+ return (
+
+
+ [block:{block} txIndex:{i}]
+ from: {from}
+ to: {to}
+ value: {value} wei
+ data: {input}
+ logs: {logs}
+ hash: {hash}
+
+
)
+ } else {
+ hash = helper.shortenHexData(data.blockHash)
+ return (
+
+
+ [block:{block} txIndex:{i}]
+ from: {from}
+ to: {to}
+ value: {value} wei
+ data: {input}
+ logs: {logs}
+ hash: {hash}
+
+
)
+ }
+}
+
+export default Context
diff --git a/libs/remix-ui/terminal/src/lib/components/RenderCall.tsx b/libs/remix-ui/terminal/src/lib/components/RenderCall.tsx
new file mode 100644
index 0000000000..3e94f1e3aa
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/components/RenderCall.tsx
@@ -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 (
+
+ txDetails(event, tx)}>
+
+
+ [call]
+ from: {from}
+ to: {to}
+ data: {input}
+
+
+
debug(event, tx)}>Debug
+
+
+
+ {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}
+
+ )
+}
+
+export default RenderCall
diff --git a/libs/remix-ui/terminal/src/lib/components/RenderKnownTransactions.tsx b/libs/remix-ui/terminal/src/lib/components/RenderKnownTransactions.tsx
new file mode 100644
index 0000000000..79e1ba4c8a
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/components/RenderKnownTransactions.tsx
@@ -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 (
+
+ txDetails(event, tx)}>
+
+
+
+
debug(event, tx)}>Debug
+
+
+
+ {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}
+
+ )
+}
+
+export default RenderKnownTransactions
diff --git a/libs/remix-ui/terminal/src/lib/components/RenderUnknownTransactions.tsx b/libs/remix-ui/terminal/src/lib/components/RenderUnknownTransactions.tsx
new file mode 100644
index 0000000000..0a8d43835b
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/components/RenderUnknownTransactions.tsx
@@ -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 (
+
+ txDetails(event, tx)}>
+
+
+
+
debug(event, tx)}>Debug
+
+
+
+ {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}
+
+ )
+}
+
+export default RenderUnKnownTransactions
diff --git a/libs/remix-ui/terminal/src/lib/components/Table.tsx b/libs/remix-ui/terminal/src/lib/components/Table.tsx
new file mode 100644
index 0000000000..33e9cabf58
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/components/Table.tsx
@@ -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 (
+
+
+
+ status
+ {`${opts.status} ${msg}`}
+
+ {opts.hash ? (
+ transaction hash
+ {opts.hash}
+
+
+ ) : null }
+ {
+ opts.contractAddress ? (
+
+ contract address
+ {opts.contractAddress}
+
+
+
+ ) : null
+ }
+ {
+ opts.from ? (
+
+ from
+ {opts.from}
+
+
+
+ ) : null
+ }
+ {
+ opts.to ? (
+
+ to
+ {toHash}
+
+
+
+ ) : null
+ }
+ {
+ opts.gas ? (
+
+ gas
+ {opts.gas} gas
+
+
+
+ ) : null
+ }
+ {
+ opts.transactionCost ? (
+
+ transaction cost
+ {opts.transactionCost} gas {callWarning}
+
+
+
+ ) : null
+ }
+ {
+ opts.executionCost ? (
+
+ execution cost
+ {opts.executionCost} gas {callWarning}
+
+
+
+ ) : null
+ }
+ {opts.hash ? (
+
+ hash
+ {opts.hash}
+
+
+
+ ) : null}
+ {opts.input ? (
+
+ input
+ {helper.shortenHexData(opts.input)}
+
+
+
+ ) : null}
+ {opts['decoded input'] ? (
+
+ decoded input
+ {opts['decoded input'].trim()}
+
+
+
+ ) : null}
+ {opts['decoded output'] ? (
+
+ decoded output
+ {opts['decoded output']}
+
+
+
+ ) : null}
+ {opts.logs ? (
+
+ logs
+
+ {JSON.stringify(stringified, null, '\t')}
+
+
+
+
+ ) : null}
+ {opts.val ? (
+
+ val
+ {val} wei
+
+
+
+ ) : null}
+
+
+ )
+}
+export default showTable
diff --git a/libs/remix-ui/terminal/src/lib/custom-hooks/useKeyPress.tsx b/libs/remix-ui/terminal/src/lib/custom-hooks/useKeyPress.tsx
new file mode 100644
index 0000000000..12bbc1cefc
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/custom-hooks/useKeyPress.tsx
@@ -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
+}
diff --git a/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts b/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts
new file mode 100644
index 0000000000..7568737eae
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts
@@ -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: '' })
+ }
+ }
+}
diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css
new file mode 100644
index 0000000000..6c3d777539
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css
@@ -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;
+}
+
diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
new file mode 100644
index 0000000000..8b729f07d4
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
@@ -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 extends SyntheticEvent {
+ 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)
+ const [separatorYPosition, setSeparatorYPosition] = useState(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) { //
+ // on enter, append the value in the cli input to the journal
+ inputEl.current.focus()
+ } else { //
+ 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) { //
+ 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, ' ')
+ 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 = () => (
+ 0 ? 'block' : 'none' }}>
+
+ {autoCompletState.data._options.map((item, index) => {
+ return (
+
+
+ {getKeyOf(item)}
+
+
+ {getValueOf(item)}
+
+
+ )
+ })}
+
+
+ )
+ /* end of autoComplete */
+
+ const handlePaste = () => {
+ setPaste(true)
+ setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false }))
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
0
+
+
+
+ listen on network
+
+
+
+
+ setSearchInput(event.target.value.trim()) }
+ type="text"
+ className="border filter form-control"
+ id="searchInput"
+ placeholder="Search with transaction hash or address"
+ data-id="terminalInputSearch" />
+
+
+
+
+ {
+ handleAutoComplete()
+ }
+
+
+
+ {!clearConsole &&
}
+ {newstate.journalBlocks && newstate.journalBlocks.map((x, index) => {
+ if (x.name === EMPTY_BLOCK) {
+ return (
+
+
+ [block:{x.message} - 0 {'transactions'} ]
+
+ )
+ } 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 ( { }
)
+ })
+ } else if (x.name === KNOWN_TRANSACTION) {
+ return x.message.map((trans) => {
+ return ( { trans.tx.isCall ? : () }
)
+ })
+ } else if (Array.isArray(x.message)) {
+ return x.message.map((msg, i) => {
+ if (typeof msg === 'object') {
+ return (
+ { msg.value ? parse(msg.value) : JSON.stringify(msg) }
+ )
+ } else {
+ return (
+ { msg ? msg.toString().replace(/,/g, '') : msg }
+ )
+ }
+ })
+ } else {
+ if (typeof x.message !== 'function') {
+ return (
+ {x.message}
+ )
+ }
+ }
+ })}
+
+
+
+ {'>'}
+ onChange(event)} onKeyDown={(event) => handleKeyDown(event) } value={autoCompletState.userInput} onPaste={handlePaste}>
+
+
+
+
+ {toaster &&
}
+ {toastProvider.show &&
}
+
+ )
+}
+
+export default RemixUiTerminal
diff --git a/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx b/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx
new file mode 100644
index 0000000000..6f8cca225b
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx
@@ -0,0 +1,29 @@
+import React from 'react' // eslint-disable-line
+
+const TerminalWelcomeMessage = ({ packageJson }) => {
+ return (
+
+
- Welcome to Remix {packageJson} -
+
You can use this terminal to:
+
+ Check transactions details and start debugging.
+ Execute JavaScript scripts:
+
+ - Input a script directly in the command line interface
+
+ - Select a Javascript file in the file explorer and then run \`remix.execute()\` or \`remix.exeCurrent()\` in the command line interface
+
+ - Right click on a JavaScript file in the file explorer and then click \`Run\`
+
+
+
The following libraries are accessible:
+
+
+ )
+}
+
+export default TerminalWelcomeMessage
diff --git a/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts b/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts
new file mode 100644
index 0000000000..66eea4d655
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts
@@ -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
+}
diff --git a/libs/remix-ui/terminal/src/lib/utils/utils.ts b/libs/remix-ui/terminal/src/lib/utils/utils.ts
new file mode 100644
index 0000000000..62091ee9c8
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/utils/utils.ts
@@ -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
+}
diff --git a/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts b/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts
new file mode 100644
index 0000000000..3d9ddcdeea
--- /dev/null
+++ b/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts
@@ -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)
+ }
+ `
+}
diff --git a/libs/remix-ui/terminal/tsconfig.json b/libs/remix-ui/terminal/tsconfig.json
new file mode 100644
index 0000000000..d52e31ad74
--- /dev/null
+++ b/libs/remix-ui/terminal/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "jsx": "react",
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ }
+ ]
+}
diff --git a/libs/remix-ui/terminal/tsconfig.lib.json b/libs/remix-ui/terminal/tsconfig.lib.json
new file mode 100644
index 0000000000..b560bc4dec
--- /dev/null
+++ b/libs/remix-ui/terminal/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"]
+}
diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
index b1e2ade14b..ad8f1cc717 100644
--- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
+++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
@@ -332,7 +332,7 @@ export const Workspace = (props: WorkspaceProps) => {
return (
-
{
handleHide={ handleHideModal }>
{ (typeof state.modal.message !== 'string') && state.modal.message }
+ }
resetFocus(true)}>
diff --git a/nx.json b/nx.json
index 45d68149ae..e86b587935 100644
--- a/nx.json
+++ b/nx.json
@@ -121,6 +121,9 @@
"remix-ui-renderer": {
"tags": []
},
+ "remix-ui-terminal": {
+ "tags": []
+ },
"solidity-compiler": {
"tags": []
},
diff --git a/package.json b/package.json
index 1ec51a58e7..ca37e38812 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 86f477732d..38538c202a 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -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"]
diff --git a/workspace.json b/workspace.json
index 3d1a242f70..8105133226 100644
--- a/workspace.json
+++ b/workspace.json
@@ -778,9 +778,25 @@
}
}
},
+ "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",
+ "root": "libs/remix-ui/plugin-manager",
+ "sourceRoot": "libs/remix-ui/plugin-manager/src",
"projectType": "library",
"schematics": {},
"architect": {