Improve wrong arg user message and add ui.windowSize

This commit is contained in:
Olivier Gagnon 2022-08-29 18:07:17 -04:00
parent a2fad677d3
commit c9a0998cc1
6 changed files with 147 additions and 25 deletions

@ -67,11 +67,33 @@ export const helpers = {
failOnHacknetServer, failOnHacknetServer,
}; };
const userFriendlyString = (v: unknown): string => {
const clip = (s: string): string => {
if (s.length > 15) return s.slice(0, 12) + "...";
return s;
};
if (typeof v === "number") return String(v);
if (typeof v === "string") {
if (v === "") return "empty string";
return `'${clip(v)}'`;
}
const json = JSON.stringify(v);
if (!json) return "???";
return `'${clip(json)}'`;
};
const debugType = (v: unknown): string => {
if (v === null) return `Is null.`;
if (v === undefined) return "Is undefined.";
if (typeof v === "function") return "Is a function.";
return `Is of type '${typeof v}', value: ${userFriendlyString(v)}`;
};
/** Convert a provided value v for argument argName to string. If it wasn't originally a string or number, throw. */ /** Convert a provided value v for argument argName to string. If it wasn't originally a string or number, throw. */
function string(ctx: NetscriptContext, argName: string, v: unknown): string { function string(ctx: NetscriptContext, argName: string, v: unknown): string {
if (typeof v === "string") return v; if (typeof v === "string") return v;
if (typeof v === "number") return v + ""; // cast to string; if (typeof v === "number") return v + ""; // cast to string;
throw makeRuntimeErrorMsg(ctx, `'${argName}' should be a string.`); throw makeRuntimeErrorMsg(ctx, `'${argName}' should be a string. ${debugType(v)}`);
} }
/** Convert provided value v for argument argName to number. Throw if could not convert to a non-NaN number. */ /** Convert provided value v for argument argName to number. Throw if could not convert to a non-NaN number. */
@ -83,7 +105,7 @@ function number(ctx: NetscriptContext, argName: string, v: unknown): number {
if (isNaN(v)) throw makeRuntimeErrorMsg(ctx, `'${argName}' is NaN.`); if (isNaN(v)) throw makeRuntimeErrorMsg(ctx, `'${argName}' is NaN.`);
return v; return v;
} }
throw makeRuntimeErrorMsg(ctx, `'${argName}' should be a number.`); throw makeRuntimeErrorMsg(ctx, `'${argName}' should be a number. ${debugType(v)}`);
} }
/** Returns args back if it is a ScriptArg[]. Throws an error if it is not. */ /** Returns args back if it is a ScriptArg[]. Throws an error if it is not. */

@ -335,6 +335,7 @@ const ui = {
resetStyles: 0, resetStyles: 0,
getGameInfo: 0, getGameInfo: 0,
clearTerminal: 0, clearTerminal: 0,
windowSize: 0,
}; };
// Grafting API // Grafting API
@ -531,6 +532,8 @@ const SourceRamCosts = {
mv: 0, mv: 0,
tail: 0, tail: 0,
toast: 0, toast: 0,
moveTail: 0,
resizeTail: 0,
closeTail: 0, closeTail: 0,
clearPort: 0, clearPort: 0,
openDevMenu: 0, openDevMenu: 0,

@ -38,7 +38,7 @@ import { WorkerScript } from "./Netscript/WorkerScript";
import { helpers } from "./Netscript/NetscriptHelpers"; import { helpers } from "./Netscript/NetscriptHelpers";
import { numeralWrapper } from "./ui/numeralFormat"; import { numeralWrapper } from "./ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions";
import { LogBoxEvents, LogBoxCloserEvents } from "./ui/React/LogBoxManager"; import { LogBoxEvents, LogBoxCloserEvents, LogBoxPositionEvents, LogBoxSizeEvents } from "./ui/React/LogBoxManager";
import { arrayToString } from "./utils/helpers/arrayToString"; import { arrayToString } from "./utils/helpers/arrayToString";
import { isString } from "./utils/helpers/isString"; import { isString } from "./utils/helpers/isString";
import { NetscriptGang } from "./NetscriptFunctions/Gang"; import { NetscriptGang } from "./NetscriptFunctions/Gang";
@ -536,7 +536,22 @@ const base: InternalAPI<NS> = {
LogBoxEvents.emit(runningScriptObj); LogBoxEvents.emit(runningScriptObj);
}, },
moveTail:
(ctx: NetscriptContext) =>
(_x: unknown, _y: unknown, _pid: unknown = ctx.workerScript.scriptRef.pid) => {
const x = helpers.number(ctx, "x", _x);
const y = helpers.number(ctx, "y", _y);
const pid = helpers.number(ctx, "pid", _pid);
LogBoxPositionEvents.emit({ pid, data: { x, y } });
},
resizeTail:
(ctx: NetscriptContext) =>
(_w: unknown, _h: unknown, _pid: unknown = ctx.workerScript.scriptRef.pid) => {
const w = helpers.number(ctx, "w", _w);
const h = helpers.number(ctx, "h", _h);
const pid = helpers.number(ctx, "pid", _pid);
LogBoxSizeEvents.emit({ pid, data: { w, h } });
},
closeTail: closeTail:
(ctx: NetscriptContext) => (ctx: NetscriptContext) =>
(_pid: unknown = ctx.workerScript.scriptRef.pid): void => { (_pid: unknown = ctx.workerScript.scriptRef.pid): void => {

@ -16,6 +16,9 @@ import { helpers } from "../Netscript/NetscriptHelpers";
export function NetscriptUserInterface(): InternalAPI<IUserInterface> { export function NetscriptUserInterface(): InternalAPI<IUserInterface> {
return { return {
windowSize: () => (): [number, number] => {
return [window.innerWidth, window.innerHeight];
},
getTheme: () => (): UserInterfaceTheme => { getTheme: () => (): UserInterfaceTheme => {
return { ...Settings.theme }; return { ...Settings.theme };
}, },

@ -4356,6 +4356,15 @@ interface Infiltration {
* @public * @public
*/ */
interface UserInterface { interface UserInterface {
/**
* Get the current window size
* @remarks
* RAM cost: 0 GB
*
* @returns An array of 2 value containing the window width and height.
*/
windowSize(): [number, number];
/** /**
* Get the current theme * Get the current theme
* @remarks * @remarks
@ -5014,6 +5023,32 @@ export interface NS {
*/ */
tail(fn?: FilenameOrPID, host?: string, ...args: (string | number | boolean)[]): void; tail(fn?: FilenameOrPID, host?: string, ...args: (string | number | boolean)[]): void;
/**
* Move a tail window
* @remarks
* RAM cost: 0 GB
*
* Moves a tail window. Coordinates are in screenspace pixels (top left is 0,0)
*
* @param x - x coordinate.
* @param y - y coordinate.
* @param pid - Optional. PID of the script having its tail moved. If omitted, the current script is used.
*/
moveTail(x: number, y: number, pid?: number): void;
/**
* Resize a tail window
* @remarks
* RAM cost: 0 GB
*
* Resize a tail window. Size are in pixel
*
* @param width - width of the window.
* @param height - height of the window.
* @param pid - Optional. PID of the script having its tail resized. If omitted, the current script is used.
*/
resizeTail(width: number, height: number, pid?: number): void;
/** /**
* Close the tail window of a script. * Close the tail window of a script.
* @remarks * @remarks

@ -7,7 +7,7 @@ import Box from "@mui/material/Box";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import Draggable, { DraggableEvent } from "react-draggable"; import Draggable, { DraggableEvent } from "react-draggable";
import { ResizableBox } from "react-resizable"; import { ResizableBox, ResizeCallbackData } from "react-resizable";
import makeStyles from "@mui/styles/makeStyles"; import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
@ -26,6 +26,25 @@ export const LogBoxEvents = new EventEmitter<[RunningScript]>();
export const LogBoxCloserEvents = new EventEmitter<[number]>(); export const LogBoxCloserEvents = new EventEmitter<[number]>();
export const LogBoxClearEvents = new EventEmitter<[]>(); export const LogBoxClearEvents = new EventEmitter<[]>();
interface LogBoxUIEvent<T> {
pid: number;
data: T;
}
interface LogBoxPositionData {
x: number;
y: number;
}
export const LogBoxPositionEvents = new EventEmitter<[LogBoxUIEvent<LogBoxPositionData>]>();
interface LogBoxResizeData {
w: number;
h: number;
}
export const LogBoxSizeEvents = new EventEmitter<[LogBoxUIEvent<LogBoxResizeData>]>();
interface Log { interface Log {
id: string; id: string;
script: RunningScript; script: RunningScript;
@ -121,11 +140,16 @@ function LogWindow(props: IProps): React.ReactElement {
const classes = useStyles(); const classes = useStyles();
const container = useRef<HTMLDivElement>(null); const container = useRef<HTMLDivElement>(null);
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
const [size, setSize] = useState<[number, number]>([500, 500]);
const [minimized, setMinimized] = useState(false); const [minimized, setMinimized] = useState(false);
function rerender(): void { function rerender(): void {
setRerender((old) => !old); setRerender((old) => !old);
} }
const onResize = (e: React.SyntheticEvent, { size }: ResizeCallbackData) => {
setSize([size.width, size.height]);
};
// useEffect( // useEffect(
// () => // () =>
// WorkerScriptStartStopEventEmitter.subscribe(() => { // WorkerScriptStartStopEventEmitter.subscribe(() => {
@ -143,6 +167,38 @@ function LogWindow(props: IProps): React.ReactElement {
// [], // [],
// ); // );
const setPosition = ({ x, y }: LogBoxPositionData) => {
const node = rootRef?.current;
if (!node) return;
const state = node.state as { x: number; y: number };
console.log(`before ${state.x} ${state.y}`);
state.x = x;
state.y = y;
};
// Listen to Logbox positioning events.
useEffect(
() =>
LogBoxPositionEvents.subscribe((e) => {
if (e.pid !== props.script.pid) return;
setPosition(e.data);
}),
[],
);
// Listen to Logbox resizing events.
useEffect(
() =>
LogBoxSizeEvents.subscribe((e) => {
if (e.pid !== props.script.pid) return;
setSize([e.data.w, e.data.h]);
}),
[],
);
// initial position if 40%/30%
useEffect(() => setPosition({ x: window.innerWidth * 0.4, y: window.innerHeight * 0.3 }), []);
useEffect(() => { useEffect(() => {
updateLayer(); updateLayer();
const id = setInterval(rerender, 1000); const id = setInterval(rerender, 1000);
@ -203,18 +259,18 @@ function LogWindow(props: IProps): React.ReactElement {
// And trigger fakeDrag when the window is resized // And trigger fakeDrag when the window is resized
useEffect(() => { useEffect(() => {
window.addEventListener("resize", onResize); window.addEventListener("resize", onWindowResize);
return () => { return () => {
window.removeEventListener("resize", onResize); window.removeEventListener("resize", onWindowResize);
}; };
}, []); }, []);
const onResize = debounce((): void => { const onWindowResize = debounce((): void => {
const node = draggableRef?.current; const node = draggableRef?.current;
if (!node) return; if (!node) return;
if (!isOnScreen(node)) { if (!isOnScreen(node)) {
resetPosition(); setPosition({ x: 0, y: 0 });
} }
}, 100); }, 100);
@ -224,15 +280,6 @@ function LogWindow(props: IProps): React.ReactElement {
return !(bounds.right < 0 || bounds.bottom < 0 || bounds.left > innerWidth || bounds.top > outerWidth); return !(bounds.right < 0 || bounds.bottom < 0 || bounds.left > innerWidth || bounds.top > outerWidth);
}; };
const resetPosition = (): void => {
const node = rootRef?.current;
if (!node) return;
const state = node.state as { x: number; y: number };
state.x = 0;
state.y = 0;
node.setState(state);
};
const boundToBody = (e: DraggableEvent): void | false => { const boundToBody = (e: DraggableEvent): void | false => {
if ( if (
e instanceof MouseEvent && e instanceof MouseEvent &&
@ -251,8 +298,6 @@ function LogWindow(props: IProps): React.ReactElement {
sx={{ sx={{
flexFlow: "column", flexFlow: "column",
position: "fixed", position: "fixed",
left: "40%",
top: "30%",
zIndex: 1400, zIndex: 1400,
minWidth: `${minConstraints[0]}px`, minWidth: `${minConstraints[0]}px`,
minHeight: `${minConstraints[1]}px`, minHeight: `${minConstraints[1]}px`,
@ -270,8 +315,9 @@ function LogWindow(props: IProps): React.ReactElement {
ref={container} ref={container}
> >
<ResizableBox <ResizableBox
height={500} height={size[0]}
width={500} width={size[1]}
onResize={onResize}
minConstraints={minConstraints} minConstraints={minConstraints}
handle={ handle={
<span <span
@ -323,9 +369,7 @@ function LogWindow(props: IProps): React.ReactElement {
<span style={{ display: "flex", flexDirection: "column" }}> <span style={{ display: "flex", flexDirection: "column" }}>
{script.logs.map( {script.logs.map(
(line: string, i: number): JSX.Element => ( (line: string, i: number): JSX.Element => (
<React.Fragment key={i}> <ANSIITypography key={i} text={line} color={lineColor(line)} />
<ANSIITypography text={line} color={lineColor(line)} />
</React.Fragment>
), ),
)} )}
</span> </span>