From c49d9a351fd9946ec4904fee4a5ec46668a482e9 Mon Sep 17 00:00:00 2001 From: tizah Date: Thu, 15 Jul 2021 08:17:51 +0100 Subject: [PATCH] implemented minimize terminal window and added custom hook --- apps/remix-ide/src/app/panels/terminal.js | 6 +- .../src/lib/custom-hooks/useKeyPress.tsx | 29 +++ .../terminal/src/lib/remix-ui-terminal.css | 4 + .../terminal/src/lib/remix-ui-terminal.tsx | 242 ++++++++++++++++-- package-lock.json | 19 ++ package.json | 5 +- 6 files changed, 279 insertions(+), 26 deletions(-) create mode 100644 libs/remix-ui/terminal/src/lib/custom-hooks/useKeyPress.tsx diff --git a/apps/remix-ide/src/app/panels/terminal.js b/apps/remix-ide/src/app/panels/terminal.js index 0029802c1e..f7e1e9d137 100644 --- a/apps/remix-ide/src/app/panels/terminal.js +++ b/apps/remix-ide/src/app/panels/terminal.js @@ -34,11 +34,13 @@ class Terminal extends Plugin { constructor (opts, api) { super(profile) 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.blockchain = opts.blockchain this._api = api this._opts = opts + this.version = packageJson.version this.data = { lineLength: opts.lineLength || 80, // ???? session: [], @@ -122,6 +124,8 @@ class Terminal extends Plugin { cmdInterpreter = {this._components.cmdInterpreter} autoCompletePopup = {this._components.autoCompletePopup} registerCommand = {this.registerCommand} + command = {this.commands} + version = {this.version} />, this.element ) diff --git a/libs/remix-ui/terminal/src/lib/custom-hooks/useKeyPress.tsx b/libs/remix-ui/terminal/src/lib/custom-hooks/useKeyPress.tsx new file mode 100644 index 0000000000..12bbc1cefc --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/custom-hooks/useKeyPress.tsx @@ -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 +} 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 a8870af675..818fd1bb4a 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css @@ -1,3 +1,7 @@ + +element.style { + height: 323px !important; +} .panel { position : relative; display : flex; 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 282bc073b4..e4acaa6caf 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -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 extends SyntheticEvent { @@ -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: ( +
+
- Welcome to Remix {props.version} -
+
+
You can use this terminal to:
+
    +
  • Check transactions details and start debugging.
  • +
  • Execute JavaScript scripts: +
    + - Input a script directly in the command line interface +
    + - Select a Javascript file in the file explorer and then run \`remix.execute()\` or \`remix.exeCurrent()\` in the command line interface +
    + - Right click on a JavaScript file in the file explorer and then click \`Run\` +
  • +
+
The following libraries are accessible:
+ +
+ ), + text: (
David
) + }, 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) => { // 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) { // + // 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) { // + 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 ( -
-
+
+
{/* ${self._view.dragbar} */} -
-
+
+
{/* ${self._view.icon} */} - +
{/* ${self._view.pendingTxCount} */}
0
-
-
+
+
{ listen on network
-
- +
+ {/* ${self._view.inputSearch} */} {
-
+
{/* 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 }}>
-
-
+
+
+
+ {Object.entries(state.journalBlocks).map((x, index) => ( +
+ {x} +
+ ))} +
{/* ${background} */}
@@ -228,9 +424,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
-
+
{'>'} - +
diff --git a/package-lock.json b/package-lock.json index 9e028132b2..c942a158c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10514,6 +10514,15 @@ "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": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", @@ -24137,6 +24146,11 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "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": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", @@ -24238,6 +24252,11 @@ "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": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", diff --git a/package.json b/package.json index a66874796c..a01e2a6346 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "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", "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", "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", @@ -144,6 +144,7 @@ "ansi-gray": "^0.1.1", "async": "^2.6.2", "axios": ">=0.21.1", + "beautiful-react-hooks": "^0.35.0", "brace": "^0.8.0", "change-case": "^4.1.1", "chokidar": "^2.1.8", @@ -177,7 +178,7 @@ "web3": "1.2.4", "winston": "^3.3.3", "ws": "^7.3.0" - }, + }, "devDependencies": { "@babel/core": "^7.4.5", "@babel/plugin-transform-modules-amd": "^7.10.4",