Merge branch 'master' into contMenu

pull/5370/head
Liana Husikyan 2 years ago committed by GitHub
commit e37717c053
  1. 10
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  2. 6
      apps/remix-ide/src/app.js
  3. 32
      apps/remix-ide/src/app/files/fileManager.ts
  4. 6
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  5. 2
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  6. 17
      apps/remix-ide/src/app/providers/abstract-provider.tsx
  7. 80
      apps/remix-ide/src/app/providers/custom-vm-fork-provider.tsx
  8. 1
      apps/remix-ide/src/app/providers/injected-L2-provider.tsx
  9. 12
      apps/remix-ide/src/app/providers/injected-provider.tsx
  10. 29
      apps/remix-ide/src/app/providers/mainnet-vm-fork-provider.tsx
  11. 15
      apps/remix-ide/src/app/providers/vm-provider.tsx
  12. 4
      apps/remix-ide/src/app/tabs/locales/en/home.json
  13. 11
      apps/remix-ide/src/app/udapp/run-tab.js
  14. 4
      apps/remix-ide/src/blockchain/blockchain.js
  15. 8
      apps/remix-ide/src/blockchain/execution-context.js
  16. 4
      apps/remix-ide/src/blockchain/providers/vm.js
  17. 2
      apps/remix-ide/src/blockchain/providers/worker-vm.ts
  18. 2
      apps/remix-ide/src/remixEngine.js
  19. 6
      libs/remix-simulator/src/provider.ts
  20. 67
      libs/remix-simulator/src/vm-context.ts
  21. 56
      libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
  22. 1
      libs/remix-ui/app/src/lib/remix-app/types/index.ts
  23. 54
      libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx
  24. 33
      libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
  25. 2
      libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx
  26. 49
      libs/remix-ui/home-tab/src/lib/components/homeTabLearn.tsx
  27. 41
      libs/remix-ui/home-tab/src/lib/components/homeTabScamAlert.tsx
  28. 37
      libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx
  29. 24
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx
  30. 2
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  31. 2
      libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities.ts
  32. 2
      libs/remix-ui/workspace/src/lib/utils/index.ts
  33. 2
      tsconfig.base.json
  34. 3
      tsconfig.json

@ -286,7 +286,15 @@ module.exports = {
.end()
}
})
}
},
'Should connect to the mainnet fork and run web3.eth.getCode in the terminal #group9': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('udapp')
.switchEnvironment('vm-mainnet-fork')
.executeScriptInTerminal(`web3.eth.getCode('0x180587b00c8642e2c7ac3a758712d97e6f7bdcc7')`) // mainnet contract
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x608060405260043610601f5760003560e01c80635c60da1b14603157602b565b36602b576029605f565b005b6029605f565b348015603c57600080fd5b5060436097565b6040516001600160a01b03909116815260200160405180910390f35b609560917f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b60d1565b565b600060c97f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b90565b3660008037600080366000845af43d6000803e80801560ef573d6000f35b3d6000fdfea2646970667358221220969dbb4b1d8aec2bb348e26488dc1a33b6bcf0190f567d161312ab7ca9193d8d64736f6c63430008110033', 120000)
},
}

@ -28,6 +28,8 @@ import { Layout } from './app/panels/layout'
import { NotificationPlugin } from './app/plugins/notification'
import { Blockchain } from './blockchain/blockchain.js'
import { BerlinVMProvider, LondonVMProvider } from './app/providers/vm-provider'
import { MainnetForkVMProvider } from './app/providers/mainnet-vm-fork-provider'
import { CustomForkVMProvider } from './app/providers/custom-vm-fork-provider'
import { HardhatProvider } from './app/providers/hardhat-provider'
import { GanacheProvider } from './app/providers/ganache-provider'
import { FoundryProvider } from './app/providers/foundry-provider'
@ -203,6 +205,8 @@ class AppComponent {
const networkModule = new NetworkModule(blockchain)
// ----------------- represent the current selected web3 provider ----
const web3Provider = new Web3ProviderModule(blockchain)
const vmProviderCustomFork = new CustomForkVMProvider(blockchain)
const vmProviderMainnetFork = new MainnetForkVMProvider(blockchain)
const vmProviderBerlin = new BerlinVMProvider(blockchain)
const vmProviderLondon = new LondonVMProvider(blockchain)
const hardhatProvider = new HardhatProvider(blockchain)
@ -273,6 +277,8 @@ class AppComponent {
storagePlugin,
vmProviderBerlin,
vmProviderLondon,
vmProviderMainnetFork,
vmProviderCustomFork,
hardhatProvider,
ganacheProvider,
foundryProvider,

@ -1,5 +1,6 @@
'use strict'
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'
@ -344,12 +345,31 @@ class FileManager extends Plugin {
}
}
async zipDir(dirPath, zip) {
const filesAndFolders = await this.readdir(dirPath)
for(let path in filesAndFolders) {
if (filesAndFolders[path].isDirectory) await this.zipDir(path, zip)
else {
path = this.normalize(path)
const content: any = await this.readFile(path)
zip.file(path, content)
}
}
}
async download(path) {
try {
const fileName = helper.extractNameFromKey(path)
path = this.normalize(path)
const content: any = await this.readFile(path)
saveAs(new Blob([content]), fileName)
const downloadFileName = helper.extractNameFromKey(path)
if (await this.isDirectory(path)) {
const zip = new JSZip()
await this.zipDir(path, zip)
const content = await zip.generateAsync({type: 'blob'})
saveAs(content, `${downloadFileName}.zip`)
} else {
path = this.normalize(path)
const content: any = await this.readFile(path)
saveAs(new Blob([content]), downloadFileName)
}
} catch (e) {
throw new Error(e)
}
@ -377,9 +397,9 @@ class FileManager extends Plugin {
/**
* Get the list of files in the directory
* @param {string} path path of the directory
* @returns {string[]} list of the file/directory name in this directory
* @returns {Object} list of the file/directory name in this directory e.g; {contracts/1_Storage.sol:{isDirectory: false}}
*/
async readdir(path) {
async readdir(path): Promise<Record<string, Record<string, boolean>>> {
try {
path = this.normalize(path)
path = this.limitPluginScope(path)

@ -26,7 +26,7 @@ export class ContractFlattener extends Plugin {
async flattenAContract(action: customAction) {
this.fileName = action.path[0]
this.call('solidity', 'compile', this.fileName)
await this.call('solidity', 'compile', this.fileName)
}
/**
@ -39,8 +39,8 @@ export class ContractFlattener extends Plugin {
const ast = data.sources
const dependencyGraph = getDependencyGraph(ast, filePath)
const sorted = dependencyGraph.isEmpty()
? [filePath]
: dependencyGraph.sort().reverse()
? [filePath]
: dependencyGraph.sort().reverse()
const sources = source.sources
const result = concatSourceFiles(sorted, sources)
await this.call('fileManager', 'writeFile', `${filePath}_flattened.sol`, result)

@ -115,7 +115,7 @@ export class RemixdHandle extends WebsocketPlugin {
// warn the user only if he/she is in the browser context
const mod: AppModal = {
id: 'remixdConnect',
title: 'Connect to localhost',
title: 'Access file system using remixd',
message: remixdDialog(),
okLabel: 'Connect',
cancelLabel: 'Cancel',

@ -19,12 +19,20 @@ export type JsonDataResult = {
export type RejectRequest = (error: Error) => void
export type SuccessRequest = (data: JsonDataResult) => void
export abstract class AbstractProvider extends Plugin {
export interface IProvider {
options: { [id: string] : any }
init(): Promise<{ [id: string] : any }>
body(): JSX.Element
sendAsync (data: JsonDataRequest): Promise<JsonDataResult>
}
export abstract class AbstractProvider extends Plugin implements IProvider {
provider: ethers.providers.JsonRpcProvider
blockchain: Blockchain
defaultUrl: string
connected: boolean
nodeUrl: string
options: { [id: string] : any } = {}
constructor (profile, blockchain, defaultUrl) {
super(profile)
@ -41,7 +49,7 @@ export abstract class AbstractProvider extends Plugin {
this.provider = null
}
async init () {
async init () {
this.nodeUrl = await ((): Promise<string> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
@ -80,9 +88,12 @@ export abstract class AbstractProvider extends Plugin {
})
})()
this.provider = new ethers.providers.JsonRpcProvider(this.nodeUrl)
return {
nodeUrl: this.nodeUrl
}
}
sendAsync (data: JsonDataRequest): Promise<any> {
sendAsync (data: JsonDataRequest): Promise<JsonDataResult> {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
if (!this.provider) return reject(new Error('provider node set'))

@ -0,0 +1,80 @@
import React, { useRef } from 'react' // eslint-disable-line
import * as packageJson from '../../../../../package.json'
import { AppModal, ModalTypes } from '@remix-ui/app'
import { BasicVMProvider } from './vm-provider'
export class CustomForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
inputs: any
constructor (blockchain) {
super({
name: 'vm-custom-fork',
displayName: 'Custom fork - Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = ''
this.nodeUrl = ''
this.blockNumber = 'latest'
this.inputs = {}
}
async init () {
this.inputs = {nodeUrl: '', evm: '', blockNumber: '' }
const body = () => {
return <div>
<div>
<label>Node URL</label>
<input type="text" value={this.inputs.nodeUrl} ></input>
</div>
<div>
<label>Block number (or "latest")</label>
<input type="text" placeholder='block number or "latest"' value={this.inputs.blockNumber} ></input>
</div>
<div>
<label>EVM</label>
<select value={this.inputs.evm}>
<option value="berlin" key="berlin">Berlin</option>
<option value="london" key="london" >London</option>
</select>
</div>
</div>
}
await ((): Promise<string> => {
return new Promise((resolve, reject) => {
const modalContent: AppModal = {
id: this.profile.name,
title: this.profile.displayName,
message: body(),
modalType: ModalTypes.default,
okLabel: 'Connect',
cancelLabel: 'Cancel',
okFn: (value: string) => {
setTimeout(() => resolve(value), 0)
},
cancelFn: () => {
setTimeout(() => reject(new Error('Canceled')), 0)
},
hideFn: () => {
setTimeout(() => reject(new Error('Hide')), 0)
}
}
this.call('notification', 'modal', modalContent)
})
})()
this.fork = this.inputs.evm
this.nodeUrl = this.inputs.nodeUrl
const block = this.inputs.blockNumber
this.blockNumber = block === 'latest' ? 'latest' : parseInt(block)
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
}
}

@ -17,6 +17,7 @@ export class InjectedL2Provider extends InjectedProvider {
if (this.chainName && this.rpcUrls && this.rpcUrls.length > 0) await addL2Network(this.chainName, this.chainId, this.rpcUrls)
else
throw new Error('Cannot add the L2 network to main injected provider')
return {}
}
}

@ -1,12 +1,15 @@
/* global ethereum */
import React from 'react' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider'
import Web3 from 'web3'
import { IProvider } from './abstract-provider'
const noInjectedProviderMsg = 'No injected provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).'
export class InjectedProvider extends Plugin {
export class InjectedProvider extends Plugin implements IProvider {
provider: any
options: { [id: string] : any } = {}
constructor (profile) {
super(profile)
@ -23,6 +26,12 @@ export class InjectedProvider extends Plugin {
}
}
body (): JSX.Element {
return (
<div></div>
)
}
async init () {
const injectedProvider = (window as any).ethereum
if (injectedProvider === undefined) {
@ -33,6 +42,7 @@ export class InjectedProvider extends Plugin {
}
this.askPermission(true)
}
return {}
}
sendAsync (data: JsonDataRequest): Promise<any> {

@ -0,0 +1,29 @@
import * as packageJson from '../../../../../package.json'
import { BasicVMProvider } from './vm-provider'
export class MainnetForkVMProvider extends BasicVMProvider {
nodeUrl: string
blockNumber: number | 'latest'
constructor (blockchain) {
super({
name: 'vm-mainnet-fork',
displayName: 'Mainet fork -Remix VM (London)',
kind: 'provider',
description: 'Remix VM (London)',
methods: ['sendAsync', 'init'],
version: packageJson.version
}, blockchain)
this.blockchain = blockchain
this.fork = 'london'
this.nodeUrl = 'https://rpc.archivenode.io/e50zmkroshle2e2e50zm0044i7ao04ym'
this.blockNumber = 'latest'
}
async init () {
return {
'fork': this.fork,
'nodeUrl': this.nodeUrl,
'blockNumber': this.blockNumber
}
}
}

@ -1,17 +1,26 @@
import React from 'react' // eslint-disable-line
import * as packageJson from '../../../../../package.json'
import { JsonDataRequest, RejectRequest, SuccessRequest } from '../providers/abstract-provider'
import { Plugin } from '@remixproject/engine'
import { IProvider } from './abstract-provider'
export class BasicVMProvider extends Plugin {
export class BasicVMProvider extends Plugin implements IProvider {
blockchain
fork: string
options: { [id: string] : any } = {}
constructor (profile, blockchain) {
super(profile)
this.blockchain = blockchain
this.fork = null
this.fork = ''
}
init () {}
async init (): Promise<{ [id: string] : any }> { return {} }
body (): JSX.Element {
return (
<div></div>
)
}
sendAsync (data: JsonDataRequest): Promise<any> {
return new Promise((resolve, reject) => {

@ -55,7 +55,7 @@
"home.files": "Files",
"home.newFile": "New File",
"home.openFile": "Open File",
"home.connectToLocalhost": "Connect to Localhost",
"home.loadFrom": "LOAD FROM",
"home.connectToLocalhost": "Access File System",
"home.loadFrom": "Load from",
"home.resources": "Resources"
}

@ -103,6 +103,7 @@ export class RunTab extends ViewPlugin {
const addProvider = async (name, displayName, isInjected, isVM, fork = '', dataId = '', title = '') => {
await this.call('blockchain', 'addProvider', {
options: {},
dataId,
name,
displayName,
@ -110,7 +111,13 @@ export class RunTab extends ViewPlugin {
isInjected,
isVM,
title,
init: () => { return this.call(name, 'init') },
init: async function () {
const options = await udapp.call(name, 'init')
if (options) {
this.options = options
if (options['fork']) this.fork = options['fork']
}
},
provider: {
async sendAsync (payload, callback) {
try {
@ -128,6 +135,8 @@ export class RunTab extends ViewPlugin {
const titleVM = 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.'
await addProvider('vm-london', 'Remix VM (London)', false, true, 'london', 'settingsVMLondonMode', titleVM)
await addProvider('vm-berlin', 'Remix VM (Berlin)', false, true, 'berlin', 'settingsVMBerlinMode', titleVM)
await addProvider('vm-mainnet-fork', 'Remix VM - Mainnet fork', false, true, 'london', 'settingsVMMainnetMode', titleVM)
// await addProvider('vm-custom-fork', 'Remix VM - Custom fork', false, true, '', 'settingsVMCustomMode', titleVM)
// external provider
await addProvider('hardhat-provider', 'Hardhat Provider', false, false)

@ -84,8 +84,6 @@ export class Blockchain extends Plugin {
setupProviders () {
const vmProvider = new VMProvider(this.executionContext)
this.providers = {}
this.providers['vm-berlin'] = vmProvider
this.providers['vm-london'] = vmProvider
this.providers['vm'] = vmProvider
this.providers.injected = new InjectedProvider(this.executionContext)
this.providers.web3 = new NodeProvider(this.executionContext, this.config)
@ -93,7 +91,7 @@ export class Blockchain extends Plugin {
getCurrentProvider () {
const provider = this.getProvider()
if (provider && provider.startsWith('vm')) return this.providers['vm']
if (this.providers[provider]) return this.providers[provider]
return this.providers.web3 // default to the common type of provider
}

@ -43,6 +43,10 @@ export class ExecutionContext {
return this.executionContext
}
getProviderObject () {
return this.customNetWorks[this.executionContext]
}
getSelectedAddress () {
return injectedProvider ? injectedProvider.selectedAddress : null
}
@ -127,10 +131,10 @@ export class ExecutionContext {
if (!confirmCb) confirmCb = () => { /* Do nothing. */ }
if (!infoCb) infoCb = () => { /* Do nothing. */ }
if (this.customNetWorks[context]) {
var network = this.customNetWorks[context]
var network = this.customNetWorks[context]
await network.init()
this.currentFork = network.fork
this.executionContext = context
await network.init()
// injected
web3.setProvider(network.provider)
await this._updateChainContext()

@ -22,7 +22,9 @@ class VMProvider {
if (this.worker) this.worker.terminate()
this.accounts = {}
this.worker = new Worker(new URL('./worker-vm', import.meta.url))
this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork() })
const provider = this.executionContext.getProviderObject()
this.worker.postMessage({ cmd: 'init', fork: this.executionContext.getCurrentFork(), nodeUrl: provider?.options['nodeUrl'], blockNumber: provider?.options['blockNumber']})
let incr = 0
const stamps = {}

@ -6,7 +6,7 @@ self.onmessage = (e: MessageEvent) => {
switch (data.cmd) {
case 'init':
{
provider = new Provider({ fork: data.fork })
provider = new Provider({ fork: data.fork, nodeUrl: data.nodeUrl, blockNumber: data.blockNumber })
if (provider) provider.init()
break
}

@ -15,7 +15,7 @@ export class RemixEngine extends Engine {
if (name === 'hardhat') return { queueTimeout: 60000 * 4 }
if (name === 'truffle') return { queueTimeout: 60000 * 4 }
if (name === 'localPlugin') return { queueTimeout: 60000 * 4 }
if (name === 'notification') return { queueTimeout: 60000 * 4 }
if (name === 'notification') return { queueTimeout: 60000 * 10 }
if (name === 'sourcify') return { queueTimeout: 60000 * 4 }
if (name === 'fetchAndCompile') return { queueTimeout: 60000 * 4 }
if (name === 'walletconnect') return { queueTimeout: 60000 * 4 }

@ -13,17 +13,17 @@ import { generateBlock } from './genesis'
import { VMContext } from './vm-context'
export class Provider {
options: Record<string, unknown>
options: Record<string, string | number>
vmContext
Accounts
Transactions
methods
connected: boolean;
constructor (options: Record<string, unknown> = {}) {
constructor (options: Record<string, string | number> = {}) {
this.options = options
this.connected = true
this.vmContext = new VMContext(options['fork'])
this.vmContext = new VMContext(options['fork'] as string, options['nodeUrl'] as string, options['blockNumber'] as number)
this.Accounts = new Web3Accounts(this.vmContext)
this.Transactions = new Transactions(this.vmContext)

@ -2,14 +2,16 @@
'use strict'
import { hash } from '@remix-project/remix-lib'
import { bufferToHex } from '@ethereumjs/util'
import type { Address } from '@ethereumjs/util'
import { decode } from 'rlp'
import { ethers } from 'ethers'
import { execution } from '@remix-project/remix-lib'
const { LogsManager } = execution
import { VmProxy } from './VmProxy'
import { VM } from '@ethereumjs/vm'
import { Common } from '@ethereumjs/common'
import { Trie } from '@ethereumjs/trie'
import { DefaultStateManager } from '@ethereumjs/statemanager'
import { DefaultStateManager, StateManager, EthersStateManager, EthersStateManagerOpts } from '@ethereumjs/statemanager'
import { StorageDump } from '@ethereumjs/statemanager/dist/interface'
import { EVM } from '@ethereumjs/evm'
import { EEI } from '@ethereumjs/vm'
@ -35,6 +37,44 @@ export interface DefaultStateManagerOpts {
prefixCodeHashes?: boolean
}
class CustomEthersStateManager extends EthersStateManager {
keyHashes: { [key: string]: string }
constructor (opts: EthersStateManagerOpts) {
super(opts)
this.keyHashes = {}
}
putContractStorage (address, key, value) {
this.keyHashes[bufferToHex(key).replace('0x', '')] = hash.keccak(key).toString('hex')
return super.putContractStorage(address, key, value)
}
copy(): CustomEthersStateManager {
const newState = new CustomEthersStateManager({
provider: (this as any).provider,
blockTag: BigInt((this as any).blockTag),
})
;(newState as any).contractCache = new Map((this as any).contractCache)
;(newState as any).storageCache = new Map((this as any).storageCache)
;(newState as any)._cache = this._cache
;(newState as any).keyHashes = this.keyHashes
return newState
}
async dumpStorage(address: Address): Promise<StorageDump> {
const storageDump = {}
const storage = await super.dumpStorage(address)
for (const key of Object.keys(storage)) {
const value = storage[key]
storageDump['0x' + this.keyHashes[key]] = {
key: '0x' + key,
value: value
}
}
return storageDump
}
}
/*
extend vm state manager and instanciate VM
*/
@ -86,7 +126,7 @@ class StateManagerCommonStorageDump extends DefaultStateManager {
export type CurrentVm = {
vm: VM,
web3vm: VmProxy,
stateManager: StateManagerCommonStorageDump,
stateManager: StateManager,
common: Common
}
@ -105,12 +145,15 @@ export class VMContext {
web3vm: VmProxy
logsManager: any // LogsManager
exeResults: Record<string, Transaction>
nodeUrl: string
blockNumber: number | 'latest'
constructor (fork?) {
constructor (fork?: string, nodeUrl?: string, blockNumber?: number | 'latest') {
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = fork || 'london'
this.nodeUrl = nodeUrl
this.blockNumber = blockNumber
this.blocks = {}
this.latestBlockNumber = "0x0"
this.blockByTxHash = {}
@ -124,7 +167,21 @@ export class VMContext {
}
async createVm (hardfork) {
const stateManager = new StateManagerCommonStorageDump()
let stateManager: StateManager
if (this.nodeUrl) {
let block = this.blockNumber
if (this.blockNumber === 'latest') {
const provider = new ethers.providers.StaticJsonRpcProvider(this.nodeUrl)
block = await provider.getBlockNumber()
}
stateManager = new CustomEthersStateManager({
provider: this.nodeUrl,
blockTag: BigInt(block)
})
} else
stateManager = new StateManagerCommonStorageDump()
const common = new Common({ chain: 'mainnet', hardfork })
const blockchain = new (Blockchain as any)({ common })
const eei = new EEI(stateManager, common, blockchain)

@ -3,19 +3,33 @@ import { ModalDialog, ModalDialogProps, ValidationResult } from '@remix-ui/modal
import { ModalTypes } from '../../types'
interface ModalWrapperProps extends ModalDialogProps {
modalType?: ModalTypes
defaultValue?: string
modalType?: ModalTypes
defaultValue?: string
}
const ModalWrapper = (props: ModalWrapperProps) => {
const [state, setState] = useState<ModalDialogProps>()
const ref = useRef()
const formRef = useRef()
const data = useRef()
const getFormData = () => {
if (formRef.current) {
const formData = new FormData(formRef.current)
const data = {}
for (const pair of formData.entries()) {
data[pair[0]] = pair[1]
}
return data
}
}
const onFinishPrompt = async () => {
if (ref.current === undefined) {
if (ref.current === undefined && formRef.current === undefined) {
onOkFn()
} else {
} else if (formRef.current) {
(props.okFn) ? props.okFn(getFormData()) : props.resolve(getFormData())
} else if(ref.current) {
// @ts-ignore: Object is possibly 'null'.
(props.okFn) ? props.okFn(ref.current.value) : props.resolve(ref.current.value)
}
@ -43,9 +57,29 @@ const ModalWrapper = (props: ModalWrapperProps) => {
<>
{props.message}
<input onChange={onInputChanged} type={props.modalType === ModalTypes.password ? 'password' : 'text'} defaultValue={defaultValue} data-id="modalDialogCustomPromp" ref={ref} className="form-control" />
{!validation.valid && <span className='text-warning'>{validation.message}</span>}
</>
)
{validation && !validation.valid && <span className='text-warning'>{validation.message}</span>}
</>
)
}
const onFormChanged = () => {
if (props.validationFn) {
const validation = props.validationFn(getFormData())
setState(prevState => {
return { ...prevState, message: createForm(validation), validation }
})
}
}
const createForm = (validation: ValidationResult) => {
return (
<>
<form onChange={onFormChanged} ref={formRef}>
{props.message}
</form>
{validation && !validation.valid && <span className='text-warning'>{validation.message}</span>}
</>
)
}
useEffect(() => {
@ -61,6 +95,14 @@ const ModalWrapper = (props: ModalWrapperProps) => {
message: createModalMessage(props.defaultValue, { valid: true })
})
break
case ModalTypes.form:
setState({
...props,
okFn: onFinishPrompt,
cancelFn: onCancelFn,
message: createForm({ valid: true })
})
break
default:
setState({
...props,

@ -4,4 +4,5 @@ export const enum ModalTypes {
prompt = 'prompt',
password = 'password',
default = 'default',
form = 'form',
}

@ -37,50 +37,56 @@ function HomeTabFeatured() {
dotListClass="position-relative mt-2"
>
<div className="mx-1 px-1 d-flex">
<img src={"assets/img/bgRemi_small.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
<div className="h6 w-50 p-4" style={{ flex: "1" }}>
<a href="https://remix-project.org" target="__blank">
<img src={"assets/img/bgRemi_small.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
</a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: "1" }}>
<h5><FormattedMessage id='home.jumpIntoWeb3' /></h5>
<div><FormattedMessage id='home.jumpIntoWeb3Text'/></div>
<div style={{ fontSize: '0.8rem' }} className="mb-3"><FormattedMessage id='home.jumpIntoWeb3Text'/></div>
<a
className="remixui_home_text btn btn-secondary mt-2 text-decoration-none mb-3"
className="remixui_home_text btn-sm btn-secondary mt-2 text-decoration-none mb-3"
onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'jumpIntoWeb3'])}
target="__blank"
href="https://remix-project.org"><FormattedMessage
id='home.jumpIntoWeb3More'
/></a>
href="https://remix-project.org"
>
<FormattedMessage id='home.jumpIntoWeb3More'/>
</a>
</div>
</div>
<div className="mx-1 px-1 d-flex">
<a href="https://www.youtube.com/@EthereumRemix/videos" target="__blank">
<a href="https://www.youtube.com/@EthereumRemix/videos" target="__blank">
<img src={"/assets/img/YouTubeLogo.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
</a>
<div className="h6 w-50 p-4" style={{ flex: "1" }}>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: "1" }}>
<h5><FormattedMessage id='home.remixYouTube' /></h5>
<p style={{ fontStyle: 'italic' }}><FormattedMessage id='home.remixYouTubeText1' /></p>
<div><FormattedMessage id='home.remixYouTubeText2' /></div>
<p style={{ fontStyle: 'italic', fontSize: '1rem' }}><FormattedMessage id='home.remixYouTubeText1' /></p>
<div style={{ fontSize: '0.8rem' }} className="mb-3"><FormattedMessage id='home.remixYouTubeText2' /></div>
<a
className="remixui_home_text btn btn-secondary mt-2 text-decoration-none mb-3"
className="remixui_home_text btn-sm btn-secondary mt-2 text-decoration-none mb-3"
onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'youTubeMore'])}
target="__blank"
href="https://www.youtube.com/@EthereumRemix/videos"><FormattedMessage
id='home.remixYouTubeMore'
/></a>
href="https://www.youtube.com/@EthereumRemix/videos"
>
<FormattedMessage id='home.remixYouTubeMore' />
</a>
</div>
</div>
<div className="mx-1 px-1 d-flex">
<img src={"/assets/img/remixRewardBetaTester_small.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
<div className="h6 w-50 p-4" style={{ flex: "1" }}>
<a href="https://docs.google.com/forms/d/e/1FAIpQLSd0WsJnKbeJo-BGrnf7WijxAdmE4PnC_Z4M0IApbBfHLHZdsQ/viewform" target="__blank">
<img src={"/assets/img/remixRewardBetaTester_small.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
</a>
<div className="h6 w-50 p-2 pl-4 align-self-center" style={{ flex: "1" }}>
<h5><FormattedMessage id='home.betaTesting' /></h5>
<p style={{ fontStyle: 'italic' }}><FormattedMessage id='home.betaTestingText1' /></p>
<div><FormattedMessage id='home.betaTestingText2' /></div>
<p style={{ fontStyle: 'italic', fontSize: '1rem' }}><FormattedMessage id='home.betaTestingText1' /></p>
<div style={{ fontSize: '0.8rem' }} className="mb-3"><FormattedMessage id='home.betaTestingText2' /></div>
<a
className="remixui_home_text btn btn-secondary mt-2 text-decoration-none mb-3"
className="remixui_home_text btn-sm btn-secondary mt-2 text-decoration-none mb-3"
onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'betatesting'])}
target="__blank"
href="https://docs.google.com/forms/d/e/1FAIpQLSd0WsJnKbeJo-BGrnf7WijxAdmE4PnC_Z4M0IApbBfHLHZdsQ/viewform"><FormattedMessage
id='home.betaTestingMore'
/></a>
href="https://docs.google.com/forms/d/e/1FAIpQLSd0WsJnKbeJo-BGrnf7WijxAdmE4PnC_Z4M0IApbBfHLHZdsQ/viewform"
>
<FormattedMessage id='home.betaTestingMore' />
</a>
</div>
</div>
</Carousel>

@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl'
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
const _paq = window._paq = window._paq || [] // eslint-disable-line
import { CustomTooltip } from '@remix-ui/helper';
interface HomeTabFileProps {
plugin: any
@ -153,17 +154,27 @@ function HomeTabFile ({plugin}: HomeTabFileProps) {
</div>
</ModalDialog>
<Toaster message={state.toasterMsg} />
<div className="justify-content-start mt-1 p-2 border-bottom d-flex flex-column" id="hTFileSection">
<label style={{fontSize: "1rem"}}><FormattedMessage id='home.files' /></label>
<button className="btn btn-primary p-2 border my-1" data-id="homeTabNewFile" style={{width: 'fit-content'}} onClick={() => createNewFile()}><FormattedMessage id='home.newFile' /></button>
<label className="btn p-2 border my-1" style={{width: 'fit-content'}} htmlFor="openFileInput"><FormattedMessage id='home.openFile' /></label>
<input title="open file" type="file" id="openFileInput" onChange={(event) => {
event.stopPropagation()
plugin.verticalIcons.select('filePanel')
uploadFile(event.target)
}} multiple />
<button className="btn p-2 border my-1" style={{width: 'fit-content'}} onClick={() => connectToLocalhost()}><FormattedMessage id='home.connectToLocalhost' /></button>
<label className="pt-2"><FormattedMessage id='home.loadFrom' /></label>
<div className="justify-content-start mt-1 p-2 d-flex flex-column" id="hTFileSection">
<label style={{fontSize: "1.2rem"}}><FormattedMessage id='home.files' /></label>
<div className="dflex">
<button className="btn btn-primary p-2 mr-2 border my-1" data-id="homeTabNewFile" style={{width: 'fit-content'}} onClick={() => createNewFile()}><FormattedMessage id='home.newFile' /></button>
<label className="btn p-2 mr-2 border my-1" style={{width: 'fit-content', cursor: 'pointer'}} htmlFor="openFileInput"><FormattedMessage id='home.openFile' /></label>
<input title="open file" type="file" id="openFileInput" onChange={(event) => {
event.stopPropagation()
plugin.verticalIcons.select('filePanel')
uploadFile(event.target)
}} multiple />
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={"Connect to Localhost"}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button className="btn p-2 border my-1" style={{width: 'fit-content'}} onClick={() => connectToLocalhost()}><FormattedMessage id='home.connectToLocalhost' /></button>
</CustomTooltip>
</div>
<label style={{fontSize: "0.8rem"}} className="pt-2"><FormattedMessage id='home.loadFrom' /></label>
<div className="d-flex">
<button
className="btn p-2 border mr-2"

@ -68,7 +68,7 @@ function HomeTabGetStarted ({plugin}: HomeTabGetStartedProps) {
return (
<div className="pl-2" id="hTGetStartedSection">
<label style={{fontSize: "1.2rem"}}>
<span className="mr-2" style={{fontWeight: "bold"}}>
<span className="mr-2">
<FormattedMessage id="home.getStarted" />
</span>
- <FormattedMessage id="home.projectTemplates" />

@ -2,6 +2,7 @@
import React, { useEffect, useState, useContext } from 'react'
import { FormattedMessage } from 'react-intl'
import { ThemeContext } from '../themeContext'
import { CustomTooltip } from '@remix-ui/helper'
declare global {
interface Window {
_paq: any
@ -27,10 +28,6 @@ function HomeTabLearn ({plugin}: HomeTabLearnProps) {
const themeFilter = useContext(ThemeContext)
const openLink = () => {
window.open("https://remix-ide.readthedocs.io/en/latest/remix_tutorials_learneth.html?highlight=learneth#learneth-tutorial-repos", '_blank')
}
const startLearnEthTutorial = async (tutorial: 'basics' | 'soliditybeginner' | 'deploylibraries') => {
await plugin.appManager.activatePlugin(['solidity', 'LearnEth', 'solidityUnitTesting'])
plugin.verticalIcons.select('LearnEth')
@ -52,17 +49,25 @@ function HomeTabLearn ({plugin}: HomeTabLearnProps) {
return (
<div className="d-flex px-2 pb-2 pt-2 d-flex flex-column" id="hTLearnSection">
<div className="d-flex justify-content-between">
<label className="py-2 align-self-center m-0" style={{fontSize: "1.2rem"}}>
<label className="py-2 pt-3 align-self-center m-0" style={{fontSize: "1.2rem"}}>
<FormattedMessage id="home.learn" />
</label>
<button
onClick={ async () => {
await goToLearnEthHome()
}}
className="h-100 px-2 pt-0 btn"
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={"See all tutorials"}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<img className="align-self-center" src="assets/img/learnEthLogo.webp" alt="" style={ { filter: themeFilter.filter, width: "1rem", height: "1ren" } } />
</button>
<button
onClick={ async () => {
await goToLearnEthHome()
}}
className="h-100 px-2 pt-0 btn"
>
<img className="align-self-center" src="assets/img/learnEthLogo.webp" alt="" style={ { filter: themeFilter.filter, width: "1rem", height: "1ren" } } />
</button>
</CustomTooltip>
</div>
<div className="d-flex flex-column">
<label className="d-flex flex-column btn border" onClick={() => setState((prevState) => {return { ...prevState, visibleTutorial: VisibleTutorial.Basics }})}>
@ -70,7 +75,7 @@ function HomeTabLearn ({plugin}: HomeTabLearnProps) {
<FormattedMessage id="home.learnEth1" />
</label>
{(state.visibleTutorial === VisibleTutorial.Basics) && <div className="pt-2 d-flex flex-column text-left">
<span>
<span className="py-1" style={{fontSize: "0.8rem"}}>
<FormattedMessage id="home.learnEth1Desc" />
</span>
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('basics')}>
@ -83,11 +88,12 @@ function HomeTabLearn ({plugin}: HomeTabLearnProps) {
<FormattedMessage id="home.learnEth2" />
</label>
{(state.visibleTutorial === VisibleTutorial.Intermediate) && <div className="pt-2 d-flex flex-column text-left">
<span>
<FormattedMessage id="home.learnEth2Desc" /></span>
<span className="py-1" style={{fontSize: "0.8rem"}}>
<FormattedMessage id="home.learnEth2Desc" />
</span>
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('soliditybeginner')}>
<FormattedMessage id="home.getStarted" />
</button>
<FormattedMessage id="home.getStarted" />
</button>
</div>}
</label>
<label className="d-flex flex-column btn border" onClick={() => setState((prevState) => {return { ...prevState, visibleTutorial: VisibleTutorial.Advanced }})}>
@ -95,11 +101,12 @@ function HomeTabLearn ({plugin}: HomeTabLearnProps) {
<FormattedMessage id="home.remixAdvanced" />
</label>
{(state.visibleTutorial === VisibleTutorial.Advanced) && <div className="pt-2 d-flex flex-column text-left">
<span>
<FormattedMessage id="home.remixAdvancedDesc" /></span>
<span className="py-1" style={{fontSize: "0.8rem"}}>
<FormattedMessage id="home.remixAdvancedDesc" />
</span>
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('deploylibraries')}>
<FormattedMessage id="home.getStarted" />
</button>
<FormattedMessage id="home.getStarted" />
</button>
</div>}
</label>
</div>

@ -8,27 +8,28 @@ function HomeTabScamAlert () {
return (
<div className="" id="hTScamAlertSection">
<label className="pl-2 text-danger" style={{fontSize: "1.2rem"}}><FormattedMessage id='home.scamAlert' /></label>
<div className="py-2 ml-2 mb-1 align-self-end mb-2 d-flex flex-column border border-danger">
<span className="pl-4 mt-2">
<i className="pr-2 text-danger fas fa-exclamation-triangle"></i>
<b><FormattedMessage id='home.scamAlert' />:</b>
</span>
<span className="pl-4 mt-1">
<FormattedMessage id='home.scamAlertText' />
</span>
<span className="pl-4 mt-1">
<FormattedMessage id='home.scamAlertText2' />:
<a className="pl-2 remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'scamAlert', 'learnMore'])} target="__blank" href="https://medium.com/remix-ide/remix-in-youtube-crypto-scams-71c338da32d">
<FormattedMessage id='home.learnMore' />
</a>
</span>
<span className="pl-4 mt-1">
<FormattedMessage id='home.scamAlertText3' />
: &nbsp;
<a className="remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'scamAlert', 'safetyTips'])} target="__blank" href="https://remix-ide.readthedocs.io/en/latest/security.html">
<FormattedMessage id='home.here' />
</a>
<div className="py-2 ml-2 mb-1 align-self-end mb-2 d-flex border border-danger">
<span className="align-self-center pl-4 mt-1">
<i style={{fontSize: 'xxx-large', fontWeight: 'lighter'}} className="pr-2 text-danger far fa-exclamation-triangle"></i>
</span>
<div className='d-flex flex-column'>
<span className="pl-4 mt-1">
<FormattedMessage id='home.scamAlertText' />
</span>
<span className="pl-4 mt-1">
<FormattedMessage id='home.scamAlertText2' />:
<a className="pl-2 remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'scamAlert', 'learnMore'])} target="__blank" href="https://medium.com/remix-ide/remix-in-youtube-crypto-scams-71c338da32d">
<FormattedMessage id='home.learnMore' />
</a>
</span>
<span className="pl-4 mt-1">
<FormattedMessage id='home.scamAlertText3' />
: &nbsp;
<a className="remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'scamAlert', 'safetyTips'])} target="__blank" href="https://remix-ide.readthedocs.io/en/latest/security.html">
<FormattedMessage id='home.here' />
</a>
</span>
</div>
</div>
</div>
)

@ -52,19 +52,22 @@ function HomeTabTitle() {
return (
<div className="px-2 pb-2 pt-2 d-flex flex-column border-bottom" id="hTTitleSection">
<div className="mr-4 d-flex">
<div onClick={() => playRemi()} style={{ filter: themeFilter.filter}} >
<BasicLogo classList="align-self-end remixui_home_logoImg" solid={false} />
</div>
<audio
id="remiAudio"
muted={false}
src="assets/audio/remiGuitar-single-power-chord-A-minor.mp3"
ref={remiAudioEl}
></audio>
</div>
<div className="d-flex justify-content-between">
<div className="d-flex py-2 justify-content-between">
<div className='d-flex justify-content-start'>
<span className="h-80 text-uppercase" style={{ fontSize: 'xx-large', fontFamily: "Noah, sans-serif" }}>Remix</span>
<div className="ml-2 d-flex">
<div onClick={() => playRemi()} >
<img className="" src="assets/img/guitarRemiCroped.webp" style={{height: "3rem"}} alt=""></img>
</div>
<audio
id="remiAudio"
muted={false}
src="assets/audio/remiGuitar-single-power-chord-A-minor.mp3"
ref={remiAudioEl}
></audio>
</div>
</div>
<span>
<CustomTooltip
placement={'top'}
@ -94,7 +97,7 @@ function HomeTabTitle() {
openLink("https://twitter.com/EthereumRemix")
_paq.push(['trackEvent', 'hometab', 'socialMedia', 'twitter'])
}}
className="border-0 h-100 pl-2 btn fab fa-twitter">
className="border-0 p-2 h-100 pl-2 btn fab fa-twitter">
</button>
</CustomTooltip>
@ -110,7 +113,7 @@ function HomeTabTitle() {
openLink("https://www.linkedin.com/company/ethereum-remix/")
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'linkedin'])
}}
className="border-0 h-100 pl-2 btn fa fa-linkedin">
className="border-0 p-2 h-100 pl-2 btn fa fa-linkedin">
</button>
</CustomTooltip>
@ -126,7 +129,7 @@ function HomeTabTitle() {
openLink("https://medium.com/remix-ide")
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'medium'])
}}
className="border-0 h-100 pl-2 btn fab fa-medium">
className="border-0 p-2 h-100 pl-2 btn fab fa-medium">
</button>
</CustomTooltip>
@ -142,12 +145,12 @@ function HomeTabTitle() {
openLink("https://gitter.im/ethereum/remix")
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'gitter'])
}}
className="border-0 h-100 pl-2 btn fab fa-gitter">
className="border-0 h-100 p-2 btn fab fa-gitter">
</button>
</CustomTooltip>
</span>
</div>
<b className="pb-1 text-dark" style={{ fontStyle: 'italic' }}>
<b className="py-1 text-dark" style={{ fontStyle: 'italic' }}>
<FormattedMessage id="home.nativeIDE" />
</b>
<div className="pb-1" id="hTGeneralLinks">

@ -45,18 +45,20 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => {
}, [])
return (
<div className="d-flex flex-row w-100" data-id="remixUIHTAll">
<div className="d-flex flex-column w-100" data-id="remixUIHTAll">
<ThemeContext.Provider value={ state.themeQuality }>
<div className="px-2 pl-3 justify-content-start d-flex border-right flex-column" id="remixUIHTLeft" style={{flex: 2, minWidth: "35%"}}>
<HomeTabTitle />
<HomeTabFile plugin={plugin} />
<HomeTabLearn plugin={plugin} />
</div>
<div className="pl-2 pr-3 justify-content-start d-flex flex-column" style={{width: "65%"}} id="remixUIHTRight">
<HomeTabFeatured></HomeTabFeatured>
<HomeTabGetStarted plugin={plugin}></HomeTabGetStarted>
<HomeTabFeaturedPlugins plugin={plugin}></HomeTabFeaturedPlugins>
<HomeTabScamAlert></HomeTabScamAlert>
<div className='d-flex flex-row w-100'>
<div className="px-2 pl-3 justify-content-start d-flex border-right flex-column" id="remixUIHTLeft" style={{ width: 'inherit' }}>
<HomeTabTitle />
<HomeTabFile plugin={plugin} />
<HomeTabLearn plugin={plugin} />
</div>
<div className="pl-2 pr-3 justify-content-start d-flex flex-column" style={{width: "65%"}} id="remixUIHTRight">
<HomeTabFeatured></HomeTabFeatured>
<HomeTabGetStarted plugin={plugin}></HomeTabGetStarted>
<HomeTabFeaturedPlugins plugin={plugin}></HomeTabFeaturedPlugins>
<HomeTabScamAlert></HomeTabScamAlert>
</div>
</div>
</ThemeContext.Provider>
</div>

@ -9,7 +9,7 @@ export interface ModalDialogProps {
timestamp?: number,
title?: string | JSX.Element,
validation?: ValidationResult
validationFn?: (value: string) => ValidationResult
validationFn?: (value: any) => ValidationResult
message?: string | JSX.Element,
okLabel?: string | JSX.Element,
okFn?: (value?:any) => void,

@ -28,7 +28,7 @@ function _traverse(graph, visited, ast, name) {
for (const dependency of dependencies) {
const path = resolve(name, dependency);
if (path in visited) {
continue;
// continue; // fixes wrong ordering of source in flattened file
}
visited[path] = 1;
graph.add(name, path);

@ -27,7 +27,7 @@ export const contextMenuActions: MenuItems = [{
},{
id: 'download',
name: 'Download',
type: ['file'],
type: ['file', 'folder'],
multiselect: false,
label: ''
}, {

@ -19,7 +19,7 @@
"target": "ES2015",
"module": "ES2020",
"typeRoots": ["node_modules/@types", "libs/**/*.d.ts"],
"lib": ["es2017", "es2019", "dom"],
"lib": ["es2017", "es2019", "dom", "dom.iterable"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": "."

@ -15,7 +15,8 @@
"module": "CommonJS",
"lib": [
"es2017",
"dom"
"dom",
"dom.iterable"
],
"skipLibCheck": true,
"skipDefaultLibCheck": true,

Loading…
Cancel
Save