diff --git a/.gitignore b/.gitignore index c6bba59..d3ef734 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,6 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +# Netscript Type Definitions. These are pulled from the game +NetscriptDefinitions.d.ts diff --git a/build.mjs b/build.mjs index 0f21066..009f706 100644 --- a/build.mjs +++ b/build.mjs @@ -8,11 +8,15 @@ const ctx = await context({ outbase: "./servers", outdir: "./dist", plugins: [BitburnerPlugin({ - port: 12525 + port: 12525, + servers: ['home'], + types: 'NetscriptDefinitions.d.ts' + })], bundle: true, format: 'esm', platform: 'browser', + logLevel: 'info' }); ctx.watch(); diff --git a/package-lock.json b/package-lock.json index c521165..6ab3a38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@types/react": "^18.2.25", + "@types/react-dom": "^18.2.10", "esbuild": "^0.19.4", "esbuild-bitburner-plugin": "file:../esbuild-bitburner-plugin", "glob": "^10.3.10" @@ -17,6 +19,9 @@ "../esbuild-bitburner-plugin": { "version": "1.0.0", "license": "MIT", + "dependencies": { + "websocket": "^1.0.34" + }, "devDependencies": { "esbuild": "^0.19.4" } @@ -376,6 +381,34 @@ "node": ">=14" } }, + "node_modules/@types/prop-types": { + "version": "15.7.8", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz", + "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==" + }, + "node_modules/@types/react": { + "version": "18.2.25", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.25.tgz", + "integrity": "sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.10.tgz", + "integrity": "sha512-5VEC5RgXIk1HHdyN1pHlg0cOqnxHzvPGpMMyGAP5qSaDRmyZNDaQ0kkVAkK6NYlDhP6YBID3llaXlmAS/mdgCA==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", + "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==" + }, "node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -440,6 +473,11 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", diff --git a/package.json b/package.json index 77abc48..3e29ecf 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ }, "homepage": "https://github.com/NilsRamstoeck/bb-external-editor#readme", "dependencies": { + "@types/react": "^18.2.25", + "@types/react-dom": "^18.2.10", "esbuild": "^0.19.4", "esbuild-bitburner-plugin": "file:../esbuild-bitburner-plugin", "glob": "^10.3.10" diff --git a/servers/home/example.js b/servers/home/example.js index 87102ab..18e40af 100644 --- a/servers/home/example.js +++ b/servers/home/example.js @@ -1,3 +1,4 @@ export async function main(ns){ + const a = 1; ns.tprint('hello world'); } diff --git a/servers/home/react.tsx b/servers/home/react.tsx new file mode 100644 index 0000000..d62482d --- /dev/null +++ b/servers/home/react.tsx @@ -0,0 +1,66 @@ +import { NS } from "NetscriptDefinitions"; + +import {render, unmountComponentAtNode} from 'react-dom'; +import React, {useState} from "react"; + +export async function main(ns: NS) { + + //i dont know a better way to open a window in bb, so feel free to use yours + + //print a string we can search for + const ID = `SCRIPT_ID:${ns.pid}`; + ns.tail(ns.pid, 'home'); + ns.print(ID); + + //wait for a bit to make sure the change took affect + await new Promise(resolve => setTimeout(() => resolve(), 100)); + + //grab the actual html element + const tailWindows = [...document.querySelectorAll('.react-resizable')]; + const spans = tailWindows.map(win => [...win.querySelectorAll('span')]).flat(); + const idSpan = spans.filter((cur) => cur.innerHTML == ID)[0]; + const windowEl = findRoot(idSpan); + + //now we can render our react stuff + render(
, windowEl); + + if(false)useState(); + + //make sure to unmount our react stuff on exit + ns.atExit(() => { + unmountComponentAtNode(windowEl); + }); + + //return a promise that resolves when the tail window gets closed so our script terminates properly + return new Promise(resolve => watchElForDeletion(windowEl, () => resolve())); +} + +//find root of tail window +function findRoot(span: Element) { + let el = span; + while (!el.parentElement.classList.contains('react-resizable')) + el = el.parentElement; + return el; +} + +function watchElForDeletion(elToWatch: Element, callback: () => void) { + const parent = document.body; + const observer = new MutationObserver(function (mutations) { + + // loop through all mutations + mutations.forEach(function (mutation) { + // check for changes to the child list + if (mutation.type === 'childList') { + mutation.removedNodes.forEach(node => !containsRecursive(node, elToWatch) || callback()); + } + }); + }); + // start observing the parent - defaults to document body + observer.observe(parent, { childList: true, subtree: true }); +}; + +//check if an element is part of another elements subtree +function containsRecursive(container: Node | Element, child: Element) { + if (!('children' in container)) return; + return [...container.children].reduce((prev, cur) => prev || cur == child || containsRecursive(cur, child), false); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9eef447 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "node", + "esModuleInterop": true, + "lib": [ + "esnext", + "dom", + "dom.iterable" + ], + "baseUrl": "./", + "jsx": "react", + "paths": { + "@/*": [ + "src/*" + ], + "NetscriptDefinitions" : ["NetscriptDefinitions.d.ts"] + } + }, + "include": [ + "servers/**/*.ts*" + ] +}