blob: 4f542f1805e61b0d1d6527dfb7330daa58301cf2 [file] [log] [blame]
import { useEffect, useRef } from "react";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import "xterm/css/xterm.css";
interface XTermProps {
logs: string;
}
const scrollbarHack = `
.xterm-viewport {
scrollbar-width: auto;
scrollbar-color: #a0a0a0 #f1f5f9;
}
.xterm-viewport::-webkit-scrollbar {
width: 8px;
}
.xterm-viewport::-webkit-scrollbar-track {
background: #f1f5f9;
}
.xterm-viewport::-webkit-scrollbar-thumb {
background-color: #a0a0a0;
border-radius: 4px;
border: 2px solid #f1f5f9;
}
`;
export function XTerm({ logs }: XTermProps) {
const termRef = useRef<HTMLDivElement>(null);
const termInstance = useRef<Terminal | null>(null);
const fitAddon = useRef<FitAddon | null>(null);
const prevLogs = useRef<string>("");
useEffect(() => {
if (termRef.current && !termInstance.current) {
const term = new Terminal({
disableStdin: true,
convertEol: true,
fontFamily: `'JetBrains Mono', monospace`,
fontSize: 12,
theme: {
background: "#f1f5f9", // bg-muted color
foreground: "#020817", // text-foreground color
cursor: "#f1f5f9",
selectionBackground: "#bfdbfe", // blue-200
selectionInactiveBackground: "#e2e8f0", // slate-200
},
});
const addon = new FitAddon();
fitAddon.current = addon;
term.loadAddon(addon);
term.open(termRef.current);
addon.fit();
termInstance.current = term;
const resizeObserver = new ResizeObserver(() => {
fitAddon.current?.fit();
});
resizeObserver.observe(termRef.current);
return () => {
resizeObserver.disconnect();
term.dispose();
};
}
}, []);
useEffect(() => {
if (termInstance.current) {
if (logs === "") {
termInstance.current.clear();
prevLogs.current = "";
return;
}
const buffer = termInstance.current.buffer.active;
const wasAtBottom = buffer.viewportY + termInstance.current.rows >= buffer.length;
const newLogContent = logs.substring(prevLogs.current.length);
prevLogs.current = logs;
if (newLogContent) {
termInstance.current.write(newLogContent, () => {
if (wasAtBottom) {
termInstance.current?.scrollToBottom();
}
});
}
}
}, [logs]);
return (
<>
<style>{scrollbarHack}</style>
<div ref={termRef} className="h-full w-full" />
</>
);
}