mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-09 17:23:53 +01:00
parent
187226a30f
commit
9697a82e0c
@ -239,9 +239,12 @@ export function refreshTheme(): void {
|
||||
},
|
||||
MuiIconButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
root: ({ ownerState }) => ({
|
||||
color: Settings.theme.primary,
|
||||
},
|
||||
...(ownerState.disableRipple && {
|
||||
p: 0,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiTooltip: {
|
||||
|
@ -1,21 +1,68 @@
|
||||
/**
|
||||
* Root React Component for the "Active Scripts" UI page. This page displays
|
||||
* and provides information about all of the player's scripts that are currently running
|
||||
*/
|
||||
import React from "react";
|
||||
import type { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { MenuItem, Typography, Select, SelectChangeEvent, TextField, IconButton, List } from "@mui/material";
|
||||
import { FirstPage, KeyboardArrowLeft, KeyboardArrowRight, LastPage, Search } from "@mui/icons-material";
|
||||
|
||||
import { ScriptProduction } from "./ScriptProduction";
|
||||
import { ServerAccordions } from "./ServerAccordions";
|
||||
import { ServerAccordion } from "./ServerAccordion";
|
||||
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { workerScripts } from "../../Netscript/WorkerScripts";
|
||||
import { getRecordEntries } from "../../Types/Record";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { isPositiveInteger } from "../../types";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
export function ActiveScriptsPage(): React.ReactElement {
|
||||
const [scriptsPerPage, setScriptsPerPage] = useState(Settings.ActiveScriptsScriptPageSize);
|
||||
const [serversPerPage, setServersPerPage] = useState(Settings.ActiveScriptsServerPageSize);
|
||||
const [filter, setFilter] = useState("");
|
||||
const [page, setPage] = useState(0);
|
||||
|
||||
interface IProps {
|
||||
workerScripts: Map<number, WorkerScript>;
|
||||
}
|
||||
function changeScriptsPerPage(e: SelectChangeEvent<number>) {
|
||||
const n = parseInt(e.target.value as string);
|
||||
if (!isPositiveInteger(n)) return;
|
||||
Settings.ActiveScriptsScriptPageSize = n;
|
||||
setScriptsPerPage(n);
|
||||
}
|
||||
function changeServersPerPage(e: SelectChangeEvent<number>) {
|
||||
const n = parseInt(e.target.value as string);
|
||||
if (!isPositiveInteger(n)) return;
|
||||
Settings.ActiveScriptsServerPageSize = n;
|
||||
setServersPerPage(n);
|
||||
}
|
||||
|
||||
const serverData: [string, WorkerScript[]][] = (() => {
|
||||
const tempData: Record<string, WorkerScript[]> = {};
|
||||
if (filter) {
|
||||
// Only check filtering if a filter exists (performance)
|
||||
for (const ws of workerScripts.values()) {
|
||||
if (!ws.hostname.includes(filter) && !ws.scriptRef.filename.includes(filter)) continue;
|
||||
const hostname = ws.hostname;
|
||||
if (tempData[hostname]) tempData[hostname].push(ws);
|
||||
else tempData[hostname] = [ws];
|
||||
}
|
||||
} else {
|
||||
for (const ws of workerScripts.values()) {
|
||||
const hostname = ws.hostname;
|
||||
if (tempData[hostname]) tempData[hostname].push(ws);
|
||||
else tempData[hostname] = [ws];
|
||||
}
|
||||
}
|
||||
return getRecordEntries(tempData);
|
||||
})();
|
||||
|
||||
const lastPage = Math.max(Math.ceil(serverData.length / serversPerPage) - 1, 0);
|
||||
function changePage(n: number) {
|
||||
if (!Number.isInteger(n) || n > lastPage || n < 0) return;
|
||||
setPage(n);
|
||||
}
|
||||
if (page > lastPage) changePage(lastPage);
|
||||
|
||||
const adjustedIndex = page * serversPerPage;
|
||||
const dataToShow = serverData.slice(adjustedIndex, adjustedIndex + serversPerPage);
|
||||
const firstServerNumber = serverData.length === 0 ? 0 : adjustedIndex + 1;
|
||||
const lastServerNumber = serverData.length === 0 ? 0 : adjustedIndex + dataToShow.length;
|
||||
|
||||
export function ActiveScriptsPage(props: IProps): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
@ -25,7 +72,50 @@ export function ActiveScriptsPage(props: IProps): React.ReactElement {
|
||||
</Typography>
|
||||
|
||||
<ScriptProduction />
|
||||
<ServerAccordions {...props} />
|
||||
<div style={{ width: "100%", display: "flex", alignItems: "center" }}>
|
||||
<TextField
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
autoFocus
|
||||
InputProps={{ startAdornment: <Search />, spellCheck: false }}
|
||||
size="small"
|
||||
/>
|
||||
<Typography marginLeft="1em">Servers/page:</Typography>
|
||||
<Select value={serversPerPage} onChange={changeServersPerPage}>
|
||||
<MenuItem value={10}>10</MenuItem>
|
||||
<MenuItem value={15}>15</MenuItem>
|
||||
<MenuItem value={20}>20</MenuItem>
|
||||
<MenuItem value={100}>100</MenuItem>
|
||||
</Select>
|
||||
<Typography marginLeft="1em">Scripts/page:</Typography>
|
||||
<Select value={scriptsPerPage} onChange={changeScriptsPerPage}>
|
||||
<MenuItem value={10}>10</MenuItem>
|
||||
<MenuItem value={15}>15</MenuItem>
|
||||
<MenuItem value={20}>20</MenuItem>
|
||||
<MenuItem value={100}>100</MenuItem>
|
||||
</Select>
|
||||
<Typography
|
||||
marginLeft="auto"
|
||||
marginRight="1em"
|
||||
>{`${firstServerNumber}-${lastServerNumber} of ${serverData.length}`}</Typography>
|
||||
<IconButton onClick={() => changePage(0)} disabled={page === 0}>
|
||||
<FirstPage />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => changePage(page - 1)} disabled={page === 0}>
|
||||
<KeyboardArrowLeft />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => changePage(page + 1)} disabled={page === lastPage}>
|
||||
<KeyboardArrowRight />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => changePage(lastPage)} disabled={page === lastPage}>
|
||||
<LastPage />
|
||||
</IconButton>
|
||||
</div>
|
||||
<List dense={true}>
|
||||
{dataToShow.map(([hostname, scripts]) => (
|
||||
<ServerAccordion key={hostname} hostname={hostname} scripts={scripts} />
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -8,14 +8,9 @@ import Tab from "@mui/material/Tab";
|
||||
|
||||
import { ActiveScriptsPage } from "./ActiveScriptsPage";
|
||||
import { RecentScriptsPage } from "./RecentScriptsPage";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { useRerender } from "../React/hooks";
|
||||
|
||||
interface IProps {
|
||||
workerScripts: Map<number, WorkerScript>;
|
||||
}
|
||||
|
||||
export function ActiveScriptsRoot(props: IProps): React.ReactElement {
|
||||
export function ActiveScriptsRoot(): React.ReactElement {
|
||||
const [tab, setTab] = useState<"active" | "recent">("active");
|
||||
useRerender(400);
|
||||
|
||||
@ -29,7 +24,7 @@ export function ActiveScriptsRoot(props: IProps): React.ReactElement {
|
||||
<Tab label={"Recently Killed"} value={"recent"} />
|
||||
</Tabs>
|
||||
|
||||
{tab === "active" && <ActiveScriptsPage workerScripts={props.workerScripts} />}
|
||||
{tab === "active" && <ActiveScriptsPage />}
|
||||
{tab === "recent" && <RecentScriptsPage />}
|
||||
</>
|
||||
);
|
||||
|
@ -16,26 +16,30 @@ import ExpandMore from "@mui/icons-material/ExpandMore";
|
||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||
import { ServerAccordionContent } from "./ServerAccordionContent";
|
||||
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
|
||||
import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
|
||||
import { GetServer } from "../../Server/AllServers";
|
||||
|
||||
interface IProps {
|
||||
server: BaseServer;
|
||||
workerScripts: WorkerScript[];
|
||||
interface ServerAccordionProps {
|
||||
hostname: string;
|
||||
scripts: WorkerScript[];
|
||||
}
|
||||
|
||||
export function ServerAccordion(props: IProps): React.ReactElement {
|
||||
export function ServerAccordion({ hostname, scripts }: ServerAccordionProps): React.ReactElement {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const server = props.server;
|
||||
const server = GetServer(hostname);
|
||||
if (!server) {
|
||||
console.error(`Invalid server ${hostname} while displaying active scripts`);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
// Accordion's header text
|
||||
// TODO: calculate the longest hostname length rather than hard coding it
|
||||
const longestHostnameLength = 18;
|
||||
const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(
|
||||
const paddedName = `${hostname}${" ".repeat(longestHostnameLength)}`.slice(
|
||||
0,
|
||||
Math.max(server.hostname.length, longestHostnameLength),
|
||||
Math.max(hostname.length, longestHostnameLength),
|
||||
);
|
||||
const barOptions = {
|
||||
progress: server.ramUsed / server.maxRam,
|
||||
@ -44,16 +48,16 @@ export function ServerAccordion(props: IProps): React.ReactElement {
|
||||
const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`;
|
||||
|
||||
return (
|
||||
<Box component={Paper}>
|
||||
<Paper>
|
||||
<ListItemButton onClick={() => setOpen((old) => !old)}>
|
||||
<ListItemText primary={<Typography style={{ whiteSpace: "pre-wrap" }}>{headerTxt}</Typography>} />
|
||||
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
|
||||
</ListItemButton>
|
||||
<Box mx={2}>
|
||||
<Collapse in={open} timeout={0} unmountOnExit>
|
||||
<ServerAccordionContent workerScripts={props.workerScripts} />
|
||||
<ServerAccordionContent scripts={scripts} />
|
||||
</Collapse>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@ -1,47 +1,56 @@
|
||||
// TODO: Probably roll this into the ServerAccordion component, no real need for a separate component
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { WorkerScriptAccordion } from "./WorkerScriptAccordion";
|
||||
import List from "@mui/material/List";
|
||||
import TablePagination from "@mui/material/TablePagination";
|
||||
import { TablePaginationActionsAll } from "../React/TablePaginationActionsAll";
|
||||
import { IconButton, List, Typography } from "@mui/material";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { FirstPage, KeyboardArrowLeft, KeyboardArrowRight, LastPage } from "@mui/icons-material";
|
||||
|
||||
interface IProps {
|
||||
workerScripts: WorkerScript[];
|
||||
interface ServerActiveScriptsProps {
|
||||
scripts: WorkerScript[];
|
||||
}
|
||||
|
||||
export function ServerAccordionContent(props: IProps): React.ReactElement {
|
||||
export function ServerAccordionContent({ scripts }: ServerActiveScriptsProps): React.ReactElement {
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(Settings.ActiveScriptsScriptPageSize);
|
||||
const handleChangePage = (event: unknown, newPage: number): void => {
|
||||
setPage(newPage);
|
||||
};
|
||||
if (scripts.length === 0) {
|
||||
console.error(`Attempted to display a server in active scripts when there were no matching scripts to show`);
|
||||
return <></>;
|
||||
}
|
||||
const scriptsPerPage = Settings.ActiveScriptsScriptPageSize;
|
||||
const lastPage = Math.ceil(scripts.length / scriptsPerPage) - 1;
|
||||
function changePage(n: number) {
|
||||
if (!Number.isInteger(n) || n > lastPage || n < 0) return;
|
||||
setPage(n);
|
||||
}
|
||||
if (page > lastPage) changePage(lastPage);
|
||||
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
Settings.ActiveScriptsScriptPageSize = parseInt(event.target.value, 10);
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
setPage(0);
|
||||
};
|
||||
const firstScriptNumber = page * scriptsPerPage + 1;
|
||||
const lastScriptNumber = Math.min((page + 1) * scriptsPerPage, scripts.length);
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.workerScripts.length > 10 ? (
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[10, 15, 20, 100]}
|
||||
component="div"
|
||||
count={props.workerScripts.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
ActionsComponent={TablePaginationActionsAll}
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<div style={{ width: "100%", display: "flex", alignItems: "center" }}>
|
||||
<Typography
|
||||
component="span"
|
||||
marginRight="auto"
|
||||
>{`Displaying scripts ${firstScriptNumber}-${lastScriptNumber} of ${scripts.length}`}</Typography>
|
||||
<IconButton onClick={() => changePage(0)} disabled={page === 0}>
|
||||
<FirstPage />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => changePage(page - 1)} disabled={page === 0}>
|
||||
<KeyboardArrowLeft />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => changePage(page + 1)} disabled={page === lastPage}>
|
||||
<KeyboardArrowRight />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => changePage(lastPage)} disabled={page === lastPage}>
|
||||
<LastPage />
|
||||
</IconButton>
|
||||
</div>
|
||||
<List dense disablePadding>
|
||||
{props.workerScripts.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((ws) => (
|
||||
<WorkerScriptAccordion key={`${ws.pid}`} workerScript={ws} />
|
||||
{scripts.slice(page * scriptsPerPage, page * scriptsPerPage + scriptsPerPage).map((ws) => (
|
||||
<WorkerScriptAccordion key={ws.pid} workerScript={ws} />
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
|
@ -1,135 +0,0 @@
|
||||
/**
|
||||
* React Component for rendering the Accordion elements for all servers
|
||||
* on which scripts are running
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { ServerAccordion } from "./ServerAccordion";
|
||||
|
||||
import TextField from "@mui/material/TextField";
|
||||
import List from "@mui/material/List";
|
||||
import TablePagination from "@mui/material/TablePagination";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { GetServer } from "../../Server/AllServers";
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { TablePaginationActionsAll } from "../React/TablePaginationActionsAll";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import { matchScriptPathUnanchored } from "../../utils/helpers/scriptKey";
|
||||
import lodash from "lodash";
|
||||
|
||||
// Map of server hostname -> all workerscripts on that server for all active scripts
|
||||
interface IServerData {
|
||||
server: BaseServer;
|
||||
workerScripts: WorkerScript[];
|
||||
}
|
||||
|
||||
type IServerToScriptsMap = Record<string, IServerData | undefined>;
|
||||
|
||||
interface IProps {
|
||||
workerScripts: Map<number, WorkerScript>;
|
||||
}
|
||||
|
||||
export function ServerAccordions(props: IProps): React.ReactElement {
|
||||
const [filter, setFilter] = useState("");
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(Settings.ActiveScriptsServerPageSize);
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number): void => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
Settings.ActiveScriptsServerPageSize = parseInt(event.target.value, 10);
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
function handleFilterChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setFilter(event.target.value);
|
||||
setPage(0);
|
||||
}
|
||||
|
||||
const serverToScriptMap: IServerToScriptsMap = {};
|
||||
for (const ws of props.workerScripts.values()) {
|
||||
const server = GetServer(ws.hostname);
|
||||
if (server == null) {
|
||||
console.warn(`WorkerScript has invalid hostname: ${ws.hostname}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
let data = serverToScriptMap[server.hostname];
|
||||
|
||||
if (data === undefined) {
|
||||
serverToScriptMap[server.hostname] = {
|
||||
server: server,
|
||||
workerScripts: [],
|
||||
};
|
||||
data = serverToScriptMap[server.hostname];
|
||||
}
|
||||
if (data !== undefined) {
|
||||
// Add only scripts that correspond to the filter
|
||||
if (ws.hostname.includes(filter) || ws.name.includes(filter)) {
|
||||
data.workerScripts.push(ws);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match filter in the scriptname part of the key
|
||||
const pattern = matchScriptPathUnanchored(lodash.escapeRegExp(filter));
|
||||
const filtered = Object.values(serverToScriptMap).filter((data) => {
|
||||
if (!data) return false;
|
||||
if (data.server.hostname.includes(filter)) return true;
|
||||
for (const k of data.server.runningScriptMap.keys()) {
|
||||
if (pattern.test(k)) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
value={filter}
|
||||
onChange={handleFilterChange}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
startAdornment: <SearchIcon />,
|
||||
spellCheck: false,
|
||||
}}
|
||||
style={{
|
||||
paddingTop: "8px",
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={8}>
|
||||
{filtered.length > 10 ? (
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[10, 15, 20, 100]}
|
||||
component="div"
|
||||
count={filtered.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
ActionsComponent={TablePaginationActionsAll}
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<List dense={true}>
|
||||
{filtered.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((data) => {
|
||||
return (
|
||||
data && (
|
||||
<ServerAccordion key={data.server.hostname} server={data.server} workerScripts={data.workerScripts} />
|
||||
)
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
}
|
@ -51,7 +51,6 @@ import { StaneksGiftRoot } from "../CotMG/ui/StaneksGiftRoot";
|
||||
import { staneksGift } from "../CotMG/Helper";
|
||||
import { CharacterOverview } from "./React/CharacterOverview";
|
||||
import { BladeburnerCinematic } from "../Bladeburner/ui/BladeburnerCinematic";
|
||||
import { workerScripts } from "../Netscript/WorkerScripts";
|
||||
import { Unclickable } from "../Exploits/Unclickable";
|
||||
import { Snackbar, SnackbarProvider } from "./React/Snackbar";
|
||||
import { LogBoxManager } from "./React/LogBoxManager";
|
||||
@ -254,7 +253,7 @@ export function GameRoot(): React.ReactElement {
|
||||
break;
|
||||
}
|
||||
case Page.ActiveScripts: {
|
||||
mainPage = <ActiveScriptsRoot workerScripts={workerScripts} />;
|
||||
mainPage = <ActiveScriptsRoot />;
|
||||
break;
|
||||
}
|
||||
case Page.Hacknet: {
|
||||
|
Loading…
Reference in New Issue
Block a user