diff --git a/src/renderer/components/OutputTerminal/index.tsx b/src/renderer/components/OutputTerminal/index.tsx index 61034bacde7158b6021f4f7ac4f6aae72120189d..3d345e03b0ba6eb419bf597047245619ce95cdab 100644 --- a/src/renderer/components/OutputTerminal/index.tsx +++ b/src/renderer/components/OutputTerminal/index.tsx @@ -1,11 +1,18 @@ import { Box, Sheet } from '@mui/joy'; import { FitAddon } from '@xterm/addon-fit'; import { Terminal } from '@xterm/xterm'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; + +import useSWRSubscription from 'swr/subscription'; +import type { SWRSubscriptionOptions } from 'swr/subscription'; + +const TERMINAL_SPEED = 100; //ms between adding each line (create an animation effect) export default function OutputTerminal({}) { const terminalRef = useRef(null); let term: Terminal | null = null; + let lineQueue: string[] = []; + let isProcessing = false; const fitAddon = new FitAddon(); @@ -13,31 +20,35 @@ export default function OutputTerminal({}) { fitAddon.fit(); } - useEffect(() => { - // This is hardcoded to local for now -- just building - var source = new EventSource('http://localhost:8338/server/stream_log'); - source.onmessage = function (event) { - // console.log(event.data); - // var logs = document.getElementById('logs'); - // logs.innerHTML += event.data + '<br>'; - // // Scroll to bottom - // logs.scrollTop = document.getElementById('logs').scrollHeight; + function processQueue() { + if (lineQueue.length === 0) { + isProcessing = false; + return; + } - if (term !== null) { - console.log(event.data); - const lines = JSON.parse(event.data); - console.log(lines); - lines.forEach((line: string) => { - term.writeln(line); - if (terminalRef.current) { - terminalRef.current.scrollIntoView({ behavior: 'smooth' }); - } - }); - } - window.addEventListener('resize', handleResize); - }; + isProcessing = true; + const line = lineQueue.shift()!; + term?.writeln(line.replace(/\n$/, '')); + if (terminalRef.current) { + terminalRef.current.scrollIntoView({ behavior: 'smooth' }); + } + + setTimeout(() => { + processQueue(); + }, TERMINAL_SPEED); // 100ms delay between each line + } + + function addLinesOneByOne(lines: string[]) { + lineQueue = lineQueue.concat(lines); + if (!isProcessing) { + processQueue(); + } + } - term = new Terminal(); + useEffect(() => { + term = new Terminal({ + smoothScrollDuration: 200, // Set smooth scroll duration to 200ms + }); term.loadAddon(fitAddon); if (terminalRef.current) term.open(terminalRef.current); @@ -45,22 +56,43 @@ export default function OutputTerminal({}) { window.addEventListener('resize', handleResize); + const eventSource = new EventSource( + 'http://localhost:8338/server/stream_log' + ); + eventSource.onmessage = (event) => { + if (term !== null) { + const lines = JSON.parse(event.data); + addLinesOneByOne(lines); + } + }; + eventSource.onerror = (error) => { + console.error('EventSource failed:', error); + }; + return () => { + eventSource.close(); term?.dispose(); - source.close(); window.removeEventListener('resize', handleResize); }; }); return ( - <Box sx={{ height: '100%', overflow: 'hidden' }}> + <Box + sx={{ + gridArea: 'footer', + height: '100%', + overflow: 'hidden', + border: '10px solid #444', + padding: '6px', + backgroundColor: '#000', + }} + > <Sheet sx={{ - gridArea: 'terminal', - height: '100%', overflow: 'auto', - backgroundColor: '#222', - // color: 'white', + backgroundColor: '#000', + color: '#aaa', + height: '100%', }} ref={terminalRef} ></Sheet> diff --git a/src/renderer/styles.css b/src/renderer/styles.css index b16be2ace67697739f7f6a88c01fcd9ced37878c..ee46913a4ea065b33846a7e73a79c936cb7f58e0 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -187,4 +187,12 @@ textarea { font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI"; font-size: 16px; padding: 16px; +} + +.xterm-viewport::-webkit-scrollbar { + background-color: var(--joy-palette-background-level-1); +} + +.xterm-viewport::-webkit-scrollbar-thumb { + background: var(--joy-palette-primary-400); } \ No newline at end of file