Created
December 1, 2023 11:34
-
-
Save mastersign/90d0ab06f040092e4ca27a3b59820cb9 to your computer and use it in GitHub Desktop.
XTermJS for React 18
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect, useRef } from 'react' | |
import PropTypes from 'prop-types' | |
import { Terminal } from 'xterm' | |
import { Unicode11Addon } from 'xterm-addon-unicode11' | |
import { CanvasAddon } from 'xterm-addon-canvas' | |
import { WebglAddon } from 'xterm-addon-webgl'; | |
import { WebLinksAddon } from 'xterm-addon-web-links' | |
import FontFaceObserver from 'fontfaceobserver' | |
import 'xterm/css/xterm.css' | |
const isWebGl2Supported = !!document.createElement('canvas').getContext('webgl2') | |
async function waitForWebFontAsync(term) { | |
const fontFamily = term.options.fontFamily | |
if (!fontFamily) return | |
const regular = new FontFaceObserver(fontFamily).load() | |
//const bold = new FontFaceObserver(fontFamily, { weight: 'bold' }).load() | |
try { | |
await regular | |
//await bold | |
} catch { | |
// loading the font failed, fallback to browser default | |
term.options.fontFamily = "monospace" | |
} | |
} | |
function useBind(termRef, handler, eventName) { | |
useEffect(() => { | |
if (!termRef.current || typeof handler !== 'function') return | |
// console.log('Bind', eventName) | |
const term = termRef.current | |
const eventBinding = term[eventName](handler) | |
return () => { | |
if (!eventBinding) return | |
// console.log('Unbind', eventName) | |
eventBinding.dispose() | |
} | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, [handler]) | |
} | |
export function XTerm({ | |
id, className, | |
options, | |
onBell, onBinary, onCursorMove, onData, onKey, onLineFeed, onRender, onResize, onScroll, onSelectionChange, onTitleChange, onWriteParsed, | |
onInit, onDispose, | |
}) { | |
const termRef = useRef(null) | |
const divRef = useRef(null) | |
useEffect(() => { | |
console.log('Mount XTerm') | |
const term = new Terminal({ ...options, allowProposedApi: true }) | |
term.loadAddon(new Unicode11Addon()) | |
term.unicode.activeVersion = '11' | |
term.loadAddon(new WebLinksAddon()) | |
if (isWebGl2Supported) { | |
const webGl2Addon = new WebglAddon(); | |
webGl2Addon.onContextLoss(() => { | |
webGl2Addon.dispose(); | |
}); | |
term.loadAddon(webGl2Addon); | |
} else { | |
term.loadAddon(new CanvasAddon()) | |
} | |
termRef.current = term | |
waitForWebFontAsync(term).then(() => { | |
if (termRef.current !== term) { | |
// component was disposed while loading web fonts | |
return | |
} | |
term.open(divRef.current) | |
}) | |
return () => { | |
console.log('Unmount XTerm') | |
if (typeof onDispose === 'function') onDispose(term) | |
try { | |
term.dispose() | |
} catch (ex) { | |
console.warn('An error occurred during disposal of the XTermJS') | |
} | |
termRef.current = null | |
} | |
}, [options, onDispose]) | |
useBind(termRef, onBell, 'onBell') | |
useBind(termRef, onBinary, 'onBinary') | |
useBind(termRef, onCursorMove, 'onCursorMove') | |
useBind(termRef, onData, 'onData') | |
useBind(termRef, onKey, 'onKey') | |
useBind(termRef, onLineFeed, 'onLineFeed') | |
useBind(termRef, onRender, 'onRender') | |
useBind(termRef, onResize, 'onResize') | |
useBind(termRef, onScroll, 'onScroll') | |
useBind(termRef, onSelectionChange, 'onSelectionChange') | |
useBind(termRef, onTitleChange, 'onTitleChange') | |
useBind(termRef, onWriteParsed, 'onWriteParsed') | |
useEffect(() => { | |
if (!termRef.current || typeof onInit !== 'function') return | |
console.log('onInit') | |
onInit(termRef.current) | |
}, [options, onInit]) | |
return <div id={id} className={className} ref={divRef}></div> | |
} | |
XTerm.propTypes = { | |
id: PropTypes.string, | |
className: PropTypes.string, | |
options: PropTypes.object, | |
onBell: PropTypes.func, | |
onBinary: PropTypes.func, | |
onCursorMove: PropTypes.func, | |
onData: PropTypes.func, | |
onKey: PropTypes.func, | |
onLineFeed: PropTypes.func, | |
onRender: PropTypes.func, | |
onResize: PropTypes.func, | |
onScroll: PropTypes.func, | |
onSelectionChange: PropTypes.func, | |
onTitleChange: PropTypes.func, | |
onWriteParsed: PropTypes.func, | |
onInit: PropTypes.func, | |
onDispose: PropTypes.func, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment