@ -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 >
{ / * < d i v c l a s s N a m e = " l i s t H a n d l e r H i d e " >
< 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 >