-
Notifications
You must be signed in to change notification settings - Fork 13
/
XTerm.tsx
135 lines (116 loc) · 3.59 KB
/
XTerm.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import React, { useImperativeHandle } from 'react';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { debounce, canUseDOM } from '@patternfly/react-core';
export interface XTermProps {
/** The number of columns to resize to */
cols?: number;
/** The number of rows to resize to */
rows?: number;
fontFamily?: string;
fontSize?: number;
/** Terminal title has been changed. */
onTitleChanged?: (title: string) => void;
/** Data to be sent from terminal to backend; (data) => {} */
onData?: (e: string) => void;
/** A reference object to attach to the xterm */
innerRef?: React.RefObject<any>;
}
export const XTerm: React.FunctionComponent<XTermProps> = ({
cols = 80,
rows = 25,
fontFamily,
fontSize,
onTitleChanged,
onData,
innerRef
}) => {
const terminalRef = React.useRef<Terminal>();
const ref = React.useRef<HTMLDivElement>();
useImperativeHandle(innerRef, () => ({
focusTerminal() {
if (terminalRef.current) {
terminalRef.current.focus();
}
},
/**
* Backend sent data.
*
* @param {string} data String content to be writen into the terminal
*/
onDataReceived: (data: string) => {
if (terminalRef.current) {
terminalRef.current.write(data);
}
},
/**
* Backend closed connection.
*
* @param {string} reason String error to be written into the terminal
*/
onConnectionClosed: (reason: string) => {
if (terminalRef.current) {
terminalRef.current.write(`\x1b[31m${reason || 'disconnected'}\x1b[m\r\n`);
terminalRef.current.refresh(terminalRef.current.rows, terminalRef.current.rows); // start to end row
}
}
}));
const onBeforeUnload = React.useCallback((event: any) => {
// Firefox requires this when the page is in an iframe
event.preventDefault();
// see "an almost cross-browser solution" at
// https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload
event.returnValue = '';
return '';
}, []);
const onFocusIn = () => {
window.addEventListener('beforeunload', onBeforeUnload);
};
const onFocusOut = React.useCallback(() => {
window.removeEventListener('beforeunload', onBeforeUnload);
}, [onBeforeUnload]);
React.useEffect(() => {
const fitAddon = new FitAddon();
terminalRef.current = new Terminal({
cols,
rows,
cursorBlink: true,
fontFamily,
fontSize,
screenReaderMode: true
});
const onWindowResize = () => {
const geometry = fitAddon.proposeDimensions();
if (geometry) {
terminalRef.current.resize(geometry.rows, geometry.cols);
}
};
if (onData) {
terminalRef.current.onData(onData);
}
if (onTitleChanged) {
terminalRef.current.onTitleChange(onTitleChanged);
}
terminalRef.current.loadAddon(fitAddon);
terminalRef.current.open(ref.current);
const resizeListener = debounce(onWindowResize, 100);
if (!rows) {
if (canUseDOM) {
window.addEventListener('resize', resizeListener);
}
onWindowResize();
}
terminalRef.current.focus();
return () => {
terminalRef.current.dispose();
if (canUseDOM) {
window.removeEventListener('resize', resizeListener);
}
onFocusOut();
};
}, [cols, fontFamily, fontSize, onData, onFocusOut, onTitleChanged, rows]);
// ensure react never reuses this div by keying it with the terminal widget
// Workaround for xtermjs/xterm.js#3172
return <div ref={ref} role="list" onFocus={onFocusIn} onBlur={onFocusOut} />;
};
XTerm.displayName = 'XTerm';