From d2c5b07170a19040ef97d2037acbccc1a907ff9a Mon Sep 17 00:00:00 2001 From: "davidzagi93@gmail.com" Date: Sat, 24 Jul 2021 22:04:45 +0100 Subject: [PATCH] implementing command execution script in react --- apps/remix-ide/src/app/panels/terminal.js | 14 +- .../src/lib/actions/terminalAction.ts | 4 + libs/remix-ui/terminal/src/lib/commands.ts | 52 ++++ .../terminal/src/lib/reducers/remixWelcom.ts | 25 ++ .../src/lib/reducers/terminalReducer.ts | 40 ++- .../terminal/src/lib/remix-ui-terminal.css | 85 ++++++- .../terminal/src/lib/remix-ui-terminal.tsx | 240 +++++++++++------- libs/remix-ui/terminal/src/lib/utils/utils.ts | 8 + 8 files changed, 355 insertions(+), 113 deletions(-) create mode 100644 libs/remix-ui/terminal/src/lib/commands.ts create mode 100644 libs/remix-ui/terminal/src/lib/reducers/remixWelcom.ts create mode 100644 libs/remix-ui/terminal/src/lib/utils/utils.ts diff --git a/apps/remix-ide/src/app/panels/terminal.js b/apps/remix-ide/src/app/panels/terminal.js index 39c5c844b1..9facd299ef 100644 --- a/apps/remix-ide/src/app/panels/terminal.js +++ b/apps/remix-ide/src/app/panels/terminal.js @@ -39,6 +39,7 @@ class Terminal extends Plugin { this.element.setAttribute('id', 'terminal-view') this.event = new EventManager() this.blockchain = opts.blockchain + this.vm = vm this._api = api this._opts = opts this.config = config @@ -75,18 +76,6 @@ class Terminal extends Plugin { } 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() } @@ -148,6 +137,7 @@ class Terminal extends Plugin { version = {this.version} config = {this.config} thisState = {this} + vm = {this.vm} />, this.element ) diff --git a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts index a986dd8ddc..38c58f3555 100644 --- a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts +++ b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts @@ -129,3 +129,7 @@ export const registerErrorScriptRunnerAction = (event, commandName, commandFn, d dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) }) } + +export const registerRemixWelcomeTextAction = (welcomeText, dispatch) => { + dispatch({ type: 'welcomeText', payload: { welcomeText } }) +} diff --git a/libs/remix-ui/terminal/src/lib/commands.ts b/libs/remix-ui/terminal/src/lib/commands.ts new file mode 100644 index 0000000000..14a3cbb97b --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/commands.ts @@ -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' } +] diff --git a/libs/remix-ui/terminal/src/lib/reducers/remixWelcom.ts b/libs/remix-ui/terminal/src/lib/reducers/remixWelcom.ts new file mode 100644 index 0000000000..2b3e392aab --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/reducers/remixWelcom.ts @@ -0,0 +1,25 @@ +export const remixWelcome = () => { + return `
+
- Welcome to Remix {props.version} -
+
+
You can use this terminal to:
+ +
The following libraries are accessible:
+ +
` +} \ No newline at end of file diff --git a/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts b/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts index c4a849a1b8..18d8a0a0d8 100644 --- a/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts +++ b/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts @@ -1,7 +1,7 @@ -export const initialState = { - journalBlocks: { - }, +export const initialState = { + journalBlocks: [ + ], data: { // lineLength: props.options.lineLength || 80, 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) => { console.log({ state }, { action }, 'register script runner reducer') switch (action.type) { case 'log': 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' }) } } } diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css index 99a0a3927c..0091042df2 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css @@ -1,6 +1,21 @@ element.style { 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 { position : relative; @@ -151,4 +166,72 @@ element.style { padding: 0 1rem; } - .ul {margin-left: 0; padding-left: 20px;} \ No newline at end of file + .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} + } \ No newline at end of file diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index 2316173ede..cb2d91393a 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -1,8 +1,11 @@ import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent } from 'react' // eslint-disable-line import { useKeyPress } from './custom-hooks/useKeyPress' // eslint-disable-line import { useWindowResize } from 'beautiful-react-hooks' -import { registerCommandAction, filterFnAction, registerLogScriptRunnerAction, registerInfoScriptRunnerAction, registerErrorScriptRunnerAction, registerWarnScriptRunnerAction } from './actions/terminalAction' -import { initialState, registerCommandReducer, registerFilterReducer, addCommandHistoryReducer, registerScriptRunnerReducer } from './reducers/terminalReducer' +import { registerCommandAction, filterFnAction, registerLogScriptRunnerAction, registerInfoScriptRunnerAction, registerErrorScriptRunnerAction, registerWarnScriptRunnerAction, registerRemixWelcomeTextAction } from './actions/terminalAction' +import { initialState, registerCommandReducer, registerFilterReducer, addCommandHistoryReducer, registerScriptRunnerReducer, remixWelcomeTextReducer } from './reducers/terminalReducer' +import { remixWelcome } from './reducers/remixWelcom' +import {getKeyOf, getValueOf} from './utils/utils' +import {allCommands, allPrograms} from './commands' // eslint-disable-line import javascriptserialize from 'javascript-serialize' import jsbeautify from 'js-beautify' @@ -23,12 +26,7 @@ export interface RemixUiTerminalProps { version: any config: any thisState: any - - // blockRenderHtml: any - // blockRenderLog: any - // blockRenderInfo: any - // blockRenderWarn: any - // blockRenderError: any + vm: any } export interface ClipboardEvent extends SyntheticEvent { @@ -52,6 +50,22 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { const [filterState, filterDispatch] = useReducer(registerFilterReducer, initialState) const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, 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({ journalBlocks: { @@ -111,6 +125,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { const inputEl = useRef(null) // events useEffect(() => { + registerRemixWelcomeTextAction(remixWelcome, welcomTextDispath) registerLogScriptRunnerAction(props.thisState, 'log', newstate.commands, scriptRunnerDispatch) registerInfoScriptRunnerAction(props.thisState, 'info', 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('script', function execute (args, scopedCommands, append) { var script = String(args[0]) - props.thisState._shell(script, scopedCommands, function (error, output) { - if (error) scopedCommands.error(error) - else if (output) scopedCommands.log(output) + _shell(script, scopedCommands, function (error, output) { + console.log({ error }, 'registerCommand scrpt') + console.log({ output }, 'registerCommand scrpt 2') + if (error) scriptRunnerDispatch({ type: 'error', payload: { message: error } }) + else if (output) scriptRunnerDispatch({ type: 'error', payload: { message: output } }) }) }, { activate: true }, dispatch) filterFnAction('log', basicFilter, filterDispatch) @@ -140,7 +157,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { // dispatch({ type: 'html', payload: { commands: htmlresullt.commands } }) // dispatch({ type: 'log', payload: { _commands: logresult._commands } }) // registerCommand('log', _blocksRenderer('log'), { activate: true }) - }, []) + }, [newstate.journalBlocks, props.thisState.autoCompletePopup, autoCompletState.text]) const placeCaretAtEnd = (el) => { el.focus() @@ -152,6 +169,35 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { 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 const handlePaste = (event: ClipboardEvent) => { // Do something @@ -204,35 +250,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { 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 isKnownScript = ['remix.', 'git'].some(prefix => script.trim().startsWith(prefix)) if (isKnownScript) return script @@ -251,39 +268,24 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { } 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.ctrlKey) { // - console.log(event.which === 32, ' enter key') // 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() - // putCursor2End(inputEl.current) - // scroll2botton () function not implemented - props.autoCompletePopup.removeAutoComplete() } else { // + event.preventDefault() console.log('hit enter') setCmdIndex(-1) setCmdTemp('') - const script = inputEl.current.innerText.trim() - console.log({ script }, ' script ') + const script = autoCompletState.userInput.trim() // inputEl.current.innerText.trim() if (script.length) { cmdHistoryDispatch({ type: 'cmdHistory', payload: { script } }) - const result = newstate.commands.script(wrapScript(script)) - console.log({ result }) + newstate.commands.script(wrapScript(script)) } - // inputEl.current.innerText += '\n' - // if (script.length) { - // // self._cmdHistory.unshift(script) - // props.command.script(wrapScript(script)) - // } - // props.autoCompletePopup.removeAutoComplete() + setAutoCompleteState(prevState => ({ ...prevState, userInput: '' })) + inputEl.current.innerText = '' + inputEl.current.focus() + setAutoCompleteState(prevState => ({ ...prevState, showSuggestions: false })) } } else if (event.which === 38) { // const len = _cmdHistory.length @@ -293,8 +295,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { } inputEl.current.innerText = _cmdHistory[_cmdIndex] inputEl.current.focus() - // putCursor2End(inputEl.current) - // self.scroll2bottom() } else if (event.which === 40) { if (_cmdIndex > -1) { @@ -302,14 +302,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { } inputEl.current.innerText = _cmdIndex >= 0 ? _cmdHistory[_cmdIndex] : _cmdTemp inputEl.current.focus() - // putCursor2End(inputEl.current) - // self.scroll2bottom() } else { setCmdTemp(inputEl.current.innerText) } - console.log({ _cmdHistory }) - console.log({ _cmdIndex }) - console.log({ _cmdTemp }) } const moveGhostbar = (event) => { @@ -347,19 +342,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { const newEditorHeight = leftHeight - e.clientY + separatorYPosition const newLeftHeight = leftHeight + separatorYPosition - 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) props.event.trigger('resize', [newLeftHeight + 32]) console.log({ newLeftHeight }) @@ -415,7 +397,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { } leftRef.style.height = `${leftHeight}px` } - }, [leftHeight, setLeftHeight]) + }, [leftHeight, setLeftHeight, inputEl]) /* block contents that gets rendered from scriptRunner */ @@ -429,7 +411,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { } } mode = { - log: 'text-info', + log: 'text-log', info: 'text-info', warn: 'text-warning', error: 'text-danger' @@ -466,9 +448,78 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { /* 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 = () => ( +
+
+ ${autoCompletState.data._options.map((item, index) => { + return ( +
auto complete here
+ //
+ //
{ handleSelect(event.srcElement.innerText) }}> + // {getKeyOf(item)} + //
+ //
+ // {getValueOf(item)} + //
+ //
+ ) + })} +
+ {/*
+
Page ${(self._startingElement / self._elementsToShow) + 1} of ${Math.ceil(data._options.length / self._elementsToShow)}
+
*/} +
+ ) + /* end of autoComplete */ + return (
{console.log({ newstate })} + {console.log({ props })} + {console.log({ autoCompletState })}
{/* ${self._view.dragbar} */}
@@ -513,8 +564,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
- {/* onScroll=${throttle(reattach, 10)} onkeydown=${focusinput} */} - {/* {props.autoCompletePopup.render()} */} + { + (autoCompletState.showSuggestions && autoCompletState.userInput) && handleAutoComplete() + }
{ }}>
-
- {Object.entries(state.journalBlocks).map((x, index) => ( -
- {x} -
- ))} -
+ {(newstate.journalBlocks).map((x, index) => ( +
+ {x.message} +
+ ))}
{/* ${background} */}
@@ -540,7 +590,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
{'>'} - + onChange(event)} onKeyDown={(event) => handleKeyDown(event) } value={autoCompletState.userInput}>
diff --git a/libs/remix-ui/terminal/src/lib/utils/utils.ts b/libs/remix-ui/terminal/src/lib/utils/utils.ts new file mode 100644 index 0000000000..46ca37fa89 --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/utils/utils.ts @@ -0,0 +1,8 @@ + +export const getKeyOf = (item) => { + return Object.keys(item)[0] +} + +export const getValueOf = (item) => { + return Object.values(item)[0] +}