Merge branch 'master' of https://github.com/ethereum/remix-project into indexdbpackupdate

pull/1647/head
filip mertens 3 years ago
commit bd502b6b44
  1. 10
      apps/remix-ide-e2e/src/commands/journalChildIncludes.ts
  2. 84
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  3. 2
      apps/remix-ide-e2e/src/types/index.d.ts
  4. 13
      apps/remix-ide/src/app/editor/editor.js
  5. 15
      apps/remix-ide/src/app/panels/tab-proxy.js
  6. 20
      apps/remix-ide/src/app/panels/terminal.js
  7. 9
      apps/remix-ide/src/app/tabs/theme-module.js
  8. 15
      libs/remix-lib/src/execution/txRunnerVM.ts
  9. 70
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  10. 1384
      libs/remix-ui/editor/src/lib/syntax.ts
  11. 5
      libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx
  12. 2
      libs/remix-ui/terminal/src/lib/components/Context.tsx
  13. 2
      libs/remix-ui/terminal/src/lib/components/RenderKnownTransactions.tsx
  14. 232
      libs/remix-ui/terminal/src/lib/components/Table.tsx
  15. 19
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  16. 3
      libs/remix-ui/terminal/src/lib/types/terminalTypes.ts

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

@ -118,6 +118,32 @@ module.exports = {
.waitForElementContainsText('*[data-id="terminalJournal"]', 'newOwner', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'newOwner', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
}, },
'Should print hardhat logs': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.addFile('printHardhatlog.sol', { content: hardhatLog })
.clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.testContracts('printHardhatlog.sol', { content: hardhatLog }, ['OwnerTest'])
.clickLaunchIcon('udapp')
.click('*[data-id="deployAndRunClearInstances"]')
.selectContract('OwnerTest')
.createContract('')
.pause(1000)
.journalChildIncludes('constructor', { shouldHaveOnlyOneOccurence: true })
.pause(5000)
.click('*[data-id="terminalClearConsole"]') // clear the terminal
.clickInstance(0)
.clickFunction('changeOwner - transact (not payable)', { types: 'address newOwner', values: '0xd9145CCE52D386f254917e481eB44e9943F39138' })
.pause(1000)
.journalChildIncludes('inside changeOwner', { shouldHaveOnlyOneOccurence: true })
.clickFunction('getOwner - call')
.pause(1000)
.journalChildIncludes('inside getOwner', { shouldHaveOnlyOneOccurence: true })
},
'Should display auto-complete menu': function (browser: NightwatchBrowser) { 'Should display auto-complete menu': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="terminalCli"]') .waitForElementVisible('*[data-id="terminalCli"]')
@ -233,3 +259,61 @@ const deployWithEthersJs = `
console.log(e.message) console.log(e.message)
} }
})()` })()`
const hardhatLog = `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "hardhat/console.sol";
/**
* @title Owner
* @dev Set & change owner
*/
contract OwnerTest {
address private owner;
// event for EVM logging
event OwnerSet(address indexed oldOwner, address indexed newOwner);
// modifier to check if caller is owner
modifier isOwner() {
// If the first argument of 'require' evaluates to 'false', execution terminates and all
// changes to the state and to Ether balances are reverted.
// This used to consume all gas in old EVM versions, but not anymore.
// It is often a good idea to use 'require' to check if functions are called correctly.
// As a second argument, you can also provide an explanation about what went wrong.
require(msg.sender == owner, "Caller is not owner");
_;
}
/**
* @dev Set contract deployer as owner
*/
constructor() {
console.log("constructor");
owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor
emit OwnerSet(address(0), owner);
}
/**
* @dev Change owner
* @param newOwner address of new owner
*/
function changeOwner(address newOwner) public isOwner {
console.log("inside changeOwner");
emit OwnerSet(owner, newOwner);
owner = newOwner;
}
/**
* @dev Return owner address
* @return address of owner
*/
function getOwner() external view returns (address) {
console.log("inside getOwner");
return owner;
}
}`

@ -23,7 +23,7 @@ declare module 'nightwatch' {
journalLastChildIncludes(val: string): NightwatchBrowser, journalLastChildIncludes(val: string): NightwatchBrowser,
executeScript(script: string): NightwatchBrowser, executeScript(script: string): NightwatchBrowser,
clearEditableContent(cssSelector: string): NightwatchBrowser, clearEditableContent(cssSelector: string): NightwatchBrowser,
journalChildIncludes(val: string): NightwatchBrowser, journalChildIncludes(val: string, opts = { shouldHaveOnlyOneOccurence: boolean }): NightwatchBrowser,
debugTransaction(index: number): NightwatchBrowser, debugTransaction(index: number): NightwatchBrowser,
checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser, checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser,
openFile(name: string): NightwatchBrowser, openFile(name: string): NightwatchBrowser,

@ -97,7 +97,7 @@ class Editor extends Plugin {
this.emit(name, ...params) // plugin stack this.emit(name, ...params) // plugin stack
} }
onActivation () { async onActivation () {
this.activated = true this.activated = true
this.on('sidePanel', 'focusChanged', (name) => { this.on('sidePanel', 'focusChanged', (name) => {
this.keepDecorationsFor(name, 'sourceAnnotationsPerFile') this.keepDecorationsFor(name, 'sourceAnnotationsPerFile')
@ -108,14 +108,15 @@ class Editor extends Plugin {
}) })
const translateTheme = (theme) => this._themes[theme.name === 'Dark' ? 'remixDark' : theme.quality] const translateTheme = (theme) => this._themes[theme.name === 'Dark' ? 'remixDark' : theme.quality]
this.on('theme', 'themeChanged', (theme) => { this.on('theme', 'themeLoaded', (theme) => {
this.currentTheme = translateTheme(theme)
this.renderComponent()
})
this.call('theme', 'currentTheme', (theme) => {
this.currentTheme = translateTheme(theme) this.currentTheme = translateTheme(theme)
this.renderComponent() this.renderComponent()
}) })
try {
this.currentTheme = translateTheme(await this.call('theme', 'currentTheme'))
} catch (e) {
console.log('unable to select the theme ' + e.message)
}
this.renderComponent() this.renderComponent()
} }

@ -191,13 +191,6 @@ export class TabProxy extends Plugin {
} }
} }
switchToActiveTab () {
const active = this.tabsApi.active()
if (active && this._handlers[active]) {
this.switchTab(active)
}
}
renameTab (oldName, newName) { renameTab (oldName, newName) {
this.addTab(newName, '', () => { this.addTab(newName, '', () => {
this.fileManager.open(newName) this.fileManager.open(newName)
@ -271,10 +264,14 @@ export class TabProxy extends Plugin {
removeTab (name) { removeTab (name) {
delete this._handlers[name] delete this._handlers[name]
this.switchToActiveTab() let previous = null
this.loadedTabs = this.loadedTabs.filter(tab => tab.name !== name) this.loadedTabs = this.loadedTabs.filter((tab, index) => {
if (tab.name === name) previous = this.loadedTabs[index - 1]
return tab.name !== name
})
this.renderComponent() this.renderComponent()
this.updateImgStyles() this.updateImgStyles()
if (previous) this.switchTab(previous.name)
} }
addHandler (type, fn) { addHandler (type, fn) {

@ -86,8 +86,6 @@ class Terminal extends Plugin {
this.call('menuicons', 'select', 'debugger') this.call('menuicons', 'select', 'debugger')
this.call('debugger', 'debug', hash) this.call('debugger', 'debug', hash)
}) })
this.logHtmlResponse = []
this.logResponse = []
} }
onActivation () { onActivation () {
@ -102,23 +100,11 @@ class Terminal extends Plugin {
} }
logHtml (html) { logHtml (html) {
this.logHtmlResponse.push(html.innerText) this.terminalApi.logHtml(html)
this.renderComponent()
this.resetLogHtml()
}
resetLogHtml () {
this.logHtmlResponse = []
} }
log (message) { log (message) {
this.logResponse.push(message) this.terminalApi.log(message)
this.renderComponent()
this.resetLog()
}
resetLog () {
this.logResponse = []
} }
render () { render () {
@ -126,9 +112,11 @@ class Terminal extends Plugin {
} }
renderComponent () { renderComponent () {
const onReady = (api) => { this.terminalApi = api }
ReactDOM.render( ReactDOM.render(
<RemixUiTerminal <RemixUiTerminal
plugin={this} plugin={this}
onReady={onReady}
/>, />,
this.element this.element
) )

@ -79,10 +79,17 @@ export class ThemeModule extends Plugin {
throw new Error(`Theme ${themeName} doesn't exist`) throw new Error(`Theme ${themeName} doesn't exist`)
} }
const next = themeName || this.active // Name const next = themeName || this.active // Name
if (next === this.active) return
_paq.push(['trackEvent', 'themeModule', 'switchTo', next]) _paq.push(['trackEvent', 'themeModule', 'switchTo', next])
const nextTheme = this.themes[next] // Theme const nextTheme = this.themes[next] // Theme
if (!this.forced) this._deps.config.set('settings/theme', next) if (!this.forced) this._deps.config.set('settings/theme', next)
document.getElementById('theme-link').setAttribute('href', nextTheme.url) document.getElementById('theme-link').remove()
const theme = yo`<link rel="stylesheet" href="${nextTheme.url}" id="theme-link"/>`
theme.addEventListener('load', () => {
this.emit('themeLoaded', nextTheme)
this.events.emit('themeLoaded', nextTheme)
})
document.head.insertBefore(theme, document.head.firstChild)
document.documentElement.style.setProperty('--theme', nextTheme.quality) document.documentElement.style.setProperty('--theme', nextTheme.quality)
if (themeName) this.active = themeName if (themeName) this.active = themeName
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)

@ -15,6 +15,7 @@ export class TxRunnerVM {
blocks blocks
logsManager logsManager
commonContext commonContext
nextNonceForCall: number
getVMObject: () => any getVMObject: () => any
constructor (vmaccounts, api, getVMObject) { constructor (vmaccounts, api, getVMObject) {
@ -31,6 +32,13 @@ export class TxRunnerVM {
this.vmaccounts = vmaccounts this.vmaccounts = vmaccounts
this.queusTxs = [] this.queusTxs = []
this.blocks = [] this.blocks = []
/*
txHash is generated using the nonce,
in order to have unique transaction hash, we need to keep using different nonce (in case of a call)
so we increment this value after each call.
For this to function we also need to skip nonce validation, in the vm: `{ skipNonce: true }`
*/
this.nextNonceForCall = 0
} }
execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) { execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) {
@ -75,7 +83,7 @@ export class TxRunnerVM {
let tx let tx
if (!EIP1559) { if (!EIP1559) {
tx = Transaction.fromTxData({ tx = Transaction.fromTxData({
nonce: new BN(res.nonce), nonce: useCall ? this.nextNonceForCall : new BN(res.nonce),
gasPrice: '0x1', gasPrice: '0x1',
gasLimit: gasLimit, gasLimit: gasLimit,
to: to, to: to,
@ -84,7 +92,7 @@ export class TxRunnerVM {
}, { common: this.commonContext }).sign(account.privateKey) }, { common: this.commonContext }).sign(account.privateKey)
} else { } else {
tx = FeeMarketEIP1559Transaction.fromTxData({ tx = FeeMarketEIP1559Transaction.fromTxData({
nonce: new BN(res.nonce), nonce: useCall ? this.nextNonceForCall : new BN(res.nonce),
maxPriorityFeePerGas: '0x01', maxPriorityFeePerGas: '0x01',
maxFeePerGas: '0x1', maxFeePerGas: '0x1',
gasLimit: gasLimit, gasLimit: gasLimit,
@ -93,6 +101,7 @@ export class TxRunnerVM {
data: Buffer.from(data.slice(2), 'hex') data: Buffer.from(data.slice(2), 'hex')
}).sign(account.privateKey) }).sign(account.privateKey)
} }
if (useCall) this.nextNonceForCall++
const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e'] const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
const difficulties = [new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10)] const difficulties = [new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10)]
@ -127,7 +136,7 @@ export class TxRunnerVM {
} }
runBlockInVm (tx, block, callback) { runBlockInVm (tx, block, callback) {
this.getVMObject().vm.runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false }).then((results) => { this.getVMObject().vm.runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false, skipNonce: true }).then((results) => {
const result = results.results[0] const result = results.results[0]
if (result) { if (result) {
const status = result.execResult.exceptionError ? 0 : 1 const status = result.execResult.exceptionError ? 0 : 1

@ -1,6 +1,7 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import Editor from '@monaco-editor/react' import Editor from '@monaco-editor/react'
import { reducerActions, reducerListener, initialState } from './actions/editor' import { reducerActions, reducerListener, initialState } from './actions/editor'
import { language } from './syntax'
import './remix-ui-editor.css' import './remix-ui-editor.css'
@ -77,13 +78,39 @@ export const EditorUI = (props: EditorUIProps) => {
const [editorModelsState, dispatch] = useReducer(reducerActions, initialState) const [editorModelsState, dispatch] = useReducer(reducerActions, initialState)
const defineAndSetDarkTheme = (monaco) => {
// see https://microsoft.github.io/monaco-editor/playground.html#customizing-the-appearence-exposed-colors
const lightColor = window.getComputedStyle(document.documentElement).getPropertyValue('--light').trim()
const infoColor = window.getComputedStyle(document.documentElement).getPropertyValue('--info').trim()
const darkColor = window.getComputedStyle(document.documentElement).getPropertyValue('--dark').trim()
const grayColor = window.getComputedStyle(document.documentElement).getPropertyValue('--gray-dark').trim()
monaco.editor.defineTheme('remix-dark', {
base: 'vs-dark',
inherit: true, // can also be false to completely replace the builtin rules
rules: [
{ background: darkColor.replace('#', '') },
{ token: 'keyword.external', foreground: infoColor }
],
colors: {
'editor.background': darkColor,
'editorSuggestWidget.background': lightColor,
'editorSuggestWidget.selectedBackground': lightColor,
'editorSuggestWidget.highlightForeground': infoColor,
'editor.lineHighlightBorder': lightColor,
'editor.lineHighlightBackground': grayColor,
'editorGutter.background': lightColor
}
})
monacoRef.current.editor.setTheme('remix-dark')
}
useEffect(() => { useEffect(() => {
if (!monacoRef.current) return if (!monacoRef.current) return
monacoRef.current.editor.setTheme(props.theme) if (props.theme === 'remix-dark') {
defineAndSetDarkTheme(monacoRef.current)
} else monacoRef.current.editor.setTheme(props.theme)
}, [props.theme]) }, [props.theme])
if (monacoRef.current) monacoRef.current.editor.setTheme(props.theme)
const setAnnotationsbyFile = (uri) => { const setAnnotationsbyFile = (uri) => {
if (props.sourceAnnotationsPerFile[uri]) { if (props.sourceAnnotationsPerFile[uri]) {
const model = editorModelsState[uri]?.model const model = editorModelsState[uri]?.model
@ -137,8 +164,10 @@ export const EditorUI = (props: EditorUIProps) => {
useEffect(() => { useEffect(() => {
if (!editorRef.current) return if (!editorRef.current) return
currentFileRef.current = props.currentFile currentFileRef.current = props.currentFile
editorRef.current.setModel(editorModelsState[props.currentFile].model) const file = editorModelsState[props.currentFile]
editorRef.current.setModel(file.model)
editorRef.current.updateOptions({ readOnly: editorModelsState[props.currentFile].readOnly }) editorRef.current.updateOptions({ readOnly: editorModelsState[props.currentFile].readOnly })
if (file.language === 'sol') monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity')
setAnnotationsbyFile(props.currentFile) setAnnotationsbyFile(props.currentFile)
setMarkerbyFile(props.currentFile) setMarkerbyFile(props.currentFile)
}, [props.currentFile]) }, [props.currentFile])
@ -207,7 +236,9 @@ export const EditorUI = (props: EditorUIProps) => {
function handleEditorDidMount (editor) { function handleEditorDidMount (editor) {
editorRef.current = editor editorRef.current = editor
monacoRef.current.editor.setTheme(props.theme) if (props.theme === 'remix-dark') {
defineAndSetDarkTheme(monacoRef.current)
} else monacoRef.current.editor.setTheme(props.theme)
reducerListener(props.plugin, dispatch, monacoRef.current, editorRef.current, props.events) reducerListener(props.plugin, dispatch, monacoRef.current, editorRef.current, props.events)
props.events.onEditorMounted() props.events.onEditorMounted()
editor.onMouseUp((e) => { editor.onMouseUp((e) => {
@ -215,29 +246,20 @@ export const EditorUI = (props: EditorUIProps) => {
(window as any).addRemixBreakpoint(e.target.position) (window as any).addRemixBreakpoint(e.target.position)
} }
}) })
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.US_EQUAL, () => {
editor.updateOptions({ fontSize: editor.getOption(42).fontSize + 1 })
})
editor.addCommand(monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.US_MINUS, () => {
editor.updateOptions({ fontSize: editor.getOption(42).fontSize - 1 })
})
} }
function handleEditorWillMount (monaco) { function handleEditorWillMount (monaco) {
monacoRef.current = monaco monacoRef.current = monaco
// see https://microsoft.github.io/monaco-editor/playground.html#customizing-the-appearence-exposed-colors // Register a new language
const lightColor = window.getComputedStyle(document.documentElement).getPropertyValue('--light').trim() monacoRef.current.languages.register({ id: 'remix-solidity' })
const infoColor = window.getComputedStyle(document.documentElement).getPropertyValue('--info').trim() // Register a tokens provider for the language
const darkColor = window.getComputedStyle(document.documentElement).getPropertyValue('--dark').trim() monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', language)
const grayColor = window.getComputedStyle(document.documentElement).getPropertyValue('--gray-dark').trim()
monaco.editor.defineTheme('remix-dark', {
base: 'vs-dark',
inherit: true, // can also be false to completely replace the builtin rules
rules: [{ background: darkColor.replace('#', '') }],
colors: {
'editor.background': darkColor,
'editorSuggestWidget.background': lightColor,
'editorSuggestWidget.selectedBackground': lightColor,
'editorSuggestWidget.highlightForeground': infoColor,
'editor.lineHighlightBorder': lightColor,
'editor.lineHighlightBackground': grayColor,
'editorGutter.background': lightColor
}
})
} }
return ( return (

File diff suppressed because it is too large Load Diff

@ -35,10 +35,10 @@ export const TabsUI = (props: TabsUIProps) => {
const classNameImg = 'my-1 mr-1 text-dark ' + tab.iconClass const classNameImg = 'my-1 mr-1 text-dark ' + tab.iconClass
const classNameTab = 'nav-item nav-link d-flex justify-content-center align-items-center px-2 py-1 tab' + (index === currentIndexRef.current ? ' active' : '') const classNameTab = 'nav-item nav-link d-flex justify-content-center align-items-center px-2 py-1 tab' + (index === currentIndexRef.current ? ' active' : '')
return ( return (
<div ref={el => { tabsRef.current[index] = el }} className={classNameTab} title={tab.tooltip}> <div onClick={() => { props.onSelect(index); currentIndexRef.current = index; setSelectedIndex(index) }} ref={el => { tabsRef.current[index] = el }} className={classNameTab} title={tab.tooltip}>
{tab.icon ? (<img className="my-1 mr-1 iconImage" src={tab.icon} />) : (<i className={classNameImg}></i>)} {tab.icon ? (<img className="my-1 mr-1 iconImage" src={tab.icon} />) : (<i className={classNameImg}></i>)}
<span className="title-tabs">{tab.title}</span> <span className="title-tabs">{tab.title}</span>
<span className="close-tabs" onClick={() => props.onClose(index)}> <span className="close-tabs" onClick={(event) => { props.onClose(index); event.stopPropagation() }}>
<i className="text-dark fas fa-times"></i> <i className="text-dark fas fa-times"></i>
</span> </span>
</div> </div>
@ -71,7 +71,6 @@ export const TabsUI = (props: TabsUIProps) => {
<Tabs <Tabs
className="tab-scroll" className="tab-scroll"
selectedIndex={selectedIndex} selectedIndex={selectedIndex}
onSelect={index => { props.onSelect(index); currentIndexRef.current = index; setSelectedIndex(index) }}
> >
<TabList className="d-flex flex-row justify-content-center align-items-center"> <TabList className="d-flex flex-row justify-content-center align-items-center">
{props.tabs.map((tab, i) => <Tab className="py-1" key={tab.name}>{renderTab(tab, i)}</Tab>)} {props.tabs.map((tab, i) => <Tab className="py-1" key={tab.name}>{renderTab(tab, i)}</Tab>)}

@ -12,7 +12,7 @@ const Context = ({ opts, blockchain }) => {
const val = data.value const val = data.value
let hash = data.hash ? helper.shortenHexData(data.hash) : '' let hash = data.hash ? helper.shortenHexData(data.hash) : ''
const input = data.input ? helper.shortenHexData(data.input) : '' const input = data.input ? helper.shortenHexData(data.input) : ''
const logs = data.logs && data.logs.decoded && data.logs.decoded.length ? data.logs.decoded.length : 0 const logs = opts.logs && opts.logs.decoded && opts.logs.decoded.length ? opts.logs.decoded.length : 0
const block = data.receipt ? data.receipt.blockNumber : data.blockNumber || '' const block = data.receipt ? data.receipt.blockNumber : data.blockNumber || ''
const i = data.receipt ? data.transactionIndex : data.transactionIndex const i = data.receipt ? data.transactionIndex : data.transactionIndex
const value = val ? typeConversion.toInt(val) : 0 const value = val ? typeConversion.toInt(val) : 0

@ -21,7 +21,7 @@ const RenderKnownTransactions = ({ tx, receipt, resolvedData, logs, index, plugi
const from = tx.from const from = tx.from
const to = resolvedData.contractName + '.' + resolvedData.fn const to = resolvedData.contractName + '.' + resolvedData.fn
const txType = 'knownTx' const txType = 'knownTx'
const options = { from, to, tx } const options = { from, to, tx, logs }
return ( return (
<span id={`tx${tx.hash}`} key={index}> <span id={`tx${tx.hash}`} key={index}>
<div className="log" onClick={(event) => txDetails(event, tx)}> <div className="log" onClick={(event) => txDetails(event, tx)}>

@ -36,124 +36,218 @@ const showTable = (opts, showTableHash) => {
} }
const val = opts.val != null ? typeConversion.toInt(opts.val) : 0 const val = opts.val != null ? typeConversion.toInt(opts.val) : 0
return ( return (
<table className={`txTable ${showTableHash.includes(opts.hash) ? 'active' : ''}`} id='txTable' data-id={`txLoggerTable${opts.hash}`}> <table
className={`txTable ${showTableHash.includes(opts.hash) ? 'active' : ''}`}
id="txTable"
data-id={`txLoggerTable${opts.hash}`}
>
<tbody> <tbody>
<tr className='tr'> {opts.status !== undefined ? (
<td className='td' data-shared={`key_${opts.hash}`}>status</td> <tr className="tr">
<td className='td' data-id={`txLoggerTableStatus${opts.hash}`} data-shared={`pair_${opts.hash}`}>{`${opts.status} ${msg}`}</td> <td className="td" data-shared={`key_${opts.hash}`}>
status
</td>
<td
className="td"
data-id={`txLoggerTableStatus${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>{`${opts.status} ${msg}`}</td>
</tr> </tr>
{opts.hash ? (<tr className='tr'> ) : null}
<td className='td' data-shared={`key_${opts.hash}`}>transaction hash</td> {opts.hash && !opts.isCall ? (
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.hash} <tr className="tr">
<td className="td" data-shared={`key_${opts.hash}`}>
transaction hash
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.hash}
<CopyToClipboard content={opts.hash} /> <CopyToClipboard content={opts.hash} />
</td> </td>
</tr>) : null } </tr>
{ ) : null}
opts.contractAddress ? ( {opts.contractAddress ? (
<tr className='tr'> <tr className="tr">
<td className='td' data-shared={`key_${opts.hash}`}>contract address</td> <td className="td" data-shared={`key_${opts.hash}`}>
<td className='td' data-id={`txLoggerTableContractAddress${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.contractAddress} contract address
</td>
<td
className="td"
data-id={`txLoggerTableContractAddress${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.contractAddress}
<CopyToClipboard content={opts.contractAddress} /> <CopyToClipboard content={opts.contractAddress} />
</td> </td>
</tr> </tr>
) : null ) : null}
} {opts.from ? (
{ <tr className="tr">
opts.from ? ( <td className="td tableTitle" data-shared={`key_${opts.hash}`}>
<tr className='tr'> from
<td className='td tableTitle' data-shared={`key_${opts.hash}`}>from</td> </td>
<td className='td' data-id={`txLoggerTableFrom${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.from} <td
className="td"
data-id={`txLoggerTableFrom${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.from}
<CopyToClipboard content={opts.from} /> <CopyToClipboard content={opts.from} />
</td> </td>
</tr> </tr>
) : null ) : null}
} {opts.to ? (
{ <tr className="tr">
opts.to ? ( <td className="td" data-shared={`key_${opts.hash}`}>
<tr className='tr'> to
<td className='td' data-shared={`key_${opts.hash}`}>to</td> </td>
<td className='td' data-id={`txLoggerTableTo${opts.hash}`} data-shared={`pair_${opts.hash}`}>{toHash} <td
className="td"
data-id={`txLoggerTableTo${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{toHash}
<CopyToClipboard content={data.to ? data.to : toHash} /> <CopyToClipboard content={data.to ? data.to : toHash} />
</td> </td>
</tr> </tr>
) : null ) : null}
} {opts.gas ? (
{ <tr className="tr">
opts.gas ? ( <td className="td" data-shared={`key_${opts.hash}`}>
<tr className='tr'> gas
<td className='td' data-shared={`key_${opts.hash}`}>gas</td> </td>
<td className='td' data-id={`txLoggerTableGas${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.gas} gas <td
className="td"
data-id={`txLoggerTableGas${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.gas} gas
<CopyToClipboard content={opts.gas} /> <CopyToClipboard content={opts.gas} />
</td> </td>
</tr> </tr>
) : null ) : null}
} {opts.transactionCost ? (
{ <tr className="tr">
opts.transactionCost ? ( <td className="td" data-shared={`key_${opts.hash}`}>
<tr className='tr'> transaction cost
<td className='td' data-shared={`key_${opts.hash}`}>transaction cost</td> </td>
<td className='td' data-id={`txLoggerTableTransactionCost${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.transactionCost} gas {callWarning} <td
className="td"
data-id={`txLoggerTableTransactionCost${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.transactionCost} gas {callWarning}
<CopyToClipboard content={opts.transactionCost} /> <CopyToClipboard content={opts.transactionCost} />
</td> </td>
</tr> </tr>
) : null ) : null}
} {opts.executionCost ? (
{ <tr className="tr">
opts.executionCost ? ( <td className="td" data-shared={`key_${opts.hash}`}>
<tr className='tr'> execution cost
<td className='td' data-shared={`key_${opts.hash}`}>execution cost</td> </td>
<td className='td' data-id={`txLoggerTableExecutionHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.executionCost} gas {callWarning} <td
className="td"
data-id={`txLoggerTableExecutionHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.executionCost} gas {callWarning}
<CopyToClipboard content={opts.executionCost} /> <CopyToClipboard content={opts.executionCost} />
</td> </td>
</tr> </tr>
) : null ) : null}
}
{opts.hash ? ( {opts.hash ? (
<tr className='tr'> <tr className="tr">
<td className='td' data-shared={`key_${opts.hash}`}>hash</td> <td className="td" data-shared={`key_${opts.hash}`}>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.hash} hash
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts.hash}
<CopyToClipboard content={opts.hash} /> <CopyToClipboard content={opts.hash} />
</td> </td>
</tr> </tr>
) : null} ) : null}
{opts.input ? ( {opts.input ? (
<tr className='tr'> <tr className="tr">
<td className='td' data-shared={`key_${opts.hash}`}>input</td> <td className="td" data-shared={`key_${opts.hash}`}>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{helper.shortenHexData(opts.input)} input
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{helper.shortenHexData(opts.input)}
<CopyToClipboard content={opts.input} /> <CopyToClipboard content={opts.input} />
</td> </td>
</tr> </tr>
) : null} ) : null}
{opts['decoded input'] ? ( {opts['decoded input'] ? (
<tr className='tr'> <tr className="tr">
<td className='td' data-shared={`key_${opts.hash}`}>decoded input</td> <td className="td" data-shared={`key_${opts.hash}`}>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts['decoded input'].trim()} decoded input
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts['decoded input'].trim()}
<CopyToClipboard content={opts['decoded input']} /> <CopyToClipboard content={opts['decoded input']} />
</td> </td>
</tr> </tr>
) : null} ) : null}
{opts['decoded output'] ? ( {opts['decoded output'] ? (
<tr className='tr'> <tr className="tr">
<td className='td' data-shared={`key_${opts.hash}`}>decoded output</td> <td className="td" data-shared={`key_${opts.hash}`}>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts['decoded output']} decoded output
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{opts['decoded output']}
<CopyToClipboard content={opts['decoded output']} /> <CopyToClipboard content={opts['decoded output']} />
</td> </td>
</tr> </tr>
) : null} ) : null}
{opts.logs ? ( {opts.logs ? (
<tr className='tr'> <tr className="tr">
<td className='td' data-shared={`key_${opts.hash}`}>logs</td> <td className="td" data-shared={`key_${opts.hash}`}>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}> logs
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{JSON.stringify(stringified, null, '\t')} {JSON.stringify(stringified, null, '\t')}
<CopyToClipboard content={JSON.stringify(stringified, null, '\t')}/> <CopyToClipboard
content={JSON.stringify(stringified, null, '\t')}
/>
<CopyToClipboard content={JSON.stringify(opts.logs.raw || '0')} /> <CopyToClipboard content={JSON.stringify(opts.logs.raw || '0')} />
</td> </td>
</tr> </tr>
) : null} ) : null}
{opts.val ? ( {opts.val ? (
<tr className='tr'> <tr className="tr">
<td className='td' data-shared={`key_${opts.hash}`}>val</td> <td className="td" data-shared={`key_${opts.hash}`}>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{val} wei val
</td>
<td
className="td"
data-id={`txLoggerTableHash${opts.hash}`}
data-shared={`pair_${opts.hash}`}
>
{val} wei
<CopyToClipboard content={`${val} wei`} /> <CopyToClipboard content={`${val} wei`} />
</td> </td>
</tr> </tr>

@ -24,7 +24,7 @@ export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> {
} }
export const RemixUiTerminal = (props: RemixUiTerminalProps) => { export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const { call, _deps, on, config, event, gistHandler, logHtmlResponse, logResponse, version } = props.plugin const { call, _deps, on, config, event, gistHandler, version } = props.plugin
const [toggleDownUp, setToggleDownUp] = useState('fa-angle-double-down') const [toggleDownUp, setToggleDownUp] = useState('fa-angle-double-down')
const [_cmdIndex, setCmdIndex] = useState(-1) const [_cmdIndex, setCmdIndex] = useState(-1)
const [_cmdTemp, setCmdTemp] = useState('') const [_cmdTemp, setCmdTemp] = useState('')
@ -84,12 +84,15 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} }
useEffect(() => { useEffect(() => {
scriptRunnerDispatch({ type: 'html', payload: { message: logHtmlResponse } }) props.onReady({
}, [logHtmlResponse]) logHtml: (html) => {
scriptRunnerDispatch({ type: 'html', payload: { message: [html.innerText] } })
useEffect(() => { },
scriptRunnerDispatch({ type: 'log', payload: { message: logResponse } }) log: (message) => {
}, [logResponse]) scriptRunnerDispatch({ type: 'log', payload: { message: [message] } })
}
})
}, [])
// events // events
useEffect(() => { useEffect(() => {
@ -115,7 +118,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
useEffect(() => { useEffect(() => {
scrollToBottom() scrollToBottom()
}, [newstate.journalBlocks.length, logHtmlResponse.length, toaster]) }, [newstate.journalBlocks.length, toaster])
function execute (file, cb) { function execute (file, cb) {
function _execute (content, cb) { function _execute (content, cb) {

@ -24,5 +24,6 @@ export const LISTEN_ON_NETWORK = 'listenOnNetWork'
export const CMD_HISTORY = 'cmdHistory' export const CMD_HISTORY = 'cmdHistory'
export interface RemixUiTerminalProps { export interface RemixUiTerminalProps {
plugin: any plugin: any,
onReady: (api: any) => void
} }

Loading…
Cancel
Save