bitburner-src/src/ui/React/LogBoxManager.tsx

383 lines
11 KiB
TypeScript
Raw Normal View History

2021-10-01 07:00:50 +02:00
import React, { useState, useEffect, useRef } from "react";
import { EventEmitter } from "../../utils/EventEmitter";
import { RunningScript } from "../../Script/RunningScript";
import { killWorkerScript } from "../../Netscript/killWorkerScript";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
2022-07-18 08:53:01 +02:00
import Draggable, { DraggableEvent } from "react-draggable";
import { ResizableBox, ResizeCallbackData } from "react-resizable";
2021-10-07 21:02:54 +02:00
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
2021-10-05 07:23:20 +02:00
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
2021-11-17 23:33:44 +01:00
import { workerScripts } from "../../Netscript/WorkerScripts";
import { startWorkerScript } from "../../NetscriptWorker";
import { GetServer } from "../../Server/AllServers";
2021-12-21 18:00:12 +01:00
import { findRunningScript } from "../../Script/ScriptHelpers";
2022-01-13 02:56:46 +01:00
import { debounce } from "lodash";
2022-05-10 00:50:45 +02:00
import { Settings } from "../../Settings/Settings";
2022-07-14 22:34:42 +02:00
import { ANSIITypography } from "./ANSIITypography";
2022-07-18 08:53:01 +02:00
import { ScriptArg } from "../../Netscript/ScriptArg";
2021-10-15 20:16:30 +02:00
let layerCounter = 0;
2021-10-01 07:00:50 +02:00
export const LogBoxEvents = new EventEmitter<[RunningScript]>();
export const LogBoxCloserEvents = new EventEmitter<[number]>();
2021-11-27 00:47:12 +01:00
export const LogBoxClearEvents = new EventEmitter<[]>();
2021-10-01 07:00:50 +02:00
interface LogBoxUIEvent<T> {
pid: number;
data: T;
}
interface LogBoxPositionData {
x: number;
y: number;
}
export const LogBoxPositionEvents = new EventEmitter<[LogBoxUIEvent<LogBoxPositionData>]>();
interface LogBoxResizeData {
w: number;
h: number;
}
export const LogBoxSizeEvents = new EventEmitter<[LogBoxUIEvent<LogBoxResizeData>]>();
2021-10-01 07:00:50 +02:00
interface Log {
id: string;
script: RunningScript;
}
2021-11-14 01:50:39 +01:00
let logs: Log[] = [];
2021-10-01 07:00:50 +02:00
export function LogBoxManager(): React.ReactElement {
2021-11-14 01:50:39 +01:00
const setRerender = useState(true)[1];
function rerender(): void {
setRerender((o) => !o);
}
2021-10-01 07:00:50 +02:00
useEffect(
() =>
LogBoxEvents.subscribe((script: RunningScript) => {
2022-07-18 08:53:01 +02:00
const id = script.server + "-" + script.filename + script.args.map((x: ScriptArg): string => `${x}`).join("-");
if (logs.find((l) => l.id === id)) return;
logs.push({
id: id,
script: script,
});
rerender();
2021-10-01 07:00:50 +02:00
}),
[],
);
//Event used by ns.closeTail to close tail windows
useEffect(
() =>
LogBoxCloserEvents.subscribe((pid: number) => {
closePid(pid);
}),
[],
);
2021-11-27 00:47:12 +01:00
useEffect(() =>
LogBoxClearEvents.subscribe(() => {
logs = [];
rerender();
}),
);
//Close tail windows by their id
2021-10-01 07:00:50 +02:00
function close(id: string): void {
2021-11-14 01:50:39 +01:00
logs = logs.filter((l) => l.id !== id);
rerender();
2021-10-01 07:00:50 +02:00
}
//Close tail windows by their pid
function closePid(pid: number): void {
logs = logs.filter((log) => log.script.pid != pid);
rerender();
}
2021-10-01 07:00:50 +02:00
return (
<>
{logs.map((log) => (
<LogWindow key={log.id} script={log.script} id={log.id} onClose={() => close(log.id)} />
))}
</>
);
}
interface IProps {
script: RunningScript;
id: string;
onClose: () => void;
}
2022-07-19 18:19:32 +02:00
const useStyles = makeStyles(() =>
2021-10-07 21:02:54 +02:00
createStyles({
logs: {
overflowY: "scroll",
overflowX: "hidden",
scrollbarWidth: "auto",
flexDirection: "column-reverse",
2022-05-10 01:15:23 +02:00
whiteSpace: "pre-wrap",
2022-05-22 18:54:05 +02:00
wordWrap: "break-word",
2021-10-07 21:02:54 +02:00
},
2022-04-23 22:48:48 +02:00
titleButton: {
2022-05-10 00:50:45 +02:00
padding: "1px 0",
height: "100%",
2021-11-19 21:44:12 +01:00
},
2021-10-07 21:02:54 +02:00
}),
);
2022-01-08 15:59:31 +01:00
export const logBoxBaseZIndex = 1500;
2021-10-01 07:00:50 +02:00
function LogWindow(props: IProps): React.ReactElement {
2022-01-13 02:56:46 +01:00
const draggableRef = useRef<HTMLDivElement>(null);
2022-03-29 23:19:14 +02:00
const rootRef = useRef<Draggable>(null);
2021-12-21 18:00:12 +01:00
const [script, setScript] = useState(props.script);
2021-10-07 21:02:54 +02:00
const classes = useStyles();
2021-10-01 07:00:50 +02:00
const container = useRef<HTMLDivElement>(null);
const setRerender = useState(false)[1];
const [size, setSize] = useState<[number, number]>([500, 500]);
2021-12-26 23:38:25 +01:00
const [minimized, setMinimized] = useState(false);
2021-10-01 07:00:50 +02:00
function rerender(): void {
setRerender((old) => !old);
}
const onResize = (e: React.SyntheticEvent, { size }: ResizeCallbackData) => {
setSize([size.width, size.height]);
};
2022-03-29 23:19:14 +02:00
// useEffect(
// () =>
// WorkerScriptStartStopEventEmitter.subscribe(() => {
// setTimeout(() => {
// const server = GetServer(script.server);
// if (server === null) return;
// const exisitingScript = findRunningScript(script.filename, script.args, server);
// if (exisitingScript) {
// exisitingScript.logs = script.logs.concat(exisitingScript.logs)
// setScript(exisitingScript)
// }
// rerender();
// }, 100)
// }),
// [],
// );
const setPosition = ({ x, y }: LogBoxPositionData) => {
const node = rootRef?.current;
if (!node) return;
const state = node.state as { x: number; y: number };
state.x = x;
state.y = y;
};
// Listen to Logbox positioning events.
useEffect(
() =>
LogBoxPositionEvents.subscribe((e) => {
if (e.pid !== props.script.pid) return;
setPosition(e.data);
}),
[],
);
// Listen to Logbox resizing events.
useEffect(
() =>
LogBoxSizeEvents.subscribe((e) => {
if (e.pid !== props.script.pid) return;
setSize([e.data.w, e.data.h]);
}),
[],
);
// initial position if 40%/30%
useEffect(() => setPosition({ x: window.innerWidth * 0.4, y: window.innerHeight * 0.3 }), []);
2021-10-01 07:00:50 +02:00
useEffect(() => {
2021-12-03 20:12:32 +01:00
updateLayer();
2021-10-01 07:00:50 +02:00
const id = setInterval(rerender, 1000);
return () => clearInterval(id);
}, []);
function kill(): void {
2022-07-20 07:48:54 +02:00
killWorkerScript({ runningScript: script, hostname: script.server });
2021-11-17 23:33:44 +01:00
}
function run(): void {
2021-12-21 18:00:12 +01:00
const server = GetServer(script.server);
2021-11-17 23:33:44 +01:00
if (server === null) return;
2021-12-21 18:00:12 +01:00
const s = findRunningScript(script.filename, script.args, server);
if (s === null) {
2022-09-05 14:19:24 +02:00
script.ramUsage = 0;
2022-08-28 02:56:12 +02:00
startWorkerScript(script, server);
2021-12-21 18:00:12 +01:00
} else {
setScript(s);
}
2021-10-01 07:00:50 +02:00
}
2021-10-15 19:49:03 +02:00
2021-10-15 19:59:42 +02:00
function updateLayer(): void {
2021-10-15 19:49:03 +02:00
const c = container.current;
if (c === null) return;
2022-01-08 15:59:31 +01:00
c.style.zIndex = logBoxBaseZIndex + layerCounter + "";
2021-10-15 20:16:30 +02:00
layerCounter++;
2021-10-15 19:49:03 +02:00
rerender();
}
2021-10-01 07:00:50 +02:00
2021-12-26 23:33:15 +01:00
function title(full = false): string {
const maxLength = 30;
2022-07-18 08:53:01 +02:00
const t = `${script.filename} ${script.args.map((x: ScriptArg): string => `${x}`).join(" ")}`;
2021-12-26 23:33:15 +01:00
if (full || t.length <= maxLength) {
return t;
}
return t.slice(0, maxLength - 3) + "...";
}
2021-12-26 23:38:25 +01:00
function minimize(): void {
setMinimized(!minimized);
}
2022-07-14 22:34:42 +02:00
function lineColor(s: string): "error" | "success" | "warn" | "info" | "primary" {
2021-11-26 23:55:14 +01:00
if (s.match(/(^\[[^\]]+\] )?ERROR/) || s.match(/(^\[[^\]]+\] )?FAIL/)) {
2022-07-14 22:34:42 +02:00
return "error";
2021-11-19 21:44:12 +01:00
}
2021-11-26 23:55:14 +01:00
if (s.match(/(^\[[^\]]+\] )?SUCCESS/)) {
2022-07-14 22:34:42 +02:00
return "success";
2021-11-19 21:44:12 +01:00
}
2021-11-26 23:55:14 +01:00
if (s.match(/(^\[[^\]]+\] )?WARN/)) {
2022-07-14 22:34:42 +02:00
return "warn";
2021-11-19 21:44:12 +01:00
}
2021-11-26 23:55:14 +01:00
if (s.match(/(^\[[^\]]+\] )?INFO/)) {
2022-07-14 22:34:42 +02:00
return "info";
2021-11-19 21:44:12 +01:00
}
2022-07-14 22:34:42 +02:00
return "primary";
2021-11-19 21:44:12 +01:00
}
2022-01-13 02:56:46 +01:00
// And trigger fakeDrag when the window is resized
useEffect(() => {
window.addEventListener("resize", onWindowResize);
2022-01-13 02:56:46 +01:00
return () => {
window.removeEventListener("resize", onWindowResize);
2022-01-13 02:56:46 +01:00
};
}, []);
const onWindowResize = debounce((): void => {
2022-01-13 02:56:46 +01:00
const node = draggableRef?.current;
if (!node) return;
if (!isOnScreen(node)) {
setPosition({ x: 0, y: 0 });
}
2022-01-13 02:56:46 +01:00
}, 100);
const isOnScreen = (node: HTMLDivElement): boolean => {
const bounds = node.getBoundingClientRect();
2022-03-29 23:19:14 +02:00
return !(bounds.right < 0 || bounds.bottom < 0 || bounds.left > innerWidth || bounds.top > outerWidth);
};
2022-07-18 08:53:01 +02:00
const boundToBody = (e: DraggableEvent): void | false => {
if (
e instanceof MouseEvent &&
(e.clientX < 0 || e.clientY < 0 || e.clientX > innerWidth || e.clientY > innerHeight)
)
return false;
2022-03-29 23:19:14 +02:00
};
2022-01-13 02:56:46 +01:00
2022-05-10 01:15:23 +02:00
// Max [width, height]
const minConstraints: [number, number] = [250, 33];
2021-10-01 07:00:50 +02:00
return (
2022-05-10 01:15:23 +02:00
<Draggable handle=".drag" onDrag={boundToBody} ref={rootRef} onMouseDown={updateLayer}>
<Box
display="flex"
sx={{
2021-10-05 06:59:40 +02:00
flexFlow: "column",
position: "fixed",
2021-10-05 07:30:37 +02:00
zIndex: 1400,
2022-05-10 01:15:23 +02:00
minWidth: `${minConstraints[0]}px`,
minHeight: `${minConstraints[1]}px`,
...(minimized
? {
2022-05-17 22:25:27 +02:00
border: "none",
margin: 0,
maxHeight: 0,
padding: 0,
}
2022-05-10 01:15:23 +02:00
: {
2022-05-17 22:25:27 +02:00
border: `1px solid ${Settings.theme.welllight}`,
}),
2021-10-01 07:00:50 +02:00
}}
2021-10-05 06:59:40 +02:00
ref={container}
2021-10-01 07:00:50 +02:00
>
2022-05-10 01:15:23 +02:00
<ResizableBox
2022-08-31 18:26:02 +02:00
width={size[0]}
height={size[1]}
onResize={onResize}
2022-05-10 01:15:23 +02:00
minConstraints={minConstraints}
handle={
<span
style={{
position: "absolute",
right: "-10px",
2022-05-10 02:26:20 +02:00
bottom: "-16px",
2022-05-10 01:15:23 +02:00
cursor: "nw-resize",
display: minimized ? "none" : "inline-block",
}}
>
<ArrowForwardIosIcon color="primary" style={{ transform: "rotate(45deg)", fontSize: "1.75rem" }} />
</span>
}
>
<>
<Paper className="drag" sx={{ display: "flex", alignItems: "center", cursor: "grab" }} ref={draggableRef}>
<Typography
variant="h6"
sx={{ marginRight: "auto", textOverflow: "ellipsis", whiteSpace: "nowrap", overflow: "hidden" }}
title={title(true)}
>
{title(true)}
2021-10-15 19:59:42 +02:00
</Typography>
2022-05-10 01:15:23 +02:00
<span style={{ minWidth: "fit-content", height: `${minConstraints[1]}px` }}>
{!workerScripts.has(script.pid) ? (
<Button className={classes.titleButton} onClick={run} onTouchEnd={run}>
Run
</Button>
) : (
<Button className={classes.titleButton} onClick={kill} onTouchEnd={kill}>
Kill
</Button>
)}
<Button className={classes.titleButton} onClick={minimize} onTouchEnd={minimize}>
{minimized ? "\u{1F5D6}" : "\u{1F5D5}"}
2022-04-17 23:40:37 +02:00
</Button>
2022-05-10 01:15:23 +02:00
<Button className={classes.titleButton} onClick={props.onClose} onTouchEnd={props.onClose}>
Close
2022-04-17 23:40:37 +02:00
</Button>
2022-05-10 01:15:23 +02:00
</span>
</Paper>
<Paper
2021-10-15 19:59:42 +02:00
className={classes.logs}
2022-05-10 01:15:23 +02:00
sx={{ height: `calc(100% - ${minConstraints[1]}px)`, display: minimized ? "none" : "flex" }}
2021-10-15 19:59:42 +02:00
>
2022-05-10 01:15:23 +02:00
<span style={{ display: "flex", flexDirection: "column" }}>
2021-12-21 18:00:12 +01:00
{script.logs.map(
2021-10-15 19:59:42 +02:00
(line: string, i: number): JSX.Element => (
<ANSIITypography key={i} text={line} color={lineColor(line)} />
2021-10-15 19:59:42 +02:00
),
)}
2022-05-10 01:15:23 +02:00
</span>
</Paper>
</>
</ResizableBox>
</Box>
2021-10-05 06:59:40 +02:00
</Draggable>
2021-10-01 07:00:50 +02:00
);
}