commit
6776be616b
@ -0,0 +1,4 @@ |
||||
{ |
||||
"presets": ["@nrwl/react/babel"], |
||||
"plugins": [] |
||||
} |
@ -0,0 +1,19 @@ |
||||
{ |
||||
"env": { |
||||
"browser": true, |
||||
"es6": true |
||||
}, |
||||
"extends": "../../../.eslintrc", |
||||
"globals": { |
||||
"Atomics": "readonly", |
||||
"SharedArrayBuffer": "readonly" |
||||
}, |
||||
"parserOptions": { |
||||
"ecmaVersion": 11, |
||||
"sourceType": "module" |
||||
}, |
||||
"rules": { |
||||
"no-unused-vars": "off", |
||||
"@typescript-eslint/no-unused-vars": "error" |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
# remix-ui-terminal |
||||
|
||||
This library was generated with [Nx](https://nx.dev). |
||||
|
||||
## Running unit tests |
||||
|
||||
Run `nx test remix-ui-terminal` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1,4 @@ |
||||
{ |
||||
"name": "remix-ui-terminal", |
||||
"version": "0.0.1" |
||||
} |
@ -0,0 +1 @@ |
||||
export * from './lib/remix-ui-terminal' |
@ -0,0 +1,152 @@ |
||||
import React from 'react' |
||||
import { EMPTY_BLOCK, KNOWN_TRANSACTION, NEW_BLOCK, NEW_CALL, NEW_TRANSACTION, UNKNOWN_TRANSACTION } from '../types/terminalTypes' |
||||
|
||||
export const registerCommandAction = (name: string, command, activate, dispatch: React.Dispatch<any>) => { |
||||
const commands: any = {} |
||||
const _commands: any = {} |
||||
_commands[name] = command |
||||
const data: any = { |
||||
session: [], |
||||
activeFilters: { commands: {}, input: '' }, |
||||
filterFns: {} |
||||
} |
||||
const _INDEX = { |
||||
all: [], |
||||
allMain: [], |
||||
commands: {}, |
||||
commandsMain: {} |
||||
} |
||||
|
||||
const registerFilter = (commandName, filterFn) => { |
||||
data.filterFns[commandName] = filterFn |
||||
} |
||||
|
||||
commands[name] = function () { |
||||
const args = [...arguments] |
||||
const steps = [] |
||||
const root = { steps, cmd: name, gidx: 0, idx: 0 } |
||||
const ITEM = { root, cmd: name } |
||||
root.gidx = _INDEX.allMain.push(ITEM) - 1 |
||||
let item |
||||
function append (cmd, params, el) { |
||||
if (cmd) { // subcommand
|
||||
item = { el, cmd, root } |
||||
} else { // command
|
||||
item = ITEM |
||||
item.el = el |
||||
cmd = name |
||||
} |
||||
item.gidx = _INDEX.all.push(item) - 1 |
||||
item.idx = _INDEX.commands[cmd].push(item) - 1 |
||||
item.step = steps.push(item) - 1 |
||||
item.args = params |
||||
} |
||||
const scopedCommands = _scopeCommands(append) |
||||
command(args, scopedCommands, el => append(null, args, el)) |
||||
} |
||||
const help = typeof command.help === 'string' ? command.help : [ |
||||
'// no help available for:', `terminal.command.${name}` |
||||
].join('\n') |
||||
commands[name].toString = () => { return help } |
||||
commands[name].help = help |
||||
data.activeFilters.commands[name] = activate && activate.activate |
||||
if (activate.filterFn) { |
||||
registerFilter(name, activate.filterFn) |
||||
} |
||||
if (name !== (KNOWN_TRANSACTION || UNKNOWN_TRANSACTION || EMPTY_BLOCK)) { |
||||
dispatch({ type: name, payload: { commands: commands, _commands: _commands, data: data } }) |
||||
} |
||||
|
||||
const _scopeCommands = (append) => { |
||||
const scopedCommands = {} |
||||
Object.keys(commands).forEach(function makeScopedCommand (cmd) { |
||||
const command = _commands[cmd] |
||||
scopedCommands[cmd] = function _command () { |
||||
const args = [...arguments] |
||||
command(args, scopedCommands, el => append(cmd, args, el)) |
||||
} |
||||
}) |
||||
return scopedCommands |
||||
} |
||||
} |
||||
|
||||
export const filterFnAction = (name: string, filterFn, dispatch: React.Dispatch<any>) => { |
||||
const data: any = { |
||||
filterFns: {} |
||||
} |
||||
data.filterFns[name] = filterFn |
||||
dispatch({ type: name, payload: { data: data } }) |
||||
} |
||||
|
||||
export const registerLogScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch<any>) => { |
||||
on('scriptRunner', commandName, (msg) => { |
||||
commandFn.log.apply(commandFn, msg.data) |
||||
dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) |
||||
}) |
||||
} |
||||
|
||||
export const registerInfoScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch<any>) => { |
||||
on('scriptRunner', commandName, (msg) => { |
||||
commandFn.info.apply(commandFn, msg.data) |
||||
dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) |
||||
}) |
||||
} |
||||
|
||||
export const registerWarnScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch<any>) => { |
||||
on('scriptRunner', commandName, (msg) => { |
||||
commandFn.warn.apply(commandFn, msg.data) |
||||
dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) |
||||
}) |
||||
} |
||||
|
||||
export const registerErrorScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch<any>) => { |
||||
on('scriptRunner', commandName, (msg) => { |
||||
commandFn.error.apply(commandFn, msg.data) |
||||
dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) |
||||
}) |
||||
} |
||||
|
||||
export const listenOnNetworkAction = async (event, isListening) => { |
||||
event.trigger('listenOnNetWork', [isListening]) |
||||
} |
||||
|
||||
export const initListeningOnNetwork = (plugins, dispatch: React.Dispatch<any>) => { |
||||
plugins.txListener.event.register(NEW_BLOCK, (block) => { |
||||
if (!block.transactions || (block.transactions && !block.transactions.length)) { |
||||
dispatch({ type: EMPTY_BLOCK, payload: { message: 0 } }) |
||||
} |
||||
}) |
||||
plugins.txListener.event.register(KNOWN_TRANSACTION, () => { |
||||
}) |
||||
plugins.txListener.event.register(NEW_CALL, (tx, receipt) => { |
||||
log(plugins, tx, receipt, dispatch) |
||||
// log(this, tx, null)
|
||||
}) |
||||
plugins.txListener.event.register(NEW_TRANSACTION, (tx, receipt) => { |
||||
log(plugins, tx, receipt, dispatch) |
||||
}) |
||||
|
||||
const log = async (plugins, tx, receipt, dispatch: React.Dispatch<any>) => { |
||||
const resolvedTransaction = await plugins.txListener.resolvedTransaction(tx.hash) |
||||
if (resolvedTransaction) { |
||||
let compiledContracts = null |
||||
if (plugins._deps.compilersArtefacts.__last) { |
||||
compiledContracts = await plugins._deps.compilersArtefacts.__last.getContracts() |
||||
} |
||||
await plugins.eventsDecoder.parseLogs(tx, resolvedTransaction.contractName, compiledContracts, async (error, logs) => { |
||||
if (!error) { |
||||
await dispatch({ type: KNOWN_TRANSACTION, payload: { message: [{ tx: tx, receipt: receipt, resolvedData: resolvedTransaction, logs: logs }] } }) |
||||
} |
||||
}) |
||||
} else { |
||||
await dispatch({ type: UNKNOWN_TRANSACTION, payload: { message: [{ tx: tx, receipt: receipt }] } }) |
||||
} |
||||
} |
||||
|
||||
plugins.txListener.event.register('debuggingRequested', async (hash) => { |
||||
// TODO should probably be in the run module
|
||||
if (!await plugins.options.appManager.isActive('debugger')) await plugins.options.appManager.activatePlugin('debugger') |
||||
plugins.call('menuicons', 'select', 'debugger') |
||||
plugins.call('debugger', 'debug', hash) |
||||
}) |
||||
} |
@ -0,0 +1,53 @@ |
||||
export const allPrograms = [ |
||||
{ ethers: 'The ethers.js library is a compact and complete JavaScript library for Ethereum.' }, |
||||
{ remix: 'Ethereum IDE and tools for the web.' }, |
||||
{ web3: 'The web3.js library is a collection of modules which contain specific functionality for the ethereum ecosystem.' } |
||||
// { swarmgw: 'This library can be used to upload/download files to Swarm via https://swarm-gateways.net/.' }
|
||||
] |
||||
|
||||
export const allCommands = [ |
||||
{ '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.' },
|
||||
{ '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 or ipfs.' },
|
||||
|
||||
// { 'swarmgw.get(url, cb)': 'Download files from Swarm via https://swarm-gateways.net/' },
|
||||
// { 'swarmgw.put(content, cb)': 'Upload files to Swarm via https://swarm-gateways.net/' },
|
||||
|
||||
{ 'ethers.Contract': 'This API provides a graceful connection to a contract deployed on the blockchain, simplifying calling and querying its functions and handling all the binary protocol and conversion as necessarily.' }, |
||||
// { 'ethers.HDNode': 'A Hierarchical Deterministic Wallet represents a large tree of private keys which can reliably be reproduced from an initial seed.' },
|
||||
// { 'ethers.Interface': 'The Interface Object is a meta-class that accepts a Solidity (or compatible) Application Binary Interface (ABI) and populates functions to deal with encoding and decoding the parameters to pass in and results returned.' },
|
||||
{ 'ethers.providers': 'A Provider abstracts a connection to the Ethereum blockchain, for issuing queries and sending state changing transactions.' }, |
||||
// { 'ethers.SigningKey': 'The SigningKey interface provides an abstraction around the secp256k1 elliptic curve cryptography library.' },
|
||||
// { 'ethers.utils': 'The utility functions exposed in both the ethers umbrella package and the ethers-utils.' },
|
||||
// { 'ethers.utils.AbiCoder': 'Create a new ABI Coder object' },
|
||||
// { 'ethers.utils.RLP': 'This encoding method is used internally for several aspects of Ethereum, such as encoding transactions and determining contract addresses.' },
|
||||
{ 'ethers.Wallet': 'A wallet manages a private/public key pair which is used to cryptographically sign transactions and prove ownership on the Ethereum network.' }, |
||||
{ 'ethers.version': 'Contains the version of the ethers container object.' }, |
||||
|
||||
{ 'web3.eth': 'Eth module for interacting with the Ethereum network.' }, |
||||
{ 'web3.eth.accounts': 'The web3.eth.accounts contains functions to generate Ethereum accounts and sign transactions and data.' }, |
||||
// TODO: need to break down the object return from abi response
|
||||
// { 'web3.eth.abi': 'The web3.eth.abi functions let you de- and encode parameters to ABI (Application Binary Interface) for function calls to the EVM (Ethereum Virtual Machine).' },
|
||||
{ 'web3.eth.ens': 'The web3.eth.ens functions let you interacting with ENS.' }, |
||||
{ 'web3.eth.Iban': 'The web3.eth.Iban function lets convert Ethereum addresses from and to IBAN and BBAN.' }, |
||||
{ 'web3.eth.net': 'Net module for interacting with network properties.' }, |
||||
{ 'web3.eth.personal': 'Personal module for interacting with the Ethereum accounts.' }, |
||||
{ 'web3.eth.subscribe': 'The web3.eth.subscribe function lets you subscribe to specific events in the blockchain.' }, |
||||
{ 'web3.givenProvider': 'When using web3.js in an Ethereum compatible browser, it will set with the current native provider by that browser. Will return the given provider by the (browser) environment, otherwise null.' }, |
||||
// { 'web3.modules': 'Contains the version of the web3 container object.' },
|
||||
{ 'web3.providers': 'Contains the current available providers.' }, |
||||
{ 'web3.shh': 'Shh module for interacting with the whisper protocol' }, |
||||
{ 'web3.utils': 'This package provides utility functions for Ethereum dapps and other web3.js packages.' }, |
||||
{ 'web3.version': 'Contains the version of the web3 container object.' }, |
||||
|
||||
{ 'web3.eth.clearSubscriptions();': 'Resets subscriptions.' } |
||||
// { 'web3.eth.Contract(jsonInterface[, address][, options])': 'The web3.eth.Contract object makes it easy to interact with smart contracts on the ethereum blockchain.' },
|
||||
// { 'web3.eth.accounts.create([entropy]);': 'The web3.eth.accounts contains functions to generate Ethereum accounts and sign transactions and data.' },
|
||||
// { 'web3.eth.getAccounts();': 'Retrieve the list of accounts' },
|
||||
// { 'web3.eth.accounts.privateKeyToAccount(privateKey [, ignoreLength ]);': 'Get the account from the private key' },
|
||||
// { 'web3.eth.accounts.signTransaction(tx, privateKey [, callback]);': 'Sign Transaction' },
|
||||
// { 'web3.eth.accounts.recoverTransaction(rawTransaction);': 'Sign Transaction' },
|
||||
// { 'web3.eth.accounts.hashMessage(message);': 'Hash message' }
|
||||
] |
@ -0,0 +1,16 @@ |
||||
import React from 'react' // eslint-disable-line
|
||||
|
||||
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>) |
||||
} |
||||
} |
||||
|
||||
export default CheckTxStatus |
@ -0,0 +1,62 @@ |
||||
import React from 'react' // eslint-disable-line
|
||||
import helper from 'apps/remix-ide/src/lib/helper' |
||||
|
||||
const remixLib = require('@remix-project/remix-lib') |
||||
const typeConversion = remixLib.execution.typeConversion |
||||
|
||||
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.receipt ? 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 { |
||||
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> |
||||
<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>) |
||||
} |
||||
} |
||||
|
||||
export default Context |
@ -0,0 +1,61 @@ |
||||
import React, { useState } from 'react' // eslint-disable-line
|
||||
|
||||
import helper from 'apps/remix-ide/src/lib/helper' |
||||
import CheckTxStatus from './ChechTxStatus' // eslint-disable-line
|
||||
import showTable from './Table' |
||||
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
|
||||
|
||||
const remixLib = require('@remix-project/remix-lib') |
||||
const typeConversion = remixLib.execution.typeConversion |
||||
|
||||
const RenderCall = ({ tx, resolvedData, logs, index, plugin, showTableHash, txDetails, modal }) => { |
||||
const to = resolvedData.contractName + '.' + resolvedData.fn |
||||
const from = tx.from ? tx.from : ' - ' |
||||
const input = tx.input ? helper.shortenHexData(tx.input) : '' |
||||
const txType = 'call' |
||||
|
||||
const debug = (event, tx) => { |
||||
event.stopPropagation() |
||||
if (tx.isCall && tx.envMode !== 'vm') { |
||||
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.', 'Ok', true, () => {}, 'Cancel', () => {}) |
||||
} else { |
||||
plugin.event.trigger('debuggingRequested', [tx.hash]) |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<span id={`tx${tx.hash}`} key={index}> |
||||
<div className="log" onClick={(event) => txDetails(event, tx)}> |
||||
<CheckTxStatus tx={tx} type={txType} /> |
||||
<span className="txLog"> |
||||
<span className="tx">[call]</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'>data:</span> {input}</div> |
||||
</span> |
||||
<div className='buttons'> |
||||
<div className="debug btn btn-primary btn-sm" onClick={(event) => debug(event, tx)}>Debug</div> |
||||
</div> |
||||
<i className="terminal_arrow fas fa-angle-down"></i> |
||||
</div> |
||||
{showTableHash.includes(tx.hash) ? showTable({ |
||||
hash: tx.hash, |
||||
isCall: tx.isCall, |
||||
contractAddress: tx.contractAddress, |
||||
data: tx, |
||||
from, |
||||
to, |
||||
gas: tx.gas, |
||||
input: tx.input, |
||||
'decoded input': resolvedData && resolvedData.params ? JSON.stringify(typeConversion.stringify(resolvedData.params), null, '\t') : ' - ', |
||||
'decoded output': resolvedData && resolvedData.decodedReturnValue ? JSON.stringify(typeConversion.stringify(resolvedData.decodedReturnValue), null, '\t') : ' - ', |
||||
val: tx.value, |
||||
logs: logs, |
||||
transactionCost: tx.transactionCost, |
||||
executionCost: tx.executionCost |
||||
}, showTableHash) : null} |
||||
</span> |
||||
) |
||||
} |
||||
|
||||
export default RenderCall |
@ -0,0 +1,56 @@ |
||||
|
||||
import React, { useState } from 'react' // eslint-disable-line
|
||||
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
|
||||
import CheckTxStatus from './ChechTxStatus' // eslint-disable-line
|
||||
import Context from './Context' // eslint-disable-line
|
||||
import showTable from './Table' |
||||
|
||||
const remixLib = require('@remix-project/remix-lib') |
||||
const typeConversion = remixLib.execution.typeConversion |
||||
|
||||
const RenderKnownTransactions = ({ tx, receipt, resolvedData, logs, index, plugin, showTableHash, txDetails, modal }) => { |
||||
const debug = (event, tx) => { |
||||
event.stopPropagation() |
||||
if (tx.isCall && tx.envMode !== 'vm') { |
||||
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.', 'Ok', true, () => {}, 'Cancel', () => {}) |
||||
} else { |
||||
plugin.event.trigger('debuggingRequested', [tx.hash]) |
||||
} |
||||
} |
||||
|
||||
const from = tx.from |
||||
const to = resolvedData.contractName + '.' + resolvedData.fn |
||||
const txType = 'knownTx' |
||||
const options = { from, to, tx } |
||||
return ( |
||||
<span id={`tx${tx.hash}`} key={index}> |
||||
<div className="log" onClick={(event) => txDetails(event, tx)}> |
||||
<CheckTxStatus tx={receipt} type={txType} /> |
||||
<Context opts = { options } blockchain={plugin.blockchain} /> |
||||
<div className='buttons'> |
||||
<div className='debug btn btn-primary btn-sm' data-shared='txLoggerDebugButton' data-id={`txLoggerDebugButton${tx.hash}`} onClick={(event) => debug(event, tx)}>Debug</div> |
||||
</div> |
||||
<i className = {`terminal_arrow fas ${(showTableHash.includes(tx.hash)) ? 'fa-angle-up' : 'fa-angle-down'}`}></i> |
||||
</div> |
||||
{showTableHash.includes(tx.hash) ? 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': resolvedData && resolvedData.params ? JSON.stringify(typeConversion.stringify(resolvedData.params), null, '\t') : ' - ', |
||||
'decoded output': resolvedData && resolvedData.decodedReturnValue ? JSON.stringify(typeConversion.stringify(resolvedData.decodedReturnValue), null, '\t') : ' - ', |
||||
logs: logs, |
||||
val: tx.value, |
||||
transactionCost: tx.transactionCost, |
||||
executionCost: tx.executionCost |
||||
}, showTableHash) : null} |
||||
</span> |
||||
) |
||||
} |
||||
|
||||
export default RenderKnownTransactions |
@ -0,0 +1,50 @@ |
||||
import React, { useState } from 'react' // eslint-disable-line
|
||||
import { ModalDialog } from '@remix-ui/modal-dialog'// eslint-disable-line
|
||||
import CheckTxStatus from './ChechTxStatus' // eslint-disable-line
|
||||
import Context from './Context' // eslint-disable-line
|
||||
import showTable from './Table' |
||||
|
||||
const RenderUnKnownTransactions = ({ tx, receipt, index, plugin, showTableHash, txDetails, modal }) => { |
||||
const debug = (event, tx) => { |
||||
event.stopPropagation() |
||||
if (tx.isCall && tx.envMode !== 'vm') { |
||||
modal('VM mode', 'Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.', 'Ok', true, () => {}, 'Cancel', () => {}) |
||||
} else { |
||||
plugin.event.trigger('debuggingRequested', [tx.hash]) |
||||
} |
||||
} |
||||
|
||||
const from = tx.from |
||||
const to = tx.to |
||||
const txType = 'unknown' + (tx.isCall ? 'Call' : 'Tx') |
||||
const options = { from, to, tx } |
||||
return ( |
||||
<span id={`tx${tx.hash}`} key={index}> |
||||
<div className="log" onClick={(event) => txDetails(event, tx)}> |
||||
<CheckTxStatus tx={receipt || tx} type={txType} /> |
||||
<Context opts = { options } blockchain={plugin.blockchain} /> |
||||
<div className='buttons'> |
||||
<div className='debug btn btn-primary btn-sm' data-shared='txLoggerDebugButton' data-id={`txLoggerDebugButton${tx.hash}`} onClick={(event) => debug(event, tx)}>Debug</div> |
||||
</div> |
||||
<i className = {`terminal_arrow fas ${(showTableHash.includes(tx.hash)) ? 'fa-angle-up' : 'fa-angle-down'}`}></i> |
||||
</div> |
||||
{showTableHash.includes(tx.hash) ? 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 output': ' - ', |
||||
val: tx.value, |
||||
transactionCost: tx.transactionCost, |
||||
executionCost: tx.executionCost |
||||
}, showTableHash) : null} |
||||
</span> |
||||
) |
||||
} |
||||
|
||||
export default RenderUnKnownTransactions |
@ -0,0 +1,165 @@ |
||||
import React, { useState } from 'react' // eslint-disable-line
|
||||
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
|
||||
import helper from 'apps/remix-ide/src/lib/helper' |
||||
|
||||
const remixLib = require('@remix-project/remix-lib') |
||||
const typeConversion = remixLib.execution.typeConversion |
||||
|
||||
const showTable = (opts, showTableHash) => { |
||||
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' |
||||
} |
||||
} |
||||
|
||||
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 ${showTableHash.includes(opts.hash) ? 'active' : ''}`} id='txTable' data-id={`txLoggerTable${opts.hash}`}> |
||||
<tbody> |
||||
<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> |
||||
{opts.hash ? (<tr className='tr'> |
||||
<td className='td' data-shared={`key_${opts.hash}`}>transaction hash</td> |
||||
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts.hash} |
||||
<CopyToClipboard content={opts.hash}/> |
||||
</td> |
||||
</tr>) : null } |
||||
{ |
||||
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> |
||||
) : null |
||||
} |
||||
{ |
||||
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> |
||||
) : null |
||||
} |
||||
{ |
||||
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> |
||||
) : null |
||||
} |
||||
{ |
||||
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> |
||||
) : null |
||||
} |
||||
{ |
||||
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> |
||||
) : null |
||||
} |
||||
{ |
||||
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> |
||||
) : null |
||||
} |
||||
{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> |
||||
) : null} |
||||
{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> |
||||
) : null} |
||||
{opts['decoded input'] ? ( |
||||
<tr className='tr'> |
||||
<td className='td' data-shared={`key_${opts.hash}`}>decoded input</td> |
||||
<td className='td' data-id={`txLoggerTableHash${opts.hash}`} data-shared={`pair_${opts.hash}`}>{opts['decoded input'].trim()} |
||||
<CopyToClipboard content={opts['decoded input']}/> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts['decoded output'] ? ( |
||||
<tr className='tr'> |
||||
<td className='td' data-shared={`key_${opts.hash}`}>decoded 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> |
||||
) : null} |
||||
{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> |
||||
) : null} |
||||
{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> |
||||
) : null} |
||||
</tbody> |
||||
</table> |
||||
) |
||||
} |
||||
export default showTable |
@ -0,0 +1,29 @@ |
||||
import React, {useEffect, useState} from "react" // eslint-disable-line
|
||||
|
||||
export const useKeyPress = (targetKey: string): boolean => { |
||||
// State for keeping track of whether key is pressed
|
||||
const [keyPressed, setKeyPressed] = useState(false) |
||||
// If pressed key is our target key then set to true
|
||||
function downHandler ({ key }): void { |
||||
if (key === targetKey) { |
||||
setKeyPressed(true) |
||||
} |
||||
} |
||||
// If released key is our target key then set to false
|
||||
const upHandler = ({ key }): void => { |
||||
if (key === targetKey) { |
||||
setKeyPressed(false) |
||||
} |
||||
} |
||||
// Add event listeners
|
||||
useEffect(() => { |
||||
window.addEventListener('keydown', downHandler) |
||||
window.addEventListener('keyup', upHandler) |
||||
// Remove event listeners on cleanup
|
||||
return () => { |
||||
window.removeEventListener('keydown', downHandler) |
||||
window.removeEventListener('keyup', upHandler) |
||||
} |
||||
}, []) // Empty array ensures that effect is only run on mount and unmount
|
||||
return keyPressed |
||||
} |
@ -0,0 +1,195 @@ |
||||
import { CLEAR_CONSOLE, CMD_HISTORY, EMPTY_BLOCK, ERROR, HTML, INFO, KNOWN_TRANSACTION, LISTEN_ON_NETWORK, LOG, NEW_TRANSACTION, SCRIPT, UNKNOWN_TRANSACTION, WARN } from '../types/terminalTypes' |
||||
|
||||
export const initialState = { |
||||
journalBlocks: [ |
||||
], |
||||
data: { |
||||
// lineLength: props.options.lineLength || 80,
|
||||
session: [], |
||||
activeFilters: { commands: {}, input: '' }, |
||||
filterFns: {} |
||||
}, |
||||
_commandHistory: [], |
||||
_commands: {}, |
||||
commands: {}, |
||||
_JOURNAL: [], |
||||
_jobs: [], |
||||
_INDEX: { |
||||
}, |
||||
_INDEXall: [], |
||||
_INDEXallMain: [], |
||||
_INDEXcommands: {}, |
||||
_INDEXcommandsMain: {}, |
||||
message: [] |
||||
} |
||||
|
||||
export const registerCommandReducer = (state, action) => { |
||||
switch (action.type) { |
||||
case HTML : |
||||
return { |
||||
...state, |
||||
_commands: Object.assign(initialState._commands, action.payload._commands), |
||||
commands: Object.assign(initialState.commands, action.payload.commands), |
||||
data: Object.assign(initialState.data, { ...action.payload.data }) |
||||
} |
||||
case LOG: |
||||
return { |
||||
...state, |
||||
_commands: Object.assign(initialState._commands, action.payload._commands), |
||||
commands: Object.assign(initialState.commands, action.payload.commands), |
||||
data: Object.assign(initialState.data, { ...action.payload.data }) |
||||
|
||||
} |
||||
case INFO: |
||||
return { |
||||
...state, |
||||
_commands: Object.assign(initialState._commands, action.payload._commands), |
||||
commands: Object.assign(initialState.commands, action.payload.commands), |
||||
data: Object.assign(initialState.data, action.payload.data) |
||||
} |
||||
case WARN: |
||||
return { |
||||
...state, |
||||
_commands: Object.assign(initialState._commands, action.payload._commands), |
||||
commands: Object.assign(initialState.commands, action.payload.commands), |
||||
data: Object.assign(initialState.data, action.payload.data) |
||||
} |
||||
case ERROR: |
||||
return { |
||||
...state, |
||||
_commands: Object.assign(initialState._commands, action.payload._commands), |
||||
commands: Object.assign(initialState.commands, action.payload.commands), |
||||
data: Object.assign(initialState.data, action.payload.data) |
||||
} |
||||
case SCRIPT: |
||||
return { |
||||
...state, |
||||
_commands: Object.assign(initialState._commands, action.payload._commands), |
||||
commands: Object.assign(initialState.commands, action.payload.commands), |
||||
data: Object.assign(initialState.data, action.payload.data) |
||||
} |
||||
case CLEAR_CONSOLE: |
||||
return { |
||||
...state, |
||||
...state.journalBlocks.splice(0) |
||||
} |
||||
case LISTEN_ON_NETWORK: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-info' }) |
||||
} |
||||
default : |
||||
return { state } |
||||
} |
||||
} |
||||
|
||||
export const registerFilterReducer = (state, action) => { |
||||
switch (action.type) { |
||||
case LOG: |
||||
return { |
||||
...state, |
||||
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns) |
||||
|
||||
} |
||||
case INFO: |
||||
return { |
||||
...state, |
||||
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns) |
||||
} |
||||
case WARN: |
||||
return { |
||||
...state, |
||||
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns) |
||||
} |
||||
case ERROR: |
||||
return { |
||||
...state, |
||||
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns) |
||||
} |
||||
case SCRIPT: |
||||
return { |
||||
...state, |
||||
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns) |
||||
} |
||||
default : |
||||
return { state } |
||||
} |
||||
} |
||||
|
||||
export const addCommandHistoryReducer = (state, action) => { |
||||
switch (action.type) { |
||||
case CMD_HISTORY: |
||||
return { |
||||
...state, |
||||
_commandHistory: initialState._commandHistory.unshift(action.payload.script) |
||||
|
||||
} |
||||
default : |
||||
return { state } |
||||
} |
||||
} |
||||
|
||||
export const remixWelcomeTextReducer = (state, action) => { |
||||
switch (action.type) { |
||||
case 'welcomeText' : |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push(action.payload.welcomeText) |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const registerScriptRunnerReducer = (state, action) => { |
||||
switch (action.type) { |
||||
case HTML: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log' }) |
||||
} |
||||
case LOG: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-info' }) |
||||
} |
||||
case INFO: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-info' }) |
||||
} |
||||
case WARN: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-warning' }) |
||||
} |
||||
case ERROR: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-danger' }) |
||||
} |
||||
case SCRIPT: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log' }) |
||||
} |
||||
case KNOWN_TRANSACTION: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'knownTransaction' }) |
||||
} |
||||
case UNKNOWN_TRANSACTION: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'unknownTransaction' }) |
||||
} |
||||
case EMPTY_BLOCK: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'emptyBlock' }) |
||||
} |
||||
case NEW_TRANSACTION: |
||||
return { |
||||
...state, |
||||
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '' }) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,421 @@ |
||||
|
||||
element.style { |
||||
height: 323px !important; |
||||
} |
||||
#terminalCliInput{ |
||||
width: 95%; |
||||
background: transparent; |
||||
border: none; |
||||
font-weight: bold; |
||||
color: #a2a3b4; |
||||
border-top-style: hidden; |
||||
|
||||
|
||||
|
||||
|
||||
border-right-style: hidden; |
||||
border-left-style: hidden; |
||||
border-bottom-style: hidden; |
||||
} |
||||
#terminalCliInput:focus { |
||||
outline: none; |
||||
} |
||||
|
||||
.border-primary { |
||||
border-color: #007aa6!important; |
||||
} |
||||
|
||||
/* seleted option should reflect the theme color */ |
||||
.selectedOptions { |
||||
background-color: #222336; |
||||
} |
||||
|
||||
.panel { |
||||
position : relative; |
||||
display : flex; |
||||
flex-direction : column; |
||||
font-size : 12px; |
||||
min-height : 3em; |
||||
} |
||||
.bar { |
||||
display : flex; |
||||
z-index : 2; |
||||
} |
||||
.menu { |
||||
position : relative; |
||||
display : flex; |
||||
align-items : center; |
||||
width : 100%; |
||||
max-height : 35px; |
||||
min-height : 35px; |
||||
} |
||||
.toggleTerminal { |
||||
cursor : pointer; |
||||
} |
||||
.toggleTerminal:hover { |
||||
color : var(--secondary); |
||||
} |
||||
.terminal_container { |
||||
display : flex; |
||||
flex-direction : column; |
||||
height : 100%; |
||||
overflow-y : auto; |
||||
font-family : monospace; |
||||
margin : 0px; |
||||
background-repeat : no-repeat; |
||||
background-position : center 15%; |
||||
background-size : auto calc(75% - 1.7em); |
||||
} |
||||
.terminal { |
||||
position : relative; |
||||
display : flex; |
||||
flex-direction : column; |
||||
height : 100%; |
||||
} |
||||
.journal { |
||||
margin-top : auto; |
||||
font-family : monospace; |
||||
} |
||||
.block { |
||||
word-break : break-word; |
||||
white-space : pre-wrap; |
||||
line-height : 2ch; |
||||
padding : 1ch; |
||||
margin-top : 2ch; |
||||
} |
||||
.block > pre { |
||||
max-height : 200px; |
||||
} |
||||
.cli { |
||||
line-height : 1.7em; |
||||
font-family : monospace; |
||||
padding : .4em; |
||||
color : var(--primary); |
||||
} |
||||
.prompt { |
||||
margin-right : 0.5em; |
||||
font-family : monospace; |
||||
font-weight : bold; |
||||
font-size : 14px; |
||||
color : lightgray; |
||||
} |
||||
.input { |
||||
word-break : break-word; |
||||
outline : none; |
||||
font-family : monospace; |
||||
} |
||||
.search { |
||||
display : flex; |
||||
align-items : center; |
||||
width : 330px; |
||||
padding-left : 20px; |
||||
height : 100%; |
||||
padding-top : 1px; |
||||
padding-bottom : 1px; |
||||
} |
||||
.filter { |
||||
height : 80%; |
||||
white-space : nowrap; |
||||
overflow : hidden; |
||||
text-overflow : ellipsis; |
||||
} |
||||
.searchIcon { |
||||
width : 25px; |
||||
border-top-left-radius : 3px; |
||||
border-bottom-left-radius : 3px; |
||||
display : flex; |
||||
align-items : center; |
||||
justify-content : center; |
||||
margin-right : 5px; |
||||
} |
||||
.listen { |
||||
margin-right : 30px; |
||||
min-width : 40px; |
||||
height : 13px; |
||||
display : flex; |
||||
align-items : center; |
||||
} |
||||
.listenLabel { |
||||
min-width: 50px; |
||||
} |
||||
.verticalLine { |
||||
border-left : 1px solid var(--secondary); |
||||
height : 65%; |
||||
} |
||||
.dragbarHorizontal { |
||||
position : absolute; |
||||
top : 0; |
||||
height : 0.2em; |
||||
right : 0; |
||||
left : 0; |
||||
cursor : row-resize; |
||||
z-index : 999; |
||||
} |
||||
|
||||
.console { |
||||
cursor : pointer; |
||||
} |
||||
|
||||
.dragbarHorizontal:hover { |
||||
background-color: #007AA6; |
||||
border:2px solid #007AA6; |
||||
} |
||||
.listenOnNetwork { |
||||
min-height: auto; |
||||
} |
||||
.ghostbar { |
||||
position : absolute; |
||||
height : 6px; |
||||
opacity : 0.5; |
||||
cursor : row-resize; |
||||
z-index : 9999; |
||||
left : 0; |
||||
right : 0; |
||||
} |
||||
|
||||
|
||||
.divider-hitbox { |
||||
color: white; |
||||
cursor: row-resize; |
||||
align-self: stretch; |
||||
display: flex; |
||||
align-items: center; |
||||
padding: 0 1rem; |
||||
} |
||||
|
||||
.ul {margin-left: 0; padding-left: 20px;} |
||||
|
||||
.popup { |
||||
position : absolute; |
||||
text-align : left; |
||||
width : 95%; |
||||
font-family : monospace; |
||||
background-color : var(--secondary); |
||||
overflow : auto; |
||||
padding-bottom : 13px; |
||||
z-index : 80; |
||||
bottom : 1em; |
||||
border-width : 4px; |
||||
left : 2em; |
||||
} |
||||
|
||||
.autoCompleteItem { |
||||
padding : 4px; |
||||
border-radius : 2px; |
||||
} |
||||
|
||||
.popup a { |
||||
cursor : pointer; |
||||
} |
||||
|
||||
.listHandlerShow { |
||||
display : block; |
||||
} |
||||
|
||||
.listHandlerHide { |
||||
display : none; |
||||
} |
||||
|
||||
.listHandlerButtonShow { |
||||
position : fixed; |
||||
width : 46%; |
||||
} |
||||
|
||||
.pageNumberAlignment { |
||||
font-size : 10px; |
||||
float : right; |
||||
} |
||||
|
||||
.modalContent { |
||||
position : absolute; |
||||
margin-left : 20%; |
||||
margin-bottom : 32px; |
||||
bottom : 0px; |
||||
padding : 0; |
||||
line-height : 18px; |
||||
font-size : 12px; |
||||
width : 40%; |
||||
box-shadow : 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); |
||||
-webkit-animation-name: animatebottom; |
||||
-webkit-animation-duration: 0.4s; |
||||
animation-name : animatetop; |
||||
animation-duration: 0.4s |
||||
} |
||||
|
||||
@-webkit-keyframes animatetop { |
||||
from {bottom: -300px; opacity: 0} |
||||
to {bottom: 0; opacity: 1} |
||||
} |
||||
|
||||
@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; |
||||
} |
||||
|
||||
.txStatus { |
||||
display: flex; |
||||
font-size: 20px; |
||||
margin-right: 20px; |
||||
float: left; |
||||
} |
||||
.succeeded { |
||||
color: var(--success); |
||||
} |
||||
.failed { |
||||
color: var(--danger); |
||||
} |
||||
|
||||
.terminal_arrow { |
||||
color: var(--text-info); |
||||
font-size: 20px; |
||||
cursor: pointer; |
||||
display: flex; |
||||
margin-left: 10px; |
||||
} |
||||
.terminal_arrow:hover { |
||||
color: var(--secondary); |
||||
} |
||||
.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); |
||||
transition: max-height 0.3s, padding 0.3s; |
||||
} |
||||
table .active { |
||||
transition: max-height 0.6s, padding 0.6s; |
||||
} |
||||
#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; |
||||
} |
||||
|
||||
|
||||
/* Style the accordion section */ |
||||
.accordion__section { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
/* Style the buttons that are used to open and close the accordion panel */ |
||||
.accordion { |
||||
background-color: #eee; |
||||
color: #444; |
||||
cursor: pointer; |
||||
padding: 18px; |
||||
display: flex; |
||||
align-items: center; |
||||
border: none; |
||||
outline: none; |
||||
transition: background-color 0.6s ease; |
||||
} |
||||
|
||||
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */ |
||||
/* .accordion:hover, |
||||
.active { |
||||
background-color: #ccc; |
||||
} */ |
||||
|
||||
/* Style the accordion content title */ |
||||
.accordion__title { |
||||
font-family: "Open Sans", sans-serif; |
||||
font-weight: 600; |
||||
font-size: 14px; |
||||
} |
||||
|
||||
/* Style the accordion chevron icon */ |
||||
.accordion__icon { |
||||
margin-left: auto; |
||||
transition: transform 0.6s ease; |
||||
} |
||||
|
||||
/* Style to rotate icon when state is active */ |
||||
.rotate { |
||||
transform: rotate(90deg); |
||||
} |
||||
|
||||
/* Style the accordion content panel. Note: hidden by default */ |
||||
.accordion__content { |
||||
background-color: white; |
||||
overflow: hidden; |
||||
transition: max-height 0.6s ease; |
||||
} |
||||
|
||||
/* Style the accordion content text */ |
||||
.accordion__text { |
||||
font-family: "Open Sans", sans-serif; |
||||
font-weight: 400; |
||||
font-size: 14px; |
||||
padding: 18px; |
||||
} |
||||
|
@ -0,0 +1,574 @@ |
||||
import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent } from 'react' // eslint-disable-line
|
||||
import { registerCommandAction, registerLogScriptRunnerAction, registerInfoScriptRunnerAction, registerErrorScriptRunnerAction, registerWarnScriptRunnerAction, listenOnNetworkAction, initListeningOnNetwork } from './actions/terminalAction' |
||||
import { initialState, registerCommandReducer, addCommandHistoryReducer, registerScriptRunnerReducer } from './reducers/terminalReducer' |
||||
import { getKeyOf, getValueOf, Objectfilter, matched } from './utils/utils' |
||||
import {allCommands, allPrograms} from './commands' // eslint-disable-line
|
||||
import TerminalWelcomeMessage from './terminalWelcome' // eslint-disable-line
|
||||
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
|
||||
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
|
||||
|
||||
import './remix-ui-terminal.css' |
||||
import vm from 'vm' |
||||
import javascriptserialize from 'javascript-serialize' |
||||
import jsbeautify from 'js-beautify' |
||||
import RenderUnKnownTransactions from './components/RenderUnknownTransactions' // eslint-disable-line
|
||||
import RenderCall from './components/RenderCall' // eslint-disable-line
|
||||
import RenderKnownTransactions from './components/RenderKnownTransactions' // eslint-disable-line
|
||||
import parse from 'html-react-parser' |
||||
import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, UNKNOWN_TRANSACTION } from './types/terminalTypes' |
||||
import { wrapScript } from './utils/wrapScript' |
||||
|
||||
/* eslint-disable-next-line */ |
||||
export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> { |
||||
clipboardData: DataTransfer; |
||||
} |
||||
|
||||
export const RemixUiTerminal = (props: RemixUiTerminalProps) => { |
||||
const { call, _deps, on, config, event, gistHandler, logHtmlResponse, logResponse, version } = props.plugin |
||||
const [toggleDownUp, setToggleDownUp] = useState('fa-angle-double-down') |
||||
const [_cmdIndex, setCmdIndex] = useState(-1) |
||||
const [_cmdTemp, setCmdTemp] = useState('') |
||||
// dragable state
|
||||
const [leftHeight, setLeftHeight] = useState<undefined | number>(undefined) |
||||
const [separatorYPosition, setSeparatorYPosition] = useState<undefined | number>(undefined) |
||||
const [dragging, setDragging] = useState(false) |
||||
|
||||
const [newstate, dispatch] = useReducer(registerCommandReducer, initialState) |
||||
const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState) |
||||
const [, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState) |
||||
const [toaster, setToaster] = useState(false) |
||||
const [toastProvider, setToastProvider] = useState({ show: false, fileName: '' }) |
||||
const [modalState, setModalState] = useState({ |
||||
message: '', |
||||
title: '', |
||||
okLabel: '', |
||||
cancelLabel: '', |
||||
hide: true, |
||||
cancelFn: () => {}, |
||||
handleHide: () => {} |
||||
}) |
||||
|
||||
const [clearConsole, setClearConsole] = useState(false) |
||||
const [paste, setPaste] = useState(false) |
||||
const [autoCompletState, setAutoCompleteState] = useState({ |
||||
activeSuggestion: 0, |
||||
data: { |
||||
_options: [] |
||||
}, |
||||
_startingElement: 0, |
||||
autoCompleteSelectedItem: {}, |
||||
_elementToShow: 4, |
||||
_selectedElement: 0, |
||||
filteredCommands: [], |
||||
filteredPrograms: [], |
||||
showSuggestions: false, |
||||
text: '', |
||||
userInput: '', |
||||
extraCommands: [], |
||||
commandHistoryIndex: 0 |
||||
}) |
||||
|
||||
const [searchInput, setSearchInput] = useState('') |
||||
const [showTableHash, setShowTableHash] = useState([]) |
||||
|
||||
// terminal inputRef
|
||||
const inputEl = useRef(null) |
||||
const messagesEndRef = useRef(null) |
||||
|
||||
// terminal dragable
|
||||
const leftRef = useRef(null) |
||||
const panelRef = useRef(null) |
||||
|
||||
const scrollToBottom = () => { |
||||
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
scriptRunnerDispatch({ type: 'html', payload: { message: logHtmlResponse } }) |
||||
}, [logHtmlResponse]) |
||||
|
||||
useEffect(() => { |
||||
scriptRunnerDispatch({ type: 'log', payload: { message: logResponse } }) |
||||
}, [logResponse]) |
||||
|
||||
// events
|
||||
useEffect(() => { |
||||
initListeningOnNetwork(props.plugin, scriptRunnerDispatch) |
||||
registerLogScriptRunnerAction(on, 'log', newstate.commands, scriptRunnerDispatch) |
||||
registerInfoScriptRunnerAction(on, 'info', newstate.commands, scriptRunnerDispatch) |
||||
registerWarnScriptRunnerAction(on, 'warn', newstate.commands, scriptRunnerDispatch) |
||||
registerErrorScriptRunnerAction(on, 'error', newstate.commands, scriptRunnerDispatch) |
||||
registerCommandAction('html', _blocksRenderer('html'), { activate: true }, dispatch) |
||||
registerCommandAction('log', _blocksRenderer('log'), { activate: true }, dispatch) |
||||
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) { |
||||
var script = String(args[0]) |
||||
_shell(script, scopedCommands, function (error, output) { |
||||
if (error) scriptRunnerDispatch({ type: 'error', payload: { message: error } }) |
||||
if (output) scriptRunnerDispatch({ type: 'script', payload: { message: '5' } }) |
||||
}) |
||||
}, { activate: true }, dispatch) |
||||
}, [autoCompletState.text]) |
||||
|
||||
useEffect(() => { |
||||
scrollToBottom() |
||||
}, [newstate.journalBlocks.length, logHtmlResponse.length, toaster]) |
||||
|
||||
function execute (file, cb) { |
||||
function _execute (content, cb) { |
||||
if (!content) { |
||||
setToaster(true) |
||||
if (cb) cb() |
||||
return |
||||
} |
||||
newstate.commands.script(content) |
||||
} |
||||
|
||||
if (typeof file === 'undefined') { |
||||
const content = _deps.editor.currentContent() |
||||
_execute(content, cb) |
||||
return |
||||
} |
||||
|
||||
const provider = _deps.fileManager.fileProviderOf(file) |
||||
console.log({ provider }) |
||||
|
||||
if (!provider) { |
||||
// toolTip(`provider for path ${file} not found`)
|
||||
setToastProvider({ show: true, fileName: file }) |
||||
if (cb) cb() |
||||
return |
||||
} |
||||
|
||||
provider.get(file, (error, content) => { |
||||
console.log({ content }) |
||||
if (error) { |
||||
// toolTip(error)
|
||||
// TODO: pop up
|
||||
if (cb) cb() |
||||
return |
||||
} |
||||
|
||||
_execute(content, cb) |
||||
}) |
||||
} |
||||
|
||||
function loadgist (id, cb) { |
||||
gistHandler.loadFromGist({ gist: id }, _deps.fileManager) |
||||
if (cb) 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.') |
||||
} |
||||
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
|
||||
const context = { remix: { exeCurrent: (script: any) => { return execute(undefined, script) }, loadgist: (id: any) => { return loadgist(id, () => {}) }, execute: (fileName, callback) => { return execute(fileName, callback) } } } |
||||
try { |
||||
const cmds = vm.createContext(context) |
||||
const result = vm.runInContext(script, cmds) // eslint-disable-line
|
||||
console.log({ result }) |
||||
return done(null, result) |
||||
} catch (error) { |
||||
return done(error.message) |
||||
} |
||||
} |
||||
try { |
||||
if (script.trim().startsWith('git')) { |
||||
// await this.call('git', 'execute', script) code might be used in the future
|
||||
} else { |
||||
await call('scriptRunner', 'execute', script) |
||||
} |
||||
done() |
||||
} catch (error) { |
||||
done(error.message || error) |
||||
} |
||||
} |
||||
|
||||
const handleMinimizeTerminal = (e) => { |
||||
e.preventDefault() |
||||
e.stopPropagation() |
||||
if (toggleDownUp === 'fa-angle-double-down') { |
||||
setToggleDownUp('fa-angle-double-up') |
||||
event.trigger('resize', []) |
||||
} else { |
||||
const terminalTopOffset = config.get('terminal-top-offset') |
||||
event.trigger('resize', [terminalTopOffset]) |
||||
setToggleDownUp('fa-angle-double-down') |
||||
} |
||||
} |
||||
|
||||
const focusinput = () => { |
||||
inputEl.current.focus() |
||||
} |
||||
|
||||
const handleKeyDown = (event) => { |
||||
const suggestionCount = autoCompletState.activeSuggestion |
||||
if (autoCompletState.userInput !== '' && (event.which === 27 || event.which === 8 || event.which === 46)) { |
||||
// 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, activeSuggestion: 0, showSuggestions: false, userInput: Object.keys(autoCompletState.data._options[0]).toString() })) |
||||
} else { |
||||
if (autoCompletState.showSuggestions && (event.which === 13 || event.which === 9)) { |
||||
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false, userInput: autoCompletState.data._options[autoCompletState.activeSuggestion] ? Object.keys(autoCompletState.data._options[autoCompletState.activeSuggestion]).toString() : inputEl.current.value })) |
||||
} else { |
||||
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false, userInput: autoCompletState.data._options.length === 1 ? Object.keys(autoCompletState.data._options[0]).toString() : inputEl.current.value })) |
||||
} |
||||
} |
||||
} |
||||
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() |
||||
} else { // <enter>
|
||||
event.preventDefault() |
||||
setCmdIndex(-1) |
||||
setCmdTemp('') |
||||
const script = autoCompletState.userInput.trim() // inputEl.current.innerText.trim()
|
||||
if (script.length) { |
||||
cmdHistoryDispatch({ type: 'cmdHistory', payload: { script } }) |
||||
newstate.commands.script(wrapScript(script)) |
||||
} |
||||
setAutoCompleteState(prevState => ({ ...prevState, userInput: '' })) |
||||
inputEl.current.innerText = '' |
||||
inputEl.current.focus() |
||||
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false })) |
||||
} |
||||
} else if (newstate._commandHistory.length && event.which === 38 && !autoCompletState.showSuggestions && (autoCompletState.userInput === '')) { |
||||
event.preventDefault() |
||||
setAutoCompleteState(prevState => ({ ...prevState, userInput: newstate._commandHistory[0] })) |
||||
} 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]).toString() })) |
||||
} else if (event.which === 38 && !autoCompletState.showSuggestions) { // <arrowUp>
|
||||
if (cmdHistory.length - 1 > _cmdIndex) { |
||||
setCmdIndex(prevState => prevState++) |
||||
} |
||||
inputEl.current.innerText = cmdHistory[_cmdIndex] |
||||
inputEl.current.focus() |
||||
} 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() })) |
||||
} else if (event.which === 40 && !autoCompletState.showSuggestions) { |
||||
if (_cmdIndex > -1) { |
||||
setCmdIndex(prevState => prevState--) |
||||
} |
||||
inputEl.current.innerText = _cmdIndex >= 0 ? cmdHistory[_cmdIndex] : _cmdTemp |
||||
inputEl.current.focus() |
||||
} else { |
||||
setCmdTemp(inputEl.current.innerText) |
||||
} |
||||
} |
||||
|
||||
/* start of mouse events */ |
||||
|
||||
const mousedown = (event: MouseEvent) => { |
||||
setSeparatorYPosition(event.clientY) |
||||
leftRef.current.style.backgroundColor = '#007AA6' |
||||
leftRef.current.style.border = '2px solid #007AA6' |
||||
setDragging(true) |
||||
} |
||||
|
||||
const onMouseMove: any = (e: MouseEvent) => { |
||||
e.preventDefault() |
||||
if (dragging && leftHeight && separatorYPosition) { |
||||
const newLeftHeight = leftHeight + separatorYPosition - e.clientY |
||||
setSeparatorYPosition(e.clientY) |
||||
setLeftHeight(newLeftHeight) |
||||
event.trigger('resize', [newLeftHeight + 32]) |
||||
} |
||||
} |
||||
|
||||
const onMouseUp = () => { |
||||
leftRef.current.style.backgroundColor = '' |
||||
leftRef.current.style.border = '' |
||||
setDragging(false) |
||||
} |
||||
|
||||
/* end of mouse event */ |
||||
|
||||
useEffect(() => { |
||||
document.addEventListener('mousemove', onMouseMove) |
||||
document.addEventListener('mouseup', onMouseUp) |
||||
|
||||
return () => { |
||||
document.removeEventListener('mousemove', onMouseMove) |
||||
document.removeEventListener('mouseup', onMouseUp) |
||||
} |
||||
}, [onMouseMove, onMouseUp]) |
||||
|
||||
React.useEffect(() => { |
||||
if (panelRef) { |
||||
if (!leftHeight) { |
||||
setLeftHeight(panelRef.current.offsetHeight) |
||||
return |
||||
} |
||||
panelRef.current.style.height = `${leftHeight}px` |
||||
} |
||||
}, [leftHeight, setLeftHeight, panelRef]) |
||||
|
||||
/* block contents that gets rendered from scriptRunner */ |
||||
|
||||
const _blocksRenderer = (mode) => { |
||||
if (mode === 'html') { |
||||
return function logger (args) { |
||||
if (args.length) { |
||||
return args[0] |
||||
} |
||||
} |
||||
} |
||||
mode = { |
||||
log: 'text-log', |
||||
info: 'text-info', |
||||
warn: 'text-warning', |
||||
error: 'text-danger' |
||||
}[mode] // defaults
|
||||
|
||||
if (mode) { |
||||
const filterUndefined = (el) => el !== undefined && el !== null |
||||
return function logger (args) { |
||||
const types = args.filter(filterUndefined).map(type => type) |
||||
const 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) { |
||||
return values |
||||
} |
||||
} |
||||
} else { |
||||
throw new Error('mode is not supported') |
||||
} |
||||
} |
||||
|
||||
/* end of block content that gets rendered from script Runner */ |
||||
|
||||
const handleClearConsole = () => { |
||||
setClearConsole(true) |
||||
dispatch({ type: 'clearconsole', payload: [] }) |
||||
inputEl.current.focus() |
||||
} |
||||
/* start of autoComplete */ |
||||
|
||||
const listenOnNetwork = (e: any) => { |
||||
const isListening = e.target.checked |
||||
// setIsListeningOnNetwork(isListening)
|
||||
listenOnNetworkAction(event, isListening) |
||||
} |
||||
|
||||
const onChange = (event: any) => { |
||||
event.preventDefault() |
||||
const inputString = event.target.value |
||||
if (matched(allPrograms, inputString) || inputString.includes('.')) { |
||||
if (paste) { |
||||
setPaste(false) |
||||
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false, userInput: inputString })) |
||||
} else { |
||||
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 } })) |
||||
} |
||||
} 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 })) |
||||
} |
||||
} |
||||
|
||||
const modal = (title: string, message: string, okLabel: string, hide: boolean, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { |
||||
setModalState(prevState => ({ ...prevState, message, okLabel, okFn, cancelLabel, cancelFn, hide })) |
||||
} |
||||
|
||||
const handleHideModal = () => { |
||||
setModalState(prevState => ({ ...prevState, hide: true })) |
||||
} |
||||
|
||||
const txDetails = (event, tx) => { |
||||
if (showTableHash.includes(tx.hash)) { |
||||
const index = showTableHash.indexOf(tx.hash) |
||||
if (index > -1) { |
||||
setShowTableHash((prevState) => prevState.filter((x) => x !== tx.hash)) |
||||
} |
||||
} else { |
||||
setShowTableHash((prevState) => ([...prevState, tx.hash])) |
||||
} |
||||
} |
||||
|
||||
const handleAutoComplete = () => ( |
||||
<div className='popup alert alert-secondary' style={{ display: (autoCompletState.showSuggestions && autoCompletState.userInput !== '') && autoCompletState.data._options.length > 0 ? 'block' : 'none' }}> |
||||
<div> |
||||
{autoCompletState.data._options.map((item, index) => { |
||||
return ( |
||||
<div key={index} data-id="autoCompletePopUpAutoCompleteItem" className={`autoCompleteItem listHandlerShow item ${autoCompletState.data._options[autoCompletState.activeSuggestion] === item ? 'border border-primary ' : ''}`} onKeyDown={ handleSelect } > |
||||
<div> |
||||
{getKeyOf(item)} |
||||
</div> |
||||
<div> |
||||
{getValueOf(item)} |
||||
</div> |
||||
</div> |
||||
) |
||||
})} |
||||
</div> |
||||
</div> |
||||
) |
||||
/* end of autoComplete */ |
||||
|
||||
const handlePaste = () => { |
||||
setPaste(true) |
||||
setAutoCompleteState(prevState => ({ ...prevState, activeSuggestion: 0, showSuggestions: false })) |
||||
} |
||||
|
||||
return ( |
||||
<div style={{ height: '323px', flexGrow: 1 }} className='panel' ref={panelRef}> |
||||
<div className="bar"> |
||||
<div className="dragbarHorizontal" onMouseDown={mousedown} ref={leftRef}></div> |
||||
<div className="menu border-top border-dark bg-light" data-id="terminalToggleMenu"> |
||||
<i className={`mx-2 toggleTerminal fas ${toggleDownUp}`} data-id="terminalToggleIcon" onClick={ handleMinimizeTerminal }></i> |
||||
<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> |
||||
<div className="mx-2" title='Pending Transactions'>0</div> |
||||
<div className="pt-1 h-80 mx-3 align-items-center listenOnNetwork custom-control custom-checkbox"> |
||||
<input |
||||
className="custom-control-input" |
||||
id="listenNetworkCheck" |
||||
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" |
||||
/> |
||||
<label |
||||
className="pt-1 form-check-label custom-control-label text-nowrap" |
||||
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you" |
||||
htmlFor="listenNetworkCheck" |
||||
> |
||||
listen on network |
||||
</label> |
||||
</div> |
||||
<div className="search"> |
||||
<i className="fas fa-search searchIcon bg-light" aria-hidden="true"></i> |
||||
<input |
||||
onChange={(event) => setSearchInput(event.target.value.trim()) } |
||||
type="text" |
||||
className="border filter form-control" |
||||
id="searchInput" |
||||
placeholder="Search with transaction hash or address" |
||||
data-id="terminalInputSearch" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div tabIndex={-1} className="terminal_container" data-id="terminalContainer" > |
||||
{ |
||||
handleAutoComplete() |
||||
} |
||||
<div data-id='terminalContainerDisplay' style = {{ |
||||
position: 'relative', |
||||
height: '100%', |
||||
width: '100%', |
||||
opacity: '0.1', |
||||
zIndex: -1 |
||||
}}></div> |
||||
<div className="terminal"> |
||||
<div id='journal' className='journal' data-id='terminalJournal'> |
||||
{!clearConsole && <TerminalWelcomeMessage packageJson={version}/>} |
||||
{newstate.journalBlocks && newstate.journalBlocks.map((x, index) => { |
||||
if (x.name === EMPTY_BLOCK) { |
||||
return ( |
||||
<div className="px-4 block" data-id='block' 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 === UNKNOWN_TRANSACTION) { |
||||
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' data-id={`block_tx${trans.tx.hash}`} key={index}> { <RenderUnKnownTransactions tx={trans.tx} receipt={trans.receipt} index={index} plugin={props.plugin} showTableHash={showTableHash} txDetails={txDetails} modal={modal}/>} </div>) |
||||
}) |
||||
} else if (x.name === KNOWN_TRANSACTION) { |
||||
return x.message.map((trans) => { |
||||
return (<div className='px-4 block' data-id={`block_tx${trans.tx.hash}`} key={index}> { trans.tx.isCall ? <RenderCall tx={trans.tx} resolvedData={trans.resolvedData} logs={trans.logs} index={index} plugin={props.plugin} showTableHash={showTableHash} txDetails={txDetails} modal={modal}/> : (<RenderKnownTransactions tx = { trans.tx } receipt = { trans.receipt } resolvedData = { trans.resolvedData } logs = {trans.logs } index = { index } plugin = { props.plugin } showTableHash = { showTableHash } txDetails = { txDetails } modal={modal}/>) } </div>) |
||||
}) |
||||
} else if (Array.isArray(x.message)) { |
||||
return x.message.map((msg, i) => { |
||||
if (typeof msg === 'object') { |
||||
return ( |
||||
<div className="px-4 block" data-id="block" key={i}><span className={x.style}>{ msg.value ? parse(msg.value) : JSON.stringify(msg) } </span></div> |
||||
) |
||||
} else { |
||||
return ( |
||||
<div className="px-4 block" data-id="block" key={i}><span className={x.style}>{ msg ? msg.toString().replace(/,/g, '') : msg }</span></div> |
||||
) |
||||
} |
||||
}) |
||||
} else { |
||||
if (typeof x.message !== 'function') { |
||||
return ( |
||||
<div className="px-4 block" data-id="block" key={index}> <span className={x.style}> {x.message}</span></div> |
||||
) |
||||
} |
||||
} |
||||
})} |
||||
<div ref={messagesEndRef} /> |
||||
</div> |
||||
<div id="terminalCli" data-id="terminalCli" className="cli" onClick={focusinput}> |
||||
<span className="prompt">{'>'}</span> |
||||
<input className="input" ref={inputEl} spellCheck="false" contentEditable="true" id="terminalCliInput" data-id="terminalCliInput" onChange={(event) => onChange(event)} onKeyDown={(event) => handleKeyDown(event) } value={autoCompletState.userInput} onPaste={handlePaste}></input> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<ModalDialog |
||||
title={ modalState.title } |
||||
message={ modalState.message } |
||||
hide={ modalState.hide } |
||||
okLabel={ modalState.okLabel } |
||||
cancelLabel={ modalState.cancelLabel } |
||||
cancelFn={ modalState.cancelFn } |
||||
handleHide={ handleHideModal } |
||||
/> |
||||
{toaster && <Toaster message="no content to execute"/>} |
||||
{toastProvider.show && <Toaster message={`provider for path ${toastProvider.fileName} not found`} />} |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
export default RemixUiTerminal |
@ -0,0 +1,29 @@ |
||||
import React from 'react' // eslint-disable-line
|
||||
|
||||
const TerminalWelcomeMessage = ({ packageJson }) => { |
||||
return ( |
||||
<div className="px-4 block" data-id="block_null"> |
||||
<div> - Welcome to Remix {packageJson} - </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.5.2</a></li> |
||||
<li><a target="_blank" href="https://docs.ethers.io">ethers.js</a> </li> |
||||
<li>remix (run remix.help() for more info)</li> |
||||
</ul> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
export default TerminalWelcomeMessage |
@ -0,0 +1,28 @@ |
||||
|
||||
export interface ROOTS { |
||||
steps: any, |
||||
cmd: string, |
||||
gidx: number, |
||||
idx: number |
||||
} |
||||
|
||||
export const KNOWN_TRANSACTION = 'knownTransaction' |
||||
export const UNKNOWN_TRANSACTION = 'unknownTransaction' |
||||
export const EMPTY_BLOCK = 'emptyBlock' |
||||
export const NEW_TRANSACTION = 'newTransaction' |
||||
export const NEW_BLOCK = 'newBlock' |
||||
export const NEW_CALL = 'newCall' |
||||
|
||||
export const HTML = 'html' |
||||
export const LOG = 'log' |
||||
export const INFO = 'info' |
||||
export const WARN = 'warn' |
||||
export const ERROR = 'error' |
||||
export const SCRIPT = 'script' |
||||
export const CLEAR_CONSOLE = 'clearconsole' |
||||
export const LISTEN_ON_NETWORK = 'listenOnNetWork' |
||||
export const CMD_HISTORY = 'cmdHistory' |
||||
|
||||
export interface RemixUiTerminalProps { |
||||
plugin: any |
||||
} |
@ -0,0 +1,42 @@ |
||||
|
||||
export const getKeyOf = (item) => { |
||||
return Object.keys(item)[0] |
||||
} |
||||
|
||||
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() |
||||
const 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 |
||||
const contains = String(value).indexOf(query.trim()) !== -1 |
||||
return contains |
||||
}) |
||||
return isMatch |
||||
} |
@ -0,0 +1,16 @@ |
||||
export const wrapScript = (script) => { |
||||
const isKnownScript = ['remix.', 'git'].some(prefix => script.trim().startsWith(prefix)) |
||||
if (isKnownScript) return script |
||||
return ` |
||||
try { |
||||
const ret = ${script}; |
||||
if (ret instanceof Promise) { |
||||
ret.then((result) => { console.log(result) }).catch((error) => { console.log(error) }) |
||||
} else { |
||||
console.log(ret) |
||||
}
|
||||
} catch (e) { |
||||
console.log(e.message) |
||||
} |
||||
` |
||||
} |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"extends": "../../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"jsx": "react", |
||||
"allowJs": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true |
||||
}, |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.lib.json" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,13 @@ |
||||
{ |
||||
"extends": "./tsconfig.json", |
||||
"compilerOptions": { |
||||
"outDir": "../../../dist/out-tsc", |
||||
"types": ["node"] |
||||
}, |
||||
"files": [ |
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
||||
], |
||||
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||
} |
Loading…
Reference in new issue