desktopmerge
filip mertens 1 year ago
parent e42a392286
commit e9c806d750
  1. 46
      .circleci/config.yml
  2. 12
      .eslintrc.json
  3. 4
      .husky/pre-commit
  4. 13
      .prettierignore
  5. 7
      .prettierrc.json
  6. 18
      CONTRIBUTING.md
  7. 23
      README.md
  8. 59
      apps/circuit-compiler/project.json
  9. 17
      apps/circuit-compiler/src/app/app.tsx
  10. 224
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  11. 0
      apps/circuit-compiler/src/css/app.css
  12. 11
      apps/circuit-compiler/src/example/simple.circom
  13. 15
      apps/circuit-compiler/src/index.html
  14. 8
      apps/circuit-compiler/src/main.tsx
  15. 7
      apps/circuit-compiler/src/polyfills.ts
  16. 17
      apps/circuit-compiler/src/profile.json
  17. 24
      apps/circuit-compiler/tsconfig.app.json
  18. 17
      apps/circuit-compiler/tsconfig.json
  19. 92
      apps/circuit-compiler/webpack.config.js
  20. 12
      apps/debugger/src/app/app.tsx
  21. 1
      apps/debugger/src/app/debugger.ts
  22. 11
      apps/debugger/src/main.tsx
  23. 67
      apps/debugger/webpack.config.js
  24. 30
      apps/doc-gen/src/app/App.tsx
  25. 25
      apps/doc-gen/src/app/hooks/useLocalStorage.tsx
  26. 23
      apps/doc-gen/src/app/views/ErrorView.tsx
  27. 10
      apps/doc-gen/src/main.tsx
  28. 25
      apps/doc-gen/webpack.config.js
  29. 7
      apps/doc-viewer/src/app/App.tsx
  30. 4
      apps/doc-viewer/src/main.tsx
  31. 32
      apps/doc-viewer/webpack.config.js
  32. 20
      apps/etherscan/src/app/AppContext.tsx
  33. 83
      apps/etherscan/src/app/RemixPlugin.tsx
  34. 90
      apps/etherscan/src/app/app.tsx
  35. 60
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  36. 30
      apps/etherscan/src/app/components/SubmitButton.tsx
  37. 5
      apps/etherscan/src/app/hooks/useLocalStorage.tsx
  38. 10
      apps/etherscan/src/app/layouts/Default.tsx
  39. 16
      apps/etherscan/src/app/routes.tsx
  40. 68
      apps/etherscan/src/app/utils/networks.ts
  41. 2
      apps/etherscan/src/app/utils/verify.ts
  42. 106
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  43. 16
      apps/etherscan/src/app/views/ErrorView.tsx
  44. 22
      apps/etherscan/src/app/views/HomeView.tsx
  45. 140
      apps/etherscan/src/app/views/ReceiptsView.tsx
  46. 325
      apps/etherscan/src/app/views/VerifyView.tsx
  47. 14
      apps/etherscan/src/main.tsx
  48. 64
      apps/etherscan/webpack.config.js
  49. 64
      apps/remix-ide-e2e/nightwatch.ts
  50. 10
      apps/remix-ide-e2e/package.json
  51. 6
      apps/remix-ide-e2e/src/commands/checkAnnotations.ts
  52. 6
      apps/remix-ide-e2e/src/commands/checkAnnotationsNotPresent.ts
  53. 39
      apps/remix-ide-e2e/src/commands/clickFunction.ts
  54. 4
      apps/remix-ide-e2e/src/commands/createContract.ts
  55. 86
      apps/remix-ide-e2e/src/commands/testConstantFunction.ts
  56. 104
      apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx
  57. 8
      apps/remix-ide-e2e/src/local-plugin/src/app/logger.tsx
  58. 2
      apps/remix-ide-e2e/src/tests/blockchain.test.ts
  59. 52
      apps/remix-ide-e2e/src/tests/contract_flattener.test.ts
  60. 132
      apps/remix-ide-e2e/src/tests/editor.test.ts
  61. 87
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  62. 88
      apps/remix-ide-e2e/src/tests/editorReferences.test.ts
  63. 27
      apps/remix-ide-e2e/src/tests/fileManager_api.test.ts
  64. 345
      apps/remix-ide-e2e/src/tests/proxy_oz_v4.test.ts
  65. 47
      apps/remix-ide-e2e/src/tests/proxy_oz_v5.test.ts
  66. 2
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  67. 64
      apps/remix-ide-e2e/src/tests/sol2uml.test.ts
  68. 6
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  69. 12
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  70. 53
      apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts
  71. 8
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  72. 16
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  73. 15
      apps/remix-ide-e2e/src/tests/url.test.ts
  74. 2
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  75. 186
      apps/remix-ide-e2e/src/types/index.d.ts
  76. 760
      apps/remix-ide-e2e/yarn.lock
  77. 4
      apps/remix-ide/background.js
  78. 94
      apps/remix-ide/ci/download_e2e_assets.js
  79. 12
      apps/remix-ide/ci/lint-targets.js
  80. 32
      apps/remix-ide/ci/splice_tests.js
  81. 2
      apps/remix-ide/project.json
  82. 4
      apps/remix-ide/release-process.md
  83. 214
      apps/remix-ide/src/app.js
  84. 26
      apps/remix-ide/src/app/components/hidden-panel.tsx
  85. 96
      apps/remix-ide/src/app/components/main-panel.tsx
  86. 276
      apps/remix-ide/src/app/components/preload.tsx
  87. 17
      apps/remix-ide/src/app/components/side-panel.tsx
  88. 70
      apps/remix-ide/src/app/components/vertical-icons.tsx
  89. 19
      apps/remix-ide/src/app/editor/editor.js
  90. 148
      apps/remix-ide/src/app/files/fileManager.ts
  91. 104
      apps/remix-ide/src/app/files/fileSystem.ts
  92. 308
      apps/remix-ide/src/app/files/filesystems/fileSystemUtility.ts
  93. 156
      apps/remix-ide/src/app/files/filesystems/indexedDB.ts
  94. 98
      apps/remix-ide/src/app/files/filesystems/localStorage.ts
  95. 14
      apps/remix-ide/src/app/panels/layout.ts
  96. 482
      apps/remix-ide/src/app/plugins/code-format.ts
  97. 64
      apps/remix-ide/src/app/plugins/code-format/index.ts
  98. 210
      apps/remix-ide/src/app/plugins/code-format/parser.ts
  99. 28
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  100. 118
      apps/remix-ide/src/app/plugins/file-decorator.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -5,7 +5,7 @@ parameters:
type: boolean
default: false
orbs:
browser-tools: circleci/browser-tools@1.4.1
browser-tools: circleci/browser-tools@1.4.4
win: circleci/windows@5.0
jobs:
build:
@ -286,8 +286,9 @@ jobs:
- browser-tools/install-browser-tools:
install-firefox: false
install-chrome: true
install-chromedriver: false
install-geckodriver: false
install-chromedriver: true
- install-chromedriver-custom-linux
- run: google-chrome --version
- run: chromedriver --version
- run: rm LICENSE.chromedriver 2> /dev/null || true
@ -310,6 +311,11 @@ jobs:
- run: ls -la ./dist/apps/remix-ide/assets/js
- run: yarn run selenium-install || yarn run selenium-install
- when:
condition:
equal: [ "chrome", << parameters.browser >> ]
steps:
- run: cp ~/bin/chromedriver /home/circleci/remix-project/node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run:
name: Start Selenium
command: yarn run selenium
@ -344,7 +350,8 @@ jobs:
install-firefox: false
install-chrome: true
install-geckodriver: false
install-chromedriver: true
install-chromedriver: false
- install-chromedriver-custom-linux
- run: google-chrome --version
- run: chromedriver --version
- run: rm LICENSE.chromedriver 2> /dev/null || true
@ -355,6 +362,7 @@ jobs:
- run: unzip ./persist/plugin-<< parameters.plugin >>.zip
- run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules
- run: yarn run selenium-install || yarn run selenium-install
- run: cp ~/bin/chromedriver /home/circleci/remix-project/node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run:
name: Start Selenium
command: yarn run selenium
@ -522,3 +530,35 @@ workflows:
only: remix_beta
# VS Code Extension Version: 1.5.1
commands:
install-chromedriver-custom-linux:
description: Custom script to install chromedriver with better version support for linux
steps:
- run:
name: install-chromedriver-custom-linux
command: |
google-chrome --version > version.txt
VERSION=$(grep -Eo '[0-9]+\.' < version.txt | head -1)
# CHROMEDRIVER_URL=$(curl -s 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json' | jq '.channels.Stable.downloads.chromedriver[] | select(.platform == "linux64") | .url' | tr -d '"')
CHROMEDRIVER_URL=$(curl -s 'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json' | jq --arg v "$VERSION" '.versions[] | select(.version | startswith($v)) | .downloads.chromedriver[] | select(.platform == "linux64") | .url' | tail -n1 | tr -d '"')
echo $CHROMEDRIVER_URL
ZIPFILEPATH="/tmp/chromedriver.zip"
echo "Downloading from $CHROMEDRIVER_URL"
curl -f --silent $CHROMEDRIVER_URL > "$ZIPFILEPATH"
BINFILEPATH="$HOME/bin/chromedriver-linux"
echo "Extracting to $BINFILEPATH"
unzip -p "$ZIPFILEPATH" chromedriver-linux64/chromedriver > "$BINFILEPATH"
echo Setting execute flag
chmod +x "$BINFILEPATH"
echo Updating symlink
ln -nfs "$BINFILEPATH" ~/bin/chromedriver
echo Removing ZIP file
rm "$ZIPFILEPATH"
rm version.txt
echo Done
chromedriver -v

@ -47,7 +47,13 @@
"eslint-disable-next-line no-empty": "off",
"no-empty": "off",
"jsx-a11y/anchor-is-valid": "off",
"@typescript-eslint/no-inferrable-types": "off"
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"react-hooks/exhaustive-deps": "off",
"array-callback-return": "off",
"prefer-spread": "off",
"indent": ["error", 2]
}
},
{
@ -58,7 +64,9 @@
"extends": [
"plugin:@nrwl/nx/javascript"
],
"rules": {}
"rules": {
"indent": ["error", 2]
}
}
],
"globals": {

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

@ -0,0 +1,13 @@
**/.yarn/*
# Ignore node_modules
./node_modules
# Ignore e2e files
./apps/remix-ide-e2e/*
# Ignore build artefacts
./dist
# Ignore all json files
**/*.json

@ -0,0 +1,7 @@
{
"tabWidth": 2,
"useTabs": false,
"printWidth": 180,
"semi": false,
"singleQuote": true
}

@ -93,3 +93,21 @@ But in some cases, the `id` prop may not be static. For example,
</h6>
```
You can't be sure there is a match key in locale file or not. So it will be better to provide a `defaultMessage` prop.
### Should I update the non-english locale json files?
You probably will have this question when you are updating the english locale json files.
Well, that depends.
If you update an old json file, then you don't need to update it in other languages, because crowdin will do it for you.
But if you add a new json file, then you have to add it in other languages, and import it in `index.js` of all languages. Because crowdin will not update `index.js`, you have to do it manually.
### How to contribute on translations?
Remix is using crowdin to manage translations. If you want to contribute on that, you can do it on crowdin. Check the link below.
https://crowdin.com/project/remix-translation
There are many languages, just get into your language, and you will see a folder named `Remix UI`, where you can do the translations.
Not only can you do the translations, you can also review it. If you agree or disagree with some translations, you can vote yes or no. If you vote no, you can comment to explain why you vote no, and give your translation.

@ -12,7 +12,7 @@
[![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md)
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green?logo=awesomelists)](https://github.com/ethereum/awesome-remix)
![GitHub](https://img.shields.io/github/license/ethereum/remix-project)
[![Gitter Chat](https://img.shields.io/badge/Gitter%20-chat-brightgreen?style=plastic&logo=gitter)](https://gitter.im/ethereum/remix)
[![Discord](https://img.shields.io/badge/join-discord-brightgreen.svg?style=flat&logo=discord)](https://discord.gg/q4vS2GVn)
[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=twitter&color=green)](https://twitter.com/ethereumremix)
</div>
@ -63,14 +63,13 @@ yarn global add nx
```bash
git clone https://github.com/ethereum/remix-project.git
```
* Build `remix-project`:
```bash
cd remix-project
yarn install
yarn run build:libs // Build remix libs
nx build
nx serve
```
* Build and Run `remix-project`:
1. Move to project directory: `cd remix-project`
2. Install dependencies: `yarn install` or simply run `yarn`
3. Build Remix libraries: `yarn run build:libs`
4. Build Remix project: `yarn build`
5. Build and run project server: `yarn serve`. Optionally, run `yarn serve:hot` to enable hot module reload for frontend updates.
Open `http://127.0.0.1:8080` in your browser to load Remix IDE locally.
@ -151,7 +150,7 @@ To run the Selenium tests via Nightwatch:
- Install Selenium for the first time: `yarn run selenium-install`
- Run a selenium server: `yarn run selenium`
- Build & Serve Remix: `nx serve`
- Build & Serve Remix: `yarn serve`
- Run all the end-to-end tests:
for Firefox: `yarn run nightwatch_local_firefox`, or
@ -283,7 +282,9 @@ parameters:
## Important Links
- Official website: https://remix-project.org
- Official documentation: https://remix-ide.readthedocs.io/en/latest/
- Curated list of Remix resources, tutorials etc.: https://github.com/ethereum/awesome-remix
- Curated list of Remix resources: https://github.com/ethereum/awesome-remix
- Medium: https://medium.com/remix-ide
- Twitter: https://twitter.com/ethereumremix
- Join Discord: https://discord.gg/q4vS2GVn

@ -0,0 +1,59 @@
{
"name": "circuit-compiler",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/circuit-compiler/src",
"projectType": "application",
"implicitDependencies": ["remixd"],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "development",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/circuit-compiler",
"index": "apps/circuit-compiler/src/index.html",
"baseHref": "./",
"main": "apps/circuit-compiler/src/main.tsx",
"polyfills": "apps/circuit-compiler/src/polyfills.ts",
"tsConfig": "apps/circuit-compiler/tsconfig.app.json",
"assets": ["apps/circuit-compiler/src/profile.json"],
"styles": ["apps/circuit-compiler/src/css/app.css"],
"scripts": [],
"webpackConfig": "apps/circuit-compiler/webpack.config.js"
},
"configurations": {
"development": {
},
"production": {
"fileReplacements": [
{
"replace": "apps/circuit-compiler/src/environments/environment.ts",
"with": "apps/circuit-compiler/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nrwl/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "circuit-compiler:build",
"hmr": true,
"baseHref": "/"
},
"configurations": {
"development": {
"buildTarget": "circuit-compiler:build:development",
"port": 2023
},
"production": {
"buildTarget": "circuit-compiler:build:production"
}
}
}
},
"tags": []
}

@ -0,0 +1,17 @@
import React, { useEffect } from 'react'
import { CircomPluginClient } from './services/circomPluginClient'
function App() {
useEffect(() => {
new CircomPluginClient()
}, [])
return (
<div className="App">
</div>
)
}
export default App

@ -0,0 +1,224 @@
import {PluginClient} from '@remixproject/plugin'
import {createClient} from '@remixproject/plugin-webview'
import EventManager from 'events'
import pathModule from 'path'
import {parse} from 'circom_wasm'
export class CircomPluginClient extends PluginClient {
public internalEvents: EventManager
constructor() {
super()
createClient(this)
this.internalEvents = new EventManager()
this.methods = ['init', 'parse']
this.onload()
}
init(): void {
console.log('initializing circom plugin...')
}
onActivation(): void {
// @ts-ignore
this.on('editor', 'contentChanged', (path: string, fileContent) => {
if (path.endsWith('.circom')) {
this.parse(path, fileContent)
}
})
}
async parse(path: string, fileContent: string): Promise<void> {
let buildFiles = {
[path]: fileContent
}
buildFiles = await this.resolveDependencies(path, fileContent, buildFiles)
const parsedOutput = parse(path, buildFiles)
try {
const result = JSON.parse(parsedOutput)
if (result.length === 0) {
// @ts-ignore
await this.call('editor', 'clearErrorMarkers', [path])
} else {
const markers = []
for (const report of result) {
for (const label in report.labels) {
if (report.labels[label].file_id === '0') {
// @ts-ignore
const startPosition: {lineNumber: number; column: number} =
await this.call(
'editor',
// @ts-ignore
'getPositionAt',
report.labels[label].range.start
)
// @ts-ignore
const endPosition: {lineNumber: number; column: number} =
await this.call(
'editor',
// @ts-ignore
'getPositionAt',
report.labels[label].range.end
)
markers.push({
message: report.message,
severity: report.type.toLowerCase(),
position: {
start: {
line: startPosition.lineNumber,
column: startPosition.column
},
end: {
line: endPosition.lineNumber,
column: endPosition.column
}
},
file: path
})
}
}
}
if (markers.length > 0) {
// @ts-ignore
await this.call('editor', 'addErrorMarker', markers)
} else {
// @ts-ignore
await this.call('editor', 'clearErrorMarkers', [path])
}
}
} catch (e) {
console.log(e)
}
}
async resolveDependencies(
filePath: string,
fileContent: string,
output = {},
depPath: string = '',
blackPath: string[] = []
): Promise<Record<string, string>> {
// extract all includes
const includes = (fileContent.match(/include ['"].*['"]/g) || []).map(
(include) => include.replace(/include ['"]/g, '').replace(/['"]/g, '')
)
await Promise.all(
includes.map(async (include) => {
// fix for endless recursive includes
if (blackPath.includes(include)) return
let dependencyContent = ''
let path = include
// @ts-ignore
const pathExists = await this.call('fileManager', 'exists', path)
if (pathExists) {
// fetch file content if include import (path) exists within same level as current file opened in editor
dependencyContent = await this.call('fileManager', 'readFile', path)
} else {
// if include import (path) does not exist, try to construct relative path using the original file path (current file opened in editor)
let relativePath = pathModule.resolve(
filePath.slice(0, filePath.lastIndexOf('/')),
include
)
if (relativePath.indexOf('/') === 0)
relativePath = relativePath.slice(1)
const relativePathExists = await this.call(
'fileManager',
// @ts-ignore
'exists',
relativePath
)
if (relativePathExists) {
// fetch file content if include import exists as a relative path
dependencyContent = await this.call(
'fileManager',
'readFile',
relativePath
)
} else {
if (depPath) {
// if depPath is provided, try to resolve include import from './deps' folder in remix
path = pathModule.resolve(
depPath.slice(0, depPath.lastIndexOf('/')),
include
)
if (path.indexOf('/') === 0) path = path.slice(1)
dependencyContent = await this.call(
'contentImport',
'resolveAndSave',
path,
null
)
} else {
if (include.startsWith('circomlib')) {
// try to resolve include import from github if it is a circomlib dependency
const splitInclude = include.split('/')
const version = splitInclude[1].match(/v[0-9]+.[0-9]+.[0-9]+/g)
if (version && version[0]) {
path = `https://raw.githubusercontent.com/iden3/circomlib/${
version[0]
}/circuits/${splitInclude.slice(2).join('/')}`
dependencyContent = await this.call(
'contentImport',
'resolveAndSave',
path,
null
)
} else {
path = `https://raw.githubusercontent.com/iden3/circomlib/master/circuits/${splitInclude
.slice(1)
.join('/')}`
dependencyContent = await this.call(
'contentImport',
'resolveAndSave',
path,
null
)
}
} else {
// If all import cases are not true, use the default import to try fetching from node_modules and unpkg
dependencyContent = await this.call(
'contentImport',
'resolveAndSave',
path,
null
)
}
}
}
}
// extract all includes from the dependency content
const dependencyIncludes = (
dependencyContent.match(/include ['"].*['"]/g) || []
).map((include) =>
include.replace(/include ['"]/g, '').replace(/['"]/g, '')
)
blackPath.push(include)
// recursively resolve all dependencies of the dependency
if (dependencyIncludes.length > 0) {
await this.resolveDependencies(
filePath,
dependencyContent,
output,
path,
blackPath
)
output[include] = dependencyContent
} else {
output[include] = dependencyContent
}
})
)
return output
}
}

@ -0,0 +1,11 @@
pragma circom 2.0.0;
template Multiplier2() {
signal input a;
signal input b;
signal output c;
c <== a*b;
}
component main = Multiplier2();

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Circuit - Compiler</title>
<base href="./" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<div id="root"></div>
</body>
</html>

@ -0,0 +1,8 @@
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,17 @@
{
"name": "circuit-compiler",
"kind": "provider",
"displayName": "Circuit Compiler",
"events": [],
"version": "2.0.0",
"methods": ["init", "parse"],
"canActivate": [],
"url": "",
"description": "Enables circuit compilation and computing a witness for ZK proofs",
"icon": "https://docs.circom.io/assets/images/favicon.png",
"location": "hiddenPanel",
"documentation": "",
"repo": "https://github.com/ethereum/remix-project/tree/master/apps/circuit-compiler",
"maintainedBy": "Remix",
"authorContact": ""
}

@ -0,0 +1,24 @@
{
"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": [
"jest.config.ts",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

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

@ -0,0 +1,92 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"path": require.resolve("path-browserify"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"constants": require.resolve("constants-browserify"),
"os": false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"),
"fs": false,
"module": false,
"tls": false,
"net": false,
"readline": false,
"child_process": false,
"buffer": require.resolve("buffer/"),
"vm": require.resolve('vm-browserify'),
}
// add externals
config.externals = {
...config.externals,
solc: 'solc',
}
// add public path
config.output.publicPath = './'
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
})
)
// set the define plugin to load the WALLET_CONNECT_PROJECT_ID
config.plugins.push(
new webpack.DefinePlugin({
WALLET_CONNECT_PROJECT_ID: JSON.stringify(process.env.WALLET_CONNECT_PROJECT_ID),
})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer
config.optimization.minimizer = [
new TerserPlugin({
parallel: true,
terserOptions: {
ecma: 2015,
compress: false,
mangle: false,
format: {
comments: false,
},
},
extractComments: false,
}),
new CssMinimizerPlugin(),
];
config.watchOptions = {
ignored: /node_modules/
}
config.experiments.syncWebAssembly = true
return config;
});

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

@ -28,4 +28,3 @@ export class DebuggerClientApi extends DebuggerApiMixin(PluginClient) {
onStartDebugging: (debuggerBackend: any) => void // called when debug starts
onStopDebugging: () => void // called when debug stops
}

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

@ -1,8 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
@ -12,56 +11,52 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"path": require.resolve("path-browserify"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"constants": require.resolve("constants-browserify"),
"os": false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"),
"fs": false,
"module": false,
"tls": false,
"net": false,
"readline": false,
"child_process": false,
"buffer": require.resolve("buffer/"),
"vm": require.resolve('vm-browserify'),
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
path: require.resolve('path-browserify'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
constants: require.resolve('constants-browserify'),
os: false, //require.resolve("os-browserify/browser"),
timers: false, // require.resolve("timers-browserify"),
zlib: require.resolve('browserify-zlib'),
fs: false,
module: false,
tls: false,
net: false,
readline: false,
child_process: false,
buffer: require.resolve('buffer/'),
vm: require.resolve('vm-browserify')
}
// add externals
config.externals = {
...config.externals,
solc: 'solc',
solc: 'solc'
}
// add public path
config.output.publicPath = '/'
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
process: 'process/browser'
})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
use: ['source-map-loader'],
enforce: 'pre'
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer
config.optimization.minimizer = [
new TerserPlugin({
@ -71,17 +66,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false,
mangle: false,
format: {
comments: false,
},
comments: false
}
},
extractComments: false,
extractComments: false
}),
new CssMinimizerPlugin(),
];
new CssMinimizerPlugin()
]
config.watchOptions = {
ignored: /node_modules/
}
return config;
});
return config
})

@ -4,13 +4,13 @@ import './App.css'
import { DocGenClient } from './docgen-client'
import { Build } from './docgen/site'
export const client = new DocGenClient()
export const client = new DocGenClient()
const App = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [themeType, setThemeType] = useState<string>('dark');
const [hasBuild, setHasBuild] = useState<boolean>(false);
const [fileName, setFileName] = useState<string>('');
const [themeType, setThemeType] = useState<string>('dark')
const [hasBuild, setHasBuild] = useState<boolean>(false)
const [fileName, setFileName] = useState<string>('')
useEffect(() => {
const watchThemeSwitch = async () => {
@ -21,19 +21,27 @@ const App = () => {
setHasBuild(true)
setFileName(fileName)
})
client.eventEmitter.on('docsGenerated', (docs: string[]) => {
})
client.eventEmitter.on('docsGenerated', (docs: string[]) => {})
}
watchThemeSwitch()
}, [])
return (
<div className="p-3">
<h5 className="h-5 mb-3">Compile a Solidity contract and generate its documentation as Markdown. (Right-click on a contract in the File Explorer and select "Generate Docs" from the context menu.).</h5>
{fileName && <div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex">
<h6>File: {fileName}</h6>
</div>}
{hasBuild && <button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>Generate Docs</button>}
<h5 className="h-5 mb-3">
Compile a Solidity contract and generate its documentation as Markdown. (Right-click on a contract in the File
Explorer and select "Generate Docs" from the context menu.).
</h5>
{fileName && (
<div className="border-bottom border-top px-2 py-3 justify-center align-items-center d-flex">
<h6>File: {fileName}</h6>
</div>
)}
{hasBuild && (
<button className="btn btn-primary btn-block mt-4" onClick={() => client.generateDocs()}>
Generate Docs
</button>
)}
</div>
)
}

@ -1,4 +1,4 @@
import { useState } from "react";
import { useState } from 'react'
export function useLocalStorage(key: string, initialValue: any) {
// State to store our value
@ -6,32 +6,31 @@ export function useLocalStorage(key: string, initialValue: any) {
const [storedValue, setStoredValue] = useState(() => {
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
const item = window.localStorage.getItem(key)
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
return item ? JSON.parse(item) : initialValue
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
console.log(error)
return initialValue
}
});
})
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value: any) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
const valueToStore = value instanceof Function ? value(storedValue) : value
// Save state
setStoredValue(valueToStore);
setStoredValue(valueToStore)
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore));
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
console.log(error)
}
};
}
return [storedValue, setValue];
return [storedValue, setValue]
}

@ -1,31 +1,28 @@
import React from "react";
import React from 'react'
export const ErrorView: React.FC = () => {
return (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
style={{ paddingBottom: "2em" }}
style={{ paddingBottom: '2em' }}
width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page"
/>
<h5>Sorry, something unexpected happened. </h5>
<h5>
Please raise an issue:{" "}
<a
style={{ color: "red" }}
href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues"
>
Please raise an issue:{' '}
<a style={{ color: 'red' }} href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues">
Here
</a>
</h5>
</div>
);
};
)
}

@ -1,11 +1,11 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./app/App";
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app/App'
// import { Routes } from "./routes";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
document.getElementById('root'),
)

@ -1,19 +1,19 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => {
module.exports = composePlugins(withNx(), withReact(), config => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
"path": require.resolve("path-browserify"),
"fs": false,
path: require.resolve('path-browserify'),
fs: false,
}
// add externals
@ -24,10 +24,9 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
config.module.rules.push({
test: /\.hbs$/,
type: 'asset/source'
type: 'asset/source',
})
// add public path
config.output.publicPath = '/'
@ -38,16 +37,14 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
url: ['url', 'URL'],
process: 'process/browser',
}),
new webpack.DefinePlugin({
}),
new webpack.DefinePlugin({}),
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
use: ['source-map-loader'],
enforce: 'pre',
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -67,7 +64,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
extractComments: false,
}),
new CssMinimizerPlugin(),
];
]
return config;
return config
})

@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react"
import { DocViewer } from "./docviewer"
import React, {useEffect, useState} from 'react'
import {DocViewer} from './docviewer'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
@ -11,12 +11,11 @@ export default function App() {
client.eventEmitter.on('contentsReady', (fileContents: string) => {
setContents(fileContents)
})
}, [])
return (
<>
<div className="m-5 p-2">
<ReactMarkdown children={contents} remarkPlugins={[remarkGfm]}/>
<ReactMarkdown children={contents} remarkPlugins={[remarkGfm]} />
</div>
</>
)

@ -6,5 +6,5 @@ ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
document.getElementById('root')
)

@ -1,8 +1,8 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react')
const {composePlugins, withNx} = require('@nrwl/webpack')
const {withReact} = require('@nrwl/react')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => {
@ -12,7 +12,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// add externals
config.externals = {
...config.externals,
solc: 'solc',
solc: 'solc'
}
// add public path
@ -23,18 +23,16 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
}),
new webpack.DefinePlugin({
process: 'process/browser'
}),
new webpack.DefinePlugin({})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
use: ['source-map-loader'],
enforce: 'pre'
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
@ -48,13 +46,13 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
compress: false,
mangle: false,
format: {
comments: false,
},
comments: false
}
},
extractComments: false,
extractComments: false
}),
new CssMinimizerPlugin(),
];
new CssMinimizerPlugin()
]
return config;
return config
})

@ -1,24 +1,24 @@
import React from "react"
import { PluginClient } from "@remixproject/plugin"
import React from 'react'
import {PluginClient} from '@remixproject/plugin'
import { Receipt, ThemeType } from "./types"
import {Receipt, ThemeType} from './types'
export const AppContext = React.createContext({
apiKey: "",
apiKey: '',
setAPIKey: (value: string) => {
console.log("Set API Key from Context")
console.log('Set API Key from Context')
},
clientInstance: {} as PluginClient,
receipts: [] as Receipt[],
setReceipts: (receipts: Receipt[]) => {
console.log("Calling Set Receipts")
console.log('Calling Set Receipts')
},
contracts: [] as string[],
setContracts: (contracts: string[]) => {
console.log("Calling Set Contract Names")
console.log('Calling Set Contract Names')
},
themeType: "dark" as ThemeType,
themeType: 'dark' as ThemeType,
setThemeType: (themeType: ThemeType) => {
console.log("Calling Set Theme Type")
},
console.log('Calling Set Theme Type')
}
})

@ -1,40 +1,59 @@
import { PluginClient } from '@remixproject/plugin';
import { verify, EtherScanReturn } from './utils/verify';
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from './utils';
import {PluginClient} from '@remixproject/plugin'
import {verify, EtherScanReturn} from './utils/verify'
import {getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus} from './utils'
export class RemixClient extends PluginClient {
loaded() {
return this.onload()
}
loaded() {
return this.onload()
}
async verify (apiKey: string, contractAddress: string, contractArguments: string, contractName: string, compilationResultParam: any, chainRef?: number | string, isProxyContract?: boolean, expectedImplAddress?: string) {
const result = await verify(apiKey, contractAddress, contractArguments, contractName, compilationResultParam, chainRef, isProxyContract, expectedImplAddress, this,
(value: EtherScanReturn) => {}, (value: string) => {})
return result
}
async verify(
apiKey: string,
contractAddress: string,
contractArguments: string,
contractName: string,
compilationResultParam: any,
chainRef?: number | string,
isProxyContract?: boolean,
expectedImplAddress?: string
) {
const result = await verify(
apiKey,
contractAddress,
contractArguments,
contractName,
compilationResultParam,
chainRef,
isProxyContract,
expectedImplAddress,
this,
(value: EtherScanReturn) => {},
(value: string) => {}
)
return result
}
async receiptStatus (receiptGuid: string, apiKey: string, isProxyContract: boolean) {
try {
const { network, networkId } = await getNetworkName(this)
if (network === "vm") {
throw new Error("Cannot check the receipt status in the selected network")
}
const etherscanApi = getEtherScanApi(networkId)
let receiptStatus
async receiptStatus(receiptGuid: string, apiKey: string, isProxyContract: boolean) {
try {
const {network, networkId} = await getNetworkName(this)
if (network === 'vm') {
throw new Error('Cannot check the receipt status in the selected network')
}
const etherscanApi = getEtherScanApi(networkId)
let receiptStatus
if (isProxyContract) receiptStatus = await getProxyContractReceiptStatus(receiptGuid, apiKey, etherscanApi)
else receiptStatus = await getReceiptStatus(receiptGuid, apiKey, etherscanApi)
return {
message: receiptStatus.result,
succeed: receiptStatus.status === '0' ? false : true
}
} catch (e: any){
return {
status: 'error',
message: e.message,
succeed: false
}
if (isProxyContract) receiptStatus = await getProxyContractReceiptStatus(receiptGuid, apiKey, etherscanApi)
else receiptStatus = await getReceiptStatus(receiptGuid, apiKey, etherscanApi)
return {
message: receiptStatus.result,
succeed: receiptStatus.status === '0' ? false : true
}
} catch (e: any) {
return {
status: 'error',
message: e.message,
succeed: false
}
}
}
}

@ -1,22 +1,19 @@
import React, { useState, useEffect, useRef } from "react"
import React, {useState, useEffect, useRef} from 'react'
import {
CompilationFileSources,
CompilationResult,
} from "@remixproject/plugin-api"
import {CompilationFileSources, CompilationResult} from '@remixproject/plugin-api'
import { RemixClient } from "./RemixPlugin";
import { createClient } from "@remixproject/plugin-webview";
import {RemixClient} from './RemixPlugin'
import {createClient} from '@remixproject/plugin-webview'
import { AppContext } from "./AppContext"
import { DisplayRoutes } from "./routes"
import {AppContext} from './AppContext'
import {DisplayRoutes} from './routes'
import { useLocalStorage } from "./hooks/useLocalStorage"
import {useLocalStorage} from './hooks/useLocalStorage'
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from "./utils"
import { Receipt, ThemeType } from "./types"
import {getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus} from './utils'
import {Receipt, ThemeType} from './types'
import "./App.css"
import './App.css'
export const getNewContractNames = (compilationResult: CompilationResult) => {
const compiledContracts = compilationResult.contracts
@ -31,11 +28,11 @@ export const getNewContractNames = (compilationResult: CompilationResult) => {
}
const App = () => {
const [apiKey, setAPIKey] = useLocalStorage("apiKey", "")
const [apiKey, setAPIKey] = useLocalStorage('apiKey', '')
const [clientInstance, setClientInstance] = useState(undefined as any)
const [receipts, setReceipts] = useLocalStorage("receipts", [])
const [receipts, setReceipts] = useLocalStorage('receipts', [])
const [contracts, setContracts] = useState([] as string[])
const [themeType, setThemeType] = useState("dark" as ThemeType)
const [themeType, setThemeType] = useState('dark' as ThemeType)
const timer = useRef(null)
const clientInstanceRef = useRef(clientInstance)
@ -49,26 +46,15 @@ const App = () => {
const loadClient = async () => {
await client.onload()
setClientInstance(client)
client.on("solidity",
"compilationFinished",
(
fileName: string,
source: CompilationFileSources,
languageVersion: string,
data: CompilationResult
) => {
const newContractsNames = getNewContractNames(data)
const newContractsToSave: string[] = [
...contractsRef.current,
...newContractsNames,
]
const uniqueContracts: string[] = [...new Set(newContractsToSave)]
setContracts(uniqueContracts)
}
)
client.on('solidity', 'compilationFinished', (fileName: string, source: CompilationFileSources, languageVersion: string, data: CompilationResult) => {
const newContractsNames = getNewContractNames(data)
const newContractsToSave: string[] = [...contractsRef.current, ...newContractsNames]
const uniqueContracts: string[] = [...new Set(newContractsToSave)]
setContracts(uniqueContracts)
})
//const currentTheme = await client.call("theme", "currentTheme")
//setThemeType(currentTheme.quality)
@ -82,7 +68,7 @@ const App = () => {
useEffect(() => {
let receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => {
return item.status === "Pending in queue" || item.status === "Max rate limit reached"
return item.status === 'Pending in queue' || item.status === 'Max rate limit reached'
})
if (receiptsNotVerified.length > 0) {
@ -91,41 +77,31 @@ const App = () => {
timer.current = null
}
timer.current = setInterval(async () => {
const { network, networkId } = await getNetworkName(clientInstanceRef.current)
const {network, networkId} = await getNetworkName(clientInstanceRef.current)
if (!clientInstanceRef.current) {
return
}
if (network === "vm") {
if (network === 'vm') {
return
}
let newReceipts = receipts
for (const item of receiptsNotVerified) {
await new Promise(r => setTimeout(r, 500)) // avoid api rate limit exceed.
await new Promise((r) => setTimeout(r, 500)) // avoid api rate limit exceed.
let status
if (item.isProxyContract) {
status = await getProxyContractReceiptStatus(
item.guid,
apiKey,
getEtherScanApi(networkId)
)
status = await getProxyContractReceiptStatus(item.guid, apiKey, getEtherScanApi(networkId))
if (status.status === '1') {
status.message = status.result
status.result = 'Successfully Updated'
}
} else
status = await getReceiptStatus(
item.guid,
apiKey,
getEtherScanApi(networkId)
)
if (status.result === "Pass - Verified" || status.result === "Already Verified" ||
status.result === "Successfully Updated") {
} else status = await getReceiptStatus(item.guid, apiKey, getEtherScanApi(networkId))
if (status.result === 'Pass - Verified' || status.result === 'Already Verified' || status.result === 'Successfully Updated') {
newReceipts = newReceipts.map((currentReceipt: Receipt) => {
if (currentReceipt.guid === item.guid) {
let res = {
const res = {
...currentReceipt,
status: status.result,
status: status.result
}
if (currentReceipt.isProxyContract) res.message = status.message
return res
@ -135,7 +111,7 @@ const App = () => {
}
}
receiptsNotVerified = newReceipts.filter((item: Receipt) => {
return item.status === "Pending in queue" || item.status === "Max rate limit reached"
return item.status === 'Pending in queue' || item.status === 'Max rate limit reached'
})
if (timer.current && receiptsNotVerified.length === 0) {
clearInterval(timer.current)
@ -157,7 +133,7 @@ const App = () => {
contracts,
setContracts,
themeType,
setThemeType,
setThemeType
}}
>
<DisplayRoutes />

@ -1,8 +1,8 @@
import React from "react"
import React from 'react'
import { NavLink } from "react-router-dom"
import { CustomTooltip } from '@remix-ui/helper'
import { AppContext } from "../AppContext"
import {NavLink} from 'react-router-dom'
import {CustomTooltip} from '@remix-ui/helper'
import {AppContext} from '../AppContext'
interface Props {
title?: string
@ -13,79 +13,67 @@ interface IconProps {
from: string
}
const HomeIcon: React.FC<IconProps> = ({ from }: IconProps) => {
const HomeIcon: React.FC<IconProps> = ({from}: IconProps) => {
return (
<NavLink
data-id="home"
to={{
pathname: "/"
pathname: '/'
}}
className={({ isActive }) => isActive ? "btn p-0 m-0" : "btn text-dark p-0 m-0"}
state={ from }
className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')}
style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={from}
>
<CustomTooltip
tooltipText='Home'
tooltipId='etherscan-nav-home'
placement='bottom'
>
<CustomTooltip tooltipText="Home" tooltipId="etherscan-nav-home" placement="bottom">
<i className="fas fa-home"></i>
</CustomTooltip>
</NavLink>
)
}
const ReceiptsIcon: React.FC<IconProps> = ({ from }: IconProps) => {
const ReceiptsIcon: React.FC<IconProps> = ({from}: IconProps) => {
return (
<NavLink
data-id="receipts"
to={{
pathname: "/receipts"
pathname: '/receipts'
}}
className={({ isActive }) => isActive ? "btn p-0 m-0 mx-2" : "btn text-dark p-0 m-0 mx-2"}
state={ from }
className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')}
style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={from}
>
<CustomTooltip
tooltipText='Receipts'
tooltipId='etherscan-nav-receipts'
placement='bottom'
>
<CustomTooltip tooltipText="Receipts" tooltipId="etherscan-nav-receipts" placement="bottom">
<i className="fas fa-receipt"></i>
</CustomTooltip>
</NavLink>
)
}
const SettingsIcon: React.FC<IconProps> = ({ from }: IconProps) => {
const SettingsIcon: React.FC<IconProps> = ({from}: IconProps) => {
return (
<NavLink
data-id="settings"
to={{
pathname: "/settings"
pathname: '/settings'
}}
className={({ isActive }) => isActive ? "btn p-0 m-0" : "btn text-dark p-0 m-0"}
state= {from}
className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')}
style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})}
state={from}
>
<CustomTooltip
tooltipText='Settings'
tooltipId='etherscan-nav-settings'
placement='bottom'
>
<CustomTooltip tooltipText="Settings" tooltipId="etherscan-nav-settings" placement="bottom">
<i className="fas fa-cog"></i>
</CustomTooltip>
</NavLink>
)
}
export const HeaderWithSettings: React.FC<Props> = ({
title = "",
from,
}) => {
export const HeaderWithSettings: React.FC<Props> = ({title = '', from}) => {
return (
<AppContext.Consumer>
{() => (
<div className="d-flex justify-content-between">
<h6 className="d-inline">{title}</h6>
<div>
<div className="nav">
<HomeIcon from={from} />
<ReceiptsIcon from={from} />
<SettingsIcon from={from} />

@ -1,5 +1,5 @@
import React from "react"
import { CustomTooltip } from '@remix-ui/helper'
import React from 'react'
import {CustomTooltip} from '@remix-ui/helper'
interface Props {
text: string
@ -8,35 +8,21 @@ interface Props {
disable?: boolean
}
export const SubmitButton: React.FC<Props> = ({
text,
dataId,
isSubmitting = false,
disable = true
}) => {
export const SubmitButton: React.FC<Props> = ({text, dataId, isSubmitting = false, disable = true}) => {
return (
<div>
<button
data-id={dataId}
type="submit"
className="btn btn-primary btn-block p-1 text-decoration-none"
disabled={disable}
>
<button data-id={dataId} type="submit" className="btn btn-primary btn-block p-1 text-decoration-none" disabled={disable}>
<CustomTooltip
tooltipText={disable ? "Fill in the valid value(s) and select a supported network" : "Click to proceed"}
tooltipId={'etherscan-submit-button-'+ dataId}
tooltipText={disable ? 'Fill in the valid value(s) and select a supported network' : 'Click to proceed'}
tooltipId={'etherscan-submit-button-' + dataId}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
placement='bottom'
placement="bottom"
>
<div>
{!isSubmitting && text}
{isSubmitting && (
<div>
<span
className="spinner-border spinner-border-sm mr-1"
role="status"
aria-hidden="true"
/>
<span className="spinner-border spinner-border-sm mr-1" role="status" aria-hidden="true" />
Verifying... Please wait
</div>
)}

@ -1,4 +1,4 @@
import { useState } from "react"
import {useState} from 'react'
export function useLocalStorage(key: string, initialValue: any) {
// State to store our value
@ -21,8 +21,7 @@ export function useLocalStorage(key: string, initialValue: any) {
const setValue = (value: any) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value
const valueToStore = value instanceof Function ? value(storedValue) : value
// Save state
setStoredValue(valueToStore)
// Save to local storage

@ -1,17 +1,13 @@
import React, { PropsWithChildren } from "react"
import React, {PropsWithChildren} from 'react'
import { HeaderWithSettings } from "../components"
import {HeaderWithSettings} from '../components'
interface Props {
from: string
title?: string
}
export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({
children,
from,
title
}) => {
export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({children, from, title}) => {
return (
<div>
<HeaderWithSettings from={from} title={title} />

@ -1,13 +1,8 @@
import React from "react"
import {
HashRouter as Router,
Route,
Routes,
RouteProps,
} from "react-router-dom"
import React from 'react'
import {HashRouter as Router, Route, Routes, RouteProps} from 'react-router-dom'
import { ErrorView, HomeView, ReceiptsView, CaptureKeyView } from "./views"
import { DefaultLayout } from "./layouts"
import {ErrorView, HomeView, ReceiptsView, CaptureKeyView} from './views'
import {DefaultLayout} from './layouts'
interface Props extends RouteProps {
component: any // TODO: new (props: any) => React.Component
@ -25,8 +20,7 @@ export const DisplayRoutes = () => (
</DefaultLayout>
}
/>
<Route path="/error"
element={<ErrorView />} />
<Route path="/error" element={<ErrorView />} />
<Route
path="/receipts"
element={

@ -1,36 +1,38 @@
export const scanAPIurls = {
// all mainnet
1: "https://api.etherscan.io/api",
56: "https://api.bscscan.com/api",
137: "https://api.polygonscan.com/api",
250: "https://api.ftmscan.com/api",
42161: "https://api.arbiscan.io/api",
43114: "https://api.snowtrace.io/api",
1285: "https://api-moonriver.moonscan.io/api",
1284: "https://api-moonbeam.moonscan.io/api",
25: "https://api.cronoscan.com/api",
199: "https://api.bttcscan.com/api",
10: "https://api-optimistic.etherscan.io/api",
42220: "https://api.celoscan.io/api",
288: "https://api.bobascan.com/api",
100: "https://api.gnosisscan.io/api",
1101: "https://api-zkevm.polygonscan.com/api",
// all mainnet
1: 'https://api.etherscan.io/api',
56: 'https://api.bscscan.com/api',
137: 'https://api.polygonscan.com/api',
250: 'https://api.ftmscan.com/api',
42161: 'https://api.arbiscan.io/api',
43114: 'https://api.snowtrace.io/api',
1285: 'https://api-moonriver.moonscan.io/api',
1284: 'https://api-moonbeam.moonscan.io/api',
25: 'https://api.cronoscan.com/api',
199: 'https://api.bttcscan.com/api',
10: 'https://api-optimistic.etherscan.io/api',
42220: 'https://api.celoscan.io/api',
288: 'https://api.bobascan.com/api',
100: 'https://api.gnosisscan.io/api',
1101: 'https://api-zkevm.polygonscan.com/api',
59144: 'https://api.lineascan.build/api',
// all testnet
5: "https://api-goerli.etherscan.io/api",
11155111: "https://api-sepolia.etherscan.io/api",
97: "https://api-testnet.bscscan.com/api",
80001: "https://api-testnet.polygonscan.com/api",
4002: "https://api-testnet.ftmscan.com/api",
421611: "https://api-testnet.arbiscan.io/api",
42170: "https://api-nova.arbiscan.io/api",
43113: "https://api-testnet.snowtrace.io/api",
1287: "https://api-moonbase.moonscan.io/api",
338: "https://api-testnet.cronoscan.com/api",
1028: "https://api-testnet.bttcscan.com/api",
420: "https://api-goerli-optimistic.etherscan.io/api",
44787: "https://api-alfajores.celoscan.io/api",
2888: "https://api-testnet.bobascan.com/api",
84531: "https://api-goerli.basescan.org/api",
1442: "https://api-testnet-zkevm.polygonscan.com/api"
// all testnet
5: 'https://api-goerli.etherscan.io/api',
11155111: 'https://api-sepolia.etherscan.io/api',
97: 'https://api-testnet.bscscan.com/api',
80001: 'https://api-testnet.polygonscan.com/api',
4002: 'https://api-testnet.ftmscan.com/api',
421611: 'https://api-testnet.arbiscan.io/api',
42170: 'https://api-nova.arbiscan.io/api',
43113: 'https://api-testnet.snowtrace.io/api',
1287: 'https://api-moonbase.moonscan.io/api',
338: 'https://api-testnet.cronoscan.com/api',
1028: 'https://api-testnet.bttcscan.com/api',
420: 'https://api-goerli-optimistic.etherscan.io/api',
44787: 'https://api-alfajores.celoscan.io/api',
2888: 'https://api-testnet.bobascan.com/api',
84531: 'https://api-goerli.basescan.org/api',
1442: 'https://api-testnet-zkevm.polygonscan.com/api',
59140: 'https://api-testnet.lineascan.build/api',
}

@ -133,7 +133,7 @@ export const verify = async (
const returnValue = {
guid: result,
status: receiptStatus.result,
message: `Verification process started correctly. Receipt GUID ${result}`,
message: `Verification request submitted successfully. Use this receipt GUID ${result} to track the status of your submission`,
succeed: true,
isProxyContract
}

@ -1,67 +1,63 @@
import React from "react"
import React, {useState} from 'react'
import { Formik, ErrorMessage, Field } from "formik"
import { useNavigate, useLocation } from "react-router-dom"
import {Formik, ErrorMessage, Field} from 'formik'
import {useNavigate, useLocation} from 'react-router-dom'
import { AppContext } from "../AppContext"
import { SubmitButton } from "../components"
import {AppContext} from '../AppContext'
import {SubmitButton} from '../components'
export const CaptureKeyView: React.FC = () => {
const location = useLocation()
const navigate = useNavigate()
const [msg, setMsg] = useState('')
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, setAPIKey }) => {
if (!apiKey && clientInstance && clientInstance.call) {
clientInstance.call('sidePanel' as any, 'currentFocus').then((current) => {
if (current === 'etherscan') clientInstance.call('notification' as any, 'toast', 'Please add API key to continue')
})
}
return <Formik
initialValues={{ apiKey }}
validate={(values) => {
const errors = {} as any
if (!values.apiKey) {
errors.apiKey = "Required"
}
return errors
}}
onSubmit={(values) => {
const apiKey = values.apiKey
if (apiKey.length === 34) {
setAPIKey(values.apiKey)
clientInstance.call('notification' as any, 'toast', 'API key saved successfully!!!')
navigate((location && location.state ? location.state : '/'))
} else clientInstance.call('notification' as any, 'toast', 'API key should be 34 characters long')
}}
>
{({ errors, touched, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div className="form-group mb-2">
<label htmlFor="apikey">API Key</label>
<Field
className={
errors.apiKey && touched.apiKey
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="password"
name="apiKey"
placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
/>
<ErrorMessage
className="invalid-feedback"
name="apiKey"
component="div"
/>
</div>
{({apiKey, clientInstance, setAPIKey}) => {
if (!apiKey) setMsg('Please provide a 34-character API key to continue')
return (
<div>
<Formik
initialValues={{apiKey}}
validate={(values) => {
const errors = {} as any
if (!values.apiKey) {
errors.apiKey = 'Required'
} else if (values.apiKey.length !== 34) {
errors.apiKey = 'API key should be 34 characters long'
}
return errors
}}
onSubmit={(values) => {
const apiKey = values.apiKey
if (apiKey.length === 34) {
setAPIKey(values.apiKey)
navigate(location && location.state ? location.state : '/')
}
}}
>
{({errors, touched, handleSubmit}) => (
<form onSubmit={handleSubmit}>
<div className="form-group mb-2">
<label htmlFor="apikey">API Key</label>
<Field
className={errors.apiKey && touched.apiKey ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'}
type="password"
name="apiKey"
placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
/>
<ErrorMessage className="invalid-feedback" name="apiKey" component="div" />
</div>
<div>
<SubmitButton text="Save" dataId="save-api-key" disable={false} />
</div>
</form>
)}
</Formik>
<div>
<SubmitButton text="Save" dataId="save-api-key" disable={errors && errors.apiKey ? true : false} />
</div>
</form>
)}
</Formik>
<div data-id="api-key-result" className="text-primary mt-4 text-center" style={{fontSize: '0.8em'}} dangerouslySetInnerHTML={{__html: msg}} />
</div>
)
}}
</AppContext.Consumer>
)

@ -1,21 +1,13 @@
import React from "react"
import React from 'react'
export const ErrorView: React.FC = () => {
return (
<div className="d-flex w-100 flex-column align-items-center">
<img
className="pb-4"
width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page"
/>
<img className="pb-4" width="250" src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png" alt="Error page" />
<h5>Sorry, something unexpected happened.</h5>
<h5>
Please raise an issue:{" "}
<a
className="text-danger"
href="https://github.com/ethereum/remix-project/issues"
>
Please raise an issue:{' '}
<a className="text-danger" href="https://github.com/ethereum/remix-project/issues">
Here
</a>
</h5>

@ -1,25 +1,20 @@
import React from "react"
import React from 'react'
import { Navigate } from "react-router-dom"
import {Navigate} from 'react-router-dom'
import { AppContext } from "../AppContext"
import { Receipt } from "../types"
import {AppContext} from '../AppContext'
import {Receipt} from '../types'
import { VerifyView } from "./VerifyView"
import {VerifyView} from './VerifyView'
export const HomeView: React.FC = () => {
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, setReceipts, receipts, contracts }) => {
if (!apiKey && clientInstance && clientInstance.call) {
clientInstance.call('sidePanel' as any, 'currentFocus').then((current) => {
if (current === 'etherscan') clientInstance.call('notification' as any, 'toast', 'Please add API key to continue')
})
}
{({apiKey, clientInstance, setReceipts, receipts, contracts}) => {
return !apiKey ? (
<Navigate
to={{
pathname: "/settings"
pathname: '/settings'
}}
/>
) : (
@ -33,8 +28,7 @@ export const HomeView: React.FC = () => {
}}
/>
)
}
}
}}
</AppContext.Consumer>
)
}

@ -1,13 +1,13 @@
import React, { useState } from "react"
import React, {useState} from 'react'
import { Formik, ErrorMessage, Field } from "formik"
import { getEtherScanApi, getNetworkName, getReceiptStatus, getProxyContractReceiptStatus } from "../utils"
import { Receipt } from "../types"
import { AppContext } from "../AppContext"
import { SubmitButton } from "../components"
import { Navigate } from "react-router-dom"
import { Button } from "react-bootstrap"
import { CustomTooltip } from '@remix-ui/helper'
import {Formik, ErrorMessage, Field} from 'formik'
import {getEtherScanApi, getNetworkName, getReceiptStatus, getProxyContractReceiptStatus} from '../utils'
import {Receipt} from '../types'
import {AppContext} from '../AppContext'
import {SubmitButton} from '../components'
import {Navigate} from 'react-router-dom'
import {Button} from 'react-bootstrap'
import {CustomTooltip} from '@remix-ui/helper'
interface FormValues {
receiptGuid: string
@ -17,38 +17,25 @@ export const ReceiptsView: React.FC = () => {
const [results, setResults] = useState({succeed: false, message: ''})
const [isProxyContractReceipt, setIsProxyContractReceipt] = useState(false)
const onGetReceiptStatus = async (
values: FormValues,
clientInstance: any,
apiKey: string
) => {
const onGetReceiptStatus = async (values: FormValues, clientInstance: any, apiKey: string) => {
try {
const { network, networkId } = await getNetworkName(clientInstance)
if (network === "vm") {
const {network, networkId} = await getNetworkName(clientInstance)
if (network === 'vm') {
setResults({
succeed: false,
message: "Cannot verify in the selected network"
message: 'Cannot verify in the selected network'
})
return
}
const etherscanApi = getEtherScanApi(networkId)
let result
if (isProxyContractReceipt) {
result = await getProxyContractReceiptStatus(
values.receiptGuid,
apiKey,
etherscanApi
)
result = await getProxyContractReceiptStatus(values.receiptGuid, apiKey, etherscanApi)
if (result.status === '1') {
result.message = result.result
result.result = 'Successfully Updated'
}
} else
result = await getReceiptStatus(
values.receiptGuid,
apiKey,
etherscanApi
)
} else result = await getReceiptStatus(values.receiptGuid, apiKey, etherscanApi)
setResults({
succeed: result.status === '1' ? true : false,
message: result.result || (result.status === '0' ? 'Verification failed' : result.message)
@ -63,51 +50,36 @@ export const ReceiptsView: React.FC = () => {
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, receipts, setReceipts }) => {
if (!apiKey && clientInstance && clientInstance.call) {
clientInstance.call('sidePanel' as any, 'currentFocus').then((current) => {
if (current === 'etherscan') clientInstance.call('notification' as any, 'toast', 'Please add API key to continue')
})
}
{({apiKey, clientInstance, receipts, setReceipts}) => {
return !apiKey ? (
<Navigate
to={{
pathname: "/settings"
pathname: '/settings'
}}
/>
) : (
<div>
<Formik
initialValues={{ receiptGuid: "" }}
initialValues={{receiptGuid: ''}}
validate={(values) => {
const errors = {} as any
if (!values.receiptGuid) {
errors.receiptGuid = "Required"
errors.receiptGuid = 'Required'
}
return errors
}}
onSubmit={(values) =>
onGetReceiptStatus(values, clientInstance, apiKey)
}
onSubmit={(values) => onGetReceiptStatus(values, clientInstance, apiKey)}
>
{({ errors, touched, handleSubmit, handleChange }) => (
{({errors, touched, handleSubmit, handleChange}) => (
<form onSubmit={handleSubmit}>
<div className="form-group mb-2">
<label htmlFor="receiptGuid">Receipt GUID</label>
<Field
className={
errors.receiptGuid && touched.receiptGuid
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
className={errors.receiptGuid && touched.receiptGuid ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'}
type="text"
name="receiptGuid"
/>
<ErrorMessage
className="invalid-feedback"
name="receiptGuid"
component="div"
/>
<ErrorMessage className="invalid-feedback" name="receiptGuid" component="div" />
</div>
<div className="d-flex mb-2 custom-control custom-checkbox">
@ -120,37 +92,44 @@ export const ReceiptsView: React.FC = () => {
handleChange(e)
if (e.target.checked) setIsProxyContractReceipt(true)
else setIsProxyContractReceipt(false)
}}
}}
/>
<label className="form-check-label custom-control-label" htmlFor="isProxyReceipt">It's a proxy contract GUID</label>
<label className="form-check-label custom-control-label" htmlFor="isProxyReceipt">
It's a proxy contract GUID
</label>
</div>
<SubmitButton text="Check" disable = {!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} />
<SubmitButton text="Check" disable={!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} />
</form>
)}
</Formik>
<div
className={results['succeed'] ? "text-success mt-3 text-center" : "text-danger mt-3 text-center"}
dangerouslySetInnerHTML={{ __html: results.message ? results.message : '' }}
className={results['succeed'] ? 'text-success mt-3 text-center' : 'text-danger mt-3 text-center'}
dangerouslySetInnerHTML={{
__html: results.message ? results.message : ''
}}
/>
<ReceiptsTable receipts={receipts} /><br/>
<CustomTooltip
tooltipText="Clear the list of receipts"
tooltipId='etherscan-clear-receipts'
placement='bottom'
>
<Button className="btn-sm" onClick={() => { setReceipts([]) }} >Clear</Button>
<ReceiptsTable receipts={receipts} />
<br />
<CustomTooltip tooltipText="Clear the list of receipts" tooltipId="etherscan-clear-receipts" placement="bottom">
<Button
className="btn-sm"
onClick={() => {
setReceipts([])
}}
>
Clear
</Button>
</CustomTooltip>
</div>
)
}
}
}}
</AppContext.Consumer>
)
}
const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
const ReceiptsTable: React.FC<{receipts: Receipt[]}> = ({receipts}) => {
return (
<div className="table-responsive">
<h6>Receipts</h6>
@ -167,20 +146,23 @@ const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
receipts.map((item: Receipt, index) => {
return (
<tr key={item.guid}>
<td className={(item.status === 'Pass - Verified' || item.status === 'Successfully Updated')
? 'text-success' : (item.status === 'Pending in queue'
? 'text-warning' : (item.status === 'Already Verified'
? 'text-info': 'text-secondary'))}>
{item.status}
{item.status === 'Successfully Updated' && <CustomTooltip
placement={'bottom'}
tooltipClasses="text-wrap"
tooltipId="etherscan-receipt-proxy-status"
tooltipText={item.message}
>
<i style={{ fontSize: 'small' }} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip>
<td
className={
item.status === 'Pass - Verified' || item.status === 'Successfully Updated'
? 'text-success'
: item.status === 'Pending in queue'
? 'text-warning'
: item.status === 'Already Verified'
? 'text-info'
: 'text-secondary'
}
>
{item.status}
{item.status === 'Successfully Updated' && (
<CustomTooltip placement={'bottom'} tooltipClasses="text-wrap" tooltipId="etherscan-receipt-proxy-status" tooltipText={item.message}>
<i style={{fontSize: 'small'}} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip>
)}
</td>
<td>{item.guid}</td>
</tr>

@ -1,16 +1,14 @@
import React, { useEffect, useRef, useState } from "react"
import React, {useEffect, useRef, useState} from 'react'
import Web3 from 'web3'
import {
PluginClient,
} from "@remixproject/plugin"
import { CustomTooltip } from '@remix-ui/helper'
import { Formik, ErrorMessage, Field } from "formik"
import {PluginClient} from '@remixproject/plugin'
import {CustomTooltip} from '@remix-ui/helper'
import {Formik, ErrorMessage, Field} from 'formik'
import { SubmitButton } from "../components"
import { Receipt } from "../types"
import { verify } from "../utils/verify"
import { etherscanScripts } from "@remix-project/remix-ws-templates"
import {SubmitButton} from '../components'
import {Receipt} from '../types'
import {verify} from '../utils/verify'
import {etherscanScripts} from '@remix-project/remix-ws-templates'
interface Props {
client: PluginClient
@ -25,15 +23,10 @@ interface FormValues {
expectedImplAddress?: string
}
export const VerifyView: React.FC<Props> = ({
apiKey,
client,
contracts,
onVerifiedContract,
}) => {
const [results, setResults] = useState("")
const [networkName, setNetworkName] = useState("Loading...")
const [selectedContract, setSelectedContract] = useState("")
export const VerifyView: React.FC<Props> = ({apiKey, client, contracts, onVerifiedContract}) => {
const [results, setResults] = useState('')
const [networkName, setNetworkName] = useState('Loading...')
const [selectedContract, setSelectedContract] = useState('')
const [showConstructorArgs, setShowConstructorArgs] = useState(false)
const [isProxyContract, setIsProxyContract] = useState(false)
const [constructorInputs, setConstructorInputs] = useState([])
@ -41,13 +34,13 @@ export const VerifyView: React.FC<Props> = ({
useEffect(() => {
if (client && client.on) {
client.on("blockchain" as any, 'networkStatus', (result) => {
client.on('blockchain' as any, 'networkStatus', (result) => {
setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`)
})
}
return () => {
// To fix memory leak
if (client && client.off) client.off("blockchain" as any, 'networkStatus')
if (client && client.off) client.off('blockchain' as any, 'networkStatus')
}
}, [client])
@ -56,8 +49,8 @@ export const VerifyView: React.FC<Props> = ({
}, [contracts])
const updateConsFields = (contractName) => {
client.call("compilerArtefacts" as any, "getArtefactsByContractName", contractName).then((result) => {
const { artefact } = result
client.call('compilerArtefacts' as any, 'getArtefactsByContractName', contractName).then((result) => {
const {artefact} = result
if (artefact && artefact.abi && artefact.abi[0] && artefact.abi[0].type && artefact.abi[0].type === 'constructor' && artefact.abi[0].inputs.length > 0) {
setConstructorInputs(artefact.abi[0].inputs)
setShowConstructorArgs(true)
@ -69,13 +62,10 @@ export const VerifyView: React.FC<Props> = ({
}
const onVerifyContract = async (values: FormValues) => {
const compilationResult = (await client.call(
"solidity",
"getCompilationResult"
)) as any
const compilationResult = (await client.call('solidity', 'getCompilationResult')) as any
if (!compilationResult) {
throw new Error("no compilation result available")
throw new Error('no compilation result available')
}
const constructorValues = []
@ -83,9 +73,9 @@ export const VerifyView: React.FC<Props> = ({
if (key.startsWith('contractArgValue')) constructorValues.push(values[key])
}
const web3 = new Web3()
const constructorTypes = constructorInputs.map(e => e.type)
const constructorTypes = constructorInputs.map((e) => e.type)
let contractArguments = web3.eth.abi.encodeParameters(constructorTypes, constructorValues)
contractArguments = contractArguments.replace("0x", "")
contractArguments = contractArguments.replace('0x', '')
verificationResult.current = await verify(
apiKey,
@ -98,7 +88,7 @@ export const VerifyView: React.FC<Props> = ({
values.expectedImplAddress,
client,
onVerifiedContract,
setResults,
setResults
)
setResults(verificationResult.current['message'])
}
@ -107,199 +97,148 @@ export const VerifyView: React.FC<Props> = ({
<div>
<Formik
initialValues={{
contractName: "",
contractAddress: ""
contractName: '',
contractAddress: ''
}}
validate={(values) => {
const errors = {} as any
if (!values.contractName) {
errors.contractName = "Required"
errors.contractName = 'Required'
}
if (!values.contractAddress) {
errors.contractAddress = "Required"
errors.contractAddress = 'Required'
}
if (values.contractAddress.trim() === "" || !values.contractAddress.startsWith('0x')
|| values.contractAddress.length !== 42) {
errors.contractAddress = "Please enter a valid contract address"
if (values.contractAddress.trim() === '' || !values.contractAddress.startsWith('0x') || values.contractAddress.length !== 42) {
errors.contractAddress = 'Please enter a valid contract address'
}
return errors
}}
onSubmit={(values) => onVerifyContract(values)}
>
{({ errors, touched, handleSubmit, handleChange, isSubmitting }) => {
return (<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="network">Selected Network</label>
<CustomTooltip
tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field"
tooltipId='etherscan-impl-address2'
placement='bottom'
>
{({errors, touched, handleSubmit, handleChange, isSubmitting}) => {
return (
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="network">Selected Network</label>
<CustomTooltip
tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field"
tooltipId="etherscan-impl-address2"
placement="bottom"
>
<Field className="form-control" type="text" name="network" value={networkName} disabled={true} />
</CustomTooltip>
</div>
<div className="form-group">
<label htmlFor="contractName">Contract Name</label>
<Field
className="form-control"
type="text"
name="network"
value={networkName}
disabled={true}
/>
</CustomTooltip>
</div>
<div className="form-group">
<label htmlFor="contractName">Contract Name</label>
<Field
as="select"
className={
errors.contractName && touched.contractName && contracts.length
? "form-control is-invalid"
: "form-control"
}
name="contractName"
onChange={async (e) => {
handleChange(e)
setSelectedContract(e.target.value)
updateConsFields(e.target.value)
}}
>
<option disabled={true} value="">
{ contracts.length ? 'Select a contract' : `--- No compiled contracts ---` }
</option>
{contracts.map((item) => (
<option key={item} value={item}>
{item}
as="select"
className={errors.contractName && touched.contractName && contracts.length ? 'form-control is-invalid' : 'form-control'}
name="contractName"
onChange={async (e) => {
handleChange(e)
setSelectedContract(e.target.value)
updateConsFields(e.target.value)
}}
>
<option disabled={true} value="">
{contracts.length ? 'Select a contract' : `--- No compiled contracts ---`}
</option>
))}
</Field>
<ErrorMessage
className="invalid-feedback"
name="contractName"
component="div"
/>
</div>
<div className={ showConstructorArgs ? 'form-group d-block': 'form-group d-none' } >
<label>Constructor Arguments</label>
{contracts.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</Field>
<ErrorMessage className="invalid-feedback" name="contractName" component="div" />
</div>
<div className={showConstructorArgs ? 'form-group d-block' : 'form-group d-none'}>
<label>Constructor Arguments</label>
{constructorInputs.map((item, index) => {
return (
<div className="d-flex">
<Field
className="form-control m-1"
type="text"
key={`contractArgName${index}`}
name={`contractArgName${index}`}
value={item.name}
disabled={true}
/>
<CustomTooltip
tooltipText={`value of ${item.name}`}
tooltipId={`etherscan-constructor-value${index}`}
placement='top'
>
<Field
className="form-control m-1"
type="text"
key={`contractArgValue${index}`}
name={`contractArgValue${index}`}
placeholder={item.type}
/>
<Field className="form-control m-1" type="text" key={`contractArgName${index}`} name={`contractArgName${index}`} value={item.name} disabled={true} />
<CustomTooltip tooltipText={`value of ${item.name}`} tooltipId={`etherscan-constructor-value${index}`} placement="top">
<Field className="form-control m-1" type="text" key={`contractArgValue${index}`} name={`contractArgValue${index}`} placeholder={item.type} />
</CustomTooltip>
</div>
)}
)}
</div>
<div className="form-group">
<label htmlFor="contractAddress">Contract Address</label>
<Field
className={
errors.contractAddress && touched.contractAddress
? "form-control is-invalid"
: "form-control"
}
type="text"
name="contractAddress"
placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/>
<ErrorMessage
className="invalid-feedback"
name="contractAddress"
component="div"
/>
<div className="d-flex mb-2 custom-control custom-checkbox">
)
})}
</div>
<div className="form-group">
<label htmlFor="contractAddress">Contract Address</label>
<Field
className="custom-control-input"
type="checkbox"
name="isProxy"
id="isProxy"
onChange={async (e) => {
handleChange(e)
if (e.target.checked) setIsProxyContract(true)
else setIsProxyContract(false)
}}
/>
<label className="form-check-label custom-control-label" htmlFor="isProxy">It's a proxy contract address</label>
</div>
</div>
<div className={ isProxyContract ? 'form-group d-block': 'form-group d-none' }>
<label htmlFor="expectedImplAddress">Expected Implementation Address</label>
<CustomTooltip
tooltipText='Providing expected implementation address enforces a check to ensure the returned implementation contract address is same as address picked up by the verifier'
tooltipId='etherscan-impl-address'
placement='bottom'
>
<Field
className="form-control"
className={errors.contractAddress && touched.contractAddress ? 'form-control is-invalid' : 'form-control'}
type="text"
name="expectedImplAddress"
placeholder="verified implementation contract address"
name="contractAddress"
placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/>
</CustomTooltip>
<i style={{ fontSize: 'x-small' }} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
<label> &nbsp;Make sure contract is already verified on Etherscan</label>
</div>
<SubmitButton dataId="verify-contract" text="Verify"
isSubmitting={isSubmitting}
disable={ !contracts.length ||
!touched.contractName ||
!touched.contractAddress ||
(touched.contractName && errors.contractName) ||
(touched.contractAddress && errors.contractAddress) ||
(networkName === 'VM (Not supported)')
? true
: false}
/>
<br/>
<CustomTooltip
tooltipText='Generate the required TS scripts to verify a contract on Etherscan'
tooltipId='etherscan-generate-scripts'
placement='bottom'
>
<button
type="button"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block"
onClick={async () => {
etherscanScripts(client)
}}
<ErrorMessage className="invalid-feedback" name="contractAddress" component="div" />
<div className="d-flex mb-2 custom-control custom-checkbox">
<Field
className="custom-control-input"
type="checkbox"
name="isProxy"
id="isProxy"
onChange={async (e) => {
handleChange(e)
if (e.target.checked) setIsProxyContract(true)
else setIsProxyContract(false)
}}
/>
<label className="form-check-label custom-control-label" htmlFor="isProxy">
It's a proxy contract address
</label>
</div>
</div>
<div className={isProxyContract ? 'form-group d-block' : 'form-group d-none'}>
<label htmlFor="expectedImplAddress">Expected Implementation Address</label>
<CustomTooltip
tooltipText="Providing expected implementation address enforces a check to ensure the returned implementation contract address is same as address picked up by the verifier"
tooltipId="etherscan-impl-address"
placement="bottom"
>
<Field className="form-control" type="text" name="expectedImplAddress" placeholder="verified implementation contract address" />
</CustomTooltip>
<i style={{fontSize: 'x-small'}} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
<label> &nbsp;Make sure contract is already verified on Etherscan</label>
</div>
<SubmitButton
dataId="verify-contract"
text="Verify"
isSubmitting={isSubmitting}
disable={
!contracts.length ||
!touched.contractName ||
!touched.contractAddress ||
(touched.contractName && errors.contractName) ||
(touched.contractAddress && errors.contractAddress) ||
networkName === 'VM (Not supported)'
? true
: false
}
/>
<br />
<CustomTooltip tooltipText="Generate the required TS scripts to verify a contract on Etherscan" tooltipId="etherscan-generate-scripts" placement="bottom">
<button
type="button"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block"
onClick={async () => {
etherscanScripts(client)
}}
>
Generate Verification Scripts
</button>
</CustomTooltip>
</form>
</form>
)
}
}
}}
</Formik>
<div
data-id="verify-result"
className={verificationResult.current['succeed'] ? "text-success mt-4 text-center" : "text-danger mt-4 text-center"}
style={{fontSize: "0.8em"}}
dangerouslySetInnerHTML={{ __html: results }}
className={verificationResult.current['succeed'] ? 'text-success mt-4 text-center' : 'text-danger mt-4 text-center'}
style={{fontSize: '0.8em'}}
dangerouslySetInnerHTML={{__html: results}}
/>
{/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}>
<Link to="/receipts">View Receipts</Link>
</div> */}

@ -1,7 +1,11 @@
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import {StrictMode} from 'react'
import * as ReactDOM from 'react-dom'
import App from './app/app'
import App from './app/app';
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root'));
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
document.getElementById('root')
)

@ -1,7 +1,7 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const versionData = {
timestamp: Date.now(),
@ -15,29 +15,29 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"path": require.resolve("path-browserify"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"constants": require.resolve("constants-browserify"),
"os": false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"),
"fs": false,
"module": false,
"tls": false,
"net": false,
"readline": false,
"child_process": false,
"buffer": require.resolve("buffer/"),
"vm": require.resolve('vm-browserify'),
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
path: require.resolve('path-browserify'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
constants: require.resolve('constants-browserify'),
os: false, //require.resolve("os-browserify/browser"),
timers: false, // require.resolve("timers-browserify"),
zlib: require.resolve('browserify-zlib'),
fs: false,
module: false,
tls: false,
net: false,
readline: false,
child_process: false,
buffer: require.resolve('buffer/'),
vm: require.resolve('vm-browserify')
}
// add externals
config.externals = {
...config.externals,
solc: 'solc',
solc: 'solc'
}
// add public path
@ -47,26 +47,24 @@ module.exports = composePlugins(withNx(), (config) => {
config.output.filename = `[name].plugin-etherscan.${versionData.timestamp}.js`
config.output.chunkFilename = `[name].plugin-etherscan.${versionData.timestamp}.js`
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
process: 'process/browser'
})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
use: ['source-map-loader'],
enforce: 'pre'
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer
config.optimization.minimizer = [
new TerserPlugin({
@ -76,17 +74,17 @@ module.exports = composePlugins(withNx(), (config) => {
compress: false,
mangle: false,
format: {
comments: false,
},
comments: false
}
},
extractComments: false,
extractComments: false
}),
new CssMinimizerPlugin(),
];
new CssMinimizerPlugin()
]
config.watchOptions = {
ignored: /node_modules/
}
return config;
});
return config
})

@ -7,7 +7,7 @@ module.exports = {
globals_path: '',
test_settings: {
default: {
'default': {
selenium_port: 4444,
selenium_host: 'localhost',
globals: {
@ -23,28 +23,29 @@ module.exports = {
exclude: ['dist/apps/remix-ide-e2e/src/tests/runAndDeploy.test.js', 'dist/apps/remix-ide-e2e/src/tests/pluginManager.test.ts']
},
chrome: {
'chrome': {
desiredCapabilities: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
'goog:chromeOptions': {
args: ['window-size=2560,1440',
'start-fullscreen',
'--no-sandbox',
'--headless',
'--verbose',
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36",
args: [
'window-size=2560,1440',
'start-fullscreen',
'--no-sandbox',
'--headless',
'--verbose',
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
]
}
}
},
chromeDesktop: {
'chromeDesktop': {
desiredCapabilities: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox']
}
@ -53,40 +54,33 @@ module.exports = {
'chrome-runAndDeploy': {
desiredCapabilities: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox', '--headless', '--verbose']
}
}
},
firefoxDesktop: {
'firefoxDesktop': {
desiredCapabilities: {
browserName: 'firefox',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'firefox',
'javascriptEnabled': true,
'acceptSslCerts': true,
'moz:firefoxOptions': {
args: [
'-width=2560',
'-height=1440'
]
args: ['-width=2560', '-height=1440']
}
}
},
firefox: {
'firefox': {
desiredCapabilities: {
browserName: 'firefox',
javascriptEnabled: true,
acceptSslCerts: true,
'browserName': 'firefox',
'javascriptEnabled': true,
'acceptSslCerts': true,
'moz:firefoxOptions': {
args: [
'-headless',
'-width=2560',
'-height=1440'
]
args: ['-headless', '-width=2560', '-height=1440']
}
}
}

@ -6,14 +6,14 @@
"npm": "^6.14.15"
},
"dependencies": {
"@openzeppelin/contracts": "^4.8.3",
"@openzeppelin/contracts-upgradeable": "^4.8.3",
"@openzeppelin/upgrades-core": "^1.22.0",
"@openzeppelin/wizard": "^0.1.1",
"@openzeppelin/contracts": "^5.0.0",
"@openzeppelin/contracts-upgradeable": "^5.0.0",
"@openzeppelin/upgrades-core": "^1.30.0",
"@openzeppelin/wizard": "^0.4.0",
"@remix-project/remixd": "../../dist/libs/remixd",
"deep-equal": "^1.0.1",
"ganache-cli": "^6.8.1",
"selenium-standalone": "^8.2.3",
"selenium-standalone": "^9.0.3",
"tree-kill": "^1.2.2"
},
"devDependencies": {

@ -1,9 +1,9 @@
import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch'
import {NightwatchBrowser} from 'nightwatch'
class checkAnnotations extends EventEmitter {
command (this: NightwatchBrowser, type: string, line: number): NightwatchBrowser {
this.api.assert.containsText(`.margin-view-overlays .${type} + div`, line.toString()).perform(() => this.emit('complete'))
command(this: NightwatchBrowser, type: string): NightwatchBrowser {
this.api.waitForElementPresent(`.glyph-margin-widgets .${type}`).perform(() => this.emit('complete'))
return this
}
}

@ -1,9 +1,9 @@
import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch'
import {NightwatchBrowser} from 'nightwatch'
class checkAnnotationsNotPresent extends EventEmitter {
command (this: NightwatchBrowser, type: string): NightwatchBrowser {
this.api.waitForElementNotPresent(`.margin-view-overlays .${type}`).perform(() => this.emit('complete'))
command(this: NightwatchBrowser, type: string): NightwatchBrowser {
this.api.waitForElementNotPresent(`.glyph-margin-widgets .${type}`).perform(() => this.emit('complete'))
return this
}
}

@ -1,20 +1,37 @@
import { NightwatchBrowser, NightwatchClickFunctionExpectedInput } from 'nightwatch'
import {
NightwatchBrowser,
NightwatchClickFunctionExpectedInput
} from 'nightwatch'
import EventEmitter from 'events'
class ClickFunction extends EventEmitter {
command (this: NightwatchBrowser, fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser {
this.api.waitForElementPresent('.instance button[data-title="' + fnFullName + '"]')
command(
this: NightwatchBrowser,
fnFullName: string,
expectedInput?: NightwatchClickFunctionExpectedInput
): NightwatchBrowser {
this.api
.waitForElementPresent('.instance *[data-title="' + fnFullName + '"]')
.perform(function (client, done) {
client.execute(function () {
document.querySelector('#runTabView').scrollTop = document.querySelector('#runTabView').scrollHeight
}, [], function () {
if (expectedInput) {
client.setValue('#runTabView input[data-title="' + expectedInput.types + '"]', expectedInput.values, _ => _)
client.execute(
function () {
document.querySelector('#runTabView').scrollTop =
document.querySelector('#runTabView').scrollHeight
},
[],
function () {
if (expectedInput) {
client.setValue(
'#runTabView input[data-title="' + expectedInput.types + '"]',
expectedInput.values,
(_) => _
)
}
done()
}
done()
})
)
})
.scrollAndClick('.instance button[data-title="' + fnFullName + '"]')
.scrollAndClick('.instance *[data-title="' + fnFullName + '"]')
.pause(2000)
.perform(() => {
this.emit('complete')

@ -16,11 +16,11 @@ class CreateContract extends EventEmitter {
function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) {
if (inputParams) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () {
browser.click('.udapp_contractActionsContainerSingle > button').pause(500).perform(function () { callback() })
browser.click('.udapp_contractActionsContainerSingle > div').pause(500).perform(function () { callback() })
})
} else {
browser
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.pause(500)
.perform(function () { callback() })
}

@ -1,36 +1,80 @@
import { NightwatchBrowser, NightwatchTestConstantFunctionExpectedInput } from 'nightwatch'
import {
NightwatchBrowser,
NightwatchTestConstantFunctionExpectedInput
} from 'nightwatch'
import EventEmitter from 'events'
class TestConstantFunction extends EventEmitter {
command (this: NightwatchBrowser, address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser {
command(
this: NightwatchBrowser,
address: string,
fnFullName: string,
expectedInput: NightwatchTestConstantFunctionExpectedInput | null,
expectedOutput: string
): NightwatchBrowser {
console.log('TestConstantFunction ' + address + ' fnFullName')
this.api.perform((done) => {
testConstantFunction(this.api, address, fnFullName, expectedInput, expectedOutput, () => {
done()
this.emit('complete')
})
testConstantFunction(
this.api,
address,
fnFullName,
expectedInput,
expectedOutput,
() => {
done()
this.emit('complete')
}
)
})
return this
}
}
function testConstantFunction (browser: NightwatchBrowser, address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput, expectedOutput: string, cb: VoidFunction) {
browser.waitForElementPresent('.instance button[data-title="' + fnFullName + '"]').perform(function (client, done) {
client.execute(function () {
document.querySelector('#runTabView').scrollTop = document.querySelector('#runTabView').scrollHeight
}, [], function () {
if (expectedInput) {
client.waitForElementPresent('#runTabView input[data-title="' + expectedInput.types + '"]')
.setValue('#runTabView input[data-title="' + expectedInput.types + '"]', expectedInput.values)
}
done()
function testConstantFunction(
browser: NightwatchBrowser,
address: string,
fnFullName: string,
expectedInput: NightwatchTestConstantFunctionExpectedInput,
expectedOutput: string,
cb: VoidFunction
) {
browser
.waitForElementPresent('.instance *[data-title="' + fnFullName + '"]')
.perform(function (client, done) {
client.execute(
function () {
document.querySelector('#runTabView').scrollTop =
document.querySelector('#runTabView').scrollHeight
},
[],
function () {
if (expectedInput) {
client
.waitForElementPresent(
'#runTabView input[data-title="' + expectedInput.types + '"]'
)
.setValue(
'#runTabView input[data-title="' + expectedInput.types + '"]',
expectedInput.values
)
}
done()
}
)
})
})
.click(`#instance${address} button[data-title="${fnFullName}"]`)
.click(`#instance${address} *[data-title="${fnFullName}"]`)
.pause(1000)
.waitForElementPresent('#instance' + address + ' .udapp_contractActionsContainer .udapp_value')
.scrollInto('#instance' + address + ' .udapp_contractActionsContainer .udapp_value')
.assert.containsText('#instance' + address + ' .udapp_contractActionsContainer', expectedOutput).perform(() => {
.waitForElementPresent(
'#instance' + address + ' .udapp_contractActionsContainer .udapp_value'
)
.scrollInto(
'#instance' + address + ' .udapp_contractActionsContainer .udapp_value'
)
.assert.containsText(
'#instance' + address + ' .udapp_contractActionsContainer',
expectedOutput
)
.perform(() => {
cb()
})
}

@ -1,32 +1,43 @@
import React, { useEffect, useState } from 'react'
import { RemixPlugin } from './Client'
import { Logger } from './logger'
import { filePanelProfile } from '@remixproject/plugin-api'
import { filSystemProfile } from '@remixproject/plugin-api'
import { dGitProfile } from '@remixproject/plugin-api'
import { editorProfile } from '@remixproject/plugin-api'
import { settingsProfile } from '@remixproject/plugin-api'
import { networkProfile } from '@remixproject/plugin-api'
import { udappProfile } from '@remixproject/plugin-api'
import { compilerProfile } from '@remixproject/plugin-api'
import { contentImportProfile } from '@remixproject/plugin-api'
import { windowProfile } from '@remixproject/plugin-api'
import { pluginManagerProfile } from '@remixproject/plugin-api'
import { Profile } from '@remixproject/plugin-utils'
import React, {useEffect, useState} from 'react'
import {RemixPlugin} from './Client'
import {Logger} from './logger'
import {filePanelProfile} from '@remixproject/plugin-api'
import {filSystemProfile} from '@remixproject/plugin-api'
import {dGitProfile} from '@remixproject/plugin-api'
import {editorProfile} from '@remixproject/plugin-api'
import {settingsProfile} from '@remixproject/plugin-api'
import {networkProfile} from '@remixproject/plugin-api'
import {udappProfile} from '@remixproject/plugin-api'
import {compilerProfile} from '@remixproject/plugin-api'
import {contentImportProfile} from '@remixproject/plugin-api'
import {windowProfile} from '@remixproject/plugin-api'
import {pluginManagerProfile} from '@remixproject/plugin-api'
import {Profile} from '@remixproject/plugin-utils'
import './app.css'
const client = new RemixPlugin()
function App () {
function App() {
const [payload, setPayload] = useState<string>('')
const [log, setLog] = useState<any>()
const [started, setStarted] = useState<boolean>(false)
const [events, setEvents] = useState<any>()
const [profiles, setProfiles] = useState<Profile[]>([pluginManagerProfile, filePanelProfile, filSystemProfile, dGitProfile, networkProfile, settingsProfile, editorProfile, compilerProfile, udappProfile, contentImportProfile, windowProfile])
const [profiles, setProfiles] = useState<Profile[]>([
pluginManagerProfile,
filePanelProfile,
filSystemProfile,
dGitProfile,
networkProfile,
settingsProfile,
editorProfile,
compilerProfile,
udappProfile,
contentImportProfile,
windowProfile
])
const handleChange = ({ target }: any) => {
const handleChange = ({target}: any) => {
setPayload(target.value)
}
@ -44,7 +55,7 @@ function App () {
const p = await client.call('manager', 'getProfile', name)
addProfiles = [...addProfiles, p]
}
setProfiles(profiles => [...profiles, ...addProfiles])
setProfiles((profiles) => [...profiles, ...addProfiles])
profiles.map((profile: Profile) => {
if (profile.events) {
@ -77,8 +88,10 @@ function App () {
let ob: any = null
try {
ob = JSON.parse(payload)
if (ob && !Array.isArray(ob)) { ob = [ob] }
} catch (e) { }
if (ob && !Array.isArray(ob)) {
ob = [ob]
}
} catch (e) {}
const args = ob || [payload]
setStarted(true)
setLog('')
@ -97,30 +110,41 @@ function App () {
return (
<div className="App container-fluid">
<h5>PLUGIN API TESTER</h5>
<label id='callStatus'>{started ? <>start</> : <>stop</> }</label><br></br>
<label id="callStatus">{started ? <>start</> : <>stop</>}</label>
<br></br>
<label>method results</label>
<Logger id='methods' log={log}></Logger>
<Logger id="methods" log={log}></Logger>
<label>events</label>
<Logger id='events' log={events}></Logger>
<input
className='form-control w-100'
type="text"
id="payload"
placeholder="Enter payload here..."
value={payload}
onChange={handleChange}
data-id="payload-input"
/>
<Logger id="events" log={events}></Logger>
<input className="form-control w-100" type="text" id="payload" placeholder="Enter payload here..." value={payload} onChange={handleChange} data-id="payload-input" />
{profiles.map((profile: Profile) => {
const methods = profile.methods.map((method: string) => {
return <button data-id={`${profile.name}:${method}`} key={method} className='btn btn-primary btn-sm ml-1 mb-1' onClick={async () => await clientMethod(profile, method)}>{method}</button>
return (
<button data-id={`${profile.name}:${method}`} key={method} className="btn btn-primary btn-sm ml-1 mb-1" onClick={async () => await clientMethod(profile, method)}>
{method}
</button>
)
})
const events = profile.events ? profile.events.map((event: string) => {
return <label key={event} className='m-1'>{event}</label>
}) : null
return <div key={profile.name} className='small border-bottom'><label className='text-uppercase'>{profile.name}</label><br></br>{methods}<br></br>{events ? <label>EVENTS:</label> : null}{events}</div>
const events = profile.events
? profile.events.map((event: string) => {
return (
<label key={event} className="m-1">
{event}
</label>
)
})
: null
return (
<div key={profile.name} className="small border-bottom">
<label className="text-uppercase">{profile.name}</label>
<br></br>
{methods}
<br></br>
{events ? <label>EVENTS:</label> : null}
{events}
</div>
)
})}
</div>
)
}

@ -1,9 +1,13 @@
import React from 'react'
interface loggerProps {
log: any,
log: any
id: string
}
export const Logger: React.FC<loggerProps> = (props) => {
return (<div id={props.id} className="jumbotron overflow-auto text-break mb-1 p-2">{props.log}</div>)
return (
<div id={props.id} className="jumbotron overflow-auto text-break mb-1 p-2">
{props.log}
</div>
)
}

@ -21,7 +21,7 @@ module.exports = {
browser.testContracts('test.sol',{ content: code } , ['A'])
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.clickFunction('foo - call', { types: 'uint256 p', values: '0' })
.perform((done) => {

@ -42,37 +42,43 @@ const sources = [
'TestContract.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, ERC721BurnableUpgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract MyToken is ERC20, ERC20Burnable, ERC20Pausable, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
ERC20Permit("MyToken")
{}
function pause() public onlyOwner {
_pause();
}
function initialize() initializer public {
__ERC721_init("MyToken", "MTK");
__ERC721Burnable_init();
__Ownable_init();
__UUPSUpgradeable_init();
function unpause() public onlyOwner {
_unpause();
}
function safeMint(address to, uint256 tokenId) public onlyOwner {
_safeMint(to, tokenId);
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function _authorizeUpgrade(address newImplementation)
// The following functions are overrides required by Solidity.
function _update(address from, address to, uint256 value)
internal
onlyOwner
override
{}
override(ERC20, ERC20Pausable)
{
super._update(from, to, value);
}
}
`
},
}

@ -1,16 +1,17 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import {NightwatchBrowser} from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
'before': function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should zoom in editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="mainPanelPluginsContainer"]')
browser
.waitForElementVisible('div[data-id="mainPanelPluginsContainer"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.openFile('contracts')
@ -23,7 +24,8 @@ module.exports = {
},
'Should zoom out editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
browser
.waitForElementVisible('#editorView')
.checkElementStyle('.view-lines', 'font-size', '16px')
.click('*[data-id="tabProxyZoomOut"]')
.click('*[data-id="tabProxyZoomOut"]')
@ -31,57 +33,72 @@ module.exports = {
},
'Should display compile error in editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
browser
.waitForElementVisible('#editorView')
.setEditorValue(storageContractWithError + 'error')
.pause(2000)
.waitForElementVisible('.margin-view-overlays .fa-exclamation-square', 120000)
.checkAnnotations('fa-exclamation-square', 29) // error
.waitForElementVisible('.glyph-margin-widgets .fa-exclamation-square', 120000)
.checkAnnotations('fa-exclamation-square') // error
.clickLaunchIcon('udapp')
.checkAnnotationsNotPresent('fa-exclamation-square') // error
.clickLaunchIcon('solidity')
.checkAnnotations('fa-exclamation-square', 29) // error
.checkAnnotations('fa-exclamation-square') // error
},
'Should minimize and maximize codeblock in editor #group1': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
.waitForElementVisible('.ace_open')
.click('.ace_start:nth-of-type(1)')
.waitForElementVisible('.ace_closed')
.click('.ace_start:nth-of-type(1)')
.waitForElementVisible('.ace_open')
},
'Should minimize and maximize codeblock in editor #group1':
'' +
function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('#editorView')
.waitForElementVisible('.ace_open')
.click('.ace_start:nth-of-type(1)')
.waitForElementVisible('.ace_closed')
.click('.ace_start:nth-of-type(1)')
.waitForElementVisible('.ace_open')
},
'Should add breakpoint to editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
.waitForElementNotPresent('.margin-view-overlays .fa-circle')
.execute(() => {
(window as any).addRemixBreakpoint(1)
}, [], () => {})
.waitForElementVisible('.margin-view-overlays .fa-circle')
},
'Should load syntax highlighter for ace light theme #group1': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
.checkElementStyle('.ace_keyword', 'color', aceThemes.light.keyword)
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.light.comment)
.checkElementStyle('.ace_function', 'color', aceThemes.light.function)
.checkElementStyle('.ace_variable', 'color', aceThemes.light.variable)
browser
.waitForElementVisible('#editorView')
.waitForElementNotPresent('.glyph-margin-widgets .fa-circle')
.execute(
() => {
;(window as any).addRemixBreakpoint(1)
},
[],
() => {}
)
.waitForElementVisible('.glyph-margin-widgets .fa-circle')
},
'Should load syntax highlighter for ace dark theme #group1': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]')
.click('*[data-id="verticalIconsKindsettings"]')
.waitForElementVisible('*[data-id="settingsTabThemeLabelDark"]')
.click('*[data-id="settingsTabThemeLabelDark"]')
.pause(2000)
.waitForElementVisible('#editorView')
/* @todo(#2863) ch for class and not colors
'Should load syntax highlighter for ace light theme #group1':
'' +
function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('#editorView')
.checkElementStyle('.ace_keyword', 'color', aceThemes.light.keyword)
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.light.comment)
.checkElementStyle('.ace_function', 'color', aceThemes.light.function)
.checkElementStyle('.ace_variable', 'color', aceThemes.light.variable)
},
'Should load syntax highlighter for ace dark theme #group1':
'' +
function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="verticalIconsKindsettings"]')
.click('*[data-id="verticalIconsKindsettings"]')
.waitForElementVisible('*[data-id="settingsTabThemeLabelDark"]')
.click('*[data-id="settingsTabThemeLabelDark"]')
.pause(2000)
.waitForElementVisible('#editorView')
/* @todo(#2863) ch for class and not colors
.checkElementStyle('.ace_keyword', 'color', aceThemes.dark.keyword)
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.dark.comment)
.checkElementStyle('.ace_function', 'color', aceThemes.dark.function)
.checkElementStyle('.ace_variable', 'color', aceThemes.dark.variable)
*/
},
},
'Should highlight source code #group1': function (browser: NightwatchBrowser) {
// include all files here because switching between plugins in side-panel removes highlight
@ -101,22 +118,26 @@ module.exports = {
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)')
},
'Should remove 1 highlight from source code #group1': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts"]')
.click('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.click('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.waitForElementNotPresent('.highlightLine33', 60000)
.checkElementStyle('.highlightLine41', 'background-color', 'rgb(52, 152, 219)')
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)')
},
'Should remove 1 highlight from source code #group1':
'' +
function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts"]')
.click('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.click('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.waitForElementNotPresent('.highlightLine33', 60000)
.checkElementStyle('.highlightLine41', 'background-color', 'rgb(52, 152, 219)')
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)')
},
'Should remove all highlights from source code #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]')
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]')
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
@ -126,8 +147,7 @@ module.exports = {
.waitForElementNotPresent('.highlightLine33', 60000)
.waitForElementNotPresent('.highlightLine41', 60000)
.waitForElementNotPresent('.highlightLine51', 60000)
},
}
}
const aceThemes = {
@ -233,5 +253,3 @@ contract Storage {
return number;
}
}`

@ -17,7 +17,6 @@ module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should load the test file': function (browser: NightwatchBrowser) {
browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
@ -86,7 +85,50 @@ module.exports = {
const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]"
const expectedContent = 'StructDefinition'
checkEditorHoverContent(browser, path, expectedContent)
}
},
'Add token file': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js')
.addFile('contracts/mytoken.sol', {
content: myToken
}).useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
},
// here we change quickly between files to test the files being parsed correctly when switching between them
'Should show ERC20 hover over contract in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(10)
const path = "//*[@class='view-line' and contains(.,'MyToken') and contains(.,'Pausable')]//span//span[contains(.,'ERC20Burnable')]"
const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Errors, IERC20Metadata, IERC20, Context'
checkEditorHoverContent(browser, path, expectedContent, 25)
},
'Go back to ballot file': function (browser: NightwatchBrowser) {
browser.openFile('contracts/3_Ballot.sol')
.useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
},
'Should show hover over function in editor again #group1': function (browser: NightwatchBrowser) {
browser
.scrollToLine(58)
const path: string = "//*[@class='view-line' and contains(.,'giveRightToVote(address') and contains(.,'function') and contains(.,'public')]//span//span[contains(.,'giveRightToVote')]"
let expectedContent = 'Estimated execution cost'
checkEditorHoverContent(browser, path, expectedContent)
expectedContent = 'function giveRightToVote (address internal voter) public nonpayable returns ()'
checkEditorHoverContent(browser, path, expectedContent)
expectedContent = "@dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'"
checkEditorHoverContent(browser, path, expectedContent)
},
'Open token file': function (browser: NightwatchBrowser) {
browser.openFile('contracts/mytoken.sol')
.useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
},
'Should show ERC20 hover over contract in editor again #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(10)
const path = "//*[@class='view-line' and contains(.,'MyToken') and contains(.,'Pausable')]//span//span[contains(.,'ERC20Burnable')]"
const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Errors, IERC20Metadata, IERC20, Context'
checkEditorHoverContent(browser, path, expectedContent, 25)
},
}
@ -234,3 +276,44 @@ contract BallotHoverTest {
}
}
`
const myToken = `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract MyToken is ERC20, ERC20Burnable, ERC20Pausable, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
ERC20Permit("MyToken")
{}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
// The following functions are overrides required by Solidity.
function _update(address from, address to, uint256 value)
internal
override(ERC20, ERC20Pausable)
{
super._update(from, to, value);
}
}
`

@ -1,53 +1,55 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import {NightwatchBrowser} from 'nightwatch'
import init from '../helpers/init'
const openReferences = (browser: NightwatchBrowser, path: string) => {
(browser as any).useXpath()
.useXpath()
.waitForElementVisible(path)
.click(path)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
keyDown(this.Keys.SHIFT).
sendKeys(this.Keys.F12)
})
;(browser as any)
.useXpath()
.useXpath()
.waitForElementVisible(path)
.click(path)
.perform(function () {
const actions = this.actions({async: true})
return actions.keyDown(this.Keys.SHIFT).sendKeys(this.Keys.F12)
})
}
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should load the test file': function (browser: NightwatchBrowser) {
browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.pause(10000) // wait for the compiler to finish
.scrollToLine(37)
},
'Should show local references': function (browser: NightwatchBrowser) {
browser.scrollToLine(48)
const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]"
openReferences(browser, path)
browser.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'length; i++')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'name:')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'constructor')]")
.keys(browser.Keys.ESCAPE)
},
'Should show references of getOwner': function (browser: NightwatchBrowser) {
browser.scrollToLine(39)
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]"
openReferences(browser, path)
browser.useXpath()
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'2_Owner.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'3_Ballot.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'cowner.getOwner')]")
.waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]")
.keys(browser.Keys.ESCAPE)
}
'before': function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should load the test file': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.pause(10000) // wait for the compiler to finish
.scrollToLine(37)
},
'Should show local references': function (browser: NightwatchBrowser) {
browser.scrollToLine(48)
const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]"
openReferences(browser, path)
browser
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'length; i++')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'name:')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'constructor')]")
.keys(browser.Keys.ESCAPE)
},
'Should show references of getOwner': function (browser: NightwatchBrowser) {
browser.scrollToLine(39)
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]"
openReferences(browser, path)
browser
.useXpath()
.waitForElementVisible("//*[@class='monaco-highlighted-label'][contains(.,'2_Owner.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label'][contains(.,'3_Ballot.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch'][contains(.,'cowner.getOwner')]")
.waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]")
.keys(browser.Keys.ESCAPE)
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars

@ -42,6 +42,25 @@ module.exports = {
})
},
'Should execute `writeMultipleFiles` api from file manager external api #group1': function (browser: NightwatchBrowser) {
browser
.addFile('writeMultipleFiles.js', { content: executeWriteMultipleFiles })
.executeScriptInTerminal('remix.exeCurrent()')
.pause(2000)
.openFile('contracts/new_contract_1.sol')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('pragma solidity ^0.6.0') !== -1, 'content does not contain "pragma solidity ^0.6.0"')
})
.openFile('new_contract_2.sol')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('pragma solidity ^0.8.0') !== -1, 'content does not contain "pragma solidity ^0.8.0"')
})
.openFile('testing.txt')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('test') !== -1, 'content does not contain "test"')
})
},
'Should execute `readFile` api from file manager external api #group2': function (browser: NightwatchBrowser) {
browser
.addFile('writeFile.js', { content: executeWriteFile })
@ -143,6 +162,14 @@ const executeWriteFile = `
run()
`
const executeWriteMultipleFiles = `
const run = async () => {
await remix.call('fileManager', 'writeMultipleFiles', ['contracts/new_contract_1.sol', 'new_contract_2.sol', 'testing.txt'], ['pragma solidity ^0.6.0', 'pragma solidity ^0.8.0', 'test'], '/')
}
run()
`
const executeReadFile = `
const run = async () => {
const result = await remix.call('fileManager', 'readFile', 'new_contract.sol')

@ -0,0 +1,345 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
let firstProxyAddress: string
let lastProxyAddress: string
let shortenedFirstAddress: string
let shortenedLastAddress: string
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Should set the compiler version to 8.19': function(browser: NightwatchBrowser) {
browser.setSolidityCompilerVersion('soljson-v0.8.19+commit.7dd6d404.js')
},
'Should show deploy proxy option for UUPS upgradeable contract #group1': function (browser: NightwatchBrowser) {
browser
.addFile('myTokenV1.sol', sources[0]['myTokenV1.sol'])
.clickLaunchIcon('solidity')
.pause(2000)
// because the compilatiom imports are slow and sometimes stop loading (not sure why, it's bug) we need to recompile and check to see if the files are really in de FS
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable@4.8.3/proxy/beacon/IBeaconUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
.clickLaunchIcon('solidity')
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable@4.8.3/proxy/beacon/IBeaconUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
.clickLaunchIcon('solidity')
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable@4.8.3/proxy/beacon/IBeaconUpgradeable.sol"]',
timeout: 120000,
})
.clickLaunchIcon('solidity')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
},
'Should show upgrade proxy option for child contract inheriting UUPS parent contract #group1': function (browser: NightwatchBrowser) {
browser
.addFile('myTokenV2.sol', sources[1]['myTokenV2.sol'])
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
},
'Should deploy proxy without initialize parameters #group1': function (browser: NightwatchBrowser) {
browser
.openFile('myTokenV1.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.verify.visible('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 < 5.0.0 as proxy...', 60000)
},
'Should interact with deployed contract via ERC1967 (proxy) #group1': function (browser: NightwatchBrowser) {
browser
.getAddressAtPosition(1, (address) => {
firstProxyAddress = address
shortenedFirstAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'name - call', null, '0:\nstring: MyToken').perform(() => {
done()
})
})
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'symbol - call', null, '0:\nstring: MTK').perform(() => {
done()
})
})
},
'Should deploy proxy with initialize parameters #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.addFile('initializeProxy.sol', sources[2]['initializeProxy.sol'])
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyInitializedToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyInitializedToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.useXpath()
.waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input')
.waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input', 'Remix')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input', "R")
.useCss()
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 < 5.0.0 as proxy...', 60000)
},
'Should interact with initialized contract to verify parameters #group1': function (browser: NightwatchBrowser) {
browser
.getAddressAtPosition(1, (address) => {
lastProxyAddress = address
shortenedLastAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'name - call', null, '0:\nstring: Remix').perform(() => {
done()
})
})
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'symbol - call', null, '0:\nstring: R').perform(() => {
done()
})
})
},
'Should upgrade contract by selecting a previously deployed proxy address from dropdown (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.click('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.click('[data-id="toggleProxyAddressDropdown"]')
.waitForElementVisible('[data-id="proxy-dropdown-items"]')
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedFirstAddress)
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedLastAddress)
.click('[data-id="proxyAddress1"]')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click(
{
selector: '[data-id="confirmProxyDeployment-modal-footer-ok-react"]',
})
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 < 5.0.0 for the proxy upgrade..', 60000)
},
'Should interact with upgraded function in contract MyTokenV2 #group1': function (browser: NightwatchBrowser) {
browser
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
},
'Should upgrade contract by providing proxy address in input field (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.clearValue('[data-id="ERC1967AddressInput"]')
.setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress)
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 < 5.0.0 for the proxy upgrade..', 60000)
},
'Should interact with upgraded contract through provided proxy address #group1': function (browser: NightwatchBrowser) {
browser
.clearConsole()
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
},
'Should debug the call': function(browser: NightwatchBrowser) {
browser
.debugTransaction(0)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"11")]',
timeout: 60000
})
.goToVMTraceStep(146)
.waitForElementContainsText('*[data-id="functionPanel"]', 'version()', 60000)
.end()
}
}
const sources = [
{
'myTokenV1.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable@4.8.3/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize() initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init();
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}, {
'myTokenV2.sol': {
content: `
import "./myTokenV1.sol";
contract MyTokenV2 is MyToken {
function version () public view returns (string memory) {
return "MyTokenV2!";
}
}
`
}
}, {
'initializeProxy.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable@4.8.3/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable@4.8.3/proxy/utils/UUPSUpgradeable.sol";
contract MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(string memory tokenName, string memory tokenSymbol) initializer public {
__ERC721_init(tokenName, tokenSymbol);
__Ownable_init();
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}
]

@ -26,7 +26,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]',
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
@ -34,7 +34,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]',
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
@ -42,7 +42,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]',
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
})
.clickLaunchIcon('solidity')
@ -81,6 +81,7 @@ module.exports = {
.verify.visible('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.setValue('[data-id="initializeInputs-initialOwner"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
@ -90,6 +91,7 @@ module.exports = {
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 >= 5.0.0 as proxy...')
},
'Should interact with deployed contract via ERC1967 (proxy) #group1': function (browser: NightwatchBrowser) {
@ -131,6 +133,7 @@ module.exports = {
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input', 'Remix')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input', "R")
.useCss()
.setValue('[data-id="initializeInputs-initialOwner"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
@ -140,6 +143,7 @@ module.exports = {
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 >= 5.0.0 as proxy...')
},
'Should interact with initialized contract to verify parameters #group1': function (browser: NightwatchBrowser) {
@ -163,6 +167,7 @@ module.exports = {
'Should upgrade contract by selecting a previously deployed proxy address from dropdown (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
@ -180,6 +185,7 @@ module.exports = {
.waitForElementVisible('[data-id="proxy-dropdown-items"]')
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedFirstAddress)
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedLastAddress)
.click('[data-id="proxyAddress1"]')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
@ -193,6 +199,7 @@ module.exports = {
})
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 >= 5.0.0 for the proxy upgrade...')
},
'Should interact with upgraded function in contract MyTokenV2 #group1': function (browser: NightwatchBrowser) {
@ -207,6 +214,7 @@ module.exports = {
'Should upgrade contract by providing proxy address in input field (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
@ -230,17 +238,30 @@ module.exports = {
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 >= 5.0.0 for the proxy upgrade...')
},
'Should interact with upgraded contract through provided proxy address #group1': function (browser: NightwatchBrowser) {
browser
.clearConsole()
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
.end()
},
'Should debug the call': function(browser: NightwatchBrowser) {
browser
.debugTransaction(0)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"7")]',
timeout: 60000
})
.goToVMTraceStep(129)
.waitForElementContainsText('*[data-id="functionPanel"]', 'version()', 60000)
.end()
}
}
@ -250,11 +271,11 @@ const sources = [
'myTokenV1.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
@ -263,9 +284,9 @@ const sources = [
_disableInitializers();
}
function initialize() initializer public {
function initialize(address initialOwner) initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init();
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
@ -280,6 +301,8 @@ const sources = [
}, {
'myTokenV2.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./myTokenV1.sol";
contract MyTokenV2 is MyToken {
@ -293,11 +316,11 @@ const sources = [
'initializeProxy.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
@ -306,9 +329,9 @@ const sources = [
_disableInitializers();
}
function initialize(string memory tokenName, string memory tokenSymbol) initializer public {
function initialize(string memory tokenName, string memory tokenSymbol, address initialOwner) initializer public {
__ERC721_init(tokenName, tokenSymbol);
__Ownable_init();
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}

@ -104,7 +104,7 @@ module.exports = {
})
.addFile('test_import_node_modules_with_github_import.sol', sources[4]['test_import_node_modules_with_github_import.sol'])
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.0 (master branch)
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20
.testContracts('test_import_node_modules_with_github_import.sol', sources[4]['test_import_node_modules_with_github_import.sol'], ['ERC20', 'test11'])
},
'Static Analysis run with remixd #group3': '' + function (browser) {

@ -22,15 +22,10 @@ module.exports = {
.rightClick('*[data-id="treeViewLitreeViewItemsecondContract.sol"]')
.click('*[id="menuitemgeneratecustomaction"')
.waitForElementVisible('*[id="sol-uml-gen"]')
.isVisible('*[data-id="treeViewLitreeViewItemsecondContract_flattened.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsecondContract_flattened.sol"]')
},
'Zoom into uml diagram #group1': function (browser: NightwatchBrowser) {
browser.addFile('secondContract.sol', sources[1]['secondContract.sol'])
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsecondContract.sol"')
.pause(3000)
.rightClick('*[data-id="treeViewLitreeViewItemsecondContract.sol"]')
.click('*[id="menuitemgeneratecustomaction"')
.waitForElementVisible('*[id="sol-uml-gen"]')
browser
.click('*[data-id="umlZoominbtn"]')
}
}
@ -182,37 +177,38 @@ contract Ballot {
{
'secondContract.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
// SPDX-License-Identifier: GPL-3.0
function initialize() initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init();
__UUPSUpgradeable_init();
}
pragma solidity ^0.5.9;
function safeMint(address to, uint256 tokenId) public onlyOwner {
_safeMint(to, tokenId);
1 + 1;
}
import "@0x/contracts-erc20/contracts/src/ERC20Token.sol";
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
/**
* @title SampleERC20
* @dev Create a sample ERC20 standard token
*/
contract SampleERC20 is ERC20Token {
string public name;
string public symbol;
uint256 public decimals;
constructor (
string memory _name,
string memory _symbol,
uint256 _decimals,
uint256 _totalSupply
)
public
{
name = _name;
symbol = _symbol;
decimals = _decimals;
_totalSupply = _totalSupply;
balances[msg.sender] = _totalSupply;
}
}
`}
}
]

@ -38,7 +38,7 @@ module.exports = {
'Test GitHub Import - from master branch #group1': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.0 (master branch)
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20 (master branch)
.addFile('Untitled4.sol', sources[3]['Untitled4.sol'])
.clickLaunchIcon('filePanel')
.verifyContracts(['test7', 'ERC20'], { wait: 10000 })
@ -54,7 +54,7 @@ module.exports = {
'Test GitHub Import - no branch specified #group2': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.0 (master branch)
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20 (master branch)
.clickLaunchIcon('filePanel')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"')
.addFile('Untitled6.sol', sources[5]['Untitled6.sol'])
@ -64,7 +64,7 @@ module.exports = {
'Test GitHub Import - raw URL #group4': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.0 (master branch)
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20 (master branch)
.clickLaunchIcon('filePanel')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"')
.addFile('Untitled7.sol', sources[6]['Untitled7.sol'])

@ -238,8 +238,8 @@ module.exports = {
})
.addFile('tests/hhLogs_test.sol', sources[0]['tests/hhLogs_test.sol'])
.clickLaunchIcon('solidityUnitTesting')
.waitForElementVisible('*[id="singleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="singleTesttests/Ballot_test.sol"]')
.waitForElementVisible('*[id="idsingleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="idsingleTesttests/Ballot_test.sol"]')
.click('#runTestsTabRunAction')
.pause(2000)
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
@ -261,8 +261,8 @@ module.exports = {
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.addFile('tests/ballotFailedLog_test.sol', sources[0]['tests/ballotFailedLog_test.sol'])
.clickLaunchIcon('solidityUnitTesting')
.waitForElementVisible('*[id="singleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="singleTesttests/Ballot_test.sol"]')
.waitForElementVisible('*[id="idsingleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="idsingleTesttests/Ballot_test.sol"]')
.click('#runTestsTabRunAction')
.pause(2000)
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
@ -278,8 +278,8 @@ module.exports = {
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.addFile('tests/ballotFailedDebug_test.sol', sources[0]['tests/ballotFailedDebug_test.sol'])
.clickLaunchIcon('solidityUnitTesting')
.waitForElementVisible('*[id="singleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="singleTesttests/Ballot_test.sol"]')
.waitForElementVisible('*[id="idsingleTesttests/Ballot_test.sol"]', 60000)
.click('*[id="idsingleTesttests/Ballot_test.sol"]')
.click('#runTestsTabRunAction')
.waitForElementVisible('*[data-id="testTabSolidityUnitTestsOutputheader"]', 120000)
.waitForElementContainsText('#solidityUnittestsOutput', 'tests/ballotFailedDebug_test.sol', 60000)

@ -33,29 +33,32 @@ module.exports = {
},
'run analysis and filter results': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.clickLaunchIcon('solidity')
.click('*[id="compileBtn"]')
.pause(10000)
.clickLaunchIcon('solidityStaticAnalysis')
.click('*[id="staticAnalysisRunBtn"]')
.waitForElementPresent('#staticanalysisresult .warning', 5000)
// Check warning count
.click('*[data-rb-event-key="remix"]')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '1')
.verify.elementPresent('input[name="showLibWarnings"]')
.verify.not.elementPresent('input[name="showLibWarnings"]:checked')
.verify.elementPresent('label[id="headingshowLibWarnings"]')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.click('*[data-rb-event-key="remix"]')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '382')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '1')
.end()
.clickLaunchIcon('filePanel')
.click('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.clickLaunchIcon('solidity')
.click('*[id="compileBtn"]')
.pause(10000)
.clickLaunchIcon('solidityStaticAnalysis')
.useXpath()
.click('//*[@id="staticAnalysisRunBtn"]')
// .waitForElementPresent('div#staticanalysisresult .warning', 5000)
.waitForElementPresent('//*[@id="staticanalysisresult"]', 5000)
.useCss()
// Check warning count
.click('*[data-rb-event-key="remix"]')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '1')
.verify.elementPresent('input[name="showLibWarnings"]')
.verify.not.elementPresent('input[name="showLibWarnings"]:checked')
.verify.elementPresent('label[id="headingshowLibWarnings"]')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.click('*[data-rb-event-key="remix"]')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '386')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '1')
.end()
}
}
@ -72,8 +75,8 @@ function runTests (browser: NightwatchBrowser) {
'Fallback function of contract TooMuchGas requires too much gas',
'TooMuchGas.() : Variables have very similar names "test" and "test1".',
'TooMuchGas.() : Variables have very similar names "test" and "test1".'],
'#staticanalysisresult .warning',
browser
'#staticanalysisresult .warning',
browser
)
})
}

@ -97,7 +97,7 @@ module.exports = {
.switchEnvironment('vm-london')
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickLaunchIcon('filePanel')
.click('*[data-id="treeViewDivtreeViewItem"]') // make sure we create the file at the root folder
.click('*[data-id="treeViewDivMenu"]') // make sure we create the file at the root folder
.addFile('deployWithEthersJs.js', { content: deployWithEthersJs })
// .openFile('deployWithEthersJs.js')
.pause(1000)
@ -562,7 +562,7 @@ describe("Storage", function () {
contractName: 'StorageWithLib',
sourceName: 'contracts/StorageWithLib.sol',
abi: metadata.abi,
bytecode: '0x' + metadata.data.bytecode.object,
bytecode: metadata.data.bytecode.object,
deployedBytecode: '0x' + metadata.data.deployedBytecode.object,
linkReferences: metadata.data.bytecode.linkReferences,
deployedLinkReferences: metadata.data.deployedBytecode.linkReferences,
@ -700,7 +700,7 @@ const scriptAutoExec = {
contractName: 'Lib',
sourceName: 'contracts/1_Storage.sol',
abi: metadataLib.abi,
bytecode: '0x' + metadataLib.data.bytecode.object,
bytecode: metadataLib.data.bytecode.object,
deployedBytecode: '0x' + metadataLib.data.deployedBytecode.object,
linkReferences: metadataLib.data.bytecode.linkReferences,
deployedLinkReferences: metadataLib.data.deployedBytecode.linkReferences,
@ -720,7 +720,7 @@ const scriptAutoExec = {
contractName: 'Storage',
sourceName: 'contracts/1_Storage.sol',
abi: metadata.abi,
bytecode: '0x' + metadata.data.bytecode.object,
bytecode: metadata.data.bytecode.object,
deployedBytecode: '0x' + metadata.data.deployedBytecode.object,
linkReferences: metadata.data.bytecode.linkReferences,
deployedLinkReferences: metadata.data.deployedBytecode.linkReferences,

@ -15,7 +15,7 @@ module.exports = {
browser.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['TestContract'])
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.clickFunction('f - transact (not payable)')
.testFunction('last',
@ -40,7 +40,7 @@ module.exports = {
'Test Complex Return Values #group1': function (browser: NightwatchBrowser) {
browser.testContracts('returnValues.sol', sources[1]['returnValues.sol'], ['testReturnValues'])
.clickLaunchIcon('udapp')
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.clickFunction('retunValues1 - transact (not payable)')
.testFunction('last',
@ -84,7 +84,7 @@ module.exports = {
'Test Complex Input Values #group2': function (browser: NightwatchBrowser) {
browser.testContracts('inputValues.sol', sources[2]['inputValues.sol'], ['test'])
.clickLaunchIcon('udapp')
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.clickFunction('inputValue1 - transact (not payable)', { types: 'uint256 _u, int256 _i, string _str', values: '"2343242", "-4324324", "string _ string _ string _ string _ string _ string _ string _ string _ string _ string _"' })
.testFunction('last',
@ -129,7 +129,7 @@ module.exports = {
browser.testContracts('eventFunctionInput.sol', sources[3]['eventFunctionInput.sol'], ['C'])
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.click('*[data-id="deployAndRunClearInstances"]')
},
@ -137,7 +137,7 @@ module.exports = {
'Should use scientific notation as parameters #group2': function (browser: NightwatchBrowser) {
browser.testContracts('scientific_notation.sol', sources[8]['scientific_notation.sol'], ['test'])
.clickLaunchIcon('udapp')
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.clickFunction('inputValue1 - transact (not payable)', { types: 'uint256 _u, int256 _i', values: '"101e3", "-1.13e4"' })
.waitForElementContainsText('*[data-id="terminalJournal"]', '101000', 60000)
@ -154,7 +154,7 @@ module.exports = {
browser.testContracts('customError.sol', sources[4]['customError.sol'], ['C'])
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:')
@ -176,7 +176,7 @@ module.exports = {
.clearTransactions()
.switchEnvironment('vm-london') // switch to London fork
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:')
@ -194,7 +194,7 @@ module.exports = {
'Should Compile and Deploy a contract which define a custom error in a library, the error should be logged in the terminal #group3': function (browser: NightwatchBrowser) {
browser.testContracts('customErrorLib.sol', sources[5]['customErrorLib.sol'], ['D'])
.clickLaunchIcon('udapp')
.click('.udapp_contractActionsContainerSingle > button')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(1)
.clickFunction('h - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:')

@ -10,11 +10,11 @@ const sources = [
'myTokenV1.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
@ -23,9 +23,9 @@ const sources = [
_disableInitializers();
}
function initialize() initializer public {
function initialize(address initialOwner) initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init();
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
@ -35,6 +35,7 @@ const sources = [
override
{}
}
`
}
}
@ -166,7 +167,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]',
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
@ -174,7 +175,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]',
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
@ -182,7 +183,7 @@ module.exports = {
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/proxy/beacon/IBeaconUpgradeable.sol"]',
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
})
.clickLaunchIcon('solidity')

@ -333,7 +333,7 @@ module.exports = {
.click('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.pause(1000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`contract MyToken is Initializable, ERC1155Upgradeable, OwnableUpgradeable, PausableUpgradeable, ERC1155BurnableUpgradeable, UUPSUpgradeable {`) !== -1,
browser.assert.ok(content.indexOf(`contract MyToken is Initializable, ERC1155Upgradeable, OwnableUpgradeable, ERC1155PausableUpgradeable, ERC1155BurnableUpgradeable, UUPSUpgradeable {`) !== -1,
'Incorrect content')
})
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')

@ -1,105 +1,105 @@
// Merge custom command types with nightwatch types
/* eslint-disable no-use-before-define */
import { NightwatchBrowser } from 'nightwatch' // eslint-disable-line @typescript-eslint/no-unused-vars
export type callbackCheckVerifyCallReturnValue = (values: string[]) => { message: string, pass: boolean }
import {NightwatchBrowser} from 'nightwatch' // eslint-disable-line @typescript-eslint/no-unused-vars
export type callbackCheckVerifyCallReturnValue = (values: string[]) => {message: string; pass: boolean}
declare module 'nightwatch' {
export interface NightwatchCustomCommands {
clickLaunchIcon(icon: string): NightwatchBrowser,
switchBrowserTab(index: number): NightwatchBrowser,
scrollAndClick(target: string): NightwatchBrowser,
scrollInto(target: string): NightwatchBrowser,
testContracts(fileName: string, contractCode: NightwatchContractContent, compiledContractNames: string[]): NightwatchBrowser,
setEditorValue(value: string, callback?: () => void): NightwatchBrowser,
addFile(name: string, content: NightwatchContractContent): NightwatchBrowser,
verifyContracts(compiledContractNames: string[], opts?: { wait: number, version?: string, runs?: string }): NightwatchBrowser,
selectAccount(account?: string): NightwatchBrowser,
clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser,
testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser,
goToVMTraceStep(step: number, incr?: number): NightwatchBrowser,
checkVariableDebug(id: string, debugValue: NightwatchCheckVariableDebugValue): NightwatchBrowser,
addAtAddressInstance(address: string, isValidFormat: boolean, isValidChecksum: boolean, isAbi?: boolean): NightwatchBrowser,
modalFooterOKClick(id?: string): NightwatchBrowser,
clickInstance(index: number): NightwatchBrowser,
journalLastChildIncludes(val: string): NightwatchBrowser,
executeScriptInTerminal(script: string): NightwatchBrowser,
clearEditableContent(cssSelector: string): NightwatchBrowser,
journalChildIncludes(val: string, opts = { shouldHaveOnlyOneOccurence: boolean }): NightwatchBrowser,
debugTransaction(index: number): NightwatchBrowser,
checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser,
openFile(name: string): NightwatchBrowser,
refreshPage(): NightwatchBrowser,
verifyLoad(): NightwatchBrowser,
renamePath(path: string, newFileName: string, renamedPath: string): NightwatchBrowser,
rightClickCustom(cssSelector: string): NightwatchBrowser,
scrollToLine(line: number): NightwatchBrowser,
waitForElementContainsText(id: string, value: string, timeout?: number): NightwatchBrowser,
getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser,
modalFooterCancelClick(id?: string): NightwatchBrowser,
selectContract(contractName: string): NightwatchBrowser,
createContract(inputParams: string): NightwatchBrowser,
getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser,
testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser,
getEditorValue(callback: (content: string) => void): NightwatchBrowser,
getInstalledPlugins(cb: (plugins: string[]) => void): NightwatchBrowser,
verifyCallReturnValue(address: string, checks: string[] | callbackCheckVerifyCallReturnValue): NightwatchBrowser,
testEditorValue(testvalue: string): NightwatchBrowser,
removeFile(path: string, workspace: string): NightwatchBrowser,
switchBrowserWindow(url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult<Window>) => void): NightwatchBrowser,
setupMetamask(passphrase: string, password: string): NightwatchBrowser,
signMessage(msg: string, callback: (hash: { value: string }, signature: { value: string }) => void): NightwatchBrowser,
setSolidityCompilerVersion(version: string): NightwatchBrowser,
clickElementAtPosition(cssSelector: string, index: number, opt?: { forceSelectIfUnselected: boolean }): NightwatchBrowser,
notContainsText(cssSelector: string, text: string): NightwatchBrowser,
sendLowLevelTx(address: string, value: string, callData: string): NightwatchBrowser,
journalLastChild(val: string): NightwatchBrowser,
checkTerminalFilter(filter: string, test: string): NightwatchBrowser,
noWorkerErrorFor(version: string): NightwatchBrowser,
validateValueInput(selector: string, valueTosSet: string, expectedValue: string): NightwatchBrowser
checkAnnotations(type: string, line: number): NightwatchBrowser
checkAnnotationsNotPresent(type: string): NightwatchBrowser
getLastTransactionHash(callback: (hash: string) => void)
currentWorkspaceIs(name: string): NightwatchBrowser
addLocalPlugin(this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile): NightwatchBrowser
acceptAndRemember (this: NightwatchBrowser, remember: boolean, accept: boolean): NightwatchBrowser
clearConsole (this: NightwatchBrowser): NightwatchBrowser
clearTransactions (this: NightwatchBrowser): NightwatchBrowser
getBrowserLogs (this: NightwatchBrowser): NightwatchBrowser
currentSelectedFileIs (name: string): NightwatchBrowser,
switchWorkspace: (workspaceName: string) => NightwatchBrowser
switchEnvironment: (provider: string) => NightwatchBrowser
connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser
}
export interface NightwatchCustomCommands {
clickLaunchIcon(icon: string): NightwatchBrowser
switchBrowserTab(index: number): NightwatchBrowser
scrollAndClick(target: string): NightwatchBrowser
scrollInto(target: string): NightwatchBrowser
testContracts(fileName: string, contractCode: NightwatchContractContent, compiledContractNames: string[]): NightwatchBrowser
setEditorValue(value: string, callback?: () => void): NightwatchBrowser
addFile(name: string, content: NightwatchContractContent): NightwatchBrowser
verifyContracts(compiledContractNames: string[], opts?: {wait: number; version?: string; runs?: string}): NightwatchBrowser
selectAccount(account?: string): NightwatchBrowser
clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser
testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser
goToVMTraceStep(step: number, incr?: number): NightwatchBrowser
checkVariableDebug(id: string, debugValue: NightwatchCheckVariableDebugValue): NightwatchBrowser
addAtAddressInstance(address: string, isValidFormat: boolean, isValidChecksum: boolean, isAbi?: boolean): NightwatchBrowser
modalFooterOKClick(id?: string): NightwatchBrowser
clickInstance(index: number): NightwatchBrowser
journalLastChildIncludes(val: string): NightwatchBrowser
executeScriptInTerminal(script: string): NightwatchBrowser
clearEditableContent(cssSelector: string): NightwatchBrowser
journalChildIncludes(val: string, opts = {shouldHaveOnlyOneOccurence: boolean}): NightwatchBrowser
debugTransaction(index: number): NightwatchBrowser
checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser
openFile(name: string): NightwatchBrowser
refreshPage(): NightwatchBrowser
verifyLoad(): NightwatchBrowser
renamePath(path: string, newFileName: string, renamedPath: string): NightwatchBrowser
rightClickCustom(cssSelector: string): NightwatchBrowser
scrollToLine(line: number): NightwatchBrowser
waitForElementContainsText(id: string, value: string, timeout?: number): NightwatchBrowser
getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser
modalFooterCancelClick(id?: string): NightwatchBrowser
selectContract(contractName: string): NightwatchBrowser
createContract(inputParams: string): NightwatchBrowser
getAddressAtPosition(index: number, cb: (pos: string) => void): NightwatchBrowser
testConstantFunction(address: string, fnFullName: string, expectedInput: NightwatchTestConstantFunctionExpectedInput | null, expectedOutput: string): NightwatchBrowser
getEditorValue(callback: (content: string) => void): NightwatchBrowser
getInstalledPlugins(cb: (plugins: string[]) => void): NightwatchBrowser
verifyCallReturnValue(address: string, checks: string[] | callbackCheckVerifyCallReturnValue): NightwatchBrowser
testEditorValue(testvalue: string): NightwatchBrowser
removeFile(path: string, workspace: string): NightwatchBrowser
switchBrowserWindow(url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult<Window>) => void): NightwatchBrowser
setupMetamask(passphrase: string, password: string): NightwatchBrowser
signMessage(msg: string, callback: (hash: {value: string}, signature: {value: string}) => void): NightwatchBrowser
setSolidityCompilerVersion(version: string): NightwatchBrowser
clickElementAtPosition(cssSelector: string, index: number, opt?: {forceSelectIfUnselected: boolean}): NightwatchBrowser
notContainsText(cssSelector: string, text: string): NightwatchBrowser
sendLowLevelTx(address: string, value: string, callData: string): NightwatchBrowser
journalLastChild(val: string): NightwatchBrowser
checkTerminalFilter(filter: string, test: string): NightwatchBrowser
noWorkerErrorFor(version: string): NightwatchBrowser
validateValueInput(selector: string, valueTosSet: string, expectedValue: string): NightwatchBrowser
checkAnnotations(type: string): NightwatchBrowser
checkAnnotationsNotPresent(type: string): NightwatchBrowser
getLastTransactionHash(callback: (hash: string) => void)
currentWorkspaceIs(name: string): NightwatchBrowser
addLocalPlugin(this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile): NightwatchBrowser
acceptAndRemember(this: NightwatchBrowser, remember: boolean, accept: boolean): NightwatchBrowser
clearConsole(this: NightwatchBrowser): NightwatchBrowser
clearTransactions(this: NightwatchBrowser): NightwatchBrowser
getBrowserLogs(this: NightwatchBrowser): NightwatchBrowser
currentSelectedFileIs(name: string): NightwatchBrowser
switchWorkspace: (workspaceName: string) => NightwatchBrowser
switchEnvironment: (provider: string) => NightwatchBrowser
connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser
}
export interface NightwatchBrowser {
api: this,
emit: (status: string) => void,
fullscreenWindow: (result?: any) => this,
keys(keysToSend: 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 NightwatchBrowser {
api: this
emit: (status: string) => void
fullscreenWindow: (result?: any) => this
keys(keysToSend: 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 NightwatchAPI {
keys(keysToSend: string, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void): NightwatchAPI
}
export interface NightwatchContractContent {
content: string;
}
export interface NightwatchContractContent {
content: string
}
export interface NightwatchClickFunctionExpectedInput {
types: string,
values: string
}
export interface NightwatchClickFunctionExpectedInput {
types: string
values: string
}
export interface NightwatchTestFunctionExpectedInput {
[key: string]: any
}
export interface NightwatchTestFunctionExpectedInput {
[key: string]: any
}
export interface NightwatchTestConstantFunctionExpectedInput {
types: string,
values: string
}
export interface NightwatchTestConstantFunctionExpectedInput {
types: string
values: string
}
export type NightwatchCheckVariableDebugValue = NightwatchTestFunctionExpectedInput
export type NightwatchCheckVariableDebugValue = NightwatchTestFunctionExpectedInput
}

File diff suppressed because it is too large Load Diff

@ -2,9 +2,9 @@
'use strict'
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.storage.sync.set({ 'chrome-app-sync': true })
chrome.storage.sync.set({'chrome-app-sync': true})
chrome.tabs.create({ 'url': chrome.extension.getURL('index.html') }, function (tab) {
chrome.tabs.create({url: chrome.extension.getURL('index.html')}, function (tab) {
// tab opened
})
})

@ -5,8 +5,8 @@ const { exit } = require('process');
const child = child_process.spawnSync('grep -r --include="*.json" --include="*.ts" --include="*.tsx" "+commit" apps/**/* libs/**/*', [], { encoding: 'utf8', cwd: process.cwd(), shell: true });
if (child.error) {
console.log("ERROR: ", child);
exit(1);
console.log("ERROR: ", child);
exit(1);
}
@ -15,63 +15,63 @@ let soljson =[];
const quotedVersionsRegex = /['"v]\d*\.\d*\.\d*\+commit\.[\d\w]*/g;
let quotedVersionsRegexMatch = child.stdout.match(quotedVersionsRegex)
if(quotedVersionsRegexMatch){
let soljson2 = quotedVersionsRegexMatch.map((item) => item.replace('\'', 'v').replace('"', 'v'))
console.log('non nightly soljson versions found: ', soljson2);
if(soljson2) soljson = soljson.concat(soljson2);
let soljson2 = quotedVersionsRegexMatch.map((item) => item.replace('\'', 'v').replace('"', 'v'))
console.log('non nightly soljson versions found: ', soljson2);
if(soljson2) soljson = soljson.concat(soljson2);
}
const nightlyVersionsRegex = /\d*\.\d*\.\d-nightly.*\+commit\.[\d\w]*/g
const nightlyVersionsRegexMatch = child.stdout.match(nightlyVersionsRegex)
if(nightlyVersionsRegexMatch){
let soljson3 = nightlyVersionsRegexMatch.map((item) => 'v' + item);
console.log('nightly soljson versions found: ', soljson3);
if(soljson3) soljson = soljson.concat(soljson3);
let soljson3 = nightlyVersionsRegexMatch.map((item) => 'v' + item);
console.log('nightly soljson versions found: ', soljson3);
if(soljson3) soljson = soljson.concat(soljson3);
}
if (soljson) {
// filter out duplicates
soljson = soljson.filter((item, index) => soljson.indexOf(item) === index);
// manually add some versions
soljson.push('v0.7.6+commit.7338295f');
console.log('soljson versions found: ', soljson, soljson.length);
for (let i = 0; i < soljson.length; i++) {
const version = soljson[i];
if (version) {
let url = ''
// if nightly
if (version.includes('nightly')) {
url = `https://binaries.soliditylang.org/bin/soljson-${version}.js`;
}else{
url = `https://binaries.soliditylang.org/wasm/soljson-${version}.js`;
}
const dir = './dist/apps/remix-ide/assets/js/soljson';
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const path = `./dist/apps/remix-ide/assets/js/soljson/soljson-${version}.js`;
// check if the file exists
const exists = fs.existsSync(path);
if (!exists) {
console.log('URL:', url)
try {
// use curl to download the file
child_process.exec(`curl -o ${path} ${url}`, { encoding: 'utf8', cwd: process.cwd(), shell: true })
} catch (e) {
console.log('Failed to download soljson' + version + ' from ' + url)
}
}
// filter out duplicates
soljson = soljson.filter((item, index) => soljson.indexOf(item) === index);
// manually add some versions
soljson.push('v0.7.6+commit.7338295f');
console.log('soljson versions found: ', soljson, soljson.length);
for (let i = 0; i < soljson.length; i++) {
const version = soljson[i];
if (version) {
let url = ''
// if nightly
if (version.includes('nightly')) {
url = `https://binaries.soliditylang.org/bin/soljson-${version}.js`;
}else{
url = `https://binaries.soliditylang.org/wasm/soljson-${version}.js`;
}
const dir = './dist/apps/remix-ide/assets/js/soljson';
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const path = `./dist/apps/remix-ide/assets/js/soljson/soljson-${version}.js`;
// check if the file exists
const exists = fs.existsSync(path);
if (!exists) {
console.log('URL:', url)
try {
// use curl to download the file
child_process.exec(`curl -o ${path} ${url}`, { encoding: 'utf8', cwd: process.cwd(), shell: true })
} catch (e) {
console.log('Failed to download soljson' + version + ' from ' + url)
}
}
}
}
}

@ -5,15 +5,15 @@ const file = fs.readFileSync('projects.json')
const projects = JSON.parse(file)
console.log(Object.keys(projects.graph.nodes))
for(let node of Object.keys(projects.graph.nodes)){
if(projects.graph.nodes[node].data.targets.lint){
if(projects.graph.nodes[node].data.targets.lint){
console.log(projects.graph.nodes[node].data.name)
const result = spawnSync('yarn', ['lint', projects.graph.nodes[node].data.name])
if(result.status == 0){
console.log('success')
console.log('success')
}else{
console.log(result.stdout.toString())
console.log(result.stderr.toString())
exit(1)
console.log(result.stdout.toString())
console.log(result.stderr.toString())
exit(1)
}
}
}
}

@ -7,21 +7,21 @@ let args = process.argv.slice(2)
const jobsize = args[0] || 10;
const job = args[1] || 0;
exec(cmd, (error, stdout, stderr) => {
if (error) {
console.error(`error: ${error.message}`);
return;
}
if (error) {
console.error(`error: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
let files = stdout.split('\n').filter(f => f.includes('.test')).map(f => f.replace('dist/apps/remix-ide-e2e/src/tests/', '')).map(f => f.replace('.js', ''))
let splitIndex = Math.ceil(files.length / jobsize);
const parts = []
for (let i = 0; i < jobsize; i++) {
parts.push(files.slice(i * splitIndex, (i + 1) * splitIndex))
}
console.log(parts[job].join('\n'))
});
let files = stdout.split('\n').filter(f => f.includes('.test')).map(f => f.replace('dist/apps/remix-ide-e2e/src/tests/', '')).map(f => f.replace('.js', ''))
let splitIndex = Math.ceil(files.length / jobsize);
const parts = []
for (let i = 0; i < jobsize; i++) {
parts.push(files.slice(i * splitIndex, (i + 1) * splitIndex))
}
console.log(parts[job].join('\n'))
});

@ -3,7 +3,7 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/remix-ide/src",
"projectType": "application",
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect"],
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect", "circuit-compiler"],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",

@ -45,9 +45,9 @@ This is not strictly speaking a release. Updating the remix site is done through
- git reset --hard -master-commit-hash-
- git push -f origin remix_live
CircleCI will build automaticaly and remix.ethereum.org will be updated
CircleCI will build automatically and remix.ethereum.org will be updated
# remix-alpha.ethereum.org update
remix-alpha.ethereum.org is automaticaly updated every time commits are pushed to master
remix-alpha.ethereum.org is automatically updated every time commits are pushed to master

@ -1,26 +1,26 @@
'use strict'
import { RunTab, makeUdapp } from './app/udapp'
import { RemixEngine } from './remixEngine'
import { RemixAppManager } from './remixAppManager'
import { ThemeModule } from './app/tabs/theme-module'
import { LocaleModule } from './app/tabs/locale-module'
import { NetworkModule } from './app/tabs/network-module'
import { Web3ProviderModule } from './app/tabs/web3-provider'
import { CompileAndRun } from './app/tabs/compile-and-run'
import { SidePanel } from './app/components/side-panel'
import { HiddenPanel } from './app/components/hidden-panel'
import { VerticalIcons } from './app/components/vertical-icons'
import { LandingPage } from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel'
import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin'
import { AstWalker } from '@remix-project/remix-astwalker'
import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin'
import { CodeParser } from './app/plugins/parser/code-parser'
import { SolidityScript } from './app/plugins/solidity-script'
import { WalkthroughService } from './walkthroughService'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler } from '@remix-project/core-plugin'
import {RunTab, makeUdapp} from './app/udapp'
import {RemixEngine} from './remixEngine'
import {RemixAppManager} from './remixAppManager'
import {ThemeModule} from './app/tabs/theme-module'
import {LocaleModule} from './app/tabs/locale-module'
import {NetworkModule} from './app/tabs/network-module'
import {Web3ProviderModule} from './app/tabs/web3-provider'
import {CompileAndRun} from './app/tabs/compile-and-run'
import {SidePanel} from './app/components/side-panel'
import {HiddenPanel} from './app/components/hidden-panel'
import {VerticalIcons} from './app/components/vertical-icons'
import {LandingPage} from './app/ui/landing-page/landing-page'
import {MainPanel} from './app/components/main-panel'
import {PermissionHandlerPlugin} from './app/plugins/permission-handler-plugin'
import {AstWalker} from '@remix-project/remix-astwalker'
import {LinkLibraries, DeployLibraries, OpenZeppelinProxy} from '@remix-project/core-plugin'
import {CodeParser} from './app/plugins/parser/code-parser'
import {SolidityScript} from './app/plugins/solidity-script'
import {WalkthroughService} from './walkthroughService'
import {OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler} from '@remix-project/core-plugin'
import Registry from './app/state/registry'
import { ConfigPlugin } from './app/plugins/config'
@ -53,6 +53,31 @@ import { electronTemplates } from './app/plugins/electron/templatesPlugin'
import { xtermPlugin } from './app/plugins/electron/xtermPlugin'
import {ConfigPlugin} from './app/plugins/config'
import {StoragePlugin} from './app/plugins/storage'
import {Layout} from './app/panels/layout'
import {NotificationPlugin} from './app/plugins/notification'
import {Blockchain} from './blockchain/blockchain'
import {MergeVMProvider, LondonVMProvider, BerlinVMProvider, ShanghaiVMProvider} from './app/providers/vm-provider'
import {MainnetForkVMProvider} from './app/providers/mainnet-vm-fork-provider'
import {SepoliaForkVMProvider} from './app/providers/sepolia-vm-fork-provider'
import {GoerliForkVMProvider} from './app/providers/goerli-vm-fork-provider'
import {CustomForkVMProvider} from './app/providers/custom-vm-fork-provider'
import {HardhatProvider} from './app/providers/hardhat-provider'
import {GanacheProvider} from './app/providers/ganache-provider'
import {FoundryProvider} from './app/providers/foundry-provider'
import {ExternalHttpProvider} from './app/providers/external-http-provider'
import {InjectedProviderDefault} from './app/providers/injected-provider-default'
import {InjectedProviderTrustWallet} from './app/providers/injected-provider-trustwallet'
import {Injected0ptimismProvider} from './app/providers/injected-optimism-provider'
import {InjectedArbitrumOneProvider} from './app/providers/injected-arbitrum-one-provider'
import {InjectedEphemeryTestnetProvider} from './app/providers/injected-ephemery-testnet-provider'
import {InjectedSKALEChaosTestnetProvider} from './app/providers/injected-skale-chaos-testnet-provider'
import {FileDecorator} from './app/plugins/file-decorator'
import {CodeFormat} from './app/plugins/code-format'
import {SolidityUmlGen} from './app/plugins/solidity-umlgen'
import {ContractFlattener} from './app/plugins/contractFlattener'
import {OpenAIGpt} from './app/plugins/openaigpt'
const isElectron = require('is-electron')
@ -61,6 +86,8 @@ const remixLib = require('@remix-project/remix-lib')
import { QueryParams } from '@remix-project/remix-lib'
import { SearchPlugin } from './app/tabs/search'
import { ElectronProvider } from './app/files/electronProvider'
import {QueryParams} from '@remix-project/remix-lib'
import {SearchPlugin} from './app/tabs/search'
const Storage = remixLib.Storage
const RemixDProvider = require('./app/files/remixDProvider')
@ -76,12 +103,12 @@ const PluginManagerComponent = require('./app/components/plugin-manager-componen
const CompileTab = require('./app/tabs/compile-tab')
const SettingsTab = require('./app/tabs/settings-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 FilePanel = require('./app/panels/file-panel')
const Editor = require('./app/editor/editor')
const Terminal = require('./app/panels/terminal')
const { TabProxy } = require('./app/panels/tab-proxy.js')
const {TabProxy} = require('./app/panels/tab-proxy.js')
@ -96,7 +123,7 @@ class AppComponent {
// load app config
const config = new Config(configStorage)
Registry.getInstance().put({ api: config, name: 'config' })
Registry.getInstance().put({api: config, name: 'config'})
// load file system
this._components.filesProviders = {}
@ -105,9 +132,7 @@ class AppComponent {
api: this._components.filesProviders.browser,
name: 'fileproviders/browser'
})
this._components.filesProviders.localhost = new RemixDProvider(
this.appManager
)
this._components.filesProviders.localhost = new RemixDProvider(this.appManager)
Registry.getInstance().put({
api: this._components.filesProviders.localhost,
name: 'fileproviders/localhost'
@ -137,7 +162,7 @@ class AppComponent {
this.panels = {}
this.workspace = pluginLoader.get()
this.engine = new RemixEngine()
this.engine.register(appManager);
this.engine.register(appManager)
const matomoDomains = {
'remix-alpha.ethereum.org': 27,
@ -145,15 +170,8 @@ class AppComponent {
'remix.ethereum.org': 23,
'6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop
}
this.showMatamo =
matomoDomains[window.location.hostname] &&
!Registry.getInstance()
.get('config')
.api.exists('settings/matomo-analytics')
this.walkthroughService = new WalkthroughService(
appManager,
this.showMatamo
)
this.showMatamo = matomoDomains[window.location.hostname] && !Registry.getInstance().get('config').api.exists('settings/matomo-analytics')
this.walkthroughService = new WalkthroughService(appManager, this.showMatamo)
const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080']
// workaround for Electron support
@ -171,19 +189,20 @@ class AppComponent {
this.themeModule = new ThemeModule()
// ----------------- locale service ---------------------------------
this.localeModule = new LocaleModule()
Registry.getInstance().put({ api: this.themeModule, name: 'themeModule' })
Registry.getInstance().put({ api: this.localeModule, name: 'localeModule' })
Registry.getInstance().put({api: this.themeModule, name: 'themeModule'})
Registry.getInstance().put({api: this.localeModule, name: 'localeModule'})
// ----------------- editor service ----------------------------
const editor = new Editor() // wrapper around ace editor
Registry.getInstance().put({ api: editor, name: 'editor' })
editor.event.register('requiringToSaveCurrentfile', () =>
Registry.getInstance().put({api: editor, name: 'editor'})
editor.event.register('requiringToSaveCurrentfile', (currentFile) => {
fileManager.saveCurrentFile()
)
if (currentFile.endsWith('.circom')) this.appManager.activatePlugin(['circuit-compiler'])
})
// ----------------- fileManager service ----------------------------
const fileManager = new FileManager(editor, appManager)
Registry.getInstance().put({ api: fileManager, name: 'filemanager' })
Registry.getInstance().put({api: fileManager, name: 'filemanager'})
// ----------------- dGit provider ---------------------------------
const dGitProvider = new DGitProvider()
@ -208,6 +227,9 @@ class AppComponent {
// ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener()
// ----------------- Open AI --------------------------------------
const openaigpt = new OpenAIGpt()
// ----------------- import content service ------------------------
const contentImport = new CompilerImports()
@ -241,9 +263,11 @@ class AppComponent {
const foundryProvider = new FoundryProvider(blockchain)
const externalHttpProvider = new ExternalHttpProvider(blockchain)
const trustWalletInjectedProvider = new InjectedProviderTrustWallet()
const defaultInjectedProvider = new InjectedProviderDefault
const defaultInjectedProvider = new InjectedProviderDefault()
const injected0ptimismProvider = new Injected0ptimismProvider()
const injectedArbitrumOneProvider = new InjectedArbitrumOneProvider()
const injectedEphemeryTestnetProvider = new InjectedEphemeryTestnetProvider()
const injectedSKALEChaosTestnetProvider = new InjectedSKALEChaosTestnetProvider()
// ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({
@ -253,11 +277,11 @@ class AppComponent {
// ----------------- run script after each compilation results -----------
const compileAndRun = new CompileAndRun()
// -------------------Terminal----------------------------------------
makeUdapp(blockchain, compilersArtefacts, domEl => terminal.logHtml(domEl))
makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl))
const terminal = new Terminal(
{ appManager, blockchain },
{appManager, blockchain},
{
getPosition: event => {
getPosition: (event) => {
const limitUp = 36
const limitDown = 20
const height = window.innerHeight
@ -320,12 +344,15 @@ class AppComponent {
trustWalletInjectedProvider,
injected0ptimismProvider,
injectedArbitrumOneProvider,
injectedEphemeryTestnetProvider,
injectedSKALEChaosTestnetProvider,
this.walkthroughService,
search,
solidityumlgen,
contractFlattener,
solidityScript,
templates
templates,
openaigpt
])
//---- fs plugin
@ -344,7 +371,7 @@ class AppComponent {
// LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel()
Registry.getInstance().put({ api: this.mainview, name: 'mainview' })
Registry.getInstance().put({api: this.mainview, name: 'mainview'})
const tabProxy = new TabProxy(fileManager, editor)
this.engine.register([appPanel, tabProxy])
@ -353,42 +380,18 @@ class AppComponent {
this.sidePanel = new SidePanel()
this.hiddenPanel = new HiddenPanel()
const pluginManagerComponent = new PluginManagerComponent(
appManager,
this.engine
)
const pluginManagerComponent = new PluginManagerComponent(appManager, this.engine)
const filePanel = new FilePanel(appManager)
const landingPage = new LandingPage(
appManager,
this.menuicons,
fileManager,
filePanel,
contentImport
)
this.settings = new SettingsTab(
Registry.getInstance().get('config').api,
editor,
appManager
)
const landingPage = new LandingPage(appManager, this.menuicons, fileManager, filePanel, contentImport)
this.settings = new SettingsTab(Registry.getInstance().get('config').api, editor, appManager)
this.engine.register([
this.menuicons,
landingPage,
this.hiddenPanel,
this.sidePanel,
filePanel,
pluginManagerComponent,
this.settings
])
this.engine.register([this.menuicons, landingPage, this.hiddenPanel, this.sidePanel, filePanel, pluginManagerComponent, this.settings])
// CONTENT VIEWS & DEFAULT PLUGINS
const openZeppelinProxy = new OpenZeppelinProxy(blockchain)
const linkLibraries = new LinkLibraries(blockchain)
const deployLibraries = new DeployLibraries(blockchain)
const compileTab = new CompileTab(
Registry.getInstance().get('config').api,
Registry.getInstance().get('filemanager').api
)
const compileTab = new CompileTab(Registry.getInstance().get('config').api, Registry.getInstance().get('filemanager').api)
const run = new RunTab(
blockchain,
Registry.getInstance().get('config').api,
@ -428,10 +431,10 @@ class AppComponent {
])
this.layout.panels = {
tabs: { plugin: tabProxy, active: true },
editor: { plugin: editor, active: true },
main: { plugin: appPanel, active: false },
terminal: { plugin: terminal, active: true, minimized: false }
tabs: {plugin: tabProxy, active: true},
editor: {plugin: editor, active: true},
main: {plugin: appPanel, active: false},
terminal: {plugin: terminal, active: true, minimized: false}
}
}
@ -447,12 +450,33 @@ class AppComponent {
await this.appManager.activatePlugin(['layout'])
await this.appManager.activatePlugin(['notification'])
await this.appManager.activatePlugin(['editor'])
await this.appManager.activatePlugin(['permissionhandler', 'theme', 'locale', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await this.appManager.activatePlugin([
'permissionhandler',
'theme',
'locale',
'fileManager',
'compilerMetadata',
'compilerArtefacts',
'network',
'web3Provider',
'offsetToLineColumnConverter'
])
await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['home'])
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'codeFormatter', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin([
'hiddenPanel',
'pluginManager',
'codeParser',
'codeFormatter',
'fileDecorator',
'terminal',
'blockchain',
'fetchAndCompile',
'contentImport',
'gistHandler'
])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder'])
await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
@ -472,6 +496,7 @@ class AppComponent {
await this.appManager.registerContextMenuItems()
}
)
await this.appManager.activatePlugin(['solidity-script', 'openaigpt'])
await this.appManager.activatePlugin(['filePanel'])
@ -483,9 +508,7 @@ class AppComponent {
.then(async () => {
try {
if (params.deactivate) {
await this.appManager.deactivatePlugin(
params.deactivate.split(',')
)
await this.appManager.deactivatePlugin(params.deactivate.split(','))
}
} catch (e) {
console.log(e)
@ -495,10 +518,7 @@ class AppComponent {
this.menuicons.select('solidity')
} else {
// If plugins are loaded from the URL params, we focus on the last one.
if (
this.appManager.pluginLoader.current === 'queryParams' &&
this.workspace.length > 0
) {
if (this.appManager.pluginLoader.current === 'queryParams' && this.workspace.length > 0) {
this.menuicons.select(this.workspace[this.workspace.length - 1])
} else {
this.appManager.call('tabs', 'focus', 'home')
@ -515,17 +535,13 @@ class AppComponent {
}
if (params.calls) {
const calls = params.calls.split("///");
const calls = params.calls.split('///')
// call all functions in the list, one after the other
for (const call of calls) {
const callDetails = call.split("//");
const callDetails = call.split('//')
if (callDetails.length > 1) {
this.appManager.call(
"notification",
"toast",
`initiating ${callDetails[0]} and calling "${callDetails[1]}" ...`
);
this.appManager.call('notification', 'toast', `initiating ${callDetails[0]} and calling "${callDetails[1]}" ...`)
// @todo(remove the timeout when activatePlugin is on 0.3.0)
try {

@ -1,9 +1,9 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import { AbstractPanel } from './panel'
import {AbstractPanel} from './panel'
import * as packageJson from '../../../../../package.json'
import { RemixPluginPanel } from '@remix-ui/panel'
import { PluginViewWrapper } from '@remix-ui/helper'
import {RemixPluginPanel} from '@remix-ui/panel'
import {PluginViewWrapper} from '@remix-ui/helper'
const profile = {
name: 'hiddenPanel',
@ -16,35 +16,37 @@ const profile = {
export class HiddenPanel extends AbstractPanel {
el: HTMLElement
dispatch: React.Dispatch<any> = () => {}
constructor () {
constructor() {
super(profile)
this.el = document.createElement('div')
this.el.setAttribute('class', 'pluginsContainer')
}
addView (profile: any, view: any): void {
addView(profile: any, view: any): void {
super.removeView(profile)
super.addView(profile, view)
this.renderComponent()
}
updateComponent (state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/>
updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins} />
}
setDispatch (dispatch: React.Dispatch<any>) {
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
render() {
return (
<div className='pluginsContainer'><PluginViewWrapper plugin={this} /></div>
);
<div className="pluginsContainer">
<PluginViewWrapper plugin={this} />
</div>
)
}
renderComponent () {
renderComponent() {
this.dispatch({
plugins: this.plugins,
plugins: this.plugins
})
}
}

@ -1,8 +1,8 @@
import React from 'react' // eslint-disable-line
import { AbstractPanel } from './panel'
import { RemixPluginPanel } from '@remix-ui/panel'
import {AbstractPanel} from './panel'
import {RemixPluginPanel} from '@remix-ui/panel'
import packageJson from '../../../../../package.json'
import { PluginViewWrapper } from '@remix-ui/helper'
import {PluginViewWrapper} from '@remix-ui/helper'
const profile = {
name: 'mainPanel',
@ -13,56 +13,60 @@ const profile = {
}
export class MainPanel extends AbstractPanel {
element: HTMLDivElement
dispatch: React.Dispatch<any> = () => {}
constructor (config) {
super(profile)
this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
this.element.setAttribute('style', 'height: 100%; width: 100%;')
// this.config = config
}
element: HTMLDivElement
dispatch: React.Dispatch<any> = () => {}
constructor(config) {
super(profile)
this.element = document.createElement('div')
this.element.setAttribute('data-id', 'mainPanelPluginsContainer')
this.element.setAttribute('style', 'height: 100%; width: 100%;')
// this.config = config
}
setDispatch (dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
onActivation () {
this.renderComponent()
}
onActivation() {
this.renderComponent()
}
focus (name) {
this.emit('focusChanged', name)
super.focus(name)
this.renderComponent()
}
focus(name) {
this.emit('focusChanged', name)
super.focus(name)
this.renderComponent()
}
addView (profile, view) {
super.addView(profile, view)
this.renderComponent()
}
addView(profile, view) {
super.addView(profile, view)
this.renderComponent()
}
removeView (profile) {
super.removeView(profile)
this.renderComponent()
}
removeView(profile) {
super.removeView(profile)
this.renderComponent()
}
async showContent (name) {
super.showContent(name)
this.renderComponent()
}
async showContent(name) {
super.showContent(name)
this.renderComponent()
}
renderComponent () {
this.dispatch({
plugins: this.plugins
})
}
renderComponent() {
this.dispatch({
plugins: this.plugins
})
}
render() {
return <div style={{height: '100%', width: '100%'}} data-id='mainPanelPluginsContainer'><PluginViewWrapper plugin={this} /></div>
}
render() {
return (
<div style={{height: '100%', width: '100%'}} data-id="mainPanelPluginsContainer">
<PluginViewWrapper plugin={this} />
</div>
)
}
updateComponent (state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins}/>
}
updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins} />
}
}

@ -1,144 +1,174 @@
import { RemixApp } from '@remix-ui/app'
import React, { useEffect, useRef, useState } from 'react'
import { render } from 'react-dom'
import {RemixApp} from '@remix-ui/app'
import React, {useEffect, useRef, useState} from 'react'
import {render} from 'react-dom'
import * as packageJson from '../../../../../package.json'
import { fileSystem, fileSystems } from '../files/fileSystem'
import { indexedDBFileSystem } from '../files/filesystems/indexedDB'
import { localStorageFS } from '../files/filesystems/localStorage'
import { fileSystemUtility, migrationTestData } from '../files/filesystems/fileSystemUtility'
import {fileSystem, fileSystems} from '../files/fileSystem'
import {indexedDBFileSystem} from '../files/filesystems/indexedDB'
import {localStorageFS} from '../files/filesystems/localStorage'
import {fileSystemUtility, migrationTestData} from '../files/filesystems/fileSystemUtility'
import './styles/preload.css'
const _paq = window._paq = window._paq || []
const _paq = (window._paq = window._paq || [])
export const Preload = () => {
const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false)
const remixFileSystems = useRef<fileSystems>(new fileSystems())
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem())
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS())
// url parameters to e2e test the fallbacks and error warnings
const testmigrationFallback = useRef<boolean>(
window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
const testmigrationResult = useRef<boolean>(
window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
const testBlockStorage = useRef<boolean>(
window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
)
const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false)
const remixFileSystems = useRef<fileSystems>(new fileSystems())
const remixIndexedDB = useRef<fileSystem>(new indexedDBFileSystem())
const localStorageFileSystem = useRef<fileSystem>(new localStorageFS())
// url parameters to e2e test the fallbacks and error warnings
const testmigrationFallback = useRef<boolean>(window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testmigrationResult = useRef<boolean>(window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
const testBlockStorage = useRef<boolean>(window.location.hash.includes('e2e_testblock_storage=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:')
function loadAppComponent() {
import('../../app').then((AppComponent) => {
const appComponent = new AppComponent.default()
appComponent.run().then(() => {
render(
<>
<RemixApp app={appComponent} />
</>,
document.getElementById('root')
)
})
}).catch(err => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
function loadAppComponent() {
import('../../app')
.then((AppComponent) => {
const appComponent = new AppComponent.default()
appComponent.run().then(() => {
render(
<>
<RemixApp app={appComponent} />
</>,
document.getElementById('root')
)
})
}
})
.catch((err) => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
})
}
const downloadBackup = async () => {
setShowDownloader(false)
const fsUtility = new fileSystemUtility()
await fsUtility.downloadBackup(remixFileSystems.current.fileSystems['localstorage'])
await migrateAndLoad()
}
const downloadBackup = async () => {
setShowDownloader(false)
const fsUtility = new fileSystemUtility()
await fsUtility.downloadBackup(remixFileSystems.current.fileSystems['localstorage'])
await migrateAndLoad()
}
const migrateAndLoad = async () => {
setShowDownloader(false)
const fsUtility = new fileSystemUtility()
const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current)
_paq.push(['trackEvent', 'Migrate', 'result', migrationResult?'success' : 'fail'])
await setFileSystems()
}
const migrateAndLoad = async () => {
setShowDownloader(false)
const fsUtility = new fileSystemUtility()
const migrationResult = await fsUtility.migrate(localStorageFileSystem.current, remixIndexedDB.current)
_paq.push(['trackEvent', 'Migrate', 'result', migrationResult ? 'success' : 'fail'])
await setFileSystems()
}
const setFileSystems = async() => {
const fsLoaded = await remixFileSystems.current.setFileSystem([(testmigrationFallback.current || testBlockStorage.current)? null: remixIndexedDB.current, testBlockStorage.current? null:localStorageFileSystem.current])
if (fsLoaded) {
console.log(fsLoaded.name + ' activated')
_paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name])
loadAppComponent()
} else {
_paq.push(['trackEvent', 'Storage', 'error', 'no supported storage'])
setSupported(false)
}
const setFileSystems = async () => {
const fsLoaded = await remixFileSystems.current.setFileSystem([
testmigrationFallback.current || testBlockStorage.current ? null : remixIndexedDB.current,
testBlockStorage.current ? null : localStorageFileSystem.current
])
if (fsLoaded) {
console.log(fsLoaded.name + ' activated')
_paq.push(['trackEvent', 'Storage', 'activate', fsLoaded.name])
loadAppComponent()
} else {
_paq.push(['trackEvent', 'Storage', 'error', 'no supported storage'])
setSupported(false)
}
}
const testmigration = async() => {
if (testmigrationResult.current) {
const fsUtility = new fileSystemUtility()
fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs)
}
const testmigration = async () => {
if (testmigrationResult.current) {
const fsUtility = new fileSystemUtility()
fsUtility.populateWorkspace(migrationTestData, remixFileSystems.current.fileSystems['localstorage'].fs)
}
}
useEffect(() => {
async function loadStorage() {
await remixFileSystems.current.addFileSystem(remixIndexedDB.current) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported'])
await remixFileSystems.current.addFileSystem(localStorageFileSystem.current) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported'])
await testmigration()
remixIndexedDB.current.loaded && await remixIndexedDB.current.checkWorkspaces()
localStorageFileSystem.current.loaded && await localStorageFileSystem.current.checkWorkspaces()
remixIndexedDB.current.loaded && ( (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces)? await setFileSystems():setShowDownloader(true))
!remixIndexedDB.current.loaded && await setFileSystems()
}
loadStorage()
}, [])
useEffect(() => {
async function loadStorage() {
;(await remixFileSystems.current.addFileSystem(remixIndexedDB.current)) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported'])
;(await remixFileSystems.current.addFileSystem(localStorageFileSystem.current)) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported'])
await testmigration()
remixIndexedDB.current.loaded && (await remixIndexedDB.current.checkWorkspaces())
localStorageFileSystem.current.loaded && (await localStorageFileSystem.current.checkWorkspaces())
remixIndexedDB.current.loaded && (remixIndexedDB.current.hasWorkSpaces || !localStorageFileSystem.current.hasWorkSpaces ? await setFileSystems() : setShowDownloader(true))
!remixIndexedDB.current.loaded && (await setFileSystems())
}
loadStorage()
}, [])
return <>
<div className='preload-container'>
<div className='preload-logo pb-4'>
{logo}
<div className="info-secondary splash">
REMIX IDE
<br />
<span className='version'> v{packageJson.version}</span>
</div>
</div>
{!supported ?
<div className='preload-info-container alert alert-warning'>
Your browser does not support any of the filesystems required by Remix.
Either change the settings in your browser or use a supported browser.
</div> : null}
{error ?
<div className='preload-info-container alert alert-danger text-left'>
An unknown error has occurred while loading the application.<br></br>
Doing a hard refresh might fix this issue:<br></br>
<div className='pt-2'>
Windows:<br></br>
- Chrome: CTRL + F5 or CTRL + Reload Button<br></br>
- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
</div>
<div className='pt-2'>
MacOS:<br></br>
- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br>
</div>
<div className='pt-2'>
Linux:<br></br>
- Chrome & FireFox: CTRL + SHIFT + R<br></br>
</div>
</div> : null}
{showDownloader ?
<div className='preload-info-container alert alert-info'>
This app will be updated now. Please download a backup of your files now to make sure you don't lose your work.
<br></br>
You don't need to do anything else, your files will be available when the app loads.
<div onClick={async () => { await downloadBackup() }} data-id='downloadbackup-btn' className='btn btn-primary mt-1'>download backup</div>
<div onClick={async () => { await migrateAndLoad() }} data-id='skipbackup-btn' className='btn btn-primary mt-1'>skip backup</div>
</div> : null}
{(supported && !error && !showDownloader) ?
<div>
<i className="fas fa-spinner fa-spin fa-2x"></i>
</div> : null}
return (
<>
<div className="preload-container">
<div className="preload-logo pb-4">
{logo}
<div className="info-secondary splash">
REMIX IDE
<br />
<span className="version"> v{packageJson.version}</span>
</div>
</div>
{!supported ? (
<div className="preload-info-container alert alert-warning">
Your browser does not support any of the filesystems required by Remix. Either change the settings in your browser or use a supported browser.
</div>
) : null}
{error ? (
<div className="preload-info-container alert alert-danger text-left">
An unknown error has occurred while loading the application.
<br></br>
Doing a hard refresh might fix this issue:<br></br>
<div className="pt-2">
Windows:<br></br>- Chrome: CTRL + F5 or CTRL + Reload Button
<br></br>- Firefox: CTRL + SHIFT + R or CTRL + F5<br></br>
</div>
<div className="pt-2">
MacOS:<br></br>- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button<br></br>
</div>
<div className="pt-2">
Linux:<br></br>- Chrome & FireFox: CTRL + SHIFT + R<br></br>
</div>
</div>
) : null}
{showDownloader ? (
<div className="preload-info-container alert alert-info">
This app will be updated now. Please download a backup of your files now to make sure you don't lose your work.
<br></br>
You don't need to do anything else, your files will be available when the app loads.
<div
onClick={async () => {
await downloadBackup()
}}
data-id="downloadbackup-btn"
className="btn btn-primary mt-1"
>
download backup
</div>
<div
onClick={async () => {
await migrateAndLoad()
}}
data-id="skipbackup-btn"
className="btn btn-primary mt-1"
>
skip backup
</div>
</div>
) : null}
{supported && !error && !showDownloader ? (
<div>
<i className="fas fa-spinner fa-spin fa-2x"></i>
</div>
) : null}
</div>
</>
)
}
const logo = <svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
const logo = (
<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105 100">
<path d="M91.84,35a.09.09,0,0,1-.1-.07,41,41,0,0,0-79.48,0,.09.09,0,0,1-.1.07C9.45,35,1,35.35,1,42.53c0,8.56,1,16,6,20.32,2.16,1.85,5.81,2.3,9.27,2.22a44.4,44.4,0,0,0,6.45-.68.09.09,0,0,0,.06-.15A34.81,34.81,0,0,1,17,45c0-.1,0-.21,0-.31a35,35,0,0,1,70,0c0,.1,0,.21,0,.31a34.81,34.81,0,0,1-5.78,19.24.09.09,0,0,0,.06.15,44.4,44.4,0,0,0,6.45.68c3.46.08,7.11-.37,9.27-2.22,5-4.27,6-11.76,6-20.32C103,35.35,94.55,35,91.84,35Z" />
<path d="M52,74,25.4,65.13a.1.1,0,0,0-.1.17L51.93,91.93a.1.1,0,0,0,.14,0L78.7,65.3a.1.1,0,0,0-.1-.17L52,74A.06.06,0,0,1,52,74Z" />
<path d="M75.68,46.9,82,45a.09.09,0,0,0,.08-.09,29.91,29.91,0,0,0-.87-6.94.11.11,0,0,0-.09-.08l-6.43-.58a.1.1,0,0,1-.06-.18l4.78-4.18a.13.13,0,0,0,0-.12,30.19,30.19,0,0,0-3.65-6.07.09.09,0,0,0-.11,0l-5.91,2a.1.1,0,0,1-.12-.14L72.19,23a.11.11,0,0,0,0-.12,29.86,29.86,0,0,0-5.84-4.13.09.09,0,0,0-.11,0l-4.47,4.13a.1.1,0,0,1-.17-.07l.09-6a.1.1,0,0,0-.07-.1,30.54,30.54,0,0,0-7-1.47.1.1,0,0,0-.1.07l-2.38,5.54a.1.1,0,0,1-.18,0l-2.37-5.54a.11.11,0,0,0-.11-.06,30,30,0,0,0-7,1.48.12.12,0,0,0-.07.1l.08,6.05a.09.09,0,0,1-.16.07L37.8,18.76a.11.11,0,0,0-.12,0,29.75,29.75,0,0,0-5.83,4.13.11.11,0,0,0,0,.12l2.59,5.6a.11.11,0,0,1-.13.14l-5.9-2a.11.11,0,0,0-.12,0,30.23,30.23,0,0,0-3.62,6.08.11.11,0,0,0,0,.12l4.79,4.19a.1.1,0,0,1-.06.17L23,37.91a.1.1,0,0,0-.09.07A29.9,29.9,0,0,0,22,44.92a.1.1,0,0,0,.07.1L28.4,47a.1.1,0,0,1,0,.18l-5.84,3.26a.16.16,0,0,0,0,.11,30.17,30.17,0,0,0,2.1,6.76c.32.71.67,1.4,1,2.08a.1.1,0,0,0,.06,0L52,68.16H52l26.34-8.78a.1.1,0,0,0,.06-.05,30.48,30.48,0,0,0,3.11-8.88.1.1,0,0,0-.05-.11l-5.83-3.26A.1.1,0,0,1,75.68,46.9Z" />
</svg>
</svg>
)

@ -1,10 +1,10 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import { AbstractPanel } from './panel'
import { RemixPluginPanel } from '@remix-ui/panel'
import {AbstractPanel} from './panel'
import {RemixPluginPanel} from '@remix-ui/panel'
import packageJson from '../../../../../package.json'
import { RemixUIPanelHeader } from '@remix-ui/panel'
import { PluginViewWrapper } from '@remix-ui/helper'
import {RemixUIPanelHeader} from '@remix-ui/panel'
import {PluginViewWrapper} from '@remix-ui/helper'
// const csjs = require('csjs-inject')
const sidePanel = {
@ -79,14 +79,17 @@ export class SidePanel extends AbstractPanel {
this.renderComponent()
}
setDispatch (dispatch: React.Dispatch<any>) {
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
render() {
return (
<section className='panel plugin-manager'> <PluginViewWrapper plugin={this} /></section>
);
<section className="panel plugin-manager">
{' '}
<PluginViewWrapper plugin={this} />
</section>
)
}
updateComponent(state: any) {

@ -1,11 +1,11 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine'
import { EventEmitter } from 'events'
import { IconRecord, RemixUiVerticalIconsPanel } from '@remix-ui/vertical-icons-panel'
import { Profile } from '@remixproject/plugin-utils'
import { PluginViewWrapper } from '@remix-ui/helper'
import {Plugin} from '@remixproject/engine'
import {EventEmitter} from 'events'
import {IconRecord, RemixUiVerticalIconsPanel} from '@remix-ui/vertical-icons-panel'
import {Profile} from '@remixproject/plugin-utils'
import {PluginViewWrapper} from '@remix-ui/helper'
const profile = {
name: 'menuicons',
@ -21,44 +21,51 @@ export class VerticalIcons extends Plugin {
htmlElement: HTMLDivElement
icons: Record<string, IconRecord> = {}
dispatch: React.Dispatch<any> = () => {}
constructor () {
constructor() {
super(profile)
this.events = new EventEmitter()
this.htmlElement = document.createElement('div')
this.htmlElement.setAttribute('id', 'icon-panel')
}
renderComponent () {
const fixedOrder = ['filePanel', 'search', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
renderComponent() {
const fixedOrder = ['filePanel', 'search', 'solidity', 'udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
const divived = Object.values(this.icons).map((value) => { return {
...value,
isRequired: fixedOrder.indexOf(value.profile.name) > -1
}}).sort((a,b) => {
return a.timestamp - b.timestamp
})
const divived = Object.values(this.icons)
.map((value) => {
return {
...value,
isRequired: fixedOrder.indexOf(value.profile.name) > -1
}
})
.sort((a, b) => {
return a.timestamp - b.timestamp
})
const required = divived.filter((value) => value.isRequired).sort((a,b) => {
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name)
})
const required = divived
.filter((value) => value.isRequired)
.sort((a, b) => {
return fixedOrder.indexOf(a.profile.name) - fixedOrder.indexOf(b.profile.name)
})
const sorted: IconRecord[] = [
...required,
...divived.filter((value) => { return !value.isRequired })
...divived.filter((value) => {
return !value.isRequired
})
]
this.dispatch({
verticalIconsPlugin: this,
icons: sorted
})
}
setDispatch (dispatch: React.Dispatch<any>) {
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
onActivation () {
onActivation() {
this.renderComponent()
this.on('sidePanel', 'focusChanged', (name: string) => {
Object.keys(this.icons).map((o) => {
@ -69,7 +76,7 @@ export class VerticalIcons extends Plugin {
})
}
async linkContent (profile: Profile) {
async linkContent(profile: Profile) {
if (!profile.icon) return
if (!profile.kind) profile.kind = 'none'
this.icons[profile.name] = {
@ -81,7 +88,7 @@ export class VerticalIcons extends Plugin {
this.renderComponent()
}
unlinkContent (profile: Profile) {
unlinkContent(profile: Profile) {
delete this.icons[profile.name]
this.renderComponent()
}
@ -95,7 +102,7 @@ export class VerticalIcons extends Plugin {
* Set an icon as active
* @param {string} name Name of profile of the module to activate
*/
select (name: string) {
select(name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('showContent', name)
this.events.emit('showContent', name)
@ -105,22 +112,21 @@ export class VerticalIcons extends Plugin {
* Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate
*/
toggle (name: string) {
toggle(name: string) {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('toggleContent', name)
this.events.emit('toggleContent', name)
}
updateComponent(state: any){
return <RemixUiVerticalIconsPanel
verticalIconsPlugin={state.verticalIconsPlugin}
icons={state.icons}
/>
updateComponent(state: any) {
return <RemixUiVerticalIconsPanel verticalIconsPlugin={state.verticalIconsPlugin} icons={state.icons} />
}
render() {
return (
<div id='icon-panel'><PluginViewWrapper plugin={this} /></div>
);
<div id="icon-panel">
<PluginViewWrapper plugin={this} />
</div>
)
}
}

@ -13,7 +13,7 @@ const profile = {
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText'],
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText', 'getPositionAt'],
}
class Editor extends Plugin {
@ -51,7 +51,8 @@ class Editor extends Plugin {
rs: 'rust',
cairo: 'cairo',
ts: 'typescript',
move: 'move'
move: 'move',
circom: 'circom'
}
this.activated = false
@ -99,8 +100,8 @@ class Editor extends Plugin {
this.ref.clearDecorationsByPlugin = (filePath, plugin, typeOfDecoration) => this.clearDecorationsByPlugin(filePath, plugin, typeOfDecoration)
this.ref.keepDecorationsFor = (name, typeOfDecoration) => this.keepDecorationsFor(name, typeOfDecoration)
}} id='editorView'>
<PluginViewWrapper plugin={this} />
</div>
<PluginViewWrapper plugin={this} />
</div>
}
renderComponent () {
@ -174,8 +175,8 @@ class Editor extends Plugin {
}
this.saveTimeout = window.setTimeout(() => {
this.triggerEvent('contentChanged', [])
this.triggerEvent('requiringToSaveCurrentfile', [])
this.triggerEvent('contentChanged', [currentFile, input])
this.triggerEvent('requiringToSaveCurrentfile', [currentFile])
}, 500)
}
@ -293,7 +294,7 @@ class Editor extends Plugin {
* Get the text in the current session, if any.
* @param {string} url Address of the content to retrieve.
*/
getText (url) {
getText (url) {
if (this.sessions[url]) {
return this.sessions[url].getValue()
}
@ -578,6 +579,10 @@ class Editor extends Plugin {
this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations)
}
}
getPositionAt(offset) {
return this.api.getPositionAt(offset)
}
}
module.exports = Editor

@ -1,3 +1,4 @@
'use strict'
import { saveAs } from 'file-saver'
import JSZip from 'jszip'
@ -23,8 +24,8 @@ const profile = {
icon: 'assets/img/fileManager.webp',
permission: true,
version: packageJson.version,
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir',
'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'selectFolder', 'setFile', 'switchFile', 'refresh',
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir',
'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh',
'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo'],
kind: 'file-system'
}
@ -223,6 +224,38 @@ class FileManager extends Plugin {
}
}
/**
* Set the content of multiple files
* @param {string[]} filePaths paths of the files
* @param {string[]} data content to write to each file
* @param {string} folderPath base folder path
* @returns {void}
*/
async writeMultipleFiles(filePaths: string[], fileData: string[], folderPath: string) {
if (this.currentRequest) {
const canCall = await this.askUserPermission(`writeFile`, `will write multiple files to ${folderPath}...`)
const required = this.appManager.isRequired(this.currentRequest.from)
if (canCall && !required) {
this.call('notification', 'toast', fileChangedToastMsg(this.currentRequest.from, folderPath))
}
}
try {
for (let i = 0; i < filePaths.length; i++) {
const installPath = folderPath + "/" + filePaths[i]
let path = this.normalize(installPath)
path = this.limitPluginScope(path)
if (!await this.exists(path)) {
await this._setFileInternal(path, fileData[i])
this.emit('fileAdded', path)
}
}
} catch (e) {
throw new Error(e)
}
}
/**
* Return the content of a specific file
* @param {string} path path of the file
@ -353,7 +386,7 @@ class FileManager extends Plugin {
async zipDir(dirPath, zip) {
const filesAndFolders = await this.readdir(dirPath)
for(let path in filesAndFolders) {
for (let path in filesAndFolders) {
if (filesAndFolders[path].isDirectory) await this.zipDir(path, zip)
else {
path = this.normalize(path)
@ -367,15 +400,15 @@ class FileManager extends Plugin {
try {
const downloadFileName = helper.extractNameFromKey(path)
if (await this.isDirectory(path)) {
const zip = new JSZip()
await this.zipDir(path, zip)
const content = await zip.generateAsync({type: 'blob'})
saveAs(content, `${downloadFileName}.zip`)
} else {
path = this.normalize(path)
const content: any = await this.readFile(path)
saveAs(new Blob([content]), downloadFileName)
}
const zip = new JSZip()
await this.zipDir(path, zip)
const content = await zip.generateAsync({ type: 'blob' })
saveAs(content, `${downloadFileName}.zip`)
} else {
path = this.normalize(path)
const content: any = await this.readFile(path)
saveAs(new Blob([content]), downloadFileName)
}
} catch (e) {
throw new Error(e)
}
@ -589,7 +622,7 @@ class FileManager extends Plugin {
// TODO : Add permission
// TODO : Change Provider to Promise
return new Promise((resolve, reject) => {
provider.set(path, content, (error) => {
provider.set(path, content, async (error) => {
if (error) reject(error)
this.syncEditor(path)
this.emit('fileSaved', path)
@ -759,14 +792,14 @@ class FileManager extends Plugin {
return collectList(path)
}
async fileList (dirPath) {
async fileList(dirPath) {
const paths: any = await this.readdir(dirPath)
for( const path in paths)
if(paths[path].isDirectory) delete paths[path]
for (const path in paths)
if (paths[path].isDirectory) delete paths[path]
return Object.keys(paths)
}
isRemixDActive () {
isRemixDActive() {
return this.appManager.isActive('remixd')
}
@ -781,7 +814,7 @@ class FileManager extends Plugin {
provider.get(currentFile, (error, oldContent) => {
provider.set(currentFile, input, (error) => {
if (error) {
if (error.message ) this.call('notification', 'toast',
if (error.message) this.call('notification', 'toast',
error.message.indexOf(
'LocalStorage is full') !== -1 ? storageFullMessage()
: error.message
@ -789,7 +822,7 @@ class FileManager extends Plugin {
provider.set(currentFile, oldContent)
return console.error(error)
} else {
this.emit('fileSaved', currentFile)
this.emit('fileSaved', currentFile)
}
})
})
@ -805,10 +838,10 @@ class FileManager extends Plugin {
if (path !== currentFile) return
const provider = this.fileProviderOf(currentFile)
if (provider) {
try{
try {
const content = await provider.get(currentFile)
if(content) this.editor.setText(currentFile, content)
}catch(error){
if (content) this.editor.setText(currentFile, content)
} catch (error) {
console.log(error)
}
} else {
@ -829,7 +862,7 @@ class FileManager extends Plugin {
}
await self.syncEditor(fileProvider + file)
} else {
try{
try {
const name = await helper.createNonClashingNameAsync(file, self._deps.filesProviders[fileProvider])
if (helper.checkSpecialChars(name)) {
this.call('notification', 'alert', {
@ -844,7 +877,7 @@ class FileManager extends Plugin {
}
self.syncEditor(fileProvider + name)
}
}catch(error){
} catch (error) {
if (error) {
this.call('notification', 'alert', {
id: 'fileManagerAlert',
@ -870,13 +903,64 @@ class FileManager extends Plugin {
}
}
async isGitRepo (): Promise<boolean> {
async isGitRepo(): Promise<boolean> {
const path = '.git'
const exists = await this.exists(path)
return exists
}
async moveFileIsAllowed (src: string, dest: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
src = this.limitPluginScope(src)
dest = this.limitPluginScope(dest)
await this._handleExists(src, `Cannot move ${src}. Path does not exist.`)
await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`)
await this._handleIsFile(src, `Cannot move ${src}. Path is not a file.`)
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const fileName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + fileName)) {
return false
}
return true
} catch (e) {
console.log(e)
return false
}
}
async moveDirIsAllowed (src: string, dest: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
src = this.limitPluginScope(src)
dest = this.limitPluginScope(dest)
await this._handleExists(src, `Cannot move ${src}. Path does not exist.`)
await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`)
await this._handleIsDir(src, `Cannot move ${src}. Path is not directory.`)
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const dirName = helper.extractNameFromKey(src)
const provider = this.fileProviderOf(src)
if (await this.exists(dest + '/' + dirName) || src === dest) {
return false
}
if (provider.isSubDirectory(src, dest)) {
this.call('notification', 'toast', recursivePasteToastMsg())
return false
}
return true
} catch (e) {
console.log(e)
return false
}
}
/**
* Moves a file to a new folder
* @param {string} src path of the source file
@ -884,7 +968,7 @@ class FileManager extends Plugin {
* @returns {void}
*/
async moveFile(src: string, dest: string) {
async moveFile(src: string, dest: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
@ -897,7 +981,7 @@ class FileManager extends Plugin {
const fileName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + fileName)) {
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. File already exists at destination ${dest}`})
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. File already exists at destination ${dest}` })
}
await this.copyFile(src, dest, fileName)
await this.remove(src)
@ -925,9 +1009,15 @@ class FileManager extends Plugin {
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const dirName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + dirName) || src === dest) {
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. Folder already exists at destination ${dest}`})
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. Folder already exists at destination ${dest}` })
}
const provider = this.fileProviderOf(src)
if (provider.isSubDirectory(src, dest)) {
this.call('notification', 'toast', recursivePasteToastMsg())
return false
}
await this.copyDir(src, dest, dirName)
await this.inDepthCopy(src, dest, dirName)
await this.remove(src)
} catch (e) {

@ -1,71 +1,71 @@
export class fileSystem {
name: string
enabled: boolean
available: boolean
fs: any
fsCallBack: any;
hasWorkSpaces: boolean
loaded: boolean
load: () => Promise<unknown>
test: () => Promise<unknown>
name: string
enabled: boolean
available: boolean
fs: any
fsCallBack: any;
hasWorkSpaces: boolean
loaded: boolean
load: () => Promise<unknown>
test: () => Promise<unknown>
constructor() {
this.available = false
this.enabled = false
this.hasWorkSpaces = false
this.loaded = false
}
constructor() {
this.available = false
this.enabled = false
this.hasWorkSpaces = false
this.loaded = false
}
checkWorkspaces = async () => {
try {
await this.fs.stat('.workspaces')
this.hasWorkSpaces = true
} catch (e) {
checkWorkspaces = async () => {
try {
await this.fs.stat('.workspaces')
this.hasWorkSpaces = true
} catch (e) {
}
}
}
set = async () => {
const w = (window as any)
if (!this.loaded) return false
w.remixFileSystem = this.fs
w.remixFileSystem.name = this.name
w.remixFileSystemCallback = this.fsCallBack
return true
}
set = async () => {
const w = (window as any)
if (!this.loaded) return false
w.remixFileSystem = this.fs
w.remixFileSystem.name = this.name
w.remixFileSystemCallback = this.fsCallBack
return true
}
}
export class fileSystems {
fileSystems: Record<string, fileSystem>
constructor() {
this.fileSystems = {}
}
fileSystems: Record<string, fileSystem>
constructor() {
this.fileSystems = {}
}
addFileSystem = async (fs: fileSystem): Promise<boolean> => {
try {
this.fileSystems[fs.name] = fs
await fs.test() && await fs.load()
console.log(fs.name + ' is loaded...')
return true
} catch (e) {
console.log(fs.name + ' not available...')
return false
}
addFileSystem = async (fs: fileSystem): Promise<boolean> => {
try {
this.fileSystems[fs.name] = fs
await fs.test() && await fs.load()
console.log(fs.name + ' is loaded...')
return true
} catch (e) {
console.log(fs.name + ' not available...')
return false
}
/**
}
/**
* sets filesystem using list as fallback
* @param {string[]} names
* @returns {Promise}
*/
setFileSystem = async (filesystems?: fileSystem[]): Promise<fileSystem> => {
for (const fs of filesystems) {
if (fs && this.fileSystems[fs.name]) {
const result = await this.fileSystems[fs.name].set()
if (result) return this.fileSystems[fs.name]
}
}
return null
setFileSystem = async (filesystems?: fileSystem[]): Promise<fileSystem> => {
for (const fs of filesystems) {
if (fs && this.fileSystems[fs.name]) {
const result = await this.fileSystems[fs.name].set()
if (result) return this.fileSystems[fs.name]
}
}
return null
}
}

@ -3,189 +3,189 @@ import JSZip from "jszip"
import { fileSystem } from "../fileSystem"
const _paq = window._paq = window._paq || []
export class fileSystemUtility {
migrate = async (fsFrom: fileSystem, fsTo: fileSystem) => {
try {
await fsFrom.checkWorkspaces()
await fsTo.checkWorkspaces()
if (fsTo.hasWorkSpaces) {
console.log(`${fsTo.name} already has files`)
return true
}
if (!fsFrom.hasWorkSpaces) {
console.log('no files to migrate')
return true
}
const fromFiles = await this.copyFolderToJson('/', null, null, fsFrom.fs)
await this.populateWorkspace(fromFiles, fsTo.fs)
const toFiles = await this.copyFolderToJson('/', null, null, fsTo.fs)
if (hashMessage(JSON.stringify(toFiles)) === hashMessage(JSON.stringify(fromFiles))) {
console.log('file migration successful')
return true
} else {
_paq.push(['trackEvent', 'Migrate', 'error', 'hash mismatch'])
console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false
return false
}
} catch (err) {
console.log(err)
_paq.push(['trackEvent', 'Migrate', 'error', err && err.message])
console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false
return false
}
migrate = async (fsFrom: fileSystem, fsTo: fileSystem) => {
try {
await fsFrom.checkWorkspaces()
await fsTo.checkWorkspaces()
if (fsTo.hasWorkSpaces) {
console.log(`${fsTo.name} already has files`)
return true
}
if (!fsFrom.hasWorkSpaces) {
console.log('no files to migrate')
return true
}
const fromFiles = await this.copyFolderToJson('/', null, null, fsFrom.fs)
await this.populateWorkspace(fromFiles, fsTo.fs)
const toFiles = await this.copyFolderToJson('/', null, null, fsTo.fs)
if (hashMessage(JSON.stringify(toFiles)) === hashMessage(JSON.stringify(fromFiles))) {
console.log('file migration successful')
return true
} else {
_paq.push(['trackEvent', 'Migrate', 'error', 'hash mismatch'])
console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false
return false
}
} catch (err) {
console.log(err)
_paq.push(['trackEvent', 'Migrate', 'error', err && err.message])
console.log('file migration failed falling back to ' + fsFrom.name)
fsTo.loaded = false
return false
}
downloadBackup = async (fs: fileSystem) => {
try {
const zip = new JSZip()
zip.file("readme.txt", "This is a Remix backup file.\nThis zip should be used by the restore backup tool in Remix.\nThe .workspaces directory contains your workspaces.")
await fs.checkWorkspaces()
await this.copyFolderToJson('/', null, null, fs.fs, ({ path, content }) => {
zip.file(path, content)
})
const blob = await zip.generateAsync({ type: 'blob' })
const today = new Date()
const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate()
const time = today.getHours() + 'h' + today.getMinutes() + 'min'
this.saveAs(blob, `remix-backup-at-${time}-${date}.zip`)
_paq.push(['trackEvent','Backup','download','preload'])
} catch (err) {
_paq.push(['trackEvent','Backup','error',err && err.message])
console.log(err)
}
}
downloadBackup = async (fs: fileSystem) => {
try {
const zip = new JSZip()
zip.file("readme.txt", "This is a Remix backup file.\nThis zip should be used by the restore backup tool in Remix.\nThe .workspaces directory contains your workspaces.")
await fs.checkWorkspaces()
await this.copyFolderToJson('/', null, null, fs.fs, ({ path, content }) => {
zip.file(path, content)
})
const blob = await zip.generateAsync({ type: 'blob' })
const today = new Date()
const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate()
const time = today.getHours() + 'h' + today.getMinutes() + 'min'
this.saveAs(blob, `remix-backup-at-${time}-${date}.zip`)
_paq.push(['trackEvent','Backup','download','preload'])
} catch (err) {
_paq.push(['trackEvent','Backup','error',err && err.message])
console.log(err)
}
populateWorkspace = async (json, fs) => {
for (const item in json) {
const isFolder = json[item].content === undefined
if (isFolder) {
await this.createDir(item, fs)
await this.populateWorkspace(json[item].children, fs)
} else {
await fs.writeFile(item, json[item].content, 'utf8')
}
}
}
populateWorkspace = async (json, fs) => {
for (const item in json) {
const isFolder = json[item].content === undefined
if (isFolder) {
await this.createDir(item, fs)
await this.populateWorkspace(json[item].children, fs)
} else {
await fs.writeFile(item, json[item].content, 'utf8')
}
}
}
/**
/**
* copy the folder recursively
* @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders
*/
copyFolderToJson = async (path, visitFile, visitFolder, fs, cb = null) => {
visitFile = visitFile || (() => { })
visitFolder = visitFolder || (() => { })
return await this._copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb)
}
copyFolderToJson = async (path, visitFile, visitFolder, fs, cb = null) => {
visitFile = visitFile || (() => { })
visitFolder = visitFolder || (() => { })
return await this._copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb)
}
/**
/**
* copy the folder recursively (internal use)
* @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders
*/
async _copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb) {
visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ }
const json = {}
// path = this.removePrefix(path)
if (await fs.exists(path)) {
const items = await fs.readdir(path)
visitFolder({ path })
if (items.length !== 0) {
for (const item of items) {
const file: any = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if ((await fs.stat(curPath)).isDirectory()) {
file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder, fs, cb)
} else {
file.content = await fs.readFile(curPath, 'utf8')
if (cb) cb({ path: curPath, content: file.content })
visitFile({ path: curPath, content: file.content })
}
json[curPath] = file
}
}
}
return json
}
createDir = async (path, fs) => {
const paths = path.split('/')
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + (currentCheck ? '/' : '') + value
if (!await fs.exists(currentCheck)) {
await fs.mkdir(currentCheck)
}
async _copyFolderToJsonInternal(path, visitFile, visitFolder, fs, cb) {
visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ }
const json = {}
// path = this.removePrefix(path)
if (await fs.exists(path)) {
const items = await fs.readdir(path)
visitFolder({ path })
if (items.length !== 0) {
for (const item of items) {
const file: any = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if ((await fs.stat(curPath)).isDirectory()) {
file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder, fs, cb)
} else {
file.content = await fs.readFile(curPath, 'utf8')
if (cb) cb({ path: curPath, content: file.content })
visitFile({ path: curPath, content: file.content })
}
json[curPath] = file
}
}
}
saveAs = (blob, name) => {
const node = document.createElement('a')
node.download = name
node.rel = 'noopener'
node.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s
setTimeout(function () {
try {
node.dispatchEvent(new MouseEvent('click'))
} catch (e) {
const evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
return json
}
createDir = async (path, fs) => {
const paths = path.split('/')
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + (currentCheck ? '/' : '') + value
if (!await fs.exists(currentCheck)) {
await fs.mkdir(currentCheck)
}
}
}
saveAs = (blob, name) => {
const node = document.createElement('a')
node.download = name
node.rel = 'noopener'
node.href = URL.createObjectURL(blob)
setTimeout(function () { URL.revokeObjectURL(node.href) }, 4E4) // 40s
setTimeout(function () {
try {
node.dispatchEvent(new MouseEvent('click'))
} catch (e) {
const evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
20, false, false, false, false, 0, null)
node.dispatchEvent(evt)
}
}, 0) // 40s
}
}
/* eslint-disable no-template-curly-in-string */
export const migrationTestData = {
'.workspaces': {
'.workspaces': {
children: {
'.workspaces/default_workspace': {
children: {
'.workspaces/default_workspace': {
children: {
'.workspaces/default_workspace/README.txt': {
content: 'TEST README'
}
}
},
'.workspaces/emptyspace': {
'.workspaces/default_workspace/README.txt': {
content: 'TEST README'
}
}
},
'.workspaces/emptyspace': {
},
'.workspaces/workspace_test': {
},
'.workspaces/workspace_test': {
children: {
'.workspaces/workspace_test/TEST_README.txt': {
content: 'TEST README'
},
'.workspaces/workspace_test/test_contracts': {
children: {
'.workspaces/workspace_test/test_contracts/1_Storage.sol': {
content: 'testing'
},
'.workspaces/workspace_test/test_contracts/artifacts': {
children: {
'.workspaces/workspace_test/TEST_README.txt': {
content: 'TEST README'
},
'.workspaces/workspace_test/test_contracts': {
children: {
'.workspaces/workspace_test/test_contracts/1_Storage.sol': {
content: 'testing'
},
'.workspaces/workspace_test/test_contracts/artifacts': {
children: {
'.workspaces/workspace_test/test_contracts/artifacts/Storage_metadata.json': {
content: '{ "test": "data" }'
}
}
}
}
}
'.workspaces/workspace_test/test_contracts/artifacts/Storage_metadata.json': {
content: '{ "test": "data" }'
}
}
}
}
}
}
}
}
}
}

@ -2,90 +2,90 @@ import LightningFS from "@isomorphic-git/lightning-fs"
import { fileSystem } from "../fileSystem"
export class IndexedDBStorage extends LightningFS {
base: LightningFS.PromisifedFS
addSlash: (file: string) => string
extended: { exists: (path: string) => Promise<unknown>; rmdir: (path: any) => Promise<void>; readdir: (path: any) => Promise<string[]>; unlink: (path: any) => Promise<void>; mkdir: (path: any) => Promise<void>; readFile: (path: any, options: any) => Promise<Uint8Array>; rename: (from: any, to: any) => Promise<void>; writeFile: (path: any, content: any, options: any) => Promise<void>; stat: (path: any) => Promise<import("fs").Stats>; init(name: string, opt?: LightningFS.FSConstructorOptions): void; activate(): Promise<void>; deactivate(): Promise<void>; lstat(filePath: string): Promise<import("fs").Stats>; readlink(filePath: string): Promise<string>; symlink(target: string, filePath: string): Promise<void> }
constructor(name: string) {
super(name)
this.addSlash = (file) => {
if (!file.startsWith('/')) file = '/' + file
return file
}
this.base = this.promises
this.extended = {
...this.promises,
exists: async (path: string) => {
return new Promise((resolve) => {
this.base.stat(this.addSlash(path)).then(() => resolve(true)).catch(() => resolve(false))
})
},
rmdir: async (path) => {
return this.base.rmdir(this.addSlash(path))
},
readdir: async (path) => {
return this.base.readdir(this.addSlash(path))
},
unlink: async (path) => {
return this.base.unlink(this.addSlash(path))
},
mkdir: async (path) => {
return this.base.mkdir(this.addSlash(path))
},
readFile: async (path, options) => {
return this.base.readFile(this.addSlash(path), options)
},
rename: async (from, to) => {
return this.base.rename(this.addSlash(from), this.addSlash(to))
},
writeFile: async (path, content, options) => {
return this.base.writeFile(this.addSlash(path), content, options)
},
stat: async (path) => {
return this.base.stat(this.addSlash(path))
}
}
base: LightningFS.PromisifedFS
addSlash: (file: string) => string
extended: { exists: (path: string) => Promise<unknown>; rmdir: (path: any) => Promise<void>; readdir: (path: any) => Promise<string[]>; unlink: (path: any) => Promise<void>; mkdir: (path: any) => Promise<void>; readFile: (path: any, options: any) => Promise<Uint8Array>; rename: (from: any, to: any) => Promise<void>; writeFile: (path: any, content: any, options: any) => Promise<void>; stat: (path: any) => Promise<import("fs").Stats>; init(name: string, opt?: LightningFS.FSConstructorOptions): void; activate(): Promise<void>; deactivate(): Promise<void>; lstat(filePath: string): Promise<import("fs").Stats>; readlink(filePath: string): Promise<string>; symlink(target: string, filePath: string): Promise<void> }
constructor(name: string) {
super(name)
this.addSlash = (file) => {
if (!file.startsWith('/')) file = '/' + file
return file
}
this.base = this.promises
this.extended = {
...this.promises,
exists: async (path: string) => {
return new Promise((resolve) => {
this.base.stat(this.addSlash(path)).then(() => resolve(true)).catch(() => resolve(false))
})
},
rmdir: async (path) => {
return this.base.rmdir(this.addSlash(path))
},
readdir: async (path) => {
return this.base.readdir(this.addSlash(path))
},
unlink: async (path) => {
return this.base.unlink(this.addSlash(path))
},
mkdir: async (path) => {
return this.base.mkdir(this.addSlash(path))
},
readFile: async (path, options) => {
return this.base.readFile(this.addSlash(path), options)
},
rename: async (from, to) => {
return this.base.rename(this.addSlash(from), this.addSlash(to))
},
writeFile: async (path, content, options) => {
return this.base.writeFile(this.addSlash(path), content, options)
},
stat: async (path) => {
return this.base.stat(this.addSlash(path))
}
}
}
}
export class indexedDBFileSystem extends fileSystem {
constructor() {
super()
this.name = 'indexedDB'
}
constructor() {
super()
this.name = 'indexedDB'
}
load = async () => {
return new Promise((resolve, reject) => {
try {
const fs = new IndexedDBStorage('RemixFileSystem')
fs.init('RemixFileSystem')
this.fs = fs.extended
this.fsCallBack = fs
this.loaded = true
resolve(true)
} catch (e) {
reject(e)
}
})
}
load = async () => {
return new Promise((resolve, reject) => {
try {
const fs = new IndexedDBStorage('RemixFileSystem')
fs.init('RemixFileSystem')
this.fs = fs.extended
this.fsCallBack = fs
this.loaded = true
resolve(true)
} catch (e) {
reject(e)
}
})
}
test = async () => {
return new Promise((resolve, reject) => {
if (!window.indexedDB) {
this.available = false
reject('No indexedDB on window')
}
const request = window.indexedDB.open("RemixTestDataBase");
request.onerror = () => {
this.available = false
reject('Error creating test database')
};
request.onsuccess = () => {
window.indexedDB.deleteDatabase("RemixTestDataBase");
this.available = true
resolve(true)
};
})
}
test = async () => {
return new Promise((resolve, reject) => {
if (!window.indexedDB) {
this.available = false
reject('No indexedDB on window')
}
const request = window.indexedDB.open("RemixTestDataBase");
request.onerror = () => {
this.available = false
reject('Error creating test database')
};
request.onsuccess = () => {
window.indexedDB.deleteDatabase("RemixTestDataBase");
this.available = true
resolve(true)
};
})
}
}

@ -2,56 +2,56 @@ import { fileSystem } from "../fileSystem";
export class localStorageFS extends fileSystem {
constructor() {
super()
this.name = 'localstorage'
}
load = async () => {
const me = this
return new Promise((resolve, reject) => {
try {
const w = window as any
w.BrowserFS.install(window)
w.BrowserFS.configure({
fs: 'LocalStorage'
}, async function (e) {
if (e) {
console.log('BrowserFS Error: ' + e)
reject(e)
} else {
me.fs = { ...window.require('fs') }
me.fsCallBack = window.require('fs')
me.fs.readdir = me.fs.readdirSync
me.fs.readFile = me.fs.readFileSync
me.fs.writeFile = me.fs.writeFileSync
me.fs.stat = me.fs.statSync
me.fs.unlink = me.fs.unlinkSync
me.fs.rmdir = me.fs.rmdirSync
me.fs.mkdir = me.fs.mkdirSync
me.fs.rename = me.fs.renameSync
me.fs.exists = me.fs.existsSync
me.loaded = true
resolve(true)
}
})
} catch (e) {
console.log('BrowserFS is not ready!')
reject(e)
}
constructor() {
super()
this.name = 'localstorage'
}
load = async () => {
const me = this
return new Promise((resolve, reject) => {
try {
const w = window as any
w.BrowserFS.install(window)
w.BrowserFS.configure({
fs: 'LocalStorage'
}, async function (e) {
if (e) {
console.log('BrowserFS Error: ' + e)
reject(e)
} else {
me.fs = { ...window.require('fs') }
me.fsCallBack = window.require('fs')
me.fs.readdir = me.fs.readdirSync
me.fs.readFile = me.fs.readFileSync
me.fs.writeFile = me.fs.writeFileSync
me.fs.stat = me.fs.statSync
me.fs.unlink = me.fs.unlinkSync
me.fs.rmdir = me.fs.rmdirSync
me.fs.mkdir = me.fs.mkdirSync
me.fs.rename = me.fs.renameSync
me.fs.exists = me.fs.existsSync
me.loaded = true
resolve(true)
}
})
}
} catch (e) {
console.log('BrowserFS is not ready!')
reject(e)
}
})
}
test = async () => {
return new Promise((resolve, reject) => {
const test = 'test';
try {
localStorage.setItem(test, test);
localStorage.removeItem(test);
resolve(true)
} catch(e) {
reject(e)
}
})
}
test = async () => {
return new Promise((resolve, reject) => {
const test = 'test';
try {
localStorage.setItem(test, test);
localStorage.removeItem(test);
resolve(true)
} catch(e) {
reject(e)
}
})
}
}

@ -6,7 +6,7 @@ import { QueryParams } from '@remix-project/remix-lib'
const profile: Profile = {
name: 'layout',
description: 'layout',
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel']
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel', 'maximizeTerminal']
}
interface panelState {
@ -61,9 +61,9 @@ export class Layout extends Plugin {
})
this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) {
case 'filePanel':
this.call('menuicons', 'select', 'filePanel')
break
case 'filePanel':
this.call('menuicons', 'select', 'filePanel')
break
}
})
this.on('sidePanel', 'focusChanged', async (name) => {
@ -109,6 +109,12 @@ export class Layout extends Plugin {
this.maximised[current] = true
}
async maximizeTerminal() {
this.panels.terminal.minimized = false
this.event.emit('change', this.panels)
this.emit('change', this.panels)
}
async resetSidePanel () {
this.event.emit('resetsidepanel')
const current = await this.call('sidePanel', 'currentFocus')

@ -8,279 +8,279 @@ import toml from 'toml'
import { filePathFilter, AnyFilter } from '@jsdevtools/file-path-filter'
const profile = {
name: 'codeFormatter',
desciption: 'prettier plugin for Remix',
methods: ['format'],
events: [''],
version: '0.0.1'
name: 'codeFormatter',
desciption: 'prettier plugin for Remix',
methods: ['format'],
events: [''],
version: '0.0.1'
}
const defaultOptions = {
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
}
},
{
"files": "*.yml",
"options": {
}
},
{
"files": "*.yaml",
"options": {
}
},
{
"files": "*.toml",
"options": {
}
},
{
"files": "*.json",
"options": {
}
},
{
"files": "*.js",
"options": {
}
},
{
"files": "*.ts",
"options": {
}
}
]
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
}
},
{
"files": "*.yml",
"options": {
}
},
{
"files": "*.yaml",
"options": {
}
},
{
"files": "*.toml",
"options": {
}
},
{
"files": "*.json",
"options": {
}
},
{
"files": "*.js",
"options": {
}
},
{
"files": "*.ts",
"options": {
}
}
]
}
export class CodeFormat extends Plugin {
prettier: any
ts: any
babel: any
espree: any
yml: any
sol: any
prettier: any
ts: any
babel: any
espree: any
yml: any
sol: any
constructor() {
super(profile)
}
constructor() {
super(profile)
}
async format(file: string) {
async format(file: string) {
// lazy load
if (!this.prettier) {
this.prettier = await import('prettier/standalone')
this.ts = await import('prettier/parser-typescript')
this.babel = await import('prettier/parser-babel')
this.espree = await import('prettier/parser-espree')
this.yml = await import('prettier/parser-yaml')
}
// lazy load
if (!this.prettier) {
this.prettier = await import('prettier/standalone')
this.ts = await import('prettier/parser-typescript')
this.babel = await import('prettier/parser-babel')
this.espree = await import('prettier/parser-espree')
this.yml = await import('prettier/parser-yaml')
}
try {
const content = await this.call('fileManager', 'readFile', file)
if (!content) return
let parserName = ''
let options: Options = {
}
switch (path.extname(file)) {
case '.sol':
parserName = 'solidity-parse'
break
case '.ts':
parserName = 'typescript'
options = {
...options,
trailingComma: 'all',
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
arrowParens: 'always',
}
break
case '.js':
parserName = "espree"
options = {
...options,
semi: false,
singleQuote: true,
}
break
case '.json':
parserName = 'json'
break
case '.yml':
parserName = 'yaml'
break
case '.yaml':
parserName = 'yaml'
break
}
try {
const content = await this.call('fileManager', 'readFile', file)
if (!content) return
let parserName = ''
let options: Options = {
}
switch (path.extname(file)) {
case '.sol':
parserName = 'solidity-parse'
break
case '.ts':
parserName = 'typescript'
options = {
...options,
trailingComma: 'all',
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
arrowParens: 'always',
}
break
case '.js':
parserName = "espree"
options = {
...options,
semi: false,
singleQuote: true,
}
break
case '.json':
parserName = 'json'
break
case '.yml':
parserName = 'yaml'
break
case '.yaml':
parserName = 'yaml'
break
}
if (file === '.prettierrc') {
parserName = 'json'
}
if (file === '.prettierrc') {
parserName = 'json'
}
const possibleFileNames = [
'.prettierrc',
'.prettierrc.json',
'.prettierrc.yaml',
'.prettierrc.yml',
'.prettierrc.toml',
'.prettierrc.js',
'.prettierrc.cjs',
'prettier.config.js',
'prettier.config.cjs',
'.prettierrc.json5',
]
const possibleFileNames = [
'.prettierrc',
'.prettierrc.json',
'.prettierrc.yaml',
'.prettierrc.yml',
'.prettierrc.toml',
'.prettierrc.js',
'.prettierrc.cjs',
'prettier.config.js',
'prettier.config.cjs',
'.prettierrc.json5',
]
const prettierConfigFile = await findAsync(possibleFileNames, async (fileName) => {
const exists = await this.call('fileManager', 'exists', fileName)
return exists
})
const prettierConfigFile = await findAsync(possibleFileNames, async (fileName) => {
const exists = await this.call('fileManager', 'exists', fileName)
return exists
})
let parsed = null
if (prettierConfigFile) {
let prettierConfig = await this.call('fileManager', 'readFile', prettierConfigFile)
if (prettierConfig) {
if (prettierConfigFile.endsWith('.yaml') || prettierConfigFile.endsWith('.yml')) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.toml')) {
try {
parsed = toml.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.json') || prettierConfigFile.endsWith('.json5')) {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile === '.prettierrc') {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
}
} else if (prettierConfigFile.endsWith('.js') || prettierConfigFile.endsWith('.cjs')) {
// remove any comments
prettierConfig = prettierConfig.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
// add quotes to keys
prettierConfig = prettierConfig.replace(/([a-zA-Z0-9_]+)(\s*):/g, '"$1"$2:')
// remove comma from last key
prettierConfig = prettierConfig.replace(/,(\s*})/g, '$1')
// remove any semi-colons
prettierConfig = prettierConfig.replace(/;/g, '')
// convert single quotes to double quotes
prettierConfig = prettierConfig.replace(/'/g, '"')
try {
parsed = JSON.parse(prettierConfig.replace('module.exports = ', '').replace('module.exports=', ''))
} catch (e) {
// do nothing
}
}
}
} else {
parsed = defaultOptions
await this.call('fileManager', 'writeFile', '.prettierrc.json', JSON.stringify(parsed, null, 2))
await this.call('notification', 'toast', 'A prettier config file has been created in the workspace.')
let parsed = null
if (prettierConfigFile) {
let prettierConfig = await this.call('fileManager', 'readFile', prettierConfigFile)
if (prettierConfig) {
if (prettierConfigFile.endsWith('.yaml') || prettierConfigFile.endsWith('.yml')) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed && prettierConfigFile) {
this.call('notification', 'toast', `Error parsing prettier config file: ${prettierConfigFile}`)
} else if (prettierConfigFile.endsWith('.toml')) {
try {
parsed = toml.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.json') || prettierConfigFile.endsWith('.json5')) {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile === '.prettierrc') {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
}
} else if (prettierConfigFile.endsWith('.js') || prettierConfigFile.endsWith('.cjs')) {
// remove any comments
prettierConfig = prettierConfig.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
// add quotes to keys
prettierConfig = prettierConfig.replace(/([a-zA-Z0-9_]+)(\s*):/g, '"$1"$2:')
// remove comma from last key
prettierConfig = prettierConfig.replace(/,(\s*})/g, '$1')
// remove any semi-colons
prettierConfig = prettierConfig.replace(/;/g, '')
// convert single quotes to double quotes
prettierConfig = prettierConfig.replace(/'/g, '"')
try {
parsed = JSON.parse(prettierConfig.replace('module.exports = ', '').replace('module.exports=', ''))
} catch (e) {
// do nothing
}
}
}
} else {
parsed = defaultOptions
await this.call('fileManager', 'writeFile', '.prettierrc.json', JSON.stringify(parsed, null, 2))
await this.call('notification', 'toast', 'A prettier config file has been created in the workspace.')
}
if (!parsed && prettierConfigFile) {
this.call('notification', 'toast', `Error parsing prettier config file: ${prettierConfigFile}`)
}
// merge options
if (parsed) {
options = {
...options,
...parsed,
}
}
// search for overrides
if (parsed && parsed.overrides) {
const override = parsed.overrides.find((override) => {
if (override.files) {
const pathFilter: AnyFilter = {}
pathFilter.include = setGlobalExpression(override.files)
const filteredFiles = [file]
.filter(filePathFilter(pathFilter))
if (filteredFiles.length) {
return true
}
}
})
const validParsers = ['typescript', 'babel', 'espree', 'solidity-parse', 'json', 'yaml', 'solidity-parse']
if (override && override.options && override.options.parser) {
if (validParsers.includes(override.options.parser)) {
parserName = override.options.parser
} else {
this.call('notification', 'toast', `Invalid parser: ${override.options.parser}! Valid options are ${validParsers.join(', ')}`)
}
delete override.options.parser
}
// merge options
if (parsed) {
options = {
...options,
...parsed,
}
}
if (override) {
options = {
...options,
...override.options,
}
}
// search for overrides
if (parsed && parsed.overrides) {
const override = parsed.overrides.find((override) => {
if (override.files) {
const pathFilter: AnyFilter = {}
pathFilter.include = setGlobalExpression(override.files)
const filteredFiles = [file]
.filter(filePathFilter(pathFilter))
if (filteredFiles.length) {
return true
}
}
})
const validParsers = ['typescript', 'babel', 'espree', 'solidity-parse', 'json', 'yaml', 'solidity-parse']
if (override && override.options && override.options.parser) {
if (validParsers.includes(override.options.parser)) {
parserName = override.options.parser
} else {
this.call('notification', 'toast', `Invalid parser: ${override.options.parser}! Valid options are ${validParsers.join(', ')}`)
}
delete override.options.parser
}
const result = this.prettier.format(content, {
plugins: [sol as any, this.ts, this.babel, this.espree, this.yml],
parser: parserName,
...options
})
await this.call('fileManager', 'writeFile', file, result)
} catch (e) {
// do nothing
if (override) {
options = {
...options,
...override.options,
}
}
}
const result = this.prettier.format(content, {
plugins: [sol as any, this.ts, this.babel, this.espree, this.yml],
parser: parserName,
...options
})
await this.call('fileManager', 'writeFile', file, result)
} catch (e) {
// do nothing
}
}
}
//*.sol, **/*.txt, contracts/*
const setGlobalExpression = (paths: string) => {
const results = []
paths.split(',').forEach(path => {
path = path.trim()
if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.')
if (path.endsWith('/*') && !path.endsWith('/**/*'))
path = path.replace(/(\*)/g, '**/*.*')
results.push(path)
})
return results
const results = []
paths.split(',').forEach(path => {
path = path.trim()
if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.')
if (path.endsWith('/*') && !path.endsWith('/**/*'))
path = path.replace(/(\*)/g, '**/*.*')
results.push(path)
})
return results
}
async function findAsync(arr, asyncCallback) {
const promises = arr.map(asyncCallback);
const results = await Promise.all(promises);
const index = results.findIndex(result => result);
return arr[index];
const promises = arr.map(asyncCallback);
const results = await Promise.all(promises);
const index = results.findIndex(result => result);
return arr[index];
}

@ -8,54 +8,54 @@ import { parse } from './parser'
// https://prettier.io/docs/en/plugins.html#languages
// https://github.com/ikatyang/linguist-languages/blob/master/data/Solidity.json
const languages = [
{
linguistLanguageId: 237469032,
name: 'Solidity',
type: 'programming',
color: '#AA6746',
aceMode: 'text',
tmScope: 'source.solidity',
extensions: ['.sol'],
parsers: ['solidity-parse'],
vscodeLanguageIds: ['solidity']
}
{
linguistLanguageId: 237469032,
name: 'Solidity',
type: 'programming',
color: '#AA6746',
aceMode: 'text',
tmScope: 'source.solidity',
extensions: ['.sol'],
parsers: ['solidity-parse'],
vscodeLanguageIds: ['solidity']
}
];
// https://prettier.io/docs/en/plugins.html#parsers
const parser = { astFormat: 'solidity-ast', parse, ...loc };
const parsers = {
'solidity-parse': parser
'solidity-parse': parser
};
const canAttachComment = (node) =>
node.type && node.type !== 'BlockComment' && node.type !== 'LineComment';
node.type && node.type !== 'BlockComment' && node.type !== 'LineComment';
// https://prettier.io/docs/en/plugins.html#printers
const printers = {
'solidity-ast': {
canAttachComment,
handleComments: {
ownLine: handleComments.handleOwnLineComment,
endOfLine: handleComments.handleEndOfLineComment,
remaining: handleComments.handleRemainingComment
},
isBlockComment: handleComments.isBlockComment,
massageAstNode,
print,
printComment
}
'solidity-ast': {
canAttachComment,
handleComments: {
ownLine: handleComments.handleOwnLineComment,
endOfLine: handleComments.handleEndOfLineComment,
remaining: handleComments.handleRemainingComment
},
isBlockComment: handleComments.isBlockComment,
massageAstNode,
print,
printComment
}
};
// https://prettier.io/docs/en/plugins.html#defaultoptions
const defaultOptions = {
bracketSpacing: false,
tabWidth: 4
bracketSpacing: false,
tabWidth: 4
};
export default {
languages,
parsers,
printers,
options,
defaultOptions
languages,
parsers,
printers,
options,
defaultOptions
};

@ -80,113 +80,113 @@ export function parse(text, _parsers, options) {
},
BinaryOperation(ctx) {
switch (ctx.operator) {
case '+':
case '-':
ctx.left = tryHug(ctx.left, ['%']);
ctx.right = tryHug(ctx.right, ['%']);
break;
case '*':
ctx.left = tryHug(ctx.left, ['/', '%']);
break;
case '/':
ctx.left = tryHug(ctx.left, ['*', '%']);
break;
case '%':
ctx.left = tryHug(ctx.left, ['*', '/', '%']);
break;
case '**':
// If the compiler has not been given as an option using we leave a**b**c.
if (!compiler) break;
case '+':
case '-':
ctx.left = tryHug(ctx.left, ['%']);
ctx.right = tryHug(ctx.right, ['%']);
break;
case '*':
ctx.left = tryHug(ctx.left, ['/', '%']);
break;
case '/':
ctx.left = tryHug(ctx.left, ['*', '%']);
break;
case '%':
ctx.left = tryHug(ctx.left, ['*', '/', '%']);
break;
case '**':
// If the compiler has not been given as an option using we leave a**b**c.
if (!compiler) break;
if (semver.satisfies(compiler, '<0.8.0')) {
// If the compiler is less than 0.8.0 then a**b**c is formatted as
// (a**b)**c.
ctx.left = tryHug(ctx.left, ['**']);
break;
}
if (
ctx.left.type === 'BinaryOperation' &&
ctx.left.operator === '**'
) {
// the parser still organizes the a**b**c as (a**b)**c so we need
// to restructure it.
ctx.right = {
type: 'TupleExpression',
components: [
{
type: 'BinaryOperation',
operator: '**',
left: ctx.left.right,
right: ctx.right
}
],
isArray: false
};
ctx.left = ctx.left.left;
}
break;
case '<<':
case '>>':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**']);
break;
case '&':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**', '<<', '>>']);
break;
case '|':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
break;
case '^':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
break;
case '||':
ctx.left = tryHug(ctx.left, ['&&']);
ctx.right = tryHug(ctx.right, ['&&']);
break;
case '&&':
default:
if (semver.satisfies(compiler, '<0.8.0')) {
// If the compiler is less than 0.8.0 then a**b**c is formatted as
// (a**b)**c.
ctx.left = tryHug(ctx.left, ['**']);
break;
}
if (
ctx.left.type === 'BinaryOperation' &&
ctx.left.operator === '**'
) {
// the parser still organizes the a**b**c as (a**b)**c so we need
// to restructure it.
ctx.right = {
type: 'TupleExpression',
components: [
{
type: 'BinaryOperation',
operator: '**',
left: ctx.left.right,
right: ctx.right
}
],
isArray: false
};
ctx.left = ctx.left.left;
}
break;
case '<<':
case '>>':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**']);
break;
case '&':
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']);
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**', '<<', '>>']);
break;
case '|':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&',
'^'
]);
break;
case '^':
ctx.left = tryHug(ctx.left, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
ctx.right = tryHug(ctx.right, [
'+',
'-',
'*',
'/',
'**',
'<<',
'>>',
'&'
]);
break;
case '||':
ctx.left = tryHug(ctx.left, ['&&']);
ctx.right = tryHug(ctx.right, ['&&']);
break;
case '&&':
default:
break;
}
}
});

@ -1,9 +1,9 @@
import React from 'react'
import { Plugin } from '@remixproject/engine'
import { customAction } from '@remixproject/plugin-api'
import { concatSourceFiles, getDependencyGraph, normalizeContractPath } from '@remix-ui/solidity-compiler'
import {Plugin} from '@remixproject/engine'
import {customAction} from '@remixproject/plugin-api'
import {concatSourceFiles, getDependencyGraph, normalizeContractPath} from '@remix-ui/solidity-compiler'
const _paq = window._paq = window._paq || []
const _paq = (window._paq = window._paq || [])
const profile = {
name: 'contractflattener',
@ -11,11 +11,10 @@ const profile = {
description: 'Flatten solidity contracts',
methods: ['flattenAContract', 'flattenContract'],
events: [],
maintainedBy: 'Remix',
maintainedBy: 'Remix'
}
export class ContractFlattener extends Plugin {
triggerFlattenContract: boolean = false
constructor() {
super(profile)
@ -23,8 +22,8 @@ export class ContractFlattener extends Plugin {
onActivation(): void {
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => {
if(data.sources && Object.keys(data.sources).length > 1) {
if(this.triggerFlattenContract) {
if (data.sources && Object.keys(data.sources).length > 1) {
if (this.triggerFlattenContract) {
this.triggerFlattenContract = false
await this.flattenContract(source, file, data)
}
@ -48,8 +47,7 @@ export class ContractFlattener extends Plugin {
* Takes the flattened result, writes it to a file and returns the result.
* @returns {Promise<string>}
*/
async flattenContract (source: { sources: any, target: string },
filePath: string, data: { contracts: any, sources: any }): Promise<string> {
async flattenContract(source: {sources: any; target: string}, filePath: string, data: {contracts: any; sources: any}): Promise<string> {
const appendage = '_flattened.sol'
const normalized = normalizeContractPath(filePath)
const path = `${normalized[normalized.length - 2]}${appendage}`
@ -58,17 +56,15 @@ export class ContractFlattener extends Plugin {
let sorted
let result
let sources
try{
try {
dependencyGraph = getDependencyGraph(ast, filePath)
sorted = dependencyGraph.isEmpty()
? [filePath]
: dependencyGraph.sort().reverse()
sorted = dependencyGraph.isEmpty() ? [filePath] : dependencyGraph.sort().reverse()
sources = source.sources
result = concatSourceFiles(sorted, sources)
}catch(err){
} catch (err) {
console.warn(err)
}
await this.call('fileManager', 'writeFile', path , result)
await this.call('fileManager', 'writeFile', path, result)
_paq.push(['trackEvent', 'plugin', 'contractFlattener', 'flattenAContract'])
// clean up memory references & return result
sorted = null

@ -6,79 +6,79 @@ import { Plugin } from '@remixproject/engine'
import { fileDecoration } from '@remix-ui/file-decorators'
const profile = {
name: 'fileDecorator',
desciption: 'Keeps decorators of the files',
methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'],
events: ['fileDecoratorsChanged'],
version: '0.0.1'
name: 'fileDecorator',
desciption: 'Keeps decorators of the files',
methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'],
events: ['fileDecoratorsChanged'],
version: '0.0.1'
}
export class FileDecorator extends Plugin {
private _fileStates: fileDecoration[] = []
constructor() {
super(profile)
}
private _fileStates: fileDecoration[] = []
constructor() {
super(profile)
}
onActivation(): void {
this.on('filePanel', 'setWorkspace', async () => {
await this.clearAllFileDecorators()
})
}
onActivation(): void {
this.on('filePanel', 'setWorkspace', async () => {
await this.clearAllFileDecorators()
})
}
/**
/**
* @param fileStates Array of file states
*/
async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) {
const { from } = this.currentRequest
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates]
// clear all file states in the previous state of this owner on the files called
fileStatesPayload.forEach((state) => {
state.workspace = workspace
state.owner = from
})
const filteredState = this._fileStates.filter((state) => {
const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => {
return from == state.owner && payloadFileState.path == state.path
})
return index == -1
})
const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath)
async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) {
const { from } = this.currentRequest
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates]
// clear all file states in the previous state of this owner on the files called
fileStatesPayload.forEach((state) => {
state.workspace = workspace
state.owner = from
})
const filteredState = this._fileStates.filter((state) => {
const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => {
return from == state.owner && payloadFileState.path == state.path
})
return index == -1
})
const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
}
async clearFileDecorators(path?: string) {
const { from } = this.currentRequest
if (!from) return
async clearFileDecorators(path?: string) {
const { from } = this.currentRequest
if (!from) return
const filteredState = this._fileStates.filter((state) => {
if(state.owner != from) return true
if(path && state.path != path) return true
})
const newState = [...filteredState].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
const filteredState = this._fileStates.filter((state) => {
if(state.owner != from) return true
if(path && state.path != path) return true
})
const newState = [...filteredState].sort(sortByPath)
if (!deepequal(newState, this._fileStates)) {
this._fileStates = newState
this.emit('fileDecoratorsChanged', this._fileStates)
}
async clearAllFileDecorators() {
this._fileStates = []
this.emit('fileDecoratorsChanged', [])
}
}
async clearAllFileDecorators() {
this._fileStates = []
this.emit('fileDecoratorsChanged', [])
}
}
const sortByPath = (a: fileDecoration, b: fileDecoration) => {
if (a.path < b.path) {
return -1;
}
if (a.path > b.path) {
return 1;
}
return 0;
if (a.path < b.path) {
return -1;
}
if (a.path > b.path) {
return 1;
}
return 0;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save