|
|
|
@ -1,4 +1,6 @@ |
|
|
|
|
import React, { useState, useEffect, useRef, SyntheticEvent } from 'react' // eslint-disable-line
|
|
|
|
|
import { useKeyPress } from './custom-hooks/useKeyPress' |
|
|
|
|
import { useWindowResize } from 'beautiful-react-hooks' |
|
|
|
|
|
|
|
|
|
import './remix-ui-terminal.css' |
|
|
|
|
|
|
|
|
@ -14,6 +16,14 @@ export interface RemixUiTerminalProps { |
|
|
|
|
data: any |
|
|
|
|
cmdInterpreter: any |
|
|
|
|
registerCommand: any |
|
|
|
|
command: any |
|
|
|
|
version: any |
|
|
|
|
|
|
|
|
|
// blockRenderHtml: any
|
|
|
|
|
// blockRenderLog: any
|
|
|
|
|
// blockRenderInfo: any
|
|
|
|
|
// blockRenderWarn: any
|
|
|
|
|
// blockRenderError: any
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> { |
|
|
|
@ -24,8 +34,40 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { |
|
|
|
|
|
|
|
|
|
const [toggleDownUp, setToggleDownUp] = useState('fa-angle-double-down') |
|
|
|
|
const [inserted, setInserted] = useState(false) |
|
|
|
|
const [_cmdIndex, setCmdIndex] = useState(-1) |
|
|
|
|
const [_cmdTemp, setCmdTemp] = useState('') |
|
|
|
|
const [_cmdHistory, setCmdHistory] = useState([]) |
|
|
|
|
const [windowHeight, setWindowHeight] = useState(window.innerHeight) |
|
|
|
|
|
|
|
|
|
const [state, setState] = useState({ |
|
|
|
|
journalBlocks: { |
|
|
|
|
intro: ( |
|
|
|
|
<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> |
|
|
|
|
), |
|
|
|
|
text: (<div>David</div>) |
|
|
|
|
}, |
|
|
|
|
data: { |
|
|
|
|
// lineLength: props.options.lineLength || 80,
|
|
|
|
|
session: [], |
|
|
|
@ -43,6 +85,15 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { |
|
|
|
|
_INDEXcommandsMain: {} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const _scopedCommands = () => { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
useWindowResize(() => { |
|
|
|
|
setWindowHeight(window.innerHeight) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// terminal inputRef
|
|
|
|
|
const inputEl = useRef(null) |
|
|
|
|
// events
|
|
|
|
@ -59,6 +110,16 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { |
|
|
|
|
// }
|
|
|
|
|
}, []) |
|
|
|
|
|
|
|
|
|
const placeCaretAtEnd = (el) => { |
|
|
|
|
el.focus() |
|
|
|
|
const range = document.createRange() |
|
|
|
|
range.selectNodeContents(el) |
|
|
|
|
range.collapse(false) |
|
|
|
|
const sel = window.getSelection() |
|
|
|
|
sel.removeAllRanges() |
|
|
|
|
sel.addRange(range) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// handle events
|
|
|
|
|
const handlePaste = (event: ClipboardEvent<HTMLInputElement>) => { |
|
|
|
|
// Do something
|
|
|
|
@ -66,27 +127,28 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { |
|
|
|
|
if (!selection.rangeCount) return false |
|
|
|
|
event.preventDefault() |
|
|
|
|
event.stopPropagation() |
|
|
|
|
var clipboard = (event.clipboardData) // || window.clipboardData
|
|
|
|
|
var text = clipboard.getData('text/plain') |
|
|
|
|
const clipboard = (event.clipboardData) // || window.clipboardData
|
|
|
|
|
let text = clipboard.getData('text/plain') |
|
|
|
|
text = text.replace(/[^\x20-\xFF]/gi, '') // remove non-UTF-8 characters
|
|
|
|
|
var temp = document.createElement('div') |
|
|
|
|
const temp = document.createElement('div') |
|
|
|
|
temp.innerHTML = text |
|
|
|
|
var textnode = document.createTextNode(temp.textContent) |
|
|
|
|
const textnode = document.createTextNode(temp.textContent) |
|
|
|
|
selection.getRangeAt(0).insertNode(textnode) |
|
|
|
|
selection.empty() |
|
|
|
|
// self.scroll2bottom()
|
|
|
|
|
// placeCaretAtEnd(event.currentTarget)
|
|
|
|
|
placeCaretAtEnd(event.currentTarget) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const handleMinimizeTerminal = (event) => { |
|
|
|
|
console.log('clikced', props.event) |
|
|
|
|
event.preventDefault() |
|
|
|
|
event.stopPropagation() |
|
|
|
|
if (toggleDownUp === 'fa-angle-double-down') { |
|
|
|
|
console.log('clikced down') |
|
|
|
|
setToggleDownUp('fa-angle-double-up') |
|
|
|
|
props.event.trigger.resize('resize', []) |
|
|
|
|
props.event.trigger('resize', []) |
|
|
|
|
} else { |
|
|
|
|
console.log('clikced up') |
|
|
|
|
// event.trigger('resize', [])
|
|
|
|
|
props.event.trigger('resize', [118]) |
|
|
|
|
setToggleDownUp('fa-angle-double-down') |
|
|
|
|
} |
|
|
|
|
console.log(props.event, 'event.trigger') |
|
|
|
@ -161,24 +223,151 @@ 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 |
|
|
|
|
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) |
|
|
|
|
}` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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) { // <ctrl+enter>
|
|
|
|
|
// 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 { |
|
|
|
|
setCmdIndex(-1) |
|
|
|
|
setCmdTemp('') |
|
|
|
|
const script = inputEl.current.innerText.trim() |
|
|
|
|
console.log({ script }) |
|
|
|
|
inputEl.current.innerText += '\n' |
|
|
|
|
if (script.length) { |
|
|
|
|
// self._cmdHistory.unshift(script)
|
|
|
|
|
props.command.script(wrapScript(script)) |
|
|
|
|
} |
|
|
|
|
props.autoCompletePopup.removeAutoComplete() |
|
|
|
|
} |
|
|
|
|
} else if (event.which === 38) { // <arrowUp>
|
|
|
|
|
const len = _cmdHistory.length |
|
|
|
|
if (len === 0) event.preventDefault() |
|
|
|
|
if (_cmdHistory.length - 1 > _cmdIndex) { |
|
|
|
|
setCmdIndex(prevState => prevState++) |
|
|
|
|
} |
|
|
|
|
inputEl.current.innerText = _cmdHistory[_cmdIndex] |
|
|
|
|
inputEl.current.focus() |
|
|
|
|
// putCursor2End(inputEl.current)
|
|
|
|
|
// self.scroll2bottom()
|
|
|
|
|
} |
|
|
|
|
else if (event.which === 40) { |
|
|
|
|
if (_cmdIndex > -1) { |
|
|
|
|
setCmdIndex(prevState => prevState--) |
|
|
|
|
} |
|
|
|
|
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) => { |
|
|
|
|
return props.api.getPosition(event) + 'px' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const removeGhostbar = (event) => { |
|
|
|
|
if (toggleDownUp === 'fa-angle-double-up') { |
|
|
|
|
console.log('remove event') |
|
|
|
|
setToggleDownUp('fa-angle-double-down') |
|
|
|
|
} |
|
|
|
|
props.event.trigger('resize', [props.api.getPostion(event)]) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const mousedown = (event) => { |
|
|
|
|
console.log({ windowHeight }) |
|
|
|
|
console.log(event.which === 1, 'event.which === 1') |
|
|
|
|
event.preventDefault() |
|
|
|
|
moveGhostbar(event) |
|
|
|
|
if (event.which === 1) { |
|
|
|
|
console.log('event .which code 1') |
|
|
|
|
moveGhostbar(event) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const cancelGhostbar = (event) => { |
|
|
|
|
if (event.keyCode === 27) { |
|
|
|
|
console.log('event .key code 27') |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div> |
|
|
|
|
<div className="bar"> |
|
|
|
|
<div style={{ height: '323px' }} className='panel_2A0YE0'> |
|
|
|
|
<div className="bar_2A0YE0"> |
|
|
|
|
{/* ${self._view.dragbar} */} |
|
|
|
|
<div className="dragbarHorizontal"></div> |
|
|
|
|
<div className="menu border-top border-dark bg-light" data-id="terminalToggleMenu"> |
|
|
|
|
<div className="dragbarHorizontal_2A0YE0" onMouseDown={mousedown}></div> |
|
|
|
|
<div className="menu_2A0YE0 border-top border-dark bg-light" data-id="terminalToggleMenu"> |
|
|
|
|
{/* ${self._view.icon} */} |
|
|
|
|
<i className={`mx-2 toggleTerminal fas ${toggleDownUp}`} data-id="terminalToggleIcon" onClick={ handleMinimizeTerminal }></i> |
|
|
|
|
<i className={`mx-2 toggleTerminal_2A0YE0 fas ${toggleDownUp}`} data-id="terminalToggleIcon" onClick={ handleMinimizeTerminal }></i> |
|
|
|
|
<div className="mx-2" id="clearConsole" data-id="terminalClearConsole" > |
|
|
|
|
<i className="fas fa-ban" aria-hidden="true" title="Clear console" |
|
|
|
|
></i> |
|
|
|
|
</div> |
|
|
|
|
{/* ${self._view.pendingTxCount} */} |
|
|
|
|
<div className="mx-2" title='Pending Transactions'>0</div> |
|
|
|
|
<div className="verticalLine"></div> |
|
|
|
|
<div className="pt-1 h-80 mx-3 align-items-center listenOnNetwork custom-control custom-checkbox"> |
|
|
|
|
<div className="verticalLine_2A0YE0"></div> |
|
|
|
|
<div className="pt-1 h-80 mx-3 align-items-center listenOnNetwork_2A0YE0 custom-control custom-checkbox"> |
|
|
|
|
<input |
|
|
|
|
className="custom-control-input" |
|
|
|
|
id="listenNetworkCheck" |
|
|
|
@ -194,13 +383,13 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { |
|
|
|
|
listen on network |
|
|
|
|
</label> |
|
|
|
|
</div> |
|
|
|
|
<div className="search"> |
|
|
|
|
<i className="fas fa-search searchIcon bg-light" aria-hidden="true"></i> |
|
|
|
|
<div className="search_2A0YE0"> |
|
|
|
|
<i className="fas fa-search searchIcon_2A0YE0 bg-light" aria-hidden="true"></i> |
|
|
|
|
{/* ${self._view.inputSearch} */} |
|
|
|
|
<input |
|
|
|
|
// spellcheck = "false"
|
|
|
|
|
type="text" |
|
|
|
|
className="border filter form-control" |
|
|
|
|
className="border filter_2A0YE0 form-control" |
|
|
|
|
id="searchInput" |
|
|
|
|
// onkeydown=${filter}
|
|
|
|
|
placeholder="Search with transaction hash or address" |
|
|
|
@ -208,7 +397,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
<div className="terminal_container" data-id="terminalContainer" > |
|
|
|
|
<div tabIndex={-1} className="terminal_container_2A0YE0" data-id="terminalContainer" > |
|
|
|
|
{/* onScroll=${throttle(reattach, 10)} onkeydown=${focusinput} */} |
|
|
|
|
{/* {props.autoCompletePopup.render()} */} |
|
|
|
|
{console.log({ props })} |
|
|
|
@ -219,8 +408,15 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { |
|
|
|
|
opacity: '0.1', |
|
|
|
|
zIndex: -1 |
|
|
|
|
}}></div> |
|
|
|
|
<div className="terminal"> |
|
|
|
|
<div id="journal" className="journal" data-id="terminalJournal"> |
|
|
|
|
<div className="terminal_2A0YE0"> |
|
|
|
|
<div id="journal" className="journal_2A0YE0" data-id="terminalJournal"> |
|
|
|
|
<div className="px-4 block_2A0YE0" data-id="block_null"> |
|
|
|
|
{Object.entries(state.journalBlocks).map((x, index) => ( |
|
|
|
|
<div key={index}> |
|
|
|
|
{x} |
|
|
|
|
</div> |
|
|
|
|
))} |
|
|
|
|
</div> |
|
|
|
|
<div className="anchor"> |
|
|
|
|
{/* ${background} */} |
|
|
|
|
<div className="overlay background"></div> |
|
|
|
@ -228,9 +424,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { |
|
|
|
|
<div className="overlay text"></div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
<div id="terminalCli" data-id="terminalCli" className="cli" onClick={focusinput}> |
|
|
|
|
<div id="terminalCli" data-id="terminalCli" className="cli_2A0YE0" onClick={focusinput}> |
|
|
|
|
<span className="prompt">{'>'}</span> |
|
|
|
|
<span className="input" ref={inputEl} spellCheck="false" contentEditable="true" id="terminalCliInput" data-id="terminalCliInput" onPaste={handlePaste}></span> |
|
|
|
|
<span className="input" ref={inputEl} spellCheck="false" contentEditable="true" id="terminalCliInput" data-id="terminalCliInput" onPaste={handlePaste} onKeyDown={ handleKeyDown }></span> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|