Skip to content

Commit 647d2cb

Browse files
authored
add scroll down helper to console (pterodactyl#2951)
1 parent 75d254a commit 647d2cb

File tree

4 files changed

+96
-25
lines changed

4 files changed

+96
-25
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"swr": "^0.2.3",
3838
"tailwindcss": "^2.0.2",
3939
"uuid": "^3.3.2",
40-
"xterm": "^4.9.0",
40+
"xterm": "^4.12.0",
4141
"xterm-addon-attach": "^0.6.0",
4242
"xterm-addon-fit": "^0.4.0",
4343
"xterm-addon-search": "^0.7.0",

resources/scripts/components/server/Console.tsx

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React, { useEffect, useMemo, useRef, useState } from 'react';
2-
import { ITerminalOptions, Terminal } from 'xterm';
2+
import { Terminal, ITerminalOptions } from 'xterm';
33
import { FitAddon } from 'xterm-addon-fit';
44
import { SearchAddon } from 'xterm-addon-search';
55
import { SearchBarAddon } from 'xterm-addon-search-bar';
66
import { WebLinksAddon } from 'xterm-addon-web-links';
7+
import { ScrollDownHelperAddon } from '@/plugins/XtermScrollDownHelperAddon';
78
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
89
import { ServerContext } from '@/state/server';
910
import styled from 'styled-components/macro';
@@ -72,12 +73,13 @@ export default () => {
7273
const searchAddon = new SearchAddon();
7374
const searchBar = new SearchBarAddon({ searchAddon });
7475
const webLinksAddon = new WebLinksAddon();
76+
const scrollDownHelperAddon = new ScrollDownHelperAddon();
7577
const { connected, instance } = ServerContext.useStoreState(state => state.socket);
76-
const [ canSendCommands ] = usePermissions([ 'control.console' ]);
78+
const [canSendCommands] = usePermissions(['control.console']);
7779
const serverId = ServerContext.useStoreState(state => state.server.data!.id);
7880
const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring);
79-
const [ history, setHistory ] = usePersistedState<string[]>(`${serverId}:command_history`, []);
80-
const [ historyIndex, setHistoryIndex ] = useState(-1);
81+
const [history, setHistory] = usePersistedState<string[]>(`${serverId}:command_history`, []);
82+
const [historyIndex, setHistoryIndex] = useState(-1);
8183

8284
const handleConsoleOutput = (line: string, prelude = false) => terminal.writeln(
8385
(prelude ? TERMINAL_PRELUDE : '') + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m',
@@ -125,7 +127,7 @@ export default () => {
125127

126128
const command = e.currentTarget.value;
127129
if (e.key === 'Enter' && command.length > 0) {
128-
setHistory(prevHistory => [ command, ...prevHistory! ].slice(0, 32));
130+
setHistory(prevHistory => [command, ...prevHistory!].slice(0, 32));
129131
setHistoryIndex(-1);
130132

131133
instance && instance.send('send command', command);
@@ -139,6 +141,7 @@ export default () => {
139141
terminal.loadAddon(searchAddon);
140142
terminal.loadAddon(searchBar);
141143
terminal.loadAddon(webLinksAddon);
144+
terminal.loadAddon(scrollDownHelperAddon);
142145

143146
terminal.open(ref.current);
144147
fitAddon.fit();
@@ -158,7 +161,7 @@ export default () => {
158161
return true;
159162
});
160163
}
161-
}, [ terminal, connected ]);
164+
}, [terminal, connected]);
162165

163166
useEventListener('resize', debounce(() => {
164167
if (terminal.element) {
@@ -196,33 +199,33 @@ export default () => {
196199
});
197200
}
198201
};
199-
}, [ connected, instance ]);
202+
}, [connected, instance]);
200203

201204
return (
202205
<div css={tw`text-xs font-mono relative`}>
203-
<SpinnerOverlay visible={!connected} size={'large'}/>
206+
<SpinnerOverlay visible={!connected} size={'large'} />
204207
<div
205208
css={[
206209
tw`rounded-t p-2 bg-black w-full`,
207210
!canSendCommands && tw`rounded-b`,
208211
]}
209212
style={{ minHeight: '16rem' }}
210213
>
211-
<TerminalDiv id={'terminal'} ref={ref}/>
214+
<TerminalDiv id={'terminal'} ref={ref} />
212215
</div>
213216
{canSendCommands &&
214-
<div css={tw`rounded-b bg-neutral-900 text-neutral-100 flex items-baseline`}>
215-
<div css={tw`flex-shrink-0 p-2 font-bold`}>$</div>
216-
<div css={tw`w-full`}>
217-
<CommandInput
218-
type={'text'}
219-
placeholder={'Type a command...'}
220-
aria-label={'Console command input.'}
221-
disabled={!instance || !connected}
222-
onKeyDown={handleCommandKeyDown}
223-
/>
217+
<div css={tw`rounded-b bg-neutral-900 text-neutral-100 flex items-baseline`}>
218+
<div css={tw`flex-shrink-0 p-2 font-bold`}>$</div>
219+
<div css={tw`w-full`}>
220+
<CommandInput
221+
type={'text'}
222+
placeholder={'Type a command...'}
223+
aria-label={'Console command input.'}
224+
disabled={!instance || !connected}
225+
onKeyDown={handleCommandKeyDown}
226+
/>
227+
</div>
224228
</div>
225-
</div>
226229
}
227230
</div>
228231
);
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { Terminal, ITerminalAddon } from 'xterm';
2+
3+
export class ScrollDownHelperAddon implements ITerminalAddon {
4+
private terminal: Terminal = new Terminal();
5+
private element?: HTMLDivElement;
6+
7+
activate (terminal: Terminal): void {
8+
this.terminal = terminal;
9+
10+
this.terminal.onScroll(() => {
11+
if (this.isScrolledDown()) {
12+
this.hide();
13+
}
14+
});
15+
16+
this.terminal.onLineFeed(() => {
17+
if (!this.isScrolledDown()) {
18+
this.show();
19+
}
20+
});
21+
22+
this.show();
23+
}
24+
25+
dispose (): void {
26+
// ignore
27+
}
28+
29+
show (): void {
30+
if (!this.terminal || !this.terminal.element) {
31+
return;
32+
}
33+
if (this.element) {
34+
this.element.style.visibility = 'visible';
35+
return;
36+
}
37+
38+
this.terminal.element.style.position = 'relative';
39+
40+
this.element = document.createElement('div');
41+
this.element.innerHTML = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="bell" class="svg-inline--fa fa-bell fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M224 512c35.32 0 63.97-28.65 63.97-64H160.03c0 35.35 28.65 64 63.97 64zm215.39-149.71c-19.32-20.76-55.47-51.99-55.47-154.29 0-77.7-54.48-139.9-127.94-155.16V32c0-17.67-14.32-32-31.98-32s-31.98 14.33-31.98 32v20.84C118.56 68.1 64.08 130.3 64.08 208c0 102.3-36.15 133.53-55.47 154.29-6 6.45-8.66 14.16-8.61 21.71.11 16.4 12.98 32 32.1 32h383.8c19.12 0 32-15.6 32.1-32 .05-7.55-2.61-15.27-8.61-21.71z"></path></svg>';
42+
this.element.style.position = 'absolute';
43+
this.element.style.right = '1.5rem';
44+
this.element.style.bottom = '.5rem';
45+
this.element.style.padding = '.5rem';
46+
this.element.style.fontSize = '1.25em';
47+
this.element.style.boxShadow = '0 2px 8px #000';
48+
this.element.style.backgroundColor = '#252526';
49+
this.element.style.zIndex = '999';
50+
this.element.style.cursor = 'pointer';
51+
52+
this.element.addEventListener('click', () => {
53+
this.terminal.scrollToBottom();
54+
});
55+
56+
this.terminal.element.appendChild(this.element);
57+
}
58+
59+
hide (): void {
60+
if (this.element) {
61+
this.element.style.visibility = 'hidden';
62+
}
63+
}
64+
65+
isScrolledDown (): boolean {
66+
return this.terminal.buffer.active.viewportY === this.terminal.buffer.active.baseY;
67+
}
68+
}

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8040,10 +8040,10 @@ xterm-addon-web-links@^0.4.0:
80408040
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0.tgz#265cbf8221b9b315d0a748e1323bee331cd5da03"
80418041
integrity sha512-xv8GeiINmx0zENO9hf5k+5bnkaE8mRzF+OBAr9WeFq2eLaQSudioQSiT34M1ofKbzcdjSsKiZm19Rw3i4eXamg==
80428042

8043-
xterm@^4.9.0:
8044-
version "4.9.0"
8045-
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.9.0.tgz#7a4c097a433d565339b5533b468bbc60c6c87969"
8046-
integrity sha512-wGfqufmioctKr8VkbRuZbVDfjlXWGZZ1PWHy1yqqpGT3Nm6yaJx8lxDbSEBANtgaiVPTcKSp97sxOy5IlpqYfw==
8043+
xterm@^4.12.0:
8044+
version "4.12.0"
8045+
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.12.0.tgz#db09b425b4dcae5b96f8cbbaaa93b3bc60997ca9"
8046+
integrity sha512-K5mF/p3txUV18mjiZFlElagoHFpqXrm5OYHeoymeXSu8GG/nMaOO/+NRcNCwfdjzAbdQ5VLF32hEHiWWKKm0bw==
80478047

80488048
y18n@^4.0.0:
80498049
version "4.0.0"

0 commit comments

Comments
 (0)