Merge branch 'master' into inflate_backup

pull/5370/head
David Disu 3 years ago committed by GitHub
commit db54b8512a
  1. 2
      apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx
  2. 4
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  3. 4
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts
  4. 87
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  5. 2
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  6. 36
      apps/remix-ide/src/app.js
  7. 8
      apps/remix-ide/src/app/plugins/notification.tsx
  8. 41
      apps/remix-ide/src/index.tsx
  9. 1
      apps/remix-ide/src/remixEngine.js
  10. 2
      apps/remix-ide/tsconfig.json
  11. 4
      libs/remix-ui/app/src/lib/remix-app/actions/modals.ts
  12. 4
      libs/remix-ui/app/src/lib/remix-app/context/context.tsx
  13. 8
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  14. 4
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  15. 105
      libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
  16. 2
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  17. 0
      libs/remix-ui/helper/src/lib/components/web3Dialog.tsx
  18. 17
      libs/remix-ui/helper/src/lib/helper-components.tsx
  19. 2
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  20. 39
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  21. 7
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  22. 3
      libs/remix-ui/run-tab/src/lib/components/environment.tsx
  23. 2
      libs/remix-ui/run-tab/src/lib/components/settingsUI.tsx
  24. 26
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  25. 25
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  26. 6
      libs/remix-ui/run-tab/src/lib/types/index.ts

@ -33,7 +33,7 @@ function App () {
useEffect(() => { useEffect(() => {
client.onload(async () => { client.onload(async () => {
const customProfiles = ['menuicons', 'tabs', 'solidityUnitTesting', 'hardhat-provider'] const customProfiles = ['menuicons', 'tabs', 'solidityUnitTesting', 'hardhat-provider', 'notification']
client.testCommand = async (data: any) => { client.testCommand = async (data: any) => {
console.log(data) console.log(data)

@ -84,9 +84,9 @@ module.exports = {
.openFile('Untitled.sol') .openFile('Untitled.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]') .click('*[data-id="settingsWeb3Mode"]')
.waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') .waitForElementPresent('[data-id="envNotification-modal-footer-ok-react"]')
.execute(function () { .execute(function () {
const modal = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any const modal = document.querySelector('[data-id="envNotification-modal-footer-ok-react"]') as any
modal.click() modal.click()
}) })

@ -79,9 +79,9 @@ module.exports = {
.openFile('Untitled.sol') .openFile('Untitled.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]') .click('*[data-id="settingsWeb3Mode"]')
.waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') .waitForElementPresent('[data-id="envNotification-modal-footer-ok-react"]')
.execute(function () { .execute(function () {
const modal = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any const modal = document.querySelector('[data-id="envNotification-modal-footer-ok-react"]') as any
modal.click() modal.click()
}) })

@ -64,12 +64,16 @@ const clearPayLoad = async (browser: NightwatchBrowser) => {
}) })
} }
const clickButton = async (browser: NightwatchBrowser, buttonText: string) => { const clickButton = async (browser: NightwatchBrowser, buttonText: string, waitResult: boolean = true) => {
return new Promise((resolve) => { return new Promise((resolve) => {
browser.useXpath().waitForElementVisible(`//*[@data-id='${buttonText}']`).pause(100) browser.useXpath().waitForElementVisible(`//*[@data-id='${buttonText}']`).pause(100)
.click(`//*[@data-id='${buttonText}']`, async () => { .click(`//*[@data-id='${buttonText}']`, async () => {
await checkForAcceptAndRemember(browser) await checkForAcceptAndRemember(browser)
browser.waitForElementContainsText('//*[@id="callStatus"]', 'stop').perform(() => resolve(true)) if (waitResult) {
browser.waitForElementContainsText('//*[@id="callStatus"]', 'stop').perform(() => resolve(true))
} else {
resolve(true)
}
}) })
}) })
} }
@ -103,7 +107,7 @@ const checkForAcceptAndRemember = async function (browser: NightwatchBrowser) {
* @return {Promise} * @return {Promise}
*/ */
const clickAndCheckLog = async (browser: NightwatchBrowser, buttonText: string, methodResult: any, eventResult: any, payload: any) => { const clickAndCheckLog = async (browser: NightwatchBrowser, buttonText: string, methodResult: any, eventResult: any, payload: any, waitResult: boolean = true) => {
if (payload) { if (payload) {
await setPayload(browser, payload) await setPayload(browser, payload)
} else { } else {
@ -112,10 +116,14 @@ const clickAndCheckLog = async (browser: NightwatchBrowser, buttonText: string,
if (methodResult && typeof methodResult !== 'string') { methodResult = JSON.stringify(methodResult) } if (methodResult && typeof methodResult !== 'string') { methodResult = JSON.stringify(methodResult) }
if (eventResult && typeof eventResult !== 'string') { eventResult = JSON.stringify(eventResult) } if (eventResult && typeof eventResult !== 'string') { eventResult = JSON.stringify(eventResult) }
if (buttonText) { if (buttonText) {
await clickButton(browser, buttonText) await clickButton(browser, buttonText, waitResult)
}
if (methodResult) {
await debugValues(browser, 'methods', methodResult)
}
if (eventResult) {
await debugValues(browser, 'events', eventResult)
} }
await debugValues(browser, 'methods', methodResult)
await debugValues(browser, 'events', eventResult)
} }
const assertPluginIsActive = function (browser: NightwatchBrowser, id: string, shouldBeVisible: boolean) { const assertPluginIsActive = function (browser: NightwatchBrowser, id: string, shouldBeVisible: boolean) {
@ -364,5 +372,72 @@ module.exports = {
const result = '{"jsonrpc":"2.0","result":true,"id":9999}' const result = '{"jsonrpc":"2.0","result":true,"id":9999}'
await clickAndCheckLog(browser, 'hardhat-provider:sendAsync', result, null, request) await clickAndCheckLog(browser, 'hardhat-provider:sendAsync', result, null, request)
}) })
},
// MODAL
'Should open 2 alert in a row and trigger 2 toaster in between #group9': function (browser: NightwatchBrowser) {
browser
.frameParent()
.useCss()
.addFile('test_modal.js', { content: testModalToasterApi })
.executeScript('remix.execute(\'test_modal.js\')')
.clickLaunchIcon('localPlugin')
.useXpath()
// @ts-ignore
.frame(0)
.perform(async () => {
await clickAndCheckLog(browser, 'notification:toast', null, null, 'message toast from local plugin', false) // create a toast on behalf of the localplugin
await clickAndCheckLog(browser, 'notification:alert', null, null, { message: 'message from local plugin', id: 'test_id_1_local_plugin' }, false) // create an alert on behalf of the localplugin
})
.frameParent()
.useCss()
// check the local plugin notifications
.waitForElementVisible('*[data-id="test_id_1_local_pluginModalDialogModalBody-react"]')
.assert.containsText('*[data-id="test_id_1_local_pluginModalDialogModalBody-react"]', 'message from local plugin')
.modalFooterOKClick('test_id_1_local_plugin')
// check the script runner notifications
.waitForElementVisible('*[data-id="test_id_1_ModalDialogModalBody-react"]')
.assert.containsText('*[data-id="test_id_1_ModalDialogModalBody-react"]', 'message 1')
.modalFooterOKClick('test_id_1_')
.waitForElementVisible('*[data-id="test_id_2_ModalDialogModalBody-react"]')
.assert.containsText('*[data-id="test_id_2_ModalDialogModalBody-react"]', 'message 2')
.modalFooterOKClick('test_id_2_')
.waitForElementVisible('*[data-id="test_id_3_ModalDialogModalBody-react"]')
.modalFooterOKClick('test_id_3_')
.journalLastChildIncludes('default value... ') // check the return value of the prompt
// check the toasters
.waitForElementVisible('*[data-shared="tooltipPopup"]')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'message toast from local plugin')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'I am a toast')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'I am a re-toast')
} }
} }
const testModalToasterApi = `
// Right click on the script name and hit "Run" to execute
(async () => {
try {
setTimeout(async () => {
console.log('test .. ')
remix.call('notification', 'alert', { message: 'message 1', id: 'test_id_1_' })
remix.call('notification', 'toast', 'I am a toast')
remix.call('notification', 'toast', 'I am a re-toast')
remix.call('notification', 'alert', { message: 'message 2', id: 'test_id_2_' })
const modalContent = {
id: 'test_id_3_',
title: 'test with input title',
message: 'test with input content',
modalType: 'prompt',
okLabel: 'OK',
cancelLabel: 'Cancel',
defaultValue: 'default value... '
}
const result = await remix.call('notification', 'modal', modalContent)
console.log(result)
}, 500)
} catch (e) {
console.log(e.message)
}
})() `

@ -51,7 +51,7 @@ module.exports = {
.click('*[data-id="terminalClearConsole"]') // clear the terminal .click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]') .click('*[data-id="settingsWeb3Mode"]')
.modalFooterOKClick('udappNotify') .modalFooterOKClick('envNotification')
.executeScript('web3.eth.getAccounts()') .executeScript('web3.eth.getAccounts()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content .waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content
.waitForElementContainsText('*[data-id="terminalJournal"]', '"]', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', '"]', 60000)

@ -1,16 +1,6 @@
'use strict' 'use strict'
import { RunTab, makeUdapp } from './app/udapp' import { RunTab, makeUdapp } from './app/udapp'
import { RemixEngine } from './remixEngine'
import { RemixAppManager } from './remixAppManager' import { RemixAppManager } from './remixAppManager'
import { ThemeModule } from './app/tabs/theme-module'
import { NetworkModule } from './app/tabs/network-module'
import { Web3ProviderModule } from './app/tabs/web3-provider'
import { SidePanel } from './app/components/side-panel'
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 { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin'
import { WalkthroughService } from './walkthroughService' import { WalkthroughService } from './walkthroughService'
@ -19,7 +9,7 @@ import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, Fetch
import Registry from './app/state/registry' import Registry from './app/state/registry'
import { ConfigPlugin } from './app/plugins/config' import { ConfigPlugin } from './app/plugins/config'
import { Layout } from './app/panels/layout' import { Layout } from './app/panels/layout'
import { ModalPlugin } from './app/plugins/modal' import { NotificationPlugin } from './app/plugins/notification'
import { Blockchain } from './blockchain/blockchain.js' import { Blockchain } from './blockchain/blockchain.js'
import { HardhatProvider } from './app/tabs/hardhat-provider' import { HardhatProvider } from './app/tabs/hardhat-provider'
@ -95,7 +85,7 @@ class AppComponent {
const pluginLoader = self.appManager.pluginLoader const pluginLoader = self.appManager.pluginLoader
self.panels = {} self.panels = {}
self.workspace = pluginLoader.get() self.workspace = pluginLoader.get()
self.engine = new RemixEngine() self.engine = new (await import('./remixEngine')).RemixEngine
self.engine.register(appManager) self.engine.register(appManager)
const matomoDomains = { const matomoDomains = {
@ -126,7 +116,7 @@ class AppComponent {
// ----------------- gist service --------------------------------- // ----------------- gist service ---------------------------------
self.gistHandler = new GistHandler() self.gistHandler = new GistHandler()
// ----------------- theme service --------------------------------- // ----------------- theme service ---------------------------------
self.themeModule = new ThemeModule() self.themeModule = new (await import('./app/tabs/theme-module')).ThemeModule()
Registry.getInstance().put({ api: self.themeModule, name: 'themeModule' }) Registry.getInstance().put({ api: self.themeModule, name: 'themeModule' })
// ----------------- editor service ---------------------------- // ----------------- editor service ----------------------------
@ -159,9 +149,9 @@ class AppComponent {
// service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it // service which fetch contract artifacts from sourve-verify, put artifacts in remix and compile it
const fetchAndCompile = new FetchAndCompile() const fetchAndCompile = new FetchAndCompile()
// ----------------- network service (resolve network id / name) ----- // ----------------- network service (resolve network id / name) -----
const networkModule = new NetworkModule(blockchain) const networkModule = new (await import('./app/tabs/network-module')).NetworkModule(blockchain)
// ----------------- represent the current selected web3 provider ---- // ----------------- represent the current selected web3 provider ----
const web3Provider = new Web3ProviderModule(blockchain) const web3Provider = new (await import('./app/tabs/web3-provider')).Web3ProviderModule(blockchain)
const hardhatProvider = new HardhatProvider(blockchain) const hardhatProvider = new HardhatProvider(blockchain)
// ----------------- convert offset to line/column service ----------- // ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter() const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
@ -187,17 +177,17 @@ class AppComponent {
) )
const contextualListener = new EditorContextListener() const contextualListener = new EditorContextListener()
self.modal = new ModalPlugin() self.notification = new NotificationPlugin()
const configPlugin = new ConfigPlugin() const configPlugin = new ConfigPlugin()
self.layout = new Layout() self.layout = new Layout()
const permissionHandler = new PermissionHandlerPlugin() const permissionHandler = new (await import('./app/plugins/permission-handler-plugin')).PermissionHandlerPlugin()
self.engine.register([ self.engine.register([
permissionHandler, permissionHandler,
self.layout, self.layout,
self.modal, self.notification,
self.gistHandler, self.gistHandler,
configPlugin, configPlugin,
blockchain, blockchain,
@ -219,22 +209,22 @@ class AppComponent {
]) ])
// LAYOUT & SYSTEM VIEWS // LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel() const appPanel = new (await import('./app/components/main-panel')).MainPanel()
Registry.getInstance().put({ api: self.mainview, name: 'mainview' }) Registry.getInstance().put({ api: self.mainview, name: 'mainview' })
const tabProxy = new TabProxy(fileManager, editor) const tabProxy = new TabProxy(fileManager, editor)
self.engine.register([appPanel, tabProxy]) self.engine.register([appPanel, tabProxy])
// those views depend on app_manager // those views depend on app_manager
self.menuicons = new VerticalIcons() self.menuicons = new (await import('./app/components/vertical-icons')).VerticalIcons()
self.sidePanel = new SidePanel() self.sidePanel = new (await import('./app/components/side-panel')).SidePanel()
self.hiddenPanel = new HiddenPanel() self.hiddenPanel = new (await import('./app/components/hidden-panel')).HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent( const pluginManagerComponent = new PluginManagerComponent(
appManager, appManager,
self.engine self.engine
) )
const filePanel = new FilePanel(appManager) const filePanel = new FilePanel(appManager)
const landingPage = new LandingPage( const landingPage = new (await import('./app/ui/landing-page/landing-page')).LandingPage(
appManager, appManager,
self.menuicons, self.menuicons,
fileManager, fileManager,

@ -4,7 +4,7 @@ import { AppModal } from '@remix-ui/app'
import { AlertModal } from 'libs/remix-ui/app/src/lib/remix-app/interface' import { AlertModal } from 'libs/remix-ui/app/src/lib/remix-app/interface'
import { dispatchModalInterface } from 'libs/remix-ui/app/src/lib/remix-app/context/context' import { dispatchModalInterface } from 'libs/remix-ui/app/src/lib/remix-app/context/context'
interface IModalApi { interface INotificationApi {
events: StatusEvents, events: StatusEvents,
methods: { methods: {
modal: (args: AppModal) => void modal: (args: AppModal) => void
@ -13,14 +13,14 @@ interface IModalApi {
} }
} }
const profile:LibraryProfile<IModalApi> = { const profile:LibraryProfile<INotificationApi> = {
name: 'notification', name: 'notification',
displayName: 'Notification', displayName: 'Notification',
description: 'Displays notifications', description: 'Displays notifications',
methods: ['modal', 'alert', 'toast'] methods: ['modal', 'alert', 'toast']
} }
export class ModalPlugin extends Plugin implements MethodApi<IModalApi> { export class NotificationPlugin extends Plugin implements MethodApi<INotificationApi> {
dispatcher: dispatchModalInterface dispatcher: dispatchModalInterface
constructor () { constructor () {
super(profile) super(profile)
@ -38,7 +38,7 @@ export class ModalPlugin extends Plugin implements MethodApi<IModalApi> {
return this.dispatcher.alert(args) return this.dispatcher.alert(args)
} }
async toast (message: string) { async toast (message: string | JSX.Element) {
this.dispatcher.toast(message) this.dispatcher.toast(message)
} }
} }

@ -1,16 +1,37 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import { render } from 'react-dom'
import AppComponent from './app'
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { RemixApp } from '@remix-ui/app' import { RemixApp } from '@remix-ui/app'
const appComponent = new AppComponent() (function () {
appComponent.run() render(
<React.StrictMode>
<div style={{ display: 'block' }} className='centered'>
<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z"/>
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z"/>
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z"/>
</svg>
<div className="info-secondary splash">
REMIX IDE
</div>
</div>
</React.StrictMode>,
document.getElementById('root')
)
})()
ReactDOM.render( import ('./app').then((AppComponent) => {
<React.StrictMode> const appComponent = new AppComponent.default()
<RemixApp app={appComponent}></RemixApp> appComponent.run().then(() => {
</React.StrictMode>, render(
document.getElementById('root') <React.StrictMode>
) <RemixApp app={appComponent} />
</React.StrictMode>,
document.getElementById('root')
)
})
}).catch(err => {
console.log('Error on loading Remix:', err)
})

@ -14,6 +14,7 @@ export class RemixEngine extends Engine {
if (name === 'slither') return { queueTimeout: 60000 * 4 } // Requires when a solc version is installed if (name === 'slither') return { queueTimeout: 60000 * 4 } // Requires when a solc version is installed
if (name === 'hardhat') return { queueTimeout: 60000 * 4 } if (name === 'hardhat') return { queueTimeout: 60000 * 4 }
if (name === 'localPlugin') return { queueTimeout: 60000 * 4 } if (name === 'localPlugin') return { queueTimeout: 60000 * 4 }
if (name === 'notification') return { queueTimeout: 60000 * 4 }
return { queueTimeout: 10000 } return { queueTimeout: 10000 }
} }

@ -4,10 +4,8 @@
"jsx": "react", "jsx": "react",
"allowJs": true, "allowJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"types": ["node", "jest"], "types": ["node", "jest"],
"module": "es6",
"resolveJsonModule": true "resolveJsonModule": true
}, },
"files": [ "files": [

@ -14,6 +14,7 @@ type ActionMap<M extends { [index: string]: any }> = {
export const enum modalActionTypes { export const enum modalActionTypes {
setModal = 'SET_MODAL', setModal = 'SET_MODAL',
setToast = 'SET_TOAST', setToast = 'SET_TOAST',
processQueue = 'PROCESS_QUEUEU',
handleHideModal = 'HANDLE_HIDE_MODAL', handleHideModal = 'HANDLE_HIDE_MODAL',
handleToaster = 'HANDLE_HIDE_TOAST' handleToaster = 'HANDLE_HIDE_TOAST'
} }
@ -22,7 +23,8 @@ type ModalPayload = {
[modalActionTypes.setModal]: AppModal [modalActionTypes.setModal]: AppModal
[modalActionTypes.handleHideModal]: any [modalActionTypes.handleHideModal]: any
[modalActionTypes.setToast]: string | JSX.Element [modalActionTypes.setToast]: string | JSX.Element
[modalActionTypes.handleToaster]: any [modalActionTypes.handleToaster]: any,
[modalActionTypes.processQueue]: any
} }
export type ModalAction = ActionMap<ModalPayload>[keyof ActionMap< export type ModalAction = ActionMap<ModalPayload>[keyof ActionMap<

@ -7,7 +7,7 @@ export const AppContext = React.createContext<any>(null)
export interface dispatchModalInterface { export interface dispatchModalInterface {
modal: (data: AppModal) => void modal: (data: AppModal) => void
toast: (message: string) => void toast: (message: string | JSX.Element) => void
alert: (data: AlertModal) => void alert: (data: AlertModal) => void
handleHideModal: () => void, handleHideModal: () => void,
handleToaster: () => void handleToaster: () => void
@ -15,7 +15,7 @@ export interface dispatchModalInterface {
export const dispatchModalContext = React.createContext<dispatchModalInterface>({ export const dispatchModalContext = React.createContext<dispatchModalInterface>({
modal: (data: AppModal) => { }, modal: (data: AppModal) => { },
toast: (message: string) => {}, toast: (message: string | JSX.Element) => {},
alert: (data: AlertModal) => {}, alert: (data: AlertModal) => {},
handleHideModal: () => {}, handleHideModal: () => {},
handleToaster: () => {} handleToaster: () => {}

@ -9,12 +9,18 @@ import { AppContext, dispatchModalContext, modalContext } from './context'
export const ModalProvider = ({ children = [], reducer = modalReducer, initialState = ModalInitialState } = {}) => { export const ModalProvider = ({ children = [], reducer = modalReducer, initialState = ModalInitialState } = {}) => {
const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState) const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState)
const onNextFn = async () => {
dispatch({
type: modalActionTypes.processQueue
})
}
const modal = (data: AppModal) => { const modal = (data: AppModal) => {
const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn } = data const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn } = data
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
dispatch({ dispatch({
type: modalActionTypes.setModal, type: modalActionTypes.setModal,
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve } payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn }
}) })
}) })
} }

@ -2,6 +2,7 @@ import { ModalTypes } from '../types'
export interface AppModal { export interface AppModal {
id: string id: string
timestamp?: number
hide?: boolean hide?: boolean
title: string title: string
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
@ -13,7 +14,8 @@ export interface AppModal {
modalType?: ModalTypes, modalType?: ModalTypes,
defaultValue?: string defaultValue?: string
hideFn?: () => void, hideFn?: () => void,
resolve?: (value?:any) => void resolve?: (value?:any) => void,
next?: () => void
} }
export interface AlertModal { export interface AlertModal {

@ -5,49 +5,78 @@ import { AppModal, ModalState } from '../interface'
export const modalReducer = (state: ModalState = ModalInitialState, action: ModalAction) => { export const modalReducer = (state: ModalState = ModalInitialState, action: ModalAction) => {
switch (action.type) { switch (action.type) {
case modalActionTypes.setModal: { case modalActionTypes.setModal: {
let modalList:AppModal[] = state.modals const timestamp = Date.now()
modalList.push(action.payload) const focusModal: AppModal = {
if (state.modals.length === 1 && state.focusModal.hide === true) { // if it's the first one show it timestamp,
const focusModal: AppModal = { id: action.payload.id || timestamp.toString(),
id: modalList[0].id, hide: false,
hide: false, title: action.payload.title,
title: modalList[0].title, message: action.payload.message,
message: modalList[0].message, okLabel: action.payload.okLabel,
okLabel: modalList[0].okLabel, okFn: action.payload.okFn,
okFn: modalList[0].okFn, cancelLabel: action.payload.cancelLabel,
cancelLabel: modalList[0].cancelLabel, cancelFn: action.payload.cancelFn,
cancelFn: modalList[0].cancelFn, modalType: action.payload.modalType,
modalType: modalList[0].modalType, defaultValue: action.payload.defaultValue,
defaultValue: modalList[0].defaultValue, hideFn: action.payload.hideFn,
hideFn: modalList[0].hideFn, resolve: action.payload.resolve,
resolve: modalList[0].resolve next: action.payload.next
} }
modalList = modalList.slice() const modalList: AppModal[] = state.modals.slice()
modalList.shift() modalList.push(focusModal)
return { ...state, modals: modalList, focusModal: focusModal }
if (modalList.length === 1) {
return { ...state, modals: modalList, focusModal }
} else {
return { ...state, modals: modalList }
} }
}
case modalActionTypes.handleHideModal: {
setTimeout(() => {
if (state.focusModal.hideFn) {
state.focusModal.hideFn()
}
if (state.focusModal.resolve) {
state.focusModal.resolve(undefined)
}
if (state.focusModal.next) {
state.focusModal.next()
}
}, 250)
const modalList: AppModal[] = state.modals.slice()
modalList.shift() // remove the current modal from the list
state.focusModal = { ...state.focusModal, hide: true, message: null }
return { ...state, modals: modalList } return { ...state, modals: modalList }
} }
case modalActionTypes.handleHideModal: case modalActionTypes.processQueue: {
if (state.focusModal.hideFn) { const modalList: AppModal[] = state.modals.slice()
state.focusModal.hideFn() if (modalList.length) {
} else if (state.focusModal.resolve) { const focusModal = modalList[0] // extract the next modal from the list
state.focusModal.resolve(undefined) return { ...state, modals: modalList, focusModal }
} else {
return { ...state, modals: modalList }
} }
state.focusModal = { ...state.focusModal, hide: true, message: null } }
return { ...state } case modalActionTypes.setToast: {
const toasterList = state.toasters.slice()
case modalActionTypes.setToast: const message = action.payload
state.toasters.push(action.payload) toasterList.push(message)
if (state.toasters.length > 0) { if (toasterList.length === 1) {
const focus = state.toasters[0] return { ...state, toasters: toasterList, focusToaster: action.payload }
state.toasters.shift() } else {
return { ...state, focusToaster: focus } return { ...state, toasters: toasterList }
} }
return { ...state } }
case modalActionTypes.handleToaster: {
case modalActionTypes.handleToaster: const toasterList = state.toasters.slice()
return { ...state, focusToaster: '' } toasterList.shift()
if (toasterList.length) {
const toaster = toasterList[0]
return { ...state, toasters: toasterList, focusToaster: toaster }
} else {
return { ...state, toasters: [] }
}
}
} }
} }

@ -83,7 +83,7 @@ const RemixApp = (props: IRemixAppUi) => {
settings: props.app.settings, settings: props.app.settings,
showMatamo: props.app.showMatamo, showMatamo: props.app.showMatamo,
appManager: props.app.appManager, appManager: props.app.appManager,
modal: props.app.modal, modal: props.app.notification,
layout: props.app.layout layout: props.app.layout
} }

@ -1,4 +1,5 @@
import React from 'react' import React from 'react'
import { Web3ProviderDialog } from './components/web3Dialog'
export const fileChangedToastMsg = (from: string, path: string) => ( export const fileChangedToastMsg = (from: string, path: string) => (
<div><i className="fas fa-exclamation-triangle text-danger mr-1"></i> <div><i className="fas fa-exclamation-triangle text-danger mr-1"></i>
@ -52,3 +53,19 @@ export const sourceVerificationNotAvailableToastMsg = () => (
<b>Source verification plugin not activated or not available.</b> continuing <i>without</i> source code debugging. <b>Source verification plugin not activated or not available.</b> continuing <i>without</i> source code debugging.
</div> </div>
) )
export const web3Dialog = (externalEndpoint: string, setWeb3Endpoint: (value: string) => void) => (
<Web3ProviderDialog externalEndpoint={externalEndpoint} setWeb3Endpoint={setWeb3Endpoint} />
)
export const envChangeNotification = (env: { context: string, fork: string }, from: string) => (
<div>
<i className="fas fa-exclamation-triangle text-danger mr-1"></i>
<span>
{ from + ' '}
<span className="font-weight-bold text-warning">
is changing your environment to
</span> {env && env.context}
</span>
</div>
)

@ -1,6 +1,7 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
export interface ModalDialogProps { export interface ModalDialogProps {
id: string id: string
timestamp?: number,
title?: string, title?: string,
message?: string | JSX.Element, message?: string | JSX.Element,
okLabel?: string, okLabel?: string,
@ -13,4 +14,5 @@ export interface ModalDialogProps {
handleHide: (hideState?: boolean) => void, handleHide: (hideState?: boolean) => void,
children?: React.ReactNode, children?: React.ReactNode,
resolve?: (value?:any) => void, resolve?: (value?:any) => void,
next?: () => void
} }

@ -2,8 +2,8 @@
import React from 'react' import React from 'react'
import * as ethJSUtil from 'ethereumjs-util' import * as ethJSUtil from 'ethereumjs-util'
import Web3 from 'web3' import Web3 from 'web3'
import { addressToString, createNonClashingNameAsync, extractNameFromKey, shortenAddress } from '@remix-ui/helper' import { addressToString, createNonClashingNameAsync, envChangeNotification, extractNameFromKey, shortenAddress, web3Dialog } from '@remix-ui/helper'
import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, fetchContractListSuccess, hidePopUp, removeExistingInstance, removeProvider, resetUdapp, 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 { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, displayNotification, displayPopUp, fetchAccountsListFailed, fetchAccountsListRequest, fetchAccountsListSuccess, fetchContractListSuccess, hidePopUp, removeExistingInstance, removeProvider, resetUdapp, setBaseFeePerGas, setConfirmSettings, setCurrentFile, setDecodedResponse, setEnvToasterContent, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setLoadType, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setRecorderCount, setSelectedAccount, setSendUnit, setSendValue, setTxFeeContent } from './payload'
import { RunTab } from '../types/run-tab' import { RunTab } from '../types/run-tab'
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
@ -75,8 +75,8 @@ const setupEvents = () => {
plugin.on('yulp', '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('udapp', 'setEnvironmentModeReducer', (env: { context: string, fork: string }, from: string) => { plugin.on('udapp', 'setEnvironmentModeReducer', (env: { context: string, fork: string }, from: string) => {
dispatch(displayPopUp(plugin.REACT_API.envToasterContent(env, from))) plugin.call('notification', 'toast', envChangeNotification(env, from))
setExecutionContext(env, plugin.REACT_API.web3Dialog()) setExecutionContext(env)
}) })
plugin.on('filePanel', 'setWorkspace', () => { plugin.on('filePanel', 'setWorkspace', () => {
@ -106,11 +106,6 @@ const setupEvents = () => {
}) })
} }
export const initWebDialogs = (envToasterContent: (env: { context: string, fork: string }, from: string) => void, web3Dialog: () => void) => async (dispatch: React.Dispatch<any>) => {
dispatch(setEnvToasterContent(envToasterContent))
dispatch(setWeb3Dialog(web3Dialog))
}
const updateAccountBalances = () => { const updateAccountBalances = () => {
const accounts = plugin.REACT_API.accounts.loadedAccounts const accounts = plugin.REACT_API.accounts.loadedAccounts
@ -225,16 +220,28 @@ const removeExternalProvider = (name) => {
dispatch(removeProvider(name)) dispatch(removeProvider(name))
} }
export const setExecutionContext = (executionContext: { context: string, fork: string }, displayContent: JSX.Element) => { export const setExecutionContext = (executionContext: { context: string, fork: string }) => {
const displayContent = web3Dialog(plugin.REACT_API.externalEndpoint, setWeb3Endpoint)
plugin.blockchain.changeExecutionContext(executionContext, () => { plugin.blockchain.changeExecutionContext(executionContext, () => {
dispatch(displayNotification('External node request', displayContent, 'OK', 'Cancel', () => { plugin.call('notification', 'modal', {
plugin.blockchain.setProviderFromEndpoint(plugin.REACT_API.externalEndpoint, executionContext, (alertMsg) => { id: 'envNotification',
if (alertMsg) dispatch(displayPopUp(alertMsg)) title: 'External node request',
message: displayContent,
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
plugin.blockchain.setProviderFromEndpoint(plugin.REACT_API.externalEndpoint, executionContext, (alertMsg) => {
if (alertMsg) plugin.call('notification', 'toast', alertMsg)
setFinalContext()
})
},
cancelFn: () => {
setFinalContext() setFinalContext()
}) }
}, () => { setFinalContext() })) })
}, (alertMsg) => { }, (alertMsg) => {
dispatch(displayPopUp(alertMsg)) plugin.call('notification', 'toast', alertMsg)
}, () => { setFinalContext() }) }, () => { setFinalContext() })
} }

@ -278,13 +278,6 @@ export const setEnvToasterContent = (content: (env: { context: string, fork: str
} }
} }
export const setWeb3Dialog = (web3Dialog: () => void) => {
return {
type: 'SET_WEB3_DIALOG',
payload: web3Dialog
}
}
export const resetUdapp = () => { export const resetUdapp = () => {
return { return {
type: 'RESET_STATE' type: 'RESET_STATE'

@ -9,9 +9,8 @@ export function EnvironmentUI (props: EnvironmentProps) {
let context = provider.value let context = provider.value
context = context.startsWith('vm') ? 'vm' : context // context has to be 'vm', 'web3' or 'injected' context = context.startsWith('vm') ? 'vm' : context // context has to be 'vm', 'web3' or 'injected'
const displayContent = props.web3ProviderDialog()
props.setExecutionContext({ context, fork }, displayContent) props.setExecutionContext({ context, fork })
} }
return ( return (

@ -12,7 +12,7 @@ export function SettingsUI (props: SettingsProps) {
return ( return (
<div className="udapp_settings"> <div className="udapp_settings">
<EnvironmentUI selectedEnv={props.selectExEnv} providers={props.providers} setExecutionContext={props.setExecutionContext} web3ProviderDialog={props.web3ProviderDialog} /> <EnvironmentUI selectedEnv={props.selectExEnv} providers={props.providers} setExecutionContext={props.setExecutionContext} />
<NetworkUI networkName={props.networkName} /> <NetworkUI networkName={props.networkName} />
<AccountUI personalMode={props.personalMode} selectExEnv={props.selectExEnv} accounts={props.accounts} setAccount={props.setAccount} createNewBlockchainAccount={props.createNewBlockchainAccount} setPassphrase={props.setPassphrase} setMatchPassphrase={props.setMatchPassphrase} tooltip={props.tooltip} modal={props.modal} signMessageWithAddress={props.signMessageWithAddress} passphrase={props.passphrase} /> <AccountUI personalMode={props.personalMode} selectExEnv={props.selectExEnv} accounts={props.accounts} setAccount={props.setAccount} createNewBlockchainAccount={props.createNewBlockchainAccount} setPassphrase={props.setPassphrase} setMatchPassphrase={props.setMatchPassphrase} tooltip={props.tooltip} modal={props.modal} signMessageWithAddress={props.signMessageWithAddress} passphrase={props.passphrase} />
<GasPriceUI gasLimit={props.gasLimit} setGasFee={props.setGasFee} /> <GasPriceUI gasLimit={props.gasLimit} setGasFee={props.setGasFee} />

@ -78,9 +78,7 @@ export interface RunTabState {
recorder: { recorder: {
pathToScenario: string, pathToScenario: string,
transactionCount: number transactionCount: number
}, }
envToasterContent: (env: { context: string, fork: string }, from: string) => JSX.Element
web3Dialog: () => JSX.Element
} }
export const runTabInitialState: RunTabState = { export const runTabInitialState: RunTabState = {
@ -166,9 +164,7 @@ export const runTabInitialState: RunTabState = {
recorder: { recorder: {
pathToScenario: 'scenario.json', pathToScenario: 'scenario.json',
transactionCount: 0 transactionCount: 0
}, }
envToasterContent: null,
web3Dialog: null
} }
type AddProvider = { type AddProvider = {
@ -650,24 +646,6 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
} }
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
}
}
case 'RESET_STATE': { case 'RESET_STATE': {
return { return {
...runTabInitialState, ...runTabInitialState,

@ -24,15 +24,14 @@ import {
removeInstance, getContext, removeInstance, getContext,
runTransactions, loadAddress, runTransactions, loadAddress,
storeScenario, runCurrentScenario, storeScenario, runCurrentScenario,
updateScenarioPath, initWebDialogs, updateScenarioPath, getFuncABIInputs,
getFuncABIInputs, setNetworkNameFromProvider setNetworkNameFromProvider
} from './actions' } from './actions'
import './css/run-tab.css' import './css/run-tab.css'
import { PublishToStorage } from '@remix-ui/publish-to-storage' import { PublishToStorage } from '@remix-ui/publish-to-storage'
import { PassphrasePrompt } from './components/passphrase' import { PassphrasePrompt } from './components/passphrase'
import { MainnetPrompt } from './components/mainnet' import { MainnetPrompt } from './components/mainnet'
import { ScenarioPrompt } from './components/scenario' import { ScenarioPrompt } from './components/scenario'
import { Web3ProviderDialog } from './components/web3Dialog'
import { setIpfsCheckedState } from './actions/payload' import { setIpfsCheckedState } from './actions/payload'
export function RunTabUI (props: RunTabProps) { export function RunTabUI (props: RunTabProps) {
@ -61,7 +60,6 @@ export function RunTabUI (props: RunTabProps) {
useEffect(() => { useEffect(() => {
initRunTab(plugin)(dispatch) initRunTab(plugin)(dispatch)
initWebDialogs(envChangeNotification, web3Dialog)(dispatch)
}, [plugin]) }, [plugin])
useEffect(() => { useEffect(() => {
@ -197,24 +195,6 @@ export function RunTabUI (props: RunTabProps) {
/> />
} }
const envChangeNotification = (env: { context: string, fork: string }, from: string) => {
return (
<div>
<i className="fas fa-exclamation-triangle text-danger mr-1"></i>
<span>
{from}
<span className="font-weight-bold text-warning">
is changing your environment to
</span> {env && env.context}
</span>
</div>
)
}
const web3Dialog = () => {
return <Web3ProviderDialog externalEndpoint={runTab.externalEndpoint} setWeb3Endpoint={setWeb3Endpoint} />
}
return ( return (
<Fragment> <Fragment>
<div className="udapp_runTabView run-tab" id="runTabView" data-id="runTabView"> <div className="udapp_runTabView run-tab" id="runTabView" data-id="runTabView">
@ -241,7 +221,6 @@ export function RunTabUI (props: RunTabProps) {
tooltip={toast} tooltip={toast}
signMessageWithAddress={signMessageWithAddress} signMessageWithAddress={signMessageWithAddress}
passphrase={runTab.passphrase} passphrase={runTab.passphrase}
web3ProviderDialog={web3Dialog}
/> />
<ContractDropdownUI <ContractDropdownUI
exEnvironment={runTab.selectExEnv} exEnvironment={runTab.selectExEnv}

@ -34,7 +34,7 @@ export interface SettingsProps {
isSuccessful: boolean, isSuccessful: boolean,
error: string error: string
}, },
setExecutionContext: (executionContext: { context: string, fork: string }, displayContent: JSX.Element) => void, setExecutionContext: (executionContext: { context: string, fork: string }) => void,
createNewBlockchainAccount: (cbMessage: JSX.Element) => void, createNewBlockchainAccount: (cbMessage: JSX.Element) => void,
setPassphrase: (passphrase: string) => void, setPassphrase: (passphrase: string) => void,
setMatchPassphrase: (passphrase: string) => void, setMatchPassphrase: (passphrase: string) => void,
@ -42,7 +42,6 @@ export interface SettingsProps {
tooltip: (toasterMsg: string) => void, tooltip: (toasterMsg: string) => void,
signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void, signMessageWithAddress: (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => void,
passphrase: string, passphrase: string,
web3ProviderDialog: () => JSX.Element,
setSendValue: (value: string) => void setSendValue: (value: string) => void
} }
@ -61,8 +60,7 @@ export interface EnvironmentProps {
isSuccessful: boolean, isSuccessful: boolean,
error: string error: string
}, },
setExecutionContext: (executionContext: { context: string, fork: string }, displayContent: JSX.Element) => void, setExecutionContext: (executionContext: { context: string, fork: string }) => void
web3ProviderDialog: () => JSX.Element
} }
export interface NetworkProps { export interface NetworkProps {

Loading…
Cancel
Save