From 647d2cbf92ac2492a03c29490351d766be999667 Mon Sep 17 00:00:00 2001 From: Jakob Date: Sat, 5 Jun 2021 17:39:36 +0200 Subject: [PATCH] add scroll down helper to console (#2951) --- package.json | 2 +- .../scripts/components/server/Console.tsx | 43 ++++++------ .../plugins/XtermScrollDownHelperAddon.ts | 68 +++++++++++++++++++ yarn.lock | 8 +-- 4 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 resources/scripts/plugins/XtermScrollDownHelperAddon.ts diff --git a/package.json b/package.json index d317966c7..049a88fc4 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "swr": "^0.2.3", "tailwindcss": "^2.0.2", "uuid": "^3.3.2", - "xterm": "^4.9.0", + "xterm": "^4.12.0", "xterm-addon-attach": "^0.6.0", "xterm-addon-fit": "^0.4.0", "xterm-addon-search": "^0.7.0", diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index d46f6d7a1..201c77de7 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -1,9 +1,10 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { ITerminalOptions, Terminal } from 'xterm'; +import { Terminal, ITerminalOptions } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; import { SearchAddon } from 'xterm-addon-search'; import { SearchBarAddon } from 'xterm-addon-search-bar'; import { WebLinksAddon } from 'xterm-addon-web-links'; +import { ScrollDownHelperAddon } from '@/plugins/XtermScrollDownHelperAddon'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import { ServerContext } from '@/state/server'; import styled from 'styled-components/macro'; @@ -72,12 +73,13 @@ export default () => { const searchAddon = new SearchAddon(); const searchBar = new SearchBarAddon({ searchAddon }); const webLinksAddon = new WebLinksAddon(); + const scrollDownHelperAddon = new ScrollDownHelperAddon(); const { connected, instance } = ServerContext.useStoreState(state => state.socket); - const [ canSendCommands ] = usePermissions([ 'control.console' ]); + const [canSendCommands] = usePermissions(['control.console']); const serverId = ServerContext.useStoreState(state => state.server.data!.id); const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring); - const [ history, setHistory ] = usePersistedState(`${serverId}:command_history`, []); - const [ historyIndex, setHistoryIndex ] = useState(-1); + const [history, setHistory] = usePersistedState(`${serverId}:command_history`, []); + const [historyIndex, setHistoryIndex] = useState(-1); const handleConsoleOutput = (line: string, prelude = false) => terminal.writeln( (prelude ? TERMINAL_PRELUDE : '') + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m', @@ -125,7 +127,7 @@ export default () => { const command = e.currentTarget.value; if (e.key === 'Enter' && command.length > 0) { - setHistory(prevHistory => [ command, ...prevHistory! ].slice(0, 32)); + setHistory(prevHistory => [command, ...prevHistory!].slice(0, 32)); setHistoryIndex(-1); instance && instance.send('send command', command); @@ -139,6 +141,7 @@ export default () => { terminal.loadAddon(searchAddon); terminal.loadAddon(searchBar); terminal.loadAddon(webLinksAddon); + terminal.loadAddon(scrollDownHelperAddon); terminal.open(ref.current); fitAddon.fit(); @@ -158,7 +161,7 @@ export default () => { return true; }); } - }, [ terminal, connected ]); + }, [terminal, connected]); useEventListener('resize', debounce(() => { if (terminal.element) { @@ -196,11 +199,11 @@ export default () => { }); } }; - }, [ connected, instance ]); + }, [connected, instance]); return (
- +
{ ]} style={{ minHeight: '16rem' }} > - +
{canSendCommands && -
-
$
-
- +
+
$
+
+ +
-
}
); diff --git a/resources/scripts/plugins/XtermScrollDownHelperAddon.ts b/resources/scripts/plugins/XtermScrollDownHelperAddon.ts new file mode 100644 index 000000000..53bc1f60d --- /dev/null +++ b/resources/scripts/plugins/XtermScrollDownHelperAddon.ts @@ -0,0 +1,68 @@ +import { Terminal, ITerminalAddon } from 'xterm'; + +export class ScrollDownHelperAddon implements ITerminalAddon { + private terminal: Terminal = new Terminal(); + private element?: HTMLDivElement; + + activate (terminal: Terminal): void { + this.terminal = terminal; + + this.terminal.onScroll(() => { + if (this.isScrolledDown()) { + this.hide(); + } + }); + + this.terminal.onLineFeed(() => { + if (!this.isScrolledDown()) { + this.show(); + } + }); + + this.show(); + } + + dispose (): void { + // ignore + } + + show (): void { + if (!this.terminal || !this.terminal.element) { + return; + } + if (this.element) { + this.element.style.visibility = 'visible'; + return; + } + + this.terminal.element.style.position = 'relative'; + + this.element = document.createElement('div'); + this.element.innerHTML = ''; + this.element.style.position = 'absolute'; + this.element.style.right = '1.5rem'; + this.element.style.bottom = '.5rem'; + this.element.style.padding = '.5rem'; + this.element.style.fontSize = '1.25em'; + this.element.style.boxShadow = '0 2px 8px #000'; + this.element.style.backgroundColor = '#252526'; + this.element.style.zIndex = '999'; + this.element.style.cursor = 'pointer'; + + this.element.addEventListener('click', () => { + this.terminal.scrollToBottom(); + }); + + this.terminal.element.appendChild(this.element); + } + + hide (): void { + if (this.element) { + this.element.style.visibility = 'hidden'; + } + } + + isScrolledDown (): boolean { + return this.terminal.buffer.active.viewportY === this.terminal.buffer.active.baseY; + } +} diff --git a/yarn.lock b/yarn.lock index b51cca230..ac59506ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8040,10 +8040,10 @@ xterm-addon-web-links@^0.4.0: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0.tgz#265cbf8221b9b315d0a748e1323bee331cd5da03" integrity sha512-xv8GeiINmx0zENO9hf5k+5bnkaE8mRzF+OBAr9WeFq2eLaQSudioQSiT34M1ofKbzcdjSsKiZm19Rw3i4eXamg== -xterm@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.9.0.tgz#7a4c097a433d565339b5533b468bbc60c6c87969" - integrity sha512-wGfqufmioctKr8VkbRuZbVDfjlXWGZZ1PWHy1yqqpGT3Nm6yaJx8lxDbSEBANtgaiVPTcKSp97sxOy5IlpqYfw== +xterm@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.12.0.tgz#db09b425b4dcae5b96f8cbbaaa93b3bc60997ca9" + integrity sha512-K5mF/p3txUV18mjiZFlElagoHFpqXrm5OYHeoymeXSu8GG/nMaOO/+NRcNCwfdjzAbdQ5VLF32hEHiWWKKm0bw== y18n@^4.0.0: version "4.0.0"