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

pull/5370/head
bunsenstraat 3 years ago
commit b9b81bdea2
  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. 30
      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. 12
      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. 40
      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. 27
      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(() => { useEffect(() => {
client.onload(async () => { client.onload(async () => {
const customProfiles = ['menuicons', 'tabs', 'solidityUnitTesting'] const customProfiles = ['menuicons', 'tabs', 'solidityUnitTesting', 'hardhat-provider']
client.testCommand = async (data: any) => { client.testCommand = async (data: any) => {
console.log(data) console.log(data)

@ -10,7 +10,7 @@ declare global {
const localPluginData: Profile & LocationProfile & ExternalProfile = { const localPluginData: Profile & LocationProfile & ExternalProfile = {
name: 'localPlugin', name: 'localPlugin',
displayName: 'Local Plugin', displayName: 'Local Plugin',
canActivate: ['dGitProvider', 'flattener', 'solidityUnitTesting', 'udapp'], canActivate: ['dGitProvider', 'flattener', 'solidityUnitTesting', 'udapp', 'hardhat-provider'],
url: 'http://localhost:2020', url: 'http://localhost:2020',
location: 'sidePanel' location: 'sidePanel'
} }
@ -82,7 +82,7 @@ const checkForAcceptAndRemember = async function (browser: NightwatchBrowser) {
// @ts-ignore // @ts-ignore
browser.frame(0, () => { resolve(true) }) browser.frame(0, () => { resolve(true) })
} else { } 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 // @ts-ignore
browser.frame(0, () => { resolve(true) }) browser.frame(0, () => { resolve(true) })
}) })
@ -324,5 +324,33 @@ module.exports = {
'Should get compilationresults #group6': async function (browser: NightwatchBrowser) { 'Should get compilationresults #group6': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'solidity:getCompilationResult', 'contracts/1_Storage.sol', null, null) 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" echo "$BUILD_ID"
TEST_EXITCODE=0 TEST_EXITCODE=0
npm run ganache-cli &
npm run serve:production & npm run serve:production &
npx nx serve remix-ide-e2e-src-local-plugin & 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 { VerticalIcons } from './app/components/vertical-icons'
import { LandingPage } from './app/ui/landing-page/landing-page' import { LandingPage } from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel' import { MainPanel } from './app/components/main-panel'
import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin'
import { WalkthroughService } from './walkthroughService' import { WalkthroughService } from './walkthroughService'
@ -35,7 +36,6 @@ const FileManager = require('./app/files/fileManager')
const FileProvider = require('./app/files/fileProvider') const FileProvider = require('./app/files/fileProvider')
const DGitProvider = require('./app/files/dgitProvider') const DGitProvider = require('./app/files/dgitProvider')
const WorkspaceFileProvider = require('./app/files/workspaceFileProvider') const WorkspaceFileProvider = require('./app/files/workspaceFileProvider')
const toolTip = require('./app/ui/tooltip')
const PluginManagerComponent = require('./app/components/plugin-manager-component') const PluginManagerComponent = require('./app/components/plugin-manager-component')
@ -191,8 +191,11 @@ class AppComponent {
const configPlugin = new ConfigPlugin() const configPlugin = new ConfigPlugin()
self.layout = new Layout() self.layout = new Layout()
const permissionHandler = new PermissionHandlerPlugin()
self.engine.register([ self.engine.register([
permissionHandler,
self.layout, self.layout,
self.modal, self.modal,
self.gistHandler, self.gistHandler,
@ -315,9 +318,9 @@ class AppComponent {
console.log("couldn't register iframe plugins", e.message) console.log("couldn't register iframe plugins", e.message)
} }
await self.appManager.activatePlugin(['layout']) await self.appManager.activatePlugin(['layout'])
await self.appManager.activatePlugin(['modal']) await self.appManager.activatePlugin(['notification'])
await self.appManager.activatePlugin(['editor']) 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(['mainPanel', 'menuicons', 'tabs'])
await self.appManager.activatePlugin(['sidePanel']) // activating host plugin separately await self.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await self.appManager.activatePlugin(['home']) await self.appManager.activatePlugin(['home'])
@ -364,7 +367,7 @@ class AppComponent {
if (params.call) { if (params.call) {
const callDetails = params.call.split('//') const callDetails = params.call.split('//')
if (callDetails.length > 1) { 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) // @todo(remove the timeout when activatePlugin is on 0.3.0)
self.appManager.call(...callDetails).catch(console.error) self.appManager.call(...callDetails).catch(console.error)
} }

@ -1,5 +1,4 @@
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import { PluginManagerSettings } from './plugin-manager-settings'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import {RemixUiPluginManager} from '@remix-ui/plugin-manager' // eslint-disable-line import {RemixUiPluginManager} from '@remix-ui/plugin-manager' // eslint-disable-line
@ -24,7 +23,6 @@ class PluginManagerComponent extends ViewPlugin {
super(profile) super(profile)
this.appManager = appManager this.appManager = appManager
this.engine = engine this.engine = engine
this.pluginManagerSettings = new PluginManagerSettings()
this.htmlElement = document.createElement('div') this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'pluginManager') this.htmlElement.setAttribute('id', 'pluginManager')
this.filter = '' this.filter = ''
@ -90,7 +88,6 @@ class PluginManagerComponent extends ViewPlugin {
ReactDOM.render( ReactDOM.render(
<RemixUiPluginManager <RemixUiPluginManager
pluginComponent={this} pluginComponent={this}
pluginManagerSettings={this.pluginManagerSettings}
/>, />,
this.htmlElement) 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' 'use strict'
import yo from 'yo-yo'
import async from 'async' import async from 'async'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry' import Registry from '../state/registry'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { RemixAppManager } from '../../../../../libs/remix-ui/plugin-manager/src/types' 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') const helper = require('../../lib/helper.js')
/* /*
@ -317,7 +315,7 @@ class FileManager extends Plugin {
if (isFile) { if (isFile) {
if (newPathExists) { if (newPathExists) {
this.call('modal', 'alert', { this.call('notification', 'alert', {
id: 'fileManagerAlert', id: 'fileManagerAlert',
message: 'File already exists' message: 'File already exists'
}) })
@ -326,7 +324,7 @@ class FileManager extends Plugin {
return provider.rename(oldPath, newPath, false) return provider.rename(oldPath, newPath, false)
} else { } else {
if (newPathExists) { if (newPathExists) {
this.call('modal', 'alert', { this.call('notification', 'alert', {
id: 'fileManagerAlert', id: 'fileManagerAlert',
message: 'Directory already exists' message: 'Directory already exists'
}) })
@ -528,17 +526,7 @@ class FileManager extends Plugin {
const required = this.appManager.isRequired(this.currentRequest.from) const required = this.appManager.isRequired(this.currentRequest.from)
if (canCall && !required) { if (canCall && !required) {
// inform the user about modification after permission is granted and even if permission was saved before // inform the user about modification after permission is granted and even if permission was saved before
toaster(yo` this.call('notification','toast', fileChangedToastMsg(this.currentRequest.from, path))
<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 })
} }
} }
return await this._setFileInternal(path, content) return await this._setFileInternal(path, content)
@ -778,12 +766,12 @@ class FileManager extends Plugin {
helper.createNonClashingName(file, self._deps.filesProviders[fileProvider], helper.createNonClashingName(file, self._deps.filesProviders[fileProvider],
(error, name) => { (error, name) => {
if (error) { if (error) {
this.call('modal', 'alert', { this.call('notification', 'alert', {
id: 'fileManagerAlert', id: 'fileManagerAlert',
message: 'Unexpected error loading file ' + file + ': ' + error message: 'Unexpected error loading file ' + file + ': ' + error
}) })
} else if (helper.checkSpecialChars(name)) { } else if (helper.checkSpecialChars(name)) {
this.call('modal', 'alert', { this.call('notification', 'alert', {
id: 'fileManagerAlert', id: 'fileManagerAlert',
message: 'Special characters are not allowed in file names.' message: 'Special characters are not allowed in file names.'
}) })

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

@ -14,9 +14,9 @@ interface IModalApi {
} }
const profile:LibraryProfile<IModalApi> = { const profile:LibraryProfile<IModalApi> = {
name: 'modal', name: 'notification',
displayName: 'Modal', displayName: 'Notification',
description: 'Modal', description: 'Displays notifications',
methods: ['modal', 'alert', 'toast'] methods: ['modal', 'alert', 'toast']
} }
@ -31,11 +31,11 @@ export class ModalPlugin extends Plugin implements MethodApi<IModalApi> {
} }
async modal (args: AppModal) { async modal (args: AppModal) {
this.dispatcher.modal(args) return this.dispatcher.modal(args)
} }
async alert (args: AlertModal) { async alert (args: AlertModal) {
this.dispatcher.alert(args) return this.dispatcher.alert(args)
} }
async toast (message: string) { 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 { export class RemixdHandle extends WebsocketPlugin {
localhostProvider: any localhostProvider: any
appManager: PluginManager appManager: PluginManager
state: State
constructor (localhostProvider, appManager) { constructor (localhostProvider, appManager) {
super(profile) super(profile)
this.localhostProvider = localhostProvider this.localhostProvider = localhostProvider
@ -48,7 +47,8 @@ export class RemixdHandle extends WebsocketPlugin {
} }
async activate () { async activate () {
await this.connectToLocalhost() this.connectToLocalhost()
return true
} }
async canceled () { async canceled () {
@ -69,7 +69,7 @@ export class RemixdHandle extends WebsocketPlugin {
id: 'connectionAlert', id: 'connectionAlert',
message: 'Cannot connect to the remixd daemon. Please make sure you have the remixd running in the background.' 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() this.canceled()
} else { } else {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
@ -80,7 +80,7 @@ export class RemixdHandle extends WebsocketPlugin {
id: 'connectionAlert', id: 'connectionAlert',
message: 'Connection to remixd terminated.Please make sure remixd is still running in the background.' 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() this.canceled()
} }
}, 3000) }, 3000)
@ -95,13 +95,15 @@ export class RemixdHandle extends WebsocketPlugin {
this.deactivate() this.deactivate()
} else if (!isElectron()) { } else if (!isElectron()) {
// warn the user only if he/she is in the browser context // warn the user only if he/she is in the browser context
this.state = State.new
const mod:AppModal = { const mod:AppModal = {
id: 'remixdConnect', id: 'remixdConnect',
title: 'Connect to localhost', title: 'Connect to localhost',
message: remixdDialog(), message: remixdDialog(),
okFn: () => { okLabel: 'Connect',
this.state = State.ok cancelLabel: 'Cancel',
}
const result = await this.call('notification', 'modal', mod)
if(result) {
try { try {
this.localhostProvider.preInit() this.localhostProvider.preInit()
super.activate() super.activate()
@ -115,18 +117,10 @@ export class RemixdHandle extends WebsocketPlugin {
} catch (error) { } catch (error) {
connection(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()
}
} }
await this.call('modal', 'modal', mod) else {
await this.canceled()
}
} else { } else {
try { try {
super.activate() super.activate()

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

@ -1,14 +1,12 @@
import toaster from '../ui/tooltip'
import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line
import { DebuggerApiMixin } from '@remixproject/debugger-plugin' import { DebuggerApiMixin } from '@remixproject/debugger-plugin'
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import modalDialogCustom from '../ui/modal-dialog-custom'
import * as remixBleach from '../../lib/remixBleach' import * as remixBleach from '../../lib/remixBleach'
import { compilationFinishedToastMsg, compilingToastMsg, localCompilationToastMsg, notFoundToastMsg, sourceVerificationNotAvailableToastMsg } from '@remix-ui/helper'
const css = require('./styles/debugger-tab-styles') const css = require('./styles/debugger-tab-styles')
const yo = require('yo-yo')
const profile = { const profile = {
name: 'debugger', name: 'debugger',
@ -26,46 +24,45 @@ const profile = {
export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) { export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
constructor () { constructor () {
super(profile) super(profile)
this.el = null this.el = document.createElement('div')
this.el.setAttribute('id', 'debugView')
this.el.classList.add(css.debuggerTabView)
this.initDebuggerApi() this.initDebuggerApi()
} }
render () { 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) => { 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) => { 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) => { 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) => { 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', () => { 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() this.renderComponent()
return this.el return this.el
} }
showMessage (title, message) { showMessage (title, message) {
try { try {
modalDialogCustom.alert(title, remixBleach.sanitize(message)) this.call('notification', 'alert', {
id: 'debuggerTabShowMessage',
title,
message: remixBleach.sanitize(message)
})
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }

@ -66,7 +66,7 @@ export class HardhatProvider extends Plugin {
value = await ((): Promise<string> => { value = await ((): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const modalContent: AppModal = { const modalContent: AppModal = {
id: 'harrhatprovider', id: 'hardhatprovider',
title: 'Hardhat node request', title: 'Hardhat node request',
message: this.hardhatProviderDialogBody(), message: this.hardhatProviderDialogBody(),
modalType: ModalTypes.prompt, modalType: ModalTypes.prompt,
@ -83,7 +83,7 @@ export class HardhatProvider extends Plugin {
}, },
defaultValue: 'http://127.0.0.1:8545' defaultValue: 'http://127.0.0.1:8545'
} }
this.call('modal', 'modal', modalContent) this.call('notification', 'modal', modalContent)
}) })
})() })()
} catch (e) { } catch (e) {
@ -110,11 +110,11 @@ export class HardhatProvider extends Plugin {
} catch (error) { } catch (error) {
this.blocked = true this.blocked = true
const modalContent: AlertModal = { const modalContent: AlertModal = {
id: 'harrhatprovider', id: 'hardhatprovider',
title: 'Hardhat Provider', title: 'Hardhat Provider',
message: `Error while connecting to the hardhat provider: ${error.message}`, 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' }) await this.call('udapp', 'setEnvironmentMode', { context: 'vm', fork: 'london' })
this.provider = null this.provider = null
setTimeout(_ => { this.blocked = false }, 1000) // we wait 1 second for letting remix to switch to vm 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 { PluginManager } from '@remixproject/engine'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import QueryParams from './lib/query-params' import QueryParams from './lib/query-params'
import { PermissionHandler } from './app/ui/persmission-handler'
import { IframePlugin } from '@remixproject/engine-web' import { IframePlugin } from '@remixproject/engine-web'
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
const requiredModules = [ // services + layout views + system views const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', '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) const dependentModules = ['git', 'hardhat', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd)
export function isNative (name) { 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) return nativePlugins.includes(name) || requiredModules.includes(name)
} }
@ -40,7 +39,6 @@ export class RemixAppManager extends PluginManager {
this.event = new EventEmitter() this.event = new EventEmitter()
this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json' this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json'
this.pluginLoader = new PluginLoader() this.pluginLoader = new PluginLoader()
this.permissionHandler = new PermissionHandler()
} }
async canActivatePlugin (from, to) { async canActivatePlugin (from, to) {
@ -72,7 +70,7 @@ export class RemixAppManager extends PluginManager {
return true return true
} }
// ask the user for permission // 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) { onPluginActivated (plugin) {

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

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

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

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

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

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

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

@ -6,33 +6,43 @@ import { Placement } from 'react-bootstrap/esm/Overlay'
import './copy-to-clipboard.css' import './copy-to-clipboard.css'
interface ICopyToClipboard { interface ICopyToClipboard {
content: any, content?: any,
tip?: string, tip?: string,
icon?: string, icon?: string,
direction?: Placement, direction?: Placement,
className?: string, className?: string,
title?: string, title?: string,
children?: JSX.Element children?: JSX.Element,
getContent?: () => {}
} }
export const CopyToClipboard = (props: ICopyToClipboard) => { 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 [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 { try {
if (typeof content !== 'string') { if (content === '') {
content = JSON.stringify(content, null, '\t') setMessage('Cannot copy empty content!')
} return
copy(content) }
setMessage('Copied') if (typeof content !== 'string') {
} catch (e) { content = JSON.stringify(content, null, '\t')
console.error(e)
} }
copy(content)
setMessage('Copied')
} 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 { } else {
setMessage('Cannot copy empty content!') content = getContent && getContent()
copyData()
} }
e.preventDefault() e.preventDefault()
return false
} }
const reset = () => { const reset = () => {

@ -1 +1,2 @@
export * from './lib/remix-ui-helper' 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", "extends": "../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

@ -11,5 +11,6 @@ export interface ModalDialogProps {
showCancelIcon?: boolean, showCancelIcon?: boolean,
hide?: boolean, hide?: boolean,
handleHide: (hideState?: boolean) => void, 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 useLocalStorage from '../custom-hooks/useLocalStorage'
import { PluginPermissions } from '../../types' import { PluginPermissions } from '../../types'
interface PermissionSettingsProps { function PermisssionsSettings () {
pluginSettings: any
}
function PermisssionsSettings ({ pluginSettings }: PermissionSettingsProps) {
const [modalVisibility, setModalVisibility] = useState<boolean>(true) const [modalVisibility, setModalVisibility] = useState<boolean>(true)
const [permissions, setPermissions] = useLocalStorage<PluginPermissions>('plugins/permissions', {} as PluginPermissions) const [permissions, setPermissions] = useLocalStorage<PluginPermissions>('plugins/permissions', {} as PluginPermissions)
const [permissionCache, setpermissionCache] = useState<PluginPermissions>() const [permissionCache, setpermissionCache] = useState<PluginPermissions>()

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

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

@ -3,7 +3,7 @@ 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, shortenAddress } from '@remix-ui/helper' 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 { 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'
@ -23,6 +23,7 @@ let plugin: RunTab, dispatch: React.Dispatch<any>
export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispatch<any>) => { export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispatch<any>) => {
plugin = udapp plugin = udapp
dispatch = reducerDispatch dispatch = reducerDispatch
resetAndInit()
setupEvents() setupEvents()
setInterval(() => { setInterval(() => {
fillAccountsList() fillAccountsList()
@ -30,29 +31,6 @@ export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispa
} }
const setupEvents = () => { 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.blockchain.events.on('newTransaction', (tx, receipt) => {
plugin.emit('newTransaction', tx, receipt) plugin.emit('newTransaction', tx, receipt)
}) })
@ -103,6 +81,11 @@ const setupEvents = () => {
setExecutionContext(env, plugin.REACT_API.web3Dialog()) setExecutionContext(env, plugin.REACT_API.web3Dialog())
}) })
plugin.on('filePanel', 'setWorkspace', () => {
dispatch(resetUdapp())
resetAndInit()
})
plugin.fileManager.events.on('currentFileChanged', (currentFile: string) => { plugin.fileManager.events.on('currentFileChanged', (currentFile: string) => {
if (/.(.abi)$/.exec(currentFile)) { if (/.(.abi)$/.exec(currentFile)) {
dispatch(setLoadType('abi')) 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>) => { export const initWebDialogs = (envToasterContent: (env: { context: string, fork: string }, from: string) => void, web3Dialog: () => void) => async (dispatch: React.Dispatch<any>) => {
dispatch(setEnvToasterContent(envToasterContent)) dispatch(setEnvToasterContent(envToasterContent))
dispatch(setWeb3Dialog) dispatch(setWeb3Dialog(web3Dialog))
} }
const updateAccountBalances = () => { const updateAccountBalances = () => {
@ -254,7 +237,7 @@ export const setExecutionContext = (executionContext: { context: string, fork: s
}, () => { setFinalContext() })) }, () => { setFinalContext() }))
}, (alertMsg) => { }, (alertMsg) => {
dispatch(displayPopUp(alertMsg)) dispatch(displayPopUp(alertMsg))
}, setFinalContext()) }, () => { setFinalContext() })
} }
export const setWeb3Endpoint = (endpoint: string) => { export const setWeb3Endpoint = (endpoint: string) => {
@ -718,3 +701,28 @@ export const getFuncABIInputs = (funcABI: FuncABI) => {
export const setSendTransactionValue = (value: string) => { export const setSendTransactionValue = (value: string) => {
dispatch(setSendValue(value)) 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 { 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, classList: string,
dataId: string dataId: string
}>({ title: '', content: '', classList: '', dataId: '' }) }>({ title: '', content: '', classList: '', dataId: '' })
const [clipboardContent, setClipboardContent] = useState<string>('')
const multiFields = useRef<Array<HTMLInputElement | null>>([]) 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(() => { useEffect(() => {
if (props.title) { if (props.title) {
setTitle(props.title) setTitle(props.title)
@ -75,6 +55,28 @@ export function ContractGUI (props: ContractGUIProps) {
} }
}, [props.lookupOnly, props.funcABI, title]) }, [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 = () => { const switchMethodViewOn = () => {
setToggleContainer(true) setToggleContainer(true)
makeMultiVal() makeMultiVal()
@ -187,7 +189,7 @@ export function ContractGUI (props: ContractGUIProps) {
})} })}
</div> </div>
<div className="udapp_group udapp_multiArg"> <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> <button onClick={handleExpandMultiClick} title={buttonOptions.title} data-id={buttonOptions.dataId} className={`udapp_instanceButton ${buttonOptions.classList}`}>{ buttonOptions.content }</button>
</div> </div>
</div> </div>

@ -171,6 +171,11 @@ export const runTabInitialState: RunTabState = {
web3Dialog: null web3Dialog: null
} }
type AddProvider = {
name: string,
provider: any
}
export const runTabReducer = (state: RunTabState = runTabInitialState, action: Action) => { export const runTabReducer = (state: RunTabState = runTabInitialState, action: Action) => {
switch (action.type) { switch (action.type) {
case 'FETCH_ACCOUNTS_LIST_REQUEST': { case 'FETCH_ACCOUNTS_LIST_REQUEST': {
@ -328,25 +333,32 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
} }
case 'ADD_PROVIDER': { 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 { return {
...state, ...state,
providers: { providers: {
...state.providers, ...state.providers,
providerList: { ...state.providers.providerList, payload } providerList: state.providers.providerList
} }
} }
} }
case 'REMOVE_PROVIDER': { case 'REMOVE_PROVIDER': {
const payload: string = action.payload const id: string = action.payload
const providers = state.providers.providerList.filter((el) => el.id !== id)
return { return {
...state, ...state,
providers: { providers: {
...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: default:
return state return state
} }

@ -5,19 +5,28 @@ import './toaster.css'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface ToasterProps { export interface ToasterProps {
message: string message: string | JSX.Element
timeOut?: number, timeOut?: number,
handleHide?: () => void handleHide?: () => void
} }
export const Toaster = (props: ToasterProps) => { 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: '', message: '',
hide: true, hide: true,
hiding: false, hiding: false,
timeOutId: null, timeOutId: null,
timeOut: props.timeOut || 7000, timeOut: props.timeOut || 7000,
showModal: false showModal: false,
showFullBtn: false
}) })
useEffect(() => { useEffect(() => {
@ -29,9 +38,15 @@ export const Toaster = (props: ToasterProps) => {
}, state.timeOut) }, state.timeOut)
setState(prevState => { 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 } 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]) }, [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}> <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"> <span className="px-2">
{ state.message } { 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>
<span style={{ alignSelf: 'baseline' }}> <span style={{ alignSelf: 'baseline' }}>
<button data-id="tooltipCloseButton" className="fas fa-times btn-info mx-1 p-0" onClick={closeTheToaster}></button> <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 { extractParentFromKey } from '@remix-ui/helper'
import React from 'react' import React from 'react'
import { action } from '../types' 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' import { addInputField, createWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace'
const LOCALHOST = ' - connect to localhost - ' const LOCALHOST = ' - connect to localhost - '
@ -45,6 +45,10 @@ export const listenOnPluginEvents = (filePanelPlugin) => {
plugin.on('fileManager', 'rootFolderChanged', async (path: string) => { plugin.on('fileManager', 'rootFolderChanged', async (path: string) => {
rootFolderChanged(path) rootFolderChanged(path)
}) })
plugin.on('fileManager', 'fileClosed', async (file: string) => {
dispatch(removeFocus(file))
})
} }
export const listenOnProviderEvents = (provider) => (reducerDispatch: React.Dispatch<any>) => { 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) => { export const setContextMenuItem = (item: action) => {
return { return {
type: 'SET_CONTEXT_MENU_ITEM', 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': { case 'SET_CONTEXT_MENU_ITEM': {
const payload = action.payload as action const payload = action.payload as action

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

70
package-lock.json generated

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

@ -45,7 +45,7 @@
"workspace-schematic": "nx workspace-schematic", "workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph", "dep-graph": "nx dep-graph",
"help": "nx help", "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", "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", "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", "publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs",
@ -147,13 +147,13 @@
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@monaco-editor/react": "^4.3.1", "@monaco-editor/react": "^4.3.1",
"@remixproject/engine": "^0.3.24", "@remixproject/engine": "^0.3.26",
"@remixproject/engine-web": "^0.3.24", "@remixproject/engine-web": "^0.3.26",
"@remixproject/plugin": "^0.3.24", "@remixproject/plugin": "^0.3.26",
"@remixproject/plugin-api": "^0.3.24", "@remixproject/plugin-api": "^0.3.26",
"@remixproject/plugin-utils": "^0.3.24", "@remixproject/plugin-utils": "^0.3.26",
"@remixproject/plugin-webview": "^0.3.24", "@remixproject/plugin-webview": "^0.3.26",
"@remixproject/plugin-ws": "^0.3.24", "@remixproject/plugin-ws": "^0.3.26",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.2", "async": "^2.6.2",
"axios": ">=0.21.1", "axios": ">=0.21.1",

@ -76,7 +76,8 @@
"@remix-ui/panel": ["libs/remix-ui/panel/src/index.ts"], "@remix-ui/panel": ["libs/remix-ui/panel/src/index.ts"],
"@remix-ui/editor-context-view": ["libs/remix-ui/editor-context-view/src/index.ts"], "@remix-ui/editor-context-view": ["libs/remix-ui/editor-context-view/src/index.ts"],
"@remix-ui/solidity-unit-testing": ["libs/remix-ui/solidity-unit-testing/src/index.ts"], "@remix-ui/solidity-unit-testing": ["libs/remix-ui/solidity-unit-testing/src/index.ts"],
"@remix-ui/run-tab": ["libs/remix-ui/run-tab/src/index.ts"] "@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"] "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": { "cli": {

Loading…
Cancel
Save