From 0b11c941b19f2038fdd2fed7703ffbda6c7cb630 Mon Sep 17 00:00:00 2001 From: Paul Mullen <101871009+mullenpaul@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:52:31 +0000 Subject: [PATCH] React Migration - part 2 (#5493) Switches TGUI from Inferno to React. Part 2 of the PR https://github.com/cmss13-devs/cmss13/pull/5435 Based heavily on the work from: https://github.com/tgstation/tgstation/pull/80044/files React is a more suitable framework
Screenshots & Videos Put screenshots and videos here with an empty line between the screenshots and the `
` tags.
:cl: refactor: switched from infernojs to react /:cl: --- tgui/.eslintignore | 1 - tgui/babel.config.js | 40 ++- tgui/package.json | 5 +- tgui/packages/common/keys.ts | 39 +++ tgui/packages/common/react.ts | 9 - tgui/packages/common/redux.ts | 16 - tgui/packages/tgui-bench/package.json | 3 +- .../packages/tgui-bench/tests/Button.test.tsx | 2 +- .../tgui-panel/{Panel.js => Panel.jsx} | 14 +- .../tgui-panel/audio/NowPlayingWidget.js | 71 ----- .../tgui-panel/audio/NowPlayingWidget.jsx | 109 +++++++ tgui/packages/tgui-panel/audio/hooks.js | 8 +- .../tgui-panel/audio/{index.js => index.ts} | 0 .../tgui-panel/chat/ChatPageSettings.js | 8 +- tgui/packages/tgui-panel/chat/ChatPanel.js | 2 +- tgui/packages/tgui-panel/chat/ChatTabs.js | 10 +- tgui/packages/tgui-panel/chat/renderer.js | 2 +- tgui/packages/tgui-panel/game/hooks.js | 6 +- .../tgui-panel/{index.js => index.jsx} | 11 +- tgui/packages/tgui-panel/package.json | 4 +- .../packages/tgui-panel/ping/PingIndicator.js | 6 +- .../{SettingsPanel.js => SettingsPanel.jsx} | 32 +- tgui/packages/tgui-panel/settings/hooks.js | 8 +- .../packages/tgui-say/components/dragzone.tsx | 2 +- .../tgui-say/helpers/ChannelIterator.ts | 50 ++++ tgui/packages/tgui-say/helpers/ChatHistory.ts | 59 ++++ tgui/packages/tgui-say/interfaces/TguiSay.tsx | 12 +- tgui/packages/tgui-say/package.json | 3 +- tgui/packages/tgui-say/timers.ts | 19 ++ tgui/packages/tgui-say/types/index.tsx | 2 +- tgui/packages/tgui/backend.ts | 48 +-- .../tgui/components/AnimatedNumber.tsx | 2 +- tgui/packages/tgui/components/Autofocus.tsx | 26 +- tgui/packages/tgui/components/Blink.jsx | 2 +- .../tgui/components/BodyZoneSelector.tsx | 2 +- tgui/packages/tgui/components/Box.tsx | 277 +++++++++--------- tgui/packages/tgui/components/Button.jsx | 4 +- tgui/packages/tgui/components/ByondUi.jsx | 2 +- tgui/packages/tgui/components/Chart.jsx | 2 +- tgui/packages/tgui/components/Collapsible.jsx | 2 +- .../tgui/components/DraggableControl.jsx | 2 +- tgui/packages/tgui/components/Dropdown.jsx | 161 ---------- tgui/packages/tgui/components/Dropdown.tsx | 215 ++++++++++++++ tgui/packages/tgui/components/FitText.tsx | 15 +- tgui/packages/tgui/components/Flex.tsx | 81 +++-- .../tgui/components/InfinitePlane.jsx | 2 +- tgui/packages/tgui/components/Input.jsx | 2 +- tgui/packages/tgui/components/KeyListener.tsx | 6 +- tgui/packages/tgui/components/LabeledList.tsx | 95 +++--- tgui/packages/tgui/components/NumberInput.jsx | 35 ++- tgui/packages/tgui/components/Popper.tsx | 146 ++++----- .../{ProgressBar.jsx => ProgressBar.tsx} | 57 ++-- .../tgui/components/RestrictedInput.jsx | 2 +- tgui/packages/tgui/components/Section.tsx | 149 ++++------ tgui/packages/tgui/components/Stack.tsx | 31 +- tgui/packages/tgui/components/TextArea.jsx | 6 +- tgui/packages/tgui/components/TimeDisplay.jsx | 2 +- tgui/packages/tgui/components/Tooltip.tsx | 64 ++-- .../tgui/components/TrackOutsideClicks.tsx | 8 +- tgui/packages/tgui/debug/KitchenSink.jsx | 6 +- tgui/packages/tgui/debug/hooks.js | 4 +- tgui/packages/tgui/drag.ts | 4 +- tgui/packages/tgui/index.tsx | 13 +- tgui/packages/tgui/interfaces/AcidVest.jsx | 4 +- tgui/packages/tgui/interfaces/Adminhelp.tsx | 12 +- tgui/packages/tgui/interfaces/AlertModal.tsx | 14 +- .../tgui/interfaces/AlmayerControl.jsx | 10 +- .../interfaces/AltitudeControlConsole.jsx | 4 +- .../tgui/interfaces/AntiAirConsole.jsx | 9 +- tgui/packages/tgui/interfaces/Apc.jsx | 6 +- .../tgui/interfaces/AresInterface.jsx | 64 ++-- .../tgui/interfaces/Autodispenser.jsx | 4 +- tgui/packages/tgui/interfaces/Autolathe.jsx | 33 +-- tgui/packages/tgui/interfaces/Binoculars.jsx | 4 +- .../tgui/interfaces/BioSyntheticPrinter.jsx | 4 +- .../packages/tgui/interfaces/BotanyEditor.jsx | 10 +- .../tgui/interfaces/BotanyExtractor.jsx | 10 +- tgui/packages/tgui/interfaces/BrigCell.jsx | 8 +- .../tgui/interfaces/CameraConsole.jsx | 10 +- tgui/packages/tgui/interfaces/CanvasLayer.jsx | 2 +- tgui/packages/tgui/interfaces/CardMod.jsx | 29 +- tgui/packages/tgui/interfaces/Centrifuge.jsx | 4 +- tgui/packages/tgui/interfaces/Changelog.jsx | 8 +- .../tgui/interfaces/ChemDispenser.jsx | 4 +- tgui/packages/tgui/interfaces/ChooseFruit.jsx | 10 +- tgui/packages/tgui/interfaces/ChooseResin.jsx | 10 +- .../tgui/interfaces/ColorMatrixEditor.tsx | 4 +- .../tgui/interfaces/CommandTablet.jsx | 4 +- tgui/packages/tgui/interfaces/CrewConsole.jsx | 8 +- tgui/packages/tgui/interfaces/Cryo.jsx | 4 +- tgui/packages/tgui/interfaces/DemoSim.jsx | 4 +- tgui/packages/tgui/interfaces/Disposals.jsx | 4 +- tgui/packages/tgui/interfaces/DrawnMap.jsx | 2 +- .../tgui/interfaces/DropshipFlightControl.tsx | 45 ++- .../tgui/interfaces/ElevatorControl.tsx | 20 +- .../tgui/interfaces/EscapePodConsole.tsx | 4 +- tgui/packages/tgui/interfaces/FaxMachine.jsx | 20 +- tgui/packages/tgui/interfaces/Filteriffic.jsx | 48 ++- .../tgui/interfaces/FiltrationControl.jsx | 4 +- tgui/packages/tgui/interfaces/HealthScan.jsx | 16 +- tgui/packages/tgui/interfaces/HiveFaction.jsx | 4 +- tgui/packages/tgui/interfaces/HiveLeaders.jsx | 4 +- tgui/packages/tgui/interfaces/HiveStatus.jsx | 48 +-- tgui/packages/tgui/interfaces/JoeEmotes.tsx | 11 +- tgui/packages/tgui/interfaces/KeyBinds.jsx | 30 +- tgui/packages/tgui/interfaces/KillPanel.jsx | 14 +- .../packages/tgui/interfaces/LanguageMenu.jsx | 10 +- tgui/packages/tgui/interfaces/ListInput.jsx | 30 +- .../tgui/interfaces/ListInputModal.tsx | 24 +- tgui/packages/tgui/interfaces/MarkMenu.tsx | 25 +- tgui/packages/tgui/interfaces/MedalsPanel.jsx | 10 +- tgui/packages/tgui/interfaces/Microwave.tsx | 4 +- tgui/packages/tgui/interfaces/Mortar.jsx | 20 +- .../tgui/interfaces/NavigationShuttle.tsx | 39 ++- tgui/packages/tgui/interfaces/NuclearBomb.jsx | 4 +- .../tgui/interfaces/NumberInputModal.tsx | 10 +- tgui/packages/tgui/interfaces/Orbit/index.tsx | 56 ++-- .../tgui/interfaces/OrbitalCannonConsole.jsx | 4 +- .../tgui/interfaces/OverwatchConsole.jsx | 63 ++-- .../tgui/interfaces/PartFabricator.jsx | 8 +- .../interfaces/ParticleEdit/EntriesBasic.tsx | 36 +-- .../ParticleEdit/EntriesGenerators.tsx | 22 +- .../interfaces/ParticleEdit/Generators.tsx | 6 +- .../tgui/interfaces/ParticleEdit/Tutorial.tsx | 4 +- .../tgui/interfaces/ParticleEdit/index.tsx | 6 +- tgui/packages/tgui/interfaces/PhoneMenu.jsx | 16 +- tgui/packages/tgui/interfaces/PlayerPanel.jsx | 55 ++-- tgui/packages/tgui/interfaces/Playtime.tsx | 10 +- tgui/packages/tgui/interfaces/PodLauncher.jsx | 134 ++++----- .../tgui/interfaces/PortableVendor.tsx | 12 +- tgui/packages/tgui/interfaces/Proximity.jsx | 4 +- .../tgui/interfaces/PublishedDocsHud.tsx | 4 +- tgui/packages/tgui/interfaces/Radar.tsx | 18 +- tgui/packages/tgui/interfaces/Radio.jsx | 4 +- .../tgui/interfaces/ResearchDoorDisplay.jsx | 4 +- .../tgui/interfaces/ResearchMemories.jsx | 16 +- .../tgui/interfaces/ResearchTerminal.tsx | 74 ++--- tgui/packages/tgui/interfaces/STUI.jsx | 28 +- .../tgui/interfaces/SelfDestructConsole.jsx | 9 +- tgui/packages/tgui/interfaces/Sentencing.jsx | 25 +- tgui/packages/tgui/interfaces/SentryGunUI.tsx | 97 +++--- .../tgui/interfaces/ShuttleManipulator.tsx | 17 +- tgui/packages/tgui/interfaces/Signaller.jsx | 4 +- tgui/packages/tgui/interfaces/SkillsMenu.jsx | 14 +- tgui/packages/tgui/interfaces/Sleeper.jsx | 35 ++- tgui/packages/tgui/interfaces/SmartFridge.tsx | 36 +-- tgui/packages/tgui/interfaces/Smes.jsx | 4 +- tgui/packages/tgui/interfaces/SquadInfo.tsx | 46 ++- tgui/packages/tgui/interfaces/SquadMod.jsx | 4 +- .../tgui/interfaces/StatbrowserOptions.jsx | 16 +- .../tgui/interfaces/StationAlertConsole.jsx | 9 +- .../tgui/interfaces/SupplyDropConsole.jsx | 4 +- .../tgui/interfaces/TacmapAdminPanel.jsx | 10 +- tgui/packages/tgui/interfaces/TacticalMap.tsx | 21 +- tgui/packages/tgui/interfaces/Tank.jsx | 4 +- tgui/packages/tgui/interfaces/TechControl.jsx | 6 +- .../{TechMemories.jsx => TechMemories.tsx} | 52 +++- tgui/packages/tgui/interfaces/TechNode.jsx | 4 +- .../tgui/interfaces/TeleporterConsole.jsx | 4 +- .../tgui/interfaces/TextInputModal.tsx | 14 +- tgui/packages/tgui/interfaces/Timer.jsx | 4 +- .../tgui/interfaces/VehicleStatus.jsx | 22 +- tgui/packages/tgui/interfaces/Vending.tsx | 36 +-- .../tgui/interfaces/VendingSorted.tsx | 37 ++- tgui/packages/tgui/interfaces/VoteMenu.jsx | 18 +- tgui/packages/tgui/interfaces/VoxPanel.jsx | 47 ++- .../packages/tgui/interfaces/VultureScope.tsx | 38 +-- tgui/packages/tgui/interfaces/WeaponStats.jsx | 97 +++--- tgui/packages/tgui/interfaces/Wires.jsx | 9 +- tgui/packages/tgui/interfaces/WorkingJoe.jsx | 36 +-- .../packages/tgui/interfaces/YautjaEmotes.tsx | 11 +- .../tgui/interfaces/common/AccessList.jsx | 8 +- .../interfaces/common/ElectricalPanel.tsx | 20 +- .../tgui/interfaces/common/InputButtons.tsx | 4 +- .../common/InterfaceLockNoticeBox.jsx | 4 +- .../tgui/interfaces/common/TimedCallback.tsx | 2 +- tgui/packages/tgui/layouts/NtosWindow.jsx | 4 +- tgui/packages/tgui/layouts/Pane.jsx | 6 +- tgui/packages/tgui/layouts/Window.jsx | 202 ------------- tgui/packages/tgui/layouts/Window.tsx | 231 +++++++++++++++ tgui/packages/tgui/package.json | 7 +- tgui/packages/tgui/renderer.ts | 2 +- tgui/packages/tgui/routes.tsx | 13 +- tgui/packages/tgui/store.ts | 19 +- tgui/packages/tgui/stories/Blink.stories.jsx | 2 +- .../tgui/stories/BlockQuote.stories.jsx | 2 +- tgui/packages/tgui/stories/Box.stories.jsx | 2 +- tgui/packages/tgui/stories/Button.stories.jsx | 2 +- .../packages/tgui/stories/ByondUi.stories.jsx | 3 +- .../tgui/stories/Collapsible.stories.jsx | 2 +- tgui/packages/tgui/stories/Flex.stories.jsx | 14 +- tgui/packages/tgui/stories/Input.stories.jsx | 6 +- .../tgui/stories/LabeledList.stories.jsx | 2 +- .../tgui/stories/ProgressBar.stories.jsx | 6 +- tgui/packages/tgui/stories/Stack.stories.jsx | 2 +- .../packages/tgui/stories/Storage.stories.jsx | 2 +- tgui/packages/tgui/stories/Tabs.stories.jsx | 10 +- tgui/packages/tgui/stories/Themes.stories.jsx | 4 +- tgui/yarn.lock | 272 ++++++++++++----- 199 files changed, 2597 insertions(+), 2403 deletions(-) create mode 100644 tgui/packages/common/keys.ts rename tgui/packages/tgui-panel/{Panel.js => Panel.jsx} (93%) delete mode 100644 tgui/packages/tgui-panel/audio/NowPlayingWidget.js create mode 100644 tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx rename tgui/packages/tgui-panel/audio/{index.js => index.ts} (100%) rename tgui/packages/tgui-panel/{index.js => index.jsx} (94%) rename tgui/packages/tgui-panel/settings/{SettingsPanel.js => SettingsPanel.jsx} (90%) create mode 100644 tgui/packages/tgui-say/helpers/ChannelIterator.ts create mode 100644 tgui/packages/tgui-say/helpers/ChatHistory.ts create mode 100644 tgui/packages/tgui-say/timers.ts delete mode 100644 tgui/packages/tgui/components/Dropdown.jsx create mode 100644 tgui/packages/tgui/components/Dropdown.tsx rename tgui/packages/tgui/components/{ProgressBar.jsx => ProgressBar.tsx} (50%) rename tgui/packages/tgui/interfaces/{TechMemories.jsx => TechMemories.tsx} (75%) delete mode 100644 tgui/packages/tgui/layouts/Window.jsx create mode 100644 tgui/packages/tgui/layouts/Window.tsx diff --git a/tgui/.eslintignore b/tgui/.eslintignore index a59187b933..7965d0731a 100644 --- a/tgui/.eslintignore +++ b/tgui/.eslintignore @@ -3,4 +3,3 @@ /**/*.bundle.* /**/*.chunk.* /**/*.hot-update.* -/packages/inferno/** diff --git a/tgui/babel.config.js b/tgui/babel.config.js index e702c9a711..69a5a401cc 100644 --- a/tgui/babel.config.js +++ b/tgui/babel.config.js @@ -6,28 +6,36 @@ const createBabelConfig = (options) => { const { presets = [], plugins = [], removeConsole } = options; - // prettier-ignore return { presets: [ - [require.resolve('@babel/preset-typescript'), { - allowDeclareFields: true, - }], - [require.resolve('@babel/preset-env'), { - modules: 'commonjs', - useBuiltIns: 'entry', - corejs: '3', - spec: false, - loose: true, - targets: [], - }], + [ + require.resolve('@babel/preset-typescript'), + { + allowDeclareFields: true, + }, + ], + [ + require.resolve('@babel/preset-env'), + { + modules: 'commonjs', + useBuiltIns: 'entry', + corejs: '3.3.2', + spec: false, + loose: true, + targets: [], + }, + ], + [require.resolve('@babel/preset-react'), { runtime: 'automatic' }], ...presets, ].filter(Boolean), plugins: [ - [require.resolve('@babel/plugin-proposal-class-properties'), { - loose: true, - }], + [ + require.resolve('@babel/plugin-transform-class-properties'), + { + loose: true, + }, + ], require.resolve('@babel/plugin-transform-jscript'), - require.resolve('babel-plugin-inferno'), removeConsole && require.resolve('babel-plugin-transform-remove-console'), require.resolve('common/string.babel-plugin.cjs'), ...plugins, diff --git a/tgui/package.json b/tgui/package.json index c52d0f754b..77ff3be9b0 100644 --- a/tgui/package.json +++ b/tgui/package.json @@ -22,9 +22,10 @@ "dependencies": { "@babel/core": "^7.15.0", "@babel/eslint-parser": "^7.15.0", - "@babel/plugin-proposal-class-properties": "^7.14.5", + "@babel/plugin-transform-class-properties": "^7.23.3", "@babel/plugin-transform-jscript": "^7.14.5", "@babel/preset-env": "^7.15.0", + "@babel/preset-react": "^7.23.3", "@babel/preset-typescript": "^7.15.0", "@types/jest": "^27.0.1", "@types/jsdom": "^16.2.13", @@ -34,7 +35,6 @@ "@typescript-eslint/parser": "^4.29.1", "babel-jest": "^27.0.6", "babel-loader": "^8.2.2", - "babel-plugin-inferno": "^6.3.0", "babel-plugin-transform-remove-console": "^6.9.4", "common": "workspace:*", "css-loader": "^5.2.7", @@ -44,7 +44,6 @@ "eslint-plugin-react": "^7.24.0", "eslint-plugin-unused-imports": "^1.1.4", "file-loader": "^6.2.0", - "inferno": "^7.4.8", "jest": "^27.0.6", "jest-circus": "^27.0.6", "jsdom": "^16.7.0", diff --git a/tgui/packages/common/keys.ts b/tgui/packages/common/keys.ts new file mode 100644 index 0000000000..34ac9e1614 --- /dev/null +++ b/tgui/packages/common/keys.ts @@ -0,0 +1,39 @@ +/** + * ### Key codes. + * event.keyCode is deprecated, use this reference instead. + * + * Handles modifier keys (Shift, Alt, Control) and arrow keys. + * + * For alphabetical keys, use the actual character (e.g. 'a') instead of the key code. + * + * Something isn't here that you want? Just add it: + * @url https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + * @usage + * ```ts + * import { KEY } from 'tgui/common/keys'; + * + * if (event.key === KEY.Enter) { + * // do something + * } + * ``` + */ +export enum KEY { + Alt = 'Alt', + Backspace = 'Backspace', + Control = 'Control', + Delete = 'Delete', + Down = 'ArrowDown', + End = 'End', + Enter = 'Enter', + Escape = 'Escape', + Home = 'Home', + Insert = 'Insert', + Left = 'ArrowLeft', + PageDown = 'PageDown', + PageUp = 'PageUp', + Right = 'ArrowRight', + Shift = 'Shift', + Space = ' ', + Tab = 'Tab', + Up = 'ArrowUp', +} diff --git a/tgui/packages/common/react.ts b/tgui/packages/common/react.ts index 8e42d0971a..dd64309534 100644 --- a/tgui/packages/common/react.ts +++ b/tgui/packages/common/react.ts @@ -51,15 +51,6 @@ export const shallowDiffers = (a: object, b: object) => { return false; }; -/** - * Default inferno hooks for pure components. - */ -export const pureComponentHooks = { - onComponentShouldUpdate: (lastProps, nextProps) => { - return shallowDiffers(lastProps, nextProps); - }, -}; - /** * A helper to determine whether the object is renderable by React. */ diff --git a/tgui/packages/common/redux.ts b/tgui/packages/common/redux.ts index 4e618bddaf..7b4999d93b 100644 --- a/tgui/packages/common/redux.ts +++ b/tgui/packages/common/redux.ts @@ -194,19 +194,3 @@ export const createAction = ( return actionCreator; }; - -// Implementation specific -// -------------------------------------------------------- - -export const useDispatch = (context: { - store: Store; -}): Dispatch => { - return context.store.dispatch; -}; - -export const useSelector = ( - context: { store: Store }, - selector: (state: State) => Selected -): Selected => { - return selector(context.store.getState()); -}; diff --git a/tgui/packages/tgui-bench/package.json b/tgui/packages/tgui-bench/package.json index 49bc0c423c..7cdb0ce7e3 100644 --- a/tgui/packages/tgui-bench/package.json +++ b/tgui/packages/tgui-bench/package.json @@ -6,10 +6,9 @@ "common": "workspace:*", "fastify": "^3.29.4", "fastify-static": "^4.2.3", - "inferno": "^7.4.8", - "inferno-vnode-flags": "^7.4.8", "lodash": "^4.17.21", "platform": "^1.3.6", + "react": "^18.2.0", "tgui": "workspace:*" } } diff --git a/tgui/packages/tgui-bench/tests/Button.test.tsx b/tgui/packages/tgui-bench/tests/Button.test.tsx index 6b806d720a..6340e915c0 100644 --- a/tgui/packages/tgui-bench/tests/Button.test.tsx +++ b/tgui/packages/tgui-bench/tests/Button.test.tsx @@ -1,4 +1,4 @@ -import { linkEvent } from 'inferno'; +import { linkEvent } from 'react'; import { Button } from 'tgui/components'; import { createRenderer } from 'tgui/renderer'; diff --git a/tgui/packages/tgui-panel/Panel.js b/tgui/packages/tgui-panel/Panel.jsx similarity index 93% rename from tgui/packages/tgui-panel/Panel.js rename to tgui/packages/tgui-panel/Panel.jsx index 83150ab6ef..cb6b141649 100644 --- a/tgui/packages/tgui-panel/Panel.js +++ b/tgui/packages/tgui-panel/Panel.jsx @@ -14,17 +14,17 @@ import { PingIndicator } from './ping'; import { ReconnectButton } from './reconnect'; import { SettingsPanel, useSettings } from './settings'; -export const Panel = (props, context) => { +export const Panel = (props) => { // IE8-10: Needs special treatment due to missing Flex support if (Byond.IS_LTE_IE10) { return ; } - const audio = useAudio(context); - const settings = useSettings(context); - const game = useGame(context); + const audio = useAudio(); + const settings = useSettings(); + const game = useGame(); if (process.env.NODE_ENV !== 'production') { const { useDebug, KitchenSink } = require('tgui/debug'); - const debug = useDebug(context); + const debug = useDebug(); if (debug.kitchenSink) { return ; } @@ -103,8 +103,8 @@ export const Panel = (props, context) => { ); }; -const HoboPanel = (props, context) => { - const settings = useSettings(context); +const HoboPanel = (props) => { + const settings = useSettings(); return ( diff --git a/tgui/packages/tgui-panel/audio/NowPlayingWidget.js b/tgui/packages/tgui-panel/audio/NowPlayingWidget.js deleted file mode 100644 index 672ecfad7c..0000000000 --- a/tgui/packages/tgui-panel/audio/NowPlayingWidget.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { toFixed } from 'common/math'; -import { useDispatch, useSelector } from 'common/redux'; -import { Button, Flex, Knob } from 'tgui/components'; -import { useSettings } from '../settings'; -import { selectAudio } from './selectors'; - -export const NowPlayingWidget = (props, context) => { - const audio = useSelector(context, selectAudio); - const dispatch = useDispatch(context); - const settings = useSettings(context); - const title = audio.meta?.title; - return ( - - {(audio.playing && ( - <> - - Now playing: - - - {title || 'Unknown Track'} - - - )) || ( - - Nothing to play. - - )} - {audio.playing && ( - - @@ -82,9 +82,9 @@ export class TguiSay extends Component<{}, State> { )} diff --git a/tgui/packages/tgui-say/package.json b/tgui/packages/tgui-say/package.json index 5ee9e43260..6fc66ade82 100644 --- a/tgui/packages/tgui-say/package.json +++ b/tgui/packages/tgui-say/package.json @@ -3,8 +3,9 @@ "name": "tgui-say", "version": "1.0.0", "dependencies": { + "@types/react": "^18.2.39", "common": "workspace:*", - "inferno": "^7.4.8", + "react": "^18.2.0", "tgui": "workspace:*", "tgui-polyfill": "workspace:*" } diff --git a/tgui/packages/tgui-say/timers.ts b/tgui/packages/tgui-say/timers.ts new file mode 100644 index 0000000000..7b9fffc87f --- /dev/null +++ b/tgui/packages/tgui-say/timers.ts @@ -0,0 +1,19 @@ +import { debounce, throttle } from 'common/timer'; + +const SECONDS = 1000; + +/** Timers: Prevents overloading the server, throttles messages */ +export const byondMessages = { + // Debounce: Prevents spamming the server + channelIncrementMsg: debounce( + (visible: boolean) => Byond.sendMessage('thinking', { visible }), + 0.4 * SECONDS + ), + forceSayMsg: debounce( + (entry: string) => Byond.sendMessage('force', { entry, channel: 'Say' }), + 1 * SECONDS, + true + ), + // Throttle: Prevents spamming the server + typingMsg: throttle(() => Byond.sendMessage('typing'), 4 * SECONDS), +} as const; diff --git a/tgui/packages/tgui-say/types/index.tsx b/tgui/packages/tgui-say/types/index.tsx index abd29630ff..02da4b1dde 100644 --- a/tgui/packages/tgui-say/types/index.tsx +++ b/tgui/packages/tgui-say/types/index.tsx @@ -1,4 +1,4 @@ -import { RefObject } from 'inferno'; +import { RefObject } from 'react'; export type Modal = { events: Events; diff --git a/tgui/packages/tgui/backend.ts b/tgui/packages/tgui/backend.ts index dd4cae9e62..746e511a50 100644 --- a/tgui/packages/tgui/backend.ts +++ b/tgui/packages/tgui/backend.ts @@ -21,6 +21,12 @@ import { resumeRenderer, suspendRenderer } from './renderer'; const logger = createLogger('backend'); +export let globalStore; + +export const setGlobalStore = (store) => { + globalStore = store; +}; + export const backendUpdate = createAction('backend/update'); export const backendSetSharedState = createAction('backend/setSharedState'); export const backendSuspendStart = createAction('backend/suspendStart'); @@ -267,6 +273,10 @@ type BackendState = { shared: Record; suspending: boolean; suspended: boolean; + debug?: { + debugLayout: boolean; + kitchenSink: boolean; + }; }; /** @@ -280,9 +290,9 @@ export const selectBackend = (state: any): BackendState => * * Includes the `act` function for performing DM actions. */ -export const useBackend = (context: any) => { - const { store } = context; - const state = selectBackend(store.getState()); +export const useBackend = () => { + const state: BackendState = globalStore?.getState()?.backend; + return { ...state, act: sendAct, @@ -308,18 +318,16 @@ type StateWithSetter = [T, (nextState: T) => void]; * @param initialState Initializes your global variable with this value. */ export const useLocalState = ( - context: any, key: string, initialState: T ): StateWithSetter => { - const { store } = context; - const state = selectBackend(store.getState()); - const sharedStates = state.shared ?? {}; + const state = globalStore?.getState()?.backend; + const sharedStates = state?.shared ?? {}; const sharedState = key in sharedStates ? sharedStates[key] : initialState; return [ sharedState, (nextState) => { - store.dispatch( + globalStore.dispatch( backendSetSharedState({ key, nextState: @@ -347,27 +355,31 @@ export const useLocalState = ( * @param initialState Initializes your global variable with this value. */ export const useSharedState = ( - context: any, key: string, initialState: T ): StateWithSetter => { - const { store } = context; - const state = selectBackend(store.getState()); - const sharedStates = state.shared ?? {}; + const state = globalStore?.getState()?.backend; + const sharedStates = state?.shared ?? {}; const sharedState = key in sharedStates ? sharedStates[key] : initialState; return [ sharedState, (nextState) => { - // prettier-ignore Byond.sendMessage({ type: 'setSharedState', key, - value: JSON.stringify( - typeof nextState === 'function' - ? nextState(sharedState) - : nextState - ) || '', + value: + JSON.stringify( + typeof nextState === 'function' ? nextState(sharedState) : nextState + ) || '', }); }, ]; }; + +export const useDispatch = () => { + return globalStore.dispatch; +}; + +export const useSelector = (selector: (state: any) => any) => { + return selector(globalStore?.getState()); +}; diff --git a/tgui/packages/tgui/components/AnimatedNumber.tsx b/tgui/packages/tgui/components/AnimatedNumber.tsx index ed3be2e5ef..956dc0a823 100644 --- a/tgui/packages/tgui/components/AnimatedNumber.tsx +++ b/tgui/packages/tgui/components/AnimatedNumber.tsx @@ -5,7 +5,7 @@ */ import { clamp, toFixed } from 'common/math'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; const isSafeNumber = (value: number) => { // prettier-ignore diff --git a/tgui/packages/tgui/components/Autofocus.tsx b/tgui/packages/tgui/components/Autofocus.tsx index 28945dd7aa..a0b3f6f765 100644 --- a/tgui/packages/tgui/components/Autofocus.tsx +++ b/tgui/packages/tgui/components/Autofocus.tsx @@ -1,19 +1,17 @@ -import { Component, createRef } from 'inferno'; +import { createRef, PropsWithChildren, useEffect } from 'react'; -export class Autofocus extends Component { - ref = createRef(); +export const Autofocus = (props: PropsWithChildren) => { + const ref = createRef(); - componentDidMount() { + useEffect(() => { setTimeout(() => { - this.ref.current?.focus(); + ref.current?.focus(); }, 1); - } + }, []); - render() { - return ( -
- {this.props.children} -
- ); - } -} + return ( +
+ {props.children} +
+ ); +}; diff --git a/tgui/packages/tgui/components/Blink.jsx b/tgui/packages/tgui/components/Blink.jsx index bd781336b4..70d2f1393e 100644 --- a/tgui/packages/tgui/components/Blink.jsx +++ b/tgui/packages/tgui/components/Blink.jsx @@ -1,4 +1,4 @@ -import { Component } from 'inferno'; +import { Component } from 'react'; const DEFAULT_BLINKING_INTERVAL = 1000; const DEFAULT_BLINKING_TIME = 1000; diff --git a/tgui/packages/tgui/components/BodyZoneSelector.tsx b/tgui/packages/tgui/components/BodyZoneSelector.tsx index eb845458cd..5daf2960a5 100644 --- a/tgui/packages/tgui/components/BodyZoneSelector.tsx +++ b/tgui/packages/tgui/components/BodyZoneSelector.tsx @@ -1,4 +1,4 @@ -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { resolveAsset } from '../assets'; import { Box } from './Box'; diff --git a/tgui/packages/tgui/components/Box.tsx b/tgui/packages/tgui/components/Box.tsx index ed10072b62..8da3235553 100644 --- a/tgui/packages/tgui/components/Box.tsx +++ b/tgui/packages/tgui/components/Box.tsx @@ -4,59 +4,58 @@ * @license MIT */ -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; -import { createVNode, InfernoNode, SFC } from 'inferno'; -import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; +import { BooleanLike, classes } from 'common/react'; +import { createElement, ReactNode } from 'react'; import { CSS_COLORS } from '../constants'; export type BoxProps = { [key: string]: any; - as?: string; - className?: string | BooleanLike; - children?: InfernoNode; - position?: string | BooleanLike; - overflow?: string | BooleanLike; - overflowX?: string | BooleanLike; - overflowY?: string | BooleanLike; - top?: string | BooleanLike; - bottom?: string | BooleanLike; - left?: string | BooleanLike; - right?: string | BooleanLike; - width?: string | BooleanLike; - minWidth?: string | BooleanLike; - maxWidth?: string | BooleanLike; - height?: string | BooleanLike; - minHeight?: string | BooleanLike; - maxHeight?: string | BooleanLike; - fontSize?: string | BooleanLike; - fontFamily?: string; - lineHeight?: string | BooleanLike; - opacity?: number; - textAlign?: string | BooleanLike; - verticalAlign?: string | BooleanLike; - inline?: BooleanLike; - bold?: BooleanLike; - italic?: BooleanLike; - nowrap?: BooleanLike; - preserveWhitespace?: BooleanLike; - m?: string | BooleanLike; - mx?: string | BooleanLike; - my?: string | BooleanLike; - mt?: string | BooleanLike; - mb?: string | BooleanLike; - ml?: string | BooleanLike; - mr?: string | BooleanLike; - p?: string | BooleanLike; - px?: string | BooleanLike; - py?: string | BooleanLike; - pt?: string | BooleanLike; - pb?: string | BooleanLike; - pl?: string | BooleanLike; - pr?: string | BooleanLike; - color?: string | BooleanLike; - textColor?: string | BooleanLike; - backgroundColor?: string | BooleanLike; - fillPositionedParent?: boolean; + readonly as?: string; + readonly className?: string | BooleanLike; + readonly children?: ReactNode; + readonly position?: string | BooleanLike; + readonly overflow?: string | BooleanLike; + readonly overflowX?: string | BooleanLike; + readonly overflowY?: string | BooleanLike; + readonly top?: string | BooleanLike; + readonly bottom?: string | BooleanLike; + readonly left?: string | BooleanLike; + readonly right?: string | BooleanLike; + readonly width?: string | BooleanLike; + readonly minWidth?: string | BooleanLike; + readonly maxWidth?: string | BooleanLike; + readonly height?: string | BooleanLike; + readonly minHeight?: string | BooleanLike; + readonly maxHeight?: string | BooleanLike; + readonly fontSize?: string | BooleanLike; + readonly fontFamily?: string; + readonly lineHeight?: string | BooleanLike; + readonly opacity?: number; + readonly textAlign?: string | BooleanLike; + readonly verticalAlign?: string | BooleanLike; + readonly inline?: BooleanLike; + readonly bold?: BooleanLike; + readonly italic?: BooleanLike; + readonly nowrap?: BooleanLike; + readonly preserveWhitespace?: BooleanLike; + readonly m?: string | BooleanLike; + readonly mx?: string | BooleanLike; + readonly my?: string | BooleanLike; + readonly mt?: string | BooleanLike; + readonly mb?: string | BooleanLike; + readonly ml?: string | BooleanLike; + readonly mr?: string | BooleanLike; + readonly p?: string | BooleanLike; + readonly px?: string | BooleanLike; + readonly py?: string | BooleanLike; + readonly pt?: string | BooleanLike; + readonly pb?: string | BooleanLike; + readonly pl?: string | BooleanLike; + readonly pr?: string | BooleanLike; + readonly color?: string | BooleanLike; + readonly textColor?: string | BooleanLike; + readonly backgroundColor?: string | BooleanLike; + readonly fillPositionedParent?: boolean; }; /** @@ -65,15 +64,12 @@ export type BoxProps = { export const unit = (value: unknown): string | undefined => { if (typeof value === 'string') { // Transparently convert pixels into rem units - if (value.endsWith('px') && !Byond.IS_LTE_IE8) { + if (value.endsWith('px')) { return parseFloat(value) / 12 + 'rem'; } return value; } if (typeof value === 'number') { - if (Byond.IS_LTE_IE8) { - return value * 12 + 'px'; - } return value + 'rem'; } }; @@ -128,70 +124,66 @@ const mapColorPropTo = (attrName) => (style, value) => { } }; -const styleMapperByPropName = { - // Direct mapping - position: mapRawPropTo('position'), - overflow: mapRawPropTo('overflow'), - overflowX: mapRawPropTo('overflow-x'), - overflowY: mapRawPropTo('overflow-y'), - top: mapUnitPropTo('top', unit), +// String / number props +const stringStyleMap = { bottom: mapUnitPropTo('bottom', unit), + fontFamily: mapRawPropTo('fontFamily'), + fontSize: mapUnitPropTo('fontSize', unit), + height: mapUnitPropTo('height', unit), left: mapUnitPropTo('left', unit), + maxHeight: mapUnitPropTo('maxHeight', unit), + maxWidth: mapUnitPropTo('maxWidth', unit), + minHeight: mapUnitPropTo('minHeight', unit), + minWidth: mapUnitPropTo('minWidth', unit), + opacity: mapRawPropTo('opacity'), + overflow: mapRawPropTo('overflow'), + overflowX: mapRawPropTo('overflowX'), + overflowY: mapRawPropTo('overflowY'), + position: mapRawPropTo('position'), right: mapUnitPropTo('right', unit), + textAlign: mapRawPropTo('textAlign'), + top: mapUnitPropTo('top', unit), + verticalAlign: mapRawPropTo('verticalAlign'), width: mapUnitPropTo('width', unit), - minWidth: mapUnitPropTo('min-width', unit), - maxWidth: mapUnitPropTo('max-width', unit), - height: mapUnitPropTo('height', unit), - minHeight: mapUnitPropTo('min-height', unit), - maxHeight: mapUnitPropTo('max-height', unit), - fontSize: mapUnitPropTo('font-size', unit), - fontFamily: mapRawPropTo('font-family'), + lineHeight: (style, value) => { if (typeof value === 'number') { - style['line-height'] = value; + style['lineHeight'] = value; } else if (typeof value === 'string') { - style['line-height'] = unit(value); + style['lineHeight'] = unit(value); } }, - opacity: mapRawPropTo('opacity'), - textAlign: mapRawPropTo('text-align'), - verticalAlign: mapRawPropTo('vertical-align'), - // Boolean props - inline: mapBooleanPropTo('display', 'inline-block'), - bold: mapBooleanPropTo('font-weight', 'bold'), - italic: mapBooleanPropTo('font-style', 'italic'), - nowrap: mapBooleanPropTo('white-space', 'nowrap'), - preserveWhitespace: mapBooleanPropTo('white-space', 'pre-wrap'), // Margin m: mapDirectionalUnitPropTo('margin', halfUnit, [ - 'top', - 'bottom', - 'left', - 'right', + 'Top', + 'Bottom', + 'Left', + 'Right', ]), - mx: mapDirectionalUnitPropTo('margin', halfUnit, ['left', 'right']), - my: mapDirectionalUnitPropTo('margin', halfUnit, ['top', 'bottom']), - mt: mapUnitPropTo('margin-top', halfUnit), - mb: mapUnitPropTo('margin-bottom', halfUnit), - ml: mapUnitPropTo('margin-left', halfUnit), - mr: mapUnitPropTo('margin-right', halfUnit), + mx: mapDirectionalUnitPropTo('margin', halfUnit, ['Left', 'Right']), + my: mapDirectionalUnitPropTo('margin', halfUnit, ['Top', 'Bottom']), + mt: mapUnitPropTo('marginTop', halfUnit), + mb: mapUnitPropTo('marginBottom', halfUnit), + ml: mapUnitPropTo('marginLeft', halfUnit), + mr: mapUnitPropTo('marginRight', halfUnit), // Padding p: mapDirectionalUnitPropTo('padding', halfUnit, [ - 'top', - 'bottom', - 'left', - 'right', + 'Top', + 'Bottom', + 'Left', + 'Right', ]), - px: mapDirectionalUnitPropTo('padding', halfUnit, ['left', 'right']), - py: mapDirectionalUnitPropTo('padding', halfUnit, ['top', 'bottom']), - pt: mapUnitPropTo('padding-top', halfUnit), - pb: mapUnitPropTo('padding-bottom', halfUnit), - pl: mapUnitPropTo('padding-left', halfUnit), - pr: mapUnitPropTo('padding-right', halfUnit), + px: mapDirectionalUnitPropTo('padding', halfUnit, ['Left', 'Right']), + py: mapDirectionalUnitPropTo('padding', halfUnit, ['Top', 'Bottom']), + pt: mapUnitPropTo('paddingTop', halfUnit), + pb: mapUnitPropTo('paddingBottom', halfUnit), + pl: mapUnitPropTo('paddingLeft', halfUnit), + pr: mapUnitPropTo('paddingRight', halfUnit), // Color props color: mapColorPropTo('color'), textColor: mapColorPropTo('color'), - backgroundColor: mapColorPropTo('background-color'), + backgroundColor: mapColorPropTo('backgroundColor'), + // Utility props fillPositionedParent: (style, value) => { if (value) { @@ -202,44 +194,42 @@ const styleMapperByPropName = { style['right'] = 0; } }, -}; +} as const; + +// Boolean props +const booleanStyleMap = { + bold: mapBooleanPropTo('fontWeight', 'bold'), + inline: mapBooleanPropTo('display', 'inline-block'), + italic: mapBooleanPropTo('fontStyle', 'italic'), + nowrap: mapBooleanPropTo('whiteSpace', 'nowrap'), + preserveWhitespace: mapBooleanPropTo('whiteSpace', 'pre-wrap'), +} as const; + +export const computeBoxProps = (props) => { + const computedProps: Record = {}; + const computedStyles: Record = {}; -export const computeBoxProps = (props: BoxProps) => { - const computedProps: HTMLAttributes = {}; - const computedStyles = {}; // Compute props for (let propName of Object.keys(props)) { if (propName === 'style') { continue; } - // IE8: onclick workaround - if (Byond.IS_LTE_IE8 && propName === 'onClick') { - computedProps.onclick = props[propName]; - continue; - } + const propValue = props[propName]; - const mapPropToStyle = styleMapperByPropName[propName]; + + const mapPropToStyle = + stringStyleMap[propName] || booleanStyleMap[propName]; + if (mapPropToStyle) { mapPropToStyle(computedStyles, propValue); } else { computedProps[propName] = propValue; } } - // Concatenate styles - let style = ''; - for (let attrName of Object.keys(computedStyles)) { - const attrValue = computedStyles[attrName]; - style += attrName + ':' + attrValue + ';'; - } - if (props.style) { - for (let attrName of Object.keys(props.style)) { - const attrValue = props.style[attrName]; - style += attrName + ':' + attrValue + ';'; - } - } - if (style.length > 0) { - computedProps.style = style; - } + + // Merge computed styles and any directly provided styles + computedProps.style = { ...computedStyles, ...props.style }; + return computedProps; }; @@ -252,27 +242,26 @@ export const computeBoxClassName = (props: BoxProps) => { ]); }; -export const Box: SFC = (props: BoxProps) => { +export const Box = (props: BoxProps) => { const { as = 'div', className, children, ...rest } = props; - // Render props - if (typeof children === 'function') { - return children(computeBoxProps(props)); - } - const computedClassName = - typeof className === 'string' - ? className + ' ' + computeBoxClassName(rest) - : computeBoxClassName(rest); + + // Compute class name and styles + const computedClassName = className + ? `${className} ${computeBoxClassName(rest)}` + : computeBoxClassName(rest); const computedProps = computeBoxProps(rest); - // Render a wrapper element - return createVNode( - VNodeFlags.HtmlElement, - as, - computedClassName, - children, - ChildFlags.UnknownChildren, - computedProps, - undefined + + if (as === 'img') { + computedProps.style['-ms-interpolation-mode'] = 'nearest-neighbor'; + } + + // Render the component + return createElement( + typeof as === 'string' ? as : 'div', + { + ...computedProps, + className: computedClassName, + }, + children ); }; - -Box.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/Button.jsx b/tgui/packages/tgui/components/Button.jsx index ef434d04c7..4264b07671 100644 --- a/tgui/packages/tgui/components/Button.jsx +++ b/tgui/packages/tgui/components/Button.jsx @@ -6,7 +6,7 @@ import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from 'common/keycodes'; import { classes, pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { createLogger } from '../logging'; import { Box, computeBoxClassName, computeBoxProps } from './Box'; import { Icon } from './Icon'; @@ -273,7 +273,7 @@ export class ButtonInput extends Component { className="NumberInput__input" style={{ 'display': !this.state.inInput ? 'none' : undefined, - 'text-align': 'left', + 'textAlign': 'left', }} onBlur={(e) => { if (!this.state.inInput) { diff --git a/tgui/packages/tgui/components/ByondUi.jsx b/tgui/packages/tgui/components/ByondUi.jsx index 4623fe5770..dc263b9482 100644 --- a/tgui/packages/tgui/components/ByondUi.jsx +++ b/tgui/packages/tgui/components/ByondUi.jsx @@ -6,7 +6,7 @@ import { shallowDiffers } from 'common/react'; import { debounce } from 'common/timer'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { createLogger } from '../logging'; import { computeBoxProps } from './Box'; diff --git a/tgui/packages/tgui/components/Chart.jsx b/tgui/packages/tgui/components/Chart.jsx index fac444bd1d..b8a9c62f89 100644 --- a/tgui/packages/tgui/components/Chart.jsx +++ b/tgui/packages/tgui/components/Chart.jsx @@ -6,7 +6,7 @@ import { map, zipWith } from 'common/collections'; import { pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { Box } from './Box'; const normalizeData = (data, scale, rangeX, rangeY) => { diff --git a/tgui/packages/tgui/components/Collapsible.jsx b/tgui/packages/tgui/components/Collapsible.jsx index f91eeddb45..805fc7d129 100644 --- a/tgui/packages/tgui/components/Collapsible.jsx +++ b/tgui/packages/tgui/components/Collapsible.jsx @@ -4,7 +4,7 @@ * @license MIT */ -import { Component } from 'inferno'; +import { Component } from 'react'; import { Box } from './Box'; import { Button } from './Button'; diff --git a/tgui/packages/tgui/components/DraggableControl.jsx b/tgui/packages/tgui/components/DraggableControl.jsx index 8ada6f2fa4..018118dd79 100644 --- a/tgui/packages/tgui/components/DraggableControl.jsx +++ b/tgui/packages/tgui/components/DraggableControl.jsx @@ -6,7 +6,7 @@ import { clamp } from 'common/math'; import { pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { AnimatedNumber } from './AnimatedNumber'; const DEFAULT_UPDATE_RATE = 400; diff --git a/tgui/packages/tgui/components/Dropdown.jsx b/tgui/packages/tgui/components/Dropdown.jsx deleted file mode 100644 index e6fe8a840a..0000000000 --- a/tgui/packages/tgui/components/Dropdown.jsx +++ /dev/null @@ -1,161 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { classes } from 'common/react'; -import { Component } from 'inferno'; -import { Box } from './Box'; -import { Icon } from './Icon'; - -export class Dropdown extends Component { - constructor(props) { - super(props); - this.state = { - selected: props.selected, - open: false, - }; - this.handleClick = () => { - if (this.state.open) { - this.setOpen(false); - } - }; - } - - componentWillUnmount() { - window.removeEventListener('click', this.handleClick); - } - - setOpen(open) { - this.setState({ open: open }); - if (open) { - setTimeout(() => { - window.addEventListener('click', this.handleClick); - }); - this.menuRef.focus(); - } else { - window.removeEventListener('click', this.handleClick); - } - } - - setSelected(selected) { - this.setState({ - selected: selected, - }); - this.setOpen(false); - this.props.onSelected(selected); - } - - buildMenu() { - const { options = [] } = this.props; - const ops = options.map((option) => { - let displayText, value; - - if (typeof option === 'string') { - displayText = option; - value = option; - } else { - displayText = option.displayText; - value = option.value; - } - - return ( - { - this.setSelected(value); - }}> - {displayText} - - ); - }); - return ops.length ? ops : 'No Options Found'; - } - - render() { - const { props } = this; - const { - icon, - iconRotation, - iconSpin, - clipSelectedText = true, - color = 'default', - dropdownStyle, - over, - noscroll, - nochevron, - width, - openWidth = width, - onClick, - onOpen, - selected, - disabled, - displayText, - ...boxProps - } = props; - const { className, ...rest } = boxProps; - - const adjustedOpen = over ? !this.state.open : this.state.open; - - const menu = this.state.open ? ( -
{ - this.menuRef = menu; - }} - tabIndex="-1" - style={{ - 'width': openWidth, - }} - className={classes([ - (noscroll && 'Dropdown__menu-noscroll') || 'Dropdown__menu', - over && 'Dropdown__over', - ])}> - {this.buildMenu()} -
- ) : null; - - return ( -
- { - if (disabled && !this.state.open) { - return; - } - this.setOpen(!this.state.open); - - if (props.onOpen) { - props.onOpen(event); - } - }}> - {icon && ( - - )} - - {displayText ? displayText : this.state.selected} - - {!!nochevron || ( - - - - )} - - {menu} -
- ); - } -} diff --git a/tgui/packages/tgui/components/Dropdown.tsx b/tgui/packages/tgui/components/Dropdown.tsx new file mode 100644 index 0000000000..b7491c84dc --- /dev/null +++ b/tgui/packages/tgui/components/Dropdown.tsx @@ -0,0 +1,215 @@ +import { classes } from 'common/react'; +import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; + +import { BoxProps, unit } from './Box'; +import { Button } from './Button'; +import { Icon } from './Icon'; +import { Popper } from './Popper'; + +type DropdownEntry = { + displayText: ReactNode; + value: string | number; +}; + +type DropdownOption = string | DropdownEntry; + +type Props = { + /** An array of strings which will be displayed in the + dropdown when open. See Dropdown.tsx for more advanced usage with DropdownEntry */ + readonly options: DropdownOption[]; + /** Called when a value is picked from the list, `value` is the value that was picked */ + readonly onSelected: (value: any) => void; +} & Partial<{ + /** Whether to display previous / next buttons */ + buttons: boolean; + /** Whether to clip the selected text */ + clipSelectedText: boolean; + /** Color of dropdown button */ + color: string; + /** Disables the dropdown */ + disabled: boolean; + /** Text to always display in place of the selected text */ + displayText: ReactNode; + /** Icon to display in dropdown button */ + icon: string; + /** Angle of the icon */ + iconRotation: number; + /** Whether or not the icon should spin */ + iconSpin: boolean; + /** Width of the dropdown menu. Default: 15rem */ + menuWidth: string; + /** Whether or not the arrow on the right hand side of the dropdown button is visible */ + noChevron: boolean; + /** Called when dropdown button is clicked */ + onClick: (event) => void; + /** Dropdown renders over instead of below */ + over: boolean; + /** Currently selected entry */ + selected: string | number; +}> & + BoxProps; + +const getOptionValue = (option: DropdownOption) => { + return typeof option === 'string' ? option : option.value; +}; + +export const Dropdown = (props: Props) => { + const { + buttons, + className, + clipSelectedText = true, + color = 'default', + disabled, + displayText, + icon, + iconRotation, + iconSpin, + menuWidth = '15rem', + noChevron, + onClick, + onSelected, + options = [], + over, + selected, + width, + } = props; + + const [open, setOpen] = useState(false); + const adjustedOpen = over ? !open : open; + const innerRef = useRef(null); + + /** Update the selected value when clicking the left/right buttons */ + const updateSelected = useCallback( + (direction: 'previous' | 'next') => { + if (options.length < 1 || disabled) { + return; + } + const startIndex = 0; + const endIndex = options.length - 1; + + let selectedIndex = options.findIndex( + (option) => getOptionValue(option) === selected + ); + + if (selectedIndex < 0) { + selectedIndex = direction === 'next' ? endIndex : startIndex; + } + + let newIndex = selectedIndex; + if (direction === 'next') { + newIndex = selectedIndex === endIndex ? startIndex : selectedIndex++; + } else { + newIndex = selectedIndex === startIndex ? endIndex : selectedIndex--; + } + + onSelected?.(getOptionValue(options[newIndex])); + }, + [disabled, onSelected, options, selected] + ); + + /** Allows the menu to be scrollable on open */ + useEffect(() => { + if (!open) return; + + innerRef.current?.focus(); + }, [open]); + + return ( + setOpen(false)} + placement={over ? 'top-start' : 'bottom-start'} + content={ +
+ {options.length === 0 && ( +
No options
+ )} + + {options.map((option, index) => { + const value = getOptionValue(option); + + return ( +
{ + setOpen(false); + onSelected?.(value); + }}> + {typeof option === 'string' ? option : option.displayText} +
+ ); + })} +
+ }> +
+
+
{ + if (disabled && !open) { + return; + } + setOpen(!open); + onClick?.(event); + }}> + {icon && ( + + )} + + {displayText || selected} + + {!noChevron && ( + + + + )} +
+ {buttons && ( + <> +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/components/FitText.tsx b/tgui/packages/tgui/components/FitText.tsx index 751a2f8980..0632626aeb 100644 --- a/tgui/packages/tgui/components/FitText.tsx +++ b/tgui/packages/tgui/components/FitText.tsx @@ -1,4 +1,4 @@ -import { Component, createRef, RefObject } from 'inferno'; +import { Component, createRef, HTMLAttributes, PropsWithChildren, RefObject } from 'react'; const DEFAULT_ACCEPTABLE_DIFFERENCE = 5; @@ -7,7 +7,7 @@ type Props = { readonly maxWidth: number; readonly maxFontSize: number; readonly native?: HTMLAttributes; -}; +} & PropsWithChildren; type State = { fontSize: number; @@ -19,8 +19,8 @@ export class FitText extends Component { fontSize: 0, }; - constructor() { - super(); + constructor(props: Props) { + super(props); this.resize = this.resize.bind(this); @@ -80,9 +80,10 @@ export class FitText extends Component { {this.props.children} diff --git a/tgui/packages/tgui/components/Flex.tsx b/tgui/packages/tgui/components/Flex.tsx index f67738280b..da063f0296 100644 --- a/tgui/packages/tgui/components/Flex.tsx +++ b/tgui/packages/tgui/components/Flex.tsx @@ -4,16 +4,19 @@ * @license MIT */ -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; +import { classes } from 'common/react'; import { BoxProps, computeBoxClassName, computeBoxProps, unit } from './Box'; -export type FlexProps = BoxProps & { - direction?: string | BooleanLike; - wrap?: string | BooleanLike; - align?: string | BooleanLike; - justify?: string | BooleanLike; - inline?: BooleanLike; -}; +export type FlexProps = Partial<{ + align: string | boolean; + direction: string; + inline: boolean; + justify: string; + scrollable: boolean; + style: Partial; + wrap: string | boolean; +}> & + BoxProps; export const computeFlexClassName = (props: FlexProps) => { return classes([ @@ -27,13 +30,14 @@ export const computeFlexClassName = (props: FlexProps) => { export const computeFlexProps = (props: FlexProps) => { const { className, direction, wrap, align, justify, inline, ...rest } = props; + return computeBoxProps({ style: { ...rest.style, - 'flex-direction': direction, - 'flex-wrap': wrap === true ? 'wrap' : wrap, - 'align-items': align, - 'justify-content': justify, + flexDirection: direction, + flexWrap: wrap === true ? 'wrap' : wrap, + alignItems: align, + justifyContent: justify, }, ...rest, }); @@ -49,15 +53,15 @@ export const Flex = (props) => { ); }; -Flex.defaultHooks = pureComponentHooks; - -export type FlexItemProps = BoxProps & { - grow?: number; - order?: number; - shrink?: number; - basis?: string | BooleanLike; - align?: string | BooleanLike; -}; +export type FlexItemProps = BoxProps & + Partial<{ + grow: number | boolean; + order: number; + shrink: number | boolean; + basis: string | number; + align: string | boolean; + style: Partial; + }>; export const computeFlexItemClassName = (props: FlexItemProps) => { return classes([ @@ -68,33 +72,26 @@ export const computeFlexItemClassName = (props: FlexItemProps) => { }; export const computeFlexItemProps = (props: FlexItemProps) => { - // prettier-ignore - const { - className, - style, - grow, - order, - shrink, - basis, - align, - ...rest - } = props; - // prettier-ignore - const computedBasis = basis + const { className, style, grow, order, shrink, basis, align, ...rest } = + props; + + const computedBasis = + basis ?? // IE11: Set basis to specified width if it's known, which fixes certain // bugs when rendering tables inside the flex. - ?? props.width + props.width ?? // If grow is used, basis should be set to 0 to be consistent with // flex css shorthand `flex: 1`. - ?? (grow !== undefined ? 0 : undefined); + (grow !== undefined ? 0 : undefined); + return computeBoxProps({ style: { ...style, - 'flex-grow': grow !== undefined && Number(grow), - 'flex-shrink': shrink !== undefined && Number(shrink), - 'flex-basis': unit(computedBasis), - 'order': order, - 'align-self': align, + flexGrow: grow !== undefined && Number(grow), + flexShrink: shrink !== undefined && Number(shrink), + flexBasis: unit(computedBasis), + order: order, + alignSelf: align, }, ...rest, }); @@ -110,6 +107,4 @@ const FlexItem = (props) => { ); }; -FlexItem.defaultHooks = pureComponentHooks; - Flex.Item = FlexItem; diff --git a/tgui/packages/tgui/components/InfinitePlane.jsx b/tgui/packages/tgui/components/InfinitePlane.jsx index 74f60f1e4d..e298537b46 100644 --- a/tgui/packages/tgui/components/InfinitePlane.jsx +++ b/tgui/packages/tgui/components/InfinitePlane.jsx @@ -2,7 +2,7 @@ import { computeBoxProps } from './Box'; import { Stack } from './Stack'; import { ProgressBar } from './ProgressBar'; import { Button } from './Button'; -import { Component } from 'inferno'; +import { Component } from 'react'; const ZOOM_MIN_VAL = 0.5; const ZOOM_MAX_VAL = 1.5; diff --git a/tgui/packages/tgui/components/Input.jsx b/tgui/packages/tgui/components/Input.jsx index ac7ce6eef3..0eac3d05eb 100644 --- a/tgui/packages/tgui/components/Input.jsx +++ b/tgui/packages/tgui/components/Input.jsx @@ -6,7 +6,7 @@ import { KEY_ENTER, KEY_ESCAPE } from 'common/keycodes'; import { classes } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { Box } from './Box'; // prettier-ignore diff --git a/tgui/packages/tgui/components/KeyListener.tsx b/tgui/packages/tgui/components/KeyListener.tsx index 62509cae96..d401642a3c 100644 --- a/tgui/packages/tgui/components/KeyListener.tsx +++ b/tgui/packages/tgui/components/KeyListener.tsx @@ -1,4 +1,4 @@ -import { Component } from 'inferno'; +import { Component } from 'react'; import { KeyEvent } from '../events'; import { listenForKeyEvents } from '../hotkeys'; @@ -11,8 +11,8 @@ type KeyListenerProps = Partial<{ export class KeyListener extends Component { dispose: () => void; - constructor() { - super(); + constructor(props) { + super(props); this.dispose = listenForKeyEvents((key) => { if (this.props.onKey) { diff --git a/tgui/packages/tgui/components/LabeledList.tsx b/tgui/packages/tgui/components/LabeledList.tsx index 0c4c608f25..8bb3ab82fd 100644 --- a/tgui/packages/tgui/components/LabeledList.tsx +++ b/tgui/packages/tgui/components/LabeledList.tsx @@ -4,35 +4,32 @@ * @license MIT */ -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; -import { InfernoNode } from 'inferno'; +import { BooleanLike, classes } from 'common/react'; +import { PropsWithChildren, ReactNode } from 'react'; import { Box, unit } from './Box'; import { Divider } from './Divider'; +import { Tooltip } from './Tooltip'; -type LabeledListProps = { - readonly children?: any; -}; - -export const LabeledList = (props: LabeledListProps) => { +export const LabeledList = (props: PropsWithChildren) => { const { children } = props; return {children}
; }; -LabeledList.defaultHooks = pureComponentHooks; - -type LabeledListItemProps = { - readonly className?: string | BooleanLike; - readonly label?: string | InfernoNode | BooleanLike; - readonly labelColor?: string | BooleanLike; - readonly labelWrap?: boolean; - readonly color?: string | BooleanLike; - readonly textAlign?: string | BooleanLike; - readonly buttons?: InfernoNode; +type LabeledListItemProps = Partial<{ + buttons: ReactNode; + className: string | BooleanLike; + color: string; + key: string | number; + label: string | ReactNode | BooleanLike; + labelColor: string; + labelWrap: boolean; + textAlign: string; /** @deprecated */ - readonly content?: any; - readonly children?: InfernoNode; - readonly verticalAlign?: string; -}; + content: any; + children: ReactNode; + verticalAlign: string; + tooltip: string; +}>; const LabeledListItem = (props: LabeledListItemProps) => { const { @@ -46,20 +43,46 @@ const LabeledListItem = (props: LabeledListItemProps) => { content, children, verticalAlign = 'baseline', + tooltip, } = props; + + let innerLabel; + if (label) { + innerLabel = label; + if (typeof label === 'string') innerLabel += ':'; + } + + if (tooltip !== undefined) { + innerLabel = ( + + + {innerLabel} + + + ); + } + + let labelChild = ( + + {innerLabel} + + ); + return ( - - {label ? (typeof label === 'string' ? label + ':' : label) : null} - + {labelChild} { ); }; -LabeledListItem.defaultHooks = pureComponentHooks; - type LabeledListDividerProps = { readonly size?: number; }; @@ -90,8 +111,8 @@ const LabeledListDivider = (props: LabeledListDividerProps) => { @@ -99,7 +120,5 @@ const LabeledListDivider = (props: LabeledListDividerProps) => { ); }; -LabeledListDivider.defaultHooks = pureComponentHooks; - LabeledList.Item = LabeledListItem; LabeledList.Divider = LabeledListDivider; diff --git a/tgui/packages/tgui/components/NumberInput.jsx b/tgui/packages/tgui/components/NumberInput.jsx index e264d811d3..a5890d5cd8 100644 --- a/tgui/packages/tgui/components/NumberInput.jsx +++ b/tgui/packages/tgui/components/NumberInput.jsx @@ -5,8 +5,9 @@ */ import { clamp } from 'common/math'; -import { classes, pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { classes } from 'common/react'; +import { Component, createRef } from 'react'; + import { AnimatedNumber } from './AnimatedNumber'; import { Box } from './Box'; @@ -165,14 +166,15 @@ export class NumberInput extends Component { displayValue = intermediateValue; } - // prettier-ignore const contentElement = ( -
- { - (animated && !dragging && !suppressingFlicker) ? - () : - (format ? format(displayValue) : displayValue) - } +
+ {animated && !dragging && !suppressingFlicker ? ( + + ) : format ? ( + format(displayValue) + ) : ( + displayValue + )} {unit ? ' ' + unit : ''}
@@ -194,10 +196,12 @@ export class NumberInput extends Component {
@@ -208,8 +212,8 @@ export class NumberInput extends Component { style={{ display: !editing ? 'none' : undefined, height: height, - 'line-height': lineHeight, - 'font-size': fontSize, + lineHeight: lineHeight, + fontSize: fontSize, }} onBlur={(e) => { if (!editing) { @@ -274,7 +278,6 @@ export class NumberInput extends Component { } } -NumberInput.defaultHooks = pureComponentHooks; NumberInput.defaultProps = { minValue: -Infinity, maxValue: +Infinity, diff --git a/tgui/packages/tgui/components/Popper.tsx b/tgui/packages/tgui/components/Popper.tsx index 08875d7f8d..682fd65a74 100644 --- a/tgui/packages/tgui/components/Popper.tsx +++ b/tgui/packages/tgui/components/Popper.tsx @@ -1,84 +1,88 @@ -import { createPopper } from '@popperjs/core'; -import { ArgumentsOf } from 'common/types'; -import { Component, findDOMfromVNode, InfernoNode, render } from 'inferno'; +import { Placement } from '@popperjs/core'; +import { PropsWithChildren, ReactNode, useEffect, useRef, useState } from 'react'; +import { usePopper } from 'react-popper'; -type PopperProps = { - readonly popperContent: InfernoNode; - readonly options?: ArgumentsOf[2]; - readonly additionalStyles?: CSSProperties; +type RequiredProps = { + /** The content to display in the popper */ + readonly content: ReactNode; + /** Whether the popper is open */ + readonly isOpen: boolean; }; -export class Popper extends Component { - static id: number = 0; +type OptionalProps = Partial<{ + /** Called when the user clicks outside the popper */ + onClickOutside: () => void; + /** Where to place the popper relative to the reference element */ + placement: Placement; +}>; - renderedContent: HTMLDivElement; - popperInstance: ReturnType; +type Props = RequiredProps & OptionalProps; - constructor() { - super(); +/** + * ## Popper + * Popper lets you position elements so that they don't go out of the bounds of the window. + * @url https://popper.js.org/react-popper/ for more information. + */ +export const Popper = (props: PropsWithChildren) => { + const { children, content, isOpen, onClickOutside, placement } = props; - Popper.id += 1; - } + const [referenceElement, setReferenceElement] = + useState(null); + const [popperElement, setPopperElement] = useState( + null + ); - componentDidMount() { - const { additionalStyles, options } = this.props; + // One would imagine we could just use useref here, but it's against react-popper documentation and causes a positioning bug + // We still need them to call focus and clickoutside events :( + const popperRef = useRef(null); + const parentRef = useRef(null); - this.renderedContent = document.createElement('div'); + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement, + }); - if (additionalStyles) { - for (const [attribute, value] of Object.entries(additionalStyles)) { - this.renderedContent.style[attribute] = value; - } + /** Close the popper when the user clicks outside */ + const handleClickOutside = (event: MouseEvent) => { + if ( + !popperRef.current?.contains(event.target as Node) && + !parentRef.current?.contains(event.target as Node) + ) { + onClickOutside?.(); } + }; - this.renderPopperContent(() => { - document.body.appendChild(this.renderedContent); - - // HACK: We don't want to create a wrapper, as it could break the layout - // of consumers, so we do the inferno equivalent of `findDOMNode(this)`. - // This is usually bad as refs are usually better, but refs did - // not work in this case, as they weren't propagating correctly. - // A previous attempt was made as a render prop that passed an ID, - // but this made consuming use too unwieldly. - // This code is copied from `findDOMNode` in inferno-extras. - // Because this component is written in TypeScript, we will know - // immediately if this internal variable is removed. - const domNode = findDOMfromVNode(this.$LI, true); - if (!domNode) { - return; - } - - this.popperInstance = createPopper( - domNode, - this.renderedContent, - options - ); - }); - } - - componentDidUpdate() { - this.renderPopperContent(() => this.popperInstance?.update()); - } - - componentWillUnmount() { - this.popperInstance?.destroy(); - render(null, this.renderedContent, () => { - this.renderedContent.remove(); - }); - } + useEffect(() => { + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); + } - renderPopperContent(callback: () => void) { - // `render` errors when given false, so we convert it to `null`, - // which is supported. - render( - this.props.popperContent || null, - this.renderedContent, - callback, - this.context - ); - } + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen]); - render() { - return this.props.children; - } -} + return ( + <> +
{ + setReferenceElement(node); + parentRef.current = node; + }}> + {children} +
+ {isOpen && ( +
{ + setPopperElement(node); + popperRef.current = node; + }} + style={{ ...styles.popper, zIndex: 5 }} + {...attributes.popper}> + {content} +
+ )} + + ); +}; diff --git a/tgui/packages/tgui/components/ProgressBar.jsx b/tgui/packages/tgui/components/ProgressBar.tsx similarity index 50% rename from tgui/packages/tgui/components/ProgressBar.jsx rename to tgui/packages/tgui/components/ProgressBar.tsx index 86116732c3..929ee232fd 100644 --- a/tgui/packages/tgui/components/ProgressBar.jsx +++ b/tgui/packages/tgui/components/ProgressBar.tsx @@ -4,12 +4,31 @@ * @license MIT */ -import { clamp01, scale, keyOfMatchingRange, toFixed } from 'common/math'; -import { classes, pureComponentHooks } from 'common/react'; -import { computeBoxClassName, computeBoxProps } from './Box'; +import { clamp01, keyOfMatchingRange, scale, toFixed } from 'common/math'; +import { classes } from 'common/react'; +import { PropsWithChildren } from 'react'; + import { CSS_COLORS } from '../constants'; +import { BoxProps, computeBoxClassName, computeBoxProps } from './Box'; + +type Props = { + readonly value: number; +} & Partial<{ + backgroundColor: string; + className: string; + color: string; + height: string | number; + maxValue: number; + minValue: number; + ranges: Record; + style: Partial; + title: string; + width: string | number; +}> & + Partial & + PropsWithChildren; -export const ProgressBar = (props) => { +export const ProgressBar = (props: Props) => { const { className, value, @@ -22,32 +41,28 @@ export const ProgressBar = (props) => { } = props; const scaledValue = scale(value, minValue, maxValue); const hasContent = children !== undefined; - // prettier-ignore - const effectiveColor = color - || keyOfMatchingRange(value, ranges) - || 'default'; + + const effectiveColor = + color || keyOfMatchingRange(value, ranges) || 'default'; // We permit colors to be in hex format, rgb()/rgba() format, // a name for a color- class, or a base CSS class. const outerProps = computeBoxProps(rest); - // prettier-ignore - const outerClasses = [ - 'ProgressBar', - className, - computeBoxClassName(rest), - ]; + + const outerClasses = ['ProgressBar', className, computeBoxClassName(rest)]; const fillStyles = { - 'width': clamp01(scaledValue) * 100 + '%', + width: clamp01(scaledValue) * 100 + '%', }; - if (CSS_COLORS.includes(effectiveColor) || effectiveColor === 'default') { + if ( + CSS_COLORS.includes(effectiveColor as any) || + effectiveColor === 'default' + ) { // If the color is a color- class, just use that. outerClasses.push('ProgressBar--color--' + effectiveColor); } else { // Otherwise, set styles directly. - // prettier-ignore - outerProps.style = (outerProps.style || "") - + `border-color: ${effectiveColor};`; - fillStyles['background-color'] = effectiveColor; + outerProps.style = { ...outerProps.style, borderColor: effectiveColor }; + fillStyles['backgroundColor'] = effectiveColor; } return ( @@ -62,5 +77,3 @@ export const ProgressBar = (props) => {
); }; - -ProgressBar.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/RestrictedInput.jsx b/tgui/packages/tgui/components/RestrictedInput.jsx index 21f357239d..082fc606d9 100644 --- a/tgui/packages/tgui/components/RestrictedInput.jsx +++ b/tgui/packages/tgui/components/RestrictedInput.jsx @@ -1,6 +1,6 @@ import { classes } from 'common/react'; import { clamp } from 'common/math'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { Box } from './Box'; import { KEY_ESCAPE, KEY_ENTER } from 'common/keycodes'; diff --git a/tgui/packages/tgui/components/Section.tsx b/tgui/packages/tgui/components/Section.tsx index 27b7898e10..9b5170e377 100644 --- a/tgui/packages/tgui/components/Section.tsx +++ b/tgui/packages/tgui/components/Section.tsx @@ -4,100 +4,79 @@ * @license MIT */ -import { canRender, classes } from 'common/react'; -import { Component, createRef, InfernoNode, RefObject } from 'inferno'; -import { addScrollableNode, removeScrollableNode } from '../events'; import { BoxProps, computeBoxClassName, computeBoxProps } from './Box'; +import { ReactNode, RefObject, createRef, useEffect } from 'react'; +import { addScrollableNode, removeScrollableNode } from '../events'; +import { canRender, classes } from 'common/react'; -type SectionProps = BoxProps & { - readonly className?: string; - readonly title?: InfernoNode; - readonly buttons?: InfernoNode; - readonly fill?: boolean; - readonly fitted?: boolean; - readonly scrollable?: boolean; - readonly scrollableHorizontal?: boolean; - /** @deprecated This property no longer works, please remove it. */ - readonly level?: boolean; - /** @deprecated Please use `scrollable` property */ - readonly overflowY?: any; +export type SectionProps = Partial<{ + buttons: ReactNode; + fill: boolean; + fitted: boolean; + scrollable: boolean; + scrollableHorizontal: boolean; + title: ReactNode; /** @member Allows external control of scrolling. */ - readonly scrollableRef?: RefObject; + scrollableRef: RefObject; /** @member Callback function for the `scroll` event */ - readonly onScroll?: (this: GlobalEventHandlers, ev: Event) => any; -}; + onScroll: ((this: GlobalEventHandlers, ev: Event) => any) | null; +}> & + BoxProps; -export class Section extends Component { - scrollableRef: RefObject; - scrollable: boolean; - onScroll?: (this: GlobalEventHandlers, ev: Event) => any; - scrollableHorizontal: boolean; +export const Section = (props: SectionProps) => { + const { + className, + title, + buttons, + fill, + fitted, + scrollable, + scrollableHorizontal, + children, + onScroll, + ...rest + } = props; - constructor(props) { - super(props); - this.scrollableRef = props.scrollableRef || createRef(); - this.scrollable = props.scrollable; - this.onScroll = props.onScroll; - this.scrollableHorizontal = props.scrollableHorizontal; - } + const scrollableRef = props.scrollableRef || createRef(); + const hasTitle = canRender(title) || canRender(buttons); - componentDidMount() { - if (this.scrollable || this.scrollableHorizontal) { - addScrollableNode(this.scrollableRef.current as HTMLElement); - if (this.onScroll && this.scrollableRef.current) { - this.scrollableRef.current.onscroll = this.onScroll; + useEffect(() => { + if (scrollable || scrollableHorizontal) { + addScrollableNode(scrollableRef.current as HTMLElement); + if (onScroll && scrollableRef.current) { + scrollableRef.current.onscroll = onScroll; } } - } - - componentWillUnmount() { - if (this.scrollable || this.scrollableHorizontal) { - removeScrollableNode(this.scrollableRef.current as HTMLElement); - } - } + return () => { + if (scrollable || scrollableHorizontal) { + removeScrollableNode(scrollableRef.current as HTMLElement); + } + }; + }, []); - render() { - const { - className, - title, - buttons, - fill, - fitted, - scrollable, - scrollableHorizontal, - children, - onScroll, - ...rest - } = this.props; - const hasTitle = canRender(title) || canRender(buttons); - return ( -
- {hasTitle && ( -
- {title} -
{buttons}
-
- )} -
-
- {children} -
+ return ( +
+ {hasTitle && ( +
+ {title} +
{buttons}
+
+ )} +
+
+ {children}
- ); - } -} +
+ ); +}; diff --git a/tgui/packages/tgui/components/Stack.tsx b/tgui/packages/tgui/components/Stack.tsx index 77c92796a0..3eb31bd325 100644 --- a/tgui/packages/tgui/components/Stack.tsx +++ b/tgui/packages/tgui/components/Stack.tsx @@ -5,22 +5,25 @@ */ import { classes } from 'common/react'; -import { RefObject } from 'inferno'; +import { RefObject } from 'react'; import { computeFlexClassName, computeFlexItemClassName, computeFlexItemProps, computeFlexProps, FlexItemProps, FlexProps } from './Flex'; -type StackProps = FlexProps & { - readonly vertical?: boolean; - readonly fill?: boolean; -}; +type Props = Partial<{ + vertical: boolean; + fill: boolean; + zebra: boolean; +}> & + FlexProps; -export const Stack = (props: StackProps) => { - const { className, vertical, fill, ...rest } = props; +export const Stack = (props: Props) => { + const { className, vertical, fill, zebra, ...rest } = props; return (
{ ); }; -type StackItemProps = FlexProps & { - readonly innerRef?: RefObject; -}; +type StackItemProps = FlexItemProps & + Partial<{ + innerRef: RefObject; + }>; const StackItem = (props: StackItemProps) => { const { className, innerRef, ...rest } = props; @@ -53,9 +57,10 @@ const StackItem = (props: StackItemProps) => { Stack.Item = StackItem; -type StackDividerProps = FlexItemProps & { - readonly hidden?: boolean; -}; +type StackDividerProps = FlexItemProps & + Partial<{ + hidden: boolean; + }>; const StackDivider = (props: StackDividerProps) => { const { className, hidden, ...rest } = props; diff --git a/tgui/packages/tgui/components/TextArea.jsx b/tgui/packages/tgui/components/TextArea.jsx index 76db8272aa..1ca70488c9 100644 --- a/tgui/packages/tgui/components/TextArea.jsx +++ b/tgui/packages/tgui/components/TextArea.jsx @@ -6,14 +6,14 @@ */ import { classes } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { Box } from './Box'; import { toInputValue } from './Input'; import { KEY_ENTER, KEY_ESCAPE, KEY_TAB } from 'common/keycodes'; export class TextArea extends Component { - constructor(props, context) { - super(props, context); + constructor(props) { + super(props); this.textareaRef = props.innerRef || createRef(); this.state = { editing: false, diff --git a/tgui/packages/tgui/components/TimeDisplay.jsx b/tgui/packages/tgui/components/TimeDisplay.jsx index 6b87ee5260..bbdd747701 100644 --- a/tgui/packages/tgui/components/TimeDisplay.jsx +++ b/tgui/packages/tgui/components/TimeDisplay.jsx @@ -1,5 +1,5 @@ import { formatTime } from '../format'; -import { Component } from 'inferno'; +import { Component } from 'react'; // AnimatedNumber Copypaste const isSafeNumber = (value) => { diff --git a/tgui/packages/tgui/components/Tooltip.tsx b/tgui/packages/tgui/components/Tooltip.tsx index f2004f9630..b565e6fda4 100644 --- a/tgui/packages/tgui/components/Tooltip.tsx +++ b/tgui/packages/tgui/components/Tooltip.tsx @@ -1,9 +1,10 @@ import { createPopper, Placement, VirtualElement } from '@popperjs/core'; -import { Component, findDOMfromVNode, InfernoNode, render } from 'inferno'; +import { Component, ReactNode } from 'react'; +import { findDOMNode, render } from 'react-dom'; type TooltipProps = { - readonly children?: InfernoNode; - readonly content: InfernoNode; + readonly children?: ReactNode; + readonly content: ReactNode; readonly position?: Placement; }; @@ -50,14 +51,16 @@ export class Tooltip extends Component { getDOMNode() { // HACK: We don't want to create a wrapper, as it could break the layout - // of consumers, so we do the inferno equivalent of `findDOMNode(this)`. - // My attempt to avoid this was a render prop that passed in - // callbacks to onmouseenter and onmouseleave, but this was unwiedly - // to consumers, specifically buttons. - // This code is copied from `findDOMNode` in inferno-extras. + // of consumers, so we use findDOMNode. + // This is usually bad as refs are usually better, but refs did + // not work in this case, as they weren't propagating correctly. + // A previous attempt was made as a render prop that passed an ID, + // but this made consuming use too unwieldly. // Because this component is written in TypeScript, we will know // immediately if this internal variable is removed. - return findDOMfromVNode(this.$LI, true); + // + // eslint-disable-next-line react/no-find-dom-node + return findDOMNode(this) as Element; } componentDidMount() { @@ -103,33 +106,28 @@ export class Tooltip extends Component { return; } - render( - {this.props.content}, - renderedTooltip, - () => { - let singletonPopper = Tooltip.singletonPopper; - if (singletonPopper === undefined) { - singletonPopper = createPopper( - Tooltip.virtualElement, - renderedTooltip!, - { - ...DEFAULT_OPTIONS, - placement: this.props.position || 'auto', - } - ); - - Tooltip.singletonPopper = singletonPopper; - } else { - singletonPopper.setOptions({ + render({this.props.content}, renderedTooltip, () => { + let singletonPopper = Tooltip.singletonPopper; + if (singletonPopper === undefined) { + singletonPopper = createPopper( + Tooltip.virtualElement, + renderedTooltip!, + { ...DEFAULT_OPTIONS, placement: this.props.position || 'auto', - }); + } + ); - singletonPopper.update(); - } - }, - this.context - ); + Tooltip.singletonPopper = singletonPopper; + } else { + singletonPopper.setOptions({ + ...DEFAULT_OPTIONS, + placement: this.props.position || 'auto', + }); + + singletonPopper.update(); + } + }); } componentDidUpdate() { diff --git a/tgui/packages/tgui/components/TrackOutsideClicks.tsx b/tgui/packages/tgui/components/TrackOutsideClicks.tsx index 8ba46ffb5b..13cfb8443e 100644 --- a/tgui/packages/tgui/components/TrackOutsideClicks.tsx +++ b/tgui/packages/tgui/components/TrackOutsideClicks.tsx @@ -1,14 +1,14 @@ -import { Component, createRef } from 'inferno'; +import { Component, createRef, PropsWithChildren } from 'react'; type Props = { readonly onOutsideClick: () => void; -}; +} & PropsWithChildren; export class TrackOutsideClicks extends Component { ref = createRef(); - constructor() { - super(); + constructor(props) { + super(props); this.handleOutsideClick = this.handleOutsideClick.bind(this); diff --git a/tgui/packages/tgui/debug/KitchenSink.jsx b/tgui/packages/tgui/debug/KitchenSink.jsx index e25751722c..23cf966981 100644 --- a/tgui/packages/tgui/debug/KitchenSink.jsx +++ b/tgui/packages/tgui/debug/KitchenSink.jsx @@ -20,10 +20,10 @@ const r = require.context('../stories', false, /\.stories\.jsx$/); */ const getStories = () => r.keys().map((path) => r(path)); -export const KitchenSink = (props, context) => { +export const KitchenSink = (props) => { const { panel } = props; - const [theme] = useLocalState(context, 'kitchenSinkTheme'); - const [pageIndex, setPageIndex] = useLocalState(context, 'pageIndex', 0); + const [theme] = useLocalState('kitchenSinkTheme'); + const [pageIndex, setPageIndex] = useLocalState('pageIndex', 0); const stories = getStories(); const story = stories[pageIndex]; const Layout = panel ? Pane : Window; diff --git a/tgui/packages/tgui/debug/hooks.js b/tgui/packages/tgui/debug/hooks.js index d324dd68d8..fc4901c496 100644 --- a/tgui/packages/tgui/debug/hooks.js +++ b/tgui/packages/tgui/debug/hooks.js @@ -4,7 +4,7 @@ * @license MIT */ -import { useSelector } from 'common/redux'; +import { useSelector } from '../backend'; import { selectDebug } from './selectors'; -export const useDebug = (context) => useSelector(context, selectDebug); +export const useDebug = () => useSelector(selectDebug); diff --git a/tgui/packages/tgui/drag.ts b/tgui/packages/tgui/drag.ts index 4b6f82e120..a34c49ac46 100644 --- a/tgui/packages/tgui/drag.ts +++ b/tgui/packages/tgui/drag.ts @@ -203,7 +203,7 @@ const constraintPosition = ( }; // Start dragging the window -export const dragStartHandler = (event: MouseEvent) => { +export const dragStartHandler = (event) => { logger.log('drag start'); dragging = true; dragPointOffset = vecSubtract( @@ -218,7 +218,7 @@ export const dragStartHandler = (event: MouseEvent) => { }; // End dragging the window -const dragEndHandler = (event: MouseEvent) => { +const dragEndHandler = (event) => { logger.log('drag end'); dragMoveHandler(event); document.removeEventListener('mousemove', dragMoveHandler); diff --git a/tgui/packages/tgui/index.tsx b/tgui/packages/tgui/index.tsx index 97640d062a..153a8486bb 100644 --- a/tgui/packages/tgui/index.tsx +++ b/tgui/packages/tgui/index.tsx @@ -32,8 +32,9 @@ import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; import { setupHotKeys } from './hotkeys'; import { captureExternalLinks } from './links'; import { createRenderer } from './renderer'; -import { configureStore, StoreProvider } from './store'; +import { configureStore } from './store'; import { setupGlobalEvents } from './events'; +import { setGlobalStore } from './backend'; perf.mark('inception', window.performance?.timing?.navigationStart); perf.mark('init'); @@ -41,13 +42,11 @@ perf.mark('init'); const store = configureStore(); const renderApp = createRenderer(() => { + setGlobalStore(store); + const { getRoutedComponent } = require('./routes'); - const Component = getRoutedComponent(store); - return ( - - - - ); + const Component = getRoutedComponent(); + return ; }); const setupApp = () => { diff --git a/tgui/packages/tgui/interfaces/AcidVest.jsx b/tgui/packages/tgui/interfaces/AcidVest.jsx index adf0e37a2e..fffbb17876 100644 --- a/tgui/packages/tgui/interfaces/AcidVest.jsx +++ b/tgui/packages/tgui/interfaces/AcidVest.jsx @@ -2,8 +2,8 @@ import { useBackend } from '../backend'; import { Button, LabeledList, Section, Slider, Flex, Box } from '../components'; import { Window } from '../layouts'; -export const AcidVest = (_props, context) => { - const { act, data } = useBackend(context); +export const AcidVest = (_props) => { + const { act, data } = useBackend(); const damageList = data.configList.Damage; const vitalsList = data.configList.Vitals; diff --git a/tgui/packages/tgui/interfaces/Adminhelp.tsx b/tgui/packages/tgui/interfaces/Adminhelp.tsx index 817e25e37c..c2a888cdc3 100644 --- a/tgui/packages/tgui/interfaces/Adminhelp.tsx +++ b/tgui/packages/tgui/interfaces/Adminhelp.tsx @@ -10,8 +10,8 @@ type AdminhelpData = { urgentAhelpPromptMessage: string; }; -export const Adminhelp = (props, context) => { - const { act, data } = useBackend(context); +export const Adminhelp = (props) => { + const { act, data } = useBackend(); const { adminCount, urgentAhelpEnabled, @@ -19,20 +19,14 @@ export const Adminhelp = (props, context) => { urgentAhelpPromptMessage, } = data; const [requestForAdmin, setRequestForAdmin] = useLocalState( - context, 'request_for_admin', false ); const [currentlyInputting, setCurrentlyInputting] = useLocalState( - context, 'confirm_request', false ); - const [ahelpMessage, setAhelpMessage] = useLocalState( - context, - 'ahelp_message', - '' - ); + const [ahelpMessage, setAhelpMessage] = useLocalState('ahelp_message', ''); const confirmationText = 'alert admins'; return ( diff --git a/tgui/packages/tgui/interfaces/AlertModal.tsx b/tgui/packages/tgui/interfaces/AlertModal.tsx index ee53a0f1b6..60f60c94c5 100644 --- a/tgui/packages/tgui/interfaces/AlertModal.tsx +++ b/tgui/packages/tgui/interfaces/AlertModal.tsx @@ -17,8 +17,8 @@ type AlertModalData = { const KEY_DECREMENT = -1; const KEY_INCREMENT = 1; -export const AlertModal = (props, context) => { - const { act, data } = useBackend(context); +export const AlertModal = (props) => { + const { act, data } = useBackend(); const { autofocus, buttons = [], @@ -27,7 +27,7 @@ export const AlertModal = (props, context) => { timeout, title, } = data; - const [selected, setSelected] = useLocalState(context, 'selected', 0); + const [selected, setSelected] = useLocalState('selected', 0); // Dynamically sets window dimensions const windowHeight = 115 + @@ -89,8 +89,8 @@ export const AlertModal = (props, context) => { * Technically this handles more than 2 buttons, but you * should just be using a list input in that case. */ -const ButtonDisplay = (props, context) => { - const { data } = useBackend(context); +const ButtonDisplay = (props) => { + const { data } = useBackend(); const { buttons = [], large_buttons, swapped_buttons } = data; const { selected } = props; @@ -127,8 +127,8 @@ const ButtonDisplay = (props, context) => { /** * Displays a button with variable sizing. */ -const AlertButton = (props, context) => { - const { act, data } = useBackend(context); +const AlertButton = (props) => { + const { act, data } = useBackend(); const { large_buttons } = data; const { button, selected } = props; const buttonWidth = button.length > 7 ? button.length : 7; diff --git a/tgui/packages/tgui/interfaces/AlmayerControl.jsx b/tgui/packages/tgui/interfaces/AlmayerControl.jsx index 6cc4473762..7a5133a3b1 100644 --- a/tgui/packages/tgui/interfaces/AlmayerControl.jsx +++ b/tgui/packages/tgui/interfaces/AlmayerControl.jsx @@ -1,10 +1,10 @@ -import { Fragment } from 'inferno'; +import { Fragment } from 'react'; import { useBackend } from '../backend'; import { Button, Section, Flex, NoticeBox, Collapsible, Divider, Box } from '../components'; import { Window } from '../layouts'; -export const AlmayerControl = (_props, context) => { - const { act, data } = useBackend(context); +export const AlmayerControl = (_props) => { + const { act, data } = useBackend(); const worldTime = data.worldtime; const messages = data.messages; @@ -225,7 +225,7 @@ export const AlmayerControl = (_props, context) => { {messages && ( - + <> @@ -251,7 +251,7 @@ export const AlmayerControl = (_props, context) => { })} - + )} diff --git a/tgui/packages/tgui/interfaces/AltitudeControlConsole.jsx b/tgui/packages/tgui/interfaces/AltitudeControlConsole.jsx index 48550514fd..fd92602a34 100644 --- a/tgui/packages/tgui/interfaces/AltitudeControlConsole.jsx +++ b/tgui/packages/tgui/interfaces/AltitudeControlConsole.jsx @@ -2,8 +2,8 @@ import { useBackend } from '../backend'; import { Button, ProgressBar, Box, Section } from '../components'; import { Window } from '../layouts'; import { createLogger } from '../logging'; -export const AltitudeControlConsole = (_props, context) => { - const { act, data } = useBackend(context); +export const AltitudeControlConsole = () => { + const { act, data } = useBackend(); const logger = createLogger('Debug'); logger.warn(data); return ( diff --git a/tgui/packages/tgui/interfaces/AntiAirConsole.jsx b/tgui/packages/tgui/interfaces/AntiAirConsole.jsx index 8bfc80bb76..cd9b68ac78 100644 --- a/tgui/packages/tgui/interfaces/AntiAirConsole.jsx +++ b/tgui/packages/tgui/interfaces/AntiAirConsole.jsx @@ -2,8 +2,8 @@ import { useBackend, useLocalState } from '../backend'; import { Stack, Section, Tabs, Button, NoticeBox, Box, Dimmer, Icon } from '../components'; import { Window } from '../layouts'; -export const AntiAirConsole = (props, context) => { - const { act, data } = useBackend(context); +export const AntiAirConsole = (props) => { + const { act, data } = useBackend(); return ( @@ -13,13 +13,12 @@ export const AntiAirConsole = (props, context) => { ); }; -const GeneralPanel = (props, context) => { - const { act, data } = useBackend(context); +const GeneralPanel = (props) => { + const { act, data } = useBackend(); const sections = data.sections; const [selectedSection, setSelectedSection] = useLocalState( - context, 'selected_section', null ); diff --git a/tgui/packages/tgui/interfaces/Apc.jsx b/tgui/packages/tgui/interfaces/Apc.jsx index 434d91f4e4..24decabb1c 100644 --- a/tgui/packages/tgui/interfaces/Apc.jsx +++ b/tgui/packages/tgui/interfaces/Apc.jsx @@ -3,7 +3,7 @@ import { Box, Button, LabeledList, NoticeBox, ProgressBar, Section } from '../co import { Window } from '../layouts'; import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; -export const Apc = (props, context) => { +export const Apc = (props) => { return ( @@ -31,8 +31,8 @@ const powerStatusMap = { }, }; -const ApcContent = (props, context) => { - const { act, data } = useBackend(context); +const ApcContent = (props) => { + const { act, data } = useBackend(); const locked = data.locked && !data.siliconUser; const externalPowerStatus = powerStatusMap[data.externalPower] || powerStatusMap[0]; diff --git a/tgui/packages/tgui/interfaces/AresInterface.jsx b/tgui/packages/tgui/interfaces/AresInterface.jsx index aae115d150..f2ced09014 100644 --- a/tgui/packages/tgui/interfaces/AresInterface.jsx +++ b/tgui/packages/tgui/interfaces/AresInterface.jsx @@ -20,8 +20,8 @@ const PAGES = { 'emergency': () => Emergency, }; -export const AresInterface = (props, context) => { - const { data } = useBackend(context); +export const AresInterface = (props) => { + const { data } = useBackend(); const { current_menu, sudo } = data; const PageComponent = PAGES[current_menu](); @@ -41,8 +41,8 @@ export const AresInterface = (props, context) => { ); }; -const Login = (props, context) => { - const { act } = useBackend(context); +const Login = (props) => { + const { act } = useBackend(); return ( { ); }; -const MainMenu = (props, context) => { - const { data, act } = useBackend(context); +const MainMenu = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, @@ -352,8 +352,8 @@ const MainMenu = (props, context) => { ); }; -const AnnouncementLogs = (props, context) => { - const { data, act } = useBackend(context); +const AnnouncementLogs = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, @@ -448,8 +448,8 @@ const AnnouncementLogs = (props, context) => { ); }; -const BioscanLogs = (props, context) => { - const { data, act } = useBackend(context); +const BioscanLogs = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, @@ -544,8 +544,8 @@ const BioscanLogs = (props, context) => { ); }; -const BombardmentLogs = (props, context) => { - const { data, act } = useBackend(context); +const BombardmentLogs = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, @@ -644,8 +644,8 @@ const BombardmentLogs = (props, context) => { ); }; -const ApolloLog = (props, context) => { - const { data, act } = useBackend(context); +const ApolloLog = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, last_page, current_menu, apollo_log } = data; return ( @@ -700,8 +700,8 @@ const ApolloLog = (props, context) => { ); }; -const AccessLogs = (props, context) => { - const { data, act } = useBackend(context); +const AccessLogs = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, last_page, current_menu, access_log } = data; return ( @@ -756,8 +756,8 @@ const AccessLogs = (props, context) => { ); }; -const DeletionLogs = (props, context) => { - const { data, act } = useBackend(context); +const DeletionLogs = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, last_page, current_menu, records_deletion } = data; @@ -842,8 +842,8 @@ const DeletionLogs = (props, context) => { ); }; -const ARESTalk = (props, context) => { - const { data, act } = useBackend(context); +const ARESTalk = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, @@ -942,8 +942,8 @@ const ARESTalk = (props, context) => { ); }; -const DeletedTalks = (props, context) => { - const { data, act } = useBackend(context); +const DeletedTalks = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, @@ -1032,8 +1032,8 @@ const DeletedTalks = (props, context) => { ); }; -const ReadingTalks = (props, context) => { - const { data, act } = useBackend(context); +const ReadingTalks = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, @@ -1093,8 +1093,8 @@ const ReadingTalks = (props, context) => { ); }; -const Requisitions = (props, context) => { - const { data, act } = useBackend(context); +const Requisitions = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, @@ -1185,8 +1185,8 @@ const Requisitions = (props, context) => { ); }; -const FlightLogs = (props, context) => { - const { data, act } = useBackend(context); +const FlightLogs = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, @@ -1280,8 +1280,8 @@ const FlightLogs = (props, context) => { ); }; -const Security = (props, context) => { - const { data, act } = useBackend(context); +const Security = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, @@ -1375,8 +1375,8 @@ const Security = (props, context) => { ); }; -const Emergency = (props, context) => { - const { data, act } = useBackend(context); +const Emergency = (props) => { + const { data, act } = useBackend(); const { logged_in, access_text, diff --git a/tgui/packages/tgui/interfaces/Autodispenser.jsx b/tgui/packages/tgui/interfaces/Autodispenser.jsx index 5d029b72b8..249965e6d4 100644 --- a/tgui/packages/tgui/interfaces/Autodispenser.jsx +++ b/tgui/packages/tgui/interfaces/Autodispenser.jsx @@ -2,8 +2,8 @@ import { useBackend } from '../backend'; import { Section, ProgressBar, Box, Flex, NoticeBox, Button, LabeledList, NumberInput } from '../components'; import { Window } from '../layouts'; -export const Autodispenser = (_props, context) => { - const { act, data } = useBackend(context); +export const Autodispenser = () => { + const { act, data } = useBackend(); const { energy, status, diff --git a/tgui/packages/tgui/interfaces/Autolathe.jsx b/tgui/packages/tgui/interfaces/Autolathe.jsx index ceb343eae0..6b21f7b6ec 100644 --- a/tgui/packages/tgui/interfaces/Autolathe.jsx +++ b/tgui/packages/tgui/interfaces/Autolathe.jsx @@ -3,11 +3,11 @@ import { Section, Flex, ProgressBar, Box, Button, Tabs, Stack, Input } from '../ import { capitalize } from 'common/string'; import { Window } from '../layouts'; import { ElectricalPanel } from './common/ElectricalPanel'; -import { Fragment } from 'inferno'; +import { Fragment } from 'react'; import { createLogger } from '../logging'; -export const Autolathe = (_props, context) => { - const { act, data } = useBackend(context); +export const Autolathe = () => { + const { act, data } = useBackend(); const { materials, @@ -30,8 +30,8 @@ export const Autolathe = (_props, context) => { ); }; -const MaterialsData = (props, context) => { - const { act, data } = useBackend(context); +const MaterialsData = (props) => { + const { act, data } = useBackend(); const { materials, capacity, @@ -63,8 +63,8 @@ const MaterialsData = (props, context) => { ); }; -const CurrentlyMaking = (props, context) => { - const { act, data } = useBackend(context); +const CurrentlyMaking = (props) => { + const { act, data } = useBackend(); const { currently_making } = data; const MakingName = @@ -82,8 +82,8 @@ const CurrentlyMaking = (props, context) => { ); }; -const QueueList = (props, context) => { - const { act, data } = useBackend(context); +const QueueList = (props) => { + const { act, data } = useBackend(); const { materials, capacity, @@ -132,8 +132,8 @@ const QueueList = (props, context) => { }; // the below all has to be in one section due to the categories and search params -const PrintablesSection = (props, context) => { - const { act, data } = useBackend(context); +const PrintablesSection = (props) => { + const { act, data } = useBackend(); const logger = createLogger('autolathe'); @@ -146,11 +146,7 @@ const PrintablesSection = (props, context) => { queuemax, } = data; - const [currentSearch, setSearch] = useLocalState( - context, - 'current_search', - '' - ); + const [currentSearch, setSearch] = useLocalState('current_search', ''); const categories = []; printables @@ -158,7 +154,6 @@ const PrintablesSection = (props, context) => { .map((x) => x.recipe_category); const [currentCategory, setCategory] = useLocalState( - context, 'current_category', 'All' ); @@ -226,7 +221,7 @@ const PrintablesSection = (props, context) => { {(val.has_multipliers && ( - + <> @@ -250,7 +245,7 @@ const PrintablesSection = (props, context) => { )} - + )) || null} diff --git a/tgui/packages/tgui/interfaces/Binoculars.jsx b/tgui/packages/tgui/interfaces/Binoculars.jsx index d846a62a65..7306cea90b 100644 --- a/tgui/packages/tgui/interfaces/Binoculars.jsx +++ b/tgui/packages/tgui/interfaces/Binoculars.jsx @@ -2,8 +2,8 @@ import { useBackend } from '../backend'; import { Section, Box } from '../components'; import { Window } from '../layouts'; -export const Binoculars = (_props, context) => { - const { data } = useBackend(context); +export const Binoculars = () => { + const { data } = useBackend(); const x_coord = data.xcoord; const y_coord = data.ycoord; diff --git a/tgui/packages/tgui/interfaces/BioSyntheticPrinter.jsx b/tgui/packages/tgui/interfaces/BioSyntheticPrinter.jsx index 283ff26a7b..8f39b0c599 100644 --- a/tgui/packages/tgui/interfaces/BioSyntheticPrinter.jsx +++ b/tgui/packages/tgui/interfaces/BioSyntheticPrinter.jsx @@ -2,8 +2,8 @@ import { useBackend } from '../backend'; import { Section, Button, Box, LabeledList, ProgressBar, NoticeBox, Divider } from '../components'; import { Window } from '../layouts'; -export const BioSyntheticPrinter = (_props, context) => { - const { act, data } = useBackend(context); +export const BioSyntheticPrinter = () => { + const { act, data } = useBackend(); const Working = data.working; diff --git a/tgui/packages/tgui/interfaces/BotanyEditor.jsx b/tgui/packages/tgui/interfaces/BotanyEditor.jsx index 425ffc9a5d..09a655e928 100644 --- a/tgui/packages/tgui/interfaces/BotanyEditor.jsx +++ b/tgui/packages/tgui/interfaces/BotanyEditor.jsx @@ -1,10 +1,10 @@ -import { Fragment } from 'inferno'; +import { Fragment } from 'react'; import { useBackend } from '../backend'; import { Section, Button, Stack, LabeledList, NoticeBox } from '../components'; import { Window } from '../layouts'; -export const BotanyEditor = (_props, context) => { - const { act, data } = useBackend(context); +export const BotanyEditor = () => { + const { act, data } = useBackend(); const { disk, seed, degradation, sourceName, locus } = data; @@ -36,7 +36,7 @@ export const BotanyEditor = (_props, context) => {
- + <> {!disk && ( No disk! Genetic data cannot be applied. @@ -45,7 +45,7 @@ export const BotanyEditor = (_props, context) => { {!seed && ( No seeds to apply genetic data to! )} - + {!!disk && ( {sourceName} diff --git a/tgui/packages/tgui/interfaces/BotanyExtractor.jsx b/tgui/packages/tgui/interfaces/BotanyExtractor.jsx index eca098a9d9..fe67538468 100644 --- a/tgui/packages/tgui/interfaces/BotanyExtractor.jsx +++ b/tgui/packages/tgui/interfaces/BotanyExtractor.jsx @@ -1,10 +1,10 @@ -import { Fragment } from 'inferno'; +import { Fragment } from 'react'; import { useBackend } from '../backend'; import { Section, Button, LabeledList, Box, Stack, NoticeBox } from '../components'; import { Window } from '../layouts'; -export const BotanyExtractor = (_props, context) => { - const { act, data } = useBackend(context); +export const BotanyExtractor = () => { + const { act, data } = useBackend(); const { disk, seed, geneMasks, degradation, hasGenetics, sourceName } = data; @@ -49,7 +49,7 @@ export const BotanyExtractor = (_props, context) => {
{(!!hasGenetics && ( - + <> {!disk && ( No disk! Genetic data cannot be extracted. @@ -86,7 +86,7 @@ export const BotanyExtractor = (_props, context) => { disabled={!hasGenetics} onClick={() => act('clear_buffer')} /> - + )) || No genetic data stored!}
diff --git a/tgui/packages/tgui/interfaces/BrigCell.jsx b/tgui/packages/tgui/interfaces/BrigCell.jsx index f0eb325e0e..23c7bf91d1 100644 --- a/tgui/packages/tgui/interfaces/BrigCell.jsx +++ b/tgui/packages/tgui/interfaces/BrigCell.jsx @@ -3,8 +3,8 @@ import { addZeros } from 'common/math'; import { Window } from '../layouts'; import { Box, ColorBox, NoticeBox, Flex, ProgressBar, Button, LabeledList, Divider } from '../components'; -export const BrigCell = (props, context) => { - const { data, act } = useBackend(context); +export const BrigCell = (props) => { + const { data, act } = useBackend(); const { viewing_incident, incidents, bit_active } = data; return ( @@ -90,8 +90,8 @@ const getTimeLeft = function (data) { return Math.max(0, timeLeft); }; -const IncidentDetails = (props, context) => { - const { data, act } = useBackend(context); +const IncidentDetails = (props) => { + const { data, act } = useBackend(); const { suspect, can_pardon, diff --git a/tgui/packages/tgui/interfaces/CameraConsole.jsx b/tgui/packages/tgui/interfaces/CameraConsole.jsx index 89f4cc4e6d..b790100e98 100644 --- a/tgui/packages/tgui/interfaces/CameraConsole.jsx +++ b/tgui/packages/tgui/interfaces/CameraConsole.jsx @@ -37,8 +37,8 @@ export const selectCameras = (cameras, searchText = '') => { ])(cameras); }; -export const CameraConsole = (props, context) => { - const { act, data } = useBackend(context); +export const CameraConsole = (props) => { + const { act, data } = useBackend(); const { mapRef, activeCamera } = data; const cameras = selectCameras(data.cameras); const [prevCameraName, nextCameraName] = prevNextCamera( @@ -89,9 +89,9 @@ export const CameraConsole = (props, context) => { ); }; -export const CameraConsoleContent = (props, context) => { - const { act, data } = useBackend(context); - const [searchText, setSearchText] = useLocalState(context, 'searchText', ''); +export const CameraConsoleContent = (props) => { + const { act, data } = useBackend(); + const [searchText, setSearchText] = useLocalState('searchText', ''); const { activeCamera } = data; const cameras = selectCameras(data.cameras, searchText); return ( diff --git a/tgui/packages/tgui/interfaces/CanvasLayer.jsx b/tgui/packages/tgui/interfaces/CanvasLayer.jsx index e647ae765b..cf87e2f601 100644 --- a/tgui/packages/tgui/interfaces/CanvasLayer.jsx +++ b/tgui/packages/tgui/interfaces/CanvasLayer.jsx @@ -1,5 +1,5 @@ import { Box, Icon, Tooltip } from '../components'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; // this file should probably not be in interfaces, should move it later. export class CanvasLayer extends Component { diff --git a/tgui/packages/tgui/interfaces/CardMod.jsx b/tgui/packages/tgui/interfaces/CardMod.jsx index 6b27125f09..6580cb18df 100644 --- a/tgui/packages/tgui/interfaces/CardMod.jsx +++ b/tgui/packages/tgui/interfaces/CardMod.jsx @@ -1,12 +1,12 @@ -import { Fragment } from 'inferno'; +import { Fragment } from 'react'; import { useBackend, useLocalState } from '../backend'; import { Box, Button, Stack, Input, Section, Tabs, Table, NumberInput } from '../components'; import { Window } from '../layouts'; import { AccessList } from './common/AccessList'; import { map } from 'common/collections'; -export const CardMod = (props, context) => { - const [tab2, setTab2] = useLocalState(context, 'tab2', 1); +export const CardMod = (props) => { + const [tab2, setTab2] = useLocalState('tab2', 1); return ( @@ -27,8 +27,8 @@ export const CardMod = (props, context) => { ); }; -export const CrewManifest = (props, context) => { - const { act, data } = useBackend(context); +export const CrewManifest = (props) => { + const { act, data } = useBackend(); const { manifest = {} } = data; return (
{ ); }; -export const CardContent = (props, context) => { - const { act, data } = useBackend(context); - const [tab, setTab] = useLocalState(context, 'tab', 1); +export const CardContent = (props) => { + const { act, data } = useBackend(); + const [tab, setTab] = useLocalState('tab', 1); const { authenticated, regions = [], @@ -75,13 +75,12 @@ export const CardContent = (props, context) => { id_account, } = data; const [selectedDepartment, setSelectedDepartment] = useLocalState( - context, 'department', Object.keys(jobs)[0] ); const departmentJobs = jobs[selectedDepartment] || []; return ( - + <>
{ ) } buttons={ - + <>
{!!has_id && !!authenticated && ( @@ -229,6 +228,6 @@ export const CardContent = (props, context) => { )} )} -
+ ); }; diff --git a/tgui/packages/tgui/interfaces/Centrifuge.jsx b/tgui/packages/tgui/interfaces/Centrifuge.jsx index 45ed7bb4b6..94a1e7594f 100644 --- a/tgui/packages/tgui/interfaces/Centrifuge.jsx +++ b/tgui/packages/tgui/interfaces/Centrifuge.jsx @@ -2,8 +2,8 @@ import { useBackend } from '../backend'; import { Section, Flex, Button, Box, Input, NoticeBox } from '../components'; import { Window } from '../layouts'; -export const Centrifuge = (_props, context) => { - const { act, data } = useBackend(context); +export const Centrifuge = () => { + const { act, data } = useBackend(); return ( diff --git a/tgui/packages/tgui/interfaces/Changelog.jsx b/tgui/packages/tgui/interfaces/Changelog.jsx index 73982f2718..5d2fa9447b 100644 --- a/tgui/packages/tgui/interfaces/Changelog.jsx +++ b/tgui/packages/tgui/interfaces/Changelog.jsx @@ -1,6 +1,6 @@ import { classes } from 'common/react'; import { useBackend } from '../backend'; -import { Component, Fragment } from 'inferno'; +import { Component, Fragment } from 'react'; import { Box, Button, Dropdown, Icon, Section, Stack, Table } from '../components'; import { Window } from '../layouts'; import { resolveAsset } from '../assets'; @@ -60,7 +60,7 @@ export class Changelog extends Component { } getData = (date, attemptNumber = 1) => { - const { act } = useBackend(this.context); + const { act } = useBackend(); const self = this; const maxAttempts = 6; @@ -92,7 +92,7 @@ export class Changelog extends Component { componentDidMount() { const { data: { dates = [] }, - } = useBackend(this.context); + } = useBackend(); if (dates) { dates.forEach((date) => @@ -107,7 +107,7 @@ export class Changelog extends Component { const { data, selectedDate, selectedIndex } = this.state; const { data: { dates }, - } = useBackend(this.context); + } = useBackend(); const { dateChoices } = this; const dateDropdown = dateChoices.length > 0 && ( diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.jsx b/tgui/packages/tgui/interfaces/ChemDispenser.jsx index d4dadb58cf..77e4cac58a 100644 --- a/tgui/packages/tgui/interfaces/ChemDispenser.jsx +++ b/tgui/packages/tgui/interfaces/ChemDispenser.jsx @@ -3,8 +3,8 @@ import { useBackend } from '../backend'; import { AnimatedNumber, Box, Button, LabeledList, NoticeBox, ProgressBar, Section } from '../components'; import { Window } from '../layouts'; -export const ChemDispenser = (props, context) => { - const { act, data } = useBackend(context); +export const ChemDispenser = (props) => { + const { act, data } = useBackend(); const beakerTransferAmounts = data.beakerTransferAmounts || []; const beakerContents = data.beakerContents || []; return ( diff --git a/tgui/packages/tgui/interfaces/ChooseFruit.jsx b/tgui/packages/tgui/interfaces/ChooseFruit.jsx index 576897b162..1a1ee9f853 100644 --- a/tgui/packages/tgui/interfaces/ChooseFruit.jsx +++ b/tgui/packages/tgui/interfaces/ChooseFruit.jsx @@ -3,15 +3,11 @@ import { useBackend, useLocalState } from '../backend'; import { Tabs, Box, Section, Stack, Button } from '../components'; import { Window } from '../layouts'; -export const ChooseFruit = (props, context) => { - const { act, data } = useBackend(context); +export const ChooseFruit = (props) => { + const { act, data } = useBackend(); const { fruits, selected_fruit } = data; - const [compact, setCompact] = useLocalState( - context, - 'choosefruit_compact', - false - ); + const [compact, setCompact] = useLocalState('choosefruit_compact', false); let heightScale = 80; if (compact) heightScale = 45; diff --git a/tgui/packages/tgui/interfaces/ChooseResin.jsx b/tgui/packages/tgui/interfaces/ChooseResin.jsx index 47f91110c0..4a031ce9d9 100644 --- a/tgui/packages/tgui/interfaces/ChooseResin.jsx +++ b/tgui/packages/tgui/interfaces/ChooseResin.jsx @@ -5,15 +5,11 @@ import { Window } from '../layouts'; export const INFINITE_BUILD_AMOUNT = -1; -export const ChooseResin = (props, context) => { - const { act, data } = useBackend(context); +export const ChooseResin = (props) => { + const { act, data } = useBackend(); const { constructions, selected_resin } = data; - const [compact, setCompact] = useLocalState( - context, - 'chooseresin_compact', - false - ); + const [compact, setCompact] = useLocalState('chooseresin_compact', false); let heightScale = 80; if (compact) heightScale = 45; diff --git a/tgui/packages/tgui/interfaces/ColorMatrixEditor.tsx b/tgui/packages/tgui/interfaces/ColorMatrixEditor.tsx index 93edd6f1b2..ec5f775c35 100644 --- a/tgui/packages/tgui/interfaces/ColorMatrixEditor.tsx +++ b/tgui/packages/tgui/interfaces/ColorMatrixEditor.tsx @@ -10,8 +10,8 @@ type Data = { const PREFIXES = ['r', 'g', 'b', 'a', 'c'] as const; -export const ColorMatrixEditor = (props, context) => { - const { act, data } = useBackend(context); +export const ColorMatrixEditor = (props) => { + const { act, data } = useBackend(); const { mapRef, currentColor } = data; return ( diff --git a/tgui/packages/tgui/interfaces/CommandTablet.jsx b/tgui/packages/tgui/interfaces/CommandTablet.jsx index 8b334d1dac..429cc943db 100644 --- a/tgui/packages/tgui/interfaces/CommandTablet.jsx +++ b/tgui/packages/tgui/interfaces/CommandTablet.jsx @@ -2,8 +2,8 @@ import { useBackend } from '../backend'; import { Button, Section, Flex, NoticeBox } from '../components'; import { Window } from '../layouts'; -export const CommandTablet = (_props, context) => { - const { act, data } = useBackend(context); +export const CommandTablet = () => { + const { act, data } = useBackend(); const evacstatus = data.evac_status; diff --git a/tgui/packages/tgui/interfaces/CrewConsole.jsx b/tgui/packages/tgui/interfaces/CrewConsole.jsx index 15c49d2806..e84827ca7c 100644 --- a/tgui/packages/tgui/interfaces/CrewConsole.jsx +++ b/tgui/packages/tgui/interfaces/CrewConsole.jsx @@ -100,8 +100,8 @@ export const CrewConsole = () => { ); }; -const CrewTable = (props, context) => { - const { act, data } = useBackend(context); +const CrewTable = (props) => { + const { act, data } = useBackend(); const sensors = sortBy((s) => s.ijob)(data.sensors ?? []); return ( @@ -127,8 +127,8 @@ const CrewTable = (props, context) => { ); }; -const CrewTableEntry = (props, context) => { - const { act, data } = useBackend(context); +const CrewTableEntry = (props) => { + const { act, data } = useBackend(); const { link_allowed } = data; const { sensor_data } = props; const { diff --git a/tgui/packages/tgui/interfaces/Cryo.jsx b/tgui/packages/tgui/interfaces/Cryo.jsx index 2403528364..338717f2d0 100644 --- a/tgui/packages/tgui/interfaces/Cryo.jsx +++ b/tgui/packages/tgui/interfaces/Cryo.jsx @@ -32,8 +32,8 @@ export const Cryo = () => { ); }; -const CryoContent = (props, context) => { - const { act, data } = useBackend(context); +const CryoContent = (props) => { + const { act, data } = useBackend(); return ( <>
diff --git a/tgui/packages/tgui/interfaces/DemoSim.jsx b/tgui/packages/tgui/interfaces/DemoSim.jsx index 2c8b7fc25f..1ef44214de 100644 --- a/tgui/packages/tgui/interfaces/DemoSim.jsx +++ b/tgui/packages/tgui/interfaces/DemoSim.jsx @@ -2,8 +2,8 @@ import { useBackend } from '../backend'; import { Button, Section, ProgressBar, NoticeBox, Box, Stack } from '../components'; import { Window } from '../layouts'; -export const DemoSim = (_props, context) => { - const { act, data } = useBackend(context); +export const DemoSim = () => { + const { act, data } = useBackend(); const timeLeft = data.nextdetonationtime - data.worldtime; const timeLeftPct = timeLeft / data.detonation_cooldown; diff --git a/tgui/packages/tgui/interfaces/Disposals.jsx b/tgui/packages/tgui/interfaces/Disposals.jsx index c64a55a913..7fa8098869 100644 --- a/tgui/packages/tgui/interfaces/Disposals.jsx +++ b/tgui/packages/tgui/interfaces/Disposals.jsx @@ -2,8 +2,8 @@ import { useBackend } from '../backend'; import { Section, ProgressBar, Button, Box } from '../components'; import { Window } from '../layouts'; -export const Disposals = (_props, context) => { - const { act, data } = useBackend(context); +export const Disposals = () => { + const { act, data } = useBackend(); const { pressure, mode, flush } = data; diff --git a/tgui/packages/tgui/interfaces/DrawnMap.jsx b/tgui/packages/tgui/interfaces/DrawnMap.jsx index cd5a9539f8..0e789b631d 100644 --- a/tgui/packages/tgui/interfaces/DrawnMap.jsx +++ b/tgui/packages/tgui/interfaces/DrawnMap.jsx @@ -1,5 +1,5 @@ import { Box } from '../components'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; export class DrawnMap extends Component { constructor(props) { diff --git a/tgui/packages/tgui/interfaces/DropshipFlightControl.tsx b/tgui/packages/tgui/interfaces/DropshipFlightControl.tsx index 2e9c649e5b..fca0c39e4d 100644 --- a/tgui/packages/tgui/interfaces/DropshipFlightControl.tsx +++ b/tgui/packages/tgui/interfaces/DropshipFlightControl.tsx @@ -35,8 +35,8 @@ interface DropshipNavigationProps extends NavigationProps { playing_launch_announcement_alarm: boolean; } -const DropshipDoorControl = (_, context) => { - const { data, act } = useBackend(context); +const DropshipDoorControl = () => { + const { data, act } = useBackend(); const in_flight = data.shuttle_mode === 'called' || data.shuttle_mode === 'pre-arrival'; const disable_door_controls = in_flight; @@ -119,10 +119,9 @@ const DropshipDoorControl = (_, context) => { ); }; -export const DropshipDestinationSelection = (_, context) => { - const { data, act } = useBackend(context); +export const DropshipDestinationSelection = () => { + const { data, act } = useBackend(); const [siteselection, setSiteSelection] = useSharedState( - context, 'target_site', undefined ); @@ -157,8 +156,8 @@ interface DestinationProps { readonly availableOnly?: boolean; } -const DestinationSelector = (props: DestinationProps, context) => { - const { data } = useBackend(context); +const DestinationSelector = (props: DestinationProps) => { + const { data } = useBackend(); return ( <> {props.options @@ -194,8 +193,8 @@ const DestinationSelector = (props: DestinationProps, context) => { ); }; -export const TouchdownCooldown = (_, context) => { - const { data } = useBackend(context); +export const TouchdownCooldown = () => { + const { data } = useBackend(); return (
@@ -218,13 +217,12 @@ export const TouchdownCooldown = (_, context) => { ); }; -const AutopilotConfig = (props, context) => { - const { data, act } = useBackend(context); +const AutopilotConfig = (props) => { + const { data, act } = useBackend(); const [automatedHangar, setAutomatedHangar] = useSharedState< string | undefined - >(context, 'autopilot_hangar', undefined); + >('autopilot_hangar', undefined); const [automatedLZ, setAutomatedLZ] = useSharedState( - context, 'autopilot_groundside', undefined ); @@ -289,8 +287,8 @@ const AutopilotConfig = (props, context) => { ); }; -const StopLaunchAnnouncementAlarm = (_, context) => { - const { act } = useBackend(context); +const StopLaunchAnnouncementAlarm = () => { + const { act } = useBackend(); return (
{props.data @@ -57,9 +57,9 @@ const PlaytimeTable = (props: { readonly data: PlaytimeRecord[] }, context) => { ); }; -export const Playtime = (props, context) => { - const { data } = useBackend(context); - const [selected, setSelected] = useLocalState(context, 'selected', 'human'); +export const Playtime = (props) => { + const { data } = useBackend(); + const [selected, setSelected] = useLocalState('selected', 'human'); const humanTime = data.stored_human_playtime.length > 0 ? data.stored_human_playtime[0].playtime diff --git a/tgui/packages/tgui/interfaces/PodLauncher.jsx b/tgui/packages/tgui/interfaces/PodLauncher.jsx index 751fe1d386..22f44abaf5 100644 --- a/tgui/packages/tgui/interfaces/PodLauncher.jsx +++ b/tgui/packages/tgui/interfaces/PodLauncher.jsx @@ -2,7 +2,7 @@ import { toFixed } from 'common/math'; import { storage } from 'common/storage'; import { multiline } from 'common/string'; import { createUuid } from 'common/uuid'; -import { Component, Fragment } from 'inferno'; +import { Component, Fragment } from 'react'; import { useBackend, useLocalState } from '../backend'; import { Box, Button, ByondUi, Divider, Input, Knob, LabeledControls, NumberInput, Section, Flex, Slider } from '../components'; import { Window } from '../layouts'; @@ -11,7 +11,7 @@ const pod_grey = { color: 'grey', }; -export const PodLauncher = (props, context) => { +export const PodLauncher = (props) => { return ( @@ -19,7 +19,7 @@ export const PodLauncher = (props, context) => { ); }; -const PodLauncherContent = (props, context) => { +const PodLauncherContent = (props) => { return ( @@ -132,8 +132,8 @@ const EFFECTS_LOAD = [ selected: (data) => { return data['launch_choice'] === data.glob_launch_options.LAUNCH_ALL; }, - onClick: (context) => { - const { act, data } = useBackend(context); + onClick: () => { + const { act, data } = useBackend(); act('set_launch_option', { launch_option: 'LAUNCH_ALL' }); }, }, @@ -143,8 +143,8 @@ const EFFECTS_LOAD = [ selected: (data) => { return data['launch_choice'] === data.glob_launch_options.LAUNCH_RANDOM; }, - onClick: (context) => { - const { act, data } = useBackend(context); + onClick: () => { + const { act, data } = useBackend(); act('set_launch_option', { launch_option: 'LAUNCH_RANDOM' }); }, }, @@ -157,8 +157,8 @@ const EFFECTS_LOAD = [ selected: (data) => { return !data['launch_random_item']; }, - onClick: (context) => { - const { act, data } = useBackend(context); + onClick: () => { + const { act, data } = useBackend(); act('launch_random_item', { should_do: false }); }, }, @@ -168,8 +168,8 @@ const EFFECTS_LOAD = [ selected: (data) => { return data['launch_random_item']; }, - onClick: (context) => { - const { act, data } = useBackend(context); + onClick: () => { + const { act, data } = useBackend(); act('launch_random_item', { should_do: true }); }, }, @@ -180,8 +180,8 @@ const EFFECTS_LOAD = [ title: 'Clone', icon: 'clone', selected: (data) => data['launch_clone'], - onClick: (context) => { - const { act, data } = useBackend(context); + onClick: () => { + const { act, data } = useBackend(); act('launch_clone', { should_do: !data['launch_clone'] }); }, }, @@ -189,8 +189,8 @@ const EFFECTS_LOAD = [ title: 'Recall', icon: 'redo', selected: (data) => data['should_recall'], - onClick: (context) => { - const { act, data } = useBackend(context); + onClick: () => { + const { act, data } = useBackend(); act('set_should_recall', { should_do: !data['should_recall'] }); }, }, @@ -203,8 +203,8 @@ const EFFECTS_NORMAL = [ selected: (data) => { return data['gib_on_land']; }, - onClick: (context) => { - const { act, data } = useBackend(context); + onClick: () => { + const { act, data } = useBackend(); act('set_gib_on_land', { should_do: !data['gib_on_land'] }); }, }, @@ -214,8 +214,8 @@ const EFFECTS_NORMAL = [ selected: (data) => { return data['stealth']; }, - onClick: (context) => { - const { act, data } = useBackend(context); + onClick: () => { + const { act, data } = useBackend(); act('set_stealth', { should_do: !data['stealth'] }); }, }, @@ -225,8 +225,8 @@ const EFFECTS_NORMAL = [ selected: (data) => { return data['can_be_opened']; }, - onClick: (context) => { - const { act, data } = useBackend(context); + onClick: () => { + const { act, data } = useBackend(); act('set_can_be_opened', { should_do: !data['can_be_opened'] }); }, }, @@ -246,13 +246,9 @@ const EFFECTS_ALL = [ }, ]; -const ViewTabHolder = (props, context) => { - const { act, data } = useBackend(context); - const [tabPageIndex, setTabPageIndex] = useLocalState( - context, - 'tabPageIndex', - 1 - ); +const ViewTabHolder = (props) => { + const { act, data } = useBackend(); + const [tabPageIndex, setTabPageIndex] = useLocalState('tabPageIndex', 1); const { glob_tab_indexes, custom_dropoff, map_ref } = data; const TabPageComponent = TABPAGES[tabPageIndex].component(); return ( @@ -342,7 +338,7 @@ const ViewTabHolder = (props, context) => { ); }; -const TabPod = (props, context) => { +const TabPod = (props) => { return ( Note: You can right click on this @@ -352,8 +348,8 @@ const TabPod = (props, context) => { ); }; -const TabBay = (props, context) => { - const { act, data } = useBackend(context); +const TabBay = (props) => { + const { act, data } = useBackend(); return ( <>
{props.items @@ -43,16 +43,12 @@ const ContentsTable = ( ); }; -const Contents = ( - props: { - readonly isLocal: boolean; - readonly items: StorageItem[]; - readonly title: string; - }, - context -) => { +const Contents = (props: { + readonly isLocal: boolean; + readonly items: StorageItem[]; + readonly title: string; +}) => { const [tabIndex, setTabIndex] = useLocalState( - context, `contentsTab_${props.isLocal}`, 'all' ); @@ -109,11 +105,11 @@ const Contents = ( ); }; -const ContentItem = ( - props: { readonly isLocal: boolean; readonly item: StorageItem }, - context -) => { - const { data, act } = useBackend(context); +const ContentItem = (props: { + readonly isLocal: boolean; + readonly item: StorageItem; +}) => { + const { data, act } = useBackend(); const { item } = props; const itemref = { 'index': item.index, 'amount': 1, isLocal: props.isLocal }; return ( @@ -149,8 +145,8 @@ const ContentItem = ( ); }; -export const SmartFridge = (_, context) => { - const { data } = useBackend(context); +export const SmartFridge = () => { + const { data } = useBackend(); return ( diff --git a/tgui/packages/tgui/interfaces/Smes.jsx b/tgui/packages/tgui/interfaces/Smes.jsx index 47f6e11991..7dce4bf194 100644 --- a/tgui/packages/tgui/interfaces/Smes.jsx +++ b/tgui/packages/tgui/interfaces/Smes.jsx @@ -6,8 +6,8 @@ import { Window } from '../layouts'; // Common power multiplier const POWER_MUL = 1e3; -export const Smes = (props, context) => { - const { act, data } = useBackend(context); +export const Smes = (props) => { + const { act, data } = useBackend(); const { capacityPercent, capacity, diff --git a/tgui/packages/tgui/interfaces/SquadInfo.tsx b/tgui/packages/tgui/interfaces/SquadInfo.tsx index 663e80f7c0..e0dfc72e85 100644 --- a/tgui/packages/tgui/interfaces/SquadInfo.tsx +++ b/tgui/packages/tgui/interfaces/SquadInfo.tsx @@ -47,11 +47,8 @@ interface SquadProps { objective: { primary?: string; secondary?: string }; } -const FireTeamLeadLabel = ( - props: { readonly ftl: SquadMarineEntry }, - context -) => { - const { data } = useBackend(context); +const FireTeamLeadLabel = (props: { readonly ftl: SquadMarineEntry }) => { + const { data } = useBackend(); const { ftl } = props; return ( <> @@ -75,11 +72,11 @@ const FireTeamLeadLabel = ( ); }; -const FireTeamLead = ( - props: { readonly fireteam: FireTeamEntry; readonly sqsgt: string }, - context -) => { - const { data, act } = useBackend(context); +const FireTeamLead = (props: { + readonly fireteam: FireTeamEntry; + readonly sqsgt: string; +}) => { + const { data, act } = useBackend(); const fireteamLead = props.fireteam.sqsgt; const isNotAssigned = fireteamLead === undefined || @@ -114,7 +111,7 @@ interface FireteamBoxProps extends BoxProps { readonly isEmpty: boolean; } -const FireteamBox = (props: FireteamBoxProps, context) => { +const FireteamBox = (props: FireteamBoxProps) => { return (
{props.name}
@@ -123,8 +120,8 @@ const FireteamBox = (props: FireteamBoxProps, context) => { ); }; -const FireTeam = (props: { readonly sqsgt: string }, context) => { - const { data, act } = useBackend(context); +const FireTeam = (props: { readonly sqsgt: string }) => { + const { data, act } = useBackend(); const fireteam: FireTeamEntry = data.fireteams[props.sqsgt]; const members: SquadMarineEntry[] = @@ -193,15 +190,12 @@ const FireTeam = (props: { readonly sqsgt: string }, context) => { ); }; -const FireTeamMember = ( - props: { - readonly member: SquadMarineEntry; - readonly team: string; - readonly fireteam?: FireTeamEntry; - }, - context -) => { - const { data, act } = useBackend(context); +const FireTeamMember = (props: { + readonly member: SquadMarineEntry; + readonly team: string; + readonly fireteam?: FireTeamEntry; +}) => { + const { data, act } = useBackend(); const assignFT1 = { target_ft: 'SQ1', target_marine: props.member.name }; const assignFT2 = { target_ft: 'SQ2', target_marine: props.member.name }; @@ -269,8 +263,8 @@ const FireTeamMember = ( ); }; -const SquadObjectives = (props, context) => { - const { data } = useBackend(context); +const SquadObjectives = (props) => { + const { data } = useBackend(); const primaryObjective = data.objective?.primary ?? 'Unset'; const secondaryObjective = data.objective?.secondary ?? 'Unset'; return ( @@ -285,8 +279,8 @@ const SquadObjectives = (props, context) => { ); }; -export const SquadInfo = (_, context) => { - const { config, data } = useBackend(context); +export const SquadInfo = () => { + const { config, data } = useBackend(); const fireteams = ['SQ1', 'SQ2', 'Unassigned']; return ( diff --git a/tgui/packages/tgui/interfaces/SquadMod.jsx b/tgui/packages/tgui/interfaces/SquadMod.jsx index 15ce727a53..d2b7722bd8 100644 --- a/tgui/packages/tgui/interfaces/SquadMod.jsx +++ b/tgui/packages/tgui/interfaces/SquadMod.jsx @@ -2,8 +2,8 @@ import { useBackend } from '../backend'; import { Button, Stack, Section, NoticeBox } from '../components'; import { Window } from '../layouts'; -export const SquadMod = (props, context) => { - const { act, data } = useBackend(context); +export const SquadMod = (props) => { + const { act, data } = useBackend(); const { squads = [], human, id_name, has_id } = data; return ( diff --git a/tgui/packages/tgui/interfaces/StatbrowserOptions.jsx b/tgui/packages/tgui/interfaces/StatbrowserOptions.jsx index d4a1a48233..2b17124533 100644 --- a/tgui/packages/tgui/interfaces/StatbrowserOptions.jsx +++ b/tgui/packages/tgui/interfaces/StatbrowserOptions.jsx @@ -2,14 +2,10 @@ import { useBackend, useLocalState } from '../backend'; import { Flex, NumberInput, Section } from '../components'; import { Window } from '../layouts'; -export const StatbrowserOptions = (props, context) => { - const { act, data } = useBackend(context); +export const StatbrowserOptions = (props) => { + const { act, data } = useBackend(); const { current_fontsize } = data; - const [fontsize, setFontsize] = useLocalState( - context, - 'fontsize', - current_fontsize - ); + const [fontsize, setFontsize] = useLocalState('fontsize', current_fontsize); return ( @@ -33,7 +29,7 @@ export const StatbrowserOptions = (props, context) => { ); }; -const Options = (props, context) => { +const Options = (props) => { const { children } = props; return ( @@ -49,7 +45,7 @@ const Options = (props, context) => { ); }; -const Option = (props, context) => { +const Option = (props) => { const { category, input } = props; return ( @@ -60,7 +56,7 @@ const Option = (props, context) => { ); }; -const NumberOption = (props, context) => { +const NumberOption = (props) => { const { category, ...rest } = props; return