Merge branch 'master' into master

pull/2392/head
yann300 3 years ago committed by GitHub
commit 514918bdec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  2. 2
      apps/remix-ide-e2e/src/tests/recorder.test.ts
  3. 2
      apps/remix-ide-e2e/src/tests/url.test.ts
  4. 4
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  5. 1
      apps/remix-ide/src/app/panels/tab-proxy.js
  6. 2
      apps/remix-ide/src/app/plugins/config.ts
  7. 5
      apps/remix-ide/src/remixAppManager.js
  8. 6
      apps/solidity-compiler/src/app/compiler-api.ts
  9. 27
      apps/solidity-compiler/src/app/compiler.ts
  10. 2
      libs/remix-lib/src/types/ICompilerApi.ts
  11. 6
      libs/remix-solidity/src/compiler/compiler-input.ts
  12. 46
      libs/remix-solidity/src/compiler/compiler.ts
  13. 2
      libs/remix-solidity/src/compiler/types.ts
  14. 12
      libs/remix-ui/helper/src/lib/remix-ui-helper.ts
  15. 6
      libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx
  16. 67
      libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx
  17. 7
      libs/remix-ui/run-tab/src/lib/css/card.css
  18. 15
      libs/remix-ui/run-tab/src/lib/css/run-tab.css
  19. 3
      libs/remix-ui/run-tab/src/lib/types/index.ts
  20. 280
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  21. 18
      libs/remix-ui/solidity-compiler/src/lib/compilerConfiguration.tsx
  22. 9
      libs/remix-ui/solidity-compiler/src/lib/css/style.css
  23. 16
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts
  24. 18
      libs/remix-ui/solidity-compiler/src/lib/solidity-compiler.tsx
  25. 2
      libs/remix-ui/solidity-compiler/src/lib/types/index.ts
  26. 2
      libs/remix-ui/solidity-compiler/tsconfig.json

@ -230,7 +230,13 @@ module.exports = {
},
'Should get current files #group7': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, scripts: { isDirectory: true }, tests: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, '/')
await clickAndCheckLog(browser, 'fileManager:readdir', {
'compiler_config.json': { isDirectory: false },
contracts: { isDirectory: true },
scripts: { isDirectory: true },
tests: { isDirectory: true },
'README.txt': { isDirectory: false }
}, null, '/')
},
'Should throw error on current file #group7': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:getCurrentFile', 'Error from IDE : Error: No such file or directory No file selected', null, null)
@ -280,12 +286,20 @@ module.exports = {
'Should create empty workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, ['emptyworkspace', true])
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'emptyworkspace', isLocalhost: false, absolutePath: '.workspaces/emptyworkspace' }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', {}, null, '/')
await clickAndCheckLog(browser, 'fileManager:readdir', {
'compiler_config.json': { isDirectory: false }
}, null, '/')
},
'Should create workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'testspace')
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'testspace', isLocalhost: false, absolutePath: '.workspaces/testspace' }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, scripts: { isDirectory: true }, tests: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', {
'compiler_config.json': { isDirectory: false },
contracts: { isDirectory: true },
scripts: { isDirectory: true },
tests: { isDirectory: true },
'README.txt': { isDirectory: false }
}, null, null)
},
'Should get all workspaces #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['default_workspace', 'emptyworkspace', 'testspace'], null, null)

@ -17,7 +17,7 @@ module.exports = {
.pause(5000)
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('[data-id="udapp_arrow"]')
.click('[data-id="udappRecorderTitleExpander"]')
.click('[data-id="runtransaction"]')
.clickInstance(0)
.clickInstance(1)

@ -81,6 +81,7 @@ module.exports = {
.refresh()
.pause(5000)
.clickLaunchIcon('solidity')
.click('*[data-id="scConfigExpander"]')
.assert.containsText('#versionSelector option[data-id="selected"]', '0.7.4+commit.3f05b770')
.assert.containsText('#evmVersionSelector option[data-id="selected"]', 'istanbul')
.assert.containsText('#compilierLanguageSelector option[data-id="selected"]', 'Yul')
@ -96,6 +97,7 @@ module.exports = {
.pause(5000)
.clickLaunchIcon('solidity')
.pause(5000)
.click('*[data-id="scConfigExpander"]')
.assert.containsText('#versionSelector option[data-id="selected"]', 'custom')
// default values
.assert.containsText('#evmVersionSelector option[data-id="selected"]', 'default')

@ -107,8 +107,8 @@ module.exports = {
const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]')
return fileList.getElementsByTagName('li').length;
}, [], function(result){
// check there are no files in FE
browser.assert.equal(result.value, 0, 'Incorrect number of files');
// check there are no files in FE except config file
browser.assert.equal(result.value, 1, 'Incorrect number of files');
});
},

@ -215,6 +215,7 @@ export class TabProxy extends Plugin {
}
renameTab (oldName, newName) {
// The new tab is being added by FileManager
this.removeTab(oldName)
}

@ -18,7 +18,7 @@ export class ConfigPlugin extends Plugin {
const queryParams = new QueryParams()
const params = queryParams.get()
const config = Registry.getInstance().get('config').api
let param = params[name] || config.get(name) || config.get('settings/' + name)
const param = params[name] || config.get(name) || config.get('settings/' + name)
if (param === 'true') return true
if (param === 'false') return false
return param

@ -7,7 +7,7 @@ const _paq = window._paq = window._paq || []
const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout',
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy', 'hardhat-provider', 'compileAndRun', 'search']
const dependentModules = ['git', 'hardhat', 'truffle', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd)
@ -19,7 +19,8 @@ const sensitiveCalls = {
}
export function isNative(name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider']
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic',
'hardhat-provider', 'solidityStaticAnalysis', 'solidityUnitTesting', 'layout', 'notification', 'hardhat-provider', 'ganache-provider']
return nativePlugins.includes(name) || requiredModules.includes(name)
}

@ -18,7 +18,7 @@ export const CompilerApiMixin = (Base) => class extends Base {
onCurrentFileChanged: (fileName: string) => void
// onResetResults: () => void
onSetWorkspace: (workspace: any) => void
onSetWorkspace: (isLocalhost: boolean, workspaceName: string) => void
onNoFileSelected: () => void
onCompilationFinished: (compilationDetails: { contractMap: { file: string } | Record<string, any>, contractsDetails: Record<string, any> }) => void
onSessionSwitched: () => void
@ -237,12 +237,12 @@ export const CompilerApiMixin = (Base) => class extends Base {
this.on('filePanel', 'setWorkspace', (workspace) => {
this.resetResults()
if (this.onSetWorkspace) this.onSetWorkspace(workspace.isLocalhost)
if (this.onSetWorkspace) this.onSetWorkspace(workspace.isLocalhost, workspace.name)
})
this.on('remixd', 'rootFolderChanged', () => {
this.resetResults()
if (this.onSetWorkspace) this.onSetWorkspace(true)
if (this.onSetWorkspace) this.onSetWorkspace(true, 'localhost')
})
this.on('editor', 'sessionSwitched', () => {

@ -6,31 +6,14 @@ import { CompilerApiMixin } from './compiler-api'
import { ICompilerApi } from '@remix-project/remix-lib-ts'
import { CompileTabLogic } from '@remix-ui/solidity-compiler'
const profile = {
name: 'solidity',
displayName: 'Solidity compiler',
icon: 'assets/img/solidity.webp',
description: 'Compile solidity contracts',
kind: 'compiler',
permission: true,
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/solidity_editor.html',
version: '0.0.1',
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState']
}
const defaultAppParameters = {
hideWarnings: false,
autoCompile: false,
includeNightlies: false
}
const defaultCompilerParameters = {
runs: '200',
optimize: false,
version: 'soljson-v0.8.7+commit.e28d00a7',
evmVersion: null, // compiler default
language: 'Solidity'
language: 'Solidity',
useFileConfiguration: false,
configFilePath: "compiler_config.json"
}
export class CompilerClientApi extends CompilerApiMixin(PluginClient) implements ICompilerApi {
constructor () {
@ -48,7 +31,9 @@ export class CompilerClientApi extends CompilerApiMixin(PluginClient) implements
optimize: localStorage.getItem('optimize') === 'true',
version: localStorage.getItem('version') || defaultCompilerParameters.version,
evmVersion: localStorage.getItem('evmVersion') || defaultCompilerParameters.evmVersion, // default
language: localStorage.getItem('language') || defaultCompilerParameters.language
language: localStorage.getItem('language') || defaultCompilerParameters.language,
useFileConfiguration: localStorage.getItem('useFileConfiguration') === 'true',
configFilePath: localStorage.getItem('configFilePath') || defaultCompilerParameters.configFilePath
}
return params
}

@ -24,7 +24,7 @@ export interface ICompilerApi {
onCurrentFileChanged: (fileName: string) => void
// onResetResults: () => void,
onSetWorkspace: (workspace: any) => void
onSetWorkspace: (isLocalhost: boolean, workspaceName: string) => void
onNoFileSelected: () => void
onCompilationFinished: (contractsDetails: any, contractMap: any) => void
onSessionSwitched: () => void

@ -46,3 +46,9 @@ export function getValidLanguage (val: string): Language {
}
return null
}
export function compilerInputForConfigFile(sources: Source, opts)
{
opts.sources = sources
return JSON.stringify(opts)
}

@ -2,7 +2,7 @@
import { update } from 'solc/abi'
import * as webworkify from 'webworkify-webpack'
import compilerInput from './compiler-input'
import compilerInput, { compilerInputForConfigFile } from './compiler-input'
import EventManager from '../lib/eventManager'
import txHelper from './helper'
import {
@ -31,6 +31,8 @@ export class Compiler {
language: 'Solidity',
compilationStartTime: null,
target: null,
useFileConfiguration: false,
configFileContent: '',
lastCompilationResult: {
data: null,
source: null
@ -111,11 +113,17 @@ export class Compiler {
return { error: 'Deferred import' }
}
let result: CompilationResult = {}
let input
let input = ""
try {
if (source && source.sources) {
const { optimize, runs, evmVersion, language } = this.state
input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
const { optimize, runs, evmVersion, language, useFileConfiguration, configFileContent } = this.state
if (useFileConfiguration) {
input = compilerInputForConfigFile(source.sources, JSON.parse(configFileContent))
} else {
input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
}
result = JSON.parse(compiler.compile(input, { import: missingInputsCallback }))
}
} catch (exception) {
@ -183,11 +191,17 @@ export class Compiler {
return { error: 'Deferred import' }
}
let result: CompilationResult = {}
let input: string
let input = ""
try {
if (source && source.sources) {
const { optimize, runs, evmVersion, language } = this.state
input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
const { optimize, runs, evmVersion, language, useFileConfiguration, configFileContent } = this.state
if (useFileConfiguration) {
input = compilerInputForConfigFile(source.sources, JSON.parse(configFileContent))
} else {
input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
}
result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback }))
}
} catch (exception) {
@ -289,12 +303,26 @@ export class Compiler {
this.state.compileJSON = (source: SourceWithTarget) => {
if (source && source.sources) {
const { optimize, runs, evmVersion, language } = this.state
const { optimize, runs, evmVersion, language, useFileConfiguration, configFileContent } = this.state
jobs.push({ sources: source })
let input = ""
try {
if (useFileConfiguration) {
input = compilerInputForConfigFile(source.sources, JSON.parse(configFileContent))
} else {
input = compilerInput(source.sources, { optimize, runs, evmVersion, language })
}
} catch (exception) {
this.onCompilationFinished({ error: { formattedMessage: exception.message } }, [], source, "", this.state.currentVersion)
return
}
this.state.worker.postMessage({
cmd: 'compile',
job: jobs.length - 1,
input: compilerInput(source.sources, { optimize, runs, evmVersion, language })
input: input
})
}
}

@ -164,6 +164,8 @@ export interface CompilerState {
language: Language,
compilationStartTime: number| null,
target: string | null,
useFileConfiguration: boolean,
configFileContent: string,
lastCompilationResult: {
data: CompilationResult | null,
source: SourceWithTarget | null | undefined

@ -56,15 +56,15 @@ export const joinPath = (...paths) => {
export const getPathIcon = (path: string) => {
return path.endsWith('.txt')
? 'far fa-file-alt' : path.endsWith('.md')
? 'far fa-file-alt' : path.endsWith('.sol')
? 'fab fa-markdown' : path.endsWith('.sol')
? 'fak fa-solidity-mono' : path.endsWith('.js')
? 'fab fa-js' : path.endsWith('.json')
? 'fas fa-brackets-curly' : path.endsWith('.vy')
? 'fak fa-vyper-mono' : path.endsWith('.lex')
? 'small fas fa-brackets-curly' : path.endsWith('.vy')
? 'small fak fa-vyper2' : path.endsWith('.lex')
? 'fak fa-lexon' : path.endsWith('ts')
? 'fad fa-brackets-curly' : path.endsWith('.contract')
? 'fab fa-ethereum' : path.endsWith('.cairo')
? 'fab fa-ethereum' : 'far fa-file' // TODO: add cairo icon
? 'small fak fa-ts-logo' : path.endsWith('.tsc')
? 'fad fa-brackets-curly' : path.endsWith('.cairo')
? 'small fak fa-cairo' : 'far fa-file'
}
export const isNumeric = (value) => {

@ -11,8 +11,8 @@ export function InstanceContainerUI (props: InstanceContainerProps) {
}
return (
<div className="udapp_instanceContainer border-0 list-group-item">
<div className="d-flex justify-content-between align-items-center pl-2 ml-1 mb-2"
<div className="udapp_instanceContainer mt-3 border-0 list-group-item">
<label className="udapp_deployedContracts d-flex justify-content-between align-items-center pl-2 mb-0"
title="Autogenerated generic user interfaces for interaction with deployed contracts">
Deployed Contracts
{ instanceList.length > 0
@ -20,7 +20,7 @@ export function InstanceContainerUI (props: InstanceContainerProps) {
title="Clear instances list and reset recorder" aria-hidden="true">
</i> : null
}
</div>
</label>
{ instanceList.length > 0
? <div> { props.instances.instanceList.map((instance, index) => {
return <UniversalDappUI

@ -1,27 +1,9 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import { TreeView, TreeViewItem } from '@remix-ui/tree-view'
import React, {useState} from 'react'
import { RecorderProps } from '../types'
export function RecorderUI (props: RecorderProps) {
const card = (title: string, recorderCount: number) => {
return (
<div className="d-flex justify-content-between align-items-center" onClick={() => {}}>
<div className="pr-1 d-flex flex-row">
<div>{title}</div>
<div>
<div className="d-flex flex-column">
<div className="ml-2 badge badge-pill badge-primary" title="The number of recorded transactions">{recorderCount}</div>
</div>
</div>
</div>
<div>
<div><i className="udapp_arrow fas fa-angle-down" data-id="udapp_arrow"></i></div>
</div>
</div>
)
}
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const triggerRecordButton = () => {
props.storeScenario(props.scenarioPrompt)
}
@ -30,24 +12,35 @@ export function RecorderUI (props: RecorderProps) {
props.runCurrentScenario(props.gasEstimationPrompt, props.passphrasePrompt, props.mainnetPrompt, props.logBuilder)
}
const toggleClass = () => {
setToggleExpander(!toggleExpander)
}
return (
<div className="udapp_cardContainer list-group-item border-0">
<TreeView>
<TreeViewItem label={card('Transactions recorded', props.count)} showIcon={false} labelClass="ml-n1">
<div className="d-flex flex-column">
<div className="udapp_recorderDescription mt-2">
All transactions (deployed contracts and function executions) in this environment can be saved and replayed in
another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3.
</div>
<div className="udapp_transactionActions">
<i className="fas fa-save savetransaction udapp_recorder udapp_icon"
onClick={triggerRecordButton} title="Save Transactions" aria-hidden="true">
</i>
<i className="fas fa-play runtransaction udapp_runTxs udapp_icon" title="Run Transactions" data-id="runtransaction" aria-hidden="true" onClick={handleClickRunButton}></i>
</div>
</div>
</TreeViewItem>
</TreeView>
<div className="udapp_cardContainer list-group-item border border-bottom">
<div className="udapp_recorderSection d-flex justify-content-between" onClick={toggleClass}>
<div className="d-flex">
<label className="mt-1 udapp_recorderSectionLabel">Transactions recorded</label>
<div className="ml-2 mb-2 badge badge-pill badge-primary" title="The number of recorded transactions">{props.count}</div>
</div>
<div>
<span data-id='udappRecorderTitleExpander' onClick={toggleClass}>
<i className={!toggleExpander ? 'fas fa-angle-right' : 'fas fa-angle-down'} aria-hidden="true"></i>
</span>
</div>
</div>
<div className={`border-bottom flex-column ${toggleExpander ? "d-flex" : "d-none"}`}>
<div className="p-2 mt-2">
All transactions (deployed contracts and function executions) can be saved and replayed in
another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3.
</div>
<div className="mb-2 udapp_transactionActions">
<i className="fas fa-save savetransaction udapp_recorder udapp_icon"
onClick={triggerRecordButton} title="Save Transactions" aria-hidden="true">
</i>
<i className="fas fa-play runtransaction udapp_runTxs udapp_icon" title="Run Transactions" data-id="runtransaction" aria-hidden="true" onClick={handleClickRunButton}></i>
</div>
</div>
</div>
)
}

@ -2,11 +2,4 @@
padding : 0 24px 16px;
margin : 0;
background : none;
}
.udapp_arrow {
font-weight : bold;
cursor : pointer;
font-size : 14px;
}
.udapp_arrow:hover {
}

@ -60,6 +60,9 @@
text-align: center;
padding: 0 14px 16px;
}
.udapp_deployedContracts {
font-size: 1rem;
}
.udapp_pendingTxsContainer {
display: flex;
flex-direction: column;
@ -70,9 +73,6 @@
.udapp_container {
padding: 0 24px 16px;
}
.udapp_recorderDescription {
margin: 0 15px 15px 0;
}
.udapp_contractNames {
width: 100%;
border: 1px solid
@ -121,7 +121,14 @@
.udapp_ataddressinput {
padding: .25rem;
}
.udapp_create {
.udapp_recorderSection:hover {
cursor: pointer;
}.udapp_recorderSectionLabel:hover {
cursor: pointer;
}
.udapp_recorderSectionLabel {
cursor: pointer;
font-size: 1rem;
}
.udapp_input {
font-size: 10px !important;

@ -227,7 +227,8 @@ export interface ContractGUIProps {
clickCallBack: (inputs: { name: string, type: string }[], input: string) => void,
widthClass?: string,
evmBC: any,
lookupOnly: boolean
lookupOnly: boolean,
disabled?: boolean
}
export interface MainnetProps {
network: Network,

@ -9,6 +9,7 @@ import { resetEditorMode, listenToEvents } from './actions/compiler'
import { OverlayTrigger, Tooltip } from 'react-bootstrap' // eslint-disable-line
import { getValidLanguage } from '@remix-project/remix-solidity'
import { CopyToClipboard } from '@remix-ui/clipboard'
import { configFileContent } from './compilerConfiguration'
import './css/style.css'
@ -21,10 +22,12 @@ declare global {
const _paq = window._paq = window._paq || [] //eslint-disable-line
export const CompilerContainer = (props: CompilerContainerProps) => {
const { api, compileTabLogic, tooltip, modal, compiledFileName, updateCurrentVersion, configurationSettings, isHardhatProject, isTruffleProject } = props // eslint-disable-line
const { api, compileTabLogic, tooltip, modal, compiledFileName, updateCurrentVersion, configurationSettings, isHardhatProject, isTruffleProject, workspaceName } = props // eslint-disable-line
const [state, setState] = useState({
hideWarnings: false,
autoCompile: false,
configFilePath: "compiler_config.json",
useFileConfiguration: false,
matomoAutocompileOnce: true,
optimize: false,
compileTimeout: null,
@ -39,13 +42,43 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
language: 'Solidity',
evmVersion: ''
})
const [showFilePathInput, setShowFilePathInput] = useState<boolean>(false)
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const [disableCompileButton, setDisableCompileButton] = useState<boolean>(false)
const compileIcon = useRef(null)
const promptMessageInput = useRef(null)
const configFilePathInput = useRef(null)
const [hhCompilation, sethhCompilation] = useState(false)
const [truffleCompilation, setTruffleCompilation] = useState(false)
const [compilerContainer, dispatch] = useReducer(compilerReducer, compilerInitialState)
useEffect(() => {
api.setAppParameter('configFilePath', "/compiler_config.json")
api.fileExists("/compiler_config.json").then((exists) => {
if (!exists) createNewConfigFile()
else {
// what to do? discuss
}
})
api.setAppParameter('configFilePath', "/compiler_config.json")
setShowFilePathInput(false)
}, [workspaceName])
useEffect(() => {
const listener = (event) => {
if (configFilePathInput.current !== event.target) {
setShowFilePathInput(false)
return;
}
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
}
})
useEffect(() => {
fetchAllVersion((allversions, selectedVersion, isURL) => {
setState(prevState => {
@ -72,6 +105,10 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
const autocompile = await api.getAppParameter('autoCompile') as boolean || false
const hideWarnings = await api.getAppParameter('hideWarnings') as boolean || false
const includeNightlies = await api.getAppParameter('includeNightlies') as boolean || false
const useFileConfiguration = await api.getAppParameter('useFileConfiguration') as boolean || false
let configFilePath = await api.getAppParameter('configFilePath')
if (!configFilePath || configFilePath == '') configFilePath = "/compiler_config.json"
setState(prevState => {
const params = api.getCompilerParameters()
const optimize = params.optimize
@ -84,6 +121,8 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
hideWarnings: hideWarnings,
autoCompile: autocompile,
includeNightlies: includeNightlies,
useFileConfiguration: useFileConfiguration,
configFilePath: configFilePath,
optimize: optimize,
runs: runs,
evmVersion: (evmVersion !== null) && (evmVersion !== 'null') && (evmVersion !== undefined) && (evmVersion !== 'undefined') ? evmVersion : 'default',
@ -140,12 +179,67 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
}
}, [compilerContainer.editor.mode])
useEffect(() => {
compileTabLogic.setUseFileConfiguration(state.useFileConfiguration)
if (state.useFileConfiguration) compileTabLogic.setConfigFilePath(state.configFilePath)
}, [state.useFileConfiguration])
useEffect(() => {
if (configurationSettings) {
setConfiguration(configurationSettings)
}
}, [configurationSettings])
const toggleConfigType = () => {
setState(prevState => {
api.setAppParameter('useFileConfiguration', !state.useFileConfiguration)
return { ...prevState, useFileConfiguration: !state.useFileConfiguration }
})
}
const openFile = async () => {
api.open(state.configFilePath)
}
const createNewConfigFile = async () => {
let filePath = configFilePathInput.current && configFilePathInput.current.value !== '' ? configFilePathInput.current.value : state.configFilePath
if (!filePath.endsWith('.json')) filePath = filePath + '.json'
await api.writeFile(filePath, configFileContent)
api.setAppParameter('configFilePath', filePath)
setState(prevState => {
return { ...prevState, configFilePath: filePath }
})
compileTabLogic.setConfigFilePath(filePath)
setShowFilePathInput(false)
}
const handleConfigPathChange = async () => {
if (configFilePathInput.current.value !== '') {
if (!configFilePathInput.current.value.endsWith('.json')) configFilePathInput.current.value += '.json'
if (await api.fileExists(configFilePathInput.current.value)) {
api.setAppParameter('configFilePath', configFilePathInput.current.value)
setState(prevState => {
return { ...prevState, configFilePath: configFilePathInput.current.value }
})
compileTabLogic.setConfigFilePath(configFilePathInput.current.value)
setShowFilePathInput(false)
} else {
modal(
'New configuration file', `The file "${configFilePathInput.current.value}" you entered does not exist. Do you want to create a new one?`,
'Create',
async () => await createNewConfigFile(),
'Cancel',
() => {
setShowFilePathInput(false)
}
)
}
}
}
const _retrieveVersion = (version?) => {
if (!version) version = state.selectedVersion
if (version === 'builtin') version = state.defaultVersion
@ -537,14 +631,18 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
onChangeRuns(settings.runs)
}
const toggleConfigurations = () => {
setToggleExpander(!toggleExpander)
}
return (
<section>
<article>
<header className='remixui_compilerSection border-bottom'>
<div className="mb-2">
<div className='pt-0 remixui_compilerSection'>
<div className="mb-1">
<label className="remixui_compilerLabel form-check-label" htmlFor="versionSelector">
Compiler
<button className="far fa-plus-square border-0 p-0 mx-2 btn-sm" onClick={promptCompiler} title="Add a custom compiler with URL"></button>
<button className="far fa-plus btn-light border-0 p-0 mx-2 btn-sm" onClick={promptCompiler} title="Add a custom compiler with URL"></button>
</label>
<select value={ state.selectedVersion || state.defaultVersion } onChange={(e) => handleLoadVersion(e.target.value) } className="custom-select" id="versionSelector" disabled={state.allversions.length <= 0}>
{ state.allversions.length <= 0 && <option disabled data-id={state.selectedVersion === state.defaultVersion ? 'selected' : ''}>{ state.defaultVersion }</option> }
@ -558,50 +656,17 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
}
</select>
</div>
<div className="mb-2 remixui_nightlyBuilds custom-control custom-checkbox">
<div className="mb-2 flex-row-reverse remixui_nightlyBuilds custom-control custom-checkbox">
<input className="mr-2 custom-control-input" id="nightlies" type="checkbox" onChange={handleNightliesChange} checked={state.includeNightlies} />
<label htmlFor="nightlies" data-id="compilerNightliesBuild" className="form-check-label custom-control-label">Include nightly builds</label>
</div>
<div className="mb-2">
<label className="remixui_compilerLabel form-check-label" htmlFor="compilierLanguageSelector">Language</label>
<select onChange={(e) => handleLanguageChange(e.target.value)} value={state.language} className="custom-select" id="compilierLanguageSelector" title="Available since v0.5.7">
<option data-id={state.language === 'Solidity' ? 'selected' : ''} value='Solidity'>Solidity</option>
<option data-id={state.language === 'Yul' ? 'selected' : ''} value='Yul'>Yul</option>
</select>
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox">
<input className="remixui_autocompile custom-control-input" type="checkbox" onChange={handleAutoCompile} data-id="compilerContainerAutoCompile" id="autoCompile" title="Auto compile" checked={state.autoCompile} />
<label className="form-check-label custom-control-label" htmlFor="autoCompile">Auto compile</label>
</div>
<div className="mb-2">
<label className="remixui_compilerLabel form-check-label" htmlFor="evmVersionSelector">EVM Version</label>
<select value={state.evmVersion} onChange={(e) => handleEvmVersionChange(e.target.value)} className="custom-select" id="evmVersionSelector">
{compileTabLogic.evmVersions.map((version, index) => (<option key={index} data-id={state.evmVersion === version ? 'selected' : ''} value={version}>{version}</option>))}
</select>
</div>
<div className="mt-3">
<p className="mt-2 remixui_compilerLabel">Compiler Configuration</p>
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox">
<input className="remixui_autocompile custom-control-input" type="checkbox" onChange={handleAutoCompile} data-id="compilerContainerAutoCompile" id="autoCompile" title="Auto compile" checked={state.autoCompile} />
<label className="form-check-label custom-control-label" htmlFor="autoCompile">Auto compile</label>
</div>
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox">
<div className="justify-content-between align-items-center d-flex">
<input onChange={(e) => { handleOptimizeChange(e.target.checked) }} className="custom-control-input" id="optimize" type="checkbox" checked={state.optimize} />
<label className="form-check-label custom-control-label" htmlFor="optimize">Enable optimization</label>
<input
min="1"
className="custom-select ml-2 remixui_runs"
id="runs"
placeholder="200"
value={state.runs}
type="number"
title="Estimated number of times each opcode of the deployed code will be executed across the life-time of the contract."
onChange={(e) => onChangeRuns(e.target.value)}
disabled={!state.optimize}
/>
</div>
</div>
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox">
<input className="remixui_autocompile custom-control-input" onChange={handleHideWarningsChange} id="hideWarningsBox" type="checkbox" title="Hide warnings" checked={state.hideWarnings} />
<label className="form-check-label custom-control-label" htmlFor="hideWarningsBox">Hide warnings</label>
</div>
<div className="mt-1 mb-2 remixui_compilerConfig custom-control custom-checkbox">
<input className="remixui_autocompile custom-control-input" onChange={handleHideWarningsChange} id="hideWarningsBox" type="checkbox" title="Hide warnings" checked={state.hideWarnings} />
<label className="form-check-label custom-control-label" htmlFor="hideWarningsBox">Hide warnings</label>
</div>
{
isHardhatProject &&
@ -635,7 +700,81 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
</a>
</div>
}
</div>
<div className="d-flex px-4 remixui_compilerConfigSection justify-content-between" onClick={toggleConfigurations}>
<div className="d-flex">
<label className="mt-1 remixui_compilerConfigSection">Advanced Configurations</label>
</div>
<div>
<span data-id='scConfigExpander' onClick={toggleConfigurations}>
<i className={!toggleExpander ? 'fas fa-angle-right' : 'fas fa-angle-down'} aria-hidden="true"></i>
</span>
</div>
</div>
<div className={`px-4 pb-4 border-bottom flex-column ${toggleExpander ? "d-flex" : "d-none"}`}>
<div className="d-flex pb-1 remixui_compilerConfig custom-control custom-radio">
<input className="custom-control-input" type="radio" name="configradio" value="manual" onChange={toggleConfigType} checked={!state.useFileConfiguration} id="scManualConfig" />
<label className="form-check-label custom-control-label" htmlFor="scManualConfig">Compiler configuration</label>
</div>
<div className={`flex-column 'd-flex'}`}>
<div className="mb-2 ml-4">
<label className="remixui_compilerLabel form-check-label" htmlFor="compilierLanguageSelector">Language</label>
<select onChange={(e) => handleLanguageChange(e.target.value)} disabled={state.useFileConfiguration} value={state.language} className="custom-select" id="compilierLanguageSelector" title="Available since v0.5.7">
<option data-id={state.language === 'Solidity' ? 'selected' : ''} value='Solidity'>Solidity</option>
<option data-id={state.language === 'Yul' ? 'selected' : ''} value='Yul'>Yul</option>
</select>
</div>
<div className="mb-2 ml-4">
<label className="remixui_compilerLabel form-check-label" htmlFor="evmVersionSelector">EVM Version</label>
<select value={state.evmVersion} onChange={(e) => handleEvmVersionChange(e.target.value)} disabled={state.useFileConfiguration} className="custom-select" id="evmVersionSelector">
{compileTabLogic.evmVersions.map((version, index) => (<option key={index} data-id={state.evmVersion === version ? 'selected' : ''} value={version}>{version}</option>))}
</select>
</div>
<div className="mt-1 mt-3 border-dark pb-3 ml-4 remixui_compilerConfig custom-control custom-checkbox">
<div className="justify-content-between align-items-center d-flex">
<input onChange={(e) => { handleOptimizeChange(e.target.checked) }} disabled={state.useFileConfiguration} className="custom-control-input" id="optimize" type="checkbox" checked={state.optimize} />
<label className="form-check-label custom-control-label" htmlFor="optimize">Enable optimization</label>
<input
min="1"
className="custom-select ml-2 remixui_runs"
id="runs"
placeholder="200"
value={state.runs}
type="number"
title="Estimated number of times each opcode of the deployed code will be executed across the life-time of the contract."
onChange={(e) => onChangeRuns(e.target.value)}
disabled={!state.optimize || state.useFileConfiguration}
/>
</div>
</div>
</div>
<div className="d-flex pb-1 remixui_compilerConfig custom-control custom-radio">
<input className="custom-control-input" type="radio" name="configradio" value="file" onChange={toggleConfigType} checked={state.useFileConfiguration} id="scFileConfig" />
<label className="form-check-label custom-control-label" htmlFor="scFileConfig">Use configuration file</label>
</div>
<div className={`pt-2 ml-4 ml-2 align-items-start justify-content-between d-flex`}>
{ (!showFilePathInput && state.useFileConfiguration) && <span
title="Click to open the config file."
onClick={openFile}
className="py-2 text-primary remixui_compilerConfigPath"
>{state.configFilePath}</span> }
{ (!showFilePathInput&& !state.useFileConfiguration) && <span className="py-2 text-secondary">{state.configFilePath}</span> }
<input
ref={configFilePathInput}
className={`py-0 my-0 form-control ${showFilePathInput ? "d-flex" : "d-none"}`}
placeholder={"Enter the new path"}
title="If the file you entered does not exist you will be able to create one in the next step."
disabled={!state.useFileConfiguration}
onKeyPress={event => {
if (event.key === 'Enter') {
handleConfigPathChange()
}
}}
/>
{ !showFilePathInput && <button disabled={!state.useFileConfiguration} className="btn-secondary" onClick={() => {setShowFilePathInput(true)}}>Change</button> }
</div>
</div>
<div className="px-4">
<button id="compileBtn" data-id="compilerContainerCompileBtn" className="btn btn-primary btn-block d-block w-100 text-break remixui_disabled mb-1 mt-3" onClick={compile} disabled={disableCompileButton}>
<OverlayTrigger overlay={
<Tooltip id="overlay-tooltip-compile">
@ -665,33 +804,32 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
</OverlayTrigger>
</button>
<OverlayTrigger overlay={
<Tooltip id="overlay-tooltip-compile-run-doc">
<div className="text-left p-2">
<div>Choose the script to execute right after compilation by adding the `dev-run-script` natspec tag, as in:</div>
<pre>
<code>
/**<br />
* @title ContractName<br />
* @dev ContractDescription<br />
* @custom:dev-run-script file_path<br />
*/<br />
contract ContractName {'{}'}<br />
</code>
</pre>
Click to know more
</div>
</Tooltip>
}>
<a href="https://remix-ide.readthedocs.io/en/latest/running_js_scripts.html#compile-a-contract-and-run-a-script-on-the-fly" target="_blank" ><i className="pl-2 ml-2 mt-3 mb-1 fas fa-info text-dark"></i></a>
</OverlayTrigger>
<CopyToClipboard tip="Copy tag to use in contract NatSpec" getContent={() => '@custom:dev-run-script file_path'} direction='top'>
<button className="btn remixui_copyButton ml-2 mt-3 mb-1 text-dark">
<i className="remixui_copyIcon far fa-copy" aria-hidden="true"></i>
</button>
</CopyToClipboard>
</div>
</div>
</header>
<Tooltip id="overlay-tooltip-compile-run-doc">
<div className="text-left p-2">
<div>Choose the script to execute right after compilation by adding the `dev-run-script` natspec tag, as in:</div>
<pre>
<code>
/**<br />
* @title ContractName<br />
* @dev ContractDescription<br />
* @custom:dev-run-script file_path<br />
*/<br />
contract ContractName {'{}'}<br />
</code>
</pre>
Click to know more
</div>
</Tooltip>
}>
<a href="https://remix-ide.readthedocs.io/en/latest/running_js_scripts.html#compile-a-contract-and-run-a-script-on-the-fly" target="_blank" ><i className="pl-2 ml-2 mt-3 mb-1 fas fa-info text-dark"></i></a>
</OverlayTrigger>
<CopyToClipboard tip="Copy tag to use in contract NatSpec" getContent={() => '@custom:dev-run-script file_path'} direction='top'>
<button className="btn remixui_copyButton ml-2 mt-3 mb-1 text-dark">
<i className="remixui_copyIcon far fa-copy" aria-hidden="true"></i>
</button>
</CopyToClipboard>
</div>
</div>
</article>
</section>
)

@ -0,0 +1,18 @@
export const configFileContent = `
{
"language": "Solidity",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"": ["ast"],
"*": ["abi", "metadata", "devdoc", "userdoc", "storageLayout", "evm.legacyAssembly", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers", "evm.gasEstimates", "evm.assembly"]
}
},
"evmVersion": "byzantium"
}
}
`

@ -72,6 +72,15 @@
.remixui_compilerSection {
padding: 12px 24px 16px;
}
.remixui_compilerConfigSection:hover {
cursor: pointer;
}
.remixui_compilerConfigSection {
font-size: 1rem;
}
.remixui_compilerConfigPath {
cursor: pointer;
}
.remixui_compilerLabel {
margin-bottom: 2px;
font-size: 11px;

@ -18,6 +18,8 @@ export class CompileTabLogic {
public compilerImport
public event
public evmVersions: Array<string>
public useFileConfiguration: boolean
public configFilePath: string
constructor (public api: ICompilerApi, public contentImport) {
this.event = new EventEmitter()
@ -52,12 +54,21 @@ export class CompileTabLogic {
}
}
setOptimize (newOptimizeValue) {
setOptimize (newOptimizeValue: boolean) {
this.optimize = newOptimizeValue
this.api.setCompilerParameters({ optimize: this.optimize })
this.compiler.set('optimize', this.optimize)
}
setUseFileConfiguration (useFileConfiguration: boolean) {
this.useFileConfiguration = useFileConfiguration
this.compiler.set('useFileConfiguration', useFileConfiguration)
}
setConfigFilePath (path) {
this.configFilePath = path
}
setRuns (runs) {
this.runs = runs
this.api.setCompilerParameters({ runs: this.runs })
@ -95,6 +106,9 @@ export class CompileTabLogic {
const sources = { [target]: { content } }
this.event.emit('removeAnnotations')
this.event.emit('startingCompilation')
this.api.readFile(this.configFilePath).then( contentConfig => {
this.compiler.set('configFileContent', contentConfig)
})
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
setTimeout(() => { this.compiler.compile(sources, target); resolve(true) }, 100)
}).catch((error) => {

@ -13,6 +13,7 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
const [state, setState] = useState({
isHardhatProject: false,
isTruffleProject: false,
workspaceName: '',
currentFile,
loading: false,
compileTabLogic: null,
@ -63,11 +64,11 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
})
}
api.onSetWorkspace = async (isLocalhost: boolean) => {
api.onSetWorkspace = async (isLocalhost: boolean, workspaceName: string) => {
const isHardhat = isLocalhost && await compileTabLogic.isHardhatProject()
const isTruffle = await compileTabLogic.isTruffleProject()
setState(prevState => {
return { ...prevState, currentFile, isHardhatProject: isHardhat, isTruffleProject: isTruffle }
return { ...prevState, currentFile, isHardhatProject: isHardhat, workspaceName: workspaceName, isTruffleProject: isTruffle }
})
}
@ -150,7 +151,18 @@ export const SolidityCompiler = (props: SolidityCompilerProps) => {
return (
<>
<div id="compileTabView">
<CompilerContainer api={api} isHardhatProject={state.isHardhatProject} isTruffleProject={state.isTruffleProject} compileTabLogic={compileTabLogic} tooltip={toast} modal={modal} compiledFileName={currentFile} updateCurrentVersion={updateCurrentVersion} configurationSettings={configurationSettings} />
<CompilerContainer
api={api}
isHardhatProject={state.isHardhatProject}
workspaceName={state.workspaceName}
isTruffleProject={state.isTruffleProject}
compileTabLogic={compileTabLogic}
tooltip={toast}
modal={modal}
compiledFileName={currentFile}
updateCurrentVersion={updateCurrentVersion}
configurationSettings={configurationSettings}
/>
{ contractsFile[currentFile] && contractsFile[currentFile].contractsDetails && <ContractSelection api={api} contractsDetails={contractsFile[currentFile].contractsDetails} contractList={contractsFile[currentFile].contractList} modal={modal} /> }
{ compileErrors[currentFile] &&
<div className="remixui_errorBlobs p-4" data-id="compiledErrors">

@ -11,6 +11,7 @@ export interface CompilerContainerProps {
compileTabLogic: CompileTabLogic,
isHardhatProject: boolean,
isTruffleProject: boolean,
workspaceName: string,
tooltip: (message: string | JSX.Element) => void,
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void,
compiledFileName: string,
@ -45,4 +46,3 @@ export interface CompilationDetails {
export interface ContractsFile {
[currentFile: string]: CompilationDetails
}

@ -4,7 +4,7 @@
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
},
"files": [],
"include": [],

Loading…
Cancel
Save