Merge remote-tracking branch 'origin/master' into sendTx

pull/747/head
yann300 4 years ago
commit 4facb863e0
  1. 6
      apps/remix-ide-e2e/src/commands/addFile.ts
  2. 9
      apps/remix-ide-e2e/src/helpers/init.ts
  3. 4
      apps/remix-ide-e2e/src/tests/debugger.spec.ts
  4. 12
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  5. 6
      apps/remix-ide-e2e/src/tests/gist.spec.ts
  6. 3
      apps/remix-ide-e2e/src/tests/solidityUnittests.spec.ts
  7. 2
      apps/remix-ide-e2e/src/tests/staticAnalysis.spec.ts
  8. 2
      apps/remix-ide-e2e/src/tests/url.spec.ts
  9. 4
      apps/remix-ide/src/app.js
  10. 6
      apps/remix-ide/src/app/compiler/compiler-imports.js
  11. 5
      apps/remix-ide/src/app/editor/contextView.js
  12. 12
      apps/remix-ide/src/app/files/file-explorer.js
  13. 29
      apps/remix-ide/src/app/files/fileManager.js
  14. 20
      apps/remix-ide/src/app/files/fileProvider.js
  15. 18
      apps/remix-ide/src/app/files/hardhat-handle.js
  16. 33
      apps/remix-ide/src/app/files/remixDProvider.js
  17. 6
      apps/remix-ide/src/app/files/remixd-handle.js
  18. 6
      apps/remix-ide/src/app/files/workspaceFileProvider.js
  19. 35
      apps/remix-ide/src/app/panels/file-panel.js
  20. 58
      apps/remix-ide/src/app/tabs/analysis-tab.js
  21. 18
      apps/remix-ide/src/app/tabs/compile-tab.js
  22. 41
      apps/remix-ide/src/app/tabs/compileTab/compileTab.js
  23. 14
      apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
  24. 6
      apps/remix-ide/src/app/tabs/runTab/contractDropdown.js
  25. 11
      apps/remix-ide/src/app/tabs/runTab/model/dropdownlogic.js
  26. 27
      apps/remix-ide/src/app/tabs/runTab/settings.js
  27. 302
      apps/remix-ide/src/app/tabs/staticanalysis/staticAnalysisView.js
  28. 36
      apps/remix-ide/src/app/tabs/staticanalysis/styles/staticAnalysisView-styles.js
  29. 2
      apps/remix-ide/src/app/tabs/test-tab.js
  30. 4
      apps/remix-ide/src/app/tabs/testTab/testTab.js
  31. 2
      apps/remix-ide/src/app/udapp/run-tab.js
  32. 5
      apps/remix-ide/src/app/ui/renderer.js
  33. 3
      apps/remix-ide/src/app/ui/universal-dapp-ui.js
  34. 35
      apps/remix-ide/src/lib/helper.js
  35. 4
      apps/remix-ide/src/remixAppManager.js
  36. 3
      libs/remix-debug/src/Ethdebugger.ts
  37. 11
      libs/remix-debug/src/debugger/solidityLocals.ts
  38. 10
      libs/remix-debug/src/solidity-decoder/internalCallTree.ts
  39. 4
      libs/remix-debug/src/solidity-decoder/localDecoder.ts
  40. 33
      libs/remix-debug/src/solidity-decoder/types/RefType.ts
  41. 4
      libs/remix-debug/src/solidity-decoder/types/StringType.ts
  42. 2
      libs/remix-debug/src/solidity-decoder/types/ValueType.ts
  43. 10
      libs/remix-debug/test/decoder/localsTests/helper.ts
  44. 4
      libs/remix-ui/checkbox/.babelrc
  45. 19
      libs/remix-ui/checkbox/.eslintrc
  46. 7
      libs/remix-ui/checkbox/README.md
  47. 1
      libs/remix-ui/checkbox/src/index.ts
  48. 0
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.css
  49. 47
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
  50. 16
      libs/remix-ui/checkbox/tsconfig.json
  51. 13
      libs/remix-ui/checkbox/tsconfig.lib.json
  52. 293
      libs/remix-ui/file-explorer/src/lib/actions/fileSystem.ts
  53. 597
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  54. 344
      libs/remix-ui/file-explorer/src/lib/reducers/fileSystem.ts
  55. 2
      libs/remix-ui/file-explorer/src/lib/types/index.ts
  56. 13
      libs/remix-ui/file-explorer/src/lib/utils/index.ts
  57. 18
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
  58. 6
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  59. 4
      libs/remix-ui/static-analyser/.babelrc
  60. 19
      libs/remix-ui/static-analyser/.eslintrc
  61. 7
      libs/remix-ui/static-analyser/README.md
  62. 1
      libs/remix-ui/static-analyser/src/index.ts
  63. 21
      libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx
  64. 65
      libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx
  65. 14
      libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts
  66. 21
      libs/remix-ui/static-analyser/src/lib/reducers/staticAnalysisReducer.ts
  67. 351
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  68. 16
      libs/remix-ui/static-analyser/tsconfig.json
  69. 13
      libs/remix-ui/static-analyser/tsconfig.lib.json
  70. 6
      libs/remix-ui/toaster/src/lib/toaster.tsx
  71. 90
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  72. 8
      libs/remixd/src/bin/remixd.ts
  73. 4
      libs/remixd/src/index.ts
  74. 1
      libs/remixd/src/serviceList.ts
  75. 49
      libs/remixd/src/services/hardhatClient.ts
  76. 73
      libs/remixd/src/services/remixdClient.ts
  77. 6
      nx.json
  78. 6
      package-lock.json
  79. 4
      package.json
  80. 6
      tsconfig.json
  81. 35
      workspace.json

@ -18,9 +18,9 @@ function addFile (browser: NightwatchBrowser, name: string, content: NightwatchC
.clickLaunchIcon('filePanel')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
.click('.newFile')
.waitForElementContainsText('*[data-id="treeViewLitreeViewItem/blank"]', '', 60000)
.sendKeys('*[data-id="treeViewLitreeViewItem/blank"] .remixui_items', name)
.sendKeys('*[data-id="treeViewLitreeViewItem/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.pause(2000)
.waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000)
.setEditorValue(content.content)

@ -2,11 +2,18 @@ import { NightwatchBrowser } from 'nightwatch'
require('dotenv').config()
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true): void {
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true, closeWorkspaceAlert = true): void {
browser
.url(url || 'http://127.0.0.1:8080')
.pause(5000)
.switchBrowserTab(0)
.perform((done) => {
if (closeWorkspaceAlert) {
browser.waitForElementVisible('*[data-id="modalDialogModalBody"]', 60000)
.modalFooterOKClick()
}
done()
})
.fullscreenWindow(() => {
if (preloadPlugins) {
initModules(browser, () => {

@ -323,7 +323,7 @@ const localVariable_step266_ABIEncoder = { // eslint-disable-line
value: '0x0000000000000000000000000000000000000000000000000000000000000002'
},
userData: {
error: '<decoding failed - no decoder for calldata>',
value: '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4',
type: 'bytes'
}
}
@ -360,7 +360,7 @@ const localVariable_step717_ABIEncoder = { // eslint-disable-line
value: '0x5b38da6a701c568545dcfcb03fcb875f56beddc45b38da6a701c568545dcfcb03fcb875f56beddc400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001'
},
userData: {
error: '<decoding failed - no decoder for calldata>',
value: '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4',
type: 'bytes'
}
}

@ -22,9 +22,9 @@ module.exports = {
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItem/blank"]')
.sendKeys('*[data-id="treeViewLitreeViewItem/blank"] .remixui_items', '5_New_contract.sol')
.sendKeys('*[data-id="treeViewLitreeViewItem/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id$="/blank"]')
.sendKeys('*[data-id$="/blank"] .remixui_items', '5_New_contract.sol')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]', 7000)
},
@ -49,9 +49,9 @@ module.exports = {
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItem/blank"]')
.sendKeys('*[data-id="treeViewLitreeViewItem/blank"] .remixui_items', 'Browser_Tests')
.sendKeys('*[data-id="treeViewLitreeViewItem/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id$="/blank"]')
.sendKeys('*[data-id$="/blank"] .remixui_items', 'Browser_Tests')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
},

@ -29,9 +29,9 @@ module.exports = {
.waitForElementVisible('*[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItem/blank"]')
.sendKeys('*[data-id="treeViewLitreeViewItem/blank"] .remixui_items', 'Browser_Tests')
.sendKeys('*[data-id="treeViewLitreeViewItem/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id$="/blank"]')
.sendKeys('*[data-id$="/blank"] .remixui_items', 'Browser_Tests')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
.addFile('File.sol', { content: '' })
.click('*[data-id="fileExplorerNewFilepublishToGist"]')

@ -179,9 +179,8 @@ function runTests (browser: NightwatchBrowser) {
.click('*[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/3_Ballot.sol')
.clickLaunchIcon('solidityUnitTesting')
.pause(500)
.setValue('*[data-id="uiPathInput"]', 'tests')
.pause(2000)
.verify.attributeEquals('*[data-id="uiPathInput"]', 'value', 'tests')
.scrollAndClick('#runTestsTabRunAction')
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]', 60000)

@ -40,7 +40,7 @@ function runTests (browser: NightwatchBrowser) {
.pause(10000)
.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['TooMuchGas', 'test1', 'test2'])
.clickLaunchIcon('solidityStaticAnalysis')
.click('#staticanalysisView button')
.click('#staticanalysisButton button')
.waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () {
listSelectorContains(['Use of tx.origin',
'Fallback function of contract TooMuchGas requires too much gas',

@ -10,7 +10,7 @@ const sources = [
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0')
init(browser, done, 'http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0', true, false)
},
'@sources': function () {

@ -431,12 +431,14 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
engine.register([
compileTab,
compileTab.compileTabLogic,
run,
debug,
analysis,
test,
filePanel.remixdHandle,
filePanel.gitHandle
filePanel.gitHandle,
filePanel.hardhatHandle
])
if (isElectron()) {

@ -121,9 +121,7 @@ module.exports = class CompilerImports extends Plugin {
if (provider.type === 'localhost' && !provider.isConnected()) {
return reject(new Error(`file provider ${provider.type} not available while trying to resolve ${url}`))
}
provider.exists(url, (error, exist) => {
if (error) return reject(error)
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.
@ -162,6 +160,8 @@ module.exports = class CompilerImports extends Plugin {
if (error) return reject(error)
resolve(content)
})
}).catch(error => {
return reject(error)
})
}
})

@ -109,10 +109,11 @@ class ContextView {
if (filename !== this._deps.config.get('currentFile')) {
const provider = this._deps.fileManager.fileProviderOf(filename)
if (provider) {
provider.exists(filename, (error, exist) => {
if (error) return console.log(error)
provider.exists(filename).then(exist => {
this._deps.fileManager.open(filename)
jumpToLine(lineColumn)
}).catch(error => {
if (error) return console.log(error)
})
}
} else {

@ -9,7 +9,7 @@ const helper = require('../../lib/helper')
const yo = require('yo-yo')
const Treeview = require('../ui/TreeView')
const modalDialog = require('../ui/modaldialog')
const EventManager = require('../../lib/events')
const EventManager = require('events')
const contextMenu = require('../ui/contextMenu')
const css = require('./styles/file-explorer-styles')
const globalRegistry = require('../../global/registry')
@ -94,11 +94,11 @@ function fileExplorer (localRegistry, files, menuItems, plugin) {
})
// register to event of the file provider
files.event.register('fileRemoved', fileRemoved)
files.event.register('fileRenamed', fileRenamed)
files.event.register('fileRenamedError', fileRenamedError)
files.event.register('fileAdded', fileAdded)
files.event.register('folderAdded', folderAdded)
files.event.on('fileRemoved', fileRemoved)
files.event.on('fileRenamed', fileRenamed)
files.event.on('fileRenamedError', fileRenamedError)
files.event.on('fileAdded', fileAdded)
files.event.on('folderAdded', folderAdded)
function fileRenamedError (error) {
modalDialogCustom.alert(error)

@ -121,10 +121,7 @@ class FileManager extends Plugin {
try {
path = this.limitPluginScope(path)
const provider = this.fileProviderOf(path)
const result = provider.exists(path, (err, result) => {
if (err) return false
return result
})
const result = provider.exists(path)
return result
} catch (e) {
@ -332,18 +329,18 @@ class FileManager extends Plugin {
workspaceExplorer: this._components.registry.get('fileproviders/workspace').api,
filesProviders: this._components.registry.get('fileproviders').api
}
this._deps.browserExplorer.event.register('fileChanged', (path) => { this.fileChangedEvent(path) })
this._deps.browserExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
this._deps.localhostExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
this._deps.browserExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this._deps.browserExplorer.event.register('fileAdded', (path) => { this.fileAddedEvent(path) })
this._deps.localhostExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this._deps.localhostExplorer.event.register('errored', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
this._deps.localhostExplorer.event.register('closed', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
this._deps.workspaceExplorer.event.register('fileChanged', (path) => { this.fileChangedEvent(path) })
this._deps.workspaceExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
this._deps.workspaceExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this._deps.workspaceExplorer.event.register('fileAdded', (path) => { this.fileAddedEvent(path) })
this._deps.browserExplorer.event.on('fileChanged', (path) => { this.fileChangedEvent(path) })
this._deps.browserExplorer.event.on('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
this._deps.localhostExplorer.event.on('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
this._deps.browserExplorer.event.on('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this._deps.browserExplorer.event.on('fileAdded', (path) => { this.fileAddedEvent(path) })
this._deps.localhostExplorer.event.on('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this._deps.localhostExplorer.event.on('errored', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
this._deps.localhostExplorer.event.on('closed', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
this._deps.workspaceExplorer.event.on('fileChanged', (path) => { this.fileChangedEvent(path) })
this._deps.workspaceExplorer.event.on('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
this._deps.workspaceExplorer.event.on('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this._deps.workspaceExplorer.event.on('fileAdded', (path) => { this.fileAddedEvent(path) })
this.getCurrentFile = this.file
this.getFile = this.readFile

@ -1,7 +1,7 @@
'use strict'
const CompilerImport = require('../compiler/compiler-imports')
const EventManager = require('../../lib/events')
const EventManager = require('events')
const modalDialogCustom = require('../ui/modal-dialog-custom')
const tooltip = require('../ui/tooltip')
const remixLib = require('@remix-project/remix-lib')
@ -63,11 +63,11 @@ class FileProvider {
})
}
exists (path, cb) {
async exists (path) {
// todo check the type (directory/file) as well #2386
// currently it is not possible to have a file and folder with same path
const ret = this._exists(path)
if (cb) cb(null, ret)
return ret
}
@ -111,9 +111,9 @@ class FileProvider {
return false
}
if (!exists) {
this.event.trigger('fileAdded', [this._normalizePath(unprefixedpath), false])
this.event.emit('fileAdded', this._normalizePath(unprefixedpath), false)
} else {
this.event.trigger('fileChanged', [this._normalizePath(unprefixedpath)])
this.event.emit('fileChanged', this._normalizePath(unprefixedpath))
}
cb()
return true
@ -128,7 +128,7 @@ class FileProvider {
currentCheck = currentCheck + '/' + value
if (!window.remixFileSystem.existsSync(currentCheck)) {
window.remixFileSystem.mkdirSync(currentCheck)
this.event.trigger('folderAdded', [this._normalizePath(currentCheck)])
this.event.emit('folderAdded', this._normalizePath(currentCheck))
}
})
if (cb) cb()
@ -184,7 +184,7 @@ class FileProvider {
// folder is empty
window.remixFileSystem.rmdirSync(path, console.log)
}
this.event.trigger('fileRemoved', [this._normalizePath(path)])
this.event.emit('fileRemoved', this._normalizePath(path))
}
} catch (e) {
console.log(e)
@ -249,7 +249,7 @@ class FileProvider {
path = this.removePrefix(path)
if (window.remixFileSystem.existsSync(path) && !window.remixFileSystem.statSync(path).isDirectory()) {
window.remixFileSystem.unlinkSync(path, console.log)
this.event.trigger('fileRemoved', [this._normalizePath(path)])
this.event.emit('fileRemoved', this._normalizePath(path))
return true
} else return false
}
@ -259,11 +259,11 @@ class FileProvider {
var unprefixednewPath = this.removePrefix(newPath)
if (this._exists(unprefixedoldPath)) {
window.remixFileSystem.renameSync(unprefixedoldPath, unprefixednewPath)
this.event.trigger('fileRenamed', [
this.event.emit('fileRenamed',
this._normalizePath(unprefixedoldPath),
this._normalizePath(unprefixednewPath),
isFolder
])
)
return true
}
return false

@ -0,0 +1,18 @@
import { WebsocketPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
const profile = {
name: 'hardhat',
displayName: 'Hardhat',
url: 'ws://127.0.0.1:65522',
methods: ['compile'],
description: 'Using Remixd daemon, allow to access hardhat API',
kind: 'other',
version: packageJson.version
}
export class HardhatHandle extends WebsocketPlugin {
constructor () {
super(profile)
}
}

@ -17,32 +17,32 @@ module.exports = class RemixDProvider extends FileProvider {
var remixdEvents = ['connecting', 'connected', 'errored', 'closed']
remixdEvents.forEach((value) => {
this._appManager.on('remixd', value, (event) => {
this.event.trigger(value, [event])
this.event.emit(value, event)
})
})
this._appManager.on('remixd', 'folderAdded', (path) => {
this.event.trigger('folderAdded', [path])
this.event.emit('folderAdded', path)
})
this._appManager.on('remixd', 'fileAdded', (path) => {
this.event.trigger('fileAdded', [path])
this.event.emit('fileAdded', path)
})
this._appManager.on('remixd', 'fileChanged', (path) => {
this.event.trigger('fileChanged', [path])
this.event.emit('fileChanged', path)
})
this._appManager.on('remixd', 'fileRemoved', (path) => {
this.event.trigger('fileRemoved', [path])
this.event.emit('fileRemoved', path)
})
this._appManager.on('remixd', 'fileRenamed', (oldPath, newPath) => {
this.event.trigger('fileRemoved', [oldPath, newPath])
this.event.emit('fileRemoved', oldPath, newPath)
})
this._appManager.on('remixd', 'rootFolderChanged', () => {
this.event.trigger('rootFolderChanged', [])
this.event.emit('rootFolderChanged')
})
}
@ -53,11 +53,11 @@ module.exports = class RemixDProvider extends FileProvider {
close (cb) {
this._isReady = false
cb()
this.event.trigger('disconnected')
this.event.emit('disconnected')
}
preInit () {
this.event.trigger('loading')
this.event.emit('loading')
}
init (cb) {
@ -67,23 +67,22 @@ module.exports = class RemixDProvider extends FileProvider {
this._isReady = true
this._readOnlyMode = result
this._registerEvent()
this.event.trigger('connected')
this.event.emit('connected')
cb && cb()
}).catch((error) => {
cb && cb(error)
})
}
exists (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
exists (path) {
if (!this._isReady) throw new Error('provider not ready')
const unprefixedpath = this.removePrefix(path)
return this._appManager.call('remixd', 'exists', { path: unprefixedpath })
.then((result) => {
if (cb) return cb(null, result)
return result
}).catch((error) => {
if (cb) return cb(error)
})
.catch((error) => {
throw new Error(error)
})
}
@ -165,13 +164,13 @@ module.exports = class RemixDProvider extends FileProvider {
this.filesContent[newPath] = this.filesContent[oldPath]
delete this.filesContent[oldPath]
this.init(() => {
this.event.trigger('fileRenamed', [oldPath, newPath, isFolder])
this.event.emit('fileRenamed', oldPath, newPath, isFolder)
})
return result
}).catch(error => {
console.log(error)
if (this.error[error.code]) error = this.error[error.code]
this.event.trigger('fileRenamedError', [this.error[error.code]])
this.event.emit('fileRenamedError', this.error[error.code])
})
}

@ -39,6 +39,7 @@ export class RemixdHandle extends WebsocketPlugin {
deactivate () {
if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
this.appManager.deactivatePlugin('hardhat')
this.locahostProvider.close((error) => {
if (error) console.log(error)
})
@ -51,6 +52,7 @@ export class RemixdHandle extends WebsocketPlugin {
async canceled () {
// await this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
await this.appManager.deactivatePlugin('remixd')
await this.appManager.deactivatePlugin('hardhat')
}
/**
@ -81,7 +83,7 @@ export class RemixdHandle extends WebsocketPlugin {
}
}, 3000)
this.locahostProvider.init(() => {})
// this.call('manager', 'activatePlugin', 'git')
this.call('manager', 'activatePlugin', 'hardhat')
}
}
if (this.locahostProvider.isConnected()) {
@ -135,7 +137,7 @@ function remixdDialog () {
<div class=${css.dialogParagraph}>If you have looked at the Remixd docs and just need remixd command, <br> here it is:
<br><b>remixd -s absolute-path-to-the-shared-folder --remix-ide your-remix-ide-URL-instance</b>
</div>
<div class=${css.dialogParagraph}>Connection will start a session between <em>${window.location.href}</em> and your local file system <i>ws://127.0.0.1:65520</i>
<div class=${css.dialogParagraph}>Connection will start a session between <em>${window.location.origin}</em> and your local file system <i>ws://127.0.0.1:65520</i>
so please make sure your system is secured enough (port 65520 neither opened nor forwarded).
</div>
<div class=${css.dialogParagraph}>

@ -1,6 +1,6 @@
'use strict'
const EventManager = require('../../lib/events')
const EventManager = require('events')
const FileProvider = require('./fileProvider')
const pathModule = require('path')
@ -33,7 +33,7 @@ class WorkspaceFileProvider extends FileProvider {
if (!this.workspace) this.createWorkspace()
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
if (path.startsWith(this.workspacesPath + '/' + this.workspace)) return path
if (path.startsWith(this.workspace)) return this.workspacesPath + '/' + this.workspace
if (path.startsWith(this.workspace)) return path.replace(this.workspace, this.workspacesPath + '/' + this.workspace)
path = super.removePrefix(path)
let ret = this.workspacesPath + '/' + this.workspace + '/' + (path === '/' ? '' : path)
@ -82,7 +82,7 @@ class WorkspaceFileProvider extends FileProvider {
createWorkspace (name) {
if (!name) name = 'default_workspace'
this.event.trigger('createWorkspace', [name])
this.event.emit('createWorkspace', name)
}
}

@ -6,13 +6,13 @@ import ReactDOM from 'react-dom'
import { Workspace } from '@remix-ui/workspace' // eslint-disable-line
import { bufferToHex, keccakFromString } from 'ethereumjs-util'
import { checkSpecialChars, checkSlash } from '../../lib/helper'
var EventManager = require('../../lib/events')
var { RemixdHandle } = require('../files/remixd-handle.js')
var { GitHandle } = require('../files/git-handle.js')
var globalRegistry = require('../../global/registry')
var examples = require('../editor/examples')
var GistHandler = require('../../lib/gist-handler')
var QueryParams = require('../../lib/query-params')
const { RemixdHandle } = require('../files/remixd-handle.js')
const { GitHandle } = require('../files/git-handle.js')
const { HardhatHandle } = require('../files/hardhat-handle.js')
const globalRegistry = require('../../global/registry')
const examples = require('../editor/examples')
const GistHandler = require('../../lib/gist-handler')
const QueryParams = require('../../lib/query-params')
const modalDialogCustom = require('../ui/modal-dialog-custom')
/*
Overview of APIs:
@ -47,7 +47,6 @@ const profile = {
module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) {
super(profile)
this.event = new EventManager()
this._components = {}
this._components.registry = globalRegistry
this._deps = {
@ -60,6 +59,7 @@ module.exports = class Filepanel extends ViewPlugin {
this.remixdHandle = new RemixdHandle(this._deps.fileProviders.localhost, appManager)
this.gitHandle = new GitHandle()
this.hardhatHandle = new HardhatHandle()
this.registeredMenuItems = []
this.request = {}
this.workspaces = []
@ -188,8 +188,11 @@ module.exports = class Filepanel extends ViewPlugin {
const browserProvider = this._deps.fileProviders.browser
const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name
const workspaceRootPath = 'browser/' + workspaceProvider.workspacesPath
if (!browserProvider.exists(workspaceRootPath)) browserProvider.createDir(workspaceRootPath)
if (!browserProvider.exists(workspacePath)) browserProvider.createDir(workspacePath)
const workspaceRootPathExists = await browserProvider.exists(workspaceRootPath)
const workspacePathExists = await browserProvider.exists(workspacePath)
if (!workspaceRootPathExists) browserProvider.createDir(workspaceRootPath)
if (!workspacePathExists) browserProvider.createDir(workspacePath)
}
async workspaceExists (name) {
@ -209,11 +212,13 @@ module.exports = class Filepanel extends ViewPlugin {
workspaceProvider.setWorkspace(workspaceName)
await this.request.setWorkspace(workspaceName) // tells the react component to switch to that workspace
for (const file in examples) {
try {
await workspaceProvider.set(examples[file].name, examples[file].content)
} catch (error) {
console.error(error)
}
setTimeout(async () => { // space creation of files to give react ui time to update.
try {
await workspaceProvider.set(examples[file].name, examples[file].content)
} catch (error) {
console.error(error)
}
}, 10)
}
}
}

@ -1,9 +1,11 @@
import React from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web'
import ReactDOM from 'react-dom'
import { EventEmitter } from 'events'
import {RemixUiStaticAnalyser} from '@remix-ui/static-analyser' // eslint-disable-line
import * as packageJson from '../../../../../package.json'
var Renderer = require('../ui/renderer')
var yo = require('yo-yo')
var StaticAnalysis = require('./staticanalysis/staticAnalysisView')
var EventManager = require('../../lib/events')
const profile = {
@ -25,23 +27,49 @@ class AnalysisTab extends ViewPlugin {
this.event = new EventManager()
this.events = new EventEmitter()
this.registry = registry
this.element = document.createElement('div')
this.element.setAttribute('id', 'staticAnalyserView')
this._components = {
renderer: new Renderer(this)
}
this._components.registry = this.registry
this._deps = {
offsetToLineColumnConverter: this.registry.get(
'offsettolinecolumnconverter').api
}
}
onActivation () {
this.renderComponent()
}
render () {
this.staticanalysis = new StaticAnalysis(this.registry, this)
this.staticanalysis.event.register('staticAnaysisWarning', (count) => {
if (count > 0) {
this.emit('statusChanged', { key: count, title: `${count} warning${count === 1 ? '' : 's'}`, type: 'warning' })
} else if (count === 0) {
this.emit('statusChanged', { key: 'succeed', title: 'no warning', type: 'success' })
} else {
// count ==-1 no compilation result
this.emit('statusChanged', { key: 'none' })
}
})
this.registry.put({ api: this.staticanalysis, name: 'staticanalysis' })
return this.element
}
return yo`<div class="px-3 pb-1" id="staticanalysisView">${this.staticanalysis.render()}</div>`
renderComponent () {
ReactDOM.render(
<RemixUiStaticAnalyser
analysisRunner={this.runner}
registry={this.registry}
staticanalysis={this.staticanalysis}
analysisModule={this}
event={this.event}
/>,
this.element,
() => {
this.event.register('staticAnaysisWarning', (count) => {
if (count > 0) {
this.emit('statusChanged', { key: count, title: `${count} warning${count === 1 ? '' : 's'}`, type: 'warning' })
} else if (count === 0) {
this.emit('statusChanged', { key: 'succeed', title: 'no warning', type: 'success' })
} else {
// count ==-1 no compilation result
this.emit('statusChanged', { key: 'none' })
}
})
}
)
}
}

@ -65,15 +65,10 @@ class CompileTab extends ViewPlugin {
eventHandlers: {},
loading: false
}
this.compileTabLogic = new CompileTabLogic(this.queryParams, this.fileManager, this.editor, this.config, this.fileProvider, this.contentImport)
}
onActivationInternal () {
const miscApi = {
clearAnnotations: () => {
this.call('editor', 'clearAnnotations')
}
}
this.compileTabLogic = new CompileTabLogic(this.queryParams, this.fileManager, this.editor, this.config, this.fileProvider, this.contentImport, miscApi)
this.compiler = this.compileTabLogic.compiler
this.compileTabLogic.init()
@ -90,6 +85,13 @@ class CompileTab extends ViewPlugin {
*/
listenToEvents () {
this.on('filePanel', 'setWorkspace', (workspace) => {
this.compileTabLogic.isHardhatProject().then((result) => {
if (result && workspace.isLocalhost) this.compilerContainer.hardhatCompilation.style.display = 'flex'
else this.compilerContainer.hardhatCompilation.style.display = 'none'
})
})
this.data.eventHandlers.onContentChanged = () => {
this.emit('statusChanged', { key: 'edited', title: 'the content has changed, needs recompilation', type: 'info' })
}
@ -199,7 +201,7 @@ class CompileTab extends ViewPlugin {
// ctrl+s or command+s
if ((e.metaKey || e.ctrlKey) && e.keyCode === 83) {
e.preventDefault()
this.compileTabLogic.runCompiler()
this.compileTabLogic.runCompiler(this.compilerContainer.hhCompilation)
}
})
}
@ -479,6 +481,7 @@ class CompileTab extends ViewPlugin {
}
onActivation () {
this.call('manager', 'activatePlugin', 'solidity-logic')
this.listenToEvents()
}
@ -492,6 +495,7 @@ class CompileTab extends ViewPlugin {
this.fileManager.events.removeListener('noFileSelected', this.data.eventHandlers.onNoFileSelected)
this.compiler.event.unregister('compilationFinished', this.data.eventHandlers.onCompilationFinished)
globalRegistry.get('themeModule').api.events.removeListener('themeChanged', this.data.eventHandlers.onThemeChanged)
this.call('manager', 'deactivatePlugin', 'solidity-logic')
}
}

@ -1,10 +1,19 @@
import * as packageJson from '../../../../../../package.json'
import { Plugin } from '@remixproject/engine'
const EventEmitter = require('events')
var Compiler = require('@remix-project/remix-solidity').Compiler
class CompileTab {
constructor (queryParams, fileManager, editor, config, fileProvider, contentImport, miscApi) {
const profile = {
name: 'solidity-logic',
displayName: 'Solidity compiler logic',
description: 'Compile solidity contracts - Logic',
version: packageJson.version
}
class CompileTab extends Plugin {
constructor (queryParams, fileManager, editor, config, fileProvider, contentImport) {
super(profile)
this.event = new EventEmitter()
this.miscApi = miscApi
this.queryParams = queryParams
this.compilerImport = contentImport
this.compiler = new Compiler((url, cb) => this.compilerImport.resolveAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message)))
@ -78,10 +87,32 @@ class CompileTab {
})
}
runCompiler () {
async isHardhatProject () {
if (this.fileManager.mode === 'localhost') {
return await this.fileManager.exists('hardhat.config.js')
} else return false
}
runCompiler (hhCompilation) {
try {
if (this.fileManager.mode === 'localhost' && hhCompilation) {
const { currentVersion, optimize, runs } = this.compiler.state
const fileContent = `module.exports = {
solidity: '${currentVersion.substring(0, currentVersion.indexOf('+commit'))}',
settings: {
optimizer: {
enabled: ${optimize},
runs: ${runs}
}
}
}
`
const configFilePath = 'remix-compiler.config.js'
this.fileManager.setFileContent(configFilePath, fileContent)
this.call('hardhat', 'compile', configFilePath)
}
this.fileManager.saveCurrentFile()
this.miscApi.clearAnnotations()
this.call('editor', 'clearAnnotations')
var currentFile = this.config.get('currentFile')
return this.compileFile(currentFile)
} catch (err) {

@ -15,6 +15,7 @@ class CompilerContainer {
this.editor = editor
this.config = config
this.queryParams = queryParams
this.hhCompilation = false
this.data = {
hideWarnings: config.get('hideWarnings') || false,
@ -183,6 +184,10 @@ class CompilerContainer {
}
})
this.hardhatCompilation = yo`<div class="mt-2 ${css.compilerConfig} custom-control custom-checkbox" style="display:none">
<input class="${css.autocompile} custom-control-input" onchange=${(e) => this.updatehhCompilation(e)} id="enableHardhat" type="checkbox" title="Enable Hardhat Compilation">
<label class="form-check-label custom-control-label" for="enableHardhat">Enable Hardhat Compilation</label>
</div>`
this._view.warnCompilationSlow = yo`<i title="Compilation Slow" style="visibility:hidden" class="${css.warnCompilationSlow} fas fa-exclamation-triangle" aria-hidden="true"></i>`
this._view.compileIcon = yo`<i class="fas fa-sync ${css.icon}" aria-hidden="true"></i>`
this._view.autoCompile = yo`<input class="${css.autocompile} custom-control-input" onchange=${() => this.updateAutoCompile()} data-id="compilerContainerAutoCompile" id="autoCompile" type="checkbox" title="Auto compile">`
@ -299,6 +304,7 @@ class CompilerContainer {
<label class="form-check-label custom-control-label" for="hideWarningsBox">Hide warnings</label>
</div>
</div>
${this.hardhatCompilation}
${this._view.compilationButton}
</header>
</article>
@ -326,12 +332,16 @@ class CompilerContainer {
this.config.set('autoCompile', this._view.autoCompile.checked)
}
updatehhCompilation (event) {
this.hhCompilation = event.target.checked
}
compile (event) {
const currentFile = this.config.get('currentFile')
if (!this.isSolFileSelected()) return
this._setCompilerVersionFromPragma(currentFile)
this.compileTabLogic.runCompiler()
this.compileTabLogic.runCompiler(this.hhCompilation)
}
compileIfAutoCompileOn () {
@ -517,7 +527,7 @@ class CompilerContainer {
// fetching both normal and wasm builds and creating a [version, baseUrl] map
async fetchAllVersion (callback) {
let selectedVersion, allVersionsWasm, isURL
let allVersions = [{ path: 'builtin', longVersion: 'latest local version - 0.7.4' }]
let allVersions = [{ path: 'builtin', longVersion: 'Stable local version - 0.7.4' }]
// fetch normal builds
const binRes = await promisedMiniXhr(`${baseURLBin}/list.json`)
// fetch wasm builds

@ -9,6 +9,7 @@ const confirmDialog = require('../../ui/confirmDialog')
const modalDialog = require('../../ui/modaldialog')
const MultiParamManager = require('../../ui/multiParamManager')
const helper = require('../../../lib/helper')
const _paq = window._paq = window._paq || []
class ContractDropdownUI {
constructor (blockchain, dropdownLogic, logCallback, runView) {
@ -300,13 +301,15 @@ class ContractDropdownUI {
if (error) {
return this.logCallback(error)
}
self.event.trigger('newContractInstanceAdded', [contractObject, address, contractObject.name])
const data = self.runView.compilersArtefacts.getCompilerAbstract(contractObject.contract.file)
self.runView.compilersArtefacts.addResolvedContract(helper.addressToString(address), data)
if (self.ipfsCheckedState) {
_paq.push(['trackEvent', 'udapp', `DeployAndPublish_${this.networkName}`])
publishToStorage('ipfs', self.runView.fileProvider, self.runView.fileManager, selectedContract)
} else {
_paq.push(['trackEvent', 'udapp', `DeployOnly_${this.networkName}`])
}
}
@ -340,6 +343,7 @@ class ContractDropdownUI {
}
deployContract (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) {
_paq.push(['trackEvent', 'udapp', 'DeployContractTo', this.networkName])
const { statusCb } = callbacks
if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) {
return this.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb)

@ -1,7 +1,8 @@
var remixLib = require('@remix-project/remix-lib')
var txHelper = remixLib.execution.txHelper
var CompilerAbstract = require('../../../compiler/compiler-abstract')
var EventManager = remixLib.EventManager
const remixLib = require('@remix-project/remix-lib')
const txHelper = remixLib.execution.txHelper
const CompilerAbstract = require('../../../compiler/compiler-abstract')
const EventManager = remixLib.EventManager
const _paq = window._paq = window._paq || []
class DropdownLogic {
constructor (compilersArtefacts, config, editor, runView) {
@ -50,9 +51,11 @@ class DropdownLogic {
} catch (e) {
return cb('Failed to parse the current file as JSON ABI.')
}
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithABI'])
cb(null, 'abi', abi)
})
} else {
_paq.push(['trackEvent', 'udapp', 'AtAddressLoadWithInstance'])
cb(null, 'instance')
}
}

@ -1,3 +1,4 @@
import { BN } from 'ethereumjs-util'
const $ = require('jquery')
const yo = require('yo-yo')
const remixLib = require('@remix-project/remix-lib')
@ -65,14 +66,26 @@ class SettingsUI {
validateValue () {
const valueEl = this.el.querySelector('#value')
valueEl.value = parseInt(valueEl.value)
// assign 0 if given value is
// - empty
// - not valid (for ex 4345-54)
// - contains only '0's (for ex 0000) copy past or edit
if (!valueEl.value) valueEl.value = 0
if (!valueEl.value) {
// assign 0 if given value is
// - empty
valueEl.value = 0
return
}
let v
try {
v = new BN(valueEl.value, 10)
valueEl.value = v.toString(10)
} catch (e) {
// assign 0 if given value is
// - not valid (for ex 4345-54)
// - contains only '0's (for ex 0000) copy past or edit
valueEl.value = 0
}
// if giveen value is negative(possible with copy-pasting) set to 0
if (valueEl.value < 0) valueEl.value = 0
if (v.lt(0)) valueEl.value = 0
}
render () {

@ -1,302 +0,0 @@
'use strict'
var StaticAnalysisRunner = require('@remix-project/remix-analyzer').CodeAnalysis
var yo = require('yo-yo')
var $ = require('jquery')
var remixLib = require('@remix-project/remix-lib')
var utils = remixLib.util
var css = require('./styles/staticAnalysisView-styles')
var Renderer = require('../../ui/renderer')
const SourceHighlighter = require('../../editor/sourceHighlighter')
var EventManager = require('../../../lib/events')
function staticAnalysisView (localRegistry, analysisModule) {
var self = this
this.event = new EventManager()
this.view = null
this.runner = new StaticAnalysisRunner()
this.modulesView = this.renderModules()
this.lastCompilationResult = null
this.lastCompilationSource = null
this.currentFile = 'No file compiled'
this.sourceHighlighter = new SourceHighlighter()
this.analysisModule = analysisModule
self._components = {
renderer: new Renderer(analysisModule)
}
self._components.registry = localRegistry
// dependencies
self._deps = {
offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api
}
analysisModule.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => {
self.lastCompilationResult = null
self.lastCompilationSource = null
if (languageVersion.indexOf('soljson') !== 0) return
self.lastCompilationResult = data
self.lastCompilationSource = source
self.currentFile = file
self.correctRunBtnDisabled()
if (self.view && self.view.querySelector('#autorunstaticanalysis').checked) {
self.run()
}
})
}
staticAnalysisView.prototype.render = function () {
this.runBtn = yo`<button class="btn btn-sm w-25 btn-primary" onclick="${() => { this.run() }}" >Run</button>`
const view = yo`
<div class="${css.analysis}">
<div class="my-2 d-flex flex-column align-items-left">
<div class="${css.top} d-flex justify-content-between">
<div class="pl-2 ${css.label}" for="checkAllEntries">
<input id="checkAllEntries"
type="checkbox"
onclick="${(event) => { this.checkAll(event) }}"
style="vertical-align:bottom"
checked="true"
>
<label class="text-nowrap pl-2 mb-0" for="checkAllEntries">
Select all
</label>
</div>
<div class="${css.label}" for="autorunstaticanalysis">
<input id="autorunstaticanalysis"
type="checkbox"
style="vertical-align:bottom"
checked="true"
>
<label class="text-nowrap pl-2 mb-0" for="autorunstaticanalysis">
Autorun
</label>
</div>
${this.runBtn}
</div>
</div>
<div id="staticanalysismodules" class="list-group list-group-flush">
${this.modulesView}
</div>
<div class="mt-2 p-2 d-flex border-top flex-column">
<span>last results for:</span>
<span class="text-break break-word word-break font-weight-bold" id="staticAnalysisCurrentFile">${this.currentFile}</span>
</div>
<div class="${css.result} my-1" id='staticanalysisresult'></div>
</div>
`
if (!this.view) {
this.view = view
}
this.correctRunBtnDisabled()
return view
}
staticAnalysisView.prototype.selectedModules = function () {
if (!this.view) {
return []
}
const selected = this.view.querySelectorAll('[name="staticanalysismodule"]:checked')
var toRun = []
for (var i = 0; i < selected.length; i++) {
toRun.push(selected[i].attributes.index.value)
}
return toRun
}
staticAnalysisView.prototype.run = function () {
if (!this.view) {
return
}
const highlightLocation = async (location, fileName) => {
await this.analysisModule.call('editor', 'discardHighlight')
await this.analysisModule.call('editor', 'highlight', location, fileName)
}
const selected = this.selectedModules()
const warningContainer = $('#staticanalysisresult')
warningContainer.empty()
this.view.querySelector('#staticAnalysisCurrentFile').innerText = this.currentFile
var self = this
if (this.lastCompilationResult && selected.length) {
this.runBtn.removeAttribute('disabled')
let warningCount = 0
this.runner.run(this.lastCompilationResult, selected, (results) => {
const groupedModules = utils.groupBy(preProcessModules(this.runner.modules()), 'categoryId')
results.map((result, j) => {
let moduleName
Object.keys(groupedModules).map((key) => {
groupedModules[key].forEach((el) => {
if (el.name === result.name) {
moduleName = groupedModules[key][0].categoryDisplayName
}
})
})
const alreadyExistedEl = this.view.querySelector(`[id="staticAnalysisModule${moduleName}"]`)
if (!alreadyExistedEl) {
warningContainer.append(`
<div class="mb-4" name="staticAnalysisModules" id="staticAnalysisModule${moduleName}">
<span class="text-dark h6">${moduleName}</span>
</div>
`)
}
result.report.map((item, i) => {
let location = ''
let locationString = 'not available'
let column = 0
let row = 0
let fileName = this.currentFile
if (item.location) {
var split = item.location.split(':')
var file = split[2]
location = {
start: parseInt(split[0]),
length: parseInt(split[1])
}
location = self._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
parseInt(file),
self.lastCompilationSource.sources,
self.lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = (row + 1) + ':' + column + ':'
fileName = Object.keys(self.lastCompilationResult.contracts)[file]
}
warningCount++
const msg = yo`
<span class="d-flex flex-column">
<span class="h6 font-weight-bold">${result.name}</span>
${item.warning}
${item.more ? yo`<span><a href="${item.more}" target="_blank">more</a></span>` : yo`<span></span>`}
<span class="" title="Position in ${fileName}">Pos: ${locationString}</span>
</span>`
self._components.renderer.error(
msg,
this.view.querySelector(`[id="staticAnalysisModule${moduleName}"]`),
{
click: () => highlightLocation(location, fileName),
type: 'warning',
useSpan: true,
errFile: fileName,
errLine: row,
errCol: column
}
)
})
})
// hide empty staticAnalysisModules sections
this.view.querySelectorAll('[name="staticAnalysisModules"]').forEach((section) => {
if (!section.getElementsByClassName('alert-warning').length) section.hidden = true
})
self.event.trigger('staticAnaysisWarning', [warningCount])
})
} else {
this.runBtn.setAttribute('disabled', 'disabled')
if (selected.length) {
warningContainer.html('No compiled AST available')
}
self.event.trigger('staticAnaysisWarning', [-1])
}
}
staticAnalysisView.prototype.checkModule = function (event) {
const selected = this.view.querySelectorAll('[name="staticanalysismodule"]:checked')
const checkAll = this.view.querySelector('[id="checkAllEntries"]')
this.correctRunBtnDisabled()
if (event.target.checked) {
checkAll.checked = true
} else if (!selected.length) {
checkAll.checked = false
}
}
staticAnalysisView.prototype.correctRunBtnDisabled = function () {
if (!this.view) {
return
}
const selected = this.view.querySelectorAll('[name="staticanalysismodule"]:checked')
if (this.lastCompilationResult && selected.length !== 0) {
this.runBtn.removeAttribute('disabled')
} else {
this.runBtn.setAttribute('disabled', 'disabled')
}
}
staticAnalysisView.prototype.checkAll = function (event) {
if (!this.view) {
return
}
// checks/unchecks all
const checkBoxes = this.view.querySelectorAll('[name="staticanalysismodule"]')
checkBoxes.forEach((checkbox) => { checkbox.checked = event.target.checked })
this.correctRunBtnDisabled()
}
staticAnalysisView.prototype.handleCollapse = function (e) {
const downs = e.toElement.parentElement.getElementsByClassName('fas fa-angle-double-right')
const iEls = document.getElementsByTagName('i')
for (var i = 0; i < iEls.length; i++) { iEls[i].hidden = false }
downs[0].hidden = true
}
staticAnalysisView.prototype.renderModules = function () {
const groupedModules = utils.groupBy(preProcessModules(this.runner.modules()), 'categoryId')
const moduleEntries = Object.keys(groupedModules).map((categoryId, i) => {
const category = groupedModules[categoryId]
const entriesDom = category.map((item, i) => {
return yo`
<div class="form-check">
<input id="staticanalysismodule_${categoryId}_${i}"
type="checkbox"
class="form-check-input staticAnalysisItem"
name="staticanalysismodule"
index=${item._index}
checked="true"
style="vertical-align:bottom"
onclick="${(event) => this.checkModule(event)}"
>
<label for="staticanalysismodule_${categoryId}_${i}" class="form-check-label mb-1">
<p class="mb-0 font-weight-bold text-capitalize">${item.name}</p>
${item.description}
</label>
</div>
`
})
return yo`
<div class="${css.block}">
<input type="radio" name="accordion" class="w-100 d-none card" id="heading${categoryId}" onclick=${(e) => this.handleCollapse(e)}"/>
<label for="heading${categoryId}" style="cursor: pointer;" class="pl-3 card-header h6 d-flex justify-content-between font-weight-bold border-left px-1 py-2 w-100">
${category[0].categoryDisplayName}
<div>
<i class="fas fa-angle-double-right"></i>
</div>
</label>
<div class="w-100 d-block px-2 my-1 ${css.entries}">
${entriesDom}
</div>
</div>
`
})
// collaps first module
moduleEntries[0].getElementsByTagName('input')[0].checked = true
moduleEntries[0].getElementsByTagName('i')[0].hidden = true
return yo`
<div class="accordion" id="accordionModules">
${moduleEntries}
</div>`
}
module.exports = staticAnalysisView
/**
* @dev Process & categorize static analysis modules to show them on UI
* @param arr list of static analysis modules received from remix-analyzer module
*/
function preProcessModules (arr) {
return arr.map((Item, i) => {
const itemObj = new Item()
itemObj._index = i
itemObj.categoryDisplayName = itemObj.category.displayName
itemObj.categoryId = itemObj.category.id
return itemObj
})
}

@ -1,36 +0,0 @@
var csjs = require('csjs-inject')
var css = csjs`
.analysis {
display: flex;
flex-direction: column;
}
.result {
margin-top: 1%;
max-height: 300px;
word-break: break-word;
}
.buttons {
margin: 1rem 0;
}
.label {
display: flex;
align-items: center;
}
.label {
display: flex;
align-items: center;
user-select: none;
}
.block input[type='radio']:checked ~ .entries{
height: auto;
transition: .5s ease-in;
}
.entries{
height: 0;
overflow: hidden;
transition: .5s ease-out;
}
`
module.exports = css

@ -52,7 +52,7 @@ module.exports = class TestTab extends ViewPlugin {
}
listenToEvents () {
this.filePanel.event.register('newTestFileCreated', file => {
this.on('filePanel', 'newTestFileCreated', file => {
var testList = this._view.el.querySelector("[class^='testList']")
var test = this.createSingleTest(file)
testList.appendChild(test)

@ -18,7 +18,9 @@ class TestTabLogic {
// Checking to ignore the value which contains only whitespaces
if (!path || !(/\S/.test(path))) return
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0])
fileProvider.exists(path, (e, res) => { if (!res) fileProvider.createDir(path) })
fileProvider.exists(path).then(res => {
if (!res) fileProvider.createDir(path)
})
}
pathExists (path) {

@ -15,6 +15,7 @@ const RecorderUI = require('../tabs/runTab/recorder.js')
const DropdownLogic = require('../tabs/runTab/model/dropdownlogic.js')
const ContractDropdownUI = require('../tabs/runTab/contractDropdown.js')
const toaster = require('../ui/tooltip')
const _paq = window._paq = window._paq || []
const UniversalDAppUI = require('../ui/universal-dapp-ui')
@ -91,6 +92,7 @@ export class RunTab extends ViewPlugin {
}
sendTransaction (tx) {
_paq.push(['trackEvent', 'udapp', 'sendTx'])
return this.blockchain.sendTransaction(tx)
}

@ -39,10 +39,11 @@ Renderer.prototype._errorClick = function (errFile, errLine, errCol) {
// TODO: refactor with this._components.contextView.jumpTo
var provider = self._deps.fileManager.fileProviderOf(errFile)
if (provider) {
provider.exists(errFile, (error, exist) => {
if (error) return console.log(error)
provider.exists(errFile).then(exist => {
self._deps.fileManager.open(errFile)
editor.gotoLine(errLine, errCol)
}).catch(error => {
if (error) return console.log(error)
})
}
} else {

@ -14,6 +14,7 @@ var txFormat = remixLib.execution.txFormat
const txHelper = remixLib.execution.txHelper
var TreeView = require('./TreeView')
var txCallBacks = require('./sendTxCallbacks')
const _paq = window._paq = window._paq || []
function UniversalDAppUI (blockchain, logCallback) {
this.blockchain = blockchain
@ -243,6 +244,8 @@ UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, i
outputOverride.appendChild(decoded)
}
}
const info = `${lookupOnly ? 'call' : args.funABI.type !== 'fallback' ? 'lowLevelInteracions' : 'transact'}_${this.blockchain.executionContext.executionContext}`
_paq.push(['trackEvent', 'udapp', info])
const params = args.funABI.type !== 'fallback' ? inputsValues : ''
this.blockchain.runOrCallContractMethod(
args.contractName,

@ -36,14 +36,12 @@ module.exports = {
async.whilst(
() => { return exist },
(callback) => {
fileProvider.exists(name + counter + prefix + '.' + ext, (error, currentExist) => {
if (error) {
callback(error)
} else {
exist = currentExist
if (exist) counter = (counter | 0) + 1
callback()
}
fileProvider.exists(name + counter + prefix + '.' + ext).then(currentExist => {
exist = currentExist
if (exist) counter = (counter | 0) + 1
callback()
}).catch(error => {
if (error) console.log(error)
})
},
(error) => { cb(error, name + counter + prefix + '.' + ext) }
@ -52,6 +50,27 @@ module.exports = {
createNonClashingName (name, fileProvider, cb) {
this.createNonClashingNameWithPrefix(name, fileProvider, '', cb)
},
async createNonClashingNameAsync (name, fileManager, prefix = '') {
if (!name) name = 'Undefined'
let counter = ''
let ext = 'sol'
const reg = /(.*)\.([^.]+)/g
const split = reg.exec(name)
if (split) {
name = split[1]
ext = split[2]
}
let exist = true
do {
const isDuplicate = await fileManager.exists(name + counter + prefix + '.' + ext)
if (isDuplicate) counter = (counter | 0) + 1
else exist = false
} while (exist)
return name + counter + prefix + '.' + ext
},
checkSpecialChars (name) {
return name.match(/[:*?"<>\\'|]/) != null
},

@ -11,10 +11,10 @@ const requiredModules = [ // services + layout views + system views
'fileManager', 'contentImport', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp']
const dependentModules = ['git'] // module which shouldn't be manually activated (e.g git is activated by remixd)
const dependentModules = ['git', 'hardhat'] // module which shouldn't be manually activated (e.g git is activated by remixd)
export function isNative (name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons']
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity']
return nativePlugins.includes(name) || requiredModules.includes(name)
}

@ -104,9 +104,10 @@ export class Ethdebugger {
const stack = this.traceManager.getStackAt(step)
const memory = this.traceManager.getMemoryAt(step)
const address = this.traceManager.getCurrentCalledAddressAt(step)
const calldata = this.traceManager.getCallDataAt(step)
try {
const storageViewer = new StorageViewer({ stepIndex: step, tx: this.tx, address: address }, this.storageResolver, this.traceManager)
const locals = await localDecoder.solidityLocals(step, this.callTree, stack, memory, storageViewer, sourceLocation, null)
const locals = await localDecoder.solidityLocals(step, this.callTree, stack, memory, storageViewer, calldata, sourceLocation, null)
if (locals['error']) {
return callback(locals['error'])
}

@ -62,6 +62,14 @@ export class DebuggerSolidityLocals {
} catch (error) {
next(error)
}
},
function getCallDataAt (stepIndex, next) {
try {
const calldata = self.traceManager.getCallDataAt(stepIndex)
next(null, calldata)
} catch (error) {
next(error)
}
}],
this.stepManager.currentStepIndex,
(error, result) => {
@ -70,9 +78,10 @@ export class DebuggerSolidityLocals {
}
var stack = result[0].value
var memory = result[1].value
var calldata = result[3].value
try {
var storageViewer = new StorageViewer({ stepIndex: this.stepManager.currentStepIndex, tx: this.tx, address: result[2].value }, this.storageResolver, this.traceManager)
solidityLocals(this.stepManager.currentStepIndex, this.internalTreeCall, stack, memory, storageViewer, sourceLocation, cursor).then((locals) => {
solidityLocals(this.stepManager.currentStepIndex, this.internalTreeCall, stack, memory, storageViewer, calldata, sourceLocation, cursor).then((locals) => {
if (!cursor) {
if (!locals['error']) {
this.event.trigger('solidityLocals', [locals])

@ -310,10 +310,10 @@ async function includeVariableDeclaration (tree, step, sourceLocation, scopeId,
// }
// input params
if (inputs && inputs.parameters) {
functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractObj.name, previousSourceLocation, stack.length, inputs.parameters.length, -1)
functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractObj, previousSourceLocation, stack.length, inputs.parameters.length, -1)
}
// output params
if (outputs) addParams(outputs, tree, scopeId, states, contractObj.name, previousSourceLocation, stack.length, 0, 1)
if (outputs) addParams(outputs, tree, scopeId, states, contractObj, previousSourceLocation, stack.length, 0, 1)
}
} catch (error) {
console.log(error)
@ -373,7 +373,8 @@ function extractFunctionDefinitions (ast, astWalker) {
return ret
}
function addParams (parameterList, tree, scopeId, states, contractName, sourceLocation, stackLength, stackPosition, dir) {
function addParams (parameterList, tree, scopeId, states, contractObj, sourceLocation, stackLength, stackPosition, dir) {
const contractName = contractObj.name
const params = []
for (const inputParam in parameterList.parameters) {
const param = parameterList.parameters[inputParam]
@ -386,7 +387,8 @@ function addParams (parameterList, tree, scopeId, states, contractName, sourceLo
name: attributesName,
type: parseType(param.typeDescriptions.typeString, states, contractName, location),
stackDepth: stackDepth,
sourceLocation: sourceLocation
sourceLocation: sourceLocation,
abi: contractObj.contract.abi
}
params.push(attributesName)
}

@ -1,6 +1,6 @@
'use strict'
export async function solidityLocals (vmtraceIndex, internalTreeCall, stack, memory, storageResolver, currentSourceLocation, cursor) {
export async function solidityLocals (vmtraceIndex, internalTreeCall, stack, memory, storageResolver, calldata, currentSourceLocation, cursor) {
const scope = internalTreeCall.findScope(vmtraceIndex)
if (!scope) {
const error = { message: 'Can\'t display locals. reason: compilation result might not have been provided' }
@ -18,7 +18,7 @@ export async function solidityLocals (vmtraceIndex, internalTreeCall, stack, mem
anonymousIncr++
}
try {
locals[name] = await variable.type.decodeFromStack(variable.stackDepth, stack, memory, storageResolver, cursor)
locals[name] = await variable.type.decodeFromStack(variable.stackDepth, stack, memory, storageResolver, calldata, cursor, variable)
} catch (e) {
console.log(e)
locals[name] = '<decoding failed - ' + e.message + '>'

@ -1,4 +1,5 @@
'use strict'
import { ethers } from 'ethers'
import { toBN } from './util'
export class RefType {
@ -7,6 +8,7 @@ export class RefType {
storageBytes
typeName
basicType
underlyingType
constructor (storageSlots, storageBytes, typeName, location) {
this.location = location
@ -33,7 +35,7 @@ export class RefType {
* @param {Object} - storageResolver
* @return {Object} decoded value
*/
async decodeFromStack (stackDepth, stack, memory, storageResolver, cursor): Promise<any> {
async decodeFromStack (stackDepth, stack, memory, storageResolver, calldata, cursor, variableDetails?): Promise<any> {
if (stack.length - 1 < stackDepth) {
return { error: '<decoding failed - stack underflow ' + stackDepth + '>', type: this.typeName }
}
@ -49,6 +51,26 @@ export class RefType {
} else if (this.isInMemory()) {
offset = parseInt(offset, 16)
return this.decodeFromMemoryInternal(offset, memory, cursor)
} else if (this.isInCallData()) {
calldata = calldata.length > 0 ? calldata[0] : '0x'
const ethersAbi = new ethers.utils.Interface(variableDetails.abi)
const fnSign = calldata.substr(0, 10)
const decodedData = ethersAbi.decodeFunctionData(ethersAbi.getFunction(fnSign), calldata)
let decodedValue = decodedData[variableDetails.name]
const isArray = Array.isArray(decodedValue)
if (isArray) {
decodedValue = decodedValue.map((el) => {
return {
value: el.toString(),
type: this.underlyingType.typeName
}
})
}
return {
length: Array.isArray(decodedValue) ? '0x' + decodedValue.length.toString(16) : undefined,
value: decodedValue,
type: this.typeName
}
} else {
return { error: '<decoding failed - no decoder for ' + this.location + '>', type: this.typeName }
}
@ -84,4 +106,13 @@ export class RefType {
isInMemory () {
return this.location.indexOf('memory') === 0
}
/**
* current type defined in storage
*
* @return {Bool} - return true if the type is defined in the storage
*/
isInCallData () {
return this.location.indexOf('calldata') === 0
}
}

@ -20,9 +20,9 @@ export class StringType extends DynamicByteArray {
return format(decoded)
}
async decodeFromStack (stackDepth, stack, memory) {
async decodeFromStack (stackDepth, stack, memory, calldata, variableDetails?) {
try {
return await super.decodeFromStack(stackDepth, stack, memory, null, null)
return await super.decodeFromStack(stackDepth, stack, memory, null, calldata, variableDetails)
} catch (e) {
console.log(e)
return '<decoding failed - ' + e.message + '>'

@ -43,7 +43,7 @@ export class ValueType {
* @param {String} - memory
* @return {Object} - decoded value
*/
async decodeFromStack (stackDepth, stack, memory) {
async decodeFromStack (stackDepth, stack, memory, calldata, variableDetails?) {
let value
if (stackDepth >= stack.length) {
value = this.decodeValue('')

@ -22,13 +22,21 @@ export function decodeLocals (st, index, traceManager, callTree, verifier) {
} catch (error) {
callback(error)
}
},
function getCallDataAt (stepIndex, callback) {
try {
const result = traceManager.getCallDataAt(stepIndex)
callback(null, result)
} catch (error) {
callback(error)
}
}],
index,
function (error, result) {
if (error) {
return st.fail(error)
}
solidityLocals(index, callTree, result[0].value, result[1].value, {}, { start: 5000 }, null).then((locals) => {
solidityLocals(index, callTree, result[0].value, result[1].value, {}, result[2].value, { start: 5000 }, null).then((locals) => {
verifier(locals)
})
})

@ -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-checkbox
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-checkbox` to execute the unit tests via [Jest](https://jestjs.io).

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

@ -0,0 +1,47 @@
import React from 'react' //eslint-disable-line
import './remix-ui-checkbox.css'
/* eslint-disable-next-line */
export interface RemixUiCheckboxProps {
onClick?: (event) => void
onChange?: (event) => void
label?: string
inputType?: string
name?: string
checked?: boolean
id?: string
itemName?: string
categoryId?: string
}
export const RemixUiCheckbox = ({
id,
label,
onClick,
inputType,
name,
checked,
onChange,
itemName,
categoryId
}: RemixUiCheckboxProps) => {
return (
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" style={{ display: 'flex', alignItems: 'center' }} onClick={onClick}>
<input
id={id}
type={inputType}
onChange={onChange}
style={{ verticalAlign: 'bottom' }}
name={name}
className="custom-control-input"
checked={checked}
/>
<label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{ paddingTop: '0.15rem' }}>
{name ? <div className="font-weight-bold">{itemName}</div> : ''}
{label}
</label>
</div>
)
}
export default RemixUiCheckbox

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

@ -0,0 +1,293 @@
import React from 'react'
import { File } from '../types'
import { extractNameFromKey, extractParentFromKey } from '../utils'
export const fetchDirectoryError = (error: any) => {
return {
type: 'FETCH_DIRECTORY_ERROR',
payload: error
}
}
export const fetchDirectoryRequest = (promise: Promise<any>) => {
return {
type: 'FETCH_DIRECTORY_REQUEST',
payload: promise
}
}
export const fetchDirectorySuccess = (path: string, files: File[]) => {
return {
type: 'FETCH_DIRECTORY_SUCCESS',
payload: { path, files }
}
}
export const fileSystemReset = () => {
return {
type: 'FILESYSTEM_RESET'
}
}
const normalize = (parent, filesList, newInputType?: string): any => {
const folders = {}
const files = {}
Object.keys(filesList || {}).forEach(key => {
key = key.replace(/^\/|\/$/g, '') // remove first and last slash
let path = key
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
if (filesList[key].isDirectory) {
folders[extractNameFromKey(key)] = {
path,
name: extractNameFromKey(path),
isDirectory: filesList[key].isDirectory
}
} else {
files[extractNameFromKey(key)] = {
path,
name: extractNameFromKey(path),
isDirectory: filesList[key].isDirectory
}
}
})
if (newInputType === 'folder') {
const path = parent + '/blank'
folders[path] = {
path: path,
name: '',
isDirectory: true
}
} else if (newInputType === 'file') {
const path = parent + '/blank'
files[path] = {
path: path,
name: '',
isDirectory: false
}
}
return Object.assign({}, folders, files)
}
const fetchDirectoryContent = async (provider, folderPath: string, newInputType?: string): Promise<any> => {
return new Promise((resolve) => {
provider.resolveDirectory(folderPath, (error, fileTree) => {
if (error) console.error(error)
const files = normalize(folderPath, fileTree, newInputType)
resolve({ [extractNameFromKey(folderPath)]: files })
})
})
}
export const fetchDirectory = (provider, path: string) => (dispatch: React.Dispatch<any>) => {
const promise = fetchDirectoryContent(provider, path)
dispatch(fetchDirectoryRequest(promise))
promise.then((files) => {
dispatch(fetchDirectorySuccess(path, files))
}).catch((error) => {
dispatch(fetchDirectoryError({ error }))
})
return promise
}
export const resolveDirectoryError = (error: any) => {
return {
type: 'RESOLVE_DIRECTORY_ERROR',
payload: error
}
}
export const resolveDirectoryRequest = (promise: Promise<any>) => {
return {
type: 'RESOLVE_DIRECTORY_REQUEST',
payload: promise
}
}
export const resolveDirectorySuccess = (path: string, files: File[]) => {
return {
type: 'RESOLVE_DIRECTORY_SUCCESS',
payload: { path, files }
}
}
export const resolveDirectory = (provider, path: string) => (dispatch: React.Dispatch<any>) => {
const promise = fetchDirectoryContent(provider, path)
dispatch(resolveDirectoryRequest(promise))
promise.then((files) => {
dispatch(resolveDirectorySuccess(path, files))
}).catch((error) => {
dispatch(resolveDirectoryError({ error }))
})
return promise
}
export const fetchProviderError = (error: any) => {
return {
type: 'FETCH_PROVIDER_ERROR',
payload: error
}
}
export const fetchProviderRequest = (promise: Promise<any>) => {
return {
type: 'FETCH_PROVIDER_REQUEST',
payload: promise
}
}
export const fetchProviderSuccess = (provider: any) => {
return {
type: 'FETCH_PROVIDER_SUCCESS',
payload: provider
}
}
export const fileAddedSuccess = (path: string, files) => {
return {
type: 'FILE_ADDED',
payload: { path, files }
}
}
export const folderAddedSuccess = (path: string, files) => {
return {
type: 'FOLDER_ADDED',
payload: { path, files }
}
}
export const fileRemovedSuccess = (path: string, removePath: string) => {
return {
type: 'FILE_REMOVED',
payload: { path, removePath }
}
}
export const fileRenamedSuccess = (path: string, removePath: string, files) => {
return {
type: 'FILE_RENAMED',
payload: { path, removePath, files }
}
}
export const init = (provider, workspaceName: string, plugin, registry) => (dispatch: React.Dispatch<any>) => {
if (provider) {
provider.event.on('fileAdded', async (filePath) => {
if (extractParentFromKey(filePath) === '/.workspaces') return
const path = extractParentFromKey(filePath) || provider.workspace || provider.type || ''
const data = await fetchDirectoryContent(provider, path)
dispatch(fileAddedSuccess(path, data))
if (filePath.includes('_test.sol')) {
plugin.emit('newTestFileCreated', filePath)
}
})
provider.event.on('folderAdded', async (folderPath) => {
if (extractParentFromKey(folderPath) === '/.workspaces') return
const path = extractParentFromKey(folderPath) || provider.workspace || provider.type || ''
const data = await fetchDirectoryContent(provider, path)
dispatch(folderAddedSuccess(path, data))
})
provider.event.on('fileRemoved', async (removePath) => {
const path = extractParentFromKey(removePath) || provider.workspace || provider.type || ''
dispatch(fileRemovedSuccess(path, removePath))
})
provider.event.on('fileRenamed', async (oldPath) => {
const path = extractParentFromKey(oldPath) || provider.workspace || provider.type || ''
const data = await fetchDirectoryContent(provider, path)
dispatch(fileRenamedSuccess(path, oldPath, data))
})
provider.event.on('fileExternallyChanged', async (path: string, file: { content: string }) => {
const config = registry.get('config').api
const editor = registry.get('editor').api
if (config.get('currentFile') === path && editor.currentContent() !== file.content) {
if (provider.isReadOnly(path)) return editor.setText(file.content)
dispatch(displayNotification(
path + ' changed',
'This file has been changed outside of Remix IDE.',
'Replace by the new content', 'Keep the content displayed in Remix',
() => {
editor.setText(file.content)
}
))
}
})
provider.event.on('fileRenamedError', async () => {
dispatch(displayNotification('File Renamed Failed', '', 'Ok', 'Cancel'))
})
provider.event.on('rootFolderChanged', async () => {
workspaceName = provider.workspace || provider.type || ''
fetchDirectory(provider, workspaceName)(dispatch)
})
dispatch(fetchProviderSuccess(provider))
dispatch(setCurrentWorkspace(workspaceName))
} else {
dispatch(fetchProviderError('No provider available'))
}
}
export const setCurrentWorkspace = (name: string) => {
return {
type: 'SET_CURRENT_WORKSPACE',
payload: name
}
}
export const addInputFieldSuccess = (path: string, files: File[]) => {
return {
type: 'ADD_INPUT_FIELD',
payload: { path, files }
}
}
export const addInputField = (provider, type: string, path: string) => (dispatch: React.Dispatch<any>) => {
const promise = fetchDirectoryContent(provider, path, type)
promise.then((files) => {
dispatch(addInputFieldSuccess(path, files))
}).catch((error) => {
console.error(error)
})
return promise
}
export const removeInputFieldSuccess = (path: string) => {
return {
type: 'REMOVE_INPUT_FIELD',
payload: { path }
}
}
export const removeInputField = (path: string) => (dispatch: React.Dispatch<any>) => {
return dispatch(removeInputFieldSuccess(path))
}
export const displayNotification = (title: string, message: string, labelOk: string, labelCancel: string, actionOk?: (...args) => void, actionCancel?: (...args) => void) => {
return {
type: 'DISPLAY_NOTIFICATION',
payload: { title, message, labelOk, labelCancel, actionOk, actionCancel }
}
}
export const hideNotification = () => {
return {
type: 'DISPLAY_NOTIFICATION'
}
}
export const closeNotificationModal = () => (dispatch: React.Dispatch<any>) => {
dispatch(hideNotification())
}

@ -1,4 +1,4 @@
import React, { useEffect, useState, useRef } from 'react' // eslint-disable-line
import React, { useEffect, useState, useRef, useReducer } from 'react' // eslint-disable-line
// import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd' // eslint-disable-line
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' // eslint-disable-line
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
@ -7,6 +7,8 @@ import Gists from 'gists'
import { FileExplorerMenu } from './file-explorer-menu' // eslint-disable-line
import { FileExplorerContextMenu } from './file-explorer-context-menu' // eslint-disable-line
import { FileExplorerProps, File } from './types'
import { fileSystemReducer, fileSystemInitialState } from './reducers/fileSystem'
import { fetchDirectory, init, resolveDirectory, addInputField, removeInputField } from './actions/fileSystem'
import * as helper from '../../../../../apps/remix-ide/src/lib/helper'
import QueryParams from '../../../../../apps/remix-ide/src/lib/query-params'
@ -15,7 +17,7 @@ import './css/file-explorer.css'
const queryParams = new QueryParams()
export const FileExplorer = (props: FileExplorerProps) => {
const { filesProvider, name, registry, plugin, focusRoot, contextMenuItems, displayInput, externalUploads } = props
const { name, registry, plugin, focusRoot, contextMenuItems, displayInput, externalUploads } = props
const [state, setState] = useState({
focusElement: [{
key: '',
@ -24,10 +26,51 @@ export const FileExplorer = (props: FileExplorerProps) => {
focusPath: null,
files: [],
fileManager: null,
filesProvider,
ctrlKey: false,
newFileName: '',
actions: [],
actions: [{
id: 'newFile',
name: 'New File',
type: ['folder'],
path: [],
extension: [],
pattern: []
}, {
id: 'newFolder',
name: 'New Folder',
type: ['folder'],
path: [],
extension: [],
pattern: []
}, {
id: 'rename',
name: 'Rename',
type: ['file', 'folder'],
path: [],
extension: [],
pattern: []
}, {
id: 'delete',
name: 'Delete',
type: ['file', 'folder'],
path: [],
extension: [],
pattern: []
}, {
id: 'pushChangesToGist',
name: 'Push changes to gist',
type: [],
path: [],
extension: [],
pattern: ['^browser/gists/([0-9]|[a-z])*$']
}, {
id: 'run',
name: 'Run',
type: [],
path: [],
extension: ['.js'],
pattern: []
}],
focusContext: {
element: null,
x: null,
@ -45,23 +88,49 @@ export const FileExplorer = (props: FileExplorerProps) => {
hide: true,
title: '',
message: '',
ok: {
label: '',
fn: () => {}
},
cancel: {
label: '',
fn: () => {}
},
okLabel: '',
okFn: () => {},
cancelLabel: '',
cancelFn: () => {},
handleHide: null
},
modals: [],
toasterMsg: '',
mouseOverElement: null,
showContextMenu: false
showContextMenu: false,
reservedKeywords: [name, 'gist-']
})
const [fileSystem, dispatch] = useReducer(fileSystemReducer, fileSystemInitialState)
const editRef = useRef(null)
useEffect(() => {
if (props.filesProvider) {
init(props.filesProvider, props.name, props.plugin, props.registry)(dispatch)
}
}, [props.filesProvider, props.name])
useEffect(() => {
const provider = fileSystem.provider.provider
if (provider) {
fetchDirectory(provider, props.name)(dispatch)
}
}, [fileSystem.provider.provider, props.name])
useEffect(() => {
if (fileSystem.notification.message) {
modal(fileSystem.notification.title, fileSystem.notification.message, fileSystem.notification.labelOk, fileSystem.notification.actionOk, fileSystem.notification.labelCancel, fileSystem.notification.actionCancel)
}
}, [fileSystem.notification.message])
useEffect(() => {
if (fileSystem.files.expandPath.length > 0) {
setState(prevState => {
return { ...prevState, expandPath: [...new Set([...prevState.expandPath, ...fileSystem.files.expandPath])] }
})
}
}, [fileSystem.files.expandPath])
useEffect(() => {
if (state.focusEdit.element) {
setTimeout(() => {
@ -75,95 +144,13 @@ export const FileExplorer = (props: FileExplorerProps) => {
useEffect(() => {
(async () => {
const fileManager = registry.get('filemanager').api
const files = await fetchDirectoryContent(name)
const actions = [{
id: 'newFile',
name: 'New File',
type: ['folder'],
path: [],
extension: [],
pattern: []
}, {
id: 'newFolder',
name: 'New Folder',
type: ['folder'],
path: [],
extension: [],
pattern: []
}, {
id: 'rename',
name: 'Rename',
type: ['file', 'folder'],
path: [],
extension: [],
pattern: []
}, {
id: 'delete',
name: 'Delete',
type: ['file', 'folder'],
path: [],
extension: [],
pattern: []
}, {
id: 'pushChangesToGist',
name: 'Push changes to gist',
type: [],
path: [],
extension: [],
pattern: ['^browser/gists/([0-9]|[a-z])*$']
}, {
id: 'run',
name: 'Run',
type: [],
path: [],
extension: ['.js'],
pattern: []
}]
setState(prevState => {
return { ...prevState, fileManager, files, actions, expandPath: [name] }
return { ...prevState, fileManager, expandPath: [name] }
})
})()
}, [name])
useEffect(() => {
if (state.fileManager) {
filesProvider.event.register('fileExternallyChanged', fileExternallyChanged)
filesProvider.event.register('fileRenamedError', fileRenamedError)
filesProvider.event.register('rootFolderChanged', rootFolderChanged)
}
}, [state.fileManager])
useEffect(() => {
const { expandPath } = state
const expandFn = async () => {
let files = state.files
for (let i = 0; i < expandPath.length; i++) {
files = await resolveDirectory(expandPath[i], files)
await setState(prevState => {
return { ...prevState, files }
})
}
}
if (expandPath && expandPath.length > 0) {
expandFn()
}
}, [state.expandPath])
useEffect(() => {
// unregister event to update state in callback
if (filesProvider.event.registered.fileAdded) filesProvider.event.unregister('fileAdded', fileAdded)
if (filesProvider.event.registered.folderAdded) filesProvider.event.unregister('folderAdded', folderAdded)
if (filesProvider.event.registered.fileRemoved) filesProvider.event.unregister('fileRemoved', fileRemoved)
if (filesProvider.event.registered.fileRenamed) filesProvider.event.unregister('fileRenamed', fileRenamed)
filesProvider.event.register('fileAdded', fileAdded)
filesProvider.event.register('folderAdded', folderAdded)
filesProvider.event.register('fileRemoved', fileRemoved)
filesProvider.event.register('fileRenamed', fileRenamed)
}, [state.files])
useEffect(() => {
if (focusRoot) {
setState(prevState => {
@ -205,8 +192,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
hide: false,
title: prevState.modals[0].title,
message: prevState.modals[0].message,
ok: prevState.modals[0].ok,
cancel: prevState.modals[0].cancel,
okLabel: prevState.modals[0].okLabel,
okFn: prevState.modals[0].okFn,
cancelLabel: prevState.modals[0].cancelLabel,
cancelFn: prevState.modals[0].cancelFn,
handleHide: prevState.modals[0].handleHide
}
@ -220,82 +209,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
}, [state.modals])
const resolveDirectory = async (folderPath, dir: File[], isChild = false): Promise<File[]> => {
if (!isChild && (state.focusEdit.element === '/blank') && state.focusEdit.isNew && (dir.findIndex(({ path }) => path === '/blank') === -1)) {
dir = state.focusEdit.type === 'file' ? [...dir, {
path: state.focusEdit.element,
name: '',
isDirectory: false
}] : [{
path: state.focusEdit.element,
name: '',
isDirectory: true
}, ...dir]
}
dir = await Promise.all(dir.map(async (file) => {
if (file.path === folderPath) {
if ((extractParentFromKey(state.focusEdit.element) === folderPath) && state.focusEdit.isNew) {
file.child = state.focusEdit.type === 'file' ? [...await fetchDirectoryContent(folderPath), {
path: state.focusEdit.element,
name: '',
isDirectory: false
}] : [{
path: state.focusEdit.element,
name: '',
isDirectory: true
}, ...await fetchDirectoryContent(folderPath)]
} else {
file.child = await fetchDirectoryContent(folderPath)
}
return file
} else if (file.child) {
file.child = await resolveDirectory(folderPath, file.child, true)
return file
} else {
return file
}
}))
return dir
}
const fetchDirectoryContent = async (folderPath: string): Promise<File[]> => {
return new Promise((resolve) => {
filesProvider.resolveDirectory(folderPath, (_error, fileTree) => {
const files = normalize(fileTree)
resolve(files)
})
})
}
const normalize = (filesList): File[] => {
const folders = []
const files = []
Object.keys(filesList || {}).forEach(key => {
key = key.replace(/^\/|\/$/g, '') // remove first and last slash
let path = key
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
if (filesList[key].isDirectory) {
folders.push({
path,
name: extractNameFromKey(path),
isDirectory: filesList[key].isDirectory
})
} else {
files.push({
path,
name: extractNameFromKey(path),
isDirectory: filesList[key].isDirectory
})
}
})
return [...folders, ...files]
}
const extractNameFromKey = (key: string):string => {
const keyPath = key.split('/')
@ -310,34 +223,30 @@ export const FileExplorer = (props: FileExplorerProps) => {
return keyPath.join('/')
}
const createNewFile = (newFilePath: string) => {
const hasReservedKeyword = (content: string): boolean => {
if (state.reservedKeywords.findIndex(value => content.startsWith(value)) !== -1) return true
else return false
}
const createNewFile = async (newFilePath: string) => {
const fileManager = state.fileManager
try {
helper.createNonClashingName(newFilePath, filesProvider, async (error, newName) => {
if (error) {
modal('Create File Failed', error, {
label: 'Close',
fn: async () => {}
}, null)
} else {
const createFile = await fileManager.writeFile(newName, '')
const newName = await helper.createNonClashingNameAsync(newFilePath, fileManager)
const createFile = await fileManager.writeFile(newName, '')
if (!createFile) {
return toast('Failed to create file ' + newName)
} else {
await fileManager.open(newName)
setState(prevState => {
return { ...prevState, focusElement: [{ key: newName, type: 'file' }] }
})
}
}
})
if (!createFile) {
return toast('Failed to create file ' + newName)
} else {
const path = newName.indexOf(props.name + '/') === 0 ? newName.replace(props.name + '/', '') : newName
await fileManager.open(path)
setState(prevState => {
return { ...prevState, focusElement: [{ key: newName, type: 'file' }] }
})
}
} catch (error) {
return modal('File Creation Failed', typeof error === 'string' ? error : error.message, {
label: 'Close',
fn: async () => {}
}, null)
return modal('File Creation Failed', typeof error === 'string' ? error : error.message, 'Close', async () => {})
}
}
@ -349,44 +258,34 @@ export const FileExplorer = (props: FileExplorerProps) => {
const exists = await fileManager.exists(dirName)
if (exists) {
return modal('Rename File Failed', `A file or folder ${extractNameFromKey(newFolderPath)} already exists at this location. Please choose a different name.`, {
label: 'Close',
fn: () => {}
}, null)
return modal('Rename File Failed', `A file or folder ${extractNameFromKey(newFolderPath)} already exists at this location. Please choose a different name.`, 'Close', () => {})
}
await fileManager.mkdir(dirName)
setState(prevState => {
return { ...prevState, focusElement: [{ key: newFolderPath, type: 'folder' }] }
})
} catch (e) {
return modal('Folder Creation Failed', typeof e === 'string' ? e : e.message, {
label: 'Close',
fn: async () => {}
}, null)
return modal('Folder Creation Failed', typeof e === 'string' ? e : e.message, 'Close', async () => {})
}
}
const deletePath = async (path: string) => {
const filesProvider = fileSystem.provider.provider
if (filesProvider.isReadOnly(path)) {
return toast('cannot delete file. ' + name + ' is a read only explorer')
}
const isDir = state.fileManager.isDirectory(path)
modal(`Delete ${isDir ? 'folder' : 'file'}`, `Are you sure you want to delete ${path} ${isDir ? 'folder' : 'file'}?`, {
label: 'OK',
fn: async () => {
try {
const fileManager = state.fileManager
modal(`Delete ${isDir ? 'folder' : 'file'}`, `Are you sure you want to delete ${path} ${isDir ? 'folder' : 'file'}?`, 'OK', async () => {
try {
const fileManager = state.fileManager
await fileManager.remove(path)
} catch (e) {
toast(`Failed to remove ${isDir ? 'folder' : 'file'} ${path}.`)
}
await fileManager.remove(path)
} catch (e) {
toast(`Failed to remove ${isDir ? 'folder' : 'file'} ${path}.`)
}
}, {
label: 'Cancel',
fn: () => {}
})
}, 'Cancel', () => {})
}
const renamePath = async (oldPath: string, newPath: string) => {
@ -395,121 +294,17 @@ export const FileExplorer = (props: FileExplorerProps) => {
const exists = await fileManager.exists(newPath)
if (exists) {
modal('Rename File Failed', `A file or folder ${extractNameFromKey(newPath)} already exists at this location. Please choose a different name.`, {
label: 'Close',
fn: () => {}
}, null)
modal('Rename File Failed', `A file or folder ${extractNameFromKey(newPath)} already exists at this location. Please choose a different name.`, 'Close', () => {})
} else {
await fileManager.rename(oldPath, newPath)
}
} catch (error) {
modal('Rename File Failed', 'Unexpected error while renaming: ' + typeof error === 'string' ? error : error.message, {
label: 'Close',
fn: async () => {}
}, null)
}
}
const removePath = (path: string, files: File[]): File[] => {
return files.map(file => {
if (file.path === path) {
return null
} else if (file.child) {
const childFiles = removePath(path, file.child)
file.child = childFiles.filter(file => file)
return file
} else {
return file
}
})
}
const fileAdded = async (filePath: string) => {
const pathArr = filePath.split('/')
const expandPath = pathArr.map((path, index) => {
return [...pathArr.slice(0, index)].join('/')
}).filter(path => path && (path !== props.name))
const files = await fetchDirectoryContent(props.name)
setState(prevState => {
const uniquePaths = [...new Set([...prevState.expandPath, ...expandPath])]
return { ...prevState, files, expandPath: uniquePaths }
})
if (filePath.includes('_test.sol')) {
plugin.event.trigger('newTestFileCreated', [filePath])
}
}
const folderAdded = async (folderPath: string) => {
const pathArr = folderPath.split('/')
const expandPath = pathArr.map((path, index) => {
return [...pathArr.slice(0, index)].join('/')
}).filter(path => path && (path !== props.name))
const files = await fetchDirectoryContent(props.name)
setState(prevState => {
const uniquePaths = [...new Set([...prevState.expandPath, ...expandPath])]
return { ...prevState, files, expandPath: uniquePaths }
})
}
const fileExternallyChanged = (path: string, file: { content: string }) => {
const config = registry.get('config').api
const editor = registry.get('editor').api
if (config.get('currentFile') === path && editor.currentContent() !== file.content) {
if (filesProvider.isReadOnly(path)) return editor.setText(file.content)
modal(path + ' changed', 'This file has been changed outside of Remix IDE.', {
label: 'Replace by the new content',
fn: () => {
editor.setText(file.content)
}
}, {
label: 'Keep the content displayed in Remix',
fn: () => {}
})
modal('Rename File Failed', 'Unexpected error while renaming: ' + typeof error === 'string' ? error : error.message, 'Close', async () => {})
}
}
const fileRemoved = (filePath) => {
const files = removePath(filePath, state.files)
const updatedFiles = files.filter(file => file)
setState(prevState => {
return { ...prevState, files: updatedFiles }
})
}
const fileRenamed = async () => {
const files = await fetchDirectoryContent(props.name)
setState(prevState => {
return { ...prevState, files, expandPath: [...prevState.expandPath] }
})
}
// register to event of the file provider
// files.event.register('fileRenamed', fileRenamed)
const fileRenamedError = (error: string) => {
modal('File Renamed Failed', error, {
label: 'Close',
fn: () => {}
}, null)
}
// register to event of the file provider
// files.event.register('rootFolderChanged', rootFolderChanged)
const rootFolderChanged = async () => {
const files = await fetchDirectoryContent(name)
setState(prevState => {
return { ...prevState, files }
})
}
const uploadFile = (target) => {
const filesProvider = fileSystem.provider.provider
// TODO The file explorer is merely a view on the current state of
// the files module. Please ask the user here if they want to overwrite
// a file and then just use `files.add`. The file explorer will
@ -527,19 +322,13 @@ export const FileExplorer = (props: FileExplorerProps) => {
fileReader.onload = async function (event) {
if (helper.checkSpecialChars(file.name)) {
modal('File Upload Failed', 'Special characters are not allowed', {
label: 'Close',
fn: async () => {}
}, null)
modal('File Upload Failed', 'Special characters are not allowed', 'Close', async () => {})
return
}
const success = await filesProvider.set(name, event.target.result)
if (!success) {
return modal('File Upload Failed', 'Failed to create file ' + name, {
label: 'Close',
fn: async () => {}
}, null)
return modal('File Upload Failed', 'Failed to create file ' + name, 'Close', async () => {})
}
const config = registry.get('config').api
const editor = registry.get('editor').api
@ -552,60 +341,38 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const name = `${parentFolder}/${file.name}`
filesProvider.exists(name, (error, exist) => {
if (error) console.log(error)
filesProvider.exists(name).then(exist => {
if (!exist) {
loadFile(name)
} else {
modal('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, {
label: 'OK',
fn: () => {
loadFile(name)
}
}, {
label: 'Cancel',
fn: () => {}
})
modal('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', () => {
loadFile(name)
}, 'Cancel', () => {})
}
}).catch(error => {
if (error) console.log(error)
})
})
}
const publishToGist = () => {
modal('Create a public gist', `Are you sure you want to anonymously publish all your files in the ${name} workspace as a public gist on github.com?`, {
label: 'OK',
fn: toGist
}, {
label: 'Cancel',
fn: () => {}
})
modal('Create a public gist', `Are you sure you want to anonymously publish all your files in the ${name} workspace as a public gist on github.com?`, 'OK', toGist, 'Cancel', () => {})
}
const toGist = (id?: string) => {
const filesProvider = fileSystem.provider.provider
const proccedResult = function (error, data) {
if (error) {
modal('Publish to gist Failed', 'Failed to manage gist: ' + error, {
label: 'Close',
fn: async () => {}
}, null)
modal('Publish to gist Failed', 'Failed to manage gist: ' + error, 'Close', () => {})
} else {
if (data.html_url) {
modal('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, {
label: 'OK',
fn: () => {
window.open(data.html_url, '_blank')
}
}, {
label: 'Cancel',
fn: () => {}
})
modal('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, 'OK', () => {
window.open(data.html_url, '_blank')
}, 'Cancel', () => {})
} else {
const error = JSON.stringify(data.errors, null, '\t') || ''
const message = data.message === 'Not Found' ? data.message + '. Please make sure the API token has right to create a gist.' : data.message
modal('Publish to gist Failed', message + ' ' + data.documentation_url + ' ' + error, {
label: 'Close',
fn: async () => {}
}, null)
modal('Publish to gist Failed', message + ' ' + data.documentation_url + ' ' + error, 'Close', () => {})
}
}
}
@ -631,20 +398,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
packageFiles(filesProvider, folder, async (error, packaged) => {
if (error) {
console.log(error)
modal('Publish to gist Failed', 'Failed to create gist: ' + error.message, {
label: 'Close',
fn: async () => {}
}, null)
modal('Publish to gist Failed', 'Failed to create gist: ' + error.message, 'Close', async () => {})
} else {
// check for token
const config = registry.get('config').api
const accessToken = config.get('settings/gist-access-token')
if (!accessToken) {
modal('Authorize Token', 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', {
label: 'Close',
fn: async () => {}
}, null)
modal('Authorize Token', 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', 'Close', () => {})
} else {
const description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' +
queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&runs=' + queryParams.get().runs + '&gist='
@ -698,6 +459,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const runScript = async (path: string) => {
const filesProvider = fileSystem.provider.provider
filesProvider.get(path, (error, content: string) => {
if (error) return console.log(error)
plugin.call('scriptRunner', 'execute', content)
@ -714,7 +477,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
const modal = (title: string, message: string, ok: { label: string, fn: () => void }, cancel: { label: string, fn: () => void }) => {
const modal = (title: string, message: string, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => {
setState(prevState => {
return {
...prevState,
@ -722,8 +485,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
{
message,
title,
ok,
cancel,
okLabel,
okFn,
cancelLabel,
cancelFn,
handleHide: handleHideModal
}]
}
@ -737,6 +502,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const handleClickFile = (path: string) => {
path = path.indexOf(props.name + '/') === 0 ? path.replace(props.name + '/', '') : path
state.fileManager.open(path)
setState(prevState => {
return { ...prevState, focusElement: [{ key: path, type: 'file' }] }
@ -759,6 +525,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
if (!state.expandPath.includes(path)) {
expandPath = [...new Set([...state.expandPath, path])]
resolveDirectory(fileSystem.provider.provider, path)(dispatch)
} else {
expandPath = [...new Set(state.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(path)))]
}
@ -790,7 +557,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const editModeOn = (path: string, type: string, isNew: boolean = false) => {
if (filesProvider.isReadOnly(path)) return
if (fileSystem.provider.provider.isReadOnly(path)) return
setState(prevState => {
return { ...prevState, focusEdit: { ...prevState.focusEdit, element: path, isNew, type } }
})
@ -802,11 +569,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
if (!content || (content.trim() === '')) {
if (state.focusEdit.isNew) {
const files = removePath(state.focusEdit.element, state.files)
const updatedFiles = files.filter(file => file)
removeInputField(parentFolder)(dispatch)
setState(prevState => {
return { ...prevState, files: updatedFiles, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
return { ...prevState, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
})
} else {
editRef.current.textContent = state.focusEdit.lastEdit
@ -822,26 +587,28 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
if (helper.checkSpecialChars(content)) {
modal('Validation Error', 'Special characters are not allowed', {
label: 'OK',
fn: () => {}
}, null)
modal('Validation Error', 'Special characters are not allowed', 'OK', () => {})
} else {
if (state.focusEdit.isNew) {
state.focusEdit.type === 'file' ? createNewFile(joinPath(parentFolder, content)) : createNewFolder(joinPath(parentFolder, content))
const files = removePath(state.focusEdit.element, state.files)
const updatedFiles = files.filter(file => file)
setState(prevState => {
return { ...prevState, files: updatedFiles }
})
if (hasReservedKeyword(content)) {
removeInputField(parentFolder)(dispatch)
modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {})
} else {
state.focusEdit.type === 'file' ? createNewFile(joinPath(parentFolder, content)) : createNewFolder(joinPath(parentFolder, content))
removeInputField(parentFolder)(dispatch)
}
} else {
const oldPath: string = state.focusEdit.element
const oldName = extractNameFromKey(oldPath)
const newPath = oldPath.replace(oldName, content)
if (hasReservedKeyword(content)) {
editRef.current.textContent = state.focusEdit.lastEdit
modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {})
} else {
const oldPath: string = state.focusEdit.element
const oldName = extractNameFromKey(oldPath)
const newPath = oldPath.replace(oldName, content)
editRef.current.textContent = extractNameFromKey(oldPath)
renamePath(oldPath, newPath)
editRef.current.textContent = extractNameFromKey(oldPath)
renamePath(oldPath, newPath)
}
}
setState(prevState => {
return { ...prevState, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
@ -851,9 +618,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const handleNewFileInput = async (parentFolder?: string) => {
if (!parentFolder) parentFolder = state.focusElement[0] ? state.focusElement[0].type === 'folder' ? state.focusElement[0].key : extractParentFromKey(state.focusElement[0].key) : name
if (!parentFolder) parentFolder = state.focusElement[0] ? state.focusElement[0].type === 'folder' ? state.focusElement[0].key ? state.focusElement[0].key : name : extractParentFromKey(state.focusElement[0].key) ? extractParentFromKey(state.focusElement[0].key) : name : name
const expandPath = [...new Set([...state.expandPath, parentFolder])]
await addInputField(fileSystem.provider.provider, 'file', parentFolder)(dispatch)
setState(prevState => {
return { ...prevState, expandPath }
})
@ -861,10 +629,11 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const handleNewFolderInput = async (parentFolder?: string) => {
if (!parentFolder) parentFolder = state.focusElement[0] ? state.focusElement[0].type === 'folder' ? state.focusElement[0].key : extractParentFromKey(state.focusElement[0].key) : name
if (!parentFolder) parentFolder = state.focusElement[0] ? state.focusElement[0].type === 'folder' ? state.focusElement[0].key ? state.focusElement[0].key : name : extractParentFromKey(state.focusElement[0].key) ? extractParentFromKey(state.focusElement[0].key) : name : name
else if ((parentFolder.indexOf('.sol') !== -1) || (parentFolder.indexOf('.js') !== -1)) parentFolder = extractParentFromKey(parentFolder)
const expandPath = [...new Set([...state.expandPath, parentFolder])]
await addInputField(fileSystem.provider.provider, 'folder', parentFolder)(dispatch)
setState(prevState => {
return { ...prevState, expandPath }
})
@ -915,12 +684,16 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const renderFiles = (file: File, index: number) => {
if (!file || !file.path || typeof file === 'string' || typeof file === 'number' || typeof file === 'boolean') return
const labelClass = state.focusEdit.element === file.path
? 'bg-light' : state.focusElement.findIndex(item => item.key === file.path) !== -1
? 'bg-secondary' : state.mouseOverElement === file.path
? 'bg-light border' : (state.focusContext.element === file.path) && (state.focusEdit.element !== file.path)
? 'bg-light border' : ''
const icon = helper.getPathIcon(file.path)
const spreadProps = {
onClick: (e) => e.stopPropagation()
}
if (file.isDirectory) {
return (
@ -952,12 +725,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
}}
>
{
file.child ? <TreeView id={`treeView${file.path}`} key={index}>{
file.child.map((file, index) => {
return renderFiles(file, index)
file.child ? <TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps }>{
Object.keys(file.child).map((key, index) => {
return renderFiles(file.child[key], index)
})
}
</TreeView> : <TreeView id={`treeView${file.path}`} key={index} />
</TreeView> : <TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps }/>
}
</TreeViewItem>
)
@ -965,7 +738,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
return (
<TreeViewItem
id={`treeViewItem${file.path}`}
key={index}
key={`treeView${file.path}`}
label={label(file)}
onClick={(e) => {
e.stopPropagation()
@ -1028,8 +801,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
<div className='pb-2'>
<TreeView id='treeViewMenu'>
{
state.files.map((file, index) => {
return renderFiles(file, index)
fileSystem.files.files[props.name] && Object.keys(fileSystem.files.files[props.name]).map((key, index) => {
return renderFiles(fileSystem.files.files[props.name][key], index)
})
}
</TreeView>
@ -1042,8 +815,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
title={ state.focusModal.title }
message={ state.focusModal.message }
hide={ state.focusModal.hide }
ok={ state.focusModal.ok }
cancel={ state.focusModal.cancel }
okLabel={ state.focusModal.okLabel }
okFn={ state.focusModal.okFn }
cancelLabel={ state.focusModal.cancelLabel }
cancelFn={ state.focusModal.cancelFn }
handleHide={ handleHideModal }
/>
}

@ -0,0 +1,344 @@
import * as _ from 'lodash'
import { extractNameFromKey } from '../utils'
interface Action {
type: string;
payload: Record<string, any>;
}
export const fileSystemInitialState = {
files: {
files: [],
expandPath: [],
workspaceName: null,
blankPath: null,
isRequesting: false,
isSuccessful: false,
error: null
},
provider: {
provider: null,
isRequesting: false,
isSuccessful: false,
error: null
},
notification: {
title: null,
message: null,
actionOk: () => {},
actionCancel: () => {},
labelOk: null,
labelCancel: null
}
}
export const fileSystemReducer = (state = fileSystemInitialState, action: Action) => {
switch (action.type) {
case 'FETCH_DIRECTORY_REQUEST': {
return {
...state,
files: {
...state.files,
isRequesting: true,
isSuccessful: false,
error: null
}
}
}
case 'FETCH_DIRECTORY_SUCCESS': {
return {
...state,
files: {
...state.files,
files: action.payload.files,
isRequesting: false,
isSuccessful: true,
error: null
}
}
}
case 'FETCH_DIRECTORY_ERROR': {
return {
...state,
files: {
...state.files,
isRequesting: false,
isSuccessful: false,
error: action.payload
}
}
}
case 'RESOLVE_DIRECTORY_REQUEST': {
return {
...state,
files: {
...state.files,
isRequesting: true,
isSuccessful: false,
error: null
}
}
}
case 'RESOLVE_DIRECTORY_SUCCESS': {
return {
...state,
files: {
...state.files,
files: resolveDirectory(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files),
isRequesting: false,
isSuccessful: true,
error: null
}
}
}
case 'RESOLVE_DIRECTORY_ERROR': {
return {
...state,
files: {
...state.files,
isRequesting: false,
isSuccessful: false,
error: action.payload
}
}
}
case 'FETCH_PROVIDER_REQUEST': {
return {
...state,
provider: {
...state.provider,
isRequesting: true,
isSuccessful: false,
error: null
}
}
}
case 'FETCH_PROVIDER_SUCCESS': {
return {
...state,
provider: {
...state.provider,
provider: action.payload,
isRequesting: false,
isSuccessful: true,
error: null
}
}
}
case 'FETCH_PROVIDER_ERROR': {
return {
...state,
provider: {
...state.provider,
isRequesting: false,
isSuccessful: false,
error: action.payload
}
}
}
case 'SET_CURRENT_WORKSPACE': {
return {
...state,
files: {
...state.files,
workspaceName: action.payload
}
}
}
case 'ADD_INPUT_FIELD': {
return {
...state,
files: {
...state.files,
files: addInputField(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files),
blankPath: action.payload.path,
isRequesting: false,
isSuccessful: true,
error: null
}
}
}
case 'REMOVE_INPUT_FIELD': {
return {
...state,
files: {
...state.files,
files: removeInputField(state.files.workspaceName, state.files.blankPath, state.files.files),
blankPath: null,
isRequesting: false,
isSuccessful: true,
error: null
}
}
}
case 'FILE_ADDED': {
return {
...state,
files: {
...state.files,
files: fileAdded(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files),
expandPath: [...new Set([...state.files.expandPath, action.payload.path])],
isRequesting: false,
isSuccessful: true,
error: null
}
}
}
case 'FOLDER_ADDED': {
return {
...state,
files: {
...state.files,
files: folderAdded(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files),
expandPath: [...new Set([...state.files.expandPath, action.payload.path])],
isRequesting: false,
isSuccessful: true,
error: null
}
}
}
case 'FILE_REMOVED': {
return {
...state,
files: {
...state.files,
files: fileRemoved(state.files.workspaceName, action.payload.path, action.payload.removePath, state.files.files),
isRequesting: false,
isSuccessful: true,
error: null
}
}
}
case 'FILE_RENAMED': {
return {
...state,
files: {
...state.files,
files: fileRenamed(state.files.workspaceName, action.payload.path, action.payload.removePath, state.files.files, action.payload.files),
isRequesting: false,
isSuccessful: true,
error: null
}
}
}
case 'DISPLAY_NOTIFICATION': {
return {
...state,
notification: {
title: action.payload.title,
message: action.payload.message,
actionOk: action.payload.actionOk || fileSystemInitialState.notification.actionOk,
actionCancel: action.payload.actionCancel || fileSystemInitialState.notification.actionCancel,
labelOk: action.payload.labelOk,
labelCancel: action.payload.labelCancel
}
}
}
case 'HIDE_NOTIFICATION': {
return {
...state,
notification: fileSystemInitialState.notification
}
}
default:
throw new Error()
}
}
const resolveDirectory = (root, path: string, files, content) => {
if (path === root) return { [root]: { ...content[root], ...files[root] } }
const pathArr: string[] = path.split('/').filter(value => value)
if (pathArr[0] !== root) pathArr.unshift(root)
const _path = pathArr.map((key, index) => index > 1 ? ['child', key] : key).reduce((acc: string[], cur) => {
return Array.isArray(cur) ? [...acc, ...cur] : [...acc, cur]
}, [])
const prevFiles = _.get(files, _path)
files = _.set(files, _path, {
isDirectory: true,
path,
name: extractNameFromKey(path),
child: { ...content[pathArr[pathArr.length - 1]], ...(prevFiles ? prevFiles.child : {}) }
})
return files
}
const removePath = (root, path: string, pathName, files) => {
const pathArr: string[] = path.split('/').filter(value => value)
if (pathArr[0] !== root) pathArr.unshift(root)
const _path = pathArr.map((key, index) => index > 1 ? ['child', key] : key).reduce((acc: string[], cur) => {
return Array.isArray(cur) ? [...acc, ...cur] : [...acc, cur]
}, [])
const prevFiles = _.get(files, _path)
prevFiles && prevFiles.child && prevFiles.child[pathName] && delete prevFiles.child[pathName]
files = _.set(files, _path, {
isDirectory: true,
path,
name: extractNameFromKey(path),
child: prevFiles ? prevFiles.child : {}
})
return files
}
const addInputField = (root, path: string, files, content) => {
if (path === root) return { [root]: { ...content[root], ...files[root] } }
const result = resolveDirectory(root, path, files, content)
return result
}
const removeInputField = (root, path: string, files) => {
if (path === root) {
delete files[root][path + '/' + 'blank']
return files
}
return removePath(root, path, path + '/' + 'blank', files)
}
const fileAdded = (root, path: string, files, content) => {
return resolveDirectory(root, path, files, content)
}
const folderAdded = (root, path: string, files, content) => {
return resolveDirectory(root, path, files, content)
}
const fileRemoved = (root, path: string, removedPath: string, files) => {
if (path === root) {
delete files[root][removedPath]
return files
}
return removePath(root, path, extractNameFromKey(removedPath), files)
}
const fileRenamed = (root, path: string, removePath: string, files, content) => {
if (path === root) {
const allFiles = { [root]: { ...content[root], ...files[root] } }
delete allFiles[root][extractNameFromKey(removePath) || removePath]
return allFiles
}
const pathArr: string[] = path.split('/').filter(value => value)
if (pathArr[0] !== root) pathArr.unshift(root)
const _path = pathArr.map((key, index) => index > 1 ? ['child', key] : key).reduce((acc: string[], cur) => {
return Array.isArray(cur) ? [...acc, ...cur] : [...acc, cur]
}, [])
const prevFiles = _.get(files, _path)
delete prevFiles.child[extractNameFromKey(removePath)]
files = _.set(files, _path, {
isDirectory: true,
path,
name: extractNameFromKey(path),
child: { ...content[pathArr[pathArr.length - 1]], ...prevFiles.child }
})
return files
}

@ -6,7 +6,7 @@ export interface FileExplorerProps {
menuItems?: string[],
plugin: any,
focusRoot: boolean,
contextMenuItems: { name: string, type: string[], path: string[], extension: string[], pattern: string[] }[],
contextMenuItems: { id: string, name: string, type: string[], path: string[], extension: string[], pattern: string[] }[],
displayInput?: boolean,
externalUploads?: EventTarget & HTMLInputElement
}

@ -0,0 +1,13 @@
export const extractNameFromKey = (key: string): string => {
const keyPath = key.split('/')
return keyPath[keyPath.length - 1]
}
export const extractParentFromKey = (key: string):string => {
if (!key) return
const keyPath = key.split('/')
keyPath.pop()
return keyPath.join('/')
}

@ -18,7 +18,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
const modalKeyEvent = (keyCode) => {
if (keyCode === 27) { // Esc
if (props.cancel && props.cancel.fn) props.cancel.fn()
if (props.cancelFn) props.cancelFn()
handleHide()
} else if (keyCode === 13) { // Enter
enterHandler()
@ -33,9 +33,9 @@ export const ModalDialog = (props: ModalDialogProps) => {
const enterHandler = () => {
if (state.toggleBtn) {
if (props.ok && props.ok.fn) props.ok.fn()
if (props.okFn) props.okFn()
} else {
if (props.cancel && props.cancel.fn) props.cancel.fn()
if (props.cancelFn) props.cancelFn()
}
handleHide()
}
@ -79,29 +79,29 @@ export const ModalDialog = (props: ModalDialogProps) => {
</div>
<div className="modal-footer" data-id={`${props.id}ModalDialogModalFooter-react`}>
{/* todo add autofocus ^^ */}
{ props.ok &&
{ props.okLabel &&
<span
data-id={`${props.id}-modal-footer-ok-react`}
className={'modal-ok btn btn-sm ' + (state.toggleBtn ? 'btn-dark' : 'btn-light')}
onClick={() => {
if (props.ok.fn) props.ok.fn()
if (props.okFn) props.okFn()
handleHide()
}}
>
{ props.ok.label ? props.ok.label : 'OK' }
{ props.okLabel ? props.okLabel : 'OK' }
</span>
}
{ props.cancel &&
{ props.cancelLabel &&
<span
data-id={`${props.id}-modal-footer-cancel-react`}
className={'modal-cancel btn btn-sm ' + (state.toggleBtn ? 'btn-light' : 'btn-dark')}
data-dismiss="modal"
onClick={() => {
if (props.cancel.fn) props.cancel.fn()
if (props.cancelFn) props.cancelFn()
handleHide()
}}
>
{ props.cancel.label ? props.cancel.label : 'Cancel' }
{ props.cancelLabel ? props.cancelLabel : 'Cancel' }
</span>
}
</div>

@ -2,8 +2,10 @@ export interface ModalDialogProps {
id?: string
title?: string,
message?: string,
ok?: { label: string, fn: () => void },
cancel: { label: string, fn: () => void },
okLabel?: string,
okFn?: () => void,
cancelLabel?: string,
cancelFn?: () => void,
modalClass?: string,
showCancelIcon?: boolean,
hide: boolean,

@ -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-static-analyser
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-static-analyser` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1 @@
export * from './lib/remix-ui-static-analyser'

@ -0,0 +1,21 @@
import React from 'react' //eslint-disable-line
interface StaticAnalyserButtonProps {
onClick: (event) => void
buttonText: string,
disabled?: boolean
}
const StaticAnalyserButton = ({
onClick,
buttonText,
disabled
}: StaticAnalyserButtonProps) => {
return (
<button className="btn btn-sm w-25 btn-primary" onClick={onClick} disabled={disabled}>
{buttonText}
</button>
)
}
export default StaticAnalyserButton

@ -0,0 +1,65 @@
import React from 'react' //eslint-disable-line
interface ErrorRendererProps {
message: any;
opt: any,
warningErrors: any
editor: any
}
const ErrorRenderer = ({ message, opt, editor }: ErrorRendererProps) => {
const getPositionDetails = (msg: any) => {
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 }
// extract line / column
let position = msg.match(/^(.*?):([0-9]*?):([0-9]*?)?/)
result.errLine = position ? parseInt(position[2]) - 1 : -1
result.errCol = position ? parseInt(position[3]) : -1
// extract file
position = msg.match(/^(https:.*?|http:.*?|.*?):/)
result.errFile = position ? position[1] : ''
return result
}
const handlePointToErrorOnClick = (location, fileName) => {
editor.call('editor', 'discardHighlight')
editor.call('editor', 'highlight', location, fileName)
}
if (!message) return
let position = getPositionDetails(message)
if (!position.errFile || (opt.errorType && opt.errorType === position.errFile)) {
// Updated error reported includes '-->' before file details
const errorDetails = message.split('-->')
// errorDetails[1] will have file details
if (errorDetails.length > 1) position = getPositionDetails(errorDetails[1])
}
opt.errLine = position.errLine
opt.errCol = position.errCol
opt.errFile = position.errFile.trim()
const classList = opt.type === 'error' ? 'alert alert-danger' : 'alert alert-warning'
return (
<div>
<div className={`sol ${opt.type} ${classList}`}>
<div className="close" data-id="renderer">
<i className="fas fa-times"></i>
</div>
<span className='d-flex flex-column' onClick={() => handlePointToErrorOnClick(opt.location, opt.fileName)}>
<span className='h6 font-weight-bold'>{opt.name}</span>
{ opt.item.warning }
{opt.item.more
? <span><a href={opt.item.more} target='_blank'>more</a></span>
: <span> </span>
}
<span title={`Position in ${opt.errFile}`}>Pos: {opt.locationString}</span>
</span>
</div>
</div>
)
}
export default ErrorRenderer

@ -0,0 +1,14 @@
import React from 'react' //eslint-disable-line
export const compilation = (analysisModule, dispatch) => {
if (analysisModule) {
analysisModule.on(
'solidity',
'compilationFinished',
(file, source, languageVersion, data) => {
if (languageVersion.indexOf('soljson') !== 0) return
dispatch({ type: 'compilationFinished', payload: { file, source, languageVersion, data } })
}
)
}
}

@ -0,0 +1,21 @@
export const initialState = {
file: null,
source: null,
languageVersion: null,
data: null
}
export const analysisReducer = (state, action) => {
switch (action.type) {
case 'compilationFinished':
return {
...state,
file: action.payload.file,
source: action.payload.source,
languageVersion: action.payload.languageVersion,
data: action.payload.data
}
default:
return initialState
}
}

@ -0,0 +1,351 @@
import React, { useEffect, useState, useReducer } from 'react'
import Button from './Button/StaticAnalyserButton' // eslint-disable-line
import remixLib from '@remix-project/remix-lib'
import _ from 'lodash'
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' // eslint-disable-line
import { RemixUiCheckbox } from '@remix-ui/checkbox' // eslint-disable-line
import ErrorRenderer from './ErrorRenderer' // eslint-disable-line
import { compilation } from './actions/staticAnalysisActions'
import { initialState, analysisReducer } from './reducers/staticAnalysisReducer'
const StaticAnalysisRunner = require('@remix-project/remix-analyzer').CodeAnalysis
const utils = remixLib.util
/* eslint-disable-next-line */
export interface RemixUiStaticAnalyserProps {
registry: any,
event: any,
analysisModule: any
}
export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
const [runner] = useState(new StaticAnalysisRunner())
const preProcessModules = (arr: any) => {
return arr.map((Item, i) => {
const itemObj = new Item()
itemObj._index = i
itemObj.categoryDisplayName = itemObj.category.displayName
itemObj.categoryId = itemObj.category.id
return itemObj
})
}
const groupedModules = utils.groupBy(
preProcessModules(runner.modules()),
'categoryId'
)
const getIndex = (modules, array) => {
Object.values(modules).map((value: {_index}) => {
if (Array.isArray(value)) {
value.forEach((x) => {
array.push(x._index.toString())
})
} else {
array.push(value._index.toString())
}
})
}
const groupedModuleIndex = (modules) => {
const indexOfCategory = []
if (!_.isEmpty(modules)) {
getIndex(modules, indexOfCategory)
}
return indexOfCategory
}
const [autoRun, setAutoRun] = useState(true)
const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules))
const warningContainer = React.useRef(null)
const [warningState, setWarningState] = useState([])
const [state, dispatch] = useReducer(analysisReducer, initialState)
useEffect(() => {
compilation(props.analysisModule, dispatch)
}, [])
useEffect(() => {
if (autoRun) {
if (state.data !== null) {
run(state.data, state.source, state.file)
}
}
return () => { }
}, [autoRun, categoryIndex, state])
const message = (name, warning, more, fileName, locationString) : string => {
return (`
<span className='d-flex flex-column'>
<span className='h6 font-weight-bold'>${name}</span>
${warning}
${more
? (<span><a href={more} target='_blank'>more</a></span>)
: (<span> </span>)
}
<span className="" title={Position in ${fileName}}>Pos: ${locationString}</span>
</span>`
)
}
const run = (lastCompilationResult, lastCompilationSource, currentFile) => {
if (state.data !== null) {
if (lastCompilationResult && categoryIndex.length > 0) {
let warningCount = 0
const warningMessage = []
runner.run(lastCompilationResult, categoryIndex, results => {
results.map((result) => {
let moduleName
Object.keys(groupedModules).map(key => {
groupedModules[key].forEach(el => {
if (el.name === result.name) {
moduleName = groupedModules[key][0].categoryDisplayName
}
})
})
const warningErrors = []
result.report.map((item) => {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
if (item.location) {
const split = item.location.split(':')
const file = split[2]
location = {
start: parseInt(split[0]),
length: parseInt(split[1])
}
location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
parseInt(file),
lastCompilationSource.sources,
lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = row + 1 + ':' + column + ':'
fileName = Object.keys(lastCompilationResult.contracts)[file]
}
warningCount++
const msg = message(item.name, item.warning, item.more, fileName, locationString)
const options = {
type: 'warning',
useSpan: true,
errFile: fileName,
fileName,
errLine: row,
errCol: column,
item: item,
name: result.name,
locationString,
more: item.more,
location: location
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName })
})
})
const resultArray = []
warningMessage.map(x => {
resultArray.push(x)
})
function groupBy (objectArray, property) {
return objectArray.reduce((acc, obj) => {
const key = obj[property]
if (!acc[key]) {
acc[key] = []
}
// Add object to list for given key's value
acc[key].push(obj)
return acc
}, {})
}
const groupedCategory = groupBy(resultArray, 'warningModuleName')
setWarningState(groupedCategory)
})
if (categoryIndex.length > 0) {
props.event.trigger('staticAnaysisWarning', [warningCount])
}
} else {
if (categoryIndex.length) {
warningContainer.current.innerText = 'No compiled AST available'
}
props.event.trigger('staticAnaysisWarning', [-1])
}
}
}
const handleCheckAllModules = (groupedModules) => {
const index = groupedModuleIndex(groupedModules)
if (index.every(el => categoryIndex.includes(el))) {
setCategoryIndex(
categoryIndex.filter((el) => {
return !index.includes(el)
})
)
} else {
setCategoryIndex(_.uniq([...categoryIndex, ...index]))
}
}
const handleCheckOrUncheckCategory = (category) => {
const index = groupedModuleIndex(category)
if (index.every(el => categoryIndex.includes(el))) {
setCategoryIndex(
categoryIndex.filter((el) => {
return !index.includes(el)
})
)
} else {
setCategoryIndex(_.uniq([...categoryIndex, ...index]))
}
}
const handleAutoRun = () => {
if (autoRun) {
setAutoRun(false)
} else {
setAutoRun(true)
}
}
const handleCheckSingle = (event, _index) => {
_index = _index.toString()
if (categoryIndex.includes(_index)) {
setCategoryIndex(categoryIndex.filter(val => val !== _index))
} else {
setCategoryIndex(_.uniq([...categoryIndex, _index]))
}
}
const categoryItem = (categoryId, item, i) => {
return (
<div className="form-check" key={i}>
<RemixUiCheckbox
categoryId={categoryId}
id={`staticanalysismodule_${categoryId}_${i}`}
inputType="checkbox"
name="checkSingleEntry"
itemName={item.name}
label={item.description}
onClick={event => handleCheckSingle(event, item._index)}
checked={categoryIndex.includes(item._index.toString())}
onChange={() => {}}
/>
</div>
)
}
const categorySection = (category, categoryId, i) => {
return (
<div className="" key={i}>
<div className="block">
<TreeView>
<TreeViewItem
label={
<label
htmlFor={`heading${categoryId}`}
style={{ cursor: 'pointer' }}
className="pl-3 card-header h6 d-flex justify-content-between font-weight-bold px-1 py-2 w-100"
data-bs-toggle="collapse"
data-bs-expanded="false"
data-bs-controls={`heading${categoryId}`}
data-bs-target={`#heading${categoryId}`}
>
{category[0].categoryDisplayName}
</label>
}
expand={false}
>
<div>
<RemixUiCheckbox onClick={() => handleCheckOrUncheckCategory(category)} id={categoryId} inputType="checkbox" label={`Select ${category[0].categoryDisplayName}`} name='checkCategoryEntry' checked={category.map(x => x._index.toString()).every(el => categoryIndex.includes(el))} onChange={() => {}}/>
</div>
<div className="w-100 d-block px-2 my-1 entries collapse multi-collapse" id={`heading${categoryId}`}>
{category.map((item, i) => {
return (
categoryItem(categoryId, item, i)
)
})}
</div>
</TreeViewItem>
</TreeView>
</div>
</div>
)
}
return (
<div className="analysis_3ECCBV px-3 pb-1">
<div className="my-2 d-flex flex-column align-items-left">
<div className="d-flex justify-content-between" id="staticanalysisButton">
<RemixUiCheckbox
id="checkAllEntries"
inputType="checkbox"
checked={Object.values(groupedModules).map((value: any) => {
return (value.map(x => {
return x._index.toString()
}))
}).flat().every(el => categoryIndex.includes(el))}
label="Select all"
onClick={() => handleCheckAllModules(groupedModules)}
onChange={() => {}}
/>
<RemixUiCheckbox
id="autorunstaticanalysis"
inputType="checkbox"
onClick={handleAutoRun}
checked={autoRun}
label="Autorun"
onChange={() => {}}
/>
<Button buttonText="Run" onClick={() => run(state.data, state.source, state.file)} disabled={state.data === null}/>
</div>
</div>
<div id="staticanalysismodules" className="list-group list-group-flush">
{Object.keys(groupedModules).map((categoryId, i) => {
const category = groupedModules[categoryId]
return (
categorySection(category, categoryId, i)
)
})
}
</div>
<div className="mt-2 p-2 d-flex border-top flex-column">
<span>last results for:</span>
<span
className="text-break break-word word-break font-weight-bold"
id="staticAnalysisCurrentFile"
>
{state.file}
</span>
</div>
{ categoryIndex.length > 0 && Object.entries(warningState).length > 0 &&
<div id='staticanalysisresult' >
<div className="mb-4">
{
(Object.entries(warningState).map((element) => (
<>
<span className="text-dark h6">{element[0]}</span>
{element[1].map(x => (
x.hasWarning ? (
<div id={`staticAnalysisModule${element[1].warningModuleName}`}>
<ErrorRenderer message={x.msg} opt={x.options} warningErrors={ x.warningErrors} editor={props.analysisModule}/>
</div>
) : null
))}
</>
)))
}
</div>
</div>
}
</div>
)
}
export default RemixUiStaticAnalyser

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

@ -91,10 +91,8 @@ export const Toaster = (props: ToasterProps) => {
<>
<ModalDialog
message={props.message}
cancel={{
label: 'Close',
fn: () => {}
}}
cancelLabel='Close'
cancelFn={() => {}}
hide={!state.showModal}
handleHide={hideFullMessage}
/>

@ -91,25 +91,30 @@ export const Workspace = (props: WorkspaceProps) => {
const localhostDisconnect = () => {
if (state.currentWorkspace === LOCALHOST) setWorkspace(props.workspaces.length > 0 ? props.workspaces[0] : NO_WORKSPACE)
// This should be removed some time after refactoring: https://github.com/ethereum/remix-project/issues/1197
else {
setWorkspace(state.currentWorkspace) // Useful to switch to last selcted workspace when remixd is disconnected
props.fileManager.setMode('browser')
}
}
props.localhost.event.unregister('disconnected', localhostDisconnect)
props.localhost.event.register('disconnected', localhostDisconnect)
props.localhost.event.off('disconnected', localhostDisconnect)
props.localhost.event.on('disconnected', localhostDisconnect)
useEffect(() => {
props.localhost.event.register('connected', () => {
props.localhost.event.on('connected', () => {
remixdExplorer.show()
setWorkspace(LOCALHOST)
})
props.localhost.event.register('disconnected', () => {
props.localhost.event.on('disconnected', () => {
remixdExplorer.hide()
})
props.localhost.event.register('loading', () => {
props.localhost.event.on('loading', () => {
remixdExplorer.loading()
})
props.workspace.event.register('createWorkspace', (name) => {
props.workspace.event.on('createWorkspace', (name) => {
createNewWorkspace(name)
})
@ -145,14 +150,10 @@ export const Workspace = (props: WorkspaceProps) => {
hide: true,
title: '',
message: null,
ok: {
label: '',
fn: () => {}
},
cancel: {
label: '',
fn: () => {}
},
okLabel: '',
okFn: () => {},
cancelLabel: '',
cancelFn: () => {},
handleHide: null
},
loadingLocalhost: false,
@ -168,41 +169,20 @@ export const Workspace = (props: WorkspaceProps) => {
/* workspace creation, renaming and deletion */
const renameCurrentWorkspace = () => {
modal('Rename Current Workspace', renameModalMessage(), {
label: 'OK',
fn: onFinishRenameWorkspace
}, {
label: '',
fn: () => {}
})
modal('Rename Current Workspace', renameModalMessage(), 'OK', onFinishRenameWorkspace, '', () => {})
}
const createWorkspace = () => {
modal('Create Workspace', createModalMessage(), {
label: 'OK',
fn: onFinishCreateWorkspace
}, {
label: '',
fn: () => {}
})
modal('Create Workspace', createModalMessage(), 'OK', onFinishCreateWorkspace, '', () => {})
}
const deleteCurrentWorkspace = () => {
modal('Delete Current Workspace', 'Are you sure to delete the current workspace?', {
label: 'OK',
fn: onFinishDeleteWorkspace
}, {
label: '',
fn: () => {}
})
modal('Delete Current Workspace', 'Are you sure to delete the current workspace?', 'OK', onFinishDeleteWorkspace, '', () => {})
}
const modalMessage = (title: string, body: string) => {
setTimeout(() => { // wait for any previous modal a chance to close
modal(title, body, {
label: 'OK',
fn: () => {}
}, null)
modal(title, body, 'OK', () => {}, '', null)
}, 200)
}
@ -272,11 +252,19 @@ export const Workspace = (props: WorkspaceProps) => {
const remixdExplorer = {
hide: async () => {
await setWorkspace(NO_WORKSPACE)
props.fileManager.setMode('browser')
setState(prevState => {
return { ...prevState, hideRemixdExplorer: true, loadingLocalhost: false }
})
// If 'connect to localhost' is clicked from home tab, mode is not 'localhost'
if (props.fileManager.mode === 'localhost') {
await setWorkspace(NO_WORKSPACE)
props.fileManager.setMode('browser')
setState(prevState => {
return { ...prevState, hideRemixdExplorer: true, loadingLocalhost: false }
})
} else {
// Hide spinner in file explorer
setState(prevState => {
return { ...prevState, loadingLocalhost: false }
})
}
},
show: () => {
props.fileManager.setMode('localhost')
@ -297,7 +285,7 @@ export const Workspace = (props: WorkspaceProps) => {
})
}
const modal = async (title: string, message: string | JSX.Element, ok: { label: string, fn: () => void }, cancel: { label: string, fn: () => void }) => {
const modal = async (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel: string, cancelFn: () => void) => {
await setState(prevState => {
return {
...prevState,
@ -306,8 +294,10 @@ export const Workspace = (props: WorkspaceProps) => {
hide: false,
message,
title,
ok,
cancel,
okLabel,
okFn,
cancelLabel,
cancelFn,
handleHide: handleHideModal
}
}
@ -339,8 +329,10 @@ export const Workspace = (props: WorkspaceProps) => {
title={ state.modal.title }
message={ state.modal.message }
hide={ state.modal.hide }
ok={ state.modal.ok }
cancel={ state.modal.cancel }
okLabel={ state.modal.okLabel }
okFn={ state.modal.okFn }
cancelLabel={ state.modal.cancelLabel }
cancelFn={ state.modal.cancelFn }
handleHide={ handleHideModal }>
{ (typeof state.modal.message !== 'string') && state.modal.message }
</ModalDialog>

@ -24,16 +24,18 @@ async function warnLatestVersion () {
const services = {
git: (readOnly: boolean) => new servicesList.GitClient(readOnly),
hardhat: (readOnly: boolean) => new servicesList.HardhatClient(readOnly),
folder: (readOnly: boolean) => new servicesList.Sharedfolder(readOnly)
}
const ports = {
git: 65521,
hardhat: 65522,
folder: 65520
}
const killCallBack: Array<Function> = []
function startService<S extends 'git' | 'folder'> (service: S, callback: (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => void) {
function startService<S extends 'git' | 'hardhat' | 'folder'> (service: S, callback: (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => void) {
const socket = new WebSocket(ports[service], { remixIdeUrl: program.remixIde }, () => services[service](program.readOnly || false))
socket.start(callback)
killCallBack.push(socket.close.bind(socket))
@ -78,6 +80,10 @@ function startService<S extends 'git' | 'folder'> (service: S, callback: (ws: WS
sharedFolderClient.setupNotifications(program.sharedFolder)
sharedFolderClient.sharedFolder(program.sharedFolder)
})
startService('hardhat', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
sharedFolderClient.setWebSocket(ws)
sharedFolderClient.sharedFolder(program.sharedFolder)
})
/*
startService('git', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
sharedFolderClient.setWebSocket(ws)

@ -1,6 +1,7 @@
'use strict'
import { RemixdClient as sharedFolder } from './services/remixdClient'
import { GitClient } from './services/gitClient'
import { HardhatClient } from './services/hardhatClient'
import Websocket from './websocket'
import * as utils from './utils'
@ -9,6 +10,7 @@ module.exports = {
utils,
services: {
sharedFolder,
GitClient
GitClient,
HardhatClient
}
}

@ -1,2 +1,3 @@
export { RemixdClient as Sharedfolder } from './services/remixdClient'
export { GitClient } from './services/gitClient'
export { HardhatClient } from './services/hardhatClient'

@ -0,0 +1,49 @@
import * as WS from 'ws' // eslint-disable-line
import { PluginClient } from '@remixproject/plugin'
const { spawn } = require('child_process')
export class HardhatClient extends PluginClient {
methods: Array<string>
websocket: WS
currentSharedFolder: string
constructor (private readOnly = false) {
super()
this.methods = ['compile']
}
setWebSocket (websocket: WS): void {
this.websocket = websocket
}
sharedFolder (currentSharedFolder: string): void {
this.currentSharedFolder = currentSharedFolder
}
compile (configPath: string) {
return new Promise((resolve, reject) => {
if (this.readOnly) {
const errMsg = '[Hardhat Compilation]: Cannot compile in read-only mode'
console.log('\x1b[31m%s\x1b[0m', `${errMsg}`)
return reject(new Error(errMsg))
}
const cmd = `npx hardhat compile --config ${configPath}`
const options = { cwd: this.currentSharedFolder, shell: true }
const child = spawn(cmd, options)
let result = ''
let error = ''
child.stdout.on('data', (data) => {
console.log('\x1b[32m%s\x1b[0m', `[Hardhat Compilation]: ${data.toString()}`)
result += data.toString()
})
child.stderr.on('data', (err) => {
console.log('\x1b[31m%s\x1b[0m', `[Hardhat Compilation]: ${err.toString()}`)
error += err.toString()
})
child.on('close', () => {
if (error) reject(error)
else resolve(result)
})
})
}
}

@ -85,11 +85,10 @@ export class RemixdClient extends PluginClient {
}
}
set (args: SharedFolderArgs): Promise<void> {
set (args: SharedFolderArgs) {
try {
return new Promise((resolve, reject) => {
if (this.readOnly) return reject(new Error('Cannot write file: read-only mode selected'))
const isFolder = args.path.endsWith('/')
const path = utils.absolutePath(args.path, this.currentSharedFolder)
const exists = fs.existsSync(path)
@ -99,31 +98,25 @@ export class RemixdClient extends PluginClient {
return reject(new Error('trying to write "undefined" ! stopping.'))
}
this.trackDownStreamUpdate[path] = path
if (isFolder) {
fs.mkdirp(path).then(() => {
let splitPath = args.path.split('/')
splitPath = splitPath.filter(dir => dir)
const dir = '/' + splitPath.join('/')
this.emit('folderAdded', dir)
resolve()
}).catch((e: Error) => reject(e))
if (!exists && args.path.indexOf('/') !== -1) {
// the last element is the filename and we should remove it
this.createDir({ path: args.path.substr(0, args.path.lastIndexOf('/')) })
}
try {
fs.writeFile(path, args.content, 'utf8', (error: Error) => {
if (error) {
console.log(error)
return reject(error)
}
resolve(true)
})
} catch (e) {
return reject(e)
}
if (!exists) {
this.emit('fileAdded', args.path)
} else {
fs.ensureFile(path).then(() => {
fs.writeFile(path, args.content, 'utf8', (error: Error) => {
if (error) {
console.log(error)
return reject(error)
}
resolve()
})
}).catch((e: Error) => reject(e))
if (!exists) {
this.emit('fileAdded', args.path)
} else {
this.emit('fileChanged', args.path)
}
this.emit('fileChanged', args.path)
}
})
} catch (error) {
@ -131,24 +124,22 @@ export class RemixdClient extends PluginClient {
}
}
createDir (args: SharedFolderArgs): Promise<void> {
createDir (args: SharedFolderArgs) {
try {
return new Promise((resolve, reject) => {
if (this.readOnly) return reject(new Error('Cannot create folder: read-only mode selected'))
const path = utils.absolutePath(args.path, this.currentSharedFolder)
const exists = fs.existsSync(path)
if (exists && !isRealPath(path)) return reject(new Error(''))
this.trackDownStreamUpdate[path] = path
fs.mkdirp(path).then(() => {
let splitPath = args.path.split('/')
splitPath = splitPath.filter(dir => dir)
const dir = '/' + splitPath.join('/')
this.emit('folderAdded', dir)
resolve()
}).catch((e: Error) => reject(e))
const paths = args.path.split('/').filter(value => value)
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
paths.forEach((value) => {
currentCheck = currentCheck ? currentCheck + '/' + value : value
const path = utils.absolutePath(currentCheck, this.currentSharedFolder)
if (!fs.existsSync(path)) {
fs.mkdirp(path)
this.emit('folderAdded', currentCheck)
}
})
resolve(true)
})
} catch (error) {
throw new Error(error)

@ -95,6 +95,12 @@
},
"remix-ui-workspace": {
"tags": []
},
"remix-ui-static-analyser": {
"tags": []
},
"remix-ui-checkbox": {
"tags": []
}
}
}

6
package-lock.json generated

@ -25139,9 +25139,9 @@
}
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash-es": {
"version": "4.17.15",

@ -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",
"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",
"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",
@ -159,7 +159,9 @@
"isbinaryfile": "^3.0.2",
"jquery": "^3.3.1",
"jszip": "^3.6.0",
"lodash": "^4.17.21",
"latest-version": "^5.1.0",
"lodash": "^4.17.21",
"merge": "^1.2.0",
"npm-install-version": "^6.0.2",
"react": "16.13.1",

@ -11,7 +11,7 @@
"target": "es2015",
"module": "commonjs",
"typeRoots": ["node_modules/@types"],
"lib": ["es2017", "dom"],
"lib": ["es2017", "es2019", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
@ -38,7 +38,9 @@
"@remix-ui/modal-dialog": ["libs/remix-ui/modal-dialog/src/index.ts"],
"@remix-ui/toaster": ["libs/remix-ui/toaster/src/index.ts"],
"@remix-ui/file-explorer": ["libs/remix-ui/file-explorer/src/index.ts"],
"@remix-ui/workspace": ["libs/remix-ui/workspace/src/index.ts"]
"@remix-ui/workspace": ["libs/remix-ui/workspace/src/index.ts"],
"@remix-ui/static-analyser": ["libs/remix-ui/static-analyser/src/index.ts"],
"@remix-ui/checkbox": ["libs/remix-ui/checkbox/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]

@ -725,6 +725,41 @@
}
}
}
},
"remix-ui-static-analyser": {
"root": "libs/remix-ui/static-analyser",
"sourceRoot": "libs/remix-ui/static-analyser/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/static-analyser/tsconfig.lib.json"],
"exclude": [
"**/node_modules/**",
"!libs/remix-ui/static-analyser/**/*"
]
}
}
}
},
"remix-ui-checkbox": {
"root": "libs/remix-ui/checkbox",
"sourceRoot": "libs/remix-ui/checkbox/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/checkbox/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/checkbox/**/*"]
}
}
}
}
},
"cli": {

Loading…
Cancel
Save