Merge branch 'master' of https://github.com/ethereum/remix-project into fix-verticalIcons-badge

pull/1861/head
bunsenstraat 3 years ago
commit 62222eb5a5
  1. 2
      apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx
  2. 32
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  3. 1
      apps/remix-ide/ci/browser_tests_plugin_api.sh
  4. 11
      apps/remix-ide/src/app.js
  5. 3
      apps/remix-ide/src/app/components/plugin-manager-component.js
  6. 147
      apps/remix-ide/src/app/components/plugin-manager-settings.js
  7. 24
      apps/remix-ide/src/app/files/fileManager.ts
  8. 8
      apps/remix-ide/src/app/files/fileProvider.js
  9. 10
      apps/remix-ide/src/app/plugins/modal.tsx
  10. 126
      apps/remix-ide/src/app/plugins/permission-handler-plugin.tsx
  11. 28
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  12. 19
      apps/remix-ide/src/app/tabs/compile-tab.js
  13. 33
      apps/remix-ide/src/app/tabs/debugger-tab.js
  14. 8
      apps/remix-ide/src/app/tabs/hardhat-provider.tsx
  15. 114
      apps/remix-ide/src/app/ui/modal-dialog-custom.js
  16. 150
      apps/remix-ide/src/app/ui/modaldialog.js
  17. 202
      apps/remix-ide/src/app/ui/persmission-handler.js
  18. 110
      apps/remix-ide/src/app/ui/tooltip.js
  19. 8
      apps/remix-ide/src/remixAppManager.js
  20. 12
      libs/remix-core-plugin/src/lib/gist-handler.ts
  21. 2
      libs/remix-tests/package.json
  22. 2
      libs/remix-ui/app/src/lib/remix-app/actions/modals.ts
  23. 25
      libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
  24. 8
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  25. 11
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  26. 5
      libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
  27. 24
      libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
  28. 1
      libs/remix-ui/helper/src/index.ts
  29. 55
      libs/remix-ui/helper/src/lib/helper-components.tsx
  30. 6
      libs/remix-ui/helper/tsconfig.json
  31. 3
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  32. 4
      libs/remix-ui/permission-handler/.babelrc
  33. 18
      libs/remix-ui/permission-handler/.eslintrc.json
  34. 2
      libs/remix-ui/permission-handler/src/index.ts
  35. 15
      libs/remix-ui/permission-handler/src/interface/index.ts
  36. 29
      libs/remix-ui/permission-handler/src/lib/permission-dialog.css
  37. 67
      libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx
  38. 20
      libs/remix-ui/permission-handler/tsconfig.json
  39. 13
      libs/remix-ui/permission-handler/tsconfig.lib.json
  40. 6
      libs/remix-ui/plugin-manager/src/lib/components/permissionsSettings.tsx
  41. 5
      libs/remix-ui/plugin-manager/src/lib/components/rootView.tsx
  42. 4
      libs/remix-ui/plugin-manager/src/lib/remix-ui-plugin-manager.tsx
  43. 60
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  44. 11
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  45. 44
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  46. 31
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  47. 25
      libs/remix-ui/toaster/src/lib/toaster.tsx
  48. 6
      libs/remix-ui/workspace/src/lib/actions/events.ts
  49. 7
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  50. 9
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  51. 3
      nx.json
  52. 70
      package-lock.json
  53. 16
      package.json
  54. 3
      tsconfig.base.json
  55. 15
      workspace.json

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

@ -10,7 +10,7 @@ declare global {
const localPluginData: Profile & LocationProfile & ExternalProfile = {
name: 'localPlugin',
displayName: 'Local Plugin',
canActivate: ['dGitProvider', 'flattener', 'solidityUnitTesting', 'udapp'],
canActivate: ['dGitProvider', 'flattener', 'solidityUnitTesting', 'udapp', 'hardhat-provider'],
url: 'http://localhost:2020',
location: 'sidePanel'
}
@ -82,7 +82,7 @@ const checkForAcceptAndRemember = async function (browser: NightwatchBrowser) {
// @ts-ignore
browser.frame(0, () => { resolve(true) })
} else {
browser.waitForElementVisible('//*[@data-id="permissionHandlerRememberUnchecked"]').click('//*[@data-id="permissionHandlerRememberUnchecked"]').waitForElementVisible('//*[@id="modal-footer-ok"]').click('//*[@id="modal-footer-ok"]', () => {
browser.waitForElementVisible('//*[@data-id="permissionHandlerRememberUnchecked"]').click('//*[@data-id="permissionHandlerRememberUnchecked"]').waitForElementVisible('//*[@data-id="PermissionHandler-modal-footer-ok-react"]').click('//*[@data-id="PermissionHandler-modal-footer-ok-react"]', () => {
// @ts-ignore
browser.frame(0, () => { resolve(true) })
})
@ -324,5 +324,33 @@ module.exports = {
'Should get compilationresults #group6': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'solidity:getCompilationResult', 'contracts/1_Storage.sol', null, null)
},
// PROVIDER
'Should switch to hardhat provider (provider plugin) #group8': function (browser: NightwatchBrowser) {
browser
.frameParent()
.useCss()
.clickLaunchIcon('pluginManager')
.scrollAndClick('[data-id="pluginManagerComponentActivateButtonhardhat-provider"]')
.clickLaunchIcon('udapp')
.click('*[data-id="Hardhat Provider"]')
.modalFooterOKClick('hardhatprovider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom') // e.g Custom (1337) network
.clickLaunchIcon('localPlugin')
.useXpath()
// @ts-ignore
.frame(0)
.perform(async () => {
const request = {
id: 9999,
jsonrpc: '2.0',
method: 'net_listening',
params: []
}
const result = '{"jsonrpc":"2.0","result":true,"id":9999}'
await clickAndCheckLog(browser, 'hardhat-provider:sendAsync', result, null, request)
})
}
}

@ -6,6 +6,7 @@ BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID"
TEST_EXITCODE=0
npm run ganache-cli &
npm run serve:production &
npx nx serve remix-ide-e2e-src-local-plugin &

@ -10,6 +10,7 @@ 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'
@ -35,7 +36,6 @@ const FileManager = require('./app/files/fileManager')
const FileProvider = require('./app/files/fileProvider')
const DGitProvider = require('./app/files/dgitProvider')
const WorkspaceFileProvider = require('./app/files/workspaceFileProvider')
const toolTip = require('./app/ui/tooltip')
const PluginManagerComponent = require('./app/components/plugin-manager-component')
@ -192,7 +192,10 @@ class AppComponent {
const configPlugin = new ConfigPlugin()
self.layout = new Layout()
const permissionHandler = new PermissionHandlerPlugin()
self.engine.register([
permissionHandler,
self.layout,
self.modal,
self.gistHandler,
@ -315,9 +318,9 @@ class AppComponent {
console.log("couldn't register iframe plugins", e.message)
}
await self.appManager.activatePlugin(['layout'])
await self.appManager.activatePlugin(['modal'])
await self.appManager.activatePlugin(['notification'])
await self.appManager.activatePlugin(['editor'])
await self.appManager.activatePlugin(['theme', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await self.appManager.activatePlugin(['permissionhandler', 'theme', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await self.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await self.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await self.appManager.activatePlugin(['home'])
@ -364,7 +367,7 @@ class AppComponent {
if (params.call) {
const callDetails = params.call.split('//')
if (callDetails.length > 1) {
toolTip(`initiating ${callDetails[0]} ...`)
self.appManager.call('notification', 'toast', `initiating ${callDetails[0]} ...`)
// @todo(remove the timeout when activatePlugin is on 0.3.0)
self.appManager.call(...callDetails).catch(console.error)
}

@ -1,5 +1,4 @@
import { ViewPlugin } from '@remixproject/engine-web'
import { PluginManagerSettings } from './plugin-manager-settings'
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import {RemixUiPluginManager} from '@remix-ui/plugin-manager' // eslint-disable-line
@ -24,7 +23,6 @@ class PluginManagerComponent extends ViewPlugin {
super(profile)
this.appManager = appManager
this.engine = engine
this.pluginManagerSettings = new PluginManagerSettings()
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'pluginManager')
this.filter = ''
@ -90,7 +88,6 @@ class PluginManagerComponent extends ViewPlugin {
ReactDOM.render(
<RemixUiPluginManager
pluginComponent={this}
pluginManagerSettings={this.pluginManagerSettings}
/>,
this.htmlElement)
}

@ -1,147 +0,0 @@
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const modalDialog = require('../ui/modaldialog')
const css = csjs`
.remixui_permissions {
position: sticky;
bottom: 0;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 5px 20px;
}
.permissions button {
padding: 2px 5px;
cursor: pointer;
}
.permissionForm h4 {
font-size: 1.3rem;
text-align: center;
}
.permissionForm h6 {
font-size: 1.1rem;
}
.permissionForm hr {
width: 80%;
}
.permissionKey {
display: flex;
justify-content: space-between;
align-items: center;
}
.permissionKey i {
cursor: pointer;
}
.checkbox {
display: flex;
align-items: center;
}
.checkbox label {
margin: 0;
font-size: 1rem;
}
`
export class PluginManagerSettings {
constructor () {
const fromLocal = window.localStorage.getItem('plugins/permissions')
this.permissions = JSON.parse(fromLocal || '{}')
}
openDialog () {
this.currentSetting = this.settings()
modalDialog('Plugin Manager Permissions', this.currentSetting,
{ fn: () => this.onValidation() }
)
}
onValidation () {
const permissions = JSON.stringify(this.permissions)
window.localStorage.setItem('plugins/permissions', permissions)
}
/** Clear one permission from a plugin */
clearPersmission (from, to, method) {
// eslint-disable-next-line no-debugger
debugger
if (this.permissions[to] && this.permissions[to][method]) {
delete this.permissions[to][method][from]
if (Object.keys(this.permissions[to][method]).length === 0) {
delete this.permissions[to][method]
}
if (Object.keys(this.permissions[to]).length === 0) {
delete this.permissions[to]
}
yo.update(this.currentSetting, this.settings())
}
}
/** Clear all persmissions from a plugin */
clearAllPersmission (to) {
// eslint-disable-next-line no-debugger
debugger
if (!this.permissions[to]) return
delete this.permissions[to]
yo.update(this.currentSetting, this.settings())
}
settings () {
const permissionByToPlugin = (toPlugin, funcObj) => {
const permissionByMethod = (methodName, fromPlugins) => {
const togglePermission = (fromPlugin) => {
this.permissions[toPlugin][methodName][fromPlugin].allow = !this.permissions[toPlugin][methodName][fromPlugin].allow
}
return Object.keys(fromPlugins).map(fromName => {
const fromPluginPermission = fromPlugins[fromName]
const checkbox = fromPluginPermission.allow
? yo`<input onchange=${() => togglePermission(fromName)} class="mr-2" type="checkbox" checked id="permission-checkbox-${toPlugin}-${methodName}-${toPlugin}" aria-describedby="module ${fromPluginPermission} asks permission for ${methodName}" />`
: yo`<input onchange=${() => togglePermission(fromName)} class="mr-2" type="checkbox" id="permission-checkbox-${toPlugin}-${methodName}-${toPlugin}" aria-describedby="module ${fromPluginPermission} asks permission for ${methodName}" />`
return yo`
<div class="form-group ${css.permissionKey}">
<div class="${css.checkbox}">
${checkbox}
<label for="permission-checkbox-${toPlugin}-${methodName}-${toPlugin}" data-id="permission-label-${toPlugin}-${methodName}-${toPlugin}">Allow <u>${fromName}</u> to call <u>${methodName}</u></label>
</div>
<i onclick="${() => this.clearPersmission(fromName, toPlugin, methodName)}" class="fa fa-trash-alt" data-id="pluginManagerSettingsRemovePermission-${toPlugin}-${methodName}-${toPlugin}"></i>
</div>
`
})
}
const permissionsByFunctions = Object
.keys(funcObj)
.map(methodName => permissionByMethod(methodName, funcObj[methodName]))
return yo`
<div border p-2>
<div class="pb-2 ${css.permissionKey}">
<h3>${toPlugin} permissions:</h3>
<i onclick="${() => this.clearAllPersmission(toPlugin)}" class="far fa-trash-alt" data-id="pluginManagerSettingsClearAllPermission-${toPlugin}"></i>
</div>
${permissionsByFunctions}
</div>`
}
const byToPlugin = Object
.keys(this.permissions)
.map(toPlugin => permissionByToPlugin(toPlugin, this.permissions[toPlugin]))
const title = byToPlugin.length === 0
? yo`<h4>No Permission requested yet.</h4>`
: yo`<h4>Current Permission settings</h4>`
return yo`<form class="${css.permissionForm}" data-id="pluginManagerSettingsPermissionForm">
${title}
<hr/>
${byToPlugin}
</form>`
}
render () {
return yo`
<footer class="bg-light ${css.permissions} remix-bg-opacity">
<button onclick="${() => this.openDialog()}" class="btn btn-primary settings-button" data-id="pluginManagerPermissionsButton">Permissions</button>
</footer>`
}
}

@ -1,13 +1,11 @@
'use strict'
import yo from 'yo-yo'
import async from 'async'
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry'
import { EventEmitter } from 'events'
import { RemixAppManager } from '../../../../../libs/remix-ui/plugin-manager/src/types'
const toaster = require('../ui/tooltip')
import { fileChangedToastMsg } from '@remix-ui/helper'
const helper = require('../../lib/helper.js')
/*
@ -317,7 +315,7 @@ class FileManager extends Plugin {
if (isFile) {
if (newPathExists) {
this.call('modal', 'alert', {
this.call('notification', 'alert', {
id: 'fileManagerAlert',
message: 'File already exists'
})
@ -326,7 +324,7 @@ class FileManager extends Plugin {
return provider.rename(oldPath, newPath, false)
} else {
if (newPathExists) {
this.call('modal', 'alert', {
this.call('notification', 'alert', {
id: 'fileManagerAlert',
message: 'Directory already exists'
})
@ -528,17 +526,7 @@ class FileManager extends Plugin {
const required = this.appManager.isRequired(this.currentRequest.from)
if (canCall && !required) {
// inform the user about modification after permission is granted and even if permission was saved before
toaster(yo`
<div>
<i class="fas fa-exclamation-triangle text-danger mr-1"></i>
<span>
${this.currentRequest.from}
<span class="font-weight-bold text-warning">
is modifying
</span>${path}
</span>
</div>
`, '', { time: 3000 })
this.call('notification','toast', fileChangedToastMsg(this.currentRequest.from, path))
}
}
return await this._setFileInternal(path, content)
@ -778,12 +766,12 @@ class FileManager extends Plugin {
helper.createNonClashingName(file, self._deps.filesProviders[fileProvider],
(error, name) => {
if (error) {
this.call('modal', 'alert', {
this.call('notification', 'alert', {
id: 'fileManagerAlert',
message: 'Unexpected error loading file ' + file + ': ' + error
})
} else if (helper.checkSpecialChars(name)) {
this.call('modal', 'alert', {
this.call('notification', 'alert', {
id: 'fileManagerAlert',
message: 'Special characters are not allowed in file names.'
})

@ -2,8 +2,6 @@
import { CompilerImports } from '@remix-project/core-plugin'
const EventManager = require('events')
const modalDialogCustom = require('../ui/modal-dialog-custom')
const tooltip = require('../ui/tooltip')
const remixLib = require('@remix-project/remix-lib')
const Storage = remixLib.Storage
@ -49,7 +47,7 @@ class FileProvider {
return this.externalFolders.includes(path)
}
discardChanges (path) {
discardChanges (path, toastCb, modalCb) {
this.remove(path)
const compilerImport = new CompilerImports()
this.providerExternalsStorage.keys().map(value => {
@ -57,10 +55,10 @@ class FileProvider {
compilerImport.import(
this.getNormalizedName(value),
true,
(loadingMsg) => { tooltip(loadingMsg) },
(loadingMsg) => { toastCb(loadingMsg) },
(error, content, cleanUrl, type, url) => {
if (error) {
modalDialogCustom.alert(error)
modalCb(error)
} else {
this.addExternal(type + '/' + cleanUrl, content, url)
}

@ -14,9 +14,9 @@ interface IModalApi {
}
const profile:LibraryProfile<IModalApi> = {
name: 'modal',
displayName: 'Modal',
description: 'Modal',
name: 'notification',
displayName: 'Notification',
description: 'Displays notifications',
methods: ['modal', 'alert', 'toast']
}
@ -31,11 +31,11 @@ export class ModalPlugin extends Plugin implements MethodApi<IModalApi> {
}
async modal (args: AppModal) {
this.dispatcher.modal(args)
return this.dispatcher.modal(args)
}
async alert (args: AlertModal) {
this.dispatcher.alert(args)
return this.dispatcher.alert(args)
}
async toast (message: string) {

@ -0,0 +1,126 @@
import React from 'react' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import { AppModal } from 'libs/remix-ui/app/src'
import { PermissionHandlerDialog, PermissionHandlerValue } from 'libs/remix-ui/permission-handler/src'
import { Profile } from '@remixproject/plugin-utils'
const profile = {
name: 'permissionhandler',
displayName: 'permissionhandler',
description: 'permissionhandler',
methods: ['askPermission']
}
export class PermissionHandlerPlugin extends Plugin {
permissions: any
currentVersion: number
constructor() {
super(profile)
this.permissions = this._getFromLocal()
this.currentVersion = 1
// here we remove the old permissions saved before adding 'permissionVersion'
// since with v1 the structure has been changed because of new engine ^0.2.0-alpha.6 changes
if (!localStorage.getItem('permissionVersion')) {
localStorage.setItem('plugins/permissions', '')
localStorage.setItem('permissionVersion', this.currentVersion.toString())
}
}
_getFromLocal() {
const permission = localStorage.getItem('plugins/permissions')
return permission ? JSON.parse(permission) : {}
}
persistPermissions() {
const permissions = JSON.stringify(this.permissions)
localStorage.setItem('plugins/permissions', permissions)
}
switchMode (from: Profile, to: Profile, method: string, set: boolean) {
set
? this.permissions[to.name][method][from.name] = {}
: delete this.permissions[to.name][method][from.name]
}
clear() {
localStorage.removeItem('plugins/permissions')
}
notAllowWarning(from: Profile, to: Profile, method: string) {
return `${from.displayName || from.name} is not allowed to call ${method} method of ${to.displayName || to.name}.`
}
async getTheme() {
return (await this.call('theme', 'currentTheme')).quality
}
/**
* Check if a plugin has the permission to call another plugin and askPermission if needed
* @param {PluginProfile} from the profile of the plugin that make the call
* @param {ModuleProfile} to The profile of the module that receive the call
* @param {string} method The name of the function to be called
* @param {string} message from the caller plugin to add more details if needed
* @returns {Promise<boolean>}
*/
async askPermission(from: Profile, to: Profile, method: string, message: string) {
try {
this.permissions = this._getFromLocal()
if (!this.permissions[to.name]) this.permissions[to.name] = {}
if (!this.permissions[to.name][method]) this.permissions[to.name][method] = {}
if (!this.permissions[to.name][method][from.name]) return this.openPermission(from, to, method, message)
const { allow, hash } = this.permissions[to.name][method][from.name]
if (!allow) {
const warning = this.notAllowWarning(from, to, method)
this.call('notification', 'toast', warning)
return false
}
return hash === from.hash
? true // Allow
: await this.openPermission(from, to, method, message)
} catch (err) {
throw new Error(err)
}
}
async openPermission(from: Profile, to: Profile, method: string, message: string) {
const remember = this.permissions[to.name][method][from.name]
const value: PermissionHandlerValue = {
from,
to,
method,
message,
remember
}
const modal: AppModal = {
id: 'PermissionHandler',
title: `Permission needed for ${to.displayName || to.name}`,
message: <PermissionHandlerDialog plugin={this} theme={await this.getTheme()} value={value}></PermissionHandlerDialog>,
okLabel: 'Accept',
cancelLabel: 'Decline'
}
const result = await this.call('notification', 'modal', modal)
return new Promise((resolve, reject) => {
if (result) {
if (this.permissions[to.name][method][from.name]) {
this.permissions[to.name][method][from.name] = {
allow: true,
hash: from.hash
}
this.persistPermissions()
}
resolve(true)
} else {
if (this.permissions[to.name][method][from.name]) {
this.permissions[to.name][method][from.name] = {
allow: false,
hash: from.hash
}
this.persistPermissions()
}
reject(this.notAllowWarning(from, to, method))
}
})
}
}

@ -30,7 +30,6 @@ enum State {
export class RemixdHandle extends WebsocketPlugin {
localhostProvider: any
appManager: PluginManager
state: State
constructor (localhostProvider, appManager) {
super(profile)
this.localhostProvider = localhostProvider
@ -48,7 +47,8 @@ export class RemixdHandle extends WebsocketPlugin {
}
async activate () {
await this.connectToLocalhost()
this.connectToLocalhost()
return true
}
async canceled () {
@ -69,7 +69,7 @@ export class RemixdHandle extends WebsocketPlugin {
id: 'connectionAlert',
message: 'Cannot connect to the remixd daemon. Please make sure you have the remixd running in the background.'
}
this.call('modal', 'alert', alert)
this.call('notification', 'alert', alert)
this.canceled()
} else {
const intervalId = setInterval(() => {
@ -80,7 +80,7 @@ export class RemixdHandle extends WebsocketPlugin {
id: 'connectionAlert',
message: 'Connection to remixd terminated.Please make sure remixd is still running in the background.'
}
this.call('modal', 'alert', alert)
this.call('notification', 'alert', alert)
this.canceled()
}
}, 3000)
@ -95,13 +95,15 @@ export class RemixdHandle extends WebsocketPlugin {
this.deactivate()
} else if (!isElectron()) {
// warn the user only if he/she is in the browser context
this.state = State.new
const mod:AppModal = {
id: 'remixdConnect',
title: 'Connect to localhost',
message: remixdDialog(),
okFn: () => {
this.state = State.ok
okLabel: 'Connect',
cancelLabel: 'Cancel',
}
const result = await this.call('notification', 'modal', mod)
if(result) {
try {
this.localhostProvider.preInit()
super.activate()
@ -115,18 +117,10 @@ export class RemixdHandle extends WebsocketPlugin {
} catch (error) {
connection(error)
}
},
cancelFn: async () => {
this.state = State.cancel
await this.canceled()
},
okLabel: 'Connect',
cancelLabel: 'Cancel',
hideFn: async () => {
if (this.state === State.new) await this.canceled()
}
else {
await this.canceled()
}
await this.call('modal', 'modal', mod)
} else {
try {
super.activate()

@ -8,11 +8,7 @@ import { ViewPlugin } from '@remixproject/engine-web'
import QueryParams from '../../lib/query-params'
// import { ICompilerApi } from '@remix-project/remix-lib-ts'
import * as packageJson from '../../../../../package.json'
const yo = require('yo-yo')
const addTooltip = require('../ui/tooltip')
const css = require('./styles/compile-tab-styles')
import { compilerConfigChangedToastMsg, compileToastMsg } from '@remix-ui/helper'
const profile = {
name: 'solidity',
@ -41,6 +37,8 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
this.compiler = this.compileTabLogic.compiler
this.compileTabLogic.init()
this.initCompilerApi()
this.el = document.createElement('div')
this.el.setAttribute('id', 'compileTabView')
}
renderComponent () {
@ -70,11 +68,6 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
}
render () {
if (this.el) return this.el
this.el = yo`
<div class="${css.debuggerTabView}" id="compileTabView">
<div id="compiler" class="${css.compiler}"></div>
</div>`
this.renderComponent()
return this.el
@ -101,11 +94,13 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
super.setCompilerConfig(settings)
this.renderComponent()
// @todo(#2875) should use loading compiler return value to check whether the compiler is loaded instead of "setInterval"
addTooltip(yo`<div><b>${this.currentRequest.from}</b> is updating the <b>Solidity compiler configuration</b>.<pre class="text-left">${JSON.stringify(settings, null, '\t')}</pre></div>`)
const value = JSON.stringify(settings, null, '\t')
this.call('notification', 'toast', compilerConfigChangedToastMsg(this.currentRequest.from, value))
}
compile (fileName) {
addTooltip(yo`<div><b>${this.currentRequest.from}</b> is requiring to compile <b>${fileName}</b></div>`)
this.call('notification', 'toast', compileToastMsg(this.currentRequest.from, fileName))
super.compile(fileName)
}

@ -1,14 +1,12 @@
import toaster from '../ui/tooltip'
import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line
import { DebuggerApiMixin } from '@remixproject/debugger-plugin'
import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import modalDialogCustom from '../ui/modal-dialog-custom'
import * as remixBleach from '../../lib/remixBleach'
import { compilationFinishedToastMsg, compilingToastMsg, localCompilationToastMsg, notFoundToastMsg, sourceVerificationNotAvailableToastMsg } from '@remix-ui/helper'
const css = require('./styles/debugger-tab-styles')
const yo = require('yo-yo')
const profile = {
name: 'debugger',
@ -26,46 +24,45 @@ const profile = {
export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
constructor () {
super(profile)
this.el = null
this.el = document.createElement('div')
this.el.setAttribute('id', 'debugView')
this.el.classList.add(css.debuggerTabView)
this.initDebuggerApi()
}
render () {
if (this.el) return this.el
this.el = yo`
<div class="${css.debuggerTabView}" id="debugView">
<div id="debugger" class="${css.debugger}"></div>
</div>`
this.on('fetchAndCompile', 'compiling', (settings) => {
toaster(yo`<div><b>Recompiling and debugging with params</b><pre class="text-left">${JSON.stringify(settings, null, '\t')}</pre></div>`)
settings = JSON.stringify(settings, null, '\t')
this.call('notification', 'toast', compilingToastMsg(settings))
})
this.on('fetchAndCompile', 'compilationFailed', (data) => {
toaster(yo`<div><b>Compilation failed...</b> continuing <i>without</i> source code debugging.</div>`)
this.call('notification', 'toast', compilationFinishedToastMsg())
})
this.on('fetchAndCompile', 'notFound', (contractAddress) => {
toaster(yo`<div><b>Contract ${contractAddress} not found in source code repository</b> continuing <i>without</i> source code debugging.</div>`)
this.call('notification', 'toast', notFoundToastMsg(contractAddress))
})
this.on('fetchAndCompile', 'usingLocalCompilation', (contractAddress) => {
toaster(yo`<div><b>Using compilation result from Solidity module</b></div>`)
this.call('notification', 'toast', localCompilationToastMsg())
})
this.on('fetchAndCompile', 'sourceVerificationNotAvailable', () => {
toaster(yo`<div><b>Source verification plugin not activated or not available.</b> continuing <i>without</i> source code debugging.</div>`)
this.call('notification', 'toast', sourceVerificationNotAvailableToastMsg())
})
this.renderComponent()
return this.el
}
showMessage (title, message) {
try {
modalDialogCustom.alert(title, remixBleach.sanitize(message))
this.call('notification', 'alert', {
id: 'debuggerTabShowMessage',
title,
message: remixBleach.sanitize(message)
})
} catch (e) {
console.log(e)
}

@ -66,7 +66,7 @@ export class HardhatProvider extends Plugin {
value = await ((): Promise<string> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
id: 'harrhatprovider',
id: 'hardhatprovider',
title: 'Hardhat node request',
message: this.hardhatProviderDialogBody(),
modalType: ModalTypes.prompt,
@ -83,7 +83,7 @@ export class HardhatProvider extends Plugin {
},
defaultValue: 'http://127.0.0.1:8545'
}
this.call('modal', 'modal', modalContent)
this.call('notification', 'modal', modalContent)
})
})()
} catch (e) {
@ -110,11 +110,11 @@ export class HardhatProvider extends Plugin {
} catch (error) {
this.blocked = true
const modalContent: AlertModal = {
id: 'harrhatprovider',
id: 'hardhatprovider',
title: 'Hardhat Provider',
message: `Error while connecting to the hardhat provider: ${error.message}`,
}
this.call('modal', 'alert', modalContent)
this.call('notification', '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

@ -1,114 +0,0 @@
var modal = require('./modaldialog.js')
var yo = require('yo-yo')
var css = require('./styles/modal-dialog-custom-styles')
module.exports = {
alert: function (title, text) {
if (text) return modal(title, yo`<div>${text}</div>`, null, { label: null })
return modal('Alert', yo`<div>${title}</div>`, null, { label: null })
},
prompt: function (title, text, inputValue, ok, cancel, focus) {
return prompt(title, text, false, inputValue, ok, cancel, focus)
},
promptPassphrase: function (title, text, inputValue, ok, cancel) {
return prompt(title, text, true, inputValue, ok, cancel)
},
promptPassphraseCreation: function (ok, cancel) {
var text = 'Please provide a Passphrase for the account creation'
var input = yo`
<div>
<input id="prompt1" type="password" name='prompt_text' class="${css.prompt_text}" oninput="${(e) => validateInput(e)}">
<br>
<br>
<input id="prompt2" type="password" name='prompt_text' class="${css.prompt_text}" oninput="${(e) => validateInput(e)}">
</div>
`
return modal(null, yo`<div>${text}<div>${input}</div></div>`,
{
fn: () => {
if (typeof ok === 'function') {
if (input.querySelector('#prompt1').value === input.querySelector('#prompt2').value) {
ok(null, input.querySelector('#prompt1').value)
} else {
ok('Passphase does not match')
}
}
}
},
{
fn: () => {
if (typeof cancel === 'function') cancel()
}
}
)
},
promptMulti: function ({ title, text, inputValue }, ok, cancel) {
if (!inputValue) inputValue = ''
const input = yo`
<textarea
id="prompt_text"
data-id="modalDialogCustomPromptText"
class=${css.prompt_text}
rows="4"
cols="50"
oninput="${(e) => validateInput(e)}"
></textarea>
`
return modal(title, yo`<div>${text}<div>${input}</div></div>`,
{
fn: () => { if (typeof ok === 'function') ok(document.getElementById('prompt_text').value) }
},
{
fn: () => { if (typeof cancel === 'function') cancel() }
}
)
},
confirm: function (title, text, ok, cancel) {
return modal(title, yo`<div>${text}</div>`,
{
fn: () => { if (typeof ok === 'function') ok() }
},
{
fn: () => { if (typeof cancel === 'function') cancel() }
}
)
}
}
const validateInput = (e) => {
if (!document.getElementById('modal-footer-ok')) return
if (e.target.value === '') {
document.getElementById('modal-footer-ok').classList.add('disabled')
document.getElementById('modal-footer-ok').style.pointerEvents = 'none'
} else {
document.getElementById('modal-footer-ok').classList.remove('disabled')
document.getElementById('modal-footer-ok').style.pointerEvents = 'auto'
}
}
function prompt (title, text, hidden, inputValue, ok, cancel, focus) {
if (!inputValue) inputValue = ''
var type = hidden ? 'password' : 'text'
var input = yo`
<input
type=${type}
name='prompt_text'
id='prompt_text'
class="${css.prompt_text} form-control"
value='${inputValue}'
data-id="modalDialogCustomPromptText"
oninput="${(e) => validateInput(e)}"
>
`
modal(title, yo`<div>${text}<div>${input}</div></div>`,
{
fn: () => { if (typeof ok === 'function') ok(document.getElementById('prompt_text').value) }
},
{
fn: () => { if (typeof cancel === 'function') cancel() }
},
focus ? '#prompt_text' : undefined
)
}

@ -1,150 +0,0 @@
var yo = require('yo-yo')
var css = require('./styles/modaldialog-styles')
let incomingModal = false // in case modals are queued, ensure we are not hiding the last one.
module.exports = (title, content, ok, cancel, focusSelector, opts) => {
let agreed = true
let footerIsActive = false
opts = opts || {}
var container = document.getElementById('modal-dialog')
if (!container) {
document.querySelector('body').appendChild(html(opts))
container = document.getElementById('modal-dialog')
incomingModal = false
} else incomingModal = true
var closeDiv = document.getElementById('modal-close')
if (opts.hideClose) closeDiv.style.display = 'none'
var okDiv = document.getElementById('modal-footer-ok')
okDiv.innerHTML = (ok && ok.label !== undefined) ? ok.label : 'OK'
okDiv.style.display = okDiv.innerHTML === '' ? 'none' : 'inline-block'
var cancelDiv = document.getElementById('modal-footer-cancel')
cancelDiv.innerHTML = (cancel && cancel.label !== undefined) ? cancel.label : 'Cancel'
cancelDiv.style.display = cancelDiv.innerHTML === '' ? 'none' : 'inline-block'
var modal = document.getElementById('modal-body-id')
var modalTitle = document.getElementById('modal-title-h6')
modalTitle.innerHTML = ''
if (title) modalTitle.innerText = title
modal.innerHTML = ''
if (content) modal.appendChild(content)
setFocusOn('ok')
show()
function setFocusOn (btn) {
var okDiv = document.getElementById('modal-footer-ok')
var cancelDiv = document.getElementById('modal-footer-cancel')
if (btn === 'ok') {
okDiv.className = okDiv.className.replace(/\bbtn-light\b/g, 'btn-dark')
cancelDiv.className = cancelDiv.className.replace(/\bbtn-dark\b/g, 'btn-light')
} else {
cancelDiv.className = cancelDiv.className.replace(/\bbtn-light\b/g, 'btn-dark')
okDiv.className = okDiv.className.replace(/\bbtn-dark\b/g, 'btn-light')
}
}
function okListener () {
removeEventListener()
if (ok && ok.fn && agreed) ok.fn()
if (!incomingModal) hide()
incomingModal = false
}
function cancelListener () {
removeEventListener()
if (cancel && cancel.fn) cancel.fn()
if (!incomingModal) hide()
incomingModal = false
}
function modalKeyEvent (e) {
if (e.keyCode === 27) { // Esc
cancelListener()
} else if (e.keyCode === 13) { // Enter
e.preventDefault()
okListener()
} else if (e.keyCode === 37 && footerIsActive) { // Arrow Left
e.preventDefault()
agreed = true
setFocusOn('ok')
} else if (e.keyCode === 39 && footerIsActive) { // Arrow Right
e.preventDefault()
agreed = false
setFocusOn('cancel')
}
}
function hide () {
if (!container) return
container.style.display = 'none'
if (container.parentElement) container.parentElement.removeChild(container)
container = null
incomingModal = false
}
function show () {
if (!container) return
container.style.display = 'block'
if (focusSelector) {
const focusTarget = document.querySelector(`.modal ${focusSelector}`)
if (focusTarget) {
focusTarget.focus()
if (typeof focusTarget.setSelectionRange === 'function') {
focusTarget.setSelectionRange(0, focusTarget.value.length)
}
}
}
}
function removeEventListener () {
okDiv.removeEventListener('click', okListener)
cancelDiv.removeEventListener('click', cancelListener)
closeDiv.removeEventListener('click', cancelListener)
document.removeEventListener('keydown', modalKeyEvent)
if (document.getElementById('modal-background')) {
document.getElementById('modal-background').removeEventListener('click', cancelListener)
}
}
okDiv.addEventListener('click', okListener)
cancelDiv.addEventListener('click', cancelListener)
closeDiv.addEventListener('click', cancelListener)
document.addEventListener('keydown', modalKeyEvent)
const modalDialog = document.getElementById('modal-dialog')
if (modalDialog) {
modalDialog.addEventListener('click', (e) => {
footerIsActive = document.activeElement === modalDialog
if (e.toElement === modalDialog) {
cancelListener() // click is outside of modal-content
}
})
}
return { container, okListener, cancelListener, hide }
}
function html (opts) {
return yo`
<div id="modal-dialog" data-id="modalDialogContainer" data-backdrop="static" data-keyboard="false" class="modal" tabindex="-1" role="dialog">
<div id="modal-background" class="modal-dialog" role="document">
<div class="modal-content ${css.modalContent} ${opts.class}">
<div class="modal-header">
<h6 id="modal-title-h6" class="modal-title" data-id="modalDialogModalTitle"></h6>
<span class="modal-close">
<i id="modal-close" title="Close" class="fas fa-times" aria-hidden="true"></i>
</span>
</div>
<div id="modal-body-id" class="modal-body ${css.modalBody}" data-id="modalDialogModalBody"> - </div>
<div class="modal-footer" data-id="modalDialogModalFooter" autofocus>
<span id="modal-footer-ok" class="${css.modalFooterOk} modal-ok btn btn-sm btn-light" tabindex='5'>OK</span>
<span id="modal-footer-cancel" class="${css.modalFooterCancel} modal-cancel btn btn-sm btn-light" tabindex='10' data-dismiss="modal">Cancel</span>
</div>
</div>
</div>
</div>`
}

@ -1,202 +0,0 @@
import Registry from '../state/registry'
/* global localStorage */
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const addTooltip = require('./tooltip')
const modalDialog = require('./modaldialog')
const css = csjs`
.permission h4 {
text-transform: uppercase;
text-align: center;
}
.permission h6 {
text-transform: uppercase;
}
.remember {
display: flex;
justify-content: space-between;
align-items: center;
}
.images {
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
}
.images img {
width: 40px;
height: 40px;
}
.images i {
margin: 0 20px;
}
`
function notAllowWarning (from, to, method) {
return `${from.displayName || from.name} is not allowed to call ${method} method of ${to.displayName || to.name}.`
}
export class PermissionHandler {
constructor () {
this.permissions = this._getFromLocal()
this.currentVersion = 1
// here we remove the old permissions saved before adding 'permissionVersion'
// since with v1 the structure has been changed because of new engine ^0.2.0-alpha.6 changes
if (!localStorage.getItem('permissionVersion')) {
localStorage.setItem('plugins/permissions', '')
localStorage.setItem('permissionVersion', this.currentVersion)
}
}
_getFromLocal () {
const permission = localStorage.getItem('plugins/permissions')
return permission ? JSON.parse(permission) : {}
}
persistPermissions () {
const permissions = JSON.stringify(this.permissions)
localStorage.setItem('plugins/permissions', permissions)
}
clear () {
localStorage.removeItem('plugins/permissions')
addTooltip('All Permissions have been reset')
}
/**
* Show a message to ask the user for a permission
* @param {PluginProfile} from The name and hash of the plugin that make the call
* @param {ModuleProfile} to The name of the plugin that receive the call
* @param {string} method The name of the function to be called
* @param {string} message from the caller plugin to add more details if needed
* @returns {Promise<{ allow: boolean; remember: boolean }} Answer from the user to the permission
*/
async openPermission (from, to, method, message) {
return new Promise((resolve, reject) => {
modalDialog(
`Permission needed for ${to.displayName || to.name}`,
this.form(from, to, method, message),
{
label: 'Accept',
fn: () => {
if (this.permissions[to.name][method][from.name]) {
this.permissions[to.name][method][from.name] = {
allow: true,
hash: from.hash
}
this.persistPermissions()
}
resolve(true)
}
},
{
label: 'Decline',
fn: () => {
if (this.permissions[to.name][method][from.name]) {
this.permissions[to.name][method][from.name] = {
allow: false,
hash: from.hash
}
this.persistPermissions()
}
reject(notAllowWarning(from, to, method))
}
}
)
})
}
/**
* Check if a plugin has the permission to call another plugin and askPermission if needed
* @param {PluginProfile} from the profile of the plugin that make the call
* @param {ModuleProfile} to The profile of the module that receive the call
* @param {string} method The name of the function to be called
* @param {string} message from the caller plugin to add more details if needed
* @returns {Promise<boolean>}
*/
async askPermission (from, to, method, message) {
try {
this.permissions = this._getFromLocal()
if (!this.permissions[to.name]) this.permissions[to.name] = {}
if (!this.permissions[to.name][method]) this.permissions[to.name][method] = {}
if (!this.permissions[to.name][method][from.name]) return this.openPermission(from, to, method, message)
const { allow, hash } = this.permissions[to.name][method][from.name]
if (!allow) {
const warning = notAllowWarning(from, to, method)
addTooltip(warning)
return false
}
return hash === from.hash
? true // Allow
: this.openPermission(from, to, method, message) // New version of a plugin
} catch (err) {
throw new Error(err)
}
}
/**
* The permission form
* @param {PluginProfile} from The name and hash of the plugin that make the call
* @param {ModuleProfile} to The name of the plugin that receive the call
* @param {string} method The name of te methode to be called
* @param {string} message from the caller plugin to add more details if needed
*/
form (from, to, method, message) {
const fromName = from.displayName || from.name
const toName = to.displayName || to.name
const remember = this.permissions[to.name][method][from.name]
const switchMode = (e) => {
e.target.checked
? this.permissions[to.name][method][from.name] = {}
: delete this.permissions[to.name][method][from.name]
}
const rememberSwitch = remember
? yo`<input type="checkbox" onchange="${switchMode}" checkbox class="form-check-input" id="remember" data-id="permissionHandlerRememberChecked">`
: yo`<input type="checkbox" onchange="${switchMode}" class="form-check-input" id="remember" data-id="permissionHandlerRememberUnchecked">`
const text = `"${fromName}" ${(remember ? 'has changed and' : '')} would like to access to "${method}" of "${toName}"`
const imgFrom = yo`<img id="permissionModalImagesFrom" src="${from.icon}" />`
const imgTo = yo`<img id="permissionModalImagesTo" src="${to.icon}" />`
const pluginsImages = yo`
<article class="${css.images}">
${imgFrom}
<i class="fas fa-arrow-right"></i>
${imgTo}
</article>
`
Registry.getInstance().get('themeModule').api.fixInvert(imgFrom)
Registry.getInstance().get('themeModule').api.fixInvert(imgTo)
const pluginMessage = message ? yo`
<div>
<h6>Description</h6>
<p>${message}</p>
</div>
` : ''
return yo`
<section class="${css.permission}">
${pluginsImages}
<article>
<h4 data-id="permissionHandlerMessage">${text} :</h4>
<h6>${fromName}</h6>
<p>${from.description || yo`<i>No description Provided</i>`}</p>
<h6>${toName} :</p>
<p>${to.description || yo`<i>No description Provided</i>`}</p>
${pluginMessage}
</article>
<article class="${css.remember}">
<div class="form-check">
${rememberSwitch}
<label class="form-check-label" for="remember" data-id="permissionHandlerRememberChoice">Remember this choice</label>
</div>
<button class="btn btn-sm" onclick="${_ => this.clear()}">Reset all Permissions</button>
</article>
</section>
`
}
}

@ -1,110 +0,0 @@
/* global Element */
var yo = require('yo-yo')
var css = require('./styles/tooltip-styles')
var modal = require('./modal-dialog-custom')
/**
* Open a tooltip
* @param {string} tooltipText The text shown by the tooltip
* @param {function} [action] Returns An HTMLElement to display for action
*/
module.exports = function addTooltip (tooltipText, action, opts) {
action = action || function () { return yo`<div></div>` }
const t = new Toaster()
return t.render(tooltipText, action(t), opts)
}
class Toaster {
hide () {
if (this.id) clearTimeout(this.id)
setTimeout(() => {
// remove from body after the animation is finished
if (this.tooltip.parentElement) this.tooltip.parentElement.removeChild(this.tooltip)
}, 2000)
animation(this.tooltip, css.animateTop.className)
}
render (tooltipText, actionElement, opts) {
opts = defaultOptions(opts)
let canShorten = true
if (tooltipText instanceof Element) {
canShorten = false
} else {
if (typeof tooltipText === 'object') {
if (tooltipText.message) {
tooltipText = tooltipText.message
} else {
try {
tooltipText = JSON.stringify(tooltipText)
} catch (e) {
}
}
}
}
return new Promise((resolve, reject) => {
const shortTooltipText = (canShorten && tooltipText.length > 201) ? tooltipText.substring(0, 200) + '...' : tooltipText
this.resolveFn = resolve
function showFullMessage () {
modal.alert(tooltipText)
}
function closeTheToaster (self) {
self.hide()
over()
resolve()
}
const button = tooltipText.length > 201 ? yo`
<button class="btn btn-secondary btn-sm mx-3" style="white-space: nowrap;" onclick=${() => showFullMessage()}>Show full message</button>
` : ''
this.tooltip = yo`
<div data-shared="tooltipPopup" class="${css.tooltip} alert alert-info p-2" onmouseenter=${() => { over() }} onmouseleave=${() => { out() }}>
<span class="px-2">
${shortTooltipText}
${button}
${actionElement}
</span>
<span style="align-self: baseline;">
<button data-id="tooltipCloseButton" class="fas fa-times btn-info mx-1 p-0" onclick=${() => closeTheToaster(this)}></button>
</span>
</div>`
const timeOut = () => {
return setTimeout(() => {
if (this.id) {
this.hide()
resolve()
}
}, opts.time)
}
const over = () => {
if (this.id) {
clearTimeout(this.id)
this.id = null
}
}
const out = () => {
if (!this.id) this.id = timeOut()
}
this.id = timeOut()
document.body.appendChild(this.tooltip)
animation(this.tooltip, css.animateBottom.className)
})
}
}
const defaultOptions = (opts) => {
opts = opts || {}
return {
time: opts.time || 7000
}
}
const animation = (tooltip, anim) => {
tooltip.classList.remove(css.animateTop.className)
tooltip.classList.remove(css.animateBottom.className)
// eslint-disable-next-line
void tooltip.offsetWidth // trick for restarting the animation
tooltip.classList.add(anim)
}

@ -2,19 +2,18 @@
import { PluginManager } from '@remixproject/engine'
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', 'gistHandler', 'layout', 'modal', 'walkthrough']
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout', 'notification', 'permissionhandler', 'walkthrough']
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', 'layout', 'modal']
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification']
return nativePlugins.includes(name) || requiredModules.includes(name)
}
@ -40,7 +39,6 @@ export class RemixAppManager extends PluginManager {
this.event = new EventEmitter()
this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json'
this.pluginLoader = new PluginLoader()
this.permissionHandler = new PermissionHandler()
}
async canActivatePlugin (from, to) {
@ -72,7 +70,7 @@ export class RemixAppManager extends PluginManager {
return true
}
// ask the user for permission
return await this.permissionHandler.askPermission(this.profiles[from], this.profiles[to], method, message)
return await this.call('permissionhandler', 'askPermission', this.profiles[from], this.profiles[to], method, message)
}
onPluginActivated (plugin) {

@ -45,7 +45,7 @@ export class GistHandler extends Plugin {
setTimeout(() => reject(new Error('Hide')), 0)
}
}
this.call('modal', 'modal', modalContent)
this.call('notification', 'modal', modalContent)
})
})()
} catch (e) {
@ -63,7 +63,7 @@ export class GistHandler extends Plugin {
title: 'Gist load error',
message: 'Error while loading gist. Please provide a valid Gist ID or URL.'
}
this.call('modal', 'alert', modalContent)
this.call('notification', 'alert', modalContent)
}
} else {
const modalContent = {
@ -71,7 +71,7 @@ export class GistHandler extends Plugin {
title: 'Gist load error',
message: 'Error while loading gist. Id cannot be empty.'
}
this.call('modal', 'alert', modalContent)
this.call('notification', 'alert', modalContent)
}
return loadingFromGist
} else {
@ -97,7 +97,7 @@ export class GistHandler extends Plugin {
modalType: 'alert',
okLabel: 'OK'
}
await this.call('modal', 'modal', modalContent)
await this.call('notification', 'modal', modalContent)
return
}
} catch (e: any) {
@ -107,7 +107,7 @@ export class GistHandler extends Plugin {
message: e.message
}
await this.call('modal', 'alert', modalContent)
await this.call('notification', 'alert', modalContent)
return
}
@ -124,7 +124,7 @@ export class GistHandler extends Plugin {
message: errorSavingFiles.message || errorSavingFiles
}
this.call('modal', 'alert', modalContent)
this.call('notification', 'alert', modalContent)
}
})
})

@ -47,7 +47,7 @@
"axios": ">=0.21.1",
"change-case": "^3.0.1",
"color-support": "^1.1.3",
"colors": "^1.1.2",
"colors": "1.4.0",
"commander": "^2.13.0",
"deep-equal": "^1.0.1",
"ethereumjs-util": "^7.0.10",

@ -21,7 +21,7 @@ export const enum modalActionTypes {
type ModalPayload = {
[modalActionTypes.setModal]: AppModal
[modalActionTypes.handleHideModal]: any
[modalActionTypes.setToast]: string
[modalActionTypes.setToast]: string | JSX.Element
[modalActionTypes.handleToaster]: any
}

@ -14,13 +14,21 @@ const ModalWrapper = (props: ModalWrapperProps) => {
const onFinishPrompt = async () => {
if (ref.current === undefined) {
props.okFn()
onOkFn()
} else {
// @ts-ignore: Object is possibly 'null'.
props.okFn(ref.current.value)
(props.okFn) ? props.okFn(ref.current.value) : props.resolve(ref.current.value)
}
}
const onOkFn = async () => {
(props.okFn) ? props.okFn() : props.resolve(true)
}
const onCancelFn = async () => {
(props.cancelFn) ? props.cancelFn() : props.resolve(false)
}
const createModalMessage = (defaultValue: string) => {
return (
<>
@ -37,15 +45,24 @@ const ModalWrapper = (props: ModalWrapperProps) => {
setState({
...props,
okFn: onFinishPrompt,
cancelFn: onCancelFn,
message: createModalMessage(props.defaultValue)
})
break
default:
setState({ ...props })
setState({
...props,
okFn: (onOkFn),
cancelFn: onCancelFn
})
break
}
} else {
setState({ ...props })
setState({
...props,
okFn: onOkFn,
cancelFn: onCancelFn
})
}
}, [props])

@ -11,14 +11,16 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
const modal = (data: AppModal) => {
const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn } = data
return new Promise((resolve, reject) => {
dispatch({
type: modalActionTypes.setModal,
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn }
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve }
})
})
}
const alert = (data: AlertModal) => {
modal({ id: data.id, title: data.title || 'Alert', message: data.message || data.title, okLabel: 'OK', okFn: (value?:any) => {}, cancelLabel: '', cancelFn: () => {} })
return modal({ id: data.id, title: data.title || 'Alert', message: data.message || data.title, okLabel: 'OK', okFn: (value?:any) => {}, cancelLabel: '', cancelFn: () => {} })
}
const handleHideModal = () => {
@ -28,7 +30,7 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
})
}
const toast = (message: string) => {
const toast = (message: string | JSX.Element) => {
dispatch({
type: modalActionTypes.setToast,
payload: message

@ -7,12 +7,13 @@ export interface AppModal {
// eslint-disable-next-line no-undef
message: string | JSX.Element
okLabel: string
okFn: (value?:any) => void
okFn?: (value?:any) => void
cancelLabel: string
cancelFn: () => void,
cancelFn?: () => void,
modalType?: ModalTypes,
defaultValue?: string
hideFn?: () => void
hideFn?: () => void,
resolve?: (value?:any) => void
}
export interface AlertModal {
@ -23,7 +24,7 @@ export interface AlertModal {
export interface ModalState {
modals: AppModal[],
toasters: string[],
toasters: (string | JSX.Element)[],
focusModal: AppModal,
focusToaster: string
focusToaster: string | JSX.Element
}

@ -19,7 +19,8 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
cancelFn: modalList[0].cancelFn,
modalType: modalList[0].modalType,
defaultValue: modalList[0].defaultValue,
hideFn: modalList[0].hideFn
hideFn: modalList[0].hideFn,
resolve: modalList[0].resolve
}
modalList = modalList.slice()
@ -31,6 +32,8 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
case modalActionTypes.handleHideModal:
if (state.focusModal.hideFn) {
state.focusModal.hideFn()
} else if (state.focusModal.resolve) {
state.focusModal.resolve(undefined)
}
state.focusModal = { ...state.focusModal, hide: true, message: null }
return { ...state }

@ -6,20 +6,25 @@ import { Placement } from 'react-bootstrap/esm/Overlay'
import './copy-to-clipboard.css'
interface ICopyToClipboard {
content: any,
content?: any,
tip?: string,
icon?: string,
direction?: Placement,
className?: string,
title?: string,
children?: JSX.Element
children?: JSX.Element,
getContent?: () => {}
}
export const CopyToClipboard = (props: ICopyToClipboard) => {
let { content, tip = 'Copy', icon = 'fa-copy', direction = 'right', children, ...otherProps } = props
let { content, tip = 'Copy', icon = 'fa-copy', direction = 'right', children, getContent, ...otherProps } = props
const [message, setMessage] = useState(tip)
const handleClick = (e) => {
if (content && content !== '') { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory
const copyData = () => {
try {
if (content === '') {
setMessage('Cannot copy empty content!')
return
}
if (typeof content !== 'string') {
content = JSON.stringify(content, null, '\t')
}
@ -28,11 +33,16 @@ export const CopyToClipboard = (props: ICopyToClipboard) => {
} catch (e) {
console.error(e)
}
}
const handleClick = (e) => {
if (content) { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory
copyData()
} else {
setMessage('Cannot copy empty content!')
content = getContent && getContent()
copyData()
}
e.preventDefault()
return false
}
const reset = () => {

@ -1 +1,2 @@
export * from './lib/remix-ui-helper'
export * from './lib/helper-components'

@ -0,0 +1,55 @@
import React from 'react'
export const fileChangedToastMsg = (from: string, path: string) => (
<div><i className="fas fa-exclamation-triangle text-danger mr-1"></i>
<span>
{from}
<span className="font-weight-bold text-warning">
is modifying
</span>{path}
</span>
</div>
)
export const compilerConfigChangedToastMsg = (from: string, value: string) => (
<div>
<b>{ from }</b> is updating the <b>Solidity compiler configuration</b>.
<pre className="text-left">{value}</pre>
</div>
)
export const compileToastMsg = (from: string, fileName: string) => (
<div>
<b>{from}</b> is requiring to compile <b>{fileName}</b>
</div>
)
export const compilingToastMsg = (settings: string) => (
<div>
<b>Recompiling and debugging with params</b>
<pre className="text-left">{settings}</pre></div>
)
export const compilationFinishedToastMsg = () => (
<div>
<b>Compilation failed...</b> continuing <i>without</i> source code debugging.
</div>
)
export const notFoundToastMsg = (address: string) => (
<div>
<b>Contract {address} not found in source code repository</b> continuing <i>without</i> source code debugging.
</div>
)
export const localCompilationToastMsg = () => (
<div>
<b>Using compilation result from Solidity module</b>
</div>
)
export const sourceVerificationNotAvailableToastMsg = () => (
<div>
<b>Source verification plugin not activated or not available.</b> continuing <i>without</i> source code debugging.
</div>
)

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

@ -11,5 +11,6 @@ export interface ModalDialogProps {
showCancelIcon?: boolean,
hide?: boolean,
handleHide: (hideState?: boolean) => void,
children?: React.ReactNode
children?: React.ReactNode,
resolve?: (value?:any) => void,
}

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

@ -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": {}
}
]
}

@ -0,0 +1,2 @@
export { default as PermissionHandlerDialog } from './lib/permission-dialog'
export { PermissionHandlerValue, PermissionHandlerProps } from './interface/index'

@ -0,0 +1,15 @@
import { Profile } from '@remixproject/plugin-utils'
export interface PermissionHandlerValue {
from: Profile,
to: Profile,
remember: boolean,
method: string,
message: string
}
export interface PermissionHandlerProps {
value: PermissionHandlerValue
theme: string
plugin: any
}

@ -0,0 +1,29 @@
.permission h4 {
text-transform: uppercase;
text-align: center;
}
.permission h6 {
text-transform: uppercase;
}
.remember {
display: flex;
justify-content: space-between;
align-items: center;
}
.images {
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
}
.images img {
width: 40px;
height: 40px;
}
.images i {
margin: 0 20px;
}
.invert {
filter: invert(1);
}

@ -0,0 +1,67 @@
import React, { ChangeEventHandler, useContext, useEffect, useRef, useState } from 'react' // eslint-disable-line
import { PermissionHandlerProps } from '../interface'
import './permission-dialog.css'
const PermissionHandlerDialog = (props: PermissionHandlerProps) => {
const { from, to, remember, method, message } = props.value
const [feedback, setFeedback] = useState<string>('')
const theme = props.theme
const switchMode = (e: any) => {
props.plugin.switchMode(from, to, method, e.target.checked)
}
const rememberSwitch = () => {
return <input type="checkbox" onChange={switchMode} className='form-check-input' id='remember' data-id={remember ? 'permissionHandlerRememberChecked' : 'permissionHandlerRememberUnchecked'}/>
}
const reset = () => {
props.plugin.clear()
setFeedback('All permisssions have been reset.')
}
const imgFrom = () => { return <img className={`${theme === 'dark' ? 'invert' : ''}`} alt='' id='permissionModalImagesFrom' src={from.icon} /> }
const imgTo = () => { return <img className={`${theme === 'dark' ? 'invert' : ''}`} alt='' id='permissionModalImagesTo' src={to.icon} /> }
const pluginsImages = () => {
return (
<article className='images'>
{imgFrom()}
<i className="fas fa-arrow-right"></i>
{imgTo()}
</article>
)
}
const text = () => {
return <>"{from.displayName}" {(remember ? 'has changed and' : '')} would like to access to "{method}" of "{to.displayName}"`</>
}
const pluginMessage = () => {
return message
? <div>
<h6>Description</h6>
<p>{message}</p>
</div> : null
}
return (<section className="permission">
{pluginsImages()}
<article>
<h4 data-id="permissionHandlerMessage">{text()} :</h4>
<h6>{from.displayName}</h6>
<p> {from.description || <i>No description Provided</i>}</p>
<h6>{to.displayName} :</h6>
<p> {to.description || <i>No description Provided</i>}</p>
{pluginMessage()}
</article>
<article className='remember'>
<div className='form-check'>
{rememberSwitch()}
<label className="form-check-label" data-id="permissionHandlerRememberChoice">Remember this choice</label>
</div>
<button className="btn btn-sm" onClick={reset}>Reset all Permissions</button>
</article>
<div>{feedback}</div>
</section>)
}
export default PermissionHandlerDialog

@ -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"
}
]
}

@ -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"]
}

@ -5,11 +5,7 @@ import { ModalDialog } from '@remix-ui/modal-dialog'
import useLocalStorage from '../custom-hooks/useLocalStorage'
import { PluginPermissions } from '../../types'
interface PermissionSettingsProps {
pluginSettings: any
}
function PermisssionsSettings ({ pluginSettings }: PermissionSettingsProps) {
function PermisssionsSettings () {
const [modalVisibility, setModalVisibility] = useState<boolean>(true)
const [permissions, setPermissions] = useLocalStorage<PluginPermissions>('plugins/permissions', {} as PluginPermissions)
const [permissionCache, setpermissionCache] = useState<PluginPermissions>()

@ -7,7 +7,6 @@ import LocalPluginForm from './LocalPluginForm'
interface RootViewProps {
pluginComponent: PluginManagerComponent
pluginManagerSettings: PluginManagerSettings
children: ReactNode
}
@ -21,7 +20,7 @@ export interface pluginActivated {
profile: Profile
}
function RootView ({ pluginComponent, pluginManagerSettings, children }: RootViewProps) {
function RootView ({ pluginComponent, children }: RootViewProps) {
const [visible, setVisible] = useState<boolean>(true)
const [filterPlugins, setFilterPlugin] = useState<string>('')
@ -52,7 +51,7 @@ function RootView ({ pluginComponent, pluginManagerSettings, children }: RootVie
</button>
</header>
{children}
<PermisssionsSettings pluginSettings={pluginManagerSettings}/>
<PermisssionsSettings />
</div>
<LocalPluginForm
closeModal={closeModal}

@ -7,11 +7,11 @@ import InactivePluginCardContainer from './components/InactivePluginCardContaine
import RootView from './components/rootView'
import './remix-ui-plugin-manager.css'
export const RemixUiPluginManager = ({ pluginComponent, pluginManagerSettings }: RemixUiPluginManagerProps) => {
export const RemixUiPluginManager = ({ pluginComponent }: RemixUiPluginManagerProps) => {
const [activeProfiles, setActiveProfiles] = useState<Profile[]>(pluginComponent.activePlugins)
const [inactiveProfiles, setinactiveProfiles] = useState<Profile[]>(pluginComponent.inactivePlugins)
return (
<RootView pluginComponent={pluginComponent} pluginManagerSettings={pluginManagerSettings}>
<RootView pluginComponent={pluginComponent}>
<section data-id="pluginManagerComponentPluginManagerSection">
<ActivePluginCardContainer
pluginComponent={pluginComponent}

@ -3,7 +3,7 @@ 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 { 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 { RunTab } from '../types/run-tab'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import * as remixLib from '@remix-project/remix-lib'
@ -23,6 +23,7 @@ let plugin: RunTab, dispatch: React.Dispatch<any>
export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispatch<any>) => {
plugin = udapp
dispatch = reducerDispatch
resetAndInit()
setupEvents()
setInterval(() => {
fillAccountsList()
@ -30,29 +31,6 @@ export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispa
}
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)
})
@ -103,6 +81,11 @@ const setupEvents = () => {
setExecutionContext(env, plugin.REACT_API.web3Dialog())
})
plugin.on('filePanel', 'setWorkspace', () => {
dispatch(resetUdapp())
resetAndInit()
})
plugin.fileManager.events.on('currentFileChanged', (currentFile: string) => {
if (/.(.abi)$/.exec(currentFile)) {
dispatch(setLoadType('abi'))
@ -127,7 +110,7 @@ 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)
dispatch(setWeb3Dialog(web3Dialog))
}
const updateAccountBalances = () => {
@ -254,7 +237,7 @@ export const setExecutionContext = (executionContext: { context: string, fork: s
}, () => { setFinalContext() }))
}, (alertMsg) => {
dispatch(displayPopUp(alertMsg))
}, setFinalContext())
}, () => { setFinalContext() })
}
export const setWeb3Endpoint = (endpoint: string) => {
@ -718,3 +701,28 @@ export const getFuncABIInputs = (funcABI: FuncABI) => {
export const setSendTransactionValue = (value: string) => {
dispatch(setSendValue(value))
}
const resetAndInit = () => {
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)
}
}
})
}

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

@ -15,28 +15,8 @@ export function ContractGUI (props: ContractGUIProps) {
classList: string,
dataId: string
}>({ title: '', content: '', classList: '', dataId: '' })
const [clipboardContent, setClipboardContent] = useState<string>('')
const multiFields = useRef<Array<HTMLInputElement | null>>([])
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)
@ -75,6 +55,28 @@ export function ContractGUI (props: ContractGUIProps) {
}
}, [props.lookupOnly, props.funcABI, title])
const getContentOnCTC = () => {
const multiString = getMultiValsString()
// copy-to-clipboard icon is only visible for method requiring input params
if (!multiString) {
return 'cannot encode empty arguments'
}
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)
return encodeObj.error
} else {
return encodeObj.data
}
}
const switchMethodViewOn = () => {
setToggleContainer(true)
makeMultiVal()
@ -187,7 +189,7 @@ export function ContractGUI (props: ContractGUIProps) {
})}
</div>
<div className="udapp_group udapp_multiArg">
<CopyToClipboard content={clipboardContent} tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'left'} />
<CopyToClipboard tip='Encode values of input fields & copy to clipboard' icon='fa-clipboard' direction={'left'} getContent={getContentOnCTC} />
<button onClick={handleExpandMultiClick} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button>
</div>
</div>

@ -171,6 +171,11 @@ export const runTabInitialState: RunTabState = {
web3Dialog: null
}
type AddProvider = {
name: string,
provider: any
}
export const runTabReducer = (state: RunTabState = runTabInitialState, action: Action) => {
switch (action.type) {
case 'FETCH_ACCOUNTS_LIST_REQUEST': {
@ -328,25 +333,32 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
case 'ADD_PROVIDER': {
const payload: string = action.payload
const payload: AddProvider = action.payload
const id = action.payload.name
state.providers.providerList.push({
content: payload.name,
dataId: id,
id,
title: payload.name,
value: id
})
return {
...state,
providers: {
...state.providers,
providerList: { ...state.providers.providerList, payload }
providerList: state.providers.providerList
}
}
}
case 'REMOVE_PROVIDER': {
const payload: string = action.payload
const id: string = action.payload
const providers = state.providers.providerList.filter((el) => el.id !== id)
return {
...state,
providers: {
...state.providers,
providerList: delete state.providers.providerList[payload] ? state.providers.providerList : state.providers.providerList
providerList: providers
}
}
}
@ -656,6 +668,13 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case 'RESET_STATE': {
return {
...runTabInitialState,
ipfsChecked: state.ipfsChecked
}
}
default:
return state
}

@ -5,19 +5,28 @@ import './toaster.css'
/* eslint-disable-next-line */
export interface ToasterProps {
message: string
message: string | JSX.Element
timeOut?: number,
handleHide?: () => void
}
export const Toaster = (props: ToasterProps) => {
const [state, setState] = useState({
const [state, setState] = useState<{
message: string | JSX.Element,
hide: boolean,
hiding: boolean,
timeOutId: any,
timeOut: number,
showModal: boolean,
showFullBtn: boolean
}>({
message: '',
hide: true,
hiding: false,
timeOutId: null,
timeOut: props.timeOut || 7000,
showModal: false
showModal: false,
showFullBtn: false
})
useEffect(() => {
@ -29,9 +38,15 @@ export const Toaster = (props: ToasterProps) => {
}, state.timeOut)
setState(prevState => {
const shortTooltipText = props.message.length > 201 ? props.message.substring(0, 200) + '...' : props.message
if (typeof props.message === 'string' && (props.message.length > 201)) {
const shortTooltipText = props.message.substring(0, 200) + '...'
return { ...prevState, hide: false, hiding: false, timeOutId, message: shortTooltipText }
} else {
const shortTooltipText = props.message
return { ...prevState, hide: false, hiding: false, timeOutId, message: shortTooltipText }
}
})
}
}, [props.message])
@ -103,7 +118,7 @@ export const Toaster = (props: ToasterProps) => {
<div data-shared="tooltipPopup" className={`remixui_tooltip alert alert-info p-2 ${state.hiding ? 'remixui_animateTop' : 'remixui_animateBottom'}`} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<span className="px-2">
{ state.message }
{ (props.message.length > 201) && <button className="btn btn-secondary btn-sm mx-3" style={{ whiteSpace: 'nowrap' }} onClick={showFullMessage}>Show full message</button> }
{ state.showFullBtn && <button className="btn btn-secondary btn-sm mx-3" style={{ whiteSpace: 'nowrap' }} onClick={showFullMessage}>Show full message</button> }
</span>
<span style={{ alignSelf: 'baseline' }}>
<button data-id="tooltipCloseButton" className="fas fa-times btn-info mx-1 p-0" onClick={closeTheToaster}></button>

@ -1,7 +1,7 @@
import { extractParentFromKey } from '@remix-ui/helper'
import React from 'react'
import { action } from '../types'
import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode } from './payload'
import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, removeFocus, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode } from './payload'
import { addInputField, createWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace'
const LOCALHOST = ' - connect to localhost - '
@ -45,6 +45,10 @@ export const listenOnPluginEvents = (filePanelPlugin) => {
plugin.on('fileManager', 'rootFolderChanged', async (path: string) => {
rootFolderChanged(path)
})
plugin.on('fileManager', 'fileClosed', async (file: string) => {
dispatch(removeFocus(file))
})
}
export const listenOnProviderEvents = (provider) => (reducerDispatch: React.Dispatch<any>) => {

@ -187,6 +187,13 @@ export const focusElement = (elements: { key: string, type: 'file' | 'folder' |
}
}
export const removeFocus = (name: string) => {
return {
type: 'REMOVE_FOCUS_ELEMENT',
payload: name
}
}
export const setContextMenuItem = (item: action) => {
return {
type: 'SET_CONTEXT_MENU_ITEM',

@ -497,6 +497,15 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
}
}
case 'REMOVE_FOCUS_ELEMENT': {
const payload: string = action.payload
return {
...state,
focusElement: state.focusElement.filter(element => element.key !== payload)
}
}
case 'SET_CONTEXT_MENU_ITEM': {
const payload = action.payload as action

@ -159,6 +159,9 @@
},
"remix-ui-run-tab": {
"tags": []
},
"remix-ui-permission-handler": {
"tags": []
}
},
"targetDependencies": {

70
package-lock.json generated

@ -9782,46 +9782,46 @@
"integrity": "sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ=="
},
"@remixproject/engine": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/@remixproject/engine/-/engine-0.3.24.tgz",
"integrity": "sha512-XVPaRIAwxTxEmc+u+bq9nIqUcP1NDdQFTm/8xmw8HcZicgagUW/y0RuLEMBj5GTGXF+EsljY27t6bPy7fmVHWQ==",
"version": "0.3.26",
"resolved": "https://registry.npmjs.org/@remixproject/engine/-/engine-0.3.26.tgz",
"integrity": "sha512-6Rq6aTUyhtXAaoQamAI8ocFSVy2txpGwu1aoYZGrqova/p/tRWn4/+PU713sffyiAQVBCk7C1z/5VKLm7tUYrQ==",
"requires": {
"@remixproject/plugin-api": "0.3.24",
"@remixproject/plugin-utils": "0.3.24"
"@remixproject/plugin-api": "0.3.26",
"@remixproject/plugin-utils": "0.3.26"
}
},
"@remixproject/engine-web": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/@remixproject/engine-web/-/engine-web-0.3.24.tgz",
"integrity": "sha512-6P2NLoL9KSa/84FumEM3QxvOW4g/hIEsq8NcbBA+/PHz9VnIRoxRg2K/jGJUHHqKw+dfoSdk5lQ+LkFQHcrp+w==",
"version": "0.3.26",
"resolved": "https://registry.npmjs.org/@remixproject/engine-web/-/engine-web-0.3.26.tgz",
"integrity": "sha512-QSW9KVOgHWuRDNqTZIp1jjBeDOXlXQZWYABgljTsC+Nig8EwlyRTfIza9PuCb+MDYT/kID8VgSPXnMrlOtvhjQ==",
"requires": {
"@remixproject/engine": "0.3.24",
"@remixproject/plugin-api": "0.3.24",
"@remixproject/plugin-utils": "0.3.24"
"@remixproject/engine": "0.3.26",
"@remixproject/plugin-api": "0.3.26",
"@remixproject/plugin-utils": "0.3.26"
}
},
"@remixproject/plugin": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/@remixproject/plugin/-/plugin-0.3.24.tgz",
"integrity": "sha512-nGtt3IZA5X2kcXauu5h5P8EEoMtHbVGm5wWnv0c7aYYWbffhQdK8dqtNNEAtQavWrsp5TL61zekqpkFzgxVv9w==",
"version": "0.3.26",
"resolved": "https://registry.npmjs.org/@remixproject/plugin/-/plugin-0.3.26.tgz",
"integrity": "sha512-j0sgl4yDOVJLCuRWOEb/Wo9/fumrWlIpsO0MrtficuhVc1FGhZxKVv8Vdu3x3HgWhXkuhLUMs8VoJ1Ntd9ZkUQ==",
"requires": {
"@remixproject/plugin-api": "0.3.24",
"@remixproject/plugin-utils": "0.3.24",
"@remixproject/plugin-api": "0.3.26",
"@remixproject/plugin-utils": "0.3.26",
"events": "3.2.0"
}
},
"@remixproject/plugin-api": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/@remixproject/plugin-api/-/plugin-api-0.3.24.tgz",
"integrity": "sha512-mBxou9OSsQt7ggMmKTJR433jJaSAckBmVj82Ek7i/+EGxEAxSqKhPfRlh5sFiTgvUmzHQzuvqa8ndyw0Bbcq4w==",
"version": "0.3.26",
"resolved": "https://registry.npmjs.org/@remixproject/plugin-api/-/plugin-api-0.3.26.tgz",
"integrity": "sha512-6vR9nVF4EfXDHA0r8MrlLyVYRMJMG7J3Y3jzpaAumetW+YpvfJqgE/uhGgm2me2ypDM8vW0POQGhRaGeZTGwFg==",
"requires": {
"@remixproject/plugin-utils": "0.3.24"
"@remixproject/plugin-utils": "0.3.26"
}
},
"@remixproject/plugin-utils": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/@remixproject/plugin-utils/-/plugin-utils-0.3.24.tgz",
"integrity": "sha512-nMXGCgs6filbgUc/Zvh1gReGG5HN2Bq+AI4Q7Ao08Thhg5ALj1UsbzQf86Ya/L7Q+EF6Em1CbgPT0VhcGlP66A==",
"version": "0.3.26",
"resolved": "https://registry.npmjs.org/@remixproject/plugin-utils/-/plugin-utils-0.3.26.tgz",
"integrity": "sha512-K/v+TXYOMV13dLf1LEgiF7CfqbOc105hC/2oapkSoHKSf3WVyWIUDlBsChRQl7osfUs/zT93q2+jNlLofWQUxg==",
"requires": {
"tslib": "2.0.1"
},
@ -9834,13 +9834,13 @@
}
},
"@remixproject/plugin-webview": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/@remixproject/plugin-webview/-/plugin-webview-0.3.24.tgz",
"integrity": "sha512-Wcyi+gGq1AYprE58vhQS181swAKZpoLAfKlKuHJ+ezbysUDuX8jgsEiQ6u1c17nQfy8Hp9sntK6VcCcDddn8gg==",
"version": "0.3.26",
"resolved": "https://registry.npmjs.org/@remixproject/plugin-webview/-/plugin-webview-0.3.26.tgz",
"integrity": "sha512-hqWaFapUfcAX2Odsj0ANKvLXQbkzZ/xNONMqE0wRxFRYTIhFGZqFzJVzwSD+U4bSehP1JtzkrxwKBqNyjz5GxQ==",
"requires": {
"@remixproject/plugin": "0.3.24",
"@remixproject/plugin-api": "0.3.24",
"@remixproject/plugin-utils": "0.3.24",
"@remixproject/plugin": "0.3.26",
"@remixproject/plugin-api": "0.3.26",
"@remixproject/plugin-utils": "0.3.26",
"axios": "^0.21.1"
},
"dependencies": {
@ -9855,13 +9855,13 @@
}
},
"@remixproject/plugin-ws": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/@remixproject/plugin-ws/-/plugin-ws-0.3.24.tgz",
"integrity": "sha512-COIROJX61vS2TRH82MflIUlScxLauNtLkMRY7vzncVOIvufApvNc84Ua8Vr6vhSb2tZeWX+u4UTiFnpFDRL7xw==",
"version": "0.3.26",
"resolved": "https://registry.npmjs.org/@remixproject/plugin-ws/-/plugin-ws-0.3.26.tgz",
"integrity": "sha512-Nerd/2vGb96G6B8pGRCRNAGlO97KnJpbFmpa47SYipgjaq5Yj5iCUY+fbQzMWdGW2W4BrUPE+YBZCkq/KlfbGw==",
"requires": {
"@remixproject/plugin": "0.3.24",
"@remixproject/plugin-api": "0.3.24",
"@remixproject/plugin-utils": "0.3.24"
"@remixproject/plugin": "0.3.26",
"@remixproject/plugin-api": "0.3.26",
"@remixproject/plugin-utils": "0.3.26"
}
},
"@restart/context": {

@ -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,remix-ui-panel,remix-ui-run-tab",
"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,remix-ui-permission-handle",
"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",
@ -147,13 +147,13 @@
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@monaco-editor/react": "^4.3.1",
"@remixproject/engine": "^0.3.24",
"@remixproject/engine-web": "^0.3.24",
"@remixproject/plugin": "^0.3.24",
"@remixproject/plugin-api": "^0.3.24",
"@remixproject/plugin-utils": "^0.3.24",
"@remixproject/plugin-webview": "^0.3.24",
"@remixproject/plugin-ws": "^0.3.24",
"@remixproject/engine": "^0.3.26",
"@remixproject/engine-web": "^0.3.26",
"@remixproject/plugin": "^0.3.26",
"@remixproject/plugin-api": "^0.3.26",
"@remixproject/plugin-utils": "^0.3.26",
"@remixproject/plugin-webview": "^0.3.26",
"@remixproject/plugin-ws": "^0.3.26",
"ansi-gray": "^0.1.1",
"async": "^2.6.2",
"axios": ">=0.21.1",

@ -76,7 +76,8 @@
"@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/run-tab": ["libs/remix-ui/run-tab/src/index.ts"]
"@remix-ui/run-tab": ["libs/remix-ui/run-tab/src/index.ts"],
"@remix-ui/permission-handler": ["libs/remix-ui/permission-handler/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]

@ -1170,6 +1170,21 @@
}
}
}
},
"remix-ui-permission-handler": {
"root": "libs/remix-ui/permission-handler",
"sourceRoot": "libs/remix-ui/permission-handler/src",
"projectType": "library",
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/permission-handler/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "libs/remix-ui/permission-handler/**/*.d.ts", "!libs/remix-ui/permission-handler/**/*"]
}
}
}
}
},
"cli": {

Loading…
Cancel
Save