implemented minimize terminal window and added custom hook

pull/5370/head
tizah 4 years ago committed by davidzagi93@gmail.com
parent 167473eafc
commit 1fda16c982
  1. 6
      apps/remix-ide/src/app/panels/terminal.js
  2. 29
      libs/remix-ui/terminal/src/lib/custom-hooks/useKeyPress.tsx
  3. 4
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.css
  4. 242
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  5. 19
      package-lock.json
  6. 3
      package.json

@ -34,11 +34,13 @@ class Terminal extends Plugin {
constructor (opts, api) { constructor (opts, api) {
super(profile) super(profile)
this.element = document.createElement('div') this.element = document.createElement('div')
this.element.setAttribute('id', 'terminalView') this.element.setAttribute('class', 'panel_2A0YE0')
this.element.setAttribute('id', 'terminal-view')
this.event = new EventManager() this.event = new EventManager()
this.blockchain = opts.blockchain this.blockchain = opts.blockchain
this._api = api this._api = api
this._opts = opts this._opts = opts
this.version = packageJson.version
this.data = { this.data = {
lineLength: opts.lineLength || 80, // ???? lineLength: opts.lineLength || 80, // ????
session: [], session: [],
@ -122,6 +124,8 @@ class Terminal extends Plugin {
cmdInterpreter = {this._components.cmdInterpreter} cmdInterpreter = {this._components.cmdInterpreter}
autoCompletePopup = {this._components.autoCompletePopup} autoCompletePopup = {this._components.autoCompletePopup}
registerCommand = {this.registerCommand} registerCommand = {this.registerCommand}
command = {this.commands}
version = {this.version}
/>, />,
this.element this.element
) )

@ -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
}

@ -1,3 +1,7 @@
element.style {
height: 323px !important;
}
.panel { .panel {
position : relative; position : relative;
display : flex; display : flex;

@ -1,4 +1,6 @@
import React, { useState, useEffect, useRef, SyntheticEvent } from 'react' // eslint-disable-line 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' import './remix-ui-terminal.css'
@ -14,6 +16,14 @@ export interface RemixUiTerminalProps {
data: any data: any
cmdInterpreter: any cmdInterpreter: any
registerCommand: 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> { 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 [toggleDownUp, setToggleDownUp] = useState('fa-angle-double-down')
const [inserted, setInserted] = useState(false) 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({ 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: { data: {
// lineLength: props.options.lineLength || 80, // lineLength: props.options.lineLength || 80,
session: [], session: [],
@ -43,6 +85,15 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
_INDEXcommandsMain: {} _INDEXcommandsMain: {}
}) })
const _scopedCommands = () => {
}
useWindowResize(() => {
setWindowHeight(window.innerHeight)
})
// terminal inputRef // terminal inputRef
const inputEl = useRef(null) const inputEl = useRef(null)
// events // 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 // handle events
const handlePaste = (event: ClipboardEvent<HTMLInputElement>) => { const handlePaste = (event: ClipboardEvent<HTMLInputElement>) => {
// Do something // Do something
@ -66,27 +127,28 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
if (!selection.rangeCount) return false if (!selection.rangeCount) return false
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
var clipboard = (event.clipboardData) // || window.clipboardData const clipboard = (event.clipboardData) // || window.clipboardData
var text = clipboard.getData('text/plain') let text = clipboard.getData('text/plain')
text = text.replace(/[^\x20-\xFF]/gi, '') // remove non-UTF-8 characters text = text.replace(/[^\x20-\xFF]/gi, '') // remove non-UTF-8 characters
var temp = document.createElement('div') const temp = document.createElement('div')
temp.innerHTML = text temp.innerHTML = text
var textnode = document.createTextNode(temp.textContent) const textnode = document.createTextNode(temp.textContent)
selection.getRangeAt(0).insertNode(textnode) selection.getRangeAt(0).insertNode(textnode)
selection.empty() selection.empty()
// self.scroll2bottom() // self.scroll2bottom()
// placeCaretAtEnd(event.currentTarget) placeCaretAtEnd(event.currentTarget)
} }
const handleMinimizeTerminal = (event) => { const handleMinimizeTerminal = (event) => {
console.log('clikced', props.event) event.preventDefault()
event.stopPropagation()
if (toggleDownUp === 'fa-angle-double-down') { if (toggleDownUp === 'fa-angle-double-down') {
console.log('clikced down') console.log('clikced down')
setToggleDownUp('fa-angle-double-up') setToggleDownUp('fa-angle-double-up')
props.event.trigger.resize('resize', []) props.event.trigger('resize', [])
} else { } else {
console.log('clikced up') console.log('clikced up')
// event.trigger('resize', []) props.event.trigger('resize', [118])
setToggleDownUp('fa-angle-double-down') setToggleDownUp('fa-angle-double-down')
} }
console.log(props.event, 'event.trigger') console.log(props.event, 'event.trigger')
@ -161,24 +223,151 @@ 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 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 ( return (
<div> <div style={{ height: '323px' }} className='panel_2A0YE0'>
<div className="bar"> <div className="bar_2A0YE0">
{/* ${self._view.dragbar} */} {/* ${self._view.dragbar} */}
<div className="dragbarHorizontal"></div> <div className="dragbarHorizontal_2A0YE0" onMouseDown={mousedown}></div>
<div className="menu border-top border-dark bg-light" data-id="terminalToggleMenu"> <div className="menu_2A0YE0 border-top border-dark bg-light" data-id="terminalToggleMenu">
{/* ${self._view.icon} */} {/* ${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" > <div className="mx-2" id="clearConsole" data-id="terminalClearConsole" >
<i className="fas fa-ban" aria-hidden="true" title="Clear console" <i className="fas fa-ban" aria-hidden="true" title="Clear console"
></i> ></i>
</div> </div>
{/* ${self._view.pendingTxCount} */} {/* ${self._view.pendingTxCount} */}
<div className="mx-2" title='Pending Transactions'>0</div> <div className="mx-2" title='Pending Transactions'>0</div>
<div className="verticalLine"></div> <div className="verticalLine_2A0YE0"></div>
<div className="pt-1 h-80 mx-3 align-items-center listenOnNetwork custom-control custom-checkbox"> <div className="pt-1 h-80 mx-3 align-items-center listenOnNetwork_2A0YE0 custom-control custom-checkbox">
<input <input
className="custom-control-input" className="custom-control-input"
id="listenNetworkCheck" id="listenNetworkCheck"
@ -194,13 +383,13 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
listen on network listen on network
</label> </label>
</div> </div>
<div className="search"> <div className="search_2A0YE0">
<i className="fas fa-search searchIcon bg-light" aria-hidden="true"></i> <i className="fas fa-search searchIcon_2A0YE0 bg-light" aria-hidden="true"></i>
{/* ${self._view.inputSearch} */} {/* ${self._view.inputSearch} */}
<input <input
// spellcheck = "false" // spellcheck = "false"
type="text" type="text"
className="border filter form-control" className="border filter_2A0YE0 form-control"
id="searchInput" id="searchInput"
// onkeydown=${filter} // onkeydown=${filter}
placeholder="Search with transaction hash or address" placeholder="Search with transaction hash or address"
@ -208,7 +397,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
</div> </div>
</div> </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} */} {/* onScroll=${throttle(reattach, 10)} onkeydown=${focusinput} */}
{/* {props.autoCompletePopup.render()} */} {/* {props.autoCompletePopup.render()} */}
{console.log({ props })} {console.log({ props })}
@ -219,8 +408,15 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
opacity: '0.1', opacity: '0.1',
zIndex: -1 zIndex: -1
}}></div> }}></div>
<div className="terminal"> <div className="terminal_2A0YE0">
<div id="journal" className="journal" data-id="terminalJournal"> <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"> <div className="anchor">
{/* ${background} */} {/* ${background} */}
<div className="overlay background"></div> <div className="overlay background"></div>
@ -228,9 +424,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
<div className="overlay text"></div> <div className="overlay text"></div>
</div> </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="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> </div>
</div> </div>

19
package-lock.json generated

@ -10656,6 +10656,15 @@
"tweetnacl": "^0.14.3" "tweetnacl": "^0.14.3"
} }
}, },
"beautiful-react-hooks": {
"version": "0.35.0",
"resolved": "https://registry.npmjs.org/beautiful-react-hooks/-/beautiful-react-hooks-0.35.0.tgz",
"integrity": "sha512-EDjpQWskuK7ob+rfH/Xt6UpFA+vC+ARM1TYAWRgYWcjcaDCGFuc3q5Ko4WJQUcFP2cq9ps3GihUSqN+KfUwQdg==",
"requires": {
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1"
}
},
"bech32": { "bech32": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
@ -24291,6 +24300,11 @@
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
}, },
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
"lodash.defaultsdeep": { "lodash.defaultsdeep": {
"version": "4.6.1", "version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz",
@ -24392,6 +24406,11 @@
"lodash._reinterpolate": "^3.0.0" "lodash._reinterpolate": "^3.0.0"
} }
}, },
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"lodash.toarray": { "lodash.toarray": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",

@ -89,7 +89,7 @@
"onchange": "onchange apps/remix-ide/build/app.js -- npm-run-all lint", "onchange": "onchange apps/remix-ide/build/app.js -- npm-run-all lint",
"remixd": "nx build remixd && nx serve remixd --folder=./apps/remix-ide/contracts --remixide=http://127.0.0.1:8080", "remixd": "nx build remixd && nx serve remixd --folder=./apps/remix-ide/contracts --remixide=http://127.0.0.1:8080",
"selenium": "selenium-standalone start", "selenium": "selenium-standalone start",
"selenium-install": "selenium-standalone install", "selenium-install": "selenium-standalone install",
"sourcemap": "exorcist --root ../ apps/remix-ide/build/app.js.map > apps/remix-ide/build/app.js", "sourcemap": "exorcist --root ../ apps/remix-ide/build/app.js.map > apps/remix-ide/build/app.js",
"test-browser": "npm-run-all -lpr selenium make-mock-compiler serve browsertest", "test-browser": "npm-run-all -lpr selenium make-mock-compiler serve browsertest",
"watch": "watchify apps/remix-ide/src/index.js -dv -p browserify-reload -o apps/remix-ide/build/app.js --exclude solc", "watch": "watchify apps/remix-ide/src/index.js -dv -p browserify-reload -o apps/remix-ide/build/app.js --exclude solc",
@ -146,6 +146,7 @@
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.2", "async": "^2.6.2",
"axios": ">=0.21.1", "axios": ">=0.21.1",
"beautiful-react-hooks": "^0.35.0",
"brace": "^0.8.0", "brace": "^0.8.0",
"change-case": "^4.1.1", "change-case": "^4.1.1",
"chokidar": "^2.1.8", "chokidar": "^2.1.8",

Loading…
Cancel
Save