diff --git a/src/Netscript/NetscriptHelpers.ts b/src/Netscript/NetscriptHelpers.ts index 4b5e17ffe..feb334a3d 100644 --- a/src/Netscript/NetscriptHelpers.ts +++ b/src/Netscript/NetscriptHelpers.ts @@ -67,11 +67,33 @@ export const helpers = { 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. */ function string(ctx: NetscriptContext, argName: string, v: unknown): string { if (typeof v === "string") return v; 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. */ @@ -83,7 +105,7 @@ function number(ctx: NetscriptContext, argName: string, v: unknown): number { if (isNaN(v)) throw makeRuntimeErrorMsg(ctx, `'${argName}' is NaN.`); 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. */ diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index 4caab8e2e..89adf146c 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -335,6 +335,7 @@ const ui = { resetStyles: 0, getGameInfo: 0, clearTerminal: 0, + windowSize: 0, }; // Grafting API @@ -531,6 +532,8 @@ const SourceRamCosts = { mv: 0, tail: 0, toast: 0, + moveTail: 0, + resizeTail: 0, closeTail: 0, clearPort: 0, openDevMenu: 0, diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index e9ab76745..96952422f 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -38,7 +38,7 @@ import { WorkerScript } from "./Netscript/WorkerScript"; import { helpers } from "./Netscript/NetscriptHelpers"; import { numeralWrapper } from "./ui/numeralFormat"; 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 { isString } from "./utils/helpers/isString"; import { NetscriptGang } from "./NetscriptFunctions/Gang"; @@ -536,7 +536,22 @@ const base: InternalAPI = { 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: (ctx: NetscriptContext) => (_pid: unknown = ctx.workerScript.scriptRef.pid): void => { diff --git a/src/NetscriptFunctions/UserInterface.ts b/src/NetscriptFunctions/UserInterface.ts index 530247ba0..03ddf38ca 100644 --- a/src/NetscriptFunctions/UserInterface.ts +++ b/src/NetscriptFunctions/UserInterface.ts @@ -16,6 +16,9 @@ import { helpers } from "../Netscript/NetscriptHelpers"; export function NetscriptUserInterface(): InternalAPI { return { + windowSize: () => (): [number, number] => { + return [window.innerWidth, window.innerHeight]; + }, getTheme: () => (): UserInterfaceTheme => { return { ...Settings.theme }; }, diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index dce7036fa..f94f89537 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -4356,6 +4356,15 @@ interface Infiltration { * @public */ 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 * @remarks @@ -5014,6 +5023,32 @@ export interface NS { */ 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. * @remarks diff --git a/src/ui/React/LogBoxManager.tsx b/src/ui/React/LogBoxManager.tsx index a9a2e6479..eda1bb094 100644 --- a/src/ui/React/LogBoxManager.tsx +++ b/src/ui/React/LogBoxManager.tsx @@ -7,7 +7,7 @@ import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Paper from "@mui/material/Paper"; import Draggable, { DraggableEvent } from "react-draggable"; -import { ResizableBox } from "react-resizable"; +import { ResizableBox, ResizeCallbackData } from "react-resizable"; import makeStyles from "@mui/styles/makeStyles"; import createStyles from "@mui/styles/createStyles"; 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 LogBoxClearEvents = new EventEmitter<[]>(); +interface LogBoxUIEvent { + pid: number; + data: T; +} + +interface LogBoxPositionData { + x: number; + y: number; +} + +export const LogBoxPositionEvents = new EventEmitter<[LogBoxUIEvent]>(); + +interface LogBoxResizeData { + w: number; + h: number; +} + +export const LogBoxSizeEvents = new EventEmitter<[LogBoxUIEvent]>(); + interface Log { id: string; script: RunningScript; @@ -121,11 +140,16 @@ function LogWindow(props: IProps): React.ReactElement { const classes = useStyles(); const container = useRef(null); const setRerender = useState(false)[1]; + const [size, setSize] = useState<[number, number]>([500, 500]); const [minimized, setMinimized] = useState(false); function rerender(): void { setRerender((old) => !old); } + const onResize = (e: React.SyntheticEvent, { size }: ResizeCallbackData) => { + setSize([size.width, size.height]); + }; + // useEffect( // () => // 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(() => { updateLayer(); const id = setInterval(rerender, 1000); @@ -203,18 +259,18 @@ function LogWindow(props: IProps): React.ReactElement { // And trigger fakeDrag when the window is resized useEffect(() => { - window.addEventListener("resize", onResize); + window.addEventListener("resize", onWindowResize); return () => { - window.removeEventListener("resize", onResize); + window.removeEventListener("resize", onWindowResize); }; }, []); - const onResize = debounce((): void => { + const onWindowResize = debounce((): void => { const node = draggableRef?.current; if (!node) return; if (!isOnScreen(node)) { - resetPosition(); + setPosition({ x: 0, y: 0 }); } }, 100); @@ -224,15 +280,6 @@ function LogWindow(props: IProps): React.ReactElement { 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 => { if ( e instanceof MouseEvent && @@ -251,8 +298,6 @@ function LogWindow(props: IProps): React.ReactElement { sx={{ flexFlow: "column", position: "fixed", - left: "40%", - top: "30%", zIndex: 1400, minWidth: `${minConstraints[0]}px`, minHeight: `${minConstraints[1]}px`, @@ -270,8 +315,9 @@ function LogWindow(props: IProps): React.ReactElement { ref={container} > {script.logs.map( (line: string, i: number): JSX.Element => ( - - - + ), )}