git4refactor
bunsenstraat 8 months ago
commit 4ff34b68fd
  1. 98
      .circleci/config.yml
  2. 2
      .github/workflows/pr-reminder.yml
  3. 2
      Dockerfile.dev
  4. 4
      apps/circuit-compiler/src/app/components/feedback.tsx
  5. 10
      apps/circuit-compiler/src/app/components/feedbackAlert.tsx
  6. 4
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  7. 1
      apps/circuit-compiler/src/app/types/index.ts
  8. 2
      apps/circuit-compiler/src/snarkjs.min.js
  9. 1
      apps/etherscan/src/app/utils/networks.ts
  10. 134
      apps/learneth/README.md
  11. 58
      apps/learneth/project.json
  12. 19
      apps/learneth/src/App.css
  13. 41
      apps/learneth/src/App.tsx
  14. 28
      apps/learneth/src/components/BackButton/index.scss
  15. 87
      apps/learneth/src/components/BackButton/index.tsx
  16. 17
      apps/learneth/src/components/LoadingScreen/index.css
  17. 16
      apps/learneth/src/components/LoadingScreen/index.tsx
  18. 4
      apps/learneth/src/components/RepoImporter/index.css
  19. 120
      apps/learneth/src/components/RepoImporter/index.tsx
  20. 21
      apps/learneth/src/components/SlideIn/index.css
  21. 18
      apps/learneth/src/components/SlideIn/index.tsx
  22. 13
      apps/learneth/src/index.css
  23. 18
      apps/learneth/src/index.html
  24. 13
      apps/learneth/src/main.tsx
  25. 24
      apps/learneth/src/pages/Home/index.css
  26. 96
      apps/learneth/src/pages/Home/index.tsx
  27. 20
      apps/learneth/src/pages/Logo/index.tsx
  28. 54
      apps/learneth/src/pages/StepDetail/index.scss
  29. 228
      apps/learneth/src/pages/StepDetail/index.tsx
  30. 144
      apps/learneth/src/pages/StepList/index.scss
  31. 41
      apps/learneth/src/pages/StepList/index.tsx
  32. 7
      apps/learneth/src/polyfills.ts
  33. 21
      apps/learneth/src/profile.json
  34. 5
      apps/learneth/src/redux/hooks.ts
  35. 14
      apps/learneth/src/redux/models/loading.ts
  36. 229
      apps/learneth/src/redux/models/remixide.ts
  37. 164
      apps/learneth/src/redux/models/workshop.ts
  38. 97
      apps/learneth/src/redux/store.ts
  39. 38
      apps/learneth/src/remix-client.ts
  40. 23
      apps/learneth/tsconfig.app.json
  41. 16
      apps/learneth/tsconfig.json
  42. 90
      apps/learneth/webpack.config.js
  43. 15
      apps/remix-ide-e2e/src/commands/addLocalPlugin.ts
  44. 32
      apps/remix-ide-e2e/src/commands/checkTerminalFilter.ts
  45. 2
      apps/remix-ide-e2e/src/commands/getLastTransactionHash.ts
  46. 8
      apps/remix-ide-e2e/src/commands/journalChildIncludes.ts
  47. 6
      apps/remix-ide-e2e/src/examples/example-contracts.ts
  48. 3
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  49. 3
      apps/remix-ide-e2e/src/tests/ballot_0_4_14.test.ts
  50. 10
      apps/remix-ide-e2e/src/tests/compiler_api.test.ts
  51. 4
      apps/remix-ide-e2e/src/tests/file_explorer_dragdrop.test.ts
  52. 34
      apps/remix-ide-e2e/src/tests/gist.test.ts
  53. 64
      apps/remix-ide-e2e/src/tests/pluginManager.test.ts
  54. 9
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  55. 2
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  56. 2
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  57. 24
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  58. 4
      apps/remix-ide-e2e/src/tests/verticalIconsPanel.test.ts
  59. 24
      apps/remix-ide-e2e/src/tests/vyper_api.test.ts
  60. 2
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  61. 18
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  62. 4
      apps/remix-ide-e2e/src/types/index.d.ts
  63. 12
      apps/remix-ide-e2e/yarn.lock
  64. 2
      apps/remix-ide/Dockerfile.dev
  65. 4
      apps/remix-ide/ci/makeMockCompiler.js
  66. 4
      apps/remix-ide/ci/sauceDisconnect.js
  67. 2
      apps/remix-ide/contracts/foundry/out/Script.sol/Script.json
  68. 8
      apps/remix-ide/meetings.md
  69. 2
      apps/remix-ide/project.json
  70. 6
      apps/remix-ide/src/app/editor/editor.js
  71. 4
      apps/remix-ide/src/app/files/dgitProvider.ts
  72. 15
      apps/remix-ide/src/app/panels/file-panel.js
  73. 2
      apps/remix-ide/src/app/plugins/code-format.ts
  74. 2
      apps/remix-ide/src/app/plugins/compile-details.tsx
  75. 9
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  76. 2
      apps/remix-ide/src/app/plugins/file-decorator.ts
  77. 2
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  78. 58
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  79. 7
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  80. 33
      apps/remix-ide/src/app/tabs/locales/en/editor.json
  81. 8
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  82. 1
      apps/remix-ide/src/app/tabs/locales/en/home.json
  83. 48
      apps/remix-ide/src/app/tabs/locales/en/index.js
  84. 1
      apps/remix-ide/src/app/tabs/locales/en/panel.json
  85. 2
      apps/remix-ide/src/app/tabs/locales/en/permissionHandler.json
  86. 1
      apps/remix-ide/src/app/tabs/locales/en/pluginManager.json
  87. 19
      apps/remix-ide/src/app/tabs/locales/en/publishToStorage.json
  88. 19
      apps/remix-ide/src/app/tabs/locales/en/remixApp.json
  89. 2
      apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json
  90. 16
      apps/remix-ide/src/app/tabs/locales/en/remixd.json
  91. 2
      apps/remix-ide/src/app/tabs/locales/en/search.json
  92. 5
      apps/remix-ide/src/app/tabs/locales/en/solidity.json
  93. 2
      apps/remix-ide/src/app/tabs/locales/en/terminal.json
  94. 4
      apps/remix-ide/src/app/tabs/locales/en/udapp.json
  95. 47
      apps/remix-ide/src/app/tabs/locales/es/index.js
  96. 47
      apps/remix-ide/src/app/tabs/locales/fr/index.js
  97. 47
      apps/remix-ide/src/app/tabs/locales/it/index.js
  98. 49
      apps/remix-ide/src/app/tabs/locales/zh/index.js
  99. 2
      apps/remix-ide/src/app/tabs/network-module.js
  100. 6
      apps/remix-ide/src/app/tabs/runTab/model/recorder.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -175,9 +175,82 @@ jobs:
key: remixdesktop-windows-deps-{{ checksum "apps/remixdesktop/yarn.lock" }}
paths:
- apps/remixdesktop/node_modules
- persist_to_workspace:
root: apps/remixdesktop
paths:
- "release"
# see https://docs.digicert.com/en/software-trust-manager/ci-cd-integrations/script-integrations/github-integration-ksp.html
sign-remixdesktop-windows:
executor: win/default # executor type
working_directory: ~/remix-project
steps:
- checkout
- attach_workspace:
at: .
- run:
name: "Certificate-Setup"
shell: powershell.exe
command: |
cd C:\
New-Item C:\CERT_FILE.p12.b64
Set-Content -Path C:\CERT_FILE.p12.b64 -Value $env:SM_CLIENT_CERT_FILE_B64
certutil -decode CERT_FILE.p12.b64 Certificate_pkcs12.p12
cat Certificate_pkcs12.p12
- restore_cache:
name: Restore smtools-windows-x64.msi
keys:
- dl-smtools-windows-x64.msi
- run:
name: "Client-Tool-Download"
shell: powershell.exe
command: |
cd C:\
if (Test-Path 'c:\smtools-windows-x64.msi') {
echo 'File exists, skipping download...'
} else {
echo 'Downloading smtools-windows-x64.msi ...'
curl.exe -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:$env:SM_API_KEY" -o smtools-windows-x64.msi
}
- save_cache:
key: dl-smtools-windows-x64.msi
paths:
- c:\smtools-windows-x64.msi
- run:
name: "Client-Tool-Setup"
shell: powershell.exe
command: |
cd C:\
msiexec.exe /i smtools-windows-x64.msi /quiet /qn | Wait-Process
& $env:SSM\smksp_cert_sync.exe
& $env:SSM\smctl.exe healthcheck
- run:
name: "Find Signtool"
shell: powershell.exe
command: |
Get-ChildItem -Path 'C:\Program Files (x86)\Windows Kits\10\App Certification Kit' -Filter signtool.exe -Recurse
- run:
name: "Signtool-Signing"
shell: powershell.exe
command: |
& $env:Signtool sign /sha1 $env:SM_CODE_SIGNING_CERT_SHA1_HASH /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 $env:RemixSetupExe
- run:
name: "Signtool-Verification"
shell: powershell.exe
command: |
$verify_output = $(& $env:Signtool verify /v /pa $env:RemixSetupExe)
echo ${verify_output}
if (!$verify_output.Contains("Number of files successfully Verified: 1")) {
echo 'Verification failed'
exit 1
}
- store_artifacts:
path: apps/remixdesktop/release/
path: ~/remix-project/release/
destination: remixdesktop-windows
environment:
SM_CLIENT_CERT_FILE: 'C:\Certificate_pkcs12.p12'
Signtool: 'C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe'
SSM: 'C:\Program Files\DigiCert\DigiCert One Signing Manager Tools'
RemixSetupExe: 'C:\Users\circleci\remix-project\release\Remix IDE.exe'
build-remixdesktop-mac:
macos:
@ -315,15 +388,24 @@ jobs:
- run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules
- run: mkdir node_modules/hardhat && wget https://unpkg.com/hardhat/console.sol -O node_modules/hardhat/console.sol
- run: ls -la ./dist/apps/remix-ide/assets/js
- run: yarn run selenium-install || yarn run selenium-install
- run: yarn run selenium-install --singleDriverInstall=firefox
- when:
condition:
equal: [ "chrome", << parameters.browser >> ]
steps:
- run: mkdir -p node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run: cp ~/bin/chromedriver /home/circleci/remix-project/node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run:
name: Start Selenium
command: yarn run selenium
name: run selenium
command: yarn selenium-standalone start --singleDriverStart=chrome
background: true
- when:
condition:
equal: [ "firefox", << parameters.browser >> ]
steps:
- run:
name: run selenium
command: yarn selenium-standalone start --singleDriverStart=firefox
background: true
- run: ./apps/remix-ide/ci/<< parameters.script >> << parameters.browser >> << parameters.jobsize >> << parameters.job >>
- store_test_results:
@ -366,11 +448,12 @@ jobs:
- run: unzip ./persist/dist.zip
- 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: yarn run selenium-install --singleDriverInstall=firefox
- run: mkdir -p node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run: cp ~/bin/chromedriver /home/circleci/remix-project/node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run:
name: Start Selenium
command: yarn run selenium
command: yarn run selenium --singleDriverStart=chrome
background: true
- run: ./apps/remix-ide/ci/browser_test_plugin.sh << parameters.plugin >>
- store_test_results:
@ -452,6 +535,9 @@ workflows:
- build-remixdesktop-windows:
requires:
- build-desktop
- sign-remixdesktop-windows:
requires:
- build-remixdesktop-windows
- build-remixdesktop-linux:
requires:
- build-desktop

@ -14,4 +14,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
freeze-date: '2024-01-29T18:00:00Z'
freeze-date: '2024-02-26T18:00:00Z'

@ -1,4 +1,4 @@
# This dockerfile is to build each branch seperately (for dev purposes)
# This dockerfile is to build each branch separately (for dev purposes)
FROM node:10
# Create Remix user, don't use root!
# RUN yes | adduser --disabled-password remix && mkdir /app

@ -45,8 +45,7 @@ export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openEr
<RenderIf condition={response.type === 'Error'}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-danger`} data-id="circuit_feedback">
<FeedbackAlert
message={response.message}
location={ response.labels[0] ? response.labels[0].message + ` ${filePathToId[response.labels[0].file_id]}:${response.labels[0].range.start}:${response.labels[0].range.end}` : null}
message={response.message + (response.labels[0] ? ": " + response.labels[0].message + ` ${filePathToId[response.labels[0].file_id]}:${response.labels[0].range.start}:${response.labels[0].range.end}` : '')}
askGPT={ () => handleAskGPT(response) } />
</div>
</RenderIf>
@ -54,7 +53,6 @@ export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openEr
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-warning`} data-id="circuit_feedback">
<FeedbackAlert
message={response.message}
location={null}
askGPT={() => { handleAskGPT(response) }} />
</div>
</RenderIf>

@ -3,7 +3,7 @@ import { FeedbackAlertProps } from '../types'
import { RenderIf } from '@remix-ui/helper'
import {CopyToClipboard} from '@remix-ui/clipboard'
export function FeedbackAlert ({ message, location, askGPT }: FeedbackAlertProps) {
export function FeedbackAlert ({ message, askGPT }: FeedbackAlertProps) {
const [ showAlert, setShowAlert] = useState<boolean>(true)
const handleCloseAlert = () => {
@ -14,9 +14,6 @@ export function FeedbackAlert ({ message, location, askGPT }: FeedbackAlertProps
<RenderIf condition={showAlert}>
<>
<span> { message } </span>
<RenderIf condition={location !== null}>
<span> { location }</span>
</RenderIf>
<div className="close" data-id="renderer" onClick={handleCloseAlert}>
<i className="fas fa-times"></i>
</div>
@ -24,7 +21,10 @@ export function FeedbackAlert ({ message, location, askGPT }: FeedbackAlertProps
<span className="ml-3 pt-1 py-1" >
<CopyToClipboard content={message} className="p-0 m-0 far fa-copy error" direction={'top'} />
</span>
<span className="border border-success text-success btn-sm" onClick={askGPT}>ASK GPT</span>
<span className="border border-success text-success btn-sm" onClick={(e) => {
e.stopPropagation()
askGPT()
}}>ASK GPT</span>
</div>
</>
</RenderIf>

@ -21,7 +21,7 @@ export class CircomPluginClient extends PluginClient {
constructor() {
super()
this.methods = ['init', 'parse', 'compile', 'generateR1cs']
this.methods = ['init', 'parse', 'compile', 'generateR1cs', 'resolveDependencies']
createClient(this)
this.internalEvents = new EventManager()
this.onload()
@ -298,7 +298,7 @@ export class CircomPluginClient extends PluginClient {
}
} else {
if (depPath) {
// resolves relative dependecies for .deps folder
// resolves relative dependencies for .deps folder
path = pathModule.resolve(depPath.slice(0, depPath.lastIndexOf('/')), include)
path = path.replace('https:/', 'https://')
if (path.indexOf('/') === 0) path = path.slice(1)

@ -72,7 +72,6 @@ export type CompilerReport = {
export type FeedbackAlertProps = {
message: string,
location: string,
askGPT: () => void
}

File diff suppressed because one or more lines are too long

@ -34,6 +34,7 @@ export const scanAPIurls = {
44787: 'https://api-alfajores.celoscan.io/api',
2888: 'https://api-testnet.bobascan.com/api',
84531: 'https://api-goerli.basescan.org/api',
84532: "https://api-sepolia.basescan.org/api",
1442: 'https://api-testnet-zkevm.polygonscan.com/api',
59140: 'https://api-testnet.lineascan.build/api',
}

@ -0,0 +1,134 @@
# Remix LearnEth Plugin
## Available Scripts
In the project directory, you can run:
### `npm run serve:plugin --plugin=learneth`
Runs the app in the development mode.\
Open [http://localhost:2024](http://localhost:2024) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm run build:plugin --plugin=learneth`
Builds the app for production to the `dist/apps/learneth` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
## Loading the plugin in remix
When testing with localhost you should use the HTTP version of either REMIX or REMIX ALPHA. Click on the plugin manager icon and
add the plugin 'Connect to a local plugin'. Your plugin will be at http://localhost:2024/.
## Setting up the REMIX IDE for working with the plugin
The plugin only works when a compiler environment is loaded as well, for example on the home screen of the IDE you select 'Solidity' or 'Vyper'. Without this the plugin
cannot compile and test files in the workshops.
## Setting up your Github workshops repo
You can create your own workshops that can be imported in the plugin.
When importing a github repo the plugin will look for a directory structure describing the workshops.
For example: https://github.com/ethereum/remix-workshops
### Root directories
Root directories are individual workshops, the name used will be the name of the workshop unless you override this with the name property in the config.yml.
### README.md
The readme in each directry contains an explanation of what the workshop is about. If an additional summary property is provided in the config.yml that will be used in the overview section of the plugin.
### config.yml
This config file contains meta data describing some properties of your workshop, for example
```
---
id: someid
name: my workshop name
summary: something about this workshop
level: 4
tags:
- solidity
- beginner
```
Level: a level of difficulty indicator ( 1 - 5 )
Tags: an array of tags
id: this is used by the system to let REMIX call startTutorial(repo,branch,id). See below for more instructions.
### Steps
Each workshop contains what we call steps.
Each step is a directory containing:
- a readme describing the step, what to do.
- sol files:
- these can be sol files and test sol files. The test files should be name yoursolname_test.sol
- ANSWER files: these are named yoursolname_answer.sol and can be used to show the solution or the correct answer. The plugin will load the
file in the IDE when a user clicks on 'Show Answer'
- js files
- vyper files
## Functions to call the plugin from the IDE
### Add a repository:
```
addRepository(repoName, branch)
```
### Start a tutorial
```
startTutorial(repoName,branch,id)
```
You don't need to add a seperate addRepository before calling startTutorial, this call will also add the repo.
_Parameters_
id: this can be two things:
- type of number, it specifies the n-th tutorial in the list
- type of string, this refers to the ID parameter in the config.yml file in the tutorial
for example:
```
---
id: basics
name: 1 Basics of Solidity
summary: Some basic functions explained
level: 4
tags:
- solidity
```
### How to call these functions in the REMIX IDE
```
(function () {
try {
// You don't need to add a seperate addRepository before calling startTutorial, this is just an example
remix.call('LearnEth', 'addRepository', "ethereum/remix-workshops", "master")
remix.call('LearnEth', 'startTutorial', "ethereum/remix-workshops", "master", "basics")
remix.call('LearnEth', 'startTutorial', "ethereum/remix-workshops", "master", 2)
} catch (e) {
console.log(e.message)
}
})()
```
Then call this in the REMIX console
```
remix.exeCurrent()
```

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

@ -0,0 +1,19 @@
/* You can add global styles to this file, and also import other style files */
h1{
font-size: 1.2rem !important;
font-weight: 700;
}
h2{
font-size: 1rem !important;
font-weight: 700;
}
h3{
font-size: 1rem !important;
}
p {
font-size: 0.9rem;
}

@ -0,0 +1,41 @@
import React from 'react'
import {createHashRouter, RouterProvider} from 'react-router-dom'
import {ToastContainer} from 'react-toastify'
import LoadingScreen from './components/LoadingScreen'
import LogoPage from './pages/Logo'
import HomePage from './pages/Home'
import StepListPage from './pages/StepList'
import StepDetailPage from './pages/StepDetail'
import 'react-toastify/dist/ReactToastify.css'
import './App.css'
export const router = createHashRouter([
{
path: '/',
element: <LogoPage />,
},
{
path: '/home',
element: <HomePage />,
},
{
path: '/list',
element: <StepListPage />,
},
{
path: '/detail',
element: <StepDetailPage />,
},
])
function App(): JSX.Element {
return (
<>
<RouterProvider router={router} />
<LoadingScreen />
<ToastContainer position="bottom-right" newestOnTop closeOnClick rtl={false} pauseOnFocusLoss draggable pauseOnHover autoClose={false} theme="colored" />
</>
)
}
export default App

@ -0,0 +1,28 @@
a {
.arrow {
display: inline-block;
opacity: 0;
transform: scale(0.5);
transition: all 0.3s;
}
span {
display: inline-block;
padding-left: 5px;
transform: translateX(-0.875em); // size of icon
transition: transform 0.3s;
}
}
.workshoptitle{
text-decoration: none;
}
a:hover {
fa-icon {
opacity: 1;
transform: scale(1);
}
span {
transform: translateX(0);
}
}

@ -0,0 +1,87 @@
import React, {useState} from 'react'
import {Link, useLocation, useNavigate} from 'react-router-dom'
import {Button, Modal, Tooltip, OverlayTrigger} from 'react-bootstrap'
import './index.scss'
function BackButton({entity}: any) {
const navigate = useNavigate()
const location = useLocation()
const [show, setShow] = useState(false)
const isDetailPage = location.pathname === '/detail'
const queryParams = new URLSearchParams(location.search)
const stepId = Number(queryParams.get('stepId'))
return (
<nav className="navbar navbar-light bg-light justify-content-between pt-1 pb-1 pl-1">
<ul className="nav mr-auto">
<li className="nav-item">
<div
className="btn back"
onClick={() => {
setShow(true)
}}
role="button"
>
<OverlayTrigger placement="right" overlay={<Tooltip id="tooltip-right">Leave tutorial</Tooltip>}>
<i className="fas fa-home pl-1" />
</OverlayTrigger>
</div>
</li>
{isDetailPage && (
<li className="nav-item">
<Link className="btn" to={`/list?id=${entity.id}`} title="Tutorial menu">
<i className="fas fa-bars" />
</Link>
</li>
)}
</ul>
{isDetailPage && (
<form className="form-inline">
{stepId > 0 && (
<Link to={`/detail?id=${entity.id}&stepId=${stepId - 1}`}>
<i className="fas fa-chevron-left pr-1" />
</Link>
)}
{stepId + 1}/{entity && <div className="">{entity.steps.length}</div>}
{stepId < entity.steps.length - 1 && (
<Link to={`/detail?id=${entity.id}&stepId=${stepId + 1}`}>
<i className="fas fa-chevron-right pl-1" />
</Link>
)}
</form>
)}
<Modal
show={show}
onHide={() => {
setShow(false)
}}
>
<Modal.Header placeholder={''} closeButton>
<Modal.Title>Leave tutorial</Modal.Title>
</Modal.Header>
<Modal.Body>Are you sure you want to leave the tutorial?</Modal.Body>
<Modal.Footer>
<Button
variant="secondary"
onClick={() => {
setShow(false)
}}
>
No
</Button>
<Button
variant="success"
onClick={() => {
setShow(false)
navigate('/home')
}}
>
Yes
</Button>
</Modal.Footer>
</Modal>
</nav>
)
}
export default BackButton

@ -0,0 +1,17 @@
.spinnersOverlay {
background-color: rgba(51, 51, 51, 0.8);
z-index: 99;
opacity: 1;
height: 100%;
left: 0;
position: fixed;
top: 0;
width: 100%;
}
.spinnersLoading {
left: 50%;
margin: 0;
position: absolute;
top: 50%;
transform: translate(-50%,-50%);
}

@ -0,0 +1,16 @@
import React from 'react'
import BounceLoader from 'react-spinners/BounceLoader'
import './index.css'
import {useAppSelector} from '../../redux/hooks'
const LoadingScreen: React.FC = () => {
const loading = useAppSelector((state) => state.loading.screen)
return loading ? (
<div className="spinnersOverlay">
<BounceLoader color="#a7b0ae" size={100} className="spinnersLoading" />
</div>
) : null
}
export default LoadingScreen

@ -0,0 +1,4 @@
.arrow-icon{
width: 3px;
display: inline-block;
}

@ -0,0 +1,120 @@
import React, {useState, useEffect} from 'react'
import {Button, Dropdown, Form, Tooltip, OverlayTrigger} from 'react-bootstrap'
import {useAppDispatch} from '../../redux/hooks'
import './index.css'
function RepoImporter({list, selectedRepo}: any): JSX.Element {
const [open, setOpen] = useState(false)
const [name, setName] = useState('')
const [branch, setBranch] = useState('')
const dispatch = useAppDispatch()
useEffect(() => {
setName(selectedRepo.name)
setBranch(selectedRepo.branch)
}, [selectedRepo])
const panelChange = () => {
setOpen(!open)
}
const selectRepo = (repo: {name: string; branch: string}) => {
dispatch({type: 'workshop/loadRepo', payload: repo})
}
const importRepo = (event: {preventDefault: () => void}) => {
event.preventDefault()
dispatch({type: 'workshop/loadRepo', payload: {name, branch}})
}
const resetAll = () => {
dispatch({type: 'workshop/resetAll'})
setName('')
setBranch('')
}
return (
<>
{selectedRepo.name && (
<div className="container-fluid mb-3 small mt-3">
Tutorials from:
<h4 className="mb-1">{selectedRepo.name}</h4>
<span className="">Date modified: {new Date(selectedRepo.datemodified).toLocaleString()}</span>
</div>
)}
<div onClick={panelChange} style={{cursor: 'pointer'}} className="container-fluid d-flex mb-3 small">
<div className="d-flex pr-2 pl-2">
<i className={`arrow-icon pt-1 fas fa-xs ${open ? 'fa-chevron-down' : 'fa-chevron-right'}`}></i>
</div>
<div className="d-flex">Import another tutorial repo</div>
</div>
{open && (
<div className="container-fluid">
<Dropdown className="w-100">
<Dropdown.Toggle className="btn btn-secondary w-100" id="dropdownBasic1">
Select a repo
</Dropdown.Toggle>
<Dropdown.Menu className="w-100">
{list.map((item: any) => (
<Dropdown.Item
key={`${item.name}/${item.branch}`}
onClick={() => {
selectRepo(item)
}}
>
{item.name}-{item.branch}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
<div onClick={resetAll} className="small mb-3" style={{cursor: 'pointer'}}>
reset list
</div>
</div>
)}
<div className="container-fluid mt-3">
{open && (
<Form onSubmit={importRepo}>
<Form.Group className="form-group">
<Form.Label className="mr-2" htmlFor="name">
REPO
</Form.Label>
<OverlayTrigger placement="right" overlay={<Tooltip id="tooltip-right">ie username/repository</Tooltip>}>
<i className="fas fa-question-circle" />
</OverlayTrigger>
<Form.Control
id="name"
required
onChange={(e) => {
setName(e.target.value)
}}
value={name}
/>
<Form.Label htmlFor="branch">BRANCH</Form.Label>
<Form.Control
id="branch"
required
onChange={(e) => {
setBranch(e.target.value)
}}
value={branch}
/>
</Form.Group>
<Button className="btn btn-success start w-100" type="submit" disabled={!name || !branch}>
Import {name}
</Button>
<a href="https://github.com/bunsenstraat/remix-learneth-plugin/blob/master/README.md" className="d-none" target="_blank" rel="noreferrer">
<i className="fas fa-info-circle" /> how to setup your repo
</a>
</Form>
)}
<hr />
</div>
</>
)
}
export default RepoImporter

@ -0,0 +1,21 @@
.slide-enter {
transform: translateY(100px);
opacity: 0;
}
.slide-enter-active {
transform: translateY(0);
opacity: 1;
transition: opacity 400ms, transform 400ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.slide-exit {
transform: translateY(0);
opacity: 1;
}
.slide-exit-active {
transform: translateY(100px);
opacity: 0;
transition: opacity 400ms, transform 400ms cubic-bezier(0.6, 0.04, 0.98, 0.335);
}

@ -0,0 +1,18 @@
import React, {type ReactNode, useEffect, useState} from 'react'
import {CSSTransition} from 'react-transition-group'
import './index.css'
const SlideIn: React.FC<{children: ReactNode}> = ({children}) => {
const [show, setShow] = useState(false)
useEffect(() => {
setShow(true)
}, [])
return (
<CSSTransition in={show} timeout={400} classNames="slide" unmountOnExit>
{children}
</CSSTransition>
)
}
export default SlideIn

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Learn ETH</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"> -->
<link rel="stylesheet" integrity="ha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf"
crossorigin="anonymous" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css">
</head>
<body>
<div id="root"></div>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script>
</body>
</html>

@ -0,0 +1,13 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import {Provider} from 'react-redux'
import './index.css'
import App from './App'
import {store} from './redux/store'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<Provider store={store}>
<App />
</Provider>
)

@ -0,0 +1,24 @@
.description-collapsed{
height: 0px;
overflow: hidden;
word-wrap: break-word;
padding: 0px !important;
margin: 0px !important;
}
.tag{
display: inline;
}
.arrow-icon{
width: 12px;
display: inline-block;
cursor: pointer;
}
.workshop-link {
cursor: pointer;
}
.workshop-link:hover {
text-decoration: underline;
}

@ -0,0 +1,96 @@
import React, {useEffect} from 'react'
import {Link} from 'react-router-dom'
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import remarkGfm from 'remark-gfm'
import {useAppDispatch, useAppSelector} from '../../redux/hooks'
import RepoImporter from '../../components/RepoImporter'
import './index.css'
function HomePage(): JSX.Element {
const [openKeys, setOpenKeys] = React.useState<string[]>([])
const isOpen = (key: string) => openKeys.includes(key)
const handleClick = (key: string) => {
setOpenKeys(isOpen(key) ? openKeys.filter((item) => item !== key) : [...openKeys, key])
}
const dispatch = useAppDispatch()
const {list, detail, selectedId} = useAppSelector((state) => state.workshop)
const selectedRepo = detail[selectedId]
const levelMap: any = {
1: 'Beginner',
2: 'Intermediate',
3: 'Advanced',
}
useEffect(() => {
dispatch({
type: 'workshop/init',
})
}, [])
return (
<div className="App">
<RepoImporter list={list} selectedRepo={selectedRepo || {}} />
{selectedRepo && (
<div className="container-fluid">
{Object.keys(selectedRepo.group).map((level) => (
<div key={level}>
<div className="mb-2 border-bottom small">{levelMap[level]}:</div>
{selectedRepo.group[level].map((item: any) => (
<div key={item.id}>
<div>
<span
className="arrow-icon"
onClick={() => {
handleClick(item.id)
}}
>
<i className={`fas fa-xs ${isOpen(item.id) ? 'fa-chevron-down' : 'fa-chevron-right'}`} />
</span>
<span
className="workshop-link"
onClick={() => {
handleClick(item.id)
}}
>
{selectedRepo.entities[item.id].name}
</span>
<Link to={`/list?id=${item.id}`} className="text-decoration-none float-right">
<i className="fas fa-play-circle fa-lg" />
</Link>
</div>
<div className={`container-fluid bg-light pt-3 mt-2 ${isOpen(item.id) ? '' : 'description-collapsed'}`}>
{levelMap[level] && <p className="tag pt-2 pr-1 font-weight-bold small text-uppercase">{levelMap[level]}</p>}
{selectedRepo.entities[item.id].metadata.data.tags?.map((tag: string) => (
<p key={tag} className="tag pr-1 font-weight-bold small text-uppercase">
{tag}
</p>
))}
{selectedRepo.entities[item.id].steps && <div className="d-none">{selectedRepo.entities[item.id].steps.length} step(s)</div>}
<div className="workshop-list_description pb-3 pt-3">
<Markdown rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{selectedRepo.entities[item.id].description?.content}
</Markdown>
</div>
<div className="actions"></div>
</div>
<div className="mb-3"></div>
</div>
))}
</div>
))}
</div>
)}
</div>
)
}
export default HomePage

@ -0,0 +1,20 @@
import React, {useEffect} from 'react'
import {useAppDispatch} from '../../redux/hooks'
const LogoPage: React.FC = () => {
const dispatch = useAppDispatch()
useEffect(() => {
dispatch({type: 'remixide/connect'})
}, [])
return (
<div>
<div>
<img className="w-100" src="https://remix.ethereum.org/assets/img/remixLogo.webp" />
</div>
</div>
)
}
export default LogoPage

@ -0,0 +1,54 @@
step-view {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
}
header, footer {
padding: 10px 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.errorloadingspacer{
padding-top: 44px;
}
.title{
pointer-events: none;
}
h1 {
text-align: left;
font-size: 1.2rem !important;
word-break: break-word;
}
markdown {
display: block;
flex: 1;
overflow: auto;
padding: 0px;
h1 {
font-size: 1.2rem !important;
}
h2 {
font-size: 1rem;
}
h3 {
font-size: 1rem;
}
h4 {
font-size: 1rem;
}
}

@ -0,0 +1,228 @@
import React, {useEffect} from 'react'
import {useLocation, useNavigate} from 'react-router-dom'
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import BackButton from '../../components/BackButton'
import {useAppSelector, useAppDispatch} from '../../redux/hooks'
import './index.scss'
function StepDetailPage() {
const navigate = useNavigate()
const location = useLocation()
const dispatch = useAppDispatch()
const queryParams = new URLSearchParams(location.search)
const id = queryParams.get('id') as string
const stepId = Number(queryParams.get('stepId'))
const {
workshop: {detail, selectedId},
remixide: {errorLoadingFile, errors, success},
} = useAppSelector((state: any) => state)
const entity = detail[selectedId].entities[id]
const steps = entity.steps
const step = steps[stepId]
console.log(step)
useEffect(() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
})
dispatch({
type: 'remixide/save',
payload: {errors: [], success: false},
})
window.scrollTo(0, 0)
}, [step])
useEffect(() => {
if (errors.length > 0 || success) {
window.scrollTo(0, document.documentElement.scrollHeight)
}
}, [errors, success])
return (
<>
<div className="fixed-top">
<div className="bg-light">
<BackButton entity={entity} />
</div>
</div>
<div id="top"></div>
{errorLoadingFile ? (
<>
<div className="errorloadingspacer"></div>
<h1 className="pl-3 pr-3 pt-3 pb-1">{step.name}</h1>
<button
className="w-100nav-item rounded-0 nav-link btn btn-success test"
onClick={() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
})
}}
>
Load the file
</button>
<div className="mb-4"></div>
</>
) : (
<>
<div className="menuspacer"></div>
<h1 className="pr-3 pl-3 pt-3 pb-1">{step.name}</h1>
</>
)}
<div className="container-fluid">
<Markdown rehypePlugins={[rehypeRaw]}>{step.markdown?.content}</Markdown>
</div>
{step.test?.content ? (
<>
<nav className="nav nav-pills nav-fill">
{errorLoadingFile ? (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
})
}}
>
Load the file
</button>
) : (
<>
{!errorLoadingFile ? (
<>
<button
className="nav-item rounded-0 nav-link btn btn-info test"
onClick={() => {
dispatch({
type: 'remixide/testStep',
payload: step,
})
}}
>
Check Answer
</button>
{step.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
})
}}
>
Show answer
</button>
)}
</>
) : (
<>
{!errorLoadingFile && (
<>
<button
className="nav-item rounded-0 nav-link btn btn-success test"
onClick={() => {
navigate(stepId === steps.length - 1 ? `/list?id=${id}` : `/detail?id=${id}&stepId=${stepId + 1}`)
}}
>
Next
</button>
{step.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
})
}}
>
Show answer
</button>
)}
</>
)}
</>
)}
</>
)}
</nav>
{success && (
<button
className="w-100 rounded-0 nav-item nav-link btn btn-success"
onClick={() => {
navigate(stepId === steps.length - 1 ? `/list?id=${id}` : `/detail?id=${id}&stepId=${stepId + 1}`)
}}
>
Next
</button>
)}
<div id="errors">
{success && (
<div className="alert rounded-0 alert-success mb-0 mt-0" role="alert">
Well done! No errors.
</div>
)}
{errors.length > 0 && (
<>
{!success && (
<div className="alert rounded-0 alert-danger mb-0 mt-0" role="alert">
Errors
</div>
)}
{errors.map((error: string, index: number) => (
<div key={index} className="alert rounded-0 alert-warning mb-0 mt-0">
{error}
</div>
))}
</>
)}
</div>
</>
) : (
<>
<nav className="nav nav-pills nav-fill">
{!errorLoadingFile && step.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
})
}}
>
Show answer
</button>
)}
</nav>
{stepId < steps.length - 1 && (
<button
className="w-100 btn btn-success"
onClick={() => {
navigate(`/detail?id=${id}&stepId=${stepId + 1}`)
}}
>
Next
</button>
)}
{stepId === steps.length - 1 && (
<button
className="w-100 btn btn-success"
onClick={() => {
navigate(`/list?id=${id}`)
}}
>
Finish tutorial
</button>
)}
</>
)}
</>
)
}
export default StepDetailPage

@ -0,0 +1,144 @@
:host {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
header {
padding: 10px 5px;
}
.menuspacer{
margin-top: 52px;
}
.steplink {
text-decoration: none;
}
.title{
pointer-events: none;
}
h1 {
text-align: left;
font-size: 1.2rem !important;
word-break: break-word;
}
section {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.start {
padding: 5px 25px;
animation: jittery 2s 0.5s infinite;
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.5);
color: white;
cursor: pointer;
}
}
footer {
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
@keyframes jittery {
5%,
50% {
transform: scale(1);
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.5);
}
10% {
transform: scale(0.9);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
}
15% {
transform: scale(1.15);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
20% {
transform: scale(1.15) rotate(-5deg);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
25% {
transform: scale(1.15) rotate(5deg);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
30% {
transform: scale(1.15) rotate(-3deg);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
35% {
transform: scale(1.15) rotate(2deg);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
40% {
transform: scale(1.15) rotate(0);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
}
.slide-in {
animation: slideIn 0.5s forwards;
visibility: hidden;
}
@keyframes slideIn {
0% {
transform: translateY(-100%);
visibility: visible;
}
100% {
transform: translateY(0);
visibility: visible;
}
}
@-moz-keyframes slideIn {
0% {
transform: translateY(-100%);
visibility: visible;
}
100% {
transform: translateY(0);
visibility: visible;
}
}
@-webkit-keyframes slideIn {
0% {
transform: translateY(-100%);
visibility: visible;
}
100% {
transform: translateY(0);
visibility: visible;
}
}
@-o-keyframes slideIn {
0% {
transform: translateY(-100%);
visibility: visible;
}
100% {
transform: translateY(0);
visibility: visible;
}
}
@-ms-keyframes slideIn {
0% {
transform: translateY(-100%);
visibility: visible;
}
100% {
transform: translateY(0);
visibility: visible;
}
}

@ -0,0 +1,41 @@
import React from 'react'
import {Link, useLocation} from 'react-router-dom'
import Markdown from 'react-markdown'
import BackButton from '../../components/BackButton'
import SlideIn from '../../components/SlideIn'
import {useAppSelector} from '../../redux/hooks'
import './index.scss'
function StepListPage(): JSX.Element {
const location = useLocation()
const queryParams = new URLSearchParams(location.search)
const id = queryParams.get('id') as string
const {detail, selectedId} = useAppSelector((state) => state.workshop)
const entity = detail[selectedId].entities[id]
return (
<>
<div className="fixed-top">
<div className="bg-light">
<BackButton />
</div>
</div>
<div id="top"></div>
<h1 className="pl-3 pr-3 pt-2 pb-1 menuspacer">{entity.name}</h1>
<div className="container-fluid">
<Markdown>{entity.text}</Markdown>
</div>
<SlideIn>
<article className="list-group m-3">
{entity.steps.map((step: any, i: number) => (
<Link key={i} to={`/detail?id=${id}&stepId=${i}`} className="rounded-0 btn btn-light border-bottom text-left steplink">
{step.name} »
</Link>
))}
</article>
</SlideIn>
</>
)
}
export default StepListPage

@ -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,21 @@
{
"name": "LearnEth",
"displayName": "LearnEth",
"description": "Learn Ethereum with Remix!",
"documentation": "https://remix-learneth-plugin.readthedocs.io/en/latest/index.html",
"version": "0.1.0",
"methods": [
"startTutorial",
"addRepository"
],
"kind": "none",
"icon": "assets/img/learnEthLogo.webp",
"location": "sidePanel",
"url": "plugins/learneth/index.html",
"repo": "https://github.com/ethereum/remix-project/tree/master/apps/learneth",
"maintainedBy": "Remix",
"authorContact": "",
"targets": [
"remix"
]
}

@ -0,0 +1,5 @@
import {useDispatch, type TypedUseSelectorHook, useSelector} from 'react-redux'
import {type AppDispatch, type RootState} from './store'
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

@ -0,0 +1,14 @@
import {type ModelType} from '../store'
const Model: ModelType = {
namespace: 'loading',
state: {screen: true},
reducers: {
save(state, {payload}) {
return {...state, ...payload}
},
},
effects: {},
}
export default Model

@ -0,0 +1,229 @@
import {toast} from 'react-toastify'
import {type ModelType} from '../store'
import remixClient from '../../remix-client'
import {router} from '../../App'
function getFilePath(file: string): string {
const name = file.split('/')
return name.length > 1 ? `${name[name.length - 1]}` : ''
}
const Model: ModelType = {
namespace: 'remixide',
state: {
errors: [],
success: false,
errorLoadingFile: false,
// theme: '',
},
reducers: {
save(state, {payload}) {
return {...state, ...payload}
},
},
effects: {
*connect(_, {put}) {
toast.info('connecting to the REMIX IDE')
yield put({
type: 'loading/save',
payload: {
screen: true,
},
})
yield remixClient.onload()
toast.dismiss()
yield put({
type: 'loading/save',
payload: {
screen: false,
},
})
yield router.navigate('/home')
},
*displayFile({payload: step}, {select, put}) {
let content = ''
let path = ''
if (step.solidity?.file) {
content = step.solidity.content
path = getFilePath(step.solidity.file)
}
if (step.js?.file) {
content = step.js.content
path = getFilePath(step.js.file)
}
if (step.vy?.file) {
content = step.vy.content
path = getFilePath(step.vy.file)
}
if (!content) {
return
}
toast.info(`loading ${path} into IDE`)
yield put({
type: 'loading/save',
payload: {
screen: true,
},
})
const {detail, selectedId} = yield select((state) => state.workshop)
const workshop = detail[selectedId]
console.log('loading ', step, workshop)
path = `.learneth/${workshop.name}/${step.name}/${path}`
try {
const isExist = yield remixClient.call('fileManager', 'exists' as any, path)
if (!isExist) {
yield remixClient.call('fileManager', 'setFile', path, content)
}
yield remixClient.call('fileManager', 'switchFile', `${path}`)
yield put({
type: 'remixide/save',
payload: {errorLoadingFile: false},
})
toast.dismiss()
} catch (error) {
toast.dismiss()
toast.error('File could not be loaded. Please try again.')
yield put({
type: 'remixide/save',
payload: {errorLoadingFile: true},
})
}
yield put({
type: 'loading/save',
payload: {
screen: false,
},
})
},
*testStep({payload: step}, {select, put}) {
yield put({
type: 'loading/save',
payload: {
screen: true,
},
})
try {
yield put({
type: 'remixide/save',
payload: {success: false},
})
const {detail, selectedId} = yield select((state) => state.workshop)
const workshop = detail[selectedId]
let path: string
if (step.solidity.file) {
path = getFilePath(step.solidity.file)
path = `.learneth/${workshop.name}/${step.name}/${path}`
yield remixClient.call('fileManager', 'switchFile', `${path}`)
}
console.log('testing ', step.test.content)
path = getFilePath(step.test.file)
path = `.learneth/${workshop.name}/${step.name}/${path}`
yield remixClient.call('fileManager', 'setFile', path, step.test.content)
const result = yield remixClient.call('solidityUnitTesting', 'testFromPath', path)
console.log('result ', result)
if (!result) {
yield put({
type: 'remixide/save',
payload: {errors: ['Compiler failed to test this file']},
})
} else {
const success = result.totalFailing === 0
if (success) {
yield put({
type: 'remixide/save',
payload: {errors: [], success: true},
})
} else {
yield put({
type: 'remixide/save',
payload: {
errors: result.errors.map((error: {message: any}) => error.message),
},
})
}
}
} catch (err) {
console.log('TESTING ERROR', err)
yield put({
type: 'remixide/save',
payload: {errors: [String(err)]},
})
}
yield put({
type: 'loading/save',
payload: {
screen: false,
},
})
},
*showAnswer({payload: step}, {select, put}) {
yield put({
type: 'loading/save',
payload: {
screen: true,
},
})
toast.info('loading answer into IDE')
try {
console.log('loading ', step)
const content = step.answer.content
let path = getFilePath(step.answer.file)
const {detail, selectedId} = yield select((state) => state.workshop)
const workshop = detail[selectedId]
path = `.learneth/${workshop.name}/${step.name}/${path}`
yield remixClient.call('fileManager', 'setFile', path, content)
yield remixClient.call('fileManager', 'switchFile', `${path}`)
} catch (err) {
yield put({
type: 'remixide/save',
payload: {errors: [String(err)]},
})
}
toast.dismiss()
yield put({
type: 'loading/save',
payload: {
screen: false,
},
})
},
*testSolidityCompiler(_, {put, select}) {
try {
yield remixClient.call('solidity', 'getCompilationResult')
} catch (err) {
const errors = yield select((state) => state.remixide.errors)
yield put({
type: 'remixide/save',
payload: {
errors: [...errors, "The `Solidity Compiler` is not yet activated.<br>Please activate it using the `SOLIDITY` button in the `Featured Plugins` section of the homepage.<img class='img-thumbnail mt-3' src='assets/activatesolidity.png'>"],
},
})
}
},
},
}
export default Model

@ -0,0 +1,164 @@
import axios from 'axios'
import {toast} from 'react-toastify'
import groupBy from 'lodash/groupBy'
import pick from 'lodash/pick'
import {type ModelType} from '../store'
import remixClient from '../../remix-client'
import {router} from '../../App'
// const apiUrl = 'http://localhost:3001';
const apiUrl = 'https://static.220.14.12.49.clients.your-server.de:3000'
const Model: ModelType = {
namespace: 'workshop',
state: {
list: [],
detail: {},
selectedId: '',
},
reducers: {
save(state, {payload}) {
return {...state, ...payload}
},
},
effects: {
*init(_, {put}) {
const cache = localStorage.getItem('workshop.state')
if (cache) {
const workshopState = JSON.parse(cache)
yield put({
type: 'workshop/save',
payload: workshopState,
})
} else {
yield put({
type: 'workshop/loadRepo',
payload: {
name: 'ethereum/remix-workshops',
branch: 'master',
},
})
}
},
*loadRepo({payload}, {put, select}) {
toast.info(`loading ${payload.name}/${payload.branch}`)
yield put({
type: 'loading/save',
payload: {
screen: true,
},
})
const {list, detail} = yield select((state) => state.workshop)
const url = `${apiUrl}/clone/${encodeURIComponent(payload.name)}/${payload.branch}?${Math.random()}`
console.log('loading ', url)
const {data} = yield axios.get(url)
console.log(data)
const repoId = `${payload.name}-${payload.branch}`
for (let i = 0; i < data.ids.length; i++) {
const {
steps,
metadata: {
data: {steps: metadataSteps},
},
} = data.entities[data.ids[i]]
let newSteps = []
if (metadataSteps) {
newSteps = metadataSteps.map((step: any) => {
return {
...steps.find((item: any) => item.name === step.path),
name: step.name,
}
})
} else {
newSteps = steps.map((step: any) => ({
...step,
name: step.name.replace('_', ' '),
}))
}
const stepKeysWithFile = ['markdown', 'solidity', 'test', 'answer', 'js', 'vy']
for (let j = 0; j < newSteps.length; j++) {
const step = newSteps[j]
for (let k = 0; k < stepKeysWithFile.length; k++) {
const key = stepKeysWithFile[k]
if (step[key]) {
try {
step[key].content = (yield remixClient.call('contentImport', 'resolve', step[key].file)).content
} catch (error) {
console.error(error)
}
}
}
}
data.entities[data.ids[i]].steps = newSteps
}
const workshopState = {
detail: {
...detail,
[repoId]: {
...data,
group: groupBy(
data.ids.map((id: string) => pick(data.entities[id], ['level', 'id'])),
(item: any) => item.level
),
...payload,
},
},
list: detail[repoId] ? list : [...list, payload],
selectedId: repoId,
}
yield put({
type: 'workshop/save',
payload: workshopState,
})
localStorage.setItem('workshop.state', JSON.stringify(workshopState))
toast.dismiss()
yield put({
type: 'loading/save',
payload: {
screen: false,
},
})
if (payload.id) {
const {detail, selectedId} = workshopState
const {ids, entities} = detail[selectedId]
for (let i = 0; i < ids.length; i++) {
const entity = entities[ids[i]]
if (entity.metadata.data.id === payload.id || i + 1 === payload.id) {
yield router.navigate(`/list?id=${ids[i]}`)
break
}
}
}
},
*resetAll(_, {put}) {
yield put({
type: 'workshop/save',
payload: {
list: [],
detail: {},
selectedId: '',
},
})
localStorage.removeItem('workshop.state')
yield put({
type: 'workshop/init',
})
},
},
}
export default Model

@ -0,0 +1,97 @@
import {configureStore, createSlice, type PayloadAction, type Reducer} from '@reduxjs/toolkit'
import createSagaMiddleware from 'redux-saga'
import {call, put, takeEvery, delay, select, all, fork, type ForkEffect} from 'redux-saga/effects'
// @ts-expect-error
const context = require.context('./models', false, /\.ts$/)
const models = context.keys().map((key: any) => context(key).default)
export type StateType = Record<string, any>
export interface ModelType {
namespace: string
state: StateType
reducers: Record<string, (state: StateType, action: PayloadAction<any>) => StateType>
effects: Record<
string,
(
action: PayloadAction<any>,
effects: {
call: typeof call
put: typeof put
delay: typeof delay
select: typeof select
}
) => Generator<any, void, any>
>
}
function createReducer(model: ModelType): Reducer {
const reducers = model.reducers
Object.keys(model.effects).forEach((key) => {
reducers[key] = (state: StateType, action: PayloadAction<any>) => state
})
const slice = createSlice({
name: model.namespace,
initialState: model.state,
reducers,
})
return slice.reducer
}
const rootReducer = models.reduce((prev: any, model: ModelType) => {
return {...prev, [model.namespace]: createReducer(model)}
}, {})
function watchEffects(model: ModelType): ForkEffect {
return fork(function* () {
for (const key in model.effects) {
const effect = model.effects[key]
yield takeEvery(`${model.namespace}/${key}`, function* (action: PayloadAction) {
yield put({
type: 'loading/save',
payload: {
[`${model.namespace}/${key}`]: true,
},
})
yield effect(action, {
call,
put,
delay,
select,
})
yield put({
type: 'loading/save',
payload: {
[`${model.namespace}/${key}`]: false,
},
})
})
}
})
}
function* rootSaga(): Generator {
yield all(models.map((model: ModelType) => watchEffects(model)))
}
const configureAppStore = (initialState = {}) => {
const reduxSagaMonitorOptions = {}
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions)
const middleware = [sagaMiddleware]
const store = configureStore({
reducer: rootReducer,
middleware: (gDM) => gDM().concat([...middleware]),
preloadedState: initialState,
devTools: process.env.NODE_ENV !== 'production',
})
sagaMiddleware.run(rootSaga)
return store
}
export const store = configureAppStore()
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>

@ -0,0 +1,38 @@
import {PluginClient} from '@remixproject/plugin'
import {createClient} from '@remixproject/plugin-webview'
import {store} from './redux/store'
import {router} from './App'
class RemixClient extends PluginClient {
constructor() {
super()
createClient(this)
}
startTutorial(name: any, branch: any, id: any): void {
console.log('start tutorial', name, branch, id)
void router.navigate('/home')
store.dispatch({
type: 'workshop/loadRepo',
payload: {
name,
branch,
id,
},
})
}
addRepository(name: any, branch: any) {
console.log('add repo', name, branch)
void router.navigate('/home')
store.dispatch({
type: 'workshop/loadRepo',
payload: {
name,
branch,
},
})
}
}
export default new RemixClient()

@ -0,0 +1,23 @@
{
"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,16 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}

@ -0,0 +1,90 @@
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
})

@ -3,9 +3,9 @@ import EventEmitter from 'events'
import { ExternalProfile, LocationProfile, Profile } from '@remixproject/plugin-utils'
class AddLocalPlugin extends EventEmitter {
command (this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile): NightwatchBrowser {
command (this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile, focus: boolean): NightwatchBrowser {
this.api.perform((done) => {
addLocalPlugin(this.api, profile, () => {
addLocalPlugin(this.api, profile, focus, () => {
done()
this.emit('complete')
})
@ -14,7 +14,7 @@ class AddLocalPlugin extends EventEmitter {
}
}
function addLocalPlugin (browser: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile, callback: VoidFunction) {
function addLocalPlugin (browser: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile, focus: boolean, callback: VoidFunction) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000).element('css selector', '*[data-id="pluginManagerComponentPluginManager"]', function (result) {
if (result.status === 0) {
@ -38,8 +38,13 @@ function addLocalPlugin (browser: NightwatchBrowser, profile: Profile & Location
.click(profile.location === 'sidePanel' ? '*[data-id="localPluginRadioButtonsidePanel"]' : '*[data-id="localPluginRadioButtonmainPanel"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalFooter-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react')
.waitForElementVisible('[data-id="verticalIconsKindlocalPlugin"]')
.click('[data-id="verticalIconsKindlocalPlugin"]')
.perform((done) => {
if (focus) {
browser.waitForElementVisible(`[data-id="verticalIconsKind${profile.name}"]`)
.click(`[data-id="verticalIconsKind${profile.name}"]`)
}
done()
})
.perform(function () { callback() })
}

@ -2,9 +2,9 @@ import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch'
class CheckTerminalFilter extends EventEmitter {
command (this: NightwatchBrowser, filter: string, test: string): NightwatchBrowser {
command (this: NightwatchBrowser, filter: string, test: string, notContain: boolean): NightwatchBrowser {
this.api.perform((done) => {
checkFilter(this.api, filter, test, () => {
checkFilter(this.api, filter, test, notContain, () => {
done()
this.emit('complete')
})
@ -13,20 +13,30 @@ class CheckTerminalFilter extends EventEmitter {
}
}
function checkFilter (browser: NightwatchBrowser, filter: string, test: string, done: VoidFunction) {
if (browser.options.desiredCapabilities.browserName === 'chrome') { // nightwatch deos not handle well that part.... works locally
function checkFilter (browser: NightwatchBrowser, filter: string, inputTest: string, notContain: boolean, done: VoidFunction) {
/*if (browser.options.desiredCapabilities.browserName === 'chrome') { // nightwatch deos not handle well that part.... works locally
done()
return
}
const filterClass = '[data-id="terminalInputSearch"]'
browser.setValue(filterClass, filter, function () {
}*/
const filterClass = '[data-id="terminalInputSearchTerminal"]'
browser.clearValue(filterClass).setValue(filterClass, filter, function () {
browser.execute(function () {
return document.querySelector('[data-id="terminalJournal"]').innerHTML === test || ''
return document.querySelector('[data-id="terminalJournal"]').innerHTML
}, [], function (result) {
browser.clearValue(filterClass).setValue(filterClass, '', function () {
if (!result.value) {
browser.assert.fail('useFilter on ' + filter + ' ' + test, 'info about error', '')
console.log(notContain, result.value, filter)
if (!notContain) {
// the input text should be contained in the result
if ((result.value as string).indexOf(filter) === -1) {
browser.assert.fail('useFilter on ' + filter + ' ' + test, 'the input text should be contained in the result', '')
}
}
if (notContain) {
// the input text should not be contained in the result
if ((result.value as string).indexOf(filter) !== -1) {
browser.assert.fail('useFilter on ' + filter + ' ' + test, 'the input text should not be contained in the result', '')
}
}
browser.clearValue(filterClass).perform(() => {
done()
})
})

@ -15,7 +15,7 @@ class GetLastTransactionHash extends EventEmitter {
}
function getLastTransactionHash (browser: NightwatchBrowser, callback: (hash: string) => void) {
browser.waitForElementPresent('*[data-shared="universalDappUiInstance"]')
browser.waitForElementPresent('*[data-id="terminalJournal"]')
.execute(function () {
const deployedContracts = document.querySelectorAll('*[data-id="terminalJournal"] > div')
for (let i = deployedContracts.length - 1; i >= 0; i--) {

@ -5,10 +5,10 @@ import EventEmitter from 'events'
Checks if any child elements of journal (console) contains a matching value.
*/
class JournalChildIncludes extends EventEmitter {
command (this: NightwatchBrowser, val: string, opts = { shouldHaveOnlyOneOccurence: false }): NightwatchBrowser {
command (this: NightwatchBrowser, val: string, opts = { shouldHaveOnlyOneOccurrence: false }): NightwatchBrowser {
let isTextFound = false
const browser = this.api
let occurence = 0
let occurrence = 0
this.api.elements('css selector', '*[data-id="terminalJournal"]', (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement) {
const jsonWebElementId = jsonWebElement[ELEMENT_KEY] || jsonWebElement[Object.keys(jsonWebElement)[0]]
@ -18,14 +18,14 @@ class JournalChildIncludes extends EventEmitter {
if (typeof text === 'string' && text.indexOf(val) !== -1) {
isTextFound = true
occurence++
occurrence++
}
})
})
})
browser.perform(() => {
browser.assert.ok(isTextFound, isTextFound ? `<*[data-id="terminalJournal"]> contains ${val}.` : `${val} not found in <*[data-id="terminalJournal"]> div:last-child>`)
if (opts.shouldHaveOnlyOneOccurence) browser.assert.ok(occurence === 1, `${occurence} occurence found of "${val}"`)
if (opts.shouldHaveOnlyOneOccurrence) browser.assert.ok(occurrence === 1, `${occurrence} occurrence found of "${val}"`)
this.emit('complete')
})
return this

@ -4,7 +4,7 @@ const storage = `pragma solidity >=0.7.0 <0.9.0;
/**
* @title Storage
* @dev Store & retreive value in a variable
* @dev Store & retrieve value in a variable
*/
contract Storage {
@ -22,7 +22,7 @@ contract Storage {
* @dev Return value
* @return value of 'number'
*/
function retreive() public view returns (uint256){
function retrieve() public view returns (uint256){
return number;
}
}`
@ -301,7 +301,7 @@ contract BallotTest {
Assert.equal(ballotToTest.winnerName(), bytes32("candidate1"), "candidate1 should be the winner name");
}
function checkWinninProposalWithReturnValue () public view returns (bool) {
function checkWinningProposalWithReturnValue () public view returns (bool) {
return ballotToTest.winningProposal() == 0;
}
}

@ -75,7 +75,7 @@ module.exports = {
abortOnFailure: false,
suppressNotFoundErrors: true,
})
// we are not changing the visibility for not checksumed contracts
// we are not changing the visibility for not checksummed contracts
// .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false)
.clickLaunchIcon('filePanel')
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true)
@ -118,7 +118,6 @@ module.exports = {
.connectToExternalHttpProvider('http://localhost:8545', 'Custom')
.clickLaunchIcon('solidity')
.clickLaunchIcon('udapp')
.pause(2000)
.clearValue('input[placeholder="bytes32[] proposalNames"]')
.setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]')
.click('*[data-id="Deploy - transact (not payable)"]')

@ -71,7 +71,7 @@ module.exports = {
abortOnFailure: false,
suppressNotFoundErrors: true,
})
// we are not changing the visibility for not checksumed contracts
// we are not changing the visibility for not checksummed contracts
// .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false)
.clickLaunchIcon('filePanel')
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true)
@ -94,7 +94,6 @@ module.exports = {
.connectToExternalHttpProvider('http://localhost:8545', 'Custom')
.clickLaunchIcon('solidity')
.clickLaunchIcon('udapp')
.pause(2000)
.clearValue('input[placeholder="uint8 _numProposals"]')
.setValue('input[placeholder="uint8 _numProposals"]', '2')
.click('*[data-id="Deploy - transact (not payable)"]')

@ -18,7 +18,7 @@ module.exports = {
return sources
},
'Should compile using "compileWithParamaters" API #group1': function (browser: NightwatchBrowser) {
'Should compile using "compileWithParameters" API #group1': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsCompile.js', { content: jsCompile })
.executeScriptInTerminal('remix.exeCurrent()')
@ -26,7 +26,7 @@ module.exports = {
.click('*[data-id="terminalClearConsole"]')
},
'Should compile using "compileWithParamaters" API with optimization On #group2': function (browser: NightwatchBrowser) {
'Should compile using "compileWithParameters" API with optimization On #group2': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsCompileWithOptimization.js', { content: jsCompileWithOptimization })
.executeScriptInTerminal('remix.exeCurrent()')
@ -34,7 +34,7 @@ module.exports = {
.click('*[data-id="terminalClearConsole"]')
},
'Should compile using "compileWithParamaters" API with optimization off check default runs #group3': function (browser: NightwatchBrowser) {
'Should compile using "compileWithParameters" API with optimization off check default runs #group3': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsCompileWithOptimizationDefault.js', { content: jsCompileWithOptimizationDefault })
.executeScriptInTerminal('remix.exeCurrent()')
@ -65,7 +65,7 @@ const simpleContract = `pragma solidity >=0.4.22 <0.9.1;
/**
* @title Storage
* @dev Store & retreive value in a variable
* @dev Store & retrieve value in a variable
*/
contract StorageTestUpdateConfiguration {
@ -83,7 +83,7 @@ contract StorageTestUpdateConfiguration {
* @dev Return value
* @return value of 'number'
*/
function retreive() public view returns (uint256){
function retrieve() public view returns (uint256){
return number;
}
}

@ -53,8 +53,8 @@ module.exports = {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemscripts"]', id)
.waitForElementVisible('div[data-id="treeViewDivDraggableItemscripts"]')
.dragAndDrop('div[data-id="treeViewDivDraggableItemscripts"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/scripts"]')

@ -37,9 +37,9 @@ module.exports = {
.addFile('File.sol', { content: '' })
.executeScriptInTerminal(`remix.loadgist('${gistid}')`)
// .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('gists') } done() })
.waitForElementVisible(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`)
.waitForElementVisible(`[data-id="treeViewLitreeViewItemREADME.txt"]`)
.openFile(`gist-${gistid}/README.txt`)
.openFile(`README.txt`)
// Remix publish to gist
/* .click('*[data-id="fileExplorerNewFilepublishToGist"]')
.pause(2000)
@ -110,8 +110,8 @@ module.exports = {
.waitForElementVisible('[data-id="settingsTabRemoveGistToken"]')
.click('[data-id="settingsTabRemoveGistToken"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="fileExplorerNewFilepublishToGist"]')
.click('*[data-id="fileExplorerNewFilepublishToGist"]')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacepublishToGist"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(10000)
@ -142,9 +142,27 @@ module.exports = {
.setValue('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]', testData.validGistId)
.modalFooterOKClick('gisthandler')
.pause(10000)
.openFile(`gist-${testData.validGistId}/README.txt`)
.waitForElementVisible(`div[data-path='default_workspace/gist-${testData.validGistId}/README.txt']`)
.assert.containsText(`div[data-path='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt')
.end()
.openFile(`README.txt`)
.waitForElementVisible(`div[data-path='gist ${testData.validGistId}/README.txt']`)
.assert.containsText(`div[data-path='gist ${testData.validGistId}/README.txt'] > span`, 'README.txt')
},
'Load Gist from URL and verify truncated files are loaded #group3': function (browser: NightwatchBrowser) {
const gistId = '1b179bf1b92c8b0664b4cbe61774e15d'
browser
.url('http://127.0.0.1:8080/#gist=' + gistId) // loading the gist
.refreshPage()
.currentWorkspaceIs('gist ' + gistId)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 15000)
.waitForElementVisible(`#fileExplorerView li[data-path='contracts']`, 30000)
.openFile(`contracts/2_Owner.sol`)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('contract Owner {') !== -1)
})
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacepublishToGist"]')
.modalFooterOKClick('fileSystem')
.waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]', 'Saving gist (' + gistId + ') ...')
}
}

@ -12,12 +12,21 @@ const testData = {
pluginUrl: 'https://zokrates.github.io/zokrates-remix-plugin/'
}
const localPluginData = {
name: testData.pluginName,
displayName: testData.pluginDisplayName,
canActivate: [],
url: testData.pluginUrl,
location: 'sidePanel'
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should Load Plugin Manager': function (browser: NightwatchBrowser) {
'Should Load Plugin Manager #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000)
.click('*[plugin="pluginManager"]')
@ -26,7 +35,7 @@ module.exports = {
.assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'PLUGIN MANAGER')
},
'Should Search for plugins': function (browser: NightwatchBrowser) {
'Should Search for plugins #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentSearchInput"]')
.keys('debugger')
@ -45,7 +54,7 @@ module.exports = {
.keys(browser.Keys.BACK_SPACE)
},
'Should activate plugins': function (browser: NightwatchBrowser) {
'Should activate plugins #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentPluginManager"]')
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtondebugger"]')
@ -57,7 +66,7 @@ module.exports = {
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonZoKrates"]', 60000)
},
'Should deactivate plugins': function (browser: NightwatchBrowser) {
'Should deactivate plugins #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentPluginManager"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtondebugger"]', 60000)
@ -69,45 +78,23 @@ module.exports = {
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonvyper"]', 60000)
},
'Should connect a local plugin': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.execute(function () {
window.testmode = true
})
.click('*[data-id="pluginManagerComponentPluginSearchButton"]')
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialogModalDialogContainer-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="localPluginName"]')
.clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', testData.pluginName)
.clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName)
.clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', testData.pluginUrl)
.click('*[data-id="localPluginRadioButtoniframe"]')
.click('*[data-id="localPluginRadioButtonsidePanel"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalFooter-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react')
'Should connect a local plugin #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000)
.click('*[plugin="pluginManager"]')
.clickLaunchIcon('filePanel')
.addLocalPlugin(localPluginData, false)
.pause(2000)
},
'Should display error message for creating already existing plugin': function (browser: NightwatchBrowser) {
'Should display error message for creating already existing plugin #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentPluginSearchButton"]')
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialogModalDialogContainer-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="localPluginName"]')
.clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', testData.pluginName)
.clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName)
.clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', testData.pluginUrl)
.click('*[data-id="localPluginRadioButtoniframe"]')
.click('*[data-id="localPluginRadioButtonsidePanel"]')
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react"]', 60000)
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react"]')
// .modalFooterOKClick()
// .pause(2000)
.waitForElementVisible('*[data-shared="tooltipPopup"]', 60000)
.pause(5000)
.assert.containsText('*[data-shared="tooltipPopup"]', 'Cannot create Plugin : This name has already been used')
.clickLaunchIcon('filePanel')
.addLocalPlugin(localPluginData, false)
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'Cannot create Plugin : This name has already been used')
},
'Should load back installed plugins after reload': function (browser: NightwatchBrowser) {
'Should load back installed plugins after reload #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeSidePanel"]',3000)
.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
@ -124,6 +111,5 @@ module.exports = {
done()
})
})
.end()
}
}

@ -128,6 +128,7 @@ const checkForAcceptAndRemember = async function (browser: NightwatchBrowser) {
*/
const clickAndCheckLog = async (browser: NightwatchBrowser, buttonText: string, methodResult: any, eventResult: any, payload: any, waitResult: boolean = true) => { // eslint-disable-line
console.log('clickAndCheckLog', buttonText, methodResult, eventResult, payload, waitResult)
if (payload) {
await setPayload(browser, payload)
} else {
@ -167,7 +168,7 @@ module.exports = {
},
'Should connect a local plugin': function (browser: NightwatchBrowser) {
browser.addLocalPlugin(localPluginData)
browser.addLocalPlugin(localPluginData, true)
// @ts-ignore
.frame(0).useXpath()
},
@ -295,7 +296,7 @@ module.exports = {
}, null, '/')
},
'Should get all workspaces #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"default_workspace",isGitRepo:false,hasGitSubmodules:false}, {name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false}, {name:"testspace",isGitRepo:false,hasGitSubmodules:false}], null, null)
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"default_workspace",isGitRepo:false,hasGitSubmodules:false,isGist:null}, {name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false,isGist:null}, {name:"testspace",isGitRepo:false,hasGitSubmodules:false,isGist:null}], null, null)
},
'Should have set workspace event #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, { event: 'setWorkspace', args: [{ name: 'newspace', isLocalhost: false }] }, 'newspace')
@ -309,11 +310,11 @@ module.exports = {
'Should rename workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:renameWorkspace', null, null, ['default_workspace', 'renamed'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false},{name:"testspace",isGitRepo:false,hasGitSubmodules:false},{name:"newspace",isGitRepo:false,hasGitSubmodules:false},{name:"renamed",isGitRepo:false,hasGitSubmodules:false}], null, null)
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false,isGist:null},{name:"testspace",isGitRepo:false,hasGitSubmodules:false,isGist:null},{name:"newspace",isGitRepo:false,hasGitSubmodules:false,isGist:null},{name:"renamed",isGitRepo:false,hasGitSubmodules:false,isGist:null}], null, null)
},
'Should delete workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:deleteWorkspace', null, null, ['testspace'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false},{name:"newspace",isGitRepo:false,hasGitSubmodules:false},{name:"renamed",isGitRepo:false,hasGitSubmodules:false}], null, null)
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false,isGist:null},{name:"newspace",isGitRepo:false,hasGitSubmodules:false,isGist:null},{name:"renamed",isGitRepo:false,hasGitSubmodules:false,isGist:null}], null, null)
},
// DGIT
'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) {

@ -38,7 +38,7 @@ module.exports = {
.openFile('ipfs/QmXYUS1ueS22EqNVRaKuZa31EgHLjKZ8uTM8vWhQLxa3pw')
},
/* Disableing the test untill refactoring and the new swarm usage
/* Disabling the test until refactoring and the new swarm usage
'Publish on Swarm': '' + function (browser: NightwatchBrowser) {
browser
.click('#publishOnSwarm')

@ -202,7 +202,7 @@ module.exports = {
},
/*
* This test is using 3 differents services:
* This test is using 3 different services:
* - Metamask for getting the transaction
* - Source Verifier service for fetching the contract code
* - Ropsten node for retrieving the trace and storage

@ -42,7 +42,7 @@ module.exports = {
.clickLaunchIcon('udapp')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.clickFunction('retunValues1 - transact (not payable)')
.clickFunction('returnValues1 - transact (not payable)')
.testFunction('last',
{
status: '0x1 Transaction mined and execution succeed',
@ -53,7 +53,7 @@ module.exports = {
3: 'address: _a 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'
}
})
.clickFunction('retunValues2 - transact (not payable)')
.clickFunction('returnValues2 - transact (not payable)')
.testFunction('last',
{
status: '0x1 Transaction mined and execution succeed',
@ -70,7 +70,7 @@ module.exports = {
9: 'bytes32: _b32 0x0325235325325235325235325235320000000000000000000000000000000000'
}
}).pause(500)
.clickFunction('retunValues3 - transact (not payable)')
.clickFunction('returnValues3 - transact (not payable)')
.testFunction('last',
{
status: '0x1 Transaction mined and execution succeed',
@ -150,6 +150,14 @@ module.exports = {
.click('*[data-id="deployAndRunClearInstances"]')
},
'Should filter displayed transactions #group2': function (browser: NightwatchBrowser) {
browser
// it should contain: 0xd9145CCE52D386f254917e481eB44e9943F39138
.checkTerminalFilter('0xd9145CCE52D386f254917e481eB44e9943F39138', '0xd9145CCE52D386f254917e481eB44e9943F39138', false)
// it should not contain: 0xd9145CCE52D386f254917e481eB44e9943F39140 (it ends with 40)
.checkTerminalFilter('0xd9145CCE52D386f254917e481eB44e9943F39140', '0xd9145CCE52D386f254917e481eB44e9943F39138', true)
},
'Should Compile and Deploy a contract which define a custom error, the error should be logged in the terminal #group3': function (browser: NightwatchBrowser) {
browser.testContracts('customError.sol', sources[4]['customError.sol'], ['C'])
.clickLaunchIcon('udapp')
@ -312,10 +320,10 @@ const sources = [
contract TestContract { function f() public returns (uint) { return 8; }
function g() public returns (uint, string memory, bool, uint) {
uint payment = 345;
bool payed = true;
bool paid = true;
string memory comment = "comment_comment_";
uint month = 4;
return (payment, comment, payed, month); } }`
return (payment, comment, paid, month); } }`
}
},
{
@ -323,14 +331,14 @@ const sources = [
content: `
contract testReturnValues {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
function retunValues1 () public returns (bool _b, uint _u, int _i, address _a) {
function returnValues1 () public returns (bool _b, uint _u, int _i, address _a) {
_b = true;
_u = 345;
_i = -345;
_a = msg.sender;
}
function retunValues2 () public returns (bytes1 _b, bytes2 _b2, bytes3 _b3, bytes memory _blit, bytes5 _b5, bytes6 _b6, string memory _str, bytes7 _b7, bytes22 _b22, bytes32 _b32) {
function returnValues2 () public returns (bytes1 _b, bytes2 _b2, bytes3 _b3, bytes memory _blit, bytes5 _b5, bytes6 _b6, string memory _str, bytes7 _b7, bytes22 _b22, bytes32 _b32) {
_b = 0x12;
_b2 = 0x1223;
_b5 = hex"043245";
@ -342,7 +350,7 @@ const sources = [
_str = "this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string";
}
function retunValues3 () public returns (ActionChoices _en, int[5][] memory _a1) {
function returnValues3 () public returns (ActionChoices _en, int[5][] memory _a1) {
_en = ActionChoices.GoStraight;
int[5][] memory a = new int[5][](3);
a[0] = [int(1),-45,-78,56,60];

@ -7,7 +7,7 @@ module.exports = {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Checks vertical icons panelcontex menu': function (browser: NightwatchBrowser) {
'Checks vertical icons panel context menu': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="verticalIconsKindpluginManager"]')
.click('*[data-id="verticalIconsKindpluginManager"]')
@ -19,7 +19,7 @@ module.exports = {
.click('*[data-id="remixIdeIconPanel"]')
},
'Checks vertical icons panel contex menu deactivate': function (browser: NightwatchBrowser) {
'Checks vertical icons panel context menu deactivate': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="verticalIconsKinddebugger"]', 7000)
.pause(5000)

@ -41,8 +41,30 @@ module.exports = {
.openFile('examples/auctions/blind_auction.vy')
},
'Context menu click to compile blind_auction should succeed #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="treeViewLitreeViewItemexamples/auctions/blind_auction.vy"]')
.rightClick('*[data-id="treeViewLitreeViewItemexamples/auctions/blind_auction.vy"]')
.waitForElementPresent('[data-id="contextMenuItemvyper"]')
.click('[data-id="contextMenuItemvyper"]')
.clickLaunchIcon('vyper')
// @ts-ignore
.frame(0)
.waitForElementVisible({
selector:'[data-id="compilation-details"]',
timeout: 120000
})
.click('[data-id="compilation-details"]')
.frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.waitForElementVisible({
selector: "//*[@class='variable-value' and contains(.,'highestBidder')]",
locateStrategy: 'xpath',
})
},
'Compile blind_auction should success #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('vyper')
browser
// @ts-ignore
.frame(0)
.click('[data-id="remote-compiler"]')

@ -501,7 +501,7 @@ module.exports = {
.pause(2000)
},
'Should change the current workspace in localstorage to a non existant value, reload the page and see the workspace created #group2': function (browser: NightwatchBrowser) {
'Should change the current workspace in localstorage to a non existent value, reload the page and see the workspace created #group2': function (browser: NightwatchBrowser) {
browser
.execute(function () {
localStorage.setItem('currentWorkspace', 'non_existing_workspace')

@ -209,7 +209,7 @@ module.exports = {
.expect.element('[data-id="workspaceGit-newLocalBranch"]').text.to.contain('✓ ')
},
'Should checkout to an exisiting local branch #group3': function (browser: NightwatchBrowser) {
'Should checkout to an existing local branch #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementPresent('[data-id="workspaceGitInput"]')
@ -239,7 +239,7 @@ module.exports = {
.expect.element('[data-id="workspaceGit-main"]').text.to.contain('✓ ')
},
'Should force checkout to a branch with exisiting local changes #group3': function (browser: NightwatchBrowser) {
'Should force checkout to a branch with existing local changes #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="workspaceGit-dev"]')
.click('[data-id="workspaceGit-dev"]')
@ -370,25 +370,23 @@ module.exports = {
// GIT WORKSPACE E2E STARTS
'Should create a git workspace (uniswapV4Periphery) #group4': function (browser: NightwatchBrowser) {
'Should create a git workspace (uniswapV4Template) #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=uniswapV4Periphery]')
.click('select[id="wstemplate"] option[value=uniswapV4Template]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts')
.openFile('contracts/hooks')
.openFile('contracts/hooks/examples')
.openFile('contracts/hooks/examples/FullRange.sol')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]')
.openFile('src')
.openFile('src/Counter.sol')
.pause(1000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`contract FullRange is BaseHook`) !== -1,
browser.assert.ok(content.indexOf(`contract Counter is BaseHook {`) !== -1,
'Incorrect content')
})
},

@ -54,14 +54,14 @@ declare module 'nightwatch' {
notContainsText(cssSelector: string, text: string): NightwatchBrowser
sendLowLevelTx(address: string, value: string, callData: string): NightwatchBrowser
journalLastChild(val: string): NightwatchBrowser
checkTerminalFilter(filter: string, test: string): NightwatchBrowser
checkTerminalFilter(filter: string, test: string, notContain: boolean): 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
addLocalPlugin(this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile, focus: boolean): NightwatchBrowser
acceptAndRemember(this: NightwatchBrowser, remember: boolean, accept: boolean): NightwatchBrowser
clearConsole(this: NightwatchBrowser): NightwatchBrowser
clearTransactions(this: NightwatchBrowser): NightwatchBrowser

@ -1910,14 +1910,14 @@ internal-slot@^1.0.5:
side-channel "^1.0.4"
ip@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48"
integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==
version "1.1.9"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396"
integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==
ip@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==
version "2.0.1"
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105"
integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==
is-accessor-descriptor@^0.1.6:
version "0.1.6"

@ -1,4 +1,4 @@
# This dockerfile is to build each branch seperately (for dev purpouses)
# This dockerfile is to build each branch separately (for dev purpouses)
FROM node:10
# Create Remix user, don't use root!
# RUN yes | adduser --disabled-password remix && mkdir /app

@ -2,8 +2,8 @@
var fs = require('fs')
var compiler = require('solc')
var compilerInput = require('@remix-project/remix-solidity').CompilerInput
var defaultVersion = 'soljson-v0.8.22+commit.4fc1097e.js'
var compilerInput = require('@remix-project/remix-solidity').compilerInputFactory
var defaultVersion = 'soljson-v0.8.24+commit.e11b9ed9.js'
const path = require('path')
compiler.loadRemoteVersion(defaultVersion, (error, solcSnapshot) => {

@ -18,7 +18,7 @@ function removeTunnel () {
retrieveTunnel(data[k], function (error, result) {
if (error) {
console.log(error)
} else if (result.identtifier === tunnelName) {
} else if (result.identifier === tunnelName) {
deleteTunnel(result.id, function () {
console.log('tunnel deleted ' + data[k] + ' ' + tunnelName)
})
@ -35,7 +35,7 @@ function retrieveTunnel (tunnelid, callback) {
if (error) {
callback(error)
} else {
callback(null, {'identtifier': JSON.parse(result).tunnel_identifier, 'id': tunnelid})
callback(null, {'identifier': JSON.parse(result).tunnel_identifier, 'id': tunnelid})
}
})
}

@ -3355,7 +3355,7 @@
"id": 1818,
"nodeType": "StructuredDocumentation",
"src": "361:227:1",
"text": "@dev Compute the address a contract will be deployed at for a given deployer address and nonce\n @notice adapated from Solmate implementation (https://github.com/transmissions11/solmate/blob/main/src/utils/LibRLP.sol)"
"text": "@dev Compute the address a contract will be deployed at for a given deployer address and nonce\n @notice adapted from Solmate implementation (https://github.com/transmissions11/solmate/blob/main/src/utils/LibRLP.sol)"
},
"implemented": true,
"kind": "function",

@ -21,8 +21,8 @@ we move the documentation to the remix-ide repository
## medium post policy
Any post that relates to Ethereum could be put in the remix plublication.
Although that is not mandatory and left up to the writter.
Any post that relates to Ethereum could be put in the remix publication.
Although that is not mandatory and left up to the writer.
## guided tour
@ -32,7 +32,7 @@ It will work as a native plugin, started by default.
Each other native plugin can request a guided tour with:
`this.call('guidedtour', 'start', 'debugger')`
Other type of plugin may be able to the native plugin guided tour but we won't push this if the integration is not working out of the box.
We rather update the remix-plugin doc saying that `guided tour framework name` is the prefferred one.
We rather update the remix-plugin doc saying that `guided tour framework name` is the preferred one.
## web site
@ -49,7 +49,7 @@ it will be set of file:
- solidity contract
- test contract
we only support md for now and move to supporting other format if needded.
we only support md for now and move to supporting other format if needed.
It requires the "test" native plugin to extend its API.
@rob/@francois are managing that.

@ -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", "circuit-compiler"],
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect", "circuit-compiler", "learneth"],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",

@ -360,7 +360,7 @@ class Editor extends Plugin {
/**
* Path of the currently editing file
* returns `undefined` if no session is being editer
* returns `undefined` if no session is being edited
* @return {String} path of the current session
*/
current () {
@ -449,7 +449,7 @@ class Editor extends Plugin {
}
/**
* Clears all the decorations for the given @arg filePath and @arg plugin, if none is given, the current sesssion is used.
* Clears all the decorations for the given @arg filePath and @arg plugin, if none is given, the current session is used.
* An annotation has the following shape:
column: -1
row: -1
@ -502,7 +502,7 @@ class Editor extends Plugin {
}
/**
* Clears all the annotations for the given @arg filePath, the plugin name is retrieved from the context, if none is given, the current sesssion is used.
* Clears all the annotations for the given @arg filePath, the plugin name is retrieved from the context, if none is given, the current session is used.
* An annotation has the following shape:
column: -1
row: -1

@ -692,7 +692,7 @@ class DGitProvider extends Plugin {
dir
})
} catch (e) {
this.call('terminal', 'log', { type: 'error', value: `[Cloning]: Error occured! ${e}` })
this.call('terminal', 'log', { type: 'error', value: `[Cloning]: Error occurred! ${e}` })
console.log(e)
}
}
@ -702,7 +702,7 @@ class DGitProvider extends Plugin {
}, 1000)
}
} catch (e) {
this.call('terminal', 'log', { type: 'error', value: `[Cloning]: Error occured! ${e}` })
this.call('terminal', 'log', { type: 'error', value: `[Cloning]: Error occurred! ${e}` })
// do nothing
}
}

@ -46,6 +46,7 @@ const profile = {
'loadTemplate',
'clone',
'isExpanded',
'isGist'
],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp',
@ -131,6 +132,20 @@ module.exports = class Filepanel extends ViewPlugin {
})
}
/**
* return the gist id if the current workspace is a gist workspace, otherwise returns null
* @argument {String} workspaceName - the name of the workspace to check against. default to the current workspace.
* @returns {string} gist id or null
*/
isGist (workspaceName) {
workspaceName = workspaceName || this.currentWorkspaceMetadata && this.currentWorkspaceMetadata.name
const isGist = workspaceName.startsWith('gist')
if (isGist) {
return workspaceName.split(' ')[1]
}
return null
}
getCurrentWorkspace() {
return this.currentWorkspaceMetadata
}

@ -9,7 +9,7 @@ import { filePathFilter, AnyFilter } from '@jsdevtools/file-path-filter'
const profile = {
name: 'codeFormatter',
desciption: 'prettier plugin for Remix',
description: 'prettier plugin for Remix',
methods: ['format'],
events: [''],
version: '0.0.1'

@ -45,7 +45,7 @@ export class CompilationDetailsPlugin extends ViewPlugin {
async showDetails(sentPayload: any) {
await this.call('tabs', 'focus', 'compilationDetails')
setTimeout(() => {
// TODO: use the react API to render when the tab is focused and tbe plugin in the view.
// TODO: use the react API to render when the tab is focused and the plugin in the view.
this.payload = sentPayload
this.renderComponent()
}, 2000)

@ -2,6 +2,7 @@ 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 type {CompilerInput, CompilationSource } from '@remix-project/remix-solidity'
const _paq = (window._paq = window._paq || [])
@ -25,7 +26,7 @@ export class ContractFlattener extends Plugin {
if (data.sources && Object.keys(data.sources).length > 1) {
if (this.triggerFlattenContract) {
this.triggerFlattenContract = false
await this.flattenContract(source, file, data)
await this.flattenContract(source, file, data, JSON.parse(input))
}
}
})
@ -47,17 +48,17 @@ 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}, input: CompilerInput): Promise<string> {
const appendage = '_flattened.sol'
const normalized = normalizeContractPath(filePath)
const path = `${normalized[normalized.length - 2]}${appendage}`
const ast = data.sources
const ast: { [contractName: string]: CompilationSource } = data.sources
let dependencyGraph
let sorted
let result
let sources
try {
dependencyGraph = getDependencyGraph(ast, filePath)
dependencyGraph = getDependencyGraph(ast, filePath, input.settings.remappings)
sorted = dependencyGraph.isEmpty() ? [filePath] : dependencyGraph.sort().reverse()
sources = source.sources
result = concatSourceFiles(sorted, sources)

@ -7,7 +7,7 @@ import { fileDecoration } from '@remix-ui/file-decorators'
const profile = {
name: 'fileDecorator',
desciption: 'Keeps decorators of the files',
description: 'Keeps decorators of the files',
methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'],
events: ['fileDecoratorsChanged'],
version: '0.0.1'

@ -35,7 +35,7 @@ type errorMarker = {
}
export default class CodeParserCompiler {
plugin: CodeParser
compiler: any // used to compile the current file seperately from the main compiler
compiler: any // used to compile the current file separately from the main compiler
onAstFinished: (success: any, data: CompilationResult, source: CompilationSourceCode, input: any, version: any) => Promise<void>;
errorState: boolean;
gastEstimateTimeOut: any

@ -1,5 +1,6 @@
/* eslint-disable no-unused-vars */
import React, {useRef, useState, useEffect} from 'react' // eslint-disable-line
import {FormattedMessage} from 'react-intl'
import {WebsocketPlugin} from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
@ -84,7 +85,7 @@ export class RemixdHandle extends WebsocketPlugin {
console.log(error)
const alert: AlertModal = {
id: 'connectionAlert',
message: 'Cannot connect to the remixd daemon. Please make sure you have the remixd running in the background.'
message: window._intl.formatMessage({id: 'remixd.connectionAlert1'}),
}
this.call('notification', 'alert', alert)
this.canceled()
@ -95,7 +96,7 @@ export class RemixdHandle extends WebsocketPlugin {
clearInterval(intervalId)
const alert: AlertModal = {
id: 'connectionAlert',
message: 'Connection to remixd terminated. Please make sure remixd is still running in the background.'
message: window._intl.formatMessage({id: 'remixd.connectionAlert2'}),
}
this.call('notification', 'alert', alert)
this.canceled()
@ -115,10 +116,10 @@ export class RemixdHandle extends WebsocketPlugin {
// warn the user only if he/she is in the browser context
const mod: AppModal = {
id: 'remixdConnect',
title: 'Access file system using remixd',
title: window._intl.formatMessage({id: 'remixd.remixdConnect'}),
message: remixdDialog(),
okLabel: 'Connect',
cancelLabel: 'Cancel'
okLabel: window._intl.formatMessage({id: 'remixd.connect'}),
cancelLabel: window._intl.formatMessage({id: 'remixd.cancel'}),
}
const result = await this.call('notification', 'modal', mod)
if (result) {
@ -159,45 +160,62 @@ function remixdDialog() {
<>
<div className="">
<div className="mb-2 text-break">
Access your local file system from Remix IDE using{' '}
<FormattedMessage
id="remixd.text1"
values={{
a: (chunks) => (
<a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">
Remixd NPM package
{chunks}
</a>
.
),
}}
/>
</div>
<div className="mb-2 text-break">
Remixd{' '}
<FormattedMessage
id="remixd.text2"
values={{
a: (chunks) => (
<a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">
documentation
{chunks}
</a>
.
),
}}
/>
</div>
<div className="mb-2 text-break">
The remixd command is:
<FormattedMessage id="remixd.text3" />
<br />
<b>{commandText}</b>
</div>
<div className="mb-2 text-break">
The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org,
https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org
<FormattedMessage id="remixd.text4" />
</div>
<div className="mb-2 text-break">
Example command with flags: <br />
<FormattedMessage id="remixd.text5" /> <br />
<b>{fullCommandText}</b>
</div>
<div className="mb-2 text-break">
For info about ports, see{' '}
<FormattedMessage
id="remixd.text6"
values={{
a: (chunks) => (
<a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">
Remixd ports usage
{chunks}
</a>
),
}}
/>
</div>
<div className="mb-2 text-break">
<FormattedMessage id="remixd.text7" />
</div>
<div className="mb-2 text-break">This feature is still in Alpha. We recommend to keep a backup of the shared folder.</div>
<div className="mb-2 text-break">
<h6 className="text-danger">
Before using, make sure remixd version is latest i.e. <b>v{remixdVersion}</b>
<FormattedMessage id="remixd.text8" /> <b>v{remixdVersion}</b>
<br></br>
<a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">
Read here how to update it
<FormattedMessage id="remixd.text9" />
</a>
</h6>
</div>

@ -11,6 +11,7 @@ import vizRenderStringSync from '@aduh95/viz.js/sync'
import {PluginViewWrapper} from '@remix-ui/helper'
import {customAction} from '@remixproject/plugin-api'
import {ClassOptions} from 'sol2uml/lib/converterClass2Dot'
import type {CompilerInput} from '@remix-project/remix-solidity'
const parser = (window as any).SolidityParser
const _paq = (window._paq = window._paq || [])
@ -74,7 +75,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
try {
if (data.sources && Object.keys(data.sources).length > 1) {
// we should flatten first as there are multiple asts
result = await this.flattenContract(source, file, data)
result = await this.flattenContract(source, file, data, JSON.parse(input))
}
const ast = result.length > 1 ? parser.parse(result) : parser.parse(source.sources[file].content)
this.umlClasses = convertAST2UmlClasses(ast, this.currentFile)
@ -142,8 +143,8 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
* and assigns to a local property
* @returns {Promise<string>}
*/
async flattenContract(source: any, filePath: string, data: any) {
const result = await this.call('contractflattener', 'flattenContract', source, filePath, data)
async flattenContract(source: any, filePath: string, data: any, input: CompilerInput) {
const result = await this.call('contractflattener', 'flattenContract', source, filePath, data, input)
return result
}

@ -0,0 +1,33 @@
{
"editor.keyboardShortcuts": "Keyboard Shortcuts",
"editor.keyboardShortcuts.text1": "Compile the current contract",
"editor.keyboardShortcuts.text2": "Open the File Explorer",
"editor.keyboardShortcuts.text3": "Open the Plugin Manager",
"editor.keyboardShortcuts.text4": "Compile the current contract & Run an associated script",
"editor.editorKeyboardShortcuts": "Editor Keyboard Shortcuts",
"editor.editorKeyboardShortcuts.text1": "Format the code in the current file",
"editor.importantLinks": "Important Links",
"editor.importantLinks.text1": "Official website about the Remix Project",
"editor.importantLinks.text2": "Official documentation",
"editor.title1": "Pasted Code Alert",
"editor.title1.message1": "You have just pasted a code snippet or contract in the editor.",
"editor.title1.message2": "Make sure you fully understand this code before deploying or interacting with it. Don't get scammed!",
"editor.title1.message3": "Running untrusted code can put your wallet <span> at risk </span>. In a worst-case scenario, you could <span>lose all your money</span>.",
"editor.title1.message4": "If you don't fully understand it, please don't run this code.",
"editor.title1.message5": "If you are not a smart contract developer, ask someone you trust who has the skills to determine if this code is safe to use.",
"editor.title1.message6": "See <a> these recommendations </a> for more information.",
"editor.zoomIn": "Zoom In",
"editor.zoomOut": "Zoom Out",
"editor.formatCode": "Format Code",
"editor.generateDocumentation": "Generate documentation for this function",
"editor.generateDocumentation2": "Generate documentation for the function \"{name}\"",
"editor.generateDocumentationByAI": "solidity code: {content}\n Generate the documentation for the function {currentFunction} using the Doxygen style syntax",
"editor.explainFunction": "Explain this function",
"editor.explainFunction2": "Explain the function \"{name}\"",
"editor.explainFunctionByAI": "solidity code: {content}\n Explain the function {currentFunction}",
"editor.executeFreeFunction": "Run a free function",
"editor.executeFreeFunction2": "Run the free function \"{name}\"",
"editor.toastText1": "This can only execute free function",
"editor.toastText2": "Please go to Remix settings and activate the code editor features or wait that the current editor context is loaded.",
"editor.text": "The file is opened in <b>read-only</b> mode."
}

@ -62,10 +62,12 @@
"filePanel.compileForNahmii": "Compile for Nahmii",
"filePanel.createNewFile": "Create new file",
"filePanel.createNewFolder": "Create new folder",
"filePanel.publishToGist": "Publish all files to GitHub gist",
"filePanel.publishToGist": "Publish to Gist",
"filePanel.workspace.publishToGist": "Publish workspace to GitHub gist",
"filePanel.uploadFile": "Upload files",
"filePanel.uploadFolder": "Upload folder",
"filePanel.updateGist": "Update the current [gist] explorer",
"filePanel.updateGist": "Update Gist",
"filePanel.workspace.updateGist": "Publish Gist update",
"filePanel.viewAllBranches": "View all branches",
"filePanel.createBranch": "Create branch",
"filePanel.switchBranches": "Switch branches",
@ -113,7 +115,7 @@
"filePanel.hashchecker": "Hash Checker",
"filePanel.rln": "Rate-Limiting Nullifier",
"filePanel.breakthroughLabsUniswapv4Hooks": "Breakthrough-Labs Hooks",
"filePanel.uniswapV4Periphery": "v4 Periphery",
"filePanel.uniswapV4Template": "v4 Template",
"filePanel.uniswapV4HookBookMultiSigSwapHook": "HookBook MultiSigSwapHook",
"filePanel.transparent": "Transparent",
"filePanel.initGitRepoTitle": "Check option to initialize workspace as a new git repository",

@ -1,4 +1,5 @@
{
"home.home": "Home",
"home.scamAlert": "Scam Alert",
"home.scamAlertText": "The only URL Remix uses is remix.ethereum.org",
"home.scamAlertText2": "Beware of online videos promoting \"liquidity front runner bots\"",

@ -1,39 +1,13 @@
import debuggerJson from './debugger.json';
import filePanelJson from './filePanel.json';
import homeJson from './home.json';
import panelJson from './panel.json';
import pluginManagerJson from './pluginManager.json';
import searchJson from './search.json';
import settingsJson from './settings.json';
import solidityJson from './solidity.json';
import terminalJson from './terminal.json';
import udappJson from './udapp.json';
import solidityUnitTestingJson from './solidityUnitTesting.json';
import permissionHandlerJson from './permissionHandler.json';
import electronJson from './electron.json';
import solUmlGenJson from './solUmlGen.json'
import remixAppJson from './remixApp.json'
import remixUiTabsJson from './remixUiTabs.json'
import circuitJson from './circuit.json';
import gitJson from './git.json'
function readAndCombineJsonFiles() {
const dataContext = require.context('./', true, /\.json$/)
export default {
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
...electronJson,
...solUmlGenJson,
...remixAppJson,
...remixUiTabsJson,
...circuitJson,
...gitJson
let combinedData = {}
dataContext.keys().forEach((key) => {
const jsonData = dataContext(key)
combinedData = {...combinedData, ...jsonData}
})
return combinedData
}
export default readAndCombineJsonFiles()

@ -4,6 +4,7 @@
"panel.documentation": "Documentation",
"panel.description": "Description",
"panel.maintainedByRemix": "Maintained by Remix",
"panel.maintainedExternally": "Not maintained by Remix",
"panel.pluginInfo": "Plugin info",
"panel.linkToDoc": "Link to documentation",
"panel.makeAnissue": "Make an issue"

@ -1,5 +1,5 @@
{
"permissionHandler.allPermissionsReset": "All permisssions have been reset.",
"permissionHandler.allPermissionsReset": "All permissions have been reset.",
"permissionHandler.rememberText": "has changed and",
"permissionHandler.permissionHandlerMessage": "\"{from}\" {rememberText} would like to access to \"{method}\" of \"{to}\"`",
"permissionHandler.description": "Description",

@ -33,6 +33,7 @@
"pluginManager.ok": "OK",
"pluginManager.cancel": "Cancel",
"pluginManager.maintainedByRemix": "Maintained by Remix",
"pluginManager.maintainedExternally": "Not maintained by Remix",
"pluginManager.linkToDoc": "Link to documentation",
"pluginManager.versionAlpha": "Version Alpha",
"pluginManager.versionBeta": "Version Beta",

@ -0,0 +1,19 @@
{
"publishToStorage.title1": "Publish To Storage",
"publishToStorage.title1.message": "This contract may be abstract, it may not implement an abstract parent's methods completely or it may not invoke an inherited contract's constructor correctly.",
"publishToStorage.title2": "Published {name}'s Metadata and Sources",
"publishToStorage.title2.message": "Metadata and sources of \"{name}\" were published successfully.",
"publishToStorage.title3": "Swarm Publish Failed",
"publishToStorage.title4": "IPFS Settings",
"publishToStorage.title4.message1": "You have not set your own custom IPFS settings.",
"publishToStorage.title4.message2": "We won’t be providing a public endpoint anymore for publishing your contracts to IPFS.",
"publishToStorage.title4.message3": "Instead of that, 4 options are now available:",
"publishToStorage.title4.message4": "DEFAULT OPTION: Use the public INFURA node. This will not guarantee your data will persist.",
"publishToStorage.title4.message5": "Use your own INFURA IPFS node. This requires a subscription. <a>Learn more</a>",
"publishToStorage.title4.message6": "Use any external IPFS which doesn’t require any authentication.",
"publishToStorage.title4.message7": "Use your own local ipfs node (which usually runs under http://localhost:5001)",
"publishToStorage.title4.message8": "You can update your IPFS settings in the SETTINGS tab.",
"publishToStorage.title4.message9": "Now the default option will be used.",
"publishToStorage.title5": "IPFS Publish Failed",
"publishToStorage.title5.message": "Failed to publish metadata file and sources to {storage}, please check the {storage} gateways is available."
}

@ -1,3 +1,20 @@
{
"remixApp.scrollToSeeAllTabs": "Scroll to see all tabs"
"remixApp.scrollToSeeAllTabs": "Scroll to see all tabs",
"remixApp.alert": "Alert",
"remixApp.ok": "OK",
"remixApp.enterText1": "Welcome to Remix IDE",
"remixApp.enterText2": "In order to understand your needs better, we would like to know how you typically use Remix",
"remixApp.enterText3": "Learning - discovering web3 development",
"remixApp.enterText4": "Prototyping - trying out concepts and techniques",
"remixApp.enterText5": "Developing projects - Remix as your main dev tool",
"remixApp.enterText6": "Production - only deployments",
"remixApp.matomoText1": "An Opt-in version of <a>Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.",
"remixApp.matomoText2": "We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.",
"remixApp.matomoText3": "All data collected through Matomo is stored on our own server - no data is ever given to third parties.",
"remixApp.matomoText4": "We do not collect nor store any personally identifiable information (PII).",
"remixApp.matomoText5": "For more info, see: <a>Matomo Analytics on Remix iDE</a>.",
"remixApp.matomoText6": "You can change your choice in the Settings panel anytime.",
"remixApp.matomoTitle": "Help us to improve Remix IDE",
"remixApp.accept": "Accept",
"remixApp.decline": "Decline"
}

@ -1,7 +1,7 @@
{
"remixUiTabs.tooltipText1": "Run script (CTRL + SHIFT + S)",
"remixUiTabs.tooltipText2": "Compile CTRL + S",
"remixUiTabs.tooltipText3": "Select .sol or .yul file to compile or a .ts or .js file and run it",
"remixUiTabs.tooltipText3": "Select .sol, .vy or .yul file to compile or a .ts or .js file and run it",
"remixUiTabs.zoomOut": "Zoom out",
"remixUiTabs.zoomIn": "Zoom in"
}

@ -0,0 +1,16 @@
{
"remixd.connectionAlert1": "Cannot connect to the remixd daemon. Please make sure you have the remixd running in the background.",
"remixd.connectionAlert2": "Connection to remixd terminated. Please make sure remixd is still running in the background.",
"remixd.remixdConnect": "Access file system using remixd",
"remixd.connect": "Connect",
"remixd.cancel": "Cancel",
"remixd.text1": "Access your local file system from Remix IDE using <a>Remixd NPM package</a>.",
"remixd.text2": "Remixd <a>documentation</a>.",
"remixd.text3": "The remixd command is:",
"remixd.text4": "The remixd command without options uses the terminal's current directory as the shared directory and the shared Remix domain can only be https://remix.ethereum.org, https://remix-alpha.ethereum.org, or https://remix-beta.ethereum.org",
"remixd.text5": "Example command with flags:",
"remixd.text6": "For info about ports, see <a>Remixd ports usage</a>",
"remixd.text7": "This feature is still in Alpha. We recommend to keep a backup of the shared folder.",
"remixd.text8": "Before using, make sure remixd version is latest i.e.",
"remixd.text9": "Read here how to update it"
}

@ -20,5 +20,5 @@
"search.no": "No",
"search.loading": "Loading",
"search.text1": "showing {count} results in {fileCount} files",
"search.text2": "Too many resuls to display...{br}Please narrow down your search."
"search.text2": "Too many results to display...{br}Please narrow down your search."
}

@ -1,5 +1,6 @@
{
"solidity.displayName": "Solidity compiler",
"solidity.openaigptMessage": "solidity code: {content}\n error message: {messageText}\n explain why the error occurred and how to fix it.",
"solidity._comment_compiler-container.tsx": "libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx",
"solidity.compiler": "Compiler",
@ -45,7 +46,7 @@
"solidity.compileIconAttribute": "compiler is loading, please wait a few moments.",
"solidity.compilerLicense": "Compiler License",
"solidity.compilerLicenseMsg1": "Compiler is loading. License will be displayed once compiler is loaded",
"solidity.compilerLicenseMsg2": "Could not retreive license for selected compiler version",
"solidity.compilerLicenseMsg2": "Could not retrieve license for selected compiler version",
"solidity.compilerLicenseMsg3": "License not available",
"solidity.seeCompilerLicense": "See compiler license",
@ -68,7 +69,7 @@
"solidity.Assembly": "Assembly opcodes describing the contract including corresponding solidity source code",
"solidity.Opcodes": "Assembly opcodes describing the contract",
"solidity.name": "Name of the compiled contract",
"solidity.metadata": "Contains all informations related to the compilation",
"solidity.metadata": "Contains all information related to the compilation",
"solidity.bytecode": "Bytecode being executed during contract creation",
"solidity.abi": "ABI: describing all the functions (input/output params, scope, ...)",
"solidity.web3Deploy": "Copy/paste this code to any JavaScript/Web3 console to deploy this contract",

@ -15,7 +15,7 @@
"terminal.welcomeText8": "Right click on a JavaScript file in the file explorer and then click `Run`",
"terminal.welcomeText9": "The following libraries are accessible",
"terminal.welcomeText10": "Type the library name to see available commands",
"terminal.text1": "This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.",
"terminal.text1": "This type of command has been deprecated and is not functioning anymore. Please run remix.help() to list available commands.",
"terminal.hideTerminal": "Hide Terminal",
"terminal.showTerminal": "Show Terminal",
"terminal.clearConsole": "Clear console",

@ -26,7 +26,7 @@
"udapp.contractOptionsTitle2": "Select a compiled contract to deploy or to use with At Address.",
"udapp.contractOptionsTitle3": "Select and compile *.sol file to deploy or access a contract.",
"udapp.contractOptionsTitle4": "When there is a compiled .sol file, choose the contract to deploy or to use with At Address.",
"udapp.checkSumWarning": "It seems you are not using a checksumed address.A checksummed address is an address that contains uppercase letters, as specified in {a}.Checksummed addresses are meant to help prevent users from sending transactions to the wrong address.",
"udapp.checkSumWarning": "It seems you are not using a checksummed address. A checksummed address is an address that contains uppercase letters, as specified in {a}. Checksummed addresses are meant to help prevent users from sending transactions to the wrong address.",
"udapp.isOverSizePromptEip170": "Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fail if the current network has activated the eip 170. More info: {a}",
"udapp.isOverSizePromptEip3860": "Contract creation init code exceeds the allowed max code size of 49152 bytes. The deployment will likely fail if the current network has activated the eip 3860. More info: {a}",
"udapp.thisContractMayBeAbstract": "This contract may be abstract, it may not implement an abstract parent's methods completely or it may not invoke an inherited contract's constructor correctly.",
@ -123,7 +123,7 @@
"udapp.contractCreation": "Contract Creation",
"udapp.transactionFee": "Transaction is invalid. Max fee should not be less than Base fee",
"udapp.title1": "Represents the part of the tx fee that goes to the miner.",
"udapp.title2": "Represents the maximum amount of fee that you will pay for this transaction. The minimun needs to be set to base fee.",
"udapp.title2": "Represents the maximum amount of fee that you will pay for this transaction. The minimum needs to be set to base fee.",
"udapp.gasPrice": "Gas price",
"udapp.gweiText": "visit {a} for current gas price info.",
"udapp.maxTransactionFee": "Max transaction fee",

@ -1,36 +1,17 @@
import debuggerJson from './debugger.json';
import filePanelJson from './filePanel.json';
import homeJson from './home.json';
import panelJson from './panel.json';
import pluginManagerJson from './pluginManager.json';
import searchJson from './search.json';
import settingsJson from './settings.json';
import solidityJson from './solidity.json';
import terminalJson from './terminal.json';
import udappJson from './udapp.json';
import solidityUnitTestingJson from './solidityUnitTesting.json';
import permissionHandlerJson from './permissionHandler.json';
import solUmlGenJson from './solUmlGen.json'
import remixAppJson from './remixApp.json'
import remixUiTabsJson from './remixUiTabs.json'
import enJson from '../en';
import enJson from '../en'
function readAndCombineJsonFiles() {
const dataContext = require.context('./', true, /\.json$/)
let combinedData = {}
dataContext.keys().forEach((key) => {
const jsonData = dataContext(key)
combinedData = {...combinedData, ...jsonData}
})
return combinedData
}
// There may have some un-translated content. Always fill in the gaps with EN JSON.
// No need for a defaultMessage prop when render a FormattedMessage component.
export default Object.assign({}, enJson, {
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
...solUmlGenJson,
...remixAppJson,
...remixUiTabsJson,
})
export default Object.assign({}, enJson, readAndCombineJsonFiles())

@ -1,36 +1,17 @@
import debuggerJson from './debugger.json';
import filePanelJson from './filePanel.json';
import homeJson from './home.json';
import panelJson from './panel.json';
import pluginManagerJson from './pluginManager.json';
import searchJson from './search.json';
import settingsJson from './settings.json';
import solidityJson from './solidity.json';
import terminalJson from './terminal.json';
import udappJson from './udapp.json';
import solidityUnitTestingJson from './solidityUnitTesting.json';
import permissionHandlerJson from './permissionHandler.json';
import solUmlGenJson from './solUmlGen.json'
import remixAppJson from './remixApp.json'
import remixUiTabsJson from './remixUiTabs.json'
import enJson from '../en';
import enJson from '../en'
function readAndCombineJsonFiles() {
const dataContext = require.context('./', true, /\.json$/)
let combinedData = {}
dataContext.keys().forEach((key) => {
const jsonData = dataContext(key)
combinedData = {...combinedData, ...jsonData}
})
return combinedData
}
// There may have some un-translated content. Always fill in the gaps with EN JSON.
// No need for a defaultMessage prop when render a FormattedMessage component.
export default Object.assign({}, enJson, {
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
...solUmlGenJson,
...remixAppJson,
...remixUiTabsJson,
})
export default Object.assign({}, enJson, readAndCombineJsonFiles())

@ -1,36 +1,17 @@
import debuggerJson from './debugger.json';
import filePanelJson from './filePanel.json';
import homeJson from './home.json';
import panelJson from './panel.json';
import pluginManagerJson from './pluginManager.json';
import searchJson from './search.json';
import settingsJson from './settings.json';
import solidityJson from './solidity.json';
import terminalJson from './terminal.json';
import udappJson from './udapp.json';
import solidityUnitTestingJson from './solidityUnitTesting.json';
import permissionHandlerJson from './permissionHandler.json';
import solUmlGenJson from './solUmlGen.json'
import remixAppJson from './remixApp.json'
import remixUiTabsJson from './remixUiTabs.json'
import enJson from '../en';
import enJson from '../en'
function readAndCombineJsonFiles() {
const dataContext = require.context('./', true, /\.json$/)
let combinedData = {}
dataContext.keys().forEach((key) => {
const jsonData = dataContext(key)
combinedData = {...combinedData, ...jsonData}
})
return combinedData
}
// There may have some un-translated content. Always fill in the gaps with EN JSON.
// No need for a defaultMessage prop when render a FormattedMessage component.
export default Object.assign({}, enJson, {
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
...solUmlGenJson,
...remixAppJson,
...remixUiTabsJson,
})
export default Object.assign({}, enJson, readAndCombineJsonFiles())

@ -1,38 +1,17 @@
import debuggerJson from './debugger.json';
import filePanelJson from './filePanel.json';
import homeJson from './home.json';
import panelJson from './panel.json';
import pluginManagerJson from './pluginManager.json';
import searchJson from './search.json';
import settingsJson from './settings.json';
import solidityJson from './solidity.json';
import terminalJson from './terminal.json';
import udappJson from './udapp.json';
import solidityUnitTestingJson from './solidityUnitTesting.json';
import permissionHandlerJson from './permissionHandler.json';
import solUmlGenJson from './solUmlGen.json'
import remixAppJson from './remixApp.json'
import remixUiTabsJson from './remixUiTabs.json'
import enJson from '../en';
import circuitJson from './circuit.json';
import enJson from '../en'
function readAndCombineJsonFiles() {
const dataContext = require.context('./', true, /\.json$/)
let combinedData = {}
dataContext.keys().forEach((key) => {
const jsonData = dataContext(key)
combinedData = {...combinedData, ...jsonData}
})
return combinedData
}
// There may have some un-translated content. Always fill in the gaps with EN JSON.
// No need for a defaultMessage prop when render a FormattedMessage component.
export default Object.assign({}, enJson, {
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
...solUmlGenJson,
...remixAppJson,
...remixUiTabsJson,
...circuitJson
})
export default Object.assign({}, enJson, readAndCombineJsonFiles())

@ -20,7 +20,7 @@ export class NetworkModule extends Plugin {
super(profile)
this.blockchain = blockchain
// TODO: See with remix-lib to make sementic coherent
// TODO: See with remix-lib to make semantic coherent
this.blockchain.event.register('contextChanged', (provider) => {
this.emit('providerChanged', provider)
})

@ -84,7 +84,7 @@ class Recorder extends Plugin {
const rawAddress = txResult.receipt.contractAddress
if (!rawAddress) return // not a contract creation
const address = addressToString(rawAddress)
// save back created addresses for the conversion from tokens to real adresses
// save back created addresses for the conversion from tokens to real addresses
this.data._createdContracts[address] = timestamp
this.data._createdContractsReverse[timestamp] = address
})
@ -98,7 +98,7 @@ class Recorder extends Plugin {
}
/**
* stop/start saving txs. If not listenning, is basically in replay mode
* stop/start saving txs. If not listening, is basically in replay mode
*
* @param {Bool} listen
*/
@ -279,7 +279,7 @@ class Recorder extends Plugin {
}
if (rawAddress) {
const address = addressToString(rawAddress)
// save back created addresses for the conversion from tokens to real adresses
// save back created addresses for the conversion from tokens to real addresses
this.data._createdContracts[address] = tx.timestamp
this.data._createdContractsReverse[tx.timestamp] = address
newContractFn(abi, address, record.contractName)

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

Loading…
Cancel
Save