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 ? (
+ <>>
+ ) : (
+
+ )}
);
}