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

282 lines
7.9 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";
2021-10-05 06:59:40 +02:00
import Draggable from "react-draggable";
2021-10-05 07:23:20 +02:00
import { ResizableBox } 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-11-19 21:44:12 +01:00
import { Theme } from "@mui/material";
2021-12-21 18:00:12 +01:00
import { findRunningScript } from "../../Script/ScriptHelpers";
2022-01-05 01:09:34 +01:00
import { Player } from "../../Player";
2022-01-13 02:56:46 +01:00
import { debounce } from "lodash";
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]>();
2021-11-27 00:47:12 +01:00
export const LogBoxClearEvents = new EventEmitter<[]>();
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) => {
const id = script.server + "-" + script.filename + script.args.map((x: any): string => `${x}`).join("-");
2022-01-08 21:31:58 +01:00
if (logs.find((l) => l.id === id)) close(id);
Promise.resolve().then(() => {
logs.push({
id: id,
script: script,
});
rerender();
})
2021-10-01 07:00:50 +02:00
}),
[],
);
2021-11-27 00:47:12 +01:00
useEffect(() =>
LogBoxClearEvents.subscribe(() => {
logs = [];
rerender();
}),
);
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
}
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;
}
2021-11-19 21:44:12 +01:00
const useStyles = makeStyles((theme: Theme) =>
2021-10-07 21:02:54 +02:00
createStyles({
2021-12-26 23:38:25 +01:00
title: {
"&.is-minimized + *": {
2022-01-05 01:09:34 +01:00
border: "none",
margin: 0,
"max-height": 0,
padding: 0,
"pointer-events": "none",
visibility: "hidden",
2021-12-26 23:38:25 +01:00
},
},
2021-10-07 21:02:54 +02:00
logs: {
overflowY: "scroll",
overflowX: "hidden",
scrollbarWidth: "auto",
display: "flex",
flexDirection: "column-reverse",
},
2021-11-19 21:44:12 +01:00
success: {
color: theme.colors.success,
},
error: {
color: theme.palette.error.main,
},
primary: {
color: theme.palette.primary.main,
},
info: {
color: theme.palette.info.main,
},
warning: {
color: theme.palette.warning.main,
},
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);
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];
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);
}
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 {
2021-12-21 18:00:12 +01:00
killWorkerScript(script, script.server, true);
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-01-05 01:09:34 +01:00
startWorkerScript(Player, 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;
2021-12-21 18:00:12 +01:00
const t = `${script.filename} ${script.args.map((x: any): 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);
}
2021-11-19 21:44:12 +01:00
function lineClass(s: string): string {
2021-11-26 23:55:14 +01:00
if (s.match(/(^\[[^\]]+\] )?ERROR/) || s.match(/(^\[[^\]]+\] )?FAIL/)) {
2021-11-19 21:44:12 +01:00
return classes.error;
}
2021-11-26 23:55:14 +01:00
if (s.match(/(^\[[^\]]+\] )?SUCCESS/)) {
2021-11-19 21:44:12 +01:00
return classes.success;
}
2021-11-26 23:55:14 +01:00
if (s.match(/(^\[[^\]]+\] )?WARN/)) {
2021-11-19 21:44:12 +01:00
return classes.warning;
}
2021-11-26 23:55:14 +01:00
if (s.match(/(^\[[^\]]+\] )?INFO/)) {
2021-11-19 21:44:12 +01:00
return classes.info;
}
return classes.primary;
}
2022-01-13 02:56:46 +01:00
// Trigger fakeDrag once to make sure loaded data is not outside bounds
useEffect(() => fakeDrag(), []);
// And trigger fakeDrag when the window is resized
useEffect(() => {
window.addEventListener("resize", fakeDrag);
return () => {
window.removeEventListener("resize", fakeDrag);
};
}, []);
const fakeDrag = debounce((): void => {
const node = draggableRef?.current;
if (!node) return;
// No official way to trigger an onChange to recompute the bounds
// See: https://github.com/react-grid-layout/react-draggable/issues/363#issuecomment-947751127
triggerMouseEvent(node, "mouseover");
triggerMouseEvent(node, "mousedown");
triggerMouseEvent(document, "mousemove");
triggerMouseEvent(node, "mouseup");
triggerMouseEvent(node, "click");
}, 100);
const triggerMouseEvent = (node: HTMLDivElement | Document, eventType: string): void => {
const clickEvent = document.createEvent("MouseEvents");
clickEvent.initEvent(eventType, true, true);
node.dispatchEvent(clickEvent);
};
2021-10-01 07:00:50 +02:00
return (
2022-01-13 02:56:46 +01:00
<Draggable handle=".drag" bounds="body">
2021-10-01 07:00:50 +02:00
<Paper
style={{
2021-10-05 06:59:40 +02:00
display: "flex",
flexFlow: "column",
position: "fixed",
2021-10-05 07:30:37 +02:00
left: "40%",
top: "30%",
zIndex: 1400,
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
>
2021-10-15 19:59:42 +02:00
<div onMouseDown={updateLayer}>
<Paper
2022-01-05 01:09:34 +01:00
className={classes.title + " " + (minimized ? "is-minimized" : "")}
2021-10-15 19:59:42 +02:00
style={{
cursor: "grab",
}}
2021-10-05 07:23:20 +02:00
>
2022-01-13 02:56:46 +01:00
<Box className="drag" display="flex" alignItems="center" ref={draggableRef}>
2021-12-26 23:33:15 +01:00
<Typography color="primary" variant="h6" title={title(true)}>
{title()}
2021-10-15 19:59:42 +02:00
</Typography>
<Box position="absolute" right={0}>
2021-12-21 18:00:12 +01:00
{!workerScripts.has(script.pid) && <Button onClick={run}>Run</Button>}
{workerScripts.has(script.pid) && <Button onClick={kill}>Kill</Button>}
2021-12-26 23:38:25 +01:00
<Button onClick={minimize}>{minimized ? "\u{1F5D6}" : "\u{1F5D5}"}</Button>
2021-10-15 19:59:42 +02:00
<Button onClick={props.onClose}>Close</Button>
</Box>
2021-10-05 07:23:20 +02:00
</Box>
2021-10-15 19:59:42 +02:00
</Paper>
2021-11-17 23:50:02 +01:00
<Paper sx={{ overflow: "scroll", overflowWrap: "break-word", whiteSpace: "pre-wrap" }}>
2021-10-15 19:59:42 +02:00
<ResizableBox
className={classes.logs}
height={500}
width={500}
handle={
<span style={{ position: "absolute", right: "-10px", bottom: "-13px", cursor: "nw-resize" }}>
<ArrowForwardIosIcon color="primary" style={{ transform: "rotate(45deg)" }} />
</span>
}
>
<Box>
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 => (
2021-11-19 21:44:12 +01:00
<Typography key={i} className={lineClass(line)}>
2021-10-15 19:59:42 +02:00
{line}
<br />
</Typography>
),
)}
</Box>
</ResizableBox>
</Paper>
</div>
2021-10-01 07:00:50 +02:00
</Paper>
2021-10-05 06:59:40 +02:00
</Draggable>
2021-10-01 07:00:50 +02:00
);
}