From b56c1c804de2b43fea7c9248c7773b387724fc05 Mon Sep 17 00:00:00 2001 From: "davidzagi93@gmail.com" Date: Wed, 11 Aug 2021 06:15:40 +0100 Subject: [PATCH] feat: clear-console, terminal-search, fix-input terminal input to buttom --- apps/remix-ide/src/app.js | 7 +- apps/remix-ide/src/app/panels/terminal.js | 483 +++++++----- .../src/lib/actions/terminalAction.ts | 106 ++- .../src/lib/reducers/terminalReducer.ts | 37 +- .../terminal/src/lib/remix-ui-terminal.css | 123 +++- .../terminal/src/lib/remix-ui-terminal.tsx | 690 ++++++++++++++---- .../remix-ui/terminal/src/lib/utils/helper.ts | 176 +++++ libs/remix-ui/terminal/src/lib/utils/utils.ts | 34 + 8 files changed, 1310 insertions(+), 346 deletions(-) create mode 100644 libs/remix-ui/terminal/src/lib/utils/helper.ts diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 2347737a20..518d93c64c 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -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([ diff --git a/apps/remix-ide/src/app/panels/terminal.js b/apps/remix-ide/src/app/panels/terminal.js index 9facd299ef..fb3bc4a130 100644 --- a/apps/remix-ide/src/app/panels/terminal.js +++ b/apps/remix-ide/src/app/panels/terminal.js @@ -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, '
') - val = el.children.length === 0 ? el.firstChild : el - } - if (types[idx] === 'element') val = jsbeautify.html(val) - return val - }) - if (values.length) { - append(yo`${values}`) - } - } - } 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, '
') + // val = el.children.length === 0 ? el.firstChild : el + // } + // if (types[idx] === 'element') val = jsbeautify.html(val) + // return val + // }) + // if (values.length) { + // append(yo`${values}`) + // } + // } + // } 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`
${result}
`) - 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`
${result}
`) + // 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`
` +// for (var k in self.commandHelp) { +// help.appendChild(yo`
${k}: ${self.commandHelp[k]}
`) +// help.appendChild(yo`
`) +// } +// self._components.terminal.commands.html(help) +// if (cb) cb() +// return '' +// } +} function blockify (el) { return yo`
${el}
` } module.exports = Terminal diff --git a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts index 38c58f3555..d032daafc5 100644 --- a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts +++ b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts @@ -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 `
${el}
` } @@ -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) + }) +} diff --git a/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts b/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts index 18d8a0a0d8..7b9043f6c5 100644 --- a/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts +++ b/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts @@ -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: '' }) + } } } diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css index 0091042df2..890460b43c 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css @@ -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} - } \ No newline at end of file + } + + + + + /* 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; + } diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index cb2d91393a..94c45e2b90 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -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 extends SyntheticEvent { @@ -34,7 +54,6 @@ export interface ClipboardEvent extends SyntheticEvent { } 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: ( -
-
- Welcome to Remix {props.version} -
-
-
You can use this terminal to:
-
    -
  • Check transactions details and start debugging.
  • -
  • Execute JavaScript scripts: -
    - - Input a script directly in the command line interface -
    - - Select a Javascript file in the file explorer and then run \`remix.execute()\` or \`remix.exeCurrent()\` in the command line interface -
    - - Right click on a JavaScript file in the file explorer and then click \`Run\` -
  • -
-
The following libraries are accessible:
- -
- ), - text: (
David
) - }, - 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 = +`
{k}: ${result[k]}

` + } + } + } + + 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) { // // 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) { // + } 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) { // 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 () + } + if (type === 'call' || type === 'unknownCall' || type === 'unknown') { + return (call) + } else if (tx.status === '0x0' || tx.status === false) { + return () + } else { + return () + } + } + + 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 ( +
+ + [{vm}] +
from: {from}
+
to: {to}
+
value: {value} wei
+
data: {input}
+
logs: {logs}
+
hash: {hash}
+
+
) + } else if (blockchain.getProvider() !== 'vm' && data.resolvedData) { + return ( +
+ + [block:${block} txIndex:${i}] +
from: {from}
+
to: {to}
+
value: {value} wei
+
data: {input}
+
logs: {logs}
+
hash: {hash}
+
+
) + } else { + to = helper.shortenHexData(to) + hash = helper.shortenHexData(data.blockHash) + return ( +
+ + [block:${block} txIndex:${i}] +
from: {from}
+
to: {to}
+
value: {value} wei
+
+
) + } + } + + 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 ( + + + + + + + + + + { + opts.contractAddress && ( + + + + + ) + } + { + opts.from && ( + + + + + ) + } + { + opts.to && ( + + + + + ) + } + { + opts.gas && ( + + + + + ) + } + { + opts.transactionCost && ( + + + + + ) + } + { + opts.executionCost && ( + + + + + ) + } + {opts.hash && ( + + + + + )} + {opts.input && ( + + + + + )} + {opts['decoded input'] && ( + + + + + )} + {opts['decoded output'] && ( + + + + + )} + {opts.logs && ( + + + + + )} + {opts.val && ( + + + + + )} +
status {opts.status}{msg}
transaction hash {opts.hash} + +
contract address {opts.contractAddress} + +
from {opts.from} + +
to {toHash} + +
gas {opts.gas} gas + +
transaction cost {opts.transactionCost} gas {callWarning} + +
execution cost {opts.executionCost} gas {callWarning} + +
hash {opts.hash} + +
input {helper.shortenHexData(opts.input)} + +
decode input {opts['decoded input']} + +
decode output {opts['decoded output']} + +
logs + {JSON.stringify(stringified, null, '\t')} + + +
val {val} wei + +
+ ) + } + + const debug = (event, tx) => { + event.stopPropagation() + if (tx.isCall && tx.envMode !== 'vm') { + console.log('start debugging') + return ( {} } + 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 ( + +
txDetails(event, tx, obj)}> + {/* onClick={e => txDetails(e, tx, data, obj)} */} + {checkTxStatus(receipt || tx, txType)} + {context({ from, to, tx }, props.blockchain)} +
+
debug(event, tx)}>Debug
+
+ +
+ {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} +
+ ) + } + const handleAutoComplete = () => ( -
+
- ${autoCompletState.data._options.map((item, index) => { + {autoCompletState.data._options.map((item, index) => { return ( -
auto complete here
- //
- //
{ handleSelect(event.srcElement.innerText) }}> - // {getKeyOf(item)} - //
- //
- // {getValueOf(item)} - //
- //
+
+
+ {getKeyOf(item)} +
+
+ {getValueOf(item)} +
+
) })}
- {/*
-
Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(data._options.length / self._elementsToShow)}
-
*/}
) /* end of autoComplete */ return ( -
+
{console.log({ newstate })} {console.log({ props })} - {console.log({ autoCompletState })}
{/* ${self._view.dragbar} */}
{/* ${self._view.icon} */} -
+
@@ -537,7 +903,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { @@ -554,6 +920,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { {/* ${self._view.inputSearch} */} setSearchInput(event.target.value) } type="text" className="border filter_2A0YE0 form-control" id="searchInput" @@ -565,7 +932,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
{ - (autoCompletState.showSuggestions && autoCompletState.userInput) && handleAutoComplete() + handleAutoComplete() }
{ }}>
- {(newstate.journalBlocks).map((x, index) => ( -
- {x.message} -
- ))} + {newstate.journalBlocks && newstate.journalBlocks.map((x, index) => { + if (x.name === 'emptyBlock') { + return ( +
+ +
[block:{x.message} - 0 {'transactions'} ]
+
+ ) + } 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 (
{renderKnownTransactions(trans.tx, trans.receipt, index)}
) + }) + } else { + return ( +
+ {x.message} +
+ ) + } + })}
{/* ${background} */}
diff --git a/libs/remix-ui/terminal/src/lib/utils/helper.ts b/libs/remix-ui/terminal/src/lib/utils/helper.ts new file mode 100644 index 0000000000..56b50a4377 --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/utils/helper.ts @@ -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 +// } diff --git a/libs/remix-ui/terminal/src/lib/utils/utils.ts b/libs/remix-ui/terminal/src/lib/utils/utils.ts index 46ca37fa89..c5732f1b53 100644 --- a/libs/remix-ui/terminal/src/lib/utils/utils.ts +++ b/libs/remix-ui/terminal/src/lib/utils/utils.ts @@ -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 +}