implementing command execution script in react

pull/1342/head
davidzagi93@gmail.com 3 years ago
parent e0f7ce4609
commit d2c5b07170
  1. 14
      apps/remix-ide/src/app/panels/terminal.js
  2. 4
      libs/remix-ui/terminal/src/lib/actions/terminalAction.ts
  3. 52
      libs/remix-ui/terminal/src/lib/commands.ts
  4. 25
      libs/remix-ui/terminal/src/lib/reducers/remixWelcom.ts
  5. 40
      libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts
  6. 83
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.css
  7. 240
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  8. 8
      libs/remix-ui/terminal/src/lib/utils/utils.ts

@ -39,6 +39,7 @@ class Terminal extends Plugin {
this.element.setAttribute('id', 'terminal-view') this.element.setAttribute('id', 'terminal-view')
this.event = new EventManager() this.event = new EventManager()
this.blockchain = opts.blockchain this.blockchain = opts.blockchain
this.vm = vm
this._api = api this._api = api
this._opts = opts this._opts = opts
this.config = config this.config = config
@ -75,18 +76,6 @@ class Terminal extends Plugin {
} }
onActivation () { onActivation () {
// this.on('scriptRunner', 'log', (msg) => {
// this.commands.log.apply(this.commands, msg.data)
// })
// this.on('scriptRunner', 'info', (msg) => {
// this.commands.info.apply(this.commands, msg.data)
// })
// this.on('scriptRunner', 'warn', (msg) => {
// this.commands.warn.apply(this.commands, msg.data)
// })
// this.on('scriptRunner', 'error', (msg) => {
// this.commands.error.apply(this.commands, msg.data)
// })
this.renderComponent() this.renderComponent()
} }
@ -148,6 +137,7 @@ class Terminal extends Plugin {
version = {this.version} version = {this.version}
config = {this.config} config = {this.config}
thisState = {this} thisState = {this}
vm = {this.vm}
/>, />,
this.element this.element
) )

@ -129,3 +129,7 @@ export const registerErrorScriptRunnerAction = (event, commandName, commandFn, d
dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) dispatch({ type: commandName, payload: { commandFn, message: msg.data } })
}) })
} }
export const registerRemixWelcomeTextAction = (welcomeText, dispatch) => {
dispatch({ type: 'welcomeText', payload: { welcomeText } })
}

@ -0,0 +1,52 @@
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.' },
{ '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,25 @@
export const remixWelcome = () => {
return `<div>
<div> - Welcome to Remix {props.version} - </div>
<br/>
<div>You can use this terminal to: </div>
<ul className='ul'>
<li>Check transactions details and start debugging.</li>
<li>Execute JavaScript scripts:
<br />
<i> - Input a script directly in the command line interface </i>
<br />
<i> - Select a Javascript file in the file explorer and then run \`remix.execute()\` or \`remix.exeCurrent()\` in the command line interface </i>
<br />
<i> - Right click on a JavaScript file in the file explorer and then click \`Run\` </i>
</li>
</ul>
<div>The following libraries are accessible:</div>
<ul className='ul'>
<li><a target="_blank" href="https://web3js.readthedocs.io/en/1.0/">web3 version 1.0.0</a></li>
<li><a target="_blank" href="https://docs.ethers.io">ethers.js</a> </li>
<li><a target="_blank" href="https://www.npmjs.com/package/swarmgw">swarmgw</a> </li>
<li>remix (run remix.help() for more info)</li>
</ul>
</div>`
}

@ -1,7 +1,7 @@
export const initialState = {
journalBlocks: {
}, export const initialState = {
journalBlocks: [
],
data: { data: {
// lineLength: props.options.lineLength || 80, // lineLength: props.options.lineLength || 80,
session: [], session: [],
@ -121,13 +121,43 @@ export const addCommandHistoryReducer = (state, action) => {
} }
} }
export const remixWelcomeTextReducer = (state, action) => {
switch (action.type) {
case 'welcomeText' :
return {
...state,
journalBlocks: initialState.journalBlocks.push(action.payload.welcomeText)
}
}
}
export const registerScriptRunnerReducer = (state, action) => { export const registerScriptRunnerReducer = (state, action) => {
console.log({ state }, { action }, 'register script runner reducer') console.log({ state }, { action }, 'register script runner reducer')
switch (action.type) { switch (action.type) {
case 'log': case 'log':
return { return {
...state ...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' })
} }
} }
} }

@ -1,6 +1,21 @@
element.style { element.style {
height: 323px !important; height: 323px !important;
}
#terminalCliInput{
width: 95%;
background-color: #222336;
color: #a2a3bd;
border-top-style: hidden;
border-right-style: hidden;
border-left-style: hidden;
border-bottom-style: hidden;
}
#terminalCliInput:focus {
outline: none;
}
input #terminalCliInput {
} }
.panel { .panel {
position : relative; position : relative;
@ -152,3 +167,71 @@ element.style {
} }
.ul {margin-left: 0; padding-left: 20px;} .ul {margin-left: 0; padding-left: 20px;}
.popup {
position : absolute;
text-align : left;
display : none;
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}
}

@ -1,8 +1,11 @@
import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent } from 'react' // eslint-disable-line import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent } from 'react' // eslint-disable-line
import { useKeyPress } from './custom-hooks/useKeyPress' // eslint-disable-line import { useKeyPress } from './custom-hooks/useKeyPress' // eslint-disable-line
import { useWindowResize } from 'beautiful-react-hooks' import { useWindowResize } from 'beautiful-react-hooks'
import { registerCommandAction, filterFnAction, registerLogScriptRunnerAction, registerInfoScriptRunnerAction, registerErrorScriptRunnerAction, registerWarnScriptRunnerAction } from './actions/terminalAction' import { registerCommandAction, filterFnAction, registerLogScriptRunnerAction, registerInfoScriptRunnerAction, registerErrorScriptRunnerAction, registerWarnScriptRunnerAction, registerRemixWelcomeTextAction } from './actions/terminalAction'
import { initialState, registerCommandReducer, registerFilterReducer, addCommandHistoryReducer, registerScriptRunnerReducer } from './reducers/terminalReducer' import { initialState, registerCommandReducer, registerFilterReducer, addCommandHistoryReducer, registerScriptRunnerReducer, remixWelcomeTextReducer } from './reducers/terminalReducer'
import { remixWelcome } from './reducers/remixWelcom'
import {getKeyOf, getValueOf} from './utils/utils'
import {allCommands, allPrograms} from './commands' // eslint-disable-line
import javascriptserialize from 'javascript-serialize' import javascriptserialize from 'javascript-serialize'
import jsbeautify from 'js-beautify' import jsbeautify from 'js-beautify'
@ -23,12 +26,7 @@ export interface RemixUiTerminalProps {
version: any version: any
config: any config: any
thisState: any thisState: any
vm: any
// blockRenderHtml: any
// blockRenderLog: any
// blockRenderInfo: any
// blockRenderWarn: any
// blockRenderError: any
} }
export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> { export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> {
@ -52,6 +50,22 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const [filterState, filterDispatch] = useReducer(registerFilterReducer, initialState) const [filterState, filterDispatch] = useReducer(registerFilterReducer, initialState)
const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState) const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState)
const [scriptRunnserState, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState) const [scriptRunnserState, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState)
const [welcomeTextState, welcomTextDispath] = useReducer(remixWelcomeTextReducer, initialState)
const [autoCompletState, setAutoCompleteState] = useState({
activeSuggestion: 0,
data: {
_options: []
},
_startingElement: 0,
_elementToShow: 4,
_selectedElement: 0,
filteredCommands: [],
filteredPrograms: [],
showSuggestions: false,
text: '',
userInput: '',
extraCommands: []
})
const [state, setState] = useState({ const [state, setState] = useState({
journalBlocks: { journalBlocks: {
@ -111,6 +125,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const inputEl = useRef(null) const inputEl = useRef(null)
// events // events
useEffect(() => { useEffect(() => {
registerRemixWelcomeTextAction(remixWelcome, welcomTextDispath)
registerLogScriptRunnerAction(props.thisState, 'log', newstate.commands, scriptRunnerDispatch) registerLogScriptRunnerAction(props.thisState, 'log', newstate.commands, scriptRunnerDispatch)
registerInfoScriptRunnerAction(props.thisState, 'info', newstate.commands, scriptRunnerDispatch) registerInfoScriptRunnerAction(props.thisState, 'info', newstate.commands, scriptRunnerDispatch)
registerWarnScriptRunnerAction(props.thisState, 'warn', newstate.commands, scriptRunnerDispatch) registerWarnScriptRunnerAction(props.thisState, 'warn', newstate.commands, scriptRunnerDispatch)
@ -122,9 +137,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
registerCommandAction('error', _blocksRenderer('error'), { activate: true }, dispatch) registerCommandAction('error', _blocksRenderer('error'), { activate: true }, dispatch)
registerCommandAction('script', function execute (args, scopedCommands, append) { registerCommandAction('script', function execute (args, scopedCommands, append) {
var script = String(args[0]) var script = String(args[0])
props.thisState._shell(script, scopedCommands, function (error, output) { _shell(script, scopedCommands, function (error, output) {
if (error) scopedCommands.error(error) console.log({ error }, 'registerCommand scrpt')
else if (output) scopedCommands.log(output) console.log({ output }, 'registerCommand scrpt 2')
if (error) scriptRunnerDispatch({ type: 'error', payload: { message: error } })
else if (output) scriptRunnerDispatch({ type: 'error', payload: { message: output } })
}) })
}, { activate: true }, dispatch) }, { activate: true }, dispatch)
filterFnAction('log', basicFilter, filterDispatch) filterFnAction('log', basicFilter, filterDispatch)
@ -140,7 +157,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
// dispatch({ type: 'html', payload: { commands: htmlresullt.commands } }) // dispatch({ type: 'html', payload: { commands: htmlresullt.commands } })
// dispatch({ type: 'log', payload: { _commands: logresult._commands } }) // dispatch({ type: 'log', payload: { _commands: logresult._commands } })
// registerCommand('log', _blocksRenderer('log'), { activate: true }) // registerCommand('log', _blocksRenderer('log'), { activate: true })
}, []) }, [newstate.journalBlocks, props.thisState.autoCompletePopup, autoCompletState.text])
const placeCaretAtEnd = (el) => { const placeCaretAtEnd = (el) => {
el.focus() el.focus()
@ -152,6 +169,35 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
sel.addRange(range) sel.addRange(range)
} }
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
var context = props.cmdInterpreter
try {
var cmds = props.vm.createContext(context)
var result = props.vm.runInContext(script, cmds)
return done(null, result)
} catch (error) {
return done(error.message)
}
}
try {
if (script.trim().startsWith('git')) {
// result = await this.call('git', 'execute', script)
} else {
result = await props.thisState.call('scriptRunner', 'execute', script)
}
done()
} catch (error) {
done(error.message || error)
}
}
// handle events // handle events
const handlePaste = (event: ClipboardEvent<HTMLInputElement>) => { const handlePaste = (event: ClipboardEvent<HTMLInputElement>) => {
// Do something // Do something
@ -204,35 +250,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
inputEl.current.focus() inputEl.current.focus()
} }
// const putCursor2End = (editable) => {
// var range = document.createRange()
// console.log({ range })
// range.selectNode(editable)
// var child = editable
// var chars
// console.log({ child })
// while (child) {
// if (child.lastChild) child = child.lastChild
// else break
// if (child.nodeType === Node.TEXT_NODE) {
// chars = child.textContent.length
// } else {
// chars = child.innerHTML.length
// }
// }
// range.setEnd(child, chars)
// var toStart = true
// var toEnd = !toStart
// range.collapse(toEnd)
// var sel = window.getSelection()
// sel.removeAllRanges()
// sel.addRange(range)
// editable.focus()
// }
const wrapScript = (script) => { const wrapScript = (script) => {
const isKnownScript = ['remix.', 'git'].some(prefix => script.trim().startsWith(prefix)) const isKnownScript = ['remix.', 'git'].some(prefix => script.trim().startsWith(prefix))
if (isKnownScript) return script if (isKnownScript) return script
@ -251,39 +268,24 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} }
const handleKeyDown = (event) => { const handleKeyDown = (event) => {
if (props.autoCompletePopup.handleAutoComplete(
event,
inputEl.current.innerText)) { return }
if (inputEl.current.innerText.length === 0) {
inputEl.current.innerText += '\n'
}
if (event.which === 13) { if (event.which === 13) {
if (event.ctrlKey) { // <ctrl+enter> if (event.ctrlKey) { // <ctrl+enter>
console.log(event.which === 32, ' enter key')
// on enter, append the value in the cli input to the journal // on enter, append the value in the cli input to the journal
// setState(prevState => ({...prevState.journalBlocks, prevState: inputEl})
inputEl.current.innerText += '\n'
inputEl.current.focus() inputEl.current.focus()
// putCursor2End(inputEl.current)
// scroll2botton () function not implemented
props.autoCompletePopup.removeAutoComplete()
} else { // <enter> } else { // <enter>
event.preventDefault()
console.log('hit enter') console.log('hit enter')
setCmdIndex(-1) setCmdIndex(-1)
setCmdTemp('') setCmdTemp('')
const script = inputEl.current.innerText.trim() const script = autoCompletState.userInput.trim() // inputEl.current.innerText.trim()
console.log({ script }, ' script ')
if (script.length) { if (script.length) {
cmdHistoryDispatch({ type: 'cmdHistory', payload: { script } }) cmdHistoryDispatch({ type: 'cmdHistory', payload: { script } })
const result = newstate.commands.script(wrapScript(script)) newstate.commands.script(wrapScript(script))
console.log({ result })
} }
// inputEl.current.innerText += '\n' setAutoCompleteState(prevState => ({ ...prevState, userInput: '' }))
// if (script.length) { inputEl.current.innerText = ''
// // self._cmdHistory.unshift(script) inputEl.current.focus()
// props.command.script(wrapScript(script)) setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false }))
// }
// props.autoCompletePopup.removeAutoComplete()
} }
} else if (event.which === 38) { // <arrowUp> } else if (event.which === 38) { // <arrowUp>
const len = _cmdHistory.length const len = _cmdHistory.length
@ -293,8 +295,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} }
inputEl.current.innerText = _cmdHistory[_cmdIndex] inputEl.current.innerText = _cmdHistory[_cmdIndex]
inputEl.current.focus() inputEl.current.focus()
// putCursor2End(inputEl.current)
// self.scroll2bottom()
} }
else if (event.which === 40) { else if (event.which === 40) {
if (_cmdIndex > -1) { if (_cmdIndex > -1) {
@ -302,14 +302,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} }
inputEl.current.innerText = _cmdIndex >= 0 ? _cmdHistory[_cmdIndex] : _cmdTemp inputEl.current.innerText = _cmdIndex >= 0 ? _cmdHistory[_cmdIndex] : _cmdTemp
inputEl.current.focus() inputEl.current.focus()
// putCursor2End(inputEl.current)
// self.scroll2bottom()
} else { } else {
setCmdTemp(inputEl.current.innerText) setCmdTemp(inputEl.current.innerText)
} }
console.log({ _cmdHistory })
console.log({ _cmdIndex })
console.log({ _cmdTemp })
} }
const moveGhostbar = (event) => { const moveGhostbar = (event) => {
@ -347,19 +342,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const newEditorHeight = leftHeight - e.clientY + separatorYPosition const newEditorHeight = leftHeight - e.clientY + separatorYPosition
const newLeftHeight = leftHeight + separatorYPosition - e.clientY const newLeftHeight = leftHeight + separatorYPosition - e.clientY
setSeparatorYPosition(e.clientY) setSeparatorYPosition(e.clientY)
// if (newLeftHeight < MIN_HEIGHT) {
// setLeftHeight(MIN_HEIGHT)
// return
// }
// if (splitPaneRef.current) {
// const splitPaneHeight = splitPaneRef.current.clientHeight
// if (newLeftHeight > splitPaneHeight - MIN_HEIGHT) {
// setLeftHeight(splitPaneHeight - MIN_HEIGHT)
// return
// }
// }
setLeftHeight(newLeftHeight) setLeftHeight(newLeftHeight)
props.event.trigger('resize', [newLeftHeight + 32]) props.event.trigger('resize', [newLeftHeight + 32])
console.log({ newLeftHeight }) console.log({ newLeftHeight })
@ -415,7 +397,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} }
leftRef.style.height = `${leftHeight}px` leftRef.style.height = `${leftHeight}px`
} }
}, [leftHeight, setLeftHeight]) }, [leftHeight, setLeftHeight, inputEl])
/* block contents that gets rendered from scriptRunner */ /* block contents that gets rendered from scriptRunner */
@ -429,7 +411,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} }
} }
mode = { mode = {
log: 'text-info', log: 'text-log',
info: 'text-info', info: 'text-info',
warn: 'text-warning', warn: 'text-warning',
error: 'text-danger' error: 'text-danger'
@ -466,9 +448,78 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
/* end of block content that gets rendered from script Runner */ /* end of block content that gets rendered from script Runner */
/* start of autoComplete */
const handleSelect = (text) => {
props.thisState.event.trigger('handleSelect', [text])
}
const onChange = (event: any) => {
event.preventDefault()
const inputString = event.target.value
console.log(event)
console.log({ inputString })
setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: true, userInput: inputString }))
const textList = inputString.split(' ')
const autoCompleteInput = textList.length > 1 ? textList[textList.length - 1] : textList[0]
allPrograms.forEach(item => {
const program = getKeyOf(item)
console.log({ program })
if (program.substring(0, program.length - 1).includes(autoCompleteInput.trim())) {
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [item] } }))
} else if (autoCompleteInput.trim().includes(program) || (program === autoCompleteInput.trim())) {
allCommands.forEach(item => {
console.log({ item })
const command = getKeyOf(item)
if (command.includes(autoCompleteInput.trim())) {
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [item] } }))
}
})
}
})
autoCompletState.extraCommands.forEach(item => {
const command = getKeyOf(item)
if (command.includes(autoCompleteInput.trim())) {
setAutoCompleteState(prevState => ({ ...prevState, data: { _options: [item] } }))
}
})
if (autoCompletState.data._options.length === 1 && event.which === 9) {
// if only one option and tab is pressed, we resolve it
event.preventDefault()
textList.pop()
textList.push(getKeyOf(autoCompletState.data._options[0]))
handleSelect(`${textList}`.replace(/,/g, ' '))
}
}
const handleAutoComplete = () => (
<div className="popup alert alert-secondary">
<div>
${autoCompletState.data._options.map((item, index) => {
return (
<div key={index}>auto complete here</div>
// <div data-id="autoCompletePopUpAutoCompleteItem" className={`autoCompleteItem listHandlerHide item ${_selectedElement === index ? 'border border-primary' : ''}`}>
// <div value={index} onClick={(event) => { handleSelect(event.srcElement.innerText) }}>
// {getKeyOf(item)}
// </div>
// <div>
// {getValueOf(item)}
// </div>
// </div>
)
})}
</div>
{/* <div className="listHandlerHide">
<div className="pageNumberAlignment">Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(data._options.length / self._elementsToShow)}</div>
</div> */}
</div>
)
/* end of autoComplete */
return ( return (
<div style={{ height: '323px' }} className='panel_2A0YE0'> <div style={{ height: '323px' }} className='panel_2A0YE0'>
{console.log({ newstate })} {console.log({ newstate })}
{console.log({ props })}
{console.log({ autoCompletState })}
<div className="bar_2A0YE0"> <div className="bar_2A0YE0">
{/* ${self._view.dragbar} */} {/* ${self._view.dragbar} */}
<div className="dragbarHorizontal" onMouseDown={mousedown} id='dragId'></div> <div className="dragbarHorizontal" onMouseDown={mousedown} id='dragId'></div>
@ -513,8 +564,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
</div> </div>
</div> </div>
<div tabIndex={-1} className="terminal_container_2A0YE0" data-id="terminalContainer" > <div tabIndex={-1} className="terminal_container_2A0YE0" data-id="terminalContainer" >
{/* onScroll=${throttle(reattach, 10)} onkeydown=${focusinput} */} {
{/* {props.autoCompletePopup.render()} */} (autoCompletState.showSuggestions && autoCompletState.userInput) && handleAutoComplete()
}
<div data-id="terminalContainerDisplay" style = {{ <div data-id="terminalContainerDisplay" style = {{
position: 'absolute', position: 'absolute',
height: '100', height: '100',
@ -524,13 +576,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}}></div> }}></div>
<div className="terminal_2A0YE0"> <div className="terminal_2A0YE0">
<div id="journal" className="journal_2A0YE0" data-id="terminalJournal"> <div id="journal" className="journal_2A0YE0" data-id="terminalJournal">
<div className="px-4 block_2A0YE0" data-id="block_null"> {(newstate.journalBlocks).map((x, index) => (
{Object.entries(state.journalBlocks).map((x, index) => ( <div className="px-4 block_2A0YE0" data-id="block_null" key={index}>
<div key={index}> <span className={x.style}>{x.message}</span>
{x} </div>
</div> ))}
))}
</div>
<div className="anchor"> <div className="anchor">
{/* ${background} */} {/* ${background} */}
<div className="overlay background"></div> <div className="overlay background"></div>
@ -540,7 +590,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
</div> </div>
<div id="terminalCli" data-id="terminalCli" className="cli_2A0YE0" onClick={focusinput}> <div id="terminalCli" data-id="terminalCli" className="cli_2A0YE0" onClick={focusinput}>
<span className="prompt_2A0YE0">{'>'}</span> <span className="prompt_2A0YE0">{'>'}</span>
<span className="input_2A0YE0" ref={inputEl} spellCheck="false" contentEditable="true" id="terminalCliInput" data-id="terminalCliInput" onPaste={handlePaste} onKeyDown={ handleKeyDown }></span> <input className="input_2A0YE0" ref={inputEl} spellCheck="false" contentEditable="true" id="terminalCliInput" data-id="terminalCliInput" onChange={(event) => onChange(event)} onKeyDown={(event) => handleKeyDown(event) } value={autoCompletState.userInput}></input>
</div> </div>
</div> </div>
</div> </div>

@ -0,0 +1,8 @@
export const getKeyOf = (item) => {
return Object.keys(item)[0]
}
export const getValueOf = (item) => {
return Object.values(item)[0]
}
Loading…
Cancel
Save