diff --git a/page/src/App.css b/page/src/App.css index cce7d5bb..90760041 100644 --- a/page/src/App.css +++ b/page/src/App.css @@ -136,3 +136,9 @@ button.fancy.bg-secondary:hover { .terminal-dark-gray { color: rgb(81, 81, 81); } + +.terminal-glass { + box-shadow: 0px 0px 15px 4px rgba(255, 255, 255, 0.04); + backdrop-filter: blur(6px) brightness(0.8) saturate(1.3); + background: transparent; +} diff --git a/page/src/components/emulation-summary.css b/page/src/components/emulation-summary.css deleted file mode 100644 index e11681b5..00000000 --- a/page/src/components/emulation-summary.css +++ /dev/null @@ -1,5 +0,0 @@ -.emulation-summary { - box-shadow: 0px 0px 15px 4px rgba(255, 255, 255, 0.04); - /*border: 1px solid rgba(255, 255, 255, 0.2);*/ - backdrop-filter: blur(7px) brightness(0.8) saturate(1.3); -} diff --git a/page/src/components/emulation-summary.tsx b/page/src/components/emulation-summary.tsx index 5cac2329..8a233c3b 100644 --- a/page/src/components/emulation-summary.tsx +++ b/page/src/components/emulation-summary.tsx @@ -2,8 +2,6 @@ import { EmulationStatus } from "@/emulator"; import { TextTooltip } from "./text-tooltip"; import { BarChartSteps, CpuFill, FloppyFill } from "react-bootstrap-icons"; -import "./emulation-summary.css"; - export interface EmulationSummaryProps { status?: EmulationStatus; } @@ -28,7 +26,7 @@ export function EmulationSummary(props: EmulationSummaryProps) { } return ( -
+
{props.status.activeThreads} diff --git a/page/src/components/output.tsx b/page/src/components/output.tsx index 4d778162..32ba45b8 100644 --- a/page/src/components/output.tsx +++ b/page/src/components/output.tsx @@ -1,10 +1,7 @@ import React from "react"; -import { - List, - ListImperativeAPI, - useListRef, - type RowComponentProps, -} from "react-window"; +import { List, ListImperativeAPI, type RowComponentProps } from "react-window"; +import { ArrowDown } from "react-bootstrap-icons"; +import { Button } from "./ui/button"; interface OutputProps {} @@ -26,6 +23,7 @@ interface FullOutputState extends OutputState { height: number; width: number; state: SizeState; + autoScroll: boolean; } interface LogLine { @@ -182,6 +180,7 @@ export class Output extends React.Component { private outputRef: React.RefObject; private listRef: React.RefObject; private resizeObserver: ResizeObserver; + private scrollElement: HTMLDivElement | null | undefined; constructor(props: OutputProps) { super(props); @@ -189,6 +188,7 @@ export class Output extends React.Component { this.clear = this.clear.bind(this); this.logLine = this.logLine.bind(this); this.logLines = this.logLines.bind(this); + this.handleScroll = this.handleScroll.bind(this); this.updateDimensions = this.updateDimensions.bind(this); this.outputRef = React.createRef(); @@ -202,6 +202,7 @@ export class Output extends React.Component { height: 10, width: 10, state: SizeState.Final, + autoScroll: true, }; this.state.grouper.handler = (lines: string[]) => { @@ -209,28 +210,65 @@ export class Output extends React.Component { }; } + handleScroll(e: Event) { + const threshold = 40; + const element = e.target as HTMLElement; + const { scrollTop, scrollHeight, clientHeight } = element; + const isAtEnd = scrollTop + clientHeight >= scrollHeight - threshold; + + this.setState({ autoScroll: isAtEnd }); + } + + unregisterScrollListener() { + this.scrollElement?.removeEventListener("scroll", this.handleScroll); + } + + registerScrollListener(element: HTMLDivElement | null | undefined) { + if (element == this.scrollElement) { + return; + } + + this.unregisterScrollListener(); + this.scrollElement = element; + element?.addEventListener("scroll", this.handleScroll); + } + + registerScrollOnList() { + this.registerScrollListener(this.listRef.current?.element); + } + componentDidMount() { this.updateDimensions(); if (this.outputRef.current) { this.resizeObserver.observe(this.outputRef.current); } + + this.registerScrollOnList(); } componentWillUnmount() { this.resizeObserver.disconnect(); + this.unregisterScrollListener(); + } + + scrollListToEnd() { + if (this.listRef.current && this.state.lines.length > 0) { + this.listRef.current.scrollToRow({ index: this.state.lines.length - 1 }); + } + + this.setState({ autoScroll: true }); } componentDidUpdate(_: OutputProps, prevState: FullOutputState) { - if ( - !this.listRef.current || - this.state.lines.length == 0 || - prevState.lines.length == this.state.lines.length - ) { - return; - } + this.registerScrollOnList(); - this.listRef.current.scrollToRow({ index: this.state.lines.length - 1 }); + if ( + this.state.autoScroll && + prevState.lines.length != this.state.lines.length + ) { + this.scrollListToEnd(); + } } clear() { @@ -292,6 +330,20 @@ export class Output extends React.Component { rowHeight={20} style={{ height: this.state.height, width: this.state.width }} /> + + {this.state.autoScroll ? ( + <> + ) : ( + + )}
); }