diff --git a/package-lock.json b/package-lock.json index ceaaefbab..46064fd43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "@types/numeral": "^2.0.2", "@types/react": "^17.0.52", "@types/react-beautiful-dnd": "^13.1.3", - "@types/react-dom": "^17.0.18", + "@types/react-dom": "^17.0.20", "@types/react-resizable": "^3.0.3", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", @@ -4472,9 +4472,9 @@ } }, "node_modules/@types/react-dom": { - "version": "17.0.18", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.18.tgz", - "integrity": "sha512-rLVtIfbwyur2iFKykP2w0pl/1unw26b5td16d5xMgp7/yjTHomkyxPYChFoCr/FtEX1lN9wY6lFj1qvKdS5kDw==", + "version": "17.0.20", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.20.tgz", + "integrity": "sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA==", "dev": true, "dependencies": { "@types/react": "^17" @@ -18963,9 +18963,9 @@ } }, "@types/react-dom": { - "version": "17.0.18", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.18.tgz", - "integrity": "sha512-rLVtIfbwyur2iFKykP2w0pl/1unw26b5td16d5xMgp7/yjTHomkyxPYChFoCr/FtEX1lN9wY6lFj1qvKdS5kDw==", + "version": "17.0.20", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.20.tgz", + "integrity": "sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA==", "dev": true, "requires": { "@types/react": "^17" diff --git a/package.json b/package.json index e823f7412..c786b6da9 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@types/numeral": "^2.0.2", "@types/react": "^17.0.52", "@types/react-beautiful-dnd": "^13.1.3", - "@types/react-dom": "^17.0.18", + "@types/react-dom": "^17.0.20", "@types/react-resizable": "^3.0.3", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index ec5eeb004..45656a51e 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -548,7 +548,7 @@ export const RamCosts: RamCostTree = { alterReality: 0, rainbow: 0, heart: { break: 0 }, - iKnowWhatImDoing: 0, + tprintRaw: 0, printRaw: 0, formulas: { diff --git a/src/NetscriptFunctions/Extra.ts b/src/NetscriptFunctions/Extra.tsx similarity index 82% rename from src/NetscriptFunctions/Extra.ts rename to src/NetscriptFunctions/Extra.tsx index 6f101d3ff..6075d6ff1 100644 --- a/src/NetscriptFunctions/Extra.ts +++ b/src/NetscriptFunctions/Extra.tsx @@ -1,4 +1,4 @@ -import type React from "react"; +import React from "react"; import { Player } from "../Player"; import { Exploit } from "../Exploits/Exploit"; import * as bcrypt from "bcryptjs"; @@ -7,6 +7,10 @@ import { InternalAPI } from "../Netscript/APIWrapper"; import { helpers } from "../Netscript/NetscriptHelpers"; import { Terminal } from "../Terminal"; import { RamCostConstants } from "../Netscript/RamCostGenerator"; +import { CustomBoundary } from "../ui/Components/CustomBoundary"; + +// Incrementing value for custom element keys +let customElementKey = 0; export interface INetscriptExtra { heart: { @@ -17,7 +21,7 @@ export interface INetscriptExtra { bypass(doc: Document): void; alterReality(): void; rainbow(guess: string): void; - iKnowWhatImDoing(): void; + tprintRaw(value: React.ReactNode): void; printRaw(value: React.ReactNode): void; } @@ -67,14 +71,15 @@ export function NetscriptExtra(): InternalAPI { Player.giveExploit(Exploit.INeedARainbow); return true; }, - iKnowWhatImDoing: (ctx) => () => { - helpers.log(ctx, () => "Unlocking unsupported feature: window.tprintRaw"); - // @ts-expect-error window has no tprintRaw property defined - window.tprintRaw = Terminal.printRaw.bind(Terminal); + tprintRaw: () => (value) => { + Terminal.printRaw( + , + ); }, printRaw: (ctx) => (value) => { - // Using this voids the warranty on your tail log - ctx.workerScript.print(value as React.ReactNode); + ctx.workerScript.print( + , + ); }, }; } diff --git a/src/ui/Components/CustomBoundary.tsx b/src/ui/Components/CustomBoundary.tsx new file mode 100644 index 000000000..14ffe7062 --- /dev/null +++ b/src/ui/Components/CustomBoundary.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import Typography from "@mui/material/Typography"; + +interface CustomBoundaryProps { + children: React.ReactNode; +} +interface CustomBoundaryState { + error?: Error; +} +/** Error boundary for custom content printed by the player using printRaw-like functions. + * Error boundaries are required to be class components due to no hook equivalent to componentDidCatch. */ +export class CustomBoundary extends React.Component { + state: CustomBoundaryState; + constructor(props: CustomBoundaryProps) { + super(props); + this.state = { error: undefined }; + } + componentDidCatch(error: Error): void { + this.setState({ error }); + console.warn("Error in custom react content:"); + console.error(error); + } + render(): React.ReactNode { + if (this.state.error) { + // Typography is used because there are no default page styles. + // Span is used because it does not conflict with the DOM validation nesting (default Typography element of p is invalid at this location in dom tree) + return Error in custom react content. See console for details.; + } + return {this.props.children}; + } +}