Merge pull request #939 from bbassie/feature/resizable-console

Make the Web UI console resizable
This commit is contained in:
Cooldude2606 authored and GitHub committed 2026-07-01 00:25:28 +01:00
commit eca5cdcd9e
2 files changed
+49 -4

No files matched your search

@@ -4,10 +4,14 @@ import { Select, Typography } from "antd";
import * as lib from "@clusterio/lib";
import { useAccount } from "../model/account";
import useLocalStorage from "../util/useLocalStorage";
import ControlContext from "./ControlContext";
const { Paragraph } = Typography;
const consoleHeightKey = "instance-console-height";
const defaultConsoleHeight = 300;
type Parsed = {
format: string;
type: string;
@@ -99,6 +103,36 @@ export default function LogConsole(props: LogConsoleProps) {
let anchor = useRef<any>();
let [pastLines, setPastLines] = useState([<span key={0}>{"Loading past entries..."}<br/></span>]);
let [lines, setLines] = useState<ReactElement[]>([]);
const [consoleHeight, setConsoleHeight] = useLocalStorage(consoleHeightKey, defaultConsoleHeight);
const consoleHeightRef = useRef(consoleHeight);
consoleHeightRef.current = consoleHeight;
// The console is resized with the browser's native resize handle, which owns
// the element's inline height. Driving the height from React would rewrite
// that inline style on every re-render and fight the drag (locking the size),
// so the height is only read here (on mount) and persisted (on resize), never
// written back through React. The scroll container is the <code> element
// wrapping the anchor.
useEffect(() => {
const element = anchor.current?.parentElement as HTMLElement | undefined;
if (!element) {
return undefined;
}
element.style.height = `${consoleHeightRef.current}px`;
if (typeof ResizeObserver === "undefined") {
return undefined;
}
const observer = new ResizeObserver(() => {
const next = Math.round(element.offsetHeight);
if (next && next !== consoleHeightRef.current) {
consoleHeightRef.current = next;
setConsoleHeight(next);
}
});
observer.observe(element);
return () => observer.disconnect();
}, []);
useEffect(() => {
let logFilter = {
@@ -164,6 +198,10 @@ export default function LogConsole(props: LogConsoleProps) {
return <>
<Paragraph code className="instance-console">
{/* Scrollable spacer (one console-height tall) that keeps short output
pinned to the bottom and leaves room to scroll up. Unlike a
padding-top it is scroll content, so it does not constrain resize. */}
<div className="console-spacer" key="spacer" />
{pastLines}
{lines}
<div className="scroll-anchor" key="anchor" ref={anchor} />
+11 -4
View File
@@ -174,18 +174,25 @@ body {
height: 1px;
}
/* Scroll headroom above the output so short output sits at the bottom; it is
scroll content, not box padding, so it does not block dragging smaller. */
.instance-console code .console-spacer {
height: 100%;
}
.instance-console code {
display: block;
margin: 0;
background: #000;
border-color: #252525;
/* Drag-resizable (the native handle needs the scroll overflow); height is set
imperatively in LogConsole, border-box keeps padding out of offsetHeight. */
box-sizing: border-box;
height: 300px;
min-height: 100px;
resize: vertical;
overflow-y: scroll;
white-space: pre-wrap;
/* Make sure there's at least one pixel of scroll space, this */
/* is necessary for the view to stick on the scroll anchor. */
padding-top: 300px;
}
.instance-console .factorio-time {