Merge branch 'master' into bunsenstraat-patch-1

pull/5370/head
EthereumRemix 4 years ago committed by GitHub
commit 5c968948be
  1. 4
      apps/debugger/.babelrc
  2. 16
      apps/debugger/.browserslistrc
  3. 17
      apps/debugger/src/app/app.tsx
  4. 157
      apps/debugger/src/app/debugger-api.ts
  5. 28
      apps/debugger/src/app/debugger.ts
  6. 0
      apps/debugger/src/assets/.gitkeep
  7. 3
      apps/debugger/src/environments/environment.prod.ts
  8. 6
      apps/debugger/src/environments/environment.ts
  9. BIN
      apps/debugger/src/favicon.ico
  10. 15
      apps/debugger/src/index.html
  11. 1
      apps/debugger/src/index.ts
  12. 9
      apps/debugger/src/main.tsx
  13. 7
      apps/debugger/src/polyfills.ts
  14. 1
      apps/debugger/src/styles.css
  15. 9
      apps/debugger/tsconfig.app.json
  16. 16
      apps/debugger/tsconfig.json
  17. 15
      apps/debugger/tsconfig.spec.json
  18. 17
      apps/debugger/webpack.config.js
  19. 26
      apps/remix-ide-e2e/src/commands/addFile.ts
  20. 2
      apps/remix-ide-e2e/src/commands/debugTransaction.ts
  21. 4
      apps/remix-ide-e2e/src/commands/getModalBody.ts
  22. 1
      apps/remix-ide-e2e/src/commands/goToVMTraceStep.ts
  23. 4
      apps/remix-ide-e2e/src/commands/openFile.ts
  24. 16
      apps/remix-ide-e2e/src/commands/removeFile.ts
  25. 13
      apps/remix-ide-e2e/src/commands/renamePath.ts
  26. 3
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  27. 2
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts
  28. 5
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  29. 20
      apps/remix-ide-e2e/src/tests/defaultLayout.test.ts
  30. 25
      apps/remix-ide-e2e/src/tests/editor.test.ts
  31. 76
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  32. 11
      apps/remix-ide-e2e/src/tests/fileManager_api.test.ts
  33. 2
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  34. 33
      apps/remix-ide-e2e/src/tests/gist.test.ts
  35. 18
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  36. 2
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  37. 1
      apps/remix-ide-e2e/src/tests/runAndDeploy.ts
  38. 6
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  39. 5
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  40. 2
      apps/remix-ide-e2e/src/tests/usingWebWorker.test.ts
  41. 35
      apps/remix-ide-e2e/src/tests/verticalIconsPanel.test.ts
  42. 6
      apps/remix-ide-e2e/src/types/index.d.ts
  43. 7
      apps/remix-ide/src/app.js
  44. 6
      apps/remix-ide/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js
  45. 15
      apps/remix-ide/src/app/components/local-plugin.js
  46. 26
      apps/remix-ide/src/app/components/vertical-icons.js
  47. 15
      apps/remix-ide/src/app/editor/editor.js
  48. 6
      apps/remix-ide/src/app/files/fileManager.js
  49. 7
      apps/remix-ide/src/app/files/remixDProvider.js
  50. 7
      apps/remix-ide/src/app/files/remixd-handle.js
  51. 117
      apps/remix-ide/src/app/panels/file-panel.js
  52. 56
      apps/remix-ide/src/app/panels/styles/file-panel-styles.css
  53. 2
      apps/remix-ide/src/app/panels/tab-proxy.js
  54. 91
      apps/remix-ide/src/app/tabs/debugger-tab.js
  55. 2
      apps/remix-ide/src/app/tabs/test-tab.js
  56. 18
      apps/remix-ide/src/app/ui/contextMenu.js
  57. 12
      apps/remix-ide/src/app/ui/modaldialog.js
  58. 2
      apps/remix-ide/src/lib/offsetToLineColumnConverter.js
  59. 26
      apps/remix-ide/src/remixAppManager.js
  60. 6
      libs/remix-debug/src/debugger/debugger.ts
  61. 2
      libs/remix-lib/src/eventManager.ts
  62. 2
      libs/remix-ui/debugger-ui/src/index.ts
  63. 64
      libs/remix-ui/debugger-ui/src/lib/DebuggerAPI.ts
  64. 51
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  65. 66
      libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts
  66. 4
      libs/remix-ui/file-explorer/.babelrc
  67. 19
      libs/remix-ui/file-explorer/.eslintrc
  68. 7
      libs/remix-ui/file-explorer/README.md
  69. 1
      libs/remix-ui/file-explorer/src/index.ts
  70. 28
      libs/remix-ui/file-explorer/src/lib/css/file-explorer-context-menu.css
  71. 56
      libs/remix-ui/file-explorer/src/lib/css/file-explorer.css
  72. 81
      libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx
  73. 97
      libs/remix-ui/file-explorer/src/lib/file-explorer-menu.tsx
  74. 947
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  75. 41
      libs/remix-ui/file-explorer/src/lib/types/index.ts
  76. 16
      libs/remix-ui/file-explorer/tsconfig.json
  77. 13
      libs/remix-ui/file-explorer/tsconfig.lib.json
  78. 56
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
  79. 13
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  80. 10
      libs/remix-ui/toaster/src/lib/toaster.tsx
  81. 257
      libs/remix-ui/tree-view/.eslintrc
  82. 2
      libs/remix-ui/tree-view/jest.config.js
  83. 4
      libs/remix-ui/tree-view/src/index.ts
  84. 2
      libs/remix-ui/tree-view/src/lib/remix-ui-tree-view.tsx
  85. 10
      libs/remix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx
  86. 17
      libs/remix-ui/tree-view/src/types/index.ts
  87. 26
      libs/remixd/src/services/remixdClient.ts
  88. 6
      nx.json
  89. 1005
      package-lock.json
  90. 12
      package.json
  91. 4
      tsconfig.json
  92. 65
      tslint.json
  93. 88
      workspace.json

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

@ -0,0 +1,16 @@
# This file is used by:
# 1. autoprefixer to adjust CSS to support the below specified browsers
# 2. babel preset-env to adjust included polyfills
#
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.

@ -0,0 +1,17 @@
import React, { useState, useEffect } from 'react';
import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line
import { DebuggerClientApi } from './debugger'
const remix = new DebuggerClientApi()
export const App = () => {
return (
<div className="debugger">
<DebuggerUI debuggerAPI={remix} />
</div>
);
};
export default App;

@ -0,0 +1,157 @@
import Web3 from 'web3'
import remixDebug, { TransactionDebugger as Debugger } from '@remix-project/remix-debug'
import { CompilationOutput, Sources } from '@remix-ui/debugger-ui'
import type { CompilationResult } from '@remix-project/remix-solidity-ts'
export const DebuggerApiMixin = (Base) => class extends Base {
initDebuggerApi () {
this.debugHash = null
const self = this
this.web3Provider = {
sendAsync(payload, callback) {
self.call('web3Provider', 'sendAsync', payload)
.then(result => callback(null, result))
.catch(e => callback(e))
}
}
this._web3 = new Web3(this.web3Provider)
this.offsetToLineColumnConverter = {
async offsetToLineColumn (rawLocation, file, sources, asts) {
return await self.call('offsetToLineColumnConverter', 'offsetToLineColumn', rawLocation, file, sources, asts)
}
}
}
// on()
// call()
// onDebugRequested()
// onRemoveHighlights()
web3 () {
return this._web3
}
async discardHighlight () {
await this.call('editor', 'discardHighlight')
}
async highlight (lineColumnPos, path) {
await this.call('editor', 'highlight', lineColumnPos, path)
}
async getFile (path) {
return await this.call('fileManager', 'getFile', path)
}
async setFile (path, content) {
await this.call('fileManager', 'setFile', path, content)
}
onBreakpointCleared (listener) {
this.onBreakpointClearedListener = listener
}
onBreakpointAdded (listener) {
this.onBreakpointAddedListener = listener
}
onEditorContentChanged (listener) {
this.onEditorContentChangedListener = listener
}
onDebugRequested (listener) {
this.onDebugRequestedListener = listener
}
onRemoveHighlights (listener) {
this.onRemoveHighlightsListener = listener
}
async fetchContractAndCompile (address, receipt) {
const target = (address && remixDebug.traceHelper.isContractCreation(address)) ? receipt.contractAddress : address
const targetAddress = target || receipt.contractAddress || receipt.to
const codeAtAddress = await this._web3.eth.getCode(targetAddress)
const output = await this.call('fetchAndCompile', 'resolve', targetAddress, codeAtAddress, 'browser/.debug')
return new CompilerAbstract(output.languageversion, output.data, output.source)
}
async getDebugWeb3 () {
let web3
let network
try {
network = await this.call('network', 'detectNetwork')
} catch (e) {
web3 = this.web3()
}
if (!web3) {
const webDebugNode = remixDebug.init.web3DebugNode(network.name)
web3 = !webDebugNode ? this.web3() : webDebugNode
}
remixDebug.init.extendWeb3(web3)
return web3
}
async getTrace (hash) {
if (!hash) return
const web3 = await this.getDebugWeb3()
const currentReceipt = await web3.eth.getTransactionReceipt(hash)
const debug = new Debugger({
web3,
offsetToLineColumnConverter: this.offsetToLineColumnConverter,
compilationResult: async (address) => {
try {
return await this.fetchContractAndCompile(address, currentReceipt)
} catch (e) {
console.error(e)
}
return null
},
debugWithGeneratedSources: false
})
return await debug.debugger.traceManager.getTrace(hash)
}
debug (hash) {
this.debugHash = hash
this.onDebugRequestedListener(hash)
}
onActivation () {
this.on('editor', 'breakpointCleared', (fileName, row) => this.onBreakpointClearedListener(fileName, row))
this.on('editor', 'breakpointAdded', (fileName, row) => this.onBreakpointAddedListener(fileName, row))
this.on('editor', 'contentChanged', () => this.onEditorContentChangedListener())
}
onDeactivation () {
this.onRemoveHighlightsListener()
this.off('editor', 'breakpointCleared')
this.off('editor', 'breakpointAdded')
this.off('editor', 'contentChanged')
}
}
export class CompilerAbstract implements CompilationOutput { // this is a subset of /remix-ide/src/app/compiler/compiler-abstract.js
languageversion
data
source
constructor (languageversion: string, data: CompilationResult, source: { sources: Sources, target: string }) {
this.languageversion = languageversion
this.data = data
this.source = source // source code
}
getSourceName (fileIndex) {
if (this.data && this.data.sources) {
return Object.keys(this.data.sources)[fileIndex]
} else if (Object.keys(this.source.sources).length === 1) {
// if we don't have ast, we return the only one filename present.
const sourcesArray = Object.keys(this.source.sources)
return sourcesArray[0]
}
return null
}
}

@ -0,0 +1,28 @@
import { PluginClient } from "@remixproject/plugin";
import { createClient } from "@remixproject/plugin-webview";
import { IDebuggerApi, RawLocation, Sources, Asts, LineColumnLocation,
onBreakpointClearedListener, onBreakpointAddedListener, onEditorContentChanged, TransactionReceipt } from '@remix-ui/debugger-ui'
import { DebuggerApiMixin, CompilerAbstract} from './debugger-api'
export class DebuggerClientApi extends DebuggerApiMixin(PluginClient) {
constructor () {
super()
createClient(this as any)
this.initDebuggerApi()
}
offsetToLineColumnConverter: IDebuggerApi['offsetToLineColumnConverter']
debugHash: string
debugHashRequest: number
removeHighlights: boolean
onBreakpointCleared: (listener: onBreakpointClearedListener) => void
onBreakpointAdded: (listener: onBreakpointAddedListener) => void
onEditorContentChanged: (listener: onEditorContentChanged) => void
discardHighlight: () => Promise<void>
highlight: (lineColumnPos: LineColumnLocation, path: string) => Promise<void>
fetchContractAndCompile: (address: string, currentReceipt: TransactionReceipt) => Promise<CompilerAbstract>
getFile: (path: string) => Promise<string>
setFile: (path: string, content: string) => Promise<void>
getDebugWeb3: () => any // returns an instance of web3.js
}

@ -0,0 +1,3 @@
export const environment = {
production: true
};

@ -0,0 +1,6 @@
// This file can be replaced during build by using the `fileReplacements` array.
// When building for production, this file is replaced with `environment.prod.ts`.
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Debugger</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous"/>
</head>
<body>
<div id="root"></div>
</body>
</html>

@ -0,0 +1 @@
export * from './app/debugger-api';

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/app';
ReactDOM.render(
<App />,
document.getElementById('root')
);

@ -0,0 +1,7 @@
/**
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
*
* See: https://github.com/zloirock/core-js#babel
*/
import 'core-js/stable';
import 'regenerator-runtime/runtime';

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"types": ["node", "jest"],
"resolveJsonModule": true
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.spec.js",
"**/*.spec.jsx",
"**/*.d.ts"
]
}

@ -0,0 +1,17 @@
const nxWebpack = require('@nrwl/react/plugins/webpack')
module.exports = config => {
const nxWebpackConfig = nxWebpack(config)
return {
...nxWebpackConfig,
node: {
fs: 'empty',
tls: 'empty',
readline: 'empty',
net: 'empty',
module: 'empty',
child_process: 'empty'
}
}
}

@ -14,21 +14,17 @@ class AddFile extends EventEmitter {
} }
function addFile (browser: NightwatchBrowser, name: string, content: NightwatchContractContent, done: VoidFunction) { function addFile (browser: NightwatchBrowser, name: string, content: NightwatchContractContent, done: VoidFunction) {
browser.clickLaunchIcon('udapp').clickLaunchIcon('fileExplorers').click('.newFile') browser.clickLaunchIcon('udapp')
.waitForElementVisible('#modal-dialog') .clickLaunchIcon('fileExplorers')
.perform((client, done) => { .click('li[data-id="treeViewLitreeViewItembrowser/README.txt"]') // focus on root directory
browser.execute(function (fileName) { .click('.newFile')
if (fileName !== 'Untitled.sol') { .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/blank"]')
document.querySelector('#modal-dialog #prompt_text').setAttribute('value', fileName) // .scrollAndClick('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items')
} .sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', name)
const elem = document.querySelector('#modal-footer-ok') as HTMLElement .sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', browser.Keys.ENTER)
.pause(2000)
elem.click() .waitForElementVisible(`li[data-id="treeViewLitreeViewItembrowser/${name}"]`)
}, [name], function (result) { .click(`li[data-id="treeViewLitreeViewItembrowser/${name}"]`)
console.log(result)
done()
})
})
.setEditorValue(content.content) .setEditorValue(content.content)
.pause(1000) .pause(1000)
.perform(function () { .perform(function () {

@ -19,7 +19,7 @@ function checkStyle (browser: NightwatchBrowser, index: number, callback: VoidFu
debugBtn && debugBtn.click() debugBtn && debugBtn.click()
}, [index], function () { }, [index], function () {
callback() browser.waitForElementVisible('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]').perform(() => callback())
}) })
} }

@ -3,8 +3,8 @@ import EventEmitter from "events"
class GetModalBody extends EventEmitter { class GetModalBody extends EventEmitter {
command (this: NightwatchBrowser, callback: (value: string, cb: VoidFunction) => void) { command (this: NightwatchBrowser, callback: (value: string, cb: VoidFunction) => void) {
this.api.waitForElementVisible('.modal-body') this.api.waitForElementPresent('.modal-body')
.getText('.modal-body', (result) => { .getText('#modal-dialog', (result) => {
console.log(result) console.log(result)
const value = typeof result.value === 'string' ? result.value : null const value = typeof result.value === 'string' ? result.value : null

@ -14,6 +14,7 @@ function goToVMtraceStep (browser: NightwatchBrowser, step: number, incr: number
browser.execute(function () { browser.execute(function () {
return document.querySelector('#stepdetail').innerHTML return document.querySelector('#stepdetail').innerHTML
}, [], function (result) { }, [], function (result) {
console.log('goToVMtraceStep', result)
if (typeof result.value === 'string' && ( result.value.indexOf('vm trace step:') !== -1 && result.value.indexOf(step.toString()) !== -1)) { if (typeof result.value === 'string' && ( result.value.indexOf('vm trace step:') !== -1 && result.value.indexOf(step.toString()) !== -1)) {
done() done()
} else if (incr > 1000) { } else if (incr > 1000) {

@ -16,8 +16,8 @@ class OpenFile extends EventEmitter {
// click on fileExplorer can toggle it. We go through settings to be sure FE is open // click on fileExplorer can toggle it. We go through settings to be sure FE is open
function openFile (browser: NightwatchBrowser, name: string, done: VoidFunction) { function openFile (browser: NightwatchBrowser, name: string, done: VoidFunction) {
browser.clickLaunchIcon('settings').clickLaunchIcon('fileExplorers') browser.clickLaunchIcon('settings').clickLaunchIcon('fileExplorers')
.waitForElementVisible('li[key="' + name + '"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItem' + name + '"')
.click('li[key="' + name + '"]') .click('li[data-id="treeViewLitreeViewItem' + name + '"')
.pause(2000) .pause(2000)
.perform(() => { .perform(() => {
done() done()

@ -34,13 +34,19 @@ function removeFile (browser: NightwatchBrowser, path: string, done: VoidFunctio
contextMenuClick(document.querySelector('[data-path="' + path + '"]')) contextMenuClick(document.querySelector('[data-path="' + path + '"]'))
}, [path], function () { }, [path], function () {
browser browser
.waitForElementVisible('#menuitemdelete', 2000) .waitForElementVisible('#menuitemdelete')
.click('#menuitemdelete') .click('#menuitemdelete')
.pause(500) .pause(2000)
.waitForElementVisible('#modal-footer-ok', 2000)
.click('#modal-footer-ok')
.waitForElementNotPresent('[data-path="' + path + '"]')
.perform(() => { .perform(() => {
if (path.indexOf('browser') !== -1) {
browser.waitForElementVisible('[data-id="browser-modal-footer-ok-react"]')
.click('[data-id="browser-modal-footer-ok-react"]')
.waitForElementNotPresent('[data-path="' + path + '"]')
} else if (path.indexOf('localhost') !== -1) {
browser.waitForElementVisible('[data-id="localhost-modal-footer-ok-react"]')
.click('[data-id="localhost-modal-footer-ok-react"]')
.waitForElementNotPresent('[data-path="' + path + '"]')
}
done() done()
}) })
}) })

@ -1,10 +1,10 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch' import { NightwatchBrowser } from 'nightwatch'
class RenameFile extends EventEmitter { class RenamePath extends EventEmitter {
command (this: NightwatchBrowser, path: string, newFileName: string, renamedPath: string) { command (this: NightwatchBrowser, path: string, newFileName: string, renamedPath: string) {
this.api.perform((done) => { this.api.perform((done) => {
renameFile(this.api, path, newFileName, renamedPath, () => { renamePath(this.api, path, newFileName, renamedPath, () => {
done() done()
this.emit('complete') this.emit('complete')
}) })
@ -13,7 +13,7 @@ class RenameFile extends EventEmitter {
} }
} }
function renameFile (browser: NightwatchBrowser, path: string, newFileName: string, renamedPath: string, done: VoidFunction) { function renamePath (browser: NightwatchBrowser, path: string, newFileName: string, renamedPath: string, done: VoidFunction) {
browser.execute(function (path: string) { browser.execute(function (path: string) {
function contextMenuClick (element) { function contextMenuClick (element) {
const evt = element.ownerDocument.createEvent('MouseEvents') const evt = element.ownerDocument.createEvent('MouseEvents')
@ -41,10 +41,9 @@ function renameFile (browser: NightwatchBrowser, path: string, newFileName: stri
doneSetValue() doneSetValue()
}) })
}) })
.click('body') // blur .pause(1000)
.waitForElementVisible('#modal-footer-ok', 100000) .click('li[data-id="treeViewLitreeViewItembrowser/README.txt"]') // focus on root directory
.pause(2000) .pause(2000)
.click('#modal-footer-ok')
.waitForElementNotPresent('[data-path="' + path + '"]') .waitForElementNotPresent('[data-path="' + path + '"]')
.waitForElementPresent('[data-path="' + renamedPath + '"]') .waitForElementPresent('[data-path="' + renamedPath + '"]')
.perform(() => { .perform(() => {
@ -53,4 +52,4 @@ function renameFile (browser: NightwatchBrowser, path: string, newFileName: stri
}) })
} }
module.exports = RenameFile module.exports = RenamePath

@ -39,10 +39,11 @@ module.exports = {
'Debug Ballot / delegate': function (browser: NightwatchBrowser) { 'Debug Ballot / delegate': function (browser: NightwatchBrowser) {
browser.pause(500) browser.pause(500)
.click('*[data-id="txLoggerDebugButton0x41fab8ea5b1d9fba5e0a6545ca1a2d62fff518578802c033c2b9a031a01c31b3"]') .click('*[data-id="txLoggerDebugButton0x41fab8ea5b1d9fba5e0a6545ca1a2d62fff518578802c033c2b9a031a01c31b3"]')
.pause(2000) .waitForElementVisible('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
// .clickLaunchIcon('debugger') // .clickLaunchIcon('debugger')
.click('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]') .click('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.pause(2000) .pause(2000)
.waitForElementVisible('#stepdetail')
.goToVMTraceStep(79) .goToVMTraceStep(79)
.pause(1000) .pause(1000)
.checkVariableDebug('soliditystate', stateCheck) .checkVariableDebug('soliditystate', stateCheck)

@ -50,8 +50,10 @@ module.exports = {
browser.pause(500) browser.pause(500)
.click('*[data-id="txLoggerDebugButton0x41fab8ea5b1d9fba5e0a6545ca1a2d62fff518578802c033c2b9a031a01c31b3"]') .click('*[data-id="txLoggerDebugButton0x41fab8ea5b1d9fba5e0a6545ca1a2d62fff518578802c033c2b9a031a01c31b3"]')
.pause(2000) .pause(2000)
.waitForElementVisible('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.click('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]') .click('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.pause(2000) .pause(2000)
.waitForElementVisible('#stepdetail')
.goToVMTraceStep(20) .goToVMTraceStep(20)
.pause(1000) .pause(1000)
.checkVariableDebug('callstackpanel', ["0x692a70D2e424a56D2C6C27aA97D1a86395877b3A"]) .checkVariableDebug('callstackpanel', ["0x692a70D2e424a56D2C6C27aA97D1a86395877b3A"])

@ -89,6 +89,7 @@ module.exports = {
.createContract('"tokenName", "symbol"') .createContract('"tokenName", "symbol"')
.debugTransaction(2) .debugTransaction(2)
.pause(2000) .pause(2000)
.waitForElementVisible('#stepdetail')
.goToVMTraceStep(10) .goToVMTraceStep(10)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf(`constructor (string memory name_, string memory symbol_) public { browser.assert.ok(content.indexOf(`constructor (string memory name_, string memory symbol_) public {
@ -109,7 +110,8 @@ module.exports = {
browser browser
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.6.12+commit.27d51765.js') .setSolidityCompilerVersion('soljson-v0.6.12+commit.27d51765.js')
.clickLaunchIcon('udapp') .clickLaunchIcon('fileExplorers')
.click('li[data-id="treeViewLitreeViewItembrowser/externalImport.sol"')
.testContracts('withABIEncoderV2.sol', sources[2]['browser/withABIEncoderV2.sol'], ['test']) .testContracts('withABIEncoderV2.sol', sources[2]['browser/withABIEncoderV2.sol'], ['test'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('test') .selectContract('test')
@ -118,6 +120,7 @@ module.exports = {
.clickFunction('test1 - transact (not payable)', {types: 'bytes userData', values: '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4'}) .clickFunction('test1 - transact (not payable)', {types: 'bytes userData', values: '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4'})
.debugTransaction(4) .debugTransaction(4)
.pause(2000) .pause(2000)
.waitForElementVisible('#stepdetail')
.goToVMTraceStep(261) .goToVMTraceStep(261)
.pause(1000) .pause(1000)
/* /*

@ -20,10 +20,10 @@ module.exports = {
browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]') browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS') .assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS')
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]') .waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.waitForElementVisible('li[key="browser/contracts"]') .waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.waitForElementVisible('li[key="browser/scripts"]') .waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/scripts"]')
.waitForElementVisible('li[key="browser/tests"]') .waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/tests"]')
.waitForElementVisible('li[key="browser/README.txt"]') .waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/README.txt"]')
}, },
'Loads Main View': function (browser: NightwatchBrowser) { 'Loads Main View': function (browser: NightwatchBrowser) {
@ -61,17 +61,17 @@ module.exports = {
'Toggles File Explorer Browser': function (browser: NightwatchBrowser) { 'Toggles File Explorer Browser': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]') .waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.assert.visible('ul[key="browser"]') .waitForElementPresent('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.click('div[data-id="treeViewTogglebrowser"]') .click('[data-path="browser"]')
.assert.hidden('ul[key="browser"]') .waitForElementNotPresent('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.click('div[data-id="treeViewTogglebrowser"]') .click('[data-path="browser"]')
.assert.visible('ul[key="browser"]') .waitForElementPresent('[data-id="treeViewLitreeViewItembrowser/contracts"]')
}, },
'Switch Tabs using tabs icon': function (browser: NightwatchBrowser) { 'Switch Tabs using tabs icon': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]') .waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.click('*[data-id="treeViewTogglebrowser/contracts"]') .click('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.openFile('browser/contracts/3_Ballot.sol') .openFile('browser/contracts/3_Ballot.sol')
.assert.containsText('div[title="browser/contracts/3_Ballot.sol"]', '3_Ballot.sol') .assert.containsText('div[title="browser/contracts/3_Ballot.sol"]', '3_Ballot.sol')
.click('span[class^=dropdownCaret]') .click('span[class^=dropdownCaret]')

@ -14,7 +14,6 @@ module.exports = {
browser.waitForElementVisible('div[data-id="mainPanelPluginsContainer"]') browser.waitForElementVisible('div[data-id="mainPanelPluginsContainer"]')
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]') .waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.click('*[data-id="treeViewLibrowser/contracts"]')
.openFile('browser/contracts/1_Storage.sol') .openFile('browser/contracts/1_Storage.sol')
.waitForElementVisible('*[data-id="editorInput"]') .waitForElementVisible('*[data-id="editorInput"]')
.checkElementStyle('*[data-id="editorInput"]', 'font-size', '12px') .checkElementStyle('*[data-id="editorInput"]', 'font-size', '12px')
@ -81,7 +80,8 @@ module.exports = {
'Should highlight source code': function (browser: NightwatchBrowser) { 'Should highlight source code': function (browser: NightwatchBrowser) {
// include all files here because switching between plugins in side-panel removes highlight // include all files here because switching between plugins in side-panel removes highlight
browser.addFile('sourcehighlight.js', sourcehighlightScript) browser
.addFile('sourcehighlight.js', sourcehighlightScript)
.addFile('removeSourcehighlightScript.js', removeSourcehighlightScript) .addFile('removeSourcehighlightScript.js', removeSourcehighlightScript)
.addFile('removeAllSourcehighlightScript.js', removeAllSourcehighlightScript) .addFile('removeAllSourcehighlightScript.js', removeAllSourcehighlightScript)
.openFile('browser/sourcehighlight.js') .openFile('browser/sourcehighlight.js')
@ -96,27 +96,26 @@ module.exports = {
}, },
'Should remove 1 highlight from source code': function (browser: NightwatchBrowser) { 'Should remove 1 highlight from source code': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('li[key="browser/removeSourcehighlightScript.js"]') browser.waitForElementVisible('li[data-id="treeViewLitreeViewItembrowser/removeSourcehighlightScript.js"]')
.click('li[key="browser/removeSourcehighlightScript.js"]') .click('li[data-id="treeViewLitreeViewItembrowser/removeSourcehighlightScript.js"]')
.pause(2000) .pause(2000)
.executeScript('remix.exeCurrent()') .executeScript('remix.exeCurrent()')
.waitForElementVisible('li[key="browser/contracts"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItembrowser/contracts"]')
.click('li[key="browser/contracts"]') // files don't appear, so we click twice to get the files .click('li[data-id="treeViewLitreeViewItembrowser/contracts"]')
.click('li[key="browser/contracts"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItembrowser/contracts/3_Ballot.sol"]')
.waitForElementVisible('li[key="browser/contracts/3_Ballot.sol"]') .click('li[data-id="treeViewLitreeViewItembrowser/contracts/3_Ballot.sol"]')
.click('li[key="browser/contracts/3_Ballot.sol"]')
.waitForElementNotPresent('.highlightLine32') .waitForElementNotPresent('.highlightLine32')
.checkElementStyle('.highlightLine40', 'background-color', 'rgb(8, 108, 181)') .checkElementStyle('.highlightLine40', 'background-color', 'rgb(8, 108, 181)')
.checkElementStyle('.highlightLine50', 'background-color', 'rgb(8, 108, 181)') .checkElementStyle('.highlightLine50', 'background-color', 'rgb(8, 108, 181)')
}, },
'Should remove all highlights from source code': function (browser: NightwatchBrowser) { 'Should remove all highlights from source code': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('li[key="browser/removeAllSourcehighlightScript.js"]') browser.waitForElementVisible('li[data-id="treeViewLitreeViewItembrowser/removeAllSourcehighlightScript.js"]')
.click('li[key="browser/removeAllSourcehighlightScript.js"]') .click('li[data-id="treeViewLitreeViewItembrowser/removeAllSourcehighlightScript.js"]')
.pause(2000) .pause(2000)
.executeScript('remix.exeCurrent()') .executeScript('remix.exeCurrent()')
.waitForElementVisible('li[key="browser/contracts/3_Ballot.sol"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItembrowser/contracts/3_Ballot.sol"]')
.click('li[key="browser/contracts/3_Ballot.sol"]') .click('li[data-id="treeViewLitreeViewItembrowser/contracts/3_Ballot.sol"]')
.pause(2000) .pause(2000)
.waitForElementNotPresent('.highlightLine32') .waitForElementNotPresent('.highlightLine32')
.waitForElementNotPresent('.highlightLine40') .waitForElementNotPresent('.highlightLine40')

@ -21,70 +21,74 @@ module.exports = {
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS') .assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS')
.click('*[data-id="fileExplorerNewFilecreateNewFile"]') .click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]') .pause(1000)
.setValue('*[data-id="modalDialogCustomPromptText"]', '5_New_contract.sol') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/blank"]')
.modalFooterOKClick() .sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', '5_New_contract.sol')
.waitForElementVisible('*[data-id="treeViewLibrowser/5_New_contract.sol"]', 7000) .sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/5_New_contract.sol"]', 7000)
}, },
'Should rename `5_New_contract.sol` to 5_Renamed_Contract.sol': function (browser: NightwatchBrowser) { 'Should rename `5_New_contract.sol` to 5_Renamed_Contract.sol': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="treeViewLibrowser/5_New_contract.sol"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/5_New_contract.sol"]')
.renameFile('browser/5_New_contract.sol', '5_Renamed_Contract.sol', 'browser/5_Renamed_Contract.sol') .renamePath('browser/5_New_contract.sol', '5_Renamed_Contract.sol', 'browser/5_Renamed_Contract.sol')
.waitForElementVisible('*[data-id="treeViewLibrowser/5_Renamed_Contract.sol"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/5_Renamed_Contract.sol"]')
}, },
'Should delete file `5_Renamed_Contract.sol` from file explorer': function (browser: NightwatchBrowser) { 'Should delete file `5_Renamed_Contract.sol` from file explorer': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="treeViewLibrowser/5_Renamed_Contract.sol"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/5_Renamed_Contract.sol"]')
.rightClick('[data-path="browser/5_Renamed_Contract.sol"]') .rightClick('[data-path="browser/5_Renamed_Contract.sol"]')
.click('*[id="menuitemdelete"]') .click('*[id="menuitemdelete"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]') .waitForElementVisible('*[data-id="browserModalDialogContainer-react"]')
.modalFooterOKClick() .pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLibrowser/5_Renamed_Contract.sol"') .click('.modal-ok')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItembrowser/5_Renamed_Contract.sol"')
}, },
'Should create a new folder': function (browser: NightwatchBrowser) { 'Should create a new folder': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="treeViewLibrowser/README.txt"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/README.txt"]')
.rightClick('[data-path="browser/README.txt"]') .click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('*[id="menuitemcreate folder"]') .pause(1000)
.waitForElementVisible('*[data-id="modalDialogContainer"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/blank"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', 'Browser_Tests') .sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', 'Browser_Tests')
.modalFooterOKClick() .sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLibrowser/Browser_Tests"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/Browser_Tests"]')
}, },
'Should rename Browser_Tests folder to Browser_E2E_Tests': function (browser: NightwatchBrowser) { 'Should rename Browser_Tests folder to Browser_E2E_Tests': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="treeViewLibrowser/Browser_Tests"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/Browser_Tests"]')
.rightClick('[data-path="browser/Browser_Tests"]') .renamePath('browser/Browser_Tests', 'Browser_E2E_Tests', 'browser/Browser_E2E_Tests')
.click('*[id="menuitemrename"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/Browser_E2E_Tests"]')
.sendKeys('[data-path="browser/Browser_Tests"]', 'Browser_E2E_Tests')
.sendKeys('[data-path="browser/Browser_Tests"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLibrowser/Browser_E2E_Tests"]')
}, },
'Should delete Browser_E2E_Tests folder': function (browser: NightwatchBrowser) { 'Should delete Browser_E2E_Tests folder': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="treeViewLibrowser/Browser_E2E_Tests"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/Browser_E2E_Tests"]')
.rightClick('[data-path="browser/Browser_E2E_Tests"]') .rightClick('[data-path="browser/Browser_E2E_Tests"]')
.click('*[id="menuitemdelete"]') .click('*[id="menuitemdelete"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]') .waitForElementVisible('*[data-id="browserModalDialogContainer-react"]')
.modalFooterOKClick() .pause(2000)
.waitForElementNotPresent('*[data-id="treeViewLibrowser/Browser_E2E_Tests"]') .click('.modal-ok')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItembrowser/Browser_E2E_Tests"]')
}, },
'Should publish all explorer files to github gist': function (browser: NightwatchBrowser) { 'Should publish all explorer files to github gist': function (browser: NightwatchBrowser) {
const runtimeBrowser = browser.options.desiredCapabilities.browserName const runtimeBrowser = browser.options.desiredCapabilities.browserName
browser browser.refresh()
.pause(10000)
.waitForElementVisible('*[data-id="fileExplorerNewFilepublishToGist"]') .waitForElementVisible('*[data-id="fileExplorerNewFilepublishToGist"]')
.click('*[data-id="fileExplorerNewFilepublishToGist"]') .click('*[data-id="fileExplorerNewFilepublishToGist"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]') .waitForElementVisible('*[data-id="browserModalDialogContainer-react"]')
.modalFooterOKClick() .pause(2000)
.waitForElementVisible('*[data-id="modalDialogContainer"]', 7000) .click('.modal-ok')
.modalFooterOKClick() .pause(2000)
.waitForElementVisible('*[data-id="browserModalDialogContainer-react"]')
.pause(2000)
.click('.modal-ok')
.pause(2000) .pause(2000)
.perform((done) => { .perform((done) => {
if (runtimeBrowser === 'chrome') { if (runtimeBrowser === 'chrome') {
@ -101,9 +105,9 @@ module.exports = {
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile1) .setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile1)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile2) .setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile2)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile3) .setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile3)
.waitForElementVisible('*[key="browser/editor.test.js"]') .waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/editor.test.js"]')
.waitForElementVisible('*[key="browser/fileExplorer.test.js"]') .waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/fileExplorer.test.js"]')
.waitForElementVisible('*[key="browser/generalSettings.test.js"]') .waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/generalSettings.test.js"]')
.end() .end()
}, },

@ -63,7 +63,7 @@ module.exports = {
.addFile('renameFile.js', { content: executeRename }) .addFile('renameFile.js', { content: executeRename })
.executeScript(`remix.exeCurrent()`) .executeScript(`remix.exeCurrent()`)
.pause(2000) .pause(2000)
.waitForElementPresent('[data-id="treeViewLibrowser/old_contract.sol"]') .waitForElementPresent('[data-id="treeViewLitreeViewItembrowser/old_contract.sol"]')
}, },
'Should execute `mkdir` api from file manager external api': function (browser: NightwatchBrowser) { 'Should execute `mkdir` api from file manager external api': function (browser: NightwatchBrowser) {
@ -71,7 +71,7 @@ module.exports = {
.addFile('mkdirFile.js', { content: executeMkdir }) .addFile('mkdirFile.js', { content: executeMkdir })
.executeScript(`remix.exeCurrent()`) .executeScript(`remix.exeCurrent()`)
.pause(2000) .pause(2000)
.waitForElementPresent('[data-id="treeViewLibrowser/Test_Folder"]') .waitForElementPresent('[data-id="treeViewLitreeViewItembrowser/Test_Folder"]')
}, },
'Should execute `readdir` api from file manager external api': function (browser: NightwatchBrowser) { 'Should execute `readdir` api from file manager external api': function (browser: NightwatchBrowser) {
@ -87,15 +87,16 @@ module.exports = {
.addFile('removeFile.js', { content: executeRemove }) .addFile('removeFile.js', { content: executeRemove })
.executeScript(`remix.exeCurrent()`) .executeScript(`remix.exeCurrent()`)
.pause(2000) .pause(2000)
.waitForElementNotPresent('[data-id="treeViewLibrowser/old_contract.sol"]') .waitForElementNotPresent('[data-id="treeViewLitreeViewItembrowser/old_contract.sol"]')
}, },
'Should execute `remove` api from file manager external api on a folder': function (browser: NightwatchBrowser) { // TODO: Fix remove root directory prefix for browser and localhost
'Should execute `remove` api from file manager external api on a folder': '' + function (browser: NightwatchBrowser) {
browser browser
.addFile('test_jsRemoveFolder.js', { content: executeRemoveOnFolder }) .addFile('test_jsRemoveFolder.js', { content: executeRemoveOnFolder })
.executeScript('remix.exeCurrent()') .executeScript('remix.exeCurrent()')
.pause(2000) .pause(2000)
.waitForElementNotPresent('*[key="browser/tests"]') .waitForElementNotPresent('[data-id="treeViewLitreeViewItembrowser/tests"]')
.end() .end()
}, },

@ -20,7 +20,7 @@ module.exports = {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000) browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.waitForElementVisible('*[data-id="settingsTabGenerateContractMetadataLabel"]', 5000) .waitForElementVisible('*[data-id="settingsTabGenerateContractMetadataLabel"]', 5000)
.click('*[data-id="verticalIconsFileExplorerIcons"]') .click('*[data-id="verticalIconsFileExplorerIcons"]')
.click('*[data-id="treeViewTogglebrowser/contracts"]') .click('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.openFile('browser/contracts/3_Ballot.sol') .openFile('browser/contracts/3_Ballot.sol')
.click('*[data-id="verticalIconsKindsolidity"]') .click('*[data-id="verticalIconsKindsolidity"]')
.pause(2000) .pause(2000)

@ -13,7 +13,7 @@ module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done) init(browser, done)
}, },
'UploadToGists': function (browser: NightwatchBrowser) { UploadToGists: function (browser: NightwatchBrowser) {
/* /*
- set the access token - set the access token
- publish to gist - publish to gist
@ -24,18 +24,24 @@ module.exports = {
const runtimeBrowser = browser.options.desiredCapabilities.browserName const runtimeBrowser = browser.options.desiredCapabilities.browserName
browser browser
.refresh()
.pause(10000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('fileExplorers') .click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.rightClick('[data-path="browser/README.txt"]') .pause(1000)
.click('*[id="menuitemcreate folder"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/blank"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]') .sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', 'Browser_Tests')
.setValue('*[data-id="modalDialogCustomPromptText"]', 'Browser_Tests') .sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', browser.Keys.ENTER)
.modalFooterOKClick() .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/Browser_Tests"]')
.waitForElementVisible('*[data-id="treeViewLibrowser/Browser_Tests"]')
.addFile('File.sol', { content: '' }) .addFile('File.sol', { content: '' })
.click('*[data-id="fileExplorerNewFilepublishToGist"]') .click('*[data-id="fileExplorerNewFilepublishToGist"]')
.modalFooterOKClick() .waitForElementVisible('*[data-id="browserModalDialogContainer-react"]')
.getModalBody((value, done) => { .pause(2000)
.click('.modal-ok')
.pause(10000)
.getText('[data-id="browserModalDialogModalBody-react"]', (result) => {
console.log(result)
const value = typeof result.value === 'string' ? result.value : null
const reg = /gist.github.com\/([^.]+)/ const reg = /gist.github.com\/([^.]+)/
const id = value.match(reg) const id = value.match(reg)
@ -45,13 +51,12 @@ module.exports = {
} else { } else {
const gistid = id[1] const gistid = id[1]
browser browser
.modalFooterCancelClick() .click('[data-id="browser-modal-footer-cancel-react"]')
.executeScript(`remix.loadgist('${gistid}')`) .executeScript(`remix.loadgist('${gistid}')`)
.perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('browser/gists') } done() }) .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('browser/gists') } done() })
.waitForElementVisible(`li[key="browser/gists/${gistid}"]`) .waitForElementVisible(`[data-id="treeViewLitreeViewItembrowser/gists/${gistid}"]`)
.click(`li[key="browser/gists/${gistid}"]`) .click(`[data-id="treeViewLitreeViewItembrowser/gists/${gistid}"]`)
.openFile(`browser/gists/${gistid}/README.txt`) .openFile(`browser/gists/${gistid}/README.txt`)
.perform(done)
} }
}) })
}, },

@ -16,10 +16,11 @@ module.exports = {
browser browser
.waitForElementVisible('#icon-panel', 10000) .waitForElementVisible('#icon-panel', 10000)
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.click('*[data-id="treeViewTogglebrowser/contracts"]') .click('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.openFile('browser/contracts/3_Ballot.sol') .openFile('browser/contracts/3_Ballot.sol')
.verifyContracts(['Ballot']) .verifyContracts(['Ballot'])
.click('#publishOnIpfs') .click('#publishOnIpfs')
.pause(8000)
.getModalBody((value, done) => { .getModalBody((value, done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed', '', '') if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed', '', '')
if (value.indexOf('dweb:/ipfs') === -1) browser.assert.fail('ipfs deploy failed', '', '') if (value.indexOf('dweb:/ipfs') === -1) browser.assert.fail('ipfs deploy failed', '', '')
@ -31,6 +32,7 @@ module.exports = {
'Publish on Swarm': '' + function (browser: NightwatchBrowser) { 'Publish on Swarm': '' + function (browser: NightwatchBrowser) {
browser browser
.click('#publishOnSwarm') .click('#publishOnSwarm')
.pause(8000)
.getModalBody((value, done) => { .getModalBody((value, done) => {
if (value.indexOf('Metadata of "ballot" was successfully.') === -1) browser.assert.fail('swarm deploy failed', '', '') if (value.indexOf('Metadata of "ballot" was successfully.') === -1) browser.assert.fail('swarm deploy failed', '', '')
if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed', '', '') if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed', '', '')
@ -43,24 +45,24 @@ module.exports = {
browser browser
.waitForElementVisible('#icon-panel') .waitForElementVisible('#icon-panel')
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.click('*[data-id="treeViewLibrowser/contracts"]')
.click('*[data-id="treeViewLibrowser/contracts"]')
.openFile('browser/contracts/1_Storage.sol') .openFile('browser/contracts/1_Storage.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="contractDropdownIpfsCheckbox"]') .waitForElementPresent('*[data-id="contractDropdownIpfsCheckbox"]')
.click('*[data-id="contractDropdownIpfsCheckbox"]') .click('*[data-id="contractDropdownIpfsCheckbox"]')
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000) .pause(8000)
.assert.containsText('*[data-id="modalDialogModalBody"]', 'Metadata of "storage" was published successfully.') .getModalBody((value, done) => {
if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed', '', '')
done()
})
.modalFooterOKClick() .modalFooterOKClick()
}, },
'Should remember choice after page refresh': function (browser: NightwatchBrowser) { 'Should remember choice after page refresh': function (browser: NightwatchBrowser) {
browser browser
.refresh() .refresh()
.waitForElementVisible('*[data-id="treeViewLibrowser/contracts"]') .waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.click('*[data-id="treeViewLibrowser/contracts"]') .click('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.click('*[data-id="treeViewLibrowser/contracts"]')
.openFile('browser/contracts/1_Storage.sol') .openFile('browser/contracts/1_Storage.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="contractDropdownIpfsCheckbox"]') .waitForElementPresent('*[data-id="contractDropdownIpfsCheckbox"]')

@ -121,7 +121,7 @@ function runTests (browser: NightwatchBrowser) {
.setEditorValue('contract test1 { function get () returns (uint) { return 10; }}') .setEditorValue('contract test1 { function get () returns (uint) { return 10; }}')
.click('[data-path="localhost/folder1/contract_' + browserName + '.sol"]') // rename a file and check .click('[data-path="localhost/folder1/contract_' + browserName + '.sol"]') // rename a file and check
.pause(1000) .pause(1000)
.renameFile('localhost/folder1/contract_' + browserName + '.sol', 'renamed_contract_' + browserName + '.sol', 'localhost/folder1/renamed_contract_' + browserName + '.sol') .renamePath('localhost/folder1/contract_' + browserName + '.sol', 'renamed_contract_' + browserName + '.sol', 'localhost/folder1/renamed_contract_' + browserName + '.sol')
.pause(1000) .pause(1000)
.removeFile('localhost/folder1/contract_' + browserName + '_toremove.sol') .removeFile('localhost/folder1/contract_' + browserName + '_toremove.sol')
.perform(function (done) { .perform(function (done) {

@ -128,6 +128,7 @@ module.exports = {
.waitForElementPresent('.transaction-status--submitted') .waitForElementPresent('.transaction-status--submitted')
.pause(25000) .pause(25000)
.switchBrowserTab(0) .switchBrowserTab(0)
.end()
}, },
'Should connect to Ethereum Main Network using MetaMask': '' + function (browser: NightwatchBrowser) { 'Should connect to Ethereum Main Network using MetaMask': '' + function (browser: NightwatchBrowser) {

@ -48,6 +48,8 @@ module.exports = {
'Test Github Import - no branch specified': function (browser: NightwatchBrowser) { 'Test Github Import - no branch specified': function (browser: NightwatchBrowser) {
browser browser
.setSolidityCompilerVersion('soljson-v0.6.2+commit.bacdbe57.js') // open-zeppelin moved to pragma ^0.6.0 (master branch) .setSolidityCompilerVersion('soljson-v0.6.2+commit.bacdbe57.js') // open-zeppelin moved to pragma ^0.6.0 (master branch)
.clickLaunchIcon('fileExplorers')
.click('li[data-id="treeViewLitreeViewItembrowser/README.txt"')
.addFile('Untitled6.sol', sources[5]['browser/Untitled6.sol']) .addFile('Untitled6.sol', sources[5]['browser/Untitled6.sol'])
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.verifyContracts(['test10', 'ERC20', 'SafeMath'], {wait: 10000}) .verifyContracts(['test10', 'ERC20', 'SafeMath'], {wait: 10000})
@ -55,6 +57,8 @@ module.exports = {
'Test Github Import - raw URL': function (browser: NightwatchBrowser) { 'Test Github Import - raw URL': function (browser: NightwatchBrowser) {
browser browser
.clickLaunchIcon('fileExplorers')
.click('li[data-id="treeViewLitreeViewItembrowser/README.txt"')
.addFile('Untitled7.sol', sources[6]['browser/Untitled7.sol']) .addFile('Untitled7.sol', sources[6]['browser/Untitled7.sol'])
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.verifyContracts(['test11', 'ERC20', 'SafeMath'], {wait: 10000}) .verifyContracts(['test11', 'ERC20', 'SafeMath'], {wait: 10000})
@ -63,6 +67,8 @@ module.exports = {
'Test switch to a github import from a solidity warning': function (browser: NightwatchBrowser) { 'Test switch to a github import from a solidity warning': function (browser: NightwatchBrowser) {
browser browser
.setSolidityCompilerVersion('soljson-v0.7.4+commit.3f05b770.js') .setSolidityCompilerVersion('soljson-v0.7.4+commit.3f05b770.js')
.clickLaunchIcon('fileExplorers')
.click('li[data-id="treeViewLitreeViewItembrowser/README.txt"')
.addFile('Untitled8.sol', sources[7]['browser/Untitled8.sol']) .addFile('Untitled8.sol', sources[7]['browser/Untitled8.sol'])
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')

@ -144,7 +144,8 @@ module.exports = {
}, },
'Changing current path': function (browser: NightwatchBrowser) { 'Changing current path': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]') browser
.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
.addFile('myTests/simple_storage_test.sol', sources[0]['browser/tests/simple_storage_test.sol']) .addFile('myTests/simple_storage_test.sol', sources[0]['browser/tests/simple_storage_test.sol'])
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.setValue('*[data-id="uiPathInput"]', 'browser/myTests') .setValue('*[data-id="uiPathInput"]', 'browser/myTests')
@ -167,7 +168,7 @@ function runTests (browser: NightwatchBrowser) {
browser browser
.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]') .waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.click('*[data-id="treeViewTogglebrowser/contracts"]') .click('*[data-id="treeViewLitreeViewItembrowser/contracts"]')
.openFile('browser/contracts/3_Ballot.sol') .openFile('browser/contracts/3_Ballot.sol')
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.pause(500) .pause(500)

@ -28,7 +28,7 @@ module.exports = {
browser browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.addFile('browser/basic.sol', sources[0]['browser/basic.sol']) .addFile('basic.sol', sources[0]['browser/basic.sol'])
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.execute(function() { .execute(function() {
const elem = document.getElementById('nightlies') as HTMLInputElement const elem = document.getElementById('nightlies') as HTMLInputElement

@ -0,0 +1,35 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
import sauce from './sauce'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Checks vertical icons panelcontex menu': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="verticalIconsKindpluginManager"]')
.click('*[data-id="verticalIconsKindpluginManager"]')
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtondebugger"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtondebugger"]', 7000)
.rightClick('[data-id="verticalIconsKinddebugger"]')
.waitForElementVisible('*[id="menuitemdeactivate"]')
.waitForElementVisible('*[id="menuitemdocumentation"]')
.click('*[data-id="remixIdeIconPanel"]')
},
'Checks vertical icons panel contex menu deactivate': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="verticalIconsKinddebugger"]', 7000)
.rightClick('[data-id="verticalIconsKinddebugger"]')
.click('*[id="menuitemdeactivate"]')
.click('*[data-id="verticalIconsKindsettings"]')
.click('*[data-id="verticalIconsKindpluginManager"]')
.scrollInto('*[data-id="pluginManagerComponentActivateButtondebugger"]')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugger"]')
},
tearDown: sauce
}

@ -28,7 +28,7 @@ declare module "nightwatch" {
checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser, checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser,
openFile(name: string): NightwatchBrowser, openFile(name: string): NightwatchBrowser,
editorScroll(direction: 'up' | 'down', numberOfTimes: number): NightwatchBrowser, editorScroll(direction: 'up' | 'down', numberOfTimes: number): NightwatchBrowser,
renameFile(path: string, newFileName: string, renamedPath: string): NightwatchBrowser, renamePath(path: string, newFileName: string, renamedPath: string): NightwatchBrowser,
rightClick(cssSelector: string): NightwatchBrowser, rightClick(cssSelector: string): NightwatchBrowser,
waitForElementContainsText(id: string, value: string): NightwatchBrowser, waitForElementContainsText(id: string, value: string): NightwatchBrowser,
getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser, getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser,
@ -63,6 +63,10 @@ declare module "nightwatch" {
sendKeys: (selector: string, inputValue: string | string[], callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void) => NightwatchBrowser sendKeys: (selector: string, inputValue: string | string[], callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void) => NightwatchBrowser
} }
export interface NightwatchAPI {
keys(keysToSend: string, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void): NightwatchAPI
}
export interface NightwatchContractContent { export interface NightwatchContractContent {
content: string; content: string;
} }

@ -50,7 +50,7 @@ const CompilersArtefacts = require('./app/compiler/compiler-artefacts')
const CompileTab = require('./app/tabs/compile-tab') const CompileTab = require('./app/tabs/compile-tab')
const SettingsTab = require('./app/tabs/settings-tab') const SettingsTab = require('./app/tabs/settings-tab')
const AnalysisTab = require('./app/tabs/analysis-tab') const AnalysisTab = require('./app/tabs/analysis-tab')
const DebuggerTab = require('./app/tabs/debugger-tab') const { DebuggerTab } = require('./app/tabs/debugger-tab')
const TestTab = require('./app/tabs/test-tab') const TestTab = require('./app/tabs/test-tab')
const FilePanel = require('./app/panels/file-panel') const FilePanel = require('./app/panels/file-panel')
const Editor = require('./app/editor/editor') const Editor = require('./app/editor/editor')
@ -370,10 +370,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
registry.get('fileproviders/browser').api registry.get('fileproviders/browser').api
) )
const analysis = new AnalysisTab(registry) const analysis = new AnalysisTab(registry)
const debug = new DebuggerTab( const debug = new DebuggerTab()
blockchain,
registry.get('editor').api,
registry.get('offsettolinecolumnconverter').api)
const test = new TestTab( const test = new TestTab(
registry.get('filemanager').api, registry.get('filemanager').api,
registry.get('offsettolinecolumnconverter').api, registry.get('offsettolinecolumnconverter').api,

@ -26,10 +26,11 @@ export default class FetchAndCompile extends Plugin {
* Returns compilation data * Returns compilation data
* *
* @param {string} contractAddress - Address of the contrac to resolve * @param {string} contractAddress - Address of the contrac to resolve
* @param {string} compilersartefacts - Object containing a mapping of compilation results (byContractAddress and __last) * @param {string} deployedBytecode - deployedBytecode of the contract
* @param {string} targetPath - Folder where to save the compilation arfefacts
* @return {CompilerAbstract} - compilation data targeting the given @arg contractAddress * @return {CompilerAbstract} - compilation data targeting the given @arg contractAddress
*/ */
async resolve (contractAddress, targetPath, web3) { async resolve (contractAddress, codeAtAddress, targetPath) {
contractAddress = ethutil.toChecksumAddress(contractAddress) contractAddress = ethutil.toChecksumAddress(contractAddress)
const compilersartefacts = globalRegistry.get('compilersartefacts').api const compilersartefacts = globalRegistry.get('compilersartefacts').api
@ -52,7 +53,6 @@ export default class FetchAndCompile extends Plugin {
if (!this.sourceVerifierNetWork.includes(network.name)) return localCompilation() if (!this.sourceVerifierNetWork.includes(network.name)) return localCompilation()
// check if the contract if part of the local compilation result // check if the contract if part of the local compilation result
const codeAtAddress = await web3.eth.getCode(contractAddress)
const compilation = localCompilation() const compilation = localCompilation()
if (compilation) { if (compilation) {
let found = false let found = false

@ -67,11 +67,20 @@ module.exports = class LocalPlugin {
this.profile[key] = e.target.value this.profile[key] = e.target.value
} }
updateMethods ({ target }) {
if (target.value) {
try {
this.profile.methods = target.value.split(',')
} catch (e) {}
}
}
/** The form to create a local plugin */ /** The form to create a local plugin */
form () { form () {
const name = this.profile.name || '' const name = this.profile.name || ''
const url = this.profile.url || '' const url = this.profile.url || ''
const displayName = this.profile.displayName || '' const displayName = this.profile.displayName || ''
const methods = (this.profile.methods && this.profile.methods.join(',')) || ''
const radioSelection = (key, label, message) => { const radioSelection = (key, label, message) => {
return this.profile[key] === label return this.profile[key] === label
? yo`<div class="radio"> ? yo`<div class="radio">
@ -94,6 +103,12 @@ module.exports = class LocalPlugin {
<label for="plugin-displayname">Display Name</label> <label for="plugin-displayname">Display Name</label>
<input class="form-control" onchange="${e => this.updateDisplayName(e)}" value="${displayName}" id="plugin-displayname" data-id="localPluginDisplayName" placeholder="Name in the header"> <input class="form-control" onchange="${e => this.updateDisplayName(e)}" value="${displayName}" id="plugin-displayname" data-id="localPluginDisplayName" placeholder="Name in the header">
</div> </div>
<div class="form-group">
<label for="plugin-methods">Api (comma separated list of methods name)</label>
<input class="form-control" onchange="${e => this.updateMethods(e)}" value="${methods}" id="plugin-methods" data-id="localPluginMethods" placeholder="Name in the header">
</div>
<div class="form-group"> <div class="form-group">
<label for="plugin-url">Url <small>(required)</small></label> <label for="plugin-url">Url <small>(required)</small></label>
<input class="form-control" onchange="${e => this.updateUrl(e)}" value="${url}" id="plugin-url" data-id="localPluginUrl" placeholder="ex: https://localhost:8000"> <input class="form-control" onchange="${e => this.updateUrl(e)}" value="${url}" id="plugin-url" data-id="localPluginUrl" placeholder="ex: https://localhost:8000">

@ -4,9 +4,10 @@ var yo = require('yo-yo')
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var helper = require('../../lib/helper') var helper = require('../../lib/helper')
const globalRegistry = require('../../global/registry') const globalRegistry = require('../../global/registry')
const contextMenu = require('../ui/contextMenu')
const { Plugin } = require('@remixproject/engine') const { Plugin } = require('@remixproject/engine')
const EventEmitter = require('events') const EventEmitter = require('events')
let VERTICALMENU_HANDLE
const profile = { const profile = {
name: 'menuicons', name: 'menuicons',
@ -63,7 +64,7 @@ export class VerticalIcons extends Plugin {
* Add an icon to the map * Add an icon to the map
* @param {ModuleProfile} profile The profile of the module * @param {ModuleProfile} profile The profile of the module
*/ */
addIcon ({ kind, name, icon, displayName, tooltip }) { addIcon ({ kind, name, icon, displayName, tooltip, documentation }) {
let title = (tooltip || displayName || name) let title = (tooltip || displayName || name)
title = title.replace(/^\w/, c => c.toUpperCase()) title = title.replace(/^\w/, c => c.toUpperCase())
this.icons[name] = yo` this.icons[name] = yo`
@ -72,6 +73,7 @@ export class VerticalIcons extends Plugin {
onclick="${() => { this.toggle(name) }}" onclick="${() => { this.toggle(name) }}"
plugin="${name}" plugin="${name}"
title="${title}" title="${title}"
oncontextmenu="${(e) => this.itemContextMenu(e, name, documentation)}"
data-id="verticalIconsKind${name}"> data-id="verticalIconsKind${name}">
<img class="image" src="${icon}" alt="${name}" /> <img class="image" src="${icon}" alt="${name}" />
</div>` </div>`
@ -221,6 +223,26 @@ export class VerticalIcons extends Plugin {
} }
} }
async itemContextMenu (e, name, documentation) {
const actions = {}
if (await this.appManager.canDeactivatePlugin(profile, { name })) {
actions.Deactivate = () => {
// this.call('manager', 'deactivatePlugin', name)
this.appManager.deactivatePlugin(name)
}
}
const links = {}
if (documentation) {
links.Documentation = documentation
}
if (Object.keys(actions).length || Object.keys(links).length) {
VERTICALMENU_HANDLE && VERTICALMENU_HANDLE.hide(null, true)
VERTICALMENU_HANDLE = contextMenu(e, actions, links)
}
e.preventDefault()
e.stopPropagation()
}
render () { render () {
const home = yo` const home = yo`
<div <div

@ -174,29 +174,34 @@ class Editor extends Plugin {
const breakpoints = e.editor.session.getBreakpoints() const breakpoints = e.editor.session.getBreakpoints()
for (const k in breakpoints) { for (const k in breakpoints) {
if (k === row.toString()) { if (k === row.toString()) {
this.event.trigger('breakpointCleared', [this.currentSession, row]) this.triggerEvent('breakpointCleared', [this.currentSession, row])
e.editor.session.clearBreakpoint(row) e.editor.session.clearBreakpoint(row)
e.stop() e.stop()
return return
} }
} }
this.setBreakpoint(row) this.setBreakpoint(row)
this.event.trigger('breakpointAdded', [this.currentSession, row]) this.triggerEvent('breakpointAdded', [this.currentSession, row])
e.stop() e.stop()
}) })
// Do setup on initialisation here // Do setup on initialisation here
this.editor.on('changeSession', () => { this.editor.on('changeSession', () => {
this._onChange() this._onChange()
this.event.trigger('sessionSwitched', []) this.triggerEvent('sessionSwitched', [])
this.editor.getSession().on('change', () => { this.editor.getSession().on('change', () => {
this._onChange() this._onChange()
this.sourceHighlighters.discardAllHighlights() this.sourceHighlighters.discardAllHighlights()
this.event.trigger('contentChanged', []) this.triggerEvent('contentChanged', [])
}) })
}) })
} }
triggerEvent (name, params) {
this.event.trigger(name, params) // internal stack
this.emit(name, ...params) // plugin stack
}
onActivation () { onActivation () {
this.on('sidePanel', 'focusChanged', (name) => this.sourceHighlighters.hideHighlightsExcept(name)) this.on('sidePanel', 'focusChanged', (name) => this.sourceHighlighters.hideHighlightsExcept(name))
this.on('sidePanel', 'pluginDisabled', (name) => this.sourceHighlighters.discardHighlight(name)) this.on('sidePanel', 'pluginDisabled', (name) => this.sourceHighlighters.discardHighlight(name))
@ -247,7 +252,7 @@ class Editor extends Plugin {
window.clearTimeout(this.saveTimeout) window.clearTimeout(this.saveTimeout)
} }
this.saveTimeout = window.setTimeout(() => { this.saveTimeout = window.setTimeout(() => {
this.event.trigger('requiringToSaveCurrentfile', []) this.triggerEvent('requiringToSaveCurrentfile', [])
}, 5000) }, 5000)
} }

@ -424,15 +424,15 @@ class FileManager extends Plugin {
} }
fileRemovedEvent (path) { fileRemovedEvent (path) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('fileRemoved', path)
this.events.emit('fileRemoved', path)
if (!this.openedFiles[path]) return if (!this.openedFiles[path]) return
if (path === this._deps.config.get('currentFile')) { if (path === this._deps.config.get('currentFile')) {
this._deps.config.set('currentFile', '') this._deps.config.set('currentFile', '')
} }
this.editor.discard(path) this.editor.discard(path)
delete this.openedFiles[path] delete this.openedFiles[path]
// TODO: Only keep `this.emit` (issue#2210)
this.emit('fileRemoved', path)
this.events.emit('fileRemoved', path)
this.openFile() this.openFile()
} }

@ -115,6 +115,13 @@ module.exports = class RemixDProvider {
}) })
} }
async createDir (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
const unprefixedpath = this.removePrefix(path)
return this._appManager.call('remixd', 'createDir', { path: unprefixedpath })
}
isReadOnly (path) { isReadOnly (path) {
return this._readOnlyMode || this._readOnlyFiles[path] === 1 return this._readOnlyMode || this._readOnlyFiles[path] === 1
} }

@ -22,7 +22,7 @@ const profile = {
name: 'remixd', name: 'remixd',
displayName: 'RemixD', displayName: 'RemixD',
url: 'ws://127.0.0.1:65520', url: 'ws://127.0.0.1:65520',
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'rename', 'remove', 'isDirectory', 'list'], methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'rename', 'remove', 'isDirectory', 'list', 'createDir'],
events: [], events: [],
description: 'Using Remixd daemon, allow to access file system', description: 'Using Remixd daemon, allow to access file system',
kind: 'other', kind: 'other',
@ -47,7 +47,6 @@ export class RemixdHandle extends WebsocketPlugin {
} }
activate () { activate () {
this.fileSystemExplorer.show()
this.connectToLocalhost() this.connectToLocalhost()
} }
@ -83,7 +82,9 @@ export class RemixdHandle extends WebsocketPlugin {
this.canceled() this.canceled()
} }
}, 3000) }, 3000)
this.locahostProvider.init(_ => this.fileSystemExplorer.ensureRoot()) this.locahostProvider.init(() => {
this.fileSystemExplorer.show()
})
this.call('manager', 'activatePlugin', 'git') this.call('manager', 'activatePlugin', 'git')
} }
} }

@ -1,13 +1,16 @@
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { FileExplorer } from '@remix-ui/file-explorer' // eslint-disable-line
import './styles/file-panel-styles.css'
var yo = require('yo-yo') var yo = require('yo-yo')
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
var FileExplorer = require('../files/file-explorer') // var FileExplorer = require('../files/file-explorer')
var { RemixdHandle } = require('../files/remixd-handle.js') var { RemixdHandle } = require('../files/remixd-handle.js')
var { GitHandle } = require('../files/git-handle.js') var { GitHandle } = require('../files/git-handle.js')
var globalRegistry = require('../../global/registry') var globalRegistry = require('../../global/registry')
var css = require('./styles/file-panel-styles')
var canUpload = window.File || window.FileReader || window.FileList || window.Blob var canUpload = window.File || window.FileReader || window.FileList || window.Blob
@ -44,63 +47,85 @@ const profile = {
module.exports = class Filepanel extends ViewPlugin { module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) { constructor (appManager) {
super(profile) super(profile)
var self = this this._components = {}
self._components = {} this._components.registry = globalRegistry
self._components.registry = globalRegistry this._deps = {
self._deps = { fileProviders: this._components.registry.get('fileproviders').api,
fileProviders: self._components.registry.get('fileproviders').api, fileManager: this._components.registry.get('filemanager').api,
fileManager: self._components.registry.get('filemanager').api, config: this._components.registry.get('config').api
config: self._components.registry.get('config').api
} }
this.hideRemixdExplorer = true
function createProvider (key, menuItems) { this.remixdExplorer = {
return new FileExplorer(self._components.registry, self._deps.fileProviders[key], menuItems, self) hide: () => {
this.hideRemixdExplorer = true
this.renderComponent()
},
show: () => {
this.hideRemixdExplorer = false
this.renderComponent()
} }
}
var fileExplorer = createProvider('browser', ['createNewFile', 'publishToGist', canUpload ? 'uploadFile' : '']) this.el = yo`
var fileSystemExplorer = createProvider('localhost') <div id="fileExplorerView">
self.remixdHandle = new RemixdHandle(fileSystemExplorer, self._deps.fileProviders.localhost, appManager)
self.gitHandle = new GitHandle()
const explorers = yo`
<div>
<div class="pl-2 ${css.treeview}" data-id="filePanelFileExplorerTree">${fileExplorer.init()}</div>
<div class="pl-2 filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div>
</div> </div>
` `
function template () { this.remixdHandle = new RemixdHandle(this.remixdExplorer, this._deps.fileProviders.localhost, appManager)
return yo` this.gitHandle = new GitHandle()
<div class=${css.container}>
<div class="${css.fileexplorer}">
<div class="${css.fileExplorerTree}">
${explorers}
</div>
</div>
</div>
`
}
var event = new EventManager() this.event = new EventManager()
self.event = event this._deps.fileProviders.localhost.event.register('connecting', (event) => {
var element = template()
fileExplorer.ensureRoot()
self._deps.fileProviders.localhost.event.register('connecting', (event) => {
}) })
self._deps.fileProviders.localhost.event.register('connected', (event) => { this._deps.fileProviders.localhost.event.register('connected', (event) => {
fileSystemExplorer.show() this.remixdExplorer.show()
}) })
self._deps.fileProviders.localhost.event.register('errored', (event) => { this._deps.fileProviders.localhost.event.register('errored', (event) => {
fileSystemExplorer.hide() this.remixdExplorer.hide()
}) })
self._deps.fileProviders.localhost.event.register('closed', (event) => { this._deps.fileProviders.localhost.event.register('closed', (event) => {
fileSystemExplorer.hide() this.remixdExplorer.hide()
}) })
self.render = function render () { return element } this.renderComponent()
}
render () {
return this.el
}
renderComponent () {
ReactDOM.render(
<div className='remixui_container'>
<div className='remixui_fileexplorer'>
<div className='remixui_fileExplorerTree'>
<div>
<div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'>
<FileExplorer
name='browser'
registry={this._components.registry}
filesProvider={this._deps.fileProviders.browser}
menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']}
plugin={this}
/>
</div>
<div className='pl-2 filesystemexplorer remixui_treeview'>
{ !this.hideRemixdExplorer &&
<FileExplorer
name='localhost'
registry={this._components.registry}
filesProvider={this._deps.fileProviders.localhost}
menuItems={['createNewFile', 'createNewFolder']}
plugin={this}
/>
}
</div>
</div>
</div>
</div>
</div>
, this.el)
} }
} }

@ -0,0 +1,56 @@
.remixui_container {
display : flex;
flex-direction : row;
width : 100%;
height : 100%;
box-sizing : border-box;
}
.remixui_fileexplorer {
display : flex;
flex-direction : column;
position : relative;
width : 100%;
padding-left : 6px;
padding-top : 6px;
}
.remixui_fileExplorerTree {
cursor : default;
}
.remixui_gist {
padding : 10px;
}
.remixui_gist i {
cursor : pointer;
}
.remixui_gist i:hover {
color : orange;
}
.remixui_connectToLocalhost {
padding : 10px;
}
.remixui_connectToLocalhost i {
cursor : pointer;
}
.remixui_connectToLocalhost i:hover {
color : var(--secondary)
}
.remixui_uploadFile {
padding : 10px;
}
.remixui_uploadFile label:hover {
color : var(--secondary)
}
.remixui_uploadFile label {
cursor : pointer;
}
.remixui_treeview {
overflow-y : auto;
}
.remixui_dialog {
display: flex;
flex-direction: column;
}
.remixui_dialogParagraph {
margin-bottom: 2em;
word-break: break-word;
}

@ -194,7 +194,7 @@ export class TabProxy extends Plugin {
} }
this._view.filetabs.addTab({ this._view.filetabs.addTab({
id: name, id: name.split(' ').join(''),
title, title,
icon, icon,
tooltip: name tooltip: name

@ -1,7 +1,7 @@
import toaster from '../ui/tooltip' import toaster from '../ui/tooltip'
import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line
import { DebuggerApiMixin } from '@remixproject/debugger-plugin'
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import remixDebug, { TransactionDebugger as Debugger } from '@remix-project/remix-debug'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
@ -21,16 +21,11 @@ const profile = {
version: packageJson.version version: packageJson.version
} }
class DebuggerTab extends ViewPlugin { export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
constructor (blockchain, editor, offsetToLineColumnConverter) { constructor () {
super(profile) super(profile)
this.el = null this.el = null
this.editor = editor this.initDebuggerApi()
this.offsetToLineColumnConverter = offsetToLineColumnConverter
this.blockchain = blockchain
this.debugHash = null
this.removeHighlights = false
this.debugHashRequest = 0
} }
render () { render () {
@ -63,90 +58,12 @@ class DebuggerTab extends ViewPlugin {
this.renderComponent() this.renderComponent()
// this.call('manager', 'activatePlugin', 'udapp')
return this.el return this.el
} }
async discardHighlight () {
await this.call('editor', 'discardHighlight')
}
async highlight (lineColumnPos, path) {
await this.call('editor', 'highlight', lineColumnPos, path)
}
async getFile (path) {
await this.call('fileManager', 'getFile', path)
}
async setFile (path, content) {
await this.call('fileManager', 'setFile', path, content)
}
renderComponent () { renderComponent () {
ReactDOM.render( ReactDOM.render(
<DebuggerUI debuggerAPI={this} /> <DebuggerUI debuggerAPI={this} />
, this.el) , this.el)
} }
deactivate () {
this.removeHighlights = true
this.renderComponent()
super.deactivate()
}
debug (hash) {
this.debugHash = hash
this.debugHashRequest++ // so we can trigger a debug using the same hash 2 times in a row. that's needs to be improved
this.renderComponent()
}
getDebugWeb3 () {
return new Promise((resolve, reject) => {
this.blockchain.detectNetwork((error, network) => {
let web3
if (error || !network) {
web3 = remixDebug.init.web3DebugNode(this.blockchain.web3())
} else {
const webDebugNode = remixDebug.init.web3DebugNode(network.name)
web3 = !webDebugNode ? this.blockchain.web3() : webDebugNode
}
remixDebug.init.extendWeb3(web3)
resolve(web3)
})
})
}
async getTrace (hash) {
if (!hash) return
const web3 = await this.getDebugWeb3()
const currentReceipt = await web3.eth.getTransactionReceipt(hash)
const debug = new Debugger({
web3,
offsetToLineColumnConverter: this.offsetToLineColumnConverter,
compilationResult: async (address) => {
try {
return await this.fetchContractAndCompile(address, currentReceipt)
} catch (e) {
console.error(e)
}
return null
},
debugWithGeneratedSources: false
})
return await debug.debugger.traceManager.getTrace(hash)
}
fetchContractAndCompile (address, receipt) {
const target = (address && remixDebug.traceHelper.isContractCreation(address)) ? receipt.contractAddress : address
const targetAddress = target || receipt.contractAddress || receipt.to
return this.call('fetchAndCompile', 'resolve', targetAddress, 'browser/.debug', this.blockchain.web3())
} }
// debugger () {
// return this.debuggerUI
// }
}
module.exports = DebuggerTab

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

@ -30,7 +30,7 @@ var css = csjs`
} }
` `
module.exports = (event, items) => { module.exports = (event, items, linkItems) => {
event.preventDefault() event.preventDefault()
function hide (event, force) { function hide (event, force) {
@ -45,7 +45,21 @@ module.exports = (event, items) => {
current.onclick = () => { hide(null, true); items[item]() } current.onclick = () => { hide(null, true); items[item]() }
return current return current
}) })
const container = yo`<div id="menuItemsContainer" class="p-1 ${css.container} bg-light shadow border"><ul id='menuitems'>${menu}</ul></div>`
let menuForLinks = yo``
if (linkItems) {
menuForLinks = Object.keys(linkItems).map((item, index) => {
const current = yo`<li id="menuitem${item.toLowerCase()}" class=${css.liitem}><a href=${linkItems[item]} target="_blank">${item}</a></li>`
current.onclick = () => { hide(null, true) }
return current
})
}
const container = yo`
<div id="menuItemsContainer" class="p-1 ${css.container} bg-light shadow border">
<ul id='menuitems'>${menu} ${menuForLinks}</ul>
</div>
`
container.style.left = event.pageX + 'px' container.style.left = event.pageX + 'px'
container.style.top = event.pageY + 'px' container.style.top = event.pageY + 'px'

@ -6,10 +6,10 @@ module.exports = (title, content, ok, cancel, focusSelector, opts) => {
let agreed = true let agreed = true
let footerIsActive = false let footerIsActive = false
opts = opts || {} opts = opts || {}
var container = document.querySelector('.modal') var container = document.getElementById('modal-dialog')
if (!container) { if (!container) {
document.querySelector('body').appendChild(html(opts)) document.querySelector('body').appendChild(html(opts))
container = document.querySelector('.modal') container = document.getElementById('modal-dialog')
incomingModal = false incomingModal = false
} else incomingModal = true } else incomingModal = true
@ -24,8 +24,8 @@ module.exports = (title, content, ok, cancel, focusSelector, opts) => {
cancelDiv.innerHTML = (cancel && cancel.label !== undefined) ? cancel.label : 'Cancel' cancelDiv.innerHTML = (cancel && cancel.label !== undefined) ? cancel.label : 'Cancel'
cancelDiv.style.display = cancelDiv.innerHTML === '' ? 'none' : 'inline-block' cancelDiv.style.display = cancelDiv.innerHTML === '' ? 'none' : 'inline-block'
var modal = document.querySelector('.modal-body') var modal = document.getElementById('modal-body-id')
var modalTitle = document.querySelector('.modal-header h6') var modalTitle = document.getElementById('modal-title-h6')
modalTitle.innerHTML = '' modalTitle.innerHTML = ''
if (title) modalTitle.innerText = title if (title) modalTitle.innerText = title
@ -134,12 +134,12 @@ function html (opts) {
<div id="modal-background" class="modal-dialog" role="document"> <div id="modal-background" class="modal-dialog" role="document">
<div class="modal-content ${css.modalContent} ${opts.class}"> <div class="modal-content ${css.modalContent} ${opts.class}">
<div class="modal-header"> <div class="modal-header">
<h6 class="modal-title" data-id="modalDialogModalTitle"></h6> <h6 id="modal-title-h6" class="modal-title" data-id="modalDialogModalTitle"></h6>
<span class="modal-close"> <span class="modal-close">
<i id="modal-close" title="Close" class="fas fa-times" aria-hidden="true"></i> <i id="modal-close" title="Close" class="fas fa-times" aria-hidden="true"></i>
</span> </span>
</div> </div>
<div class="modal-body ${css.modalBody}" data-id="modalDialogModalBody"> - </div> <div id="modal-body-id" class="modal-body ${css.modalBody}" data-id="modalDialogModalBody"> - </div>
<div class="modal-footer" data-id="modalDialogModalFooter" autofocus> <div class="modal-footer" data-id="modalDialogModalFooter" autofocus>
<span id="modal-footer-ok" class="${css.modalFooterOk} modal-ok btn btn-sm btn-light" tabindex='5'>OK</span> <span id="modal-footer-ok" class="${css.modalFooterOk} modal-ok btn btn-sm btn-light" tabindex='5'>OK</span>
<span id="modal-footer-cancel" class="${css.modalFooterCancel} modal-cancel btn btn-sm btn-light" tabindex='10' data-dismiss="modal">Cancel</span> <span id="modal-footer-cancel" class="${css.modalFooterCancel} modal-cancel btn btn-sm btn-light" tabindex='10' data-dismiss="modal">Cancel</span>

@ -5,7 +5,7 @@ import { sourceMappingDecoder } from '@remix-project/remix-debug'
const profile = { const profile = {
name: 'offsetToLineColumnConverter', name: 'offsetToLineColumnConverter',
methods: [], methods: ['offsetToLineColumn'],
events: [], events: [],
version: packageJson.version version: packageJson.version
} }

@ -6,17 +6,29 @@ import QueryParams from './lib/query-params'
import { PermissionHandler } from './app/ui/persmission-handler' import { PermissionHandler } from './app/ui/persmission-handler'
const requiredModules = [ // services + layout views + system views const requiredModules = [ // services + layout views + system views
'manager', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'fileManager', 'contentImport', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'manager', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'fileExplorers', 'fileManager', 'contentImport', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'terminal', 'settings', 'pluginManager', 'tabs'] 'fileExplorers', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp']
export function isNative (name) { export function isNative (name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd'] const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons']
return nativePlugins.includes(name) || requiredModules.includes(name) return nativePlugins.includes(name) || requiredModules.includes(name)
} }
export function canActivate (name) { /**
return ['ethdoc'].includes(name) || isNative(name) * Checks if plugin caller 'from' is allowed to activate plugin 'to'
* The caller can have 'canActivate' as a optional property in the plugin profile.
* This is an array containing the 'name' property of the plugin it wants to call.
* canActivate = ['plugin1-to-call','plugin2-to-call',....]
* or the plugin is allowed by default because it is native
*
* @param {any, any}
* @returns {boolean}
*/
export function canActivate (from, to) {
return ['ethdoc'].includes(from.name) ||
isNative(from.name) ||
(to && from && from.canActivate && from.canActivate.includes[to.name])
} }
export class RemixAppManager extends PluginManager { export class RemixAppManager extends PluginManager {
@ -29,7 +41,7 @@ export class RemixAppManager extends PluginManager {
} }
async canActivatePlugin (from, to) { async canActivatePlugin (from, to) {
return canActivate(from.name) return canActivate(from, to)
} }
async canDeactivatePlugin (from, to) { async canDeactivatePlugin (from, to) {

@ -37,7 +37,7 @@ export class Debugger {
locationToRowConverter: async (sourceLocation) => { locationToRowConverter: async (sourceLocation) => {
const compilationResult = await this.compilationResult() const compilationResult = await this.compilationResult()
if (!compilationResult) return { start: null, end: null } if (!compilationResult) return { start: null, end: null }
return this.offsetToLineColumnConverter.offsetToLineColumn(sourceLocation, sourceLocation.file, compilationResult.source.sources, compilationResult.data.sources) return await this.offsetToLineColumnConverter.offsetToLineColumn(sourceLocation, sourceLocation.file, compilationResult.source.sources, compilationResult.data.sources)
} }
}) })
@ -70,7 +70,7 @@ export class Debugger {
const compilationResultForAddress = await this.compilationResult(address) const compilationResultForAddress = await this.compilationResult(address)
if (!compilationResultForAddress) return if (!compilationResultForAddress) return
this.debugger.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, index, compilationResultForAddress.data.contracts).then((rawLocation) => { this.debugger.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, index, compilationResultForAddress.data.contracts).then(async (rawLocation) => {
if (compilationResultForAddress && compilationResultForAddress.data) { if (compilationResultForAddress && compilationResultForAddress.data) {
const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address) const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address)
const astSources = Object.assign({}, compilationResultForAddress.data.sources) const astSources = Object.assign({}, compilationResultForAddress.data.sources)
@ -81,7 +81,7 @@ export class Debugger {
sources[genSource.name] = { content: genSource.contents } sources[genSource.name] = { content: genSource.contents }
} }
} }
var lineColumnPos = this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources) var lineColumnPos = await this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources)
this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address]) this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address])
} else { } else {
this.event.trigger('newSourceLocation', [null]) this.event.trigger('newSourceLocation', [null])

@ -26,7 +26,7 @@ export class EventManager {
obj = this.anonymous obj = this.anonymous
} }
for (const reg in this.registered[eventName]) { for (const reg in this.registered[eventName]) {
if (this.registered[eventName][reg].obj === obj && this.registered[eventName][reg].func === func) { if ((this.registered[eventName][reg].obj === obj) && (this.registered[eventName][reg].func.toString() === func.toString())) {
this.registered[eventName].splice(reg, 1) this.registered[eventName].splice(reg, 1)
} }
} }

@ -1 +1,3 @@
export * from './lib/debugger-ui' export * from './lib/debugger-ui'
export * from './lib/idebugger-api'
export * from './lib/idebugger-api'

@ -1,64 +0,0 @@
import type { CompilationResult, CompilationSource } from '@remix-project/remix-solidity-ts'
export interface DebuggerUIProps {
debuggerAPI: DebuggerAPI
}
interface EditorEvent {
event: {
register(eventName: 'breakpointCleared' | 'breakpointAdded' | 'contentChanged',
callback: (fileName: string, row: string | number) => void)
}
}
interface LineColumnLocation {
start: {
line: number, column: number
},
end: {
line: number, column: number
}
}
interface RawLocation {
start: number, length: number
}
interface Sources {
[fileName: string] : {content: string}
}
interface CompilationOutput {
source: { sources: Sources, target: string }
data: CompilationResult
getSourceName: (id: number) => string
}
interface Asts {
[fileName: string] : CompilationSource // ast
}
interface TransactionReceipt {
blockHash: string
blockNumber: number
transactionHash: string
transactionIndex: number
from: string
to: string
contractAddress: string | null
}
export interface DebuggerAPI {
offsetToLineColumnConverter: { offsetToLineColumn: (sourceLocation: RawLocation, file: number, contents: Sources, asts: Asts) => LineColumnLocation }
debugHash: string
debugHashRequest: string
removeHighlights: boolean
editor: EditorEvent
discardHighlight: () => void
highlight: (lineColumnPos: LineColumnLocation, path: string) => void
fetchContractAndCompile: (address: string, currentReceipt: TransactionReceipt) => CompilationOutput
getFile: (path: string) => string
setFile: (path: string, content: string) => void
getDebugWeb3: () => any // returns an instance of web3.js
}

@ -4,7 +4,7 @@ import StepManager from './step-manager/step-manager'
import VmDebugger from './vm-debugger/vm-debugger' import VmDebugger from './vm-debugger/vm-debugger'
import VmDebuggerHead from './vm-debugger/vm-debugger-head' import VmDebuggerHead from './vm-debugger/vm-debugger-head'
import { TransactionDebugger as Debugger } from '@remix-project/remix-debug' import { TransactionDebugger as Debugger } from '@remix-project/remix-debug'
import { DebuggerUIProps } from './DebuggerAPI' import { DebuggerUIProps } from './idebugger-api'
import { Toaster } from '@remix-ui/toaster' import { Toaster } from '@remix-ui/toaster'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
import './debugger-ui.css' import './debugger-ui.css'
@ -25,39 +25,35 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
opt: { opt: {
debugWithGeneratedSources: false debugWithGeneratedSources: false
}, },
toastMessage: '' toastMessage: '',
currentDebugTransaction: ''
}) })
useEffect(() => { useEffect(() => {
return unLoad() return unLoad()
}, []) }, [])
useEffect(() => { debuggerModule.onDebugRequested((hash) => {
if (debuggerModule.debugHash) { if (hash) debug(hash)
debug(debuggerModule.debugHash) })
}
}, [debuggerModule.debugHashRequest])
useEffect(() => { debuggerModule.onRemoveHighlights(async () => {
if (debuggerModule.removeHighlights) deleteHighlights() await debuggerModule.discardHighlight()
}, [debuggerModule.removeHighlights]) })
useEffect(() => { useEffect(() => {
const setEditor = () => { const setEditor = () => {
const editor = debuggerModule.editor
editor.event.register('breakpointCleared', (fileName, row) => { debuggerModule.onBreakpointCleared((fileName, row) => {
if (state.debugger) state.debugger.breakPointManager.remove({fileName: fileName, row: row}) if (state.debugger) state.debugger.breakPointManager.remove({fileName: fileName, row: row})
}) })
editor.event.register('breakpointAdded', (fileName, row) => { debuggerModule.onBreakpointAdded((fileName, row) => {
if (state.debugger) { if (state.debugger) state.debugger.breakPointManager.add({fileName: fileName, row: row})
state.debugger.breakPointManager.add({fileName: fileName, row: row})
}
}) })
editor.event.register('contentChanged', () => { debuggerModule.onEditorContentChanged(() => {
unLoad() if (state.debugger) unLoad()
}) })
} }
@ -117,10 +113,6 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
unLoad() unLoad()
} }
const isDebuggerActive = () => {
return state.isActive
}
const unLoad = () => { const unLoad = () => {
if (state.debugger) state.debugger.unload() if (state.debugger) state.debugger.unload()
setState(prevState => { setState(prevState => {
@ -138,13 +130,20 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
vmDebugger: false, vmDebugger: false,
vmDebuggerHead: false vmDebuggerHead: false
}, },
debugging: false debugging: false,
currentDebugTransaction: ''
} }
}) })
} }
const startDebugging = async (blockNumber, txNumber, tx) => { const startDebugging = async (blockNumber, txNumber, tx) => {
if (state.debugger) unLoad() if (state.debugger) unLoad()
if (!txNumber) return if (!txNumber) return
setState(prevState => {
return {
...prevState,
currentDebugTransaction: txNumber
}
})
const web3 = await debuggerModule.getDebugWeb3() const web3 = await debuggerModule.getDebugWeb3()
const currentReceipt = await web3.eth.getTransactionReceipt(txNumber) const currentReceipt = await web3.eth.getTransactionReceipt(txNumber)
const debuggerInstance = new Debugger({ const debuggerInstance = new Debugger({
@ -189,12 +188,6 @@ const debug = (txHash) => {
startDebugging(null, txHash, null) startDebugging(null, txHash, null)
} }
const deleteHighlights = async () => {
await debuggerModule.discardHighlight()
}
const stepManager = { const stepManager = {
jumpTo: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.jumpTo.bind(state.debugger.step_manager) : null, jumpTo: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.jumpTo.bind(state.debugger.step_manager) : null,
stepOverBack: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.stepOverBack.bind(state.debugger.step_manager) : null, stepOverBack: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.stepOverBack.bind(state.debugger.step_manager) : null,

@ -0,0 +1,66 @@
import type { CompilationResult, CompilationSource } from '@remix-project/remix-solidity-ts'
export interface DebuggerUIProps {
debuggerAPI: IDebuggerApi
}
export interface LineColumnLocation {
start: {
line: number, column: number
},
end: {
line: number, column: number
}
}
export interface RawLocation {
start: number, length: number
}
export interface Sources {
[fileName: string] : {content: string}
}
export interface CompilationOutput {
source: { sources: Sources, target: string }
data: CompilationResult
getSourceName: (id: number) => string
}
export interface Asts {
[fileName: string] : CompilationSource // ast
}
export interface TransactionReceipt {
blockHash: string
blockNumber: number
transactionHash: string
transactionIndex: number
from: string
to: string
contractAddress: string | null
}
export type onBreakpointClearedListener = (params: string, row: number) => void
export type onBreakpointAddedListener = (params: string, row: number) => void
export type onEditorContentChanged = () => void
export type onDebugRequested = (hash: string) => void
export interface IDebuggerApi {
offsetToLineColumnConverter: { offsetToLineColumn: (sourceLocation: RawLocation, file: number, contents: Sources, asts: Asts) => Promise<LineColumnLocation> }
debugHash: string
debugHashRequest: number
removeHighlights: boolean
onRemoveHighlights: (listener: VoidFunction) => void
onDebugRequested: (listener: onDebugRequested) => void
onBreakpointCleared: (listener: onBreakpointClearedListener) => void
onBreakpointAdded: (listener: onBreakpointAddedListener) => void
onEditorContentChanged: (listener: onEditorContentChanged) => void
discardHighlight: () => Promise<void>
highlight: (lineColumnPos: LineColumnLocation, path: string) => Promise<void>
fetchContractAndCompile: (address: string, currentReceipt: TransactionReceipt) => Promise<CompilationOutput>
getFile: (path: string) => Promise<string>
setFile: (path: string, content: string) => Promise<void>
getDebugWeb3: () => any // returns an instance of web3.js
}

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

@ -0,0 +1 @@
export * from './lib/file-explorer'

@ -0,0 +1,28 @@
.remixui_contextContainer
{
display: block;
position: fixed;
border-radius: 2px;
z-index: 1000;
box-shadow: 0 0 4px var(--dark);
}
.remixui_contextContainer:focus {
outline: none;
}
.remixui_liitem
{
padding: 2px;
padding-left: 6px;
cursor: pointer;
color: var(--text-dark);
background-color: var(--light);
}
.remixui_liitem:hover
{
background-color: var(--secondary);
}
#remixui_menuitems
{
list-style: none;
margin: 0px;
}

@ -0,0 +1,56 @@
.remixui_label {
margin-top : 4px;
}
.remixui_leaf {
overflow : hidden;
text-overflow : ellipsis;
width : 90%;
margin-bottom : 0px;
}
.remixui_fileexplorer {
box-sizing : border-box;
user-select : none;
}
input[type="file"] {
display: none;
}
.remixui_folder,
.remixui_file {
font-size : 14px;
cursor : pointer;
}
.remixui_file {
padding : 4px;
}
.remixui_newFile {
padding-right : 10px;
}
.remixui_newFile i {
cursor : pointer;
}
.remixui_newFile:hover {
transform : scale(1.3);
}
.remixui_menu {
margin-left : 20px;
}
.remixui_items {
display : inline
}
.remixui_remove {
margin-left : auto;
padding-left : 5px;
padding-right : 5px;
}
.remixui_activeMode {
display : flex;
width : 100%;
margin-right : 10px;
padding-right : 19px;
}
.remixui_activeMode > div {
min-width : 10px;
}
ul {
padding : 0;
}

@ -0,0 +1,81 @@
import React, { useRef, useEffect } from 'react' // eslint-disable-line
import { FileExplorerContextMenuProps } from './types'
import './css/file-explorer-context-menu.css'
export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => {
const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, publishToGist, runScript, pageX, pageY, path, type, ...otherProps } = props
const contextMenuRef = useRef(null)
useEffect(() => {
contextMenuRef.current.focus()
}, [])
useEffect(() => {
const menuItemsContainer = contextMenuRef.current
const boundary = menuItemsContainer.getBoundingClientRect()
if (boundary.bottom > (window.innerHeight || document.documentElement.clientHeight)) {
menuItemsContainer.style.position = 'absolute'
menuItemsContainer.style.bottom = '10px'
menuItemsContainer.style.top = null
}
}, [pageX, pageY])
const menu = () => {
return actions.filter(item => {
if (item.type.findIndex(name => name === type) !== -1) return true
else if (item.path.findIndex(key => key === path) !== -1) return true
else if (item.extension.findIndex(ext => path.endsWith(ext)) !== -1) return true
else if (item.pattern.filter(value => path.match(new RegExp(value))).length > 0) return true
else return false
}).map((item, index) => {
return <li
id={`menuitem${item.name.toLowerCase()}`}
key={index}
className='remixui_liitem'
onClick={(e) => {
e.stopPropagation()
switch (item.name) {
case 'New File':
createNewFile(path)
break
case 'New Folder':
createNewFolder(path)
break
case 'Rename':
renamePath(path, type)
break
case 'Delete':
deletePath(path)
break
case 'Push changes to gist':
publishToGist()
break
case 'Run':
runScript(path)
break
default:
break
}
hideContextMenu()
}}>{item.name}</li>
})
}
return (
<div
id="menuItemsContainer"
className="p-1 remixui_contextContainer bg-light shadow border"
style={{ left: pageX, top: pageY }}
ref={contextMenuRef}
onBlur={hideContextMenu}
tabIndex={500}
{...otherProps}
>
<ul id='remixui_menuitems'>{menu()}</ul>
</div>
)
}
export default FileExplorerContextMenu

@ -0,0 +1,97 @@
import React, { useState, useEffect } from 'react' //eslint-disable-line
import { FileExplorerMenuProps } from './types'
export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
const [state, setState] = useState({
menuItems: [
{
action: 'createNewFile',
title: 'Create New File',
icon: 'far fa-file'
},
{
action: 'createNewFolder',
title: 'Create New Folder',
icon: 'far fa-folder'
},
{
action: 'publishToGist',
title: 'Publish all [browser] explorer files to a github gist',
icon: 'fab fa-github'
},
{
action: 'uploadFile',
title: 'Load a local file into Remix\'s browser folder',
icon: 'fa fa-upload'
},
{
action: 'updateGist',
title: 'Update the current [gist] explorer',
icon: 'fab fa-github'
}
].filter(item => props.menuItems && props.menuItems.find((name) => { return name === item.action })),
actions: {}
})
useEffect(() => {
const actions = {
updateGist: () => {}
}
setState(prevState => {
return { ...prevState, actions }
})
}, [])
return (
<>
<span className='remixui_label' title={props.title} data-path={props.title} style={{ fontWeight: 'bold' }}>{ props.title }</span>
<span className="remixui_menu">{
state.menuItems.map(({ action, title, icon }, index) => {
if (action === 'uploadFile') {
return (
<label
id={action}
data-id={'fileExplorerUploadFile' + action }
className={icon + ' mb-0 remixui_newFile'}
title={title}
key={index}
>
<input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onChange={(e) => {
e.stopPropagation()
props.uploadFile(e.target)
}}
multiple />
</label>
)
} else {
return (
<span
id={action}
data-id={'fileExplorerNewFile' + action}
onClick={(e) => {
e.stopPropagation()
if (action === 'createNewFile') {
props.createNewFile()
} else if (action === 'createNewFolder') {
props.createNewFolder()
} else if (action === 'publishToGist') {
props.publishToGist()
} else {
state.actions[action]()
}
}}
className={'newFile ' + icon + ' remixui_newFile'}
title={title}
key={index}
>
</span>
)
}
})}
</span>
</>
)
}
export default FileExplorerMenu

@ -0,0 +1,947 @@
import React, { useEffect, useState, useRef } 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
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import * as async from 'async'
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 * as helper from '../../../../../apps/remix-ide/src/lib/helper'
import QueryParams from '../../../../../apps/remix-ide/src/lib/query-params'
import './css/file-explorer.css'
const queryParams = new QueryParams()
export const FileExplorer = (props: FileExplorerProps) => {
const { filesProvider, name, registry, plugin } = props
const [state, setState] = useState({
focusElement: [{
key: name,
type: 'folder'
}],
focusPath: null,
files: [],
fileManager: null,
accessToken: null,
ctrlKey: false,
newFileName: '',
actions: [],
focusContext: {
element: null,
x: null,
y: null
},
focusEdit: {
element: null,
type: '',
isNew: false,
lastEdit: ''
},
expandPath: [],
modalOptions: {
hide: true,
title: '',
message: '',
ok: {
label: 'Ok',
fn: null
},
cancel: {
label: 'Cancel',
fn: null
},
handleHide: null
},
toasterMsg: ''
})
const editRef = useRef(null)
useEffect(() => {
if (state.focusEdit.element) {
setTimeout(() => {
if (editRef && editRef.current) {
editRef.current.focus()
}
}, 150)
}
}, [state.focusEdit.element])
useEffect(() => {
(async () => {
const fileManager = registry.get('filemanager').api
const config = registry.get('config').api
const accessToken = config.get('settings/gist-access-token')
const files = await fetchDirectoryContent(name)
const actions = [{
name: 'New File',
type: ['folder'],
path: [],
extension: [],
pattern: []
}, {
name: 'New Folder',
type: ['folder'],
path: [],
extension: [],
pattern: []
}, {
name: 'Rename',
type: ['file', 'folder'],
path: [],
extension: [],
pattern: []
}, {
name: 'Delete',
type: ['file', 'folder'],
path: [],
extension: [],
pattern: []
}, {
name: 'Push changes to gist',
type: [],
path: [],
extension: [],
pattern: ['^browser/gists/([0-9]|[a-z])*$']
}, {
name: 'Run',
type: [],
path: [],
extension: ['.js'],
pattern: []
}]
setState(prevState => {
return { ...prevState, fileManager, accessToken, files, actions }
})
})()
}, [])
useEffect(() => {
if (state.fileManager) {
filesProvider.event.register('fileExternallyChanged', fileExternallyChanged)
filesProvider.event.register('fileRenamedError', fileRenamedError)
}
}, [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])
const resolveDirectory = async (folderPath, dir: File[], isChild = false): Promise<File[]> => {
if (!isChild && (state.focusEdit.element === 'browser/blank') && state.focusEdit.isNew && (dir.findIndex(({ path }) => path === 'browser/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) => {
if (error) console.error(error)
const files = normalize(folderPath, fileTree)
resolve(files)
})
})
}
const normalize = (path, filesList): File[] => {
const folders = []
const files = []
const prefix = path.split('/')[0]
Object.keys(filesList || {}).forEach(key => {
const path = prefix + '/' + key
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('/')
return keyPath[keyPath.length - 1]
}
const extractParentFromKey = (key: string):string => {
if (!key) return
const keyPath = key.split('/')
keyPath.pop()
return keyPath.join('/')
}
const createNewFile = (newFilePath: string) => {
const fileManager = state.fileManager
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, '')
if (!createFile) {
toast('Failed to create file ' + newName)
}
}
})
}
const createNewFolder = async (newFolderPath: string) => {
const fileManager = state.fileManager
const dirName = newFolderPath + '/'
try {
const exists = await fileManager.exists(dirName)
if (exists) return
await fileManager.mkdir(dirName)
// addFolder(parentFolder, newFolderPath)
} catch (e) {
console.log('error: ', e)
toast('Failed to create folder: ' + newFolderPath)
}
}
const deletePath = async (path: string) => {
if (filesProvider.isReadOnly(path)) {
return toast('cannot delete file. ' + name + ' is a read only explorer')
}
const isDir = state.fileManager.isDirectory(path)
modal('Delete file', `Are you sure you want to delete ${path} ${isDir ? 'folder' : 'file'}?`, {
label: 'Ok',
fn: async () => {
try {
const fileManager = state.fileManager
await fileManager.remove(path)
} catch (e) {
toast(`Failed to remove file ${path}.`)
}
}
}, {
label: 'Cancel',
fn: () => {}
})
}
const renamePath = async (oldPath: string, newPath: string) => {
try {
const fileManager = state.fileManager
const exists = await fileManager.exists(newPath)
if (exists) {
modal('Rename File Failed', 'File name already exists', {
label: 'Close',
fn: () => {}
}, null)
} else {
await fileManager.rename(oldPath, newPath)
}
} catch (error) {
modal('Rename File Failed', 'Unexpected error while renaming: ' + error, {
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, focusElement: [{ key: filePath, type: 'file' }] }
})
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, focusElement: [{ key: folderPath, type: 'folder' }] }
})
}
const fileExternallyChanged = (path: string, file: { content: string }) => {
const config = registry.get('config').api
if (config.get('currentFile') === path && registry.editor.currentContent() && registry.editor.currentContent() !== file.content) {
if (filesProvider.isReadOnly(path)) return registry.editor.setText(file.content)
modal(path + ' changed', 'This file has been changed outside of Remix IDE.', {
label: 'Replace by the new content',
fn: () => {
registry.editor.setText(file.content)
}
}, {
label: 'Keep the content displayed in Remix',
fn: () => {}
})
}
}
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)
}
const uploadFile = (target) => {
// 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
// pick that up via the 'fileAdded' event from the files module.
[...target.files].forEach((file) => {
const files = filesProvider
const loadFile = (name: string): void => {
const fileReader = new FileReader()
fileReader.onload = async function (event) {
if (helper.checkSpecialChars(file.name)) {
modal('File Upload Failed', 'Special characters are not allowed', {
label: 'Close',
fn: async () => {}
}, null)
return
}
const success = await files.set(name, event.target.result)
if (!success) {
modal('File Upload Failed', 'Failed to create file ' + name, {
label: 'Close',
fn: async () => {}
}, null)
}
}
fileReader.readAsText(file)
}
const name = files.type + '/' + file.name
files.exists(name, (error, exist) => {
if (error) console.log(error)
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: () => {}
})
}
})
})
}
const publishToGist = () => {
modal('Create a public gist', 'Are you sure you want to publish all your files in browser directory anonymously as a public gist on github.com? Note: this will not include directories.', {
label: 'Ok',
fn: toGist
}, {
label: 'Cancel',
fn: () => {}
})
}
const toGist = (id?: string) => {
const proccedResult = function (error, data) {
if (error) {
modal('Publish to gist Failed', 'Failed to manage gist: ' + error, {
label: 'Close',
fn: async () => {}
}, null)
} 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: () => {}
})
} else {
modal('Publish to gist Failed', data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t'), {
label: 'Close',
fn: async () => {}
}, null)
}
}
}
/**
* This function is to get the original content of given gist
* @params id is the gist id to fetch
*/
const getOriginalFiles = async (id) => {
if (!id) {
return []
}
const url = `https://api.github.com/gists/${id}`
const res = await fetch(url)
const data = await res.json()
return data.files || []
}
// If 'id' is not defined, it is not a gist update but a creation so we have to take the files from the browser explorer.
const folder = id ? 'browser/gists/' + id : 'browser/'
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)
} else {
// check for token
if (!state.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)
} 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='
const gists = new Gists({ token: state.accessToken })
if (id) {
const originalFileList = await getOriginalFiles(id)
// Telling the GIST API to remove files
const updatedFileList = Object.keys(packaged)
const allItems = Object.keys(originalFileList)
.filter(fileName => updatedFileList.indexOf(fileName) === -1)
.reduce((acc, deleteFileName) => ({
...acc,
[deleteFileName]: null
}), originalFileList)
// adding new files
updatedFileList.forEach((file) => {
const _items = file.split('/')
const _fileName = _items[_items.length - 1]
allItems[_fileName] = packaged[file]
})
toast('Saving gist (' + id + ') ...')
gists.edit({
description: description,
public: true,
files: allItems,
id: id
}, (error, result) => {
proccedResult(error, result)
if (!error) {
for (const key in allItems) {
if (allItems[key] === null) delete allItems[key]
}
}
})
} else {
// id is not existing, need to create a new gist
toast('Creating a new gist ...')
gists.create({
description: description,
public: true,
files: packaged
}, (error, result) => {
proccedResult(error, result)
})
}
}
}
})
}
const runScript = async (path: string) => {
filesProvider.get(path, (error, content: string) => {
if (error) return console.log(error)
plugin.call('scriptRunner', 'execute', content)
})
}
const handleHideModal = () => {
setState(prevState => {
return { ...prevState, modalOptions: { ...state.modalOptions, hide: true } }
})
}
const modal = (title: string, message: string, ok: { label: string, fn: () => void }, cancel: { label: string, fn: () => void }) => {
setState(prevState => {
return {
...prevState,
modalOptions: {
...prevState.modalOptions,
hide: false,
message,
title,
ok,
cancel,
handleHide: handleHideModal
}
}
})
}
const toast = (message: string) => {
setState(prevState => {
return { ...prevState, toasterMsg: message }
})
}
const handleClickFile = (path: string) => {
state.fileManager.open(path)
setState(prevState => {
return { ...prevState, focusElement: [{ key: path, type: 'file' }] }
})
}
const handleClickFolder = async (path: string) => {
if (state.ctrlKey) {
if (state.focusElement.findIndex(item => item.key === path) !== -1) {
setState(prevState => {
return { ...prevState, focusElement: [...prevState.focusElement.filter(item => item.key !== path)] }
})
} else {
setState(prevState => {
return { ...prevState, focusElement: [...prevState.focusElement, { key: path, type: 'folder' }] }
})
}
} else {
let expandPath = []
if (!state.expandPath.includes(path)) {
expandPath = [...new Set([...state.expandPath, path])]
} else {
expandPath = [...new Set(state.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(path)))]
}
setState(prevState => {
return { ...prevState, focusElement: [{ key: path, type: 'folder' }], expandPath }
})
}
}
const handleContextMenuFile = (pageX: number, pageY: number, path: string, content: string) => {
if (!content) return
setState(prevState => {
return { ...prevState, focusContext: { element: path, x: pageX, y: pageY }, focusEdit: { ...prevState.focusEdit, lastEdit: content } }
})
}
const handleContextMenuFolder = (pageX: number, pageY: number, path: string, content: string) => {
if (!content) return
setState(prevState => {
return { ...prevState, focusContext: { element: path, x: pageX, y: pageY }, focusEdit: { ...prevState.focusEdit, lastEdit: content } }
})
}
const hideContextMenu = () => {
setState(prevState => {
return { ...prevState, focusContext: { element: null, x: 0, y: 0 } }
})
}
const editModeOn = (path: string, type: string, isNew: boolean = false) => {
if (filesProvider.isReadOnly(path)) return
setState(prevState => {
return { ...prevState, focusEdit: { ...prevState.focusEdit, element: path, isNew, type } }
})
}
const editModeOff = async (content: string) => {
const parentFolder = extractParentFromKey(state.focusEdit.element)
if (!content || (content.trim() === '')) {
if (state.focusEdit.isNew) {
const files = removePath(state.focusEdit.element, state.files)
const updatedFiles = files.filter(file => file)
setState(prevState => {
return { ...prevState, files: updatedFiles, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
})
} else {
editRef.current.textContent = state.focusEdit.lastEdit
setState(prevState => {
return { ...prevState, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
})
}
} else {
if (state.focusEdit.lastEdit === content) {
return setState(prevState => {
return { ...prevState, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
})
}
if (helper.checkSpecialChars(content)) {
modal('Validation Error', 'Special characters are not allowed', {
label: 'Ok',
fn: () => {}
}, null)
} else {
if (state.focusEdit.isNew) {
state.focusEdit.type === 'file' ? createNewFile(parentFolder + '/' + content) : createNewFolder(parentFolder + '/' + content)
const files = removePath(state.focusEdit.element, state.files)
const updatedFiles = files.filter(file => file)
setState(prevState => {
return { ...prevState, files: updatedFiles }
})
} 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)
}
setState(prevState => {
return { ...prevState, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
})
}
}
}
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
const expandPath = [...new Set([...state.expandPath, parentFolder])]
setState(prevState => {
return { ...prevState, expandPath }
})
editModeOn(parentFolder + '/blank', 'file', true)
}
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
else if ((parentFolder.indexOf('.sol') !== -1) || (parentFolder.indexOf('.js') !== -1)) parentFolder = extractParentFromKey(parentFolder)
const expandPath = [...new Set([...state.expandPath, parentFolder])]
setState(prevState => {
return { ...prevState, expandPath }
})
editModeOn(parentFolder + '/blank', 'folder', true)
}
const handleEditInput = (event) => {
if (event.which === 13) {
event.preventDefault()
editModeOff(editRef.current.innerText)
}
}
const label = (file: File) => {
return (
<div
className='remixui_items d-inline-block w-100'
ref={state.focusEdit.element === file.path ? editRef : null}
suppressContentEditableWarning={true}
contentEditable={state.focusEdit.element === file.path}
onKeyDown={handleEditInput}
onBlur={(e) => {
e.stopPropagation()
editModeOff(editRef.current.innerText)
}}
>
<span
title={file.path}
className={'remixui_label ' + (file.isDirectory ? 'folder' : 'remixui_leaf')}
data-path={file.path}
>
{ file.name }
</span>
</div>
)
}
const renderFiles = (file: File, index: number) => {
if (file.isDirectory) {
return (
<div key={index}>
<TreeViewItem
id={`treeViewItem${file.path}`}
iconX='pr-3 fa fa-folder'
iconY='pr-3 fa fa-folder-open'
key={`${file.path + index}`}
label={label(file)}
onClick={(e) => {
e.stopPropagation()
if (state.focusEdit.element !== file.path) handleClickFolder(file.path)
}}
onContextMenu={(e) => {
e.preventDefault()
e.stopPropagation()
handleContextMenuFolder(e.pageX, e.pageY, file.path, e.target.textContent)
}}
labelClass={ state.focusEdit.element === file.path ? 'bg-light' : state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' }
controlBehaviour={ state.ctrlKey }
expand={state.expandPath.includes(file.path)}
>
{
file.child ? <TreeView id={`treeView${file.path}`} key={index}>{
file.child.map((file, index) => {
return renderFiles(file, index)
})
}
</TreeView> : <TreeView id={`treeView${file.path}`} key={index} />
}
</TreeViewItem>
{ ((state.focusContext.element === file.path) && (state.focusEdit.element !== file.path)) &&
<FileExplorerContextMenu
actions={state.actions}
hideContextMenu={hideContextMenu}
createNewFile={handleNewFileInput}
createNewFolder={handleNewFolderInput}
deletePath={deletePath}
renamePath={editModeOn}
extractParentFromKey={extractParentFromKey}
publishToGist={publishToGist}
pageX={state.focusContext.x}
pageY={state.focusContext.y}
path={file.path}
type='folder'
/>
}
</div>
)
} else {
return (
<div key={index}>
<TreeViewItem
id={`treeViewItem${file.path}`}
key={index}
label={label(file)}
onClick={(e) => {
e.stopPropagation()
if (state.focusEdit.element !== file.path) handleClickFile(file.path)
}}
onContextMenu={(e) => {
e.preventDefault()
e.stopPropagation()
handleContextMenuFile(e.pageX, e.pageY, file.path, e.target.textContent)
}}
icon='far fa-file'
labelClass={ state.focusEdit.element === file.path ? 'bg-light' : state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' }
/>
{ ((state.focusContext.element === file.path) && (state.focusEdit.element !== file.path)) &&
<FileExplorerContextMenu
actions={state.actions}
hideContextMenu={hideContextMenu}
createNewFile={handleNewFileInput}
createNewFolder={handleNewFolderInput}
deletePath={deletePath}
renamePath={editModeOn}
runScript={runScript}
pageX={state.focusContext.x}
pageY={state.focusContext.y}
path={file.path}
type='file'
/>
}
</div>
)
}
}
return (
<div>
<TreeView id='treeView'>
<TreeViewItem id="treeViewItem"
label={
<FileExplorerMenu
title={name}
menuItems={props.menuItems}
createNewFile={handleNewFileInput}
createNewFolder={handleNewFolderInput}
publishToGist={publishToGist}
uploadFile={uploadFile}
fileManager={state.fileManager}
/>
}
expand={true}>
<div className='pb-2'>
<TreeView id='treeViewMenu'>
{
state.files.map((file, index) => {
return renderFiles(file, index)
})
}
</TreeView>
</div>
</TreeViewItem>
</TreeView>
{
props.name && <ModalDialog
id={ props.name }
title={ state.modalOptions.title }
message={ state.modalOptions.message }
hide={ state.modalOptions.hide }
ok={ state.modalOptions.ok }
cancel={ state.modalOptions.cancel }
handleHide={ handleHideModal }
/>
}
<Toaster message={state.toasterMsg} />
</div>
)
}
export default FileExplorer
function packageFiles (filesProvider, directory, callback) {
const ret = {}
filesProvider.resolveDirectory(directory, (error, files) => {
if (error) callback(error)
else {
async.eachSeries(Object.keys(files), (path, cb) => {
if (filesProvider.isDirectory(path)) {
cb()
} else {
filesProvider.get(path, (error, content) => {
if (error) return cb(error)
if (/^\s+$/.test(content) || !content.length) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
ret[path] = { content }
cb()
})
}
}, (error) => {
callback(error, ret)
})
}
})
}

@ -0,0 +1,41 @@
/* eslint-disable-next-line */
export interface FileExplorerProps {
name: string,
registry: any,
filesProvider: any,
menuItems?: string[],
plugin: any
}
export interface File {
path: string,
name: string,
isDirectory: boolean,
child?: File[]
}
export interface FileExplorerMenuProps {
title: string,
menuItems: string[],
fileManager: any,
createNewFile: (folder?: string) => void,
createNewFolder: (parentFolder?: string) => void,
publishToGist: () => void,
uploadFile: (target: EventTarget & HTMLInputElement) => void
}
export interface FileExplorerContextMenuProps {
actions: { name: string, type: string[], path: string[], extension: string[], pattern: string[] }[],
createNewFile: (folder?: string) => void,
createNewFolder: (parentFolder?: string) => void,
deletePath: (path: string) => void,
renamePath: (path: string, type: string) => void,
hideContextMenu: () => void,
extractParentFromKey?: (key: string) => string,
publishToGist?: () => void,
runScript?: (path: string) => void,
pageX: number,
pageY: number,
path: string,
type: string
}

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

@ -9,14 +9,12 @@ export const ModalDialog = (props: ModalDialogProps) => {
}) })
const modal = useRef(null) const modal = useRef(null)
const handleHide = () => { const handleHide = () => {
props.hide() props.handleHide()
} }
useEffect( useEffect(() => {
() => {
modal.current.focus() modal.current.focus()
}, [] }, [props.hide])
)
const modalKeyEvent = (keyCode) => { const modalKeyEvent = (keyCode) => {
if (keyCode === 27) { // Esc if (keyCode === 27) { // Esc
@ -41,74 +39,72 @@ export const ModalDialog = (props: ModalDialogProps) => {
} }
handleHide() handleHide()
} }
return (<>
return (
<div <div
id="modal-dialog" data-id={`${props.id}ModalDialogContainer-react`}
data-id="modalDialogContainer"
data-backdrop="static" data-backdrop="static"
data-keyboard="false" data-keyboard="false"
tabIndex={-1} className='modal'
className="modal d-block" style={{ display: props.hide ? 'none' : 'block' }}
role="dialog" role="dialog"
> >
<div id="modal-background" className="modal-dialog" role="document"> <div className="modal-dialog" role="document">
<div <div
tabIndex={1}
onBlur={(e) => { onBlur={(e) => {
e.stopPropagation() e.stopPropagation()
handleHide() handleHide()
}} }}
ref={modal} ref={modal}
className={'modal-content remixModalContent ' + (props.opts ? props.opts.class ? props.opts.class : '' : '')} tabIndex={-1}
className={'modal-content remixModalContent ' + (props.modalClass ? props.modalClass : '')}
onKeyDown={({ keyCode }) => { modalKeyEvent(keyCode) }} onKeyDown={({ keyCode }) => { modalKeyEvent(keyCode) }}
> >
<div className="modal-header"> <div className="modal-header">
<h6 className="modal-title" data-id="modalDialogModalTitle"> <h6 className="modal-title" data-id={`${props.id}ModalDialogModalTitle-react`}>
{props.title && props.title} {props.title && props.title}
</h6> </h6>
{!props.opts.hideClose && {!props.showCancelIcon &&
<span className="modal-close" onClick={() => handleHide()}> <span className="modal-close" onClick={() => handleHide()}>
<i id="modal-close" title="Close" className="fas fa-times" aria-hidden="true"></i> <i title="Close" className="fas fa-times" aria-hidden="true"></i>
</span> </span>
} }
</div> </div>
<div className="modal-body text-break remixModalBody" data-id="modalDialogModalBody"> <div className="modal-body text-break remixModalBody" data-id={`${props.id}ModalDialogModalBody-react`}>
{props.content && { props.children ? props.children : props.message }
props.content
}
</div> </div>
<div className="modal-footer" data-id="modalDialogModalFooter"> <div className="modal-footer" data-id={`${props.id}ModalDialogModalFooter-react`}>
{/* todo add autofocus ^^ */} {/* todo add autofocus ^^ */}
{ props.ok && { props.ok &&
<span <span
id="modal-footer-ok" data-id={`${props.id}-modal-footer-ok-react`}
className={'modal-ok btn btn-sm ' + (state.toggleBtn ? 'btn-dark' : 'btn-light')} className={'modal-ok btn btn-sm ' + (state.toggleBtn ? 'btn-dark' : 'btn-light')}
onClick={() => { onClick={() => {
if (props.ok && props.ok.fn) props.ok.fn() if (props.ok.fn) props.ok.fn()
handleHide() handleHide()
}} }}
tabIndex={1}
> >
{props.ok && props.ok.label ? props.ok.label : 'OK'} { props.ok.label ? props.ok.label : 'OK' }
</span> </span>
} }
{ props.cancel &&
<span <span
id="modal-footer-cancel" data-id={`${props.id}-modal-footer-cancel-react`}
className={'modal-cancel btn btn-sm ' + (state.toggleBtn ? 'btn-light' : 'btn-dark')} className={'modal-cancel btn btn-sm ' + (state.toggleBtn ? 'btn-light' : 'btn-dark')}
data-dismiss="modal" data-dismiss="modal"
onClick={() => { onClick={() => {
if (props.cancel && props.cancel.fn) props.cancel.fn() if (props.cancel.fn) props.cancel.fn()
handleHide() handleHide()
}} }}
tabIndex={2}
> >
{props.cancel && props.cancel.label ? props.cancel.label : 'Cancel'} { props.cancel.label ? props.cancel.label : 'Cancel' }
</span> </span>
}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</>) )
} }
export default ModalDialog export default ModalDialog

@ -1,9 +1,12 @@
export interface ModalDialogProps { export interface ModalDialogProps {
id?: string
title?: string, title?: string,
content?: JSX.Element, message?: string,
ok?: { label: string, fn: () => void }, ok?: { label: string, fn: () => void },
cancel?: {label:string, fn: () => void}, cancel: { label: string, fn: () => void },
focusSelector?: string, modalClass?: string,
opts?: {class: string, hideClose?: boolean}, showCancelIcon?: boolean,
hide: () => void hide: boolean,
handleHide: (hideState?: boolean) => void,
children?: React.ReactNode
} }

@ -89,7 +89,15 @@ export const Toaster = (props: ToasterProps) => {
return ( return (
<> <>
{/* <ModalDialog /> */} <ModalDialog
message={props.message}
cancel={{
label: 'Close',
fn: () => {}
}}
hide={!state.showModal}
handleHide={hideFullMessage}
/>
{ !state.hide && { !state.hide &&
<div data-shared="tooltipPopup" className={`remixui_tooltip alert alert-info p-2 ${state.hiding ? 'remixui_animateTop' : 'remixui_animateBottom'}`} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}> <div data-shared="tooltipPopup" className={`remixui_tooltip alert alert-info p-2 ${state.hiding ? 'remixui_animateTop' : 'remixui_animateBottom'}`} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<span className="px-2"> <span className="px-2">

@ -1,248 +1,19 @@
{ {
"rules": {
"array-callback-return": "warn",
"dot-location": ["warn", "property"],
"eqeqeq": ["warn", "smart"],
"new-parens": "warn",
"no-caller": "warn",
"no-cond-assign": ["warn", "except-parens"],
"no-const-assign": "warn",
"no-control-regex": "warn",
"no-delete-var": "warn",
"no-dupe-args": "warn",
"no-dupe-keys": "warn",
"no-duplicate-case": "warn",
"no-empty-character-class": "warn",
"no-empty-pattern": "warn",
"no-eval": "warn",
"no-ex-assign": "warn",
"no-extend-native": "warn",
"no-extra-bind": "warn",
"no-extra-label": "warn",
"no-fallthrough": "warn",
"no-func-assign": "warn",
"no-implied-eval": "warn",
"no-invalid-regexp": "warn",
"no-iterator": "warn",
"no-label-var": "warn",
"no-labels": ["warn", { "allowLoop": true, "allowSwitch": false }],
"no-lone-blocks": "warn",
"no-loop-func": "warn",
"no-mixed-operators": [
"warn",
{
"groups": [
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["&&", "||"],
["in", "instanceof"]
],
"allowSamePrecedence": false
}
],
"no-multi-str": "warn",
"no-native-reassign": "warn",
"no-negated-in-lhs": "warn",
"no-new-func": "warn",
"no-new-object": "warn",
"no-new-symbol": "warn",
"no-new-wrappers": "warn",
"no-obj-calls": "warn",
"no-octal": "warn",
"no-octal-escape": "warn",
"no-redeclare": "warn",
"no-regex-spaces": "warn",
"no-restricted-syntax": ["warn", "WithStatement"],
"no-script-url": "warn",
"no-self-assign": "warn",
"no-self-compare": "warn",
"no-sequences": "warn",
"no-shadow-restricted-names": "warn",
"no-sparse-arrays": "warn",
"no-template-curly-in-string": "warn",
"no-this-before-super": "warn",
"no-throw-literal": "warn",
"no-restricted-globals": [
"error",
"addEventListener",
"blur",
"close",
"closed",
"confirm",
"defaultStatus",
"defaultstatus",
"event",
"external",
"find",
"focus",
"frameElement",
"frames",
"history",
"innerHeight",
"innerWidth",
"length",
"location",
"locationbar",
"menubar",
"moveBy",
"moveTo",
"name",
"onblur",
"onerror",
"onfocus",
"onload",
"onresize",
"onunload",
"open",
"opener",
"opera",
"outerHeight",
"outerWidth",
"pageXOffset",
"pageYOffset",
"parent",
"print",
"removeEventListener",
"resizeBy",
"resizeTo",
"screen",
"screenLeft",
"screenTop",
"screenX",
"screenY",
"scroll",
"scrollbars",
"scrollBy",
"scrollTo",
"scrollX",
"scrollY",
"self",
"status",
"statusbar",
"stop",
"toolbar",
"top"
],
"no-unexpected-multiline": "warn",
"no-unreachable": "warn",
"no-unused-expressions": [
"error",
{
"allowShortCircuit": true,
"allowTernary": true,
"allowTaggedTemplates": true
}
],
"no-unused-labels": "warn",
"no-useless-computed-key": "warn",
"no-useless-concat": "warn",
"no-useless-escape": "warn",
"no-useless-rename": [
"warn",
{
"ignoreDestructuring": false,
"ignoreImport": false,
"ignoreExport": false
}
],
"no-with": "warn",
"no-whitespace-before-property": "warn",
"react-hooks/exhaustive-deps": "warn",
"require-yield": "warn",
"rest-spread-spacing": ["warn", "never"],
"strict": ["warn", "never"],
"unicode-bom": ["warn", "never"],
"use-isnan": "warn",
"valid-typeof": "warn",
"no-restricted-properties": [
"error",
{
"object": "require",
"property": "ensure",
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting"
},
{
"object": "System",
"property": "import",
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting"
}
],
"getter-return": "warn",
"import/first": "error",
"import/no-amd": "error",
"import/no-webpack-loader-syntax": "error",
"react/forbid-foreign-prop-types": ["warn", { "allowInPropTypes": true }],
"react/jsx-no-comment-textnodes": "warn",
"react/jsx-no-duplicate-props": "warn",
"react/jsx-no-target-blank": "warn",
"react/jsx-no-undef": "error",
"react/jsx-pascal-case": ["warn", { "allowAllCaps": true, "ignore": [] }],
"react/jsx-uses-react": "warn",
"react/jsx-uses-vars": "warn",
"react/no-danger-with-children": "warn",
"react/no-direct-mutation-state": "warn",
"react/no-is-mounted": "warn",
"react/no-typos": "error",
"react/react-in-jsx-scope": "error",
"react/require-render-return": "error",
"react/style-prop-object": "warn",
"react/jsx-no-useless-fragment": "warn",
"jsx-a11y/accessible-emoji": "warn",
"jsx-a11y/alt-text": "warn",
"jsx-a11y/anchor-has-content": "warn",
"jsx-a11y/anchor-is-valid": [
"warn",
{ "aspects": ["noHref", "invalidHref"] }
],
"jsx-a11y/aria-activedescendant-has-tabindex": "warn",
"jsx-a11y/aria-props": "warn",
"jsx-a11y/aria-proptypes": "warn",
"jsx-a11y/aria-role": "warn",
"jsx-a11y/aria-unsupported-elements": "warn",
"jsx-a11y/heading-has-content": "warn",
"jsx-a11y/iframe-has-title": "warn",
"jsx-a11y/img-redundant-alt": "warn",
"jsx-a11y/no-access-key": "warn",
"jsx-a11y/no-distracting-elements": "warn",
"jsx-a11y/no-redundant-roles": "warn",
"jsx-a11y/role-has-required-aria-props": "warn",
"jsx-a11y/role-supports-aria-props": "warn",
"jsx-a11y/scope": "warn",
"react-hooks/rules-of-hooks": "error",
"default-case": "off",
"no-dupe-class-members": "off",
"no-undef": "off",
"@typescript-eslint/consistent-type-assertions": "warn",
"no-array-constructor": "off",
"@typescript-eslint/no-array-constructor": "warn",
"@typescript-eslint/no-namespace": "error",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": [
"warn",
{
"functions": false,
"classes": false,
"variables": false,
"typedefs": false
}
],
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{ "args": "none", "ignoreRestSiblings": true }
],
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": "warn"
},
"env": { "env": {
"browser": true, "browser": true,
"commonjs": true, "es6": true
"es6": true,
"jest": true,
"node": true
}, },
"settings": { "react": { "version": "detect" } }, "extends": "../../../.eslintrc",
"plugins": ["import", "jsx-a11y", "react", "react-hooks"], "globals": {
"extends": ["../../../.eslintrc"], "Atomics": "readonly",
"ignorePatterns": ["!**/*"] "SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
} }

@ -9,4 +9,4 @@ module.exports = {
}, },
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
coverageDirectory: '../../../coverage/libs/remix-ui/tree-view' coverageDirectory: '../../../coverage/libs/remix-ui/tree-view'
}; }

@ -1,2 +1,2 @@
export * from './lib/tree-view-item/tree-view-item'; export * from './lib/tree-view-item/tree-view-item'
export * from './lib/remix-ui-tree-view'; export * from './lib/remix-ui-tree-view'

@ -1,4 +1,4 @@
import React from 'react' import React from 'react' // eslint-disable-line
import { TreeViewProps } from '../types' import { TreeViewProps } from '../types'
import './remix-ui-tree-view.css' import './remix-ui-tree-view.css'

@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react' // eslint-disable-line
import { TreeViewItemProps } from '../../types' import { TreeViewItemProps } from '../../types'
import './tree-view-item.css' import './tree-view-item.css'
export const TreeViewItem = (props: TreeViewItemProps) => { export const TreeViewItem = (props: TreeViewItemProps) => {
const { id, children, label, expand, ...otherProps } = props const { id, children, label, labelClass, expand, iconX = 'fas fa-caret-right', iconY = 'fas fa-caret-down', icon, controlBehaviour = false, innerRef, ...otherProps } = props
const [isExpanded, setIsExpanded] = useState(false) const [isExpanded, setIsExpanded] = useState(false)
useEffect(() => { useEffect(() => {
@ -12,9 +12,9 @@ export const TreeViewItem = (props: TreeViewItemProps) => {
}, [expand]) }, [expand])
return ( return (
<li key={`treeViewLi${id}`} data-id={`treeViewLi${id}`} className='li_tv' {...otherProps}> <li ref={innerRef} key={`treeViewLi${id}`} data-id={`treeViewLi${id}`} className='li_tv' {...otherProps}>
<div key={`treeViewDiv${id}`} data-id={`treeViewDiv${id}`} className='d-flex flex-row align-items-center' onClick={() => setIsExpanded(!isExpanded)}> <div key={`treeViewDiv${id}`} data-id={`treeViewDiv${id}`} className={`d-flex flex-row align-items-center ${labelClass}`} onClick={() => !controlBehaviour && setIsExpanded(!isExpanded)}>
<div className={isExpanded ? 'px-1 fas fa-caret-down caret caret_tv' : 'px-1 fas fa-caret-right caret caret_tv'} style={{ visibility: children ? 'visible' : 'hidden' }}></div> { children ? <div className={isExpanded ? `px-1 ${iconY} caret caret_tv` : `px-1 ${iconX} caret caret_tv`} style={{ visibility: children ? 'visible' : 'hidden' }}></div> : icon ? <div className={`pr-3 pl-1 ${icon} caret caret_tv`}></div> : null }
<span className='w-100 pl-1'> <span className='w-100 pl-1'>
{ label } { label }
</span> </span>

@ -1,13 +1,22 @@
export interface TreeViewProps { export interface TreeViewProps {
children?: React.ReactNode, children?: React.ReactNode,
id: string id?: string
} }
export interface TreeViewItemProps { export interface TreeViewItemProps {
children?: React.ReactNode, children?: React.ReactNode,
id: string, id?: string,
label: string | number | React.ReactNode, label: string | number | React.ReactNode,
expand?: boolean, expand?: boolean,
onClick?: VoidFunction, onClick?: (...args: any) => void,
className?: string onInput?: (...args: any) => void,
className?: string,
iconX?: string,
iconY?: string,
icon?: string,
labelClass?: string,
controlBehaviour?: boolean
innerRef?: any,
onContextMenu?: (...args: any) => void,
onBlur?: (...args: any) => void
} }

@ -7,7 +7,7 @@ import * as fs from 'fs-extra'
import * as isbinaryfile from 'isbinaryfile' import * as isbinaryfile from 'isbinaryfile'
export class RemixdClient extends PluginClient { export class RemixdClient extends PluginClient {
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'list', 'isDirectory'] methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'list', 'isDirectory', 'createDir']
trackDownStreamUpdate: TrackDownStreamUpdate = {} trackDownStreamUpdate: TrackDownStreamUpdate = {}
websocket: WS websocket: WS
currentSharedFolder: string currentSharedFolder: string
@ -127,6 +127,30 @@ export class RemixdClient extends PluginClient {
} }
} }
createDir (args: SharedFolderArgs): Promise<void> {
try {
return new Promise((resolve, reject) => {
if (this.readOnly) 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)) 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))
})
} catch (error) {
throw new Error(error)
}
}
rename (args: SharedFolderArgs): Promise<boolean> { rename (args: SharedFolderArgs): Promise<boolean> {
try { try {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

@ -86,6 +86,12 @@
}, },
"remix-ui-toaster": { "remix-ui-toaster": {
"tags": [] "tags": []
},
"remix-ui-file-explorer": {
"tags": []
},
"debugger": {
"tags": []
} }
} }
} }

1005
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -41,7 +41,7 @@
"workspace-schematic": "nx workspace-schematic", "workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph", "dep-graph": "nx dep-graph",
"help": "nx help", "help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remixd,remix-ui-modal-dialog,remix-ui-toaster", "lint:libs": "nx run-many --target=lint --projects=remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs & lerna publish --skip-git & npm run bumpVersion:libs", "publish:libs": "npm run build:libs & lerna publish --skip-git & npm run bumpVersion:libs",
@ -82,6 +82,8 @@
"nightwatch_local_fileManager": "npm run build:e2e & nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/fileManager_api.test.js --env=chrome", "nightwatch_local_fileManager": "npm run build:e2e & nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/fileManager_api.test.js --env=chrome",
"nightwatch_local_runAndDeploy": "npm run build:e2e & nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/runAndDeploy.js --env=chrome-runAndDeploy", "nightwatch_local_runAndDeploy": "npm run build:e2e & nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/runAndDeploy.js --env=chrome-runAndDeploy",
"nightwatch_local_url": "npm run build:e2e & nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/url.test.js --env=chrome", "nightwatch_local_url": "npm run build:e2e & nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/url.test.js --env=chrome",
"nightwatch_local_verticalIconscontextmenu": "npm run build:e2e & nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/verticalIconsPanel.test.js --env=chrome",
"onchange": "onchange apps/remix-ide/build/app.js -- npm-run-all lint", "onchange": "onchange apps/remix-ide/build/app.js -- npm-run-all lint",
"remixd": "nx build remixd & nx serve remixd --folder=./apps/remix-ide/contracts --remixide=http://127.0.0.1:8080", "remixd": "nx build remixd & nx serve remixd --folder=./apps/remix-ide/contracts --remixide=http://127.0.0.1:8080",
"selenium": "selenium-standalone start", "selenium": "selenium-standalone start",
@ -153,12 +155,14 @@
"merge": "^1.2.0", "merge": "^1.2.0",
"npm-install-version": "^6.0.2", "npm-install-version": "^6.0.2",
"react": "16.13.1", "react": "16.13.1",
"react-beautiful-dnd": "^13.0.0",
"react-bootstrap": "^1.3.0", "react-bootstrap": "^1.3.0",
"react-dom": "16.13.1", "react-dom": "16.13.1",
"signale": "^1.4.0", "signale": "^1.4.0",
"time-stamp": "^2.2.0", "time-stamp": "^2.2.0",
"winston": "^3.3.3", "winston": "^3.3.3",
"ws": "^7.3.0" "ws": "^7.3.0",
"document-register-element": "1.13.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.4.5", "@babel/core": "^7.4.5",
@ -190,6 +194,7 @@
"@types/nightwatch": "^1.1.6", "@types/nightwatch": "^1.1.6",
"@types/node": "~8.9.4", "@types/node": "~8.9.4",
"@types/react": "16.9.17", "@types/react": "16.9.17",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "16.9.4", "@types/react-dom": "16.9.4",
"@types/react-router-dom": "5.1.3", "@types/react-router-dom": "5.1.3",
"@types/ws": "^7.2.4", "@types/ws": "^7.2.4",
@ -281,6 +286,7 @@
"worker-loader": "^2.0.0", "worker-loader": "^2.0.0",
"yo-yo": "github:ioedeveloper/yo-yo", "yo-yo": "github:ioedeveloper/yo-yo",
"yo-yoify": "^3.7.3", "yo-yoify": "^3.7.3",
"@types/jest": "25.1.4" "@types/jest": "25.1.4",
"@testing-library/react": "10.4.1"
} }
} }

@ -26,6 +26,7 @@
"@remix-project/remix-url-resolver": [ "@remix-project/remix-url-resolver": [
"dist/libs/remix-url-resolver/index.js" "dist/libs/remix-url-resolver/index.js"
], ],
"@remixproject/debugger-plugin": ["apps/debugger/src/index.ts"],
"@remix-project/remixd": ["dist/libs/remixd/index.js"], "@remix-project/remixd": ["dist/libs/remixd/index.js"],
"@remix-ui/tree-view": ["libs/remix-ui/tree-view/src/index.ts"], "@remix-ui/tree-view": ["libs/remix-ui/tree-view/src/index.ts"],
"@remix-ui/debugger-ui": ["libs/remix-ui/debugger-ui/src/index.ts"], "@remix-ui/debugger-ui": ["libs/remix-ui/debugger-ui/src/index.ts"],
@ -33,7 +34,8 @@
"@remix-ui/clipboard": ["libs/remix-ui/clipboard/src/index.ts"], "@remix-ui/clipboard": ["libs/remix-ui/clipboard/src/index.ts"],
"@remix-project/remix-solidity-ts": ["libs/remix-solidity/src/index.ts"], "@remix-project/remix-solidity-ts": ["libs/remix-solidity/src/index.ts"],
"@remix-ui/modal-dialog": ["libs/remix-ui/modal-dialog/src/index.ts"], "@remix-ui/modal-dialog": ["libs/remix-ui/modal-dialog/src/index.ts"],
"@remix-ui/toaster": ["libs/remix-ui/toaster/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"]
} }
}, },
"exclude": ["node_modules", "tmp"] "exclude": ["node_modules", "tmp"]

@ -0,0 +1,65 @@
{
"rulesDirectory": ["node_modules/@nrwl/workspace/src/tslint"],
"linterOptions": {
"exclude": ["**/*"]
},
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"deprecation": {
"severity": "warn"
},
"forin": true,
"import-blacklist": [true, "rxjs/Rx"],
"interface-over-type-literal": true,
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [true, "ignore-params"],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"prefer-const": true,
"radix": true,
"triple-equals": [true, "allow-null-check"],
"unified-signatures": true,
"variable-name": false,
"nx-enforce-module-boundaries": [
true,
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{ "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] }
]
}
]
}
}

@ -617,6 +617,94 @@
} }
} }
} }
},
"remix-ui-file-explorer": {
"root": "libs/remix-ui/file-explorer",
"sourceRoot": "libs/remix-ui/file-explorer/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/file-explorer/tsconfig.lib.json"],
"exclude": [
"**/node_modules/**",
"!libs/remix-ui/file-explorer/**/*"
]
}
}
}
},
"debugger": {
"root": "apps/debugger",
"sourceRoot": "apps/debugger/src",
"projectType": "application",
"schematics": {},
"architect": {
"build": {
"builder": "@nrwl/web:build",
"options": {
"outputPath": "dist/apps/debugger",
"index": "apps/debugger/src/index.html",
"main": "apps/debugger/src/main.tsx",
"polyfills": "apps/debugger/src/polyfills.ts",
"tsConfig": "apps/debugger/tsconfig.app.json",
"assets": [
"apps/debugger/src/assets",
"apps/debugger/src/index.html",
"apps/debugger/src/favicon.ico"
],
"styles": [],
"scripts": [],
"webpackConfig": "apps/debugger/webpack.config.js",
"maxWorkers": 2
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "apps/debugger/src/environments/environment.ts",
"with": "apps/debugger/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
}
}
},
"serve": {
"builder": "@nrwl/web:dev-server",
"options": {
"buildTarget": "debugger:build"
},
"configurations": {
"production": {
"buildTarget": "debugger:build:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": ["apps/debugger/tsconfig.app.json"],
"exclude": ["**/node_modules/**", "!apps/debugger/**/*"]
}
}
}
} }
}, },
"cli": { "cli": {

Loading…
Cancel
Save