diff --git a/src/Themes/ui/Theme.tsx b/src/Themes/ui/Theme.tsx index d410eeb9f..e254e70dd 100644 --- a/src/Themes/ui/Theme.tsx +++ b/src/Themes/ui/Theme.tsx @@ -239,9 +239,12 @@ export function refreshTheme(): void { }, MuiIconButton: { styleOverrides: { - root: { + root: ({ ownerState }) => ({ color: Settings.theme.primary, - }, + ...(ownerState.disableRipple && { + p: 0, + }), + }), }, }, MuiTooltip: { diff --git a/src/ui/ActiveScripts/ActiveScriptsPage.tsx b/src/ui/ActiveScripts/ActiveScriptsPage.tsx index a42f2efc9..88f3cf5f1 100644 --- a/src/ui/ActiveScripts/ActiveScriptsPage.tsx +++ b/src/ui/ActiveScripts/ActiveScriptsPage.tsx @@ -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; -} + function changeScriptsPerPage(e: SelectChangeEvent) { + const n = parseInt(e.target.value as string); + if (!isPositiveInteger(n)) return; + Settings.ActiveScriptsScriptPageSize = n; + setScriptsPerPage(n); + } + function changeServersPerPage(e: SelectChangeEvent) { + const n = parseInt(e.target.value as string); + if (!isPositiveInteger(n)) return; + Settings.ActiveScriptsServerPageSize = n; + setServersPerPage(n); + } + + const serverData: [string, WorkerScript[]][] = (() => { + const tempData: Record = {}; + 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 ( <> @@ -25,7 +72,50 @@ export function ActiveScriptsPage(props: IProps): React.ReactElement { - +
+ setFilter(e.target.value)} + autoFocus + InputProps={{ startAdornment: , spellCheck: false }} + size="small" + /> + Servers/page: + + Scripts/page: + + {`${firstServerNumber}-${lastServerNumber} of ${serverData.length}`} + changePage(0)} disabled={page === 0}> + + + changePage(page - 1)} disabled={page === 0}> + + + changePage(page + 1)} disabled={page === lastPage}> + + + changePage(lastPage)} disabled={page === lastPage}> + + +
+ + {dataToShow.map(([hostname, scripts]) => ( + + ))} + ); } diff --git a/src/ui/ActiveScripts/ActiveScriptsRoot.tsx b/src/ui/ActiveScripts/ActiveScriptsRoot.tsx index 601288d63..a91b86739 100644 --- a/src/ui/ActiveScripts/ActiveScriptsRoot.tsx +++ b/src/ui/ActiveScripts/ActiveScriptsRoot.tsx @@ -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; -} - -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 === "active" && } + {tab === "active" && } {tab === "recent" && } ); diff --git a/src/ui/ActiveScripts/ServerAccordion.tsx b/src/ui/ActiveScripts/ServerAccordion.tsx index 6b354e964..f4af65d38 100644 --- a/src/ui/ActiveScripts/ServerAccordion.tsx +++ b/src/ui/ActiveScripts/ServerAccordion.tsx @@ -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 ( - + setOpen((old) => !old)}> {headerTxt}} /> {open ? : } - + - + ); } diff --git a/src/ui/ActiveScripts/ServerAccordionContent.tsx b/src/ui/ActiveScripts/ServerAccordionContent.tsx index df167dedb..a21316c6a 100644 --- a/src/ui/ActiveScripts/ServerAccordionContent.tsx +++ b/src/ui/ActiveScripts/ServerAccordionContent.tsx @@ -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): 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 ? ( - - ) : ( - "" - )} +
+ {`Displaying scripts ${firstScriptNumber}-${lastScriptNumber} of ${scripts.length}`} + changePage(0)} disabled={page === 0}> + + + changePage(page - 1)} disabled={page === 0}> + + + changePage(page + 1)} disabled={page === lastPage}> + + + changePage(lastPage)} disabled={page === lastPage}> + + +
- {props.workerScripts.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((ws) => ( - + {scripts.slice(page * scriptsPerPage, page * scriptsPerPage + scriptsPerPage).map((ws) => ( + ))} diff --git a/src/ui/ActiveScripts/ServerAccordions.tsx b/src/ui/ActiveScripts/ServerAccordions.tsx deleted file mode 100644 index e06de5147..000000000 --- a/src/ui/ActiveScripts/ServerAccordions.tsx +++ /dev/null @@ -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; - -interface IProps { - workerScripts: Map; -} - -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): void => { - Settings.ActiveScriptsServerPageSize = parseInt(event.target.value, 10); - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; - - function handleFilterChange(event: React.ChangeEvent): 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 ( - <> - - - , - spellCheck: false, - }} - style={{ - paddingTop: "8px", - }} - /> - - - {filtered.length > 10 ? ( - - ) : ( - "" - )} - - - - {filtered.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((data) => { - return ( - data && ( - - ) - ); - })} - - - ); -} diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 64affb20a..4066512db 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -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 = ; + mainPage = ; break; } case Page.Hacknet: {