Merge branch 'master' of https://github.com/ethereum/remix-project into indexdbpackupdate
commit
843bc69b9b
@ -0,0 +1,12 @@ |
|||||||
|
import "remix_tests.sol"; // this import is automatically injected by Remix. |
||||||
|
|
||||||
|
contract AssertOkTest { |
||||||
|
|
||||||
|
function okPassTest() public { |
||||||
|
Assert.ok(true, "okPassTest passes"); |
||||||
|
} |
||||||
|
|
||||||
|
function okFailTest() public { |
||||||
|
Assert.ok(false, "okFailTest fails"); |
||||||
|
} |
||||||
|
} |
@ -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