mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-25 07:32:27 +01:00
EDITOR: useVimEditor uses Material UI (#1332)
This commit is contained in:
parent
cf48d666f5
commit
463d4cdb1d
@ -373,7 +373,7 @@ function Root(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { VimStatus } = useVimEditor({
|
const { statusBarRef } = useVimEditor({
|
||||||
editor: editorRef.current,
|
editor: editorRef.current,
|
||||||
vim: options.vim,
|
vim: options.vim,
|
||||||
onSave: save,
|
onSave: save,
|
||||||
@ -411,7 +411,7 @@ function Root(props: IProps): React.ReactElement {
|
|||||||
<div style={{ flex: "0 0 5px" }} />
|
<div style={{ flex: "0 0 5px" }} />
|
||||||
<Editor onMount={onMount} onChange={updateCode} onUnmount={onUnmountEditor} />
|
<Editor onMount={onMount} onChange={updateCode} onUnmount={onUnmountEditor} />
|
||||||
|
|
||||||
{VimStatus}
|
{statusBarRef.current}
|
||||||
|
|
||||||
<Toolbar onSave={save} editor={editorRef.current} />
|
<Toolbar onSave={save} editor={editorRef.current} />
|
||||||
</div>
|
</div>
|
||||||
|
199
src/ScriptEditor/ui/StatusBar.tsx
Normal file
199
src/ScriptEditor/ui/StatusBar.tsx
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import { Input, Typography } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import type { editor } from "monaco-editor";
|
||||||
|
import React from "react";
|
||||||
|
type IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
|
||||||
|
|
||||||
|
const StatusBarContainer = styled("div")({
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
height: 36,
|
||||||
|
marginLeft: 4,
|
||||||
|
marginRight: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
const StatusBarLeft = styled("div")({
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
});
|
||||||
|
|
||||||
|
// This Class is injected into the monaco-vim initVimMode function to override the status bar.
|
||||||
|
export class StatusBar {
|
||||||
|
// modeInfoNode is used to display the mode in the status bar.
|
||||||
|
// notifNode is used to display notifications in the status bar.
|
||||||
|
// secInfoNode is used to display the input box in the status bar.
|
||||||
|
// keyInfoNode is used to display the operator and count in the status bar.
|
||||||
|
// editor is kept to focus when closing the input box.
|
||||||
|
// sanitizer is weird.
|
||||||
|
|
||||||
|
node: React.MutableRefObject<React.ReactElement | null>;
|
||||||
|
editor: IStandaloneCodeEditor;
|
||||||
|
|
||||||
|
mode = "--NORMAL--";
|
||||||
|
showInput = false;
|
||||||
|
inputValue = "";
|
||||||
|
buffer = "";
|
||||||
|
|
||||||
|
rerender: () => void;
|
||||||
|
|
||||||
|
onCloseHandler: ((query: string) => void) | null;
|
||||||
|
onKeyDownHandler: ((e: React.KeyboardEvent, query: string, close: () => void) => void) | null;
|
||||||
|
onKeyUpHandler: ((e: React.KeyboardEvent, query: string, close: () => void) => void) | null;
|
||||||
|
|
||||||
|
// node is used to setup the status bar. However, we use it to forward the resulting status bar to the outside.
|
||||||
|
// sanitizer is weird, so we use it to forward a rerender hook.
|
||||||
|
constructor(
|
||||||
|
node: React.MutableRefObject<React.ReactElement | null>,
|
||||||
|
editor: IStandaloneCodeEditor,
|
||||||
|
rerender: () => void,
|
||||||
|
) {
|
||||||
|
this.node = node;
|
||||||
|
this.editor = editor;
|
||||||
|
|
||||||
|
this.rerender = rerender;
|
||||||
|
|
||||||
|
this.onCloseHandler = null;
|
||||||
|
this.onKeyDownHandler = null;
|
||||||
|
this.onKeyUpHandler = null;
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is used to change the mode shown in the status bar.
|
||||||
|
setMode(ev: { mode: string; subMode?: string }) {
|
||||||
|
if (ev.mode === "visual") {
|
||||||
|
if (ev.subMode === "linewise") {
|
||||||
|
this.mode = "--VISUAL LINE--";
|
||||||
|
} else if (ev.subMode === "blockwise") {
|
||||||
|
this.mode = "--VISUAL BLOCK--";
|
||||||
|
} else {
|
||||||
|
this.mode = "--VISUAL--";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.mode = `--${ev.mode.toUpperCase()}--`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is used to set the current operator, like d, r, etc or the count, like 5j, 3w, etc.
|
||||||
|
setKeyBuffer(key: string) {
|
||||||
|
this.buffer = key;
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is used to set the input box.
|
||||||
|
setSec(
|
||||||
|
// this is the created HTML element from monaco-vim. We're not going to use it, so it is marked as unused.
|
||||||
|
__text: HTMLElement,
|
||||||
|
// this is used to close the input box and set the cursor back to the line in the monaco-editor. query is the text in the input box.
|
||||||
|
onClose: ((query: string) => void) | null,
|
||||||
|
options: {
|
||||||
|
// This handles ESC, Backspace when input is empty, CTRL-C and CTRL-[. query is the text in the input box. close is a function that closes the input box. e is the key event.
|
||||||
|
onKeyDown: ((e: React.KeyboardEvent, query: string, close: () => void) => void) | undefined;
|
||||||
|
// This handles all other key events. query is the text in the input box. close is a function that closes the input box. e is the key event.
|
||||||
|
onKeyUp: ((e: React.KeyboardEvent, query: string, close: () => void) => void) | undefined;
|
||||||
|
// this is a default value for the input box. The box should be empty if this is not set.
|
||||||
|
value: string | undefined;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this.onCloseHandler = onClose;
|
||||||
|
this.onKeyDownHandler = options.onKeyDown ?? null;
|
||||||
|
this.onKeyUpHandler = options.onKeyUp ?? null;
|
||||||
|
this.inputValue = options.value || "";
|
||||||
|
this.showInput = true;
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is used to toggle showing the status bar.
|
||||||
|
toggleVisibility(toggle: boolean) {
|
||||||
|
if (toggle) {
|
||||||
|
this.node.current = this.StatusBar();
|
||||||
|
} else {
|
||||||
|
this.node.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is used to close the input box.
|
||||||
|
closeInput = () => {
|
||||||
|
this.showInput = false;
|
||||||
|
this.update();
|
||||||
|
this.editor.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
// this is used to clean up the status bar on unmount.
|
||||||
|
clear = () => {
|
||||||
|
this.node.current = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// this is used to show notifications. In game, these won't be rendered, so the function is empty.
|
||||||
|
showNotification(__text: HTMLElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
update = () => {
|
||||||
|
this.node.current = this.StatusBar();
|
||||||
|
this.rerender();
|
||||||
|
};
|
||||||
|
|
||||||
|
keyUp = (e: React.KeyboardEvent) => {
|
||||||
|
if (this.onKeyUpHandler !== null) {
|
||||||
|
this.onKeyUpHandler(e, this.inputValue, this.closeInput);
|
||||||
|
} else {
|
||||||
|
// if the player somehow gets stuck here, they can also press enter to close the input box.
|
||||||
|
if (e.key === "Enter") this.closeInput();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
keyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (this.onKeyDownHandler !== null) {
|
||||||
|
this.onKeyDownHandler(e, this.inputValue, this.closeInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this handles pressing escape in the input box.
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.closeInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this handles pressing enter in the input box.
|
||||||
|
if (e.key === "Enter" && this.onCloseHandler !== null) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
this.onCloseHandler(this.inputValue);
|
||||||
|
this.closeInput();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.inputValue = e.target.value;
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
StatusBar(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<StatusBarContainer>
|
||||||
|
<StatusBarLeft>
|
||||||
|
<Typography sx={{ mr: 4 }}>{this.mode}</Typography>
|
||||||
|
{this.showInput && (
|
||||||
|
<Input
|
||||||
|
value={this.inputValue}
|
||||||
|
onChange={this.handleInput}
|
||||||
|
onKeyUp={this.keyUp}
|
||||||
|
onKeyDown={this.keyDown}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!this.showInput && <div />}
|
||||||
|
</StatusBarLeft>
|
||||||
|
<Typography>{this.buffer}</Typography>
|
||||||
|
</StatusBarContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,10 @@ import * as MonacoVim from "monaco-vim";
|
|||||||
import type { editor } from "monaco-editor";
|
import type { editor } from "monaco-editor";
|
||||||
type IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
|
type IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
|
||||||
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
|
|
||||||
import { Router } from "../../ui/GameRoot";
|
import { Router } from "../../ui/GameRoot";
|
||||||
import { Page } from "../../ui/Router";
|
import { Page } from "../../ui/Router";
|
||||||
|
import { StatusBar } from "./StatusBar";
|
||||||
|
import { useRerender } from "../../ui/React/hooks";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
vim: boolean;
|
vim: boolean;
|
||||||
@ -21,7 +21,8 @@ export function useVimEditor({ editor, vim, onOpenNextTab, onOpenPreviousTab, on
|
|||||||
// monaco-vim does not have types, so this is an any
|
// monaco-vim does not have types, so this is an any
|
||||||
const [vimEditor, setVimEditor] = useState<any>(null);
|
const [vimEditor, setVimEditor] = useState<any>(null);
|
||||||
|
|
||||||
const vimStatusRef = useRef<HTMLElement>(null);
|
const statusBarRef = useRef<React.ReactElement | null>(null);
|
||||||
|
const rerender = useRerender();
|
||||||
|
|
||||||
const actionsRef = useRef({ save: onSave, openNextTab: onOpenNextTab, openPreviousTab: onOpenPreviousTab });
|
const actionsRef = useRef({ save: onSave, openNextTab: onOpenNextTab, openPreviousTab: onOpenPreviousTab });
|
||||||
actionsRef.current = { save: onSave, openNextTab: onOpenNextTab, openPreviousTab: onOpenPreviousTab };
|
actionsRef.current = { save: onSave, openNextTab: onOpenNextTab, openPreviousTab: onOpenPreviousTab };
|
||||||
@ -31,7 +32,7 @@ export function useVimEditor({ editor, vim, onOpenNextTab, onOpenPreviousTab, on
|
|||||||
if (vim && editor && !vimEditor) {
|
if (vim && editor && !vimEditor) {
|
||||||
// Using try/catch because MonacoVim does not have types.
|
// Using try/catch because MonacoVim does not have types.
|
||||||
try {
|
try {
|
||||||
setVimEditor(MonacoVim.initVimMode(editor, vimStatusRef.current));
|
setVimEditor(MonacoVim.initVimMode(editor, statusBarRef, StatusBar, rerender));
|
||||||
MonacoVim.VimMode.Vim.defineEx("write", "w", function () {
|
MonacoVim.VimMode.Vim.defineEx("write", "w", function () {
|
||||||
// your own implementation on what you want to do when :w is pressed
|
// your own implementation on what you want to do when :w is pressed
|
||||||
actionsRef.current.save();
|
actionsRef.current.save();
|
||||||
@ -40,6 +41,10 @@ export function useVimEditor({ editor, vim, onOpenNextTab, onOpenPreviousTab, on
|
|||||||
Router.toPage(Page.Terminal);
|
Router.toPage(Page.Terminal);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Remove any macro recording, since it isn't supported.
|
||||||
|
MonacoVim.VimMode.Vim.mapCommand("q", "", "", null, { context: "normal" });
|
||||||
|
MonacoVim.VimMode.Vim.mapCommand("@", "", "", null, { context: "normal" });
|
||||||
|
|
||||||
const saveNQuit = (): void => {
|
const saveNQuit = (): void => {
|
||||||
actionsRef.current.save();
|
actionsRef.current.save();
|
||||||
Router.toPage(Page.Terminal);
|
Router.toPage(Page.Terminal);
|
||||||
@ -72,19 +77,7 @@ export function useVimEditor({ editor, vim, onOpenNextTab, onOpenPreviousTab, on
|
|||||||
return () => {
|
return () => {
|
||||||
vimEditor?.dispose();
|
vimEditor?.dispose();
|
||||||
};
|
};
|
||||||
}, [vim, editor, vimEditor]);
|
}, [vim, editor, vimEditor, rerender]);
|
||||||
|
|
||||||
const VimStatus = (
|
return { statusBarRef };
|
||||||
<Box
|
|
||||||
ref={vimStatusRef}
|
|
||||||
className="vim-display"
|
|
||||||
display="flex"
|
|
||||||
flexGrow="0"
|
|
||||||
flexDirection="row"
|
|
||||||
sx={{ p: 1 }}
|
|
||||||
alignItems="center"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return { VimStatus };
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user