Merge pull request #4388 from ethereum/newFEdesktop

desktop alpha
desktop
yann300 11 months ago committed by GitHub
commit 051a1054db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 166
      .circleci/config.yml
  2. 7
      .gitignore
  3. 3
      .prettierrc.json
  4. 2
      apps/circuit-compiler/src/app/components/feedback.tsx
  5. 11
      apps/circuit-compiler/src/main.tsx
  6. 8
      apps/debugger/src/main.tsx
  7. 2
      apps/doc-gen/src/app/views/ErrorView.tsx
  8. 14
      apps/doc-gen/src/main.tsx
  9. 13
      apps/doc-viewer/src/main.tsx
  10. 2
      apps/etherscan/src/app/EtherscanPluginClient.ts
  11. 31
      apps/etherscan/src/app/app.tsx
  12. 14
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  13. 2
      apps/etherscan/src/app/components/SubmitButton.tsx
  14. 2
      apps/etherscan/src/app/layouts/Default.tsx
  15. 5
      apps/etherscan/src/app/routes.tsx
  16. 97
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  17. 2
      apps/etherscan/src/app/views/ErrorView.tsx
  18. 42
      apps/etherscan/src/app/views/HomeView.tsx
  19. 149
      apps/etherscan/src/app/views/ReceiptsView.tsx
  20. 2
      apps/etherscan/src/app/views/VerifyView.tsx
  21. 17
      apps/etherscan/src/main.tsx
  22. 6
      apps/remix-ide-e2e/src/commands/addFile.ts
  23. 35
      apps/remix-ide-e2e/src/commands/enableClipBoard.ts
  24. 0
      apps/remix-ide-e2e/src/commands/getClipBoardContent.ts
  25. 29
      apps/remix-ide-e2e/src/commands/hideToolTips.ts
  26. 1
      apps/remix-ide-e2e/src/commands/noWorkerErrorFor.ts
  27. 39
      apps/remix-ide-e2e/src/commands/refreshPage.ts
  28. 12
      apps/remix-ide-e2e/src/commands/renamePath.ts
  29. 2
      apps/remix-ide-e2e/src/commands/sendLowLevelTx.ts
  30. 11
      apps/remix-ide-e2e/src/commands/setSolidityCompilerVersion.ts
  31. 15
      apps/remix-ide-e2e/src/commands/validateValueInput.ts
  32. 67
      apps/remix-ide-e2e/src/helpers/init.ts
  33. 2
      apps/remix-ide-e2e/src/local-plugin/src/app/Client.ts
  34. 2
      apps/remix-ide-e2e/src/local-plugin/src/app/logger.tsx
  35. 13
      apps/remix-ide-e2e/src/local-plugin/src/main.tsx
  36. 4
      apps/remix-ide-e2e/src/tests/editor.test.ts
  37. 6
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  38. 2
      apps/remix-ide-e2e/src/tests/etherscan_api.test.ts
  39. 82
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  40. 7
      apps/remix-ide-e2e/src/tests/file_decorator.test.ts
  41. 129
      apps/remix-ide-e2e/src/tests/file_explorer_context_menu.test.ts
  42. 103
      apps/remix-ide-e2e/src/tests/file_explorer_dragdrop.test.ts
  43. 8
      apps/remix-ide-e2e/src/tests/gist.test.ts
  44. 9
      apps/remix-ide-e2e/src/tests/migrateFileSystem.test.ts
  45. 2
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  46. 2
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  47. 22
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  48. 1
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  49. 7
      apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts
  50. 60
      apps/remix-ide-e2e/src/tests/stressEditor.test.ts
  51. 39
      apps/remix-ide-e2e/src/tests/url.test.ts
  52. 3
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  53. 3
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  54. 4
      apps/remix-ide-e2e/src/types/index.d.ts
  55. 1
      apps/remix-ide/ci/downloadsoljson.sh
  56. 7
      apps/remix-ide/project.json
  57. 107
      apps/remix-ide/src/app.js
  58. 18
      apps/remix-ide/src/app/components/preload.tsx
  59. 5
      apps/remix-ide/src/app/editor/editor.js
  60. 461
      apps/remix-ide/src/app/files/dgitProvider.ts
  61. 92
      apps/remix-ide/src/app/files/electronProvider.ts
  62. 59
      apps/remix-ide/src/app/files/fileManager.ts
  63. 38
      apps/remix-ide/src/app/files/fileProvider.ts
  64. 2
      apps/remix-ide/src/app/files/remixDProvider.js
  65. 2
      apps/remix-ide/src/app/files/workspaceFileProvider.js
  66. 40
      apps/remix-ide/src/app/panels/file-panel.js
  67. 3
      apps/remix-ide/src/app/panels/layout.ts
  68. 36
      apps/remix-ide/src/app/panels/terminal.js
  69. 10
      apps/remix-ide/src/app/plugins/compile-details.tsx
  70. 2
      apps/remix-ide/src/app/plugins/config.ts
  71. 64
      apps/remix-ide/src/app/plugins/electron/compilerLoaderPlugin.ts
  72. 12
      apps/remix-ide/src/app/plugins/electron/electronConfigPlugin.ts
  73. 127
      apps/remix-ide/src/app/plugins/electron/fsPlugin.ts
  74. 12
      apps/remix-ide/src/app/plugins/electron/isoGitPlugin.ts
  75. 12
      apps/remix-ide/src/app/plugins/electron/ripgrepPlugin.ts
  76. 11
      apps/remix-ide/src/app/plugins/electron/templatesPlugin.ts
  77. 11
      apps/remix-ide/src/app/plugins/electron/xtermPlugin.ts
  78. 44
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  79. 9
      apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts
  80. 38
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  81. 34
      apps/remix-ide/src/app/plugins/parser/services/code-parser-imports.ts
  82. 30
      apps/remix-ide/src/app/plugins/remix-templates.ts
  83. 7
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  84. 2
      apps/remix-ide/src/app/plugins/solidity-script.tsx
  85. 6
      apps/remix-ide/src/app/providers/external-http-provider.tsx
  86. 2
      apps/remix-ide/src/app/tabs/analysis-tab.js
  87. 6
      apps/remix-ide/src/app/tabs/compile-tab.js
  88. 2
      apps/remix-ide/src/app/tabs/locale-module.js
  89. 4
      apps/remix-ide/src/app/tabs/locales/en/electron.json
  90. 3
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  91. 2
      apps/remix-ide/src/app/tabs/locales/en/index.js
  92. 3
      apps/remix-ide/src/app/tabs/locales/en/pluginManager.json
  93. 1
      apps/remix-ide/src/app/tabs/locales/en/solidity.json
  94. 2
      apps/remix-ide/src/app/tabs/settings-tab.tsx
  95. 5
      apps/remix-ide/src/app/tabs/test-tab.js
  96. 34
      apps/remix-ide/src/app/tabs/theme-module.js
  97. 2
      apps/remix-ide/src/app/udapp/make-udapp.js
  98. 1
      apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css
  99. 19022
      apps/remix-ide/src/assets/fontawesome/css/all.css
  100. BIN
      apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.ttf
  101. Some files were not shown because too many files have changed in this diff Show More

@ -6,6 +6,7 @@ parameters:
default: false
orbs:
browser-tools: circleci/browser-tools@1.4.4
win: circleci/windows@5.0
jobs:
build:
docker:
@ -28,9 +29,9 @@ jobs:
name: Build
command: |
if [ "${CIRCLE_BRANCH}" == "master" ]; then
NX_BIN_URL=http://127.0.0.1:8080/assets/js/soljson NX_WASM_URL=http://127.0.0.1:8080/assets/js/soljson NPM_URL=http://localhost:9090/ yarn build:production
NX_BIN_URL=http://127.0.0.1:8080/assets/js/soljson NX_WASM_URL=http://127.0.0.1:8080/assets/js/soljson NX_NPM_URL=http://127.0.0.1:9090/ yarn build:production
else
NX_BIN_URL=http://127.0.0.1:8080/assets/js/soljson NX_WASM_URL=http://127.0.0.1:8080/assets/js/soljson NPM_URL=http://localhost:9090/ yarn build
NX_BIN_URL=http://127.0.0.1:8080/assets/js/soljson NX_WASM_URL=http://127.0.0.1:8080/assets/js/soljson NX_NPM_URL=http://127.0.0.1:9090/ yarn build
fi
- run: yarn run build:e2e
@ -50,6 +51,32 @@ jobs:
paths:
- "persist"
build-desktop:
docker:
- image: cimg/node:20.0.0-browsers
resource_class:
xlarge
working_directory: ~/remix-project
steps:
- checkout
- restore_cache:
keys:
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn
- save_cache:
key: v1-deps-{{ checksum "yarn.lock" }}
paths:
- node_modules
- run:
name: Build
command: |
yarn build:desktop
- run: mkdir persist && zip -0 -r persist/desktopbuild.zip dist/apps/remix-ide
- persist_to_workspace:
root: .
paths:
- "persist"
build-plugin:
docker:
@ -77,6 +104,126 @@ jobs:
paths:
- "persist"
build-remixdesktop-linux:
machine:
image: ubuntu-2004:current
resource_class:
xlarge
working_directory: ~/remix-project
steps:
- run: ldd --version
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/desktopbuild.zip
- run:
command: |
node -v
mkdir apps/remixdesktop/build
cp -r dist/apps/remix-ide apps/remixdesktop/build
cd apps/remixdesktop/
yarn add node-pty
yarn --ignore-optional
yarn add @remix-project/remix-ws-templates
PUBLISH_FOR_PULL_REQUEST='true' yarn dist
rm -rf release/*-unpacked
- save_cache:
key: remixdesktop-linux-deps-{{ checksum "apps/remixdesktop/yarn.lock" }}
paths:
- apps/remixdesktop/node_modules
- store_artifacts:
path: apps/remixdesktop/release/
destination: remixdesktop-linux
build-remixdesktop-windows:
executor:
name: win/default # executor type
size: xlarge # can be medium, large, xlarge, 2xlarge
shell: bash.exe
working_directory: ~/remix-project
steps:
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/desktopbuild.zip
- restore_cache:
key: node-20-windows-v3
- run:
command: |
nvm install 20.0.0
nvm use 20.0.0
node -v
npx -v
npm install --global yarn
yarn -v
- save_cache:
key: node-20-windows-v3
paths:
- /ProgramData/nvm/v20.0.0
- restore_cache:
keys:
- remixdesktop-windows-deps-{{ checksum "apps/remixdesktop/yarn.lock" }}
- run:
command: |
mkdir apps/remixdesktop/build
cp -r dist/apps/remix-ide apps/remixdesktop/build
cd apps/remixdesktop/
yarn
PUBLISH_FOR_PULL_REQUEST='true' yarn dist
rm -rf release/*-unpacked
- save_cache:
key: remixdesktop-windows-deps-{{ checksum "apps/remixdesktop/yarn.lock" }}
paths:
- apps/remixdesktop/node_modules
- store_artifacts:
path: apps/remixdesktop/release/
destination: remixdesktop-windows
build-remixdesktop-mac:
macos:
xcode: 14.2.0
resource_class:
macos.m1.large.gen1
working_directory: ~/remix-project
steps:
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/desktopbuild.zip
- run:
command: |
ls -la dist/apps/remix-ide
nvm install 20.0.0
nvm use 20.0.0
- restore_cache:
keys:
- remixdesktop-deps-mac-{{ checksum "apps/remixdesktop/yarn.lock" }}
- run:
command: |
nvm use 20.0.0
cd apps/remixdesktop && yarn
yarn add @remix-project/remix-ws-templates
- save_cache:
key: remixdesktop-deps-mac-{{ checksum "apps/remixdesktop/yarn.lock" }}
paths:
- apps/remixdesktop/node_modules
# use USE_HARD_LINK=false https://github.com/electron-userland/electron-builder/issues/3179
- run:
command: |
nvm use 20.0.0
mkdir apps/remixdesktop/build
cp -r dist/apps/remix-ide apps/remixdesktop/build
cd apps/remixdesktop
yarn
yarn installRipGrepMacOXarm64
PUBLISH_FOR_PULL_REQUEST='true' USE_HARD_LINKS=false yarn dist --mac --arm64
yarn installRipGrepMacOXx64
PUBLISH_FOR_PULL_REQUEST='true' USE_HARD_LINKS=false yarn dist --mac --x64
rm -rf release/mac*
- store_artifacts:
path: apps/remixdesktop/release/
destination: remixdesktop-mac
lint:
docker:
- image: cimg/node:20.0.0-browsers
@ -166,7 +313,7 @@ jobs:
at: .
- run: unzip ./persist/dist.zip
- 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
- when:
@ -295,6 +442,19 @@ workflows:
unless: << pipeline.parameters.run_flaky_tests >>
jobs:
- build
- build-desktop:
filters:
branches:
only: ['master', /.*desktop.*/]
- build-remixdesktop-mac:
requires:
- build-desktop
- build-remixdesktop-windows:
requires:
- build-desktop
- build-remixdesktop-linux:
requires:
- build-desktop
- build-plugin:
matrix:
parameters:

7
.gitignore vendored

@ -57,3 +57,10 @@ testem.log
.DS_Store
.vscode/settings.json
.vscode/launch.json
apps/remixdesktop/.webpack
apps/remixdesktop/out
apps/remixdesktop/release/
apps/remix-ide/src/assets/list.json
apps/remix-ide/src/assets/esbuild.wasm
apps/remixdesktop/build*

@ -4,5 +4,6 @@
"bracketSpacing": false,
"useTabs": false,
"semi": false,
"singleQuote": true
"singleQuote": true,
"bracketSpacing": false
}

@ -22,7 +22,7 @@ export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openEr
<div className="circuit_errors_box py-4">
<RenderIf condition={ (typeof feedback === "string") && showException }>
<div className="circuit_feedback error alert alert-danger" data-id="circuit_feedback">
<span> { feedback } </span>
<span> <>{ feedback }</> </span>
<div className="close" data-id="renderer" onClick={handleCloseException}>
<i className="fas fa-times"></i>
</div>

@ -1,8 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { createRoot } from 'react-dom/client';
import App from './app/app'
ReactDOM.render(
<App />,
document.getElementById('root')
)
const container = document.getElementById('root');
if (container) {
createRoot(container).render(<App />);
}

@ -1,6 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { createRoot } from 'react-dom/client';
import App from './app/app'
ReactDOM.render(<App />, document.getElementById('root'))
const container = document.getElementById('root');
if (container) {
createRoot(container).render(<App />);
}

@ -1,6 +1,6 @@
import React from 'react'
export const ErrorView: React.FC = () => {
export const ErrorView = () => {
return (
<div
style={{

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

@ -1,10 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { createRoot } from 'react-dom/client';
import App from './app/App'
ReactDOM.render(
<React.StrictMode>
const container = document.getElementById('root');
if (container) {
createRoot(container).render(<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
</React.StrictMode>);
}

@ -9,8 +9,8 @@ export class EtherscanPluginClient extends PluginClient {
constructor() {
super()
createClient(this)
this.internalEvents = new EventManager()
createClient(this)
this.onload()
}

@ -40,22 +40,27 @@ const App = () => {
contractsRef.current = contracts
useEffect(() => {
plugin.internalEvents.on('etherscan_activated', () => {
plugin.on('solidity', 'compilationFinished', (fileName: string, source: CompilationFileSources, languageVersion: string, data: CompilationResult) => {
const newContractsNames = getNewContractNames(data)
const setListeners = () => {
plugin.on('solidity', 'compilationFinished', (fileName: string, source: CompilationFileSources, languageVersion: string, data: CompilationResult) => {
const newContractsNames = getNewContractNames(data)
const newContractsToSave: string[] = [...contractsRef.current, ...newContractsNames]
const newContractsToSave: string[] = [...contractsRef.current, ...newContractsNames]
const uniqueContracts: string[] = [...new Set(newContractsToSave)]
const uniqueContracts: string[] = [...new Set(newContractsToSave)]
setContracts(uniqueContracts)
})
plugin.on('blockchain' as any, 'networkStatus', (result) => {
setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`)
})
// @ts-ignore
plugin.call('blockchain', 'getCurrentNetworkStatus').then((result: any) => setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`))
setContracts(uniqueContracts)
})
plugin.on('blockchain' as any, 'networkStatus', (result) => {
setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`)
})
// @ts-ignore
plugin.call('blockchain', 'getCurrentNetworkStatus').then((result: any) => setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`))
}
useEffect(() => {
plugin.onload(() => {
setListeners()
})
}, [])

@ -13,7 +13,7 @@ interface IconProps {
from: string
}
const HomeIcon: React.FC<IconProps> = ({from}: IconProps) => {
const HomeIcon = ({from}: IconProps) => {
return (
<NavLink
data-id="home"
@ -31,7 +31,7 @@ const HomeIcon: React.FC<IconProps> = ({from}: IconProps) => {
)
}
const ReceiptsIcon: React.FC<IconProps> = ({from}: IconProps) => {
const ReceiptsIcon = ({from}: IconProps) => {
return (
<NavLink
data-id="receipts"
@ -49,7 +49,7 @@ const ReceiptsIcon: React.FC<IconProps> = ({from}: IconProps) => {
)
}
const SettingsIcon: React.FC<IconProps> = ({from}: IconProps) => {
const SettingsIcon = ({from}: IconProps) => {
return (
<NavLink
data-id="settings"
@ -67,11 +67,9 @@ const SettingsIcon: React.FC<IconProps> = ({from}: IconProps) => {
)
}
export const HeaderWithSettings: React.FC<Props> = ({title = '', from}) => {
export const HeaderWithSettings = ({title = '', from}) => {
return (
<AppContext.Consumer>
{() => (
<div className="d-flex justify-content-between">
<div className="d-flex justify-content-between">
<h6 className="d-inline">{title}</h6>
<div className="nav">
<HomeIcon from={from} />
@ -79,7 +77,5 @@ export const HeaderWithSettings: React.FC<Props> = ({title = '', from}) => {
<SettingsIcon from={from} />
</div>
</div>
)}
</AppContext.Consumer>
)
}

@ -8,7 +8,7 @@ interface Props {
disable?: boolean
}
export const SubmitButton: React.FC<Props> = ({text, dataId, isSubmitting = false, disable = true}) => {
export const SubmitButton = ({text, dataId, isSubmitting = false, disable = true}) => {
return (
<div>
<button data-id={dataId} type="submit" className="btn btn-primary btn-block p-1 text-decoration-none" disabled={disable}>

@ -7,7 +7,7 @@ interface Props {
title?: string
}
export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({children, from, title}) => {
export const DefaultLayout = ({children, from, title}) => {
return (
<div>
<HeaderWithSettings from={from} title={title} />

@ -4,11 +4,6 @@ import {HashRouter as Router, Route, Routes, RouteProps} from 'react-router-dom'
import {ErrorView, HomeView, ReceiptsView, CaptureKeyView} from './views'
import {DefaultLayout} from './layouts'
interface Props extends RouteProps {
component: any // TODO: new (props: any) => React.Component
from: string
}
export const DisplayRoutes = () => (
<Router>
<Routes>

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

@ -1,6 +1,6 @@
import React from 'react'
export const ErrorView: React.FC = () => {
export const ErrorView = () => {
return (
<div className="d-flex w-100 flex-column align-items-center">
<img className="pb-4" width="250" src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png" alt="Error page" />

@ -7,29 +7,25 @@ import {Receipt} from '../types'
import {VerifyView} from './VerifyView'
export const HomeView: React.FC = () => {
return (
<AppContext.Consumer>
{({apiKey, clientInstance, setReceipts, receipts, contracts, networkName}) => {
return !apiKey ? (
<Navigate
to={{
pathname: '/settings'
}}
/>
) : (
<VerifyView
contracts={contracts}
client={clientInstance}
apiKey={apiKey}
onVerifiedContract={(receipt: Receipt) => {
const newReceipts = [...receipts, receipt]
setReceipts(newReceipts)
}}
networkName={networkName}
/>
)
export const HomeView = () => {
const context = React.useContext(AppContext)
return !context.apiKey ? (
<Navigate
to={{
pathname: '/settings'
}}
</AppContext.Consumer>
/>
) : (
<VerifyView
contracts={context.contracts}
client={context.clientInstance}
apiKey={context.apiKey}
onVerifiedContract={(receipt: Receipt) => {
const newReceipts = [...context.receipts, receipt]
context.setReceipts(newReceipts)
}}
networkName={context.networkName}
/>
)
}

@ -13,9 +13,10 @@ interface FormValues {
receiptGuid: string
}
export const ReceiptsView: React.FC = () => {
export const ReceiptsView = () => {
const [results, setResults] = useState({succeed: false, message: ''})
const [isProxyContractReceipt, setIsProxyContractReceipt] = useState(false)
const context = React.useContext(AppContext)
const onGetReceiptStatus = async (values: FormValues, clientInstance: any, apiKey: string) => {
try {
@ -48,88 +49,82 @@ export const ReceiptsView: React.FC = () => {
}
}
return (
<AppContext.Consumer>
{({apiKey, clientInstance, receipts, setReceipts}) => {
return !apiKey ? (
<Navigate
to={{
pathname: '/settings'
}}
/>
) : (
<div>
<Formik
initialValues={{receiptGuid: ''}}
validate={(values) => {
const errors = {} as any
if (!values.receiptGuid) {
errors.receiptGuid = 'Required'
}
return errors
}}
onSubmit={(values) => onGetReceiptStatus(values, clientInstance, apiKey)}
>
{({errors, touched, handleSubmit, handleChange}) => (
<form onSubmit={handleSubmit}>
<div className="form-group mb-2">
<label htmlFor="receiptGuid">Receipt GUID</label>
<Field
className={errors.receiptGuid && touched.receiptGuid ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'}
type="text"
name="receiptGuid"
/>
<ErrorMessage className="invalid-feedback" name="receiptGuid" component="div" />
</div>
return !context.apiKey ? (
<Navigate
to={{
pathname: '/settings'
}}
/>
) : (
<div>
<Formik
initialValues={{receiptGuid: ''}}
validate={(values) => {
const errors = {} as any
if (!values.receiptGuid) {
errors.receiptGuid = 'Required'
}
return errors
}}
onSubmit={(values) => onGetReceiptStatus(values, context.clientInstance, context.apiKey)}
>
{({errors, touched, handleSubmit, handleChange}) => (
<form onSubmit={handleSubmit}>
<div className="form-group mb-2">
<label htmlFor="receiptGuid">Receipt GUID</label>
<Field
className={errors.receiptGuid && touched.receiptGuid ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'}
type="text"
name="receiptGuid"
/>
<ErrorMessage className="invalid-feedback" name="receiptGuid" component="div" />
</div>
<div className="d-flex mb-2 custom-control custom-checkbox">
<Field
className="custom-control-input"
type="checkbox"
name="isProxyReceipt"
id="isProxyReceipt"
onChange={async (e) => {
handleChange(e)
if (e.target.checked) setIsProxyContractReceipt(true)
else setIsProxyContractReceipt(false)
}}
/>
<label className="form-check-label custom-control-label" htmlFor="isProxyReceipt">
It's a proxy contract GUID
</label>
</div>
<SubmitButton text="Check" disable={!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} />
</form>
)}
</Formik>
<div className="d-flex mb-2 custom-control custom-checkbox">
<Field
className="custom-control-input"
type="checkbox"
name="isProxyReceipt"
id="isProxyReceipt"
onChange={async (e) => {
handleChange(e)
if (e.target.checked) setIsProxyContractReceipt(true)
else setIsProxyContractReceipt(false)
}}
/>
<label className="form-check-label custom-control-label" htmlFor="isProxyReceipt">
It's a proxy contract GUID
</label>
</div>
<SubmitButton dataId={null} text="Check" disable={!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} />
</form>
)}
</Formik>
<div
className={results['succeed'] ? 'text-success mt-3 text-center' : 'text-danger mt-3 text-center'}
dangerouslySetInnerHTML={{
__html: results.message ? results.message : ''
}}
/>
<div
className={results['succeed'] ? 'text-success mt-3 text-center' : 'text-danger mt-3 text-center'}
dangerouslySetInnerHTML={{
__html: results.message ? results.message : ''
}}
/>
<ReceiptsTable receipts={receipts} />
<br />
<CustomTooltip tooltipText="Clear the list of receipts" tooltipId="etherscan-clear-receipts" placement="bottom">
<Button
className="btn-sm"
onClick={() => {
setReceipts([])
}}
>
Clear
</Button>
</CustomTooltip>
</div>
)
}}
</AppContext.Consumer>
<ReceiptsTable receipts={context.receipts} />
<br />
<CustomTooltip tooltipText="Clear the list of receipts" tooltipId="etherscan-clear-receipts" placement="bottom">
<Button
className="btn-sm"
onClick={() => {
context.setReceipts([])
}}
>
Clear
</Button>
</CustomTooltip>
</div>
)
}
const ReceiptsTable: React.FC<{receipts: Receipt[]}> = ({receipts}) => {
const ReceiptsTable = ({receipts}) => {
return (
<div className="table-responsive">
<h6>Receipts</h6>

@ -24,7 +24,7 @@ interface FormValues {
expectedImplAddress?: string
}
export const VerifyView: React.FC<Props> = ({apiKey, client, contracts, onVerifiedContract, networkName}) => {
export const VerifyView = ({apiKey, client, contracts, onVerifiedContract, networkName}) => {
const [results, setResults] = useState('')
const [selectedContract, setSelectedContract] = useState('')
const [showConstructorArgs, setShowConstructorArgs] = useState(false)

@ -1,11 +1,14 @@
import {StrictMode} from 'react'
import React from 'react'
import * as ReactDOM from 'react-dom'
import { createRoot } from 'react-dom/client';
import App from './app/app'
ReactDOM.render(
<StrictMode>
const container = document.getElementById('root');
if (container) {
createRoot(container).render(
<App />
</StrictMode>,
document.getElementById('root')
)
);
}

@ -44,9 +44,9 @@ function addFile(browser: NightwatchBrowser, name: string, content: NightwatchCo
})
} else {
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
// isvisible is protocol action called isDisplayed https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/WebElement.html#isDisplayed--
.isVisible({
selector: `li[data-id="treeViewLitreeViewItem${name}"]`,

@ -0,0 +1,35 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class EnableClipBoard extends EventEmitter {
command (this: NightwatchBrowser, remember:boolean, accept: boolean): NightwatchBrowser {
const browser = this.api
if(browser.browserName.indexOf('chrome') > -1){
const chromeBrowser = (browser as any).chrome
chromeBrowser.setPermission('clipboard-read', 'granted')
chromeBrowser.setPermission('clipboard-write', 'granted')
// test it
browser.executeAsyncScript(function (done) {
navigator.clipboard.writeText('test').then(function () {
navigator.clipboard.readText().then(function (text) {
console.log('Pasted content: ', text)
done(text)
}).catch(function (err) {
console.error('Failed to read clipboard contents: ', err)
done()
})
}).catch(function (err) {
console.error('Failed to write to clipboard: ', err)
done()
})
}, [], function (result) {
browser.assert.ok((result as any).value === 'test', 'copy paste should work')
})
}
this.emit('complete')
return this
}
}
module.exports = EnableClipBoard

@ -0,0 +1,29 @@
import {NightwatchBrowser} from 'nightwatch'
import EventEmitter from 'events'
class HideToolTips extends EventEmitter {
command(this: NightwatchBrowser) {
browser
.perform((done) => {
browser.execute(function () {
// hide tooltips
function addStyle(styleString) {
const style = document.createElement('style')
style.textContent = styleString
document.head.append(style)
}
addStyle(`
.popover {
display:none !important;
}
`)
}, [], done())
})
.perform((done) => {
done()
this.emit('complete')
})
}
}
module.exports = HideToolTips

@ -16,6 +16,7 @@ class NoWorkerErrorFor extends EventEmitter {
function noWorkerErrorFor (browser: NightwatchBrowser, version: string, callback: VoidFunction) {
browser
.setSolidityCompilerVersion(version)
.waitForElementVisible('*[data-id="compilerContainerCompileBtn"]')
.click('*[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('*[data-id="compilationFinishedWith_' + version + '"]', 60000)
.notContainsText('*[data-id="compiledErrors"]', `Worker error: Uncaught NetworkError: Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at 'https://binaries.soliditylang.org/wasm/${version}' failed to load.`)

@ -1,16 +1,33 @@
import { NightwatchBrowser } from 'nightwatch'
import {NightwatchBrowser} from 'nightwatch'
import EventEmitter from 'events'
class RefreshPage extends EventEmitter {
command(this: NightwatchBrowser) {
browser.refresh()
.verifyLoad()
.perform((done) => {
done()
this.emit('complete')
})
}
}
command(this: NightwatchBrowser) {
browser
.refresh()
.verifyLoad()
.perform((done) => {
//if (hideToolTips) {
browser.execute(function () {
// hide tooltips
function addStyle(styleString) {
const style = document.createElement('style')
style.textContent = styleString
document.head.append(style)
}
addStyle(`
.popover {
display:none !important;
}
`)
}, [], done())
})
.perform((done) => {
done()
this.emit('complete')
})
}
}
module.exports = RefreshPage
module.exports = RefreshPage

@ -34,16 +34,8 @@ function renamePath (browser: NightwatchBrowser, path: string, newFileName: stri
}, [path], function () {
browser
.click('#menuitemrename')
.perform((client, doneSetValue) => {
browser.execute(function (path, addvalue) {
document.querySelector('[data-path="' + path + '"]').innerHTML = addvalue
}, [path, newFileName], () => {
doneSetValue()
})
})
.pause(1000)
.click('div[data-id="remixIdeMainPanel"]') // focus out to save
.pause(2000)
.sendKeys('[data-input-path="' + path + '"]', newFileName)
.sendKeys('[data-input-path="' + path + '"]', browser.Keys.ENTER)
.waitForElementNotPresent('[data-path="' + path + '"]')
.waitForElementPresent('[data-path="' + renamedPath + '"]')
.perform(() => {

@ -9,7 +9,7 @@ class sendLowLevelTx extends EventEmitter {
.sendKeys(`#instance${address} #deployAndRunLLTxCalldata`, ['_', this.api.Keys.BACK_SPACE, callData])
.waitForElementVisible('#value')
.clearValue('#value')
.sendKeys('#value', ['1', this.api.Keys.BACK_SPACE, value])
.sendKeys('#value', value)
.pause(2000)
.scrollAndClick(`#instance${address} #deployAndRunLLTxSendTransaction`)
.perform(() => {

@ -8,15 +8,16 @@ class SetSolidityCompilerVersion extends EventEmitter {
selector: "//*[@id='versionSelector']",
locateStrategy: 'xpath'
})
.waitForElementPresent({
selector: `//option[@value='${version}']`,
.click({
selector: "//*[@id='versionSelector']",
locateStrategy: 'xpath'
})
.click(`#compileTabView #versionSelector [value="${version}"]`)
.waitForElementVisible(`[data-id="dropdown-item-${version}"]`)
.click(`[data-id="dropdown-item-${version}"]`)
.waitForElementPresent({
selector: `//span[@data-version='${version}']`,
selector: `//*[@data-id='compilerloaded' and @data-version='${version}']`,
locateStrategy: 'xpath',
timeout: 60000
timeout: 120000
})
.perform(() => {
this.emit('complete')

@ -2,20 +2,21 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class ValidateValueInput extends EventEmitter {
command (this: NightwatchBrowser, selector: string, valueTosSet: string, expectedValue: string) {
command (this: NightwatchBrowser, selector: string, valueTosSet: string[], expectedValue: string) {
const browser = this.api
browser.perform((done) => {
browser.clearValue(selector)
.pause(2000)
.setValue(selector, valueTosSet).pause(2000)
browser
.click(selector)
.sendKeys(selector, browser.Keys.BACK_SPACE + browser.Keys.BACK_SPACE + browser.Keys.BACK_SPACE + browser.Keys.BACK_SPACE)
.sendKeys(selector, valueTosSet)
.pause(500)
.execute(function (selector) {
const elem = document.querySelector(selector) as HTMLInputElement
return elem.value
}, [selector], function (result) {
browser.assert.equal(result.value, expectedValue)
})
done()
this.emit('complete')
}).perform(() => { done() ;this.emit('complete') })
})
return this
}

@ -10,8 +10,6 @@ type LoadPlugin = {
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true, loadPlugin?: LoadPlugin, hideToolTips: boolean = true): void {
browser
.url(url || 'http://127.0.0.1:8080')
//.switchBrowserTab(0)
.perform((done) => {
if (!loadPlugin) return done()
browser
@ -24,8 +22,8 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
.perform(done())
})
.verifyLoad()
.perform(() => {
if (hideToolTips) {
.enableClipBoard()
.perform((done) => {
browser.execute(function () { // hide tooltips
function addStyle(styleString) {
const style = document.createElement('style');
@ -34,35 +32,42 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
}
addStyle(`
.bs-popover-right {
display:none !important;
}
.bs-popover-top {
display:none !important;
}
.bs-popover-left {
display:none !important;
}
.bs-popover-bottom {
display:none !important;
}
.popover {
display:none !important;
}
`);
}, [], done())
})
.perform(() => {
browser.execute(function () {
(window as any).logs = [];
(console as any).browserLog = console.log;
(console as any).browserError = console.error
console.log = function () {
(window as any).logs.push(JSON.stringify(arguments));
(console as any).browserLog(...arguments)
}
console.error = function () {
(window as any).logs.push(JSON.stringify(arguments));
(console as any).browserError(...arguments)
}
})
}
if (preloadPlugins) {
initModules(browser, () => {
browser
.clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.verify.elementPresent('[data-id="compilerContainerAutoCompile"]:checked')
.perform(() => { callback() })
})
} else {
callback()
}
})
})
.perform(() => {
if (preloadPlugins) {
initModules(browser, () => {
browser
.pause(4000)
.clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.verify.elementPresent('[data-id="compilerContainerAutoCompile"]:checked')
.perform(() => { callback() })
})
} else {
callback()
}
})
}
function initModules(browser: NightwatchBrowser, callback: VoidFunction) {

@ -4,7 +4,7 @@ export class RemixPlugin extends PluginClient {
constructor () {
super()
this.methods = ['testCommand']
createClient(this)
createClient(this as any)
}
async testCommand (data: any) {

@ -4,7 +4,7 @@ interface loggerProps {
id: string
}
export const Logger: React.FC<loggerProps> = (props) => {
export const Logger = (props) => {
return (
<div id={props.id} className="jumbotron overflow-auto text-break mb-1 p-2">
{props.log}

@ -1,11 +1,12 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { createRoot } from 'react-dom/client';
import App from './app/app'
ReactDOM.render(
<React.StrictMode>
const container = document.getElementById('root');
if (container) {
createRoot(container).render(<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
</React.StrictMode>);
}

@ -20,13 +20,13 @@ module.exports = {
.checkElementStyle('.view-lines', 'font-size', '14px')
.click('*[data-id="tabProxyZoomIn"]')
.click('*[data-id="tabProxyZoomIn"]')
.checkElementStyle('.view-lines', 'font-size', '16px')
.checkElementStyle('.view-lines', 'font-size', '16.8px')
},
'Should zoom out editor #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('#editorView')
.checkElementStyle('.view-lines', 'font-size', '16px')
.checkElementStyle('.view-lines', 'font-size', '16.8px')
.click('*[data-id="tabProxyZoomOut"]')
.click('*[data-id="tabProxyZoomOut"]')
.checkElementStyle('.view-lines', 'font-size', '14px')

@ -15,9 +15,9 @@ const checkEditorHoverContent = (browser: NightwatchBrowser, path: string, expec
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
init(browser, done, 'http://127.0.0.1:8080', false, null, true)
},
'Should load the test file': function (browser: NightwatchBrowser) {
'Should load the test file #group1': function (browser: NightwatchBrowser) {
browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
@ -86,7 +86,7 @@ module.exports = {
const expectedContent = 'StructDefinition'
checkEditorHoverContent(browser, path, expectedContent)
},
'Add token file': function (browser: NightwatchBrowser) {
'Add token file #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js')

@ -45,7 +45,7 @@ module.exports = {
.setValue('*[name="contractAddress"]', ['0x9981c9d00103da481c3c65b22a79582a3e3ff50b', browser.Keys.TAB])
.click('[data-id="verify-contract"]')
.waitForElementVisible('[data-id="verify-result"]')
.waitForElementContainsText('[data-id="verify-result"]', 'Contract source code already verified')
.waitForElementContainsText('[data-id="verify-result"]', 'Contract source code already verified', 15000)
},
'Should call the etherscan plugin api #group1': function (browser: NightwatchBrowser) {

@ -22,9 +22,9 @@ module.exports = {
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"]')
.sendKeys('*[data-id$="/blank"] .remixui_items', '5_New_contract.sol')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', '5_New_contract.sol')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]', 7000)
},
@ -32,7 +32,7 @@ module.exports = {
browser
.waitForElementVisible('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]')
.click('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]')
.renamePath('5_New_contract.sol', '5_Renamed_Contract.sol', '5_Renamed_Contract.sol')
.renamePath('5_New_contract.sol', '5_Renamed_Contract', '5_Renamed_Contract.sol')
.waitForElementVisible('*[data-id="treeViewLitreeViewItem5_Renamed_Contract.sol"]')
},
@ -49,9 +49,9 @@ module.exports = {
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"]')
.sendKeys('*[data-id$="/blank"] .remixui_items', 'Browser_Tests')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'Browser_Tests')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
},
@ -113,5 +113,73 @@ module.exports = {
.waitForElementVisible('[data-id="treeViewLitreeViewItemfileExplorer.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemgeneralSettings.test.js"]')
.end()
},
'Should add deep tree with buttons #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="filePanelFileExplorerTree"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep1')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep2')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep3')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2/deep3"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFile"]')
.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep4.sol')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2/deep3/deep4.sol"]')
// click on root to focus
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep5')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep5"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep6')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep5/deep6"]')
// focus on contracts
.click('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep7')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep8')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7/deep8"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFile"]')
.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep9.sol')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7/deep8/deep9.sol"]')
.end()
}
}

@ -4,7 +4,6 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
@ -17,7 +16,7 @@ module.exports = {
.openFile('contracts/3_Ballot.sol')
.addFile('scripts/decorators.ts', { content: testScriptSet })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.click('[data-id="play-editor"]')
.pause(4000)
.useXpath()
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2')
@ -38,7 +37,7 @@ module.exports = {
.useCss()
.addFile('scripts/clearballot.ts', { content: testScriptClearBallot })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.click('[data-id="play-editor"]')
.pause(4000)
.waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000)
},
@ -46,7 +45,7 @@ module.exports = {
browser
.addFile('scripts/clearall.ts', { content: testScriptClear })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.click('[data-id="play-editor"]')
.pause(4000)
.waitForElementNotPresent('[data-id="file-decoration-error-contracts/2_Owner.sol"]', 10000)
.waitForElementNotPresent('[data-id="file-decoration-warning-contracts/1_Storage.sol"]', 10000)

@ -0,0 +1,129 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
// file copy file name tests
'Should copy file name and paste in root with new file button and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.rightClick('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementPresent('[data-id="contextMenuItemcopyFileName"]')
.click('[data-id="contextMenuItemcopyFileName"]')
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME1.txt"]', 7000)
},
'Should copy file name and paste in another folder with new file button and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.rightClick('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementPresent('[data-id="contextMenuItemcopyFileName"]')
.click('[data-id="contextMenuItemcopyFileName"]')
.click('[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/README.txt"]', 7000)
},
'Should copy file name and paste in another folder that has the same filename with new file button and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.rightClick('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementPresent('[data-id="contextMenuItemcopyFileName"]')
.click('[data-id="contextMenuItemcopyFileName"]')
.click('[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/README1.txt"]', 7000)
},
'Should copy file name and paste in root with right click and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.rightClick('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementPresent('[data-id="contextMenuItemcopyFileName"]')
.click('[data-id="contextMenuItemcopyFileName"]')
.rightClick('*[data-id="treeViewUltreeViewMenu"]')
.click('*[data-id="contextMenuItemnewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME2.txt"]', 7000)
},
'Should copy file name and paste in contracts with right click and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.rightClick('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementPresent('[data-id="contextMenuItemcopyFileName"]')
.click('[data-id="contextMenuItemcopyFileName"]')
.rightClick('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="contextMenuItemnewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME2.txt"]', 7000)
},
// file copy paste tests
'Should copy file and paste in root with right click and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.rightClick('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementPresent('[data-id="contextMenuItemcopy')
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewLiMenu"]')
.saveScreenshot('./reports/screenshot/file_explorer_context_menu.png')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemCopy_README.txt"]', 7000)
},
'Should copy file and paste in contracts with right click and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.rightClick('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementPresent('[data-id="contextMenuItemcopy')
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/Copy_README.txt"]', 7000)
},
// folder copy paste tests
'Should copy folder and paste in root with right click and it will contain a copied folder #group1 ': function (browser: NightwatchBrowser) {
browser
.rightClick('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementPresent('[data-id="contextMenuItemcopy')
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewLiMenu"]')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemCopy_contracts"]', 7000)
},
'Should copy folder and paste in contracts with right click and it will contain a copied folder #group1 ': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
.rightClick('li[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementPresent('[data-id="contextMenuItemcopy')
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/Copy_scripts"]', 7000)
}
}

@ -0,0 +1,103 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const checkBrowserIsChrome = function (browser: NightwatchBrowser) {
return browser.browserName.indexOf('chrome') > -1
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'drag and drop file from root to contracts #group1 ': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.findElement('*[data-id="treeViewLitreeViewItemcontracts"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemREADME.txt"]', 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/README.txt"]')
})
}
},
'drag and drop file from contracts to root #group1': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser.findElement('*[data-id="treeViewUltreeViewMenu"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
})
browser.pause(1000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItem1_Storage.sol"]')
}
},
'drag and drop scripts from root to contracts #group1': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.findElement('*[data-id="treeViewLitreeViewItemcontracts"]', (el) => {
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)
.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"]')
})
}
},
'drag scripts from contracts to root #group1': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser.findElement('*[data-id="treeViewUltreeViewMenu"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/scripts"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/scripts"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
})
browser.pause(1000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
}
},
'drag into nested folder': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
.rightClick('li[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementPresent('[data-id="contextMenuItemnewFolder')
.click('[data-id="contextMenuItemnewFolder')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'nested')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.findElement('*[data-id="treeViewLitreeViewItemscripts/nested"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/README.txt"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/README.txt"]', 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="treeViewLitreeViewItemscripts/nested/README.txt"]')
})
}
}
}

@ -30,15 +30,15 @@ module.exports = {
.waitForElementVisible('*[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"]')
.sendKeys('*[data-id$="/blank"] .remixui_items', 'Browser_Tests')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'Browser_Tests')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
.addFile('File.sol', { content: '' })
.executeScriptInTerminal(`remix.loadgist('${gistid}')`)
// .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('gists') } done() })
.waitForElementVisible(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`)
.click(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`)
.openFile(`gist-${gistid}/README.txt`)
// Remix publish to gist
/* .click('*[data-id="fileExplorerNewFilepublishToGist"]')

@ -13,6 +13,7 @@ module.exports = {
.click('*[data-id="skipbackup-btn"]')
.pause(5000)
.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 10000)
.hideToolTips()
},
'Should load the testmigration url and refresh and still have test data #group7': function (browser: NightwatchBrowser) {
browser.url('http://127.0.0.1:8080?e2e_testmigration=true')
@ -23,6 +24,7 @@ module.exports = {
.click('*[data-id="skipbackup-btn"]')
.pause(5000)
.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 10000)
.hideToolTips()
.refreshPage()
},
'should have indexedDB storage in terminal #group1 #group7': function (browser: NightwatchBrowser) {
@ -30,11 +32,13 @@ module.exports = {
},
'Should fallback to localstorage with default data #group2': function (browser: NightwatchBrowser) {
browser.url('http://127.0.0.1:8080?e2e_testmigration_fallback=true')
.hideToolTips()
.pause(6000)
.switchBrowserTab(0)
.maximizeWindow()
.pause(5000)
.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 10000)
.hideToolTips()
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.openFile('README.txt')
.waitForElementVisible('*[id="editorView"]', 10000)
@ -57,6 +61,7 @@ module.exports = {
.click('*[data-id="skipbackup-btn"]')
.pause(5000)
.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 10000)
.hideToolTips()
},
'Should generate error in migration by deleting indexedDB and falling back to local storage with test #group5': function (browser: NightwatchBrowser) {
browser.url('http://127.0.0.1:8080?e2e_testmigration=true')
@ -67,6 +72,7 @@ module.exports = {
.click('*[data-id="skipbackup-btn"]')
.pause(5000)
.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 10000)
.hideToolTips()
},
'should have localstorage storage in terminal #group2 #group3 #group5': function (browser: NightwatchBrowser) {
browser.assert.containsText('*[data-id="terminalJournal"]', 'localstorage')
@ -112,6 +118,7 @@ module.exports = {
// end of test data entries
'Should load with all storage blocked #group4': function (browser: NightwatchBrowser) {
browser.url('http://127.0.0.1:8080?e2e_testblock_storage=true')
.hideToolTips()
.pause(6000)
.switchBrowserTab(0)
.maximizeWindow()
@ -119,11 +126,13 @@ module.exports = {
},
'Should with errors #group6': function (browser: NightwatchBrowser) {
browser.url('http://127.0.0.1:8080?e2e_testmigration=true')
.pause(6000)
.switchBrowserTab(0)
.maximizeWindow().execute('delete window.localStorage')
.waitForElementVisible('*[data-id="skipbackup-btn"]', 5000)
.click('*[data-id="skipbackup-btn"]')
.hideToolTips()
.assert.containsText('.alert-danger', 'An unknown error')
},

@ -431,7 +431,7 @@ module.exports = {
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'I am a re-toast')
},
'Should open 2 alerts from localplugin #group9': function (browser: NightwatchBrowser) {
'Should open 2 alerts from localplugin #group9': !function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('localPlugin')
.useXpath()

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

@ -25,9 +25,10 @@ module.exports = {
'Should load run and deploy tab and check value validation #group1': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'DEPLOY & RUN TRANSACTIONS')
.validateValueInput('#value', '0000', '0')
.validateValueInput('#value', '', '0')
.validateValueInput('#value', 'dragon', '0')
.validateValueInput('*[data-id="dandrValue"]', ['9','9','9'], '999')
.validateValueInput('*[data-id="dandrValue"]', ['0','0','0'], '0')
.validateValueInput('*[data-id="dandrValue"]', ['1','.','3'], '0') // no decimal
// .validateValueInput('*[data-id="dandrValue"]', 'dragon', '0') // only numbers
},
'Should sign message using account key #group2': function (browser: NightwatchBrowser) {
@ -79,12 +80,23 @@ module.exports = {
.pause(1000)
.getAddressAtPosition(1, (address) => {
instanceAddress = address
console.log('instanceAddress', instanceAddress)
browser
.waitForElementVisible(`#instance${instanceAddress} [data-id="instanceContractBal"]`)
.assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000111 ETH')
//*[@id="instance0xbBF289D846208c16EDc8474705C748aff07732dB" and contains(.,"Balance") and contains(.,'0.000000000000000111')]
.waitForElementVisible({
locateStrategy: 'xpath',
selector: `//*[@id="instance${instanceAddress}" and contains(.,"Balance") and contains(.,'0.000000000000000111')]`,
timeout: 60000
})
//.waitForElementContainsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000111 ETH', 60000)
.clickFunction('sendSomeEther - transact (not payable)', { types: 'uint256 num', values: '2' })
.pause(1000)
.assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000109 ETH')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: `//*[@id="instance${instanceAddress}" and contains(.,"Balance") and contains(.,'0.000000000000000109')]`,
timeout: 60000
})
})
},

@ -210,7 +210,6 @@ module.exports = {
browser
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.clickLaunchIcon('filePanel')
.click('*[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/3_Ballot.sol')
.clickLaunchIcon('solidityUnitTesting')
.pause(2000)

@ -46,14 +46,17 @@ module.exports = {
.waitForElementPresent('//*[@id="staticanalysisresult"]', 5000)
.useCss()
// Check warning count
.click('*[data-rb-event-key="remix"]')
.pause()
.waitForElementVisible('span#ssaRemixtab')
.click('span#ssaRemixtab')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '1')
.verify.elementPresent('input[name="showLibWarnings"]')
.verify.not.elementPresent('input[name="showLibWarnings"]:checked')
.verify.elementPresent('label[id="headingshowLibWarnings"]')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.click('*[data-rb-event-key="remix"]')
.waitForElementVisible('span#ssaRemixtab')
.click('span#ssaRemixtab')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '386')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)

@ -36,9 +36,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -51,9 +51,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -66,9 +66,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -81,9 +81,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -96,9 +96,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -111,9 +111,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -126,9 +126,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -141,9 +141,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -156,9 +156,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -171,9 +171,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})

@ -91,15 +91,15 @@ module.exports = {
})
},
'Should load Etherscan verified contracts from URL "address" param)': function (browser: NightwatchBrowser) {
'Should load Etherscan verified contracts from URL "address" param) #group1': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#address=0xdac17f958d2ee523a2206206994597c13d831ec7')
.refreshPage()
.pause(7000)
.currentWorkspaceIs('code-sample')
.assert.elementPresent('*[data-id=treeViewLitreeViewItemmainnet]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7/TetherToken.sol"]')
.waitForElementVisible('*[data-id=treeViewLitreeViewItemmainnet]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7/TetherToken.sol"]')
.getEditorValue((content) => {
browser.assert.ok(content && content.indexOf(
'contract TetherToken is Pausable, StandardToken, BlackList {') !== -1)
@ -218,8 +218,10 @@ module.exports = {
.clickLaunchIcon('solidity')
.click('*[data-id="scConfigExpander"]')
.waitForElementVisible('#versionSelector option[data-id="selected"]')
.assert.containsText('#versionSelector option[data-id="selected"]', '0.8.16+commit.07a7930e')
.waitForElementVisible({
selector: "//*[@data-id='selectedVersion' and contains(.,'0.8.16+commit.07a7930e')]",
locateStrategy: 'xpath'
})
.assert.containsText('#evmVersionSelector option[data-id="selected"]', 'istanbul')
.assert.containsText('#compilierLanguageSelector option[data-id="selected"]', 'Yul')
.verify.elementPresent('#optimize:checked')
@ -229,14 +231,18 @@ module.exports = {
.refreshPage()
.clickLaunchIcon('solidity')
.waitForElementVisible('#versionSelector option[data-id="selected"]')
.assert.containsText('#versionSelector option[data-id="selected"]', '0.8.7+commit.e28d00a7')
.waitForElementVisible({
selector: "//*[@data-id='selectedVersion' and contains(.,'0.8.7+commit.e28d00a7')]",
locateStrategy: 'xpath'
})
.url('http://127.0.0.1:8080/#version=0.8.15+commit.e14f2714')
.refreshPage()
.pause(3000)
.clickLaunchIcon('solidity')
.waitForElementVisible('#versionSelector option[data-id="selected"]')
.assert.containsText('#versionSelector option[data-id="selected"]', '0.8.15+commit.e14f2714')
.waitForElementVisible({
selector: "//*[@data-id='selectedVersion' and contains(.,'0.8.15+commit.e14f2714')]",
locateStrategy: 'xpath'
})
},
'Should load using compiler from link passed in remix URL #group3': function (browser: NightwatchBrowser) {
@ -247,7 +253,10 @@ module.exports = {
.clickLaunchIcon('solidity')
.click('*[data-id="scConfigExpander"]')
.assert.containsText('#versionSelector option[data-id="selected"]', 'custom')
.waitForElementVisible({
selector: "//*[@data-id='selectedVersion' and contains(.,'custom')]",
locateStrategy: 'xpath'
})
// default values
.assert.containsText('#evmVersionSelector option[data-id="selected"]', 'default')
.verify.elementPresent('#optimize')
@ -261,12 +270,8 @@ module.exports = {
browser
.url('http://127.0.0.1:8080/#optimize=false&runs=200&url=https://raw.githubusercontent.com/EthVM/evm-source-verification/main/contracts/1/0x011e5846975c6463a8c6337eecf3cbf64e328884/input.json')
.refreshPage()
.switchWorkspace('code-sample')
.openFile('@openzeppelin')
.openFile('@openzeppelin/contracts')
.openFile('@openzeppelin/contracts/access')
.openFile('@openzeppelin/contracts/access/AccessControl.sol')
.currentWorkspaceIs('code-sample')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItem@openzeppelin/contracts/access/AccessControl.sol"]')
.openFile('contracts')
.openFile('contracts/governance')
.openFile('contracts/governance/UnionGovernor.sol')

@ -555,6 +555,9 @@ module.exports = {
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('[data-id="PermissionHandler-modal-footer-ok-react"]', 300000)
.click('[data-id="PermissionHandler-modal-footer-ok-react"]')
// click on lib to close it
.waitForElementVisible('*[data-id="treeViewLitreeViewItemlib"]')
.click('*[data-id="treeViewLitreeViewItemlib"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc/MULTI_SIG"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc/MULTI_SIG/MultiSigSwapHook.sol"]')

@ -226,9 +226,8 @@ module.exports = {
'Should prevent checkout to a branch if local changes exists #group3': function (browser: NightwatchBrowser) {
browser
.renamePath('README.md', 'README.txt', 'README.txt')
.renamePath('README.md', 'README_2', 'README_2.md')
.waitForElementVisible('[data-id="workspaceGitBranchesDropdown"]')
.click('[data-id="workspaceGitBranchesDropdown"]')
.waitForElementVisible('[data-id="workspaceGit-dev"]')
.click('[data-id="workspaceGit-dev"]')
.waitForElementVisible('[data-id="switchBranchModalDialogContainer-react"]')

@ -56,7 +56,7 @@ declare module 'nightwatch' {
journalLastChild(val: string): NightwatchBrowser
checkTerminalFilter(filter: string, test: string): NightwatchBrowser
noWorkerErrorFor(version: string): NightwatchBrowser
validateValueInput(selector: string, valueTosSet: string, expectedValue: string): NightwatchBrowser
validateValueInput(selector: string, valueTosSet: string[], expectedValue: string): NightwatchBrowser
checkAnnotations(type: string): NightwatchBrowser
checkAnnotationsNotPresent(type: string): NightwatchBrowser
getLastTransactionHash(callback: (hash: string) => void)
@ -71,6 +71,8 @@ declare module 'nightwatch' {
switchEnvironment: (provider: string) => NightwatchBrowser
connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser
waitForElementNotContainsText: (id: string, value: string, timeout: number = 10000) => NightwatchBrowser
hideToolTips: (this: NightwatchBrowser) => NightwatchBrowser
enableClipBoard: () => NightwatchBrowser
}
export interface NightwatchBrowser {

@ -27,6 +27,7 @@ if [ ! -d "./apps/remix-ide/src/assets/js/soljson" ]; then
mkdir ./apps/remix-ide/src/assets/js/soljson
fi
cp ./apps/remix-ide/src/assets/js/soljson.js ./apps/remix-ide/src/assets/js/soljson/$url
cp list.json ./apps/remix-ide/src/assets/list.json
# remove list.json
rm list.json

@ -39,6 +39,13 @@
"generateIndexHtml": true,
"extractCss": false,
"vendorChunk": false
},
"desktop": {
"optimization": false,
"generateIndexHtml": true,
"extractCss": false,
"vendorChunk": false,
"baseHref": "./"
}
}
},

@ -22,7 +22,7 @@ import {WalkthroughService} from './walkthroughService'
import {OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler} from '@remix-project/core-plugin'
import Registry from './app/state/registry'
import {Registry} from '@remix-project/remix-lib'
import {ConfigPlugin} from './app/plugins/config'
import {StoragePlugin} from './app/plugins/storage'
import {Layout} from './app/panels/layout'
@ -43,20 +43,30 @@ import {Injected0ptimismProvider} from './app/providers/injected-optimism-provid
import {InjectedArbitrumOneProvider} from './app/providers/injected-arbitrum-one-provider'
import {InjectedEphemeryTestnetProvider} from './app/providers/injected-ephemery-testnet-provider'
import {InjectedSKALEChaosTestnetProvider} from './app/providers/injected-skale-chaos-testnet-provider'
import {FileDecorator} from './app/plugins/file-decorator'
import {CodeFormat} from './app/plugins/code-format'
import {SolidityUmlGen} from './app/plugins/solidity-umlgen'
import { FileDecorator } from './app/plugins/file-decorator'
import { CodeFormat } from './app/plugins/code-format'
import { SolidityUmlGen } from './app/plugins/solidity-umlgen'
import { CompilationDetailsPlugin } from './app/plugins/compile-details'
import { VyperCompilationDetailsPlugin } from './app/plugins/vyper-compilation-details'
import {ContractFlattener} from './app/plugins/contractFlattener'
import { ContractFlattener } from './app/plugins/contractFlattener'
import { TemplatesPlugin } from './app/plugins/remix-templates'
import { fsPlugin } from './app/plugins/electron/fsPlugin'
import { isoGitPlugin } from './app/plugins/electron/isoGitPlugin'
import { electronConfig } from './app/plugins/electron/electronConfigPlugin'
import { electronTemplates } from './app/plugins/electron/templatesPlugin'
import { xtermPlugin } from './app/plugins/electron/xtermPlugin'
import { ripgrepPlugin } from './app/plugins/electron/ripgrepPlugin'
import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins/electron/compilerLoaderPlugin'
import {OpenAIGpt} from './app/plugins/openaigpt'
const isElectron = require('is-electron')
const remixLib = require('@remix-project/remix-lib')
import {QueryParams} from '@remix-project/remix-lib'
import {SearchPlugin} from './app/tabs/search'
import { QueryParams } from '@remix-project/remix-lib'
import { SearchPlugin } from './app/tabs/search'
import { ElectronProvider } from './app/files/electronProvider'
import { CopilotSuggestion } from './app/plugins/copilot/suggestion-service/copilot-suggestion'
const Storage = remixLib.Storage
@ -64,7 +74,8 @@ const RemixDProvider = require('./app/files/remixDProvider')
const Config = require('./config')
const FileManager = require('./app/files/fileManager')
const FileProvider = require('./app/files/fileProvider')
import FileProvider from "./app/files/fileProvider"
import { appPlatformTypes } from '@remix-ui/app'
const DGitProvider = require('./app/files/dgitProvider')
const WorkspaceFileProvider = require('./app/files/workspaceFileProvider')
@ -80,8 +91,23 @@ const Editor = require('./app/editor/editor')
const Terminal = require('./app/panels/terminal')
const {TabProxy} = require('./app/panels/tab-proxy.js')
export class platformApi {
get name () {
return isElectron() ? appPlatformTypes.desktop : appPlatformTypes.web
}
isDesktop () {
return isElectron()
}
}
class AppComponent {
constructor() {
const PlatFormAPi = new platformApi()
Registry.getInstance().put({
api: PlatFormAPi,
name: 'platform'
})
this.appManager = new RemixAppManager({})
this.queryParams = new QueryParams()
this._components = {}
@ -110,10 +136,18 @@ class AppComponent {
name: 'fileproviders/workspace'
})
this._components.filesProviders.electron = new ElectronProvider(this.appManager)
Registry.getInstance().put({
api: this._components.filesProviders.electron,
name: 'fileproviders/electron'
})
Registry.getInstance().put({
api: this._components.filesProviders,
name: 'fileproviders'
})
}
async run() {
@ -138,6 +172,8 @@ class AppComponent {
this.walkthroughService = new WalkthroughService(appManager)
this.platform = isElectron() ? 'desktop' : 'web'
const hosts = ['127.0.0.1:8080', '192.168.0.101:8080', 'localhost:8080']
// workaround for Electron support
if (!isElectron() && !hosts.includes(window.location.host)) {
@ -183,6 +219,9 @@ class AppComponent {
//----- search
const search = new SearchPlugin()
//---- templates
const templates = new TemplatesPlugin()
//---------------- Solidity UML Generator -------------------------
const solidityumlgen = new SolidityUmlGen(appManager)
@ -268,6 +307,7 @@ class AppComponent {
const permissionHandler = new PermissionHandlerPlugin()
this.engine.register([
permissionHandler,
this.layout,
@ -318,10 +358,30 @@ class AppComponent {
vyperCompilationDetails,
contractFlattener,
solidityScript,
templates,
openaigpt,
copilotSuggestion
])
//---- fs plugin
if (isElectron()) {
const FSPlugin = new fsPlugin()
this.engine.register([FSPlugin])
const isoGit = new isoGitPlugin()
this.engine.register([isoGit])
const electronConfigPlugin = new electronConfig()
this.engine.register([electronConfigPlugin])
const templatesPlugin = new electronTemplates()
this.engine.register([templatesPlugin])
const xterm = new xtermPlugin()
this.engine.register([xterm])
const ripgrep = new ripgrepPlugin()
this.engine.register([ripgrep])
}
const compilerloader = isElectron()? new compilerLoaderPluginDesktop(): new compilerLoaderPlugin()
this.engine.register([compilerloader])
// LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel()
Registry.getInstance().put({api: this.mainview, name: 'mainview'})
@ -400,6 +460,9 @@ class AppComponent {
} catch (e) {
console.log("couldn't register iframe plugins", e.message)
}
if (isElectron()){
await this.appManager.activatePlugin(['fs'])
}
await this.appManager.activatePlugin(['layout'])
await this.appManager.activatePlugin(['notification'])
await this.appManager.activatePlugin(['editor'])
@ -428,19 +491,31 @@ class AppComponent {
'blockchain',
'fetchAndCompile',
'contentImport',
'gistHandler'
'gistHandler',
'compilerloader'
])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder'])
await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
if (isElectron()){
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep'])
}
this.appManager.on(
'filePanel',
'workspaceInitializationCompleted',
async () => {
// for e2e tests
const loadedElement = document.createElement('span')
loadedElement.setAttribute('data-id', 'workspaceloaded')
document.body.appendChild(loadedElement)
await this.appManager.registerContextMenuItems()
}
)
await this.appManager.activatePlugin(['solidity-script', 'openaigpt'])
this.appManager.on('filePanel', 'workspaceInitializationCompleted', async () => {
// for e2e tests
const loadedElement = document.createElement('span')
loadedElement.setAttribute('data-id', 'workspaceloaded')
document.body.appendChild(loadedElement)
await this.appManager.registerContextMenuItems()
})
await this.appManager.activatePlugin(['filePanel'])
// Set workspace after initial activation

@ -1,16 +1,17 @@
import {RemixApp} from '@remix-ui/app'
import axios from 'axios'
import React, {useEffect, useRef, useState} from 'react'
import {render} from 'react-dom'
import { createRoot } from 'react-dom/client'
import * as packageJson from '../../../../../package.json'
import {fileSystem, fileSystems} from '../files/fileSystem'
import {indexedDBFileSystem} from '../files/filesystems/indexedDB'
import {localStorageFS} from '../files/filesystems/localStorage'
import {fileSystemUtility, migrationTestData} from '../files/filesystems/fileSystemUtility'
import './styles/preload.css'
import isElectron from 'is-electron'
const _paq = (window._paq = window._paq || [])
export const Preload = () => {
export const Preload = (props: any) => {
const [tip, setTip] = useState<string>('')
const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false)
@ -34,12 +35,7 @@ export const Preload = () => {
.then((AppComponent) => {
const appComponent = new AppComponent.default()
appComponent.run().then(() => {
render(
<>
<RemixApp app={appComponent} />
</>,
document.getElementById('root')
)
props.root.render(<RemixApp app={appComponent} />)
})
})
.catch((err) => {
@ -86,7 +82,11 @@ export const Preload = () => {
}
}
useEffect(() => {
useEffect (() => {
if(isElectron()){
loadAppComponent()
return
}
async function loadStorage() {
;(await remixFileSystems.current.addFileSystem(remixIndexedDB.current)) || _paq.push(['trackEvent', 'Storage', 'error', 'indexedDB not supported'])
;(await remixFileSystems.current.addFileSystem(localStorageFileSystem.current)) || _paq.push(['trackEvent', 'Storage', 'error', 'localstorage not supported'])

@ -402,10 +402,7 @@ class Editor extends Plugin {
*/
editorFontSize (incr) {
if (!this.activated) return
const newSize = this.api.getFontSize() + incr
if (newSize >= 6) {
this.emit('setFontSize', newSize)
}
this.emit('setFontSize', incr)
}
/**

@ -10,10 +10,11 @@ import {
} from 'file-saver'
import http from 'isomorphic-git/http/web'
const JSZip = require('jszip')
const path = require('path')
const FormData = require('form-data')
const axios = require('axios')
import JSZip from 'jszip'
import path from 'path'
import FormData from 'form-data'
import axios from 'axios'
import {Registry} from '@remix-project/remix-lib'
const profile = {
name: 'dGitProvider',
@ -21,11 +22,17 @@ const profile = {
description: 'Decentralized git provider',
icon: 'assets/img/fileManager.webp',
version: '0.0.1',
methods: ['init', 'localStorageUsed', 'addremote', 'delremote', 'remotes', 'fetch', 'clone', 'export', 'import', 'status', 'log', 'commit', 'add', 'remove', 'rm', 'lsfiles', 'readblob', 'resolveref', 'branches', 'branch', 'checkout', 'currentbranch', 'push', 'pin', 'pull', 'pinList', 'unPin', 'setIpfsConfig', 'zip', 'setItem', 'getItem', 'updateSubmodules'],
methods: ['init', 'localStorageUsed', 'addremote', 'delremote', 'remotes', 'fetch', 'clone', 'export', 'import', 'status', 'log', 'commit', 'add', 'remove', 'reset', 'rm', 'lsfiles', 'readblob', 'resolveref', 'branches', 'branch', 'checkout', 'currentbranch', 'push', 'pin', 'pull', 'pinList', 'unPin', 'setIpfsConfig', 'zip', 'setItem', 'getItem', 'version', 'updateSubmodules'],
kind: 'file-system'
}
class DGitProvider extends Plugin {
constructor () {
ipfsconfig: { host: string; port: number; protocol: string; ipfsurl: string }
globalIPFSConfig: { host: string; port: number; protocol: string; ipfsurl: string }
remixIPFS: { host: string; port: number; protocol: string; ipfsurl: string }
ipfsSources: any[]
ipfs: any
filesToSend: any[]
constructor() {
super(profile)
this.ipfsconfig = {
host: 'jqgt.remixproject.org',
@ -48,7 +55,15 @@ class DGitProvider extends Plugin {
this.ipfsSources = [this.remixIPFS, this.globalIPFSConfig, this.ipfsconfig]
}
async getGitConfig (dir = '') {
async getGitConfig(dir = '') {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
return {
fs: window.remixFileSystem,
dir: '/'
}
}
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
if (!workspace) return
@ -58,7 +73,7 @@ class DGitProvider extends Plugin {
}
}
async parseInput (input) {
async parseInput(input) {
return {
corsProxy: 'https://corsproxy.remixproject.org/',
http,
@ -73,7 +88,15 @@ class DGitProvider extends Plugin {
}
}
async init (input) {
async init(input?) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
await this.call('isogit', 'init', {
defaultBranch: (input && input.branch) || 'main'
})
this.emit('init')
return
}
await git.init({
...await this.getGitConfig(),
defaultBranch: (input && input.branch) || 'main'
@ -81,74 +104,140 @@ class DGitProvider extends Plugin {
this.emit('init')
}
async status (cmd) {
async version() {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
return await this.call('isogit', 'version')
}
const version = 'built-in'
return version
}
async status(cmd) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
const status = await this.call('isogit', 'status', cmd)
return status
}
const status = await git.statusMatrix({
...await this.getGitConfig(),
...cmd
})
return status
}
async add (cmd) {
await git.add({
...await this.getGitConfig(),
...cmd
})
async add(cmd) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
await this.call('isogit', 'add', cmd)
} else {
await git.add({
...await this.getGitConfig(),
...cmd
})
}
this.emit('add')
}
async rm (cmd) {
await git.remove({
...await this.getGitConfig(),
...cmd
})
async rm(cmd) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
await this.call('isogit', 'rm', cmd)
} else {
await git.remove({
...await this.getGitConfig(),
...cmd
})
this.emit('rm')
}
}
async checkout (cmd, refresh = true) {
const gitmodules = await this.parseGitmodules() || []
await git.checkout({
...await this.getGitConfig(),
...cmd
})
const newgitmodules = await this.parseGitmodules() || []
// find the difference between the two gitmodule versions
const toRemove = gitmodules.filter((module) => {
return !newgitmodules.find((newmodule) => {
return newmodule.name === module.name
async reset(cmd) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
await this.call('isogit', 'reset', cmd)
} else {
await git.resetIndex({
...await this.getGitConfig(),
...cmd
})
})
this.emit('rm')
for (const module of toRemove) {
const path = (await this.getGitConfig(module.path)).dir
if (await window.remixFileSystem.exists(path)) {
const stat = await window.remixFileSystem.stat(path)
try {
if (stat.isDirectory()) {
await window.remixFileSystem.unlink((await this.getGitConfig(module.path)).dir)
}
}
async checkout(cmd, refresh = true) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
await this.call('isogit', 'checkout', cmd)
} else {
const gitmodules = await this.parseGitmodules() || []
await git.checkout({
...await this.getGitConfig(),
...cmd
})
const newgitmodules = await this.parseGitmodules() || []
// find the difference between the two gitmodule versions
const toRemove = gitmodules.filter((module) => {
return !newgitmodules.find((newmodule) => {
return newmodule.name === module.name
})
})
for (const module of toRemove) {
const path = (await this.getGitConfig(module.path)).dir
if (await window.remixFileSystem.exists(path)) {
const stat = await window.remixFileSystem.stat(path)
try {
if (stat.isDirectory()) {
await window.remixFileSystem.unlink((await this.getGitConfig(module.path)).dir)
}
} catch (e) {
// do nothing
}
} catch (e) {
// do nothing
}
}
}
if (refresh)
if (refresh) {
setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
}
this.emit('checkout')
}
async log (cmd) {
async log(cmd) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
const status = await this.call('isogit', 'log', {
...cmd,
depth: 10
})
return status
}
const status = await git.log({
...await this.getGitConfig(),
...cmd
...cmd,
depth: 10
})
return status
}
async remotes (config) {
async remotes(config) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
return await this.call('isogit', 'remotes', config)
}
let remotes = []
try {
remotes = await git.listRemotes({ ...config ? config : await this.getGitConfig() })
@ -158,11 +247,17 @@ class DGitProvider extends Plugin {
return remotes
}
async branch (cmd, refresh = true) {
const status = await git.branch({
...await this.getGitConfig(),
...cmd
})
async branch(cmd, refresh = true) {
let status
if ((Registry.getInstance().get('platform').api.isDesktop())) {
status = await this.call('isogit', 'branch', cmd)
} else {
status = await git.branch({
...await this.getGitConfig(),
...cmd
})
}
if (refresh) {
setTimeout(async () => {
await this.call('fileManager', 'refresh')
@ -172,7 +267,14 @@ class DGitProvider extends Plugin {
return status
}
async currentbranch (config) {
async currentbranch(config) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
return await this.call('isogit', 'currentbranch')
}
try {
const defaultConfig = await this.getGitConfig()
const cmd = config ? defaultConfig ? { ...defaultConfig, ...config } : config : defaultConfig
@ -184,7 +286,12 @@ class DGitProvider extends Plugin {
}
}
async branches (config) {
async branches(config) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
return await this.call('isogit', 'branches')
}
try {
const defaultConfig = await this.getGitConfig()
const cmd = config ? defaultConfig ? { ...defaultConfig, ...config } : config : defaultConfig
@ -202,21 +309,39 @@ class DGitProvider extends Plugin {
}
}
async commit (cmd) {
await this.init()
try {
const sha = await git.commit({
...await this.getGitConfig(),
...cmd
})
this.emit('commit')
return sha
} catch (e) {
throw new Error(e)
async commit(cmd) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
try {
await this.call('isogit', 'init')
const sha = await this.call('isogit', 'commit', cmd)
this.emit('commit')
return sha
} catch (e) {
throw new Error(e)
}
} else {
await this.init()
try {
const sha = await git.commit({
...await this.getGitConfig(),
...cmd
})
this.emit('commit')
return sha
} catch (e) {
throw new Error(e)
}
}
}
async lsfiles (cmd) {
async lsfiles(cmd) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
return await this.call('isogit', 'lsfiles', cmd)
}
const filesInStaging = await git.listFiles({
...await this.getGitConfig(),
...cmd
@ -224,7 +349,12 @@ class DGitProvider extends Plugin {
return filesInStaging
}
async resolveref (cmd) {
async resolveref(cmd) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
return await this.call('isogit', 'resolveref', cmd)
}
const oid = await git.resolveRef({
...await this.getGitConfig(),
...cmd
@ -232,22 +362,27 @@ class DGitProvider extends Plugin {
return oid
}
async readblob (cmd) {
async readblob(cmd) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
const readBlobResult = await this.call('isogit', 'readblob', cmd)
return readBlobResult
}
const readBlobResult = await git.readBlob({
...await this.getGitConfig(),
...cmd
})
return readBlobResult
}
async setIpfsConfig (config) {
async setIpfsConfig(config) {
this.ipfsconfig = config
return new Promise((resolve) => {
resolve(this.checkIpfsConfig())
})
}
async checkIpfsConfig (config) {
async checkIpfsConfig(config?) {
this.ipfs = IpfsHttpClient(config || this.ipfsconfig)
try {
await this.ipfs.config.getAll()
@ -257,40 +392,73 @@ class DGitProvider extends Plugin {
}
}
async addremote (input) {
async addremote(input) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
await this.call('isogit', 'addremote', { url: input.url, remote: input.remote })
return
}
await git.addRemote({ ...await this.getGitConfig(), url: input.url, remote: input.remote })
}
async delremote (input) {
async delremote(input) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
await this.call('isogit', 'delremote', { remote: input.remote })
return
}
await git.deleteRemote({ ...await this.getGitConfig(), remote: input.remote })
}
async localStorageUsed () {
async localStorageUsed() {
return this.calculateLocalStorage()
}
async clone (input, workspaceName, workspaceExists = false) {
const permission = await this.askUserPermission('clone', 'Import multiple files into your workspaces.')
if (!permission) return false
if (this.calculateLocalStorage() > 10000) throw new Error('The local storage of the browser is full.')
if (!workspaceExists) await this.call('filePanel', 'createWorkspace', workspaceName || `workspace_${Date.now()}`, true)
const cmd = {
url: input.url,
singleBranch: input.singleBranch,
ref: input.branch,
depth: input.depth || 10,
...await this.parseInput(input),
...await this.getGitConfig()
}
this.call('terminal', 'logHtml', `Cloning ${input.url}...`)
const result = await git.clone(cmd)
if (!workspaceExists) {
setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
async clone(input, workspaceName, workspaceExists = false) {
if ((Registry.getInstance().get('platform').api.isDesktop())) {
const folder = await this.call('fs', 'selectFolder', null, 'Select or create a folder to clone the repository in', 'Select as Repository Destination')
if (!folder) return false
const cmd = {
url: input.url,
singleBranch: input.singleBranch,
ref: input.branch,
depth: input.depth || 10,
dir: folder,
input
}
this.call('terminal', 'logHtml', `Cloning ${input.url}... please wait...`)
try{
const result = await this.call('isogit', 'clone', cmd)
this.call('fs', 'openWindow', folder)
return result
}catch(e){
this.call('notification', 'alert', {
id: 'dgitAlert',
message: 'Unexpected error while cloning the repository: \n' + e.toString(),
})
}
} else {
const permission = await this.askUserPermission('clone', 'Import multiple files into your workspaces.')
if (!permission) return false
if (parseFloat(this.calculateLocalStorage()) > 10000) throw new Error('The local storage of the browser is full.')
if (!workspaceExists) await this.call('filePanel', 'createWorkspace', workspaceName || `workspace_${Date.now()}`, true)
const cmd = {
url: input.url,
singleBranch: input.singleBranch,
ref: input.branch,
depth: input.depth || 10,
...await this.parseInput(input),
...await this.getGitConfig()
}
this.call('terminal', 'logHtml', `Cloning ${input.url}...`)
const result = await git.clone(cmd)
if (!workspaceExists) {
setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
}
this.emit('clone')
return result
}
this.emit('clone')
return result
}
async parseGitmodules (dir = '') {
@ -298,8 +466,8 @@ class DGitProvider extends Plugin {
const gitmodules = await this.call('fileManager', 'readFile', path.join(dir, '.gitmodules'))
if (gitmodules) {
const lines = gitmodules.split('\n')
let currentModule = {}
let modules = []
let currentModule:any = {}
const modules = []
for (let line of lines) {
line = line.trim()
if (line.startsWith('[')) {
@ -331,7 +499,7 @@ class DGitProvider extends Plugin {
this.call('terminal', 'logHtml', `Found ${(gitmodules && gitmodules.length) || 0} submodules in ${currentDir || '/'}`)
//parse gitmodules
if (gitmodules) {
for (let module of gitmodules) {
for (const module of gitmodules) {
const dir = path.join(currentDir, module.path)
const targetPath = (await this.getGitConfig(dir)).dir
if (await window.remixFileSystem.exists(targetPath)) {
@ -345,7 +513,7 @@ class DGitProvider extends Plugin {
}
}
}
for (let module of gitmodules) {
for (const module of gitmodules) {
const dir = path.join(currentDir, module.path)
// if url contains git@github.com: convert it
if(module.url && module.url.startsWith('git@github.com:')) {
@ -434,13 +602,25 @@ class DGitProvider extends Plugin {
name: input.name,
email: input.email
},
...await this.parseInput(input),
...await this.getGitConfig()
input,
}
if ((Registry.getInstance().get('platform').api.isDesktop())) {
return await this.call('isogit', 'push', cmd)
} else {
const cmd2 = {
...cmd,
...await this.parseInput(input),
}
return await git.push({
...await this.getGitConfig(),
...cmd2
})
}
return await git.push(cmd)
}
async pull (input) {
async pull(input) {
const cmd = {
ref: input.ref,
remoteRef: input.remoteRef,
@ -449,17 +629,29 @@ class DGitProvider extends Plugin {
email: input.email
},
remote: input.remote,
...await this.parseInput(input),
...await this.getGitConfig()
input,
}
let result
if ((Registry.getInstance().get('platform').api.isDesktop())) {
result = await this.call('isogit', 'pull', cmd)
}
else {
const cmd2 = {
...cmd,
...await this.parseInput(input),
}
result = await git.pull({
...await this.getGitConfig(),
...cmd2
})
}
const result = await git.pull(cmd)
setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
return result
}
async fetch (input) {
async fetch(input) {
const cmd = {
ref: input.ref,
remoteRef: input.remoteRef,
@ -468,17 +660,28 @@ class DGitProvider extends Plugin {
email: input.email
},
remote: input.remote,
...await this.parseInput(input),
...await this.getGitConfig()
input
}
let result
if ((Registry.getInstance().get('platform').api.isDesktop())) {
result = await this.call('isogit', 'fetch', cmd)
} else {
const cmd2 = {
...cmd,
...await this.parseInput(input),
}
result = await git.fetch({
...await this.getGitConfig(),
...cmd2
})
}
const result = await git.fetch(cmd)
setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
return result
}
async export (config) {
async export(config) {
if (!this.checkIpfsConfig(config)) return false
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const files = await this.getDirectory('/')
@ -498,7 +701,7 @@ class DGitProvider extends Plugin {
return r.cid.string
}
async pin (pinataApiKey, pinataSecretApiKey) {
async pin(pinataApiKey, pinataSecretApiKey) {
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const files = await this.getDirectory('/')
this.filesToSend = []
@ -551,21 +754,21 @@ class DGitProvider extends Plugin {
.post(url, data, {
maxBodyLength: 'Infinity',
headers: {
'Content-Type': `multipart/form-data; boundary=${data._boundary}`,
'Content-Type': `multipart/form-data; boundary=${(data as any)._boundary}`,
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey
}
}).catch((e) => {
} as any).catch((e) => {
console.log(e)
})
// also commit to remix IPFS for availability after pinning to Pinata
return await this.export(this.remixIPFS) || result.data.IpfsHash
return await this.export(this.remixIPFS) || (result as any).data.IpfsHash
} catch (error) {
throw new Error(error)
}
}
async pinList (pinataApiKey, pinataSecretApiKey) {
async pinList(pinataApiKey, pinataSecretApiKey) {
const url = 'https://api.pinata.cloud/data/pinList?status=pinned'
try {
const result = await axios
@ -575,16 +778,16 @@ class DGitProvider extends Plugin {
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey
}
}).catch((e) => {
} as any).catch((e) => {
console.log('Pinata unreachable')
})
return result.data
return (result as any).data
} catch (error) {
throw new Error(error)
}
}
async unPin (pinataApiKey, pinataSecretApiKey, hashToUnpin) {
async unPin(pinataApiKey, pinataSecretApiKey, hashToUnpin) {
const url = `https://api.pinata.cloud/pinning/unpin/${hashToUnpin}`
try {
await axios
@ -600,7 +803,7 @@ class DGitProvider extends Plugin {
}
}
async importIPFSFiles (config, cid, workspace) {
async importIPFSFiles(config, cid, workspace) {
const ipfs = IpfsHttpClient(config)
let result = false
try {
@ -629,9 +832,9 @@ class DGitProvider extends Plugin {
return result
}
calculateLocalStorage () {
var _lsTotal = 0
var _xLen; var _x
calculateLocalStorage() {
let _lsTotal = 0
let _xLen; let _x
for (_x in localStorage) {
// eslint-disable-next-line no-prototype-builtins
if (!localStorage.hasOwnProperty(_x)) {
@ -643,10 +846,10 @@ class DGitProvider extends Plugin {
return (_lsTotal / 1024).toFixed(2)
}
async import (cmd) {
async import(cmd) {
const permission = await this.askUserPermission('import', 'Import multiple files into your workspaces.')
if (!permission) return false
if (this.calculateLocalStorage() > 10000) throw new Error('The local storage of the browser is full.')
if (parseFloat(this.calculateLocalStorage()) > 10000) throw new Error('The local storage of the browser is full.')
const cid = cmd.cid
await this.call('filePanel', 'createWorkspace', `workspace_${Date.now()}`, true)
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
@ -662,13 +865,13 @@ class DGitProvider extends Plugin {
if (!result) throw new Error(`Cannot pull files from IPFS at ${cid}`)
}
async getItem (name) {
async getItem(name) {
if (typeof window !== 'undefined') {
return window.localStorage.getItem(name)
}
}
async setItem (name, content) {
async setItem(name, content) {
try {
if (typeof window !== 'undefined') {
window.localStorage.setItem(name, content)
@ -680,7 +883,7 @@ class DGitProvider extends Plugin {
return true
}
async zip () {
async zip() {
const zip = new JSZip()
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const files = await this.getDirectory('/')
@ -697,7 +900,7 @@ class DGitProvider extends Plugin {
})
}
async createDirectories (strdirectories) {
async createDirectories(strdirectories) {
const ignore = ['.', '/.', '']
if (ignore.indexOf(strdirectories) > -1) return false
const directories = strdirectories.split('/')
@ -715,7 +918,7 @@ class DGitProvider extends Plugin {
}
}
async getDirectory (dir) {
async getDirectory(dir) {
let result = []
const files = await this.call('fileManager', 'readdir', dir)
const fileArray = normalize(files)
@ -739,7 +942,7 @@ class DGitProvider extends Plugin {
}
const addSlash = (file) => {
if (!file.startsWith('/'))file = '/' + file
if (!file.startsWith('/')) file = '/' + file
return file
}

@ -0,0 +1,92 @@
import FileProvider from "./fileProvider"
declare global {
interface Window {
remixFileSystem: any
}
}
export class ElectronProvider extends FileProvider {
_appManager: any
constructor(appManager) {
super('')
this._appManager = appManager
}
async init() {
this._appManager.on('fs', 'change', async (event, path) => {
this.handleEvent(event, path)
})
this._appManager.on('fs', 'eventGroup', async (data) => {
for (const event of data) {
this.handleEvent(event.payload[0], event.payload[1])
}
})
}
handleEvent = (event, path) => {
switch (event) {
case 'add':
this.event.emit('fileAdded', path)
break
case 'unlink':
this.event.emit('fileRemoved', path)
break
case 'change':
this.get(path, (_error, content) => {
this.event.emit('fileExternallyChanged', path, content, false)
})
break
case 'rename':
this.event.emit('fileRenamed', path)
break
case 'addDir':
this.event.emit('folderAdded', path)
break
case 'unlinkDir':
this.event.emit('fileRemoved', path)
}
}
// isDirectory is already included
// this is a more efficient version of the default implementation
async resolveDirectory(path, cb) {
path = this.removePrefix(path)
if (path.indexOf('/') !== 0) path = '/' + path
try {
const files = await window.remixFileSystem.readdir(path)
const ret = {}
if (files) {
for (const element of files) {
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
const file = element.file.replace(/^\/|\/$/g, '') // remove first and last slash
const absPath = (path === '/' ? '' : path) + '/' + file
ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: element.isDirectory }
// ^ ret does not accept path starting with '/'
}
}
if (cb) cb(null, ret)
return ret
} catch (error) {
if (cb) cb(error, null)
}
}
/**
* Removes the folder recursively
* @param {*} path is the folder to be removed
*/
async remove(path: string) {
try {
await window.remixFileSystem.rmdir(path)
return true
} catch (error) {
console.log(error)
return false
}
}
}

@ -4,8 +4,7 @@ import { saveAs } from 'file-saver'
import JSZip from 'jszip'
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry'
import { EventEmitter } from 'events'
import {Registry} from '@remix-project/remix-lib'
import { fileChangedToastMsg, recursivePasteToastMsg, storageFullMessage } from '@remix-ui/helper'
import helper from '../../lib/helper.js'
import { RemixAppManager } from '../../remixAppManager'
@ -42,7 +41,6 @@ const createError = (err) => {
class FileManager extends Plugin {
mode: string
openedFiles: any
events: EventEmitter
editor: any
_components: any
appManager: RemixAppManager
@ -56,7 +54,6 @@ class FileManager extends Plugin {
super(profile)
this.mode = 'browser'
this.openedFiles = {} // list all opened files
this.events = new EventEmitter()
this.editor = editor
this._components = {}
this._components.registry = Registry.getInstance()
@ -155,8 +152,12 @@ class FileManager extends Plugin {
refresh() {
const provider = this.fileProviderOf('/')
// emit rootFolderChanged so that File Explorer reloads the file tree
provider.event.emit('rootFolderChanged', provider.workspace || '/')
this.emit('rootFolderChanged', provider.workspace || '/')
if (Registry.getInstance().get('platform').api.isDesktop()) {
provider.event.emit('refresh')
} else {
provider.event.emit('rootFolderChanged', provider.workspace || '/')
this.emit('rootFolderChanged', provider.workspace || '/')
}
}
/**
@ -466,14 +467,13 @@ class FileManager extends Plugin {
return new Promise((resolve, reject) => {
const provider = this.fileProviderOf(path)
provider.resolveDirectory(path, (error, filesProvider) => {
if (error) reject(error)
resolve(filesProvider)
})
})
} catch (e) {
throw new Error(e)
return {}
}
}
@ -500,7 +500,8 @@ class FileManager extends Plugin {
browserExplorer: this._components.registry.get('fileproviders/browser').api,
localhostExplorer: this._components.registry.get('fileproviders/localhost').api,
workspaceExplorer: this._components.registry.get('fileproviders/workspace').api,
filesProviders: this._components.registry.get('fileproviders').api
filesProviders: this._components.registry.get('fileproviders').api,
electronExplorer: this._components.registry.get('fileproviders/electron').api,
}
this._deps.config.set('currentFile', '') // make sure we remove the current file from the previous session
@ -518,6 +519,11 @@ class FileManager extends Plugin {
this._deps.workspaceExplorer.event.on('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this._deps.workspaceExplorer.event.on('fileAdded', (path) => { this.fileAddedEvent(path) })
this._deps.electronExplorer.event.on('fileChanged', (path) => { this.fileChangedEvent(path) })
this._deps.electronExplorer.event.on('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
this._deps.electronExplorer.event.on('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this._deps.electronExplorer.event.on('fileAdded', (path) => { this.fileAddedEvent(path) })
this.getCurrentFile = this.file
this.getFile = this.readFile
this.getFolder = this.readdir
@ -554,9 +560,7 @@ class FileManager extends Plugin {
}
}
}
// TODO: Only keep `this.emit` (issue#2210)
this.emit('fileRenamed', oldName, newName, isFolder)
this.events.emit('fileRenamed', oldName, newName, isFolder)
}
currentFileProvider() {
@ -572,9 +576,7 @@ class FileManager extends Plugin {
}
async closeAllFiles() {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('filesAllClosed')
this.events.emit('filesAllClosed')
for (const file in this.openedFiles) {
await this.closeFile(file)
}
@ -584,13 +586,9 @@ class FileManager extends Plugin {
delete this.openedFiles[name]
if (!Object.keys(this.openedFiles).length) {
this._deps.config.set('currentFile', '')
// TODO: Only keep `this.emit` (issue#2210)
this.emit('noFileSelected')
this.events.emit('noFileSelected')
}
// TODO: Only keep `this.emit` (issue#2210)
this.emit('fileClosed', name)
this.events.emit('fileClosed', name)
}
currentPath() {
@ -610,7 +608,10 @@ class FileManager extends Plugin {
if (!provider) throw createError({ code: 'ENOENT', message: `${path} not available` })
// TODO: change provider to Promise
return new Promise((resolve, reject) => {
if (this.currentFile() === path) return resolve(this.editor.currentContent())
if (this.currentFile() === path) {
const editorContent = this.editor.currentContent()
if (editorContent) resolve(editorContent)
}
provider.get(path, (err, content) => {
if (err) reject(err)
resolve(content)
@ -689,24 +690,21 @@ class FileManager extends Plugin {
}
this.editor.discard(path)
delete this.openedFiles[path]
// TODO: Only keep `this.emit` (issue#2210)
this.emit('fileRemoved', path)
this.events.emit('fileRemoved', path)
this.openFile(this._deps.config.get('currentFile'))
if (path === this._deps.config.get('currentFile')) {
this.openFile(this._deps.config.get('currentFile'))
}
}
async unselectCurrentFile() {
await this.saveCurrentFile()
this._deps.config.set('currentFile', '')
// TODO: Only keep `this.emit` (issue#2210)
this.emit('noFileSelected')
this.events.emit('noFileSelected')
}
async openFile(file?: string) {
if (!file) {
this.emit('noFileSelected')
this.events.emit('noFileSelected')
} else {
file = this.normalize(file)
const resolved = this.getPathFromUrl(file)
@ -738,9 +736,7 @@ class FileManager extends Plugin {
} else {
await this.editor.open(file, content)
}
// TODO: Only keep `this.emit` (issue#2210)
this.emit('currentFileChanged', file)
this.events.emit('currentFileChanged', file)
return true
}
}
@ -779,8 +775,9 @@ class FileManager extends Plugin {
if (file.startsWith('localhost') || this.mode === 'localhost') {
return this._deps.filesProviders.localhost
}
if (file.startsWith('browser')) {
return this._deps.filesProviders.browser
if (Registry.getInstance().get('platform').api.isDesktop()) {
return this._deps.filesProviders.electron
}
return this._deps.filesProviders.workspace
}
@ -904,6 +901,10 @@ class FileManager extends Plugin {
}
currentWorkspace() {
if (Registry.getInstance().get('platform').api.isDesktop()) {
return ''
}
if (this.mode !== 'localhost') {
const file = this.currentFile() || ''
const provider = this.fileProviderOf(file)
@ -980,7 +981,7 @@ class FileManager extends Plugin {
/**
* Moves a file to a new folder
* @param {string} src path of the source file
* @param {string} dest path of the destrination file
* @param {string} dest path of the destination file
* @returns {void}
*/

@ -1,12 +1,17 @@
'use strict'
import { CompilerImports } from '@remix-project/core-plugin'
const EventManager = require('events')
const remixLib = require('@remix-project/remix-lib')
const pathModule = require('path')
const Storage = remixLib.Storage
import EventManager from 'events'
import { Storage } from '@remix-project/remix-lib'
import pathModule from 'path'
class FileProvider {
export default class FileProvider {
event: any
type: any
providerExternalsStorage: any
externalFolders: string[]
reverseKey: string
constructor (name) {
this.event = new EventManager()
this.type = name
@ -79,7 +84,7 @@ class FileProvider {
async _exists (path) {
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
var unprefixedpath = this.removePrefix(path)
const unprefixedpath = this.removePrefix(path)
return path === this.type ? true : await window.remixFileSystem.exists(unprefixedpath)
}
@ -90,7 +95,7 @@ class FileProvider {
async get (path, cb, options = { encoding: 'utf8' }) {
cb = cb || function () { /* do nothing. */ }
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
var unprefixedpath = this.removePrefix(path)
const unprefixedpath = this.removePrefix(path)
try {
const content = await window.remixFileSystem.readFile(unprefixedpath, options)
if (cb) cb(null, content)
@ -103,13 +108,13 @@ class FileProvider {
async set (path, content, cb, options = { encoding: 'utf8' }) {
cb = cb || function () { /* do nothing. */ }
var unprefixedpath = this.removePrefix(path)
const unprefixedpath = this.removePrefix(path)
const exists = await window.remixFileSystem.exists(unprefixedpath)
if (exists && await window.remixFileSystem.readFile(unprefixedpath, options) === content) {
if (cb) cb()
return null
}
await this.createDir(path.substr(0, path.lastIndexOf('/')))
await this.createDir(path.substr(0, path.lastIndexOf('/')), null)
try {
await window.remixFileSystem.writeFile(unprefixedpath, content, options)
} catch (e) {
@ -152,7 +157,7 @@ class FileProvider {
// this will not add a folder as readonly but keep the original url to be able to restore it later
async addExternal (path, content, url) {
if (url) this.addNormalizedName(path, url)
return await this.set(path, content)
return await this.set(path, content, null)
}
isReadOnly (path) {
@ -161,7 +166,8 @@ class FileProvider {
async isDirectory (path) {
const unprefixedpath = this.removePrefix(path)
return path === this.type ? true : (await window.remixFileSystem.stat(unprefixedpath)).isDirectory()
const isDirectory = path === this.type ? true : (await window.remixFileSystem.stat(unprefixedpath)).isDirectory()
return isDirectory
}
async isFile (path) {
@ -174,7 +180,7 @@ class FileProvider {
* Removes the folder recursively
* @param {*} path is the folder to be removed
*/
async remove (path) {
async remove (path: string) {
path = this.removePrefix(path)
if (await window.remixFileSystem.exists(path)) {
const stat = await window.remixFileSystem.stat(path)
@ -210,7 +216,7 @@ class FileProvider {
visitFolder({ path })
if (items.length !== 0) {
for (const item of items) {
const file = {}
const file: any = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if ((await window.remixFileSystem.stat(curPath)).isDirectory()) {
file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder)
@ -251,8 +257,8 @@ class FileProvider {
}
async rename (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath)
const unprefixedoldPath = this.removePrefix(oldPath)
const unprefixednewPath = this.removePrefix(newPath)
if (await this._exists(unprefixedoldPath)) {
await window.remixFileSystem.rename(unprefixedoldPath, unprefixednewPath)
this.event.emit('fileRenamed',
@ -266,6 +272,7 @@ class FileProvider {
}
async resolveDirectory (path, cb) {
const startTime = Date.now()
path = this.removePrefix(path)
if (path.indexOf('/') !== 0) path = '/' + path
try {
@ -306,4 +313,3 @@ class FileProvider {
}
}
module.exports = FileProvider

@ -1,5 +1,5 @@
'use strict'
const FileProvider = require('./fileProvider')
import FileProvider from "./fileProvider"
module.exports = class RemixDProvider extends FileProvider {
constructor (appManager) {

@ -1,7 +1,7 @@
'use strict'
const EventManager = require('events')
const FileProvider = require('./fileProvider')
import FileProvider from "./fileProvider"
class WorkspaceFileProvider extends FileProvider {
constructor () {

@ -3,8 +3,9 @@ import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import { FileSystemProvider } from '@remix-ui/workspace' // eslint-disable-line
import Registry from '../state/registry'
import {Registry} from '@remix-project/remix-lib'
import { RemixdHandle } from '../plugins/remixd-handle'
import {PluginViewWrapper} from '@remix-ui/helper'
const { HardhatHandle } = require('../files/hardhat-handle.js')
const { FoundryHandle } = require('../files/foundry-handle.js')
const { TruffleHandle } = require('../files/truffle-handle.js')
@ -42,6 +43,9 @@ const profile = {
'registerContextMenuItem',
'renameWorkspace',
'deleteWorkspace',
'loadTemplate',
'clone',
'isExpanded',
],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp',
@ -70,15 +74,33 @@ module.exports = class Filepanel extends ViewPlugin {
this.workspaces = []
this.appManager = appManager
this.currentWorkspaceMetadata = null
this.expandPath = []
}
setDispatch(dispatch) {
this.dispatch = dispatch
this.renderComponent()
}
render() {
return (
<div id="fileExplorerView">
<FileSystemProvider plugin={this} />
<PluginViewWrapper plugin={this} />
</div>
)
}
updateComponent(state) {
return (
<FileSystemProvider plugin={state.plugin} />
)
}
renderComponent() {
this.dispatch({
plugin: this,
})
}
/**
* @param item { id: string, name: string, type?: string[], path?: string[], extension?: string[], pattern?: string[] }
@ -95,10 +117,8 @@ module.exports = class Filepanel extends ViewPlugin {
*/
registerContextMenuItem(item) {
return new Promise((resolve, reject) => {
this.emit('registerContextMenuItemReducerEvent', item, (err, data) => {
if (err) reject(err)
else resolve(data)
})
this.emit('registerContextMenuItemReducerEvent', item)
resolve(item)
})
}
@ -228,5 +248,13 @@ module.exports = class Filepanel extends ViewPlugin {
workspaceCreated(workspace) {
this.emit('workspaceCreated', workspace)
}
isExpanded(path) {
if(path === '/') return true
// remove leading slash
path = path.replace(/^\/+/, '')
return this.expandPath.includes(path)
}
/** end section */
}

@ -100,7 +100,8 @@ export class Layout extends Plugin {
minimize (name: string, minimized:boolean): void {
this.panels[name].minimized = minimized
this.event.emit('change', null)
this.event.emit('change', this.panels)
this.emit('change', this.panels)
}
async maximiseSidePanel () {

@ -3,17 +3,18 @@ import React from 'react' // eslint-disable-line
import { RemixUiTerminal } from '@remix-ui/terminal' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry'
import {Registry} from '@remix-project/remix-lib'
import { PluginViewWrapper } from '@remix-ui/helper'
import vm from 'vm'
const EventManager = require('../../lib/events')
import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line
import { RemixUiXterminals } from '@remix-ui/xterm'
const KONSOLES = []
function register (api) { KONSOLES.push(api) }
function register(api) { KONSOLES.push(api) }
const profile = {
displayName: 'Terminal',
@ -25,7 +26,7 @@ const profile = {
}
class Terminal extends Plugin {
constructor (opts, api) {
constructor(opts, api) {
super(profile)
this.fileImport = new CompilerImports()
this.event = new EventManager()
@ -81,26 +82,26 @@ class Terminal extends Plugin {
this.call('debugger', 'debug', hash)
})
this.dispatch = null
}
onActivation() {
this.renderComponent()
}
onDeactivation () {
onDeactivation() {
this.off('scriptRunner', 'log')
this.off('scriptRunner', 'info')
this.off('scriptRunner', 'warn')
this.off('scriptRunner', 'error')
}
logHtml (html) {
logHtml(html) {
this.terminalApi.logHtml(html)
}
log (message, type) {
log(message, type) {
this.terminalApi.log(message, type)
}
@ -108,18 +109,21 @@ class Terminal extends Plugin {
this.dispatch = dispatch
}
render () {
return <div id='terminal-view' className='panel' data-id='terminalContainer-view'><PluginViewWrapper plugin={this}/></div>
render() {
return <div id='terminal-view' className='panel' data-id='terminalContainer-view'><PluginViewWrapper plugin={this} /></div>
}
updateComponent(state) {
return <RemixUiTerminal
plugin={state.plugin}
onReady={state.onReady}
/>
return (Registry.getInstance().get('platform').api.isDesktop()) ? <RemixUiXterminals onReady={state.onReady} plugin={state.plugin}>
</RemixUiXterminals>
: <RemixUiTerminal
plugin={state.plugin}
onReady={state.onReady}
visible={true}
/>
}
renderComponent () {
renderComponent() {
const onReady = (api) => { this.terminalApi = api }
this.dispatch({
plugin: this,
@ -127,7 +131,7 @@ class Terminal extends Plugin {
})
}
scroll2bottom () {
scroll2bottom() {
setTimeout(function () {
// do nothing.
}, 0)

@ -35,8 +35,6 @@ export class CompilationDetailsPlugin extends ViewPlugin {
}
async onActivation() {
await this.call('tabs', 'focus', 'compilationDetails')
this.renderComponent()
_paq.push(['trackEvent', 'plugin', 'activated', 'compilationDetails'])
}
@ -46,13 +44,17 @@ export class CompilationDetailsPlugin extends ViewPlugin {
async showDetails(sentPayload: any) {
await this.call('tabs', 'focus', 'compilationDetails')
this.payload = sentPayload
this.renderComponent()
setTimeout(() => {
// TODO: use the react API to render when the tab is focused and tbe plugin in the view.
this.payload = sentPayload
this.renderComponent()
}, 2000)
}
setDispatch(dispatch: React.Dispatch<any>): void {
this.dispatch = dispatch
}
render() {
return (
<div id="compileDetails">

@ -1,6 +1,6 @@
import { Plugin } from '@remixproject/engine'
import { QueryParams } from '@remix-project/remix-lib'
import Registry from '../state/registry'
import {Registry} from '@remix-project/remix-lib'
const profile = {
name: 'config',

@ -0,0 +1,64 @@
import { ElectronPlugin } from '@remixproject/engine-electron';
import { Plugin } from '@remixproject/engine';
import { baseURLBin, baseURLWasm } from '@remix-project/remix-solidity'
import axios, {AxiosResponse} from 'axios'
import { iSolJsonBinData } from '@remix-project/remix-lib'
const profile = {
displayName: 'compilerLoader',
name: 'compilerloader',
description: 'Loads the compiler for offline use',
}
const methods = ['getJsonBinData']
export class compilerLoaderPlugin extends Plugin {
constructor() {
super(profile)
this.methods = methods
}
async getJsonBinData() {
const response: iSolJsonBinData = {
baseURLBin: '',
baseURLWasm: '',
binList: [],
wasmList: [],
selectorList: []
}
let binRes: AxiosResponse
let wasmRes: AxiosResponse
try {
// fetch normal builds
binRes = await axios(`${baseURLBin}/list.json`)
// fetch wasm builds
wasmRes = await axios(`${baseURLWasm}/list.json`)
} catch (e) {
}
if (wasmRes.status === 200) {
response.wasmList = wasmRes.data.builds
}
if (binRes.status === 200) {
response.binList = binRes.data.builds
}
response.baseURLBin = baseURLBin
response.baseURLWasm = baseURLWasm
this.emit('jsonBinDataLoaded', response)
}
}
export class compilerLoaderPluginDesktop extends ElectronPlugin {
constructor() {
super(profile)
this.methods = []
}
async onActivation(): Promise<void> {
this.on('solidity', 'loadingCompiler', async (url) => {
await this.call('compilerloader', 'downloadCompiler', url)
})
}
}

@ -0,0 +1,12 @@
import { ElectronPlugin } from '@remixproject/engine-electron';
export class electronConfig extends ElectronPlugin {
constructor() {
super({
displayName: 'electronconfig',
name: 'electronconfig',
description: 'electronconfig',
})
this.methods = []
}
}

@ -0,0 +1,127 @@
import { ElectronPlugin } from '@remixproject/engine-electron';
let workingDir = null
const fixPath = (path: string) => {
return path
}
export class fsPlugin extends ElectronPlugin {
public fs: any
public fsSync: any
constructor() {
super({
displayName: 'fs',
name: 'fs',
description: 'fs',
})
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'lstat', 'exists', 'setWorkingDir', 'getRecentFolders', 'openWindow']
// List of commands all filesystems are expected to provide. `rm` is not
// included since it may not exist and must be handled as a special case
const commands = [
'readFile',
'writeFile',
'mkdir',
'rmdir',
'unlink',
'stat',
'lstat',
'readdir',
'readlink',
'symlink',
]
this.fs = {
exists: async (path: string) => {
path = fixPath(path)
const exists = await this.call('fs', 'exists', path)
return exists
},
rmdir: async (path: string) => {
path = fixPath(path)
return await this.call('fs', 'rmdir', path)
},
readdir: async (path: string) => {
path = fixPath(path)
const files = await this.call('fs', 'readdir', path)
return files
},
unlink: async (path: string) => {
path = fixPath(path)
return await this.call('fs', 'unlink', path)
},
mkdir: async (path: string) => {
path = fixPath(path)
return await this.call('fs', 'mkdir', path)
},
readFile: async (path: string, options) => {
try {
path = fixPath(path)
const file = await this.call('fs', 'readFile', path, options)
return file
} catch (e) {
return undefined
}
}
,
rename: async (from: string, to: string) => {
return await this.call('fs', 'rename', from, to)
},
writeFile: async (path: string, content: string, options: any) => {
path = fixPath(path)
return await this.call('fs', 'writeFile', path, content, options)
}
,
stat: async (path: string) => {
try {
path = fixPath(path)
const stat = await this.call('fs', 'stat', path)
if(!stat) return undefined
stat.isDirectory = () => stat.isDirectoryValue
stat.isFile = () => !stat.isDirectoryValue
return stat
} catch (e) {
return undefined
}
},
lstat: async (path: string) => {
try {
path = fixPath(path)
const stat = await this.call('fs', 'lstat', path)
if(!stat) return undefined
stat.isDirectory = () => stat.isDirectoryValue
stat.isFile = () => !stat.isDirectoryValue
return stat
} catch (e) {
return undefined
}
},
readlink: async (path: string) => {
path = fixPath(path)
return await this.call('fs', 'readlink', path)
},
symlink: async (target: string, path: string) => {
path = fixPath(path)
return await this.call('fs', 'symlink', target, path)
}
}
}
async onActivation() {
(window as any).remixFileSystem = this.fs
this.on('fs', 'workingDirChanged', async (path: string) => {
workingDir = path
await this.call('fileManager', 'refresh')
})
this.on('fs', 'error', async (error: string) => {
if(error === 'ENOSPC'){
this.call('notification', 'alert', {
id: 'fsError',
message: 'Cannot watch file changes. There are too many files in your project.'
})
}
})
}
}

@ -0,0 +1,12 @@
import { ElectronPlugin } from '@remixproject/engine-electron';
export class isoGitPlugin extends ElectronPlugin {
constructor() {
super({
displayName: 'isogit',
name: 'isogit',
description: 'isogit',
})
this.methods = []
}
}

@ -0,0 +1,12 @@
import { ElectronPlugin } from '@remixproject/engine-electron';
export class ripgrepPlugin extends ElectronPlugin {
constructor(){
super({
displayName: 'ripgrep',
name: 'ripgrep',
description: 'ripgrep'
})
this.methods = ['glob']
}
}

@ -0,0 +1,11 @@
import { ElectronPlugin } from '@remixproject/engine-electron';
export class electronTemplates extends ElectronPlugin {
constructor() {
super({
displayName: 'electronTemplates',
name: 'electronTemplates',
description: 'templates',
})
}
}

@ -0,0 +1,11 @@
import { ElectronPlugin } from '@remixproject/engine-electron';
export class xtermPlugin extends ElectronPlugin {
constructor(){
super({
displayName: 'xterm',
name: 'xterm',
description: 'xterm',
})
}
}

@ -1,14 +1,14 @@
'use strict'
import {Plugin} from '@remixproject/engine'
import {sourceMappingDecoder} from '@remix-project/remix-debug'
import {CompilerAbstract} from '@remix-project/remix-solidity'
import {CompilationResult} from '@remix-project/remix-solidity'
import { Plugin } from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { CompilationResult } from '@remix-project/remix-solidity'
import CodeParserGasService from './services/code-parser-gas-service'
import CodeParserCompiler from './services/code-parser-compiler'
import CodeParserAntlrService from './services/code-parser-antlr-service'
import CodeParserImports, {CodeParserImportsData} from './services/code-parser-imports'
import CodeParserImports, { CodeParserImportsData } from './services/code-parser-imports'
import React from 'react'
import {Profile} from '@remixproject/plugin-utils'
import { Profile } from '@remixproject/plugin-utils'
import {
ContractDefinitionAstNode,
EventDefinitionAstNode,
@ -21,9 +21,9 @@ import {
StructDefinitionAstNode,
VariableDeclarationAstNode
} from '@remix-project/remix-analyzer'
import {lastCompilationResult, RemixApi} from '@remixproject/plugin-api'
import {antlr} from './types'
import {ParseResult} from './types/antlr-types'
import { lastCompilationResult, RemixApi } from '@remixproject/plugin-api'
import { antlr } from './types'
import { ParseResult } from './types/antlr-types'
const profile: Profile = {
name: 'codeParser',
@ -169,14 +169,14 @@ export class CodeParser extends Plugin {
this.on('filePanel', 'setWorkspace', async () => {
await this.call('fileDecorator', 'clearFileDecorators')
await this.importService.setFileTree()
await this.importService.updateDirectoryCacheTimeStamp()
})
this.on('fileManager', 'fileAdded', async () => {
await this.importService.setFileTree()
await this.importService.updateDirectoryCacheTimeStamp()
})
this.on('fileManager', 'fileRemoved', async () => {
await this.importService.setFileTree()
await this.importService.updateDirectoryCacheTimeStamp()
})
this.on('fileManager', 'currentFileChanged', async () => {
@ -193,11 +193,19 @@ export class CodeParser extends Plugin {
})
this.on('config', 'configChanged', async (config) => {
await this.reload()
if (config.key === 'settings/auto-completion' ||
config.key === 'settings/display-errors' ||
config.key === 'settings/show-gas') {
await this.reload()
}
})
this.on('settings', 'configChanged', async (config) => {
await this.reload()
if (config.key === 'settings/auto-completion' ||
config.key === 'settings/display-errors' ||
config.key === 'settings/show-gas') {
await this.reload()
}
})
await this.compilerService.init()
@ -368,7 +376,7 @@ export class CodeParser extends Plugin {
} else if (visibility === 'private' || visibility === 'internal') {
executionCost = estimationObj === null ? '-' : estimationObj.internal[fn]
}
return {executionCost}
return { executionCost }
} else {
return {
creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost,
@ -524,9 +532,9 @@ export class CodeParser extends Plugin {
if (nodeDefinition.ast && nodeDefinition.parser) {
if (nodeDefinition.ast.name === nodeDefinition.parser.name && nodeDefinition.ast.nodeType === nodeDefinition.parser.type) {
return nodeDefinition.ast
}else{
} else {
// if there is a difference and the compiler has compiled correctly assume the ast node is the definition
if(this.compilerService.errorState === false){
if (this.compilerService.errorState === false) {
return nodeDefinition.ast
}
}
@ -702,7 +710,7 @@ export class CodeParser extends Plugin {
async getNodeDocumentation(node: genericASTNode) {
if ('documentation' in node && node.documentation && (node.documentation as any).text) {
let text = ''
;(node.documentation as any).text.split('\n').forEach((line) => {
; (node.documentation as any).text.split('\n').forEach((line) => {
text += `${line.trim()}\n`
})
return text

@ -4,6 +4,7 @@ import { AstNode } from "@remix-project/remix-solidity"
import { CodeParser } from "../code-parser"
import { antlr } from '../types'
import { pathToFileURL } from 'url'
import {Registry} from '@remix-project/remix-lib'
const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || []
@ -45,8 +46,8 @@ export default class CodeParserAntlrService {
this.worker = new Worker(new URL('./antlr-worker', import.meta.url))
this.worker.postMessage({
cmd: 'load',
url: document.location.protocol + '//' + document.location.host + '/assets/js/parser/antlr.js',
});
url: Registry.getInstance().get('platform').api.isDesktop() ? 'assets/js/parser/antlr.js' : document.location.protocol + '//' + document.location.host + '/assets/js/parser/antlr.js',
})
const self = this
this.worker.addEventListener('message', function (ev) {
@ -79,7 +80,7 @@ export default class CodeParserAntlrService {
this.cache[file].parsingEnabled = false
} else {
this.cache[file].parsingEnabled = true
}
}
}
}
}
@ -96,7 +97,6 @@ export default class CodeParserAntlrService {
clearInterval(this.workerTimer)
}
async parseWithWorker(text: string, file: string) {
this.parserStartTime = Date.now()
this.worker.postMessage({
@ -284,5 +284,4 @@ export default class CodeParserAntlrService {
const block = walkAst(blocks)
return block
}
}

@ -48,7 +48,7 @@ export default class CodeParserCompiler {
init() {
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSourceCode, input: any, version) => {
this.plugin.call('editor', 'clearAnnotations')
await this.plugin.call('editor', 'clearAnnotations')
this.errorState = true
const result = new CompilerAbstract('soljson', data, source, input)
let allErrors: errorMarker[] = []
@ -93,7 +93,7 @@ export default class CodeParserCompiler {
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors')
if (displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors)
this.addDecorators(allErrors, sources)
await this.addDecorators(allErrors, sources)
} else {
await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources)
await this.clearDecorators(result.getSourceCode().sources)
@ -168,8 +168,6 @@ export default class CodeParserCompiler {
}
this.compiler.set('configFileContent', JSON.stringify(configFileContent))
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.plugin.currentFile) return
const content = await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
const sources = { [this.plugin.currentFile]: { content } }
this.compiler.compile(sources, this.plugin.currentFile)
@ -227,20 +225,10 @@ export default class CodeParserCompiler {
}
for (const fileName of filesWithOutErrors) {
const fileTarget = await this.plugin.call('fileManager', 'getPathFromUrl', fileName)
const decorator: fileDecoration = {
path: fileTarget.file,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
await this.plugin.call('fileDecorator', 'clearFileDecorators', fileTarget.file)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
if(decorators.length > 0)
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
await this.plugin.call('editor', 'clearErrorMarkers', filesWithOutErrors)
}
@ -267,22 +255,8 @@ export default class CodeParserCompiler {
const decorators: fileDecoration[] = []
if (!sources) return
for (const fileName of Object.keys(sources)) {
const decorator: fileDecoration = {
path: fileName,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
await this.plugin.call('fileDecorator', 'clearFileDecorators', fileName)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
}
async getPositionForImportErrors(importedFileName: string, text: string) {

@ -1,22 +1,28 @@
'use strict'
import {Registry} from '@remix-project/remix-lib'
import { CodeParser } from "../code-parser";
export type CodeParserImportsData= {
export type CodeParserImportsData = {
files?: string[],
modules?: string[],
packages?: string[],
timestamp?: number
}
export default class CodeParserImports {
plugin: CodeParser
data: CodeParserImportsData = {}
directoryUpdateCacheTimeStamp = 0
constructor(plugin: CodeParser) {
this.plugin = plugin
this.init()
}
async getImports(){
if(!this.data || !this.data.files || !this.data.timestamp || this.data.timestamp != this.directoryUpdateCacheTimeStamp){
await this.setFileTree()
}
return this.data
}
@ -38,9 +44,31 @@ export default class CodeParserImports {
this.data.packages = [...new Set(this.data.modules.map(x => x.split('/')[0]))]
}
updateDirectoryCacheTimeStamp = async () => {
this.directoryUpdateCacheTimeStamp = Date.now()
}
setFileTree = async () => {
this.data.files = await this.getDirectory('/')
this.data.files = this.data.files.filter(x => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git'))
if (Registry.getInstance().get('platform').api.isDesktop()) {
const search = {
path: '/',
include: ['**/*.sol', '**/*.vy', '**/*.py'],
exclude: [],
pattern: [],
matchCase: false,
useRegExp: false,
matchWholeWord: false,
maxResults: 10000
}
const files = await this.plugin.call('ripgrep', 'glob', search)
// only get path property of files
this.data.files = files.map((x) => x.path)
} else {
this.data.files = await this.getDirectory('/')
this.data.files = this.data.files.filter((x) => x.endsWith('.sol') && !x.startsWith('.deps') && !x.startsWith('.git'))
}
this.data.timestamp = this.directoryUpdateCacheTimeStamp || Date.now()
}
getDirectory = async (dir: string) => {

@ -0,0 +1,30 @@
import { Plugin } from '@remixproject/engine'
import * as templateWithContent from '@remix-project/remix-ws-templates'
const profile = {
name: 'remix-templates',
displayName: 'remix-templates',
description: 'Remix Templates plugin',
methods: ['getTemplate', 'loadTemplateInNewWindow'],
}
export class TemplatesPlugin extends Plugin {
constructor() {
super(profile)
}
async getTemplate (template: string, opts?: any) {
const templateList = Object.keys(templateWithContent)
if (!templateList.includes(template)) return
// @ts-ignore
const files = await templateWithContent[template](opts)
return files
}
// electron only method
async loadTemplateInNewWindow (template: string, opts?: any) {
const files = await this.getTemplate(template, opts)
this.call('electronTemplates', 'loadTemplateInNewWindow', files)
}
}

@ -1,12 +1,12 @@
/* eslint-disable no-unused-vars */
import React, {useRef, useState, useEffect} from 'react' // eslint-disable-line
import isElectron from 'is-electron'
import {WebsocketPlugin} from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import {version as remixdVersion} from '../../../../../libs/remixd/package.json'
import {PluginManager} from '@remixproject/engine'
import {AppModal, AlertModal} from '@remix-ui/app'
import {AppModal, AlertModal, appPlatformTypes} from '@remix-ui/app'
import {Registry} from '@remix-project/remix-lib'
const LOCALHOST = ' - connect to localhost - '
@ -50,6 +50,7 @@ export class RemixdHandle extends WebsocketPlugin {
}
async activate() {
console.trace('activate remixd')
this.connectToLocalhost()
return true
}
@ -111,7 +112,7 @@ export class RemixdHandle extends WebsocketPlugin {
}
if (this.localhostProvider.isConnected()) {
this.deactivate()
} else if (!isElectron()) {
} else if (!(Registry.getInstance().get('platform').api.isDesktop())) {
// warn the user only if he/she is in the browser context
const mod: AppModal = {
id: 'remixdConnect',

@ -21,7 +21,7 @@ export class SolidityScript extends Plugin {
_paq.push(['trackEvent', 'SolidityScript', 'execute', 'script'])
this.call('terminal', 'log', `Running free function '${functionName}' from ${path}...`)
let content = await this.call('fileManager', 'readFile', path)
const params = await this.call('solidity', 'getCompilerParameters')
const params = await this.call('solidity', 'getCompilerQueryParameters')
content = `
// SPDX-License-Identifier: GPL-3.0

@ -27,7 +27,7 @@ export class ExternalHttpProvider extends AbstractProvider {
values={{
a: (chunks) => (
<a href="https://geth.ethereum.org/docs/rpc/server" target="_blank" rel="noreferrer">
{chunks}
<>{chunks}</>
</a>
)
}}
@ -39,7 +39,7 @@ export class ExternalHttpProvider extends AbstractProvider {
values={{
a: (chunks) => (
<a href="https://geth.ethereum.org/getting-started/dev-mode" target="_blank" rel="noreferrer">
{chunks}
<>{chunks}</>
</a>
)
}}
@ -49,7 +49,7 @@ export class ExternalHttpProvider extends AbstractProvider {
</div>
<br />
<br />
<FormattedMessage id="udapp.externalHttpProviderText3" values={{b: (chunks) => <b>{chunks}</b>}} />
<FormattedMessage id="udapp.externalHttpProviderText3" values={{b: (chunks) => <b><>{chunks}</></b>}} />
<br />
<br />
<FormattedMessage

@ -3,7 +3,7 @@ import { ViewPlugin } from '@remixproject/engine-web'
import { EventEmitter } from 'events'
import {RemixUiStaticAnalyser} from '@remix-ui/static-analyser' // eslint-disable-line
import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry'
import {Registry} from '@remix-project/remix-lib'
import { PluginViewWrapper } from '@remix-ui/helper'
var EventManager = require('../../lib/events')

@ -21,7 +21,7 @@ const profile = {
documentation: 'https://remix-ide.readthedocs.io/en/latest/compile.html',
version: packageJson.version,
maintainedBy: 'Remix',
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState', 'getCompilerParameters', 'getCompiler']
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState', 'getCompilerQueryParameters', 'getCompiler']
}
// EditorApi:
@ -138,7 +138,7 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
return this.compileTabLogic.compiler
}
getCompilerParameters () {
getCompilerQueryParameters () {
const params = this.queryParams.get()
params.evmVersion = params.evmVersion === 'null' || params.evmVersion === 'undefined' ? null : params.evmVersion
params.optimize = (params.optimize === 'false' || params.optimize === null || params.optimize === undefined) ? false : params.optimize
@ -146,7 +146,7 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
return params
}
setCompilerParameters (params) {
setCompilerQueryParameters (params) {
this.queryParams.update(params)
}

@ -2,7 +2,7 @@ import { Plugin } from '@remixproject/engine'
import { EventEmitter } from 'events'
import { QueryParams } from '@remix-project/remix-lib'
import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry'
import {Registry} from '@remix-project/remix-lib'
import enJson from './locales/en'
import zhJson from './locales/zh'
import esJson from './locales/es'

@ -0,0 +1,4 @@
{
"electron.openFolder": "Open Folder",
"electron.recentFolders": "Recent Folders"
}

@ -2,6 +2,7 @@
"filePanel.displayName": "File explorer",
"filePanel.workspace": "WORKSPACES",
"filePanel.create": "Create",
"filePanel.create.desktop": "Create Project",
"filePanel.clone": "Clone",
"filePanel.download": "Download",
"filePanel.backup": "Backup",
@ -9,6 +10,7 @@
"filePanel.name": "Name",
"filePanel.save": "Save",
"filePanel.workspace.create": "Create Workspace",
"filePanel.workspace.create.desktop": "Create project in new folder",
"filePanel.workspace.rename": "Rename Workspace",
"filePanel.workspace.save_workspace": "Save Workspace",
"filePanel.workspace.delete": "Delete Workspace",
@ -78,6 +80,7 @@
"filePanel.ok": "OK",
"filePanel.yes": "Yes",
"filePanel.cancel": "Cancel",
"filePanel.selectFolder": "Select or create folder",
"filePanel.createNewWorkspace": "create a new workspace",
"filePanel.connectToLocalhost": "connect to localhost",
"filePanel.copiedToClipboard": "Copied to clipboard {path}",

@ -10,6 +10,7 @@ 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'
@ -28,6 +29,7 @@ export default {
...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
...electronJson,
...solUmlGenJson,
...remixAppJson,
...remixUiTabsJson,

@ -39,5 +39,6 @@
"pluginManager.deactivatePlugin": "Deactivate {pluginName}",
"pluginManager.activatePlugin": "Activate {pluginName}",
"pluginManager.search": "Search",
"pluginManager.managePluginsPermissions": "Manage plugins Permissions"
"pluginManager.managePluginsPermissions": "Manage plugins Permissions",
"pluginManager.UnavailableOffline": "Unavailable Offline"
}

@ -6,6 +6,7 @@
"solidity.addACustomCompiler": "Add a custom compiler",
"solidity.addACustomCompilerWithURL": "Add a custom compiler with URL",
"solidity.includeNightlyBuilds": "Include nightly builds",
"solidity.downloadedCompilers": "Show downloaded only",
"solidity.autoCompile": "Auto compile",
"solidity.hideWarnings": "Hide warnings",
"solidity.enableHardhat": "Enable Hardhat Compilation",

@ -3,7 +3,7 @@ import React from 'react' // eslint-disable-line
import {ViewPlugin} from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import {RemixUiSettings} from '@remix-ui/settings' //eslint-disable-line
import Registry from '../state/registry'
import {Registry} from '@remix-project/remix-lib'
import {PluginViewWrapper} from '@remix-ui/helper'
declare global {
interface Window {

@ -81,13 +81,14 @@ module.exports = class TestTab extends ViewPlugin {
onDeactivation () {
this.off('filePanel', 'newTestFileCreated')
this.off('filePanel', 'setWorkspace')
this.off('filePanel', 'workspaceCreated')
this.off('fileManager', 'currentFileChanged')
// 'currentFileChanged' event is added more than once
this.fileManager.events.removeAllListeners('currentFileChanged')
}
listenToEvents () {
this.on('filePanel', 'workspaceCreated', async () => {
this.createTestLibs()
setTimeout(() => this.createTestLibs(), 50)
})
this.testRunner.event.on('compilationFinished', (success, data, source, input, version) => {

@ -2,7 +2,8 @@ import { Plugin } from '@remixproject/engine'
import { EventEmitter } from 'events'
import { QueryParams } from '@remix-project/remix-lib'
import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry'
import {Registry} from '@remix-project/remix-lib'
const isElectron = require('is-electron')
const _paq = window._paq = window._paq || []
//sol2uml dot files cannot work with css variables so hex values for colors are used
@ -43,7 +44,7 @@ const profile = {
}
export class ThemeModule extends Plugin {
constructor () {
constructor() {
super(profile)
this.events = new EventEmitter()
this._deps = {
@ -53,7 +54,7 @@ export class ThemeModule extends Plugin {
themes.map((theme) => {
this.themes[theme.name.toLocaleLowerCase()] = {
...theme,
url: window.location.origin + ( window.location.pathname.startsWith('/address/') || window.location.pathname.endsWith('.sol') ? '/' : window.location.pathname ) + theme.url
url: isElectron() ? theme.url : window.location.origin + (window.location.pathname.startsWith('/address/') || window.location.pathname.endsWith('.sol') ? '/' : window.location.pathname) + theme.url
}
})
this._paq = _paq
@ -71,22 +72,26 @@ export class ThemeModule extends Plugin {
/** Return the active theme
* @return {{ name: string, quality: string, url: string }} - The active theme
*/
currentTheme () {
currentTheme() {
if (isElectron()) {
const theme = 'https://remix.ethereum.org/' + this.themes[this.active].url.replace(/\\/g, '/').replace(/\/\//g, '/').replace(/\/$/g, '')
return { ...this.themes[this.active], url: theme }
}
return this.themes[this.active]
}
/** Returns all themes as an array */
getThemes () {
getThemes() {
return Object.keys(this.themes).map(key => this.themes[key])
}
/**
* Init the theme
*/
initTheme (callback) { // callback is setTimeOut in app.js which is always passed
initTheme(callback) { // callback is setTimeOut in app.js which is always passed
if (callback) this.initCallback = callback
if (this.active) {
document.getElementById('theme-link') ? document.getElementById('theme-link').remove():null
document.getElementById('theme-link') ? document.getElementById('theme-link').remove() : null
const nextTheme = this.themes[this.active] // Theme
document.documentElement.style.setProperty('--theme', nextTheme.quality)
@ -98,6 +103,7 @@ export class ThemeModule extends Plugin {
if (callback) callback()
})
document.head.insertBefore(theme, document.head.firstChild)
//if (callback) callback()
}
}
@ -115,7 +121,7 @@ export class ThemeModule extends Plugin {
_paq.push(['trackEvent', 'themeModule', 'switchTo', next])
const nextTheme = this.themes[next] // Theme
if (!this.forced) this._deps.config.set('settings/theme', next)
document.getElementById('theme-link') ? document.getElementById('theme-link').remove():null
document.getElementById('theme-link') ? document.getElementById('theme-link').remove() : null
const theme = document.createElement('link')
theme.setAttribute('rel', 'stylesheet')
@ -129,15 +135,21 @@ export class ThemeModule extends Plugin {
document.documentElement.style.setProperty('--theme', nextTheme.quality)
if (themeName) this.active = themeName
// TODO: Only keep `this.emit` (issue#2210)
this.emit('themeChanged', nextTheme)
this.events.emit('themeChanged', nextTheme)
if (isElectron()) {
const theme = 'https://remix.ethereum.org/' + nextTheme.url.replace(/\\/g, '/').replace(/\/\//g, '/').replace(/\/$/g, '')
this.emit('themeChanged', { ...nextTheme, url: theme })
this.events.emit('themeChanged', { ...nextTheme, url: theme })
} else {
this.emit('themeChanged', nextTheme)
this.events.emit('themeChanged', nextTheme)
}
}
/**
* fixes the invertion for images since this should be adjusted when we switch between dark/light qualified themes
* @param {element} [image] - the dom element which invert should be fixed to increase visibility
*/
fixInvert (image) {
fixInvert(image) {
const invert = this.currentTheme().quality === 'dark' ? 1 : 0
if (image) {
image.style.filter = `invert(${invert})`

@ -1,4 +1,4 @@
import Registry from '../state/registry'
import {Registry} from '@remix-project/remix-lib'
var remixLib = require('@remix-project/remix-lib')
var EventsDecoder = remixLib.execution.EventsDecoder

@ -1,4 +1,3 @@
@import url('https://fonts.googleapis.com/css?family=Nunito+Sans:400,600&display=swap');
:root {
--blue: #007aa6;
--indigo: #6610f2;

File diff suppressed because it is too large Load Diff

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

Loading…
Cancel
Save