pull/4566/head
filip mertens 9 months ago committed by bunsenstraat
parent 962e28a0c0
commit 34718f43b2
  1. 6
      apps/remix-ide/src/app/panels/terminal.tsx
  2. 3
      libs/remix-ui/terminal/src/index.ts
  3. 101
      libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-bar.tsx
  4. 25
      libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-buttons.tsx
  5. 30
      libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-toggle.tsx
  6. 76
      libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu.tsx
  7. 3
      libs/remix-ui/terminal/src/lib/context/context.ts
  8. 11
      libs/remix-ui/terminal/src/lib/context/index.ts
  9. 8
      libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts
  10. 25
      libs/remix-ui/terminal/src/lib/remix-ui-terminal-wrapper.tsx
  11. 2
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.css
  12. 40
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  13. 1
      libs/remix-ui/terminal/src/lib/types/terminalTypes.ts
  14. 5
      libs/remix-ui/xterm/src/index.ts
  15. 27
      libs/remix-ui/xterm/src/lib/actions/index.ts
  16. 55
      libs/remix-ui/xterm/src/lib/components/remix-ui-terminal-menu-xterm.tsx
  17. 154
      libs/remix-ui/xterm/src/lib/components/remix-ui-xterminals.tsx
  18. 69
      libs/remix-ui/xterm/src/lib/reducer/index.ts
  19. 36
      libs/remix-ui/xterm/src/lib/types/index.ts

@ -142,12 +142,12 @@ class Terminal extends Plugin {
} }
updateComponent(state) { updateComponent(state) {
return (Registry.getInstance().get('platform').api.isDesktop()) ? <RemixUiXterminals onReady={state.onReady} plugin={state.plugin}/> return(
: <RemixUITerminalWrapper <RemixUITerminalWrapper
plugin={state.plugin} plugin={state.plugin}
onReady={state.onReady} onReady={state.onReady}
visible={true} visible={true}
/> />)
} }
renderComponent() { renderComponent() {

@ -1,2 +1,3 @@
export * from './lib/remix-ui-terminal' export * from './lib/remix-ui-terminal'
export * from './lib/remix-ui-terminal-wrapper' export * from './lib/remix-ui-terminal-wrapper'
export * from './lib/context'

@ -1,100 +1,37 @@
import { appPlatformTypes, platformContext } from '@remix-ui/app'
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl'
import { listenOnNetworkAction } from '../actions/terminalAction' import { listenOnNetworkAction } from '../actions/terminalAction'
import { TerminalContext } from '../context/context' import { TerminalContext } from '../context'
import { RemixUiTerminalProps } from '../types/terminalTypes' import { RemixUiTerminalProps } from '../types/terminalTypes'
import { RemixUITerminalMenu } from './remix-ui-terminal-menu'
import { RemixUITerminalMenuToggle } from './remix-ui-terminal-menu-toggle'
import { RemixUIXtermMenu } from '../../../../xterm/src/lib/components/remix-ui-terminal-menu-xterm'
import { RemixUITerminalMenuButtons } from './remix-ui-terminal-menu-buttons'
export const RemixUITerminalBar = (props: RemixUiTerminalProps) => { export const RemixUITerminalBar = (props: RemixUiTerminalProps) => {
const { newstate: state, dispatch } = useContext(TerminalContext) const { terminalState, xtermState } = useContext(TerminalContext)
const platform = useContext(platformContext)
const intl = useIntl() const intl = useIntl()
const terminalMenu = useRef(null) const terminalMenu = useRef(null)
function handleToggleTerminal(event: any): void {
dispatch({ type: 'toggle' })
}
useEffect(() => {
props.plugin.call('layout', 'minimize', props.plugin.profile.name, !state.isOpen)
}, [state.isOpen])
useEffect(() => { useEffect(() => {
console.log('state change', state) props.plugin.call('layout', 'minimize', props.plugin.profile.name, !terminalState.isOpen)
}, [state]) }, [terminalState.isOpen])
function handleClearConsole(event: any): void {
dispatch({ type: 'clearconsole', payload: [] })
}
function listenOnNetwork(event: any): void {
const isListening = event.target.checked
listenOnNetworkAction(props.plugin, isListening)
}
function setSearchInput(arg0: string): void {
dispatch({ type: 'search', payload: arg0 })
}
return (<> return (<>
<div className="remix_ui_terminal_bar d-flex"> <div className="remix_ui_terminal_bar d-flex">
<div className="remix_ui_terminal_menu d-flex w-100 align-items-center position-relative border-top border-dark bg-light" ref={terminalMenu} data-id="terminalToggleMenu"> <div className="remix_ui_terminal_menu d-flex w-100 align-items-center position-relative border-top border-dark bg-light" ref={terminalMenu} data-id="terminalToggleMenu">
<CustomTooltip <RemixUITerminalMenuToggle {...props} />
placement="top" {platform === appPlatformTypes.desktop ?
tooltipId="terminalToggle" <>
tooltipClasses="text-nowrap" <RemixUITerminalMenuButtons {...props} />
tooltipText={state.isOpen ? <FormattedMessage id="terminal.hideTerminal" /> : <FormattedMessage id="terminal.showTerminal" />} {xtermState.showOutput? <RemixUITerminalMenu {...props} />: <RemixUIXtermMenu {...props} />}
> </> :
<i <RemixUITerminalMenu {...props} />
className={`mx-2 remix_ui_terminal_toggleTerminal fas ${state.isOpen ? 'fa-angle-double-down' : 'fa-angle-double-up'}`} }
data-id="terminalToggleIcon"
onClick={handleToggleTerminal}
></i>
</CustomTooltip>
<div className="mx-2 remix_ui_terminal_console" id="clearConsole" data-id="terminalClearConsole" onClick={handleClearConsole}>
<CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="terminal.clearConsole" />}>
<i className="fas fa-ban" aria-hidden="true"></i>
</CustomTooltip>
</div>
<CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="terminal.pendingTransactions" />}>
<div className="mx-2">0</div>
</CustomTooltip>
<CustomTooltip
placement="top"
tooltipId="terminalClear"
tooltipClasses="text-nowrap"
tooltipText={intl.formatMessage({ id: state.isVM ? 'terminal.listenVM' : 'terminal.listenTitle' })}
>
<div className="h-80 mx-3 align-items-center remix_ui_terminal_listenOnNetwork custom-control custom-checkbox">
<CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText={intl.formatMessage({ id: 'terminal.listenTitle' })}>
<input
className="custom-control-input"
id="listenNetworkCheck"
onChange={listenOnNetwork}
type="checkbox"
disabled={state.isVM}
/>
</CustomTooltip>
<label
className="form-check-label custom-control-label text-nowrap"
style={{ paddingTop: '0.125rem' }}
htmlFor="listenNetworkCheck"
data-id="listenNetworkCheckInput"
>
<FormattedMessage id="terminal.listen" />
</label>
</div>
</CustomTooltip>
<div className="remix_ui_terminal_search d-flex align-items-center h-100">
<i className="remix_ui_terminal_searchIcon d-flex align-items-center justify-content-center fas fa-search bg-light" aria-hidden="true"></i>
<input
onChange={(event) => setSearchInput(event.target.value.trim())}
type="text"
className="remix_ui_terminal_filter border form-control"
id="searchInput"
placeholder={intl.formatMessage({ id: 'terminal.search' })}
data-id="terminalInputSearch"
/>
</div>
</div> </div>
</div></>) </div></>)
} }

@ -0,0 +1,25 @@
import React, { useContext, useEffect } from 'react' // eslint-disable-line
import { TerminalContext } from '../context'
import { RemixUiTerminalProps, SET_OPEN } from '../types/terminalTypes'
export const RemixUITerminalMenuButtons = (props: RemixUiTerminalProps) => {
const { xtermState, dispatchXterm, terminalState, dispatch } = useContext(TerminalContext)
function selectOutput(event: any): void {
props.plugin.call('layout', 'minimize', props.plugin.profile.name, false)
dispatchXterm({ type: 'SHOW_OUTPUT', payload: true })
dispatch({ type: SET_OPEN, payload: true })
}
function showTerminal(event: any): void {
props.plugin.call('layout', 'minimize', props.plugin.profile.name, false)
dispatchXterm({ type: 'SHOW_OUTPUT', payload: false })
dispatch({ type: SET_OPEN, payload: true })
}
return (
<div>
<button className={`btn btn-sm btn-secondary mr-2 ${!xtermState.showOutput ? 'xterm-btn-none' : 'xterm-btn-active'}`} onClick={selectOutput}>output</button>
<button className={`btn btn-sm btn-secondary ${xtermState.terminalsEnabled ? '' : 'd-none'} ${xtermState.showOutput ? 'xterm-btn-none' : 'xterm-btn-active'}`} onClick={showTerminal}><span className="far fa-terminal border-0 ml-1"></span></button>
</div>
)
}

@ -0,0 +1,30 @@
import { CustomTooltip } from '@remix-ui/helper'
import React, { useContext, useEffect } from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl'
import { TerminalContext } from '../context'
import { RemixUiTerminalProps, TOGGLE } from '../types/terminalTypes'
export const RemixUITerminalMenuToggle = (props: RemixUiTerminalProps) => {
const { terminalState, dispatch } = useContext(TerminalContext)
function handleToggleTerminal(event: any): void {
dispatch({ type: TOGGLE })
}
return (
<>
<CustomTooltip
placement="top"
tooltipId="terminalToggle"
tooltipClasses="text-nowrap"
tooltipText={terminalState.isOpen ? <FormattedMessage id="terminal.hideTerminal" /> : <FormattedMessage id="terminal.showTerminal" />}
>
<i
className={`mx-2 remix_ui_terminal_toggleTerminal fas ${terminalState.isOpen ? 'fa-angle-double-down' : 'fa-angle-double-up'}`}
data-id="terminalToggleIcon"
onClick={handleToggleTerminal}
></i>
</CustomTooltip>
</>
)
}

@ -0,0 +1,76 @@
import { CustomTooltip } from '@remix-ui/helper'
import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl'
import { listenOnNetworkAction } from '../actions/terminalAction'
import { TerminalContext } from '../context'
import { RemixUiTerminalProps } from '../types/terminalTypes'
export const RemixUITerminalMenu = (props: RemixUiTerminalProps) => {
const { terminalState, dispatch } = useContext(TerminalContext)
const intl = useIntl()
useEffect(() => {
props.plugin.call('layout', 'minimize', props.plugin.profile.name, !terminalState.isOpen)
}, [terminalState.isOpen])
function handleClearConsole(event: any): void {
dispatch({ type: 'clearconsole', payload: [] })
}
function listenOnNetwork(event: any): void {
const isListening = event.target.checked
listenOnNetworkAction(props.plugin, isListening)
}
function setSearchInput(arg0: string): void {
dispatch({ type: 'search', payload: arg0 })
}
return (<>
<div className="mx-2 remix_ui_terminal_console" id="clearConsole" data-id="terminalClearConsole" onClick={handleClearConsole}>
<CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="terminal.clearConsole" />}>
<i className="fas fa-ban" aria-hidden="true"></i>
</CustomTooltip>
</div>
<CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText={<FormattedMessage id="terminal.pendingTransactions" />}>
<div className="mx-2">0</div>
</CustomTooltip>
<CustomTooltip
placement="top"
tooltipId="terminalClear"
tooltipClasses="text-nowrap"
tooltipText={intl.formatMessage({ id: terminalState.isVM ? 'terminal.listenVM' : 'terminal.listenTitle' })}
>
<div className="h-80 mx-3 align-items-center remix_ui_terminal_listenOnNetwork custom-control custom-checkbox">
<CustomTooltip placement="top" tooltipId="terminalClear" tooltipClasses="text-nowrap" tooltipText={intl.formatMessage({ id: 'terminal.listenTitle' })}>
<input
className="custom-control-input"
id="listenNetworkCheck"
onChange={listenOnNetwork}
type="checkbox"
disabled={terminalState.isVM}
/>
</CustomTooltip>
<label
className="form-check-label custom-control-label text-nowrap"
style={{ paddingTop: '0.125rem' }}
htmlFor="listenNetworkCheck"
data-id="listenNetworkCheckInput"
>
<FormattedMessage id="terminal.listen" />
</label>
</div>
</CustomTooltip>
<div className="remix_ui_terminal_search d-flex align-items-center h-100">
<i className="remix_ui_terminal_searchIcon d-flex align-items-center justify-content-center fas fa-search bg-light" aria-hidden="true"></i>
<input
onChange={(event) => setSearchInput(event.target.value.trim())}
type="text"
className="remix_ui_terminal_filter border form-control"
id="searchInput"
placeholder={intl.formatMessage({ id: 'terminal.search' })}
data-id="terminalInputSearch"
/>
</div>
</>)
}

@ -1,3 +0,0 @@
import React from 'react'
export const TerminalContext = React.createContext(null)

@ -0,0 +1,11 @@
import { Actions, xTerminalUiState } from '@remix-ui/xterm'
import React, { Dispatch } from 'react'
type terminalProviderContextType = {
terminalState: any,
dispatch: Dispatch<any>,
xtermState: xTerminalUiState,
dispatchXterm: Dispatch<Actions>
}
export const TerminalContext = React.createContext<terminalProviderContextType>(null)

@ -1,4 +1,4 @@
import {CLEAR_CONSOLE, CMD_HISTORY, EMPTY_BLOCK, ERROR, HTML, INFO, KNOWN_TRANSACTION, LISTEN_ON_NETWORK, LOG, TYPEWRITERLOG, TYPEWRITERWARNING, TYPEWRITERSUCCESS, NEW_TRANSACTION, SCRIPT, UNKNOWN_TRANSACTION, WARN, TOGGLE, SEARCH, SET_ISVM} from '../types/terminalTypes' import {CLEAR_CONSOLE, CMD_HISTORY, EMPTY_BLOCK, ERROR, HTML, INFO, KNOWN_TRANSACTION, LISTEN_ON_NETWORK, LOG, TYPEWRITERLOG, TYPEWRITERWARNING, TYPEWRITERSUCCESS, NEW_TRANSACTION, SCRIPT, UNKNOWN_TRANSACTION, WARN, TOGGLE, SEARCH, SET_ISVM, SET_OPEN} from '../types/terminalTypes'
export const initialState = { export const initialState = {
journalBlocks: [], journalBlocks: [],
@ -89,6 +89,12 @@ export const registerCommandReducer = (state, action) => {
isOpen: !state.isOpen, isOpen: !state.isOpen,
} }
case SET_OPEN:
return {
...state,
isOpen: action.payload,
}
case LISTEN_ON_NETWORK: case LISTEN_ON_NETWORK:
return { return {
...state, ...state,

@ -1,22 +1,33 @@
import React, { useReducer } from 'react' // eslint-disable-line import { appPlatformTypes, platformContext } from '@remix-ui/app'
import { RemixUiXterminals, xTerminInitialState, xtermReducer } from '@remix-ui/xterm'
import React, { useContext, useReducer } from 'react' // eslint-disable-line
import { RemixUITerminalBar } from './components/remix-ui-terminal-bar' import { RemixUITerminalBar } from './components/remix-ui-terminal-bar'
import { TerminalContext } from './context/context' import { TerminalContext } from './context'
import { initialState, registerCommandReducer } from './reducers/terminalReducer' import { initialState, registerCommandReducer } from './reducers/terminalReducer'
import RemixUiTerminal from './remix-ui-terminal' import RemixUiTerminal from './remix-ui-terminal'
import { RemixUiTerminalProps } from './types/terminalTypes' import { RemixUiTerminalProps } from './types/terminalTypes'
export const RemixUITerminalWrapper = (props: RemixUiTerminalProps) => { export const RemixUITerminalWrapper = (props: RemixUiTerminalProps) => {
const [newstate, dispatch] = useReducer(registerCommandReducer, initialState) const [terminalState, dispatch] = useReducer(registerCommandReducer, initialState)
const [xtermState, dispatchXterm] = useReducer(xtermReducer, xTerminInitialState)
const platform = useContext(platformContext)
const providerState = { const providerState = {
newstate, terminalState,
dispatch dispatch,
xtermState,
dispatchXterm
} }
return (<> return (<>
<TerminalContext.Provider value={providerState}> <TerminalContext.Provider value={providerState}>
<RemixUITerminalBar {...props} /> <RemixUITerminalBar {...props} />
<RemixUiTerminal {...props} /> {platform !== appPlatformTypes.desktop && <RemixUiTerminal {...props} />}
{platform === appPlatformTypes.desktop &&
<>
<RemixUiTerminal visible={xtermState.showOutput} plugin={props.plugin} onReady={props.onReady} />
<RemixUiXterminals {...props} />
</>
}
</TerminalContext.Provider> </TerminalContext.Provider>
</>) </>)
} }

@ -19,7 +19,7 @@ element.style {
min-height : 3em; min-height : 3em;
} }
.remix_ui_terminal_bar { .remix_ui_terminal_bar {
z-index : 2; z-index : 20;
} }
.remix_ui_terminal_menu { .remix_ui_terminal_menu {
max-height : 35px; max-height : 35px;

@ -28,7 +28,7 @@ import RenderKnownTransactions from './components/RenderKnownTransactions' // es
import parse from 'html-react-parser' import parse from 'html-react-parser'
import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, SET_ISVM, UNKNOWN_TRANSACTION } from './types/terminalTypes' import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, SET_ISVM, UNKNOWN_TRANSACTION } from './types/terminalTypes'
import { wrapScript } from './utils/wrapScript' import { wrapScript } from './utils/wrapScript'
import { TerminalContext } from './context/context' import { TerminalContext } from './context'
const _paq = (window._paq = window._paq || []) const _paq = (window._paq = window._paq || [])
/* eslint-disable-next-line */ /* eslint-disable-next-line */
@ -41,7 +41,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const [_cmdIndex, setCmdIndex] = useState(-1) const [_cmdIndex, setCmdIndex] = useState(-1)
const [_cmdTemp, setCmdTemp] = useState('') const [_cmdTemp, setCmdTemp] = useState('')
const [isOpen, setIsOpen] = useState<boolean>(true) const [isOpen, setIsOpen] = useState<boolean>(true)
const { newstate, dispatch } = useContext(TerminalContext) const { terminalState, dispatch } = useContext(TerminalContext)
const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState) const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState)
const [, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState) const [, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState)
const [toaster, setToaster] = useState(false) const [toaster, setToaster] = useState(false)
@ -130,10 +130,10 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
// events // events
useEffect(() => { useEffect(() => {
initListeningOnNetwork(props.plugin, scriptRunnerDispatch) initListeningOnNetwork(props.plugin, scriptRunnerDispatch)
registerLogScriptRunnerAction(on, 'log', newstate.commands, scriptRunnerDispatch) registerLogScriptRunnerAction(on, 'log', terminalState.commands, scriptRunnerDispatch)
registerInfoScriptRunnerAction(on, 'info', newstate.commands, scriptRunnerDispatch) registerInfoScriptRunnerAction(on, 'info', terminalState.commands, scriptRunnerDispatch)
registerWarnScriptRunnerAction(on, 'warn', newstate.commands, scriptRunnerDispatch) registerWarnScriptRunnerAction(on, 'warn', terminalState.commands, scriptRunnerDispatch)
registerErrorScriptRunnerAction(on, 'error', newstate.commands, scriptRunnerDispatch) registerErrorScriptRunnerAction(on, 'error', terminalState.commands, scriptRunnerDispatch)
registerCommandAction('html', _blocksRenderer('html'), { activate: true }, dispatch) registerCommandAction('html', _blocksRenderer('html'), { activate: true }, dispatch)
registerCommandAction('log', _blocksRenderer('log'), { activate: true }, dispatch) registerCommandAction('log', _blocksRenderer('log'), { activate: true }, dispatch)
registerCommandAction('info', _blocksRenderer('info'), { activate: true }, dispatch) registerCommandAction('info', _blocksRenderer('info'), { activate: true }, dispatch)
@ -154,14 +154,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
) )
}, [autoCompletState.text]) }, [autoCompletState.text])
useEffect(() => {
console.log('newstate.journalBlocks change', newstate.journalBlocks)
}, [newstate])
useEffect(() => { useEffect(() => {
scrollToBottom() scrollToBottom()
console.log('newstate.journalBlocks.length', newstate.journalBlocks) }, [terminalState.journalBlocks.length, toaster])
}, [newstate.journalBlocks.length, toaster])
function execute(file, cb) { function execute(file, cb) {
function _execute(content, cb) { function _execute(content, cb) {
@ -170,7 +165,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
if (cb) cb() if (cb) cb()
return return
} }
newstate.commands.script(content) terminalState.commands.script(content)
} }
if (typeof file === 'undefined') { if (typeof file === 'undefined') {
@ -306,7 +301,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const script = autoCompletState.userInput.trim() // inputEl.current.innerText.trim() const script = autoCompletState.userInput.trim() // inputEl.current.innerText.trim()
if (script.length) { if (script.length) {
cmdHistoryDispatch({ type: 'cmdHistory', payload: { script } }) cmdHistoryDispatch({ type: 'cmdHistory', payload: { script } })
newstate.commands.script(wrapScript(script)) terminalState.commands.script(wrapScript(script))
} }
setAutoCompleteState((prevState) => ({ ...prevState, userInput: '' })) setAutoCompleteState((prevState) => ({ ...prevState, userInput: '' }))
inputEl.current.innerText = '' inputEl.current.innerText = ''
@ -316,11 +311,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
showSuggestions: false, showSuggestions: false,
})) }))
} }
} else if (newstate._commandHistory.length && event.which === 38 && !autoCompletState.showSuggestions && autoCompletState.userInput === '') { } else if (terminalState._commandHistory.length && event.which === 38 && !autoCompletState.showSuggestions && autoCompletState.userInput === '') {
event.preventDefault() event.preventDefault()
setAutoCompleteState((prevState) => ({ setAutoCompleteState((prevState) => ({
...prevState, ...prevState,
userInput: newstate._commandHistory[0], userInput: terminalState._commandHistory[0],
})) }))
} else if (event.which === 38 && autoCompletState.showSuggestions) { } else if (event.which === 38 && autoCompletState.showSuggestions) {
event.preventDefault() event.preventDefault()
@ -401,12 +396,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
} }
useEffect(() => { useEffect(() => {
console.log('clearConsole change', newstate.clearConsole) if(terminalState.clearConsole){
if(newstate.clearConsole){
typeWriterIndexes.current = [] typeWriterIndexes.current = []
inputEl.current.focus() inputEl.current.focus()
} }
},[newstate.clearConsole]) },[terminalState.clearConsole])
/* end of block content that gets rendered from script Runner */ /* end of block content that gets rendered from script Runner */
@ -592,9 +586,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
{handleAutoComplete()} {handleAutoComplete()}
<div className="position-relative d-flex flex-column-reverse h-100"> <div className="position-relative d-flex flex-column-reverse h-100">
<div id="journal" className="remix_ui_terminal_journal d-flex flex-column pt-3 pb-4 px-2 mx-2 mr-0" data-id="terminalJournal"> <div id="journal" className="remix_ui_terminal_journal d-flex flex-column pt-3 pb-4 px-2 mx-2 mr-0" data-id="terminalJournal">
{!newstate.clearConsole && <TerminalWelcomeMessage storage={storage} packageJson={version} />} {!terminalState.clearConsole && <TerminalWelcomeMessage storage={storage} packageJson={version} />}
{newstate.journalBlocks && {terminalState.journalBlocks &&
newstate.journalBlocks.map((x, index) => { terminalState.journalBlocks.map((x, index) => {
if (x.name === EMPTY_BLOCK) { if (x.name === EMPTY_BLOCK) {
return ( return (
<div className={classNameBlock} data-id="block" key={index}> <div className={classNameBlock} data-id="block" key={index}>
@ -607,7 +601,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
) )
} else if (x.name === UNKNOWN_TRANSACTION) { } else if (x.name === UNKNOWN_TRANSACTION) {
return x.message return x.message
.filter((x) => x.tx.hash.includes(newstate.searchInput) || x.tx.from.includes(newstate.searchInput) || x.tx.to.includes(newstate.searchInput)) .filter((x) => x.tx.hash.includes(terminalState.searchInput) || x.tx.from.includes(terminalState.searchInput) || x.tx.to.includes(terminalState.searchInput))
.map((trans) => { .map((trans) => {
return ( return (
<div className={classNameBlock} data-id={`block_tx${trans.tx.hash}`} key={index}> <div className={classNameBlock} data-id={`block_tx${trans.tx.hash}`} key={index}>

@ -24,6 +24,7 @@ export const ERROR = 'error'
export const SCRIPT = 'script' export const SCRIPT = 'script'
export const CLEAR_CONSOLE = 'clearconsole' export const CLEAR_CONSOLE = 'clearconsole'
export const TOGGLE = 'toggle' export const TOGGLE = 'toggle'
export const SET_OPEN = 'setOpen'
export const LISTEN_ON_NETWORK = 'listenOnNetWork' export const LISTEN_ON_NETWORK = 'listenOnNetWork'
export const CMD_HISTORY = 'cmdHistory' export const CMD_HISTORY = 'cmdHistory'
export const SEARCH = 'search' export const SEARCH = 'search'

@ -1,2 +1,5 @@
export * from './lib/components/remix-ui-xterm' export * from './lib/components/remix-ui-xterm'
export * from './lib/components/remix-ui-xterminals' export * from './lib/components/remix-ui-xterminals'
export * from './lib/reducer'
export * from './lib/types'
export * from './lib/actions'

@ -0,0 +1,27 @@
import { Actions } from "@remix-ui/xterm"
import { Plugin } from "@remixproject/engine"
export const createTerminal = async (shell: string = '', plugin: Plugin, workingDir: string, dispatch: React.Dispatch<Actions>) => {
const shells: string[] = await plugin.call('xterm', 'getShells')
dispatch({ type: 'ADD_SHELLS', payload: shells })
const pid = await plugin.call('xterm', 'createTerminal', workingDir, shell)
dispatch({ type: 'SHOW_OUTPUT', payload: false })
dispatch({ type: 'HIDE_ALL_TERMINALS', payload: null })
dispatch({ type: 'ADD_TERMINAL', payload: { pid, queue: '', timeStamp: Date.now(), ref: null, hidden: false } })
/*
setTerminals(prevState => {
// set all to hidden
prevState.forEach(xtermState => {
xtermState.hidden = true
})
return [...prevState, {
pid: pid,
queue: '',
timeStamp: Date.now(),
ref: null,
hidden: false
}]
})
*/
}

@ -0,0 +1,55 @@
import { CustomTooltip } from '@remix-ui/helper';
import { TerminalContext } from '@remix-ui/terminal';
import { createTerminal } from '@remix-ui/xterm';
import React, { useState, useEffect, useRef, useContext } from 'react' // eslint-disable-line
import { Dropdown, ButtonGroup } from 'react-bootstrap';
import { FormattedMessage } from 'react-intl';
import { RemixUiTerminalProps } from "../../../../terminal/src/lib/types/terminalTypes";
export const RemixUIXtermMenu = (props: RemixUiTerminalProps) => {
const { xtermState, dispatchXterm } = useContext(TerminalContext)
function onClearTerminal(): void | PromiseLike<void> {
const terminal = xtermState.terminals.find(xtermState => xtermState.hidden === false)
if (terminal && terminal.ref && terminal.ref.terminal)
terminal.ref.terminal.clear()
}
function onCreateTerminal(shell?: string): void | PromiseLike<void> {
createTerminal(shell, props.plugin, xtermState.workingDir, dispatchXterm)
}
function onCloseTerminal(): void | PromiseLike<void> {
const pid = xtermState.terminals.find(xtermState => xtermState.hidden === false).pid
if (pid)
props.plugin.call('xterm', 'closeTerminal', pid)
}
return (<>
<div className={`${xtermState.showOutput ? 'd-none' : ''}`}>
<button className="btn btn-sm btn-secondary mr-2" onClick={async () => onClearTerminal()}>
<CustomTooltip tooltipText={<FormattedMessage id='xterm.clear' defaultMessage='Clear terminal' />}>
<span className="far fa-ban border-0 p-0 m-0"></span>
</CustomTooltip>
</button>
<button className="btn btn-sm btn-secondary" onClick={async () => onCreateTerminal()}>
<CustomTooltip tooltipText={<FormattedMessage id='xterm.new' defaultMessage='New terminal' />}>
<span className="far fa-plus border-0 p-0 m-0"></span>
</CustomTooltip>
</button>
<Dropdown as={ButtonGroup}>
<Dropdown.Toggle split variant="secondary" id="dropdown-split-basic" />
<Dropdown.Menu className='custom-dropdown-items remixui_menuwidth'>
{xtermState.shells.map((shell, index) => {
return (<Dropdown.Item key={index} onClick={async () => await onCreateTerminal(shell)}>{shell}</Dropdown.Item>)
})}
</Dropdown.Menu>
</Dropdown>
<button className="btn ml-2 btn-sm btn-secondary" onClick={onCloseTerminal}>
<CustomTooltip tooltipText={<FormattedMessage id='xterm.close' defaultMessage='Close terminal' />}>
<span className="far fa-trash border-0 ml-1"></span>
</CustomTooltip>
</button>
</div>
</>)
}

@ -1,31 +1,25 @@
import React, { useState, useEffect } from 'react' // eslint-disable-line import React, { useState, useEffect, useContext } from 'react' // eslint-disable-line
import { ElectronPlugin } from '@remixproject/engine-electron' import { ElectronPlugin } from '@remixproject/engine-electron'
import RemixUiXterm from './remix-ui-xterm' import RemixUiXterm from './remix-ui-xterm'
import '../css/index.css' import '../css/index.css'
import { Button, ButtonGroup, Dropdown, Tab, Tabs } from 'react-bootstrap' import { Button, ButtonGroup, Dropdown, Tab, Tabs } from 'react-bootstrap'
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
import { RemixUiTerminal } from '@remix-ui/terminal' import { RemixUiTerminal, TerminalContext } from '@remix-ui/terminal'
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl'
import { xtermState } from '../types'
import { createTerminal } from '@remix-ui/xterm'
export interface RemixUiXterminalsProps { export interface RemixUiXterminalsProps {
plugin: ElectronPlugin plugin: ElectronPlugin
onReady: (api: any) => void onReady: (api: any) => void
} }
export interface xtermState {
pid: number
queue: string
timeStamp: number
ref: any
hidden: boolean
}
export const RemixUiXterminals = (props: RemixUiXterminalsProps) => { export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
const { xtermState, dispatchXterm } = useContext(TerminalContext)
const [terminals, setTerminals] = useState<xtermState[]>([]) const [terminals, setTerminals] = useState<xtermState[]>([])
const [workingDir, setWorkingDir] = useState<string>('') //const [workingDir, setWorkingDir] = useState<string>('')
const [showOutput, setShowOutput] = useState<boolean>(true)
const [theme, setTheme] = useState<any>(themeCollection[0]) const [theme, setTheme] = useState<any>(themeCollection[0])
const [terminalsEnabled, setTerminalsEnabled] = useState<boolean>(false)
const [shells, setShells] = useState<string[]>([])
const { plugin } = props const { plugin } = props
useEffect(() => { useEffect(() => {
@ -36,43 +30,25 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
}) })
plugin.on('xterm', 'close', async (pid: number) => { plugin.on('xterm', 'close', async (pid: number) => {
setTerminals(prevState => { dispatchXterm({ type: 'REMOVE_TERMINAL', payload: pid })
const removed = prevState.filter(xtermState => xtermState.pid !== pid)
if (removed.length > 0)
removed[removed.length - 1].hidden = false
if (removed.length === 0)
setShowOutput(true)
return [...removed]
})
}) })
plugin.on('xterm', 'new', async (pid: number) => { plugin.on('xterm', 'new', async (pid: number) => {
setShowOutput(false) console.log('new terminal')
setTerminals(prevState => { dispatchXterm({ type: 'SHOW_OUTPUT', payload: false })
// set all to hidden dispatchXterm({ type: 'ADD_TERMINAL', payload: { pid, queue: '', timeStamp: Date.now(), ref: null, hidden: false } })
prevState.forEach(xtermState => {
xtermState.hidden = true
})
return [...prevState, {
pid: pid,
queue: '',
timeStamp: Date.now(),
ref: null,
hidden: false
}]
})
}) })
plugin.on('fs', 'workingDirChanged', (path: string) => { plugin.on('fs', 'workingDirChanged', (path: string) => {
setWorkingDir(path) dispatchXterm({ type: 'SET_WORKING_DIR', payload: path })
setTerminalsEnabled(true) dispatchXterm({ type: 'ENABLE_TERMINALS', payload: null })
}) })
const workingDir = await plugin.call('fs', 'getWorkingDir') const workingDir = await plugin.call('fs', 'getWorkingDir')
if(workingDir && workingDir !== '') { if(workingDir && workingDir !== '') {
setTerminalsEnabled(true) dispatchXterm({ type: 'ENABLE_TERMINALS', payload: null })
setWorkingDir(workingDir) dispatchXterm({ type: 'SET_WORKING_DIR', payload: workingDir })
} }
plugin.on('theme', 'themeChanged', async (theme) => { plugin.on('theme', 'themeChanged', async (theme) => {
@ -97,6 +73,12 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
}, 2000) }, 2000)
}, []) }, [])
useEffect(() => {
setTerminals(xtermState.terminals)
if(xtermState.terminals.length === 0) {
dispatchXterm({ type: 'SHOW_OUTPUT', payload: true })
}
}, [xtermState.terminals])
const handleThemeChange = (theme: any) => { const handleThemeChange = (theme: any) => {
themeCollection.forEach((themeItem) => { themeCollection.forEach((themeItem) => {
@ -129,27 +111,6 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
plugin.call('xterm', 'resize', event, pid) plugin.call('xterm', 'resize', event, pid)
} }
const createTerminal = async (shell?: string) => {
const shells = await plugin.call('xterm', 'getShells')
setShells(shells)
const pid = await plugin.call('xterm', 'createTerminal', workingDir, shell)
setShowOutput(false)
setTerminals(prevState => {
// set all to hidden
prevState.forEach(xtermState => {
xtermState.hidden = true
})
return [...prevState, {
pid: pid,
queue: '',
timeStamp: Date.now(),
ref: null,
hidden: false
}]
})
}
const setTerminalRef = (pid: number, ref: any) => { const setTerminalRef = (pid: number, ref: any) => {
setTerminals(prevState => { setTerminals(prevState => {
const terminal = prevState.find(xtermState => xtermState.pid === pid) const terminal = prevState.find(xtermState => xtermState.pid === pid)
@ -174,76 +135,17 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
}) })
} }
const closeTerminal = () => { useEffect(() => {
const pid = terminals.find(xtermState => xtermState.hidden === false).pid if (!xtermState.showOutput) {
if (pid) if (terminals.length === 0) createTerminal('', plugin, xtermState.workingDir, dispatchXterm)
plugin.call('xterm', 'closeTerminal', pid) }
} }, [xtermState.showOutput])
const selectOutput = () => {
props.plugin.call('layout', 'minimize', props.plugin.profile.name, false)
setShowOutput(true)
}
const showTerminal = () => {
setShowOutput(false)
props.plugin.call('layout', 'minimize', props.plugin.profile.name, false)
if (terminals.length === 0) createTerminal()
}
const clearTerminal = () => {
const terminal = terminals.find(xtermState => xtermState.hidden === false)
if (terminal && terminal.ref && terminal.ref.terminal)
terminal.ref.terminal.clear()
}
return (<> return (<>
<div className='xterm-panel-header bg-light'>
<div className='xterm-panel-header-left p-1'>
<button className={`btn btn-sm btn-secondary mr-2 ${!showOutput ? 'xterm-btn-none' : 'xterm-btn-active'}`} onClick={selectOutput}>output</button>
<button className={`btn btn-sm btn-secondary ${terminalsEnabled ? '' : 'd-none'} ${showOutput ? 'xterm-btn-none' : 'xterm-btn-active'}`} onClick={showTerminal}><span className="far fa-terminal border-0 ml-1"></span></button>
</div>
<div className={`xterm-panel-header-right ${showOutput ? 'd-none' : ''}`}>
<Dropdown as={ButtonGroup}>
<button className="btn btn-sm btn-secondary mr-2" onClick={async () => clearTerminal()}>
<CustomTooltip tooltipText={<FormattedMessage id='xterm.clear' defaultMessage='Clear terminal' />}>
<span className="far fa-ban border-0 p-0 m-0"></span>
</CustomTooltip>
</button>
<button className="btn btn-sm btn-secondary" onClick={async () => createTerminal()}>
<CustomTooltip tooltipText={<FormattedMessage id='xterm.new' defaultMessage='New terminal' />}>
<span className="far fa-plus border-0 p-0 m-0"></span>
</CustomTooltip>
</button>
<Dropdown.Toggle split variant="secondary" id="dropdown-split-basic" />
<Dropdown.Menu className='custom-dropdown-items remixui_menuwidth'>
{shells.map((shell, index) => {
return (<Dropdown.Item key={index} onClick={async () => await createTerminal(shell)}>{shell}</Dropdown.Item>)
})}
</Dropdown.Menu>
</Dropdown>
<button className="btn ml-2 btn-sm btn-secondary" onClick={closeTerminal}>
<CustomTooltip tooltipText={<FormattedMessage id='xterm.close' defaultMessage='Close terminal' />}>
<span className="far fa-trash border-0 ml-1"></span>
</CustomTooltip>
</button>
</div>
</div>
<RemixUiTerminal
plugin={props.plugin}
onReady={props.onReady}
visible={showOutput}
/>
<div className='remix-ui-xterminals-container'> <div className='remix-ui-xterminals-container'>
<> <>
<div className={`remix-ui-xterminals-section ${showOutput ? 'd-none' : 'd-flex'} `}> <div className={`remix-ui-xterminals-section ${xtermState.showOutput ? 'd-none' : 'd-flex'} `}>
{terminals.map((xtermState) => { {terminals.map((xtermState) => {
return ( return (
<div className={`h-100 xterm-terminal ${xtermState.hidden ? 'hide-xterm' : 'show-xterm'}`} key={xtermState.pid} data-id={`remixUIXT${xtermState.pid}`}> <div className={`h-100 xterm-terminal ${xtermState.hidden ? 'hide-xterm' : 'show-xterm'}`} key={xtermState.pid} data-id={`remixUIXT${xtermState.pid}`}>

@ -0,0 +1,69 @@
import { Actions, xTerminalUiState } from "@remix-ui/xterm"
export const xTerminInitialState: xTerminalUiState = {
terminalsEnabled: false,
terminals: [],
shells: [],
showOutput: true,
workingDir: ''
}
export const xtermReducer = (state = xTerminInitialState, action: Actions) => {
switch (action.type) {
case 'ENABLE_TERMINALS':
return {
...state,
terminalsEnabled: true
}
case 'DISABLE_TERMINALS':
return {
...state,
terminalsEnabled: false
}
case 'ADD_TERMINAL':
return {
...state,
terminals: [...state.terminals, action.payload]
}
case 'HIDE_TERMINAL':
return {
...state,
terminals: state.terminals.map(terminal => terminal.pid === action.payload ? { ...terminal, hidden: true } : terminal)
}
case 'SHOW_TERMINAL':
return {
...state,
terminals: state.terminals.map(terminal => terminal.pid === action.payload ? { ...terminal, hidden: false } : terminal)
}
case 'HIDE_ALL_TERMINALS':
return {
...state,
terminals: state.terminals.map(terminal => ({ ...terminal, hidden: true }))
}
case 'REMOVE_TERMINAL':
const removed = state.terminals.filter(xtermState => xtermState.pid !== action.payload)
if (removed.length > 0)
removed[removed.length - 1].hidden = false
return {
...state,
terminals: removed
}
case 'ADD_SHELLS':
return {
...state,
shells: action.payload
}
case 'SHOW_OUTPUT':
return {
...state,
showOutput: action.payload
}
case 'SET_WORKING_DIR':
return {
...state,
workingDir: action.payload
}
default:
return state
}
}

@ -0,0 +1,36 @@
export interface xtermState {
pid: number
queue: string
timeStamp: number
ref: any
hidden: boolean
}
export interface xTerminalUiState {
terminalsEnabled: boolean
terminals: xtermState[]
shells: string[]
showOutput: boolean
workingDir: string
}
export interface ActionPayloadTypes {
ENABLE_TERMINALS: undefined,
DISABLE_TERMINALS: undefined,
ADD_TERMINAL: xtermState,
HIDE_TERMINAL: number,
SHOW_TERMINAL: number,
HIDE_ALL_TERMINALS: undefined,
REMOVE_TERMINAL: number,
ADD_SHELLS: string[],
SHOW_OUTPUT: boolean
SET_WORKING_DIR: string
}
export interface Action<T extends keyof ActionPayloadTypes> {
type: T,
payload: ActionPayloadTypes[T]
}
export type Actions = {[A in keyof ActionPayloadTypes]: Action<A>}[keyof ActionPayloadTypes]
Loading…
Cancel
Save