diff --git a/apps/remix-ide-e2e/src/commands/openFile.ts b/apps/remix-ide-e2e/src/commands/openFile.ts index 7f7e7b2026..f214100eee 100644 --- a/apps/remix-ide-e2e/src/commands/openFile.ts +++ b/apps/remix-ide-e2e/src/commands/openFile.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() }) diff --git a/apps/remix-ide/src/app/panels/terminal.js b/apps/remix-ide/src/app/panels/terminal.tsx similarity index 78% rename from apps/remix-ide/src/app/panels/terminal.js rename to apps/remix-ide/src/app/panels/terminal.tsx index 5ad90adf00..3b93cd5c22 100644 --- a/apps/remix-ide/src/app/panels/terminal.js +++ b/apps/remix-ide/src/app/panels/terminal.tsx @@ -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()) ? - - : + />) } renderComponent() { diff --git a/libs/remix-ui/panel/src/lib/plugins/panel.css b/libs/remix-ui/panel/src/lib/plugins/panel.css index f0cc402b55..437471fabb 100644 --- a/libs/remix-ui/panel/src/lib/plugins/panel.css +++ b/libs/remix-ui/panel/src/lib/plugins/panel.css @@ -104,7 +104,5 @@ iframe { height: 2rem !important; } - .terminal-wrap.minimized.desktop { - height: 4.5rem !important; } diff --git a/libs/remix-ui/terminal/src/index.ts b/libs/remix-ui/terminal/src/index.ts index 5b8eeca5b5..60fa49f835 100644 --- a/libs/remix-ui/terminal/src/index.ts +++ b/libs/remix-ui/terminal/src/index.ts @@ -1 +1,3 @@ export * from './lib/remix-ui-terminal' +export * from './lib/remix-ui-terminal-wrapper' +export * from './lib/context' \ No newline at end of file diff --git a/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-bar.tsx b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-bar.tsx new file mode 100644 index 0000000000..f88efce844 --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-bar.tsx @@ -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 (<> +
+
+ + {platform === appPlatformTypes.desktop ? +
+ + {xtermState.showOutput? : } +
: + + } +
+
+ ) +} \ No newline at end of file diff --git a/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-buttons.css b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-buttons.css new file mode 100644 index 0000000000..f4f8c73e76 --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-buttons.css @@ -0,0 +1,3 @@ +.xtermButton { + width: 4rem;; +} \ No newline at end of file diff --git a/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-buttons.tsx b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-buttons.tsx new file mode 100644 index 0000000000..7856a67e5d --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-buttons.tsx @@ -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 ( +
+ + +
+ ) +} \ No newline at end of file diff --git a/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-toggle.tsx b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-toggle.tsx new file mode 100644 index 0000000000..0cfa811fc0 --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu-toggle.tsx @@ -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 ( + <> + : } + > + + + + ) +} \ No newline at end of file diff --git a/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu.tsx b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu.tsx new file mode 100644 index 0000000000..8eac46c62d --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/components/remix-ui-terminal-menu.tsx @@ -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 (
+ }> +
0
+
+ +
+ + + + +
+
+
+ + 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" + /> +
+
+ }> + + +
+
) +} \ No newline at end of file diff --git a/libs/remix-ui/terminal/src/lib/context/index.ts b/libs/remix-ui/terminal/src/lib/context/index.ts new file mode 100644 index 0000000000..810b3fba20 --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/context/index.ts @@ -0,0 +1,11 @@ +import { Actions, xTerminalUiState } from '@remix-ui/xterm' +import React, { Dispatch } from 'react' + +type terminalProviderContextType = { + terminalState: any, + dispatch: Dispatch, + xtermState: xTerminalUiState, + dispatchXterm: Dispatch +} + +export const TerminalContext = React.createContext(null) \ No newline at end of file diff --git a/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts b/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts index c451abfbb8..3b1ed4d469 100644 --- a/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts +++ b/libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts @@ -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}), } } } diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal-wrapper.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal-wrapper.tsx new file mode 100644 index 0000000000..d21123eaef --- /dev/null +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal-wrapper.tsx @@ -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 (<> + + + {platform !== appPlatformTypes.desktop && } + {platform === appPlatformTypes.desktop && + <> + + + + } + + ) +} \ No newline at end of file diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css index 13402e45ee..fb9d6bf735 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.css @@ -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; diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index 4c35d28981..3a16832b18 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -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(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(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? <>: -
-
-
- : } - > - - -
- }> - - -
- }> -
0
-
- -
- - - - -
-
-
- - 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" - /> -
-
-
+ ( props.visible && +
{handleAutoComplete()}
- {!clearConsole && } - {newstate.journalBlocks && - newstate.journalBlocks.map((x, index) => { + {!terminalState.clearConsole && } + {terminalState.journalBlocks && + terminalState.journalBlocks.map((x, index) => { if (x.name === EMPTY_BLOCK) { return (
@@ -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 (
@@ -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 (
@@ -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 diff --git a/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts b/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts index 29d5af476a..52e667badb 100644 --- a/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts +++ b/libs/remix-ui/terminal/src/lib/types/terminalTypes.ts @@ -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, } diff --git a/libs/remix-ui/xterm/src/index.ts b/libs/remix-ui/xterm/src/index.ts index d0850bef79..6d438dd286 100644 --- a/libs/remix-ui/xterm/src/index.ts +++ b/libs/remix-ui/xterm/src/index.ts @@ -1,2 +1,5 @@ export * from './lib/components/remix-ui-xterm' -export * from './lib/components/remix-ui-xterminals' \ No newline at end of file +export * from './lib/components/remix-ui-xterminals' +export * from './lib/reducer' +export * from './lib/types' +export * from './lib/actions' \ No newline at end of file diff --git a/libs/remix-ui/xterm/src/lib/actions/index.ts b/libs/remix-ui/xterm/src/lib/actions/index.ts new file mode 100644 index 0000000000..956c850296 --- /dev/null +++ b/libs/remix-ui/xterm/src/lib/actions/index.ts @@ -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) => { + 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 } }) + +} diff --git a/libs/remix-ui/xterm/src/lib/components/remix-ui-terminal-menu-xterm.tsx b/libs/remix-ui/xterm/src/lib/components/remix-ui-terminal-menu-xterm.tsx new file mode 100644 index 0000000000..a2732762a8 --- /dev/null +++ b/libs/remix-ui/xterm/src/lib/components/remix-ui-terminal-menu-xterm.tsx @@ -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 { + 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 { + createTerminal(shell, props.plugin, xtermState.workingDir, dispatchXterm) + } + + function onCloseTerminal(): void | PromiseLike { + const pid = xtermState.terminals.find(xtermState => xtermState.hidden === false).pid + if (pid) + props.plugin.call('xterm', 'closeTerminal', pid) + } + + return (<> +
+
onCreateTerminal()}> + }> + + +
+
+ }> + + + + {xtermState.shells.map((shell, index) => { + return ( await onCreateTerminal(shell)}>{shell}) + })} + + + +
+
+ }> + + +
+
onClearTerminal()}> + }> + + +
+
+ ) +} \ No newline at end of file diff --git a/libs/remix-ui/xterm/src/lib/components/remix-ui-xterm.tsx b/libs/remix-ui/xterm/src/lib/components/remix-ui-xterm.tsx index 703b335f7f..7aa71d962b 100644 --- a/libs/remix-ui/xterm/src/lib/components/remix-ui-xterm.tsx +++ b/libs/remix-ui/xterm/src/lib/components/remix-ui-xterm.tsx @@ -54,8 +54,6 @@ const RemixUiXterm = (props: RemixUiXtermProps) => { resize(event, pid) } - - return ( 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([]) - const [workingDir, setWorkingDir] = useState('') - const [showOutput, setShowOutput] = useState(true) + //const [workingDir, setWorkingDir] = useState('') + const [theme, setTheme] = useState(themeCollection[0]) - const [terminalsEnabled, setTerminalsEnabled] = useState(false) - const [shells, setShells] = useState([]) + 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 (<> -
-
- - -
-
- - - - - - - - - - {shells.map((shell, index) => { - return ( await createTerminal(shell)}>{shell}) - })} - - - -
-
- - - -
+ {
<> - -
+ {
{terminals.map((xtermState) => { return ( -
- +
+
) })} -
+
{terminals.map((xtermState, index) => { - return () + return () })}
+ } -
- - +
} ) } @@ -313,4 +226,4 @@ const themeCollection = [ themeName: 'Pride', backgroundColor: '#f1eef6', textColor: '#343a40', shapeColor: '#343a40', fillColor: '#f8fafe' }, -] \ No newline at end of file +] diff --git a/libs/remix-ui/xterm/src/lib/components/xterm-wrap.tsx b/libs/remix-ui/xterm/src/lib/components/xterm-wrap.tsx index 03b54ccf6f..4998c3e750 100644 --- a/libs/remix-ui/xterm/src/lib/components/xterm-wrap.tsx +++ b/libs/remix-ui/xterm/src/lib/components/xterm-wrap.tsx @@ -232,6 +232,6 @@ export class Xterm extends React.Component { } render() { - return
+ return
} } \ No newline at end of file diff --git a/libs/remix-ui/xterm/src/lib/css/index.css b/libs/remix-ui/xterm/src/lib/css/index.css index d7a13a031f..5912949212 100644 --- a/libs/remix-ui/xterm/src/lib/css/index.css +++ b/libs/remix-ui/xterm/src/lib/css/index.css @@ -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; } diff --git a/libs/remix-ui/xterm/src/lib/reducer/index.ts b/libs/remix-ui/xterm/src/lib/reducer/index.ts new file mode 100644 index 0000000000..a96d218523 --- /dev/null +++ b/libs/remix-ui/xterm/src/lib/reducer/index.ts @@ -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 + } +} diff --git a/libs/remix-ui/xterm/src/lib/types/index.ts b/libs/remix-ui/xterm/src/lib/types/index.ts new file mode 100644 index 0000000000..170441acb9 --- /dev/null +++ b/libs/remix-ui/xterm/src/lib/types/index.ts @@ -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 { + type: T, + payload: ActionPayloadTypes[T] +} + +export type Actions = {[A in keyof ActionPayloadTypes]: Action}[keyof ActionPayloadTypes] \ No newline at end of file