mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 22:38:34 +02:00
Merge branch 'dev' into add-ns-getRecentScripts
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import React, { useState, useEffect, useRef, useMemo } from "react";
|
||||
import Editor, { Monaco } from "@monaco-editor/react";
|
||||
import * as monaco from "monaco-editor";
|
||||
@@ -18,6 +17,7 @@ import { calculateRamUsage, checkInfiniteLoop } from "../../Script/RamCalculatio
|
||||
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
|
||||
import { NetscriptFunctions } from "../../NetscriptFunctions";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
@@ -43,7 +43,7 @@ import { PromptEvent } from "../../ui/React/PromptManager";
|
||||
import { Modal } from "../../ui/React/Modal";
|
||||
|
||||
import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts";
|
||||
import { Tooltip } from "@mui/material";
|
||||
import { TextField, Tooltip } from "@mui/material";
|
||||
|
||||
interface IProps {
|
||||
// Map of filename -> code
|
||||
@@ -54,7 +54,7 @@ interface IProps {
|
||||
vim: boolean;
|
||||
}
|
||||
|
||||
// TODO: try to removve global symbols
|
||||
// TODO: try to remove global symbols
|
||||
let symbolsLoaded = false;
|
||||
let symbols: string[] = [];
|
||||
export function SetupTextEditor(): void {
|
||||
@@ -114,6 +114,8 @@ export function Root(props: IProps): React.ReactElement {
|
||||
const vimStatusRef = useRef<HTMLElement>(null);
|
||||
const [vimEditor, setVimEditor] = useState<any>(null);
|
||||
const [editor, setEditor] = useState<IStandaloneCodeEditor | null>(null);
|
||||
const [filter, setFilter] = useState("");
|
||||
const [searchExpanded, setSearchExpanded] = useState(false);
|
||||
|
||||
const [ram, setRAM] = useState("RAM: ???");
|
||||
const [ramEntries, setRamEntries] = useState<string[][]>([["???", ""]]);
|
||||
@@ -205,6 +207,32 @@ export function Root(props: IProps): React.ReactElement {
|
||||
save();
|
||||
props.router.toTerminal();
|
||||
});
|
||||
|
||||
// Setup "go to next tab" and "go to previous tab". This is a little more involved
|
||||
// since these aren't Ex commands (they run in normal mode, not after typing `:`)
|
||||
MonacoVim.VimMode.Vim.defineAction("nextTabs", function (_cm: any, args: { repeat?: number }) {
|
||||
const nTabs = args.repeat ?? 1;
|
||||
// Go to the next tab (to the right). Wraps around when at the rightmost tab
|
||||
const currIndex = currentTabIndex();
|
||||
if (currIndex !== undefined) {
|
||||
const nextIndex = (currIndex + nTabs) % openScripts.length;
|
||||
onTabClick(nextIndex);
|
||||
}
|
||||
});
|
||||
MonacoVim.VimMode.Vim.defineAction("prevTabs", function (_cm: any, args: { repeat?: number }) {
|
||||
const nTabs = args.repeat ?? 1;
|
||||
// Go to the previous tab (to the left). Wraps around when at the leftmost tab
|
||||
const currIndex = currentTabIndex();
|
||||
if (currIndex !== undefined) {
|
||||
let nextIndex = currIndex - nTabs;
|
||||
while (nextIndex < 0) {
|
||||
nextIndex += openScripts.length;
|
||||
}
|
||||
onTabClick(nextIndex);
|
||||
}
|
||||
});
|
||||
MonacoVim.VimMode.Vim.mapCommand("gt", "action", "nextTabs", {}, { context: "normal" });
|
||||
MonacoVim.VimMode.Vim.mapCommand("gT", "action", "prevTabs", {}, { context: "normal" });
|
||||
editor.focus();
|
||||
});
|
||||
} catch {}
|
||||
@@ -498,7 +526,7 @@ export function Root(props: IProps): React.ReactElement {
|
||||
const textFile = new TextFile(scriptToSave.fileName, scriptToSave.code);
|
||||
server.textFiles.push(textFile);
|
||||
} else {
|
||||
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)");
|
||||
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or a text file (.txt)");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -582,7 +610,7 @@ export function Root(props: IProps): React.ReactElement {
|
||||
const textFile = new TextFile(currentScript.fileName, currentScript.code);
|
||||
server.textFiles.push(textFile);
|
||||
} else {
|
||||
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)");
|
||||
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or a text file (.txt)");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -609,16 +637,26 @@ export function Root(props: IProps): React.ReactElement {
|
||||
openScripts = items;
|
||||
}
|
||||
|
||||
function onTabClick(index: number): void {
|
||||
function currentTabIndex(): number | undefined {
|
||||
if (currentScript !== null) {
|
||||
// Save currentScript to openScripts
|
||||
const curIndex = openScripts.findIndex(
|
||||
return openScripts.findIndex(
|
||||
(script) =>
|
||||
currentScript !== null &&
|
||||
script.fileName === currentScript.fileName &&
|
||||
script.hostname === currentScript.hostname,
|
||||
);
|
||||
openScripts[curIndex] = currentScript;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function onTabClick(index: number): void {
|
||||
if (currentScript !== null) {
|
||||
// Save currentScript to openScripts
|
||||
const curIndex = currentTabIndex();
|
||||
if (curIndex !== undefined) {
|
||||
openScripts[curIndex] = currentScript;
|
||||
}
|
||||
}
|
||||
|
||||
currentScript = { ...openScripts[index] };
|
||||
@@ -653,7 +691,7 @@ export function Root(props: IProps): React.ReactElement {
|
||||
if (serverScriptIndex === -1 || savedScriptCode !== server.scripts[serverScriptIndex as number].code) {
|
||||
PromptEvent.emit({
|
||||
txt: "Do you want to save changes to " + closingScript.fileName + "?",
|
||||
resolve: (result: boolean) => {
|
||||
resolve: (result: boolean | string) => {
|
||||
if (result) {
|
||||
// Save changes
|
||||
closingScript.code = savedScriptCode;
|
||||
@@ -710,7 +748,7 @@ export function Root(props: IProps): React.ReactElement {
|
||||
"Do you want to overwrite the current editor content with the contents of " +
|
||||
openScript.fileName +
|
||||
" on the server? This cannot be undone.",
|
||||
resolve: (result: boolean) => {
|
||||
resolve: (result: boolean | string) => {
|
||||
if (result) {
|
||||
// Save changes
|
||||
openScript.code = serverScriptCode;
|
||||
@@ -752,6 +790,16 @@ export function Root(props: IProps): React.ReactElement {
|
||||
const serverScript = server.scripts.find((s) => s.filename === openScript.fileName);
|
||||
return serverScript?.code ?? null;
|
||||
}
|
||||
function handleFilterChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setFilter(event.target.value);
|
||||
}
|
||||
function handleExpandSearch(): void {
|
||||
setFilter("");
|
||||
setSearchExpanded(!searchExpanded);
|
||||
}
|
||||
const filteredOpenScripts = Object.values(openScripts).filter(
|
||||
(script) => script.hostname.includes(filter) || script.fileName.includes(filter),
|
||||
);
|
||||
|
||||
// Toolbars are roughly 112px:
|
||||
// 8px body margin top
|
||||
@@ -760,7 +808,11 @@ export function Root(props: IProps): React.ReactElement {
|
||||
// 44px bottom tool bar + 16px margin
|
||||
// + vim bar 34px
|
||||
const editorHeight = dimensions.height - (130 + (options.vim ? 34 : 0));
|
||||
|
||||
const tabsMaxWidth = 1640;
|
||||
const tabMargin = 5;
|
||||
const tabMaxWidth = filteredOpenScripts.length ? tabsMaxWidth / filteredOpenScripts.length - tabMargin : 0;
|
||||
const tabIconWidth = 25;
|
||||
const tabTextWidth = tabMaxWidth - tabIconWidth * 2;
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: currentScript !== null ? "block" : "none", height: "100%", width: "100%" }}>
|
||||
@@ -768,7 +820,7 @@ export function Root(props: IProps): React.ReactElement {
|
||||
<Droppable droppableId="tabs" direction="horizontal">
|
||||
{(provided, snapshot) => (
|
||||
<Box
|
||||
maxWidth="1640px"
|
||||
maxWidth={`${tabsMaxWidth}px`}
|
||||
display="flex"
|
||||
flexDirection="row"
|
||||
alignItems="center"
|
||||
@@ -782,13 +834,31 @@ export function Root(props: IProps): React.ReactElement {
|
||||
overflowX: "scroll",
|
||||
}}
|
||||
>
|
||||
{openScripts.map(({ fileName, hostname }, index) => {
|
||||
<Tooltip title={"Search Open Scripts"}>
|
||||
{searchExpanded ? (
|
||||
<TextField
|
||||
value={filter}
|
||||
onChange={handleFilterChange}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
startAdornment: <SearchIcon />,
|
||||
spellCheck: false,
|
||||
endAdornment: <CloseIcon onClick={handleExpandSearch} />,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Button onClick={handleExpandSearch}>
|
||||
<SearchIcon />
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
{filteredOpenScripts.map(({ fileName, hostname }, index) => {
|
||||
const iconButtonStyle = {
|
||||
maxWidth: "25px",
|
||||
minWidth: "25px",
|
||||
maxWidth: `${tabIconWidth}px`,
|
||||
minWidth: `${tabIconWidth}px`,
|
||||
minHeight: "38.5px",
|
||||
maxHeight: "38.5px",
|
||||
...(currentScript?.fileName === openScripts[index].fileName
|
||||
...(currentScript?.fileName === filteredOpenScripts[index].fileName
|
||||
? {
|
||||
background: Settings.theme.button,
|
||||
borderColor: Settings.theme.button,
|
||||
@@ -800,6 +870,8 @@ export function Root(props: IProps): React.ReactElement {
|
||||
color: Settings.theme.secondary,
|
||||
}),
|
||||
};
|
||||
|
||||
const scriptTabText = `${hostname}:~/${fileName} ${dirty(index)}`;
|
||||
return (
|
||||
<Draggable
|
||||
key={fileName + hostname}
|
||||
@@ -814,33 +886,41 @@ export function Root(props: IProps): React.ReactElement {
|
||||
{...provided.dragHandleProps}
|
||||
style={{
|
||||
...provided.draggableProps.style,
|
||||
marginRight: "5px",
|
||||
maxWidth: `${tabMaxWidth}px`,
|
||||
marginRight: `${tabMargin}px`,
|
||||
flexShrink: 0,
|
||||
border: "1px solid " + Settings.theme.well,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={() => onTabClick(index)}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
if (e.button === 1) onTabClose(index);
|
||||
}}
|
||||
style={{
|
||||
...(currentScript?.fileName === openScripts[index].fileName
|
||||
? {
|
||||
background: Settings.theme.button,
|
||||
borderColor: Settings.theme.button,
|
||||
color: Settings.theme.primary,
|
||||
}
|
||||
: {
|
||||
background: Settings.theme.backgroundsecondary,
|
||||
borderColor: Settings.theme.backgroundsecondary,
|
||||
color: Settings.theme.secondary,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{hostname}:~/{fileName} {dirty(index)}
|
||||
</Button>
|
||||
<Tooltip title={scriptTabText}>
|
||||
<Button
|
||||
onClick={() => onTabClick(index)}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
if (e.button === 1) onTabClose(index);
|
||||
}}
|
||||
style={{
|
||||
maxWidth: `${tabTextWidth}px`,
|
||||
minHeight: "38.5px",
|
||||
overflow: "hidden",
|
||||
...(currentScript?.fileName === filteredOpenScripts[index].fileName
|
||||
? {
|
||||
background: Settings.theme.button,
|
||||
borderColor: Settings.theme.button,
|
||||
color: Settings.theme.primary,
|
||||
}
|
||||
: {
|
||||
background: Settings.theme.backgroundsecondary,
|
||||
borderColor: Settings.theme.backgroundsecondary,
|
||||
color: Settings.theme.secondary,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<span style={{ overflow: "hidden", direction: "rtl", textOverflow: "ellipsis" }}>
|
||||
{scriptTabText}
|
||||
</span>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Overwrite editor content with saved file content">
|
||||
<Button onClick={() => onTabUpdate(index)} style={iconButtonStyle}>
|
||||
<SyncIcon fontSize="small" />
|
||||
@@ -895,11 +975,11 @@ export function Root(props: IProps): React.ReactElement {
|
||||
>
|
||||
{ram}
|
||||
</Button>
|
||||
<Button sx={{ mr: 1 }} onClick={save}>
|
||||
Save (Ctrl/Cmd + s)
|
||||
<Button onClick={save}>Save (Ctrl/Cmd + s)</Button>
|
||||
<Button sx={{ mx: 1 }} onClick={props.router.toTerminal}>
|
||||
Terminal (Ctrl/Cmd + b)
|
||||
</Button>
|
||||
<Button onClick={props.router.toTerminal}>Close (Ctrl/Cmd + b)</Button>
|
||||
<Typography sx={{ mx: 1 }}>
|
||||
<Typography>
|
||||
{" "}
|
||||
<strong>Documentation:</strong>{" "}
|
||||
<Link target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html">
|
||||
|
||||
Reference in New Issue
Block a user