Merge pull request #4551 from ethereum/terminalwrapper

Terminalwrapper
pull/4621/head
bunsenstraat 8 months ago committed by GitHub
commit 0d49c573db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      apps/remix-ide-e2e/src/commands/openFile.ts
  2. 41
      apps/remix-ide/src/app/panels/terminal.tsx
  3. 2
      libs/remix-ui/panel/src/lib/plugins/panel.css
  4. 2
      libs/remix-ui/terminal/src/index.ts
  5. 41
      libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-bar.tsx
  6. 3
      libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-buttons.css
  7. 31
      libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-buttons.tsx
  8. 30
      libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-toggle.tsx
  9. 76
      libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu.tsx
  10. 11
      libs/remix-ui/terminal/src/lib/context/index.ts
  11. 119
      libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts
  12. 33
      libs/remix-ui/terminal/src/lib/remix-ui-terminal-wrapper.tsx
  13. 2
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.css
  14. 129
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  15. 10
      libs/remix-ui/terminal/src/lib/types/terminalTypes.ts
  16. 5
      libs/remix-ui/xterm/src/index.ts
  17. 12
      libs/remix-ui/xterm/src/lib/actions/index.ts
  18. 59
      libs/remix-ui/xterm/src/lib/components/remix-ui-terminal-menu-xterm.tsx
  19. 2
      libs/remix-ui/xterm/src/lib/components/remix-ui-xterm.tsx
  20. 185
      libs/remix-ui/xterm/src/lib/components/remix-ui-xterminals.tsx
  21. 2
      libs/remix-ui/xterm/src/lib/components/xterm-wrap.tsx
  22. 72
      libs/remix-ui/xterm/src/lib/css/index.css
  23. 70
      libs/remix-ui/xterm/src/lib/reducer/index.ts
  24. 36
      libs/remix-ui/xterm/src/lib/types/index.ts

@ -21,7 +21,7 @@ function openFile (browser: NightwatchBrowser, name: string, done: VoidFunction)
// if side panel is shown, check this is the file panel
browser.element('css selector', '[data-id="verticalIconsKindfilePanel"] img[data-id="selected"]', (result) => {
if (result.status === 0) {
done()
done()
} else browser.clickLaunchIcon('filePanel').perform(() => {
done()
})

@ -1,12 +1,12 @@
/* global Node, requestAnimationFrame */ // eslint-disable-line
import React from 'react' // eslint-disable-line
import { RemixUiTerminal } from '@remix-ui/terminal' // eslint-disable-line
import { RemixUiTerminal, RemixUITerminalWrapper } from '@remix-ui/terminal' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import {Registry} from '@remix-project/remix-lib'
import { PluginViewWrapper } from '@remix-ui/helper'
import vm from 'vm'
const EventManager = require('../../lib/events')
import EventManager from '../../lib/events'
import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line
import { RemixUiXterminals } from '@remix-ui/xterm'
@ -26,6 +26,34 @@ const profile = {
}
class Terminal extends Plugin {
fileImport: CompilerImports
event: any
globalRegistry: Registry
element: HTMLDivElement
eventsDecoder: any
txListener: any
_deps: { fileManager: any; editor: any; compilersArtefacts: any; offsetToLineColumnConverter: any }
commandHelp: { 'remix.loadgist(id)': string; 'remix.loadurl(url)': string; 'remix.execute(filepath)': string; 'remix.exeCurrent()': string; 'remix.help()': string }
blockchain: any
vm: typeof vm
_api: any
_opts: any
config: any
version: string
data: {
lineLength: any // ????
session: any[]; activeFilters: { commands: any; input: string }; filterFns: any
}
_view: { el: any; bar: any; input: any; term: any; journal: any; cli: any }
_components: any
_commands: any
commands: any
_JOURNAL: any[]
_jobs: any[]
_INDEX: any
_shell: any
dispatch: any
terminalApi: any
constructor(opts, api) {
super(profile)
this.fileImport = new CompilerImports()
@ -75,7 +103,7 @@ class Terminal extends Plugin {
this._INDEX.commandsMain = {}
if (opts.shell) this._shell = opts.shell // ???
register(this)
this.event.register('debuggingRequested', async (hash) => {
this.event.register('debuggingRequested', async (hash: any) => {
// TODO should probably be in the run module
if (!await this._opts.appManager.isActive('debugger')) await this._opts.appManager.activatePlugin('debugger')
this.call('menuicons', 'select', 'debugger')
@ -114,13 +142,12 @@ class Terminal extends Plugin {
}
updateComponent(state) {
return (Registry.getInstance().get('platform').api.isDesktop()) ? <RemixUiXterminals onReady={state.onReady} plugin={state.plugin}>
</RemixUiXterminals>
: <RemixUiTerminal
return(
<RemixUITerminalWrapper
plugin={state.plugin}
onReady={state.onReady}
visible={true}
/>
/>)
}
renderComponent() {

@ -104,7 +104,5 @@ iframe {
height: 2rem !important;
}
.terminal-wrap.minimized.desktop {
height: 4.5rem !important;
}

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

@ -0,0 +1,41 @@
import { appPlatformTypes, platformContext } from '@remix-ui/app'
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'
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) => {
const { terminalState, xtermState } = useContext(TerminalContext)
const platform = useContext(platformContext)
const intl = useIntl()
const terminalMenu = useRef(null)
useEffect(() => {
props.plugin.call('layout', 'minimize', props.plugin.profile.name, !terminalState.isOpen)
}, [terminalState.isOpen])
return (<>
<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"
>
<RemixUITerminalMenuToggle {...props} />
{platform === appPlatformTypes.desktop ?
<div className='d-flex flex-row w-100 justify-content-between '>
<RemixUITerminalMenuButtons {...props} />
{xtermState.showOutput? <RemixUITerminalMenu {...props} />: <RemixUIXtermMenu {...props} />}
</div> :
<RemixUITerminalMenu {...props} />
}
</div>
</div></>
)
}

@ -0,0 +1,31 @@
import React, { useContext, useEffect } from 'react' // eslint-disable-line
import { TerminalContext } from '../context'
import { RemixUiTerminalProps, SET_OPEN } from '../types/terminalTypes'
import './remix-ui-terminal-menu-buttons.css'
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 className='d-flex flex-row align-items-center'>
<button id="tabOutput" className={`xtermButton btn btn-sm border-secondary mr-2 border ${!xtermState.showOutput ? '' : 'd-flex btn-secondary'}`} onClick={selectOutput}>
Output
</button>
<button id="tabXTerm" className={`xtermButton btn btn-sm border-secondary ${xtermState.terminalsEnabled ? 'd-block' : 'd-none'} ${xtermState.showOutput ? 'd-none' : 'btn-secondary'}`} 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='d-flex flex-row align-items-center'>
<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="terminalInputSearchTerminal"
/>
</div>
<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>
</div>)
}

@ -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,85 +1,112 @@
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 } 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 = {
journalBlocks: [
],
journalBlocks: [],
data: {
// lineLength: props.options.lineLength || 80,
session: [],
activeFilters: { commands: {}, input: '' },
filterFns: {}
activeFilters: {commands: {}, input: ''},
filterFns: {},
},
_commandHistory: [],
_commands: {},
commands: {},
_JOURNAL: [],
_jobs: [],
_INDEX: {
},
_INDEX: {},
_INDEXall: [],
_INDEXallMain: [],
_INDEXcommands: {},
_INDEXcommandsMain: {},
message: []
message: [],
isOpen: true,
searchInput: '',
clearConsole: false,
isVM: true
}
export const registerCommandReducer = (state, action) => {
switch (action.type) {
case HTML :
case HTML:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, { ...action.payload.data })
data: Object.assign(initialState.data, {...action.payload.data}),
}
case LOG:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, { ...action.payload.data })
data: Object.assign(initialState.data, {...action.payload.data}),
}
case INFO:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, action.payload.data)
data: Object.assign(initialState.data, action.payload.data),
}
case WARN:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, action.payload.data)
data: Object.assign(initialState.data, action.payload.data),
}
case ERROR:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, action.payload.data)
data: Object.assign(initialState.data, action.payload.data),
}
case SCRIPT:
return {
...state,
_commands: Object.assign(initialState._commands, action.payload._commands),
commands: Object.assign(initialState.commands, action.payload.commands),
data: Object.assign(initialState.data, action.payload.data)
data: Object.assign(initialState.data, action.payload.data),
}
case CLEAR_CONSOLE:
return {
...state,
...state.journalBlocks.splice(0)
...state.journalBlocks.splice(0),
clearConsole: true,
}
case SEARCH:
return {
...state,
searchInput: action.payload,
}
case TOGGLE:
return {
...state,
isOpen: !state.isOpen,
}
case SET_OPEN:
return {
...state,
isOpen: action.payload,
}
case LISTEN_ON_NETWORK:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log' })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: 'text-log'}),
}
default :
return { state }
case SET_ISVM:
return {
...state,
isVM: action.payload,
}
default:
return {state}
}
}
@ -88,31 +115,30 @@ export const registerFilterReducer = (state, action) => {
case LOG:
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns),
}
case INFO:
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns),
}
case WARN:
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns),
}
case ERROR:
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns),
}
case SCRIPT:
return {
...state,
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns)
data: Object.assign(initialState.data.filterFns, action.payload.data.filterFns),
}
default :
return { state }
default:
return {state}
}
}
@ -121,20 +147,19 @@ export const addCommandHistoryReducer = (state, action) => {
case CMD_HISTORY:
return {
...state,
_commandHistory: initialState._commandHistory.unshift(action.payload.script)
_commandHistory: initialState._commandHistory.unshift(action.payload.script),
}
default :
return { state }
default:
return {state}
}
}
export const remixWelcomeTextReducer = (state, action) => {
switch (action.type) {
case 'welcomeText' :
case 'welcomeText':
return {
...state,
journalBlocks: initialState.journalBlocks.push(action.payload.welcomeText)
journalBlocks: initialState.journalBlocks.push(action.payload.welcomeText),
}
}
}
@ -144,67 +169,67 @@ export const registerScriptRunnerReducer = (state, action) => {
case HTML:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: 'text-log', provider: action.payload.provider}),
}
case LOG:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: 'text-log', provider: action.payload.provider}),
}
case TYPEWRITERLOG:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, typewriter: true, style: 'text-log', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, typewriter: true, style: 'text-log', provider: action.payload.provider}),
}
case TYPEWRITERWARNING:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, typewriter: true, style: 'text-warning', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, typewriter: true, style: 'text-warning', provider: action.payload.provider}),
}
case TYPEWRITERSUCCESS:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, typewriter: true, style: 'text-success', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, typewriter: true, style: 'text-success', provider: action.payload.provider}),
}
case INFO:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-success', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: 'text-success', provider: action.payload.provider}),
}
case WARN:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-warning', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: 'text-warning', provider: action.payload.provider}),
}
case ERROR:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-danger', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: 'text-danger', provider: action.payload.provider}),
}
case SCRIPT:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: 'text-log', provider: action.payload.provider}),
}
case KNOWN_TRANSACTION:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'knownTransaction', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: '', name: 'knownTransaction', provider: action.payload.provider}),
}
case UNKNOWN_TRANSACTION:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'unknownTransaction', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: '', name: 'unknownTransaction', provider: action.payload.provider}),
}
case EMPTY_BLOCK:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', name: 'emptyBlock', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: '', name: 'emptyBlock', provider: action.payload.provider}),
}
case NEW_TRANSACTION:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: '', provider: action.payload.provider })
journalBlocks: initialState.journalBlocks.push({message: action.payload.message, style: '', provider: action.payload.provider}),
}
}
}

@ -0,0 +1,33 @@
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 { TerminalContext } from './context'
import { initialState, registerCommandReducer } from './reducers/terminalReducer'
import RemixUiTerminal from './remix-ui-terminal'
import { RemixUiTerminalProps } from './types/terminalTypes'
export const RemixUITerminalWrapper = (props: RemixUiTerminalProps) => {
const [terminalState, dispatch] = useReducer(registerCommandReducer, initialState)
const [xtermState, dispatchXterm] = useReducer(xtermReducer, xTerminInitialState)
const platform = useContext(platformContext)
const providerState = {
terminalState,
dispatch,
xtermState,
dispatchXterm
}
return (<>
<TerminalContext.Provider value={providerState}>
<RemixUITerminalBar {...props} />
{platform !== appPlatformTypes.desktop && <RemixUiTerminal {...props} />}
{platform === appPlatformTypes.desktop &&
<>
<RemixUiTerminal visible={xtermState.showOutput} plugin={props.plugin} onReady={props.onReady} />
<RemixUiXterminals {...props} />
</>
}
</TerminalContext.Provider>
</>)
}

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

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent } from 'react' // eslint-disable-line
import React, { useState, useEffect, useReducer, useRef, SyntheticEvent, MouseEvent, useContext } from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl'
import {
registerCommandAction,
@ -27,8 +27,9 @@ import RenderUnKnownTransactions from './components/RenderUnknownTransactions' /
import RenderCall from './components/RenderCall' // eslint-disable-line
import RenderKnownTransactions from './components/RenderKnownTransactions' // eslint-disable-line
import parse from 'html-react-parser'
import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, UNKNOWN_TRANSACTION } from './types/terminalTypes'
import { EMPTY_BLOCK, KNOWN_TRANSACTION, RemixUiTerminalProps, SET_ISVM, UNKNOWN_TRANSACTION } from './types/terminalTypes'
import { wrapScript } from './utils/wrapScript'
import { TerminalContext } from './context'
const _paq = (window._paq = window._paq || [])
/* eslint-disable-next-line */
@ -41,7 +42,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const [_cmdIndex, setCmdIndex] = useState(-1)
const [_cmdTemp, setCmdTemp] = useState('')
const [isOpen, setIsOpen] = useState<boolean>(true)
const [newstate, dispatch] = useReducer(registerCommandReducer, initialState)
const { terminalState, dispatch } = useContext(TerminalContext)
const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState)
const [, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState)
const [toaster, setToaster] = useState(false)
@ -59,7 +60,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
handleHide: () => {},
})
const [clearConsole, setClearConsole] = useState(false)
const [isVM, setIsVM] = useState(false)
const [paste, setPaste] = useState(false)
const [storage, setStorage] = useState<any>(null)
@ -81,7 +81,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
commandHistoryIndex: 0,
})
const [searchInput, setSearchInput] = useState('')
const [showTableHash, setShowTableHash] = useState([])
// terminal inputRef
@ -101,7 +100,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
useEffect(() => {
props.plugin.on('network', 'providerChanged', (provider) => {
setIsVM(provider.startsWith('vm-'))
dispatch({ type: SET_ISVM, payload: provider.startsWith('vm-') })
})
props.onReady({
@ -132,10 +131,10 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
// events
useEffect(() => {
initListeningOnNetwork(props.plugin, scriptRunnerDispatch)
registerLogScriptRunnerAction(on, 'log', newstate.commands, scriptRunnerDispatch)
registerInfoScriptRunnerAction(on, 'info', newstate.commands, scriptRunnerDispatch)
registerWarnScriptRunnerAction(on, 'warn', newstate.commands, scriptRunnerDispatch)
registerErrorScriptRunnerAction(on, 'error', newstate.commands, scriptRunnerDispatch)
registerLogScriptRunnerAction(on, 'log', terminalState.commands, scriptRunnerDispatch)
registerInfoScriptRunnerAction(on, 'info', terminalState.commands, scriptRunnerDispatch)
registerWarnScriptRunnerAction(on, 'warn', terminalState.commands, scriptRunnerDispatch)
registerErrorScriptRunnerAction(on, 'error', terminalState.commands, scriptRunnerDispatch)
registerCommandAction('html', _blocksRenderer('html'), { activate: true }, dispatch)
registerCommandAction('log', _blocksRenderer('log'), { activate: true }, dispatch)
registerCommandAction('info', _blocksRenderer('info'), { activate: true }, dispatch)
@ -158,7 +157,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
useEffect(() => {
scrollToBottom()
}, [newstate.journalBlocks.length, toaster])
}, [terminalState.journalBlocks.length, toaster])
function execute(file, cb) {
function _execute(content, cb) {
@ -167,7 +166,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
if (cb) cb()
return
}
newstate.commands.script(content)
terminalState.commands.script(content)
}
if (typeof file === 'undefined') {
@ -303,7 +302,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const script = autoCompletState.userInput.trim() // inputEl.current.innerText.trim()
if (script.length) {
cmdHistoryDispatch({ type: 'cmdHistory', payload: { script } })
newstate.commands.script(wrapScript(script))
terminalState.commands.script(wrapScript(script))
}
setAutoCompleteState((prevState) => ({ ...prevState, userInput: '' }))
inputEl.current.innerText = ''
@ -313,11 +312,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
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()
setAutoCompleteState((prevState) => ({
...prevState,
userInput: newstate._commandHistory[0],
userInput: terminalState._commandHistory[0],
}))
} else if (event.which === 38 && autoCompletState.showSuggestions) {
event.preventDefault()
@ -397,21 +396,17 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}
}
useEffect(() => {
if(terminalState.clearConsole){
typeWriterIndexes.current = []
inputEl.current.focus()
}
},[terminalState.clearConsole])
/* end of block content that gets rendered from script Runner */
const handleClearConsole = () => {
setClearConsole(true)
typeWriterIndexes.current = []
dispatch({ type: 'clearconsole', payload: [] })
inputEl.current.focus()
}
/* start of autoComplete */
const listenOnNetwork = (e: any) => {
const isListening = e.target.checked
listenOnNetworkAction(props.plugin, isListening)
}
const onChange = (event: any) => {
event.preventDefault()
const inputString = event.target.value
@ -562,11 +557,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}))
}
const handleToggleTerminal = () => {
setIsOpen(!isOpen)
props.plugin.call('layout', 'minimize', props.plugin.profile.name, isOpen)
}
useEffect(() => {
;(async () => {
const storage = await props.plugin.call('storage', 'formatString', await props.plugin.call('storage', 'getStorage'))
@ -601,76 +591,15 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
}
return (
( !props.visible? <></>:
<div style={{ flexGrow: 1 }} className="remix_ui_terminal_panel" ref={panelRef}>
<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">
<CustomTooltip
placement="top"
tooltipId="terminalToggle"
tooltipClasses="text-nowrap"
tooltipText={isOpen ? <FormattedMessage id="terminal.hideTerminal" /> : <FormattedMessage id="terminal.showTerminal" />}
>
<i
className={`mx-2 remix_ui_terminal_toggleTerminal fas ${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: 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={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="terminalInputSearchTerminal"
/>
</div>
</div>
</div>
( props.visible &&
<div style={{ flexGrow: 1 }} className="remix_ui_terminal_panel h-100" ref={panelRef}>
<div tabIndex={-1} className="remix_ui_terminal_container d-flex h-100 m-0 flex-column" data-id="terminalContainer">
{handleAutoComplete()}
<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">
{!clearConsole && <TerminalWelcomeMessage storage={storage} packageJson={version} />}
{newstate.journalBlocks &&
newstate.journalBlocks.map((x, index) => {
{!terminalState.clearConsole && <TerminalWelcomeMessage storage={storage} packageJson={version} />}
{terminalState.journalBlocks &&
terminalState.journalBlocks.map((x, index) => {
if (x.name === EMPTY_BLOCK) {
return (
<div className={classNameBlock} data-id="block" key={index}>
@ -683,7 +612,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
)
} else if (x.name === UNKNOWN_TRANSACTION) {
return x.message
.filter((x) => includeSearch(x, searchInput))
.filter((x) => includeSearch(x, terminalState.searchInput))
.map((trans) => {
return (
<div className={classNameBlock} data-id={`block_tx${trans.tx.hash}`} key={index}>
@ -705,7 +634,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
})
} else if (x.name === KNOWN_TRANSACTION) {
return x.message
.filter((x) => includeSearch(x, searchInput))
.filter((x) => includeSearch(x, terminalState.searchInput))
.map((trans) => {
return (
<div className={classNameBlock} data-id={`block_tx${trans.tx.hash}`} key={index}>
@ -738,7 +667,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
)
})
} else if (Array.isArray(x.message)) {
if (searchInput !== '') return []
if (terminalState.searchInput !== '') return []
return x.message.map((msg, i) => {
// strictly check condition on 0, false, except undefined, NaN.
// if you type `undefined`, terminal automatically throws error, it's error message: "undefined" is not valid JSON

@ -23,11 +23,15 @@ export const WARN = 'warn'
export const ERROR = 'error'
export const SCRIPT = 'script'
export const CLEAR_CONSOLE = 'clearconsole'
export const TOGGLE = 'toggle'
export const SET_OPEN = 'setOpen'
export const LISTEN_ON_NETWORK = 'listenOnNetWork'
export const CMD_HISTORY = 'cmdHistory'
export const SEARCH = 'search'
export const SET_ISVM = 'setIsVM'
export interface RemixUiTerminalProps {
plugin: any,
onReady: (api: any) => void,
visible: boolean,
plugin: any,
onReady: (api: any) => void,
visible: boolean,
}

@ -1,2 +1,5 @@
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,12 @@
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 } })
}

@ -0,0 +1,59 @@
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={`d-flex flex-row align-items-center ${xtermState.showOutput ? 'd-none' : ''}`}>
<div className="mx-2" onClick={async () => onCreateTerminal()}>
<CustomTooltip tooltipText={<FormattedMessage id='xterm.new' defaultMessage='New terminal' />}>
<i className="fas fa-plus border-0 p-0 m-0"></i>
</CustomTooltip>
</div>
<div className=''>
<CustomTooltip tooltipText={<FormattedMessage id='xterm.shells' defaultMessage='Shells' />}>
<Dropdown as={ButtonGroup}>
<Dropdown.Toggle split variant="" 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>
</CustomTooltip>
</div>
<div className="mx-2" onClick={onCloseTerminal}>
<CustomTooltip tooltipText={<FormattedMessage id='xterm.close' defaultMessage='Close terminal' />}>
<i className="far fa-trash border-0 ml-1"></i>
</CustomTooltip>
</div>
<div className="mx-2" onClick={async () => onClearTerminal()}>
<CustomTooltip tooltipText={<FormattedMessage id='xterm.clear' defaultMessage='Clear Terminal' />}>
<i className="fas fa-ban border-0 p-0 m-0"></i>
</CustomTooltip>
</div>
</div>
</>)
}

@ -54,8 +54,6 @@ const RemixUiXterm = (props: RemixUiXtermProps) => {
resize(event, pid)
}
return (
<Xterm
addons={[fitAddon]}

@ -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 RemixUiXterm from './remix-ui-xterm'
import '../css/index.css'
import { Button, ButtonGroup, Dropdown, Tab, Tabs } from 'react-bootstrap'
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 { xtermState } from '../types'
import { createTerminal } from '@remix-ui/xterm'
export interface RemixUiXterminalsProps {
plugin: ElectronPlugin
onReady: (api: any) => void
}
export interface xtermState {
pid: number
queue: string
timeStamp: number
ref: any
hidden: boolean
}
export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
const { xtermState, dispatchXterm } = useContext(TerminalContext)
const [terminals, setTerminals] = useState<xtermState[]>([])
const [workingDir, setWorkingDir] = useState<string>('')
const [showOutput, setShowOutput] = useState<boolean>(true)
//const [workingDir, setWorkingDir] = useState<string>('')
const [theme, setTheme] = useState<any>(themeCollection[0])
const [terminalsEnabled, setTerminalsEnabled] = useState<boolean>(false)
const [shells, setShells] = useState<string[]>([])
const { plugin } = props
useEffect(() => {
@ -36,43 +30,24 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
})
plugin.on('xterm', 'close', async (pid: number) => {
setTerminals(prevState => {
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]
})
dispatchXterm({ type: 'REMOVE_TERMINAL', payload: pid })
})
plugin.on('xterm', 'new', async (pid: number) => {
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
}]
})
dispatchXterm({ type: 'SHOW_OUTPUT', payload: false })
dispatchXterm({ type: 'ADD_TERMINAL', payload: { pid, queue: '', timeStamp: Date.now(), ref: null, hidden: false } })
})
plugin.on('fs', 'workingDirChanged', (path: string) => {
setWorkingDir(path)
setTerminalsEnabled(true)
dispatchXterm({ type: 'SET_WORKING_DIR', payload: path })
dispatchXterm({ type: 'ENABLE_TERMINALS', payload: null })
})
const workingDir = await plugin.call('fs', 'getWorkingDir')
if(workingDir && workingDir !== '') {
setTerminalsEnabled(true)
setWorkingDir(workingDir)
dispatchXterm({ type: 'ENABLE_TERMINALS', payload: null })
dispatchXterm({ type: 'SET_WORKING_DIR', payload: workingDir })
}
plugin.on('theme', 'themeChanged', async (theme) => {
@ -97,6 +72,12 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
}, 2000)
}, [])
useEffect(() => {
setTerminals(xtermState.terminals)
if(xtermState.terminals.length === 0) {
dispatchXterm({ type: 'SHOW_OUTPUT', payload: true })
}
}, [xtermState.terminals])
const handleThemeChange = (theme: any) => {
themeCollection.forEach((themeItem) => {
@ -129,27 +110,6 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
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) => {
setTerminals(prevState => {
const terminal = prevState.find(xtermState => xtermState.pid === pid)
@ -174,93 +134,46 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
})
}
const closeTerminal = () => {
const pid = terminals.find(xtermState => xtermState.hidden === false).pid
if (pid)
plugin.call('xterm', 'closeTerminal', pid)
}
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()
}
useEffect(() => {
if (!xtermState.showOutput) {
if (terminals.length === 0) createTerminal('', plugin, xtermState.workingDir, dispatchXterm)
}
}, [xtermState.showOutput])
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 style={{ flexGrow: 1 }} className={`flex-row ${xtermState.showOutput ? 'h-0 d-none' : 'h-100 d-flex'}`}>
<>
<div className={`remix-ui-xterminals-section ${showOutput ? 'd-none' : 'd-flex'} `}>
{ <div className={`flex-row w-100 h-100 ${xtermState.showOutput ? 'h-0 d-none' : 'h-100 d-flex'}`}>
{terminals.map((xtermState) => {
return (
<div className={`h-100 xterm-terminal ${xtermState.hidden ? 'hide-xterm' : 'show-xterm'}`} key={xtermState.pid} data-id={`remixUIXT${xtermState.pid}`}>
<RemixUiXterm theme={theme} setTerminalRef={setTerminalRef} timeStamp={xtermState.timeStamp} send={send} resize={resize} pid={xtermState.pid} plugin={plugin}></RemixUiXterm>
<div className={`h-100 w-100 ${xtermState.hidden ? 'd-none' : 'd-block'}`} key={xtermState.pid} data-id={`remixUIXT${xtermState.pid}`}>
<RemixUiXterm
theme={theme}
setTerminalRef={setTerminalRef}
timeStamp={xtermState.timeStamp}
send={send}
resize={resize}
pid={xtermState.pid}
plugin={plugin}
></RemixUiXterm>
</div>
)
})}
<div className='remix-ui-xterminals-buttons border-left'>
<div className='d-flex flex-column border-left xterm-panel-left'>
{terminals.map((xtermState, index) => {
return (<button key={index} onClick={async () => selectTerminal(xtermState)} className={`btn btn-sm mt-2 btn-secondary ${xtermState.hidden ? 'xterm-btn-none' : 'xterm-btn-active'}`}><span className="fa fa-terminal border-0 p-0 m-0"></span></button>)
return (<button
key={index}
onClick={async () => selectTerminal(xtermState)}
className={`btn btn-sm m-1 p-1 px-2 ${xtermState.hidden ? 'border border-secondary' : 'btn-secondary'}`}
>
<span className="fa fa-terminal border-0 p-0 m-0"></span>
</button>)
})}
</div>
</div>
}
</>
</div>
</div>}
</>)
}
@ -313,4 +226,4 @@ const themeCollection = [
themeName: 'Pride', backgroundColor: '#f1eef6', textColor: '#343a40',
shapeColor: '#343a40', fillColor: '#f8fafe'
},
]
]

@ -232,6 +232,6 @@ export class Xterm extends React.Component<IProps> {
}
render() {
return <div className={this.props.className} ref={this.terminalRef} />
return <div className={`${this.props.className} mt-2 ml-2`} ref={this.terminalRef} />
}
}

@ -1,70 +1,4 @@
.remix-ui-xterminals-container {
display: flex;
flex-direction: row;
flex: 1;
}
.xterm-panel {
display: flex;
flex-direction: column;
flex: 1;
}
.remix-ui-xterminals-buttons {
display: flex;
flex-direction: column;
}
.hide-xterm{
display: none;
}
.show-xterm{
display: block;
}
.xterm-btn-active {
background-color: var(--primary);
}
.xterm-btn-none {
background-color: var(--secondary);
}
.xterm-terminal {
flex-grow: 1;
height: 100%;
width: 100%;
}
.xterm-panel-header-right {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-self: flex-end;
}
.xterm-panel-header {
display: flex;
flex-direction: row;
}
.xterm-panel-header-left {
display: flex;
flex-direction: row;
flex-grow: 1;
}
.remix-ui-xterminals-section {
display: flex;
flex-direction: row;
width: 100%;
z-index: 3;
}
.hide-terminals {
width: 0;
}
.show-terminals {
width: 100%;
.xterm-panel-left {
overflow-y: scroll;
margin-bottom: 2.1rem;
}

@ -0,0 +1,70 @@
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