feat: clear-console, terminal-search, fix-input terminal input to buttom

pull/1342/head
davidzagi93@gmail.com 3 years ago
parent 8c6afc236e
commit b56c1c804d
  1. 7
      apps/remix-ide/src/app.js
  2. 483
      apps/remix-ide/src/app/panels/terminal.js
  3. 106
      libs/remix-ui/terminal/src/lib/actions/terminalAction.ts
  4. 37
      libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts
  5. 123
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.css
  6. 690
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  7. 176
      libs/remix-ui/terminal/src/lib/utils/helper.ts
  8. 34
      libs/remix-ui/terminal/src/lib/utils/utils.ts

@ -288,6 +288,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
// -------------------Terminal----------------------------------------
// move this up for *** level so as to access it in the terminal
makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl))
const terminal = new Terminal(
{ appManager, blockchain },
{
@ -300,10 +302,11 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
return height - newpos
}
},
{ config: registry.get('config').api }
{ config: registry.get('config').api },
registry
)
makeUdapp(blockchain, compilersArtefacts, (domEl) => terminal.logHtml(domEl))
// previous *** level for makeUdapp method
const contextualListener = new ContextualListener({ editor })
engine.register([

@ -18,6 +18,11 @@ var AutoCompletePopup = require('../ui/auto-complete-popup')
var css = require('./styles/terminal-styles')
import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line
var globalRegistry = require('../../global/registry')
var SourceHighlighter = require('../../app/editor/sourceHighlighter')
var GistHandler = require('../../lib/gist-handler')
var KONSOLES = []
function register (api) { KONSOLES.push(api) }
@ -32,12 +37,32 @@ const profile = {
}
class Terminal extends Plugin {
constructor (opts, api, config) {
constructor (opts, api, config, registry) {
super(profile)
this.fileImport = new CompilerImports()
this.gistHandler = new GistHandler()
this.event = new EventManager()
this.registry = registry
this.globalRegistry = globalRegistry
this.element = document.createElement('div')
this.element.setAttribute('class', 'panel_2A0YE0')
this.element.setAttribute('id', 'terminal-view')
this.event = new EventManager()
this.eventsDecoder = this.globalRegistry.get('eventsDecoder').api
this.txListener = this.globalRegistry.get('txlistener').api
this.sourceHighlighter = new SourceHighlighter()
this._deps = {
fileManager: this.registry.get('filemanager').api,
editor: this.registry.get('editor').api,
compilersArtefacts: this.registry.get('compilersartefacts').api,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api
}
this.commandHelp = {
'remix.loadgist(id)': 'Load a gist in the file explorer.',
'remix.loadurl(url)': 'Load the given url in the file explorer. The url can be of type github, swarm, ipfs or raw http',
'remix.execute(filepath)': 'Run the script specified by file path. If filepath is empty, script currently displayed in the editor is executed.',
'remix.exeCurrent()': 'Run the script currently displayed in the editor',
'remix.help()': 'Display this help message'
}
this.blockchain = opts.blockchain
this.vm = vm
this._api = api
@ -54,14 +79,14 @@ class Terminal extends Plugin {
this._components = {}
this._components.cmdInterpreter = new CommandInterpreterAPI(this, null, this.blockchain)
this._components.autoCompletePopup = new AutoCompletePopup(this._opts)
this._components.autoCompletePopup.event.register('handleSelect', function (input) {
const textList = this._view.input.innerText.split(' ')
textList.pop()
textList.push(input)
this._view.input.innerText = textList
this._view.input.focus()
this.putCursor2End(this._view.input)
})
// this._components.autoCompletePopup.event.register('handleSelect', function (input) {
// const textList = this._view.input.innerText.split(' ')
// textList.pop()
// textList.push(input)
// this._view.input.innerText = textList
// this._view.input.focus()
// this.putCursor2End(this._view.input)
// })
this._commands = {}
this.commands = {}
this._JOURNAL = []
@ -86,31 +111,10 @@ class Terminal extends Plugin {
this.off('scriptRunner', 'error')
}
log (message) {
var command = this.commands[message.type]
if (typeof command === 'function') {
if (typeof message.value === 'string' && message.type === 'html') {
var el = document.createElement('div')
el.innerHTML = remixBleach.sanitize(message.value, {
list: [
'a',
'b',
'p',
'em',
'strong',
'div',
'span',
'ul',
'li',
'ol',
'hr'
]
})
message.value = el
}
command(message.value)
};
}
// logHtml (html) {
// var command = this.commands.html
// if (typeof command === 'function') command(html)
// }
logHtml (html) {
var command = this.commands.html
@ -128,7 +132,7 @@ class Terminal extends Plugin {
autoCompletePopupEvent = {this._components.autoCompletePopup.event}
blockchain = {this.blockchain}
api = {this._api}
options = {this.opts}
options = {this._opts}
data = {this.data}
cmdInterpreter = {this._components.cmdInterpreter}
autoCompletePopup = {this._components.autoCompletePopup}
@ -138,170 +142,285 @@ class Terminal extends Plugin {
config = {this.config}
thisState = {this}
vm = {this.vm}
blockchain = {this.blockchain}
commandHelp = {this.commandHelp}
event = {this.event}
_deps = {this._deps}
fileImport = {this.fileImport}
sourceHighlighter = {this.sourceHighlighter}
gistHandler ={this.gistHandler}
registry = {this.registry}
commands = {this.commands}
txListener = {this.txListener}
eventsDecoder = {this.eventsDecoder}
/>,
this.element
)
}
_appendItem (item) {
var self = this
var { el, gidx } = item
self._JOURNAL[gidx] = item
if (!self._jobs.length) {
requestAnimationFrame(function updateTerminal () {
self._jobs.forEach(el => self._view.journal.appendChild(el))
self.scroll2bottom()
self._jobs = []
})
}
if (self.data.activeFilters.commands[item.cmd]) self._jobs.push(el)
}
// _appendItem (item) {
// var self = this
// var { el, gidx } = item
// self._JOURNAL[gidx] = item
// if (!self._jobs.length) {
// requestAnimationFrame(function updateTerminal () {
// self._jobs.forEach(el => self._view.journal.appendChild(el))
// self.scroll2bottom()
// self._jobs = []
// })
// }
// if (self.data.activeFilters.commands[item.cmd]) self._jobs.push(el)
// }
scroll2bottom () {
var self = this
setTimeout(function () {
self._view.term.scrollTop = self._view.term.scrollHeight
// self._view.term.scrollTop = self._view.term.scrollHeight
}, 0)
}
_blocksRenderer (mode) {
if (mode === 'html') {
return function logger (args, scopedCommands, append) {
if (args.length) append(args[0])
}
}
mode = {
log: 'text-info',
info: 'text-info',
warn: 'text-warning',
error: 'text-danger'
}[mode] // defaults
if (mode) {
const filterUndefined = (el) => el !== undefined && el !== null
return function logger (args, scopedCommands, append) {
var types = args.filter(filterUndefined).map(type)
var values = javascriptserialize.apply(null, args.filter(filterUndefined)).map(function (val, idx) {
if (typeof args[idx] === 'string') {
const el = document.createElement('div')
el.innerHTML = args[idx].replace(/(\r\n|\n|\r)/gm, '<br>')
val = el.children.length === 0 ? el.firstChild : el
}
if (types[idx] === 'element') val = jsbeautify.html(val)
return val
})
if (values.length) {
append(yo`<span class="${mode}" >${values}</span>`)
}
}
} else {
throw new Error('mode is not supported')
}
}
// _blocksRenderer (mode) {
// if (mode === 'html') {
// return function logger (args, scopedCommands, append) {
// if (args.length) append(args[0])
// }
// }
// mode = {
// log: 'text-info',
// info: 'text-info',
// warn: 'text-warning',
// error: 'text-danger'
// }[mode] // defaults
_scopeCommands (append) {
var self = this
var scopedCommands = {}
Object.keys(self.commands).forEach(function makeScopedCommand (cmd) {
var command = self._commands[cmd]
scopedCommands[cmd] = function _command () {
var args = [...arguments]
command(args, scopedCommands, el => append(cmd, args, blockify(el)))
}
})
return scopedCommands
}
// if (mode) {
// const filterUndefined = (el) => el !== undefined && el !== null
// return function logger (args, scopedCommands, append) {
// var types = args.filter(filterUndefined).map(type)
// var values = javascriptserialize.apply(null, args.filter(filterUndefined)).map(function (val, idx) {
// if (typeof args[idx] === 'string') {
// const el = document.createElement('div')
// el.innerHTML = args[idx].replace(/(\r\n|\n|\r)/gm, '<br>')
// val = el.children.length === 0 ? el.firstChild : el
// }
// if (types[idx] === 'element') val = jsbeautify.html(val)
// return val
// })
// if (values.length) {
// append(yo`<span class="${mode}" >${values}</span>`)
// }
// }
// } else {
// throw new Error('mode is not supported')
// }
// }
registerFilter (commandName, filterFn) {
this.data.filterFns[commandName] = filterFn
}
// _scopeCommands (append) {
// var self = this
// var scopedCommands = {}
// Object.keys(self.commands).forEach(function makeScopedCommand (cmd) {
// var command = self._commands[cmd]
// scopedCommands[cmd] = function _command () {
// var args = [...arguments]
// command(args, scopedCommands, el => append(cmd, args, blockify(el)))
// }
// })
// return scopedCommands
// }
registerCommand (name, command, opts) {
var self = this
name = String(name)
if (self._commands[name]) throw new Error(`command "${name}" exists already`)
if (typeof command !== 'function') throw new Error(`invalid command: ${command}`)
self._commands[name] = command
console.log({ command })
console.log(self._commands)
self._INDEX.commands[name] = []
self._INDEX.commandsMain[name] = []
self.commands[name] = function _command () {
var args = [...arguments]
var steps = []
var root = { steps, cmd: name }
var ITEM = { root, cmd: name }
root.gidx = self._INDEX.allMain.push(ITEM) - 1
root.idx = self._INDEX.commandsMain[name].push(ITEM) - 1
function append (cmd, params, el) {
var item
if (cmd) { // subcommand
item = { el, cmd, root }
} else { // command
item = ITEM
item.el = el
cmd = name
}
item.gidx = self._INDEX.all.push(item) - 1
item.idx = self._INDEX.commands[cmd].push(item) - 1
item.step = steps.push(item) - 1
item.args = params
self._appendItem(item)
}
var scopedCommands = self._scopeCommands(append)
command(args, scopedCommands, el => append(null, args, blockify(el)))
}
var help = typeof command.help === 'string' ? command.help : [
'// no help available for:',
`terminal.commands.${name}(...)`
].join('\n')
self.commands[name].toString = _ => { return help }
self.commands[name].help = help
self.data.activeFilters.commands[name] = opts && opts.activate
if (opts.filterFn) {
self.registerFilter(name, opts.filterFn)
}
return self.commands[name]
}
// registerFilter (commandName, filterFn) {
// this.data.filterFns[commandName] = filterFn
// }
async _shell (script, scopedCommands, done) { // default shell
if (script.indexOf('remix:') === 0) {
return done(null, 'This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.')
}
var self = this
if (script.indexOf('remix.') === 0) {
// we keep the old feature. This will basically only be called when the command is querying the "remix" object.
// for all the other case, we use the Code Executor plugin
var context = domTerminalFeatures(scopedCommands, self.blockchain)
try {
var cmds = vm.createContext(context)
var result = vm.runInContext(script, cmds)
return done(null, result)
} catch (error) {
return done(error.message)
}
}
try {
let result
if (script.trim().startsWith('git')) {
// result = await this.call('git', 'execute', script)
} else {
result = await this.call('scriptRunner', 'execute', script)
}
if (result) self.commands.html(yo`<pre>${result}</pre>`)
done()
} catch (error) {
done(error.message || error)
}
}
}
// registerCommand (name, command, opts) {
// var self = this
// name = String(name)
// if (this._commands[name]) throw new Error(`command "${name}" exists already`)
// if (typeof command !== 'function') throw new Error(`invalid command: ${command}`)
// this._commands[name] = command
// console.log({ command })
// console.log(self._commands)
// this._INDEX.commands[name] = []
// this._INDEX.commandsMain[name] = []
// this.commands[name] = function _command () {
// var args = [...arguments]
// var steps = []
// var root = { steps, cmd: name }
// var ITEM = { root, cmd: name }
// root.gidx = self._INDEX.allMain.push(ITEM) - 1
// root.idx = self._INDEX.commandsMain[name].push(ITEM) - 1
// function append (cmd, params, el) {
// var item
// if (cmd) { // subcommand
// item = { el, cmd, root }
// } else { // command
// item = ITEM
// item.el = el
// cmd = name
// }
// item.gidx = self._INDEX.all.push(item) - 1
// item.idx = self._INDEX.commands[cmd].push(item) - 1
// item.step = steps.push(item) - 1
// item.args = params
// // self._appendItem(item)
// }
// var scopedCommands = self._scopeCommands(append)
// command(args, scopedCommands, el => append(null, args, blockify(el)))
// }
// var help = typeof command.help === 'string' ? command.help : [
// '// no help available for:',
// `terminal.commands.${name}(...)`
// ].join('\n')
// this.commands[name].toString = _ => { return help }
// this.commands[name].help = help
// this.data.activeFilters.commands[name] = opts && opts.activate
// if (opts.filterFn) {
// this.registerFilter(name, opts.filterFn)
// }
// return this.commands[name]
// }
function domTerminalFeatures (scopedCommands, blockchain) {
return {
remix: this._components.cmdInterpreter
}
}
// async _shell (script, scopedCommands, done) { // default shell
// if (script.indexOf('remix:') === 0) {
// return done(null, 'This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.')
// }
// var self = this
// if (script.indexOf('remix.') === 0) {
// // we keep the old feature. This will basically only be called when the command is querying the "remix" object.
// // for all the other case, we use the Code Executor plugin
// var context = domTerminalFeatures(scopedCommands, self.blockchain)
// try {
// var cmds = vm.createContext(context)
// var result = vm.runInContext(script, cmds)
// return done(null, result)
// } catch (error) {
// return done(error.message)
// }
// }
// try {
// let result
// if (script.trim().startsWith('git')) {
// // result = await this.call('git', 'execute', script)
// } else {
// result = await this.call('scriptRunner', 'execute', script)
// }
// if (result) self.commands.html(yo`<pre>${result}</pre>`)
// done()
// } catch (error) {
// done(error.message || error)
// }
// }
// }
// function domTerminalFeatures (scopedCommands, blockchain) {
// return {
// remix: {
// blockchain: this.blockchain,
// commandHelp: this.commandHelp,
// event: this.event,
// _deps: this._deps
// }
// }
// }
// function loadgist (id, cb) {
// const self = this
// self._components.gistHandler.loadFromGist({ gist: id }, this._deps.fileManager)
// if (cb) cb()
// }
// function loadurl (url, cb) {
// const self = this
// self._components.fileImport.import(url,
// (loadingMsg) => { toolTip(loadingMsg) },
// (err, content, cleanUrl, type, url) => {
// if (err) {
// toolTip(`Unable to load ${url}: ${err}`)
// if (cb) cb(err)
// } else {
// self._deps.fileManager.writeFile(type + '/' + cleanUrl, content)
// try {
// content = JSON.parse(content)
// async.eachOfSeries(content.sources, (value, file, callbackSource) => {
// var url = value.urls[0] // @TODO retrieve all other contents ?
// self._components.fileImport.import(url,
// (loadingMsg) => { toolTip(loadingMsg) },
// async (error, content, cleanUrl, type, url) => {
// if (error) {
// toolTip(`Cannot retrieve the content of ${url}: ${error}`)
// return callbackSource(`Cannot retrieve the content of ${url}: ${error}`)
// } else {
// try {
// await self._deps.fileManager.writeFile(type + '/' + cleanUrl, content)
// callbackSource()
// } catch (e) {
// callbackSource(e.message)
// }
// }
// })
// }, (error) => {
// if (cb) cb(error)
// })
// } catch (e) {}
// if (cb) cb()
// }
// })
// }
// function exeCurrent (cb) {
// return this.execute(undefined, cb)
// }
// function execute (file, cb) {
// const self = this
// function _execute (content, cb) {
// if (!content) {
// toolTip('no content to execute')
// if (cb) cb()
// return
// }
// self._components.terminal.commands.script(content)
// }
// if (typeof file === 'undefined') {
// var content = self._deps.editor.currentContent()
// _execute(content, cb)
// return
// }
// var provider = self._deps.fileManager.fileProviderOf(file)
// if (!provider) {
// toolTip(`provider for path ${file} not found`)
// if (cb) cb()
// return
// }
// provider.get(file, (error, content) => {
// if (error) {
// // toolTip(error)
// // TODO: pop up
// if (cb) cb()
// return
// }
// _execute(content, cb)
// })
// }
// function help (cb) {
// const self = this
// var help = yo`<div></div>`
// for (var k in self.commandHelp) {
// help.appendChild(yo`<div>${k}: ${self.commandHelp[k]}</div>`)
// help.appendChild(yo`<br>`)
// }
// self._components.terminal.commands.html(help)
// if (cb) cb()
// return ''
// }
}
function blockify (el) { return yo`<div class="px-4 ${css.block}" data-id="block_${el.getAttribute ? el.getAttribute('id') : ''}">${el}</div>` }
module.exports = Terminal

@ -70,8 +70,9 @@ export const registerCommandAction = (name, command, activate, dispatch) => {
if (activate.filterFn) {
registerFilter(name, activate.filterFn)
}
dispatch({ type: name, payload: { commands: commands, _commands: _commands, data: data } })
if (name !== ('knownTransaction' || 'unkownTransaction' || 'emptyBlock')) {
dispatch({ type: name, payload: { commands: commands, _commands: _commands, data: data } })
}
const blockify = (el) => {
return `<div class="px-4 block_2A0YE0" data-id="block_null">${el}</div>`
}
@ -89,7 +90,6 @@ export const registerCommandAction = (name, command, activate, dispatch) => {
console.log({ scopedCommands })
return scopedCommands
}
}
export const filterFnAction = (name, filterFn, dispatch) => {
@ -133,3 +133,103 @@ export const registerErrorScriptRunnerAction = (event, commandName, commandFn, d
export const registerRemixWelcomeTextAction = (welcomeText, dispatch) => {
dispatch({ type: 'welcomeText', payload: { welcomeText } })
}
export const listenOnNetworkAction = async (props, isListening) => {
props.event.trigger('listenOnNetWork', [isListening])
}
export const initListeningOnNetwork = (props, dispatch) => {
props.txListener.event.register('newBlock', (block) => {
if (!block.transactions || (block.transactions && !block.transactions.length)) {
dispatch({ type: 'emptyBlock', payload: { message: 0 } })
console.log({ block }, ' david')
// registerCommandAction('emptyBlock', (args, cmds, append) => {
// const data = args[0]
// console.log({ data }, ' useEffect props')
// // // var el = renderEmptyBlock(this, data)
// // // append(el)
// }, { activate: true }, dispatch)
} else {
registerCommandAction('knownTransaction', function (args, cmds, append) {
var data = args[0]
console.log({ data })
// let el
// if (data.tx.isCall) {
// console.log({ data })
// // el = renderCall(this, data)
// } else {
// // el = renderKnownTransaction(this, data, blockchain)
// }
// this.seen[data.tx.hash] = el
// append(el)
}, { activate: true }, dispatch)
}
})
props.txListener.event.register('newCall', (tx) => {
console.log('new call action')
// log(this, tx, null)
})
props.txListener.event.register('newTransaction', (tx, receipt) => {
log(props, tx, receipt, dispatch)
registerCommandAction('knownTransaction', function (args, cmds, append) {
var data = args[0]
console.log({ data })
// let el
// if (data.tx.isCall) {
// console.log({ data })
// // el = renderCall(this, data)
// } else {
// // el = renderKnownTransaction(this, data, blockchain)
// }
// this.seen[data.tx.hash] = el
// append(el)
}, { activate: true }, dispatch)
// const result = Object.assign([], tx)
// console.log({ result })
// scriptRunnerDispatch({ type: 'knownTransaction', payload: { message: result } })
})
const log = async (props, tx, receipt, dispatch) => {
const resolvedTransaction = await props.txListener.resolvedTransaction(tx.hash)
if (resolvedTransaction) {
var compiledContracts = null
if (props._deps.compilersArtefacts.__last) {
compiledContracts = await props._deps.compilersArtefacts.__last.getContracts()
}
console.log({ compiledContracts })
await props.eventsDecoder.parseLogs(tx, resolvedTransaction.contractName, compiledContracts, (error, logs) => {
if (!error) {
console.log({ tx: tx, receipt: receipt, resolvedData: resolvedTransaction, logs: logs })
console.log('knownTransaction')
// logKnownTX({ tx: tx, receipt: receipt, resolvedData: resolvedTransaction, logs: logs })
registerCommandAction('knownTransaction', function (args, cmds, append) {
var data = args[0]
console.log({ data }, 'knownTransaction')
// let el
// if (data.tx.isCall) {
// console.log({ data })
// // el = renderCall(this, data)
// } else {
// // el = renderKnownTransaction(this, data, blockchain)
// }
// this.seen[data.tx.hash] = el
// append(el)
}, { activate: true }, dispatch)
}
})
} else {
console.log('unknownTransaction')
// contract unknown - just displaying raw tx.
// logUnknownTX({ tx: tx, receipt: receipt })
await dispatch({ type: 'unknownTransaction', payload: { message: [{ tx: tx, receipt: receipt }] } })
}
}
props.txListener.event.register('debuggingRequested', async (hash) => {
// TODO should probably be in the run module
console.log({ hash }, 'register Call')
if (!await props.options.appManager.isActive('debugger')) await props.options.appManager.activatePlugin('debugger')
props.thisState.call('menuicons', 'select', 'debugger')
props.thisState.call('debugger', 'debug', hash)
})
}

@ -67,6 +67,17 @@ export const registerCommandReducer = (state, action) => {
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, action.payload.data)
}
case 'clearconsole':
return {
...state,
...state.journalBlocks.splice(0)
}
case 'listenOnNetWork':
console.log({ action: action.payload })
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-info' })
}
default :
return { state }
}
@ -75,14 +86,12 @@ export const registerCommandReducer = (state, action) => {
export const registerFilterReducer = (state, action) => {
switch (action.type) {
case 'log':
console.log({ action }, { state }, 'register Filter')
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
}
case 'info':
console.log({ action }, 'registerFilter')
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
@ -132,7 +141,8 @@ export const remixWelcomeTextReducer = (state, action) => {
}
export const registerScriptRunnerReducer = (state, action) => {
console.log({ state }, { action }, 'register script runner reducer')
const result = Object.assign([], action.payload.message)
console.log({ result })
switch (action.type) {
case 'log':
return {
@ -159,5 +169,26 @@ export const registerScriptRunnerReducer = (state, action) => {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log' })
}
case 'knownTransaction':
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'knownTransaction' })
}
case 'unknownTransaction':
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'knownTransaction' })
}
case 'emptyBlock':
console.log({ action: action.payload.message }, ' emptyBLock reducer')
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'emptyBlock' })
}
case 'newTransaction':
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '' })
}
}
}

@ -17,6 +17,18 @@ element.style {
input #terminalCliInput {
}
.border-primary {
border-color: #007aa6!important;
}
.border {
border: 1px solid #3f4455!important;
}
.selectedOptions {
background-color: #222336;
}
.panel {
position : relative;
display : flex;
@ -138,6 +150,10 @@ input #terminalCliInput {
cursor : row-resize;
z-index : 999;
}
.console {
cursor : pointer;
}
.dragbarHorizontal:hover {
background-color: #007AA6;
@ -171,7 +187,6 @@ input #terminalCliInput {
.popup {
position : absolute;
text-align : left;
display : none;
width : 95%;
font-family : monospace;
background-color : var(--secondary);
@ -234,4 +249,108 @@ input #terminalCliInput {
@keyframes animatetop {
from {bottom: -300px; opacity: 0}
to {bottom: 0; opacity: 1}
}
}
/* tx logger css*/
.log {
display: flex;
cursor: pointer;
align-items: center;
cursor: pointer;
}
.log:hover {
opacity: 0.8;
}
.arrow {
color: var(--text-info);
font-size: 20px;
cursor: pointer;
display: flex;
margin-left: 10px;
}
.arrow:hover {
color: var(--secondary);
}
.txLog {
}
.txStatus {
display: flex;
font-size: 20px;
margin-right: 20px;
float: left;
}
.succeeded {
color: var(--success);
}
.failed {
color: var(--danger);
}
.notavailable {
}
.call {
font-size: 7px;
border-radius: 50%;
min-width: 20px;
min-height: 20px;
display: flex;
justify-content: center;
align-items: center;
color: var(--text-info);
text-transform: uppercase;
font-weight: bold;
}
.txItem {
color: var(--text-info);
margin-right: 5px;
float: left;
}
.txItemTitle {
font-weight: bold;
}
.tx {
color: var(--text-info);
font-weight: bold;
float: left;
margin-right: 10px;
}
.txTable,
.tr,
.td {
border-collapse: collapse;
font-size: 10px;
color: var(--text-info);
border: 1px solid var(--text-info);
}
#txTable {
margin-top: 1%;
margin-bottom: 5%;
align-self: center;
width: 85%;
}
.tr, .td {
padding: 4px;
vertical-align: baseline;
}
.td:first-child {
min-width: 30%;
width: 30%;
align-items: baseline;
font-weight: bold;
}
.tableTitle {
width: 25%;
}
.buttons {
display: flex;
margin-left: auto;
}
.debug {
white-space: nowrap;
}
.debug:hover {
opacity: 0.8;
}

@ -1,15 +1,26 @@
import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent } from 'react' // eslint-disable-line
import { useKeyPress } from './custom-hooks/useKeyPress' // eslint-disable-line
import { useWindowResize } from 'beautiful-react-hooks'
import { registerCommandAction, filterFnAction, registerLogScriptRunnerAction, registerInfoScriptRunnerAction, registerErrorScriptRunnerAction, registerWarnScriptRunnerAction, registerRemixWelcomeTextAction } from './actions/terminalAction'
import { registerCommandAction, filterFnAction, registerLogScriptRunnerAction, registerInfoScriptRunnerAction, registerErrorScriptRunnerAction, registerWarnScriptRunnerAction, registerRemixWelcomeTextAction, listenOnNetworkAction, initListeningOnNetwork } from './actions/terminalAction'
import { initialState, registerCommandReducer, registerFilterReducer, addCommandHistoryReducer, registerScriptRunnerReducer, remixWelcomeTextReducer } from './reducers/terminalReducer'
import { remixWelcome } from './reducers/remixWelcom'
import {getKeyOf, getValueOf} from './utils/utils'
import { getKeyOf, getValueOf, Objectfilter, matched, find } from './utils/utils'
import {allCommands, allPrograms} from './commands' // eslint-disable-line
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
import { ModalDialog } from '@remix-ui/modal-dialog'
// const TxLogger from '../../../apps/'
import vm from 'vm'
import javascriptserialize from 'javascript-serialize'
import jsbeautify from 'js-beautify'
import helper from '../../../../../apps/remix-ide/src/lib/helper'
import parse from 'html-react-parser'
import './remix-ui-terminal.css'
import { debug } from 'console'
import { eventNames } from 'process'
const remixLib = require('@remix-project/remix-lib')
var typeConversion = remixLib.execution.typeConversion
/* eslint-disable-next-line */
export interface RemixUiTerminalProps {
@ -27,6 +38,15 @@ export interface RemixUiTerminalProps {
config: any
thisState: any
vm: any
commandHelp: any,
_deps: any,
fileImport: any,
gistHandler: any,
sourceHighlighter: any,
registry: any,
commands: any,
txListener: any,
eventsDecoder: any
}
export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> {
@ -34,7 +54,6 @@ export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> {
}
export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const [toggleDownUp, setToggleDownUp] = useState('fa-angle-double-down')
const [inserted, setInserted] = useState(false)
const [_cmdIndex, setCmdIndex] = useState(-1)
@ -51,12 +70,14 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState)
const [scriptRunnserState, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState)
const [welcomeTextState, welcomTextDispath] = useReducer(remixWelcomeTextReducer, initialState)
const [isListeningOnNetwork, setIsListeningOnNetwork] = useState(false)
const [autoCompletState, setAutoCompleteState] = useState({
activeSuggestion: 0,
data: {
_options: []
},
_startingElement: 0,
autoCompleteSelectedItem: {},
_elementToShow: 4,
_selectedElement: 0,
filteredCommands: [],
@ -64,58 +85,13 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
showSuggestions: false,
text: '',
userInput: '',
extraCommands: []
})
const [state, setState] = useState({
journalBlocks: {
intro: (
<div>
<div> - Welcome to Remix {props.version} - </div>
<br/>
<div>You can use this terminal to: </div>
<ul className='ul'>
<li>Check transactions details and start debugging.</li>
<li>Execute JavaScript scripts:
<br />
<i> - Input a script directly in the command line interface </i>
<br />
<i> - Select a Javascript file in the file explorer and then run \`remix.execute()\` or \`remix.exeCurrent()\` in the command line interface </i>
<br />
<i> - Right click on a JavaScript file in the file explorer and then click \`Run\` </i>
</li>
</ul>
<div>The following libraries are accessible:</div>
<ul className='ul'>
<li><a target="_blank" href="https://web3js.readthedocs.io/en/1.0/">web3 version 1.0.0</a></li>
<li><a target="_blank" href="https://docs.ethers.io">ethers.js</a> </li>
<li><a target="_blank" href="https://www.npmjs.com/package/swarmgw">swarmgw</a> </li>
<li>remix (run remix.help() for more info)</li>
</ul>
</div>
),
text: (<div>David</div>)
},
data: {
// lineLength: props.options.lineLength || 80,
session: [],
activeFilters: { commands: {}, input: '' },
filterFns: {}
},
_commands: {},
commands: {},
_JOURNAL: [],
_jobs: [],
_INDEX: {},
_INDEXall: [],
_INDEXallMain: [],
_INDEXcommands: {},
_INDEXcommandsMain: {}
extraCommands: [],
commandHistoryIndex: 0
})
const _scopedCommands = () => {
}
const [searchInput, setSearchInput] = useState('')
// const [showTableDetails, setShowTableDetails] = useState([])
const [showTableDetails, setShowTableDetails] = useState(null)
useWindowResize(() => {
setWindowHeight(window.innerHeight)
@ -125,7 +101,8 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const inputEl = useRef(null)
// events
useEffect(() => {
registerRemixWelcomeTextAction(remixWelcome, welcomTextDispath)
initListeningOnNetwork(props, scriptRunnerDispatch)
// registerRemixWelcomeTextAction(remixWelcome, welcomTextDispath)
registerLogScriptRunnerAction(props.thisState, 'log', newstate.commands, scriptRunnerDispatch)
registerInfoScriptRunnerAction(props.thisState, 'info', newstate.commands, scriptRunnerDispatch)
registerWarnScriptRunnerAction(props.thisState, 'warn', newstate.commands, scriptRunnerDispatch)
@ -135,15 +112,18 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
registerCommandAction('info', _blocksRenderer('info'), { activate: true }, dispatch)
registerCommandAction('warn', _blocksRenderer('warn'), { activate: true }, dispatch)
registerCommandAction('error', _blocksRenderer('error'), { activate: true }, dispatch)
registerCommandAction('script', function execute (args, scopedCommands, append) {
var script = String(args[0])
console.log({ script })
console.log({ scopedCommands })
_shell(script, scopedCommands, function (error, output) {
console.log({ error }, 'registerCommand scrpt')
console.log({ output }, 'registerCommand scrpt 2')
if (error) scriptRunnerDispatch({ type: 'error', payload: { message: error } })
else if (output) scriptRunnerDispatch({ type: 'error', payload: { message: output } })
if (output) scriptRunnerDispatch({ type: 'script', payload: { message: '5' } })
})
}, { activate: true }, dispatch)
filterFnAction('log', basicFilter, filterDispatch)
filterFnAction('info', basicFilter, filterDispatch)
filterFnAction('warn', basicFilter, filterDispatch)
@ -157,7 +137,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
// dispatch({ type: 'html', payload: { commands: htmlresullt.commands } })
// dispatch({ type: 'log', payload: { _commands: logresult._commands } })
// registerCommand('log', _blocksRenderer('log'), { activate: true })
}, [newstate.journalBlocks, props.thisState.autoCompletePopup, autoCompletState.text])
}, [props.thisState.autoCompletePopup, autoCompletState.text])
const placeCaretAtEnd = (el) => {
el.focus()
@ -169,6 +149,53 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
sel.addRange(range)
}
const domTerminalFeatures = () => {
return {
remix: props.cmdInterpreter
}
}
function exeCurrent (cb) {
return execute(undefined, cb)
}
function execute (file, cb) {
function _execute (content, cb) {
if (!content) {
// toolTip('no content to execute')
if (cb) cb()
return
}
newstate.commands.script(content)
}
if (typeof file === 'undefined') {
var content = props._deps.editor.currentContent()
_execute(content, cb)
return
}
var provider = props._deps.fileManager.fileProviderOf(file)
if (!provider) {
// toolTip(`provider for path ${file} not found`)
if (cb) cb()
return
}
provider.get(file, (error, content) => {
if (error) {
// toolTip(error)
// TODO: pop up
if (cb) cb()
return
}
_execute(content, cb)
})
}
const _shell = async (script, scopedCommands, done) => { // default shell
if (script.indexOf('remix:') === 0) {
return done(null, 'This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.')
@ -177,21 +204,36 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
if (script.indexOf('remix.') === 0) {
// we keep the old feature. This will basically only be called when the command is querying the "remix" object.
// for all the other case, we use the Code Executor plugin
var context = props.cmdInterpreter
var context = domTerminalFeatures()
try {
var cmds = props.vm.createContext(context)
var result = props.vm.runInContext(script, cmds)
return done(null, result)
const cmds = vm.createContext(context)
// const result
let result = vm.runInContext(script, cmds)
if (script === 'remix.exeCurrent()') {
result = exeCurrent(undefined)
} else {
if (result === {}) {
for (const k in result) {
result = +`<div> {k}: ${result[k]}</div> <br>`
}
}
}
console.log(result === {}, ' is result === object')
console.log({ result })
return done(null, '')
} catch (error) {
return done(error.message)
}
}
try {
let result: any
if (script.trim().startsWith('git')) {
// result = await this.call('git', 'execute', script)
} else {
result = await props.thisState.call('scriptRunner', 'execute', script)
}
console.log({ result })
done()
} catch (error) {
done(error.message || error)
@ -231,7 +273,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}
}
const _appendItem = (item: any) => {
let { _JOURNAL, _jobs, data } = state
const self = props
@ -268,7 +309,20 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}
const handleKeyDown = (event) => {
if (event.which === 13) {
const suggestionCount = autoCompletState.activeSuggestion
if (autoCompletState.userInput !== '' && (event.which === 27 || event.which === 8 || event.which === 46)) {
console.log(' enter esc and delete')
// backspace or any key that should remove the autocompletion
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false }))
}
if (autoCompletState.showSuggestions && (event.which === 13 || event.which === 9)) {
if (autoCompletState.userInput.length === 1) {
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false, userInput: Object.keys(autoCompletState.data._options[0]).toString() }))
} else {
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false, userInput: autoCompletState.userInput }))
}
}
if (event.which === 13 && !autoCompletState.showSuggestions) {
if (event.ctrlKey) { // <ctrl+enter>
// on enter, append the value in the cli input to the journal
inputEl.current.focus()
@ -287,7 +341,50 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
inputEl.current.focus()
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false }))
}
} else if (event.which === 38) { // <arrowUp>
} else if (newstate._commandHistory.length && event.which === 38 && !autoCompletState.showSuggestions && (autoCompletState.userInput === '')) {
console.log('previous command up')
// if (autoCompletState.commandHistoryIndex < 1) {
event.preventDefault()
console.log(newstate._commandHistory[0], ' up value')
setAutoCompleteState(prevState => ({ ...prevState, userInput: newstate._commandHistory[0] }))
// }
// else if (newstate._commandHistory.length < autoCompletState.commandHistoryIndex) {
// setAutoCompleteState(prevState => ({ ...prevState, commandHistoryIndex: --autoCompletState.commandHistoryIndex }))
// console.log(newstate._commandHistory[newstate._commandHistory.length > 1 ? autoCompletState.commandHistoryIndex-- : newstate._commandHistory.length + 1], ' up value')
// } else if (newstate._commandHistory.length === autoCompletState.commandHistoryIndex) {
// console.log(newstate._commandHistory.length === autoCompletState.commandHistoryIndex, ' up value middle')
// setAutoCompleteState(prevState => ({ ...prevState, commandHistoryIndex: autoCompletState.commandHistoryIndex - 2, userInput: newstate._commandHistory[autoCompletState.commandHistoryIndex] }))
// } else {
// setAutoCompleteState(prevState => ({ ...prevState, commandHistoryIndex: autoCompletState.commandHistoryIndex - 1, userInput: newstate._commandHistory[autoCompletState.commandHistoryIndex] }))
// console.log(newstate._commandHistory[newstate._commandHistory.length - 1], ' up value last')
// }
// if (newstate._commandHistory.length === 0) {
// setAutoCompleteState(prevState => ({ ...prevState, userInput: newstate[0] }))
// }
// setAutoCompleteState(prevState => ({ ...prevState, userInput: newstate[autoCompletState.commandHistoryIndex] }))
// TODO: giving error => need to work on the logic
// // } else if (newstate._commandHistory.length && event.which === 40 && !autoCompletState.showSuggestions && (autoCompletState.userInput !== '')) {
// // console.log('previous command down')
// // if (autoCompletState.commandHistoryIndex < newstate._commandHistory.length) {
// // setAutoCompleteState(prevState => ({ ...prevState, commandHistoryIndex: autoCompletState.commandHistoryIndex + 1, userInput: newstate._commandHistory[autoCompletState.commandHistoryIndex + 1] }))
// // console.log(newstate._commandHistory[newstate._commandHistory.length > 1 ? autoCompletState.commandHistoryIndex++ : newstate._commandHistory.length - 1], ' down ++ value')
// // } else {
// // console.log(newstate._commandHistory[newstate._commandHistory.length - 1], ' down value last')
// // setAutoCompleteState(prevState => ({ ...prevState, commandHistoryIndex: newstate._commandHistory.length - 1, userInput: newstate._commandHistory[newstate._commandHistory.length - 1] }))
// // }
// // // if (autoCompletState.commandHistoryIndex === newstate._commandHistory.length) {
// // // return
// // // }
// setAutoCompleteState(prevState => ({ ...prevState, userInput: newstate[autoCompletState.commandHistoryIndex] + 1 }))
} else if (event.which === 38 && autoCompletState.showSuggestions) {
event.preventDefault()
if (autoCompletState.activeSuggestion === 0) {
return
}
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: suggestionCount - 1, userInput: Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion - 1]).toString() }))
console.log('disable up an down key in input box')
} else if (event.which === 38 && !autoCompletState.showSuggestions) { // <arrowUp>
const len = _cmdHistory.length
if (len === 0) event.preventDefault()
if (_cmdHistory.length - 1 > _cmdIndex) {
@ -295,8 +392,14 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}
inputEl.current.innerText = _cmdHistory[_cmdIndex]
inputEl.current.focus()
}
else if (event.which === 40) {
} else if (event.which === 40 && autoCompletState.showSuggestions) {
event.preventDefault()
if ((autoCompletState.activeSuggestion + 1) === autoCompletState.data._options.length) {
return
}
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: suggestionCount + 1, userInput: Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion + 1]).toString() }))
console.log('disable up an down key in input box')
} else if (event.which === 40 && !autoCompletState.showSuggestions) {
if (_cmdIndex > -1) {
setCmdIndex(prevState => prevState--)
}
@ -305,6 +408,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} else {
setCmdTemp(inputEl.current.innerText)
}
console.log({ autoCompletState })
}
const moveGhostbar = (event) => {
@ -326,14 +430,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const mousedown = (event: MouseEvent) => {
setSeparatorYPosition(event.clientY)
setDragging(true)
// console.log({ windowHeight })
// console.log(event.which === 1, 'event.which === 1')
// event.preventDefault()
// moveGhostbar(event)
// if (event.which === 1) {
// console.log('event .which code 1')
// moveGhostbar(event)
// }
}
const onMouseMove: any = (e: MouseEvent) => {
@ -352,36 +448,13 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
setDragging(false)
}
/* end of mouse event */
const cancelGhostbar = (event) => {
if (event.keyCode === 27) {
console.log('event .key code 27')
}
}
useEffect(() => {
// document.addEventListener('mousemove', changeBg)
// function changeBg () {
// document.getElementById('dragId').style.backgroundColor = 'skyblue'
// }
// document.addEventListener('mouseup', changeBg2)
// function changeBg2 () {
// document.getElementById('dragId').style.backgroundColor = ''
// }
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
return () => {
// document.addEventListener('mousemove', changeBg)
// function changeBg () {
// document.getElementById('dragId').style.backgroundColor = 'skyblue'
// }
// document.addEventListener('mouseup', changeBg2)
// function changeBg2 () {
// document.getElementById('dragId').style.backgroundColor = ''
// }
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
@ -448,85 +521,378 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
/* end of block content that gets rendered from script Runner */
const handleClearConsole = () => {
dispatch({ type: 'clearconsole', payload: [] })
inputEl.current.focus()
}
/* start of autoComplete */
const handleSelect = (text) => {
props.thisState.event.trigger('handleSelect', [text])
const listenOnNetwork = (event: any) => {
const isListening = event.target.checked
setIsListeningOnNetwork(isListening)
listenOnNetworkAction(props, isListening)
}
const onChange = (event: any) => {
event.preventDefault()
const inputString = event.target.value
console.log(event)
console.log({ inputString })
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: true, userInput: inputString }))
const textList = inputString.split(' ')
const autoCompleteInput = textList.length > 1 ? textList[textList.length - 1] : textList[0]
allPrograms.forEach(item => {
const program = getKeyOf(item)
console.log({ program })
if (program.substring(0, program.length - 1).includes(autoCompleteInput.trim())) {
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [item] } }))
} else if (autoCompleteInput.trim().includes(program) || (program === autoCompleteInput.trim())) {
allCommands.forEach(item => {
console.log({ item })
const command = getKeyOf(item)
if (command.includes(autoCompleteInput.trim())) {
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [item] } }))
}
})
if (matched(allPrograms, inputString) || inputString.includes('.')) {
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: true, userInput: inputString }))
const textList = inputString.split('.')
if (textList.length === 1) {
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [] } }))
const result = Objectfilter(allPrograms, autoCompletState.userInput)
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: result } }))
} else {
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [] } }))
const result = Objectfilter(allCommands, autoCompletState.userInput)
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: result } }))
}
})
autoCompletState.extraCommands.forEach(item => {
const command = getKeyOf(item)
if (command.includes(autoCompleteInput.trim())) {
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [item] } }))
} else {
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false, userInput: inputString }))
}
}
const handleSelect = (event) => {
const suggestionCount = autoCompletState.activeSuggestion
if (event.keyCode === 38) {
if (autoCompletState.activeSuggestion === 0) {
return
}
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: suggestionCount - 1 }))
} else if (event.keyCode === 40) {
if (autoCompletState.activeSuggestion - 1 === autoCompletState.data._options.length) {
return
}
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: suggestionCount + 1 }))
}
// props.thisState.event.trigger('handleSelect', [text])
}
const checkTxStatus = (tx, type) => {
if (tx.status === '0x1' || tx.status === true) {
return (<i className='txStatus succeeded fas fa-check-circle'></i>)
}
if (type === 'call' || type === 'unknownCall' || type === 'unknown') {
return (<i className='txStatus call'>call</i>)
} else if (tx.status === '0x0' || tx.status === false) {
return (<i className='txStatus failed fas fa-times-circle'></i>)
} else {
return (<i className='txStatus notavailable fas fa-circle-thin' title='Status not available' ></i>)
}
}
const context = (opts, blockchain) => {
const data = opts.tx || ''
const from = opts.from ? helper.shortenHexData(opts.from) : ''
let to = opts.to
if (data.to) to = to + ' ' + helper.shortenHexData(data.to)
const val = data.value
let hash = data.hash ? helper.shortenHexData(data.hash) : ''
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 block = data.receipt ? data.receipt.blockNumber : data.blockNumber || ''
const i = data ? data.transactionIndex : data.transactionIndex
const value = val ? typeConversion.toInt(val) : 0
if (blockchain.getProvider() === 'vm') {
return (
<div>
<span className='txLog_7Xiho'>
<span className='tx'>[{vm}]</span>
<div className='txItem'><span className='txItemTitle'>from:</span> {from}</div>
<div className='txItem'><span className='txItemTitle'>to:</span> {to}</div>
<div className='txItem'><span className='txItemTitle'>value:</span> {value} wei</div>
<div className='txItem'><span className='txItemTitle'>data:</span> {input}</div>
<div className='txItem'><span className='txItemTitle'>logs:</span> {logs}</div>
<div className='txItem'><span className='txItemTitle'>hash:</span> {hash}</div>
</span>
</div>)
} else if (blockchain.getProvider() !== 'vm' && data.resolvedData) {
return (
<div>
<span className='txLog_7Xiho'>
<span className='tx'>[block:${block} txIndex:${i}]</span>
<div className='txItem'><span className='txItemTitle'>from:</span> {from}</div>
<div className='txItem'><span className='txItemTitle'>to:</span> {to}</div>
<div className='txItem'><span className='txItemTitle'>value:</span> {value} wei</div>
<div className='txItem'><span className='txItemTitle'>data:</span> {input}</div>
<div className='txItem'><span className='txItemTitle'>logs:</span> {logs}</div>
<div className='txItem'><span className='txItemTitle'>hash:</span> {hash}</div>
</span>
</div>)
} else {
to = helper.shortenHexData(to)
hash = helper.shortenHexData(data.blockHash)
return (
<div>
<span className='txLog'>
<span className='tx'>[block:${block} txIndex:${i}]</span>
<div className='txItem'><span className='txItemTitle'>from:</span> {from}</div>
<div className='txItem'><span className='txItemTitle'>to:</span> {to}</div>
<div className='txItem'><span className='txItemTitle'>value:</span> {value} wei</div>
</span>
</div>)
}
}
const txDetails = (event, tx, obj) => {
if (showTableDetails === null) {
setShowTableDetails(true)
} else {
setShowTableDetails(null)
}
// if (showTableDetails.length === 0) {
// setShowTableDetails([{ hash: tx.hash, show: true }])
// }
// const id = showTableDetails.filter(x => x.hash !== tx.hash)
// if ((showTableDetails.length !== 0) && (id[0] === tx.hash)) {
// setShowTableDetails(currentState => ([...currentState, { hash: tx.hash, show: false }]))
// }
// console.log((showTableDetails.length !== 0) && (id[0] === tx.hash))
// console.log({ showTableDetails }, ' clicked button')
}
const showTable = (opts) => {
let msg = ''
let toHash
const data = opts.data // opts.data = data.tx
if (data.to) {
toHash = opts.to + ' ' + data.to
} else {
toHash = opts.to
}
let callWarning = ''
if (opts.isCall) {
callWarning = '(Cost only applies when called by a contract)'
}
if (!opts.isCall) {
if (opts.status !== undefined && opts.status !== null) {
if (opts.status === '0x0' || opts.status === false) {
msg = ' Transaction mined but execution failed'
} else if (opts.status === '0x1' || opts.status === true) {
msg = ' Transaction mined and execution succeed'
}
} else {
msg = ' Status not available at the moment'
}
})
if (autoCompletState.data._options.length === 1 && event.which === 9) {
// if only one option and tab is pressed, we resolve it
event.preventDefault()
textList.pop()
textList.push(getKeyOf(autoCompletState.data._options[0]))
handleSelect(`${textList}`.replace(/,/g, ' '))
}
let stringified = ' - '
if (opts.logs && opts.logs.decoded) {
stringified = typeConversion.stringify(opts.logs.decoded)
}
const val = opts.val != null ? typeConversion.toInt(opts.val) : 0
return (
<table className='txTable' id='txTable' data-id={`txLoggerTable${opts.hash}`}>
<tr className='tr'>
<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 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}/>
</td>
</tr>
{
opts.contractAddress && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> contract address </td>
<td className='td' data-id={`txLoggerTableContractAddress${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.contractAddress}
<CopyToClipboard content={opts.contractAddress}/>
</td>
</tr>
)
}
{
opts.from && (
<tr className='tr'>
<td className='td tableTitle' data-shared={`key_${opts.hash}`}> from </td>
<td className='td' data-id={`txLoggerTableFrom${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.from}
<CopyToClipboard content={opts.from}/>
</td>
</tr>
)
}
{
opts.to && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> to </td>
<td className='td' data-id={`txLoggerTableTo${opts.hash}`} data-shared={`pair_${opts.hash}`}>{toHash}
<CopyToClipboard content={data.to ? data.to : toHash}/>
</td>
</tr>
)
}
{
opts.gas && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> gas </td>
<td className='td' data-id={`txLoggerTableGas${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.gas} gas
<CopyToClipboard content={opts.gas}/>
</td>
</tr>
)
}
{
opts.transactionCost && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> transaction cost </td>
<td className='td' data-id={`txLoggerTableTransactionCost${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.transactionCost} gas {callWarning}
<CopyToClipboard content={opts.transactionCost}/>
</td>
</tr>
)
}
{
opts.executionCost && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> execution cost </td>
<td className='td' data-id={`txLoggerTableExecutionHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.executionCost} gas {callWarning}
<CopyToClipboard content={opts.executionCost}/>
</td>
</tr>
)
}
{opts.hash && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> hash </td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.hash}
<CopyToClipboard content={opts.hash}/>
</td>
</tr>
)}
{opts.input && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> input </td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{helper.shortenHexData(opts.input)}
<CopyToClipboard content={opts.input}/>
</td>
</tr>
)}
{opts['decoded input'] && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> decode input </td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts['decoded input']}
<CopyToClipboard content={opts['decoded input']}/>
</td>
</tr>
)}
{opts['decoded output'] && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> decode output </td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts['decoded output']}
<CopyToClipboard content={opts['decoded output']}/>
</td>
</tr>
)}
{opts.logs && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> logs </td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>
{JSON.stringify(stringified, null, '\t')}
<CopyToClipboard content={JSON.stringify(stringified, null, '\t')}/>
<CopyToClipboard content={JSON.stringify(opts.logs.raw || '0')}/>
</td>
</tr>
)}
{opts.val && (
<tr className='tr'>
<td className='td' data-shared={`key_${opts.hash}`}> val </td>
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{val} wei
<CopyToClipboard content={`${val} wei`}/>
</td>
</tr>
)}
</table>
)
}
const debug = (event, tx) => {
event.stopPropagation()
if (tx.isCall && tx.envMode !== 'vm') {
console.log('start debugging')
return (<ModalDialog
hide={false}
handleHide={() => {} }
message="Cannot debug this call. Debugging calls is only possible in JavaScript VM mode."
/>)
} else {
props.event.trigger('debuggingRequested', [tx.hash])
console.log('trigger ', { tx: props.event.trigger })
}
}
const renderKnownTransactions = (tx, receipt, index) => {
const from = tx.from
const to = tx.to
const obj = { from, to }
const showDetails = showTableDetails === tx.from
const txType = 'unknown' + (tx.isCall ? 'Call' : 'Tx')
return (
<span id={`tx${tx.hash}`} key={index}>
<div className="log" onClick={(event) => txDetails(event, tx, obj)}>
{/* onClick={e => txDetails(e, tx, data, obj)} */}
{checkTxStatus(receipt || tx, txType)}
{context({ from, to, tx }, props.blockchain)}
<div className='buttons'>
<div className='debug btn btn-primary btn-sm' onClick={(event) => debug(event, tx)}>Debug</div>
</div>
<i className = {`arrow fas ${(showDetails) ? 'fa-angle-up' : 'fa-angle-down'}`}></i>
</div>
{showTableDetails ? showTable({
hash: tx.hash,
status: receipt !== null ? receipt.status : null,
isCall: tx.isCall,
contractAddress: tx.contractAddress,
data: tx,
from,
to,
gas: tx.gas,
input: tx.input,
'decoded input': tx.resolvedData && tx.resolvedData.params ? JSON.stringify(typeConversion.stringify(tx.resoparams), null, '\t') : ' - ',
'decoded output': tx.resolvedData && tx.resolvedData.decodedReturnValue ? JSON.stringify(typeConversion.stringify(tx.resolvedData.decodedReturnValue), null, '\t') : ' - ',
logs: tx.logs,
val: tx.value,
transactionCost: tx.transactionCost,
executionCost: tx.executionCost
}) : null}
</span>
)
}
const handleAutoComplete = () => (
<div className="popup alert alert-secondary">
<div className='popup alert alert-secondary' style={{ display: autoCompletState.showSuggestions && autoCompletState.userInput !== '' ? 'block' : 'none' }}>
<div>
${autoCompletState.data._options.map((item, index) => {
{autoCompletState.data._options.map((item, index) => {
return (
<div key={index}>auto complete here</div>
// <div data-id="autoCompletePopUpAutoCompleteItem" className={`autoCompleteItem listHandlerHide item ${_selectedElement === index ? 'border border-primary' : ''}`}>
// <div value={index} onClick={(event) => { handleSelect(event.srcElement.innerText) }}>
// {getKeyOf(item)}
// </div>
// <div>
// {getValueOf(item)}
// </div>
// </div>
<div key={index} data-id="autoCompletePopUpAutoCompleteItem" className={`autoCompleteItem listHandlerShow item ${autoCompletState.data._options[autoCompletState.activeSuggestion] === item ? 'border border-primary selectedOptions' : ''}`} onKeyDown={ handleSelect }>
<div>
{getKeyOf(item)}
</div>
<div>
{getValueOf(item)}
</div>
</div>
)
})}
</div>
{/* <div className="listHandlerHide">
<div className="pageNumberAlignment">Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(data._options.length / self._elementsToShow)}</div>
</div> */}
</div>
)
/* end of autoComplete */
return (
<div style={{ height: '323px' }} className='panel_2A0YE0'>
<div style={{ height: '323px', flexGrow: 1 }} className='panel_2A0YE0'>
{console.log({ newstate })}
{console.log({ props })}
{console.log({ autoCompletState })}
<div className="bar_2A0YE0">
{/* ${self._view.dragbar} */}
<div className="dragbarHorizontal" onMouseDown={mousedown} id='dragId'></div>
<div className="menu_2A0YE0 border-top border-dark bg-light" data-id="terminalToggleMenu">
{/* ${self._view.icon} */}
<i className={`mx-2 toggleTerminal_2A0YE0 fas ${toggleDownUp}`} data-id="terminalToggleIcon" onClick={ handleMinimizeTerminal }></i>
<div className="mx-2" id="clearConsole" data-id="terminalClearConsole" >
<div className="mx-2 console" id="clearConsole" data-id="terminalClearConsole" onClick={handleClearConsole} >
<i className="fas fa-ban" aria-hidden="true" title="Clear console"
></i>
</div>
@ -537,7 +903,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
<input
className="custom-control-input"
id="listenNetworkCheck"
// onChange=${listenOnNetwork}
onChange={listenOnNetwork}
type="checkbox"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
/>
@ -554,6 +920,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
{/* ${self._view.inputSearch} */}
<input
// spellcheck = "false"
onChange={(event) => setSearchInput(event.target.value) }
type="text"
className="border filter_2A0YE0 form-control"
id="searchInput"
@ -565,7 +932,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
</div>
<div tabIndex={-1} className="terminal_container_2A0YE0" data-id="terminalContainer" >
{
(autoCompletState.showSuggestions && autoCompletState.userInput) && handleAutoComplete()
handleAutoComplete()
}
<div data-id="terminalContainerDisplay" style = {{
position: 'absolute',
@ -576,11 +943,26 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}}></div>
<div className="terminal_2A0YE0">
<div id="journal" className="journal_2A0YE0" data-id="terminalJournal">
{(newstate.journalBlocks).map((x, index) => (
<div className="px-4 block_2A0YE0" data-id="block_null" key={index}>
<span className={x.style}>{x.message}</span>
</div>
))}
{newstate.journalBlocks && newstate.journalBlocks.map((x, index) => {
if (x.name === 'emptyBlock') {
return (
<div className="px-4 block_2A0YE0" data-id="block_null" key={index}>
<span className='txLog'>
<span className='tx'><div className='txItem'>[<span className='txItemTitle'>block:{x.message} - </span> 0 {'transactions'} ] </div></span></span>
</div>
)
} else if (x.name === 'unknownTransaction' || x.name === 'knownTransaction') {
return x.message.filter(x => x.tx.hash.includes(searchInput) || x.tx.from.includes(searchInput) || x.tx.to.includes(searchInput)).map((trans) => {
return (<div className='px-4 block_2A0YE0' data-id={`block_tx${trans.tx.hash}`}> {renderKnownTransactions(trans.tx, trans.receipt, index)} </div>)
})
} else {
return (
<div className="px-4 block_2A0YE0" data-id="block_null" key={index}>
<span className={x.style}>{x.message}</span>
</div>
)
}
})}
<div className="anchor">
{/* ${background} */}
<div className="overlay background"></div>

@ -0,0 +1,176 @@
// var async = require('async')
// const ethJSUtil = require('ethereumjs-util')
// export const shortenAddress = (address, etherBalance) => {
// var len = address.length
// return address.slice(0, 5) + '...' + address.slice(len - 5, len) + (etherBalance ? ' (' + etherBalance.toString() + ' ether)' : '')
// }
// export const addressToString = (address) => {
// if (!address) return null
// if (typeof address !== 'string') {
// address = address.toString('hex')
// }
// if (address.indexOf('0x') === -1) {
// address = '0x' + address
// }
// return ethJSUtil.toChecksumAddress(address)
// }
// export const shortenHexData = (data) => {
// if (!data) return ''
// if (data.length < 5) return data
// var len = data.length
// return data.slice(0, 5) + '...' + data.slice(len - 5, len)
// }
// export const createNonClashingNameWithPrefix = (name, fileProvider, prefix, cb) => {
// if (!name) name = 'Undefined'
// var counter = ''
// var ext = 'sol'
// var reg = /(.*)\.([^.]+)/g
// var split = reg.exec(name)
// if (split) {
// name = split[1]
// ext = split[2]
// }
// var exist = true
// async.whilst(
// () => { return exist },
// (callback) => {
// fileProvider.exists(name + counter + prefix + '.' + ext).then(currentExist => {
// exist = currentExist
// if (exist) counter = (counter | 0) + 1
// callback()
// }).catch(error => {
// if (error) console.log(error)
// })
// },
// (error) => { cb(error, name + counter + prefix + '.' + ext) }
// )
// }
// export const createNonClashingName = (name, fileProvider, cb) => {
// this.createNonClashingNameWithPrefix(name, fileProvider, '', cb)
// },
// export const createNonClashingNameAsync = async (name, fileManager, prefix = '') => {
// if (!name) name = 'Undefined'
// let counter = ''
// let ext = 'sol'
// const reg = /(.*)\.([^.]+)/g
// const split = reg.exec(name)
// if (split) {
// name = split[1]
// ext = split[2]
// }
// let exist = true
// do {
// const isDuplicate = await fileManager.exists(name + counter + prefix + '.' + ext)
// if (isDuplicate) counter = (counter | 0) + 1
// else exist = false
// } while (exist)
// return name + counter + prefix + '.' + ext
// }
// export const createNonClashingDirNameAsync = async (name, fileManager) => {
// if (!name) name = 'Undefined'
// let counter = ''
// let exist = true
// do {
// const isDuplicate = await fileManager.exists(name + counter)
// if (isDuplicate) counter = (counter | 0) + 1
// else exist = false
// } while (exist)
// return name + counter
// }
// export const checkSpecialChars = (name) => {
// return name.match(/[:*?"<>\\'|]/) != null
// }
// export const checkSlash = (name) => {
// return name.match(/\//) != null
// }
// export const isHexadecimal = (value) => {
// return /^[0-9a-fA-F]+$/.test(value) && (value.length % 2 === 0)
// }
// export const is0XPrefixed = (value) => {
// return value.substr(0, 2) === '0x'
// }
// export const isNumeric = (value) => {
// return /^\+?(0|[1-9]\d*)$/.test(value)
// }
// export const isValidHash = (hash) => { // 0x prefixed, hexadecimal, 64digit
// const hexValue = hash.slice(2, hash.length)
// return this.is0XPrefixed(hash) && /^[0-9a-fA-F]{64}$/.test(hexValue)
// }
// export const removeTrailingSlashes = (text) {
// // Remove single or consecutive trailing slashes
// return text.replace(/\/+$/g, '')
// },
// removeMultipleSlashes (text) {
// // Replace consecutive slashes with '/'
// return text.replace(/\/+/g, '/')
// },
// find: find,
// getPathIcon (path) {
// return path.endsWith('.txt')
// ? 'far fa-file-alt' : path.endsWith('.md')
// ? 'far fa-file-alt' : 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')
// ? 'fak fa-lexon' : path.endsWith('.contract')
// ? 'fab fa-ethereum' : 'far fa-file'
// },
// joinPath (...paths) {
// paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash)
// if (paths.length === 1) return paths[0]
// return paths.join('/')
// },
// extractNameFromKey (key) {
// const keyPath = key.split('/')
// return keyPath[keyPath.length - 1]
// }
// const findDeep = (object, fn, found = { break: false, value: undefined }) => {
// if (typeof object !== 'object' || object === null) return
// for (var i in object) {
// if (found.break) break
// var el = object[i]
// if (el && el.innerText !== undefined && el.innerText !== null) el = el.innerText
// if (fn(el, i, object)) {
// found.value = el
// found.break = true
// break
// } else {
// findDeep(el, fn, found)
// }
// }
// return found.value
// }
// const find = (args, query) => {
// query = query.trim()
// var isMatch = !!findDeep(args, function check (value, key) {
// if (value === undefined || value === null) return false
// if (typeof value === 'function') return false
// if (typeof value === 'object') return false
// var contains = String(value).indexOf(query.trim()) !== -1
// return contains
// })
// return isMatch
// }

@ -6,3 +6,37 @@ export const getKeyOf = (item) => {
export const getValueOf = (item) => {
return Object.values(item)[0]
}
export const Objectfilter = (obj: any, filterValue: any) =>
obj.filter((item: any) => Object.keys(item)[0].indexOf(filterValue) > -1)
export const matched = (arr, value) => arr.map(x => Object.keys(x).some(x => x.startsWith(value))).some(x => x === true)
const findDeep = (object, fn, found = { break: false, value: undefined }) => {
if (typeof object !== 'object' || object === null) return
for (var i in object) {
if (found.break) break
var el = object[i]
if (el && el.innerText !== undefined && el.innerText !== null) el = el.innerText
if (fn(el, i, object)) {
found.value = el
found.break = true
break
} else {
findDeep(el, fn, found)
}
}
return found.value
}
export const find = (args, query) => {
query = query.trim()
var isMatch = !!findDeep(args, function check (value) {
if (value === undefined || value === null) return false
if (typeof value === 'function') return false
if (typeof value === 'object') return false
var contains = String(value).indexOf(query.trim()) !== -1
return contains
})
return isMatch
}

Loading…
Cancel
Save