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.icon} -
- -
- ${self._view.pendingTxCount} -
-
- - -
-
- - ${self._view.inputSearch} -
-
-
- ` - 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:
- -
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 ( + + + + + + + {opts.hash ? ( + + + ) : null } + { + opts.contractAddress ? ( + + + + + ) : null + } + { + opts.from ? ( + + + + + ) : null + } + { + opts.to ? ( + + + + + ) : null + } + { + opts.gas ? ( + + + + + ) : null + } + { + opts.transactionCost ? ( + + + + + ) : null + } + { + opts.executionCost ? ( + + + + + ) : null + } + {opts.hash ? ( + + + + + ) : null} + {opts.input ? ( + + + + + ) : null} + {opts['decoded input'] ? ( + + + + + ) : null} + {opts['decoded output'] ? ( + + + + + ) : null} + {opts.logs ? ( + + + + + ) : null} + {opts.val ? ( + + + + + ) : null} + +
status{`${opts.status} ${msg}`}
transaction hash{opts.hash} + +
contract address{opts.contractAddress} + +
from{opts.from} + +
to{toHash} + +
gas{opts.gas} gas + +
transaction cost{opts.transactionCost} gas {callWarning} + +
execution cost{opts.executionCost} gas {callWarning} + +
hash{opts.hash} + +
input{helper.shortenHexData(opts.input)} + +
decoded input{opts['decoded input'].trim()} + +
decoded output{opts['decoded output']} + +
logs + {JSON.stringify(stringified, null, '\t')} + + +
val{val} wei + +
+ ) +} +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
+
+ + +
+
+ + 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": {