import { FitAddon } from '@xterm/addon-fit' import { WebLinksAddon } from '@xterm/addon-web-links' import { WebglAddon } from '@xterm/addon-webgl' import { Terminal } from '@xterm/xterm' import '@xterm/xterm/css/xterm.css' import clsx from 'clsx' import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react' import { useTheme } from '@/hooks/use-theme' import { gradientText } from '@/utils/terminal' export type XTermRef = { write: ( ...args: Parameters ) => ReturnType writeAsync: (data: Parameters[0]) => Promise writeln: ( ...args: Parameters ) => ReturnType writelnAsync: (data: Parameters[0]) => Promise clear: () => void } const XTerm = forwardRef>( (props, ref) => { const domRef = useRef(null) const terminalRef = useRef(null) const { className, ...rest } = props const { theme } = useTheme() useEffect(() => { if (!domRef.current) { return } const terminal = new Terminal({ allowTransparency: true, fontFamily: '"Fira Code", "Harmony", "Noto Serif SC", monospace' }) terminalRef.current = terminal const fitAddon = new FitAddon() terminal.loadAddon(new WebLinksAddon()) terminal.loadAddon(fitAddon) terminal.loadAddon(new WebglAddon()) terminal.open(domRef.current) fitAddon.fit() terminal.writeln( gradientText( 'Welcome to NapCat WebUI', [255, 0, 0], [0, 255, 0], true, true, true ) ) const resizeObserver = new ResizeObserver(() => { fitAddon.fit() }) resizeObserver.observe(domRef.current) const handleFontLoad = () => { terminal.refresh(0, terminal.rows - 1) } document.fonts.addEventListener('loadingdone', handleFontLoad) return () => { resizeObserver.disconnect() document.fonts.removeEventListener('loadingdone', handleFontLoad) setTimeout(() => { terminal.dispose() }, 0) } }, []) useEffect(() => { if (terminalRef.current) { terminalRef.current.options.theme = { background: theme === 'dark' ? 'rgba(0, 0, 0, 0)' : 'rgba(255, 255, 255, 0)', foreground: theme === 'dark' ? '#fff' : '#000', selectionBackground: theme === 'dark' ? '#666' : '#ddd', cursor: theme === 'dark' ? '#fff' : '#000', cursorAccent: theme === 'dark' ? '#000' : '#fff', black: theme === 'dark' ? '#fff' : '#000' } } }, [theme]) useImperativeHandle( ref, () => ({ write: (...args) => { return terminalRef.current?.write(...args) }, writeAsync: async (data) => { return new Promise((resolve) => { terminalRef.current?.write(data, resolve) }) }, writeln: (...args) => { return terminalRef.current?.writeln(...args) }, writelnAsync: async (data) => { return new Promise((resolve) => { terminalRef.current?.writeln(data, resolve) }) }, clear: () => { terminalRef.current?.clear() } }), [] ) return (
) } ) export default XTerm