Merge branch 'master' into fixdgit

pull/5370/head
bunsenstraat 3 years ago committed by GitHub
commit 0135b0a99c
  1. 8
      .circleci/config.yml
  2. 1
      .gitignore
  3. 2
      apps/remix-ide-e2e/src/commands/removeFile.ts
  4. 48
      apps/remix-ide-e2e/src/tests/pluginManager.spec.ts
  5. 27
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  6. 3
      apps/remix-ide-e2e/src/tests/url.spec.ts
  7. 3
      apps/remix-ide-e2e/src/tests/verticalIconsPanel.spec.ts
  8. 1
      apps/remix-ide/ci/copy_resources.sh
  9. 1
      apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh
  10. 1
      apps/remix-ide/ci/deploy_from_travis_remix-beta.sh
  11. 2
      apps/remix-ide/ci/deploy_from_travis_remix-live.sh
  12. 9
      apps/remix-ide/src/app.js
  13. 129
      apps/remix-ide/src/app/components/local-plugin.js
  14. 292
      apps/remix-ide/src/app/components/plugin-manager-component.js
  15. 13
      apps/remix-ide/src/app/components/plugin-manager-settings.js
  16. 97
      apps/remix-ide/src/app/files/fileManager.js
  17. 12
      apps/remix-ide/src/app/files/fileProvider.js
  18. 8
      apps/remix-ide/src/app/files/remixDProvider.js
  19. 2
      apps/remix-ide/src/app/tabs/runTab/contractDropdown.js
  20. 2
      apps/remix-ide/src/app/ui/confirmDialog.js
  21. 10
      apps/remix-ide/src/blockchain/blockchain.js
  22. 12
      apps/remix-ide/src/framingService.js
  23. 117
      apps/remix-ide/src/production.index.html
  24. 2
      apps/remix-ide/team-best-practices.md
  25. 135
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  26. 2
      libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx
  27. 6
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
  28. 4
      libs/remix-ui/plugin-manager/.babelrc
  29. 19
      libs/remix-ui/plugin-manager/.eslintrc
  30. 7
      libs/remix-ui/plugin-manager/README.md
  31. 1
      libs/remix-ui/plugin-manager/src/index.ts
  32. 54
      libs/remix-ui/plugin-manager/src/lib/components/ActivePluginCard.tsx
  33. 36
      libs/remix-ui/plugin-manager/src/lib/components/ActivePluginCardContainer.tsx
  34. 59
      libs/remix-ui/plugin-manager/src/lib/components/InactivePluginCard.tsx
  35. 47
      libs/remix-ui/plugin-manager/src/lib/components/InactivePluginCardContainer.tsx
  36. 218
      libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx
  37. 20
      libs/remix-ui/plugin-manager/src/lib/components/moduleHeading.tsx
  38. 144
      libs/remix-ui/plugin-manager/src/lib/components/permissionsSettings.tsx
  39. 66
      libs/remix-ui/plugin-manager/src/lib/components/rootView.tsx
  40. 78
      libs/remix-ui/plugin-manager/src/lib/custom-hooks/useLocalStorage.ts
  41. 14
      libs/remix-ui/plugin-manager/src/lib/reducers/pluginManagerReducer.ts
  42. 96
      libs/remix-ui/plugin-manager/src/lib/remix-ui-plugin-manager.css
  43. 29
      libs/remix-ui/plugin-manager/src/lib/remix-ui-plugin-manager.tsx
  44. 208
      libs/remix-ui/plugin-manager/src/types.d.ts
  45. 17
      libs/remix-ui/plugin-manager/tsconfig.json
  46. 13
      libs/remix-ui/plugin-manager/tsconfig.lib.json
  47. 29
      libs/remix-ui/renderer/src/lib/renderer.tsx
  48. 6
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  49. 17
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  50. 8
      libs/remixd/src/bin/remixd.ts
  51. 1
      libs/remixd/src/services/slitherClient.ts
  52. 5
      nx.json
  53. 221
      package-lock.json
  54. 3
      package.json
  55. 2
      team-best-practices.md
  56. 1
      tsconfig.base.json
  57. 15
      workspace.json

@ -232,7 +232,7 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/assets dist/apps/remix-ide/index.html dist/apps/remix-ide/main.js dist/apps/remix-ide/polyfills.js dist/apps/remix-ide/favicon.ico"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/assets dist/apps/remix-ide/production.index.html dist/apps/remix-ide/main.js dist/apps/remix-ide/polyfills.js dist/apps/remix-ide/favicon.ico"
working_directory: ~/remix-project
steps:
@ -260,7 +260,7 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/assets dist/apps/remix-ide/index.html dist/apps/remix-ide/main.js dist/apps/remix-ide/polyfills.js dist/apps/remix-ide/favicon.ico"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/assets dist/apps/remix-ide/production.index.html dist/apps/remix-ide/main.js dist/apps/remix-ide/polyfills.js dist/apps/remix-ide/favicon.ico"
working_directory: ~/remix-project
steps:
@ -286,7 +286,7 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/assets dist/apps/remix-ide/index.html dist/apps/remix-ide/main.js dist/apps/remix-ide/polyfills.js dist/apps/remix-ide/favicon.ico"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/assets dist/apps/remix-ide/production.index.html dist/apps/remix-ide/main.js dist/apps/remix-ide/polyfills.js dist/apps/remix-ide/favicon.ico"
working_directory: ~/remix-project
steps:
@ -314,7 +314,7 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/assets dist/apps/remix-ide/index.html dist/apps/remix-ide/main.js dist/apps/remix-ide/polyfills.js dist/apps/remix-ide/favicon.ico"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/assets dist/apps/remix-ide/production.index.html dist/apps/remix-ide/main.js dist/apps/remix-ide/polyfills.js dist/apps/remix-ide/favicon.ico"
working_directory: ~/remix-project
steps:

1
.gitignore vendored

@ -51,4 +51,3 @@ testem.log
# System Files
.DS_Store
Thumbs.db

@ -34,7 +34,7 @@ function removeFile (browser: NightwatchBrowser, path: string, workspace: string
contextMenuClick(document.querySelector('[data-path="' + path + '"]'))
}, [path], function () {
browser
.waitForElementVisible('#menuitemdelete')
.waitForElementVisible('#menuitemdelete', 60000)
.click('#menuitemdelete')
.pause(2000)
.perform(() => {

@ -2,6 +2,10 @@
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
declare global {
interface Window { testmode: boolean; }
}
const testData = {
pluginName: 'remixIde',
pluginDisplayName: 'Remix IDE',
@ -17,6 +21,7 @@ module.exports = {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000)
.click('*[plugin="pluginManager"]')
.pause(3000)
.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'PLUGIN MANAGER')
},
@ -36,7 +41,8 @@ module.exports = {
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonZoKrates"]')
.clearValue('*[data-id="pluginManagerComponentSearchInput"]')
.click('*[data-id="pluginManagerComponentSearchInput"]')
.keys(browser.Keys.ENTER)
.keys(browser.Keys.SPACE)
.keys(browser.Keys.BACK_SPACE)
},
'Should activate plugins': function (browser: NightwatchBrowser) {
@ -46,7 +52,7 @@ module.exports = {
.pause(2000)
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtondebugger"]', 60000)
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtonvyper"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonvyper"]', 60000)
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonvyper"]', 70000)
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtonZoKrates"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonZoKrates"]', 60000)
},
@ -103,37 +109,42 @@ module.exports = {
'Should connect a local plugin': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.execute(function () {
window.testmode = true
})
.click('*[data-id="pluginManagerComponentPluginSearchButton"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.click('*[data-id="modalDialogModalBody"]')
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialogModalDialogContainer-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="localPluginName"]')
.setValue('*[data-id="localPluginName"]', testData.pluginName)
.setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName)
.setValue('*[data-id="localPluginUrl"]', testData.pluginUrl)
.clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', testData.pluginName)
.clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName)
.clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', testData.pluginUrl)
.click('*[data-id="localPluginRadioButtoniframe"]')
.click('*[data-id="localPluginRadioButtonsidePanel"]')
.click('*[data-id="modalDialogModalFooter"]')
.modalFooterOKClick()
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonremixIde"]', 100000)
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalFooter-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react')
// .modalFooterOKClick()
// .waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonremixIde"]', 60000)
},
'Should display error message for creating already existing plugin': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentPluginSearchButton"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.click('*[data-id="modalDialogModalBody"]')
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialogModalDialogContainer-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="localPluginName"]')
.clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', testData.pluginName)
.clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName)
.clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', testData.pluginUrl)
.click('*[data-id="localPluginRadioButtoniframe"]')
.click('*[data-id="localPluginRadioButtonsidePanel"]')
.click('*[data-id="modalDialogModalFooter"]')
.modalFooterOKClick()
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react"]', 60000)
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react"]')
// .modalFooterOKClick()
// .pause(2000)
.waitForElementVisible('*[data-shared="tooltipPopup"]', 60000)
.pause(5000)
.waitForElementVisible('*[data-shared="tooltipPopup"]:nth-last-of-type(1)')
.pause(2000)
.assert.containsText('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 'Cannot create Plugin : This name has already been used')
.assert.containsText('*[data-shared="tooltipPopup"]', 'Cannot create Plugin : This name has already been used')
},
'Should load back installed plugins after reload': function (browser: NightwatchBrowser) {
@ -143,8 +154,9 @@ module.exports = {
.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000)
.perform((done) => {
// const filtered = plugins.filter(plugin => plugin !== 'testremixIde') // remove this when localplugin bug is resolved
plugins.forEach(plugin => {
if (plugin !== testData.pluginName) {
if (plugin !== testData.pluginName && plugin !== 'testremixIde') {
browser.waitForElementVisible(`[plugin="${plugin}"`)
}
})

@ -34,6 +34,13 @@ const sources = [
},
{
'test_import_node_modules_with_github_import.sol': { content: 'import "openzeppelin-solidity/contracts/sample.sol";' }
},
{
'test_static_analysis_with_remixd_and_hardhat.sol': {
content: `
import "hardhat/console.sol";
contract test5 { function get () public returns (uint) { return 8; }}`
}
}
]
@ -71,6 +78,21 @@ module.exports = {
.setSolidityCompilerVersion('soljson-v0.8.0+commit.c7dfd78e.js') // open-zeppelin moved to pragma ^0.8.0
.testContracts('test_import_node_modules_with_github_import.sol', sources[4]['test_import_node_modules_with_github_import.sol'], ['ERC20', 'test11'])
},
'Static Analysis run with remixd': function (browser) {
browser.testContracts('test_static_analysis_with_remixd_and_hardhat.sol', sources[5]['test_static_analysis_with_remixd_and_hardhat.sol'], ['test5'])
.clickLaunchIcon('solidityStaticAnalysis')
.click('#staticanalysisButton button')
.waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () {
browser
.click('[data-id="staticAnalysisModuleMiscellaneous1"')
.waitForElementPresent('.highlightLine15', 60000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(
'function _sendLogPayload(bytes memory payload) private view {') !== -1,
'code has not been loaded')
})
})
},
'Run git status': '' + function (browser) {
browser
@ -82,7 +104,7 @@ module.exports = {
'Close Remixd': function (browser) {
browser
.clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_remixd"] button')
.scrollAndClick('#pluginManager *[data-id="pluginManagerComponentDeactivateButtonremixd"]')
.end()
}
}
@ -99,10 +121,11 @@ function runTests (browser: NightwatchBrowser) {
.waitForElementVisible('#icon-panel', 2000)
.clickLaunchIcon('filePanel')
.clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_remixd"] button')
.scrollAndClick('#pluginManager *[data-id="pluginManagerComponentActivateButtonremixd"]')
.waitForElementVisible('#modal-footer-ok', 2000)
.pause(2000)
.click('#modal-footer-ok')
// .click('*[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('[data-path="folder1"]')
.click('[data-path="folder1"]')

@ -71,13 +71,14 @@ module.exports = {
'Should load using URL compiler params': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js')
.url('http://127.0.0.1:8080/#optimize=true&runs=300&autoCompile=true&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js')
.refresh()
.pause(5000)
.clickLaunchIcon('solidity')
.assert.containsText('#versionSelector option[data-id="selected"]', '0.7.4+commit.3f05b770')
.assert.containsText('#evmVersionSelector option[data-id="selected"]', 'istanbul')
.verify.elementPresent('#optimize:checked')
.verify.elementPresent('#autoCompile:checked')
.verify.attributeEquals('#runs', 'value', '300')
},

@ -27,7 +27,6 @@ module.exports = {
.click('*[id="menuitemdeactivate"]')
.click('*[data-id="verticalIconsKindsettings"]')
.click('*[data-id="verticalIconsKindpluginManager"]')
.scrollInto('*[data-id="pluginManagerComponentActivateButtondebugger"]')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugger"]')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugPlugin"]')
}
}

@ -5,3 +5,4 @@ rm -rf temp_publish_docker
mkdir temp_publish_docker
cp -r $FILES_TO_PACKAGE temp_publish_docker
ls
mv temp_publish_docker/production.index.html temp_publish_docker/index.html

@ -14,6 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md
cp -r $FILES_TO_PACKAGE "./"
rm -rf dist
ls
mv production.index.html index.html
FILES_TO_DEPLOY="assets index.html main.js polyfills.js favicon.ico"
# ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY

@ -14,6 +14,7 @@ echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md
cp -r $FILES_TO_PACKAGE "./"
rm -rf dist
ls
mv production.index.html index.html
FILES_TO_DEPLOY="assets index.html main.js polyfills.js favicon.ico"
# ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY

@ -13,6 +13,8 @@ echo "Built website from \`$SHA\`. See https://github.com/ethereum/remix-ide/ fo
echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md
cp -r $FILES_TO_PACKAGE "./"
rm -rf dist
ls
mv production.index.html index.html
FILES_TO_DEPLOY="assets index.html main.js polyfills.js"
# ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY

@ -495,8 +495,13 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
console.log(e)
}
// If plugins are loaded from the URL params, we focus on the last one.
if (pluginLoader.current === 'queryParams' && workspace.length > 0) menuicons.select(workspace[workspace.length - 1])
if (params.code) {
// if code is given in url we focus on solidity plugin
menuicons.select('solidity')
} else {
// If plugins are loaded from the URL params, we focus on the last one.
if (pluginLoader.current === 'queryParams' && workspace.length > 0) menuicons.select(workspace[workspace.length - 1])
}
if (params.call) {
const callDetails = params.call.split('//')

@ -1,129 +0,0 @@
/* global localStorage */
const yo = require('yo-yo')
const modalDialog = require('../ui/modaldialog')
const defaultProfile = {
methods: [],
location: 'sidePanel',
type: 'iframe'
}
module.exports = class LocalPlugin {
/**
* Open a modal to create a local plugin
* @param {Profile[]} plugins The list of the plugins in the store
* @returns {Promise<{api: any, profile: any}>} A promise with the new plugin profile
*/
open (plugins) {
this.profile = JSON.parse(localStorage.getItem('plugins/local')) || defaultProfile
return new Promise((resolve, reject) => {
const onValidation = () => {
try {
const profile = this.create()
resolve(profile)
} catch (err) {
reject(err)
}
}
modalDialog('Local Plugin', this.form(),
{ fn: () => onValidation() },
{ fn: () => resolve() }
)
})
}
/**
* Create the object to add to the plugin-list
*/
create () {
const profile = {
icon: 'assets/img/localPlugin.webp',
methods: [],
location: 'sidePanel',
type: 'iframe',
...this.profile,
hash: `local-${this.profile.name}`
}
if (!profile.location) throw new Error('Plugin should have a location')
if (!profile.name) throw new Error('Plugin should have a name')
if (!profile.url) throw new Error('Plugin should have an URL')
localStorage.setItem('plugins/local', JSON.stringify(profile))
return profile
}
updateName ({ target }) {
this.profile.name = target.value
}
updateUrl ({ target }) {
this.profile.url = target.value
}
updateDisplayName ({ target }) {
this.profile.displayName = target.value
}
updateProfile (key, e) {
this.profile[key] = e.target.value
}
updateMethods ({ target }) {
if (target.value) {
try {
this.profile.methods = target.value.split(',')
} catch (e) {}
}
}
/** The form to create a local plugin */
form () {
const name = this.profile.name || ''
const url = this.profile.url || ''
const displayName = this.profile.displayName || ''
const methods = (this.profile.methods && this.profile.methods.join(',')) || ''
const radioSelection = (key, label, message) => {
return this.profile[key] === label
? yo`<div class="radio">
<input class="form-check-input" type="radio" name="${key}" onclick="${e => this.updateProfile(key, e)}" value="${label}" id="${label}" data-id="localPluginRadioButton${label}" checked="checked" />
<label class="form-check-label" for="${label}">${message}</label>
</div>`
: yo`<div class="radio">
<input class="form-check-input" type="radio" name="${key}" onclick="${e => this.updateProfile(key, e)}" value="${label}" id="${label}" data-id="localPluginRadioButton${label}" />
<label class="form-check-label" for="${label}">${message}</label>
</div>`
}
return yo`
<form id="local-plugin-form">
<div class="form-group">
<label for="plugin-name">Plugin Name <small>(required)</small></label>
<input class="form-control" onchange="${e => this.updateName(e)}" value="${name}" id="plugin-name" data-id="localPluginName" placeholder="Should be camelCase">
</div>
<div class="form-group">
<label for="plugin-displayname">Display Name</label>
<input class="form-control" onchange="${e => this.updateDisplayName(e)}" value="${displayName}" id="plugin-displayname" data-id="localPluginDisplayName" placeholder="Name in the header">
</div>
<div class="form-group">
<label for="plugin-methods">Api (comma separated list of methods name)</label>
<input class="form-control" onchange="${e => this.updateMethods(e)}" value="${methods}" id="plugin-methods" data-id="localPluginMethods" placeholder="Name in the header">
</div>
<div class="form-group">
<label for="plugin-url">Url <small>(required)</small></label>
<input class="form-control" onchange="${e => this.updateUrl(e)}" value="${url}" id="plugin-url" data-id="localPluginUrl" placeholder="ex: https://localhost:8000">
</div>
<h6>Type of connection <small>(required)</small></h6>
<div class="form-check form-group">
${radioSelection('type', 'iframe', 'Iframe')}
${radioSelection('type', 'ws', 'Websocket')}
</div>
<h6>Location in remix <small>(required)</small></h6>
<div class="form-check form-group">
${radioSelection('location', 'sidePanel', 'Side Panel')}
${radioSelection('location', 'mainPanel', 'Main Panel')}
${radioSelection('location', 'none', 'None')}
</div>
</form>`
}
}

@ -1,73 +1,11 @@
import { IframePlugin, ViewPlugin, WebsocketPlugin } from '@remixproject/engine-web'
import { ViewPlugin } from '@remixproject/engine-web'
import { PluginManagerSettings } from './plugin-manager-settings'
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import {RemixUiPluginManager} from '@remix-ui/plugin-manager' // eslint-disable-line
import * as packageJson from '../../../../../package.json'
const yo = require('yo-yo')
const csjs = require('csjs-inject')
const EventEmitter = require('events')
const LocalPlugin = require('./local-plugin')
const addToolTip = require('../ui/tooltip')
const _paq = window._paq = window._paq || []
const css = csjs`
.pluginSearch {
display: flex;
flex-direction: column;
align-items: center;
background-color: var(--light);
padding: 10px;
position: sticky;
top: 0;
z-index: 2;
margin-bottom: 0px;
}
.pluginSearchInput {
height: 38px;
}
.pluginSearchButton {
font-size: 13px;
}
.displayName {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.pluginIcon {
height: 0.7rem;
width: 0.7rem;
filter: invert(0.5);
}
.description {
font-size: 13px;
line-height: 18px;
}
.descriptiontext {
display: block;
}
.descriptiontext:first-letter {
text-transform: uppercase;
}
.row {
display: flex;
flex-direction: row;
}
.isStuck {
background-color: var(--primary);
color:
}
.versionWarning {
padding: 4px;
margin: 0 8px;
font-weight: 700;
font-size: 9px;
line-height: 12px;
text-transform: uppercase;
cursor: default;
border: 1px solid;
border-radius: 2px;
}
`
const profile = {
name: 'pluginManager',
displayName: 'Plugin manager',
@ -84,112 +22,86 @@ const profile = {
class PluginManagerComponent extends ViewPlugin {
constructor (appManager, engine) {
super(profile)
this.event = new EventEmitter()
this.appManager = appManager
this.views = {
root: null,
items: {}
}
this.localPlugin = new LocalPlugin()
this.filter = ''
this.appManager.event.on('activate', () => { this.reRender() })
this.appManager.event.on('deactivate', () => { this.reRender() })
this.engine = engine
this.engine.event.on('onRegistration', () => { this.reRender() })
this.pluginManagerSettings = new PluginManagerSettings()
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'pluginManager')
this.filter = ''
this.activePlugins = []
this.inactivePlugins = []
this.activeProfiles = this.appManager.actives
this._paq = _paq
this.listenOnEvent()
}
/**
* Checks and returns true or false if plugin name
* passed in exists in the actives string array in
* RemixAppManager
* @param {string} name name of Plugin
*/
isActive (name) {
return this.appManager.actives.includes(name)
}
/**
* Delegates to method activatePlugin in
* RemixAppManager to enable plugin activation
* @param {string} name name of Plugin
*/
activateP (name) {
this.appManager.activatePlugin(name)
_paq.push(['trackEvent', 'manager', 'activate', name])
}
/**
* Takes the name of a local plugin and does both
* activation and registration
* @param {Profile} pluginName
* @returns {void}
*/
async activateAndRegisterLocalPlugin (localPlugin) {
if (localPlugin) {
this.engine.register(localPlugin)
this.appManager.activatePlugin(localPlugin.profile.name)
this.getAndFilterPlugins()
localStorage.setItem('plugins/local', JSON.stringify(localPlugin.profile))
}
}
/**
* Calls and triggers the event deactivatePlugin
* with with manager permission passing in the name
* of the plugin
* @param {string} name name of Plugin
*/
deactivateP (name) {
this.call('manager', 'deactivatePlugin', name)
_paq.push(['trackEvent', 'manager', 'deactivate', name])
}
renderItem (profile) {
const displayName = (profile.displayName) ? profile.displayName : profile.name
const doclink = profile.documentation ? yo`<a href="${profile.documentation}" class="px-1" title="link to documentation" target="_blank"><i aria-hidden="true" class="fas fa-book"></i></a>`
: yo``
// Check version of the plugin
let versionWarning
// Alpha
if (profile.version && profile.version.match(/\b(\w*alpha\w*)\b/g)) {
versionWarning = yo`<small title="Version Alpha" class="${css.versionWarning} plugin-version">alpha</small>`
}
// Beta
if (profile.version && profile.version.match(/\b(\w*beta\w*)\b/g)) {
versionWarning = yo`<small title="Version Beta" class="${css.versionWarning} plugin-version">beta</small>`
}
const activationButton = this.isActive(profile.name)
? yo`
<button
onclick="${() => this.deactivateP(profile.name)}"
class="btn btn-secondary btn-sm" data-id="pluginManagerComponentDeactivateButton${profile.name}"
>
Deactivate
</button>
`
: yo`
<button
onclick="${() => this.activateP(profile.name)}"
class="btn btn-success btn-sm" data-id="pluginManagerComponentActivateButton${profile.name}"
>
Activate
</button>`
return yo`
<article id="remixPluginManagerListItem_${profile.name}" class="list-group-item py-1 mb-1 plugins-list-group-item" title="${displayName}" >
<div class="${css.row} justify-content-between align-items-center mb-2">
<h6 class="${css.displayName} plugin-name">
<div>
${displayName}
${doclink}
${versionWarning}
</div>
${activationButton}
</h6>
</div>
<div class="${css.description} d-flex text-body plugin-text mb-2">
<img src="${profile.icon}" class="mr-1 mt-1 ${css.pluginIcon}" />
<span class="${css.descriptiontext}">${profile.description}</span>
</div>
</article>
`
onActivation () {
this.renderComponent()
}
/***************
* SUB-COMPONENT
*/
/**
* Add a local plugin to the list of plugins
*/
async openLocalPlugin () {
try {
const profile = await this.localPlugin.open(this.appManager.getAll())
if (!profile) return
if (this.appManager.getIds().includes(profile.name)) {
throw new Error('This name has already been used')
}
const plugin = profile.type === 'iframe' ? new IframePlugin(profile) : new WebsocketPlugin(profile)
this.engine.register(plugin)
await this.appManager.activatePlugin(plugin.name)
} catch (err) {
// TODO : Use an alert to handle this error instead of a console.log
console.log(`Cannot create Plugin : ${err.message}`)
addToolTip(`Cannot create Plugin : ${err.message}`)
}
renderComponent () {
ReactDOM.render(
<RemixUiPluginManager
pluginComponent={this}
pluginManagerSettings={this.pluginManagerSettings}
/>,
this.htmlElement)
}
render () {
// Filtering helpers
return this.htmlElement
}
getAndFilterPlugins (filter) {
this.filter = typeof filter === 'string' ? filter.toLowerCase() : this.filter
const isFiltered = (profile) => (profile.displayName ? profile.displayName : profile.name).toLowerCase().includes(this.filter)
const isNotRequired = (profile) => !this.appManager.isRequired(profile.name)
const isNotDependent = (profile) => !this.appManager.isDependent(profile.name)
@ -199,71 +111,35 @@ class PluginManagerComponent extends ViewPlugin {
const nameB = ((profileB.displayName) ? profileB.displayName : profileB.name).toUpperCase()
return (nameA < nameB) ? -1 : (nameA > nameB) ? 1 : 0
}
// Filter all active and inactive modules that are not required
const { actives, inactives } = this.appManager.getAll()
const activatedPlugins = []
const deactivatedPlugins = []
const tempArray = this.appManager.getAll()
.filter(isFiltered)
.filter(isNotRequired)
.filter(isNotDependent)
.filter(isNotHome)
.sort(sortByName)
.reduce(({ actives, inactives }, profile) => {
return this.isActive(profile.name)
? { actives: [...actives, profile], inactives }
: { inactives: [...inactives, profile], actives }
}, { actives: [], inactives: [] })
const activeTile = actives.length !== 0
? yo`
<nav class="plugins-list-header justify-content-between navbar navbar-expand-lg bg-light navbar-light align-items-center">
<span class="navbar-brand plugins-list-title">Active Modules</span>
<span class="badge badge-primary" data-id="pluginManagerComponentActiveTilesCount">${actives.length}</span>
</nav>`
: ''
const inactiveTile = inactives.length !== 0
? yo`
<nav class="plugins-list-header justify-content-between navbar navbar-expand-lg bg-light navbar-light align-items-center">
<span class="navbar-brand plugins-list-title h6 mb-0 mr-2">Inactive Modules</span>
<span class="badge badge-primary" style = "cursor: default;" data-id="pluginManagerComponentInactiveTilesCount">${inactives.length}</span>
</nav>`
: ''
const settings = new PluginManagerSettings().render()
const rootView = yo`
<div id='pluginManager' data-id="pluginManagerComponentPluginManager">
<header class="form-group ${css.pluginSearch} plugins-header py-3 px-4 border-bottom" data-id="pluginManagerComponentPluginManagerHeader">
<input onkeyup="${e => this.filterPlugins(e)}" class="${css.pluginSearchInput} form-control" placeholder="Search" data-id="pluginManagerComponentSearchInput">
<button onclick="${_ => this.openLocalPlugin()}" class="${css.pluginSearchButton} btn bg-transparent text-dark border-0 mt-2 text-underline" data-id="pluginManagerComponentPluginSearchButton">
Connect to a Local Plugin
</button>
</header>
<section data-id="pluginManagerComponentPluginManagerSection">
${activeTile}
<div class="list-group list-group-flush plugins-list-group" data-id="pluginManagerComponentActiveTile">
${actives.map(profile => this.renderItem(profile))}
</div>
${inactiveTile}
<div class="list-group list-group-flush plugins-list-group" data-id="pluginManagerComponentInactiveTile">
${inactives.map(profile => this.renderItem(profile))}
</div>
</section>
${settings}
</div>
`
if (!this.views.root) this.views.root = rootView
return rootView
}
reRender () {
if (this.views.root) {
yo.update(this.views.root, this.render())
}
}
filterPlugins ({ target }) {
this.filter = target.value.toLowerCase()
this.reRender()
tempArray.forEach(profile => {
if (this.appManager.actives.includes(profile.name)) {
activatedPlugins.push(profile)
} else {
deactivatedPlugins.push(profile)
}
})
this.activePlugins = activatedPlugins
this.inactivePlugins = deactivatedPlugins
this.renderComponent()
}
listenOnEvent () {
this.engine.event.on('onRegistration', () => this.renderComponent())
this.appManager.event.on('activate', () => {
this.getAndFilterPlugins()
})
this.appManager.event.on('deactivate', () => {
this.getAndFilterPlugins()
})
}
}

@ -2,8 +2,8 @@ const yo = require('yo-yo')
const csjs = require('csjs-inject')
const modalDialog = require('../ui/modaldialog')
const css = csjs`
.permissions {
const css = csjs`
.remixui_permissions {
position: sticky;
bottom: 0;
display: flex;
@ -44,9 +44,12 @@ const css = csjs`
`
export class PluginManagerSettings {
openDialog () {
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() }
@ -60,6 +63,8 @@ export class PluginManagerSettings {
/** 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) {
@ -74,6 +79,8 @@ export class PluginManagerSettings {
/** 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())

@ -22,7 +22,7 @@ const profile = {
icon: 'assets/img/fileManager.webp',
permission: true,
version: packageJson.version,
methods: ['file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName'],
methods: ['file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath'],
kind: 'file-system'
}
const errorMsg = {
@ -168,14 +168,11 @@ class FileManager extends Plugin {
* @returns {void}
*/
async open (path) {
try {
path = this.limitPluginScope(path)
await this._handleExists(path, `Cannot open file ${path}`)
await this._handleIsFile(path, `Cannot open file ${path}`)
return this.openFile(path)
} catch (e) {
throw new Error(e)
}
path = this.limitPluginScope(path)
path = this.getPathFromUrl(path).file
await this._handleExists(path, `Cannot open file ${path}`)
await this._handleIsFile(path, `Cannot open file ${path}`)
await this.openFile(path)
}
/**
@ -538,6 +535,36 @@ class FileManager extends Plugin {
}
}
/**
* Try to resolve the given file path (the actual path in the file system)
* e.g if it's specified a github link, npm library, or any external content,
* it returns the actual path where the content can be found.
* @param {string} file url we are trying to resolve
* @returns {{ string, provider }} file path resolved and its provider.
*/
getPathFromUrl (file) {
const provider = this.fileProviderOf(file)
if (!provider) throw new Error(`no provider for ${file}`)
return {
file: provider.getPathFromUrl(file) || file, // in case an external URL is given as input, we resolve it to the right internal path
provider
}
}
/**
* Try to resolve the given file URl. opposite of getPathFromUrl
* @param {string} file path we are trying to resolve
* @returns {{ string, provider }} file url resolved and its provider.
*/
getUrlFromPath (file) {
const provider = this.fileProviderOf(file)
if (!provider) throw new Error(`no provider for ${file}`)
return {
file: provider.getUrlFromPath(file) || file, // in case an external URL is given as input, we resolve it to the right internal path
provider
}
}
removeTabsOf (provider) {
for (var tab in this.openedFiles) {
if (this.fileProviderOf(tab).type === provider.type) {
@ -566,33 +593,37 @@ class FileManager extends Plugin {
this.events.emit('noFileSelected')
}
openFile (file) {
const _openFile = (file) => {
async openFile (file) {
if (!file) {
this.emit('noFileSelected')
this.events.emit('noFileSelected')
} else {
this.saveCurrentFile()
const provider = this.fileProviderOf(file)
if (!provider) return console.error(`no provider for ${file}`)
file = provider.getPathFromUrl(file) || file // in case an external URL is given as input, we resolve it to the right internal path
const resolved = this.getPathFromUrl(file)
file = resolved.file
const provider = resolved.provider
this._deps.config.set('currentFile', file)
this.openedFiles[file] = file
provider.get(file, (error, content) => {
if (error) {
console.log(error)
} else {
if (provider.isReadOnly(file)) {
this.editor.openReadOnly(file, content)
} else {
this.editor.open(file, content)
}
// TODO: Only keep `this.emit` (issue#2210)
this.emit('currentFileChanged', file)
this.events.emit('currentFileChanged', file)
}
})
}
if (file) return _openFile(file)
else {
this.emit('noFileSelected')
this.events.emit('noFileSelected')
await (() => {
return new Promise((resolve, reject) => {
provider.get(file, (error, content) => {
if (error) {
console.log(error)
reject(error)
} else {
if (provider.isReadOnly(file)) {
this.editor.openReadOnly(file, content)
} else {
this.editor.open(file, content)
}
// TODO: Only keep `this.emit` (issue#2210)
this.emit('currentFileChanged', file)
this.events.emit('currentFileChanged', file)
resolve()
}
})
})
})()
}
}

@ -13,17 +13,18 @@ class FileProvider {
this.type = name
this.providerExternalsStorage = new Storage('providerExternals:')
this.externalFolders = [this.type + '/swarm', this.type + '/ipfs', this.type + '/github', this.type + '/gists', this.type + '/https']
this.reverseKey = this.type + '-reverse-'
}
addNormalizedName (path, url) {
this.providerExternalsStorage.set(this.type + '/' + path, url)
this.providerExternalsStorage.set('reverse-' + url, this.type + '/' + path)
this.providerExternalsStorage.set(this.reverseKey + url, this.type + '/' + path)
}
removeNormalizedName (path) {
const value = this.providerExternalsStorage.get(path)
this.providerExternalsStorage.remove(path)
this.providerExternalsStorage.remove('reverse-' + value)
this.providerExternalsStorage.remove(this.reverseKey + value)
}
normalizedNameExists (path) {
@ -35,7 +36,12 @@ class FileProvider {
}
getPathFromUrl (url) {
return this.providerExternalsStorage.get('reverse-' + url)
return this.providerExternalsStorage.get(this.reverseKey + url)
}
getUrlFromPath (path) {
if (!path.startsWith(this.type)) path = this.type + '/' + path
return this.providerExternalsStorage.get(path)
}
isExternalFolder (path) {

@ -87,14 +87,6 @@ module.exports = class RemixDProvider extends FileProvider {
})
}
getNormalizedName (path) {
return path
}
getPathFromUrl (path) {
return path
}
get (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
var unprefixedpath = this.removePrefix(path)

@ -319,7 +319,7 @@ class ContractDropdownUI {
try {
contractMetadata = await this.runView.call('compilerMetadata', 'deployMetadataOf', selectedContract.name, selectedContract.contract.file)
} catch (error) {
return statusCb(`creation of ${selectedContract.name} errored: ` + (error.message ? error.message : error))
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
}
const compilerContracts = this.dropdownLogic.getCompilerContracts()

@ -89,7 +89,7 @@ function confirmDialog (tx, network, amount, gasEstimation, newGasPriceCb, initi
<div class="align-items-center my-1" title="Represents the part of the tx fee that goes to the miner.">
<div class='d-flex'>
<span class="text-dark mr-2 text-nowrap">Max Priority fee:</span>
<input class="form-control mr-1 text-right" style='height: 1.2rem; width: 6rem;' value="0" id='maxpriorityfee' />
<input class="form-control mr-1 text-right" style='height: 1.2rem; width: 6rem;' value="1" id='maxpriorityfee' />
<span title="visit https://ethgasstation.info for current gas price info.">Gwei</span>
</div>
</div>

@ -108,7 +108,7 @@ class Blockchain extends Plugin {
const { continueCb, promptCb, statusCb, finalCb } = callbacks
const constructor = selectedContract.getConstructorInterface()
txFormat.buildData(selectedContract.name, selectedContract.object, compilerContracts, true, constructor, args, (error, data) => {
if (error) return statusCb(`creation of ${selectedContract.name} errored: ` + error)
if (error) return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
statusCb(`creation of ${selectedContract.name} pending...`)
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb)
@ -122,7 +122,7 @@ class Blockchain extends Plugin {
const { continueCb, promptCb, statusCb, finalCb } = callbacks
const constructor = selectedContract.getConstructorInterface()
txFormat.encodeConstructorCallAndLinkLibraries(selectedContract.object, args, constructor, contractMetadata.linkReferences, selectedContract.bytecodeLinkReferences, (error, data) => {
if (error) return statusCb(`creation of ${selectedContract.name} errored: ` + (error.message ? error.message : error))
if (error) return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
statusCb(`creation of ${selectedContract.name} pending...`)
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb)
@ -139,7 +139,7 @@ class Blockchain extends Plugin {
this.runTx({ data: data, useCall: false }, confirmationCb, continueCb, promptCb,
(error, txResult, address) => {
if (error) {
return finalCb(`creation of ${selectedContract.name} errored: ${(error.message ? error.message : error)}`)
return finalCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error}`)
}
if (txResult.receipt.status === false || txResult.receipt.status === '0x0') {
return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
@ -265,7 +265,7 @@ class Blockchain extends Plugin {
// contractsDetails is used to resolve libraries
txFormat.buildData(contractName, contractAbi, {}, false, funABI, callType, (error, data) => {
if (error) {
return logCallback(`${logMsg} errored: ${error} `)
return logCallback(`${logMsg} errored: ${error.message ? error.message : error}`)
}
if (!lookupOnly) {
logCallback(`${logMsg} pending ... `)
@ -282,7 +282,7 @@ class Blockchain extends Plugin {
const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure'
this.runTx({ to: address, data, useCall }, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => {
if (error) {
return logCallback(`${logMsg} errored: ${error} `)
return logCallback(`${logMsg} errored: ${error.message ? error.message : error}`)
}
if (lookupOnly) {
outputCb(returnValue)

@ -1,7 +1,7 @@
export class FramingService {
constructor (sidePanel, verticalIcon, mainView, resizeFeature) {
constructor (sidePanel, verticalIcons, mainView, resizeFeature) {
this.sidePanel = sidePanel
this.verticalIcon = verticalIcon
this.verticalIcons = verticalIcons
this.mainPanel = mainView.getAppPanel()
this.mainView = mainView
this.resizeFeature = resizeFeature
@ -18,16 +18,16 @@ export class FramingService {
this.resizeFeature.showPanel()
})
this.verticalIcon.select('filePanel')
this.verticalIcons.select('filePanel')
document.addEventListener('keypress', (e) => {
if (e.shiftKey && e.ctrlKey) {
if (e.code === 'KeyF') { // Ctrl+Shift+F
this.verticalIcon.select('filePanel')
this.verticalIcons.select('filePanel')
} else if (e.code === 'KeyA') { // Ctrl+Shift+A
this.verticalIcon.select('pluginManager')
this.verticalIcons.select('pluginManager')
} else if (e.code === 'KeyS') { // Ctrl+Shift+S
this.verticalIcon.select('settings')
this.verticalIcons.select('settings')
}
e.preventDefault()
}

@ -0,0 +1,117 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!--
The MIT License (MIT)
Copyright (c) 2014, 2015, the individual contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Remix - Ethereum IDE</title>
<link rel="stylesheet" href="assets/css/pygment_trac.css">
<link rel="icon" type="x-icon" href="assets/img/icon.png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/4.1.0/introjs.min.css">
<script src="assets/js/browserfs.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<!-- Matomo -->
<script type="text/javascript">
const domains = {
'remix-alpha.ethereum.org': 27,
'remix-beta.ethereum.org': 25,
'remix.ethereum.org': 23
}
if (domains[window.location.hostname]) {
var _paq = window._paq = window._paq || []
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['disableCookies']);
_paq.push(['enableJSErrorTracking']);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://matomo.ethereum.org/";
_paq.push(['setTrackerUrl', u+'matomo.php'])
_paq.push(['setSiteId', domains[window.location.hostname]])
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]
g.type='text/javascript'; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s)
})()
}
</script>
<!-- End Matomo Code -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/2.7.0/introjs.min.css">
</head>
<body>
<script>
function urlParams () {
var qs = window.location.hash.substr(1)
if (window.location.search.length > 0) {
// use legacy query params instead of hash
window.location.hash = window.location.search.substr(1)
window.location.search = ''
}
var params = {}
var parts = qs.split('&')
for (var x in parts) {
var keyValue = parts[x].split('=')
if (keyValue[0] !== '') {
params[keyValue[0]] = keyValue[1]
}
}
return params
}
const defaultVersion = '0.8.0'
let versionToLoad = urlParams().appVersion ? urlParams().appVersion : defaultVersion
let assets = {
'0.8.0': ['https://use.fontawesome.com/releases/v5.8.1/css/all.css', 'assets/css/pygment_trac.css'],
'0.7.7': ['assets/css/font-awesome.min.css', 'assets/css/pygment_trac.css']
}
let versions = {
'0.7.7': 'assets/js/0.7.7/app.js', // commit 7b5c7ae3de935e0ccc32eadfd83bf7349478491e
'0.8.0': 'main.js'
}
for (let k in assets[versionToLoad]) {
let app = document.createElement('link')
app.setAttribute('rel', 'stylesheet')
app.setAttribute('href', assets[versionToLoad][k])
if (assets[versionToLoad][k] === 'https://use.fontawesome.com/releases/v5.8.1/css/all.css') {
app.setAttribute('integrity', 'sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf')
app.setAttribute('crossorigin', 'anonymous')
}
document.head.appendChild(app)
}
window.onload = () => {
BrowserFS.install(window)
BrowserFS.configure({
fs: "LocalStorage"
}, function(e) {
if (e) console.log(e)
let app = document.createElement('script')
app.setAttribute('src', versions[versionToLoad])
document.body.appendChild(app)
window.remixFileSystem = require('fs')
})
}
</script>
<script src="polyfills.js" type="module"></script>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script>
<script type="text/javascript" src="assets/js/intro.min.js"></script>
</body>
</html>

@ -177,7 +177,7 @@ Before starting coding, we should ensure all devs / contributors are aware of:
# Coding best practices
- https://github.com/ethereum/remix-ide/blob/master/best-practices.md
- https://github.com/ethereum/remix-project/blob/master/best-practices.md
---

@ -2,7 +2,6 @@
import { Plugin } from '@remixproject/engine'
import { RemixURLResolver } from '@remix-project/remix-url-resolver'
const remixTests = require('@remix-project/remix-tests')
const async = require('async')
const profile = {
name: 'contentImport',
@ -88,21 +87,23 @@ export class CompilerImports extends Plugin {
}
}
importExternal (url, targetPath, cb) {
this.import(url,
// TODO: handle this event
(loadingMsg) => { this.emit('message', loadingMsg) },
async (error, content, cleanUrl, type, url) => {
if (error) return cb(error)
try {
const provider = await this.call('fileManager', 'getProviderOf', null)
const path = targetPath || type + '/' + cleanUrl
if (provider) provider.addExternal('.deps/' + path, content, url)
} catch (err) {
}
cb(null, content)
}, null)
importExternal (url, targetPath) {
return new Promise((resolve, reject) => {
this.import(url,
// TODO: handle this event
(loadingMsg) => { this.emit('message', loadingMsg) },
async (error, content, cleanUrl, type, url) => {
if (error) return reject(error)
try {
const provider = await this.call('fileManager', 'getProviderOf', null)
const path = targetPath || type + '/' + cleanUrl
if (provider) provider.addExternal('.deps/' + path, content, url)
} catch (err) {
console.error(err)
}
resolve(content)
}, null)
})
}
/**
@ -115,66 +116,58 @@ export class CompilerImports extends Plugin {
* @param {String} targetPath - (optional) internal path where the content should be saved to
* @returns {Promise} - string content
*/
resolveAndSave (url, targetPath) {
return new Promise((resolve, reject) => {
if (url.indexOf('remix_tests.sol') !== -1) resolve(remixTests.assertLibCode)
this.call('fileManager', 'getProviderOf', url).then((provider) => {
if (provider) {
if (provider.type === 'localhost' && !provider.isConnected()) {
return reject(new Error(`file provider ${provider.type} not available while trying to resolve ${url}`))
}
provider.exists(url).then(exist => {
/*
if the path is absolute and the file does not exist, we can stop here
Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop.
*/
if (!exist && url.startsWith('browser/')) return reject(new Error(`not found ${url}`))
if (!exist && url.startsWith('localhost/')) return reject(new Error(`not found ${url}`))
if (exist) {
return provider.get(url, (error, content) => {
if (error) return reject(error)
resolve(content)
})
}
async resolveAndSave (url, targetPath) {
if (url.indexOf('remix_tests.sol') !== -1) return remixTests.assertLibCode
try {
const provider = await this.call('fileManager', 'getProviderOf', url)
if (provider) {
if (provider.type === 'localhost' && !provider.isConnected()) {
throw new Error(`file provider ${provider.type} not available while trying to resolve ${url}`)
}
const exist = await provider.exists(url)
/*
if the path is absolute and the file does not exist, we can stop here
Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop.
*/
if (!exist && url.startsWith('browser/')) throw new Error(`not found ${url}`)
if (!exist && url.startsWith('localhost/')) throw new Error(`not found ${url}`)
// try to resolve localhost modules (aka truffle imports) - e.g from the node_modules folder
this.call('fileManager', 'getProviderByName', 'localhost').then((localhostProvider) => {
if (localhostProvider.isConnected()) {
var splitted = /([^/]+)\/(.*)$/g.exec(url)
return async.tryEach([
(cb) => { this.resolveAndSave('localhost/installed_contracts/' + url, null).then((result) => cb(null, result)).catch((error) => cb(error.message)) },
// eslint-disable-next-line standard/no-callback-literal
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], null).then((result) => cb(null, result)).catch((error) => cb(error.message)) } },
(cb) => { this.resolveAndSave('localhost/node_modules/' + url, null).then((result) => cb(null, result)).catch((error) => cb(error.message)) },
// eslint-disable-next-line standard/no-callback-literal
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], null).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }],
(error, result) => {
if (error) {
return this.importExternal(url, targetPath, (error, content) => {
if (error) return reject(error)
resolve(content)
})
}
resolve(result)
})
}
this.importExternal(url, targetPath, (error, content) => {
if (exist) {
const content = await (() => {
return new Promise((resolve, reject) => {
provider.get(url, (error, content) => {
if (error) return reject(error)
resolve(content)
})
})
}).catch(error => {
return reject(error)
})
})()
return content
} else {
const localhostProvider = await this.call('fileManager', 'getProviderByName', 'localhost')
if (localhostProvider.isConnected()) {
const splitted = /([^/]+)\/(.*)$/g.exec(url)
const possiblePaths = ['localhost/installed_contracts/' + url]
if (splitted) possiblePaths.push('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2])
possiblePaths.push('localhost/node_modules/' + url)
if (splitted) possiblePaths.push('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2])
for (const path of possiblePaths) {
try {
const content = await this.resolveAndSave(path, null)
if (content) {
localhostProvider.addNormalizedName(path.replace('localhost/', ''), url)
return content
}
} catch (e) {}
}
return await this.importExternal(url, targetPath)
}
return await this.importExternal(url, targetPath)
}
}).catch(() => {
// fallback to just resolving the file, it won't be saved in file manager
return this.importExternal(url, targetPath, (error, content) => {
if (error) return reject(error)
resolve(content)
})
})
})
}
} catch (e) {
throw new Error(`not found ${url}`)
}
}
}

@ -108,7 +108,7 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
deletePath(getPath())
break
default:
_paq.push(['trackEvent', 'fileExplorer', 'customAction', item.name])
_paq.push(['trackEvent', 'fileExplorer', 'customAction', `${item.id}/${item.name}`])
emit && emit({ ...item, path: [path] } as customAction)
break
}

@ -3,6 +3,10 @@ import { ModalDialogProps } from './types' // eslint-disable-line
import './remix-ui-modal-dialog.css'
declare global {
interface Window { testmode: boolean; }
}
export const ModalDialog = (props: ModalDialogProps) => {
const [state, setState] = useState({
toggleBtn: true
@ -21,7 +25,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
e.stopPropagation()
if (document.activeElement !== this) {
handleHide()
!window.testmode && handleHide()
}
}
}

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

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

@ -0,0 +1,7 @@
# remix-ui-plugin-manager
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-plugin-manager` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1 @@
export * from './lib/remix-ui-plugin-manager'

@ -0,0 +1,54 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React from 'react'
import '../remix-ui-plugin-manager.css'
interface PluginCardProps {
profile: any
buttonText: string
deactivatePlugin: (pluginName: string) => void
}
function ActivePluginCard ({
profile,
buttonText,
deactivatePlugin
}: PluginCardProps) {
return (
<div className="list-group list-group-flush plugins-list-group" data-id="pluginManagerComponentActiveTile">
<article className="list-group-item py-1 mb-1 plugins-list-group-item" title={profile.displayName || profile.name}>
<div className="remixui_row justify-content-between align-items-center mb-2">
<h6 className="remixui_displayName plugin-name">
<div>
{ profile.displayName || profile.name }
{ profile.documentation &&
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-book"/>
</a>
}
{ profile.version && profile.version.match(/\b(\w*alpha\w*)\b/g)
? <small title="Version Alpha" className="remixui_versionWarning plugin-version">alpha</small>
: profile.version && profile.version.match(/\b(\w*beta\w*)\b/g)
? <small title="Version Beta" className="remixui_versionWarning plugin-version">beta</small>
: null
}
</div>
{<button
onClick={() => {
deactivatePlugin(profile.name)
} }
className="btn btn-secondary btn-sm"
data-id={`pluginManagerComponentDeactivateButton${profile.name}`}
>
{buttonText}
</button>}
</h6>
</div>
<div className="remixui_description d-flex text-body plugin-text mb-2">
{profile.icon ? <img src={profile.icon} className="mr-1 mt-1 remixui_pluginIcon" alt="profile icon" /> : null}
<span className="remixui_descriptiontext">{profile.description}</span>
</div>
</article>
</div>
)
}
export default ActivePluginCard

@ -0,0 +1,36 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Profile } from '@remixproject/plugin-utils'
import React from 'react'
import { PluginManagerComponent } from '../../types'
import ActivePluginCard from './ActivePluginCard'
import ModuleHeading from './moduleHeading'
interface ActivePluginCardContainerProps {
pluginComponent: PluginManagerComponent
setActiveProfiles: React.Dispatch<React.SetStateAction<Profile<any>[]>>
activeProfiles: Profile[]
}
function ActivePluginCardContainer ({ pluginComponent }: ActivePluginCardContainerProps) {
const deactivatePlugin = (pluginName: string) => {
pluginComponent.deactivateP(pluginName)
}
return (
<React.Fragment>
{(pluginComponent.activePlugins && pluginComponent.activePlugins.length) ? <ModuleHeading headingLabel="Active Modules" count={pluginComponent.activePlugins.length} /> : null}
{pluginComponent.activePlugins && pluginComponent.activePlugins.map((profile, idx) => {
return (
<ActivePluginCard
buttonText="Deactivate"
profile={profile}
deactivatePlugin={deactivatePlugin}
key={idx}
/>
)
})
}
</React.Fragment>
)
}
export default ActivePluginCardContainer

@ -0,0 +1,59 @@
import { Profile } from '@remixproject/plugin-utils'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React from 'react'
import '../remix-ui-plugin-manager.css'
interface PluginCardProps {
profile: Profile & {
icon?: string
}
buttonText: string
activatePlugin: (plugin: string) => void
}
function InactivePluginCard ({
profile,
buttonText,
activatePlugin
}: PluginCardProps) {
return (
<div className="list-group list-group-flush plugins-list-group" data-id="pluginManagerComponentActiveTile">
<article className="list-group-item py-1 mb-1 plugins-list-group-item" title={profile.displayName || profile.name}>
<div className="remixui_row justify-content-between align-items-center mb-2">
<h6 className="remixui_displayName plugin-name">
<div>
{ profile.displayName || profile.name }
{ profile.documentation &&
<a href={profile.documentation} className="px-1" title="link to documentation" target="_blank" rel="noreferrer">
<i aria-hidden="true" className="fas fa-book"/>
</a>
}
{ profile.version && profile.version.match(/\b(\w*alpha\w*)\b/g)
? <small title="Version Alpha" className="remixui_versionWarning plugin-version">alpha</small>
: profile.version && profile.version.match(/\b(\w*beta\w*)\b/g)
? <small title="Version Beta" className="remixui_versionWarning plugin-version">beta</small>
: null
}
</div>
{
<button
onClick={() => {
activatePlugin(profile.name)
}}
className="btn btn-success btn-sm"
data-id={`pluginManagerComponentActivateButton${profile.name}`}
>
{buttonText}
</button>
}
</h6>
</div>
<div className="remixui_description d-flex text-body plugin-text mb-2">
{ profile.icon ? <img src={profile.icon} className="mr-1 mt-1 remixui_pluginIcon" alt="profile icon"/> : null }
<span className="remixui_descriptiontext">{profile.description}</span>
</div>
</article>
</div>
)
}
export default InactivePluginCard

@ -0,0 +1,47 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Profile } from '@remixproject/plugin-utils'
import React from 'react'
import { PluginManagerComponent, PluginManagerProfile } from '../../types'
import InactivePluginCard from './InactivePluginCard'
import ModuleHeading from './moduleHeading'
interface InactivePluginCardContainerProps {
pluginComponent: PluginManagerComponent
setInactiveProfiles: React.Dispatch<React.SetStateAction<Profile<any>[]>>
inactiveProfiles: Profile<any>[]
}
interface LocalPluginInterface {
profile: Partial<PluginManagerProfile>
activateService: {}
requestQueue: []
options: { queueTimeout: number }
id: number
pendingRequest: {}
listener: []
iframe: {}
}
function InactivePluginCardContainer ({ pluginComponent }: InactivePluginCardContainerProps) {
const activatePlugin = (pluginName: string) => {
pluginComponent.activateP(pluginName)
}
return (
<React.Fragment>
{(pluginComponent.inactivePlugins && pluginComponent.inactivePlugins.length) ? <ModuleHeading headingLabel="Inactive Modules" count={pluginComponent.inactivePlugins.length} /> : null}
{pluginComponent.inactivePlugins && pluginComponent.inactivePlugins.map((profile, idx) => {
return (
<InactivePluginCard
buttonText="Activate"
profile={profile}
key={idx}
activatePlugin={activatePlugin}
/>
)
})
}
</React.Fragment>
)
}
export default InactivePluginCardContainer

@ -0,0 +1,218 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useReducer, useState } from 'react'
import { ModalDialog } from '@remix-ui/modal-dialog'
import { Toaster } from '@remix-ui/toaster'
import { IframePlugin, WebsocketPlugin } from '@remixproject/engine-web'
import { localPluginReducerActionType, localPluginToastReducer } from '../reducers/pluginManagerReducer'
import { FormStateProps, PluginManagerComponent } from '../../types'
interface LocalPluginFormProps {
closeModal: () => void
visible: boolean
pluginManager: PluginManagerComponent
}
const initialState: FormStateProps = {
name: '',
displayName: '',
url: '',
type: 'iframe',
hash: '',
methods: [],
location: 'sidePanel'
}
const defaultProfile = {
methods: [],
location: 'sidePanel',
type: 'iframe',
name: '',
displayName: '',
url: '',
hash: ''
}
function LocalPluginForm ({ closeModal, visible, pluginManager }: LocalPluginFormProps) {
const [errorMsg, dispatchToastMsg] = useReducer(localPluginToastReducer, '')
const [name, setName] = useState<string>('')
const [displayName, setDisplayName] = useState<string>('')
const [url, setUrl] = useState<string>('')
const [type, setType] = useState<'iframe' | 'ws'>('iframe')
const [location, setLocation] = useState<'sidePanel' | 'mainPanel' | 'none'>('sidePanel')
const [methods, setMethods] = useState<string>('')
useEffect(() => {
const storagePlugin:FormStateProps = localStorage.getItem('plugins/local') ? JSON.parse(localStorage.getItem('plugins/local')) : defaultProfile
setName(storagePlugin.name)
setUrl(storagePlugin.url)
setLocation(storagePlugin.location as 'sidePanel' | 'mainPanel' | 'none')
setMethods(storagePlugin.methods)
setType(storagePlugin.type)
setDisplayName(storagePlugin.displayName)
}, [])
const handleModalOkClick = async () => {
try {
if (!name) throw new Error('Plugin should have a name')
if (pluginManager.appManager.getIds().includes(name)) {
throw new Error('This name has already been used')
}
if (!location) throw new Error('Plugin should have a location')
if (!url) throw new Error('Plugin should have an URL')
const newMethods = typeof methods === 'string' ? methods.split(',').filter(val => val) : []
const targetPlugin = {
name: name,
displayName: displayName,
description: '',
documentation: '',
events: [],
hash: '',
kind: '',
methods: newMethods,
url: url,
type: type,
location: location,
icon: 'assets/img/localPlugin.webp'
}
const localPlugin = type === 'iframe' ? new IframePlugin(initialState) : new WebsocketPlugin(initialState)
localPlugin.profile.hash = `local-${name}`
targetPlugin.description = localPlugin.profile.description !== undefined ? localPlugin.profile.description : ''
targetPlugin.events = localPlugin.profile.events !== undefined ? localPlugin.profile.events : []
targetPlugin.kind = localPlugin.profile.kind !== undefined ? localPlugin.profile.kind : ''
localPlugin.profile = { ...localPlugin.profile, ...targetPlugin }
pluginManager.activateAndRegisterLocalPlugin(localPlugin)
} catch (error) {
const action: localPluginReducerActionType = { type: 'show', payload: `${error.message}` }
dispatchToastMsg(action)
console.log(error)
}
}
return (
<><ModalDialog
handleHide={closeModal}
id="pluginManagerLocalPluginModalDialog"
hide={visible}
title="Local Plugin"
okLabel="OK"
okFn={ handleModalOkClick }
cancelLabel="Cancel"
cancelFn={closeModal}
>
<form id="local-plugin-form">
<div className="form-group">
<label htmlFor="plugin-name">Plugin Name <small>(required)</small></label>
<input
className="form-control"
onChange={e => setName(e.target.value)}
value={ name}
id="plugin-name"
data-id="localPluginName"
placeholder="Should be camelCase" />
</div>
<div className="form-group">
<label htmlFor="plugin-displayname">Display Name</label>
<input
className="form-control"
onChange={e => setDisplayName(e.target.value)}
value={ displayName }
id="plugin-displayname"
data-id="localPluginDisplayName"
placeholder="Name in the header" />
</div>
<div className="form-group">
<label htmlFor="plugin-methods">Api (comma separated list of methods name)</label>
<input
className="form-control"
onChange={e => setMethods(e.target.value)}
value={ methods }
id="plugin-methods"
data-id="localPluginMethods"
placeholder="Name in the header" />
</div>
<div className="form-group">
<label htmlFor="plugin-url">Url <small>(required)</small></label>
<input
className="form-control"
onChange={e => setUrl(e.target.value)}
value={ url }
id="plugin-url"
data-id="localPluginUrl"
placeholder="ex: https://localhost:8000" />
</div>
<h6>Type of connection <small>(required)</small></h6>
<div className="form-check form-group">
<div className="radio">
<input
className="form-check-input"
type="radio"
name="type"
value="iframe"
id="iframe"
data-id='localPluginRadioButtoniframe'
checked={type === 'iframe'}
onChange={(e) => setType(e.target.value as 'iframe' | 'ws')} />
<label className="form-check-label" htmlFor="iframe">Iframe</label>
</div>
<div className="radio">
<input
className="form-check-input"
type="radio"
name="type"
value="ws"
id="ws"
data-id='localPluginRadioButtonws'
checked={type === 'ws'}
onChange={(e) => setType(e.target.value as 'iframe' | 'ws')} />
<label className="form-check-label" htmlFor="ws">Websocket</label>
</div>
</div>
<h6>Location in remix <small>(required)</small></h6>
<div className="form-check form-group">
<div className="radio">
<input
className="form-check-input"
type="radio"
name="location"
value="sidePanel"
id="sidePanel"
data-id='localPluginRadioButtonsidePanel'
checked={location === 'sidePanel'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />
<label className="form-check-label" htmlFor="sidePanel">Side Panel</label>
</div>
<div className="radio">
<input
className="form-check-input"
type="radio"
name="location"
value="mainPanel"
id="mainPanel"
data-id='localPluginRadioButtonmainPanel'
checked={location === 'mainPanel'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />
<label className="form-check-label" htmlFor="mainPanel">Main Panel</label>
</div>
<div className="radio">
<input
className="form-check-input"
type="radio"
name="location"
value="none"
id="none"
data-id='localPluginRadioButtonnone'
checked={location === 'none'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} />
<label className="form-check-label" htmlFor="none">None</label>
</div>
</div>
</form>
</ModalDialog>
{errorMsg ? <Toaster message={errorMsg} /> : null}
</>
)
}
export default LocalPluginForm

@ -0,0 +1,20 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React from 'react'
interface ModuleHeadingProps {
headingLabel: string
count: number
}
function ModuleHeading ({ headingLabel, count }: ModuleHeadingProps) {
return (
<nav className="plugins-list-header justify-content-between navbar navbar-expand-lg bg-light navbar-light align-items-center">
<span className="navbar-brand plugins-list-title h6 mb-0 mr-2">{headingLabel}</span>
<span className="badge badge-primary" style={{ cursor: 'default' }} data-id="pluginManagerComponentInactiveTilesCount">
{count}
</span>
</nav>
)
}
export default ModuleHeading

@ -0,0 +1,144 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { Fragment, useState } from 'react'
/* eslint-disable-line */
import { ModalDialog } from '@remix-ui/modal-dialog'
import useLocalStorage from '../custom-hooks/useLocalStorage'
import { PluginPermissions } from '../../types'
interface PermissionSettingsProps {
pluginSettings: any
}
function PermisssionsSettings ({ pluginSettings }: PermissionSettingsProps) {
const [modalVisibility, setModalVisibility] = useState<boolean>(true)
const [permissions, setPermissions] = useLocalStorage<PluginPermissions>('plugins/permissions', {} as PluginPermissions)
const [permissionCache, setpermissionCache] = useState<PluginPermissions>()
const closeModal = () => setModalVisibility(true)
const openModal = () => {
const currentValue = JSON.parse(window.localStorage.getItem('plugins/permissions') || '{}')
setpermissionCache(currentValue)
setPermissions(currentValue)
setModalVisibility(!modalVisibility)
}
const cancel = () => {
setPermissions(permissionCache)
}
const getState = (targetPlugin:string, funcName:string, pluginName :string) => {
return permissions[targetPlugin][funcName][pluginName].allow
}
const handleCheckboxClick = (targetPlugin:string, funcName:string, pluginName :string) => {
setPermissions((permissions) => {
permissions[targetPlugin][funcName][pluginName].allow = !permissions[targetPlugin][funcName][pluginName].allow
return permissions
})
}
function clearFunctionPermission (targetPlugin:string, funcName:string, pluginName :string) {
setPermissions((permissions) => {
delete permissions[targetPlugin][funcName][pluginName]
if (Object.keys(permissions[targetPlugin][funcName]).length === 0) delete permissions[targetPlugin][funcName]
if (Object.keys(permissions[targetPlugin]).length === 0) delete permissions[targetPlugin]
return permissions
})
}
function clearTargetPermission (targetPlugin: string) {
setPermissions((permissions) => {
delete permissions[targetPlugin]
return permissions
})
}
function RenderPluginHeader ({ headingName }) {
return (
<div className="pb-2 remixui_permissionKey">
<h3>{headingName} permissions:</h3>
<i
onClick={() => {
clearTargetPermission(headingName)
}}
className="far fa-trash-alt"
data-id={`pluginManagerSettingsClearAllPermission-${headingName}`}>
</i>
</div>
)
}
function RenderPermissions ({ targetPlugin }) {
return <>{Object.keys(permissions[targetPlugin]).map(funcName => {
return Object.keys(permissions[targetPlugin][funcName]).map((pluginName, index) => (
<div className="form-group remixui_permissionKey" key={pluginName}>
{ permissions && Object.keys(permissions).length > 0
? (
<><div className="remixui_checkbox">
<span className="mr-2">
<input
type="checkbox"
onChange={() => handleCheckboxClick(targetPlugin, funcName, pluginName)}
checked={getState(targetPlugin, funcName, pluginName)}
id={`permission-checkbox-${targetPlugin}-${funcName}-${pluginName}`}
aria-describedby={`module ${pluginName} asks permission for ${funcName}`} />
<label
className="ml-4"
htmlFor={`permission-checkbox-${targetPlugin}-${funcName}-${targetPlugin}`}
data-id={`permission-label-${targetPlugin}-${funcName}-${targetPlugin}`}
>
Allow <u>{pluginName}</u> to call <u>{funcName}</u>
</label>
</span>
</div><i
onClick={() => {
clearFunctionPermission(targetPlugin, funcName, pluginName)
} }
className="fa fa-trash-alt"
data-id={`pluginManagerSettingsRemovePermission-${targetPlugin}-${funcName}-${targetPlugin}`} /></>
) : null
}
</div>
))
})}</>
}
return (
<Fragment>
<ModalDialog
handleHide={closeModal}
cancelFn={cancel}
hide={modalVisibility}
title="Plugin Manager Permissions"
okLabel="OK"
cancelLabel="Cancel"
>
{permissions && Object.keys(permissions).length > 0
? (<h4 className="text-center">Current Permission Settings</h4>)
: (<h4 className="text-center">No Permission requested yet.</h4>)
}
<form className="remixui_permissionForm" data-id="pluginManagerSettingsPermissionForm">
<div className="p-2">
{
Object.keys(permissions).map(targetPlugin => (
<div key={`container-${targetPlugin}`}>
<RenderPluginHeader key={`header-${targetPlugin}`} headingName={targetPlugin} />
<RenderPermissions key={`permissions-${targetPlugin}`} targetPlugin={targetPlugin}/>
</div>
))
}
</div>
</form>
</ModalDialog>
<footer className="bg-light remixui_permissions remix-bg-opacity">
<button
onClick={openModal}
className="btn btn-primary settings-button"
data-id="pluginManagerPermissionsButton">
Permissions
</button>
</footer>
</Fragment>
)
}
export default PermisssionsSettings

@ -0,0 +1,66 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { Fragment, ReactNode, useEffect, useState } from 'react'
import { PluginManagerComponent, PluginManagerSettings } from '../../types'
import PermisssionsSettings from './permissionsSettings'
import { Profile } from '@remixproject/plugin-utils'
import LocalPluginForm from './LocalPluginForm'
interface RootViewProps {
pluginComponent: PluginManagerComponent
pluginManagerSettings: PluginManagerSettings
children: ReactNode
}
export interface pluginDeactivated {
flag: boolean
profile: Profile
}
export interface pluginActivated {
flag: boolean
profile: Profile
}
function RootView ({ pluginComponent, pluginManagerSettings, children }: RootViewProps) {
const [visible, setVisible] = useState<boolean>(true)
const [filterPlugins, setFilterPlugin] = useState<string>('')
const openModal = () => {
setVisible(false)
}
const closeModal = () => setVisible(true)
useEffect(() => {
pluginComponent.getAndFilterPlugins(filterPlugins)
}, [filterPlugins])
return (
<Fragment>
<div id="pluginManager" data-id="pluginManagerComponentPluginManager">
<header className="form-group remixui_pluginSearch plugins-header py-3 px-4 border-bottom" data-id="pluginManagerComponentPluginManagerHeader">
<input
type="text"
onChange={(event) => {
setFilterPlugin(event.target.value.toLowerCase())
}}
value={filterPlugins}
className="form-control"
placeholder="Search"
data-id="pluginManagerComponentSearchInput"
/>
<button onClick={openModal} className="remixui_pluginSearchButton btn bg-transparent text-dark border-0 mt-2 text-underline" data-id="pluginManagerComponentPluginSearchButton">
Connect to a Local Plugin
</button>
</header>
{children}
<PermisssionsSettings pluginSettings={pluginManagerSettings}/>
</div>
<LocalPluginForm
closeModal={closeModal}
visible={visible}
pluginManager={pluginComponent}
/>
</Fragment>
)
}
export default RootView

@ -0,0 +1,78 @@
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
type SetValue<T> = Dispatch<SetStateAction<T>>
function useLocalStorage<T> (key: string, initialValue: T): [T, SetValue<T>] {
// Get from local storage then
// parse stored json or return initialValue
const readValue = (): T => {
// Prevent build error "window is undefined" but keep keep working
if (typeof window === 'undefined') {
return initialValue
}
try {
const item = window.localStorage.getItem(key)
return item ? (JSON.parse(item) as T) : initialValue
} catch (error) {
console.warn(`Error reading localStorage key “${key}”:`, error)
return initialValue
}
}
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState<T>(readValue)
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue: SetValue<T> = value => {
// Prevent build error "window is undefined" but keeps working
if (typeof window === 'undefined') {
console.warn(
`Tried setting localStorage key “${key}” even though environment is not a client`
)
}
try {
// Allow value to be a function so we have the same API as useState
const newValue = value instanceof Function ? value(storedValue) : value
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(newValue))
// Save state
setStoredValue(newValue)
// We dispatch a custom event so every useLocalStorage hook are notified
window.dispatchEvent(new Event('local-storage'))
} catch (error) {
console.warn(`Error setting localStorage key “${key}”:`, error)
}
}
useEffect(() => {
setStoredValue(readValue())
}, [])
useEffect(() => {
const handleStorageChange = () => {
setStoredValue(readValue())
}
// this only works for other documents, not the current one
window.addEventListener('storage', handleStorageChange)
// this is a custom event, triggered in writeValueToLocalStorage
window.addEventListener('local-storage', handleStorageChange)
return () => {
window.removeEventListener('storage', handleStorageChange)
window.removeEventListener('local-storage', handleStorageChange)
}
}, [])
return [storedValue, setValue]
}
export default useLocalStorage

@ -0,0 +1,14 @@
export type localPluginReducerActionType = {
type: 'show' | 'close',
payload?: any
}
export function localPluginToastReducer (currentState: string, toastAction: localPluginReducerActionType) {
switch (toastAction.type) {
case 'show':
return `Cannot create Plugin : ${toastAction.payload!}`
default:
return currentState
}
}

@ -0,0 +1,96 @@
.remixui_pluginSearch {
display: flex;
flex-direction: column;
align-items: center;
background-color: var(--light);
padding: 10px;
position: sticky;
top: 0;
z-index: 2;
margin-bottom: 0px;
}
.remixui_pluginSearchInput {
height: 38px;
}
.remixui_pluginSearchButton {
font-size: 13px;
}
.remixui_displayName {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.remixui_pluginIcon {
height: 0.7rem;
width: 0.7rem;
filter: invert(0.5);
}
.remixui_description {
font-size: 13px;
line-height: 18px;
}
.remixui_descriptiontext {
display: block;
}
.remixui_descriptiontext:first-letter {
text-transform: uppercase;
}
.remixui_row {
display: flex;
flex-direction: row;
}
.remixui_isStuck {
background-color: var(--primary);
/* color: */
}
.remixui_versionWarning {
padding: 4px;
margin: 0 8px;
font-weight: 700;
font-size: 9px;
line-height: 12px;
text-transform: uppercase;
cursor: default;
border: 1px solid;
border-radius: 2px;
}
.remixui_permissions {
position: sticky;
bottom: 0;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 5px 20px;
}
.remixui_permissions button {
padding: 2px 5px;
cursor: pointer;
}
.remixui_permissionForm h4 {
font-size: 1.3rem;
text-align: center;
}
.remixui_permissionForm h6 {
font-size: 1.1rem;
}
.remixui_permissionForm hr {
width: 80%;
}
.remixui_permissionKey {
display: flex;
justify-content: space-between;
align-items: center;
}
.remixui_permissionKey i {
cursor: pointer;
}
.remixui_checkbox {
display: flex;
align-items: center;
}
.remixui_checkbox label {
margin: 0;
font-size: 1rem;
}

@ -0,0 +1,29 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Profile } from '@remixproject/plugin-utils'
import React, { useState } from 'react'
import { RemixUiPluginManagerProps } from '../types'
import ActivePluginCardContainer from './components/ActivePluginCardContainer'
import InactivePluginCardContainer from './components/InactivePluginCardContainer'
import RootView from './components/rootView'
import './remix-ui-plugin-manager.css'
export const RemixUiPluginManager = ({ pluginComponent, pluginManagerSettings }: RemixUiPluginManagerProps) => {
const [activeProfiles, setActiveProfiles] = useState<Profile[]>(pluginComponent.activePlugins)
const [inactiveProfiles, setinactiveProfiles] = useState<Profile[]>(pluginComponent.inactivePlugins)
return (
<RootView pluginComponent={pluginComponent} pluginManagerSettings={pluginManagerSettings}>
<section data-id="pluginManagerComponentPluginManagerSection">
<ActivePluginCardContainer
pluginComponent={pluginComponent}
setActiveProfiles={setActiveProfiles}
activeProfiles={activeProfiles}
/>
<InactivePluginCardContainer
pluginComponent={pluginComponent}
setInactiveProfiles={setinactiveProfiles}
inactiveProfiles={inactiveProfiles}
/>
</section>
</RootView>
)
}

@ -0,0 +1,208 @@
import { PermissionHandler } from './app/ui/persmission-handler'
import { PluginManager } from '@remixproject/engine/lib/manager'
import { EventEmitter } from 'events'
import { Engine } from '@remixproject/engine/lib/engine'
import { PluginBase, Profile } from '@remixproject/plugin-utils'
import { IframePlugin, ViewPlugin, WebsocketPlugin } from '@remixproject/engine-web'
/* eslint-disable camelcase */
interface SetPluginOptionType {
queueTimeout: number
}
export class RemixEngine extends Engine {
event: EventEmitter;
setPluginOption ({ name, kind }) : SetPluginOptionType
onRegistration (plugin) : void
}
export function isNative(name: any): any;
/**
* Checks if plugin caller 'from' is allowed to activate plugin 'to'
* The caller can have 'canActivate' as a optional property in the plugin profile.
* This is an array containing the 'name' property of the plugin it wants to call.
* canActivate = ['plugin1-to-call','plugin2-to-call',....]
* or the plugin is allowed by default because it is native
*
* @param {any, any}
* @returns {boolean}
*/
export function canActivate(from: any, to: any): boolean;
export class RemixAppManager extends PluginManager {
constructor();
event: EventEmitter;
pluginsDirectory: string;
pluginLoader: PluginLoader;
permissionHandler: PermissionHandler;
getAll(): import('@remixproject/plugin-utils').Profile<any>[];
getIds(): string[];
isDependent(name: any): any;
isRequired(name: any): any;
registeredPlugins(): Promise<any>;
turnPluginOn(name: string | string[]);
turnPluginOff(name: string);
}
export class PluginManagerSettings {
openDialog(): void;
permissions: any;
currentSetting: any;
onValidation(): void;
/** Clear one permission from a plugin */
clearPersmission(from: string, to: string, method: string): void;
/** Clear all persmissions from a plugin */
clearAllPersmission(to: string): void;
settings(): any;
render(): any;
}
export type PluginPermissions = {
fileManager : {
writeFile: {
pluginName: {
allow: boolean
}
}
}
}
export class PluginManagerComponent extends ViewPlugin extends Plugin implements PluginBase {
constructor(appManager: RemixAppManager, engine: Engine)
appManager: RemixAppManager
pluginSettings: PluginManagerSettings
app: PluginApi<any>
engine: Engine
htmlElement: HTMLDivElement
views: { root: null, items: {} }
localPlugin: LocalPlugin
pluginNames: string[]
inactivePlugins: Profile[]
activePlugins: Profile[]
filter: string
isActive(name: string): boolean
activateP(name: string): void
deactivateP(name: string): void
onActivation(): void
renderComponent(): void
openLocalPlugin(): Promise<void>
render(): HTMLDivElement
getAndFilterPlugins: (filter?: string, profiles?: Profile[]) => void
triggerEngineEventListener: () => void
activateAndRegisterLocalPlugin: (localPlugin: IframePlugin | WebsocketPlugin) => Promise<void>
activeProfiles: string[]
_paq: any
}
// eslint-disable-next-line no-use-before-define
export = LocalPlugin;
declare class LocalPlugin {
/**
* Open a modal to create a local plugin
* @param {Profile[]} plugins The list of the plugins in the store
* @returns {Promise<{api: any, profile: any}>} A promise with the new plugin profile
*/
open(plugins: any[]): Promise<{
api: any;
profile: any;
}>;
profile: any;
/**
* Create the object to add to the plugin-list
*/
create(): any;
updateName({ target }: {
target: any;
}): void;
updateUrl({ target }: {
target: any;
}): void;
updateDisplayName({ target }: {
target: any;
}): void;
updateProfile(key: any, e: any): void;
updateMethods({ target }: {
target: any;
}): void;
/** The form to create a local plugin */
form(): any;
}
export interface PluginManagerContextProviderProps {
children: React.ReactNode
pluginComponent: PluginManagerComponent
}
export interface RemixUiPluginManagerProps {
pluginComponent: PluginManagerComponent
pluginManagerSettings: PluginManagerSettings
}
/** @class Reference loaders.
* A loader is a get,set based object which load a workspace from a defined sources.
* (localStorage, queryParams)
**/
declare class PluginLoader {
get currentLoader(): any;
donotAutoReload: string[];
loaders: {};
current: string;
set(plugin: any, actives: any): void;
get(): any;
}
export type PluginManagerSettings = {
openDialog: () => void
onValidation: () => void
clearPermission: (from: any, to: any, method: any) => void
settings: () => HTMLElement
render: () => HTMLElement
}
export interface DefaultLocalPlugin extends Profile {
name: string
displayName: string
url: string
type: string
hash: string
methods: any
location: string
}
export interface FormStateProps {
name: string
displayName: string
url: string
type: 'iframe' | 'ws'
hash: string
methods: any
location: string
}
export type PluginManagerProfile = Profile & {
name: string,
displayName: string,
methods: Array<any>,
events?: Array<any>,
icon: 'assets/img/pluginManager.webp',
description: string,
kind?: string,
location: 'sidePanel' | 'mainPanel' | 'none',
documentation: 'https://remix-ide.readthedocs.io/en/latest/plugin_manager.html',
version: any
type: 'iframe' | 'ws'
hash: string
}
export type LocalPlugin = {
create: () => Profile
updateName: (target: string) => void
updateDisplayName: (displayName: string) => void
updateProfile: (key: string, e: Event) => void
updateMethods: (target: any) => void
form: () => HTMLElement
}
export { }

@ -0,0 +1,17 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": 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"]
}

@ -29,20 +29,13 @@ export const Renderer = ({ message, opt = {}, plugin }: RendererProps) => {
// ^ e.g:
// browser/gm.sol: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.6.12
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0/contracts/introspection/IERC1820Registry.sol:3:1: ParserError: Source file requires different compiler version (current compiler is 0.7.4+commit.3f05b770.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version
let positionDetails = getPositionDetails(text)
const options = opt
if (!positionDetails.errFile || (opt.errorType && opt.errorType === positionDetails.errFile)) {
// Updated error reported includes '-->' before file details
const errorDetails = text.split('-->')
// errorDetails[1] will have file details
if (errorDetails.length > 1) positionDetails = getPositionDetails(errorDetails[1])
}
options.errLine = positionDetails.errLine
options.errCol = positionDetails.errCol
options.errFile = positionDetails.errFile.trim()
const positionDetails = getPositionDetails(text)
opt.errLine = positionDetails.errLine
opt.errCol = positionDetails.errCol
opt.errFile = positionDetails.errFile ? (positionDetails.errFile as string).trim() : ''
if (!opt.noAnnotations && opt.errFile) {
if (!opt.noAnnotations && opt.errFile && opt.errFile !== '') {
addAnnotation(opt.errFile, {
row: opt.errLine,
column: opt.errCol,
@ -52,15 +45,17 @@ export const Renderer = ({ message, opt = {}, plugin }: RendererProps) => {
}
setMessageText(text)
setEditorOptions(options)
setEditorOptions(opt)
setClose(false)
}, [message, opt])
const getPositionDetails = (msg: any) => {
const getPositionDetails = (msg: string) => {
const result = { } as Record<string, number | string>
// To handle some compiler warning without location like SPDX license warning etc
if (!msg.includes(':')) return { errLine: -1, errCol: -1, errFile: msg }
if (!msg.includes(':')) return { errLine: -1, errCol: -1, errFile: '' }
if (msg.includes('-->')) msg = msg.split('-->')[1].trim()
// extract line / column
let pos = msg.match(/^(.*?):([0-9]*?):([0-9]*?)?/)
@ -69,7 +64,7 @@ export const Renderer = ({ message, opt = {}, plugin }: RendererProps) => {
// extract file
pos = msg.match(/^(https:.*?|http:.*?|.*?):/)
result.errFile = pos ? pos[1] : ''
result.errFile = pos ? pos[1] : msg
return result
}

@ -69,13 +69,14 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
const optimize = params.optimize === 'false' ? false : params.optimize === 'true' ? true : null
const runs = params.runs
const evmVersion = params.evmVersion
const autoCompile = params.autoCompile === 'false' ? false : params.autoCompile === 'true' ? true : null
return {
...prevState,
hideWarnings: api.getConfiguration('hideWarnings') || false,
autoCompile: api.getConfiguration('autoCompile') || false,
autoCompile: typeof autoCompile === 'boolean' ? autoCompile : api.getConfiguration('autoCompile') || false,
includeNightlies: api.getConfiguration('includeNightlies') || false,
optimise: (optimize !== null) && (optimize !== undefined) ? optimize : api.getConfiguration('optimise') || false,
optimise: typeof optimize === 'boolean' ? optimize : api.getConfiguration('optimise') || false,
runs: (runs !== null) && (runs !== 'null') && (runs !== undefined) && (runs !== 'undefined') ? runs : 200,
evmVersion: (evmVersion !== null) && (evmVersion !== 'null') && (evmVersion !== undefined) && (evmVersion !== 'undefined') ? evmVersion : 'default'
}
@ -407,6 +408,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
const checked = e.target.checked
api.setConfiguration('autoCompile', checked)
checked && compile()
setState(prevState => {
return { ...prevState, autoCompile: checked }
})

@ -209,14 +209,14 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
})
// Slither Analysis
if (slitherEnabled) {
props.analysisModule.call('solidity-logic', 'getCompilerState').then((compilerState) => {
props.analysisModule.call('solidity-logic', 'getCompilerState').then(async (compilerState) => {
const { currentVersion, optimize, evmVersion } = compilerState
props.analysisModule.call('terminal', 'log', { type: 'info', value: '[Slither Analysis]: Running...' })
props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion }).then((result) => {
props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion }).then(async (result) => {
if (result.status) {
props.analysisModule.call('terminal', 'log', { type: 'info', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` })
const report = result.data
report.map((item) => {
for (const item of report) {
let location: any = {}
let locationString = 'not available'
let column = 0
@ -224,7 +224,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
let fileName = currentFile
if (item.sourceMap && item.sourceMap.length) {
const fileIndex = Object.keys(lastCompilationResult.sources).indexOf(item.sourceMap[0].source_mapping.filename_relative)
let path = item.sourceMap[0].source_mapping.filename_relative
let fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path)
if (fileIndex === -1) {
path = await props.analysisModule.call('fileManager', 'getUrlFromPath', path)
fileIndex = Object.keys(lastCompilationResult.sources).indexOf(path.file)
}
if (fileIndex >= 0) {
location = {
start: item.sourceMap[0].source_mapping.start,
@ -259,7 +264,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' })
})
}
showWarnings(warningMessage, 'warningModuleName')
props.event.trigger('staticAnaysisWarning', [warningCount])
}
@ -466,7 +471,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
<span className="text-dark h6">{element[0]}</span>
{element[1]['map']((x, i) => ( // eslint-disable-line dot-notation
x.hasWarning ? ( // eslint-disable-next-line dot-notation
<div id={`staticAnalysisModule${element[1]['warningModuleName']}`} key={i}>
<div data-id={`staticAnalysisModule${x.warningModuleName}${i}`} id={`staticAnalysisModule${x.warningModuleName}${i}`} key={i}>
<ErrorRenderer message={x.msg} opt={x.options} warningErrors={ x.warningErrors} editor={props.analysisModule}/>
</div>

@ -6,7 +6,7 @@ import * as servicesList from '../serviceList'
import * as WS from 'ws' // eslint-disable-line
import { getDomain, absolutePath } from '../utils'
import Axios from 'axios'
import * as fs from 'fs-extra'
import { writeJSON, existsSync } from 'fs-extra'
import * as path from 'path'
import * as program from 'commander'
@ -83,7 +83,7 @@ function errorHandler (error: any, service: string) {
console.log('\x1b[33m%s\x1b[0m', '[WARN] You may now only use IDE at ' + program.remixIde + ' to connect to that instance')
}
if (program.sharedFolder) {
if (program.sharedFolder && existsSync(absolutePath('./', program.sharedFolder))) {
console.log('\x1b[33m%s\x1b[0m', '[WARN] Any application that runs on your computer can potentially read from and write to all files in the directory.')
console.log('\x1b[33m%s\x1b[0m', '[WARN] Symbolic links are not forwarded to Remix IDE\n')
try {
@ -102,7 +102,7 @@ function errorHandler (error: any, service: string) {
})
// Run hardhat service if a hardhat project is shared as folder
const hardhatConfigFilePath = absolutePath('./', program.sharedFolder) + '/hardhat.config.js'
const isHardhatProject = fs.existsSync(hardhatConfigFilePath)
const isHardhatProject = existsSync(hardhatConfigFilePath)
if (isHardhatProject) {
startService('hardhat', (ws: WS, sharedFolderClient: servicesList.Sharedfolder, error: Error) => {
if (error) {
@ -151,7 +151,7 @@ function errorHandler (error: any, service: string) {
const { data } = await Axios.get(gistUrl)
try {
await fs.writeJSON(path.resolve(path.join(__dirname, '..', 'origins.json')), { data })
await writeJSON(path.resolve(path.join(__dirname, '..', 'origins.json')), { data })
} catch (e) {
console.error(e)
}

@ -137,7 +137,6 @@ export class SlitherClient extends PluginClient {
const outputFile: string = 'remix-slitherReport_' + Math.floor(Date.now() / 1000) + '.json'
const cmd: string = `slither ${filePath} ${solcArgs} ${solcRemaps} --json ${outputFile}`
console.log('\x1b[32m%s\x1b[0m', '[Slither Analysis]: Running Slither...')
console.log(cmd)
// Added `stdio: 'ignore'` as for contract with NPM imports analysis which is exported in 'stderr'
// get too big and hangs the process. We process analysis from the report file only
const child = spawn(cmd, { cwd: this.currentSharedFolder, shell: true, stdio: 'ignore' })

@ -106,6 +106,9 @@
"remix-ui-checkbox": {
"tags": []
},
"remix-ui-plugin-manager": {
"tags": []
},
"remix-core-plugin": {
"tags": []
},
@ -118,5 +121,5 @@
"remix-ui-renderer": {
"tags": []
}
}
}
}

221
package-lock.json generated

@ -7533,6 +7533,12 @@
"defer-to-connect": "^1.0.1"
}
},
"@testim/chrome-version": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.0.7.tgz",
"integrity": "sha512-8UT/J+xqCYfn3fKtOznAibsHpiuDshCb0fwgWxRazTT19Igp9ovoXMPhXyLD6m3CKQGTMHgqoxaFfMWaL40Rnw==",
"dev": true
},
"@testing-library/dom": {
"version": "7.29.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.1.tgz",
@ -7861,6 +7867,12 @@
"@types/node": "*"
}
},
"@types/lodash": {
"version": "4.14.172",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.172.tgz",
"integrity": "sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@ -8079,6 +8091,16 @@
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz",
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw=="
},
"@types/yauzl": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
"integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==",
"dev": true,
"optional": true,
"requires": {
"@types/node": "*"
}
},
"@typescript-eslint/eslint-plugin": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.1.tgz",
@ -12255,6 +12277,159 @@
}
}
},
"chromedriver": {
"version": "92.0.1",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-92.0.1.tgz",
"integrity": "sha512-LptlDVCs1GgyFNVbRoHzzy948JDVzTgGiVPXjNj385qXKQP3hjAVBIgyvb/Hco0xSEW8fjwJfsm1eQRmu6t4pQ==",
"dev": true,
"requires": {
"@testim/chrome-version": "^1.0.7",
"axios": "^0.21.1",
"del": "^6.0.0",
"extract-zip": "^2.0.1",
"https-proxy-agent": "^5.0.0",
"proxy-from-env": "^1.1.0",
"tcp-port-used": "^1.0.1"
},
"dependencies": {
"agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"dev": true,
"requires": {
"debug": "4"
}
},
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
},
"debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"del": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz",
"integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==",
"dev": true,
"requires": {
"globby": "^11.0.1",
"graceful-fs": "^4.2.4",
"is-glob": "^4.0.1",
"is-path-cwd": "^2.2.0",
"is-path-inside": "^3.0.2",
"p-map": "^4.0.0",
"rimraf": "^3.0.2",
"slash": "^3.0.0"
}
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true,
"requires": {
"path-type": "^4.0.0"
}
},
"extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"dev": true,
"requires": {
"@types/yauzl": "^2.9.1",
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
}
},
"get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"dev": true,
"requires": {
"pump": "^3.0.0"
}
},
"globby": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz",
"integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==",
"dev": true,
"requires": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.1.1",
"ignore": "^5.1.4",
"merge2": "^1.3.0",
"slash": "^3.0.0"
}
},
"https-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
"dev": true,
"requires": {
"agent-base": "6",
"debug": "4"
}
},
"ignore": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
"dev": true
},
"is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true
},
"p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
"dev": true,
"requires": {
"aggregate-error": "^3.0.0"
}
},
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
}
}
},
"ci-info": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
@ -20946,6 +21121,12 @@
"unc-path-regex": "^0.1.2"
}
},
"is-url": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
"dev": true
},
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@ -20975,6 +21156,25 @@
"integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==",
"dev": true
},
"is2": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/is2/-/is2-2.0.7.tgz",
"integrity": "sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==",
"dev": true,
"requires": {
"deep-is": "^0.1.3",
"ip-regex": "^4.1.0",
"is-url": "^1.2.4"
},
"dependencies": {
"ip-regex": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz",
"integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==",
"dev": true
}
}
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@ -36896,6 +37096,27 @@
"readable-stream": "^3.1.1"
}
},
"tcp-port-used": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz",
"integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==",
"dev": true,
"requires": {
"debug": "4.3.1",
"is2": "^2.0.6"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
}
}
},
"temp-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz",

@ -41,7 +41,7 @@
"workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph",
"help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,remix-ui-plugin-manager",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs",
@ -208,6 +208,7 @@
"@types/chai": "^4.2.11",
"@types/fs-extra": "^9.0.1",
"@types/jest": "25.1.4",
"@types/lodash": "^4.14.172",
"@types/mocha": "^7.0.2",
"@types/nightwatch": "^1.1.6",
"@types/node": "~8.9.4",

@ -189,4 +189,4 @@ Before starting coding, we should ensure all devs / contributors are aware of:
# Coding best practices
- https://github.com/ethereum/remix-ide/blob/master/best-practices.md
- https://github.com/ethereum/remix-project/blob/master/best-practices.md

@ -41,6 +41,7 @@
"@remix-project/core-plugin": ["libs/remix-core-plugin/src/index.ts"],
"@remix-ui/solidity-compiler": ["libs/remix-ui/solidity-compiler/src/index.ts"],
"@remix-ui/publish-to-storage": ["libs/remix-ui/publish-to-storage/src/index.ts"],
"@remix-ui/plugin-manager": ["libs/remix-ui/plugin-manager/src/index.ts"],
"@remix-ui/renderer": ["libs/remix-ui/renderer/src/index.ts"]
}
},

@ -18,7 +18,8 @@
"assets": [
"apps/remix-ide/src/assets",
"apps/remix-ide/src/index.html",
"apps/remix-ide/src/favicon.ico"
"apps/remix-ide/src/favicon.ico",
"apps/remix-ide/src/production.index.html"
],
"styles": [],
"scripts": [],
@ -777,6 +778,15 @@
}
}
},
"remix-ui-plugin-manager": {
"root": "libs/remix-ui/plugin-manager",
"sourceRoot": "libs/remix-ui/plugin-manager/src",
"tsConfig": ["libs/remix-ui/plugin-manager/tsconfig.lib.json"],
"exclude": [
"**/node_modules/**",
"!libs/remix-ui/plugin-manager/**/*"
]
},
"remix-core-plugin": {
"root": "libs/remix-core-plugin",
"sourceRoot": "libs/remix-core-plugin/src",
@ -858,6 +868,7 @@
"tsConfig": ["libs/remix-ui/renderer/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/renderer/**/*"]
}
}
}
}
@ -928,4 +939,4 @@
}
},
"defaultProject": "remix-ide"
}
}

Loading…
Cancel
Save