From a560f5c0431ceabb186341c276af84a0c7dd829b Mon Sep 17 00:00:00 2001 From: Bobbanz1 Date: Mon, 18 Sep 2023 20:40:06 +0200 Subject: [PATCH 01/11] To have a way to revert --- code/controllers/subsystem/tgui.dm | 4 + code/modules/tgui/tgui.dm | 2 +- code/modules/tgui/tgui_window.dm | 46 ++- code/modules/tgui_panel/tgui_panel.dm | 2 +- dependencies.sh | 4 +- tgui/.gitignore | 1 + tgui/.yarnrc.yml | 2 + tgui/README.md | 20 +- tgui/docs/component-reference.md | 2 - tgui/docs/tgui-for-custom-html-popups.md | 259 ++++++++++++ tgui/docs/writing-tests.md | 6 +- tgui/global.d.ts | 55 ++- tgui/package.json | 6 +- tgui/packages/tgui-panel/index.js | 15 +- tgui/packages/tgui-panel/ping/middleware.js | 6 +- tgui/packages/tgui-panel/stat/StatTabs.js | 23 +- tgui/packages/tgui-panel/stat/StatText.js | 60 ++- tgui/packages/tgui-panel/stat/StatTicket.js | 9 +- tgui/packages/tgui-panel/stat/middleware.js | 8 +- tgui/packages/tgui-panel/telemetry.js | 11 +- .../{html5shiv.js => 00-html5shiv.js} | 0 .../tgui-polyfill/{ie8.js => 01-ie8.js} | 0 .../tgui-polyfill/{dom4.js => 02-dom4.js} | 0 .../tgui-polyfill/{css-om.js => 03-css-om.js} | 0 tgui/packages/tgui-polyfill/10-misc.js | 61 +++ tgui/packages/tgui-polyfill/index.js | 9 +- tgui/packages/tgui-polyfill/inferno.js | 10 - tgui/packages/tgui-polyfill/package.json | 6 + tgui/packages/tgui/backend.ts | 42 +- tgui/packages/tgui/components/ByondUi.js | 2 +- tgui/packages/tgui/components/Popper.tsx | 16 +- tgui/packages/tgui/debug/middleware.js | 4 +- tgui/packages/tgui/drag.js | 14 +- tgui/packages/tgui/focus.js | 2 +- tgui/packages/tgui/index.js | 15 +- tgui/packages/tgui/layouts/Layout.js | 4 +- tgui/packages/tgui/layouts/Window.js | 2 +- tgui/packages/tgui/links.js | 4 +- tgui/packages/tgui/logging.js | 4 +- tgui/packages/tgui/stories/ByondUi.stories.js | 2 +- tgui/public/tgui.html | 380 ++++++++++-------- tgui/yarn.lock | 1 + tools/bootstrap/node.bat | 8 +- tools/bootstrap/node_.ps1 | 4 +- 44 files changed, 727 insertions(+), 404 deletions(-) create mode 100644 tgui/docs/tgui-for-custom-html-popups.md rename tgui/packages/tgui-polyfill/{html5shiv.js => 00-html5shiv.js} (100%) rename tgui/packages/tgui-polyfill/{ie8.js => 01-ie8.js} (100%) rename tgui/packages/tgui-polyfill/{dom4.js => 02-dom4.js} (100%) rename tgui/packages/tgui-polyfill/{css-om.js => 03-css-om.js} (100%) create mode 100644 tgui/packages/tgui-polyfill/10-misc.js delete mode 100644 tgui/packages/tgui-polyfill/inferno.js diff --git a/code/controllers/subsystem/tgui.dm b/code/controllers/subsystem/tgui.dm index 848d83e06ae..f83c2edfcfc 100644 --- a/code/controllers/subsystem/tgui.dm +++ b/code/controllers/subsystem/tgui.dm @@ -25,6 +25,10 @@ SUBSYSTEM_DEF(tgui) /datum/controller/subsystem/tgui/PreInit() basehtml = file2text('tgui/public/tgui.html') + // Inject inline polyfills + var/polyfill = file2text('tgui/public/tgui-polyfill.min.js') + polyfill = "" + basehtml = replacetextEx(basehtml, "", polyfill) /datum/controller/subsystem/tgui/Shutdown() close_all_uis() diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index a91f5b06624..b3f1f070031 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -97,7 +97,7 @@ if(!window.is_ready()) window.initialize( fancy = (user.client.prefs.toggles & PREFTOGGLE_2_FANCY_TGUI), - inline_assets = list( + assets = list( get_asset_datum(/datum/asset/simple/tgui), )) else diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index ebc7759ce55..ff2434fd90b 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -18,8 +18,11 @@ var/message_queue var/sent_assets = list() // Vars passed to initialize proc (and saved for later) - var/inline_assets - var/fancy + var/initial_fancy + var/initial_assets + var/initial_inline_html + var/initial_inline_js + var/initial_inline_css /** * public @@ -44,19 +47,24 @@ * state. You can begin sending messages right after initializing. Messages * will be put into the queue until the window finishes loading. * - * optional inline_assets list List of assets to inline into the html. + * optional assets list List of assets to inline into the html. * optional inline_html string Custom HTML to inject. * optional fancy bool If TRUE, will hide the window titlebar. */ /datum/tgui_window/proc/initialize( - inline_assets = list(), + fancy = FALSE, + assets = list(), inline_html = "", - fancy = FALSE) + inline_js = "", + inline_css = "") log_tgui(client, "[id]/initialize") if(!client) return - src.inline_assets = inline_assets - src.fancy = fancy + src.initial_fancy = fancy + src.initial_assets = assets + src.initial_inline_html = inline_html + src.initial_inline_js = inline_js + src.initial_inline_css = inline_css status = TGUI_WINDOW_LOADING fatally_errored = FALSE // Build window options @@ -69,9 +77,9 @@ // Generate page html var/html = SStgui.basehtml html = replacetextEx(html, "\[tgui:windowId]", id) - // Inject inline assets + // Inject assets var/inline_assets_str = "" - for(var/datum/asset/asset in inline_assets) + for(var/datum/asset/asset in assets) var/mappings = asset.get_url_mappings() for(var/name in mappings) var/url = mappings[name] @@ -85,8 +93,17 @@ if(length(inline_assets_str)) inline_assets_str = "\n" html = replacetextEx(html, "\n", inline_assets_str) - // Inject custom HTML - html = replacetextEx(html, "\n", inline_html) + // Inject inline HTML + if (inline_html) + html = replacetextEx(html, "", inline_html) + // Inject inline JS + if (inline_js) + inline_js = "" + html = replacetextEx(html, "", inline_js) + // Inject inline CSS + if (inline_css) + inline_css = "" + html = replacetextEx(html, "", inline_css) // Open the window client << browse(html, "window=[id];[options]") // Detect whether the control is a browser @@ -314,7 +331,12 @@ client << link(href_list["url"]) if("cacheReloaded") // Reinitialize - initialize(inline_assets = inline_assets, fancy = fancy) + initialize( + fancy = initial_fancy, + assets = initial_assets, + inline_html = initial_inline_html, + inline_js = initial_inline_js, + inline_css = initial_inline_css) // Resend the assets for(var/asset in sent_assets) send_asset(asset) diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm index a4d67935823..ca544069b24 100644 --- a/code/modules/tgui_panel/tgui_panel.dm +++ b/code/modules/tgui_panel/tgui_panel.dm @@ -42,7 +42,7 @@ sleep(1) initialized_at = world.time // Perform a clean initialization - window.initialize(inline_assets = list( + window.initialize(assets = list( get_asset_datum(/datum/asset/simple/tgui_panel), )) window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome)) diff --git a/dependencies.sh b/dependencies.sh index b4f1d0f2771..d2aad950241 100755 --- a/dependencies.sh +++ b/dependencies.sh @@ -14,8 +14,8 @@ export RUST_VERSION=1.54.0 export RUST_G_VERSION=0.4.7.1 #node version -export NODE_VERSION=12 -export NODE_VERSION_PRECISE=12.22.4 +export NODE_VERSION=14 +export NODE_VERSION_PRECISE=14.16.1 # SpacemanDMM git tag export SPACEMAN_DMM_VERSION=suite-1.7.1 diff --git a/tgui/.gitignore b/tgui/.gitignore index 55091b21862..4d0dd666d88 100644 --- a/tgui/.gitignore +++ b/tgui/.gitignore @@ -16,6 +16,7 @@ package-lock.json /public/.tmp/**/* /public/**/* !/public/*.html +!/public/tgui-polyfill.min.js /coverage ## Previously ignored locations that are kept to avoid confusing git diff --git a/tgui/.yarnrc.yml b/tgui/.yarnrc.yml index 11371e4e665..53846c71f31 100644 --- a/tgui/.yarnrc.yml +++ b/tgui/.yarnrc.yml @@ -10,6 +10,8 @@ plugins: - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs spec: "@yarnpkg/plugin-interactive-tools" +pnpEnableEsmLoader: false + preferAggregateCacheInfo: true preferInteractive: true diff --git a/tgui/README.md b/tgui/README.md index e3d2ad045c5..0585d43645a 100644 --- a/tgui/README.md +++ b/tgui/README.md @@ -23,6 +23,13 @@ This project uses **Inferno** - a very fast UI rendering engine with a similar A If you were already familiar with an older, Ractive-based tgui, and want to translate concepts between old and new tgui, read this [interface conversion guide](docs/converting-old-tgui-interfaces.md). +### Other Documentation + +- [Component Reference](docs/component-reference.md) - UI building blocks +- [Using TGUI and Byond API for custom HTML popups](docs/tgui-for-custom-html-popups.md) +- [Chat Embedded Components](docs/chat-embedded-components.md) +- [Writing Tests](docs/writing-tests.md) + ## Pre-requisites If you are using the tooling provided in this repo, everything is included! Feel free to skip this step. @@ -32,10 +39,11 @@ If you are using the tooling provided in this repo, everything is included! Feel - [Git Bash](https://git-scm.com/downloads) or [MSys2](https://www.msys2.org/) (optional) -- [Node v12.20+](https://nodejs.org/en/download/) +- [Node v16.13+](https://nodejs.org/en/download/) + - **LTS** release is recommended instead of latest - **DO NOT install Chocolatey if Node installer asks you to!** - [Yarn v1.22.4+](https://yarnpkg.com/getting-started/install) - - You only need to run `npm install -g yarn`. + - You can run `npm install -g yarn` to install it. ## Usage @@ -86,8 +94,10 @@ Run `yarn install` once to install tgui dependencies. - `yarn tgui:test` - Run unit and integration tests. - `yarn tgui:analyze` - Run a bundle analyzer. - `yarn tgui:bench` - Run benchmarks. +- `yarn tgfont:build` - Build icon fonts. +- `yarn tgui-polyfill:build` - Build polyfills. You need to run it when updating any of the static (numbered) polyfills. -## Important memo +## Important Memo Remember to always run a full build of tgui before submitting a PR, because it comes with the full suite of CI checks, and runs much faster on your computer than on GitHub servers. It will save you some time and possibly a few broken commits! Address the issues that are reported by the tooling as much as possible, because maintainers will beat you with a ruler and force you to address them anyway (unless it's a false positive or something unfixable). @@ -142,10 +152,6 @@ When developing with `tgui-dev-server`, you will have access to certain developm - `/packages/tgui/styles/layouts` - Layout-related styles. - `/packages/tgui/styles/themes` - Contains themes that you can use in tgui. Each theme must be registered in `/packages/tgui/index.js` file. -## Component Reference - -See: [Component Reference](docs/component-reference.md). - ## License Source code is covered by /tg/station's parent license - **AGPL-3.0** (see the main [README](../README.md)), unless otherwise indicated. diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md index db1ad970fa9..8824ba5818b 100644 --- a/tgui/docs/component-reference.md +++ b/tgui/docs/component-reference.md @@ -74,8 +74,6 @@ to understand what this is about. - Lower case names are native browser events and should be used sparingly, for example when you need an explicit IE8 support. **DO NOT** use lowercase event handlers unless you really know what you are doing. -- [Button](#button) component does not support the lowercase `onclick` event. -Use the camel case `onClick` instead. ## `tgui/components` diff --git a/tgui/docs/tgui-for-custom-html-popups.md b/tgui/docs/tgui-for-custom-html-popups.md new file mode 100644 index 00000000000..f25060e9fa0 --- /dev/null +++ b/tgui/docs/tgui-for-custom-html-popups.md @@ -0,0 +1,259 @@ +# Using TGUI and Byond API for custom HTML popups + +TGUI in its current form would not exist without a very robust underlying layer that interfaces TGUI code with the BYOND browser component. This very layer can also be used to write simple and robust HTML popups, with access to many convenient APIs. In this article, you'll learn how to make a TGUI powered HTML popup and leverage all APIs that it provides. + +## How to create a window + +TGUI in order to create a window (popup) uses the `/datum/tgui_window` class. Feel free to take a look at its [source code](../../code/modules/tgui/tgui_window.dm), as all of its procs are very well documented. This class takes care of spawning the BYOND's browser element, normalizes the browser environment (because users might have IE8 on their system, or in future, it might be Microsoft Edge) and specifies a very rigid communication protocol between DM and JS. + +> **Notice:** Because `/datum/tgui_window` includes a lot of boilerplate in the final html that it displays in the browser, it is somewhat more expensive to render than a traditional, dumb popup using a `browse()` proc call. Therefore, its best to use it with static popups or very custom pieces of client-side code, e.g. stat panel, chat or a background music player. +Create a window that prints hello world. + +```dm +var/datum/tgui_window/window = new(usr.client, "custom_popup") +window.initialize( + inline_html = "

Hello world!

", +) +``` + +Here, `custom_popup` is a unique id for the BYOND skin element that this window uses, and it can be anything you want. If you want to reference a specific element from `interface/skin.dmf`, you can use that id instead, and UI will initialize inside of that element. This is how for example chat initializes itself, by using a `browseroutput` id, which is also specified in `interface/skin.dmf`. + +In case you want to re-initialize it with different content, you can do that as well by calling `initialize` again with different arguments. + +```dm +window.initialize( + inline_html = "

Hello world, but smaller!

", +) +``` + +You can close the window as easily as you've opened it. + +```dm +window.close() +``` + +## Sending assets + +TGUI in /tg/station codebase has `/datum/asset`, that packs scripts and stylesheets for delivery via CDN for efficiency. TGUI internally uses this asset system to render TGUI interfaces *proper* and TGUI chat. This is a snippet from internal TGUI code: + +```dm +window.initialize( + fancy = user.client.prefs.read_preference( + /datum/preference/toggle/tgui_fancy + ), + assets = list( + get_asset_datum(/datum/asset/simple/tgui), + )) +``` + +You can see two new arguments: + +- `fancy` - See [Fancy mode](#fancy-mode) +- `assets` - This is a list of asset datums, and all JS and CSS in the assets will be loaded in the page. + +Using asset datums has a big benefit over including ` +``` + +## Inlined HTML, CSS and JS + +You can also make a popup that doesn't rely on network requests to get JS and CSS. In the following case, the entirety of the page will be contained in a single HTML file. + +```dm +window.initialize( + inline_html = "

Hello world!

", + inline_js = "window.alert('Warning!')", + inline_css = "h1 { color: red }", +) +``` + +You can also do the same by splitting your code into separate files, and then leveraging tgui window to serve it all as one big HTML file. + +```dm +window.initialize( + inline_html = file2text('code/modules/thing/thing.html'), + inline_js = file2text('code/modules/thing/thing.js'), + inline_css = file2text('code/modules/thing/thing.css'), +) +``` + +If you need to inline multiple JS or CSS files, you can concatenate them for now, and separate contents of each file with an `\n` symbol. *This can be a point of improvement (add support for file lists)*. + +## Fancy mode + +You may have noticed the fancy mode in previous snippets: + +```dm +window.initialize(fancy = TRUE) +``` + +This removes the native window titlebar and border, which effectively turns window into a floating panel. TGUI heavily uses this option to draw completely custom, fancy windows. You can use it too, but not having the default titlebar limits usability of the browser window, since you can't even close it or drag around without implementing that functionality yourself. This mode might be useful for creating popups and tooltips. + +## Communication + +It is very often necessary to exchange data between DM and JS, and in vanilla BYOND programming it is a huge pain in the butt, because the `browse()` API is very convoluted, out of box it can send only strings, and sending data back to DM requires using hrefs. + +``` +location.href = '?src=12345¶m=1' +``` + +If you're familiar with the href syntax of BYOND topic calls, then perhaps this doesn't surprise you, but this API artificially limits you to sending 2048 characters of string-typed data; you need to reinvent the wheel if you want to send something more complex than strings. It differs from the way you send messages from DM. And it's very hard to read as well. + +Thankfully, TGUI implements a very robust protocol that makes this slightly less of an eye sore and very convenient to use in the long run. + +### Message structure + +```ts +{ + type: string; + payload?: any; + // ... +} +``` + +Each message always has a **type**, which is usually (but not always) the first argument on all message sending functions. The next property is the **payload**, which contains all the data sent in the message. + +You can think of it in these terms: + +- **type** - function name +- **payload** - function arguments + +Of course we're not working with functions here, but hopefully this analogy makes the concept easier to understand. + +Finally, message can contain custom properties, and how you use them is *completely up to you*. They have an important limitation - all additional properties are string-typed, and require you to use a slightly more verbose API for sending them (more about it in the next section). + +```js +Byond.sendMessage({ + type: 'click', + payload: { buttonId: 1 }, + popup_section: 'left', +}); +``` + +### DM ➡ JS + +To send a message from DM, you can use the `window.send_message()` proc. + +```dm +window.send_message("alert", list( + text = "Hello, world!", +)) +``` + +To receive it in JS, you have two different syntaxes. First one is the most verbose one, but allows receiving all types of messages, and deciding what to do via `if` conditions. + +> NOTE: We're using ECMAScript 5 syntax here, because this is the version that is supported by IE 11 natively without any additional compilation. If you're coding in a compiled environment (TGUI/Webpack), then feel free to use arrow functions and other fancy syntaxes. +```js +Byond.subscribe(function (type, payload) { + if (type === 'alert') { + window.alert(payload.text); + return; + } + if (type === 'other') { + // ... + return; + } + // ... +}); +``` + +Second one is more compact, because it already filters messages by type and passes the payload directly to the callback. + +```js +Byond.subscribeTo('alert', function (payload) { + window.alert(payload.text); +}); +``` + +### JS ➡ DM + +To send a message from JS, you can use the `Byond.sendMessage()` function. + +```js +Byond.sendMessage('click', { + button: 'explode-mech', +}); +``` + +To receive it in DM, you must register a delegate proc (callback) that will receive the messages (usually called `on_message`), and handle the message in that proc. + +```dm +/datum/my_object/proc/initialize() + // ... + window.subscribe(src, .proc/on_message) +/datum/my_object/proc/on_message(type, payload) + if (type == "click") + process_button_click(payload["button"]) + return +``` + +**Advanced variant** + +You can send messages with custom fields in case if you want to bypass JSON serialization of the **payload**. Not sending the **payload** is a little bit faster if you send a lot of messages (because BYOND is slow in general with proc calls, especially `json_decode`). All raw message fields are available in the third argument `href_list`. + +```js +Byond.sendMessage({ + type: "something", + ref: "[0x12345678]", +}); +``` + +```dm +/datum/my_object/proc/on_message(type, payload, href_list) + if (type == "something") + process_something(locate(href_list["ref"])) + return +``` + +## BYOND Skin API + +There is a full assortment of BYOND client-side features that you can access via the `Byond` API object. + +Full reference of the `Byond` API object is here: [global.d.ts](../global.d.ts). It's a global type definition file, which provides auto-completion in VSCode when coding TGUI interfaces. When writing custom popups outside of TGUI, autocompletion doesn't work, so you might need to peek into this file sometimes. + +Here's the summary of what it has. + +- `Byond.winget()` - Returns a property of a skin element. This is an async function call, more on that later. +- `Byond.winset()` - Sets a property of a skin element. +- `Byond.topic()` - Makes a Topic call to the server. Similar to `sendMessage`, but all topic calls are native to BYOND, string typed and processed in `/client/Topic()` proc. +- `Byond.command()` - Runs a command on the client, as if you typed it into the command bar yourself. Can be any verb, or a special client-side command, such as `.output`. + +> As of now, `Byond.winget()` requires a Promise polyfill, which is only available in compiled TGUI, but not in plain popups, and if you try using it, you'll get a bluescreen error. If you'd like to have winget in non-compiled contexts, then ping maintainers on Discord to request this feature. +When working with `winset` and `winget`, it can be very useful to consult [BYOND 5.0 controls and parameters guide](https://secure.byond.com/docs/ref/skinparams.html) to figure out what you can control in the BYOND client. Via these controls and parameters, you can do many interesting things, such as dynamically define BYOND macros, or show/hide and reposition various skin elements. + +Another source of information is the official [BYOND Reference](https://secure.byond.com/docs/ref/info.html#/{skin}), which is a much larger, but a more comprehensive doc. + +Id of the current tgui window can be accessed via `Byond.windowId`, and below in an example of changing its `size`. + +```js +Byond.winset(Byond.windowId, { + size: '1280x640', +}); +``` + +Id of the main SS13 window is `'mainwindow'`, as defined in [skin.dmf](../../interface/skin.dmf). + +Little known feature, but you can also get non-UI parameters on the client by using a `null` id. + +```js +// Fetch URL of a server client is currently connected to +Byond.winget(null, 'url').then((serverUrl) => { + // Connect to this server + Byond.call(serverUrl); + // Close our client because it is now connecting in background + Byond.command('.quit'); +}); +``` diff --git a/tgui/docs/writing-tests.md b/tgui/docs/writing-tests.md index 99cb39e158a..633e73bcd1a 100644 --- a/tgui/docs/writing-tests.md +++ b/tgui/docs/writing-tests.md @@ -10,11 +10,7 @@ test('something', () => { }); ``` -To run the tests, type the following into the terminal: - -``` -bin/tgui --test -``` +Refer to [README](../README.md) to learn how to run tests. There is an example test in `packages/common/react.spec.ts`. diff --git a/tgui/global.d.ts b/tgui/global.d.ts index 90e692a18ed..0503ed84521 100644 --- a/tgui/global.d.ts +++ b/tgui/global.d.ts @@ -21,12 +21,29 @@ declare module '*.svg' { export default content; } +type TguiMessage = { + type: string; + payload?: any; + [key: string]: any; +}; + type ByondType = { + /** + * ID of the Byond window this script is running on. + * Can be used as a parameter to winget/winset. + */ + windowId: string; + /** * True if javascript is running in BYOND. */ IS_BYOND: boolean; + /** + * Version of Trident engine of Internet Explorer. Null if N/A. + */ + TRIDENT: number | null; + /** * True if browser is IE8 or lower. */ @@ -79,14 +96,14 @@ type ByondType = { * * Returns a promise with a key-value object containing all properties. */ - winget(id: string): Promise; + winget(id: string | null): Promise; /** * Retrieves all properties of the BYOND skin element. * * Returns a promise with a key-value object containing all properties. */ - winget(id: string, propName: '*'): Promise; + winget(id: string | null, propName: '*'): Promise; /** * Retrieves an exactly one property of the BYOND skin element, @@ -94,7 +111,7 @@ type ByondType = { * * Returns a promise with the value of that property. */ - winget(id: string, propName: string): Promise; + winget(id: string | null, propNames: string[]): Promise; /** * Retrieves multiple properties of the BYOND skin element, @@ -105,27 +122,44 @@ type ByondType = { winget(id: string, propNames: string[]): Promise; /** - * Assigns properties to BYOND skin elements. + * Assigns properties to BYOND skin elements in bulk. */ winset(props: object): void; /** * Assigns properties to the BYOND skin element. */ - winset(id: string, props: object): void; + winset(id: string | null, props: object): void; /** * Sets a property on the BYOND skin element to a certain value. */ - winset(id: string, propName: string, propValue: any): void; + winset(id: string | null, propName: string, propValue: any): void; /** * Parses BYOND JSON. * - * Uses a special encoding to preverse Infinity and NaN. + * Uses a special encoding to preserve `Infinity` and `NaN`. */ parseJson(text: string): any; + /** + * Sends a message to `/datum/tgui_window` which hosts this window instance. + */ + sendMessage(type: string, payload?: any): void; + sendMessage(message: TguiMessage): void; + + /** + * Subscribe to incoming messages that were sent from `/datum/tgui_window`. + */ + subscribe(listener: (type: string, payload: any) => void): void; + + /** + * Subscribe to incoming messages *of some specific type* + * that were sent from `/datum/tgui_window`. + */ + subscribeTo(type: string, listener: (payload: any) => void): void; + /** * Loads a stylesheet into the document. */ @@ -144,12 +178,5 @@ type ByondType = { const Byond: ByondType; interface Window { - /** - * ID of the Byond window this script is running on. - * Should be used as a parameter to winget/winset. - */ - __windowId__: string; - __updateQueue__: unknown[]; - update: (msg: unknown) => unknown; Byond: ByondType; } diff --git a/tgui/package.json b/tgui/package.json index 59963c1a712..e9948ecdfc3 100644 --- a/tgui/package.json +++ b/tgui/package.json @@ -2,11 +2,12 @@ "private": true, "name": "tgui-workspace", "version": "4.3.0", + "packageManager": "yarn@3.1.1", "workspaces": [ "packages/*" ], "scripts": { - "tgui:build": "webpack", + "tgui:build": "BROWSERSLIST_IGNORE_OLD_DATA=true webpack", "tgui:analyze": "webpack --analyze", "tgui:dev": "node --experimental-modules packages/tgui-dev-server/index.js", "tgui:lint": "eslint packages --ext .js,.cjs,.ts,.tsx", @@ -59,6 +60,5 @@ "ajv": "^6.12.3", "minimist": "^1.2.6", "terser": "^5.14.2" - }, - "packageManager": "yarn@3.1.1" + } } diff --git a/tgui/packages/tgui-panel/index.js b/tgui/packages/tgui-panel/index.js index b20b1ef937a..f415c1afead 100644 --- a/tgui/packages/tgui-panel/index.js +++ b/tgui/packages/tgui-panel/index.js @@ -71,20 +71,11 @@ const setupApp = () => { setupPanelFocusHacks(); captureExternalLinks(); - // Subscribe for Redux state updates + // Re-render UI on store updates store.subscribe(renderApp); - // Subscribe for bankend updates - window.update = msg => store.dispatch(Byond.parseJson(msg)); - - // Process the early update queue - while (true) { - const msg = window.__updateQueue__.shift(); - if (!msg) { - break; - } - window.update(msg); - } + // Dispatch incoming messages as store actions + Byond.subscribe((type, payload) => store.dispatch({ type, payload })); // Unhide the panel Byond.winset('output', { diff --git a/tgui/packages/tgui-panel/ping/middleware.js b/tgui/packages/tgui-panel/ping/middleware.js index fdd84109571..3b504bfbcad 100644 --- a/tgui/packages/tgui-panel/ping/middleware.js +++ b/tgui/packages/tgui-panel/ping/middleware.js @@ -4,7 +4,6 @@ * @license MIT */ -import { sendMessage } from 'tgui/backend'; import { pingFail, pingSuccess } from './actions'; import { PING_INTERVAL, PING_QUEUE_SIZE, PING_TIMEOUT } from './constants'; @@ -22,10 +21,7 @@ export const pingMiddleware = store => { } const ping = { index, sentAt: Date.now() }; pings[index] = ping; - sendMessage({ - type: 'ping', - payload: { index }, - }); + Byond.sendMessage('ping', { index }); index = (index + 1) % PING_QUEUE_SIZE; }; return next => action => { diff --git a/tgui/packages/tgui-panel/stat/StatTabs.js b/tgui/packages/tgui-panel/stat/StatTabs.js index 8b6a18d8904..7cdb1a4524c 100644 --- a/tgui/packages/tgui-panel/stat/StatTabs.js +++ b/tgui/packages/tgui-panel/stat/StatTabs.js @@ -6,7 +6,6 @@ import { selectStatPanel } from './selectors'; import { StatStatus, HoboStatStatus } from './StatStatus'; import { StatText, HoboStatText } from './StatText'; import { StatTicket } from './StatTicket'; -import { sendMessage } from 'tgui/backend'; // ======================= // Flex Supported @@ -46,15 +45,14 @@ export const StatTabs = (props, context) => { sendMessage({ - type: 'stat/pressed', - payload: { + onEnter={(e, value) => Byond.sendMessage('stat/pressed', + { action_id: "ticket_message", params: { msg: value, }, - }, - })} /> + } + )} /> )} @@ -146,13 +144,14 @@ export const HoboStatTabs = (props, context) => { sendMessage({ - type: 'stat/pressed', - payload: { + onEnter={(e, value) => Byond.sendMessage('stat/pressed', + { action_id: "ticket_message", - params: value, - }, - })} /> + params: { + msg: value, + }, + } + )} /> )} diff --git a/tgui/packages/tgui-panel/stat/StatText.js b/tgui/packages/tgui-panel/stat/StatText.js index eed46c75f90..9db48d81872 100644 --- a/tgui/packages/tgui-panel/stat/StatText.js +++ b/tgui/packages/tgui-panel/stat/StatText.js @@ -1,11 +1,9 @@ import { useSelector } from 'common/redux'; -import { Button, Flex, Knob, Tabs, Box, Section, Fragment } from 'tgui/components'; +import { Button, Flex, Box, Section } from 'tgui/components'; import { useSettings } from '../settings'; import { selectStatPanel } from './selectors'; -import { sendMessage } from 'tgui/backend'; -import { Divider, Grid, Table } from '../../tgui/components'; -import { STAT_TEXT, STAT_BUTTON, STAT_ATOM, STAT_DIVIDER, STAT_VERB, STAT_BLANK } from './constants'; -import { sendLogEntry } from 'tgui-dev-server/link/client.cjs'; +import { Divider, Table } from '../../tgui/components'; +import { STAT_TEXT, STAT_BUTTON, STAT_ATOM, STAT_DIVIDER, STAT_BLANK } from './constants'; export const StatText = (props, context) => { const stat = useSelector(context, selectStatPanel); @@ -85,17 +83,15 @@ export const StatTextButton = (props, context) => { action_id, params = [], } = props; - const settings = useSettings(context); return ( @@ -195,13 +189,11 @@ export const StatTextVerb = (props, context) => { width="200px">