diff --git a/.eslintrc b/.eslintrc index 8babbc1619..75aca1f145 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,21 +1,21 @@ { - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module", - "project": "./tsconfig.base.json" - }, - "plugins": ["@typescript-eslint", "@nrwl/nx"], - "extends": "standard", - "rules": { - }, - "overrides": [ - { - "files": ["*.tsx"], - "rules": { - "@typescript-eslint/no-unused-vars": "off" - } + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module", + "project": "./tsconfig.base.json" + }, + "plugins": ["@typescript-eslint", "@nrwl/nx"], + "extends": "standard", + "rules": { + }, + "overrides": [ + { + "files": ["*.tsx"], + "rules": { + "@typescript-eslint/no-unused-vars": "off" } - ] - } \ No newline at end of file + } + ] +} \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 06cc47d9a2..6557fb70d1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,12 +24,21 @@ { "files": ["*.ts", "*.tsx"], "extends": ["plugin:@nrwl/nx/typescript"], - "rules": {} + "rules": { + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/no-empty-function": "off", + "eslint-disable-next-line no-empty": "off", + "no-empty": "off" + } }, { "files": ["*.js", "*.jsx"], "extends": ["plugin:@nrwl/nx/javascript"], "rules": {} } - ] + ], + "globals": { + "JSX": true + } } diff --git a/apps/remix-ide-e2e/nightwatch.ts b/apps/remix-ide-e2e/nightwatch.ts index 2bc20e8274..d8a111a617 100644 --- a/apps/remix-ide-e2e/nightwatch.ts +++ b/apps/remix-ide-e2e/nightwatch.ts @@ -68,7 +68,13 @@ module.exports = { desiredCapabilities: { browserName: 'firefox', javascriptEnabled: true, - acceptSslCerts: true + acceptSslCerts: true, + 'moz:firefoxOptions': { + args: [ + '-width=2560', + '-height=1440' + ] + } } }, @@ -78,7 +84,11 @@ module.exports = { javascriptEnabled: true, acceptSslCerts: true, 'moz:firefoxOptions': { - args: ['-headless'] + args: [ + '-headless', + '-width=2560', + '-height=1440' + ] } } } diff --git a/apps/remix-ide-e2e/src/commands/addAtAddressInstance.ts b/apps/remix-ide-e2e/src/commands/addAtAddressInstance.ts index 8df05cba92..39df8c7ef0 100644 --- a/apps/remix-ide-e2e/src/commands/addAtAddressInstance.ts +++ b/apps/remix-ide-e2e/src/commands/addAtAddressInstance.ts @@ -2,9 +2,9 @@ import { NightwatchBrowser } from 'nightwatch' import EventEmitter from 'events' class addAtAddressInstance extends EventEmitter { - command (this: NightwatchBrowser, address: string, isValidFormat: boolean, isValidChecksum: boolean): NightwatchBrowser { + command (this: NightwatchBrowser, address: string, isValidFormat: boolean, isValidChecksum: boolean, isAbi = true): NightwatchBrowser { this.api.perform((done: VoidFunction) => { - addInstance(this.api, address, isValidFormat, isValidChecksum, () => { + addInstance(this.api, address, isValidFormat, isValidChecksum, isAbi, () => { done() this.emit('complete') }) @@ -13,15 +13,19 @@ class addAtAddressInstance extends EventEmitter { } } -function addInstance (browser: NightwatchBrowser, address: string, isValidFormat: boolean, isValidChecksum: boolean, callback: VoidFunction) { +function addInstance (browser: NightwatchBrowser, address: string, isValidFormat: boolean, isValidChecksum: boolean, isAbi: boolean, callback: VoidFunction) { browser.clickLaunchIcon('udapp').clearValue('.ataddressinput').setValue('.ataddressinput', address, function () { if (!isValidFormat || !isValidChecksum) browser.assert.elementPresent('button[id^="runAndDeployAtAdressButton"]:disabled') - else { + else if (isAbi) { browser.click('button[id^="runAndDeployAtAdressButton"]') + .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') .execute(function () { - const modal = document.querySelector('#modal-footer-ok') as HTMLElement + const modal = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any + modal.click() }) + } else { + browser.click('button[id^="runAndDeployAtAdressButton"]') } callback() }) diff --git a/apps/remix-ide-e2e/src/commands/clickInstance.ts b/apps/remix-ide-e2e/src/commands/clickInstance.ts index f9d5ee8e6f..4da4b51b05 100644 --- a/apps/remix-ide-e2e/src/commands/clickInstance.ts +++ b/apps/remix-ide-e2e/src/commands/clickInstance.ts @@ -3,8 +3,7 @@ import EventEmitter from 'events' class ClickInstance extends EventEmitter { command (this: NightwatchBrowser, index: number): NightwatchBrowser { - index = index + 2 - const selector = '.instance:nth-of-type(' + index + ') > div > button' + const selector = `[data-id="universalDappUiTitleExpander${index}"]` this.api.waitForElementPresent(selector).waitForElementContainsText(selector, '', 60000).scrollAndClick(selector).perform(() => { this.emit('complete') }) return this diff --git a/apps/remix-ide-e2e/src/commands/createContract.ts b/apps/remix-ide-e2e/src/commands/createContract.ts index d3f7befee1..015a4f2012 100644 --- a/apps/remix-ide-e2e/src/commands/createContract.ts +++ b/apps/remix-ide-e2e/src/commands/createContract.ts @@ -15,12 +15,12 @@ class CreateContract extends EventEmitter { function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) { if (inputParams) { - browser.setValue('div[class^="contractActionsContainerSingle"] input', inputParams, function () { - browser.click('#runTabView button[class^="instanceButton"]').pause(500).perform(function () { callback() }) + browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () { + browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() }) }) } else { browser - .click('#runTabView button[class^="instanceButton"]') + .click('.udapp_contractActionsContainerSingle > button') .pause(500) .perform(function () { callback() }) } diff --git a/apps/remix-ide-e2e/src/commands/modalFooterCancelClick.ts b/apps/remix-ide-e2e/src/commands/modalFooterCancelClick.ts index e3f90e0c1b..c6505f8450 100644 --- a/apps/remix-ide-e2e/src/commands/modalFooterCancelClick.ts +++ b/apps/remix-ide-e2e/src/commands/modalFooterCancelClick.ts @@ -1,14 +1,15 @@ import { NightwatchBrowser } from 'nightwatch' import EventEmitter from 'events' -class ModalFooterOKClick extends EventEmitter { - command (this: NightwatchBrowser): NightwatchBrowser { - this.api.waitForElementVisible('#modal-footer-cancel').perform((client, done) => { - this.api.execute(function () { - const elem = document.querySelector('#modal-footer-cancel') as HTMLElement +class ModalFooterCancelClick extends EventEmitter { + command (this: NightwatchBrowser, id?: string): NightwatchBrowser { + const clientId = id ? `*[data-id="${id}-modal-footer-cancel-react"]` : '#modal-footer-cancel' + this.api.waitForElementVisible(clientId).perform((client, done) => { + this.api.execute(function (clientId) { + const elem = document.querySelector(clientId) as HTMLElement elem.click() - }, [], () => { + }, [clientId], () => { done() this.emit('complete') }) @@ -17,4 +18,4 @@ class ModalFooterOKClick extends EventEmitter { } } -module.exports = ModalFooterOKClick +module.exports = ModalFooterCancelClick diff --git a/apps/remix-ide-e2e/src/commands/modalFooterOKClick.ts b/apps/remix-ide-e2e/src/commands/modalFooterOKClick.ts index bea13a01b2..2e656f287c 100644 --- a/apps/remix-ide-e2e/src/commands/modalFooterOKClick.ts +++ b/apps/remix-ide-e2e/src/commands/modalFooterOKClick.ts @@ -2,13 +2,14 @@ import { NightwatchBrowser } from 'nightwatch' import EventEmitter from 'events' class ModalFooterOKClick extends EventEmitter { - command (this: NightwatchBrowser): NightwatchBrowser { - this.api.waitForElementVisible('#modal-footer-ok').perform((client, done) => { - this.api.execute(function () { - const elem = document.querySelector('#modal-footer-ok') as HTMLElement + command (this: NightwatchBrowser, id?: string): NightwatchBrowser { + const clientId = id ? `*[data-id="${id}-modal-footer-ok-react"]` : '#modal-footer-ok' + this.api.waitForElementVisible(clientId).perform((client, done) => { + this.api.execute(function (clientId) { + const elem = document.querySelector(clientId) as HTMLElement elem.click() - }, [], () => { + }, [clientId], () => { done() this.emit('complete') }) diff --git a/apps/remix-ide-e2e/src/commands/selectContract.ts b/apps/remix-ide-e2e/src/commands/selectContract.ts index fcd181ee3e..d270f8949d 100644 --- a/apps/remix-ide-e2e/src/commands/selectContract.ts +++ b/apps/remix-ide-e2e/src/commands/selectContract.ts @@ -1,7 +1,7 @@ import { NightwatchBrowser } from 'nightwatch' import EventEmitter from 'events' -const selector = '#runTabView select[class^="contractNames"]' +const selector = '.udapp_contractNames' class SelectContract extends EventEmitter { command (this: NightwatchBrowser, contractName: string): NightwatchBrowser { diff --git a/apps/remix-ide-e2e/src/commands/sendLowLevelTx.ts b/apps/remix-ide-e2e/src/commands/sendLowLevelTx.ts index a910a391c8..94f4a249d3 100644 --- a/apps/remix-ide-e2e/src/commands/sendLowLevelTx.ts +++ b/apps/remix-ide-e2e/src/commands/sendLowLevelTx.ts @@ -6,10 +6,11 @@ class sendLowLevelTx extends EventEmitter { console.log('low level transact to ', address, value, callData) this.api.waitForElementVisible(`#instance${address} #deployAndRunLLTxSendTransaction`, 1000) .clearValue(`#instance${address} #deployAndRunLLTxCalldata`) - .setValue(`#instance${address} #deployAndRunLLTxCalldata`, callData) + .sendKeys(`#instance${address} #deployAndRunLLTxCalldata`, ['_', this.api.Keys.BACK_SPACE, callData]) .waitForElementVisible('#value') .clearValue('#value') - .setValue('#value', value) + .sendKeys('#value', ['1', this.api.Keys.BACK_SPACE, value]) + .pause(2000) .scrollAndClick(`#instance${address} #deployAndRunLLTxSendTransaction`) .perform(() => { this.emit('complete') diff --git a/apps/remix-ide-e2e/src/commands/signMessage.ts b/apps/remix-ide-e2e/src/commands/signMessage.ts index d40735d76e..273871505e 100644 --- a/apps/remix-ide-e2e/src/commands/signMessage.ts +++ b/apps/remix-ide-e2e/src/commands/signMessage.ts @@ -20,25 +20,28 @@ function signMsg (browser: NightwatchBrowser, msg: string, cb: (hash: { value: s browser .waitForElementPresent('i[id="remixRunSignMsg"]') .click('i[id="remixRunSignMsg"]') - .waitForElementVisible('textarea[id="prompt_text"]') - .setValue('textarea[id="prompt_text"]', msg, () => { - browser.modalFooterOKClick().perform( - (client, done) => { - browser.waitForElementVisible('span[id="remixRunSignMsgHash"]').getText('span[id="remixRunSignMsgHash"]', (v) => { hash = v; done() }) - } - ) - .perform( - (client, done) => { - browser.waitForElementVisible('span[id="remixRunSignMsgSignature"]').getText('span[id="remixRunSignMsgSignature"]', (v) => { signature = v; done() }) - } - ) - .modalFooterOKClick() - .perform( - () => { - cb(hash, signature) - } - ) - }) + .waitForElementVisible('*[data-id="signMessageTextarea"]', 120000) + .click('*[data-id="signMessageTextarea"]') + .setValue('*[data-id="signMessageTextarea"]', msg) + .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') + .click('[data-id="udappNotify-modal-footer-ok-react"]') + .perform( + (client, done) => { + browser.waitForElementVisible('span[id="remixRunSignMsgHash"]').getText('span[id="remixRunSignMsgHash"]', (v) => { hash = v; done() }) + } + ) + .perform( + (client, done) => { + browser.waitForElementVisible('span[id="remixRunSignMsgSignature"]').getText('span[id="remixRunSignMsgSignature"]', (v) => { signature = v; done() }) + } + ) + .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') + .click('[data-id="udappNotify-modal-footer-ok-react"]') + .perform( + () => { + cb(hash, signature) + } + ) } module.exports = SelectContract diff --git a/apps/remix-ide-e2e/src/commands/testConstantFunction.ts b/apps/remix-ide-e2e/src/commands/testConstantFunction.ts index db0d3b0946..1ce727c076 100644 --- a/apps/remix-ide-e2e/src/commands/testConstantFunction.ts +++ b/apps/remix-ide-e2e/src/commands/testConstantFunction.ts @@ -20,16 +20,17 @@ function testConstantFunction (browser: NightwatchBrowser, address: string, fnFu document.querySelector('#runTabView').scrollTop = document.querySelector('#runTabView').scrollHeight }, [], function () { if (expectedInput) { - client.setValue('#runTabView input[title="' + expectedInput.types + '"]', expectedInput.values) + client.waitForElementPresent('#runTabView input[title="' + expectedInput.types + '"]') + .setValue('#runTabView input[title="' + expectedInput.types + '"]', expectedInput.values) } done() }) }) .click('.instance button[title="' + fnFullName + '"]') .pause(1000) - .waitForElementPresent('#instance' + address + ' div[class^="contractActionsContainer"] div[class^="value"]') - .scrollInto('#instance' + address + ' div[class^="contractActionsContainer"] div[class^="value"]') - .assert.containsText('#instance' + address + ' div[class^="contractActionsContainer"] div[class^="value"]', expectedOutput).perform(() => { + .waitForElementPresent('#instance' + address + ' .udapp_contractActionsContainer .udapp_value') + .scrollInto('#instance' + address + ' .udapp_contractActionsContainer .udapp_value') + .assert.containsText('#instance' + address + ' .udapp_contractActionsContainer .udapp_value', expectedOutput).perform(() => { cb() }) } diff --git a/apps/remix-ide-e2e/src/commands/validateValueInput.ts b/apps/remix-ide-e2e/src/commands/validateValueInput.ts index 2ea3e403bf..dd7f653ad7 100644 --- a/apps/remix-ide-e2e/src/commands/validateValueInput.ts +++ b/apps/remix-ide-e2e/src/commands/validateValueInput.ts @@ -6,6 +6,7 @@ class ValidateValueInput extends EventEmitter { const browser = this.api browser.perform((done) => { browser.clearValue(selector) + .pause(2000) .setValue(selector, valueTosSet) .execute(function (selector) { const elem = document.querySelector(selector) as HTMLInputElement diff --git a/apps/remix-ide-e2e/src/commands/verifyCallReturnValue.ts b/apps/remix-ide-e2e/src/commands/verifyCallReturnValue.ts index af049164f7..c17fc1e130 100644 --- a/apps/remix-ide-e2e/src/commands/verifyCallReturnValue.ts +++ b/apps/remix-ide-e2e/src/commands/verifyCallReturnValue.ts @@ -15,7 +15,7 @@ class VerifyCallReturnValue extends EventEmitter { function verifyCallReturnValue (browser: NightwatchBrowser, address: string, checks: string[], done: VoidFunction) { browser.execute(function (address: string) { - const nodes = document.querySelectorAll('#instance' + address + ' div[class^="contractActionsContainer"] div[class^="value"]') as NodeListOf + const nodes = document.querySelectorAll('#instance' + address + ' [data-id="udapp_value"]') as NodeListOf const ret = [] for (let k = 0; k < nodes.length; k++) { const text = nodes[k].innerText ? nodes[k].innerText : nodes[k].textContent @@ -23,7 +23,6 @@ function verifyCallReturnValue (browser: NightwatchBrowser, address: string, che } return ret }, [address], function (result) { - console.log('verifyCallReturnValue', result) for (const k in checks) { browser.assert.equal(result.value[k].trim(), checks[k].trim()) } diff --git a/apps/remix-ide-e2e/src/helpers/init.ts b/apps/remix-ide-e2e/src/helpers/init.ts index a5926bd028..e934ae2f7b 100644 --- a/apps/remix-ide-e2e/src/helpers/init.ts +++ b/apps/remix-ide-e2e/src/helpers/init.ts @@ -9,6 +9,7 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url .switchBrowserTab(0) .waitForElementVisible('[id="remixTourSkipbtn"]') .click('[id="remixTourSkipbtn"]') + .maximizeWindow() .fullscreenWindow(() => { if (preloadPlugins) { initModules(browser, () => { diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index c563bd459e..cc2a32cb2f 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -25,7 +25,7 @@ module.exports = { .setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]') .click('*[data-id="Deploy - transact (not payable)"]') .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) - .click('*[data-id="universalDappUiTitleExpander"]') + .clickInstance(0) .clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' }) .testFunction('last', { @@ -70,7 +70,7 @@ module.exports = { .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true) .pause(500) .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) - .click('*[data-id="universalDappUiTitleExpander"]') + .clickInstance(0) .clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' }) .testFunction('last', { @@ -84,17 +84,24 @@ module.exports = { .openFile('Untitled.sol') .clickLaunchIcon('udapp') .click('*[data-id="settingsWeb3Mode"]') - .modalFooterOKClick() + .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') + .execute(function () { + const modal = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any + + modal.click() + }) + .pause(5000) .execute(function () { 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') .clickLaunchIcon('udapp') .pause(2000) + .clearValue('input[placeholder="bytes32[] proposalNames"]') .setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]') .click('*[data-id="Deploy - transact (not payable)"]') .clickInstance(0) diff --git a/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts b/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts index b48a0df1c8..a8bfa626e4 100644 --- a/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts @@ -33,7 +33,7 @@ module.exports = { .setValue('input[placeholder="uint8 _numProposals"]', '2') .click('*[data-id="Deploy - transact (not payable)"]') .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) - .click('*[data-id="universalDappUiTitleExpander"]') + .clickInstance(0) .clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' }) .testFunction('last', { @@ -65,7 +65,7 @@ module.exports = { .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true) .pause(500) .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) - .click('*[data-id="universalDappUiTitleExpander"]') + .clickInstance(0) .clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' }) .testFunction('last', { @@ -79,10 +79,16 @@ module.exports = { .openFile('Untitled.sol') .clickLaunchIcon('udapp') .click('*[data-id="settingsWeb3Mode"]') - .modalFooterOKClick() + .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') + .execute(function () { + const modal = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any + + modal.click() + }) .clickLaunchIcon('solidity') .clickLaunchIcon('udapp') .pause(2000) + .clearValue('input[placeholder="uint8 _numProposals"]') .setValue('input[placeholder="uint8 _numProposals"]', '2') .click('*[data-id="Deploy - transact (not payable)"]') .clickInstance(0) diff --git a/apps/remix-ide-e2e/src/tests/debugger.test.ts b/apps/remix-ide-e2e/src/tests/debugger.test.ts index 6698c13635..cd3fafc3b3 100644 --- a/apps/remix-ide-e2e/src/tests/debugger.test.ts +++ b/apps/remix-ide-e2e/src/tests/debugger.test.ts @@ -25,8 +25,7 @@ module.exports = { 'Should debug failing transaction #group1': function (browser: NightwatchBrowser) { browser.waitForElementVisible('*[data-id="verticalIconsKindudapp"]') .clickLaunchIcon('udapp') - .waitForElementPresent('*[data-id="universalDappUiTitleExpander"]') - .click('*[data-id="universalDappUiTitleExpander"]') + .clickInstance(0) .scrollAndClick('*[title="string name, uint256 goal"]') .setValue('*[title="string name, uint256 goal"]', '"toast", 999') .click('*[data-id="createProject - transact (not payable)"]') diff --git a/apps/remix-ide-e2e/src/tests/gist.test.ts b/apps/remix-ide-e2e/src/tests/gist.test.ts index 3216ff4b39..0fd6efa234 100644 --- a/apps/remix-ide-e2e/src/tests/gist.test.ts +++ b/apps/remix-ide-e2e/src/tests/gist.test.ts @@ -76,29 +76,34 @@ module.exports = { .waitForElementVisible('button[data-id="landingPageImportFromGistButton"]') .pause(1000) .scrollAndClick('button[data-id="landingPageImportFromGistButton"]') - .waitForElementVisible('*[data-id="modalDialogModalTitle"]') - .assert.containsText('*[data-id="modalDialogModalTitle"]', 'Load a Gist') - .waitForElementVisible('*[data-id="modalDialogModalBody"]') - .assert.containsText('*[data-id="modalDialogModalBody"]', 'Enter the ID of the Gist or URL you would like to load.') - .waitForElementVisible('*[data-id="modalDialogCustomPromptText"]') - .modalFooterCancelClick() + .waitForElementVisible('*[data-id="gisthandlerModalDialogModalTitle-react"]') + .assert.containsText('*[data-id="gisthandlerModalDialogModalTitle-react"]', 'Load a Gist') + .waitForElementVisible('*[data-id="gisthandlerModalDialogModalBody-react"]') + .assert.containsText('*[data-id="gisthandlerModalDialogModalBody-react"]', 'Enter the ID of the Gist or URL you would like to load.') + .waitForElementVisible('*[data-id="modalDialogCustomPromp"]') + .modalFooterCancelClick('gisthandler') }, 'Display Error Message For Invalid Gist ID': function (browser: NightwatchBrowser) { browser + .pause(1000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .clickLaunchIcon('filePanel') .scrollAndClick('*[data-id="landingPageImportFromGistButton"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptText"]') - .setValue('*[data-id="modalDialogCustomPromptText"]', testData.invalidGistId) - .modalFooterOKClick() - .waitForElementVisible('*[data-id="modalDialogModalBody"]') - .assert.containsText('*[data-id="modalDialogModalBody"]', 'Not Found') - .modalFooterOKClick() + .waitForElementVisible('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]') + .execute(() => { + (document.querySelector('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]') as any).focus() + }, [], () => {}) + .setValue('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]', testData.invalidGistId) + .modalFooterOKClick('gisthandler') + .waitForElementVisible('*[data-id="gisthandlerModalDialogModalBody-react"]') + .assert.containsText('*[data-id="gisthandlerModalDialogModalBody-react"]', 'Not Found') + .modalFooterOKClick('gisthandler') }, 'Display Error Message For Missing Gist Token When Publishing': function (browser: NightwatchBrowser) { browser + .pause(1000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .clickLaunchIcon('settings') .waitForElementVisible('[data-id="settingsTabRemoveGistToken"]') @@ -129,9 +134,12 @@ module.exports = { .click('[data-id="settingsTabSaveGistToken"]') .clickLaunchIcon('filePanel') .scrollAndClick('*[data-id="landingPageImportFromGistButton"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptText"]') - .setValue('*[data-id="modalDialogCustomPromptText"]', testData.validGistId) - .modalFooterOKClick() + .waitForElementVisible('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]') + .execute(function () { + (document.querySelector('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]') as any).focus() + }) + .setValue('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]', testData.validGistId) + .modalFooterOKClick('gisthandler') .openFile(`gist-${testData.validGistId}/README.txt`) .waitForElementVisible(`div[title='default_workspace/gist-${testData.validGistId}/README.txt']`) .assert.containsText(`div[title='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt') diff --git a/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts b/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts index 9293775733..42d17625e0 100644 --- a/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts +++ b/apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts @@ -27,8 +27,7 @@ module.exports = { console.log('testAutoDeployLib ' + address) addressRef = address }) - .waitForElementPresent('.instance:nth-of-type(2)') - .click('.instance:nth-of-type(2) > div > button') + .clickInstance(0) .perform((done) => { browser.testConstantFunction(addressRef, 'get - call', null, '0:\nuint256: 45').perform(() => { done() @@ -37,7 +36,6 @@ module.exports = { }, 'Test Manual Deploy Lib': function (browser: NightwatchBrowser) { - console.log('testManualDeployLib') browser.click('*[data-id="deployAndRunClearInstances"]') .pause(5000) .clickLaunchIcon('settings') @@ -104,8 +102,7 @@ function checkDeployShouldSucceed (browser: NightwatchBrowser, address: string, .getAddressAtPosition(1, (address) => { addressRef = address }) - .waitForElementPresent('.instance:nth-of-type(3)') - .click('.instance:nth-of-type(3) > div > button') + .clickInstance(1) .perform(() => { browser .testConstantFunction(addressRef, 'get - call', null, '0:\nuint256: 45') diff --git a/apps/remix-ide-e2e/src/tests/publishContract.test.ts b/apps/remix-ide-e2e/src/tests/publishContract.test.ts index 03987555c5..e1a19ccabb 100644 --- a/apps/remix-ide-e2e/src/tests/publishContract.test.ts +++ b/apps/remix-ide-e2e/src/tests/publishContract.test.ts @@ -60,12 +60,14 @@ module.exports = { .click('*[data-id="contractDropdownIpfsCheckbox"]') .waitForElementVisible('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]') - .pause(8000) - .getModalBody((value, done) => { + .pause(5000) + .waitForElementVisible('[data-id="udappModalDialogModalBody-react"]') + .getText('[data-id="udappModalDialogModalBody-react"]', (result) => { + const value = typeof result.value === 'string' ? result.value : null + if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed') - done() }) - .modalFooterOKClick() + .modalFooterOKClick('udapp') }, 'Should remember choice after page refresh': function (browser: NightwatchBrowser) { diff --git a/apps/remix-ide-e2e/src/tests/recorder.test.ts b/apps/remix-ide-e2e/src/tests/recorder.test.ts index 58e1f867ae..838ac567da 100644 --- a/apps/remix-ide-e2e/src/tests/recorder.test.ts +++ b/apps/remix-ide-e2e/src/tests/recorder.test.ts @@ -17,16 +17,14 @@ module.exports = { .pause(5000) .clickLaunchIcon('udapp') .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite - .click('div[class^="cardContainer"] i[class^="arrow"]') - .click('#runTabView .runtransaction') - .waitForElementPresent('.instance:nth-of-type(2)') - .click('.instance:nth-of-type(2) > div > button') - .waitForElementPresent('.instance:nth-of-type(3)') - .click('.instance:nth-of-type(3) > div > button') + .click('[data-id="udapp_arrow"]') + .click('[data-id="runtransaction"]') + .clickInstance(0) + .clickInstance(1) .clickFunction('getInt - call') .clickFunction('getAddress - call') .clickFunction('getFromLib - call') - .waitForElementPresent('div[class^="contractActionsContainer"] div[class^="value"] ul') + .waitForElementPresent('[data-id="udapp_value"]') .getAddressAtPosition(1, (address) => { console.log('Test Recorder ' + address) addressRef = address @@ -39,11 +37,15 @@ module.exports = { .testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder']) .clickLaunchIcon('udapp') .createContract('12') - .waitForElementPresent('.instance:nth-of-type(2)') - .click('.instance:nth-of-type(2) > div > button') + .clickInstance(0) .clickFunction('set - transact (not payable)', { types: 'uint256 _p', values: '34' }) .click('i.savetransaction') - .modalFooterOKClick() + .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') + .execute(function () { + const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any + + modalOk.click() + }) .getEditorValue(function (result) { const parsed = JSON.parse(result) browser.assert.equal(JSON.stringify(parsed.transactions[0].record.parameters), JSON.stringify(scenario.transactions[0].record.parameters)) @@ -73,11 +75,16 @@ module.exports = { .pause(1000) .createContract('') .click('i.savetransaction') - .modalFooterOKClick() + .waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]') + .execute(function () { + const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any + + modalOk.click() + }) .click('*[data-id="deployAndRunClearInstances"]') // clear udapp .click('*[data-id="terminalClearConsole"]') // clear terminal - .click('#runTabView .runtransaction') - .clickInstance(1) + .click('[data-id="runtransaction"]') + .clickInstance(2) .pause(1000) .clickFunction('set2 - transact (not payable)', { types: 'uint256 _po', values: '10' }) .testFunction('last', diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts index 33021082ae..7b3315e614 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts @@ -36,16 +36,19 @@ module.exports = { .pause(2000) .click('*[data-id="settingsRemixRunSignMsg"]') .pause(2000) - .waitForElementVisible('*[data-id="modalDialogCustomPromptText"]', 120000) - .setValue('*[data-id="modalDialogCustomPromptText"]', 'Remix is cool!') + .waitForElementVisible('*[data-id="signMessageTextarea"]', 120000) + .click('*[data-id="signMessageTextarea"]') + .setValue('*[data-id="signMessageTextarea"]', 'Remix is cool!') .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgHash"]') .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgSignature"]') .pause(2000) - .modalFooterOKClick() - .waitForElementVisible('*[data-id="modalDialogContainer"]', 12000) + .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') + .click('[data-id="udappNotify-modal-footer-ok-react"]') + .waitForElementVisible('*[data-id="udappNotifyModalDialogModalBody-react"]', 12000) .assert.elementPresent('*[data-id="settingsRemixRunSignMsgHash"]') .assert.elementPresent('*[data-id="settingsRemixRunSignMsgSignature"]') - .modalFooterOKClick() + .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') + .click('[data-id="udappNotify-modal-footer-ok-react"]') }, 'Should deploy contract on JavascriptVM #group3': function (browser: NightwatchBrowser) { @@ -64,8 +67,7 @@ module.exports = { 'Should run low level interaction (fallback function) #group3': function (browser: NightwatchBrowser) { browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') - .waitForElementPresent('*[data-id="universalDappUiTitleExpander"]') - .click('*[data-id="universalDappUiTitleExpander"]') + .clickInstance(0) .waitForElementPresent('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]') .click('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]') .pause(5000) @@ -181,7 +183,7 @@ module.exports = { .useCss().switchBrowserTab(0) .refresh() .clickLaunchIcon('pluginManager') // load debugger and source verification - // .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_source-verification"] button') + // .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_sourcify"] button') // debugger already activated .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button') .clickLaunchIcon('udapp') .waitForElementPresent('*[data-id="settingsSelectEnvOptions"]') diff --git a/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts b/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts index dd405cc243..b3e463b63f 100644 --- a/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts +++ b/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts @@ -173,6 +173,7 @@ module.exports = { .waitForElementVisible('#value') .clearValue('#value') .setValue('#value', '0') + .pause(2000) .createContract('') .clickInstance(1) .pause(1000) diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index 0bcfbcb3d7..69c3acc765 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -51,7 +51,7 @@ module.exports = { .click('*[data-id="terminalClearConsole"]') // clear the terminal .clickLaunchIcon('udapp') .click('*[data-id="settingsWeb3Mode"]') - .modalFooterOKClick() + .modalFooterOKClick('udappNotify') .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) @@ -109,10 +109,10 @@ module.exports = { .waitForElementContainsText('*[data-id="terminalJournal"]', 'Contract Address:', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'Deployment successful.', 60000) - .addAtAddressInstance('0xd9145CCE52D386f254917e481eB44e9943F39138', true, true) + .addAtAddressInstance('0xd9145CCE52D386f254917e481eB44e9943F39138', true, true, false) .click('*[data-id="terminalClearConsole"]') // clear the terminal .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) - .click('*[data-id="universalDappUiTitleExpander"]') + .clickInstance(0) .clickFunction('changeOwner - transact (not payable)', { types: 'address newOwner', values: '0xd9145CCE52D386f254917e481eB44e9943F39138' }) // execute the "changeOwner" function .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 diff --git a/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts b/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts index cfb2c7a323..1be9168abb 100644 --- a/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts +++ b/apps/remix-ide-e2e/src/tests/transactionExecution.test.ts @@ -15,11 +15,8 @@ module.exports = { browser.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['TestContract']) .clickLaunchIcon('udapp') .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite - .click('#runTabView button[class^="instanceButton"]') - .waitForElementPresent('.instance:nth-of-type(2)') - .click('.instance:nth-of-type(2) > div > button') - .click('#runTabView .instance div[class^="title"]') - .click('#runTabView .instance div[class^="title"]') + .click('.udapp_contractActionsContainerSingle > button') + .clickInstance(0) .clickFunction('f - transact (not payable)') .testFunction('last', { @@ -45,9 +42,8 @@ module.exports = { 'Test Complex Return Values #group1': function (browser: NightwatchBrowser) { browser.testContracts('returnValues.sol', sources[1]['returnValues.sol'], ['testReturnValues']) .clickLaunchIcon('udapp') - .click('#runTabView button[class^="instanceButton"]') - .waitForElementPresent('.instance:nth-of-type(2)') - .click('.instance:nth-of-type(2) > div > button') + .click('.udapp_contractActionsContainerSingle > button') + .clickInstance(0) .clickFunction('retunValues1 - transact (not payable)') .testFunction('last', { @@ -90,9 +86,8 @@ module.exports = { 'Test Complex Input Values #group2': function (browser: NightwatchBrowser) { browser.testContracts('inputValues.sol', sources[2]['inputValues.sol'], ['test']) .clickLaunchIcon('udapp') - .click('#runTabView button[class^="instanceButton"]') - .waitForElementPresent('.instance:nth-of-type(2)') - .click('.instance:nth-of-type(2) > div > button') + .click('.udapp_contractActionsContainerSingle > button') + .clickInstance(0) .clickFunction('inputValue1 - transact (not payable)', { types: 'uint256 _u, int256 _i, string _str', values: '"2343242", "-4324324", "string _ string _ string _ string _ string _ string _ string _ string _ string _ string _"' }) .testFunction('last', { @@ -136,8 +131,8 @@ module.exports = { browser.testContracts('eventFunctionInput.sol', sources[3]['eventFunctionInput.sol'], ['C']) .clickLaunchIcon('udapp') .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite - .click('#runTabView button[class^="instanceButton"]') - .waitForElementPresent('.instance:nth-of-type(2)') + .click('.udapp_contractActionsContainerSingle > button') + .clickInstance(0) .click('*[data-id="deployAndRunClearInstances"]') }, @@ -145,7 +140,7 @@ module.exports = { browser.testContracts('customError.sol', sources[4]['customError.sol'], ['C']) .clickLaunchIcon('udapp') .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite - .click('#runTabView button[class^="instanceButton"]') + .click('.udapp_contractActionsContainerSingle > button') .clickInstance(0) .clickFunction('g - transact (not payable)') .pause(5000) @@ -168,7 +163,7 @@ module.exports = { .clearTransactions() .click('*[data-id="settingsVMLondonMode"]') // switch to London fork .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite - .click('#runTabView button[class^="instanceButton"]') + .click('.udapp_contractActionsContainerSingle > button') .clickInstance(0) .clickFunction('g - transact (not payable)') .journalLastChildIncludes('Error provided by the contract:') @@ -186,7 +181,7 @@ module.exports = { 'Should Compile and Deploy a contract which define a custom error in a library, the error should be logged in the terminal #group3': function (browser: NightwatchBrowser) { browser.testContracts('customErrorLib.sol', sources[5]['customErrorLib.sol'], ['D']) .clickLaunchIcon('udapp') - .click('#runTabView button[class^="instanceButton"]') + .click('.udapp_contractActionsContainerSingle > button') .clickInstance(1) .clickFunction('h - transact (not payable)') .pause(5000) diff --git a/apps/remix-ide-e2e/src/tests/txListener.test.ts b/apps/remix-ide-e2e/src/tests/txListener.test.ts index 7a7b170d6e..41dc6882e7 100644 --- a/apps/remix-ide-e2e/src/tests/txListener.test.ts +++ b/apps/remix-ide-e2e/src/tests/txListener.test.ts @@ -26,7 +26,7 @@ module.exports = { .setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]') .click('*[data-id="Deploy - transact (not payable)"]') .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) - .click('*[data-id="universalDappUiTitleExpander"]') + .clickInstance(0) .clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' }) .testFunction('last', { diff --git a/apps/remix-ide-e2e/src/types/index.d.ts b/apps/remix-ide-e2e/src/types/index.d.ts index 373139fbf4..f9a7c35d63 100644 --- a/apps/remix-ide-e2e/src/types/index.d.ts +++ b/apps/remix-ide-e2e/src/types/index.d.ts @@ -17,8 +17,8 @@ declare module 'nightwatch' { testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser, goToVMTraceStep(step: number, incr?: number): NightwatchBrowser, checkVariableDebug(id: string, debugValue: NightwatchCheckVariableDebugValue): NightwatchBrowser, - addAtAddressInstance(address: string, isValidFormat: boolean, isValidChecksum: boolean): NightwatchBrowser, - modalFooterOKClick(): NightwatchBrowser, + addAtAddressInstance(address: string, isValidFormat: boolean, isValidChecksum: boolean, isAbi?: boolean): NightwatchBrowser, + modalFooterOKClick(id?: string): NightwatchBrowser, clickInstance(index: number): NightwatchBrowser, journalLastChildIncludes(val: string): NightwatchBrowser, executeScript(script: string): NightwatchBrowser, @@ -32,7 +32,7 @@ declare module 'nightwatch' { scrollToLine(line: number): NightwatchBrowser, waitForElementContainsText(id: string, value: string, timeout?: number): NightwatchBrowser, getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser, - modalFooterCancelClick(): NightwatchBrowser, + modalFooterCancelClick(id?: string): NightwatchBrowser, selectContract(contractName: string): NightwatchBrowser, createContract(inputParams: string): NightwatchBrowser, getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser, diff --git a/apps/remix-ide/.eslintrc b/apps/remix-ide/.eslintrc index 7276b9859c..b7d498eea1 100644 --- a/apps/remix-ide/.eslintrc +++ b/apps/remix-ide/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../.eslintrc", + "extends": "../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/apps/remix-ide/best-practices.md b/apps/remix-ide/best-practices.md deleted file mode 100644 index 915759a054..0000000000 --- a/apps/remix-ide/best-practices.md +++ /dev/null @@ -1,153 +0,0 @@ -# Current "Best Practice" Conventions - - -- Please use [JS Standard Style](https://standardjs.com/) as a coding style guide. -- `ES6 class` rather than ES5 to create class. -- CSS declaration using `csjs-inject`. -- CSS files: - -  **if** the CSS section of an UI component is too important, CSS declarations should be put in a different file and in a different folder. - - The folder should be named `styles` and the file should be named with the extension `-styles.css`. - -  e.g: `file-explorer.js` being an UI component `file-explorer-styles.css` is created in the `styles` folder right under `file-explorer.js` - - **if** the CSS section of an UI component is rather limited it is preferable to put it in the corresponding JS file. - -- HTML declaration using `yo-yo`. - -- A module trigger events using `event` property: -  `self.event = new EventManager()`. - Events can then be triggered: -  `self.event.trigger('eventName', [param1, param2])` -- `self._view` is the HTML view renderered by `yo-yo` in the `render` function. -- `render()` this function should be called at the first rendering (make sure that the returned node element is put on the DOM), and should *not* by called again from outside the component. -- `update()` call this function to update the DOM when the state of the component has changed (this function must be called after the initial call to `render()`). -- for all functions / properties, prefixing by underscore (`_`) means the scope is private, and they should **not** be accessed not changed from outside the component. -- constructor arguments: There is no fixed rule whether it is preferrable to use multiples arguments or a single option *{}* argument (or both). - We recommend: - - use a specific slot for **obligatory** arguments and/or for complex arguments (meaning not boolean, not string, etc...). - - put arguments in an option *{}* for non critical and for optionnal arguments. - - if a component has more than 4/5 parameters, it is recommended to find a way to group some in one or more *opt* arguments. - -- look them up, discuss them, update them. -     -## Module Definition (example) -```js -// user-card.js -var yo = require('yo-yo') -var csjs = require('csjs-inject') -var EventManager = require('remix-lib').EventManager - -var css = csjs` - .userCard { - position : relative; - box-sizing : border-box; - display : flex; - flex-direction : column; - align-items : center; - border : 1px solid black; - width : 400px; - padding : 50px; - } - .clearFunds { background-color: lightblue; } -` - -class UserCard { - constructor (api, events, opts = {}) { - var self = this - - self.event = new EventManager() - self.opts = opts - self._api = api - self._consumedEvents = events - self._view = undefined - - events.funds.register('fundsChanged', function (amount) { - if (amount < self.state._funds) self.state.totalSpend += self.state._funds - amount - self.state._funds = amount - self.render() - }) - self.event.trigger('eventName', [param1, param2]) - } - render () { - var self = this - var view = yo` -
-

@${self.state._nickname}

-

Welcome, ${self.state.title || ''} ${self.state.name || 'anonymous'} ${self.state.surname}

-
  • User Funds: $${self.state._funds}
-
  • Spent Funds: $${self.state.totalSpend}
- -
- ` - if (!self._view) { - self._view = view - } - return self._view - } - update () { - yo.update(this._view, this.render()) - } - setNickname (name) { - this._nickname = name - } - getNickname () { - var self = this - return `@${self.state._nickname}` - } - getFullName () { - var self = this - return `${self.state.title} ${self.state.name} ${self.state.surname}` - } - _spendAll (event) { - var self = this - self._appAPI.clearUserFunds() - } - _constraint (msg) { throw new Error(msg) } -} - -module.exports = UserCard -``` -## Module Usage (example) -```js -/*****************************************************************************/ -// 1. SETUP CONTEXT -var EventManager = require('remix-lib').EventManager -var funds = { event: new EventManager() } -var userfunds = 15 -function getUserFunds () { return userfunds } -function clearUserFunds () { - var spent = userfunds - userfunds = 0 - console.log(`all funds of ${usercard.getFullName()} were spent.`) - funds.event.trigger('fundsChanged', [userfunds]) - return spent -} -setInterval(function () { - userfunds++ - funds.event.trigger('fundsChanged', [userfunds]) -}, 100) - -/*****************************************************************************/ -// 2. EXAMPLE USAGE -var UserCard = require('./user-card') - -var usercard = new UserCard( - { getUserFunds, clearUserFunds }, - { funds: funds.event }, - { - title: 'Dr.', - name: 'John', - surname: 'Doe', - nickname: 'johndoe99' - }) - -var el = usercard.render() -document.body.appendChild(el) -setTimeout(function () { - userCard.setNickname('new name') - usercard.update() -}, 5000) - -``` diff --git a/apps/remix-ide/docs/code_contribution_guide.md b/apps/remix-ide/docs/code_contribution_guide.md index 6db3d1294c..5553b87c21 100644 --- a/apps/remix-ide/docs/code_contribution_guide.md +++ b/apps/remix-ide/docs/code_contribution_guide.md @@ -5,7 +5,7 @@ Remix is an open source tool and we encourage anyone to help us improve our tool You can do that by opening issues, giving feedback or by contributing a pull request to our codebase. -The Remix application is built with JavaScript and it doesn't use any framework. We only -rely on selected set of npm modules, like `yo-yo`, `csjs-inject` and others. Check out the `package.json` files in the Remix submodules to learn more about the stack. +The Remix application is built with JavaScript and React. +Check out the `package.json` files in the Remix submodules to learn more about the stack. To learn more, please visit our [GitHub page](https://github.com/ethereum/remix-ide). diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index d537d0812e..7038c120cd 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -2,7 +2,6 @@ import { RunTab, makeUdapp } from './app/udapp' import { RemixEngine } from './remixEngine' import { RemixAppManager } from './remixAppManager' -import { MainView } from './app/panels/main-view' import { ThemeModule } from './app/tabs/theme-module' import { NetworkModule } from './app/tabs/network-module' import { Web3ProviderModule } from './app/tabs/web3-provider' @@ -11,17 +10,17 @@ import { HiddenPanel } from './app/components/hidden-panel' import { VerticalIcons } from './app/components/vertical-icons' import { LandingPage } from './app/ui/landing-page/landing-page' import { MainPanel } from './app/components/main-panel' -import { FramingService } from './framingService' -import { ModalPluginTester } from './app/plugins/test' import { WalkthroughService } from './walkthroughService' -import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, EditorContextListener } from '@remix-project/core-plugin' +import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, EditorContextListener, GistHandler } from '@remix-project/core-plugin' -import migrateFileSystem from './migrateFileSystem' import Registry from './app/state/registry' import { ConfigPlugin } from './app/plugins/config' +import { Layout } from './app/panels/layout' import { ModalPlugin } from './app/plugins/modal' +import { Blockchain } from './blockchain/blockchain.js' +import { HardhatProvider } from './app/tabs/hardhat-provider' const isElectron = require('is-electron') @@ -30,7 +29,6 @@ const remixLib = require('@remix-project/remix-lib') const QueryParams = require('./lib/query-params') const Storage = remixLib.Storage const RemixDProvider = require('./app/files/remixDProvider') -const HardhatProvider = require('./app/tabs/hardhat-provider') const Config = require('./config') const FileManager = require('./app/files/fileManager') @@ -39,8 +37,6 @@ const DGitProvider = require('./app/files/dgitProvider') const WorkspaceFileProvider = require('./app/files/workspaceFileProvider') const toolTip = require('./app/ui/tooltip') -const Blockchain = require('./blockchain/blockchain.js') - const PluginManagerComponent = require('./app/components/plugin-manager-component') const CompileTab = require('./app/tabs/compile-tab') @@ -51,6 +47,7 @@ const TestTab = require('./app/tabs/test-tab') const FilePanel = require('./app/panels/file-panel') const Editor = require('./app/editor/editor') const Terminal = require('./app/panels/terminal') +const { TabProxy } = require('./app/panels/tab-proxy.js') class AppComponent { constructor () { @@ -68,15 +65,27 @@ class AppComponent { // load file system self._components.filesProviders = {} self._components.filesProviders.browser = new FileProvider('browser') - Registry.getInstance().put({ api: self._components.filesProviders.browser, name: 'fileproviders/browser' }) - self._components.filesProviders.localhost = new RemixDProvider(self.appManager) - Registry.getInstance().put({ api: self._components.filesProviders.localhost, name: 'fileproviders/localhost' }) + Registry.getInstance().put({ + api: self._components.filesProviders.browser, + name: 'fileproviders/browser' + }) + self._components.filesProviders.localhost = new RemixDProvider( + self.appManager + ) + Registry.getInstance().put({ + api: self._components.filesProviders.localhost, + name: 'fileproviders/localhost' + }) self._components.filesProviders.workspace = new WorkspaceFileProvider() - Registry.getInstance().put({ api: self._components.filesProviders.workspace, name: 'fileproviders/workspace' }) - - Registry.getInstance().put({ api: self._components.filesProviders, name: 'fileproviders' }) + Registry.getInstance().put({ + api: self._components.filesProviders.workspace, + name: 'fileproviders/workspace' + }) - migrateFileSystem(self._components.filesProviders.browser) + Registry.getInstance().put({ + api: self._components.filesProviders, + name: 'fileproviders' + }) } async run () { @@ -84,6 +93,7 @@ class AppComponent { // APP_MANAGER const appManager = self.appManager const pluginLoader = self.appManager.pluginLoader + self.panels = {} self.workspace = pluginLoader.get() self.engine = new RemixEngine() self.engine.register(appManager) @@ -93,8 +103,15 @@ class AppComponent { 'remix-beta.ethereum.org': 25, 'remix.ethereum.org': 23 } - self.showMatamo = (matomoDomains[window.location.hostname] && !Registry.getInstance().get('config').api.exists('settings/matomo-analytics')) - self.walkthroughService = new WalkthroughService(appManager, self.showMatamo) + self.showMatamo = + matomoDomains[window.location.hostname] && + !Registry.getInstance() + .get('config') + .api.exists('settings/matomo-analytics') + self.walkthroughService = new WalkthroughService( + appManager, + self.showMatamo + ) const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080'] // workaround for Electron support @@ -106,6 +123,8 @@ class AppComponent { } // SERVICES + // ----------------- gist service --------------------------------- + self.gistHandler = new GistHandler() // ----------------- theme service --------------------------------- self.themeModule = new ThemeModule() Registry.getInstance().put({ api: self.themeModule, name: 'themeModule' }) @@ -113,7 +132,9 @@ class AppComponent { // ----------------- editor service ---------------------------- const editor = new Editor() // wrapper around ace editor Registry.getInstance().put({ api: editor, name: 'editor' }) - editor.event.register('requiringToSaveCurrentfile', () => fileManager.saveCurrentFile()) + editor.event.register('requiringToSaveCurrentfile', () => + fileManager.saveCurrentFile() + ) // ----------------- fileManager service ---------------------------- const fileManager = new FileManager(editor, appManager) @@ -130,7 +151,10 @@ class AppComponent { const compilerMetadataGenerator = new CompilerMetadata() // ----------------- compilation result service (can keep track of compilation results) ---------------------------- const compilersArtefacts = new CompilerArtefacts() // store all the compilation results (key represent a compiler name) - Registry.getInstance().put({ api: compilersArtefacts, name: 'compilersartefacts' }) + Registry.getInstance().put({ + api: compilersArtefacts, + name: 'compilersartefacts' + }) // service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it const fetchAndCompile = new FetchAndCompile() @@ -141,19 +165,22 @@ class AppComponent { const hardhatProvider = new HardhatProvider(blockchain) // ----------------- convert offset to line/column service ----------- const offsetToLineColumnConverter = new OffsetToLineColumnConverter() - Registry.getInstance().put({ api: offsetToLineColumnConverter, name: 'offsettolinecolumnconverter' }) + Registry.getInstance().put({ + api: offsetToLineColumnConverter, + name: 'offsettolinecolumnconverter' + }) // -------------------Terminal---------------------------------------- - makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl)) + makeUdapp(blockchain, compilersArtefacts, domEl => terminal.logHtml(domEl)) const terminal = new Terminal( { appManager, blockchain }, { - getPosition: (event) => { + getPosition: event => { const limitUp = 36 const limitDown = 20 const height = window.innerHeight - let newpos = (event.pageY < limitUp) ? limitUp : event.pageY - newpos = (newpos < height - limitDown) ? newpos : height - limitDown + let newpos = event.pageY < limitUp ? limitUp : event.pageY + newpos = newpos < height - limitDown ? newpos : height - limitDown return height - newpos } } @@ -163,9 +190,12 @@ class AppComponent { self.modal = new ModalPlugin() const configPlugin = new ConfigPlugin() + self.layout = new Layout() self.engine.register([ + self.layout, self.modal, + self.gistHandler, configPlugin, blockchain, contentImport, @@ -187,22 +217,27 @@ class AppComponent { // LAYOUT & SYSTEM VIEWS const appPanel = new MainPanel() - self.mainview = new MainView(contextualListener, editor, appPanel, fileManager, appManager, terminal) Registry.getInstance().put({ api: self.mainview, name: 'mainview' }) - - self.engine.register([ - appPanel, - self.mainview.tabProxy - ]) + const tabProxy = new TabProxy(fileManager, editor) + self.engine.register([appPanel, tabProxy]) // those views depend on app_manager self.menuicons = new VerticalIcons(appManager) self.sidePanel = new SidePanel(appManager, self.menuicons) self.hiddenPanel = new HiddenPanel() - const pluginManagerComponent = new PluginManagerComponent(appManager, self.engine) + const pluginManagerComponent = new PluginManagerComponent( + appManager, + self.engine + ) const filePanel = new FilePanel(appManager) - const landingPage = new LandingPage(appManager, self.menuicons, fileManager, filePanel, contentImport) + const landingPage = new LandingPage( + appManager, + self.menuicons, + fileManager, + filePanel, + contentImport + ) self.settings = new SettingsTab( Registry.getInstance().get('config').api, editor, @@ -220,7 +255,10 @@ class AppComponent { ]) // CONTENT VIEWS & DEFAULT PLUGINS - const compileTab = new CompileTab(Registry.getInstance().get('config').api, Registry.getInstance().get('filemanager').api) + const compileTab = new CompileTab( + Registry.getInstance().get('config').api, + Registry.getInstance().get('filemanager').api + ) const run = new RunTab( blockchain, Registry.getInstance().get('config').api, @@ -229,7 +267,6 @@ class AppComponent { filePanel, Registry.getInstance().get('compilersartefacts').api, networkModule, - self.mainview, Registry.getInstance().get('fileproviders/browser').api ) const analysis = new AnalysisTab() @@ -243,9 +280,7 @@ class AppComponent { contentImport ) - const testplugin = new ModalPluginTester() self.engine.register([ - testplugin, compileTab, run, debug, @@ -256,6 +291,13 @@ class AppComponent { filePanel.hardhatHandle, filePanel.slitherHandle ]) + + self.layout.panels = { + tabs: { plugin: tabProxy, active: true }, + editor: { plugin: editor, active: true }, + main: { plugin: appPanel, active: false }, + terminal: { plugin: terminal, active: true, minimized: false } + } } async activate () { @@ -270,9 +312,9 @@ class AppComponent { try { self.engine.register(await self.appManager.registeredPlugins()) } catch (e) { - console.log('couldn\'t register iframe plugins', e.message) + console.log("couldn't register iframe plugins", e.message) } - + await self.appManager.activatePlugin(['layout']) await self.appManager.activatePlugin(['modal']) await self.appManager.activatePlugin(['editor']) await self.appManager.activatePlugin(['theme', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter']) @@ -280,53 +322,60 @@ class AppComponent { await self.appManager.activatePlugin(['sidePanel']) // activating host plugin separately await self.appManager.activatePlugin(['home']) await self.appManager.activatePlugin(['settings', 'config']) - await self.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport']) + await self.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) await self.appManager.activatePlugin(['settings']) await self.appManager.activatePlugin(['walkthrough']) - await self.appManager.activatePlugin(['testerplugin']) - self.appManager.on('filePanel', 'workspaceInitializationCompleted', async () => { - await self.appManager.registerContextMenuItems() - }) + self.appManager.on( + 'filePanel', + 'workspaceInitializationCompleted', + async () => { + await self.appManager.registerContextMenuItems() + } + ) await self.appManager.activatePlugin(['filePanel']) // Set workspace after initial activation self.appManager.on('editor', 'editorMounted', () => { if (Array.isArray(self.workspace)) { - self.appManager.activatePlugin(self.workspace).then(async () => { - try { - if (params.deactivate) { - await self.appManager.deactivatePlugin(params.deactivate.split(',')) + self.appManager + .activatePlugin(self.workspace) + .then(async () => { + try { + if (params.deactivate) { + await self.appManager.deactivatePlugin( + params.deactivate.split(',') + ) + } + } catch (e) { + console.log(e) + } + if (params.code) { + // if code is given in url we focus on solidity plugin + self.menuicons.select('solidity') + } else { + // If plugins are loaded from the URL params, we focus on the last one. + if ( + self.appManager.pluginLoader.current === 'queryParams' && + self.workspace.length > 0 + ) { self.menuicons.select(self.workspace[self.workspace.length - 1]) } } - } catch (e) { - console.log(e) - } - if (params.code) { - // if code is given in url we focus on solidity plugin - self.menuicons.select('solidity') - } else { - // If plugins are loaded from the URL params, we focus on the last one. - if (self.appManager.pluginLoader.current === 'queryParams' && self.workspace.length > 0) self.menuicons.select(self.workspace[self.workspace.length - 1]) - } - - if (params.call) { - const callDetails = params.call.split('//') - if (callDetails.length > 1) { - toolTip(`initiating ${callDetails[0]} ...`) - // @todo(remove the timeout when activatePlugin is on 0.3.0) - self.appManager.call(...callDetails).catch(console.error) + + if (params.call) { + const callDetails = params.call.split('//') + if (callDetails.length > 1) { + toolTip(`initiating ${callDetails[0]} ...`) + // @todo(remove the timeout when activatePlugin is on 0.3.0) + self.appManager.call(...callDetails).catch(console.error) + } } - } - }).catch(console.error) + }) + .catch(console.error) } }) // activate solidity plugin self.appManager.activatePlugin(['solidity', 'udapp']) // Load and start the service who manager layout and frame - const framingService = new FramingService(self.sidePanel, self.menuicons, self.mainview, null) - - if (params.embed) framingService.embed() - framingService.start(params) } } diff --git a/apps/remix-ide/src/app/components/hidden-panel.js b/apps/remix-ide/src/app/components/hidden-panel.js deleted file mode 100644 index 77f67c6f4c..0000000000 --- a/apps/remix-ide/src/app/components/hidden-panel.js +++ /dev/null @@ -1,31 +0,0 @@ -import { AbstractPanel } from './panel' -import * as packageJson from '../../../../../package.json' -const csjs = require('csjs-inject') -const yo = require('yo-yo') - -const css = csjs` - .pluginsContainer { - display: none; - } -` - -const profile = { - name: 'hiddenPanel', - displayName: 'Hidden Panel', - description: '', - version: packageJson.version, - methods: ['addView', 'removeView'] -} - -export class HiddenPanel extends AbstractPanel { - constructor () { - super(profile) - } - - render () { - return yo` -
- ${this.view} -
` - } -} diff --git a/apps/remix-ide/src/app/components/hidden-panel.tsx b/apps/remix-ide/src/app/components/hidden-panel.tsx new file mode 100644 index 0000000000..bfdff5a11a --- /dev/null +++ b/apps/remix-ide/src/app/components/hidden-panel.tsx @@ -0,0 +1,37 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import ReactDOM from 'react-dom' // eslint-disable-line +import { AbstractPanel } from './panel' +import * as packageJson from '../../../../../package.json' +import { RemixPluginPanel } from '@remix-ui/panel' + +const profile = { + name: 'hiddenPanel', + displayName: 'Hidden Panel', + description: '', + version: packageJson.version, + methods: ['addView', 'removeView'] +} + +export class HiddenPanel extends AbstractPanel { + el: HTMLElement + constructor () { + super(profile) + this.el = document.createElement('div') + this.el.setAttribute('class', 'pluginsContainer') + } + + addView (profile: any, view: any): void { + super.removeView(profile) + super.addView(profile, view) + this.renderComponent() + } + + render () { + return this.el + } + + renderComponent () { + ReactDOM.render(} plugins={this.plugins}/>, this.el) + } +} diff --git a/apps/remix-ide/src/app/components/main-panel.js b/apps/remix-ide/src/app/components/main-panel.js deleted file mode 100644 index 66e6a50245..0000000000 --- a/apps/remix-ide/src/app/components/main-panel.js +++ /dev/null @@ -1,38 +0,0 @@ -import { AbstractPanel } from './panel' -import * as packageJson from '../../../../../package.json' -const yo = require('yo-yo') -const csjs = require('csjs-inject') - -const css = csjs` - .pluginsContainer { - height: 100%; - display: flex; - overflow-y: hidden; - } -` - -const profile = { - name: 'mainPanel', - displayName: 'Main Panel', - description: '', - version: packageJson.version, - methods: ['addView', 'removeView'] -} - -export class MainPanel extends AbstractPanel { - constructor () { - super(profile) - } - - focus (name) { - this.emit('focusChanged', name) - super.focus(name) - } - - render () { - return yo` -
- ${this.view} -
` - } -} diff --git a/apps/remix-ide/src/app/components/main-panel.tsx b/apps/remix-ide/src/app/components/main-panel.tsx new file mode 100644 index 0000000000..b9d180f194 --- /dev/null +++ b/apps/remix-ide/src/app/components/main-panel.tsx @@ -0,0 +1,57 @@ +import React from 'react' // eslint-disable-line +import { AbstractPanel } from './panel' +import ReactDOM from 'react-dom' // eslint-disable-line +import { RemixPluginPanel } from '@remix-ui/panel' +import packageJson from '../../../../../package.json' + +const profile = { + name: 'mainPanel', + displayName: 'Main Panel', + description: '', + version: packageJson.version, + methods: ['addView', 'removeView', 'showContent'] +} + +export class MainPanel extends AbstractPanel { + element: HTMLDivElement + constructor (config) { + super(profile) + this.element = document.createElement('div') + this.element.setAttribute('data-id', 'mainPanelPluginsContainer') + this.element.setAttribute('style', 'height: 100%; width: 100%;') + // this.config = config + } + + onActivation () { + this.renderComponent() + } + + focus (name) { + this.emit('focusChanged', name) + super.focus(name) + this.renderComponent() + } + + addView (profile, view) { + super.addView(profile, view) + this.renderComponent() + } + + removeView (profile) { + super.removeView(profile) + this.renderComponent() + } + + async showContent (name) { + super.showContent(name) + this.renderComponent() + } + + render () { + return this.element + } + + renderComponent () { + ReactDOM.render(} plugins={this.plugins}/>, this.element) + } +} diff --git a/apps/remix-ide/src/app/components/panel.js b/apps/remix-ide/src/app/components/panel.js deleted file mode 100644 index 08d9abf20f..0000000000 --- a/apps/remix-ide/src/app/components/panel.js +++ /dev/null @@ -1,111 +0,0 @@ -import { EventEmitter } from 'events' -import { HostPlugin } from '@remixproject/engine-web' -const csjs = require('csjs-inject') -const yo = require('yo-yo') - -const css = csjs` - .plugins { - height: 100%; - } - .plugItIn { - display : none; - height : 100%; - } - .plugItIn > div { - overflow-y : auto; - overflow-x : hidden; - height : 100%; - width : 100%; - } - .plugItIn.active { - display : block; - } - .pluginsContainer { - height : 100%; - overflow-y : hidden; - } -` - -/** Abstract class used for hosting the view of a plugin */ -export class AbstractPanel extends HostPlugin { - constructor (profile) { - super(profile) - this.events = new EventEmitter() - this.contents = {} - this.active = undefined - - // View where the plugin HTMLElement leaves - this.view = yo`
` - } - - /** - * Add the plugin to the panel - * @param {String} name the name of the plugin - * @param {HTMLElement} content the HTMLContent of the plugin - */ - add (view, name) { - if (this.contents[name]) throw new Error(`Plugin ${name} already rendered`) - view.style.height = '100%' - view.style.width = '100%' - view.style.border = '0' - - const isIframe = view.tagName === 'IFRAME' - view.style.display = isIframe ? 'none' : 'block' - const loading = isIframe ? yo` -
-
- Loading... -
-
- ` : '' - this.contents[name] = yo`
${view}${loading}
` - - if (view.tagName === 'IFRAME') { - view.addEventListener('load', () => { - if (this.contents[name].contains(loading)) { - this.contents[name].removeChild(loading) - } - view.style.display = 'block' - }) - } - this.contents[name].style.display = 'none' - this.view.appendChild(this.contents[name]) - } - - addView (profile, view) { - this.add(view, profile.name) - } - - removeView (profile) { - this.remove(profile.name) - } - - /** - * Remove a plugin from the panel - * @param {String} name The name of the plugin to remove - */ - remove (name) { - const el = this.contents[name] - delete this.contents[name] - if (el) el.parentElement.removeChild(el) - if (name === this.active) this.active = undefined - } - - /** - * Display the content of this specific plugin - * @param {String} name The name of the plugin to display the content - */ - showContent (name) { - if (!this.contents[name]) throw new Error(`Plugin ${name} is not yet activated`) - // hiding the current view and display the `moduleName` - if (this.active) { - this.contents[this.active].style.display = 'none' - } - this.contents[name].style.display = 'flex' - this.active = name - } - - focus (name) { - this.showContent(name) - } -} diff --git a/apps/remix-ide/src/app/components/panel.ts b/apps/remix-ide/src/app/components/panel.ts new file mode 100644 index 0000000000..a5747188af --- /dev/null +++ b/apps/remix-ide/src/app/components/panel.ts @@ -0,0 +1,63 @@ +import React from 'react' // eslint-disable-line +import { EventEmitter } from 'events' +import { HostPlugin } from '@remixproject/engine-web' // eslint-disable-line +import { PluginRecord } from 'libs/remix-ui/panel/src/lib/types' +const EventManager = require('../../lib/events') + +export class AbstractPanel extends HostPlugin { + events: EventEmitter + event: any + public plugins: Record = {} + constructor (profile) { + super(profile) + this.events = new EventEmitter() + this.event = new EventManager() + } + + currentFocus (): string { + return Object.values(this.plugins).find(plugin => { + return plugin.active + }).profile.name + } + + addView (profile, view) { + if (this.plugins[profile.name]) throw new Error(`Plugin ${profile.name} already rendered`) + this.plugins[profile.name] = { + profile: profile, + view: view, + active: false, + class: 'plugItIn active' + } + } + + removeView (profile) { + this.emit('pluginDisabled', profile.name) + this.call('menuicons', 'unlinkContent', profile) + this.remove(profile.name) + } + + /** + * Remove a plugin from the panel + * @param {String} name The name of the plugin to remove + */ + remove (name) { + delete this.plugins[name] + } + + /** + * Display the content of this specific plugin + * @param {String} name The name of the plugin to display the content + */ + showContent (name) { + if (!this.plugins[name]) throw new Error(`Plugin ${name} is not yet activated`) + + Object.values(this.plugins).forEach(plugin => { + plugin.active = false + }) + this.plugins[name].active = true + } + + focus (name) { + this.showContent(name) + } +} diff --git a/apps/remix-ide/src/app/components/side-panel.js b/apps/remix-ide/src/app/components/side-panel.js deleted file mode 100644 index 0cc09d84d4..0000000000 --- a/apps/remix-ide/src/app/components/side-panel.js +++ /dev/null @@ -1,156 +0,0 @@ -import { AbstractPanel } from './panel' -import * as packageJson from '../../../../../package.json' -const csjs = require('csjs-inject') -const yo = require('yo-yo') - -const css = csjs` - .panel { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - flex: auto; - } - .swapitTitle { - margin: 0; - text-transform: uppercase; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .swapitTitle i{ - padding-left: 6px; - font-size: 14px; - } - .swapitHeader { - display: flex; - align-items: center; - padding: 16px 24px 15px; - justify-content: space-between; - } - .icons i { - height: 80%; - cursor: pointer; - } - .pluginsContainer { - height: 100%; - overflow-y: auto; - } - .titleInfo { - padding-left: 10px; - } - .versionBadge { - background-color: var(--light); - padding: 0 7px; - font-weight: bolder; - margin-left: 5px; - text-transform: lowercase; - cursor: default; - } -` - -const sidePanel = { - name: 'sidePanel', - displayName: 'Side Panel', - description: '', - version: packageJson.version, - methods: ['addView', 'removeView'] -} - -// TODO merge with vertical-icons.js -export class SidePanel extends AbstractPanel { - constructor (appManager, verticalIcons) { - super(sidePanel) - this.appManager = appManager - this.header = yo`
` - this.renderHeader() - this.verticalIcons = verticalIcons - - // Toggle content - verticalIcons.events.on('toggleContent', (name) => { - if (!this.contents[name]) return - if (this.active === name) { - // TODO: Only keep `this.emit` (issue#2210) - this.emit('toggle', name) - this.events.emit('toggle', name) - return - } - this.showContent(name) - // TODO: Only keep `this.emit` (issue#2210) - this.emit('showing', name) - this.events.emit('showing', name) - }) - // Force opening - verticalIcons.events.on('showContent', (name) => { - if (!this.contents[name]) return - this.showContent(name) - // TODO: Only keep `this.emit` (issue#2210) - this.emit('showing', name) - this.events.emit('showing', name) - }) - } - - focus (name) { - this.emit('focusChanged', name) - super.focus(name) - } - - removeView (profile) { - super.removeView(profile) - this.emit('pluginDisabled', profile.name) - this.verticalIcons.unlinkContent(profile) - } - - addView (profile, view) { - super.addView(profile, view) - this.verticalIcons.linkContent(profile) - } - - /** - * Display content and update the header - * @param {String} name The name of the plugin to display - */ - async showContent (name) { - super.showContent(name) - this.renderHeader() - this.emit('focusChanged', name) - } - - /** The header of the side panel */ - async renderHeader () { - let name = ' - ' - let docLink = '' - let versionWarning - if (this.active) { - const profile = await this.appManager.getProfile(this.active) - name = profile.displayName ? profile.displayName : profile.name - docLink = profile.documentation ? yo`` : '' - if (profile.version && profile.version.match(/\b(\w*alpha\w*)\b/g)) { - versionWarning = yo`alpha` - } - // Beta - if (profile.version && profile.version.match(/\b(\w*beta\w*)\b/g)) { - versionWarning = yo`beta` - } - } - - const header = yo` -
-
${name}
- ${docLink} - ${versionWarning} -
- ` - yo.update(this.header, header) - } - - render () { - return yo` -
- ${this.header} -
- ${this.view} -
-
` - } -} diff --git a/apps/remix-ide/src/app/components/side-panel.tsx b/apps/remix-ide/src/app/components/side-panel.tsx new file mode 100644 index 0000000000..8b64d64e45 --- /dev/null +++ b/apps/remix-ide/src/app/components/side-panel.tsx @@ -0,0 +1,95 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import ReactDOM from 'react-dom' +import { AbstractPanel } from './panel' +import { RemixPluginPanel } from '@remix-ui/panel' +import packageJson from '../../../../../package.json' +import { RemixAppManager } from '../../remixAppManager' +import { VerticalIcons } from 'libs/remix-ui/vertical-icons-panel/types/vertical-icons-panel' +import RemixUIPanelHeader from 'libs/remix-ui/panel/src/lib/plugins/panel-header' +// const csjs = require('csjs-inject') + +const sidePanel = { + name: 'sidePanel', + displayName: 'Side Panel', + description: '', + version: packageJson.version, + methods: ['addView', 'removeView'] +} + +// TODO merge with vertical-icons.js +export class SidePanel extends AbstractPanel { + appManager: RemixAppManager + sideelement: any + verticalIcons: VerticalIcons; + constructor (appManager: RemixAppManager, verticalIcons: VerticalIcons) { + super(sidePanel) + this.appManager = appManager + this.sideelement = document.createElement('section') + this.sideelement.setAttribute('class', 'panel plugin-manager') + this.verticalIcons = verticalIcons + + // Toggle content + verticalIcons.events.on('toggleContent', (name) => { + if (!this.plugins[name]) return + if (this.plugins[name].active) { + // TODO: Only keep `this.emit` (issue#2210) + this.emit('toggle', name) + this.events.emit('toggle', name) + return + } + this.showContent(name) + // TODO: Only keep `this.emit` (issue#2210) + this.emit('showing', name) + this.events.emit('showing', name) + }) + // Force opening + verticalIcons.events.on('showContent', (name) => { + if (!this.plugins[name]) return + this.showContent(name) + // TODO: Only keep `this.emit` (issue#2210) + this.emit('showing', name) + this.events.emit('showing', name) + }) + } + + onActivation () { + this.renderComponent() + } + + focus (name) { + this.emit('focusChanged', name) + super.focus(name) + } + + removeView (profile) { + super.removeView(profile) + this.emit('pluginDisabled', profile.name) + this.call('menuicons', 'unlinkContent', profile) + this.renderComponent() + } + + addView (profile, view) { + super.addView(profile, view) + this.verticalIcons.linkContent(profile) + this.renderComponent() + } + + /** + * Display content and update the header + * @param {String} name The name of the plugin to display + */ + async showContent (name) { + super.showContent(name) + this.emit('focusChanged', name) + this.renderComponent() + } + + render () { + return this.sideelement + } + + renderComponent () { + ReactDOM.render(} plugins={this.plugins}/>, this.sideelement) + } +} diff --git a/apps/remix-ide/src/app/components/vertical-icons.js b/apps/remix-ide/src/app/components/vertical-icons.js index 86cedb0014..c458e9e5ef 100644 --- a/apps/remix-ide/src/app/components/vertical-icons.js +++ b/apps/remix-ide/src/app/components/vertical-icons.js @@ -1,12 +1,9 @@ import * as packageJson from '../../../../../package.json' -// eslint-disable-next-line no-unused-vars -import { basicLogo } from '../ui/svgLogo' import ReactDOM from 'react-dom' import React from 'react' // eslint-disable-line // eslint-disable-next-line no-unused-vars import { RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel' import Registry from '../state/registry' -// var helper = require('../../lib/helper') const { Plugin } = require('@remixproject/engine') const EventEmitter = require('events') @@ -15,7 +12,7 @@ const profile = { displayName: 'Vertical Icons', description: '', version: packageJson.version, - methods: ['select'] + methods: ['select', 'unlinkContent'] } // TODO merge with side-panel.js. VerticalIcons should not be a plugin diff --git a/apps/remix-ide/src/app/editor/contextView.js b/apps/remix-ide/src/app/editor/contextView.js deleted file mode 100644 index dd3f695763..0000000000 --- a/apps/remix-ide/src/app/editor/contextView.js +++ /dev/null @@ -1,194 +0,0 @@ -'use strict' -import { sourceMappingDecoder } from '@remix-project/remix-debug' -import Registry from '../state/registry' -const yo = require('yo-yo') - -const css = require('./styles/contextView-styles') - -/* - Display information about the current focused code: - - if it's a reference, display information about the declaration - - jump to the declaration - - number of references - - rename declaration/references -*/ -class ContextView { - constructor (opts) { - this._components = {} - this._components.registry = Registry.getInstance() - this.contextualListener = opts.contextualListener - this.editor = opts.editor - this._deps = { - compilersArtefacts: this._components.registry.get('compilersartefacts').api, - offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api, - config: this._components.registry.get('config').api, - fileManager: this._components.registry.get('filemanager').api - } - this._view = null - this._nodes = null - this._current = null - this.sourceMappingDecoder = sourceMappingDecoder - this.previousElement = null - this.contextualListener.event.register('contextChanged', nodes => { - this.show() - this._nodes = nodes - this.update() - }) - this.contextualListener.event.register('stopHighlighting', () => { - }) - } - - render () { - const view = yo` -
-
- ${this._renderTarget()} -
-
` - if (!this._view) { - this._view = view - } - return view - } - - hide () { - if (this._view) { - this._view.style.display = 'none' - } - } - - show () { - if (this._view) { - this._view.style.display = 'block' - } - } - - update () { - if (this._view) { - yo.update(this._view, this.render()) - } - } - - _renderTarget () { - let last - const previous = this._current - if (this._nodes && this._nodes.length) { - last = this._nodes[this._nodes.length - 1] - if (isDefinition(last)) { - this._current = last - } else { - const target = this.contextualListener.declarationOf(last) - if (target) { - this._current = target - } else { - this._current = null - } - } - } - if (!this._current || !previous || previous.id !== this._current.id || (this.previousElement && !this.previousElement.children.length)) { - this.previousElement = this._render(this._current, last) - } - return this.previousElement - } - - _jumpToInternal (position) { - const jumpToLine = (lineColumn) => { - if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) { - this.editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1) - } - } - const lastCompilationResult = this._deps.compilersArtefacts.__last - if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) { - const lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn( - position, - position.file, - lastCompilationResult.getSourceCode().sources, - lastCompilationResult.getAsts()) - const filename = lastCompilationResult.getSourceName(position.file) - // TODO: refactor with rendererAPI.errorClick - if (filename !== this._deps.config.get('currentFile')) { - const provider = this._deps.fileManager.fileProviderOf(filename) - if (provider) { - provider.exists(filename).then(exist => { - this._deps.fileManager.open(filename) - jumpToLine(lineColumn) - }).catch(error => { - if (error) return console.log(error) - }) - } - } else { - jumpToLine(lineColumn) - } - } - } - - _render (node, nodeAtCursorPosition) { - if (!node) return yo`
` - let references = this.contextualListener.referencesOf(node) - const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType - references = `${references ? references.length : '0'} reference(s)` - - let ref = 0 - const nodes = this.contextualListener.getActiveHighlights() - for (const k in nodes) { - if (nodeAtCursorPosition.id === nodes[k].nodeId) { - ref = k - break - } - } - - // JUMP BETWEEN REFERENCES - const jump = (e) => { - e.target.dataset.action === 'next' ? ref++ : ref-- - if (ref < 0) ref = nodes.length - 1 - if (ref >= nodes.length) ref = 0 - this._jumpToInternal(nodes[ref].position) - } - - const jumpTo = () => { - if (node && node.src) { - const position = this.sourceMappingDecoder.decode(node.src) - if (position) { - this._jumpToInternal(position) - } - } - } - - const showGasEstimation = () => { - if (node.nodeType === 'FunctionDefinition') { - const result = this.contextualListener.gasEstimation(node) - const executionCost = ' Execution cost: ' + result.executionCost + ' gas' - const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas' - const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}` - return yo` -
- - ${estimatedGas} -
- ` - } - } - - return yo` -
${showGasEstimation()} -
${type}
-
${node.name}
- - ${references} - - -
- ` - } -} - -function isDefinition (node) { - return node.nodeType === 'ContractDefinition' || - node.nodeType === 'FunctionDefinition' || - node.nodeType === 'ModifierDefinition' || - node.nodeType === 'VariableDeclaration' || - node.nodeType === 'StructDefinition' || - node.nodeType === 'EventDefinition' -} - -module.exports = ContextView diff --git a/apps/remix-ide/src/app/files/fileManager.ts b/apps/remix-ide/src/app/files/fileManager.ts index 3fa571df83..14aa2d424c 100644 --- a/apps/remix-ide/src/app/files/fileManager.ts +++ b/apps/remix-ide/src/app/files/fileManager.ts @@ -22,7 +22,7 @@ const profile = { icon: 'assets/img/fileManager.webp', permission: true, version: packageJson.version, - methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile'], + methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles'], kind: 'file-system' } const errorMsg = { diff --git a/apps/remix-ide/src/app/panels/layout.ts b/apps/remix-ide/src/app/panels/layout.ts new file mode 100644 index 0000000000..451c2aefe7 --- /dev/null +++ b/apps/remix-ide/src/app/panels/layout.ts @@ -0,0 +1,94 @@ +import { Plugin } from '@remixproject/engine' +import { Profile } from '@remixproject/plugin-utils' +import { EventEmitter } from 'events' +import QueryParams from '../../lib/query-params' + +const profile: Profile = { + name: 'layout', + description: 'layout', + methods: ['minimize'] +} + +interface panelState { + active: boolean + plugin: Plugin + minimized: boolean +} +interface panels { + tabs: panelState + editor: panelState + main: panelState + terminal: panelState +} + +export class Layout extends Plugin { + event: any + panels: panels + constructor () { + super(profile) + this.event = new EventEmitter() + } + + async onActivation (): Promise { + this.on('fileManager', 'currentFileChanged', () => { + this.panels.editor.active = true + this.panels.main.active = false + this.event.emit('change', null) + }) + this.on('tabs', 'openFile', () => { + this.panels.editor.active = true + this.panels.main.active = false + this.event.emit('change', null) + }) + this.on('tabs', 'switchApp', (name: string) => { + this.call('mainPanel', 'showContent', name) + this.panels.editor.active = false + this.panels.main.active = true + this.event.emit('change', null) + }) + this.on('tabs', 'closeApp', (name: string) => { + this.panels.editor.active = true + this.panels.main.active = false + this.event.emit('change', null) + }) + this.on('tabs', 'tabCountChanged', async count => { + if (!count) await this.call('manager', 'activatePlugin', 'home') + }) + this.on('manager', 'activate', (profile: Profile) => { + switch (profile.name) { + case 'filePanel': + this.call('menuicons', 'select', 'filePanel') + break + } + }) + document.addEventListener('keypress', e => { + if (e.shiftKey && e.ctrlKey) { + if (e.code === 'KeyF') { + // Ctrl+Shift+F + this.call('menuicons', 'select', 'filePanel') + } else if (e.code === 'KeyA') { + // Ctrl+Shift+A + this.call('menuicons', 'select', 'pluginManager') + } else if (e.code === 'KeyS') { + // Ctrl+Shift+S + this.call('menuicons', 'select', 'settings') + } + e.preventDefault() + } + }) + const queryParams = new QueryParams() + const params = queryParams.get() + if (params.minimizeterminal || params.embed) { + this.panels.terminal.minimized = true + this.event.emit('change', null) + } + if (params.minimizesidepanel || params.embed) { + this.event.emit('minimizesidepanel') + } + } + + minimize (name: string, minimized:boolean): void { + this.panels[name].minimized = minimized + this.event.emit('change', null) + } +} diff --git a/apps/remix-ide/src/app/panels/main-view.js b/apps/remix-ide/src/app/panels/main-view.js deleted file mode 100644 index bf3a07a35b..0000000000 --- a/apps/remix-ide/src/app/panels/main-view.js +++ /dev/null @@ -1,204 +0,0 @@ -import Registry from '../state/registry' - -var yo = require('yo-yo') -var EventManager = require('../../lib/events') - -var { TabProxy } = require('./tab-proxy.js') - -var csjs = require('csjs-inject') - -var css = csjs` - .mainview { - display : flex; - flex-direction : column; - height : 100%; - width : 100%; - } -` - -// @todo(#650) Extract this into two classes: MainPanel (TabsProxy + Iframe/Editor) & BottomPanel (Terminal) -export class MainView { - constructor (contextualListener, editor, mainPanel, fileManager, appManager, terminal) { - var self = this - self.event = new EventManager() - self._view = {} - self._components = {} - self._components.registry = Registry.getInstance() - self.contextualListener = contextualListener - self.editor = editor - self.fileManager = fileManager - self.mainPanel = mainPanel - self.txListener = Registry.getInstance().get('txlistener').api - self._components.terminal = terminal - this.appManager = appManager - this.init() - } - - showApp (name) { - this.fileManager.unselectCurrentFile() - this.mainPanel.showContent(name) - this._view.editor.style.display = 'none' - this._view.mainPanel.style.display = 'block' - } - - getAppPanel () { - return this.mainPanel - } - - init () { - var self = this - self._deps = { - config: self._components.registry.get('config').api, - fileManager: self._components.registry.get('filemanager').api - } - - self.tabProxy = new TabProxy(self.fileManager, self.editor) - /* - We listen here on event from the tab component to display / hide the editor and mainpanel - depending on the content that should be displayed - */ - self.fileManager.events.on('currentFileChanged', (file) => { - // we check upstream for "fileChanged" - self._view.editor.style.display = 'block' - self._view.mainPanel.style.display = 'none' - }) - self.tabProxy.event.on('openFile', (file) => { - self._view.editor.style.display = 'block' - self._view.mainPanel.style.display = 'none' - }) - self.tabProxy.event.on('closeFile', (file) => { - }) - self.tabProxy.event.on('switchApp', self.showApp.bind(self)) - self.tabProxy.event.on('closeApp', (name) => { - self._view.editor.style.display = 'block' - self._view.mainPanel.style.display = 'none' - }) - self.tabProxy.event.on('tabCountChanged', (count) => { - if (!count) this.editor.displayEmptyReadOnlySession() - }) - self.data = { - _layout: { - top: { - offset: self._terminalTopOffset(), - show: true - } - } - } - - self._components.terminal.event.register('resize', delta => self._adjustLayout('top', delta)) - if (self.txListener) { - self._components.terminal.event.register('listenOnNetWork', (listenOnNetWork) => { - self.txListener.setListenOnNetwork(listenOnNetWork) - }) - } - } - - _terminalTopOffset () { - return this._deps.config.get('terminal-top-offset') || 150 - } - - _adjustLayout (direction, delta) { - var limitUp = 0 - var limitDown = 32 - var containerHeight = window.innerHeight - limitUp // - menu bar containerHeight - var self = this - var layout = self.data._layout[direction] - if (layout) { - if (delta === undefined) { - layout.show = !layout.show - if (layout.show) delta = layout.offset - else delta = 0 - } else { - layout.show = true - self._deps.config.set(`terminal-${direction}-offset`, delta) - layout.offset = delta - } - } - var tmp = delta - limitDown - delta = tmp > 0 ? tmp : 0 - if (direction === 'top') { - var mainPanelHeight = containerHeight - delta - mainPanelHeight = mainPanelHeight < 0 ? 0 : mainPanelHeight - self._view.editor.style.height = `${mainPanelHeight}px` - self._view.mainPanel.style.height = `${mainPanelHeight}px` - self._view.terminal.style.height = `${delta}px` // - menu bar height - self.editor.resize((document.querySelector('#editorWrap') || {}).checked) - self._components.terminal.scroll2bottom() - } - } - - minimizeTerminal () { - this._adjustLayout('top') - } - - showTerminal (offset) { - this._adjustLayout('top', offset || this._terminalTopOffset()) - } - - getTerminal () { - return this._components.terminal - } - - getEditor () { - var self = this - return self.editor - } - - refresh () { - var self = this - self._view.tabs.onmouseenter() - } - - log (data = {}) { - var self = this - var command = self._components.terminal.commands[data.type] - if (typeof command === 'function') command(data.value) - } - - logMessage (msg) { - var self = this - self.log({ type: 'log', value: msg }) - } - - logHtmlMessage (msg) { - var self = this - self.log({ type: 'html', value: msg }) - } - - render () { - var self = this - if (self._view.mainview) return self._view.mainview - self._view.editor = self.editor.render() - self._view.editor.style.display = 'none' - self._view.mainPanel = self.mainPanel.render() - self._view.terminal = self._components.terminal.render() - - self._view.mainview = yo` -
- ${self.tabProxy.renderTabsbar()} - ${self._view.editor} - ${self._view.mainPanel} -
- ${self._view.terminal} -
- ` - - // INIT - self._adjustLayout('top', self.data._layout.top.offset) - - document.addEventListener('keydown', (e) => { - if (e.altKey && e.keyCode === 84) self.tabProxy.switchNextTab() // alt + t - }) - - return self._view.mainview - } - - registerCommand (name, command, opts) { - var self = this - return self._components.terminal.registerCommand(name, command, opts) - } - - updateTerminalFilter (filter) { - this._components.terminal.updateJournal(filter) - } -} diff --git a/apps/remix-ide/src/app/panels/tab-proxy.js b/apps/remix-ide/src/app/panels/tab-proxy.js index 5b65254830..267b54d853 100644 --- a/apps/remix-ide/src/app/panels/tab-proxy.js +++ b/apps/remix-ide/src/app/panels/tab-proxy.js @@ -22,6 +22,7 @@ export class TabProxy extends Plugin { this._view = {} this._handlers = {} this.loadedTabs = [] + this.el = document.createElement('div') } onActivation () { @@ -72,10 +73,12 @@ export class TabProxy extends Plugin { this.addTab(workspacePath, '', () => { this.fileManager.open(file) this.event.emit('openFile', file) + this.emit('openFile', file) }, () => { this.fileManager.closeFile(file) this.event.emit('closeFile', file) + this.emit('closeFile', file) }) this.tabsApi.activateTab(workspacePath) } else { @@ -88,10 +91,12 @@ export class TabProxy extends Plugin { this.addTab(path, '', () => { this.fileManager.open(file) this.event.emit('openFile', file) + this.emit('openFile', file) }, () => { this.fileManager.closeFile(file) this.event.emit('closeFile', file) + this.emit('closeFile', file) }) this.tabsApi.activateTab(path) } @@ -132,9 +137,9 @@ export class TabProxy extends Plugin { this.addTab( name, displayName, - () => this.event.emit('switchApp', name), + () => this.emit('switchApp', name), () => { - this.event.emit('closeApp', name) + this.emit('closeApp', name) this.call('manager', 'deactivatePlugin', name) }, icon @@ -149,7 +154,7 @@ export class TabProxy extends Plugin { } focus (name) { - this.event.emit('switchApp', name) + this.emit('switchApp', name) this.tabsApi.activateTab(name) } @@ -199,6 +204,7 @@ export class TabProxy extends Plugin { () => { this.fileManager.closeFile(newName) this.event.emit('closeFile', newName) + this.emit('closeFile', newName) }) this.removeTab(oldName) } @@ -285,7 +291,7 @@ export class TabProxy extends Plugin { if (this.loadedTabs[index]) { const name = this.loadedTabs[index].name if (this._handlers[name]) this._handlers[name].switchTo() - this.event.emit('tabCountChanged', this.loadedTabs.length) + this.emit('tabCountChanged', this.loadedTabs.length) } } @@ -293,7 +299,7 @@ export class TabProxy extends Plugin { if (this.loadedTabs[index]) { const name = this.loadedTabs[index].name if (this._handlers[name]) this._handlers[name].close() - this.event.emit('tabCountChanged', this.loadedTabs.length) + this.emit('tabCountChanged', this.loadedTabs.length) } } @@ -308,8 +314,6 @@ export class TabProxy extends Plugin { } renderTabsbar () { - this.el = document.createElement('div') - this.renderComponent() return this.el } } diff --git a/apps/remix-ide/src/app/panels/terminal.js b/apps/remix-ide/src/app/panels/terminal.js index 4837f24203..fd17d8b450 100644 --- a/apps/remix-ide/src/app/panels/terminal.js +++ b/apps/remix-ide/src/app/panels/terminal.js @@ -8,13 +8,8 @@ import Registry from '../state/registry' const vm = require('vm') const EventManager = require('../../lib/events') -const CommandInterpreterAPI = require('../../lib/cmdInterpreterAPI') -const AutoCompletePopup = require('../ui/auto-complete-popup') - import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line -const GistHandler = require('../../lib/gist-handler') - const KONSOLES = [] function register (api) { KONSOLES.push(api) } @@ -22,7 +17,7 @@ function register (api) { KONSOLES.push(api) } const profile = { displayName: 'Terminal', name: 'terminal', - methods: ['log'], + methods: ['log', 'logHtml'], events: [], description: ' - ', version: packageJson.version @@ -32,7 +27,6 @@ class Terminal extends Plugin { constructor (opts, api) { super(profile) this.fileImport = new CompilerImports() - this.gistHandler = new GistHandler() this.event = new EventManager() this.globalRegistry = Registry.getInstance() this.element = document.createElement('div') @@ -68,8 +62,6 @@ class Terminal extends Plugin { } this._view = { el: null, bar: null, input: null, term: null, journal: null, cli: null } this._components = {} - this._components.cmdInterpreter = new CommandInterpreterAPI(this, this.blockchain) - this._components.autoCompletePopup = new AutoCompletePopup(this._opts) this._commands = {} this.commands = {} this._JOURNAL = [] diff --git a/apps/remix-ide/src/app/plugins/test.ts b/apps/remix-ide/src/app/plugins/test.ts deleted file mode 100644 index da32f24039..0000000000 --- a/apps/remix-ide/src/app/plugins/test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Plugin } from '@remixproject/engine' -import { Profile } from '@remixproject/plugin-utils' -import { AlertModal } from 'libs/remix-ui/app/src/lib/remix-app/interface' -import { ModalTypes } from 'libs/remix-ui/app/src/lib/remix-app/types' -import { AppModal } from '../../../../../libs/remix-ui/app/src' - -const profile:Profile = { - name: 'testerplugin', - displayName: 'testerplugin', - description: 'testerplugin', - methods: [] -} - -export class ModalPluginTester extends Plugin { - constructor () { - super(profile) - } - - handleMessage (message: any): void { - console.log(message) - } - - onActivation (): void { - // just a modal - let mod:AppModal = { - id: 'modal1', - title: 'test', - message: 'test', - okFn: this.handleMessage, - okLabel: 'yes', - cancelFn: null, - cancelLabel: 'no' - } - // this.call('modal', 'modal', mod) - - // modal with callback - mod = { ...mod, message: 'gist url', modalType: ModalTypes.prompt, defaultValue: 'prompting' } - // this.call('modal', 'modal', mod) - - // modal with password - mod = { ...mod, message: 'enter password to give me eth', modalType: ModalTypes.password, defaultValue: 'pass' } - // this.call('modal', 'modal', mod) - - const al:AlertModal = { - id: 'myalert', - message: 'alert message' - } - // this.call('modal', 'alert', al) - - // set toaster - // this.call('modal', 'toast', 'toast message') - } -} diff --git a/apps/remix-ide/src/app/tabs/analysis-tab.js b/apps/remix-ide/src/app/tabs/analysis-tab.js index fbddc61b61..dcd894d02c 100644 --- a/apps/remix-ide/src/app/tabs/analysis-tab.js +++ b/apps/remix-ide/src/app/tabs/analysis-tab.js @@ -5,7 +5,6 @@ import { EventEmitter } from 'events' import {RemixUiStaticAnalyser} from '@remix-ui/static-analyser' // eslint-disable-line import * as packageJson from '../../../../../package.json' import Registry from '../state/registry' -var Renderer = require('../ui/renderer') var EventManager = require('../../lib/events') @@ -30,9 +29,7 @@ class AnalysisTab extends ViewPlugin { this.registry = Registry.getInstance() this.element = document.createElement('div') this.element.setAttribute('id', 'staticAnalyserView') - this._components = { - renderer: new Renderer(this) - } + this._components = {} this._components.registry = this.registry this._deps = { offsetToLineColumnConverter: this.registry.get( diff --git a/apps/remix-ide/src/app/tabs/hardhat-provider.js b/apps/remix-ide/src/app/tabs/hardhat-provider.js deleted file mode 100644 index 75c20a90cd..0000000000 --- a/apps/remix-ide/src/app/tabs/hardhat-provider.js +++ /dev/null @@ -1,82 +0,0 @@ -import * as packageJson from '../../../../../package.json' -import { Plugin } from '@remixproject/engine' -import Web3 from 'web3' -const yo = require('yo-yo') -const modalDialogCustom = require('../ui/modal-dialog-custom') - -const profile = { - name: 'hardhat-provider', - displayName: 'Hardhat Provider', - kind: 'provider', - description: 'Hardhat provider', - methods: ['sendAsync'], - version: packageJson.version -} - -export default class HardhatProvider extends Plugin { - constructor (blockchain) { - super(profile) - this.provider = null - this.blocked = false // used to block any call when trying to recover after a failed connection. - this.blockchain = blockchain - } - - onDeactivation () { - this.provider = null - this.blocked = false - } - - hardhatProviderDialogBody () { - return yo` -
- Note: To run Hardhat network node on your system, go to hardhat project folder and run command: -
npx hardhat node
-
- For more info, visit: Hardhat Documentation -

- Hardhat JSON-RPC Endpoint -
- ` - } - - sendAsync (data) { - return new Promise((resolve, reject) => { - if (this.blocked) return reject(new Error('provider unable to connect')) - // If provider is not set, allow to open modal only when provider is trying to connect - if (!this.provider) { - modalDialogCustom.prompt('Hardhat node request', this.hardhatProviderDialogBody(), 'http://127.0.0.1:8545', (target) => { - this.provider = new Web3.providers.HttpProvider(target) - this.sendAsyncInternal(data, resolve, reject) - }, () => { - this.sendAsyncInternal(data, resolve, reject) - }) - } else { - this.sendAsyncInternal(data, resolve, reject) - } - }) - } - - sendAsyncInternal (data, resolve, reject) { - if (this.provider) { - // Check the case where current environment is VM on UI and it still sends RPC requests - // This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!' - if (this.blockchain.getProvider() !== 'Hardhat Provider' && data.method !== 'net_listening') return reject(new Error('Environment Updated !!')) - this.provider[this.provider.sendAsync ? 'sendAsync' : 'send'](data, async (error, message) => { - if (error) { - this.blocked = true - modalDialogCustom.alert('Hardhat Provider', `Error while connecting to the hardhat provider: ${error.message}`) - await this.call('udapp', 'setEnvironmentMode', { context: 'vm', fork: 'london' }) - this.provider = null - setTimeout(_ => { this.blocked = false }, 1000) // we wait 1 second for letting remix to switch to vm - return reject(error) - } - resolve(message) - }) - } else { - const result = data.method === 'net_listening' ? 'canceled' : [] - resolve({ jsonrpc: '2.0', result: result, id: data.id }) - } - } -} - -module.exports = HardhatProvider diff --git a/apps/remix-ide/src/app/tabs/hardhat-provider.tsx b/apps/remix-ide/src/app/tabs/hardhat-provider.tsx new file mode 100644 index 0000000000..abb5eabddf --- /dev/null +++ b/apps/remix-ide/src/app/tabs/hardhat-provider.tsx @@ -0,0 +1,128 @@ +import * as packageJson from '../../../../../package.json' +import { Plugin } from '@remixproject/engine' +import { AppModal, AlertModal, ModalTypes } from '@remix-ui/app' +import React from 'react' // eslint-disable-line +import { Blockchain } from '../../blockchain/blockchain' +import { ethers } from 'ethers' + +const profile = { + name: 'hardhat-provider', + displayName: 'Hardhat Provider', + kind: 'provider', + description: 'Hardhat provider', + methods: ['sendAsync'], + version: packageJson.version +} + +type JsonDataRequest = { + id: number, + jsonrpc: string // version + method: string, + params: Array, +} + +type JsonDataResult = { + id: number, + jsonrpc: string // version + result: any +} + +type RejectRequest = (error: Error) => void +type SuccessRequest = (data: JsonDataResult) => void + +export class HardhatProvider extends Plugin { + provider: ethers.providers.JsonRpcProvider + blocked: boolean + blockchain: Blockchain + target: String + + constructor (blockchain) { + super(profile) + this.provider = null + this.blocked = false // used to block any call when trying to recover after a failed connection. + this.blockchain = blockchain + } + + onDeactivation () { + this.provider = null + this.blocked = false + } + + hardhatProviderDialogBody (): JSX.Element { + return (
Note: To run Hardhat network node on your system, go to hardhat project folder and run command: +
npx hardhat node
+ For more info, visit: Hardhat Documentation + Hardhat JSON-RPC Endpoint +
) + } + + sendAsync (data: JsonDataRequest): Promise { + return new Promise(async (resolve, reject) => { + if (this.blocked) return reject(new Error('provider unable to connect')) + // If provider is not set, allow to open modal only when provider is trying to connect + if (!this.provider) { + let value: string + try { + value = await ((): Promise => { + return new Promise((resolve, reject) => { + const modalContent: AppModal = { + id: 'harrhatprovider', + title: 'Hardhat node request', + message: this.hardhatProviderDialogBody(), + modalType: ModalTypes.prompt, + okLabel: 'OK', + cancelLabel: 'Cancel', + okFn: (value: string) => { + setTimeout(() => resolve(value), 0) + }, + cancelFn: () => { + setTimeout(() => reject(new Error('Canceled')), 0) + }, + hideFn: () => { + setTimeout(() => reject(new Error('Hide')), 0) + }, + defaultValue: 'http://127.0.0.1:8545' + } + this.call('modal', 'modal', modalContent) + }) + })() + } catch (e) { + // the modal has been canceled/hide + return + } + this.provider = new ethers.providers.JsonRpcProvider(value) + this.sendAsyncInternal(data, resolve, reject) + } else { + this.sendAsyncInternal(data, resolve, reject) + } + }) + } + + private async sendAsyncInternal (data: JsonDataRequest, resolve: SuccessRequest, reject: RejectRequest): Promise { + if (this.provider) { + // Check the case where current environment is VM on UI and it still sends RPC requests + // This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!' + if (this.blockchain.getProvider() !== 'Hardhat Provider' && data.method !== 'net_listening') return reject(new Error('Environment Updated !!')) + + try { + const result = await this.provider.send(data.method, data.params) + resolve({ jsonrpc: '2.0', result, id: data.id }) + } catch (error) { + this.blocked = true + const modalContent: AlertModal = { + id: 'harrhatprovider', + title: 'Hardhat Provider', + message: `Error while connecting to the hardhat provider: ${error.message}`, + } + this.call('modal', 'alert', modalContent) + await this.call('udapp', 'setEnvironmentMode', { context: 'vm', fork: 'london' }) + this.provider = null + setTimeout(_ => { this.blocked = false }, 1000) // we wait 1 second for letting remix to switch to vm + reject(error) + } + } else { + const result = data.method === 'net_listening' ? 'canceled' : [] + resolve({ jsonrpc: '2.0', result: result, id: data.id }) + } + } +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/tabs/runTab/contractDropdown.js b/apps/remix-ide/src/app/tabs/runTab/contractDropdown.js deleted file mode 100644 index 9cdbedd326..0000000000 --- a/apps/remix-ide/src/app/tabs/runTab/contractDropdown.js +++ /dev/null @@ -1,423 +0,0 @@ -import publishToStorage from '../../../publishToStorage' -const yo = require('yo-yo') -const ethJSUtil = require('ethereumjs-util') -const css = require('../styles/run-tab-styles') -const modalDialogCustom = require('../../ui/modal-dialog-custom') -const remixLib = require('@remix-project/remix-lib') -const EventManager = remixLib.EventManager -const confirmDialog = require('../../ui/confirmDialog') -const modalDialog = require('../../ui/modaldialog') -const MultiParamManager = require('../../ui/multiParamManager') -const helper = require('../../../lib/helper') -const addTooltip = require('../../ui/tooltip') -const _paq = window._paq = window._paq || [] - -class ContractDropdownUI { - constructor (blockchain, dropdownLogic, logCallback, runView) { - this.blockchain = blockchain - this.dropdownLogic = dropdownLogic - this.logCallback = logCallback - this.runView = runView - this.event = new EventManager() - - this.listenToEvents() - this.ipfsCheckedState = false - this.exEnvironment = blockchain.getProvider() - this.listenToContextChange() - this.loadType = 'other' - } - - listenToEvents () { - this.dropdownLogic.event.register('newlyCompiled', (success, data, source, compiler, compilerFullName, file) => { - if (!this.selectContractNames) return - this.selectContractNames.innerHTML = '' - if (success) { - this.dropdownLogic.getCompiledContracts(compiler, compilerFullName).forEach((contract) => { - this.selectContractNames.appendChild(yo``) - }) - } - this.enableAtAddress(success) - this.enableContractNames(success) - this.setInputParamsPlaceHolder() - - if (success) { - this.compFails.style.display = 'none' - } else { - this.compFails.style.display = 'block' - } - }) - } - - listenToContextChange () { - this.blockchain.event.register('networkStatus', ({ error, network }) => { - if (error) { - console.log('can\'t detect network') - return - } - this.exEnvironment = this.blockchain.getProvider() - this.networkName = network.name - this.networkId = network.id - - const savedConfig = window.localStorage.getItem(`ipfs/${this.exEnvironment}/${this.networkName}`) - - // check if an already selected option exist else use default workflow - if (savedConfig !== null) { - this.setCheckedState(savedConfig) - } else { - this.setCheckedState(this.networkName === 'Main') - } - }) - } - - setCheckedState (value) { - value = value === 'true' ? true : value === 'false' ? false : value - this.ipfsCheckedState = value - if (this.ipfsCheckbox) this.ipfsCheckbox.checked = value - } - - toggleCheckedState () { - if (this.exEnvironment === 'vm') this.networkName = 'VM' - this.ipfsCheckedState = !this.ipfsCheckedState - window.localStorage.setItem(`ipfs/${this.exEnvironment}/${this.networkName}`, this.ipfsCheckedState) - } - - enableContractNames (enable) { - if (enable) { - if (this.selectContractNames.value === '') return - this.selectContractNames.removeAttribute('disabled') - this.selectContractNames.setAttribute('title', 'Select contract for Deploy or At Address.') - } else { - this.selectContractNames.setAttribute('disabled', true) - if (this.loadType === 'sol') { - this.selectContractNames.setAttribute('title', '⚠ Select and compile *.sol file to deploy or access a contract.') - } else { - this.selectContractNames.setAttribute('title', '⚠ Selected *.abi file allows accessing contracts, select and compile *.sol file to deploy and access one.') - } - } - } - - enableAtAddress (enable) { - if (enable) { - const address = this.atAddressButtonInput.value - if (!address || !ethJSUtil.isValidAddress(address)) { - this.enableAtAddress(false) - return - } - this.atAddress.removeAttribute('disabled') - this.atAddress.setAttribute('title', 'Interact with the given contract.') - } else { - this.atAddress.setAttribute('disabled', true) - if (this.atAddressButtonInput.value === '') { - this.atAddress.setAttribute('title', '⚠ Compile *.sol file or select *.abi file & then enter the address of deployed contract.') - } else { - this.atAddress.setAttribute('title', '⚠ Compile *.sol file or select *.abi file.') - } - } - } - - render () { - this.compFails = yo`` - this.atAddress = yo`` - this.atAddressButtonInput = yo`` - this.selectContractNames = yo`` - this.abiLabel = yo`ABI file selected` - if (this.exEnvironment === 'vm') this.networkName = 'VM' - this.enableAtAddress(false) - this.abiLabel.style.display = 'none' - - const savedConfig = window.localStorage.getItem(`ipfs/${this.exEnvironment}/${this.networkName}`) - this.ipfsCheckedState = savedConfig === 'true' ? true : false // eslint-disable-line - - this.ipfsCheckbox = yo` - this.toggleCheckedState()} - > - ` - if (this.ipfsCheckedState) this.ipfsCheckbox.checked = true - - this.deployCheckBox = yo` -
- ${this.ipfsCheckbox} - -
- ` - this.createPanel = yo`
` - this.orLabel = yo`
or
` - - const contractNamesContainer = yo` -
- -
- ${this.selectContractNames} ${this.compFails} - ${this.abiLabel} -
-
- ${this.createPanel} - ${this.orLabel} -
- ${this.atAddress} - ${this.atAddressButtonInput} -
-
-
- ` - this.selectContractNames.addEventListener('change', this.setInputParamsPlaceHolder.bind(this)) - this.setInputParamsPlaceHolder() - if (!this.contractNamesContainer) { - this.contractNamesContainer = contractNamesContainer - } - return contractNamesContainer - } - - atAddressChanged (event) { - if (!this.atAddressButtonInput.value) { - this.enableAtAddress(false) - } else { - if ((this.selectContractNames && !this.selectContractNames.getAttribute('disabled') && this.loadType === 'sol') || - this.loadType === 'abi') { - this.enableAtAddress(true) - } else { - this.enableAtAddress(false) - } - } - } - - changeCurrentFile (currentFile) { - if (!this.selectContractNames) return - if (/.(.abi)$/.exec(currentFile)) { - this.createPanel.style.display = 'none' - this.orLabel.style.display = 'none' - this.compFails.style.display = 'none' - this.loadType = 'abi' - this.contractNamesContainer.style.display = 'block' - this.abiLabel.style.display = 'block' - this.abiLabel.innerHTML = currentFile - this.selectContractNames.style.display = 'none' - this.enableContractNames(true) - this.enableAtAddress(true) - } else if (/.(.sol)$/.exec(currentFile) || - /.(.vy)$/.exec(currentFile) || // vyper - /.(.lex)$/.exec(currentFile) || // lexon - /.(.contract)$/.exec(currentFile)) { - this.createPanel.style.display = 'block' - this.orLabel.style.display = 'block' - this.contractNamesContainer.style.display = 'block' - this.loadType = 'sol' - this.selectContractNames.style.display = 'block' - this.abiLabel.style.display = 'none' - if (this.selectContractNames.value === '') this.enableAtAddress(false) - } else { - this.loadType = 'other' - this.createPanel.style.display = 'block' - this.orLabel.style.display = 'block' - this.contractNamesContainer.style.display = 'block' - this.selectContractNames.style.display = 'block' - this.abiLabel.style.display = 'none' - if (this.selectContractNames.value === '') this.enableAtAddress(false) - } - } - - setInputParamsPlaceHolder () { - this.createPanel.innerHTML = '' - if (this.selectContractNames.selectedIndex < 0 || this.selectContractNames.children.length <= 0) { - this.createPanel.innerHTML = 'No compiled contracts' - return - } - - const selectedContract = this.getSelectedContract() - const clickCallback = async (valArray, inputsValues) => { - var selectedContract = this.getSelectedContract() - this.createInstance(selectedContract, inputsValues) - } - const createConstructorInstance = new MultiParamManager( - 0, - selectedContract.getConstructorInterface(), - clickCallback, - selectedContract.getConstructorInputs(), - 'Deploy', - selectedContract.bytecodeObject, - true - ) - this.createPanel.appendChild(createConstructorInstance.render()) - this.createPanel.appendChild(this.deployCheckBox) - } - - getSelectedContract () { - var contract = this.selectContractNames.children[this.selectContractNames.selectedIndex] - var contractName = contract.getAttribute('value') - var compilerAtributeName = contract.getAttribute('compiler') - - return this.dropdownLogic.getSelectedContract(contractName, compilerAtributeName) - } - - async createInstance (selectedContract, args) { - if (selectedContract.bytecodeObject.length === 0) { - return modalDialogCustom.alert('This contract may be abstract, not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.') - } - - var continueCb = (error, continueTxExecution, cancelCb) => { - if (error) { - var msg = typeof error !== 'string' ? error.message : error - modalDialog('Gas estimation failed', yo`
Gas estimation errored with the following message (see below). - The transaction execution will likely fail. Do you want to force sending?
- ${msg} -
`, - { - label: 'Send Transaction', - fn: () => { - continueTxExecution() - } - }, { - label: 'Cancel Transaction', - fn: () => { - cancelCb() - } - }) - } else { - continueTxExecution() - } - } - - const self = this - - var promptCb = (okCb, cancelCb) => { - modalDialogCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) - } - - var statusCb = (msg) => { - return this.logCallback(msg) - } - - var finalCb = (error, contractObject, address) => { - self.event.trigger('clearInstance') - - if (error) { - return this.logCallback(error) - } - self.event.trigger('newContractInstanceAdded', [contractObject, address, contractObject.name]) - - const data = self.runView.compilersArtefacts.getCompilerAbstract(contractObject.contract.file) - self.runView.compilersArtefacts.addResolvedContract(helper.addressToString(address), data) - if (self.ipfsCheckedState) { - _paq.push(['trackEvent', 'udapp', 'DeployAndPublish', this.networkName + '_' + this.networkId]) - publishToStorage('ipfs', self.runView.fileProvider, self.runView.fileManager, selectedContract) - } else { - _paq.push(['trackEvent', 'udapp', 'DeployOnly', this.networkName + '_' + this.networkId]) - } - } - - let contractMetadata - try { - contractMetadata = await this.runView.call('compilerMetadata', 'deployMetadataOf', selectedContract.name, selectedContract.contract.file) - } catch (error) { - return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) - } - - const compilerContracts = this.dropdownLogic.getCompilerContracts() - const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog) - - if (selectedContract.isOverSizeLimit()) { - return modalDialog('Contract code size over limit', yo`
Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails.
- More info: eip-170 -
`, - { - label: 'Force Send', - fn: () => { - this.deployContract(selectedContract, args, contractMetadata, compilerContracts, { continueCb, promptCb, statusCb, finalCb }, confirmationCb) - } - }, { - label: 'Cancel', - fn: () => { - this.logCallback(`creation of ${selectedContract.name} canceled by user.`) - } - }) - } - this.deployContract(selectedContract, args, contractMetadata, compilerContracts, { continueCb, promptCb, statusCb, finalCb }, confirmationCb) - } - - deployContract (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) { - _paq.push(['trackEvent', 'udapp', 'DeployContractTo', this.networkName + '_' + this.networkId]) - const { statusCb } = callbacks - if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) { - return this.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) - } - if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`) - this.blockchain.deployContractWithLibrary(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) - } - - getConfirmationCb (modalDialog, confirmDialog) { - // this code is the same as in recorder.js. TODO need to be refactored out - const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { - if (network.name !== 'Main') { - return continueTxExecution(null) - } - const amount = this.blockchain.fromWei(tx.value, true, 'ether') - const content = confirmDialog(tx, network, amount, gasEstimation, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain)) - - modalDialog('Confirm transaction', content, - { - label: 'Confirm', - fn: () => { - this.blockchain.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked) - // TODO: check if this is check is still valid given the refactor - if (!content.gasPriceStatus) { - cancelCb('Given transaction fee is not correct') - } else { - continueTxExecution(content.txFee) - } - } - }, { - label: 'Cancel', - fn: () => { - return cancelCb('Transaction canceled by user.') - } - } - ) - } - - return confirmationCb - } - - loadFromAddress () { - this.event.trigger('clearInstance') - - let address = this.atAddressButtonInput.value - if (!ethJSUtil.isValidChecksumAddress(address)) { - addTooltip(yo` - - It seems you are not using a checksumed address. -
A checksummed address is an address that contains uppercase letters, as specified in EIP-55. -
Checksummed addresses are meant to help prevent users from sending transactions to the wrong address. -
`) - address = ethJSUtil.toChecksumAddress(address) - } - this.dropdownLogic.loadContractFromAddress(address, - (cb) => { - modalDialogCustom.confirm('At Address', `Do you really want to interact with ${address} using the current ABI definition?`, cb) - }, - (error, loadType, abi) => { - if (error) { - return modalDialogCustom.alert(error) - } - if (loadType === 'abi') { - return this.event.trigger('newContractABIAdded', [abi, address]) - } - var selectedContract = this.getSelectedContract() - this.event.trigger('newContractInstanceAdded', [selectedContract.object, address, this.selectContractNames.value]) - } - ) - } -} - -module.exports = ContractDropdownUI diff --git a/apps/remix-ide/src/app/tabs/runTab/model/dropdownlogic.js b/apps/remix-ide/src/app/tabs/runTab/model/dropdownlogic.js deleted file mode 100644 index 386f654775..0000000000 --- a/apps/remix-ide/src/app/tabs/runTab/model/dropdownlogic.js +++ /dev/null @@ -1,108 +0,0 @@ -import { CompilerAbstract } from '@remix-project/remix-solidity' -const remixLib = require('@remix-project/remix-lib') -const txHelper = remixLib.execution.txHelper -const EventManager = remixLib.EventManager -const _paq = window._paq = window._paq || [] - -class DropdownLogic { - constructor (compilersArtefacts, config, editor, runView) { - this.compilersArtefacts = compilersArtefacts - this.config = config - this.editor = editor - this.runView = runView - - this.event = new EventManager() - - this.listenToCompilationEvents() - } - - // TODO: can be moved up; the event in contractDropdown will have to refactored a method instead - listenToCompilationEvents () { - const broadcastCompilationResult = (file, source, languageVersion, data) => { - // TODO check whether the tab is configured - const compiler = new CompilerAbstract(languageVersion, data, source) - this.compilersArtefacts[languageVersion] = compiler - this.compilersArtefacts.__last = compiler - this.event.trigger('newlyCompiled', [true, data, source, compiler, languageVersion, file]) - } - this.runView.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => - broadcastCompilationResult(file, source, languageVersion, data) - ) - this.runView.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => - broadcastCompilationResult(file, source, languageVersion, data) - ) - this.runView.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => - broadcastCompilationResult(file, source, languageVersion, data) - ) - this.runView.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => - broadcastCompilationResult(file, source, languageVersion, data) - ) - this.runView.on('optimism-compiler', 'compilationFinished', (file, source, languageVersion, data) => - broadcastCompilationResult(file, source, languageVersion, data) - ) - } - - loadContractFromAddress (address, confirmCb, cb) { - if (/.(.abi)$/.exec(this.config.get('currentFile'))) { - confirmCb(() => { - var abi - try { - abi = JSON.parse(this.editor.currentContent()) - } catch (e) { - return cb('Failed to parse the current file as JSON ABI.') - } - _paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithABI']) - cb(null, 'abi', abi) - }) - } else { - _paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithArtifacts']) - cb(null, 'instance') - } - } - - getCompiledContracts (compiler, compilerFullName) { - var contracts = [] - compiler.visitContracts((contract) => { - contracts.push(contract) - }) - return contracts - } - - getSelectedContract (contractName, compilerAtributeName) { - if (!contractName) return null - - var compiler = this.compilersArtefacts[compilerAtributeName] - if (!compiler) return null - - var contract = compiler.getContract(contractName) - - return { - name: contractName, - contract: contract, - compiler: compiler, - abi: contract.object.abi, - bytecodeObject: contract.object.evm.bytecode.object, - bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences, - object: contract.object, - deployedBytecode: contract.object.evm.deployedBytecode, - getConstructorInterface: () => { - return txHelper.getConstructorInterface(contract.object.abi) - }, - getConstructorInputs: () => { - var constructorInteface = txHelper.getConstructorInterface(contract.object.abi) - return txHelper.inputParametersDeclarationToString(constructorInteface.inputs) - }, - isOverSizeLimit: () => { - var deployedBytecode = contract.object.evm.deployedBytecode - return (deployedBytecode && deployedBytecode.object.length / 2 > 24576) - }, - metadata: contract.object.metadata - } - } - - getCompilerContracts () { - return this.compilersArtefacts.__last.getData().contracts - } -} - -module.exports = DropdownLogic diff --git a/apps/remix-ide/src/app/tabs/runTab/recorder.js b/apps/remix-ide/src/app/tabs/runTab/recorder.js deleted file mode 100644 index 52d5367464..0000000000 --- a/apps/remix-ide/src/app/tabs/runTab/recorder.js +++ /dev/null @@ -1,160 +0,0 @@ -import { Plugin } from '@remixproject/engine' - -import * as packageJson from '../../../../../../package.json' -var yo = require('yo-yo') -var remixLib = require('@remix-project/remix-lib') -var EventManager = remixLib.EventManager -var csjs = require('csjs-inject') -var css = require('../styles/run-tab-styles') - -var modalDialogCustom = require('../../ui/modal-dialog-custom') -var modalDialog = require('../../ui/modaldialog') -var confirmDialog = require('../../ui/confirmDialog') - -var helper = require('../../../lib/helper.js') - -const profile = { - name: 'recorder', - methods: ['runScenario'], - version: packageJson.version -} - -class RecorderUI extends Plugin { - constructor (blockchain, fileManager, recorder, logCallBack, config) { - super(profile) - this.fileManager = fileManager - this.blockchain = blockchain - this.recorder = recorder - this.logCallBack = logCallBack - this.config = config - this.event = new EventManager() - } - - render () { - var css2 = csjs` - .container {} - .runTxs {} - .recorder {} - ` - - this.runButton = yo`` - this.recordButton = yo` - ` - - this.runButton.onclick = () => { - const file = this.config.get('currentFile') - if (!file) return modalDialogCustom.alert('A scenario file has to be selected') - this.runScenario(file) - } - } - - runScenario (file) { - if (!file) return modalDialogCustom.alert('Unable to run scenerio, no specified scenario file') - var continueCb = (error, continueTxExecution, cancelCb) => { - if (error) { - var msg = typeof error !== 'string' ? error.message : error - modalDialog('Gas estimation failed', yo`
Gas estimation errored with the following message (see below). - The transaction execution will likely fail. Do you want to force sending?
- ${msg} -
`, - { - label: 'Send Transaction', - fn: () => { - continueTxExecution() - } - }, { - label: 'Cancel Transaction', - fn: () => { - cancelCb() - } - }) - } else { - continueTxExecution() - } - } - - var promptCb = (okCb, cancelCb) => { - modalDialogCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) - } - - var alertCb = (msg) => { - modalDialogCustom.alert(msg) - } - - const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog) - - this.fileManager.readFile(file).then((json) => { - // TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily - this.recorder.runScenario(json, continueCb, promptCb, alertCb, confirmationCb, this.logCallBack, (error, abi, address, contractName) => { - if (error) { - return modalDialogCustom.alert(error) - } - - this.event.trigger('newScenario', [abi, address, contractName]) - }) - }).catch((error) => modalDialogCustom.alert(error)) - } - - getConfirmationCb (modalDialog, confirmDialog) { - // this code is the same as in contractDropdown.js. TODO need to be refactored out - const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { - if (network.name !== 'Main') { - return continueTxExecution(null) - } - const amount = this.blockchain.fromWei(tx.value, true, 'ether') - const content = confirmDialog(tx, network, amount, gasEstimation, this.blockchain.determineGasFees(tx), this.blockchain.determineGasPrice.bind(this.blockchain)) - - modalDialog('Confirm transaction', content, - { - label: 'Confirm', - fn: () => { - this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked) - // TODO: check if this is check is still valid given the refactor - if (!content.gasPriceStatus) { - cancelCb('Given transaction fee is not correct') - } else { - continueTxExecution(content.txFee) - } - } - }, { - label: 'Cancel', - fn: () => { - return cancelCb('Transaction canceled by user.') - } - } - ) - } - - return confirmationCb - } - - triggerRecordButton () { - this.saveScenario( - (path, cb) => { - modalDialogCustom.prompt('Save transactions as scenario', 'Transactions will be saved in a file under ' + path, 'scenario.json', cb) - }, - (error) => { - if (error) return modalDialogCustom.alert(error) - } - ) - } - - saveScenario (promptCb, cb) { - var txJSON = JSON.stringify(this.recorder.getAll(), null, 2) - var path = this.fileManager.currentPath() - promptCb(path, input => { - var fileProvider = this.fileManager.fileProviderOf(path) - if (!fileProvider) return - var newFile = path + '/' + input - helper.createNonClashingName(newFile, fileProvider, (error, newFile) => { - if (error) return cb('Failed to create file. ' + newFile + ' ' + error) - if (!fileProvider.set(newFile, txJSON)) return cb('Failed to create file ' + newFile) - this.fileManager.open(newFile) - }) - }) - } -} - -module.exports = RecorderUI diff --git a/apps/remix-ide/src/app/tabs/runTab/settings.js b/apps/remix-ide/src/app/tabs/runTab/settings.js deleted file mode 100644 index cc5a15409d..0000000000 --- a/apps/remix-ide/src/app/tabs/runTab/settings.js +++ /dev/null @@ -1,449 +0,0 @@ -import { BN } from 'ethereumjs-util' -import Registry from '../../state/registry' -const $ = require('jquery') -const yo = require('yo-yo') -const remixLib = require('@remix-project/remix-lib') -const EventManager = remixLib.EventManager -const css = require('../styles/run-tab-styles') -const copyToClipboard = require('../../ui/copy-to-clipboard') -const modalDialogCustom = require('../../ui/modal-dialog-custom') -const addTooltip = require('../../ui/tooltip') -const helper = require('../../../lib/helper.js') - -class SettingsUI { - constructor (blockchain, networkModule) { - this.blockchain = blockchain - this.event = new EventManager() - this._components = {} - - this.blockchain.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => { - if (!lookupOnly) this.el.querySelector('#value').value = 0 - if (error) return - this.updateAccountBalances() - }) - this._components = { - registry: Registry.getInstance(), - networkModule: networkModule - } - this._components.registry = Registry.getInstance() - this._deps = { - config: this._components.registry.get('config').api - } - - this._deps.config.events.on('settings/personal-mode_changed', this.onPersonalChange.bind(this)) - - setInterval(() => { - this.updateAccountBalances() - }, 1000) - - this.accountListCallId = 0 - this.loadedAccounts = {} - } - - updateAccountBalances () { - if (!this.el) return - var accounts = $(this.el.querySelector('#txorigin')).children('option') - accounts.each((index, account) => { - this.blockchain.getBalanceInEther(account.value, (err, balance) => { - if (err) return - const updated = helper.shortenAddress(account.value, balance) - if (updated !== account.innerText) { // check if the balance has been updated and update UI accordingly. - account.innerText = updated - } - }) - }) - } - - validateInputKey (e) { - // preventing not numeric keys - // preventing 000 case - if (!helper.isNumeric(e.key) || - (e.key === '0' && !parseInt(this.el.querySelector('#value').value) && this.el.querySelector('#value').value.length > 0)) { - e.preventDefault() - e.stopImmediatePropagation() - } - } - - validateValue () { - const valueEl = this.el.querySelector('#value') - if (!valueEl.value) { - // assign 0 if given value is - // - empty - valueEl.value = 0 - return - } - - let v - try { - v = new BN(valueEl.value, 10) - valueEl.value = v.toString(10) - } catch (e) { - // assign 0 if given value is - // - not valid (for ex 4345-54) - // - contains only '0's (for ex 0000) copy past or edit - valueEl.value = 0 - } - - // if giveen value is negative(possible with copy-pasting) set to 0 - if (v.lt(0)) valueEl.value = 0 - } - - render () { - this.netUI = yo`` - - var environmentEl = yo` -
- -
- - -
-
- ` - const networkEl = yo` -
-
-
-
- ${this.netUI} -
-
- ` - const accountEl = yo` -
- -
- -
${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)}
- -
-
- ` - - const gasPriceEl = yo` -
- - -
- ` - - const valueEl = yo` -
- -
- this.validateInputKey(e)} - onchange=${() => this.validateValue()} - > - -
-
- ` - - const el = yo` -
- ${environmentEl} - ${networkEl} - ${accountEl} - ${gasPriceEl} - ${valueEl} -
- ` - - var selectExEnv = environmentEl.querySelector('#selectExEnvOptions') - this.setDropdown(selectExEnv) - - this.blockchain.event.register('contextChanged', (context, silent) => { - this.setFinalContext() - }) - - this.blockchain.event.register('networkStatus', ({ error, network }) => { - if (error) { - this.netUI.innerHTML = 'can\'t detect network ' - return - } - const networkProvider = this._components.networkModule.getNetworkProvider.bind(this._components.networkModule) - this.netUI.innerHTML = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : '' - }) - - setInterval(() => { - this.fillAccountsList() - }, 1000) - - this.el = el - - this.fillAccountsList() - return el - } - - setDropdown (selectExEnv) { - this.selectExEnv = selectExEnv - - const addProvider = (network) => { - selectExEnv.appendChild(yo``) - addTooltip(yo`${network.name} provider added`) - } - - const removeProvider = (name) => { - var env = selectExEnv.querySelector(`option[value="${name}"]`) - if (env) { - selectExEnv.removeChild(env) - addTooltip(yo`${name} provider removed`) - } - } - this.blockchain.event.register('addProvider', provider => addProvider(provider)) - this.blockchain.event.register('removeProvider', name => removeProvider(name)) - - selectExEnv.addEventListener('change', (event) => { - const provider = selectExEnv.options[selectExEnv.selectedIndex] - const fork = provider.getAttribute('fork') // can be undefined if connected to an external source (web3 provider / injected) - let context = provider.value - context = context.startsWith('vm') ? 'vm' : context // context has to be 'vm', 'web3' or 'injected' - this.setExecutionContext({ context, fork }) - }) - - selectExEnv.value = this._getProviderDropdownValue() - } - - setExecutionContext (context) { - this.blockchain.changeExecutionContext(context, () => { - modalDialogCustom.prompt('External node request', this.web3ProviderDialogBody(), 'http://127.0.0.1:8545', (target) => { - this.blockchain.setProviderFromEndpoint(target, context, (alertMsg) => { - if (alertMsg) addTooltip(alertMsg) - this.setFinalContext() - }) - }, this.setFinalContext.bind(this)) - }, (alertMsg) => { - addTooltip(alertMsg) - }, this.setFinalContext.bind(this)) - } - - web3ProviderDialogBody () { - const thePath = '' - - return yo` -
- Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see Geth Docs on rpc server) -
geth --http --http.corsdomain https://remix.ethereum.org
-
- To run Remix & a local Geth test node, use this command: (see Geth Docs on Dev mode) -
geth --http --http.corsdomain="${window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir ${thePath} --dev console
-
-
- WARNING: It is not safe to use the --http.corsdomain flag with a wildcard: --http.corsdomain * -
-
For more info: Remix Docs on Web3 Provider -
-
- Web3 Provider Endpoint -
- ` - } - - /** - * generate a value used by the env dropdown list. - * @return {String} - can return 'vm-berlin, 'vm-london', 'injected' or 'web3' - */ - _getProviderDropdownValue () { - const provider = this.blockchain.getProvider() - const fork = this.blockchain.getCurrentFork() - return provider === 'vm' ? provider + '-' + fork : provider - } - - setFinalContext () { - // set the final context. Cause it is possible that this is not the one we've originaly selected - this.selectExEnv.value = this._getProviderDropdownValue() - this.event.trigger('clearInstance', []) - this.updatePlusButton() - } - - updatePlusButton () { - // enable/disable + button - const plusBtn = document.getElementById('remixRunPlus') - const plusTitle = document.getElementById('remixRunPlusWraper') - switch (this.selectExEnv.value) { - case 'injected': - plusBtn.classList.add(css.disableMouseEvents) - plusTitle.title = "Unfortunately it's not possible to create an account using injected web3. Please create the account directly from your provider (i.e metamask or other of the same type)." - - break - case 'vm': - plusBtn.classList.remove(css.disableMouseEvents) - plusTitle.title = 'Create a new account' - - break - - case 'web3': - this.onPersonalChange() - - break - default: { - plusBtn.classList.add(css.disableMouseEvents) - plusTitle.title = `Unfortunately it's not possible to create an account using an external wallet (${this.selectExEnv.value}).` - } - } - } - - onPersonalChange () { - const plusBtn = document.getElementById('remixRunPlus') - const plusTitle = document.getElementById('remixRunPlusWraper') - if (!this._deps.config.get('settings/personal-mode')) { - plusBtn.classList.add(css.disableMouseEvents) - plusTitle.title = 'Creating an account is possible only in Personal mode. Please go to Settings to enable it.' - } else { - plusBtn.classList.remove(css.disableMouseEvents) - plusTitle.title = 'Create a new account' - } - } - - newAccount () { - this.blockchain.newAccount( - '', - (cb) => { - modalDialogCustom.promptPassphraseCreation((error, passphrase) => { - if (error) { - return modalDialogCustom.alert(error) - } - cb(passphrase) - }, () => {}) - }, - (error, address) => { - if (error) { - return addTooltip('Cannot create an account: ' + error) - } - addTooltip(`account ${address} created`) - } - ) - } - - getSelectedAccount () { - return this.el.querySelector('#txorigin').selectedOptions[0].value - } - - getEnvironment () { - return this.blockchain.getProvider() - } - - signMessage () { - this.blockchain.getAccounts((err, accounts) => { - if (err) { - return addTooltip(`Cannot get account list: ${err}`) - } - - var signMessageDialog = { title: 'Sign a message', text: 'Enter a message to sign', inputvalue: 'Message to sign' } - var $txOrigin = this.el.querySelector('#txorigin') - if (!$txOrigin.selectedOptions[0] && (this.blockchain.isInjectedWeb3() || this.blockchain.isWeb3Provider())) { - return addTooltip('Account list is empty, please make sure the current provider is properly connected to remix') - } - - var account = $txOrigin.selectedOptions[0].value - - var promptCb = (passphrase) => { - const modal = modalDialogCustom.promptMulti(signMessageDialog, (message) => { - this.blockchain.signMessage(message, account, passphrase, (err, msgHash, signedData) => { - if (err) { - return addTooltip(err) - } - modal.hide() - modalDialogCustom.alert(yo` -
- hash:
- ${msgHash} -
signature:
- ${signedData} -
- `) - }) - }, false) - } - - if (this.blockchain.isWeb3Provider()) { - return modalDialogCustom.promptPassphrase( - 'Passphrase to sign a message', - 'Enter your passphrase for this account to sign the message', - '', - promptCb, - false - ) - } - promptCb() - }) - } - - // TODO: unclear what's the goal of accountListCallId, feels like it can be simplified - async fillAccountsList () { - this.accountListCallId++ - const callid = this.accountListCallId - const txOrigin = this.el.querySelector('#txorigin') - let accounts = [] - try { - accounts = await this.blockchain.getAccounts() - } catch (e) { - addTooltip(`Cannot get account list: ${e}`) - } - if (!accounts) accounts = [] - if (this.accountListCallId > callid) return - this.accountListCallId++ - for (const loadedaddress in this.loadedAccounts) { - if (accounts.indexOf(loadedaddress) === -1) { - txOrigin.removeChild(txOrigin.querySelector('option[value="' + loadedaddress + '"]')) - delete this.loadedAccounts[loadedaddress] - } - } - for (const i in accounts) { - const address = accounts[i] - if (!this.loadedAccounts[address]) { - txOrigin.appendChild(yo``) - this.loadedAccounts[address] = 1 - } - } - txOrigin.setAttribute('value', accounts[0]) - } -} - -module.exports = SettingsUI diff --git a/apps/remix-ide/src/app/tabs/styles/run-tab-styles.js b/apps/remix-ide/src/app/tabs/styles/run-tab-styles.js deleted file mode 100644 index fd193f0444..0000000000 --- a/apps/remix-ide/src/app/tabs/styles/run-tab-styles.js +++ /dev/null @@ -1,225 +0,0 @@ -var csjs = require('csjs-inject') - -var css = csjs` - .runTabView { - display: flex; - flex-direction: column; - } - .runTabView::-webkit-scrollbar { - display: none; - } - .settings { - padding: 0 24px 16px; - } - .crow { - display: block; - margin-top: 8px; - } - .col1 { - width: 30%; - float: left; - align-self: center; - } - .settingsLabel { - font-size: 11px; - margin-bottom: 4px; - text-transform: uppercase; - } - .environment { - display: flex; - align-items: center; - position: relative; - width: 100%; - } - .environment a { - margin-left: 7px; - } - .account { - display: flex; - align-items: center; - } - .account i { - margin-left: 12px; - } - .col2 { - border-radius: 3px; - } - .col2_1 { - width: 164px; - min-width: 164px; - } - .col2_2 { - } - .select { - font-weight: normal; - width: 100%; - overflow: hidden; - } - .instanceContainer { - display: flex; - flex-direction: column; - margin-bottom: 2%; - border: none; - text-align: center; - padding: 0 14px 16px; - } - .pendingTxsContainer { - display: flex; - flex-direction: column; - margin-top: 2%; - border: none; - text-align: center; - } - .container { - padding: 0 24px 16px; - } - .recorderDescription { - margin: 0 15px 15px 0; - } - .contractNames { - width: 100%; - border: 1px solid - } - .subcontainer { - display: flex; - flex-direction: row; - align-items: center; - margin-bottom: 8px; - } - .subcontainer i { - width: 16px; - display: flex; - justify-content: center; - margin-left: 1px; - } - .button button{ - flex: none; - } - .button { - display: flex; - align-items: center; - margin-top: 13px; - } - .transaction { - } - .atAddress { - margin: 0; - min-width: 100px; - width: 100px; - height: 100%; - word-break: inherit; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-right: 0; - } - .atAddressSect { - margin-top: 8px; - height: 32px; - } - .atAddressSect input { - height: 32px; - border-top-left-radius: 0 !important; - border-bottom-left-radius: 0 !important; - } - .ataddressinput { - padding: .25rem; - } - .create { - } - .input { - font-size: 10px !important; - } - .noInstancesText { - font-style: italic; - text-align: left; - padding-left: 15px; - } - .pendingTxsText { - font-style: italic; - display: flex; - justify-content: space-evenly; - align-items: center; - flex-wrap: wrap; - } - .item { - margin-right: 1em; - display: flex; - align-items: center; - } - .pendingContainer { - display: flex; - align-items: baseline; - } - .pending { - height: 25px; - text-align: center; - padding-left: 10px; - border-radius: 3px; - margin-left: 5px; - } - .disableMouseEvents { - pointer-events: none; - } - .icon { - cursor: pointer; - font-size: 12px; - cursor: pointer; - margin-left: 5px; - } - .icon:hover { - font-size: 12px; - color: var(--warning); - } - .errorIcon { - color: var(--warning); - margin-left: 15px; - } - .failDesc { - color: var(--warning); - padding-left: 10px; - display: inline; - } - .network { - margin-left: 8px; - pointer-events: none; - } - .networkItem { - margin-right: 5px; - } - .transactionActions { - display: flex; - justify-content: space-evenly; - width: 145px; - } - .orLabel { - text-align: center; - text-transform: uppercase; - } - .infoDeployAction { - margin-left: 1px; - font-size: 13px; - color: var(--info); - } - .gasValueContainer { - flex-direction: row; - display: flex; - } - .gasNval { - width: 55%; - font-size: 0.8rem; - } - .gasNvalUnit { - width: 41%; - margin-left: 10px; - font-size: 0.8rem; - } - .deployDropdown { - text-align: center; - text-transform: uppercase; - } - .checkboxAlign { - padding-top: 2px; - } -` - -module.exports = css diff --git a/apps/remix-ide/src/app/tabs/test-tab.js b/apps/remix-ide/src/app/tabs/test-tab.js index 82861a66dc..40dd78b326 100644 --- a/apps/remix-ide/src/app/tabs/test-tab.js +++ b/apps/remix-ide/src/app/tabs/test-tab.js @@ -8,8 +8,6 @@ import { ViewPlugin } from '@remixproject/engine-web' import helper from '../../lib/helper' import { canUseWorker, urlFromVersion } from '@remix-project/remix-solidity' -// var tooltip = require('../ui/tooltip') -var Renderer = require('../ui/renderer') var { UnitTestRunner, assertLibCode } = require('@remix-project/remix-tests') const profile = { @@ -31,7 +29,6 @@ module.exports = class TestTab extends ViewPlugin { this.fileManager = fileManager this.filePanel = filePanel this.appManager = appManager - this.renderer = new Renderer(this) this.testRunner = new UnitTestRunner() this.testTabLogic = new TestTabLogic(this.fileManager, helper) this.offsetToLineColumnConverter = offsetToLineColumnConverter diff --git a/apps/remix-ide/src/app/udapp/make-udapp.js b/apps/remix-ide/src/app/udapp/make-udapp.js index cab11136f2..0b8b13433a 100644 --- a/apps/remix-ide/src/app/udapp/make-udapp.js +++ b/apps/remix-ide/src/app/udapp/make-udapp.js @@ -1,31 +1,9 @@ import Registry from '../state/registry' var remixLib = require('@remix-project/remix-lib') -var yo = require('yo-yo') var EventsDecoder = remixLib.execution.EventsDecoder -const transactionDetailsLinks = { - Main: 'https://www.etherscan.io/tx/', - Rinkeby: 'https://rinkeby.etherscan.io/tx/', - Ropsten: 'https://ropsten.etherscan.io/tx/', - Kovan: 'https://kovan.etherscan.io/tx/', - Goerli: 'https://goerli.etherscan.io/tx/' -} - -function txDetailsLink (network, hash) { - if (transactionDetailsLinks[network]) { - return transactionDetailsLinks[network] + hash - } -} - export function makeUdapp (blockchain, compilersArtefacts, logHtmlCallback) { - // ----------------- UniversalDApp ----------------- - // TODO: to remove when possible - blockchain.event.register('transactionBroadcasted', (txhash, networkName) => { - var txLink = txDetailsLink(networkName, txhash) - if (txLink && logHtmlCallback) logHtmlCallback(yo`${txLink}`) - }) - // ----------------- Tx listener ----------------- const _transactionReceipts = {} const transactionReceiptResolver = (tx, cb) => { diff --git a/apps/remix-ide/src/app/udapp/run-tab.js b/apps/remix-ide/src/app/udapp/run-tab.js index b966287690..5fe56053fa 100644 --- a/apps/remix-ide/src/app/udapp/run-tab.js +++ b/apps/remix-ide/src/app/udapp/run-tab.js @@ -1,24 +1,13 @@ +import React from 'react' // eslint-disable-line +import ReactDOM from 'react-dom' +import { RunTabUI } from '@remix-ui/run-tab' import { ViewPlugin } from '@remixproject/engine-web' import * as packageJson from '../../../../../package.json' -const $ = require('jquery') -const yo = require('yo-yo') -const ethJSUtil = require('ethereumjs-util') -const Web3 = require('web3') const EventManager = require('../../lib/events') -const Card = require('../ui/card') - -const css = require('../tabs/styles/run-tab-styles') -const SettingsUI = require('../tabs/runTab/settings.js') const Recorder = require('../tabs/runTab/model/recorder.js') -const RecorderUI = require('../tabs/runTab/recorder.js') -const DropdownLogic = require('../tabs/runTab/model/dropdownlogic.js') -const ContractDropdownUI = require('../tabs/runTab/contractDropdown.js') -const toaster = require('../ui/tooltip') const _paq = window._paq = window._paq || [] -const UniversalDAppUI = require('../ui/universal-dapp-ui') - const profile = { name: 'udapp', displayName: 'Deploy & run transactions', @@ -34,19 +23,25 @@ const profile = { } export class RunTab extends ViewPlugin { - constructor (blockchain, config, fileManager, editor, filePanel, compilersArtefacts, networkModule, mainView, fileProvider) { + constructor (blockchain, config, fileManager, editor, filePanel, compilersArtefacts, networkModule, fileProvider) { super(profile) this.event = new EventManager() this.config = config this.blockchain = blockchain this.fileManager = fileManager this.editor = editor - this.logCallback = (msg) => { mainView.getTerminal().logHtml(yo`
${msg}
`) } this.filePanel = filePanel this.compilersArtefacts = compilersArtefacts this.networkModule = networkModule this.fileProvider = fileProvider + this.recorder = new Recorder(blockchain) + this.REACT_API = {} this.setupEvents() + this.el = document.createElement('div') + } + + onActivation () { + this.renderComponent() } setupEvents () { @@ -57,33 +52,19 @@ export class RunTab extends ViewPlugin { getSettings () { return new Promise((resolve, reject) => { - if (!this.container) reject(new Error('UI not ready')) - else { - resolve({ - selectedAccount: this.settingsUI.getSelectedAccount(), - selectedEnvMode: this.blockchain.getProvider(), - networkEnvironment: this.container.querySelector('*[data-id="settingsNetworkEnv"]').textContent - } - ) - } + resolve({ + selectedAccount: this.REACT_API.accounts.selectedAccount, + selectedEnvMode: this.REACT_API.selectExEnv, + networkEnvironment: this.REACT_API.networkName + }) }) } async setEnvironmentMode (env) { const canCall = await this.askUserPermission('setEnvironmentMode', 'change the environment used') if (canCall) { - toaster(yo` -
- - - ${this.currentRequest.from} - - is changing your environment to - ${env} - -
- `, '', { time: 3000 }) - this.settingsUI.setExecutionContext(env) + env = typeof env === 'string' ? { context: env } : env + this.emit('setEnvironmentModeReducer', env, this.currentRequest.from) } } @@ -104,183 +85,25 @@ export class RunTab extends ViewPlugin { return this.blockchain.pendingTransactionsCount() } - renderContainer () { - this.container = yo`
` - - var el = yo` -
- ${this.settingsUI.render()} - ${this.contractDropdownUI.render()} - ${this.recorderCard.render()} - ${this.instanceContainer} -
- ` - this.container.appendChild(el) - return this.container - } - - renderInstanceContainer () { - this.instanceContainer = yo`
` - - const instanceContainerTitle = yo` -
- Deployed Contracts - this.event.trigger('clearInstance', [])} - title="Clear instances list and reset recorder" aria-hidden="true"> - -
` - - this.noInstancesText = yo` - - Currently you have no contract instances to interact with. - ` - - this.event.register('clearInstance', () => { - this.instanceContainer.innerHTML = '' // clear the instances list - this.instanceContainer.appendChild(instanceContainerTitle) - this.instanceContainer.appendChild(this.noInstancesText) - }) - - this.instanceContainer.appendChild(instanceContainerTitle) - this.instanceContainer.appendChild(this.noInstancesText) - } - - renderSettings () { - this.settingsUI = new SettingsUI(this.blockchain, this.networkModule) - - this.settingsUI.event.register('clearInstance', () => { - this.event.trigger('clearInstance', []) - }) + render () { + return this.el } - renderDropdown (udappUI, fileManager, compilersArtefacts, config, editor, logCallback) { - const dropdownLogic = new DropdownLogic(compilersArtefacts, config, editor, this) - this.contractDropdownUI = new ContractDropdownUI(this.blockchain, dropdownLogic, logCallback, this) - - fileManager.events.on('currentFileChanged', this.contractDropdownUI.changeCurrentFile.bind(this.contractDropdownUI)) - - this.contractDropdownUI.event.register('clearInstance', () => { - const noInstancesText = this.noInstancesText - if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) } - }) - this.contractDropdownUI.event.register('newContractABIAdded', (abi, address) => { - this.instanceContainer.appendChild(udappUI.renderInstanceFromABI(abi, address, '')) - }) - this.contractDropdownUI.event.register('newContractInstanceAdded', (contractObject, address, value) => { - this.instanceContainer.appendChild(udappUI.renderInstance(contractObject, address, value)) - }) + renderComponent () { + ReactDOM.render( + + , this.el) } - renderRecorder (udappUI, fileManager, config, logCallback) { - this.recorderCount = yo`0` - - const recorder = new Recorder(this.blockchain) - recorder.event.register('recorderCountChange', (count) => { - this.recorderCount.innerText = count - }) - this.event.register('clearInstance', recorder.clearAll.bind(recorder)) - - this.recorderInterface = new RecorderUI(this.blockchain, fileManager, recorder, logCallback, config) - - this.recorderInterface.event.register('newScenario', (abi, address, contractName) => { - var noInstancesText = this.noInstancesText - if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) } - this.instanceContainer.appendChild(udappUI.renderInstanceFromABI(abi, address, contractName)) - }) - - this.recorderInterface.render() + onReady (api) { + this.REACT_API = api } - renderRecorderCard () { - const collapsedView = yo` -
-
${this.recorderCount}
-
` - - const expandedView = yo` -
-
- All transactions (deployed contracts and function executions) in this environment can be saved and replayed in - another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3. -
-
- ${this.recorderInterface.recordButton} - ${this.recorderInterface.runButton} -
-
- ` - - this.recorderCard = new Card({}, {}, { title: 'Transactions recorded', collapsedView: collapsedView }) - this.recorderCard.event.register('expandCollapseCard', (arrow, body, status) => { - body.innerHTML = '' - status.innerHTML = '' - if (arrow === 'down') { - status.appendChild(collapsedView) - body.appendChild(expandedView) - } else if (arrow === 'up') { - status.appendChild(collapsedView) - } - }) + writeFile (fileName, content) { + return this.call('fileManager', 'writeFile', fileName, content) } - render () { - this.udappUI = new UniversalDAppUI(this.blockchain, this.logCallback) - this.blockchain.resetAndInit(this.config, { - getAddress: (cb) => { - cb(null, $('#txorigin').val()) - }, - getValue: (cb) => { - try { - const number = document.querySelector('#value').value - const select = document.getElementById('unit') - const index = select.selectedIndex - const selectedUnit = select.querySelectorAll('option')[index].dataset.unit - let unit = 'ether' // default - if (['ether', 'finney', 'gwei', 'wei'].indexOf(selectedUnit) >= 0) { - unit = selectedUnit - } - cb(null, Web3.utils.toWei(number, unit)) - } catch (e) { - cb(e) - } - }, - getGasLimit: (cb) => { - try { - cb(null, '0x' + new ethJSUtil.BN($('#gasLimit').val(), 10).toString(16)) - } catch (e) { - cb(e.message) - } - } - }) - this.renderInstanceContainer() - this.renderSettings() - this.renderDropdown(this.udappUI, this.fileManager, this.compilersArtefacts, this.config, this.editor, this.logCallback) - this.renderRecorder(this.udappUI, this.fileManager, this.config, this.logCallback) - this.renderRecorderCard() - - const addPluginProvider = (profile) => { - if (profile.kind === 'provider') { - ((profile, app) => { - const web3Provider = { - async sendAsync (payload, callback) { - try { - const result = await app.call(profile.name, 'sendAsync', payload) - callback(null, result) - } catch (e) { - callback(e) - } - } - } - app.blockchain.addProvider({ name: profile.displayName, provider: web3Provider }) - })(profile, this) - } - } - const removePluginProvider = (profile) => { - if (profile.kind === 'provider') this.blockchain.removeProvider(profile.displayName) - } - this.on('manager', 'pluginActivated', addPluginProvider.bind(this)) - this.on('manager', 'pluginDeactivated', removePluginProvider.bind(this)) - return this.renderContainer() + readFile (fileName) { + return this.call('fileManager', 'readFile', fileName) } } diff --git a/apps/remix-ide/src/app/ui/TreeView.js b/apps/remix-ide/src/app/ui/TreeView.js deleted file mode 100644 index a282cafbd6..0000000000 --- a/apps/remix-ide/src/app/ui/TreeView.js +++ /dev/null @@ -1,212 +0,0 @@ -'use strict' -var yo = require('yo-yo') -var csjs = require('csjs-inject') -var css = csjs` - .li_tv { - list-style-type: none; - -webkit-margin-before: 0px; - -webkit-margin-after: 0px; - -webkit-margin-start: 0px; - -webkit-margin-end: 0px; - -webkit-padding-start: 0px; - } - .ul_tv { - list-style-type: none; - -webkit-margin-before: 0px; - -webkit-margin-after: 0px; - -webkit-margin-start: 0px; - -webkit-margin-end: 0px; - -webkit-padding-start: 0px; - } - .caret_tv { - width: 10px; - flex-shrink: 0; - padding-right: 5px; - } - .label_item { - word-break: break-all; - } - .label_key { - min-width: max-content; - max-width: 80%; - word-break: break-word; - } - .label_value { - min-width: 10%; - } - .cursor_pointer { - cursor: pointer; - } -` - -var EventManager = require('../../lib/events') - -/** - * TreeView - * - extendable by specifying custom `extractData` and `formatSelf` function - * - trigger `nodeClick` and `leafClick` - */ -class TreeView { - constructor (opts) { - this.event = new EventManager() - this.extractData = opts.extractData || this.extractDataDefault - this.formatSelf = opts.formatSelf || this.formatSelfDefault - this.loadMore = opts.loadMore - this.view = null - this.expandPath = [] - } - - render (json, expand) { - var view = this.renderProperties(json, expand) - if (!this.view) { - this.view = view - } - return view - } - - update (json) { - if (this.view) { - yo.update(this.view, this.render(json)) - } - } - - renderObject (item, parent, key, expand, keyPath) { - var data = this.extractData(item, parent, key) - var children = (data.children || []).map((child, index) => { - return this.renderObject(child.value, data, child.key, expand, keyPath + '/' + child.key) - }) - return this.formatData(key, data, children, expand, keyPath) - } - - renderProperties (json, expand, key) { - key = key || '' - var children = Object.keys(json).map((innerkey) => { - return this.renderObject(json[innerkey], json, innerkey, expand, innerkey) - }) - return yo`
    ${children}
` - } - - formatData (key, data, children, expand, keyPath) { - var self = this - var li = yo`
  • ` - var caret = yo`
    ` - var label = yo` -
    - ${caret} - ${self.formatSelf(key, data, li)} -
    ` - const expanded = self.expandPath.includes(keyPath) - li.appendChild(label) - if (data.children) { - var list = yo`
      ${children}
    ` - list.style.display = expanded ? 'block' : 'none' - caret.className = list.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}` - caret.setAttribute('data-id', `treeViewToggle${keyPath}`) - label.onclick = function () { - self.expand(keyPath) - if (self.isExpanded(keyPath)) { - if (!self.expandPath.includes(keyPath)) self.expandPath.push(keyPath) - } else { - self.expandPath = self.expandPath.filter(path => !path.startsWith(keyPath)) - } - } - label.oncontextmenu = function (event) { - self.event.trigger('nodeRightClick', [keyPath, data, label, event]) - } - li.appendChild(list) - if (data.hasNext) { - list.appendChild(yo`
  • Load more
  • `) - } - } else { - caret.style.visibility = 'hidden' - label.oncontextmenu = function (event) { - self.event.trigger('leafRightClick', [keyPath, data, label, event]) - } - label.onclick = function (event) { - self.event.trigger('leafClick', [keyPath, data, label, event]) - } - } - return li - } - - isExpanded (path) { - var current = this.nodeAt(path) - if (current) { - return current.style.display !== 'none' - } - return false - } - - expand (path) { - var caret = this.caretAt(path) - var node = this.nodeAt(path) - if (node) { - node.style.display = node.style.display === 'none' ? 'block' : 'none' - caret.className = node.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}` - this.event.trigger('nodeClick', [path, node]) - } - } - - caretAt (path) { - var label = this.labelAt(path) - if (label) { - return label.querySelector('.caret') - } - } - - itemAt (path) { - return this.view.querySelector(`li[key="${path}"]`) - } - - labelAt (path) { - return this.view.querySelector(`div[key="${path}"]`) - } - - nodeAt (path) { - return this.view.querySelector(`ul[key="${path}"]`) - } - - updateNodeFromJSON (path, jsonTree, expand) { - var newTree = this.renderProperties(jsonTree, expand, path) - var current = this.nodeAt(path) - if (current && current.parentElement) { - current.parentElement.replaceChild(newTree, current) - } - } - - formatSelfDefault (key, data) { - return yo` -
    - - -
    - ` - } - - extractDataDefault (item, parent, key) { - var ret = {} - if (item instanceof Array) { - ret.children = item.map((item, index) => { - return { key: index, value: item } - }) - ret.self = 'Array' - ret.isNode = true - ret.isLeaf = false - } else if (item instanceof Object) { - ret.children = Object.keys(item).map((key) => { - return { key: key, value: item[key] } - }) - ret.self = 'Object' - ret.isNode = true - ret.isLeaf = false - } else { - ret.self = item - ret.children = null - ret.isNode = false - ret.isLeaf = true - } - return ret - } -} - -module.exports = TreeView diff --git a/apps/remix-ide/src/app/ui/auto-complete-popup.js b/apps/remix-ide/src/app/ui/auto-complete-popup.js deleted file mode 100644 index 36e4fa9697..0000000000 --- a/apps/remix-ide/src/app/ui/auto-complete-popup.js +++ /dev/null @@ -1,212 +0,0 @@ -var yo = require('yo-yo') -var remixLib = require('@remix-project/remix-lib') -var EventManager = remixLib.EventManager -var Commands = require('../../lib/commands') - -// -------------- styling ---------------------- -var css = require('./styles/auto-complete-popup-styles') - -/* USAGE: - - var autoCompletePopup = new AutoCompletePopup({ - options: [] - }) - autoCompletePopup.event.register('handleSelect', function (input) { }) - autoCompletePopup.event.register('updateList', function () { }) - -*/ - -class AutoCompletePopup { - constructor (opts = {}) { - var self = this - self.event = new EventManager() - self.isOpen = false - self.opts = opts - self.data = { - _options: [] - } - self._components = {} - self._view = null - self._startingElement = 0 - self._elementsToShow = 4 - self._selectedElement = 0 - this.extraCommands = [] - } - - render () { - var self = this - const autoComplete = yo` -
    -
    - ${self.data._options.map((item, index) => { - return yo` -
    -
    { self.handleSelect(event.srcElement.innerText) }}> - ${getKeyOf(item)} -
    -
    - ${getValueOf(item)} -
    -
    - ` - })} -
    -
    -
    Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(self.data._options.length / self._elementsToShow)}
    -
    -
    - ` - function setUpPopUp (autoComplete) { - handleOpenPopup(autoComplete) - handleListSize(autoComplete) - } - - function handleOpenPopup (autoComplete) { - autoComplete.style.display = self.data._options.length > 0 ? 'block' : 'none' - } - - function handleListSize (autoComplete) { - if (self.data._options.length >= self._startingElement) { - for (let i = self._startingElement; i < (self._elementsToShow + self._startingElement); i++) { - const el = autoComplete.querySelectorAll('.item')[i] - if (el) { - el.classList.remove(css.listHandlerHide) - el.classList.add(css.listHandlerShow) - } - } - } - } - setUpPopUp(autoComplete) - if (!this._view) this._view = autoComplete - return autoComplete - } - - handleSelect (text) { - this.removeAutoComplete() - this.event.trigger('handleSelect', [text]) - } - - moveUp () { - if (this._selectedElement === 0) return - this._selectedElement-- - this._startingElement = this._selectedElement > 0 ? this._selectedElement - 1 : 0 - this.event.trigger('updateList') - yo.update(this._view, this.render()) - } - - moveDown () { - if (this.data._options.length <= this._selectedElement + 1) return - this._selectedElement++ - this._startingElement = this._selectedElement - 1 - this.event.trigger('updateList') - yo.update(this._view, this.render()) - } - - handleAutoComplete (event, inputString) { - if (this.isOpen && (event.which === 27 || event.which === 8 || event.which === 46)) { - // backspace or any key that should remove the autocompletion - this.removeAutoComplete() - return true - } - if (this.isOpen && (event.which === 13 || event.which === 9)) { - // enter and tab (validate completion) - event.preventDefault() - if (this.data._options[this._selectedElement]) { - this.handleSelect(getKeyOf(this.data._options[this._selectedElement])) - } - this.removeAutoComplete() - return true - } - if (this.isOpen && event.which === 38) { - // move up - event.preventDefault() - this.isOpen = true - this.moveUp() - return true - } - if (this.isOpen && event.which === 40) { - // move down - event.preventDefault() - this.isOpen = true - this.moveDown() - return true - } - if (event.which === 13 || event.which === 9) { - // enter || tab and autocompletion is off, just returning false - return false - } - const textList = inputString.split(' ') - const autoCompleteInput = textList.length > 1 ? textList[textList.length - 1] : textList[0] - if (inputString.length >= 2) { - // more than 2 letters, start completion - this.data._options = [] - Commands.allPrograms.forEach(item => { - const program = getKeyOf(item) - if (program.substring(0, program.length - 1).includes(autoCompleteInput.trim())) { - this.data._options.push(item) - } else if (autoCompleteInput.trim().includes(program) || (program === autoCompleteInput.trim())) { - Commands.allCommands.forEach(item => { - const command = getKeyOf(item) - if (command.includes(autoCompleteInput.trim())) { - this.data._options.push(item) - } - }) - } - }) - this.extraCommands.forEach(item => { - const command = getKeyOf(item) - if (command.includes(autoCompleteInput.trim())) { - this.data._options.push(item) - } - }) - - if (this.data._options.length === 1 && event.which === 9) { - // if only one option and tab is pressed, we resolve it - event.preventDefault() - textList.pop() - textList.push(getKeyOf(this.data._options[0])) - this.handleSelect(`${textList}`.replace(/,/g, ' ')) - this.removeAutoComplete() - return - } - if (this.data._options.length) this.isOpen = true - yo.update(this._view, this.render()) - return true - } - return false - } - - removeAutoComplete () { - if (!this.isOpen) return - this._view.style.display = 'none' - this.isOpen = false - this.data._options = [] - this._startingElement = 0 - this._selectedElement = 0 - yo.update(this._view, this.render()) - } - - extendAutocompletion () { - // TODO: this is not using the appManager interface. Terminal should be put as module - this.opts.appManager.event.on('activate', async (profile) => { - if (!profile.methods) return - profile.methods.forEach((method) => { - const key = `remix.call('${profile.name}', '${method}')` - const keyValue = {} - keyValue[key] = `call ${profile.name} - ${method}` - if (this.extraCommands.includes(keyValue)) return - this.extraCommands.push(keyValue) - }) - }) - } -} - -function getKeyOf (item) { - return Object.keys(item)[0] -} - -function getValueOf (item) { - return Object.values(item)[0] -} - -module.exports = AutoCompletePopup diff --git a/apps/remix-ide/src/app/ui/card.js b/apps/remix-ide/src/app/ui/card.js deleted file mode 100644 index a689b27772..0000000000 --- a/apps/remix-ide/src/app/ui/card.js +++ /dev/null @@ -1,70 +0,0 @@ -var yo = require('yo-yo') -var csjs = require('csjs-inject') -var EventManager = require('../../lib/events') - -module.exports = class Card { - constructor (api, events, opts) { - const self = this - self._api = api - self._events = events - self._opts = opts - self._view = {} - self.event = new EventManager() - } - - render () { - const self = this - if (self._view.el) return self._view.el - - self._view.cardBody = yo`
    ` - self._view.arrow = yo`` - - self._view.expandCollapseButton = yo` -
    ${self._view.arrow}
    ` - - self._view.statusBar = yo`
    ${self._opts.collapsedView}
    ` - self._view.cardHeader = yo` -
    trigger(self._view.arrow)}> -
    -
    ${self._opts.title}
    - ${self._view.statusBar} -
    -
    ${self._view.expandCollapseButton}
    -
    ` - - function trigger (el) { - var body = self._view.cardBody - var status = self._view.statusBar - if (el.classList) { - el.classList.toggle('fa-angle-up') - var arrow = el.classList.toggle('fa-angle-down') ? 'up' : 'down' - self.event.trigger('expandCollapseCard', [arrow, body, status]) - } - } - - // HTML - self._view.el = yo` -
    - ${self._view.cardHeader} - ${self._view.cardBody} -
    ` - - return self._view.el - } -} - -const css = csjs` - .cardContainer { - padding : 0 24px 16px; - margin : 0; - background : none; - } - .arrow { - font-weight : bold; - cursor : pointer; - font-size : 14px; - } - .arrow:hover { - } - -` diff --git a/apps/remix-ide/src/app/ui/confirmDialog.js b/apps/remix-ide/src/app/ui/confirmDialog.js deleted file mode 100644 index 9bf44e6333..0000000000 --- a/apps/remix-ide/src/app/ui/confirmDialog.js +++ /dev/null @@ -1,142 +0,0 @@ -var yo = require('yo-yo') -var csjs = require('csjs-inject') -const copyToClipboard = require('./copy-to-clipboard') -const Web3 = require('web3') - -var css = csjs` - #confirmsetting { - z-index: 1; - } - .txInfoBox { - } - .wrapword { - white-space: pre-wrap; /* Since CSS 2.1 */ - white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ - white-space: -pre-wrap; /* Opera 4-6 */ - white-space: -o-pre-wrap; /* Opera 7 */ - word-wrap: break-word; /* Internet Explorer 5.5+ */ - } -` - -function confirmDialog (tx, network, amount, gasEstimation, newGasPriceCb, initialParamsCb) { - const onGasPriceChange = function () { - var gasPrice = el.querySelector('#gasprice').value - newGasPriceCb(gasPrice, (txFeeText, priceStatus) => { - el.querySelector('#txfee').innerHTML = txFeeText - el.gasPriceStatus = priceStatus - el.txFee = { gasPrice } - }) - } - - const onMaxFeeChange = function () { - var maxFee = el.querySelector('#maxfee').value - var confirmBtn = document.querySelector('#modal-footer-ok') - var maxPriorityFee = el.querySelector('#maxpriorityfee').value - if (parseInt(network.lastBlock.baseFeePerGas, 16) > Web3.utils.toWei(maxFee, 'Gwei')) { - el.querySelector('#txfee').innerHTML = 'Transaction is invalid. Max fee should not be less than Base fee' - el.gasPriceStatus = false - confirmBtn.hidden = true - return - } else { - el.gasPriceStatus = true - confirmBtn.hidden = false - } - - newGasPriceCb(maxFee, (txFeeText, priceStatus) => { - el.querySelector('#txfee').innerHTML = txFeeText - if (priceStatus) { - confirmBtn.hidden = false - } else { - confirmBtn.hidden = true - } - el.gasPriceStatus = priceStatus - el.txFee = { maxFee, maxPriorityFee, baseFeePerGas: network.lastBlock.baseFeePerGas } - }) - } - - const el = yo` -
    -
    You are about to create a transaction on ${network.name} Network. Confirm the details to send the info to your provider. -
    The provider for many users is MetaMask. The provider will ask you to sign the transaction before it is sent to ${network.name} Network. -
    -
    -
    - From: - ${tx.from} -
    -
    - To: - ${tx.to ? tx.to : '(Contract Creation)'} -
    -
    - Data: -
    ${tx.data && tx.data.length > 50 ? tx.data.substring(0, 49) + '...' : tx.data} ${copyToClipboard(() => { return tx.data })}
    -
    -
    - Amount: - ${amount} Ether -
    -
    - Gas estimation: - ${gasEstimation} -
    -
    - Gas limit: - ${tx.gas} -
    - ${ - network.lastBlock.baseFeePerGas ? yo` -
    -
    - Max Priority fee: - - Gwei -
    -
    -
    -
    - Max fee (Not less than base fee ${Web3.utils.fromWei(Web3.utils.toBN(parseInt(network.lastBlock.baseFeePerGas, 16)), 'Gwei')} Gwei): - - Gwei - -
    -
    ` - : yo`
    - Gas price: - - Gwei (visit ethgasstation.info for current gas price info.) -
    ` - } -
    - Max transaction fee: - -
    -
    -
    - - -
    -
    - ` - - initialParamsCb((txFeeText, gasPriceValue, gasPriceStatus) => { - if (txFeeText) { - el.querySelector('#txfee').innerHTML = txFeeText - } - if (el.querySelector('#gasprice') && gasPriceValue) { - el.querySelector('#gasprice').value = gasPriceValue - onGasPriceChange() - } - if (el.querySelector('#maxfee') && network && network.lastBlock && network.lastBlock.baseFeePerGas) { - el.querySelector('#maxfee').value = Web3.utils.fromWei(Web3.utils.toBN(parseInt(network.lastBlock.baseFeePerGas, 16)), 'Gwei') - onMaxFeeChange() - } - if (gasPriceStatus !== undefined) { - el.gasPriceStatus = gasPriceStatus - } - }) - - return el -} - -module.exports = confirmDialog diff --git a/apps/remix-ide/src/app/ui/contextMenu.js b/apps/remix-ide/src/app/ui/contextMenu.js deleted file mode 100644 index 0bc2dae605..0000000000 --- a/apps/remix-ide/src/app/ui/contextMenu.js +++ /dev/null @@ -1,83 +0,0 @@ -var yo = require('yo-yo') -// -------------- copyToClipboard ---------------------- -var csjs = require('csjs-inject') - -var css = csjs` - .container - { - display: none; - position: fixed; - border-radius: 2px; - z-index: 1000; - box-shadow: 0 0 4px var(--dark); - } - .liitem - { - padding: 2px; - padding-left: 6px; - cursor: pointer; - color: var(--text-dark); - background-color: var(--light); - } - .liitem:hover - { - background-color: var(--secondary); - } - #menuitems - { - list-style: none; - margin: 0px; - } -` - -module.exports = (event, items, linkItems) => { - event.preventDefault() - - function hide (event, force) { - if (container && container.parentElement && (force || (event.target !== container))) { - container.parentElement.removeChild(container) - } - window.removeEventListener('click', hide) - } - - const menu = Object.keys(items).map((item, index) => { - const current = yo`` - current.onclick = () => { hide(null, true); items[item]() } - return current - }) - - let menuForLinks = yo`` - if (linkItems) { - menuForLinks = Object.keys(linkItems).map((item, index) => { - const current = yo`` - current.onclick = () => { hide(null, true) } - return current - }) - } - - const container = yo` - - ` - - container.style.left = event.pageX + 'px' - container.style.top = event.pageY + 'px' - container.style.display = 'block' - document.querySelector('body').appendChild(container) - - const menuItemsContainer = document.getElementById('menuItemsContainer') - const boundary = menuItemsContainer.getBoundingClientRect() - - if (boundary.bottom > (window.innerHeight || document.documentElement.clientHeight)) { - menuItemsContainer.style.position = 'absolute' - menuItemsContainer.style.bottom = '10px' - menuItemsContainer.style.top = null - } - - setTimeout(() => { - window.addEventListener('click', hide) - }, 500) - - return { hide } -} diff --git a/apps/remix-ide/src/app/ui/copy-to-clipboard.js b/apps/remix-ide/src/app/ui/copy-to-clipboard.js deleted file mode 100644 index 26c96b0d2f..0000000000 --- a/apps/remix-ide/src/app/ui/copy-to-clipboard.js +++ /dev/null @@ -1,39 +0,0 @@ -var yo = require('yo-yo') -// -------------- copyToClipboard ---------------------- -const copy = require('copy-to-clipboard') -var addTooltip = require('./tooltip') -// -------------- styling ---------------------- -var csjs = require('csjs-inject') - -var css = csjs` - .copyIcon { - margin-left: 5px; - cursor: pointer; - } -` - -module.exports = function copyToClipboard (getContent, tip = 'Copy value to clipboard', icon = 'fa-copy') { - var copyIcon = yo`` - copyIcon.onclick = (event) => { - event.stopPropagation() - var copiableContent - try { - copiableContent = getContent() - } catch (e) { - addTooltip(e.message) - return - } - if (copiableContent) { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory - try { - if (typeof copiableContent !== 'string') { - copiableContent = JSON.stringify(copiableContent, null, '\t') - } - } catch (e) {} - copy(copiableContent) - addTooltip('Copied value to clipboard.') - } else { - addTooltip('Cannot copy empty content!') - } - } - return copyIcon -} diff --git a/apps/remix-ide/src/app/ui/draggableContent.js b/apps/remix-ide/src/app/ui/draggableContent.js deleted file mode 100644 index 6aedaf1b85..0000000000 --- a/apps/remix-ide/src/app/ui/draggableContent.js +++ /dev/null @@ -1,131 +0,0 @@ -'use strict' -var yo = require('yo-yo') -var csjs = require('csjs-inject') - -var css = csjs` - .containerDraggableModal { - position: absolute; - z-index: 1000; - text-align: center; - width: 500px; - height: 500px; - overflow-y: hidden; - } - - .headerDraggableModal { - cursor: move; - z-index: 10; - text-overflow: ellipsis; - overflow-x: hidden; - } - - .modalActions { - float: right; - } - - .modalAction { - padding-right: 1px; - padding-left: 1px; - cursor: pointer; - } -` - -module.exports = - class DraggableContent { - constructor (closeCb) { - this.closeCb = closeCb - this.isMaximised = false - } - - render (title, url, content) { - this.content = content - var el = yo` -
    -
    -
    ${title} - ${url} -
    - { this.minimize() }} class="fas fa-window-minimize ${css.modalAction}"> - { this.maximise() }} class="fas fa-window-maximize ${css.modalAction}"> - { this.close() }} class="fas fa-window-close-o ${css.modalAction}"> -
    -
    -
    - ${content} -
    ` - dragElement(el) - el.style.top = '20%' - el.style.left = '50%' - this.el = el - return el - } - - setTitle (title) { - this.el.querySelector('.title span').innerHTML = title - } - - minimize () { - this.isMaximised = false - this.content.style.display = 'none' - this.el.style.height = 'inherit' - this.el.style.width = '150px' - this.el.querySelector('.title').style.width = '146px' - } - - maximise () { - this.content.style.display = 'block' - var body = document.querySelector('body') - this.el.style.height = this.isMaximised ? '500px' : body.offsetHeight + 'px' - this.el.style.width = this.isMaximised ? '500px' : body.offsetWidth + 'px' - this.isMaximised = !this.isMaximised - this.el.style.top = this.isMaximised ? '0%' : '20%' - this.el.style.left = this.isMaximised ? '0%' : '50%' - this.el.querySelector('.title').style.width = 'inherit' - } - - close () { - if (this.closeCb) this.closeCb() - if (this.el.parentElement) { - this.el.parentElement.removeChild(this.el) - } - } - } - -function dragElement (elmnt) { - var pos1 = 0 - var pos2 = 0 - var pos3 = 0 - var pos4 = 0 - - elmnt.querySelector('.title').onmousedown = dragMouseDown - - function dragMouseDown (e) { - e = e || window.event - if (e.button !== 0) return - e.preventDefault() - // get the mouse cursor position at startup: - pos3 = e.clientX - pos4 = e.clientY - document.onmouseup = closeDragElement - // call a function whenever the cursor moves: - document.onmousemove = elementDrag - } - - function elementDrag (e) { - e = e || window.event - e.preventDefault() - // calculate the new cursor position: - pos1 = pos3 - e.clientX - pos2 = pos4 - e.clientY - pos3 = e.clientX - pos4 = e.clientY - // set the element's new position: - elmnt.style.top = (elmnt.offsetTop - pos2) + 'px' - elmnt.style.left = (elmnt.offsetLeft - pos1) + 'px' - } - - function closeDragElement () { - /* stop moving when mouse button is released: */ - document.onmouseup = null - document.onmousemove = null - } -} diff --git a/apps/remix-ide/src/app/ui/dropdown.js b/apps/remix-ide/src/app/ui/dropdown.js deleted file mode 100644 index 929395e624..0000000000 --- a/apps/remix-ide/src/app/ui/dropdown.js +++ /dev/null @@ -1,128 +0,0 @@ -var yo = require('yo-yo') -var EventManager = require('../../lib/events') - -// -------------- styling ---------------------- - -var css = require('./styles/dropdown-styles') - -/* USAGE: - - var dropdown = new Dropdown({ - options: [ - 'knownTransaction', - 'unknownTransaction', - 'log', - 'info', - 'error' - ], - defaults: ['knownTransaction'] - }) - dropdown.event.register('deselect', function (label) { }) - dropdown.event.register('select', function (label) { }) - -*/ -class Dropdown { - constructor (opts = {}) { - var self = this - self.event = new EventManager() - self.data = { - _options: opts.options || [], - _dependencies: opts.dependencies || [], - selected: opts.defaults || [], - _elements: [] - } - self._view = {} - self._api = opts.api - self._events = opts.events - } - - render () { - var self = this - if (self._view.el) return self._view.el - self._view.selected = yo` -
    - [${self.data.selected.length}] ${self.data.selected.join(', ')} - -
    - ` - self._view.el = yo` -
    - ${self._view.selected} - -
    - ` - return self._view.el - function emit (event) { - var input = event.currentTarget - var label = input.nextSibling.innerText - if (input.checked) { - self.data.selected.push(label) - if (event.type === 'change') { - self.event.trigger('select', [label]) - updateDependencies(label) - } - } else { - var idx = self.data.selected.indexOf(label) - self.data.selected.splice(idx, 1) - if (event.type === 'change') { - self.event.trigger('deselect', [label]) - updateDependencies(label) - } - } - self._view.selected.children[0].innerText = `[${self.data.selected.length}] ${self.data.selected.join(', ')}` - } - function updateDependencies (changed) { - if (self.data._dependencies[changed]) { - for (var dep in self.data._dependencies[changed]) { - var label = self.data._dependencies[changed][dep] - var el = self.data._elements[self.data._options.indexOf(label)] - el.checked = !el.checked - emit({ currentTarget: el, type: 'changeDependencies' }) - } - } - } - function show (event) { - event.stopPropagation() - var options = event.currentTarget.children[1] - var parent = event.target.parentElement - var isOption = parent === options || parent.parentElement === options - if (isOption) return - if (options.style.display === 'none') { - options.style.display = '' - document.body.addEventListener('click', handler) - } else { - options.style.display = 'none' - document.body.removeEventListener('click', handler) - } - function handler (event) { - options.style.display = 'none' - document.body.removeEventListener('click', handler) - } - } - } -} - -module.exports = Dropdown diff --git a/apps/remix-ide/src/app/ui/landing-page/landing-page.js b/apps/remix-ide/src/app/ui/landing-page/landing-page.js index a100c4ea4e..b05271dc96 100644 --- a/apps/remix-ide/src/app/ui/landing-page/landing-page.js +++ b/apps/remix-ide/src/app/ui/landing-page/landing-page.js @@ -5,8 +5,6 @@ import * as packageJson from '../../../../../../package.json' import { ViewPlugin } from '@remixproject/engine-web' import { RemixUiHomeTab } from '@remix-ui/home-tab' // eslint-disable-line -const GistHandler = require('../../../lib/gist-handler') - const profile = { name: 'home', displayName: 'Home', @@ -26,7 +24,6 @@ export class LandingPage extends ViewPlugin { this.contentImport = contentImport this.appManager = appManager this.verticalIcons = verticalIcons - this.gistHandler = new GistHandler() this.el = document.createElement('div') this.el.setAttribute('id', 'landingPageHomeContainer') this.el.setAttribute('class', 'remixui_homeContainer justify-content-between bg-light d-flex') diff --git a/apps/remix-ide/src/app/ui/multiParamManager.js b/apps/remix-ide/src/app/ui/multiParamManager.js deleted file mode 100644 index 74e43187c6..0000000000 --- a/apps/remix-ide/src/app/ui/multiParamManager.js +++ /dev/null @@ -1,228 +0,0 @@ -'use strict' - -var yo = require('yo-yo') -var css = require('../../universal-dapp-styles') -var copyToClipboard = require('./copy-to-clipboard') -var remixLib = require('@remix-project/remix-lib') -var txFormat = remixLib.execution.txFormat - -class MultiParamManager { - /** - * - * @param {bool} lookupOnly - * @param {Object} funABI - * @param {Function} clickMultiCallBack - * @param {string} inputs - * @param {string} title - * @param {string} evmBC - * - */ - constructor (lookupOnly, funABI, clickCallBack, inputs, title, evmBC, isDeploy) { - this.lookupOnly = lookupOnly - this.funABI = funABI - this.clickCallBack = clickCallBack - this.inputs = inputs - this.title = title - this.evmBC = evmBC - this.basicInputField = null - this.multiFields = null - this.isDeploy = isDeploy - } - - switchMethodViewOn () { - this.contractActionsContainerSingle.style.display = 'none' - this.contractActionsContainerMulti.style.display = 'flex' - this.makeMultiVal() - } - - switchMethodViewOff () { - this.contractActionsContainerSingle.style.display = 'flex' - this.contractActionsContainerMulti.style.display = 'none' - var multiValString = this.getMultiValsString() - if (multiValString) this.basicInputField.value = multiValString - } - - getValue (item, index) { - var valStr = item.value.join('') - return valStr - } - - getMultiValsString () { - var valArray = this.multiFields.querySelectorAll('input') - var ret = '' - var valArrayTest = [] - - for (var j = 0; j < valArray.length; j++) { - if (ret !== '') ret += ',' - var elVal = valArray[j].value - valArrayTest.push(elVal) - elVal = elVal.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number - elVal = elVal.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string - try { - JSON.parse(elVal) - } catch (e) { - elVal = '"' + elVal + '"' - } - ret += elVal - } - var valStringTest = valArrayTest.join('') - if (valStringTest) { - return ret - } else { - return '' - } - } - - emptyInputs () { - var valArray = this.multiFields.querySelectorAll('input') - for (var k = 0; k < valArray.length; k++) { - valArray[k].value = '' - } - this.basicInputField.value = '' - } - - makeMultiVal () { - var inputString = this.basicInputField.value - if (inputString) { - inputString = inputString.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number - inputString = inputString.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string - var inputJSON = JSON.parse('[' + inputString + ']') - var multiInputs = this.multiFields.querySelectorAll('input') - for (var k = 0; k < multiInputs.length; k++) { - if (inputJSON[k]) { - multiInputs[k].value = JSON.stringify(inputJSON[k]) - } - } - } - } - - createMultiFields () { - if (this.funABI.inputs) { - return yo`
    - ${this.funABI.inputs.map(function (inp) { - return yo`
    ` - })} -
    ` - } - } - - render () { - var title - if (this.title) { - title = this.title - } else if (this.funABI.name) { - title = this.funABI.name - } else { - title = this.funABI.type === 'receive' ? '(receive)' : '(fallback)' - } - - this.basicInputField = yo`` - this.basicInputField.setAttribute('placeholder', this.inputs) - this.basicInputField.setAttribute('title', this.inputs) - this.basicInputField.setAttribute('data-id', this.inputs) - - var onClick = () => { - this.clickCallBack(this.funABI.inputs, this.basicInputField.value) - } - const width = this.isDeploy ? '' : 'w-50' - const funcButton = yo`` - this.contractActionsContainerSingle = yo` -
    - ${funcButton} - ${this.basicInputField} - this.switchMethodViewOn()} title=${title} > -
    ` - - this.multiFields = this.createMultiFields() - - var multiOnClick = () => { - var valsString = this.getMultiValsString() - if (valsString) { - this.clickCallBack(this.funABI.inputs, valsString) - } else { - this.clickCallBack(this.funABI.inputs, '') - } - } - - var expandedButton = yo`` - - this.contractActionsContainerMulti = yo`
    -
    -
    { this.switchMethodViewOff() }} class="${css.multiHeader}"> -
    ${title}
    - -
    - ${this.multiFields} -
    - ${copyToClipboard( - () => { - var multiString = this.getMultiValsString() - var multiJSON = JSON.parse('[' + multiString + ']') - var encodeObj - if (this.evmBC) { - encodeObj = txFormat.encodeData(this.funABI, multiJSON, this.evmBC) - } else { - encodeObj = txFormat.encodeData(this.funABI, multiJSON) - } - if (encodeObj.error) { - throw new Error(encodeObj.error) - } else { - return encodeObj.data - } - }, 'Encode values of input fields & copy to clipboard', 'fa-clipboard')} - ${expandedButton} -
    -
    -
    ` - - var contractProperty = yo` -
    - ${this.contractActionsContainerSingle} ${this.contractActionsContainerMulti} -
    - ` - if (this.lookupOnly) { - // call. stateMutability is either pure or view - expandedButton.setAttribute('title', (title + ' - call')) - expandedButton.innerHTML = 'call' - expandedButton.classList.add('btn-info') - expandedButton.setAttribute('data-id', (title + ' - call')) - funcButton.setAttribute('title', (title + ' - call')) - funcButton.classList.add('btn-info') - funcButton.setAttribute('data-id', (title + ' - call')) - } else if (this.funABI.stateMutability === 'payable' || this.funABI.payable) { - // transact. stateMutability = payable - expandedButton.setAttribute('title', (title + ' - transact (payable)')) - expandedButton.innerHTML = 'transact' - expandedButton.classList.add('btn-danger') - expandedButton.setAttribute('data-id', (title + ' - transact (payable)')) - funcButton.setAttribute('title', (title + ' - transact (payable)')) - funcButton.classList.add('btn-danger') - funcButton.setAttribute('data-id', (title + ' - transact (payable)')) - } else { - // transact. stateMutability = nonpayable - expandedButton.setAttribute('title', (title + ' - transact (not payable)')) - expandedButton.innerHTML = 'transact' - expandedButton.classList.add('btn-warning') - expandedButton.setAttribute('data-id', (title + ' - transact (not payable)')) - funcButton.classList.add('btn-warning') - funcButton.setAttribute('title', (title + ' - transact (not payable)')) - funcButton.setAttribute('data-id', (title + ' - transact (not payable)')) - } - - if (this.funABI.inputs && this.funABI.inputs.length > 0) { - contractProperty.classList.add(css.hasArgs) - } else if (this.funABI.type === 'fallback' || this.funABI.type === 'receive') { - contractProperty.classList.add(css.hasArgs) - this.basicInputField.setAttribute('title', `'(${this.funABI.type}')`) // probably should pass name instead - this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden' - this.basicInputField.setAttribute('data-id', `'(${this.funABI.type}')`) - } else { - this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden' - this.basicInputField.style.visibility = 'hidden' - } - - return contractProperty - } -} - -module.exports = MultiParamManager diff --git a/apps/remix-ide/src/app/ui/renderer.js b/apps/remix-ide/src/app/ui/renderer.js deleted file mode 100644 index 315bc4b22c..0000000000 --- a/apps/remix-ide/src/app/ui/renderer.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict' - -var $ = require('jquery') -var yo = require('yo-yo') -const { default: Registry } = require('../state/registry') -var css = require('./styles/renderer-styles') - -/** - * After refactor, the renderer is only used to render error/warning - * TODO: This don't need to be an object anymore. Simplify and just export the renderError function. - * - */ -function Renderer (service) { - const self = this - self.service = service - self._components = {} - self._components.registry = Registry.getInstance() - // dependencies - self._deps = { - fileManager: self._components.registry.get('filemanager').api, - config: self._components.registry.get('config').api - } - if (document && document.head) { - document.head.appendChild(css) - } -} - -Renderer.prototype._error = function (file, error) { - const self = this - if (file === self._deps.config.get('currentFile')) { - self.service.call('editor', 'addAnnotation', error, file) - } -} - -Renderer.prototype._errorClick = function (errFile, errLine, errCol) { - const self = this - const editor = self._components.registry.get('editor').api - if (errFile !== self._deps.config.get('currentFile')) { - // TODO: refactor with this._components.contextView.jumpTo - var provider = self._deps.fileManager.fileProviderOf(errFile) - if (provider) { - provider.exists(errFile).then(exist => { - self._deps.fileManager.open(errFile) - editor.gotoLine(errLine, errCol) - }).catch(error => { - if (error) return console.log(error) - }) - } - } else { - editor.gotoLine(errLine, errCol) - } -} - -function getPositionDetails (msg) { - const result = {} - - // To handle some compiler warning without location like SPDX license warning etc - if (!msg.includes(':')) return { errLine: -1, errCol: -1, errFile: msg } - - // extract line / column - let position = msg.match(/^(.*?):([0-9]*?):([0-9]*?)?/) - result.errLine = position ? parseInt(position[2]) - 1 : -1 - result.errCol = position ? parseInt(position[3]) : -1 - - // extract file - position = msg.match(/^(https:.*?|http:.*?|.*?):/) - result.errFile = position ? position[1] : '' - return result -} - -/** - * format msg like error or warning, - * - * @param {String or DOMElement} message - * @param {DOMElement} container - * @param {Object} options { - * useSpan, - * noAnnotations, - * click:(Function), - * type:( - * warning, - * error - * ), - * errFile, - * errLine, - * errCol - * } - */ - -Renderer.prototype.error = function (message, container, opt) { - if (!message) return - if (container === undefined) return - opt = opt || {} - - var text - if (typeof message === 'string') { - text = message - message = yo`${message}` - } else if (message.innerText) { - text = message.innerText - } - - // ^ e.g: - // browser/gm.sol: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.6.12 - // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0/contracts/introspection/IERC1820Registry.sol:3:1: ParserError: Source file requires different compiler version (current compiler is 0.7.4+commit.3f05b770.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version - - let position = getPositionDetails(text) - - // For compiler version 0.8.0 and upcoming versions, errors and warning will be reported in a different way - // Above method regex will return type of error as 'errFile' - // Comparison of 'errFile' with passed error type will ensure the reporter type - if (!position.errFile || (opt.errorType && opt.errorType === position.errFile)) { - // Updated error reported includes '-->' before file details - const errorDetails = text.split('-->') - // errorDetails[1] will have file details - if (errorDetails.length > 1) position = getPositionDetails(errorDetails[1]) - } - - opt.errLine = position.errLine - opt.errCol = position.errCol - opt.errFile = position.errFile.trim() - - if (!opt.noAnnotations && opt.errFile) { - this._error(opt.errFile, { - row: opt.errLine, - column: opt.errCol, - text: text, - type: opt.type - }) - } - - var $pre = $(opt.useSpan ? yo`` : yo`
    `).html(message)
    -
    -  const classList = opt.type === 'error' ? 'alert alert-danger' : 'alert alert-warning'
    -  var $error = $(yo`
    `).prepend($pre) - $(container).append($error) - - $error.click((ev) => { - if (opt.click) { - opt.click(message) - } else if (opt.errFile !== undefined && opt.errLine !== undefined && opt.errCol !== undefined) { - this._errorClick(opt.errFile, opt.errLine, opt.errCol) - } - }) - - $error.find('.close').click(function (ev) { - ev.preventDefault() - $error.remove() - return false - }) -} - -module.exports = Renderer diff --git a/apps/remix-ide/src/app/ui/sendTxCallbacks.js b/apps/remix-ide/src/app/ui/sendTxCallbacks.js deleted file mode 100644 index 3f5c7c7506..0000000000 --- a/apps/remix-ide/src/app/ui/sendTxCallbacks.js +++ /dev/null @@ -1,109 +0,0 @@ -const yo = require('yo-yo') -const remixLib = require('@remix-project/remix-lib') -const confirmDialog = require('./confirmDialog') -const modalCustom = require('./modal-dialog-custom') -const modalDialog = require('./modaldialog') -const typeConversion = remixLib.execution.typeConversion -const Web3 = require('web3') - -module.exports = { - getCallBacksWithContext: (udappUI, blockchain) => { - const callbacks = {} - callbacks.confirmationCb = confirmationCb - callbacks.continueCb = continueCb - callbacks.promptCb = promptCb - callbacks.udappUI = udappUI - callbacks.blockchain = blockchain - return callbacks - } -} - -const continueCb = function (error, continueTxExecution, cancelCb) { - if (error) { - const msg = typeof error !== 'string' ? error.message : error - modalDialog( - 'Gas estimation failed', - yo` -
    Gas estimation errored with the following message (see below). - The transaction execution will likely fail. Do you want to force sending?
    ${msg}
    - `, - { - label: 'Send Transaction', - fn: () => continueTxExecution() - }, - { - label: 'Cancel Transaction', - fn: () => cancelCb() - } - ) - } else { - continueTxExecution() - } -} - -const promptCb = function (okCb, cancelCb) { - modalCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) -} - -const confirmationCb = function (network, tx, gasEstimation, continueTxExecution, cancelCb) { - const self = this - if (network.name !== 'Main') { - return continueTxExecution(null) - } - var amount = Web3.utils.fromWei(typeConversion.toInt(tx.value), 'ether') - var content = confirmDialog(tx, network, amount, gasEstimation, - (gasPrice, cb) => { - let txFeeText, priceStatus - // TODO: this try catch feels like an anti pattern, can/should be - // removed, but for now keeping the original logic - try { - var fee = Web3.utils.toBN(tx.gas).mul(Web3.utils.toBN(Web3.utils.toWei(gasPrice.toString(10), 'gwei'))) - txFeeText = ' ' + Web3.utils.fromWei(fee.toString(10), 'ether') + ' Ether' - priceStatus = true - } catch (e) { - txFeeText = ' Please fix this issue before sending any transaction. ' + e.message - priceStatus = false - } - cb(txFeeText, priceStatus) - }, - (cb) => { - self.blockchain.web3().eth.getGasPrice((error, gasPrice) => { - const warnMessage = ' Please fix this issue before sending any transaction. ' - if (error) { - return cb('Unable to retrieve the current network gas price.' + warnMessage + error) - } - try { - var gasPriceValue = Web3.utils.fromWei(gasPrice.toString(10), 'gwei') - cb(null, gasPriceValue) - } catch (e) { - cb(warnMessage + e.message, null, false) - } - }) - } - ) - modalDialog( - 'Confirm transaction', - content, - { - label: 'Confirm', - fn: () => { - self.blockchain.config.setUnpersistedProperty( - 'doNotShowTransactionConfirmationAgain', - content.querySelector('input#confirmsetting').checked - ) - // TODO: check if this is check is still valid given the refactor - if (!content.gasPriceStatus) { - cancelCb('Given transaction fee is not correct') - } else { - continueTxExecution(content.txFee) - } - } - }, - { - label: 'Cancel', - fn: () => { - return cancelCb('Transaction canceled by user.') - } - } - ) -} diff --git a/apps/remix-ide/src/app/ui/styles/renderer-styles.js b/apps/remix-ide/src/app/ui/styles/renderer-styles.js deleted file mode 100644 index 7dc047c440..0000000000 --- a/apps/remix-ide/src/app/ui/styles/renderer-styles.js +++ /dev/null @@ -1,52 +0,0 @@ -var yo = require('yo-yo') - -var css = yo`` - -module.exports = css diff --git a/apps/remix-ide/src/app/ui/svgLogo.js b/apps/remix-ide/src/app/ui/svgLogo.js deleted file mode 100644 index 3f8513151a..0000000000 --- a/apps/remix-ide/src/app/ui/svgLogo.js +++ /dev/null @@ -1,8 +0,0 @@ -import yo from 'yo-yo' -export function basicLogo () { - return yo` - - - -` -} diff --git a/apps/remix-ide/src/app/ui/txLogger.js b/apps/remix-ide/src/app/ui/txLogger.js deleted file mode 100644 index 9ae277c304..0000000000 --- a/apps/remix-ide/src/app/ui/txLogger.js +++ /dev/null @@ -1,590 +0,0 @@ -'use strict' -var yo = require('yo-yo') -var copyToClipboard = require('./copy-to-clipboard') - -// -------------- styling ---------------------- -var csjs = require('csjs-inject') -var remixLib = require('@remix-project/remix-lib') - -var EventManager = require('../../lib/events') -var helper = require('../../lib/helper') -var modalDialog = require('./modal-dialog-custom') -const { default: Registry } = require('../state/registry') - -var typeConversion = remixLib.execution.typeConversion - -var css = csjs` - .log { - display: flex; - cursor: pointer; - align-items: center; - cursor: pointer; - } - .log:hover { - opacity: 0.8; - } - .arrow { - color: var(--text-info); - font-size: 20px; - cursor: pointer; - display: flex; - margin-left: 10px; - } - .arrow:hover { - color: var(--secondary); - } - .txLog { - } - .txStatus { - display: flex; - font-size: 20px; - margin-right: 20px; - float: left; - } - .succeeded { - color: var(--success); - } - .failed { - color: var(--danger); - } - .notavailable { - } - .call { - font-size: 7px; - border-radius: 50%; - min-width: 20px; - min-height: 20px; - display: flex; - justify-content: center; - align-items: center; - color: var(--text-info); - text-transform: uppercase; - font-weight: bold; - } - .txItem { - color: var(--text-info); - margin-right: 5px; - float: left; - } - .txItemTitle { - font-weight: bold; - } - .tx { - color: var(--text-info); - font-weight: bold; - float: left; - margin-right: 10px; - } - .txTable, - .tr, - .td { - border-collapse: collapse; - font-size: 10px; - color: var(--text-info); - border: 1px solid var(--text-info); - } - #txTable { - margin-top: 1%; - margin-bottom: 5%; - align-self: center; - width: 85%; - } - .tr, .td { - padding: 4px; - vertical-align: baseline; - } - .td:first-child { - min-width: 30%; - width: 30%; - align-items: baseline; - font-weight: bold; - } - .tableTitle { - width: 25%; - } - .buttons { - display: flex; - margin-left: auto; - } - .debug { - white-space: nowrap; - } - .debug:hover { - opacity: 0.8; - }` -/** - * This just export a function that register to `newTransaction` and forward them to the logger. - * - */ -class TxLogger { - constructor (terminal, blockchain) { - this.event = new EventManager() - this.seen = {} - function filterTx (value, query) { - if (value.length) { - return helper.find(value, query) - } - return false - } - this.eventsDecoder = Registry.getInstance().get('eventsDecoder').api - this.txListener = Registry.getInstance().get('txlistener').api - this.terminal = terminal - // dependencies - this._deps = { - compilersArtefacts: Registry.getInstance().get('compilersartefacts').api - } - - this.logKnownTX = this.terminal.registerCommand('knownTransaction', (args, cmds, append) => { - var data = args[0] - var el - if (data.tx.isCall) { - el = renderCall(this, data) - } else { - el = renderKnownTransaction(this, data, blockchain) - } - this.seen[data.tx.hash] = el - append(el) - }, { activate: true, filterFn: filterTx }) - - this.logUnknownTX = this.terminal.registerCommand('unknownTransaction', (args, cmds, append) => { - // triggered for transaction AND call - var data = args[0] - var el = renderUnknownTransaction(this, data, blockchain) - append(el) - }, { activate: false, filterFn: filterTx }) - - this.logEmptyBlock = this.terminal.registerCommand('emptyBlock', (args, cmds, append) => { - var data = args[0] - var el = renderEmptyBlock(this, data) - append(el) - }, { activate: true }) - - this.txListener.event.register('newBlock', (block) => { - if (!block.transactions || (block.transactions && !block.transactions.length)) { - this.logEmptyBlock({ block: block }) - } - }) - - this.txListener.event.register('newTransaction', (tx, receipt) => { - log(this, tx, receipt) - }) - - this.txListener.event.register('newCall', (tx) => { - log(this, tx, null) - }) - - this.terminal.updateJournal({ type: 'select', value: 'unknownTransaction' }) - this.terminal.updateJournal({ type: 'select', value: 'knownTransaction' }) - } -} - -function debug (e, data, self) { - e.stopPropagation() - if (data.tx.isCall && data.tx.envMode !== 'vm') { - modalDialog.alert('Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.') - } else { - self.event.trigger('debuggingRequested', [data.tx.hash]) - } -} - -function log (self, tx, receipt) { - var resolvedTransaction = self.txListener.resolvedTransaction(tx.hash) - if (resolvedTransaction) { - var compiledContracts = null - if (self._deps.compilersArtefacts.__last) { - compiledContracts = self._deps.compilersArtefacts.__last.getContracts() - } - self.eventsDecoder.parseLogs(tx, resolvedTransaction.contractName, compiledContracts, (error, logs) => { - if (!error) { - self.logKnownTX({ tx: tx, receipt: receipt, resolvedData: resolvedTransaction, logs: logs }) - } - }) - } else { - // contract unknown - just displaying raw tx. - self.logUnknownTX({ tx: tx, receipt: receipt }) - } -} - -function renderKnownTransaction (self, data, blockchain) { - var from = data.tx.from - var to = data.resolvedData.contractName + '.' + data.resolvedData.fn - var obj = { from, to } - var txType = 'knownTx' - var tx = yo` - -
    txDetails(e, tx, data, obj)}> - ${checkTxStatus(data.receipt, txType)} - ${context(self, { from, to, data }, blockchain)} -
    -
    -
    - - -
    - ` - return tx -} - -function renderCall (self, data) { - var to = data.resolvedData.contractName + '.' + data.resolvedData.fn - var from = data.tx.from ? data.tx.from : ' - ' - var input = data.tx.input ? helper.shortenHexData(data.tx.input) : '' - var obj = { from, to } - var txType = 'call' - var tx = yo` - -
    txDetails(e, tx, data, obj)}> - ${checkTxStatus(data.tx, txType)} - - [call] -
    from: ${from}
    -
    to: ${to}
    -
    data: ${input}
    -
    -
    -
    debug(e, data, self)}>Debug
    -
    - -
    -
    - ` - return tx -} - -function renderUnknownTransaction (self, data, blockchain) { - var from = data.tx.from - var to = data.tx.to - var obj = { from, to } - var txType = 'unknown' + (data.tx.isCall ? 'Call' : 'Tx') - var tx = yo` - -
    txDetails(e, tx, data, obj)}> - ${checkTxStatus(data.receipt || data.tx, txType)} - ${context(self, { from, to, data }, blockchain)} -
    -
    debug(e, data, self)}>Debug
    -
    - -
    -
    - ` - return tx -} - -function renderEmptyBlock (self, data) { - return yo` - -
    [block:${data.block.number} - 0 transactions] - ` -} - -function checkTxStatus (tx, type) { - if (tx.status === '0x1' || tx.status === true) { - return yo`` - } - if (type === 'call' || type === 'unknownCall') { - return yo`call` - } else if (tx.status === '0x0' || tx.status === false) { - return yo`` - } else { - return yo`` - } -} - -function context (self, opts, blockchain) { - var data = opts.data || '' - var from = opts.from ? helper.shortenHexData(opts.from) : '' - var to = opts.to - if (data.tx.to) to = to + ' ' + helper.shortenHexData(data.tx.to) - var val = data.tx.value - var hash = data.tx.hash ? helper.shortenHexData(data.tx.hash) : '' - var input = data.tx.input ? helper.shortenHexData(data.tx.input) : '' - var logs = data.logs && data.logs.decoded && data.logs.decoded.length ? data.logs.decoded.length : 0 - var block = data.receipt ? data.receipt.blockNumber : data.tx.blockNumber || '' - var i = data.receipt ? data.receipt.transactionIndex : data.tx.transactionIndex - var value = val ? typeConversion.toInt(val) : 0 - if (blockchain.getProvider() === 'vm') { - return yo` -
    - - [vm] -
    from: ${from}
    -
    to: ${to}
    -
    value: ${value} wei
    -
    data: ${input}
    -
    logs: ${logs}
    -
    hash: ${hash}
    -
    -
    ` - } else if (blockchain.getProvider() !== 'vm' && data.resolvedData) { - return yo` -
    - - [block:${block} txIndex:${i}] -
    from: ${from}
    -
    to: ${to}
    -
    value: ${value} wei
    -
    data: ${input}
    -
    logs: ${logs}
    -
    hash: ${hash}
    -
    -
    ` - } else { - to = helper.shortenHexData(to) - hash = helper.shortenHexData(data.tx.blockHash) - return yo` -
    - - [block:${block} txIndex:${i}] -
    from: ${from}
    -
    to: ${to}
    -
    value: ${value} wei
    -
    -
    ` - } -} - -module.exports = TxLogger - -// helpers - -function isDescendant (parent, child) { - var node = child.parentNode - while (node != null) { - if (node === parent) { - return true - } - node = node.parentNode - } - return false -} - -function txDetails (e, tx, data, obj) { - const from = obj.from - const to = obj.to - const arrowUp = yo`` - const arrowDown = yo`` - - let blockElement = e.target - while (true) { // get the parent block element - if (blockElement.className.startsWith('block')) break - else if (blockElement.parentElement) { - blockElement = blockElement.parentElement - } else break - } - - const tables = blockElement.querySelectorAll(`#${tx.id} [class^="txTable"]`) - const logs = blockElement.querySelectorAll(`#${tx.id} [class^='log']`) - const arrows = blockElement.querySelectorAll(`#${tx.id} [class^='arrow']`) - - let table = [...tables].filter((t) => isDescendant(tx, t))[0] - const log = [...logs].filter((t) => isDescendant(tx, t))[0] - const arrow = [...arrows].filter((t) => isDescendant(tx, t))[0] - - if (table && table.parentNode) { - tx.removeChild(table) - log.removeChild(arrow) - log.appendChild(arrowDown) - } else { - log.removeChild(arrow) - log.appendChild(arrowUp) - table = createTable({ - hash: data.tx.hash, - status: data.receipt ? data.receipt.status : null, - isCall: data.tx.isCall, - contractAddress: data.tx.contractAddress, - data: data.tx, - from, - to, - gas: data.tx.gas, - input: data.tx.input, - 'decoded input': data.resolvedData && data.resolvedData.params ? JSON.stringify(typeConversion.stringify(data.resolvedData.params), null, '\t') : ' - ', - 'decoded output': data.resolvedData && data.resolvedData.decodedReturnValue ? JSON.stringify(typeConversion.stringify(data.resolvedData.decodedReturnValue), null, '\t') : ' - ', - logs: data.logs, - val: data.tx.value, - transactionCost: data.tx.transactionCost, - executionCost: data.tx.executionCost - }) - tx.appendChild(table) - } -} - -function createTable (opts) { - var table = yo`
    ` - if (!opts.isCall) { - var msg = '' - if (opts.status !== undefined && opts.status !== null) { - if (opts.status === '0x0' || opts.status === false) { - msg = ' Transaction mined but execution failed' - } else if (opts.status === '0x1' || opts.status === true) { - msg = ' Transaction mined and execution succeed' - } - } else { - msg = ' Status not available at the moment' - } - table.appendChild(yo` - - status - ${opts.status}${msg} - `) - } - - var transactionHash = yo` - - transaction hash - ${opts.hash} - ${copyToClipboard(() => opts.hash)} - - - ` - table.appendChild(transactionHash) - - var contractAddress = yo` - - contract address - ${opts.contractAddress} - ${copyToClipboard(() => opts.contractAddress)} - - - ` - if (opts.contractAddress) table.appendChild(contractAddress) - - var from = yo` - - from - ${opts.from} - ${copyToClipboard(() => opts.from)} - - - ` - if (opts.from) table.appendChild(from) - - var toHash - var data = opts.data // opts.data = data.tx - if (data.to) { - toHash = opts.to + ' ' + data.to - } else { - toHash = opts.to - } - var to = yo` - - to - ${toHash} - ${copyToClipboard(() => data.to ? data.to : toHash)} - - - ` - if (opts.to) table.appendChild(to) - - var gas = yo` - - gas - ${opts.gas} gas - ${copyToClipboard(() => opts.gas)} - - - ` - if (opts.gas) table.appendChild(gas) - - var callWarning = '' - if (opts.isCall) { - callWarning = '(Cost only applies when called by a contract)' - } - if (opts.transactionCost) { - table.appendChild(yo` - - transaction cost - ${opts.transactionCost} gas ${callWarning} - ${copyToClipboard(() => opts.transactionCost)} - - `) - } - - if (opts.executionCost) { - table.appendChild(yo` - - execution cost - ${opts.executionCost} gas ${callWarning} - ${copyToClipboard(() => opts.executionCost)} - - `) - } - - var hash = yo` - - hash - ${opts.hash} - ${copyToClipboard(() => opts.hash)} - - - ` - if (opts.hash) table.appendChild(hash) - - var input = yo` - - input - ${helper.shortenHexData(opts.input)} - ${copyToClipboard(() => opts.input)} - - - ` - if (opts.input) table.appendChild(input) - - if (opts['decoded input']) { - var inputDecoded = yo` - - decoded input - ${opts['decoded input']} - ${copyToClipboard(() => opts['decoded input'])} - - ` - table.appendChild(inputDecoded) - } - - if (opts['decoded output']) { - var outputDecoded = yo` - - decoded output - ${opts['decoded output']} - ${copyToClipboard(() => opts['decoded output'])} - - ` - table.appendChild(outputDecoded) - } - - var stringified = ' - ' - if (opts.logs && opts.logs.decoded) { - stringified = typeConversion.stringify(opts.logs.decoded) - } - var logs = yo` - - logs - - ${JSON.stringify(stringified, null, '\t')} - ${copyToClipboard(() => JSON.stringify(stringified, null, '\t'))} - ${copyToClipboard(() => JSON.stringify(opts.logs.raw || '0'))} - - - ` - if (opts.logs) table.appendChild(logs) - - var val = opts.val != null ? typeConversion.toInt(opts.val) : 0 - val = yo` - - value - ${val} wei - ${copyToClipboard(() => `${val} wei`)} - - - ` - if (opts.val) table.appendChild(val) - - return table -} diff --git a/apps/remix-ide/src/app/ui/universal-dapp-ui.js b/apps/remix-ide/src/app/ui/universal-dapp-ui.js deleted file mode 100644 index 217217503f..0000000000 --- a/apps/remix-ide/src/app/ui/universal-dapp-ui.js +++ /dev/null @@ -1,272 +0,0 @@ -/* global */ -'use strict' - -var $ = require('jquery') -var yo = require('yo-yo') -var ethJSUtil = require('ethereumjs-util') -var BN = ethJSUtil.BN -var helper = require('../../lib/helper') -var copyToClipboard = require('./copy-to-clipboard') -var css = require('../../universal-dapp-styles') -var MultiParamManager = require('./multiParamManager') -var remixLib = require('@remix-project/remix-lib') -var txFormat = remixLib.execution.txFormat -const txHelper = remixLib.execution.txHelper -var TreeView = require('./TreeView') -var txCallBacks = require('./sendTxCallbacks') -const _paq = window._paq = window._paq || [] - -function UniversalDAppUI (blockchain, logCallback) { - this.blockchain = blockchain - this.logCallback = logCallback - this.compilerData = { contractsDetails: {} } -} - -function decodeResponseToTreeView (response, fnabi) { - var treeView = new TreeView({ - extractData: (item, parent, key) => { - var ret = {} - if (BN.isBN(item)) { - ret.self = item.toString(10) - ret.children = [] - } else { - ret = treeView.extractDataDefault(item, parent, key) - } - return ret - } - }) - return treeView.render(txFormat.decodeResponse(response, fnabi)) -} - -UniversalDAppUI.prototype.renderInstance = function (contract, address, contractName) { - var noInstances = document.querySelector('[data-id="deployAndRunNoInstanceText"]') - if (noInstances) { - noInstances.parentNode.removeChild(noInstances) - } - const abi = txHelper.sortAbiFunction(contract.abi) - return this.renderInstanceFromABI(abi, address, contractName, contract) -} - -// TODO this function was named before "appendChild". -// this will render an instance: contract name, contract address, and all the public functions -// basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed -// this returns a DOM element -UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName, contract) { - const self = this - address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex') - address = ethJSUtil.toChecksumAddress(address) - var instance = yo`
    ` - const context = this.blockchain.context() - - var shortAddress = helper.shortenAddress(address) - var title = yo` -
    - -
    -
    - - ${contractName} at ${shortAddress} (${context}) - -
    -
    - -
    -
    -
    - ` - - var close = yo` - ` - title.querySelector('.btn-group').appendChild(close) - - var contractActionsWrapper = yo` -
    -
    - ` - - function remove () { - instance.remove() - // @TODO perhaps add a callack here to warn the caller that the instance has been removed - } - - function toggleClass (e) { - $(instance).toggleClass(`${css.hidesub} bg-light`) - // e.currentTarget.querySelector('i') - e.currentTarget.querySelector('i').classList.toggle('fa-angle-right') - e.currentTarget.querySelector('i').classList.toggle('fa-angle-down') - } - - instance.appendChild(title) - instance.appendChild(contractActionsWrapper) - - $.each(contractABI, (i, funABI) => { - if (funABI.type !== 'function') { - return - } - // @todo getData cannot be used with overloaded functions - contractActionsWrapper.appendChild(this.getCallButton({ - funABI: funABI, - address: address, - contractABI: contractABI, - contractName: contractName, - contract - })) - }) - - const calldataInput = yo` - - ` - const llIError = yo` - - ` - // constract LLInteractions elements - const lowLevelInteracions = yo` -
    -
    -
    - Low level interactions -
    - - - -
    -
    - -
    - ${calldataInput} - -
    -
    -
    - ${llIError} -
    -
    - ` - - function sendData () { - function setLLIError (text) { - llIError.innerText = text - } - - setLLIError('') - const fallback = txHelper.getFallbackInterface(contractABI) - const receive = txHelper.getReceiveInterface(contractABI) - const args = { - funABI: fallback || receive, - address: address, - contractName: contractName, - contractABI: contractABI - } - const amount = document.querySelector('#value').value - if (amount !== '0') { - // check for numeric and receive/fallback - if (!helper.isNumeric(amount)) { - return setLLIError('Value to send should be a number') - } else if (!receive && !(fallback && fallback.stateMutability === 'payable')) { - return setLLIError("In order to receive Ether transfer the contract should have either 'receive' or payable 'fallback' function") - } - } - let calldata = calldataInput.value - if (calldata) { - if (calldata.length < 4 && helper.is0XPrefixed(calldata)) { - return setLLIError('The calldata should be a valid hexadecimal value with size of at least one byte.') - } else { - if (helper.is0XPrefixed(calldata)) { - calldata = calldata.substr(2, calldata.length) - } - if (!helper.isHexadecimal(calldata)) { - return setLLIError('The calldata should be a valid hexadecimal value.') - } - } - if (!fallback) { - return setLLIError("'Fallback' function is not defined") - } - } - - if (!receive && !fallback) return setLLIError('Both \'receive\' and \'fallback\' functions are not defined') - - // we have to put the right function ABI: - // if receive is defined and that there is no calldata => receive function is called - // if fallback is defined => fallback function is called - if (receive && !calldata) args.funABI = receive - else if (fallback) args.funABI = fallback - - if (!args.funABI) return setLLIError('Please define a \'Fallback\' function to send calldata and a either \'Receive\' or payable \'Fallback\' to send ethers') - self.runTransaction(false, args, null, calldataInput.value, null) - } - - contractActionsWrapper.appendChild(lowLevelInteracions) - return instance -} - -// TODO this is used by renderInstance when a new instance is displayed. -// this returns a DOM element. -UniversalDAppUI.prototype.getCallButton = function (args) { - const self = this - var outputOverride = yo`
    ` // show return value - const isConstant = args.funABI.constant !== undefined ? args.funABI.constant : false - const lookupOnly = args.funABI.stateMutability === 'view' || args.funABI.stateMutability === 'pure' || isConstant - const multiParamManager = new MultiParamManager( - lookupOnly, - args.funABI, - (valArray, inputsValues) => self.runTransaction(lookupOnly, args, valArray, inputsValues, outputOverride), - self.blockchain.getInputs(args.funABI) - ) - - const contractActionsContainer = yo`
    ${multiParamManager.render()}
    ` - contractActionsContainer.appendChild(outputOverride) - - return contractActionsContainer -} - -UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, inputsValues, outputOverride) { - const functionName = args.funABI.type === 'function' ? args.funABI.name : `(${args.funABI.type})` - const logMsg = `${lookupOnly ? 'call' : 'transact'} to ${args.contractName}.${functionName}` - - const callbacksInContext = txCallBacks.getCallBacksWithContext(this, this.blockchain) - - const outputCb = (returnValue) => { - if (outputOverride) { - const decoded = decodeResponseToTreeView(returnValue, args.funABI) - outputOverride.innerHTML = '' - outputOverride.appendChild(decoded) - } - } - let callinfo = '' - if (lookupOnly) callinfo = 'call' - else if (args.funABI.type === 'fallback' || args.funABI.type === 'receive') callinfo = 'lowLevelInteracions' - else callinfo = 'transact' - - _paq.push(['trackEvent', 'udapp', callinfo, this.blockchain.getCurrentNetworkStatus().network.name]) - const params = args.funABI.type !== 'fallback' ? inputsValues : '' - this.blockchain.runOrCallContractMethod( - args.contractName, - args.contractABI, - args.funABI, - args.contract, - inputsValues, - args.address, - params, - lookupOnly, - logMsg, - this.logCallback, - outputCb, - callbacksInContext.confirmationCb.bind(callbacksInContext), - callbacksInContext.continueCb.bind(callbacksInContext), - callbacksInContext.promptCb.bind(callbacksInContext)) -} - -module.exports = UniversalDAppUI diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js index f280e47d8b..88ea275779 100644 --- a/apps/remix-ide/src/blockchain/blockchain.js +++ b/apps/remix-ide/src/blockchain/blockchain.js @@ -1,3 +1,5 @@ + +import React from 'react' // eslint-disable-line import Web3 from 'web3' import { Plugin } from '@remixproject/engine' import { toBuffer, addHexPrefix } from 'ethereumjs-util' @@ -9,6 +11,7 @@ import VMProvider from './providers/vm.js' import InjectedProvider from './providers/injected.js' import NodeProvider from './providers/node.js' import { execution, EventManager, helpers } from '@remix-project/remix-lib' +import { etherScanLink } from './helper' const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution const { txResultHelper: resultToRemixTx } = helpers const packageJson = require('../../../../package.json') @@ -23,7 +26,7 @@ const profile = { version: packageJson.version } -class Blockchain extends Plugin { +export class Blockchain extends Plugin { // NOTE: the config object will need to be refactored out in remix-lib constructor (config) { super(profile) @@ -343,14 +346,18 @@ class Blockchain extends Plugin { return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false } }, _ => this.executionContext.web3(), _ => this.executionContext.currentblockGasLimit()) - - this.txRunner = new TxRunner(web3Runner, { runAsync: true }) - this.txRunner.event.register('transactionBroadcasted', (txhash) => { + + web3Runner.event.register('transactionBroadcasted', (txhash) => { this.executionContext.detectNetwork((error, network) => { if (error || !network) return - this.event.trigger('transactionBroadcasted', [txhash, network.name]) + if (network.name === 'VM') return + this.call('terminal', 'logHtml', + ( + open in etherscan + )) }) }) + this.txRunner = new TxRunner(web3Runner, { runAsync: true }) } /** @@ -545,5 +552,3 @@ class Blockchain extends Plugin { }) } } - -module.exports = Blockchain diff --git a/apps/remix-ide/src/blockchain/helper.ts b/apps/remix-ide/src/blockchain/helper.ts new file mode 100644 index 0000000000..652a088078 --- /dev/null +++ b/apps/remix-ide/src/blockchain/helper.ts @@ -0,0 +1,13 @@ +const transactionDetailsLinks = { + Main: 'https://www.etherscan.io/tx/', + Rinkeby: 'https://rinkeby.etherscan.io/tx/', + Ropsten: 'https://ropsten.etherscan.io/tx/', + Kovan: 'https://kovan.etherscan.io/tx/', + Goerli: 'https://goerli.etherscan.io/tx/' + } + + export function etherScanLink (network: string, hash: string): string { + if (transactionDetailsLinks[network]) { + return transactionDetailsLinks[network] + hash + } + } \ No newline at end of file diff --git a/apps/remix-ide/src/framingService.js b/apps/remix-ide/src/framingService.js deleted file mode 100644 index ed029b7a65..0000000000 --- a/apps/remix-ide/src/framingService.js +++ /dev/null @@ -1,34 +0,0 @@ -export class FramingService { - constructor (sidePanel, verticalIcons, mainView, resizeFeature) { - this.sidePanel = sidePanel - this.verticalIcons = verticalIcons - this.mainPanel = mainView.getAppPanel() - this.mainView = mainView - this.resizeFeature = resizeFeature - } - - start (params) { - this.verticalIcons.select('filePanel') - - document.addEventListener('keypress', (e) => { - if (e.shiftKey && e.ctrlKey) { - if (e.code === 'KeyF') { // Ctrl+Shift+F - this.verticalIcons.select('filePanel') - } else if (e.code === 'KeyA') { // Ctrl+Shift+A - this.verticalIcons.select('pluginManager') - } else if (e.code === 'KeyS') { // Ctrl+Shift+S - this.verticalIcons.select('settings') - } - e.preventDefault() - } - }) - - if (params.minimizeterminal) this.mainView.minimizeTerminal() - if (params.minimizesidepanel) this.resizeFeature.hidePanel() - } - - embed () { - this.mainView.minimizeTerminal() - this.resizeFeature.hidePanel() - } -} diff --git a/apps/remix-ide/src/lib/cmdInterpreterAPI.js b/apps/remix-ide/src/lib/cmdInterpreterAPI.js deleted file mode 100644 index 530846873d..0000000000 --- a/apps/remix-ide/src/lib/cmdInterpreterAPI.js +++ /dev/null @@ -1,135 +0,0 @@ -'use strict' -import { CompilerImports } from '@remix-project/core-plugin' -import Registry from '../app/state/registry' -var yo = require('yo-yo') -var async = require('async') -var EventManager = require('../lib/events') - -var toolTip = require('../app/ui/tooltip') -var GistHandler = require('./gist-handler') - -class CmdInterpreterAPI { - constructor (terminal, blockchain) { - const self = this - self.event = new EventManager() - self.blockchain = blockchain - self._components = {} - self._components.registry = Registry.getInstance() - self._components.terminal = terminal - self._components.fileImport = new CompilerImports() - self._components.gistHandler = new GistHandler() - self._deps = { - fileManager: self._components.registry.get('filemanager').api, - editor: self._components.registry.get('editor').api, - compilersArtefacts: self._components.registry.get('compilersartefacts').api, - offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api - } - self.commandHelp = { - 'remix.loadgist(id)': 'Load a gist in the file explorer.', - 'remix.loadurl(url)': 'Load the given url in the file explorer. The url can be of type github, swarm, ipfs or raw http', - 'remix.execute(filepath)': 'Run the script specified by file path. If filepath is empty, script currently displayed in the editor is executed.', - 'remix.exeCurrent()': 'Run the script currently displayed in the editor', - 'remix.help()': 'Display this help message' - } - } - - log () { arguments[0] != null ? this._components.terminal.commands.html(arguments[0]) : this._components.terminal.commands.html(arguments[1]) } - loadgist (id, cb) { - const self = this - self._components.gistHandler.loadFromGist({ gist: id }, this._deps.fileManager) - if (cb) cb() - } - - loadurl (url, cb) { - const self = this - self._components.fileImport.import(url, - (loadingMsg) => { toolTip(loadingMsg) }, - (err, content, cleanUrl, type, url) => { - if (err) { - toolTip(`Unable to load ${url}: ${err}`) - if (cb) cb(err) - } else { - self._deps.fileManager.writeFile(type + '/' + cleanUrl, content) - try { - content = JSON.parse(content) - async.eachOfSeries(content.sources, (value, file, callbackSource) => { - var url = value.urls[0] // @TODO retrieve all other contents ? - self._components.fileImport.import(url, - (loadingMsg) => { toolTip(loadingMsg) }, - async (error, content, cleanUrl, type, url) => { - if (error) { - toolTip(`Cannot retrieve the content of ${url}: ${error}`) - return callbackSource(`Cannot retrieve the content of ${url}: ${error}`) - } else { - try { - await self._deps.fileManager.writeFile(type + '/' + cleanUrl, content) - callbackSource() - } catch (e) { - callbackSource(e.message) - } - } - }) - }, (error) => { - if (cb) cb(error) - }) - } catch (e) {} - if (cb) cb() - } - }) - } - - exeCurrent (cb) { - return this.execute(undefined, cb) - } - - execute (file, cb) { - const self = this - - function _execute (content, cb) { - if (!content) { - toolTip('no content to execute') - if (cb) cb() - return - } - self._components.terminal.commands.script(content) - } - - if (typeof file === 'undefined') { - var content = self._deps.editor.currentContent() - _execute(content, cb) - return - } - - var provider = self._deps.fileManager.fileProviderOf(file) - - if (!provider) { - toolTip(`provider for path ${file} not found`) - if (cb) cb() - return - } - - provider.get(file, (error, content) => { - if (error) { - toolTip(error) - if (cb) cb() - return - } - - _execute(content, cb) - }) - } - - help (cb) { - const self = this - var help = yo`
    ` - for (var k in self.commandHelp) { - help.appendChild(yo`
    ${k}: ${self.commandHelp[k]}
    `) - help.appendChild(yo`
    `) - } - self._components.terminal.commands.html(help) - if (cb) cb() - return '' - } -} - -module.exports = CmdInterpreterAPI diff --git a/apps/remix-ide/src/lib/gist-handler.js b/apps/remix-ide/src/lib/gist-handler.js deleted file mode 100644 index da881e1103..0000000000 --- a/apps/remix-ide/src/lib/gist-handler.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict' -var modalDialogCustom = require('../app/ui/modal-dialog-custom') -var request = require('request') - -// Allowing window to be overriden for testing -function GistHandler (_window) { - if (_window !== undefined) { - modalDialogCustom = _window - } - - this.handleLoad = function (params, cb) { - if (!cb) cb = () => {} - var loadingFromGist = false - var gistId - if (params.gist === '') { - loadingFromGist = true - modalDialogCustom.prompt('Load a Gist', 'Enter the ID of the Gist or URL you would like to load.', null, (target) => { - if (target !== '') { - gistId = getGistId(target) - if (gistId) { - cb(gistId) - } else { - modalDialogCustom.alert('Gist load error', 'Error while loading gist. Please provide a valid Gist ID or URL.') - } - } - }) - return loadingFromGist - } else { - gistId = params.gist - loadingFromGist = !!gistId - } - if (loadingFromGist) { - cb(gistId) - } - return loadingFromGist - } - - function getGistId (str) { - var idr = /[0-9A-Fa-f]{8,}/ - var match = idr.exec(str) - return match ? match[0] : null - } - - this.loadFromGist = (params, fileManager) => { - const self = this - return self.handleLoad(params, function (gistId) { - request.get({ - url: `https://api.github.com/gists/${gistId}`, - json: true - }, async (error, response, data = {}) => { - if (error || !data.files) { - modalDialogCustom.alert('Gist load error', error || data.message) - return - } - const obj = {} - Object.keys(data.files).forEach((element) => { - const path = element.replace(/\.\.\./g, '/') - - obj['/' + 'gist-' + gistId + '/' + path] = data.files[element] - }) - fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => { - if (!errorLoadingFile) { - const provider = fileManager.getProvider('workspace') - provider.lastLoadedGistId = gistId - } else { - modalDialogCustom.alert('Gist load error', errorLoadingFile.message || errorLoadingFile) - } - }) - }) - }) - } -} - -module.exports = GistHandler diff --git a/apps/remix-ide/src/lib/panels-resize.js b/apps/remix-ide/src/lib/panels-resize.js deleted file mode 100644 index 157535a869..0000000000 --- a/apps/remix-ide/src/lib/panels-resize.js +++ /dev/null @@ -1,88 +0,0 @@ -const yo = require('yo-yo') -const csjs = require('csjs-inject') - -const css = csjs` - .dragbar { - width : 2px; - height : 100%; - cursor : col-resize; - z-index : 999; - } - .ghostbar { - width : 3px; - background-color : var(--primary); - opacity : 0.5; - position : absolute; - cursor : col-resize; - z-index : 9999; - top : 0; - bottom : 0; - } -` - -export default class PanelsResize { - constructor (panel) { - this.panel = panel - const string = panel.style.minWidth - this.minWidth = string.length > 2 ? parseInt(string.substring(0, string.length - 2)) : 0 - } - - render () { - this.ghostbar = yo`
    ` - - const mousedown = (event) => { - event.preventDefault() - if (event.which === 1) { - moveGhostbar(event) - document.body.appendChild(this.ghostbar) - document.addEventListener('mousemove', moveGhostbar) - document.addEventListener('mouseup', removeGhostbar) - document.addEventListener('keydown', cancelGhostbar) - } - } - - const cancelGhostbar = (event) => { - if (event.keyCode === 27) { - document.body.removeChild(this.ghostbar) - document.removeEventListener('mousemove', moveGhostbar) - document.removeEventListener('mouseup', removeGhostbar) - document.removeEventListener('keydown', cancelGhostbar) - } - } - - const moveGhostbar = (event) => { - this.ghostbar.style.left = event.x + 'px' - } - - const removeGhostbar = (event) => { - document.body.removeChild(this.ghostbar) - document.removeEventListener('mousemove', moveGhostbar) - document.removeEventListener('mouseup', removeGhostbar) - document.removeEventListener('keydown', cancelGhostbar) - this.setPosition(event) - } - - return yo`
    ` - } - - calculatePanelWidth (event) { - return event.x - this.panel.offsetLeft - } - - setPosition (event) { - const panelWidth = this.calculatePanelWidth(event) - // close the panel if the width is less than a minWidth - if (panelWidth > this.minWidth - 10 || this.panel.style.display === 'none') { - this.panel.style.width = panelWidth + 'px' - this.showPanel() - } else this.hidePanel() - } - - hidePanel () { - this.panel.style.display = 'none' - } - - showPanel () { - this.panel.style.display = 'flex' - } -} diff --git a/apps/remix-ide/src/lib/publishOnIpfs.js b/apps/remix-ide/src/lib/publishOnIpfs.js deleted file mode 100644 index 07f8256540..0000000000 --- a/apps/remix-ide/src/lib/publishOnIpfs.js +++ /dev/null @@ -1,121 +0,0 @@ -'use strict' - -const async = require('async') -const IpfsClient = require('ipfs-mini') - -const ipfsNodes = [ - new IpfsClient({ host: 'ipfs.remixproject.org', port: 443, protocol: 'https' }), - new IpfsClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' }), - new IpfsClient({ host: '127.0.0.1', port: 5001, protocol: 'http' }) -] - -module.exports = (contract, fileManager, cb, ipfsVerifiedPublishCallBack) => { - // gather list of files to publish - var sources = [] - - var metadata - - try { - metadata = JSON.parse(contract.metadata) - } catch (e) { - return cb(e) - } - - if (metadata === undefined) { - return cb('No metadata') - } - - async.eachSeries(Object.keys(metadata.sources), function (fileName, cb) { - // find hash - let hash = null - try { - // we try extract the hash defined in the metadata.json - // in order to check if the hash that we get after publishing is the same as the one located in metadata.json - // if it's not the same, we throw "hash mismatch between solidity bytecode and uploaded content" - // if we don't find the hash in the metadata.json, the check is not done. - // - // TODO: refactor this with publishOnSwarm - if (metadata.sources[fileName].urls) { - metadata.sources[fileName].urls.forEach(url => { - if (url.includes('ipfs')) hash = url.match('dweb:/ipfs/(.+)')[1] - }) - } - } catch (e) { - return cb('Error while extracting the hash from metadata.json') - } - - fileManager.fileProviderOf(fileName).get(fileName, (error, content) => { - if (error) { - console.log(error) - } else { - sources.push({ - content: content, - hash: hash, - filename: fileName - }) - } - cb() - }) - }, function (error) { - if (error) { - cb(error) - } else { - // publish the list of sources in order, fail if any failed - var uploaded = [] - async.eachSeries(sources, function (item, cb) { - ipfsVerifiedPublish(item.content, item.hash, (error, result) => { - try { - item.hash = result.url.match('dweb:/ipfs/(.+)')[1] - } catch (e) { - item.hash = ' - ' + item.fileName - } - if (!error && ipfsVerifiedPublishCallBack) ipfsVerifiedPublishCallBack(item) - item.output = result - uploaded.push(item) - cb(error) - }) - }, () => { - const metadataContent = JSON.stringify(metadata) - ipfsVerifiedPublish(metadataContent, '', (error, result) => { - try { - contract.metadataHash = result.url.match('dweb:/ipfs/(.+)')[1] - } catch (e) { - contract.metadataHash = ' - metadata.json' - } - if (!error && ipfsVerifiedPublishCallBack) { - ipfsVerifiedPublishCallBack({ - content: metadataContent, - hash: contract.metadataHash - }) - } - uploaded.push({ - content: contract.metadata, - hash: contract.metadataHash, - filename: 'metadata.json', - output: result - }) - cb(error, uploaded) - }) - }) - } - }) -} - -async function ipfsVerifiedPublish (content, expectedHash, cb) { - try { - const results = await severalGatewaysPush(content) - if (expectedHash && results !== expectedHash) { - cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + results, hash: results }) - } else { - cb(null, { message: 'ok', url: 'dweb:/ipfs/' + results, hash: results }) - } - } catch (error) { - cb(error) - } -} - -function severalGatewaysPush (content) { - const invert = p => new Promise((resolve, reject) => p.then(reject).catch(resolve)) // Invert res and rej - const promises = ipfsNodes.map((node) => invert(node.add(content))) - return invert(Promise.all(promises)) -} diff --git a/apps/remix-ide/src/lib/publishOnSwarm.js b/apps/remix-ide/src/lib/publishOnSwarm.js deleted file mode 100644 index 10fd618cf6..0000000000 --- a/apps/remix-ide/src/lib/publishOnSwarm.js +++ /dev/null @@ -1,109 +0,0 @@ -'use strict' - -var async = require('async') -var swarmgw = require('swarmgw')() - -module.exports = (contract, fileManager, cb, swarmVerifiedPublishCallBack) => { - // gather list of files to publish - var sources = [] - - var metadata - try { - metadata = JSON.parse(contract.metadata) - } catch (e) { - return cb(e) - } - - if (metadata === undefined) { - return cb('No metadata') - } - - async.eachSeries(Object.keys(metadata.sources), function (fileName, cb) { - // find hash - let hash = null - try { - // we try extract the hash defined in the metadata.json - // in order to check if the hash that we get after publishing is the same as the one located in metadata.json - // if it's not the same, we throw "hash mismatch between solidity bytecode and uploaded content" - // if we don't find the hash in the metadata.json, the check is not done. - // - // TODO: refactor this with publishOnIpfs - if (metadata.sources[fileName].urls) { - metadata.sources[fileName].urls.forEach(url => { - if (url.includes('bzz')) hash = url.match('(bzzr|bzz-raw)://(.+)')[1] - }) - } - } catch (e) { - return cb('Error while extracting the hash from metadata.json') - } - - fileManager.fileProviderOf(fileName).get(fileName, (error, content) => { - if (error) { - console.log(error) - } else { - sources.push({ - content: content, - hash: hash, - filename: fileName - }) - } - cb() - }) - }, function (error) { - if (error) { - cb(error) - } else { - // publish the list of sources in order, fail if any failed - var uploaded = [] - async.eachSeries(sources, function (item, cb) { - swarmVerifiedPublish(item.content, item.hash, (error, result) => { - try { - item.hash = result.url.match('bzz-raw://(.+)')[1] - } catch (e) { - item.hash = ' - ' + item.fileName - } - if (!error && swarmVerifiedPublishCallBack) swarmVerifiedPublishCallBack(item) - item.output = result - uploaded.push(item) - // TODO this is a fix cause Solidity metadata does not contain the right swarm hash (poc 0.3) - metadata.sources[item.filename].urls[0] = result.url - cb(error) - }) - }, () => { - const metadataContent = JSON.stringify(metadata) - swarmVerifiedPublish(metadataContent, '', (error, result) => { - try { - contract.metadataHash = result.url.match('bzz-raw://(.+)')[1] - } catch (e) { - contract.metadataHash = ' - metadata.json' - } - if (!error && swarmVerifiedPublishCallBack) { - swarmVerifiedPublishCallBack({ - content: metadataContent, - hash: contract.metadataHash - }) - } - uploaded.push({ - content: contract.metadata, - hash: contract.metadataHash, - filename: 'metadata.json', - output: result - }) - cb(error, uploaded) - }) - }) - } - }) -} - -function swarmVerifiedPublish (content, expectedHash, cb) { - swarmgw.put(content, function (err, ret) { - if (err) { - cb(err) - } else if (expectedHash && ret !== expectedHash) { - cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + ret, hash: ret }) - } else { - cb(null, { message: 'ok', url: 'bzz-raw://' + ret, hash: ret }) - } - }) -} diff --git a/apps/remix-ide/src/lib/remixd.js b/apps/remix-ide/src/lib/remixd.js deleted file mode 100644 index 2555ccef0b..0000000000 --- a/apps/remix-ide/src/lib/remixd.js +++ /dev/null @@ -1,145 +0,0 @@ -'use strict' -var EventManager = require('../lib/events') -var modalDialog = require('../app/ui/modaldialog') -var yo = require('yo-yo') - -class Remixd { - constructor (port) { - this.event = new EventManager() - this.port = port - this.callbacks = {} - this.callid = 0 - this.socket = null - this.connected = false - this.receiveResponse() - } - - online () { - return this.socket !== null - } - - close () { - if (this.socket) { - this.socket.close() - this.socket = null - } - } - - start (cb) { - if (this.socket) { - try { - this.socket.close() - } catch (e) {} - } - this.event.trigger('connecting', []) - this.socket = new WebSocket('ws://localhost:' + this.port, 'echo-protocol') // eslint-disable-line - - this.socket.addEventListener('open', (event) => { - this.connected = true - this.event.trigger('connected', [event]) - cb() - }) - - this.socket.addEventListener('message', (event) => { - var data = JSON.parse(event.data) - if (data.type === 'reply') { - if (this.callbacks[data.id]) { - this.callbacks[data.id](data.error, data.result) - delete this.callbacks[data.id] - } - this.event.trigger('replied', [data]) - } else if (data.type === 'notification') { - this.event.trigger('notified', [data]) - } else if (data.type === 'system') { - if (data.error) { - this.event.trigger('system', [{ - error: data.error - }]) - } - } - }) - - this.socket.addEventListener('error', (event) => { - this.errored(event) - cb(event) - }) - - this.socket.addEventListener('close', (event) => { - if (event.wasClean) { - this.connected = false - this.event.trigger('closed', [event]) - } else { - this.errored(event) - } - this.socket = null - }) - } - - async receiveResponse (requestId) { - return new Promise((resolve, reject) => { - this.event.register('replied', (data) => { - if (data.id === requestId) { - if (data.error) reject(data.error) - else resolve(data.result) - } - }) - }) - } - - errored (event) { - function remixdDialog () { - return yo`
    Connection to Remixd closed. Localhost connection not available anymore.
    ` - } - if (this.connected) { - modalDialog('Lost connection to Remixd!', remixdDialog(), {}, { label: '' }) - } - this.connected = false - this.socket = null - this.event.trigger('errored', [event]) - } - - call (service, fn, args, callback) { - return new Promise((resolve, reject) => { - this.ensureSocket((error) => { - if (error) { - callback && typeof callback === 'function' && callback(error) - reject(error) - return - } - if (this.socket && this.socket.readyState === this.socket.OPEN) { - var data = this.format(service, fn, args) - this.callbacks[data.id] = callback - this.socket.send(JSON.stringify(data)) - resolve(data.id) - } else { - callback && typeof callback === 'function' && callback('Socket not ready. state:' + this.socket.readyState) - reject(new Error('Socket not ready. state:' + this.socket.readyState)) - } - }) - }) - } - - ensureSocket (cb) { - if (this.socket) return cb(null, this.socket) - this.start((error) => { - if (error) { - cb(error) - } else { - cb(null, this.socket) - } - }) - } - - format (service, fn, args) { - var data = { - id: this.callid, - service: service, - fn: fn, - args: args - } - this.callid++ - return data - } -} - -module.exports = Remixd diff --git a/apps/remix-ide/src/migrateFileSystem.js b/apps/remix-ide/src/migrateFileSystem.js deleted file mode 100644 index 94ef8c0b7d..0000000000 --- a/apps/remix-ide/src/migrateFileSystem.js +++ /dev/null @@ -1,64 +0,0 @@ -import { Storage } from '@remix-project/remix-lib' -import { joinPath } from './lib/helper' -import yo from 'yo-yo' -const modalDialogCustom = require('./app/ui/modal-dialog-custom') -/* - Migrating the files to the BrowserFS storage instead or raw localstorage -*/ -export default (fileProvider) => { - const fileStorage = new Storage('sol:') - const flag = 'status' - const fileStorageBrowserFS = new Storage('remix_browserFS_migration:') - if (fileStorageBrowserFS.get(flag) === 'done') return - fileStorage.keys().forEach((path) => { - if (path !== '.remix.config') { - const content = fileStorage.get(path) - fileProvider.set(path, content) - // TODO https://github.com/ethereum/remix-ide/issues/2377 - // fileStorage.remove(path) we don't want to remove it as we are still supporting the old version - } - }) - fileStorageBrowserFS.set(flag, 'done') -} - -export async function migrateToWorkspace (fileManager, filePanel) { - const browserProvider = fileManager.getProvider('browser') - const workspaceProvider = fileManager.getProvider('workspace') - const files = await browserProvider.copyFolderToJson('/') - - if (Object.keys(files).length === 0) { - // we don't have any root file, only .workspaces - // don't need to create a workspace - throw new Error('No file to migrate') - } - - if (Object.keys(files).length === 1 && files['/.workspaces']) { - // we don't have any root file, only .workspaces - // don't need to create a workspace - throw new Error('No file to migrate') - } - - const workspaceName = 'workspace_migrated_' + Date.now() - await filePanel.processCreateWorkspace(workspaceName) - filePanel.getWorkspaces() // refresh list - const workspacePath = joinPath('browser', workspaceProvider.workspacesPath, workspaceName) - await populateWorkspace(workspacePath, files, browserProvider) - return workspaceName -} - -const populateWorkspace = async (workspace, json, browserProvider) => { - for (const item in json) { - const isFolder = json[item].content === undefined - if (isFolder && item === '/.workspaces') continue // we don't want to replicate this one. - if (isFolder) { - browserProvider.createDir(joinPath(workspace, item)) - await populateWorkspace(workspace, json[item].children, browserProvider) - } else { - await browserProvider.set(joinPath(workspace, item), json[item].content, (err) => { - if (err && err.message) { - modalDialogCustom.alert(yo`
    There was an error migrating your files:${err.message}
    Please use the ‘Download all Files' action, clear the local storage and re-import your files manually or use the 'Restore files' action.
    `) - } - }) - } - } -} diff --git a/apps/remix-ide/src/publishToStorage.js b/apps/remix-ide/src/publishToStorage.js deleted file mode 100644 index 5803d9a6a6..0000000000 --- a/apps/remix-ide/src/publishToStorage.js +++ /dev/null @@ -1,47 +0,0 @@ -const yo = require('yo-yo') -const publishOnSwarm = require('./lib/publishOnSwarm') -const publishOnIpfs = require('./lib/publishOnIpfs') -const modalDialogCustom = require('./app/ui/modal-dialog-custom') - -export default function publish (storage, fileProvider, fileManager, contract) { - if (contract) { - if (contract.metadata === undefined || contract.metadata.length === 0) { - modalDialogCustom.alert('This contract may be abstract, may not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.') - } else { - if (storage === 'swarm') { - publishOnSwarm(contract, fileManager, function (err, uploaded) { - if (err) { - try { - err = JSON.stringify(err) - } catch (e) {} - console.log(`Failed to publish metadata file to swarm, please check the Swarm gateways is available ( swarm-gateways.net ) ${err}`) - } else { - var result = yo`
    ${uploaded.map((value) => { - return yo`
    ${value.filename} :
    ${value.output.url}
    ` - })}
    ` - modalDialogCustom.alert(`Published ${contract.name}'s Metadata`, yo`Metadata of "${contract.name.toLowerCase()}" was published successfully.
    ${result}
    `) - } - }, (item) => { // triggered each time there's a new verified publish (means hash correspond) - fileProvider.addExternal('swarm/' + item.hash, item.content) - }) - } else { - publishOnIpfs(contract, fileManager, function (err, uploaded) { - if (err) { - try { - err = JSON.stringify(err) - } catch (e) {} - modalDialogCustom.alert(yo`Failed to publish metadata file to ${storage}, please check the ${storage} gateways is available.
    - ${err}
    `) - } else { - var result = yo`
    ${uploaded.map((value) => { - return yo`
    ${value.filename} :
    ${value.output.url.replace('dweb:/ipfs/', 'ipfs://')}
    ` - })}
    ` - modalDialogCustom.alert(`Published ${contract.name}'s Metadata`, yo`Metadata of "${contract.name.toLowerCase()}" was published successfully.
    ${result}
    `) - } - }, (item) => { // triggered each time there's a new verified publish (means hash correspond) - fileProvider.addExternal('ipfs/' + item.hash, item.content) - }) - } - } - } -} diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 32afcaec9a..5f5fa2f8eb 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -1,20 +1,20 @@ /* global localStorage, fetch */ import { PluginManager } from '@remixproject/engine' -import { IframePlugin } from '@remixproject/engine-web' import { EventEmitter } from 'events' import QueryParams from './lib/query-params' import { PermissionHandler } from './app/ui/persmission-handler' +import { IframePlugin } from '@remixproject/engine-web' const _paq = window._paq = window._paq || [] const requiredModules = [ // services + layout views + system views 'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', - 'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic'] + 'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout', 'modal'] const dependentModules = ['git', 'hardhat', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd) export function isNative (name) { - const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting'] + const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'modal'] return nativePlugins.includes(name) || requiredModules.includes(name) } @@ -78,6 +78,7 @@ export class RemixAppManager extends PluginManager { onPluginActivated (plugin) { this.pluginLoader.set(plugin, this.actives) this.event.emit('activate', plugin) + this.emit('activate', plugin) if (!requiredModules.includes(plugin.name)) _paq.push(['trackEvent', 'pluginManager', 'activate', plugin.name]) } @@ -131,6 +132,7 @@ export class RemixAppManager extends PluginManager { } return plugins.map(plugin => { return new IframePlugin(plugin) + // return new IframeReactPlugin(plugin) }) } diff --git a/apps/remix-ide/src/universal-dapp-styles.js b/apps/remix-ide/src/universal-dapp-styles.js deleted file mode 100644 index 4ae5034a5c..0000000000 --- a/apps/remix-ide/src/universal-dapp-styles.js +++ /dev/null @@ -1,285 +0,0 @@ -const csjs = require('csjs-inject') - -var css = csjs` - .instanceTitleContainer { - display: flex; - align-items: center; - } - .calldataInput{ - height: 32px; - } - .title { - display: flex; - justify-content: space-between; - font-size: 11px; - width: 100%; - overflow: hidden; - word-break: break-word; - line-height: initial; - overflow: visible; - padding: 0 0 8px; - margin: 0; - background: none; - border: none; - } - .title button { - background: none; - border: none; - } - .titleLine { - display: flex; - align-items: baseline; - } - .titleText { - word-break: break-word; - width: 100%; - border: none; - overflow: hidden; - } - .spanTitleText { - line-height: 12px; - padding: 0; - font-size: 11px; - width:100%; - border: none; - background: none; - text-transform: uppercase; - overflow: hidden; - } - .inputGroupText { - width: 100%; - } - .title .copy { - color: var(--primary); - } - .titleExpander { - padding: 5px 7px; - } - .nameNbuts { - display: contents; - flex-wrap: nowrap; - width: 100%; - } - .instance { - display: block; - flex-direction: column; - margin-bottom: 12px; - background: none; - border-radius: 2px; - } - .instance.hidesub { - border-bottom: 1px solid; - } - .instance.hidesub .title { - display: flex; - } - .instance.hidesub .udappClose { - display: flex; - } - .instance.hidesub > * { - display: none; - } - .methCaret { - min-width: 12px; - width: 12px; - margin-left: 4px; - cursor: pointer; - font-size: 16px; - line-height: 0.6; - vertical-align: middle; - padding: 0; - } - .cActionsWrapper { - border-top-left-radius: 0; - border-bottom-left-radius: 0.25rem; - border-top-rightt-radius: 0; - border-bottom-right-radius: 0.25rem; - padding: 8px 10px 7px; - } - .group:after { - content: ""; - display: table; - clear: both; - } - .buttonsContainer { - margin-top: 2%; - display: flex; - overflow: hidden; - } - .instanceButton { - height: 32px; - border-radius: 3px; - white-space: nowrap; - font-size: 11px; - overflow: hidden; - text-overflow: ellipsis; - } - .closeIcon { - font-size: 12px; - cursor: pointer; - margin-left: 5px; - } - .udappClose { - display: flex; - justify-content: flex-end; - } - .contractProperty { - width:100%; - } - .contractProperty.hasArgs input { - padding: .36em; - border-radius: 5px; - } - .contractProperty .contractActionsContainerSingle input{ - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - .contractProperty button { - min-width: 100px; - width: 100px; - margin:0; - word-break: inherit; - } - .contractProperty button:disabled { - cursor: not-allowed; - background-color: white; - border-color: lightgray; - } - .contractProperty.constant button { - min-width: 100px; - width: 100px; - margin:0; - word-break: inherit; - outline: none; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .contractProperty > .value { - box-sizing: border-box; - float: left; - align-self: center; - margin-left: 4px; - } - .contractActionsContainer { - width: 100%; - margin-bottom: 8px; - } - .contractActionsContainerSingle { - display: flex; - width: 100%; - } - .contractActionsContainerSingle i { - line-height: 2; - } - .contractActionsContainerMulti { - display:none; - width: 100%; - } - .contractActionsContainerMultiInner { - width: 100%; - padding: 16px 8px 16px 14px; - border-radius: 3px; - margin-bottom: 8px; - } - .multiHeader { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; - text-align: left; - font-size: 10px; - font-weight: bold; - } - .contractActionsContainerMultiInner .multiTitle { - padding-left: 10px; - } - .contractProperty .multiTitle { - padding: 0; - line-height: 16px; - display: inline-block; - font-size: 12px; - font-weight: bold; - cursor: default; - } - .contractProperty .contractActionsContainerMultiInner .multiArg label{ - text-align: right; - } - .multiHeader .methCaret { - float: right; - margin-right: 0; - } - .contractProperty.constant .multiTitle { - display: inline-block; - width: 90%; - /* font-size: 10px; */ - height: 25px; - padding-left: 20px; - font-weight: bold; - line-height: 25px; - cursor: default; - } - .multiArg { - display: flex; - align-items: center; - justify-content: flex-end; - margin-top: 4px; - } - .multiArg input{ - padding: 5px; - } - .multiArg label { - width: auto; - padding: 0; - margin: 0 4px 0 0; - font-size: 10px; - line-height: 12px; - text-align: right; - word-break: initial; - } - .multiArg button { - max-width: 100px; - border-radius: 3px; - border-width: 1px; - width: inherit; - } - .multiHeader button { - display: inline-block; - width: 94%; - } - .hasArgs .multiArg input { - border-left: 1px solid #dddddd; - width: 67%; - } - .hasArgs input { - display: block; - height: 32px; - border: 1px solid #dddddd; - padding: .36em; - border-left: none; - padding: 8px 8px 8px 10px; - font-size: 10px !important; - } - .hasArgs button { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-right: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: 11px; - } - .hasArgs .contractActionsContainerMulti button { - border-radius: 3px; - } - .contractActionsContainerMultiInner .multiArg i { - padding-right: 10px; - } - .hideWarningsContainer { - display: flex; - align-items: center; - margin-left: 2% - } -` - -module.exports = css diff --git a/apps/remix-ide/test/compiler-test.js b/apps/remix-ide/test/compiler-test.js deleted file mode 100644 index 5af70fd14f..0000000000 --- a/apps/remix-ide/test/compiler-test.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' - -var test = require('tape') - -var Compiler = require('@remix-project/remix-solidity').Compiler - -test('compiler.compile smoke', function (t) { - t.plan(1) - - var noop = function () {} - var fakeImport = function (url, cb) { cb('Not implemented') } - var compiler = new Compiler(fakeImport) - compiler.compileJSON = noop - compiler.compile({ 'test': '' }, 'test') - t.ok(compiler) -}) diff --git a/apps/remix-ide/test/gist-handler-test.js b/apps/remix-ide/test/gist-handler-test.js deleted file mode 100644 index 07eaad830c..0000000000 --- a/apps/remix-ide/test/gist-handler-test.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict' -var modalDialogCustom -if (typeof window !== 'undefined') { - modalDialogCustom = require('../app/ui/modal-dialog-custom') -} -// ^ this class can be load in a non browser context when running node unit testing. -// should not load UI in that case - -// Allowing window to be overriden for testing -function GistHandler (_window) { - if (_window !== undefined) { - modalDialogCustom = _window - } - - this.handleLoad = function (params, cb) { - if (!cb) cb = () => {} - var loadingFromGist = false - var gistId - if (params['gist'] === '') { - loadingFromGist = true - modalDialogCustom.prompt( - 'Load a Gist', - 'Enter the URL or ID of the Gist you would like to load.', - null, - target => { - if (target !== '') { - gistId = getGistId(target) - if (gistId) { - cb(gistId) - } - } - } - ) - return loadingFromGist - } else { - gistId = params['gist'] - loadingFromGist = !!gistId - } - if (loadingFromGist) { - cb(gistId) - } - return loadingFromGist - } - - function getGistId (str) { - var idr = /[0-9A-Fa-f]{8,}/ - var match = idr.exec(str) - return match ? match[0] : null - } -} - -module.exports = GistHandler diff --git a/apps/remix-ide/test/index.js b/apps/remix-ide/test/index.js deleted file mode 100644 index 84d8a5c68f..0000000000 --- a/apps/remix-ide/test/index.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict' - -require('./compiler-test') -require('./gist-handler-test') -require('./query-params-test') diff --git a/apps/remix-ide/test/query-params-test.js b/apps/remix-ide/test/query-params-test.js deleted file mode 100644 index c9062f6da0..0000000000 --- a/apps/remix-ide/test/query-params-test.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict' - -var test = require('tape') - -var QueryParams = require('../src/lib/query-params') - -test('queryParams.get', function (t) { - t.plan(2) - - var fakeWindow = {location: {hash: '#wat=sup&foo=bar', search: ''}} - var params = new QueryParams(fakeWindow).get() - t.equal(params.wat, 'sup') - t.equal(params.foo, 'bar') -}) - -test('queryParams.update', function (t) { - t.plan(1) - - var fakeWindow = {location: {hash: '#wat=sup', search: ''}} - var qp = new QueryParams(fakeWindow) - qp.update({foo: 'bar'}) - t.equal(fakeWindow.location.hash, '#wat=sup&foo=bar') -}) diff --git a/apps/remix-ide/tsconfig.json b/apps/remix-ide/tsconfig.json index 052537730e..04d219a926 100644 --- a/apps/remix-ide/tsconfig.json +++ b/apps/remix-ide/tsconfig.json @@ -4,6 +4,7 @@ "jsx": "react", "allowJs": true, "esModuleInterop": true, + "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "types": ["node", "jest"], "module": "es6", diff --git a/best-practices.md b/best-practices.md deleted file mode 100644 index 3a21907978..0000000000 --- a/best-practices.md +++ /dev/null @@ -1,153 +0,0 @@ -# Current "Best Practice" Conventions - - -- Coding style is defined in [.eslintrc](https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/.eslintrc). -- `ES6 class` rather than ES5 to create class. -- CSS declaration using `csjs-inject`. -- CSS files: - -  **if** the CSS section of an UI component is too important, CSS declarations should be put in a different file and in a different folder. - - The folder should be named `styles` and the file should be named with the extension `-styles.css`. - -  e.g: `file-explorer.js` being an UI component `file-explorer-styles.css` is created in the `styles` folder right under `file-explorer.js` - - **if** the CSS section of an UI component is rather limited it is preferable to put it in the corresponding JS file. - -- HTML declaration using `yo-yo`. - -- A module trigger events using `event` property: -  `self.event = new EventManager()`. - Events can then be triggered: -  `self.event.trigger('eventName', [param1, param2])` -- `self._view` is the HTML view renderered by `yo-yo` in the `render` function. -- `render()` this function should be called at the first rendering (make sure that the returned node element is put on the DOM), and should *not* by called again from outside the component. -- `update()` call this function to update the DOM when the state of the component has changed (this function must be called after the initial call to `render()`). -- for all functions / properties, prefixing by underscore (`_`) means the scope is private, and they should **not** be accessed not changed from outside the component. -- constructor arguments: There is no fixed rule whether it is preferrable to use multiples arguments or a single option *{}* argument (or both). - We recommend: - - use a specific slot for **obligatory** arguments and/or for complex arguments (meaning not boolean, not string, etc...). - - put arguments in an option *{}* for non critical and for optionnal arguments. - - if a component has more than 4/5 parameters, it is recommended to find a way to group some in one or more *opt* arguments. - -- look them up, discuss them, update them. -     -## Module Definition (example) -```js -// user-card.js -var yo = require('yo-yo') -var csjs = require('csjs-inject') -var EventManager = require('remix-lib').EventManager - -var css = csjs` - .userCard { - position : relative; - box-sizing : border-box; - display : flex; - flex-direction : column; - align-items : center; - border : 1px solid black; - width : 400px; - padding : 50px; - } - .clearFunds { background-color: lightblue; } -` - -class UserCard { - constructor (api, events, opts = {}) { - var self = this - - self.event = new EventManager() - self.opts = opts - self._api = api - self._consumedEvents = events - self._view = undefined - - events.funds.register('fundsChanged', function (amount) { - if (amount < self.state._funds) self.state.totalSpend += self.state._funds - amount - self.state._funds = amount - self.render() - }) - self.event.trigger('eventName', [param1, param2]) - } - render () { - var self = this - var view = yo` -
    -

    @${self.state._nickname}

    -

    Welcome, ${self.state.title || ''} ${self.state.name || 'anonymous'} ${self.state.surname}

    -
    • User Funds: $${self.state._funds}
    -
    • Spent Funds: $${self.state.totalSpend}
    - -
    - ` - if (!self._view) { - self._view = view - } - return self._view - } - update () { - yo.update(this._view, this.render()) - } - setNickname (name) { - this._nickname = name - } - getNickname () { - var self = this - return `@${self.state._nickname}` - } - getFullName () { - var self = this - return `${self.state.title} ${self.state.name} ${self.state.surname}` - } - _spendAll (event) { - var self = this - self._appAPI.clearUserFunds() - } - _constraint (msg) { throw new Error(msg) } -} - -module.exports = UserCard -``` -## Module Usage (example) -```js -/*****************************************************************************/ -// 1. SETUP CONTEXT -var EventManager = require('remix-lib').EventManager -var funds = { event: new EventManager() } -var userfunds = 15 -function getUserFunds () { return userfunds } -function clearUserFunds () { - var spent = userfunds - userfunds = 0 - console.log(`all funds of ${usercard.getFullName()} were spent.`) - funds.event.trigger('fundsChanged', [userfunds]) - return spent -} -setInterval(function () { - userfunds++ - funds.event.trigger('fundsChanged', [userfunds]) -}, 100) - -/*****************************************************************************/ -// 2. EXAMPLE USAGE -var UserCard = require('./user-card') - -var usercard = new UserCard( - { getUserFunds, clearUserFunds }, - { funds: funds.event }, - { - title: 'Dr.', - name: 'John', - surname: 'Doe', - nickname: 'johndoe99' - }) - -var el = usercard.render() -document.body.appendChild(el) -setTimeout(function () { - userCard.setNickname('new name') - usercard.update() -}, 5000) - -``` diff --git a/libs/remix-core-plugin/src/index.ts b/libs/remix-core-plugin/src/index.ts index fe8a5c661e..b91e6b76a6 100644 --- a/libs/remix-core-plugin/src/index.ts +++ b/libs/remix-core-plugin/src/index.ts @@ -4,3 +4,4 @@ export { FetchAndCompile } from './lib/compiler-fetch-and-compile' export { CompilerImports } from './lib/compiler-content-imports' export { CompilerArtefacts } from './lib/compiler-artefacts' export { EditorContextListener } from './lib/editor-context-listener' +export { GistHandler } from './lib/gist-handler' diff --git a/libs/remix-core-plugin/src/lib/compiler-fetch-and-compile.ts b/libs/remix-core-plugin/src/lib/compiler-fetch-and-compile.ts index b182b7d54c..706937da7a 100644 --- a/libs/remix-core-plugin/src/lib/compiler-fetch-and-compile.ts +++ b/libs/remix-core-plugin/src/lib/compiler-fetch-and-compile.ts @@ -69,7 +69,7 @@ export class FetchAndCompile extends Plugin { let data try { - data = await this.call('source-verification', 'fetchByNetwork', contractAddress, network.id) + data = await this.call('sourcify', 'fetchByNetwork', contractAddress, network.id) } catch (e) { setTimeout(_ => this.emit('notFound', contractAddress), 0) // plugin framework returns a time out error although it actually didn't find the source... this.unresolvedAddresses.push(contractAddress) diff --git a/libs/remix-core-plugin/src/lib/gist-handler.ts b/libs/remix-core-plugin/src/lib/gist-handler.ts new file mode 100644 index 0000000000..6c22c29aa8 --- /dev/null +++ b/libs/remix-core-plugin/src/lib/gist-handler.ts @@ -0,0 +1,138 @@ +/* global fetch */ +'use strict' +import { Plugin } from '@remixproject/engine' + +interface StringByString { + [key: string]: string; +} + +const profile = { + name: 'gistHandler', + methods: ['load'], + events: [], + version: '0.0.1' +} + +export class GistHandler extends Plugin { + constructor () { + super(profile) + } + + async handleLoad (gistId: String | null, cb: Function) { + if (!cb) cb = () => {} + + var loadingFromGist = false + if (!gistId) { + loadingFromGist = true + let value + try { + value = await (() => { + return new Promise((resolve, reject) => { + const modalContent = { + id: 'gisthandler', + title: 'Load a Gist', + message: 'Enter the ID of the Gist or URL you would like to load.', + modalType: 'prompt', + okLabel: 'OK', + cancelLabel: 'Cancel', + okFn: (value) => { + setTimeout(() => resolve(value), 0) + }, + cancelFn: () => { + setTimeout(() => reject(new Error('Canceled')), 0) + }, + hideFn: () => { + setTimeout(() => reject(new Error('Hide')), 0) + } + } + this.call('modal', 'modal', modalContent) + }) + })() + } catch (e) { + // the modal has been canceled + return + } + + if (value !== '') { + gistId = getGistId(value) + if (gistId) { + cb(gistId) + } else { + const modalContent = { + id: 'gisthandler', + title: 'Gist load error', + message: 'Error while loading gist. Please provide a valid Gist ID or URL.' + } + this.call('modal', 'alert', modalContent) + } + } else { + const modalContent = { + id: 'gisthandlerEmpty', + title: 'Gist load error', + message: 'Error while loading gist. Id cannot be empty.' + } + this.call('modal', 'alert', modalContent) + } + return loadingFromGist + } else { + loadingFromGist = !!gistId + } + if (loadingFromGist) { + cb(gistId) + } + return loadingFromGist + } + + load (gistId: String | null) { + const self = this + return self.handleLoad(gistId, async (gistId: String | null) => { + let data: any + try { + data = await (await fetch(`https://api.github.com/gists/${gistId}`)).json() as any + if (!data.files) { + const modalContent = { + id: 'gisthandler', + title: 'Gist load error', + message: data.message, + modalType: 'alert', + okLabel: 'OK' + } + await this.call('modal', 'modal', modalContent) + return + } + } catch (e: any) { + const modalContent = { + id: 'gisthandler', + title: 'Gist load error', + message: e.message + + } + await this.call('modal', 'alert', modalContent) + return + } + + const obj: StringByString = {} + Object.keys(data.files).forEach((element) => { + const path = element.replace(/\.\.\./g, '/') + obj['/' + 'gist-' + gistId + '/' + path] = data.files[element] + }) + this.call('fileManager', 'setBatchFiles', obj, 'workspace', true, async (errorSavingFiles: any) => { + if (errorSavingFiles) { + const modalContent = { + id: 'gisthandler', + title: 'Gist load error', + message: errorSavingFiles.message || errorSavingFiles + + } + this.call('modal', 'alert', modalContent) + } + }) + }) + } +} + +const getGistId = (str) => { + var idr = /[0-9A-Fa-f]{8,}/ + var match = idr.exec(str) + return match ? match[0] : null +} diff --git a/libs/remix-ui/app/src/index.ts b/libs/remix-ui/app/src/index.ts index e00682a41b..e706f1ad94 100644 --- a/libs/remix-ui/app/src/index.ts +++ b/libs/remix-ui/app/src/index.ts @@ -3,3 +3,4 @@ export { dispatchModalContext } from './lib/remix-app/context/context' export { ModalProvider } from './lib/remix-app/context/provider' export { AppModal } from './lib/remix-app/interface/index' export { AlertModal } from './lib/remix-app/interface/index' +export { ModalTypes } from './lib/remix-app/types/index' diff --git a/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.css b/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.css index a423dd6605..1330c1179c 100644 --- a/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.css +++ b/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.css @@ -1,26 +1,27 @@ /* dragbar UI */ -.dragbar { - display : block; - height : 100%; - position : absolute; - left: 0px; - top: 0px; - width: 0.3em; - z-index: 9999; - } - - .overlay { - position: absolute; - left: 0; - top: 0; - width: 100vw; - height: 100vh; - display: block; - z-index: 9998; - } - - .dragbar:hover, .dragbar.ondrag{ - background-color: var(--secondary); - cursor:col-resize; - } \ No newline at end of file +.dragbar { + display: block; + height: 100%; + position: absolute; + left: 0px; + top: 0px; + width: 0.3em; + z-index: 9999; +} + +.overlay { + position: absolute; + left: 0; + top: 0; + width: 100vw; + height: 100vh; + display: block; + z-index: 9998; +} + +.dragbar:hover, +.dragbar.ondrag { + background-color: var(--secondary); + cursor: col-resize; +} diff --git a/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx b/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx index 29c4eeda6b..429e49f638 100644 --- a/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx @@ -15,18 +15,23 @@ const DragBar = (props: IRemixDragBarUi) => { const [offset, setOffSet] = useState(0) const nodeRef = React.useRef(null) // fix for strictmode - useEffect(() => { - // arbitrary time out to wait the the UI to be completely done - setTimeout(() => { - setOffSet(props.refObject.current.offsetLeft) - setDragBarPosX(offset + props.refObject.current.offsetWidth) - }, 1000) - }, []) - useEffect(() => { setDragBarPosX(offset + (props.hidden ? 0 : props.refObject.current.offsetWidth)) }, [props.hidden, offset]) + const handleResize = () => { + setOffSet(props.refObject.current.offsetLeft) + setDragBarPosX(props.refObject.current.offsetLeft + props.refObject.current.offsetWidth) + } + + useEffect(() => { + window.addEventListener('resize', handleResize) + // TODO: not a good way to wait on the ref doms element to be rendered of course + setTimeout(() => + handleResize(), 2000) + return () => window.removeEventListener('resize', handleResize) + }, []) + function stopDrag (e: MouseEvent, data: any) { setDragState(false) if (data.x < props.minWidth) { diff --git a/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx b/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx index e86e4ed63d..27d5a7b832 100644 --- a/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useRef, useState } from 'react' import './style/remix-app.css' +import { RemixUIMainPanel } from '@remix-ui/panel' import RemixSplashScreen from './components/splashscreen' import MatomoDialog from './components/modals/matomo' import OriginWarning from './components/modals/origin-warning' @@ -62,6 +63,13 @@ const RemixApp = (props: IRemixAppUi) => { props.app.sidePanel.events.on('showing', () => { setHideSidePanel(false) }) + + props.app.layout.event.on('minimizesidepanel', () => { + // the 'showing' event always fires from sidepanel, so delay this a bit + setTimeout(() => { + setHideSidePanel(true) + }, 1000) + }) } const components = { @@ -75,7 +83,8 @@ const RemixApp = (props: IRemixAppUi) => { settings: props.app.settings, showMatamo: props.app.showMatamo, appManager: props.app.appManager, - modal: props.app.modal + modal: props.app.modal, + layout: props.app.layout } return ( @@ -88,8 +97,9 @@ const RemixApp = (props: IRemixAppUi) => { {components.iconPanel} {components.sidePanel} - {components.mainPanel} - +
    + +
    {components.hiddenPanel} diff --git a/libs/remix-ui/checkbox/.eslintrc b/libs/remix-ui/checkbox/.eslintrc index dae5c6feeb..0d43d424e3 100644 --- a/libs/remix-ui/checkbox/.eslintrc +++ b/libs/remix-ui/checkbox/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx b/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx index a892831d99..88d91d3867 100644 --- a/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx +++ b/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx @@ -36,7 +36,7 @@ export const CopyToClipboard = (props: ICopyToClipboard) => { } const reset = () => { - setTimeout(() => setMessage('Copy'), 500) + setTimeout(() => setMessage(tip), 500) } return ( diff --git a/libs/remix-ui/debugger-ui/.eslintrc b/libs/remix-ui/debugger-ui/.eslintrc index b8666066e9..4d698941a4 100644 --- a/libs/remix-ui/debugger-ui/.eslintrc +++ b/libs/remix-ui/debugger-ui/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/editor/.eslintrc b/libs/remix-ui/editor/.eslintrc index 598257efa0..22eaba3281 100644 --- a/libs/remix-ui/editor/.eslintrc +++ b/libs/remix-ui/editor/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/helper/.eslintrc b/libs/remix-ui/helper/.eslintrc index 1655d72922..7e6538f1ae 100644 --- a/libs/remix-ui/helper/.eslintrc +++ b/libs/remix-ui/helper/.eslintrc @@ -1 +1 @@ -{ "extends": "../../../.eslintrc", "rules": {}, "ignorePatterns": ["!**/*"] } +{ "extends": "../../../.eslintrc.json", "rules": {}, "ignorePatterns": ["!**/*"] } diff --git a/libs/remix-ui/helper/src/lib/remix-ui-helper.ts b/libs/remix-ui/helper/src/lib/remix-ui-helper.ts index 88cd0ed7a6..779709c75c 100644 --- a/libs/remix-ui/helper/src/lib/remix-ui-helper.ts +++ b/libs/remix-ui/helper/src/lib/remix-ui-helper.ts @@ -1,3 +1,5 @@ +import * as ethJSUtil from 'ethereumjs-util' + export const extractNameFromKey = (key: string): string => { if (!key) return const keyPath = key.split('/') @@ -8,6 +10,7 @@ export const extractNameFromKey = (key: string): string => { export const extractParentFromKey = (key: string):string => { if (!key) return const keyPath = key.split('/') + keyPath.pop() return keyPath.join('/') @@ -62,3 +65,32 @@ export const getPathIcon = (path: string) => { ? 'fab fa-ethereum' : path.endsWith('.cairo') ? 'fab fa-ethereum' : 'far fa-file' // TODO: add cairo icon } + +export const isNumeric = (value) => { + return /^\+?(0|[1-9]\d*)$/.test(value) +} + +export const shortenAddress = (address, etherBalance?) => { + const len = address.length + + return address.slice(0, 5) + '...' + address.slice(len - 5, len) + (etherBalance ? ' (' + etherBalance.toString() + ' ether)' : '') +} + +export const addressToString = (address) => { + if (!address) return null + if (typeof address !== 'string') { + address = address.toString('hex') + } + if (address.indexOf('0x') === -1) { + address = '0x' + address + } + return ethJSUtil.toChecksumAddress(address) +} + +export const is0XPrefixed = (value) => { + return value.substr(0, 2) === '0x' +} + +export const isHexadecimal = (value) => { + return /^[0-9a-fA-F]+$/.test(value) && (value.length % 2 === 0) +} diff --git a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx index 13dec9d47d..c130d35a64 100644 --- a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx +++ b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx @@ -136,7 +136,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => { plugin.appManager.activatePlugin('remixd') } const importFromGist = () => { - plugin.gistHandler.loadFromGist({ gist: '' }, fileManager) + plugin.call('gistHandler', 'load', '') plugin.verticalIcons.select('filePanel') } const switchToPreviousVersion = () => { @@ -166,9 +166,9 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => { _paq.push(['trackEvent', 'pluginManager', 'userActivate', 'learnEth']) } const startSourceVerify = async () => { - await plugin.appManager.activatePlugin(['solidity', 'source-verification']) - plugin.verticalIcons.select('source-verification') - _paq.push(['trackEvent', 'pluginManager', 'userActivate', 'source-verification']) + await plugin.appManager.activatePlugin(['solidity', 'sourcify']) + plugin.verticalIcons.select('sourcify') + _paq.push(['trackEvent', 'pluginManager', 'userActivate', 'sourcify']) } const startPluginManager = async () => { await plugin.appManager.activatePlugin('pluginManager') diff --git a/libs/remix-ui/modal-dialog/.eslintrc b/libs/remix-ui/modal-dialog/.eslintrc index b8666066e9..4d698941a4 100644 --- a/libs/remix-ui/modal-dialog/.eslintrc +++ b/libs/remix-ui/modal-dialog/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx b/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx index 524caaad62..714255aa93 100644 --- a/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx +++ b/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx @@ -86,40 +86,38 @@ export const ModalDialog = (props: ModalDialogProps) => { {props.title && props.title} {!props.showCancelIcon && - handleHide()}> - - + handleHide()}> + + }
    - { props.children ? props.children : props.message } + {props.children ? props.children : props.message}
    {/* todo add autofocus ^^ */} - { props.okLabel && - { - if (props.okFn) props.okFn() - handleHide() - }} - > - { props.okLabel ? props.okLabel : 'OK' } - + { props.okLabel && { + if (props.okFn) props.okFn() + handleHide() + }} + > + {props.okLabel ? props.okLabel : 'OK'} + } - { props.cancelLabel && - { - if (props.cancelFn) props.cancelFn() - handleHide() - }} - > - { props.cancelLabel ? props.cancelLabel : 'Cancel' } - + { props.cancelLabel && { + if (props.cancelFn) props.cancelFn() + handleHide() + }} + > + {props.cancelLabel ? props.cancelLabel : 'Cancel'} + }
    diff --git a/libs/remix-ui/panel/.babelrc b/libs/remix-ui/panel/.babelrc new file mode 100644 index 0000000000..64a3748691 --- /dev/null +++ b/libs/remix-ui/panel/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@nrwl/react/babel"], + "plugins": [] +} \ No newline at end of file diff --git a/libs/remix-ui/panel/.eslintrc.json b/libs/remix-ui/panel/.eslintrc.json new file mode 100644 index 0000000000..5a1c541d11 --- /dev/null +++ b/libs/remix-ui/panel/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/libs/remix-ui/panel/README.md b/libs/remix-ui/panel/README.md new file mode 100644 index 0000000000..fa765fcb39 --- /dev/null +++ b/libs/remix-ui/panel/README.md @@ -0,0 +1,7 @@ +# remix-ui-side-panel + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test remix-ui-side-panel` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/remix-ui/panel/src/index.ts b/libs/remix-ui/panel/src/index.ts new file mode 100644 index 0000000000..c61d9612f1 --- /dev/null +++ b/libs/remix-ui/panel/src/index.ts @@ -0,0 +1,2 @@ +export { default as RemixPluginPanel } from './lib/plugins/remix-ui-panel' +export { default as RemixUIMainPanel } from './lib/main/main-panel' diff --git a/libs/remix-ui/panel/src/lib/dragbar/dragbar.css b/libs/remix-ui/panel/src/lib/dragbar/dragbar.css new file mode 100644 index 0000000000..1ad9f9de85 --- /dev/null +++ b/libs/remix-ui/panel/src/lib/dragbar/dragbar.css @@ -0,0 +1,27 @@ +/* dragbar UI */ + +.dragbar_terminal { + display: block; + width: 100%; + position: absolute; + left: 0px; + top: 0px; + height: 0.3em; + z-index: 9999; +} + +.overlay { + position: absolute; + left: 0; + top: 0; + width: 100vw; + height: 100vh; + display: block; + z-index: 900; +} + +.dragbar_terminal:hover, +.dragbar_terminal.ondrag { + background-color: var(--secondary); + cursor: row-resize; +} diff --git a/libs/remix-ui/panel/src/lib/dragbar/dragbar.tsx b/libs/remix-ui/panel/src/lib/dragbar/dragbar.tsx new file mode 100644 index 0000000000..232f23ff10 --- /dev/null +++ b/libs/remix-ui/panel/src/lib/dragbar/dragbar.tsx @@ -0,0 +1,51 @@ +// eslint-disable-next-line no-use-before-define +import React, { useEffect, useState } from 'react' +import Draggable from 'react-draggable' +import './dragbar.css' + +interface IRemixDragBarUi { + refObject: React.MutableRefObject; + setHideStatus: (hide: boolean) => void; + hidden: boolean + minHeight?: number +} + +const DragBar = (props: IRemixDragBarUi) => { + const [dragState, setDragState] = useState(false) + const [dragBarPosY, setDragBarPosY] = useState(0) + const nodeRef = React.useRef(null) // fix for strictmode + + function stopDrag (e: MouseEvent, data: any) { + const h = window.innerHeight - data.y + props.refObject.current.setAttribute('style', `height: ${h}px;`) + setDragBarPosY(window.innerHeight - props.refObject.current.offsetHeight) + setDragState(false) + } + const handleResize = () => { + setDragBarPosY(window.innerHeight - props.refObject.current.offsetHeight) + } + + useEffect(() => { + handleResize() + }, [props.hidden]) + + useEffect(() => { + window.addEventListener('resize', handleResize) + // TODO: not a good way to wait on the ref doms element to be rendered of course + setTimeout(() => + handleResize(), 2000) + return () => window.removeEventListener('resize', handleResize) + }, []) + + function startDrag () { + setDragState(true) + } + return <> +
    + +
    +
    + +} + +export default DragBar diff --git a/libs/remix-ui/panel/src/lib/main/main-panel.css b/libs/remix-ui/panel/src/lib/main/main-panel.css new file mode 100644 index 0000000000..d569338fab --- /dev/null +++ b/libs/remix-ui/panel/src/lib/main/main-panel.css @@ -0,0 +1,8 @@ +.mainview { + display : flex; + flex-direction : column; + height : 100%; + width : 100%; + position: relative; + } + diff --git a/libs/remix-ui/panel/src/lib/main/main-panel.tsx b/libs/remix-ui/panel/src/lib/main/main-panel.tsx new file mode 100644 index 0000000000..4fb00ddf65 --- /dev/null +++ b/libs/remix-ui/panel/src/lib/main/main-panel.tsx @@ -0,0 +1,60 @@ +/* eslint-disable no-unused-expressions */ +import { AppContext } from 'libs/remix-ui/app/src/lib/remix-app/context/context' +import React, { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react' // eslint-disable-line +import DragBar from '../dragbar/dragbar' +import RemixUIPanelPlugin from '../plugins/panel-plugin' +import { PluginRecord } from '../types' +import './main-panel.css' + +const RemixUIMainPanel = () => { + const appContext = useContext(AppContext) + const [plugins, setPlugins] = useState([]) + const editorRef = useRef(null) + const mainPanelRef = useRef(null) + const tabsRef = useRef(null) + const terminalRef = useRef(null) + + const refs = [tabsRef, editorRef, mainPanelRef, terminalRef] + + const renderPanels = () => { + if (appContext) { + const pluginPanels: PluginRecord[] = [] + Object.values(appContext.layout.panels).map((panel: any) => { + pluginPanels.push({ + profile: panel.plugin.profile, + active: panel.active, + view: panel.plugin.profile.name === 'tabs' ? panel.plugin.renderTabsbar() : panel.plugin.render(), + class: panel.plugin.profile.name + '-wrap ' + (panel.minimized ? 'minimized' : ''), + minimized: panel.minimized + }) + }) + setPlugins(pluginPanels) + } + } + + useEffect(() => { + renderPanels() + appContext.layout.event.on('change', () => { + renderPanels() + }) + }, []) + + return ( +
    + {Object.values(plugins).map((pluginRecord, i) => { + return ( + + {(pluginRecord.profile.name === 'terminal') ? : null} + + + ) + })} +
    + ) +} + +export default RemixUIMainPanel diff --git a/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx b/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx new file mode 100644 index 0000000000..5acd50541a --- /dev/null +++ b/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx @@ -0,0 +1,27 @@ +/* eslint-disable jsx-a11y/anchor-has-content */ +import React, { useEffect, useRef, useState } from 'react' // eslint-disable-line +import { PluginRecord } from '../types' +import './panel.css' + +export interface RemixPanelProps { + plugins: Record; + } +const RemixUIPanelHeader = (props: RemixPanelProps) => { + const [plugin, setPlugin] = useState() + + useEffect(() => { + if (props.plugins) { + const p = Object.values(props.plugins).find((pluginRecord) => { + return pluginRecord.active === true + }) + setPlugin(p) + } + }, [props]) + + return ( +
    {plugin?.profile.displayName || plugin?.profile.name}
    + {plugin?.profile.documentation ? () : ''} +
    ) +} + +export default RemixUIPanelHeader diff --git a/libs/remix-ui/panel/src/lib/plugins/panel-plugin.tsx b/libs/remix-ui/panel/src/lib/plugins/panel-plugin.tsx new file mode 100644 index 0000000000..9eb30391bc --- /dev/null +++ b/libs/remix-ui/panel/src/lib/plugins/panel-plugin.tsx @@ -0,0 +1,37 @@ +/* eslint-disable no-undef */ +import React, { forwardRef, useEffect, useRef, useState } from 'react' // eslint-disable-line +import { PluginRecord } from '../types' +import './panel.css' +interface panelPLuginProps { + pluginRecord: PluginRecord +} + +const RemixUIPanelPlugin = (props: panelPLuginProps, panelRef: any) => { + const localRef = useRef(null) + const [view, setView] = useState() + useEffect(() => { + const ref:any = panelRef || localRef + if (ref.current) { + if (props.pluginRecord.view) { + if (React.isValidElement(props.pluginRecord.view)) { + setView(props.pluginRecord.view) + } else { + ref.current.appendChild(props.pluginRecord.view) + } + } + } + }, []) + + return ( +
    + {view} +
    + ) +} + +export default forwardRef(RemixUIPanelPlugin) diff --git a/libs/remix-ui/panel/src/lib/plugins/panel.css b/libs/remix-ui/panel/src/lib/plugins/panel.css new file mode 100644 index 0000000000..d2b2133667 --- /dev/null +++ b/libs/remix-ui/panel/src/lib/plugins/panel.css @@ -0,0 +1,110 @@ +.panel { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + flex: auto; +} + +.swapitTitle { + margin: 0; + text-transform: uppercase; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.swapitTitle i { + padding-left: 6px; + font-size: 14px; +} + +.swapitHeader { + display: flex; + align-items: center; + padding: 16px 24px 15px; + justify-content: space-between; + text-transform: uppercase; +} + +.icons i { + height: 80%; + cursor: pointer; +} + +.pluginsContainer { + height: 100%; + overflow-y: auto; +} + +.titleInfo { + padding-left: 10px; +} + +.versionBadge { + background-color: var(--light); + padding: 0 7px; + font-weight: bolder; + margin-left: 5px; + text-transform: lowercase; + cursor: default; +} + +iframe { + height: 100%; + width: 100%; + border: 0; +} + +.plugins { + height: 100%; +} + +.plugItIn { + display: none; + height: 100%; +} + +.plugItIn>div { + overflow-y: auto; + overflow-x: hidden; + height: 100%; + width: 100%; +} + +.plugItIn.active { + display: block; +} + +.pluginsContainer { + height: 100%; + overflow-y: hidden; +} + +#editorView { + height: 100%; + width: 100%; + border: 0; + display: block; +} + +#mainPanel { + height: 100%; + width: 100%; + border: 0; + display: block; +} + +.mainPanel-wrap, .editor-wrap { + flex: 1; + min-height: 100px; +} + +.terminal-wrap { + min-height: 35px; + height: 20%; +} + +.terminal-wrap.minimized { + height: 2rem !important; +} diff --git a/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx b/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx new file mode 100644 index 0000000000..37fa018c92 --- /dev/null +++ b/libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx @@ -0,0 +1,29 @@ +/* eslint-disable no-undef */ +import React, { useEffect, useState } from 'react' // eslint-disable-line +import './panel.css' +import RemixUIPanelPlugin from './panel-plugin' +import { PluginRecord } from '../types' + +/* eslint-disable-next-line */ +export interface RemixPanelProps { + plugins: Record + header: JSX.Element +} + +export function RemixPluginPanel (props: RemixPanelProps) { + return ( + <> + {props.header} +
    +
    + {Object.values(props.plugins).map((pluginRecord) => { + return + })} +
    +
    + + + ) +} + +export default RemixPluginPanel diff --git a/libs/remix-ui/panel/src/lib/types/index.ts b/libs/remix-ui/panel/src/lib/types/index.ts new file mode 100644 index 0000000000..f8407033ab --- /dev/null +++ b/libs/remix-ui/panel/src/lib/types/index.ts @@ -0,0 +1,9 @@ +import { Profile } from '@remixproject/plugin-utils' + +export type PluginRecord = { + profile: Profile + view: any + active: boolean + class?: string + minimized?: boolean + } diff --git a/libs/remix-ui/panel/tsconfig.json b/libs/remix-ui/panel/tsconfig.json new file mode 100644 index 0000000000..8bd701c578 --- /dev/null +++ b/libs/remix-ui/panel/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/libs/remix-ui/panel/tsconfig.lib.json b/libs/remix-ui/panel/tsconfig.lib.json new file mode 100644 index 0000000000..b560bc4dec --- /dev/null +++ b/libs/remix-ui/panel/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/plugin-manager/.eslintrc b/libs/remix-ui/plugin-manager/.eslintrc index 10ba445cf2..69dd87352e 100644 --- a/libs/remix-ui/plugin-manager/.eslintrc +++ b/libs/remix-ui/plugin-manager/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/plugin-manager/src/types.d.ts b/libs/remix-ui/plugin-manager/src/types.d.ts index d30ae31457..66ee57bc77 100644 --- a/libs/remix-ui/plugin-manager/src/types.d.ts +++ b/libs/remix-ui/plugin-manager/src/types.d.ts @@ -4,6 +4,7 @@ import { EventEmitter } from 'events' import { Engine } from '@remixproject/engine/lib/engine' import { PluginBase, Profile } from '@remixproject/plugin-utils' import { IframePlugin, ViewPlugin, WebsocketPlugin } from '@remixproject/engine-web' +import { IframeReactPlugin } from '@remix-ui/app' /* eslint-disable camelcase */ interface SetPluginOptionType { @@ -88,7 +89,7 @@ export class PluginManagerComponent extends ViewPlugin extends Plugin implements render(): HTMLDivElement getAndFilterPlugins: (filter?: string, profiles?: Profile[]) => void triggerEngineEventListener: () => void - activateAndRegisterLocalPlugin: (localPlugin: IframePlugin | WebsocketPlugin) => Promise + activateAndRegisterLocalPlugin: (localPlugin: IframePlugin | IframeReactPlugin | WebsocketPlugin) => Promise activeProfiles: string[] _paq: any } diff --git a/libs/remix-ui/publish-to-storage/.eslintrc b/libs/remix-ui/publish-to-storage/.eslintrc index b8666066e9..4d698941a4 100644 --- a/libs/remix-ui/publish-to-storage/.eslintrc +++ b/libs/remix-ui/publish-to-storage/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx b/libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx index f9245dc15e..1ebe31a60e 100644 --- a/libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx +++ b/libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx @@ -79,7 +79,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => { resetStorage() } - const modal = async (title: string, message: string | JSX.Element) => { // eslint-disable-line no-undef + const modal = async (title: string, message: string | JSX.Element) => { await setState(prevState => { return { ...prevState, @@ -95,7 +95,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => { return ( void } diff --git a/libs/remix-ui/renderer/.eslintrc b/libs/remix-ui/renderer/.eslintrc index dae5c6feeb..0d43d424e3 100644 --- a/libs/remix-ui/renderer/.eslintrc +++ b/libs/remix-ui/renderer/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/run-tab/.babelrc b/libs/remix-ui/run-tab/.babelrc new file mode 100644 index 0000000000..ccae900be4 --- /dev/null +++ b/libs/remix-ui/run-tab/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/remix-ui/run-tab/.eslintrc.json b/libs/remix-ui/run-tab/.eslintrc.json new file mode 100644 index 0000000000..50e59482cf --- /dev/null +++ b/libs/remix-ui/run-tab/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/remix-ui/run-tab/README.md b/libs/remix-ui/run-tab/README.md new file mode 100644 index 0000000000..248e570f6c --- /dev/null +++ b/libs/remix-ui/run-tab/README.md @@ -0,0 +1,7 @@ +# remix-ui-run-tab + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test remix-ui-run-tab` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/remix-ui/run-tab/src/index.ts b/libs/remix-ui/run-tab/src/index.ts new file mode 100644 index 0000000000..1964da6480 --- /dev/null +++ b/libs/remix-ui/run-tab/src/index.ts @@ -0,0 +1 @@ +export * from './lib/run-tab' diff --git a/libs/remix-ui/run-tab/src/lib/actions/index.ts b/libs/remix-ui/run-tab/src/lib/actions/index.ts new file mode 100644 index 0000000000..9c0b06e36b --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/actions/index.ts @@ -0,0 +1,720 @@ +// eslint-disable-next-line no-unused-vars +import React from 'react' +import * as ethJSUtil from 'ethereumjs-util' +import Web3 from 'web3' +import { addressToString, createNonClashingNameAsync, shortenAddress } from '@remix-ui/helper' +import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, fetchContractListSuccess, hidePopUp, removeExistingInstance, removeProvider, setBaseFeePerGas, setConfirmSettings, setCurrentFile, setDecodedResponse, setEnvToasterContent, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setLoadType, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setRecorderCount, setSelectedAccount, setSendUnit, setSendValue, setTxFeeContent, setWeb3Dialog } from './payload' +import { RunTab } from '../types/run-tab' +import { CompilerAbstract } from '@remix-project/remix-solidity' +import * as remixLib from '@remix-project/remix-lib' +import { ContractData, FuncABI, MainnetPrompt } from '../types' + +const txFormat = remixLib.execution.txFormat +declare global { + interface Window { + _paq: any + } +} + +const _paq = window._paq = window._paq || [] //eslint-disable-line +const txHelper = remixLib.execution.txHelper +let plugin: RunTab, dispatch: React.Dispatch + +export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispatch) => { + plugin = udapp + dispatch = reducerDispatch + setupEvents() + setInterval(() => { + fillAccountsList() + }, 1000) +} + +const setupEvents = () => { + plugin.blockchain.resetAndInit(plugin.config, { + getAddress: (cb) => { + cb(null, plugin.REACT_API.accounts.selectedAccount) + }, + getValue: (cb) => { + try { + const number = plugin.REACT_API.sendValue + const unit = plugin.REACT_API.sendUnit + + cb(null, Web3.utils.toWei(number, unit)) + } catch (e) { + cb(e) + } + }, + getGasLimit: (cb) => { + try { + cb(null, '0x' + new ethJSUtil.BN(plugin.REACT_API.gasLimit, 10).toString(16)) + } catch (e) { + cb(e.message) + } + } + }) + + plugin.blockchain.events.on('newTransaction', (tx, receipt) => { + plugin.emit('newTransaction', tx, receipt) + }) + + plugin.blockchain.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => { + if (!lookupOnly) dispatch(setSendValue('0')) + if (error) return + updateAccountBalances() + }) + + plugin.blockchain.event.register('contextChanged', (context, silent) => { + setFinalContext() + }) + + plugin.blockchain.event.register('networkStatus', ({ error, network }) => { + if (error) { + const netUI = 'can\'t detect network ' + setNetworkNameFromProvider(netUI) + + return + } + const networkProvider = plugin.networkModule.getNetworkProvider.bind(plugin.networkModule) + const netUI = (networkProvider() !== 'vm') ? `${network.name} (${network.id || '-'}) network` : 'VM' + + setNetworkNameFromProvider(netUI) + }) + + plugin.blockchain.event.register('addProvider', provider => addExternalProvider(provider)) + + plugin.blockchain.event.register('removeProvider', name => removeExternalProvider(name)) + + plugin.on('manager', 'pluginActivated', addPluginProvider.bind(plugin)) + + plugin.on('manager', 'pluginDeactivated', removePluginProvider.bind(plugin)) + + plugin.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) + + plugin.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) + + plugin.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) + + plugin.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) + + plugin.on('optimism-compiler', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult(file, source, languageVersion, data)) + + plugin.on('udapp', 'setEnvironmentModeReducer', (env: { context: string, fork: string }, from: string) => { + dispatch(displayPopUp(plugin.REACT_API.envToasterContent(env, from))) + setExecutionContext(env, plugin.REACT_API.web3Dialog()) + }) + + plugin.fileManager.events.on('currentFileChanged', (currentFile: string) => { + if (/.(.abi)$/.exec(currentFile)) { + dispatch(setLoadType('abi')) + } else if (/.(.sol)$/.exec(currentFile) || + /.(.vy)$/.exec(currentFile) || // vyper + /.(.lex)$/.exec(currentFile) || // lexon + /.(.contract)$/.exec(currentFile)) { + dispatch(setLoadType('sol')) + } else { + dispatch(setLoadType('other')) + } + }) + + plugin.recorder.event.register('recorderCountChange', (count) => { + dispatch(setRecorderCount(count)) + }) + + plugin.event.register('cleared', () => { + dispatch(clearRecorderCount()) + }) +} + +export const initWebDialogs = (envToasterContent: (env: { context: string, fork: string }, from: string) => void, web3Dialog: () => void) => async (dispatch: React.Dispatch) => { + dispatch(setEnvToasterContent(envToasterContent)) + dispatch(setWeb3Dialog) +} + +const updateAccountBalances = () => { + const accounts = plugin.REACT_API.accounts.loadedAccounts + + Object.keys(accounts).map((value) => { + plugin.blockchain.getBalanceInEther(value, (err, balance) => { + if (err) return + const updated = shortenAddress(value, balance) + + accounts[value] = updated + }) + }) + dispatch(fetchAccountsListSuccess(accounts)) +} + +const fillAccountsList = async () => { + try { + dispatch(fetchAccountsListRequest()) + const promise = plugin.blockchain.getAccounts() + + promise.then(async (accounts: string[]) => { + const loadedAccounts = {} + + if (!accounts) accounts = [] + // allSettled is undefined.. + // so the current promise (all) will finish when: + // - all the promises resolve + // - at least one reject + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all + await (Promise as any).all(accounts.map((account) => { + return new Promise((resolve, reject) => { + plugin.blockchain.getBalanceInEther(account, (err, balance) => { + if (err) return reject(err) + const updated = shortenAddress(account, balance) + + loadedAccounts[account] = updated + resolve(account) + }) + }) + })) + dispatch(fetchAccountsListSuccess(loadedAccounts)) + }).catch((e) => { + dispatch(fetchAccountsListFailed(e.message)) + }) + } catch (e) { + dispatch(displayPopUp(`Cannot get account list: ${e}`)) + } +} + +export const setAccount = (account: string) => { + dispatch(setSelectedAccount(account)) +} + +export const setUnit = (unit: 'ether' | 'finney' | 'gwei' | 'wei') => { + dispatch(setSendUnit(unit)) +} + +export const setGasFee = (value: number) => { + dispatch(setGasLimit(value)) +} + +const addPluginProvider = (profile) => { + if (profile.kind === 'provider') { + ((profile, app) => { + const web3Provider = { + async sendAsync (payload, callback) { + try { + const result = await app.call(profile.name, 'sendAsync', payload) + callback(null, result) + } catch (e) { + callback(e) + } + } + } + app.blockchain.addProvider({ name: profile.displayName, provider: web3Provider }) + })(profile, plugin) + } +} + +const removePluginProvider = (profile) => { + if (profile.kind === 'provider') plugin.blockchain.removeProvider(profile.displayName) +} + +const setFinalContext = () => { + // set the final context. Cause it is possible that this is not the one we've originaly selected + const value = _getProviderDropdownValue() + + setExecEnv(value) + clearInstances() +} + +const _getProviderDropdownValue = (): string => { + const provider = plugin.blockchain.getProvider() + const fork = plugin.blockchain.getCurrentFork() + + return provider === 'vm' ? provider + '-' + fork : provider +} + +const setExecEnv = (env: string) => { + dispatch(setExecutionEnvironment(env)) +} + +export const setNetworkNameFromProvider = (networkName: string) => { + dispatch(setNetworkName(networkName)) +} + +const addExternalProvider = (network) => { + dispatch(addProvider(network)) + dispatch(displayPopUp(`${network.name} provider added`)) +} + +const removeExternalProvider = (name) => { + dispatch(removeProvider(name)) +} + +export const setExecutionContext = (executionContext: { context: string, fork: string }, displayContent: JSX.Element) => { + plugin.blockchain.changeExecutionContext(executionContext, () => { + dispatch(displayNotification('External node request', displayContent, 'OK', 'Cancel', () => { + plugin.blockchain.setProviderFromEndpoint(plugin.REACT_API.externalEndpoint, executionContext, (alertMsg) => { + if (alertMsg) dispatch(displayPopUp(alertMsg)) + setFinalContext() + }) + }, () => { setFinalContext() })) + }, (alertMsg) => { + dispatch(displayPopUp(alertMsg)) + }, setFinalContext()) +} + +export const setWeb3Endpoint = (endpoint: string) => { + dispatch(setExternalEndpoint(endpoint)) +} + +export const clearPopUp = async () => { + dispatch(hidePopUp()) +} + +export const createNewBlockchainAccount = async (cbMessage: JSX.Element) => { + plugin.blockchain.newAccount( + '', + (cb) => { + dispatch(displayNotification('Enter Passphrase', cbMessage, 'OK', 'Cancel', async () => { + if (plugin.REACT_API.passphrase === plugin.REACT_API.matchPassphrase) { + cb(plugin.REACT_API.passphrase) + } else { + dispatch(displayNotification('Error', 'Passphase does not match', 'OK', null)) + } + setPassphrase('') + setMatchPassphrase('') + }, () => {})) + }, + async (error, address) => { + if (error) { + return dispatch(displayPopUp('Cannot create an account: ' + error)) + } + dispatch(displayPopUp(`account ${address} created`)) + await fillAccountsList() + } + ) +} + +export const setPassphrasePrompt = (passphrase: string) => { + dispatch(setPassphrase(passphrase)) +} + +export const setMatchPassphrasePrompt = (passphrase: string) => { + dispatch(setMatchPassphrase(passphrase)) +} + +export const signMessageWithAddress = (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => { + plugin.blockchain.signMessage(message, account, passphrase, (err, msgHash, signedData) => { + if (err) { + return displayPopUp(err) + } + dispatch(displayNotification('Signed Message', modalContent(msgHash, signedData), 'OK', null, () => {}, null)) + }) +} + +const broadcastCompilationResult = (file, source, languageVersion, data) => { + // TODO check whether the tab is configured + const compiler = new CompilerAbstract(languageVersion, data, source) + + plugin.compilersArtefacts[languageVersion] = compiler + plugin.compilersArtefacts.__last = compiler + + const contracts = getCompiledContracts(compiler).map((contract) => { + return { name: languageVersion, alias: contract.name, file: contract.file } + }) + + dispatch(fetchContractListSuccess(contracts)) + dispatch(setCurrentFile(file)) +} + +const loadContractFromAddress = (address, confirmCb, cb) => { + if (/.(.abi)$/.exec(plugin.config.get('currentFile'))) { + confirmCb(() => { + let abi + try { + abi = JSON.parse(plugin.editor.currentContent()) + } catch (e) { + return cb('Failed to parse the current file as JSON ABI.') + } + _paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithABI']) + cb(null, 'abi', abi) + }) + } else { + _paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithArtifacts']) + cb(null, 'instance') + } +} + +const getCompiledContracts = (compiler) => { + const contracts = [] + + compiler.visitContracts((contract) => { + contracts.push(contract) + }) + return contracts +} + +export const getSelectedContract = (contractName: string, compilerAtributeName: string) => { + if (!contractName) return null + const compiler = plugin.compilersArtefacts[compilerAtributeName] + + if (!compiler) return null + + const contract = compiler.getContract(contractName) + + return { + name: contractName, + contract: contract, + compiler: compiler, + abi: contract.object.abi, + bytecodeObject: contract.object.evm.bytecode.object, + bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences, + object: contract.object, + deployedBytecode: contract.object.evm.deployedBytecode, + getConstructorInterface: () => { + return txHelper.getConstructorInterface(contract.object.abi) + }, + getConstructorInputs: () => { + const constructorInteface = txHelper.getConstructorInterface(contract.object.abi) + + return txHelper.inputParametersDeclarationToString(constructorInteface.inputs) + }, + isOverSizeLimit: () => { + const deployedBytecode = contract.object.evm.deployedBytecode + + return (deployedBytecode && deployedBytecode.object.length / 2 > 24576) + }, + metadata: contract.object.metadata + } +} + +const getCompilerContracts = () => { + return plugin.compilersArtefacts.__last.getData().contracts +} + +const terminalLogger = (view: JSX.Element) => { + plugin.call('terminal', 'logHtml', view) +} + +const confirmationHandler = (confirmDialogContent: MainnetPrompt, network, tx, gasEstimation, continueTxExecution, cancelCb) => { + if (network.name !== 'Main') { + return continueTxExecution(null) + } + const amount = plugin.blockchain.fromWei(tx.value, true, 'ether') + const content = confirmDialogContent(tx, network, amount, gasEstimation, plugin.blockchain.determineGasFees(tx), plugin.blockchain.determineGasPrice.bind(plugin.blockchain)) + + dispatch(displayNotification('Confirm transaction', content, 'Confirm', 'Cancel', () => { + plugin.blockchain.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', plugin.REACT_API.confirmSettings) + // TODO: check if this is check is still valid given the refactor + if (!plugin.REACT_API.gasPriceStatus) { + cancelCb('Given transaction fee is not correct') + } else { + continueTxExecution({ maxFee: plugin.REACT_API.maxFee, maxPriorityFee: plugin.REACT_API.maxPriorityFee, baseFeePerGas: plugin.REACT_API.baseFeePerGas, gasPrice: plugin.REACT_API.gasPrice }) + } + }, () => { + return cancelCb('Transaction canceled by user.') + })) +} + +const getConfirmationCb = (confirmDialogContent: MainnetPrompt) => { + // this code is the same as in recorder.js. TODO need to be refactored out + return (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + confirmationHandler(confirmDialogContent, network, tx, gasEstimation, continueTxExecution, cancelCb) + } +} + +const continueHandler = (gasEstimationPrompt: (msg: string) => JSX.Element, error, continueTxExecution, cancelCb) => { + if (error) { + const msg = typeof error !== 'string' ? error.message : error + + dispatch(displayNotification('Gas estimation failed', gasEstimationPrompt(msg), 'Send Transaction', 'Cancel Transaction', () => { + continueTxExecution() + }, () => { + cancelCb() + })) + } else { + continueTxExecution() + } +} + +const promptHandler = (passphrasePrompt, okCb, cancelCb) => { + dispatch(displayNotification('Passphrase requested', passphrasePrompt('Personal mode is enabled. Please provide passphrase of account'), 'OK', 'Cancel', okCb, cancelCb)) +} + +export const createInstance = async ( + selectedContract: ContractData, + gasEstimationPrompt: (msg: string) => JSX.Element, + passphrasePrompt: (msg: string) => JSX.Element, + logBuilder: (msg: string) => JSX.Element, + publishToStorage: (storage: 'ipfs' | 'swarm', + contract: ContractData) => void, + mainnetPrompt: MainnetPrompt, + isOverSizePrompt: () => JSX.Element, + args) => { + const statusCb = (msg: string) => { + const log = logBuilder(msg) + + return terminalLogger(log) + } + + const finalCb = (error, contractObject, address) => { + if (error) { + const log = logBuilder(error) + + return terminalLogger(log) + } + addInstance({ contractData: contractObject, address, name: contractObject.name }) + + const data = plugin.compilersArtefacts.getCompilerAbstract(contractObject.contract.file) + + plugin.compilersArtefacts.addResolvedContract(addressToString(address), data) + if (plugin.REACT_API.ipfsChecked) { + _paq.push(['trackEvent', 'udapp', 'DeployAndPublish', plugin.REACT_API.networkName]) + publishToStorage('ipfs', selectedContract) + } else { + _paq.push(['trackEvent', 'udapp', 'DeployOnly', plugin.REACT_API.networkName]) + } + } + + let contractMetadata + try { + contractMetadata = await plugin.call('compilerMetadata', 'deployMetadataOf', selectedContract.name, selectedContract.contract.file) + } catch (error) { + return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`) + } + + const compilerContracts = getCompilerContracts() + const confirmationCb = getConfirmationCb(mainnetPrompt) + + if (selectedContract.isOverSizeLimit()) { + return dispatch(displayNotification('Contract code size over limit', isOverSizePrompt(), 'Force Send', 'Cancel', () => { + deployContract(selectedContract, args, contractMetadata, compilerContracts, { + continueCb: (error, continueTxExecution, cancelCb) => { + continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb) + }, + promptCb: (okCb, cancelCb) => { + promptHandler(passphrasePrompt, okCb, cancelCb) + }, + statusCb, + finalCb + }, confirmationCb) + }, () => { + const log = logBuilder(`creation of ${selectedContract.name} canceled by user.`) + + return terminalLogger(log) + })) + } + deployContract(selectedContract, args, contractMetadata, compilerContracts, { + continueCb: (error, continueTxExecution, cancelCb) => { + continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb) + }, + promptCb: (okCb, cancelCb) => { + promptHandler(passphrasePrompt, okCb, cancelCb) + }, + statusCb, + finalCb + }, confirmationCb) +} + +const deployContract = (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) => { + _paq.push(['trackEvent', 'udapp', 'DeployContractTo', plugin.REACT_API.networkName]) + const { statusCb } = callbacks + + if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) { + return plugin.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) + } + if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`) + plugin.blockchain.deployContractWithLibrary(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) +} + +export const updateGasPriceStatus = (status: boolean) => { + dispatch(setGasPriceStatus(status)) +} + +export const updateConfirmSettings = (confirmation: boolean) => { + dispatch(setConfirmSettings(confirmation)) +} + +export const updateMaxFee = (fee: string) => { + dispatch(setMaxFee(fee)) +} + +export const updateMaxPriorityFee = (fee: string) => { + dispatch(setMaxPriorityFee(fee)) +} + +export const updateBaseFeePerGas = (baseFee: string) => { + dispatch(setBaseFeePerGas(baseFee)) +} + +export const updateGasPrice = (price: string) => { + dispatch(setGasPrice(price)) +} + +export const updateTxFeeContent = (content: string) => { + dispatch(setTxFeeContent(content)) +} + +const addInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record }) => { + instance.decodedResponse = {} + dispatch(addNewInstance(instance)) +} + +export const removeInstance = (index: number) => { + dispatch(removeExistingInstance(index)) +} + +export const clearInstances = () => { + dispatch(clearAllInstances()) + dispatch(clearRecorderCount()) +} + +export const loadAddress = (contract: ContractData, address: string) => { + if (!contract) return dispatch(displayPopUp('No compiled contracts found.')) + clearInstances() + loadContractFromAddress(address, + (cb) => { + dispatch(displayNotification('At Address', `Do you really want to interact with ${address} using the current ABI definition?`, 'OK', 'Cancel', cb, null)) + }, + (error, loadType, abi) => { + if (error) { + return dispatch(displayNotification('Alert', error, 'OK', null)) + } + const compiler = plugin.REACT_API.contracts.contractList.find(item => item.alias === contract.name) + const contractData = getSelectedContract(contract.name, compiler.name) + + if (loadType === 'abi') { + return addInstance({ contractData, address, name: '' }) + } + addInstance({ contractData, address, name: contract.name }) + } + ) +} + +export const getContext = () => { + return plugin.blockchain.context() +} + +export const runTransactions = ( + instanceIndex: number, + lookupOnly: boolean, + funcABI: FuncABI, + inputsValues: string, + contractName: string, + contractABI, contract, + address, + logMsg:string, + logBuilder: (msg: string) => JSX.Element, + mainnetPrompt: MainnetPrompt, + gasEstimationPrompt: (msg: string) => JSX.Element, + passphrasePrompt: (msg: string) => JSX.Element, + funcIndex?: number) => { + let callinfo = '' + if (lookupOnly) callinfo = 'call' + else if (funcABI.type === 'fallback' || funcABI.type === 'receive') callinfo = 'lowLevelInteracions' + else callinfo = 'transact' + _paq.push(['trackEvent', 'udapp', callinfo, plugin.blockchain.getCurrentNetworkStatus().network.name]) + + const params = funcABI.type !== 'fallback' ? inputsValues : '' + plugin.blockchain.runOrCallContractMethod( + contractName, + contractABI, + funcABI, + contract, + inputsValues, + address, + params, + lookupOnly, + logMsg, + (msg) => { + const log = logBuilder(msg) + + return terminalLogger(log) + }, + (returnValue) => { + const response = txFormat.decodeResponse(returnValue, funcABI) + + dispatch(setDecodedResponse(instanceIndex, response, funcIndex)) + }, + (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + confirmationHandler(mainnetPrompt, network, tx, gasEstimation, continueTxExecution, cancelCb) + }, + (error, continueTxExecution, cancelCb) => { + continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb) + }, + (okCb, cancelCb) => { + promptHandler(passphrasePrompt, okCb, cancelCb) + } + ) +} + +const saveScenario = (promptCb, cb) => { + const txJSON = JSON.stringify(plugin.recorder.getAll(), null, 2) + const path = plugin.fileManager.currentPath() + + promptCb(path, async () => { + const fileProvider = plugin.fileManager.fileProviderOf(path) + + if (!fileProvider) return + const newFile = path + '/' + plugin.REACT_API.recorder.pathToScenario + try { + const newPath = await createNonClashingNameAsync(newFile, plugin.fileManager) + if (!fileProvider.set(newPath, txJSON)) return cb('Failed to create file ' + newFile) + plugin.fileManager.open(newFile) + } catch (error) { + if (error) return cb('Failed to create file. ' + newFile + ' ' + error) + } + }) +} + +export const storeScenario = (prompt: (msg: string) => JSX.Element) => { + saveScenario( + (path, cb) => { + dispatch(displayNotification('Save transactions as scenario', prompt('Transactions will be saved in a file under ' + path), 'Ok', 'Cancel', cb, null)) + }, + (error) => { + if (error) return dispatch(displayNotification('Alert', error, 'Ok', null)) + } + ) +} + +const runScenario = (file: string, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => { + if (!file) return dispatch(displayNotification('Alert', 'Unable to run scenerio, no specified scenario file', 'Ok', null)) + + plugin.fileManager.readFile(file).then((json) => { + // TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily + plugin.recorder.runScenario( + json, + (error, continueTxExecution, cancelCb) => { + continueHandler(gasEstimationPrompt, error, continueTxExecution, cancelCb) + }, (okCb, cancelCb) => { + promptHandler(passphrasePrompt, okCb, cancelCb) + }, (msg) => { + dispatch(displayNotification('Alert', msg, 'Ok', null)) + }, (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + confirmationHandler(confirmDialogContent, network, tx, gasEstimation, continueTxExecution, cancelCb) + }, (msg: string) => { + const log = logBuilder(msg) + + return terminalLogger(log) + }, (error, abi, address, contractName) => { + if (error) { + return dispatch(displayNotification('Alert', error, 'Ok', null)) + } + addInstance({ name: contractName, address, abi }) + }) + }).catch((error) => dispatch(displayNotification('Alert', error, 'Ok', null))) +} + +export const runCurrentScenario = (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => { + const file = plugin.config.get('currentFile') + + if (!file) return dispatch(displayNotification('Alert', 'A scenario file has to be selected', 'Ok', null)) + runScenario(file, gasEstimationPrompt, passphrasePrompt, confirmDialogContent, logBuilder) +} + +export const updateScenarioPath = (path: string) => { + dispatch(setPathToScenario(path)) +} + +export const getFuncABIInputs = (funcABI: FuncABI) => { + return plugin.blockchain.getInputs(funcABI) +} + +export const setSendTransactionValue = (value: string) => { + dispatch(setSendValue(value)) +} diff --git a/libs/remix-ui/run-tab/src/lib/actions/payload.ts b/libs/remix-ui/run-tab/src/lib/actions/payload.ts new file mode 100644 index 0000000000..bf547cbecf --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/actions/payload.ts @@ -0,0 +1,285 @@ +import { ContractData } from '../types' + +export const fetchAccountsListRequest = () => { + return { + type: 'FETCH_ACCOUNTS_LIST_REQUEST', + payload: null + } +} + +export const fetchAccountsListSuccess = (accounts: Record) => { + return { + type: 'FETCH_ACCOUNTS_LIST_SUCCESS', + payload: accounts + } +} + +export const fetchAccountsListFailed = (error: string) => { + return { + type: 'FETCH_ACCOUNTS_LIST_FAILED', + payload: error + } +} + +export const setSendValue = (value: string) => { + return { + type: 'SET_SEND_VALUE', + payload: value + } +} + +export const setSelectedAccount = (account: string) => { + return { + type: 'SET_SELECTED_ACCOUNT', + payload: account + } +} + +export const setSendUnit = (unit: 'ether' | 'finney' | 'gwei' | 'wei') => { + return { + type: 'SET_SEND_UNIT', + payload: unit + } +} + +export const setGasLimit = (gasLimit: number) => { + return { + type: 'SET_GAS_LIMIT', + payload: gasLimit + } +} + +export const setExecutionEnvironment = (executionEnvironment: string) => { + return { + type: 'SET_EXECUTION_ENVIRONMENT', + payload: executionEnvironment + } +} + +export const setPersonalMode = (mode: boolean) => { + return { + type: 'SET_PERSONAL_MODE', + payload: mode + } +} + +export const setNetworkName = (networkName: string) => { + return { + type: 'SET_NETWORK_NAME', + payload: networkName + } +} + +export const addProvider = (provider: string) => { + return { + type: 'ADD_PROVIDER', + payload: provider + } +} + +export const removeProvider = (provider: string) => { + return { + type: 'REMOVE_PROVIDER', + payload: provider + } +} + +export const displayNotification = (title: string, message: string | JSX.Element, labelOk: string, labelCancel: string, actionOk?: (...args) => void, actionCancel?: (...args) => void) => { + return { + type: 'DISPLAY_NOTIFICATION', + payload: { title, message, labelOk, labelCancel, actionOk, actionCancel } + } +} + +export const hideNotification = () => { + return { + type: 'HIDE_NOTIFICATION' + } +} + +export const setExternalEndpoint = (endpoint: string) => { + return { + type: 'SET_EXTERNAL_WEB3_ENDPOINT', + payload: endpoint + } +} + +export const displayPopUp = (message: string | JSX.Element) => { + return { + type: 'DISPLAY_POPUP_MESSAGE', + payload: message + } +} + +export const hidePopUp = () => { + return { + type: 'HIDE_POPUP_MESSAGE' + } +} + +export const setPassphrase = (passphrase: string) => { + return { + type: 'SET_PASSPHRASE', + payload: passphrase + } +} + +export const setMatchPassphrase = (passphrase: string) => { + return { + type: 'SET_MATCH_PASSPHRASE', + payload: passphrase + } +} + +export const fetchContractListRequest = () => { + return { + type: 'FETCH_CONTRACT_LIST_REQUEST' + } +} + +export const fetchContractListSuccess = (contracts: { name: string, alias: string, file: string }[]) => { + return { + type: 'FETCH_CONTRACT_LIST_SUCCESS', + payload: contracts + } +} + +export const fetchContractListFailed = (error: string) => { + return { + type: 'FETCH_CONTRACT_LIST_FAILED', + payload: error + } +} + +export const setLoadType = (type: 'abi' | 'sol' | 'other') => { + return { + type: 'SET_LOAD_TYPE', + payload: type + } +} + +export const setCurrentFile = (file: string) => { + return { + type: 'SET_CURRENT_FILE', + payload: file + } +} +export const setIpfsCheckedState = (state: boolean) => { + return { + type: 'SET_IPFS_CHECKED_STATE', + payload: state + } +} + +export const setGasPriceStatus = (status: boolean) => { + return { + type: 'SET_GAS_PRICE_STATUS', + payload: status + } +} + +export const setConfirmSettings = (confirmation: boolean) => { + return { + type: 'SET_CONFIRM_SETTINGS', + payload: confirmation + } +} + +export const setMaxFee = (fee: string) => { + return { + type: 'SET_MAX_FEE', + payload: fee + } +} + +export const setMaxPriorityFee = (fee: string) => { + return { + type: 'SET_MAX_PRIORITY_FEE', + payload: fee + } +} + +export const setBaseFeePerGas = (baseFee: string) => { + return { + type: 'SET_BASE_FEE_PER_GAS', + payload: baseFee + } +} + +export const setGasPrice = (price: string) => { + return { + type: 'SET_GAS_PRICE', + payload: price + } +} + +export const setTxFeeContent = (content: string) => { + return { + type: 'SET_TX_FEE_CONTENT', + payload: content + } +} + +export const addNewInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any }) => { + return { + type: 'ADD_INSTANCE', + payload: instance + } +} + +export const removeExistingInstance = (index: number) => { + return { + type: 'REMOVE_INSTANCE', + payload: index + } +} + +export const clearAllInstances = () => { + return { + type: 'CLEAR_INSTANCES' + } +} + +export const setDecodedResponse = (instanceIndex: number, response, funcIndex?: number) => { + return { + type: 'SET_DECODED_RESPONSE', + payload: { + instanceIndex, + funcIndex, + response + } + } +} + +export const setPathToScenario = (path: string) => { + return { + type: 'SET_PATH_TO_SCENARIO', + payload: path + } +} + +export const setRecorderCount = (count: number) => { + return { + type: 'SET_RECORDER_COUNT', + payload: count + } +} + +export const clearRecorderCount = () => { + return { + type: 'CLEAR_RECORDER_COUNT' + } +} + +export const setEnvToasterContent = (content: (env: { context: string, fork: string }, from: string) => void) => { + return { + type: 'SET_ENV_TOASTER_CONTENT', + payload: content + } +} + +export const setWeb3Dialog = () => { + return { + type: 'SET_WEB3_DIALOG' + } +} diff --git a/libs/remix-ui/run-tab/src/lib/components/account.tsx b/libs/remix-ui/run-tab/src/lib/components/account.tsx new file mode 100644 index 0000000000..9d34294026 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/account.tsx @@ -0,0 +1,168 @@ +// eslint-disable-next-line no-use-before-define +import React, { useEffect, useState } from 'react' +import { CopyToClipboard } from '@remix-ui/clipboard' +import { AccountProps } from '../types' +import { PassphrasePrompt } from './passphrase' + +export function AccountUI (props: AccountProps) { + const { selectedAccount, loadedAccounts } = props.accounts + const accounts = Object.keys(loadedAccounts) + const [plusOpt, setPlusOpt] = useState({ + classList: '', + title: '' + }) + const [message, setMessage] = useState('') + + useEffect(() => { + if (!selectedAccount && accounts.length > 0) props.setAccount(accounts[0]) + }, [accounts, selectedAccount]) + + useEffect(() => { + switch (props.selectExEnv) { + case 'injected': + setPlusOpt({ + classList: 'udapp_disableMouseEvents', + title: "Unfortunately it's not possible to create an account using injected web3. Please create the account directly from your provider (i.e metamask or other of the same type)." + }) + break + + case 'vm-london': + setPlusOpt({ + classList: '', + title: 'Create a new account' + }) + break + + case 'vm-berlin': + setPlusOpt({ + classList: '', + title: 'Create a new account' + }) + break + + case 'web3': + if (!props.personalMode) { + setPlusOpt({ + classList: 'disableMouseEvents', + title: 'Creating an account is possible only in Personal mode. Please go to Settings to enable it.' + }) + } else { + setPlusOpt({ + classList: '', + title: 'Create a new account' + }) + } + break + + default: + setPlusOpt({ + classList: 'disableMouseEvents', + title: `Unfortunately it's not possible to create an account using an external wallet (${props.selectExEnv}).` + }) + } + // this._deps.config.get('settings/personal-mode') + }, [props.selectExEnv, props.personalMode]) + + const newAccount = () => { + props.createNewBlockchainAccount(passphraseCreationPrompt()) + } + + const signMessage = () => { + if (!accounts[0]) { + return props.tooltip('Account list is empty, please make sure the current provider is properly connected to remix') + } + + if (props.selectExEnv !== 'vm-london' && props.selectExEnv !== 'vm-berlin' && props.selectExEnv !== 'injected') { + return props.modal('Passphrase to sign a message', + , 'OK', () => { + props.modal('Sign a message', signMessagePrompt(), 'OK', () => { + props.signMessageWithAddress(selectedAccount, message, signedMessagePrompt, props.passphrase) + props.setPassphrase('') + }, 'Cancel', null) + }, 'Cancel', () => { + props.setPassphrase('') + }) + } + + props.modal('Sign a message', signMessagePrompt(), 'OK', () => { + props.signMessageWithAddress(selectedAccount, message, signedMessagePrompt) + }, 'Cancel', null) + } + + const handlePassphrase = (e) => { + props.setPassphrase(e.target.value) + } + + const handleMatchPassphrase = (e) => { + props.setMatchPassphrase(e.target.value) + } + + const handleMessageInput = (e) => { + setMessage(e.target.value) + } + + const passphraseCreationPrompt = () => { + return ( +
    Please provide a Passphrase for the account creation +
    + +
    +
    + +
    +
    + ) + } + + const signMessagePrompt = () => { + return ( +
    Enter a message to sign +
    + +
    +
    + ) + } + + const signedMessagePrompt = (msgHash: string, signedData: string) => { + return ( +
    + hash:
    + {msgHash} +
    signature:
    + {signedData} +
    + ) + } + + return ( +
    + +
    + +
    + +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx new file mode 100644 index 0000000000..acf2e79899 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx @@ -0,0 +1,251 @@ +// eslint-disable-next-line no-use-before-define +import React, { useEffect, useState } from 'react' +import { ContractData, ContractDropdownProps, FuncABI } from '../types' +import * as ethJSUtil from 'ethereumjs-util' +import { ContractGUI } from './contractGUI' + +export function ContractDropdownUI (props: ContractDropdownProps) { + const [abiLabel, setAbiLabel] = useState<{ + display: string, + content: string + }>({ + display: '', + content: '' + }) + const [atAddressOptions, setAtAddressOptions] = useState<{title: string, disabled: boolean}>({ + title: 'address of contract', + disabled: true + }) + const [loadedAddress, setLoadedAddress] = useState('') + const [contractOptions, setContractOptions] = useState<{title: string, disabled: boolean}>({ + title: 'Please compile *.sol file to deploy or access a contract', + disabled: true + }) + const [selectedContract, setSelectedContract] = useState('') + const [compFails, setCompFails] = useState<'none' | 'block'>('none') + const [loadedContractData, setLoadedContractData] = useState(null) + const [constructorInterface, setConstructorInterface] = useState(null) + const [constructorInputs, setConstructorInputs] = useState(null) + const { contractList, loadType, currentFile, compilationCount } = props.contracts + + useEffect(() => { + enableAtAddress(false) + setAbiLabel({ + display: 'none', + content: 'ABI file selected' + }) + }, []) + + useEffect(() => { + if (props.exEnvironment && props.networkName) { + const savedConfig = window.localStorage.getItem(`ipfs/${props.exEnvironment}/${props.networkName}`) + const isCheckedIPFS = savedConfig === 'true' ? true : false // eslint-disable-line + + props.setIpfsCheckedState(isCheckedIPFS) + } + }, [props.exEnvironment, props.networkName]) + + useEffect(() => { + if (!loadFromAddress || !ethJSUtil.isValidAddress(loadedAddress)) enableAtAddress(false) + }, [loadedAddress]) + + useEffect(() => { + if (/.(.abi)$/.exec(currentFile)) { + setAbiLabel({ + display: 'block', + content: currentFile + }) + enableAtAddress(true) + } else if (/.(.sol)$/.exec(currentFile) || + /.(.vy)$/.exec(currentFile) || // vyper + /.(.lex)$/.exec(currentFile) || // lexon + /.(.contract)$/.exec(currentFile)) { + if (!selectedContract) enableAtAddress(false) + } else { + if (!selectedContract) enableAtAddress(false) + } + if (currentFile) { + enableContractNames(true) + setCompFails('none') + } else { + enableContractNames(false) + setCompFails('block') + } + initSelectedContract() + }, [loadType, currentFile, compilationCount]) + + useEffect(() => { + if (selectedContract) { + const contract = contractList.find(contract => contract.alias === selectedContract) + + if (contract) { + const loadedContractData = props.getSelectedContract(selectedContract, contract.name) + + if (loadedContractData) { + setLoadedContractData(loadedContractData) + setConstructorInterface(loadedContractData.getConstructorInterface()) + setConstructorInputs(loadedContractData.getConstructorInputs()) + } + } + } + }, [selectedContract, compilationCount]) + + useEffect(() => { + initSelectedContract() + }, [contractList]) + + const initSelectedContract = () => { + if (contractList.length > 0) { + const contract = contractList.find(contract => contract.alias === selectedContract) + + if (!selectedContract || !contract) setSelectedContract(contractList[0].alias) + } + } + + const enableAtAddress = (enable: boolean) => { + if (enable) { + setAtAddressOptions({ + disabled: false, + title: 'Interact with the given contract.' + }) + } else { + setAtAddressOptions({ + disabled: true, + title: loadedAddress ? '⚠ Compile *.sol file or select *.abi file.' : '⚠ Compile *.sol file or select *.abi file & then enter the address of deployed contract.' + }) + } + } + + const enableContractNames = (enable: boolean) => { + if (enable) { + setContractOptions({ + disabled: false, + title: 'Select contract for Deploy or At Address.' + }) + } else { + setContractOptions({ + disabled: true, + title: loadType === 'sol' ? '⚠ Select and compile *.sol file to deploy or access a contract.' : '⚠ Selected *.abi file allows accessing contracts, select and compile *.sol file to deploy and access one.' + }) + } + } + + const clickCallback = (inputs, value) => { + createInstance(loadedContractData, value) + } + + const createInstance = (selectedContract, args) => { + if (selectedContract.bytecodeObject.length === 0) { + return props.modal('Alert', 'This contract may be abstract, not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.', 'OK', () => {}) + } + props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.logBuilder, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args) + } + + const atAddressChanged = (event) => { + const value = event.target.value + + if (!value) { + enableAtAddress(false) + } else { + if (atAddressOptions.disabled && (loadType === 'sol' || loadType === 'abi')) { + enableAtAddress(true) + } else { + enableAtAddress(false) + } + } + setLoadedAddress(value) + } + + const loadFromAddress = () => { + let address = loadedAddress + + if (!ethJSUtil.isValidChecksumAddress(address)) { + props.tooltip(checkSumWarning()) + address = ethJSUtil.toChecksumAddress(address) + } + props.loadAddress(loadedContractData, address) + } + + const handleCheckedIPFS = () => { + const checkedState = !props.ipfsCheckedState + + props.setIpfsCheckedState(checkedState) + window.localStorage.setItem(`ipfs/${props.exEnvironment}/${props.networkName}`, checkedState.toString()) + } + + const handleContractChange = (e) => { + const value = e.target.value + + setSelectedContract(value) + } + + const checkSumWarning = () => { + return ( + + It seems you are not using a checksumed address. +
    A checksummed address is an address that contains uppercase letters, as specified in EIP-55. +
    Checksummed addresses are meant to help prevent users from sending transactions to the wrong address. +
    + ) + } + + const isOverSizePrompt = () => { + return ( +
    Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails.
    + More info: eip-170 +
    + ) + } + + return ( +
    + +
    + + { (contractList.length <= 0) && } + { abiLabel.content } +
    +
    +
    + { contractList.length <= 0 ? 'No compiled contracts' + : loadedContractData ?
    + +
    + + +
    +
    : '' + } +
    +
    or
    +
    + + +
    +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx new file mode 100644 index 0000000000..2006999b35 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx @@ -0,0 +1,197 @@ +// eslint-disable-next-line no-use-before-define +import React, { useEffect, useRef, useState } from 'react' +import * as remixLib from '@remix-project/remix-lib' +import { ContractGUIProps } from '../types' +import { CopyToClipboard } from '@remix-ui/clipboard' + +const txFormat = remixLib.execution.txFormat +export function ContractGUI (props: ContractGUIProps) { + const [title, setTitle] = useState('') + const [basicInput, setBasicInput] = useState('') + const [toggleContainer, setToggleContainer] = useState(false) + const [buttonOptions, setButtonOptions] = useState<{ + title: string, + content: string, + classList: string, + dataId: string + }>({ title: '', content: '', classList: '', dataId: '' }) + const [clipboardContent, setClipboardContent] = useState('') + const multiFields = useRef>([]) + + useEffect(() => { + const multiString = getMultiValsString() + const multiJSON = JSON.parse('[' + multiString + ']') + let encodeObj + + if (props.evmBC) { + encodeObj = txFormat.encodeData(props.funcABI, multiJSON, props.evmBC) + } else { + encodeObj = txFormat.encodeData(props.funcABI, multiJSON, null) + } + if (encodeObj.error) { + console.error(encodeObj.error) + // throw new Error(encodeObj.error) + setClipboardContent(encodeObj.error) + } else { + setClipboardContent(encodeObj.data) + } + }, []) + + useEffect(() => { + if (props.title) { + setTitle(props.title) + } else if (props.funcABI.name) { + setTitle(props.funcABI.name) + } else { + setTitle(props.funcABI.type === 'receive' ? '(receive)' : '(fallback)') + } + }, [props.title, props.funcABI]) + + useEffect(() => { + if (props.lookupOnly) { + // // call. stateMutability is either pure or view + setButtonOptions({ + title: title + ' - call', + content: 'call', + classList: 'btn-info', + dataId: title + ' - call' + }) + } else if (props.funcABI.stateMutability === 'payable' || props.funcABI.payable) { + // // transact. stateMutability = payable + setButtonOptions({ + title: title + ' - transact (payable)', + content: 'transact', + classList: 'btn-danger', + dataId: title + ' - transact (payable)' + }) + } else { + // // transact. stateMutability = nonpayable + setButtonOptions({ + title: title + ' - transact (not payable)', + content: 'transact', + classList: 'btn-warning', + dataId: title + ' - transact (not payable)' + }) + } + }, [props.lookupOnly, props.funcABI, title]) + + const switchMethodViewOn = () => { + setToggleContainer(true) + makeMultiVal() + } + + const switchMethodViewOff = () => { + setToggleContainer(false) + const multiValString = getMultiValsString() + + if (multiValString) setBasicInput(multiValString) + } + + const getMultiValsString = () => { + const valArray = multiFields.current + let ret = '' + const valArrayTest = [] + + for (let j = 0; j < valArray.length; j++) { + if (ret !== '') ret += ',' + let elVal = valArray[j] ? valArray[j].value : '' + + valArrayTest.push(elVal) + elVal = elVal.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number + elVal = elVal.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string + if (elVal) { + try { + JSON.parse(elVal) + } catch (e) { + elVal = '"' + elVal + '"' + } + } + ret += elVal + } + const valStringTest = valArrayTest.join('') + + if (valStringTest) { + return ret + } else { + return '' + } + } + + const makeMultiVal = () => { + let inputString = basicInput + + if (inputString) { + inputString = inputString.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted number by quoted number + inputString = inputString.replace(/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, '$1"$2"$3') // replace non quoted hex string by quoted hex string + const inputJSON = JSON.parse('[' + inputString + ']') + const multiInputs = multiFields.current + + for (let k = 0; k < multiInputs.length; k++) { + if (inputJSON[k]) { + multiInputs[k].value = JSON.stringify(inputJSON[k]) + } + } + } + } + + const handleActionClick = () => { + props.clickCallBack(props.funcABI.inputs, basicInput) + } + + const handleBasicInput = (e) => { + const value = e.target.value + + setBasicInput(value) + } + + const handleExpandMultiClick = () => { + const valsString = getMultiValsString() + + if (valsString) { + props.clickCallBack(props.funcABI.inputs, valsString) + } else { + props.clickCallBack(props.funcABI.inputs, '') + } + } + + return ( +
    0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive') ? 'udapp_hasArgs' : ''}`}> +
    + + 0) || (props.funcABI.type === 'fallback') || (props.funcABI.type === 'receive')) ? 'hidden' : 'visible' }} /> + 0) ? 'hidden' : 'visible' }}> +
    +
    +
    +
    +
    {title}
    + +
    +
    + {props.funcABI.inputs.map((inp, index) => { + return ( +
    + + { multiFields.current[index] = el }} className="form-control" placeholder={inp.type} title={inp.name} data-id={`multiParamManagerInput${inp.name}`} /> +
    ) + })} +
    +
    + + +
    +
    +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/environment.tsx b/libs/remix-ui/run-tab/src/lib/components/environment.tsx new file mode 100644 index 0000000000..29a9d7fc58 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/environment.tsx @@ -0,0 +1,37 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import { EnvironmentProps } from '../types' + +export function EnvironmentUI (props: EnvironmentProps) { + const handleChangeExEnv = (env: string) => { + const provider = props.providers.providerList.find(exEnv => exEnv.value === env) + const fork = provider.fork // can be undefined if connected to an external source (web3 provider / injected) + let context = provider.value + + context = context.startsWith('vm') ? 'vm' : context // context has to be 'vm', 'web3' or 'injected' + const displayContent = props.web3ProviderDialog() + + props.setExecutionContext({ context, fork }, displayContent) + } + + return ( +
    + +
    + + +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx b/libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx new file mode 100644 index 0000000000..19e7876a75 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx @@ -0,0 +1,16 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import { GasPriceProps } from '../types' + +export function GasPriceUI (props: GasPriceProps) { + const handleGasLimit = (e) => { + props.setGasFee(e.target.value) + } + + return ( +
    + + +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx b/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx new file mode 100644 index 0000000000..cca6777535 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx @@ -0,0 +1,48 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import { InstanceContainerProps } from '../types' +import { UniversalDappUI } from './universalDappUI' + +export function InstanceContainerUI (props: InstanceContainerProps) { + const { instanceList } = props.instances + + const clearInstance = () => { + props.clearInstances() + } + + return ( +
    +
    + Deployed Contracts + { instanceList.length > 0 + ? : null + } +
    + { instanceList.length > 0 + ?
    { props.instances.instanceList.map((instance, index) => { + return + }) } +
    + : + Currently you have no contract instances to interact with. + + } +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/mainnet.tsx b/libs/remix-ui/run-tab/src/lib/components/mainnet.tsx new file mode 100644 index 0000000000..30da897734 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/mainnet.tsx @@ -0,0 +1,133 @@ +// eslint-disable-next-line no-use-before-define +import React, { useEffect, useState } from 'react' +import { CopyToClipboard } from '@remix-ui/clipboard' +import Web3 from 'web3' +import { MainnetProps } from '../types' + +export function MainnetPrompt (props: MainnetProps) { + const [baseFee, setBaseFee] = useState('') + + useEffect(() => { + props.init((txFeeText, gasPriceValue, gasPriceStatus) => { + if (txFeeText) props.setTxFeeContent(txFeeText) + if (gasPriceValue) onGasPriceChange(gasPriceValue) + if (props.network && props.network.lastBlock && props.network.lastBlock.baseFeePerGas) { + const baseFee = Web3.utils.fromWei(Web3.utils.toBN(parseInt(props.network.lastBlock.baseFeePerGas, 16)), 'Gwei') + + setBaseFee(baseFee) + onMaxFeeChange(baseFee) + } + if (gasPriceStatus !== undefined) props.updateGasPriceStatus(gasPriceStatus) + }) + }, []) + + const onMaxFeeChange = (value: string) => { + console.log('value: ', value) + const maxFee = value + // @ts-ignore + if (parseInt(props.network.lastBlock.baseFeePerGas, 16) > Web3.utils.toWei(maxFee, 'Gwei')) { + props.setTxFeeContent('Transaction is invalid. Max fee should not be less than Base fee') + props.updateGasPriceStatus(false) + props.updateConfirmSettings(true) + return + } else { + props.updateGasPriceStatus(true) + props.updateConfirmSettings(false) + } + + props.setNewGasPrice(maxFee, (txFeeText, priceStatus) => { + props.setTxFeeContent(txFeeText) + if (priceStatus) { + props.updateConfirmSettings(false) + } else { + props.updateConfirmSettings(true) + } + props.updateGasPriceStatus(priceStatus) + props.updateMaxFee(maxFee) + props.updateBaseFeePerGas(props.network.lastBlock.baseFeePerGas) + }) + } + + const onGasPriceChange = (value: string) => { + const gasPrice = value + + props.setNewGasPrice(gasPrice, (txFeeText, priceStatus) => { + props.setTxFeeContent(txFeeText) + props.updateGasPriceStatus(priceStatus) + props.updateGasPrice(gasPrice) + }) + } + + const onMaxPriorityFeeChange = (value: string) => { + props.updateMaxPriorityFee(value) + } + + return ( +
    +
    You are about to create a transaction on {props.network.name} Network. Confirm the details to send the info to your provider. +
    The provider for many users is MetaMask. The provider will ask you to sign the transaction before it is sent to {props.network.name} Network. +
    +
    +
    + From: + {props.tx.from} +
    +
    + To: + {props.tx.to ? props.tx.to : '(Contract Creation)'} +
    +
    + Data: +
    {props.tx.data && props.tx.data.length > 50 ? props.tx.data.substring(0, 49) + '...' : props.tx.data}
    +            
    +          
    +
    +
    + Amount: + {props.amount} Ether +
    +
    + Gas estimation: + {props.gasEstimation} +
    +
    + Gas limit: + ${props.tx.gas} +
    + { + props.network.lastBlock.baseFeePerGas + ?
    +
    +
    + Max Priority fee: + onMaxPriorityFeeChange(e.target.value)} defaultValue={props.maxPriorityFee} /> + Gwei +
    +
    +
    +
    + Max fee (Not less than base fee {Web3.utils.fromWei(Web3.utils.toBN(parseInt(props.network.lastBlock.baseFeePerGas, 16)), 'Gwei')} Gwei): + onMaxFeeChange(e.target.value)} defaultValue={baseFee} /> + Gwei + +
    +
    +
    + :
    + Gas price: + onGasPriceChange(e.target.value)} /> + Gwei (visit ethgasstation.info for current gas price info.) +
    + } +
    + Max transaction fee: + { props.txFeeContent } +
    +
    +
    + + +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/network.tsx b/libs/remix-ui/run-tab/src/lib/components/network.tsx new file mode 100644 index 0000000000..3a7a9c7480 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/network.tsx @@ -0,0 +1,15 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import { NetworkProps } from '../types' + +export function NetworkUI (props: NetworkProps) { + return ( +
    +
    +
    +
    + { props.networkName } +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/passphrase.tsx b/libs/remix-ui/run-tab/src/lib/components/passphrase.tsx new file mode 100644 index 0000000000..c118ede4a0 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/passphrase.tsx @@ -0,0 +1,22 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' + +interface PromptProps { + message: string, + setPassphrase: (passphrase: string) => void, + defaultValue?: string +} + +export function PassphrasePrompt (props: PromptProps) { + const handleSignPassphrase = (e) => { + props.setPassphrase(e.target.value) + } + + return ( +
    { props.message } +
    + +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx b/libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx new file mode 100644 index 0000000000..7cb30e65a6 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx @@ -0,0 +1,53 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import { TreeView, TreeViewItem } from '@remix-ui/tree-view' +import { RecorderProps } from '../types' + +export function RecorderUI (props: RecorderProps) { + const card = (title: string, recorderCount: number) => { + return ( +
    {}}> +
    +
    {title}
    +
    +
    +
    {recorderCount}
    +
    +
    +
    +
    +
    +
    +
    + ) + } + + const triggerRecordButton = () => { + props.storeScenario(props.scenarioPrompt) + } + + const handleClickRunButton = () => { + props.runCurrentScenario(props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt, props.logBuilder) + } + + return ( +
    + + +
    +
    + All transactions (deployed contracts and function executions) in this environment can be saved and replayed in + another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3. +
    +
    + + +
    +
    +
    +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/scenario.tsx b/libs/remix-ui/run-tab/src/lib/components/scenario.tsx new file mode 100644 index 0000000000..689fd116b3 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/scenario.tsx @@ -0,0 +1,22 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' + +interface ScenarioProps { + message: string, + setScenarioPath: (path: string) => void, + defaultValue?: string +} + +export function ScenarioPrompt (props: ScenarioProps) { + const handleScenarioPath = (e) => { + props.setScenarioPath(e.target.value) + } + + return ( +
    { props.message } +
    + +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx b/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx new file mode 100644 index 0000000000..842d047899 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx @@ -0,0 +1,22 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import { SettingsProps } from '../types' +import { EnvironmentUI } from './environment' +import { NetworkUI } from './network' +import { AccountUI } from './account' +import { GasPriceUI } from './gasPrice' +import { ValueUI } from './value' + +export function SettingsUI (props: SettingsProps) { + // this._deps.config.events.on('settings/personal-mode_changed', this.onPersonalChange.bind(this)) + + return ( +
    + + + + + +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx new file mode 100644 index 0000000000..8c3bc9df2d --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx @@ -0,0 +1,295 @@ +// eslint-disable-next-line no-use-before-define +import React, { useEffect, useState } from 'react' +import { FuncABI, UdappProps } from '../types' +import { CopyToClipboard } from '@remix-ui/clipboard' +import * as remixLib from '@remix-project/remix-lib' +import * as ethJSUtil from 'ethereumjs-util' +import { ContractGUI } from './contractGUI' +import { TreeView, TreeViewItem } from '@remix-ui/tree-view' +import { BN } from 'ethereumjs-util' +import { is0XPrefixed, isHexadecimal, isNumeric, shortenAddress } from '@remix-ui/helper' + +const txHelper = remixLib.execution.txHelper + +export function UniversalDappUI (props: UdappProps) { + const [toggleExpander, setToggleExpander] = useState(true) + const [contractABI, setContractABI] = useState(null) + const [address, setAddress] = useState('') + const [expandPath, setExpandPath] = useState([]) + const [llIError, setLlIError] = useState('') + const [calldataValue, setCalldataValue] = useState('') + const [evmBC, setEvmBC] = useState(null) + + useEffect(() => { + if (!props.instance.abi) { + const abi = txHelper.sortAbiFunction(props.instance.contractData.abi) + + setContractABI(abi) + } else { + setContractABI(props.instance.abi) + } + }, [props.instance.abi]) + + useEffect(() => { + if (props.instance.address) { + // @ts-ignore + let address = (props.instance.address.slice(0, 2) === '0x' ? '' : '0x') + props.instance.address.toString('hex') + + address = ethJSUtil.toChecksumAddress(address) + setAddress(address) + } + }, [props.instance.address]) + + useEffect(() => { + if (props.instance.contractData) { + setEvmBC(props.instance.contractData.bytecodeObject) + } + }, [props.instance.contractData]) + + const sendData = () => { + setLlIError('') + const fallback = txHelper.getFallbackInterface(contractABI) + const receive = txHelper.getReceiveInterface(contractABI) + const args = { + funcABI: fallback || receive, + address: address, + contractName: props.instance.name, + contractABI: contractABI + } + const amount = props.sendValue + + if (amount !== '0') { + // check for numeric and receive/fallback + if (!isNumeric(amount)) { + return setLlIError('Value to send should be a number') + } else if (!receive && !(fallback && fallback.stateMutability === 'payable')) { + return setLlIError("In order to receive Ether transfer the contract should have either 'receive' or payable 'fallback' function") + } + } + let calldata = calldataValue + + if (calldata) { + if (calldata.length < 4 && is0XPrefixed(calldata)) { + return setLlIError('The calldata should be a valid hexadecimal value with size of at least one byte.') + } else { + if (is0XPrefixed(calldata)) { + calldata = calldata.substr(2, calldata.length) + } + if (!isHexadecimal(calldata)) { + return setLlIError('The calldata should be a valid hexadecimal value.') + } + } + if (!fallback) { + return setLlIError("'Fallback' function is not defined") + } + } + + if (!receive && !fallback) return setLlIError('Both \'receive\' and \'fallback\' functions are not defined') + + // we have to put the right function ABI: + // if receive is defined and that there is no calldata => receive function is called + // if fallback is defined => fallback function is called + if (receive && !calldata) args.funcABI = receive + else if (fallback) args.funcABI = fallback + + if (!args.funcABI) return setLlIError('Please define a \'Fallback\' function to send calldata and a either \'Receive\' or payable \'Fallback\' to send ethers') + runTransaction(false, args.funcABI, null, calldataValue) + } + + const toggleClass = () => { + setToggleExpander(!toggleExpander) + } + + const remove = () => { + props.removeInstance(props.index) + } + + const runTransaction = (lookupOnly, funcABI: FuncABI, valArr, inputsValues, funcIndex?: number) => { + const functionName = funcABI.type === 'function' ? funcABI.name : `(${funcABI.type})` + const logMsg = `${lookupOnly ? 'call' : 'transact'} to ${props.instance.name}.${functionName}` + + props.runTransactions( + props.index, + lookupOnly, + funcABI, + inputsValues, + props.instance.name, + contractABI, + props.instance.contractData, + address, + logMsg, + props.logBuilder, + props.mainnetPrompt, + props.gasEstimationPrompt, + props.passphrasePrompt, + funcIndex) + } + + const extractDataDefault = (item, parent?) => { + const ret: any = {} + + if (BN.isBN(item)) { + ret.self = item.toString(10) + ret.children = [] + } else { + if (item instanceof Array) { + ret.children = item.map((item, index) => { + return { key: index, value: item } + }) + ret.self = 'Array' + ret.isNode = true + ret.isLeaf = false + } else if (item instanceof Object) { + ret.children = Object.keys(item).map((key) => { + return { key: key, value: item[key] } + }) + ret.self = 'Object' + ret.isNode = true + ret.isLeaf = false + } else { + ret.self = item + ret.children = null + ret.isNode = false + ret.isLeaf = true + } + } + return ret + } + + const handleExpand = (path: string) => { + if (expandPath.includes(path)) { + const filteredPath = expandPath.filter(value => value !== path) + + setExpandPath(filteredPath) + } else { + setExpandPath([...expandPath, path]) + } + } + + const handleCalldataChange = (e) => { + const value = e.target.value + + setCalldataValue(value) + } + + const label = (key: string | number, value: string) => { + return ( +
    + + +
    + ) + } + + const renderData = (item, parent, key: string | number, keyPath: string) => { + const data = extractDataDefault(item, parent) + const children = (data.children || []).map((child) => { + return ( + renderData(child.value, data, child.key, keyPath + '/' + child.key) + ) + }) + + if (children && children.length > 0) { + return ( + handleExpand(keyPath)} expand={expandPath.includes(keyPath)}> + + {children} + + + ) + } else { + return handleExpand(keyPath)} expand={expandPath.includes(keyPath)} /> + } + } + + return ( +
    +
    + +
    +
    + + {props.instance.name} at {shortenAddress(address)} ({props.context}) + +
    +
    + +
    +
    + +
    +
    +
    + { + contractABI && contractABI.map((funcABI, index) => { + if (funcABI.type !== 'function') return null + const isConstant = funcABI.constant !== undefined ? funcABI.constant : false + const lookupOnly = funcABI.stateMutability === 'view' || funcABI.stateMutability === 'pure' || isConstant + const inputs = props.getFuncABIInputs(funcABI) + + return <> + { + runTransaction(lookupOnly, funcABI, valArray, inputsValues, index) + }} + inputs={inputs} + evmBC={evmBC} + lookupOnly={lookupOnly} + key={index} + /> +
    + + { + Object.keys(props.instance.decodedResponse || {}).map((key) => { + const funcIndex = index.toString() + const response = props.instance.decodedResponse[key] + + return key === funcIndex ? Object.keys(response || {}).map((innerkey) => { + return renderData(props.instance.decodedResponse[key][innerkey], response, innerkey, innerkey) + }) : null + }) + } + +
    + + }) + } +
    +
    +
    +
    + Low level interactions +
    + + + +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/value.tsx b/libs/remix-ui/run-tab/src/lib/components/value.tsx new file mode 100644 index 0000000000..3b2008a5f1 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/value.tsx @@ -0,0 +1,75 @@ +// eslint-disable-next-line no-use-before-define +import React, { useEffect, useRef, useState } from 'react' +import { BN } from 'ethereumjs-util' +import { isNumeric } from '@remix-ui/helper' +import { ValueProps } from '../types' + +export function ValueUI (props: ValueProps) { + const [sendValue, setSendValue] = useState(props.sendValue) + const inputValue = useRef({} as HTMLInputElement) + + useEffect(() => { + (sendValue !== props.sendValue) && props.setSendValue(sendValue) + }, [sendValue]) + + const validateInputKey = (e) => { + // preventing not numeric keys + // preventing 000 case + if (!isNumeric(e.key) || + (e.key === '0' && !parseInt(inputValue.current.value) && inputValue.current.value.length > 0)) { + e.preventDefault() + } + } + + const validateValue = (e) => { + const value = e.target.value + + if (!value) { + // assign 0 if given value is + // - empty + return setSendValue('0') + } + + let v + try { + v = new BN(value, 10) + setSendValue(v.toString(10)) + } catch (e) { + // assign 0 if given value is + // - not valid (for ex 4345-54) + // - contains only '0's (for ex 0000) copy past or edit + setSendValue('0') + } + + // if giveen value is negative(possible with copy-pasting) set to 0 + if (v.lt(0)) setSendValue('0') + } + + return ( +
    + +
    + + +
    +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/components/web3Dialog.tsx b/libs/remix-ui/run-tab/src/lib/components/web3Dialog.tsx new file mode 100644 index 0000000000..30055f322a --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/web3Dialog.tsx @@ -0,0 +1,44 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' + +interface web3ProviderDialogProps { + setWeb3Endpoint: (value: string) => void, + externalEndpoint: string +} +const thePath = '' + +export function Web3ProviderDialog (props: web3ProviderDialogProps) { + const handleInputEndpoint = (e) => { + props.setWeb3Endpoint(e.target.value) + } + + return ( + <> +
    + Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see Geth Docs on rpc server) +
    geth --http --http.corsdomain https://remix.ethereum.org
    +
    + To run Remix & a local Geth test node, use this command: (see Geth Docs on Dev mode) +
    geth --http --http.corsdomain="{window.origin}" --http.api web3,eth,debug,personal,net --vmdebug --datadir {thePath} --dev console
    +
    +
    + WARNING: It is not safe to use the --http.corsdomain flag with a wildcard: --http.corsdomain * +
    +
    For more info: Remix Docs on Web3 Provider +
    +
    + Web3 Provider Endpoint +
    + + + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/css/card.css b/libs/remix-ui/run-tab/src/lib/css/card.css new file mode 100644 index 0000000000..1decf7c720 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/css/card.css @@ -0,0 +1,12 @@ +.udapp_cardContainer { + padding : 0 24px 16px; + margin : 0; + background : none; +} +.udapp_arrow { + font-weight : bold; + cursor : pointer; + font-size : 14px; +} +.udapp_arrow:hover { +} \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/css/run-tab.css b/libs/remix-ui/run-tab/src/lib/css/run-tab.css new file mode 100644 index 0000000000..f5ec6c2e65 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/css/run-tab.css @@ -0,0 +1,509 @@ +.udapp_runTabView { + display: flex; + flex-direction: column; +} +.udapp_runTabView::-webkit-scrollbar { + display: none; +} +.udapp_settings { + padding: 0 24px 16px; +} +.udapp_crow { + display: block; + margin-top: 8px; +} +.udapp_col1 { + width: 30%; + float: left; + align-self: center; +} +.udapp_settingsLabel { + font-size: 11px; + margin-bottom: 4px; + text-transform: uppercase; +} +.udapp_environment { + display: flex; + align-items: center; + position: relative; + width: 100%; +} +.udapp_environment a { + margin-left: 7px; +} +.udapp_account { + display: flex; + align-items: center; +} +.udapp_account i { + margin-left: 12px; +} +.udapp_col2 { + border-radius: 3px; +} +.udapp_col2_1 { + width: 164px; + min-width: 164px; +} +.udapp_col2_2 { +} +.udapp_select { + font-weight: normal; + width: 100%; + overflow: hidden; +} +.udapp_instanceContainer { + display: flex; + flex-direction: column; + margin-bottom: 2%; + border: none; + text-align: center; + padding: 0 14px 16px; +} +.udapp_pendingTxsContainer { + display: flex; + flex-direction: column; + margin-top: 2%; + border: none; + text-align: center; +} +.udapp_container { + padding: 0 24px 16px; +} +.udapp_recorderDescription { + margin: 0 15px 15px 0; + } +.udapp_contractNames { + width: 100%; + border: 1px solid +} +.udapp_subcontainer { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 8px; +} +.udapp_subcontainer i { + width: 16px; + display: flex; + justify-content: center; + margin-left: 1px; +} +.udapp_button button{ + flex: none; +} +.udapp_button { + display: flex; + align-items: center; + margin-top: 13px; +} +.udapp_transaction { +} +.udapp_atAddress { + margin: 0; + min-width: 100px; + width: 100px; + height: 100%; + word-break: inherit; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: 0; +} +.udapp_atAddressSect { + margin-top: 8px; + height: 32px; +} +.udapp_atAddressSect input { + height: 32px; + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; +} +.udapp_ataddressinput { + padding: .25rem; +} +.udapp_create { +} +.udapp_input { + font-size: 10px !important; +} +.udapp_noInstancesText { + font-style: italic; + text-align: left; + padding-left: 15px; +} +.udapp_pendingTxsText { + font-style: italic; + display: flex; + justify-content: space-evenly; + align-items: center; + flex-wrap: wrap; +} +.udapp_item { + margin-right: 1em; + display: flex; + align-items: center; +} +.udapp_pendingContainer { + display: flex; + align-items: baseline; +} +.udapp_pending { + height: 25px; + text-align: center; + padding-left: 10px; + border-radius: 3px; + margin-left: 5px; +} +.udapp_disableMouseEvents { + pointer-events: none; +} +.udapp_icon { + cursor: pointer; + font-size: 12px; + cursor: pointer; + margin-left: 5px; +} +.udapp_icon:hover { + font-size: 12px; + color: var(--warning); +} +.udapp_errorIcon { + color: var(--warning); + margin-left: 15px; +} +.udapp_failDesc { + color: var(--warning); + padding-left: 10px; + display: inline; +} +.udapp_network { + margin-left: 8px; + pointer-events: none; +} +.udapp_networkItem { + margin-right: 5px; +} +.udapp_transactionActions { + display: flex; + justify-content: space-evenly; + width: 145px; +} +.udapp_orLabel { + text-align: center; + text-transform: uppercase; +} +.udapp_infoDeployAction { + margin-left: 1px; + font-size: 13px; + color: var(--info); +} +.udapp_gasValueContainer { + flex-direction: row; + display: flex; +} +.udapp_gasNval { + width: 55%; + font-size: 0.8rem; +} +.udapp_gasNvalUnit { + width: 41%; + margin-left: 10px; + font-size: 0.8rem; +} +.udapp_deployDropdown { + text-align: center; + text-transform: uppercase; +} +.udapp_checkboxAlign { + padding-top: 2px; +} +.udapp_instanceTitleContainer { + display: flex; + align-items: center; +} +.udapp_calldataInput{ + height: 32px; +} +.udapp_title { + display: flex; + justify-content: space-between; + font-size: 11px; + width: 100%; + overflow: hidden; + word-break: break-word; + line-height: initial; + overflow: visible; + padding: 0 0 8px; + margin: 0; + background: none; + border: none; +} +.udapp_title button { + background: none; + border: none; +} +.udapp_titleLine { + display: flex; + align-items: baseline; +} +.udapp_titleText { + word-break: break-word; + width: 100%; + border: none; + overflow: hidden; +} +.udapp_spanTitleText { + line-height: 12px; + padding: 0; + font-size: 11px; + width:100%; + border: none; + background: none; + text-transform: uppercase; + overflow: hidden; +} +.udapp_inputGroupText { + width: 100%; +} +.udapp_title .udapp_copy { + color: var(--primary); +} +.udapp_titleExpander { + padding: 5px 7px; +} +.udapp_nameNbuts { + display: contents; + flex-wrap: nowrap; + width: 100%; +} +.udapp_instance { + display: block; + flex-direction: column; + margin-bottom: 12px; + background: none; + border-radius: 2px; +} +.udapp_instance.udapp_hidesub { + border-bottom: 1px solid; +} +.udapp_instance.udapp_hidesub .udapp_title { + display: flex; +} +.udapp_instance.udapp_hidesub .udapp_udappClose { + display: flex; +} +.udapp_instance.udapp_hidesub > * { + display: none; +} +.udapp_methCaret { + min-width: 12px; + width: 12px; + margin-left: 4px; + cursor: pointer; + font-size: 16px; + line-height: 0.6; + vertical-align: middle; + padding: 0; +} +.udapp_cActionsWrapper { + border-top-left-radius: 0; + border-bottom-left-radius: 0.25rem; + border-top-rightt-radius: 0; + border-bottom-right-radius: 0.25rem; + padding: 8px 10px 7px; +} +.udapp_group:after { + content: ""; + display: table; + clear: both; +} +.udapp_buttonsContainer { + margin-top: 2%; + display: flex; + overflow: hidden; +} +.udapp_instanceButton { + height: 32px; + border-radius: 3px; + white-space: nowrap; + font-size: 11px; + overflow: hidden; + text-overflow: ellipsis; +} +.udapp_closeIcon { + font-size: 12px; + cursor: pointer; + margin-left: 5px; +} +.udapp_udappClose { + display: flex; + justify-content: flex-end; +} +.udapp_contractProperty { + width:100%; +} +.udapp_contractProperty.udapp_hasArgs input { + padding: .36em; + border-radius: 5px; +} +.udapp_contractProperty .udapp_contractActionsContainerSingle input{ + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.udapp_contractProperty button { + min-width: 100px; + width: 100px; + margin:0; + word-break: inherit; +} +.udapp_contractProperty button:disabled { + cursor: not-allowed; + background-color: white; + border-color: lightgray; +} +.udapp_contractProperty.udapp_constant button { + min-width: 100px; + width: 100px; + margin:0; + word-break: inherit; + outline: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.udapp_contractProperty > .udapp_value { + box-sizing: border-box; + float: left; + align-self: center; + margin-left: 4px; +} +.udapp_contractActionsContainer { + width: 100%; + margin-bottom: 8px; +} +.udapp_contractActionsContainerSingle { + display: flex; + width: 100%; +} +.udapp_contractActionsContainerSingle i { + line-height: 2; +} +.udapp_contractActionsContainerMulti { + display:none; + width: 100%; +} +.udapp_contractActionsContainerMultiInner { + width: 100%; + padding: 16px 8px 16px 14px; + border-radius: 3px; + margin-bottom: 8px; +} +.udapp_multiHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + text-align: left; + font-size: 10px; + font-weight: bold; +} +.udapp_contractActionsContainerMultiInner .udapp_multiTitle { + padding-left: 10px; +} +.udapp_contractProperty .udapp_multiTitle { + padding: 0; + line-height: 16px; + display: inline-block; + font-size: 12px; + font-weight: bold; + cursor: default; +} +.udapp_contractProperty .udapp_contractActionsContainerMultiInner .udapp_multiArg label{ + text-align: right; +} +.udapp_multiHeader .udapp_methCaret { + float: right; + margin-right: 0; +} +.udapp_contractProperty.udapp_constant .udapp_multiTitle { + display: inline-block; + width: 90%; + /* font-size: 10px; */ + height: 25px; + padding-left: 20px; + font-weight: bold; + line-height: 25px; + cursor: default; +} +.udapp_multiArg { + display: flex; + align-items: center; + justify-content: flex-end; + margin-top: 4px; +} +.udapp_multiArg input{ + padding: 5px; +} +.udapp_multiArg label { + width: auto; + padding: 0; + margin: 0 4px 0 0; + font-size: 10px; + line-height: 12px; + text-align: right; + word-break: initial; +} +.udapp_multiArg button { + max-width: 100px; + border-radius: 3px; + border-width: 1px; + width: inherit; +} +.udapp_multiHeader button { + display: inline-block; + width: 94%; +} +.udapp_hasArgs .udapp_multiArg input { + border-left: 1px solid #dddddd; + width: 67%; +} +.udapp_hasArgs input { + display: block; + height: 32px; + border: 1px solid #dddddd; + padding: .36em; + border-left: none; + padding: 8px 8px 8px 10px; + font-size: 10px !important; +} +.udapp_hasArgs button { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 11px; +} +.udapp_hasArgs .udapp_contractActionsContainerMulti button { + border-radius: 3px; +} +.udapp_contractActionsContainerMultiInner .udapp_multiArg i { + padding-right: 10px; +} +.udapp_hideWarningsContainer { + display: flex; + align-items: center; + margin-left: 2% +} +#confirmsetting { + z-index: 1; +} +.udapp_wrapword { + white-space: pre-wrap; /* Since CSS 2.1 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + diff --git a/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts new file mode 100644 index 0000000000..7ad0b0f500 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts @@ -0,0 +1,662 @@ +import { ContractData } from '../types' + +interface Action { + type: string + payload: any +} + +export interface RunTabState { + accounts: { + loadedAccounts: Record, + isRequesting: boolean, + isSuccessful: boolean, + error: string, + selectedAccount: string + }, + sendValue: string, + sendUnit: 'ether' | 'finney' | 'gwei' | 'wei', + gasLimit: number, + selectExEnv: string, + personalMode: boolean, + networkName: string, + providers: { + providerList: { + id?: string, + dataId?: string, + title?: string, + value: string, + fork?: string + content: string + }[], + isRequesting: boolean, + isSuccessful: boolean, + error: string + }, + notification: { + title: string, + message: string, + actionOk: () => void, + actionCancel: (() => void) | null, + labelOk: string, + labelCancel: string + }, + externalEndpoint: string, + popup: string, + passphrase: string, + matchPassphrase: string, + contracts: { + contractList: { + name: string, + alias: string, + file: string + }[], + loadType: 'abi' | 'sol' | 'other' + currentFile: string, + compilationCount: number, + isRequesting: boolean, + isSuccessful: boolean, + error: string + }, + ipfsChecked: boolean, + gasPriceStatus: boolean, + confirmSettings: boolean, + maxFee: string, + maxPriorityFee: string, + baseFeePerGas: string, + txFeeContent: string, + gasPrice: string, + instances: { + instanceList: { + contractData?: ContractData, + address: string, + name: string, + decodedResponse?: Record, + abi?: any + }[], + error: string + }, + recorder: { + pathToScenario: string, + transactionCount: number + }, + envToasterContent: (env: { context: string, fork: string }, from: string) => JSX.Element + web3Dialog: () => JSX.Element +} + +export const runTabInitialState: RunTabState = { + accounts: { + loadedAccounts: {}, + isRequesting: false, + isSuccessful: false, + error: null, + selectedAccount: '' + }, + sendValue: '0', + sendUnit: 'wei', + gasLimit: 3000000, + selectExEnv: 'vm-london', + personalMode: false, + networkName: 'VM', + providers: { + providerList: [{ + id: 'vm-mode-london', + dataId: 'settingsVMLondonMode', + title: 'Execution environment does not connect to any node, everything is local and in memory only.', + value: 'vm-london', + fork: 'london', + content: 'JavaScript VM (London)' + }, { + id: 'vm-mode-berlin', + dataId: 'settingsVMBerlinMode', + title: 'Execution environment does not connect to any node, everything is local and in memory only.', + value: 'vm-berlin', + fork: 'berlin', + content: 'JavaScript VM (Berlin)' + }, { + id: 'injected-mode', + dataId: 'settingsInjectedMode', + title: 'Execution environment has been provided by Metamask or similar provider.', + value: 'injected', + content: 'Injected Web3' + }, { + id: 'web3-mode', + dataId: 'settingsWeb3Mode', + title: `Execution environment connects to node at localhost (or via IPC if available), transactions will be sent to the network and can cause loss of money or worse! + If this page is served via https and you access your node via http, it might not work. In this case, try cloning the repository and serving it via http.`, + value: 'web3', + content: 'Web3 Provider' + }], + isRequesting: false, + isSuccessful: false, + error: null + }, + notification: { + title: '', + message: '', + actionOk: () => {}, + actionCancel: () => {}, + labelOk: '', + labelCancel: '' + }, + externalEndpoint: 'http://127.0.0.1:8545', + popup: '', + passphrase: '', + matchPassphrase: '', + contracts: { + contractList: [], + loadType: 'other', + currentFile: '', + compilationCount: 0, + isRequesting: false, + isSuccessful: false, + error: null + }, + ipfsChecked: false, + gasPriceStatus: false, + confirmSettings: false, + maxFee: '', + maxPriorityFee: '1', + baseFeePerGas: '', + txFeeContent: '', + gasPrice: '', + instances: { + instanceList: [], + error: null + }, + recorder: { + pathToScenario: 'scenario.json', + transactionCount: 0 + }, + envToasterContent: null, + web3Dialog: null +} + +export const runTabReducer = (state: RunTabState = runTabInitialState, action: Action) => { + switch (action.type) { + case 'FETCH_ACCOUNTS_LIST_REQUEST': { + return { + ...state, + accounts: { + ...state.accounts, + isRequesting: true, + isSuccessful: false, + error: null + } + } + } + + case 'FETCH_ACCOUNTS_LIST_SUCCESS': { + const payload: Record = action.payload + + return { + ...state, + accounts: { + ...state.accounts, + loadedAccounts: payload, + isSuccessful: true, + isRequesting: false, + error: null + } + } + } + + case 'FETCH_ACCOUNTS_LIST_FAILED': { + const payload: string = action.payload + + return { + ...state, + accounts: { + ...state.accounts, + isRequesting: false, + isSuccessful: false, + error: payload + } + } + } + + case 'SET_SEND_VALUE': { + const payload: string = action.payload + + return { + ...state, + sendValue: payload + } + } + + case 'SET_SELECTED_ACCOUNT': { + const payload: string = action.payload + + return { + ...state, + accounts: { + ...state.accounts, + selectedAccount: payload + } + } + } + + case 'SET_SEND_UNIT': { + const payload: 'ether' | 'finney' | 'gwei' | 'wei' = action.payload + + return { + ...state, + sendUnit: payload + } + } + + case 'SET_GAS_LIMIT': { + const payload: number = action.payload + + return { + ...state, + gasLimit: payload + } + } + + case 'SET_EXECUTION_ENVIRONMENT': { + const payload: string = action.payload + + return { + ...state, + selectExEnv: payload, + networkName: state.selectExEnv === 'vm-london' || state.selectExEnv === 'vm-berlin' ? 'VM' : state.networkName, + accounts: { + ...state.accounts, + selectedAccount: '', + loadedAccounts: {} + } + } + } + + case 'SET_PERSONAL_MODE': { + const payload: boolean = action.payload + + return { + ...state, + personalMode: payload + } + } + + case 'SET_NETWORK_NAME': { + const payload: string = action.payload + + return { + ...state, + networkName: payload + } + } + + case 'FETCH_PROVIDER_LIST_REQUEST': { + return { + ...state, + providers: { + ...state.providers, + isRequesting: true, + isSuccessful: false, + error: null + } + } + } + + case 'FETCH_PROVIDER_LIST_SUCCESS': { + const payload: { id?: string, dataId?: string, title?: string, value: string, fork?: string, content: string }[] = action.payload + + return { + ...state, + providers: { + ...state.providers, + providerList: payload, + isSuccessful: true, + isRequesting: false, + error: null + } + } + } + + case 'FETCH_PROVIDER_LIST_FAILED': { + const payload: string = action.payload + + return { + ...state, + providers: { + ...state.providers, + isRequesting: false, + isSuccessful: false, + error: payload + } + } + } + + case 'ADD_PROVIDER': { + const payload: string = action.payload + + return { + ...state, + providers: { + ...state.providers, + providerList: { ...state.providers.providerList, payload } + } + } + } + + case 'REMOVE_PROVIDER': { + const payload: string = action.payload + + return { + ...state, + providers: { + ...state.providers, + providerList: delete state.providers.providerList[payload] ? state.providers.providerList : state.providers.providerList + } + } + } + + case 'DISPLAY_NOTIFICATION': { + const payload = action.payload as { title: string, message: string, actionOk: () => void, actionCancel: () => void, labelOk: string, labelCancel: string } + + return { + ...state, + notification: { + title: payload.title, + message: payload.message, + actionOk: payload.actionOk || runTabInitialState.notification.actionOk, + actionCancel: payload.actionCancel || runTabInitialState.notification.actionCancel, + labelOk: payload.labelOk, + labelCancel: payload.labelCancel + } + } + } + + case 'HIDE_NOTIFICATION': { + return { + ...state, + notification: runTabInitialState.notification + } + } + + case 'SET_EXTERNAL_WEB3_ENDPOINT': { + const payload: string = action.payload + + return { + ...state, + externalEndpoint: payload + } + } + + case 'DISPLAY_POPUP_MESSAGE': { + const payload = action.payload as string + + return { + ...state, + popup: payload + } + } + + case 'HIDE_POPUP_MESSAGE': { + return { + ...state, + popup: '' + } + } + + case 'SET_PASSPHRASE': { + const passphrase: string = action.payload + + return { + ...state, + passphrase + } + } + + case 'SET_MATCH_PASSPHRASE': { + const passphrase: string = action.payload + + return { + ...state, + matchPassphrase: passphrase + } + } + + case 'FETCH_CONTRACT_LIST_REQUEST': { + return { + ...state, + contracts: { + ...state.contracts, + isRequesting: true, + isSuccessful: false, + error: null + } + } + } + + case 'FETCH_CONTRACT_LIST_SUCCESS': { + const payload: { name: string, alias: string, file: string }[] = action.payload + + return { + ...state, + contracts: { + ...state.contracts, + contractList: payload, + isSuccessful: true, + isRequesting: false, + error: null + } + } + } + + case 'FETCH_CONTRACT_LIST_FAILED': { + const payload: string = action.payload + + return { + ...state, + contracts: { + ...state.contracts, + isRequesting: false, + isSuccessful: false, + error: payload + } + } + } + + case 'SET_LOAD_TYPE': { + const payload: 'abi' | 'sol' | 'other' = action.payload + + return { + ...state, + contracts: { + ...state.contracts, + loadType: payload + } + } + } + + case 'SET_CURRENT_FILE': { + const payload: string = action.payload + + return { + ...state, + contracts: { + ...state.contracts, + currentFile: payload, + compilationCount: state.contracts.compilationCount + 1 + } + } + } + + case 'SET_IPFS_CHECKED_STATE': { + const payload: boolean = action.payload + + return { + ...state, + ipfsChecked: payload + } + } + + case 'SET_GAS_PRICE_STATUS': { + const payload: boolean = action.payload + + return { + ...state, + gasPriceStatus: payload + } + } + + case 'SET_CONFIRM_SETTINGS': { + const payload: boolean = action.payload + + return { + ...state, + confirmSettings: payload + } + } + + case 'SET_MAX_FEE': { + const payload: string = action.payload + + return { + ...state, + maxFee: payload + } + } + + case 'SET_MAX_PRIORITY_FEE': { + const payload: string = action.payload + + return { + ...state, + maxPriorityFee: payload + } + } + + case 'SET_BASE_FEE_PER_GAS': { + const payload: string = action.payload + + return { + ...state, + baseFeePerGas: payload + } + } + + case 'SET_GAS_PRICE': { + const payload: string = action.payload + + return { + ...state, + gasPrice: payload + } + } + + case 'SET_TX_FEE_CONTENT': { + const payload: string = action.payload + + return { + ...state, + txFeeContent: payload + } + } + + case 'ADD_INSTANCE': { + const payload: { contractData: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record } = action.payload + + return { + ...state, + instances: { + ...state.instances, + instanceList: [...state.instances.instanceList, payload] + } + } + } + + case 'REMOVE_INSTANCE': { + const payload: number = action.payload + + return { + ...state, + instances: { + ...state.instances, + instanceList: state.instances.instanceList.filter((_, index) => index !== payload) + } + } + } + + case 'CLEAR_INSTANCES': { + return { + ...state, + instances: { + instanceList: [], + error: null + } + } + } + + case 'SET_DECODED_RESPONSE': { + const payload: { instanceIndex: number, funcIndex: number, response: any } = action.payload + + return { + ...state, + instances: { + ...state.instances, + instanceList: state.instances.instanceList.map((instance, index) => { + if (payload.instanceIndex === index) instance.decodedResponse[payload.funcIndex] = payload.response + return instance + }) + } + } + } + + case 'SET_PATH_TO_SCENARIO': { + const payload: string = action.payload + + return { + ...state, + recorder: { + ...state.recorder, + pathToScenario: payload + } + } + } + + case 'SET_RECORDER_COUNT': { + const payload: number = action.payload + + return { + ...state, + recorder: { + ...state.recorder, + transactionCount: payload + } + } + } + + case 'CLEAR_RECORDER_COUNT': { + return { + ...state, + recorder: { + ...state.recorder, + transactionCount: 0 + } + } + } + + case 'SET_ENV_TOASTER_CONTENT': { + const payload: (env: string, from: string) => JSX.Element = action.payload + + return { + ...state, + envToasterContent: payload + } + } + + case 'SET_WEB3_DIALOG': { + const payload: () => JSX.Element = action.payload + + return { + ...state, + web3Dialog: payload + } + } + + default: + return state + } +} diff --git a/libs/remix-ui/run-tab/src/lib/run-tab.tsx b/libs/remix-ui/run-tab/src/lib/run-tab.tsx new file mode 100644 index 0000000000..19700f43fa --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/run-tab.tsx @@ -0,0 +1,296 @@ +// eslint-disable-next-line no-use-before-define +import React, { Fragment, useEffect, useReducer, useState } from 'react' +import { ModalDialog } from '@remix-ui/modal-dialog' +// eslint-disable-next-line no-unused-vars +import { Toaster } from '@remix-ui/toaster' +import { ContractDropdownUI } from './components/contractDropdownUI' +import { InstanceContainerUI } from './components/instanceContainerUI' +import { RecorderUI } from './components/recorderCardUI' +import { SettingsUI } from './components/settingsUI' +import { ContractData, Modal, Network, RunTabProps, Tx } from './types' +import { runTabInitialState, runTabReducer } from './reducers/runTab' +import { + initRunTab, setAccount, + setUnit, setGasFee, + setExecutionContext, setWeb3Endpoint, + clearPopUp, createNewBlockchainAccount, + setPassphrasePrompt, setMatchPassphrasePrompt, + signMessageWithAddress, getSelectedContract, + createInstance, setSendTransactionValue, + updateBaseFeePerGas, updateConfirmSettings, + updateGasPrice, updateGasPriceStatus, + updateMaxFee, updateMaxPriorityFee, + updateTxFeeContent, clearInstances, + removeInstance, getContext, + runTransactions, loadAddress, + storeScenario, runCurrentScenario, + updateScenarioPath, initWebDialogs, + getFuncABIInputs, setNetworkNameFromProvider +} from './actions' +import './css/run-tab.css' +import { PublishToStorage } from '@remix-ui/publish-to-storage' +import { PassphrasePrompt } from './components/passphrase' +import { MainnetPrompt } from './components/mainnet' +import { ScenarioPrompt } from './components/scenario' +import { Web3ProviderDialog } from './components/web3Dialog' +import { setIpfsCheckedState } from './actions/payload' + +export function RunTabUI (props: RunTabProps) { + const { plugin } = props + const [focusModal, setFocusModal] = useState({ + hide: true, + title: '', + message: '', + okLabel: '', + okFn: () => {}, + cancelLabel: '', + cancelFn: () => {} + }) + const [modals, setModals] = useState([]) + const [focusToaster, setFocusToaster] = useState('') + const [toasters, setToasters] = useState([]) + const [publishData, setPublishData] = useState<{ + storage: 'ipfs' | 'swarm', + contract: ContractData + }>({ + storage: null, + contract: null + }) + const [runTab, dispatch] = useReducer(runTabReducer, runTabInitialState) + const REACT_API = { runTab } + + useEffect(() => { + initRunTab(plugin)(dispatch) + initWebDialogs(envChangeNotification, web3Dialog)(dispatch) + }, [plugin]) + + useEffect(() => { + plugin.onReady(runTab) + }, [REACT_API]) + + useEffect(() => { + if (modals.length > 0) { + setFocusModal(() => { + const focusModal = { + hide: false, + title: modals[0].title, + message: modals[0].message, + okLabel: modals[0].okLabel, + okFn: modals[0].okFn, + cancelLabel: modals[0].cancelLabel, + cancelFn: modals[0].cancelFn + } + return focusModal + }) + const modalList = modals.slice() + + modalList.shift() + setModals(modalList) + } + }, [modals]) + + useEffect(() => { + if (runTab.notification.title) { + modal(runTab.notification.title, runTab.notification.message, runTab.notification.labelOk, runTab.notification.actionOk, runTab.notification.labelCancel, runTab.notification.actionCancel) + } + }, [runTab.notification]) + + useEffect(() => { + if (toasters.length > 0) { + setFocusToaster(() => { + return toasters[0] + }) + const toasterList = toasters.slice() + + toasterList.shift() + setToasters(toasterList) + } + }, [toasters]) + + useEffect(() => { + if (runTab.popup) { + toast(runTab.popup) + } + }, [runTab.popup]) + + const setCheckIpfs = (value: boolean) => { + dispatch(setIpfsCheckedState(value)) + } + + const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { + setModals(modals => { + modals.push({ message, title, okLabel, okFn, cancelLabel, cancelFn }) + return [...modals] + }) + } + + const handleHideModal = () => { + setFocusModal(modal => { + return { ...modal, hide: true, message: null } + }) + } + + const handleToaster = () => { + setFocusToaster('') + clearPopUp() + } + + const toast = (toasterMsg: string) => { + setToasters(messages => { + messages.push(toasterMsg) + return [...messages] + }) + } + + const resetStorage = () => { + setPublishData({ + storage: null, + contract: null + }) + } + + const publishToStorage = (storage: 'ipfs' | 'swarm', contract: ContractData) => { + setPublishData({ + storage, + contract + }) + } + + const gasEstimationPrompt = (msg: string) => { + return ( +
    Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
    + {msg} +
    + ) + } + + const logBuilder = (msg: string) => { + return
    {msg}
    + } + + const passphrasePrompt = (message: string) => { + return + } + + const scenarioPrompt = (message: string) => { + return + } + + const mainnetPrompt = (tx: Tx, network: Network, amount: string, gasEstimation: string, gasFees: (maxFee: string, cb: (txFeeText: string, priceStatus: boolean) => void) => void, determineGasPrice: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void) => { + return + } + + const envChangeNotification = (env: { context: string, fork: string }, from: string) => { + return ( +
    + + + {from} + + is changing your environment to + {env} + +
    + ) + } + + const web3Dialog = () => { + return + } + + return ( + +
    +
    + + + + +
    +
    + + + +
    + ) +} diff --git a/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts b/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts new file mode 100644 index 0000000000..406459b1a0 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts @@ -0,0 +1,82 @@ +export class Blockchain extends Plugin { + constructor(config: any); + event: any; + executionContext: ExecutionContext; + events: EventEmitter; + config: any; + txRunner: any; + networkcallid: number; + networkStatus: { + name: string; + id: string; + }; + setupEvents(): void; + getCurrentNetworkStatus(): { + name: string; + id: string; + network?: { + name: string; + id: string; + }; + }; + setupProviders(): void; + providers: {}; + getCurrentProvider(): any; + /** Return the list of accounts */ + getAccounts(cb?: any): any; + deployContractAndLibraries(selectedContract: any, args: any, contractMetadata: any, compilerContracts: any, callbacks: any, confirmationCb: any): void; + deployContractWithLibrary(selectedContract: any, args: any, contractMetadata: any, compilerContracts: any, callbacks: any, confirmationCb: any): void; + createContract(selectedContract: any, data: any, continueCb: any, promptCb: any, confirmationCb: any, finalCb: any): void; + determineGasPrice(cb: any): void; + getInputs(funABI: any): any; + fromWei(value: any, doTypeConversion: any, unit: any): string; + toWei(value: any, unit: any): import("bn.js"); + calculateFee(gas: any, gasPrice: any, unit: any): import("bn.js"); + determineGasFees(tx: any): (gasPrice: any, cb: any) => void; + changeExecutionContext(context: any, confirmCb: any, infoCb: any, cb: any): Promise; + setProviderFromEndpoint(target: any, context: any, cb: any): void; + detectNetwork(cb: any): void; + getProvider(): any; + /** + * return the fork name applied to the current envionment + * @return {String} - fork name + */ + getCurrentFork(): string; + isWeb3Provider(): boolean; + isInjectedWeb3(): boolean; + signMessage(message: any, account: any, passphrase: any, cb: any): void; + web3(): any; + getTxListener(opts: any): any; + runOrCallContractMethod(contractName: any, contractAbi: any, funABI: any, contract: any, value: any, address: any, callType: any, lookupOnly: any, logMsg: any, logCallback: any, outputCb: any, confirmationCb: any, continueCb: any, promptCb: any): void; + context(): "memory" | "blockchain"; + resetAndInit(config: any, transactionContextAPI: any): void; + transactionContextAPI: any; + addProvider(provider: any): void; + removeProvider(name: any): void; + /** Listen on New Transaction. (Cannot be done inside constructor because txlistener doesn't exist yet) */ + startListening(txlistener: any): void; + resetEnvironment(): void; + /** + * Create a VM Account + * @param {{privateKey: string, balance: string}} newAccount The new account to create + */ + createVMAccount(newAccount: { + privateKey: string; + balance: string; + }): any; + newAccount(_password: any, passwordPromptCb: any, cb: any): any; + /** Get the balance of an address, and convert wei to ether */ + getBalanceInEther(address: any, cb: any): void; + pendingTransactionsCount(): number; + /** + * This function send a tx only to javascript VM or testnet, will return an error for the mainnet + * SHOULD BE TAKEN CAREFULLY! + * + * @param {Object} tx - transaction. + */ + sendTransaction(tx: any): any; + runTx(args: any, confirmationCb: any, continueCb: any, promptCb: any, cb: any): void; +} +import { Plugin } from "@remixproject/engine/lib/abstract"; +import { ExecutionContext } from "./execution-context"; +import { EventEmitter } from "events"; diff --git a/libs/remix-ui/run-tab/src/lib/types/execution-context.d.ts b/libs/remix-ui/run-tab/src/lib/types/execution-context.d.ts new file mode 100644 index 0000000000..6547ebb1b8 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/types/execution-context.d.ts @@ -0,0 +1,38 @@ +import Web3 from 'web3' + +export class ExecutionContext { + event: any; + executionContext: any; + lastBlock: any; + blockGasLimitDefault: number; + blockGasLimit: number; + currentFork: string; + mainNetGenesisHash: string; + customNetWorks: {}; + blocks: {}; + latestBlockNumber: number; + txs: {}; + customWeb3: {}; + init(config: any): void; + askPermission(): void; + getProvider(): any; + getCurrentFork(): string; + isVM(): boolean; + setWeb3(context: any, web3: any): void; + web3(): any; + detectNetwork(callback: any): void; + removeProvider(name: any): void; + addProvider(network: any): void; + internalWeb3(): any; + blankWeb3(): Web3; + setContext(context: any, endPointUrl: any, confirmCb: any, infoCb: any): void; + executionContextChange(value: any, endPointUrl: any, confirmCb: any, infoCb: any, cb: any): Promise; + currentblockGasLimit(): number; + stopListenOnLastBlock(): void; + // eslint-disable-next-line no-undef + listenOnLastBlockId: NodeJS.Timer; + _updateChainContext(): Promise; + listenOnLastBlock(): void; + setProviderFromEndpoint(endpoint: any, value: any, cb: any): void; + txDetailsLink(network: any, hash: any): any; +} diff --git a/libs/remix-ui/run-tab/src/lib/types/index.ts b/libs/remix-ui/run-tab/src/lib/types/index.ts new file mode 100644 index 0000000000..0647fca2fd --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/types/index.ts @@ -0,0 +1,308 @@ +import { RunTab } from './run-tab' +export interface RunTabProps { + plugin: RunTab +} + +export interface SettingsProps { + selectExEnv: string, + accounts: { + loadedAccounts: Record, + selectedAccount: string, + isRequesting: boolean, + isSuccessful: boolean, + error: string + }, + setAccount: (account: string) => void, + setUnit: (unit: 'ether' | 'finney' | 'gwei' | 'wei') => void, + sendValue: string, + sendUnit: string, + gasLimit: number, + setGasFee: (value: number) => void, + setWeb3Endpoint: (endpoint: string) => void, + personalMode: boolean, + networkName: string, + providers: { + providerList: { + id?: string, + dataId?: string, + title?: string, + value: string, + fork?: string + content: string + }[], + isRequesting: boolean, + isSuccessful: boolean, + error: string + }, + setExecutionContext: (executionContext: { context: string, fork: string }, displayContent: JSX.Element) => void, + createNewBlockchainAccount: (cbMessage: JSX.Element) => void, + setPassphrase: (passphrase: string) => void, + setMatchPassphrase: (passphrase: string) => void, + modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, + tooltip: (toasterMsg: string) => void, + signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void, + passphrase: string, + web3ProviderDialog: () => JSX.Element, + setSendValue: (value: string) => void +} + +export interface EnvironmentProps { + selectedEnv: string, + providers: { + providerList: { + id?: string, + dataId?: string, + title?: string, + value: string, + fork?: string + content: string + }[], + isRequesting: boolean, + isSuccessful: boolean, + error: string + }, + setExecutionContext: (executionContext: { context: string, fork: string }, displayContent: JSX.Element) => void, + web3ProviderDialog: () => JSX.Element +} + +export interface NetworkProps { + networkName: string +} + +export interface AccountProps { + selectExEnv: string, + accounts: { + loadedAccounts: Record, + selectedAccount: string, + isRequesting: boolean, + isSuccessful: boolean, + error: string + }, + setAccount: (account: string) => void, + personalMode: boolean, + createNewBlockchainAccount: (cbMessage: JSX.Element) => void, + setPassphrase: (passphrase: string) => void, + setMatchPassphrase: (passphrase: string) => void, + tooltip: (toasterMsg: string) => void, + modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, + signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void, + passphrase: string +} + +export interface GasPriceProps { + gasLimit: number, + setGasFee: (value: number) => void +} + +export interface ValueProps { + setUnit: (unit: 'ether' | 'finney' | 'gwei' | 'wei') => void, + setSendValue: (value: string) => void, + sendValue: string, + sendUnit: string +} + +export interface FuncABI { + name: string, + type: string, + inputs: { name: string, type: string }[], + stateMutability: string, + payable: boolean, + constant: any +} +export interface ContractData { + name: string, + contract: any, + compiler: any, + abi: FuncABI[], + bytecodeObject: any, + bytecodeLinkReferences: any, + object: any, + deployedBytecode: any, + getConstructorInterface: () => any, + getConstructorInputs: () => any, + isOverSizeLimit: () => boolean, + metadata: any +} + +export interface Tx { + from: string, + to: string, + data: string, + gas: string +} + +export interface Network { + name: string, + lastBlock: { + baseFeePerGas: string + } +} + +export type MainnetPrompt = ( + tx: Tx, network: + Network, amount: string, + gasEstimation: string, + gasFees: (maxFee: string, cb: (txFeeText: string, priceStatus: boolean) => void) => void, + determineGasPrice: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void + ) => JSX.Element + +export interface ContractDropdownProps { + exEnvironment: string, + contracts: { + contractList: { + name: string, + alias: string, + file: string + }[], + loadType: 'abi' | 'sol' | 'other', + currentFile: string, + compilationCount: number, + isRequesting: boolean, + isSuccessful: boolean, + error: string + }, + getSelectedContract: (contractName: string, compilerAtributeName: string) => ContractData, + modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, + passphrase: string, + setPassphrase: (passphrase: string) => void, + createInstance: ( + selectedContract: ContractData, + gasEstimationPrompt: (msg: string) => JSX.Element, + passphrasePrompt: (msg: string) => JSX.Element, + logBuilder: (msg: string) => JSX.Element, + publishToStorage: (storage: 'ipfs' | 'swarm', + contract: ContractData) => void, + mainnetPrompt: MainnetPrompt, + isOverSizePrompt: () => JSX.Element, + args) => void, + ipfsCheckedState: boolean, + setIpfsCheckedState: (value: boolean) => void, + publishToStorage: (storage: 'ipfs' | 'swarm', contract: ContractData) => void, + gasEstimationPrompt: (msg: string) => JSX.Element, + logBuilder: (msg: string) => JSX.Element, + passphrasePrompt: (message: string) => JSX.Element, + mainnetPrompt: (tx: Tx, network: Network, amount: string, gasEstimation: string, gasFees: (maxFee: string, cb: (txFeeText: string, priceStatus: boolean) => void) => void, determineGasPrice: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void) => JSX.Element, + tooltip: (toasterMsg: string | JSX.Element) => void, + loadAddress: (contract: ContractData, address: string) => void, + networkName: string, + setNetworkName: (name: string) => void +} + +export interface RecorderProps { + storeScenario: (prompt: (msg: string) => JSX.Element) => void, + runCurrentScenario: (gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, confirmDialogContent: MainnetPrompt, logBuilder: (msg: string) => JSX.Element) => void, + logBuilder: (msg: string) => JSX.Element, + mainnetPrompt: MainnetPrompt, + gasEstimationPrompt: (msg: string) => JSX.Element, + passphrasePrompt: (msg: string) => JSX.Element, + scenarioPrompt: (msg: string) => JSX.Element, + count: number +} + +export interface InstanceContainerProps { + instances: { + instanceList: { + contractData?: ContractData, + address: string, + name: string, + decodedResponse?: Record, + abi?: any + }[], + error: string + }, + clearInstances: () => void, + removeInstance: (index: number) => void, + getContext: () => 'memory' | 'blockchain', + runTransactions: ( + instanceIndex: number, + lookupOnly: boolean, + funcABI: FuncABI, + inputsValues: string, + contractName: string, + contractABI, contract, + address, + logMsg:string, + logBuilder: (msg: string) => JSX.Element, + mainnetPrompt: MainnetPrompt, + gasEstimationPrompt: (msg: string) => JSX.Element, + passphrasePrompt: (msg: string) => JSX.Element, + funcIndex?: number) => void, + gasEstimationPrompt: (msg: string) => JSX.Element, + logBuilder: (msg: string) => JSX.Element, + passphrasePrompt: (message: string) => JSX.Element, + mainnetPrompt: (tx: Tx, network: Network, amount: string, gasEstimation: string, gasFees: (maxFee: string, cb: (txFeeText: string, priceStatus: boolean) => void) => void, determineGasPrice: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void) => JSX.Element, + sendValue: string, + getFuncABIInputs: (funcABI: FuncABI) => string +} + +export interface Modal { + hide?: boolean + title: string + // eslint-disable-next-line no-undef + message: string | JSX.Element + okLabel: string + okFn: () => void + cancelLabel: string + cancelFn: () => void +} + +export interface ContractGUIProps { + title?: string, + funcABI: FuncABI, + inputs: any, + clickCallBack: (inputs: { name: string, type: string }[], input: string) => void, + widthClass?: string, + evmBC: any, + lookupOnly: boolean +} +export interface MainnetProps { + network: Network, + tx: Tx, + amount: string, + gasEstimation: string, + setNewGasPrice: (maxFee: string, cb: (txFeeText: string, priceStatus: boolean) => void) => void, + updateGasPriceStatus: (status: boolean) => void, + updateConfirmSettings: (confirmation: boolean) => void, + updateMaxFee: (fee: string) => void, + updateBaseFeePerGas: (fee: string) => void, + init: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void, + setTxFeeContent: (content: string) => void, + updateGasPrice: (price: string) => void, + updateMaxPriorityFee: (fee: string) => void + txFeeContent: string, + maxFee: string, + maxPriorityFee: string +} + +export interface UdappProps { + instance: { + contractData?: ContractData, + address: string, + name: string, + decodedResponse?: Record, + abi?: any + }, + context: 'memory' | 'blockchain', + removeInstance: (index: number) => void, + index: number, + gasEstimationPrompt: (msg: string) => JSX.Element, + logBuilder: (msg: string) => JSX.Element, + passphrasePrompt: (message: string) => JSX.Element, + mainnetPrompt: (tx: Tx, network: Network, amount: string, gasEstimation: string, gasFees: (maxFee: string, cb: (txFeeText: string, priceStatus: boolean) => void) => void, determineGasPrice: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void) => JSX.Element, + runTransactions: ( + instanceIndex: number, + lookupOnly: boolean, + funcABI: FuncABI, + inputsValues: string, + contractName: string, + contractABI, contract, + address, + logMsg:string, + logBuilder: (msg: string) => JSX.Element, + mainnetPrompt: MainnetPrompt, + gasEstimationPrompt: (msg: string) => JSX.Element, + passphrasePrompt: (msg: string) => JSX.Element, + funcIndex?: number) => void, + sendValue: string, + getFuncABIInputs: (funcABI: FuncABI) => string +} diff --git a/libs/remix-ui/run-tab/src/lib/types/injected.d.ts b/libs/remix-ui/run-tab/src/lib/types/injected.d.ts new file mode 100644 index 0000000000..c12ab88933 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/types/injected.d.ts @@ -0,0 +1,12 @@ +export = InjectedProvider; +declare class InjectedProvider { + constructor(executionContext: any); + executionContext: any; + getAccounts(cb: any): any; + newAccount(passwordPromptCb: any, cb: any): void; + resetEnvironment(): void; + getBalanceInEther(address: any, cb: any): void; + getGasPrice(cb: any): void; + signMessage(message: any, account: any, _passphrase: any, cb: any): void; + getProvider(): string; +} diff --git a/libs/remix-ui/run-tab/src/lib/types/node.d.ts b/libs/remix-ui/run-tab/src/lib/types/node.d.ts new file mode 100644 index 0000000000..8979746943 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/types/node.d.ts @@ -0,0 +1,13 @@ +export = NodeProvider; +declare class NodeProvider { + constructor(executionContext: any, config: any); + executionContext: any; + config: any; + getAccounts(cb: any): any; + newAccount(passwordPromptCb: any, cb: any): any; + resetEnvironment(): void; + getBalanceInEther(address: any, cb: any): void; + getGasPrice(cb: any): void; + signMessage(message: any, account: any, passphrase: any, cb: any): void; + getProvider(): any; +} diff --git a/libs/remix-ui/run-tab/src/lib/types/recorder.d.ts b/libs/remix-ui/run-tab/src/lib/types/recorder.d.ts new file mode 100644 index 0000000000..44f79bda4f --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/types/recorder.d.ts @@ -0,0 +1,15 @@ +export class Recorder { + constructor(blockchain: Blockchain); + event: any; + data: { _listen: boolean, _replay: boolean, journal: any[], _createdContracts: any, _createdContractsReverse: any, _usedAccounts: any, _abis: any, _contractABIReferences: any, _linkReferences: any }; + setListen: (listen) => void; + extractTimestamp: (value) => any; + resolveAddress: (record, accounts, options) => any; + append: (timestamp, record) => any; + getAll: () => void; + clearAll: () => void; + run: (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, newContractFn) => void + runScenario: (json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) => void +} +import { Blockchain } from "./blockchain"; + diff --git a/libs/remix-ui/run-tab/src/lib/types/run-tab.d.ts b/libs/remix-ui/run-tab/src/lib/types/run-tab.d.ts new file mode 100644 index 0000000000..37783c2119 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/types/run-tab.d.ts @@ -0,0 +1,43 @@ +export class RunTab extends ViewPlugin { + constructor(blockchain: any, config: any, fileManager: any, editor: any, filePanel: any, compilersArtefacts: any, networkModule: any, mainView: any, fileProvider: any); + event: any; + config: any; + blockchain: Blockchain; + fileManager: any; + editor: any; + logCallback: (msg: any) => void; + filePanel: any; + compilersArtefacts: any; + networkModule: any; + fileProvider: any; + REACT_API: RunTabState; + el: HTMLDivElement; + setupEvents(): void; + getSettings(): any; + setEnvironmentMode(env: any): Promise; + createVMAccount(newAccount: any): any; + sendTransaction(tx: any): any; + getAccounts(cb: any): any; + pendingTransactionsCount(): any; + renderInstanceContainer(): void; + instanceContainer: any; + noInstancesText: any; + renderSettings(): void; + settingsUI: any; + renderDropdown(udappUI: any, fileManager: any, compilersArtefacts: any, config: any, editor: any, logCallback: any): void; + contractDropdownUI: any; + renderRecorder(udappUI: any, fileManager: any, config: any, logCallback: any): void; + recorderCount: any; + recorderInterface: any; + renderRecorderCard(): void; + recorderCard: any; + udappUI: any; + renderComponent(): void; + onReady(api: any): void; + recorder: Recorder; +} +import { ViewPlugin } from "@remixproject/engine-web/lib/view"; +import { Blockchain } from "./blockchain"; +import { RunTabState } from "../reducers/runTab"; +import { Recorder } from "./recorder"; + diff --git a/libs/remix-ui/run-tab/src/lib/types/vm.d.ts b/libs/remix-ui/run-tab/src/lib/types/vm.d.ts new file mode 100644 index 0000000000..0cd4adaca8 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/types/vm.d.ts @@ -0,0 +1,16 @@ +export = VMProvider; +declare class VMProvider { + constructor(executionContext: any); + executionContext: any; + getAccounts(cb: any): void; + resetEnvironment(): void; + accounts: {}; + RemixSimulatorProvider: any; + web3: any; + createVMAccount(newAccount: any): string; + newAccount(_passwordPromptCb: any, cb: any): void; + getBalanceInEther(address: any, cb: any): void; + getGasPrice(cb: any): void; + signMessage(message: any, account: any, _passphrase: any, cb: any): void; + getProvider(): string; +} diff --git a/libs/remix-ui/run-tab/tsconfig.json b/libs/remix-ui/run-tab/tsconfig.json new file mode 100644 index 0000000000..d52e31ad74 --- /dev/null +++ b/libs/remix-ui/run-tab/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/run-tab/tsconfig.lib.json b/libs/remix-ui/run-tab/tsconfig.lib.json new file mode 100644 index 0000000000..b560bc4dec --- /dev/null +++ b/libs/remix-ui/run-tab/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/settings/.eslintrc b/libs/remix-ui/settings/.eslintrc index dae5c6feeb..0d43d424e3 100644 --- a/libs/remix-ui/settings/.eslintrc +++ b/libs/remix-ui/settings/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/solidity-compiler/.eslintrc b/libs/remix-ui/solidity-compiler/.eslintrc index dae5c6feeb..0d43d424e3 100644 --- a/libs/remix-ui/solidity-compiler/.eslintrc +++ b/libs/remix-ui/solidity-compiler/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx b/libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx index 11852a17ca..dcc86f387f 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx +++ b/libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx @@ -81,7 +81,7 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => { setCurrentVersion(value) api.setCompilerParameters({ version: value }) } - // eslint-disable-next-line no-undef + const modal = async (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { await setState(prevState => { return { diff --git a/libs/remix-ui/solidity-compiler/src/lib/types/index.ts b/libs/remix-ui/solidity-compiler/src/lib/types/index.ts index 83100a0bd5..4932b1456c 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/types/index.ts +++ b/libs/remix-ui/solidity-compiler/src/lib/types/index.ts @@ -10,8 +10,8 @@ export interface CompilerContainerProps { api: ICompilerApi, compileTabLogic: CompileTabLogic, isHardhatProject: boolean, - tooltip: (message: string | JSX.Element) => void, // eslint-disable-line no-undef - modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, // eslint-disable-line no-undef + tooltip: (message: string | JSX.Element) => void, + modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, compiledFileName: string, updateCurrentVersion: any, configurationSettings: ConfigurationSettings @@ -21,6 +21,6 @@ export interface ContractSelectionProps { contractMap: { file: string } | Record, - modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, // eslint-disable-line no-undef + modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, contractsDetails: Record } diff --git a/libs/remix-ui/solidity-unit-testing/.babelrc b/libs/remix-ui/solidity-unit-testing/.babelrc index 09d67939cc..64a3748691 100644 --- a/libs/remix-ui/solidity-unit-testing/.babelrc +++ b/libs/remix-ui/solidity-unit-testing/.babelrc @@ -1,4 +1,4 @@ { "presets": ["@nrwl/react/babel"], "plugins": [] -} +} \ No newline at end of file diff --git a/libs/remix-ui/static-analyser/.eslintrc b/libs/remix-ui/static-analyser/.eslintrc index dae5c6feeb..0d43d424e3 100644 --- a/libs/remix-ui/static-analyser/.eslintrc +++ b/libs/remix-ui/static-analyser/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/terminal/.eslintrc b/libs/remix-ui/terminal/.eslintrc index dae5c6feeb..0d43d424e3 100644 --- a/libs/remix-ui/terminal/.eslintrc +++ b/libs/remix-ui/terminal/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts index dafe6f78cf..d0efd289b5 100644 --- a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts +++ b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts @@ -106,8 +106,8 @@ export const registerErrorScriptRunnerAction = (on, commandName, commandFn, disp }) } -export const listenOnNetworkAction = async (event, isListening) => { - event.trigger('listenOnNetWork', [isListening]) +export const listenOnNetworkAction = async (plugins, isListening) => { + plugins.txListener.setListenOnNetwork(isListening) } export const initListeningOnNetwork = (plugins, dispatch: React.Dispatch) => { diff --git a/libs/remix-ui/terminal/src/lib/custom-hooks/useDragTerminal.tsx b/libs/remix-ui/terminal/src/lib/custom-hooks/useDragTerminal.tsx deleted file mode 100644 index aba4bbf6a5..0000000000 --- a/libs/remix-ui/terminal/src/lib/custom-hooks/useDragTerminal.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useEffect, useState } from 'react' - -export const useDragTerminal = (minHeight: number, defaultPosition: number) => { - const [isOpen, setIsOpen] = useState(defaultPosition > minHeight) - const [lastYPosition, setLastYPosition] = useState(0) - const [terminalPosition, setTerminalPosition] = useState(defaultPosition) - // Used to save position of the terminal when it is closed - const [lastTerminalPosition, setLastTerminalPosition] = useState(defaultPosition) - const [isDragging, setIsDragging] = useState(false) - - const handleDraggingStart = (event: React.MouseEvent) => { - setLastYPosition(event.clientY) - setIsDragging(true) - } - - const handleDragging = (event: MouseEvent) => { - event.preventDefault() - - if (isDragging) { - const mouseYPosition = event.clientY - const difference = lastYPosition - mouseYPosition - const newTerminalPosition = terminalPosition + difference - setTerminalPosition(newTerminalPosition) - setLastYPosition(mouseYPosition) - } - } - - const handleDraggingEnd = () => { - if (!isDragging) return - - setIsDragging(false) - - // Check terminal position to determine if it should be open or closed - setIsOpen(terminalPosition > minHeight) - } - - const handleToggleTerminal = (event: React.MouseEvent) => { - event.preventDefault() - event.stopPropagation() - - if (isOpen) { - setLastTerminalPosition(terminalPosition) - setLastYPosition(0) - setTerminalPosition(minHeight) - } else { - setTerminalPosition(lastTerminalPosition <= minHeight ? 323 : lastTerminalPosition) - } - - setIsOpen(!isOpen) - } - - // Add event listeners for dragging - useEffect(() => { - document.addEventListener('mousemove', handleDragging) - document.addEventListener('mouseup', handleDraggingEnd) - - return () => { - document.removeEventListener('mousemove', handleDragging) - document.removeEventListener('mouseup', handleDraggingEnd) - } - }, [handleDragging, handleDraggingEnd]) - - // Reset terminal position - useEffect(() => { - if (!terminalPosition) { - setTerminalPosition(defaultPosition) - } - }, [terminalPosition, setTerminalPosition]) - - return { - isOpen, - terminalPosition, - isDragging, - handleDraggingStart, - handleToggleTerminal - } -} diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index acb13ecd68..776ca1fbb0 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ 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' @@ -17,7 +18,6 @@ import RenderKnownTransactions from './components/RenderKnownTransactions' // es import parse from 'html-react-parser' import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, UNKNOWN_TRANSACTION } from './types/terminalTypes' import { wrapScript } from './utils/wrapScript' -import { useDragTerminal } from './custom-hooks/useDragTerminal' /* eslint-disable-next-line */ export interface ClipboardEvent extends SyntheticEvent { @@ -25,10 +25,10 @@ export interface ClipboardEvent extends SyntheticEvent { } export const RemixUiTerminal = (props: RemixUiTerminalProps) => { - const { call, _deps, on, config, event, gistHandler, version } = props.plugin + const { call, _deps, on, config, event, version } = props.plugin const [_cmdIndex, setCmdIndex] = useState(-1) const [_cmdTemp, setCmdTemp] = useState('') - + const [isOpen, setIsOpen] = useState(true) const [newstate, dispatch] = useReducer(registerCommandReducer, initialState) const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState) const [, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState) @@ -79,24 +79,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { const terminalMenuOffsetHeight = (terminalMenu.current && terminalMenu.current.offsetHeight) || 35 const terminalDefaultPosition = config.get('terminal-top-offset') - const { - isOpen, - isDragging, - terminalPosition, - handleDraggingStart, - handleToggleTerminal - } = useDragTerminal(terminalMenuOffsetHeight, terminalDefaultPosition) - - // Check open state - useEffect(() => { - const resizeValue = isOpen ? [config.get('terminal-top-offset')] : [] - event.trigger('resize', resizeValue) - }, [isOpen]) - - useEffect(() => { - event.trigger('resize', [terminalPosition]) - }, [terminalPosition]) - const scrollToBottom = () => { messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }) } @@ -104,8 +86,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { useEffect(() => { props.onReady({ logHtml: (html) => { - scriptRunnerDispatch({ type: 'html', payload: { message: [html.innerText] } }) + scriptRunnerDispatch({ type: 'html', payload: { message: [html ? html.innerText ? html.innerText : html : null] } }) }, + log: (message) => { scriptRunnerDispatch({ type: 'log', payload: { message: [message] } }) } @@ -178,7 +161,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { } function loadgist (id, cb) { - gistHandler.loadFromGist({ gist: id }, _deps.fileManager) + props.plugin.call('gistHandler', 'load', id) if (cb) cb() } @@ -332,8 +315,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { const listenOnNetwork = (e: any) => { const isListening = e.target.checked - // setIsListeningOnNetwork(isListening) - listenOnNetworkAction(event, isListening) + listenOnNetworkAction(props.plugin, isListening) } const onChange = (event: any) => { @@ -426,10 +408,14 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false })) } + const handleToggleTerminal = () => { + setIsOpen(!isOpen) + props.plugin.call('layout', 'minimize', props.plugin.profile.name, isOpen) + } + return ( -
    +
    -
    @@ -497,7 +483,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { }) } else if (Array.isArray(x.message)) { return x.message.map((msg, i) => { - if (typeof msg === 'object') { + if (React.isValidElement(msg)) { + return ( +
    { msg }
    + ) + } else if (typeof msg === 'object') { return (
    { msg.value ? parse(msg.value) : JSON.stringify(msg) }
    ) diff --git a/libs/remix-ui/toaster/.eslintrc b/libs/remix-ui/toaster/.eslintrc index b8666066e9..4d698941a4 100644 --- a/libs/remix-ui/toaster/.eslintrc +++ b/libs/remix-ui/toaster/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/tree-view/.eslintrc b/libs/remix-ui/tree-view/.eslintrc index dae5c6feeb..0d43d424e3 100644 --- a/libs/remix-ui/tree-view/.eslintrc +++ b/libs/remix-ui/tree-view/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx b/libs/remix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx index c570de5f07..e03e624977 100644 --- a/libs/remix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx +++ b/libs/remix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx @@ -4,7 +4,7 @@ import { TreeViewItemProps } from '../../types' import './tree-view-item.css' export const TreeViewItem = (props: TreeViewItemProps) => { - const { id, children, label, labelClass, expand, iconX = 'fas fa-caret-right', iconY = 'fas fa-caret-down', icon, controlBehaviour = false, innerRef, ...otherProps } = props + const { id, children, label, labelClass, expand, iconX = 'fas fa-caret-right', iconY = 'fas fa-caret-down', icon, controlBehaviour = false, innerRef, showIcon = true, ...otherProps } = props const [isExpanded, setIsExpanded] = useState(false) useEffect(() => { @@ -14,7 +14,7 @@ export const TreeViewItem = (props: TreeViewItemProps) => { return (
  • !controlBehaviour && setIsExpanded(!isExpanded)}> - { children ?
    : icon ?
    : null } + { children && showIcon ?
    : icon ?
    : null } { label } diff --git a/libs/remix-ui/tree-view/src/types/index.ts b/libs/remix-ui/tree-view/src/types/index.ts index 1c91934c76..4f6bfc9e02 100644 --- a/libs/remix-ui/tree-view/src/types/index.ts +++ b/libs/remix-ui/tree-view/src/types/index.ts @@ -21,5 +21,6 @@ export interface TreeViewItemProps { controlBehaviour?: boolean innerRef?: any, onContextMenu?: (...args: any) => void, - onBlur?: (...args: any) => void + onBlur?: (...args: any) => void, + showIcon?: boolean } diff --git a/libs/remix-ui/utils/.eslintrc b/libs/remix-ui/utils/.eslintrc index 977f139a09..6d5b6d952d 100644 --- a/libs/remix-ui/utils/.eslintrc +++ b/libs/remix-ui/utils/.eslintrc @@ -243,6 +243,6 @@ }, "settings": { "react": { "version": "detect" } }, "plugins": ["import", "jsx-a11y", "react", "react-hooks"], - "extends": ["../../../.eslintrc"], + "extends": ["../../../.eslintrc.json"], "ignorePatterns": ["!**/*"] } diff --git a/libs/remix-ui/workspace/.eslintrc b/libs/remix-ui/workspace/.eslintrc index dae5c6feeb..0d43d424e3 100644 --- a/libs/remix-ui/workspace/.eslintrc +++ b/libs/remix-ui/workspace/.eslintrc @@ -3,7 +3,7 @@ "browser": true, "es6": true }, - "extends": "../../../.eslintrc", + "extends": "../../../.eslintrc.json", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index fddabeba59..3dd6b16bbb 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -117,11 +117,7 @@ export const loadWorkspacePreset = async (template: 'gist-template' | 'code-temp obj['/' + 'gist-' + gistId + '/' + path] = data.files[element] }) plugin.fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => { - if (!errorLoadingFile) { - const provider = plugin.fileManager.getProvider('workspace') - - provider.lastLoadedGistId = gistId - } else { + if (errorLoadingFile) { dispatch(displayNotification('', errorLoadingFile.message || errorLoadingFile, 'OK', null, () => {}, null)) } }) diff --git a/libs/remix-ui/workspace/src/lib/contexts/index.ts b/libs/remix-ui/workspace/src/lib/contexts/index.ts index 6e189693a9..1aedce98b5 100644 --- a/libs/remix-ui/workspace/src/lib/contexts/index.ts +++ b/libs/remix-ui/workspace/src/lib/contexts/index.ts @@ -4,7 +4,6 @@ import { BrowserState } from '../reducers/workspace' export const FileSystemContext = createContext<{ fs: BrowserState, - // eslint-disable-next-line no-undef modal:(title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void, dispatchInitWorkspace:() => Promise, dispatchFetchDirectory:(path: string) => Promise, diff --git a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx index 53891615c2..904ed7cade 100644 --- a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx +++ b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx @@ -170,7 +170,6 @@ export const FileSystemProvider = (props: WorkspaceProps) => { }) } - // eslint-disable-next-line no-undef const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { setModals(modals => { modals.push({ message, title, okLabel, okFn, cancelLabel, cancelFn }) diff --git a/libs/remix-ui/workspace/src/lib/types/index.ts b/libs/remix-ui/workspace/src/lib/types/index.ts index 37f6c22833..bf4b24d661 100644 --- a/libs/remix-ui/workspace/src/lib/types/index.ts +++ b/libs/remix-ui/workspace/src/lib/types/index.ts @@ -41,7 +41,6 @@ export interface WorkspaceState { export interface Modal { hide?: boolean title: string - // eslint-disable-next-line no-undef message: string | JSX.Element okLabel: string okFn: () => void diff --git a/nx.json b/nx.json index e1bc0d858a..5f50461adc 100644 --- a/nx.json +++ b/nx.json @@ -7,7 +7,7 @@ }, "tsconfig.base.json": "*", "tslint.json": "*", - ".eslintrc": "*", + ".eslintrc.json": "*", "nx.json": "*" }, "npmScope": "remix-project", @@ -145,6 +145,9 @@ "remix-ui-tabs": { "tags": [] }, + "remix-ui-panel": { + "tags": [] + }, "remix-ui-theme-module": { "tags": [] }, @@ -153,6 +156,9 @@ }, "remix-ui-editor-context-view": { "tags": [] + }, + "remix-ui-run-tab": { + "tags": [] } }, "targetDependencies": { diff --git a/package-lock.json b/package-lock.json index a05d1a99a7..bab7da51d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41619,15 +41619,6 @@ "which": "^2.0.1" } }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, "decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -41699,9 +41690,9 @@ } }, "keyv": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", - "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz", + "integrity": "sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg==", "dev": true, "requires": { "json-buffer": "3.0.1" diff --git a/package.json b/package.json index 03e89015e9..0e55dd276d 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "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-helper,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,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs", + "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-helper,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,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab", "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", @@ -162,7 +162,7 @@ "chokidar": "^2.1.8", "color-support": "^1.1.3", "commander": "^2.20.3", - "core-js": "^3.19.3", + "core-js": "^3.6.5", "deep-equal": "^1.0.1", "document-register-element": "1.13.1", "ethereumjs-util": "^7.0.10", diff --git a/tsconfig.base.json b/tsconfig.base.json index 4ef2c10dcb..c483b6f07e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -69,12 +69,14 @@ "@remix-ui/tabs": ["libs/remix-ui/tabs/src/index.ts"], "@remix-ui/helper": ["libs/remix-ui/helper/src/index.ts"], "@remix-ui/app": ["libs/remix-ui/app/src/index.ts"], - "@remix-ui/vertical-icons-panel": ["libs/remix-ui/vertical-icons-panel/src/index.ts"], + "@remix-ui/vertical-icons-panel": [ + "libs/remix-ui/vertical-icons-panel/src/index.ts" + ], "@remix-ui/theme-module": ["libs/remix-ui/theme-module/src/index.ts"], + "@remix-ui/panel": ["libs/remix-ui/panel/src/index.ts"], "@remix-ui/editor-context-view": ["libs/remix-ui/editor-context-view/src/index.ts"], - "@remix-ui/solidity-unit-testing": [ - "libs/remix-ui/solidity-unit-testing/src/index.ts" - ] + "@remix-ui/solidity-unit-testing": ["libs/remix-ui/solidity-unit-testing/src/index.ts"], + "@remix-ui/run-tab": ["libs/remix-ui/run-tab/src/index.ts"] } }, "exclude": ["node_modules", "tmp"] diff --git a/workspace.json b/workspace.json index 1718e70f69..fa7930463c 100644 --- a/workspace.json +++ b/workspace.json @@ -1,3 +1,4 @@ + { "version": 1, "projects": { @@ -82,16 +83,6 @@ "apps/remix-ide/src/assets/js/**/*.js" ] } - }, - "test": { - "builder": "@nrwl/workspace:run-commands", - "options": { - "commands": [ - { - "command": "csslint && node apps/remix-ide/test/index.js" - } - ] - } } } }, @@ -1117,6 +1108,21 @@ } } }, + "remix-ui-panel": { + "root": "libs/remix-ui/panel", + "sourceRoot": "libs/remix-ui/panel/src", + "projectType": "library", + "architect": { + "lint": { + "builder": "@nrwl/linter:lint", + "options": { + "linter": "eslint", + "tsConfig": ["libs/remix-ui/panel/tsconfig.lib.json"], + "exclude": ["**/node_modules/**", "!libs/remix-ui/panel/**/*"] + } + } + } + }, "solidity-unit-testing": { "root": "libs/remix-ui/solidity-unit-testing", "sourceRoot": "libs/remix-ui/solidity-unit-testing/src", @@ -1149,6 +1155,21 @@ } } } + }, + "remix-ui-run-tab": { + "root": "libs/remix-ui/run-tab", + "sourceRoot": "libs/remix-ui/run-tab/src", + "projectType": "library", + "architect": { + "lint": { + "builder": "@nrwl/linter:lint", + "options": { + "linter": "eslint", + "tsConfig": ["libs/remix-ui/run-tab/tsconfig.lib.json"], + "exclude": ["**/node_modules/**", "libs/remix-ui/run-tab/**/*.d.ts", "!libs/remix-ui/run-tab/**/*"] + } + } + } } }, "cli": {