mirror of
https://github.com/stackblitz/bolt.new
synced 2024-11-30 23:56:07 +00:00
84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
|
import { FitAddon } from '@xterm/addon-fit';
|
||
|
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||
|
import { Terminal as XTerm } from '@xterm/xterm';
|
||
|
import { forwardRef, memo, useEffect, useImperativeHandle, useRef } from 'react';
|
||
|
import type { Theme } from '~/lib/stores/theme';
|
||
|
import { getTerminalTheme } from './theme';
|
||
|
|
||
|
import '@xterm/xterm/css/xterm.css';
|
||
|
|
||
|
export interface TerminalRef {
|
||
|
reloadStyles: () => void;
|
||
|
}
|
||
|
|
||
|
export interface TerminalProps {
|
||
|
className?: string;
|
||
|
theme: Theme;
|
||
|
readonly?: boolean;
|
||
|
onTerminalReady?: (terminal: XTerm) => void;
|
||
|
onTerminalResize?: (cols: number, rows: number) => void;
|
||
|
}
|
||
|
|
||
|
export const Terminal = memo(
|
||
|
forwardRef<TerminalRef, TerminalProps>(({ className, theme, readonly, onTerminalReady, onTerminalResize }, ref) => {
|
||
|
const terminalElementRef = useRef<HTMLDivElement>(null);
|
||
|
const terminalRef = useRef<XTerm>();
|
||
|
|
||
|
useEffect(() => {
|
||
|
const element = terminalElementRef.current!;
|
||
|
|
||
|
const fitAddon = new FitAddon();
|
||
|
const webLinksAddon = new WebLinksAddon();
|
||
|
|
||
|
const terminal = new XTerm({
|
||
|
cursorBlink: true,
|
||
|
convertEol: true,
|
||
|
disableStdin: readonly,
|
||
|
theme: getTerminalTheme(readonly ? { cursor: '#00000000' } : {}),
|
||
|
fontSize: 13,
|
||
|
fontFamily: 'Menlo, courier-new, courier, monospace',
|
||
|
});
|
||
|
|
||
|
terminalRef.current = terminal;
|
||
|
|
||
|
terminal.loadAddon(fitAddon);
|
||
|
terminal.loadAddon(webLinksAddon);
|
||
|
terminal.open(element);
|
||
|
|
||
|
const resizeObserver = new ResizeObserver(() => {
|
||
|
fitAddon.fit();
|
||
|
onTerminalResize?.(terminal.cols, terminal.rows);
|
||
|
});
|
||
|
|
||
|
resizeObserver.observe(element);
|
||
|
|
||
|
onTerminalReady?.(terminal);
|
||
|
|
||
|
return () => {
|
||
|
resizeObserver.disconnect();
|
||
|
terminal.dispose();
|
||
|
};
|
||
|
}, []);
|
||
|
|
||
|
useEffect(() => {
|
||
|
const terminal = terminalRef.current!;
|
||
|
|
||
|
// we render a transparent cursor in case the terminal is readonly
|
||
|
terminal.options.theme = getTerminalTheme(readonly ? { cursor: '#00000000' } : {});
|
||
|
|
||
|
terminal.options.disableStdin = readonly;
|
||
|
}, [theme, readonly]);
|
||
|
|
||
|
useImperativeHandle(ref, () => {
|
||
|
return {
|
||
|
reloadStyles: () => {
|
||
|
const terminal = terminalRef.current!;
|
||
|
terminal.options.theme = getTerminalTheme(readonly ? { cursor: '#00000000' } : {});
|
||
|
},
|
||
|
};
|
||
|
}, []);
|
||
|
|
||
|
return <div className={className} ref={terminalElementRef} />;
|
||
|
}),
|
||
|
);
|