{
this.setSelected(value);
}}>
@@ -256,27 +248,39 @@ export class Dropdown extends Component
{
}
toPrevious(): void {
- const selectedIndex = this.getSelectedIndex();
-
- if (selectedIndex < 0) {
+ if (this.props.options.length < 1) {
return;
}
+ let selectedIndex = this.getSelectedIndex();
+ const startIndex = 0;
const endIndex = this.props.options.length - 1;
- const previousIndex = selectedIndex === 0 ? endIndex : selectedIndex - 1;
+
+ const hasSelected = selectedIndex >= 0;
+ if (!hasSelected) {
+ selectedIndex = startIndex;
+ }
+
+ const previousIndex = selectedIndex === startIndex ? endIndex : selectedIndex - 1;
this.setSelected(this.getOptionValue(this.props.options[previousIndex]));
}
toNext(): void {
- const selectedIndex = this.getSelectedIndex();
-
- if (selectedIndex < 0) {
+ if (this.props.options.length < 1) {
return;
}
+ let selectedIndex = this.getSelectedIndex();
+ const startIndex = 0;
const endIndex = this.props.options.length - 1;
- const nextIndex = selectedIndex === endIndex ? 0 : selectedIndex + 1;
+
+ const hasSelected = selectedIndex >= 0;
+ if (!hasSelected) {
+ selectedIndex = endIndex;
+ }
+
+ const nextIndex = selectedIndex === endIndex ? startIndex : selectedIndex + 1;
this.setSelected(this.getOptionValue(this.props.options[nextIndex]));
}
diff --git a/tgui/packages/tgui/components/Grid.js b/tgui/packages/tgui/components/Grid.jsx
similarity index 100%
rename from tgui/packages/tgui/components/Grid.js
rename to tgui/packages/tgui/components/Grid.jsx
diff --git a/tgui/packages/tgui/components/InfinitePlane.js b/tgui/packages/tgui/components/InfinitePlane.jsx
similarity index 100%
rename from tgui/packages/tgui/components/InfinitePlane.js
rename to tgui/packages/tgui/components/InfinitePlane.jsx
diff --git a/tgui/packages/tgui/components/Input.js b/tgui/packages/tgui/components/Input.jsx
similarity index 100%
rename from tgui/packages/tgui/components/Input.js
rename to tgui/packages/tgui/components/Input.jsx
diff --git a/tgui/packages/tgui/components/Knob.js b/tgui/packages/tgui/components/Knob.jsx
similarity index 100%
rename from tgui/packages/tgui/components/Knob.js
rename to tgui/packages/tgui/components/Knob.jsx
diff --git a/tgui/packages/tgui/components/LabeledControls.js b/tgui/packages/tgui/components/LabeledControls.jsx
similarity index 100%
rename from tgui/packages/tgui/components/LabeledControls.js
rename to tgui/packages/tgui/components/LabeledControls.jsx
diff --git a/tgui/packages/tgui/components/Modal.js b/tgui/packages/tgui/components/Modal.jsx
similarity index 100%
rename from tgui/packages/tgui/components/Modal.js
rename to tgui/packages/tgui/components/Modal.jsx
diff --git a/tgui/packages/tgui/components/NoticeBox.js b/tgui/packages/tgui/components/NoticeBox.jsx
similarity index 100%
rename from tgui/packages/tgui/components/NoticeBox.js
rename to tgui/packages/tgui/components/NoticeBox.jsx
diff --git a/tgui/packages/tgui/components/NumberInput.js b/tgui/packages/tgui/components/NumberInput.jsx
similarity index 100%
rename from tgui/packages/tgui/components/NumberInput.js
rename to tgui/packages/tgui/components/NumberInput.jsx
diff --git a/tgui/packages/tgui/components/OrbitalMapComponent.js b/tgui/packages/tgui/components/OrbitalMapComponent.jsx
similarity index 100%
rename from tgui/packages/tgui/components/OrbitalMapComponent.js
rename to tgui/packages/tgui/components/OrbitalMapComponent.jsx
diff --git a/tgui/packages/tgui/components/OrbitalMapSvg.js b/tgui/packages/tgui/components/OrbitalMapSvg.jsx
similarity index 100%
rename from tgui/packages/tgui/components/OrbitalMapSvg.js
rename to tgui/packages/tgui/components/OrbitalMapSvg.jsx
diff --git a/tgui/packages/tgui/components/ProgressBar.js b/tgui/packages/tgui/components/ProgressBar.js
deleted file mode 100644
index 151516299b655..0000000000000
--- a/tgui/packages/tgui/components/ProgressBar.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { clamp01, scale, keyOfMatchingRange, toFixed } from 'common/math';
-import { classes, pureComponentHooks } from 'common/react';
-import { computeBoxClassName, computeBoxProps } from './Box';
-
-export const ProgressBar = (props) => {
- const { className, value, minValue = 0, maxValue = 1, color, ranges = {}, children, ...rest } = props;
- const scaledValue = scale(value, minValue, maxValue);
- const hasContent = children !== undefined;
- const effectiveColor = color || keyOfMatchingRange(value, ranges) || 'default';
- return (
-
-
-
{hasContent ? children : toFixed(scaledValue * 100) + '%'}
-
- );
-};
-
-ProgressBar.defaultHooks = pureComponentHooks;
diff --git a/tgui/packages/tgui/components/ProgressBar.jsx b/tgui/packages/tgui/components/ProgressBar.jsx
new file mode 100644
index 0000000000000..1f7658e28f159
--- /dev/null
+++ b/tgui/packages/tgui/components/ProgressBar.jsx
@@ -0,0 +1,42 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { clamp01, scale, keyOfMatchingRange, toFixed } from 'common/math';
+import { classes, pureComponentHooks } from 'common/react';
+import { computeBoxClassName, computeBoxProps } from './Box';
+import { CSS_COLORS } from '../constants';
+
+export const ProgressBar = (props) => {
+ const { className, value, minValue = 0, maxValue = 1, color, ranges = {}, children, ...rest } = props;
+ const scaledValue = scale(value, minValue, maxValue);
+ const hasContent = children !== undefined;
+ 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);
+ const outerClasses = ['ProgressBar', className, computeBoxClassName(rest)];
+ const fillStyles = {
+ 'width': clamp01(scaledValue) * 100 + '%',
+ };
+ if (CSS_COLORS.includes(effectiveColor) || effectiveColor === 'default') {
+ // If the color is a color- class, just use that.
+ outerClasses.push('ProgressBar--color--' + effectiveColor);
+ } else {
+ // Otherwise, set styles directly.
+ outerProps.style = (outerProps.style || '') + `border-color: ${effectiveColor};`;
+ fillStyles['background-color'] = effectiveColor;
+ }
+
+ return (
+
+
+
{hasContent ? children : toFixed(scaledValue * 100) + '%'}
+
+ );
+};
+
+ProgressBar.defaultHooks = pureComponentHooks;
diff --git a/tgui/packages/tgui/components/RestrictedInput.js b/tgui/packages/tgui/components/RestrictedInput.jsx
similarity index 100%
rename from tgui/packages/tgui/components/RestrictedInput.js
rename to tgui/packages/tgui/components/RestrictedInput.jsx
diff --git a/tgui/packages/tgui/components/ScrollableBox.js b/tgui/packages/tgui/components/ScrollableBox.jsx
similarity index 100%
rename from tgui/packages/tgui/components/ScrollableBox.js
rename to tgui/packages/tgui/components/ScrollableBox.jsx
diff --git a/tgui/packages/tgui/components/Section.tsx b/tgui/packages/tgui/components/Section.tsx
index b3adbcbc756c6..13dcf2dadbb99 100644
--- a/tgui/packages/tgui/components/Section.tsx
+++ b/tgui/packages/tgui/components/Section.tsx
@@ -4,11 +4,11 @@
* @license MIT
*/
-import { canRender, classes } from 'common/react';
-import { Component, createRef, RefObject } from 'inferno';
-import { addScrollableNode, removeScrollableNode } from '../events';
import { BoxProps, computeBoxClassName, computeBoxProps } from './Box';
+import { Component, createRef, RefObject } from 'inferno';
import type { InfernoNode } from 'inferno';
+import { addScrollableNode, removeScrollableNode } from '../events';
+import { canRender, classes } from 'common/react';
interface SectionProps extends BoxProps {
className?: string;
@@ -18,9 +18,9 @@ interface SectionProps extends BoxProps {
fitted?: boolean;
scrollable?: boolean;
/** @deprecated This property no longer works, please remove it. */
- level?: boolean;
+ level?: never;
/** @deprecated Please use `scrollable` property */
- overflowY?: any;
+ overflowY?: never;
/** @member Allows external control of scrolling. */
scrollableRef?: RefObject;
/** @member Callback function for the `scroll` event */
@@ -41,7 +41,7 @@ export class Section extends Component {
componentDidMount() {
if (this.scrollable) {
- addScrollableNode(this.scrollableRef.current);
+ addScrollableNode(this.scrollableRef.current as HTMLElement);
if (this.onScroll && this.scrollableRef.current) {
this.scrollableRef.current.onscroll = this.onScroll;
}
@@ -50,7 +50,7 @@ export class Section extends Component {
componentWillUnmount() {
if (this.scrollable) {
- removeScrollableNode(this.scrollableRef.current);
+ removeScrollableNode(this.scrollableRef.current as HTMLElement);
}
}
diff --git a/tgui/packages/tgui/components/Slider.js b/tgui/packages/tgui/components/Slider.jsx
similarity index 100%
rename from tgui/packages/tgui/components/Slider.js
rename to tgui/packages/tgui/components/Slider.jsx
diff --git a/tgui/packages/tgui/components/Table.js b/tgui/packages/tgui/components/Table.jsx
similarity index 100%
rename from tgui/packages/tgui/components/Table.js
rename to tgui/packages/tgui/components/Table.jsx
diff --git a/tgui/packages/tgui/components/Tabs.js b/tgui/packages/tgui/components/Tabs.jsx
similarity index 100%
rename from tgui/packages/tgui/components/Tabs.js
rename to tgui/packages/tgui/components/Tabs.jsx
diff --git a/tgui/packages/tgui/components/TextArea.js b/tgui/packages/tgui/components/TextArea.jsx
similarity index 100%
rename from tgui/packages/tgui/components/TextArea.js
rename to tgui/packages/tgui/components/TextArea.jsx
diff --git a/tgui/packages/tgui/components/TimeDisplay.js b/tgui/packages/tgui/components/TimeDisplay.jsx
similarity index 100%
rename from tgui/packages/tgui/components/TimeDisplay.js
rename to tgui/packages/tgui/components/TimeDisplay.jsx
diff --git a/tgui/packages/tgui/components/index.js b/tgui/packages/tgui/components/index.ts
similarity index 100%
rename from tgui/packages/tgui/components/index.js
rename to tgui/packages/tgui/components/index.ts
diff --git a/tgui/packages/tgui/constants.js b/tgui/packages/tgui/constants.ts
similarity index 61%
rename from tgui/packages/tgui/constants.js
rename to tgui/packages/tgui/constants.ts
index 182bcd0634c7a..0697a2395c5ec 100644
--- a/tgui/packages/tgui/constants.js
+++ b/tgui/packages/tgui/constants.ts
@@ -4,6 +4,14 @@
* @license MIT
*/
+type Gas = {
+ id: string;
+ path: string;
+ name: string;
+ label: string;
+ color: string;
+};
+
// UI states, which are mirrored from the BYOND code.
export const UI_INTERACTIVE = 2;
export const UI_UPDATE = 1;
@@ -31,7 +39,12 @@ export const COLORS = {
burn: '#e67e22',
brute: '#e74c3c',
},
-};
+ // reagent / chemistry related colours
+ reagent: {
+ acidicbuffer: '#fbc314',
+ basicbuffer: '#3853a4',
+ },
+} as const;
// Colors defined in CSS
export const CSS_COLORS = [
@@ -126,7 +139,7 @@ export const RADIO_CHANNELS = [
freq: 1459,
color: '#1ecc43',
},
-];
+] as const;
const GASES = [
{
@@ -157,7 +170,7 @@ const GASES = [
'id': 'water_vapor',
'name': 'Water Vapor',
'label': 'H₂O',
- 'color': 'grey',
+ 'color': 'lightsteelblue',
},
{
'id': 'nob',
@@ -169,7 +182,7 @@ const GASES = [
'id': 'n2o',
'name': 'Nitrous Oxide',
'label': 'N₂O',
- 'color': 'red',
+ 'color': 'bisque',
},
{
'id': 'no2',
@@ -181,25 +194,25 @@ const GASES = [
'id': 'tritium',
'name': 'Tritium',
'label': 'Tritium',
- 'color': 'green',
+ 'color': 'limegreen',
},
{
'id': 'bz',
'name': 'BZ',
'label': 'BZ',
- 'color': 'purple',
+ 'color': 'mediumpurple',
},
{
'id': 'stim',
'name': 'Stimulum',
'label': 'Stimulum',
- 'color': 'purple',
+ 'color': 'darkviolet',
},
{
'id': 'pluox',
'name': 'Pluoxium',
'label': 'Pluoxium',
- 'color': 'blue',
+ 'color': 'mediumslateblue',
},
{
'id': 'miasma',
@@ -207,22 +220,88 @@ const GASES = [
'label': 'Miasma',
'color': 'olive',
},
+ {
+ 'id': 'Freon',
+ 'name': 'Freon',
+ 'label': 'Freon',
+ 'color': 'paleturquoise',
+ },
{
'id': 'hydrogen',
'name': 'Hydrogen',
'label': 'H₂',
'color': 'white',
},
-];
+ // Bee doesn't have most of these \/ - but having them for future proofing is useful. Nothing iterates this list.
+ {
+ 'id': 'healium',
+ 'name': 'Healium',
+ 'label': 'Healium',
+ 'color': 'salmon',
+ },
+ {
+ 'id': 'proto_nitrate',
+ 'name': 'Proto Nitrate',
+ 'label': 'Proto-Nitrate',
+ 'color': 'greenyellow',
+ },
+ {
+ 'id': 'zauker',
+ 'name': 'Zauker',
+ 'label': 'Zauker',
+ 'color': 'darkgreen',
+ },
+ {
+ 'id': 'halon',
+ 'name': 'Halon',
+ 'label': 'Halon',
+ 'color': 'purple',
+ },
+ {
+ 'id': 'helium',
+ 'name': 'Helium',
+ 'label': 'He',
+ 'color': 'aliceblue',
+ },
+ {
+ 'id': 'antinoblium',
+ 'name': 'Antinoblium',
+ 'label': 'Anti-Noblium',
+ 'color': 'maroon',
+ },
+] as const;
-export const getGasLabel = (gasId, fallbackValue) => {
- const gasSearchString = String(gasId).toLowerCase();
+// Returns gas label based on gasId
+export const getGasLabel = (gasId: string, fallbackValue?: string) => {
+ const gasSearchString = gasId.toLowerCase();
const gas = GASES.find((gas) => gas.id === gasSearchString || gas.name.toLowerCase() === gasSearchString);
- return (gas && gas.label) || fallbackValue || gasId;
+ return gas?.label || fallbackValue || gasId;
};
-export const getGasColor = (gasId) => {
- const gasSearchString = String(gasId).toLowerCase();
+// Returns gas color based on gasId
+export const getGasColor = (gasId: string) => {
+ const gasSearchString = gasId.toLowerCase();
const gas = GASES.find((gas) => gas.id === gasSearchString || gas.name.toLowerCase() === gasSearchString);
- return gas && gas.color;
+ return gas?.color;
+};
+
+/*
+From https://github.com/tgstation/tgstation/pull/69240
+
+PLEASE enable the tests in constants.test.ts if you port this
+
+// Returns gas object based on gasId
+export const getGasFromId = (gasId: string): Gas | undefined => {
+ const gasSearchString = gasId.toLowerCase();
+ const gas = GASES.find(
+ (gas) =>
+ gas.id === gasSearchString || gas.name.toLowerCase() === gasSearchString
+ );
+ return gas;
+};
+
+// Returns gas object based on gasPath
+export const getGasFromPath = (gasPath: string): Gas | undefined => {
+ return GASES.find((gas) => gas.path === gasPath);
};
+*/
diff --git a/tgui/packages/tgui/contants.test.ts b/tgui/packages/tgui/contants.test.ts
new file mode 100644
index 0000000000000..51c863b3387e9
--- /dev/null
+++ b/tgui/packages/tgui/contants.test.ts
@@ -0,0 +1,72 @@
+import { getGasColor, getGasLabel } from './constants';
+
+describe('gas helper functions', () => {
+ it('should get the proper gas label', () => {
+ const gasId = 'antinoblium';
+ const gasLabel = getGasLabel(gasId);
+ expect(gasLabel).toBe('Anti-Noblium');
+ });
+
+ it('should get the proper gas label with a fallback', () => {
+ const gasId = 'nonexistent';
+ const gasLabel = getGasLabel(gasId, 'fallback');
+
+ expect(gasLabel).toBe('fallback');
+ });
+
+ it('should return the id if no gas and no fallback is found', () => {
+ const gasId = 'nonexistent';
+ const gasLabel = getGasLabel(gasId);
+
+ expect(gasLabel).toBe('nonexistent');
+ });
+
+ it('should get the proper gas color', () => {
+ const gasId = 'antinoblium';
+ const gasColor = getGasColor(gasId);
+
+ expect(gasColor).toBe('maroon');
+ });
+
+ it('should return undefined if no gas is found', () => {
+ const gasId = 'nonexistent';
+ const gasColor = getGasColor(gasId);
+
+ expect(gasColor).toBeUndefined();
+ });
+ /*
+ https://github.com/tgstation/tgstation/pull/69240
+ it('should return the gas object if found', () => {
+ const gasId = 'antinoblium';
+ const gas = getGasFromId(gasId);
+
+ expect(gas).toEqual({
+ id: 'antinoblium',
+ path: '/datum/gas/antinoblium',
+ name: 'Antinoblium',
+ label: 'Anti-Noblium',
+ color: 'maroon',
+ });
+ });
+
+ it('should return undefined if no gas is found', () => {
+ const gasId = 'nonexistent';
+ const gas = getGasFromId(gasId);
+
+ expect(gas).toBeUndefined();
+ });
+
+ it('should return the gas using a path', () => {
+ const gasPath = '/datum/gas/antinoblium';
+ const gas = getGasFromPath(gasPath);
+
+ expect(gas).toEqual({
+ id: 'antinoblium',
+ path: '/datum/gas/antinoblium',
+ name: 'Antinoblium',
+ label: 'Anti-Noblium',
+ color: 'maroon',
+ });
+ });
+ */
+});
diff --git a/tgui/packages/tgui/debug/KitchenSink.js b/tgui/packages/tgui/debug/KitchenSink.jsx
similarity index 95%
rename from tgui/packages/tgui/debug/KitchenSink.js
rename to tgui/packages/tgui/debug/KitchenSink.jsx
index 372074d215ad7..ab51025e1d91e 100644
--- a/tgui/packages/tgui/debug/KitchenSink.js
+++ b/tgui/packages/tgui/debug/KitchenSink.jsx
@@ -8,7 +8,7 @@ import { useLocalState } from '../backend';
import { Flex, Section, Tabs } from '../components';
import { Pane, Window } from '../layouts';
-const r = require.context('../stories', false, /\.stories\.js$/);
+const r = require.context('../stories', false, /\.stories\.jsx$/);
/**
* @returns {{
diff --git a/tgui/packages/tgui/debug/index.js b/tgui/packages/tgui/debug/index.ts
similarity index 100%
rename from tgui/packages/tgui/debug/index.js
rename to tgui/packages/tgui/debug/index.ts
diff --git a/tgui/packages/tgui/drag.js b/tgui/packages/tgui/drag.ts
similarity index 69%
rename from tgui/packages/tgui/drag.js
rename to tgui/packages/tgui/drag.ts
index 73c47cce55fdc..048e1c770175c 100644
--- a/tgui/packages/tgui/drag.js
+++ b/tgui/packages/tgui/drag.ts
@@ -4,47 +4,54 @@
* @license MIT
*/
-import { storage } from 'common/storage';
-import { vecAdd, vecSubtract, vecMultiply, vecScale } from 'common/vector';
+import { vecAdd, vecMultiply, vecScale, vecSubtract } from 'common/vector';
+
import { createLogger } from './logging';
+import { storage } from 'common/storage';
const logger = createLogger('drag');
const pixelRatio = window.devicePixelRatio ?? 1;
-
let windowKey = Byond.windowId;
let dragging = false;
let resizing = false;
-let screenOffset = [0, 0];
-let screenOffsetPromise;
-let dragPointOffset;
-let resizeMatrix;
-let initialSize;
-let size;
-
-export const setWindowKey = (key) => {
+let screenOffset: [number, number] = [0, 0];
+let screenOffsetPromise: Promise<[number, number]>;
+let dragPointOffset: [number, number];
+let resizeMatrix: [number, number];
+let initialSize: [number, number];
+let size: [number, number];
+
+// Set the window key
+export const setWindowKey = (key: string): void => {
windowKey = key;
};
-const getWindowPosition = () => [window.screenLeft * pixelRatio, window.screenTop * pixelRatio];
+// Get window position
+export const getWindowPosition = (): [number, number] => [window.screenLeft * pixelRatio, window.screenTop * pixelRatio];
-const getWindowSize = () => [window.innerWidth * pixelRatio, window.innerHeight * pixelRatio];
+// Get window size
+export const getWindowSize = (): [number, number] => [window.innerWidth * pixelRatio, window.innerHeight * pixelRatio];
-const setWindowPosition = (vec) => {
+// Set window position
+const setWindowPosition = (vec: [number, number]) => {
const byondPos = vecAdd(vec, screenOffset);
return Byond.winset(Byond.windowId, {
pos: byondPos[0] + ',' + byondPos[1],
});
};
-const setWindowSize = (vec) => {
+// Set window size
+const setWindowSize = (vec: [number, number]) => {
return Byond.winset(Byond.windowId, {
size: vec[0] + 'x' + vec[1],
});
};
-const getScreenPosition = () => [0 - screenOffset[0], 0 - screenOffset[1]];
+// Get screen position
+const getScreenPosition = (): [number, number] => [0 - screenOffset[0], 0 - screenOffset[1]];
-const getScreenSize = () => [window.screen.availWidth * pixelRatio, window.screen.availHeight * pixelRatio];
+// Get screen size
+const getScreenSize = (): [number, number] => [window.screen.availWidth * pixelRatio, window.screen.availHeight * pixelRatio];
/**
* Moves an item to the top of the recents array, and keeps its length
@@ -54,9 +61,9 @@ const getScreenSize = () => [window.screen.availWidth * pixelRatio, window.scree
*
* Returns new recents and an item which was trimmed.
*/
-const touchRecents = (recents, touchedItem, limit = 50) => {
- const nextRecents = [touchedItem];
- let trimmedItem;
+export const touchRecents = (recents: string[], touchedItem: string, limit = 50): [string[], string | undefined] => {
+ const nextRecents: string[] = [touchedItem];
+ let trimmedItem: string | undefined;
for (let i = 0; i < recents.length; i++) {
const item = recents[i];
if (item === touchedItem) {
@@ -71,6 +78,7 @@ const touchRecents = (recents, touchedItem, limit = 50) => {
return [nextRecents, trimmedItem];
};
+// Store window geometry in local storage
const storeWindowGeometry = async () => {
logger.log('storing geometry');
const geometry = {
@@ -86,8 +94,15 @@ const storeWindowGeometry = async () => {
storage.set('geometries', geometries);
};
-export const recallWindowGeometry = async (options = {}) => {
- // Only recall geometry in fancy mode
+// Recall window geometry from local storage and apply it
+export const recallWindowGeometry = async (
+ options: {
+ fancy?: boolean;
+ pos?: [number, number];
+ size?: [number, number];
+ locked?: boolean;
+ } = {}
+) => {
const geometry = options.fancy && (await storage.get(windowKey));
if (geometry) {
logger.log('recalled geometry:', geometry);
@@ -115,14 +130,14 @@ export const recallWindowGeometry = async (options = {}) => {
pos = constraintPosition(pos, size)[1];
}
setWindowPosition(pos);
- }
- // Set window position at the center of the screen.
- else if (size) {
+ // Set window position at the center of the screen.
+ } else if (size) {
pos = vecAdd(vecScale(areaAvailable, 0.5), vecScale(size, -0.5), vecScale(screenOffset, -1.0));
setWindowPosition(pos);
}
};
+// Setup draggable window
export const setupDrag = async () => {
// Calculate screen offset caused by the windows taskbar
let windowPosition = getWindowPosition();
@@ -139,10 +154,10 @@ export const setupDrag = async () => {
* Constraints window position to safe screen area, accounting for safe
* margins which could be a system taskbar.
*/
-const constraintPosition = (pos, size) => {
+const constraintPosition = (pos: [number, number], size: [number, number]): [boolean, [number, number]] => {
const screenPos = getScreenPosition();
const screenSize = getScreenSize();
- const nextPos = [pos[0], pos[1]];
+ const nextPos: [number, number] = [pos[0], pos[1]];
let relocated = false;
for (let i = 0; i < 2; i++) {
const leftBoundary = screenPos[i];
@@ -158,19 +173,21 @@ const constraintPosition = (pos, size) => {
return [relocated, nextPos];
};
-export const dragStartHandler = (event) => {
+// Start dragging the window
+export const dragStartHandler = (event: MouseEvent) => {
logger.log('drag start');
dragging = true;
let windowPosition = getWindowPosition();
dragPointOffset = vecSubtract([event.screenX, event.screenY], getWindowPosition());
// Focus click target
- event.target?.focus();
+ (event.target as HTMLElement)?.focus();
document.addEventListener('mousemove', dragMoveHandler);
document.addEventListener('mouseup', dragEndHandler);
dragMoveHandler(event);
};
-const dragEndHandler = (event) => {
+// End dragging the window
+const dragEndHandler = (event: MouseEvent) => {
logger.log('drag end');
dragMoveHandler(event);
document.removeEventListener('mousemove', dragMoveHandler);
@@ -179,7 +196,8 @@ const dragEndHandler = (event) => {
storeWindowGeometry();
};
-const dragMoveHandler = (event) => {
+// Move the window while dragging
+const dragMoveHandler = (event: MouseEvent) => {
if (!dragging) {
return;
}
@@ -187,20 +205,22 @@ const dragMoveHandler = (event) => {
setWindowPosition(vecSubtract([event.screenX, event.screenY], dragPointOffset));
};
-export const resizeStartHandler = (x, y) => (event) => {
+// Start resizing the window
+export const resizeStartHandler = (x: number, y: number) => (event: MouseEvent) => {
resizeMatrix = [x, y];
logger.log('resize start', resizeMatrix);
resizing = true;
dragPointOffset = vecSubtract([event.screenX, event.screenY], getWindowPosition());
initialSize = getWindowSize();
// Focus click target
- event.target?.focus();
+ (event.target as HTMLElement)?.focus();
document.addEventListener('mousemove', resizeMoveHandler);
document.addEventListener('mouseup', resizeEndHandler);
resizeMoveHandler(event);
};
-const resizeEndHandler = (event) => {
+// End resizing the window
+const resizeEndHandler = (event: MouseEvent) => {
logger.log('resize end', size);
resizeMoveHandler(event);
document.removeEventListener('mousemove', resizeMoveHandler);
@@ -209,7 +229,8 @@ const resizeEndHandler = (event) => {
storeWindowGeometry();
};
-const resizeMoveHandler = (event) => {
+// Move the window while resizing
+const resizeMoveHandler = (event: MouseEvent) => {
if (!resizing) {
return;
}
diff --git a/tgui/packages/tgui/events.test.ts b/tgui/packages/tgui/events.test.ts
new file mode 100644
index 0000000000000..8f3e8e3109c3f
--- /dev/null
+++ b/tgui/packages/tgui/events.test.ts
@@ -0,0 +1,60 @@
+import { KeyEvent, addScrollableNode, canStealFocus, removeScrollableNode, setupGlobalEvents } from './events';
+
+describe('focusEvents', () => {
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('setupGlobalEvents sets the ignoreWindowFocus flag correctly', () => {
+ setupGlobalEvents({ ignoreWindowFocus: true });
+ // Test other functionality that depends on the ignoreWindowFocus flag
+ });
+
+ it('canStealFocus returns true for input and textarea elements', () => {
+ const inputElement = document.createElement('input');
+ const textareaElement = document.createElement('textarea');
+ const divElement = document.createElement('div');
+
+ expect(canStealFocus(inputElement)).toBe(true);
+ expect(canStealFocus(textareaElement)).toBe(true);
+ expect(canStealFocus(divElement)).toBe(false);
+ });
+
+ it('addScrollableNode and removeScrollableNode manage the list of scrollable nodes correctly', () => {
+ const divElement1 = document.createElement('div');
+ const divElement2 = document.createElement('div');
+
+ addScrollableNode(divElement1);
+ addScrollableNode(divElement2);
+ // Test other functionality that depends on the list of scrollable nodes
+
+ removeScrollableNode(divElement1);
+ removeScrollableNode(divElement2);
+ // Test other functionality that depends on the list of scrollable nodes
+ });
+
+ it('KeyEvent class works correctly', () => {
+ const keyboardEvent = new KeyboardEvent('keydown', {
+ key: 'a',
+ keyCode: 65,
+ ctrlKey: true,
+ altKey: true,
+ shiftKey: true,
+ });
+
+ const keyEvent = new KeyEvent(keyboardEvent, 'keydown', false);
+
+ expect(keyEvent.event).toBe(keyboardEvent);
+ expect(keyEvent.type).toBe('keydown');
+ expect(keyEvent.code).toBe(65);
+ expect(keyEvent.ctrl).toBe(true);
+ expect(keyEvent.alt).toBe(true);
+ expect(keyEvent.shift).toBe(true);
+ expect(keyEvent.repeat).toBe(false);
+ expect(keyEvent.hasModifierKeys()).toBe(true);
+ expect(keyEvent.isModifierKey()).toBe(false);
+ expect(keyEvent.isDown()).toBe(true);
+ expect(keyEvent.isUp()).toBe(false);
+ expect(keyEvent.toString()).toBe('Ctrl+Alt+Shift+A');
+ });
+});
diff --git a/tgui/packages/tgui/events.js b/tgui/packages/tgui/events.ts
similarity index 76%
rename from tgui/packages/tgui/events.js
rename to tgui/packages/tgui/events.ts
index bcd4bb3ec4c78..37db0c2e3ac60 100644
--- a/tgui/packages/tgui/events.js
+++ b/tgui/packages/tgui/events.ts
@@ -6,25 +6,24 @@
* @license MIT
*/
-import { EventEmitter } from 'common/events';
import { KEY_ALT, KEY_CTRL, KEY_F1, KEY_F12, KEY_SHIFT } from 'common/keycodes';
-import { logger } from './logging';
+import { EventEmitter } from 'common/events';
export const globalEvents = new EventEmitter();
let ignoreWindowFocus = false;
-export const setupGlobalEvents = (options = {}) => {
+export const setupGlobalEvents = (options: { ignoreWindowFocus?: boolean } = {}): void => {
ignoreWindowFocus = !!options.ignoreWindowFocus;
};
// Window focus
// --------------------------------------------------------
-let windowFocusTimeout;
+let windowFocusTimeout: ReturnType | null;
let windowFocused = true;
-const setWindowFocus = (value, delayed) => {
- // Pretend to always be in focus.
+// Pretend to always be in focus.
+const setWindowFocus = (value: boolean, delayed?: boolean) => {
if (ignoreWindowFocus) {
windowFocused = true;
return;
@@ -47,14 +46,14 @@ const setWindowFocus = (value, delayed) => {
// Focus stealing
// --------------------------------------------------------
-let focusStolenBy = null;
+let focusStolenBy: HTMLElement | null = null;
-export const canStealFocus = (node) => {
+export const canStealFocus = (node: HTMLElement) => {
const tag = String(node.tagName).toLowerCase();
return tag === 'input' || tag === 'textarea';
};
-const stealFocus = (node) => {
+const stealFocus = (node: HTMLElement) => {
releaseStolenFocus();
focusStolenBy = node;
focusStolenBy.addEventListener('blur', releaseStolenFocus);
@@ -70,22 +69,22 @@ const releaseStolenFocus = () => {
// Focus follows the mouse
// --------------------------------------------------------
-let focusedNode = null;
-let lastVisitedNode = null;
-const trackedNodes = [];
+let focusedNode: HTMLElement | null = null;
+let lastVisitedNode: HTMLElement | null = null;
+const trackedNodes: HTMLElement[] = [];
-export const addScrollableNode = (node) => {
+export const addScrollableNode = (node: HTMLElement) => {
trackedNodes.push(node);
};
-export const removeScrollableNode = (node) => {
+export const removeScrollableNode = (node: HTMLElement) => {
const index = trackedNodes.indexOf(node);
if (index >= 0) {
trackedNodes.splice(index, 1);
}
};
-const focusNearestTrackedParent = (node) => {
+const focusNearestTrackedParent = (node: HTMLElement | null) => {
if (focusStolenBy || !windowFocused) {
return;
}
@@ -100,12 +99,12 @@ const focusNearestTrackedParent = (node) => {
node.focus();
return;
}
- node = node.parentNode;
+ node = node.parentElement;
}
};
window.addEventListener('mousemove', (e) => {
- const node = e.target;
+ const node = e.target as HTMLElement;
if (node !== lastVisitedNode) {
lastVisitedNode = node;
focusNearestTrackedParent(node);
@@ -117,10 +116,10 @@ window.addEventListener('mousemove', (e) => {
window.addEventListener('focusin', (e) => {
lastVisitedNode = null;
- focusedNode = e.target;
+ focusedNode = e.target as HTMLElement;
setWindowFocus(true);
- if (canStealFocus(e.target)) {
- stealFocus(e.target);
+ if (canStealFocus(e.target as HTMLElement)) {
+ stealFocus(e.target as HTMLElement);
return;
}
});
@@ -142,13 +141,22 @@ window.addEventListener('beforeunload', (e) => {
// Key events
// --------------------------------------------------------
-const keyHeldByCode = {};
+const keyHeldByCode: Record = {};
export class KeyEvent {
- constructor(e, type, repeat) {
+ event: KeyboardEvent;
+ type: 'keydown' | 'keyup';
+ code: number;
+ ctrl: boolean;
+ shift: boolean;
+ alt: boolean;
+ repeat: boolean;
+ _str?: string;
+
+ constructor(e: KeyboardEvent, type: 'keydown' | 'keyup', repeat?: boolean) {
this.event = e;
this.type = type;
- this.code = window.event ? e.which : e.keyCode;
+ this.code = e.keyCode;
this.ctrl = e.ctrlKey;
this.shift = e.shiftKey;
this.alt = e.altKey;
@@ -198,7 +206,7 @@ export class KeyEvent {
// IE8: Keydown event is only available on document.
document.addEventListener('keydown', (e) => {
- if (canStealFocus(e.target)) {
+ if (canStealFocus(e.target as HTMLElement)) {
return;
}
const code = e.keyCode;
@@ -209,7 +217,7 @@ document.addEventListener('keydown', (e) => {
});
document.addEventListener('keyup', (e) => {
- if (canStealFocus(e.target)) {
+ if (canStealFocus(e.target as HTMLElement)) {
return;
}
const code = e.keyCode;
diff --git a/tgui/packages/tgui/focus.js b/tgui/packages/tgui/focus.ts
similarity index 100%
rename from tgui/packages/tgui/focus.js
rename to tgui/packages/tgui/focus.ts
diff --git a/tgui/packages/tgui/format.js b/tgui/packages/tgui/format.js
deleted file mode 100644
index c261a3dfdbd96..0000000000000
--- a/tgui/packages/tgui/format.js
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { clamp, round, toFixed } from 'common/math';
-
-const SI_SYMBOLS = [
- 'f', // femto
- 'p', // pico
- 'n', // nano
- 'μ', // micro
- 'm', // milli
- // NOTE: This is a space for a reason. When we right align si numbers,
- // in monospace mode, we want to units and numbers stay in their respective
- // columns. If rendering in HTML mode, this space will collapse into
- // a single space anyway.
- ' ',
- 'k', // kilo
- 'M', // mega
- 'G', // giga
- 'T', // tera
- 'P', // peta
- 'E', // exa
- 'Z', // zetta
- 'Y', // yotta
-];
-
-const SI_BASE_INDEX = SI_SYMBOLS.indexOf(' ');
-
-/**
- * Formats a number to a human readable form, by reducing it to SI units.
- * TODO: This is quite a shit code and shit math, needs optimization.
- */
-export const formatSiUnit = (value, minBase1000 = -SI_BASE_INDEX, unit = '') => {
- if (typeof value !== 'number' || !Number.isFinite(value)) {
- return value;
- }
- const realBase10 = Math.floor(Math.log10(value));
- const base10 = Math.floor(Math.max(minBase1000 * 3, realBase10));
- const realBase1000 = Math.floor(realBase10 / 3);
- const base1000 = Math.floor(base10 / 3);
- const symbolIndex = clamp(SI_BASE_INDEX + base1000, 0, SI_SYMBOLS.length);
- const symbol = SI_SYMBOLS[symbolIndex];
- const scaledNumber = value / Math.pow(1000, base1000);
- const scaledPrecision = realBase1000 > minBase1000 ? 2 + base1000 * 3 - base10 : 0;
- // TODO: Make numbers bigger than precision value show
- // up to 2 decimal numbers.
- const finalString = toFixed(scaledNumber, scaledPrecision) + ' ' + symbol + unit;
- return finalString.trim();
-};
-
-export const formatPower = (value, minBase1000 = 0) => {
- return formatSiUnit(value, minBase1000, 'W');
-};
-
-export const formatMoney = (value, precision = 0) => {
- if (!Number.isFinite(value)) {
- return value;
- }
- // Round the number and make it fixed precision
- let fixed = round(value, precision);
- if (precision > 0) {
- fixed = toFixed(value, precision);
- }
- fixed = String(fixed);
- // Place thousand separators
- const length = fixed.length;
- let indexOfPoint = fixed.indexOf('.');
- if (indexOfPoint === -1) {
- indexOfPoint = length;
- }
- let result = '';
- for (let i = 0; i < length; i++) {
- if (i > 0 && i < indexOfPoint && (indexOfPoint - i) % 3 === 0) {
- // Thin space
- result += '\u2009';
- }
- result += fixed.charAt(i);
- }
- return result;
-};
-
-/**
- * Formats a floating point number as a number on the decibel scale.
- */
-export const formatDb = (value) => {
- const db = (20 * Math.log(value)) / Math.log(10);
- const sign = db >= 0 ? '+' : '–';
- let formatted = Math.abs(db);
- if (formatted === Infinity) {
- formatted = 'Inf';
- } else {
- formatted = toFixed(formatted, 2);
- }
- return sign + formatted + ' dB';
-};
diff --git a/tgui/packages/tgui/format.test.ts b/tgui/packages/tgui/format.test.ts
new file mode 100644
index 0000000000000..011d9e9e6c07a
--- /dev/null
+++ b/tgui/packages/tgui/format.test.ts
@@ -0,0 +1,112 @@
+import { formatDb, formatMoney, formatSiBaseTenUnit, formatSiUnit, formatTime } from './format';
+
+describe('formatSiUnit', () => {
+ it('formats base values correctly', () => {
+ const value = 100;
+ const result = formatSiUnit(value);
+ expect(result).toBe('100');
+ });
+
+ it('formats kilo values correctly', () => {
+ const value = 1500;
+ const result = formatSiUnit(value);
+ expect(result).toBe('1.50 k');
+ });
+
+ it('formats micro values correctly', () => {
+ const value = 0.0001;
+ const result = formatSiUnit(value);
+ expect(result).toBe('100 μ');
+ });
+
+ it('formats values with custom units correctly', () => {
+ const value = 0.5;
+ const result = formatSiUnit(value, 0, 'Hz');
+ expect(result).toBe('0.50 Hz');
+ });
+
+ it('handles non-finite values correctly', () => {
+ const value = Infinity;
+ const result = formatSiUnit(value);
+ expect(result).toBe('Infinity');
+ });
+});
+
+describe('formatMoney', () => {
+ it('formats integer values with default precision', () => {
+ const value = 1234567;
+ const result = formatMoney(value);
+ expect(result).toBe('1\u2009234\u2009567');
+ });
+
+ it('formats float values with specified precision', () => {
+ const value = 1234567.89;
+ const result = formatMoney(value, 2);
+ expect(result).toBe('1\u2009234\u2009567.89');
+ });
+
+ it('formats negative values correctly', () => {
+ const value = -1234567.89;
+ const result = formatMoney(value, 2);
+ expect(result).toBe('-1\u2009234\u2009567.89');
+ });
+
+ it('returns non-finite values as is', () => {
+ const value = Infinity;
+ const result = formatMoney(value);
+ expect(result).toBe('Infinity');
+ });
+
+ it('formats zero correctly', () => {
+ const value = 0;
+ const result = formatMoney(value);
+ expect(result).toBe('0');
+ });
+});
+
+describe('formatDb', () => {
+ it('formats positive values correctly', () => {
+ const value = 1;
+ const result = formatDb(value);
+ expect(result).toBe('+0.00 dB');
+ });
+
+ it('formats negative values correctly', () => {
+ const value = 0.5;
+ const result = formatDb(value);
+ expect(result).toBe('-6.02 dB');
+ });
+
+ it('formats Infinity correctly', () => {
+ const value = 0;
+ const result = formatDb(value);
+ expect(result).toBe('-Inf dB');
+ });
+
+ it('formats very large values correctly', () => {
+ const value = 1e6;
+ const result = formatDb(value);
+ expect(result).toBe('+120.00 dB');
+ });
+
+ it('formats very small values correctly', () => {
+ const value = 1e-6;
+ const result = formatDb(value);
+ expect(result).toBe('-120.00 dB');
+ });
+});
+
+describe('formatSiBaseTenUnit', () => {
+ it('formats SI base 10 units', () => {
+ expect(formatSiBaseTenUnit(1e9)).toBe('1.00 · 10⁹');
+ expect(formatSiBaseTenUnit(1234567890, 0, 'm')).toBe('1.23 · 10⁹ m');
+ });
+});
+
+describe('formatTime', () => {
+ it('formats time values', () => {
+ expect(formatTime(36000)).toBe('01:00:00');
+ expect(formatTime(36610)).toBe('01:01:01');
+ expect(formatTime(36610, 'short')).toBe('1h1m1s');
+ });
+});
diff --git a/tgui/packages/tgui/format.ts b/tgui/packages/tgui/format.ts
new file mode 100644
index 0000000000000..c48a8baacfe42
--- /dev/null
+++ b/tgui/packages/tgui/format.ts
@@ -0,0 +1,157 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+const SI_SYMBOLS = [
+ 'f', // femto
+ 'p', // pico
+ 'n', // nano
+ 'μ', // micro
+ 'm', // milli
+ // NOTE: This is a space for a reason. When we right align si numbers,
+ // in monospace mode, we want to units and numbers stay in their respective
+ // columns. If rendering in HTML mode, this space will collapse into
+ // a single space anyway.
+ ' ', // base
+ 'k', // kilo
+ 'M', // mega
+ 'G', // giga
+ 'T', // tera
+ 'P', // peta
+ 'E', // exa
+ 'Z', // zetta
+ 'Y', // yotta
+ 'R', // ronna
+ 'Q', // quecca
+ 'F',
+ 'N',
+ 'H',
+] as const;
+
+const SI_BASE_INDEX = SI_SYMBOLS.indexOf(' ');
+
+// Formats a number to a human readable form, with a custom unit
+export const formatSiUnit = (value: number, minBase1000 = -SI_BASE_INDEX, unit = ''): string => {
+ if (!isFinite(value)) {
+ return value.toString();
+ }
+
+ const realBase10 = Math.floor(Math.log10(Math.abs(value)));
+ const base10 = Math.max(minBase1000 * 3, realBase10);
+ const base1000 = Math.floor(base10 / 3);
+ const symbol = SI_SYMBOLS[Math.min(base1000 + SI_BASE_INDEX, SI_SYMBOLS.length - 1)];
+
+ const scaledValue = value / Math.pow(1000, base1000);
+
+ let formattedValue = scaledValue.toFixed(2);
+ if (formattedValue.endsWith('.00')) {
+ formattedValue = formattedValue.slice(0, -3);
+ } else if (formattedValue.endsWith('.0')) {
+ formattedValue = formattedValue.slice(0, -2);
+ }
+
+ return `${formattedValue} ${symbol.trim()}${unit}`.trim();
+};
+
+// Formats a number to a human readable form, with power (W) as the unit
+export const formatPower = (value: number, minBase1000 = 0) => {
+ return formatSiUnit(value, minBase1000, 'W');
+};
+
+// Formats a number as a currency string
+export const formatMoney = (value: number, precision = 0) => {
+ if (!Number.isFinite(value)) {
+ return String(value);
+ }
+
+ // Round the number and make it fixed precision
+ const roundedValue = Number(value.toFixed(precision));
+
+ // Handle the negative sign
+ const isNegative = roundedValue < 0;
+ const absoluteValue = Math.abs(roundedValue);
+
+ // Convert to string and place thousand separators
+ const parts = absoluteValue.toString().split('.');
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, '\u2009'); // Thin space
+
+ const formattedValue = parts.join('.');
+
+ return isNegative ? `-${formattedValue}` : formattedValue;
+};
+
+// Formats a floating point number as a number on the decibel scale
+export const formatDb = (value: number) => {
+ const db = 20 * Math.log10(value);
+ const sign = db >= 0 ? '+' : '-';
+ let formatted: string | number = Math.abs(db);
+
+ if (formatted === Infinity) {
+ formatted = 'Inf';
+ } else {
+ formatted = formatted.toFixed(2);
+ }
+
+ return `${sign}${formatted} dB`;
+};
+
+const SI_BASE_TEN_UNITS = [
+ '',
+ '· 10³', // kilo
+ '· 10⁶', // mega
+ '· 10⁹', // giga
+ '· 10¹²', // tera
+ '· 10¹⁵', // peta
+ '· 10¹⁸', // exa
+ '· 10²¹', // zetta
+ '· 10²⁴', // yotta
+ '· 10²⁷', // ronna
+ '· 10³⁰', // quecca
+ '· 10³³',
+ '· 10³⁶',
+ '· 10³⁹',
+] as const;
+
+// Converts a number to a string with SI base 10 units
+export const formatSiBaseTenUnit = (value: number, minBase1000 = 0, unit = ''): string => {
+ if (!isFinite(value)) {
+ return 'NaN';
+ }
+
+ const realBase10 = Math.floor(Math.log10(value));
+ const base10 = Math.max(minBase1000 * 3, realBase10);
+ const base1000 = Math.floor(base10 / 3);
+ const symbol = SI_BASE_TEN_UNITS[base1000];
+
+ const scaledValue = value / Math.pow(1000, base1000);
+ const precision = Math.max(0, 2 - (base10 % 3));
+ const formattedValue = scaledValue.toFixed(precision);
+
+ return `${formattedValue} ${symbol} ${unit}`.trim();
+};
+
+/**
+ * Formats decisecond count into HH:MM:SS display by default
+ * "short" format does not pad and adds hms suffixes
+ */
+export const formatTime = (val: number, formatType: 'short' | 'default' = 'default'): string => {
+ const totalSeconds = Math.floor(val / 10);
+ const hours = Math.floor(totalSeconds / 3600);
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
+ const seconds = totalSeconds % 60;
+
+ if (formatType === 'short') {
+ const hoursFormatted = hours > 0 ? `${hours}h` : '';
+ const minutesFormatted = minutes > 0 ? `${minutes}m` : '';
+ const secondsFormatted = seconds > 0 ? `${seconds}s` : '';
+ return `${hoursFormatted}${minutesFormatted}${secondsFormatted}`;
+ }
+
+ const hoursPadded = String(hours).padStart(2, '0');
+ const minutesPadded = String(minutes).padStart(2, '0');
+ const secondsPadded = String(seconds).padStart(2, '0');
+
+ return `${hoursPadded}:${minutesPadded}:${secondsPadded}`;
+};
diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.tsx
similarity index 98%
rename from tgui/packages/tgui/index.js
rename to tgui/packages/tgui/index.tsx
index 7554cac0eccf3..2f0a7e9aaa796 100644
--- a/tgui/packages/tgui/index.js
+++ b/tgui/packages/tgui/index.tsx
@@ -43,13 +43,14 @@ import './styles/themes/retro.scss';
import './styles/themes/syndicate.scss';
import './styles/themes/thinktronic-classic.scss';
-import { perf } from 'common/perf';
-import { setupHotReloading } from 'tgui-dev-server/link/client.cjs';
-import { setupHotKeys } from './hotkeys';
+import { StoreProvider, configureStore } from './store';
+
import { captureExternalLinks } from './links';
import { createRenderer } from './renderer';
-import { configureStore, StoreProvider } from './store';
+import { perf } from 'common/perf';
import { setupGlobalEvents } from './events';
+import { setupHotKeys } from './hotkeys';
+import { setupHotReloading } from 'tgui-dev-server/link/client.cjs';
perf.mark('inception', window.performance?.timing?.navigationStart);
perf.mark('init');
diff --git a/tgui/packages/tgui/interfaces/AbductorConsole.js b/tgui/packages/tgui/interfaces/AbductorConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AbductorConsole.js
rename to tgui/packages/tgui/interfaces/AbductorConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/Achievements.js b/tgui/packages/tgui/interfaces/Achievements.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Achievements.js
rename to tgui/packages/tgui/interfaces/Achievements.jsx
diff --git a/tgui/packages/tgui/interfaces/AdminFax.js b/tgui/packages/tgui/interfaces/AdminFax.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AdminFax.js
rename to tgui/packages/tgui/interfaces/AdminFax.jsx
diff --git a/tgui/packages/tgui/interfaces/AdminSecretsPanel.js b/tgui/packages/tgui/interfaces/AdminSecretsPanel.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AdminSecretsPanel.js
rename to tgui/packages/tgui/interfaces/AdminSecretsPanel.jsx
diff --git a/tgui/packages/tgui/interfaces/AdvancedAirlockController.js b/tgui/packages/tgui/interfaces/AdvancedAirlockController.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AdvancedAirlockController.js
rename to tgui/packages/tgui/interfaces/AdvancedAirlockController.jsx
diff --git a/tgui/packages/tgui/interfaces/AiAirlock.js b/tgui/packages/tgui/interfaces/AiAirlock.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AiAirlock.js
rename to tgui/packages/tgui/interfaces/AiAirlock.jsx
diff --git a/tgui/packages/tgui/interfaces/AiRestorer.js b/tgui/packages/tgui/interfaces/AiRestorer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AiRestorer.js
rename to tgui/packages/tgui/interfaces/AiRestorer.jsx
diff --git a/tgui/packages/tgui/interfaces/AirAlarm.js b/tgui/packages/tgui/interfaces/AirAlarm.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AirAlarm.js
rename to tgui/packages/tgui/interfaces/AirAlarm.jsx
diff --git a/tgui/packages/tgui/interfaces/AirlockElectronics.js b/tgui/packages/tgui/interfaces/AirlockElectronics.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AirlockElectronics.js
rename to tgui/packages/tgui/interfaces/AirlockElectronics.jsx
diff --git a/tgui/packages/tgui/interfaces/Apc.js b/tgui/packages/tgui/interfaces/Apc.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Apc.js
rename to tgui/packages/tgui/interfaces/Apc.jsx
diff --git a/tgui/packages/tgui/interfaces/Aquarium.js b/tgui/packages/tgui/interfaces/Aquarium.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Aquarium.js
rename to tgui/packages/tgui/interfaces/Aquarium.jsx
diff --git a/tgui/packages/tgui/interfaces/AtmosAlertConsole.js b/tgui/packages/tgui/interfaces/AtmosAlertConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AtmosAlertConsole.js
rename to tgui/packages/tgui/interfaces/AtmosAlertConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/AtmosControlConsole.js b/tgui/packages/tgui/interfaces/AtmosControlConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AtmosControlConsole.js
rename to tgui/packages/tgui/interfaces/AtmosControlConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/AtmosFilter.js b/tgui/packages/tgui/interfaces/AtmosFilter.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AtmosFilter.js
rename to tgui/packages/tgui/interfaces/AtmosFilter.jsx
index 323e90f200432..cf08df84bd5be 100644
--- a/tgui/packages/tgui/interfaces/AtmosFilter.js
+++ b/tgui/packages/tgui/interfaces/AtmosFilter.jsx
@@ -1,7 +1,7 @@
import { useBackend } from '../backend';
import { Button, LabeledList, NumberInput, Section } from '../components';
-import { getGasLabel } from '../constants';
import { Window } from '../layouts';
+import { getGasLabel } from '../constants';
export const AtmosFilter = (props, context) => {
const { act, data } = useBackend(context);
diff --git a/tgui/packages/tgui/interfaces/AtmosMixer.js b/tgui/packages/tgui/interfaces/AtmosMixer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AtmosMixer.js
rename to tgui/packages/tgui/interfaces/AtmosMixer.jsx
diff --git a/tgui/packages/tgui/interfaces/AtmosPump.js b/tgui/packages/tgui/interfaces/AtmosPump.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AtmosPump.js
rename to tgui/packages/tgui/interfaces/AtmosPump.jsx
diff --git a/tgui/packages/tgui/interfaces/AtmosTempGate.js b/tgui/packages/tgui/interfaces/AtmosTempGate.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AtmosTempGate.js
rename to tgui/packages/tgui/interfaces/AtmosTempGate.jsx
diff --git a/tgui/packages/tgui/interfaces/AtmosTempPump.js b/tgui/packages/tgui/interfaces/AtmosTempPump.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AtmosTempPump.js
rename to tgui/packages/tgui/interfaces/AtmosTempPump.jsx
diff --git a/tgui/packages/tgui/interfaces/AutomatedAnnouncement.js b/tgui/packages/tgui/interfaces/AutomatedAnnouncement.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/AutomatedAnnouncement.js
rename to tgui/packages/tgui/interfaces/AutomatedAnnouncement.jsx
diff --git a/tgui/packages/tgui/interfaces/BankMachine.js b/tgui/packages/tgui/interfaces/BankMachine.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/BankMachine.js
rename to tgui/packages/tgui/interfaces/BankMachine.jsx
diff --git a/tgui/packages/tgui/interfaces/BanningPanel.js b/tgui/packages/tgui/interfaces/BanningPanel.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/BanningPanel.js
rename to tgui/packages/tgui/interfaces/BanningPanel.jsx
diff --git a/tgui/packages/tgui/interfaces/Biogenerator.js b/tgui/packages/tgui/interfaces/Biogenerator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Biogenerator.js
rename to tgui/packages/tgui/interfaces/Biogenerator.jsx
diff --git a/tgui/packages/tgui/interfaces/BluespaceArtillery.js b/tgui/packages/tgui/interfaces/BluespaceArtillery.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/BluespaceArtillery.js
rename to tgui/packages/tgui/interfaces/BluespaceArtillery.jsx
diff --git a/tgui/packages/tgui/interfaces/BluespaceLocator.js b/tgui/packages/tgui/interfaces/BluespaceLocator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/BluespaceLocator.js
rename to tgui/packages/tgui/interfaces/BluespaceLocator.jsx
diff --git a/tgui/packages/tgui/interfaces/BluespaceTap.js b/tgui/packages/tgui/interfaces/BluespaceTap.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/BluespaceTap.js
rename to tgui/packages/tgui/interfaces/BluespaceTap.jsx
diff --git a/tgui/packages/tgui/interfaces/BorgPanel.js b/tgui/packages/tgui/interfaces/BorgPanel.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/BorgPanel.js
rename to tgui/packages/tgui/interfaces/BorgPanel.jsx
diff --git a/tgui/packages/tgui/interfaces/BountyBoard.js b/tgui/packages/tgui/interfaces/BountyBoard.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/BountyBoard.js
rename to tgui/packages/tgui/interfaces/BountyBoard.jsx
diff --git a/tgui/packages/tgui/interfaces/BrigTimer.js b/tgui/packages/tgui/interfaces/BrigTimer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/BrigTimer.js
rename to tgui/packages/tgui/interfaces/BrigTimer.jsx
diff --git a/tgui/packages/tgui/interfaces/CameraConsole.js b/tgui/packages/tgui/interfaces/CameraConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/CameraConsole.js
rename to tgui/packages/tgui/interfaces/CameraConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/Canister.js b/tgui/packages/tgui/interfaces/Canister.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Canister.js
rename to tgui/packages/tgui/interfaces/Canister.jsx
diff --git a/tgui/packages/tgui/interfaces/Canvas.js b/tgui/packages/tgui/interfaces/Canvas.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Canvas.js
rename to tgui/packages/tgui/interfaces/Canvas.jsx
diff --git a/tgui/packages/tgui/interfaces/Cargo.js b/tgui/packages/tgui/interfaces/Cargo.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Cargo.js
rename to tgui/packages/tgui/interfaces/Cargo.jsx
diff --git a/tgui/packages/tgui/interfaces/CargoBountyConsole.js b/tgui/packages/tgui/interfaces/CargoBountyConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/CargoBountyConsole.js
rename to tgui/packages/tgui/interfaces/CargoBountyConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/CargoExpress.js b/tgui/packages/tgui/interfaces/CargoExpress.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/CargoExpress.js
rename to tgui/packages/tgui/interfaces/CargoExpress.jsx
diff --git a/tgui/packages/tgui/interfaces/CargoHoldTerminal.js b/tgui/packages/tgui/interfaces/CargoHoldTerminal.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/CargoHoldTerminal.js
rename to tgui/packages/tgui/interfaces/CargoHoldTerminal.jsx
diff --git a/tgui/packages/tgui/interfaces/CellularEmporium.js b/tgui/packages/tgui/interfaces/CellularEmporium.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/CellularEmporium.js
rename to tgui/packages/tgui/interfaces/CellularEmporium.jsx
diff --git a/tgui/packages/tgui/interfaces/CentcomPodLauncher.js b/tgui/packages/tgui/interfaces/CentcomPodLauncher.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/CentcomPodLauncher.js
rename to tgui/packages/tgui/interfaces/CentcomPodLauncher.jsx
diff --git a/tgui/packages/tgui/interfaces/ChemAcclimator.js b/tgui/packages/tgui/interfaces/ChemAcclimator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ChemAcclimator.js
rename to tgui/packages/tgui/interfaces/ChemAcclimator.jsx
diff --git a/tgui/packages/tgui/interfaces/ChemDebugSynthesizer.js b/tgui/packages/tgui/interfaces/ChemDebugSynthesizer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ChemDebugSynthesizer.js
rename to tgui/packages/tgui/interfaces/ChemDebugSynthesizer.jsx
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.js b/tgui/packages/tgui/interfaces/ChemDispenser.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ChemDispenser.js
rename to tgui/packages/tgui/interfaces/ChemDispenser.jsx
diff --git a/tgui/packages/tgui/interfaces/ChemFilter.js b/tgui/packages/tgui/interfaces/ChemFilter.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ChemFilter.js
rename to tgui/packages/tgui/interfaces/ChemFilter.jsx
diff --git a/tgui/packages/tgui/interfaces/ChemHeater.js b/tgui/packages/tgui/interfaces/ChemHeater.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ChemHeater.js
rename to tgui/packages/tgui/interfaces/ChemHeater.jsx
diff --git a/tgui/packages/tgui/interfaces/ChemMaster.js b/tgui/packages/tgui/interfaces/ChemMaster.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ChemMaster.js
rename to tgui/packages/tgui/interfaces/ChemMaster.jsx
diff --git a/tgui/packages/tgui/interfaces/ChemReactionChamber.js b/tgui/packages/tgui/interfaces/ChemReactionChamber.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ChemReactionChamber.js
rename to tgui/packages/tgui/interfaces/ChemReactionChamber.jsx
diff --git a/tgui/packages/tgui/interfaces/ChemSplitter.js b/tgui/packages/tgui/interfaces/ChemSplitter.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ChemSplitter.js
rename to tgui/packages/tgui/interfaces/ChemSplitter.jsx
diff --git a/tgui/packages/tgui/interfaces/ChemSynthesizer.js b/tgui/packages/tgui/interfaces/ChemSynthesizer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ChemSynthesizer.js
rename to tgui/packages/tgui/interfaces/ChemSynthesizer.jsx
diff --git a/tgui/packages/tgui/interfaces/CircuitModule.js b/tgui/packages/tgui/interfaces/CircuitModule.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/CircuitModule.js
rename to tgui/packages/tgui/interfaces/CircuitModule.jsx
diff --git a/tgui/packages/tgui/interfaces/Clipboard.js b/tgui/packages/tgui/interfaces/Clipboard.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Clipboard.js
rename to tgui/packages/tgui/interfaces/Clipboard.jsx
diff --git a/tgui/packages/tgui/interfaces/ClockworkSlab.js b/tgui/packages/tgui/interfaces/ClockworkSlab.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ClockworkSlab.js
rename to tgui/packages/tgui/interfaces/ClockworkSlab.jsx
diff --git a/tgui/packages/tgui/interfaces/CloningConsole.js b/tgui/packages/tgui/interfaces/CloningConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/CloningConsole.js
rename to tgui/packages/tgui/interfaces/CloningConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/CodexGigas.js b/tgui/packages/tgui/interfaces/CodexGigas.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/CodexGigas.js
rename to tgui/packages/tgui/interfaces/CodexGigas.jsx
diff --git a/tgui/packages/tgui/interfaces/ColorMatrixEditor.js b/tgui/packages/tgui/interfaces/ColorMatrixEditor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ColorMatrixEditor.js
rename to tgui/packages/tgui/interfaces/ColorMatrixEditor.jsx
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole.js b/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/CommunicationsConsole.js
rename to tgui/packages/tgui/interfaces/CommunicationsConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator.js b/tgui/packages/tgui/interfaces/ComputerFabricator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ComputerFabricator.js
rename to tgui/packages/tgui/interfaces/ComputerFabricator.jsx
diff --git a/tgui/packages/tgui/interfaces/Crayon.js b/tgui/packages/tgui/interfaces/Crayon.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Crayon.js
rename to tgui/packages/tgui/interfaces/Crayon.jsx
diff --git a/tgui/packages/tgui/interfaces/Cryo.js b/tgui/packages/tgui/interfaces/Cryo.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Cryo.js
rename to tgui/packages/tgui/interfaces/Cryo.jsx
diff --git a/tgui/packages/tgui/interfaces/DisposalUnit.js b/tgui/packages/tgui/interfaces/DisposalUnit.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/DisposalUnit.js
rename to tgui/packages/tgui/interfaces/DisposalUnit.jsx
diff --git a/tgui/packages/tgui/interfaces/DnaConsole.js b/tgui/packages/tgui/interfaces/DnaConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/DnaConsole.js
rename to tgui/packages/tgui/interfaces/DnaConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/DnaVault.js b/tgui/packages/tgui/interfaces/DnaVault.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/DnaVault.js
rename to tgui/packages/tgui/interfaces/DnaVault.jsx
diff --git a/tgui/packages/tgui/interfaces/EightBallVote.js b/tgui/packages/tgui/interfaces/EightBallVote.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/EightBallVote.js
rename to tgui/packages/tgui/interfaces/EightBallVote.jsx
diff --git a/tgui/packages/tgui/interfaces/Electropack.js b/tgui/packages/tgui/interfaces/Electropack.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Electropack.js
rename to tgui/packages/tgui/interfaces/Electropack.jsx
diff --git a/tgui/packages/tgui/interfaces/Elevator.js b/tgui/packages/tgui/interfaces/Elevator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Elevator.js
rename to tgui/packages/tgui/interfaces/Elevator.jsx
diff --git a/tgui/packages/tgui/interfaces/EmagConsole.js b/tgui/packages/tgui/interfaces/EmagConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/EmagConsole.js
rename to tgui/packages/tgui/interfaces/EmagConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/EmergencyShuttleConsole.js b/tgui/packages/tgui/interfaces/EmergencyShuttleConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/EmergencyShuttleConsole.js
rename to tgui/packages/tgui/interfaces/EmergencyShuttleConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/EmojiInputModal.js b/tgui/packages/tgui/interfaces/EmojiInputModal.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/EmojiInputModal.js
rename to tgui/packages/tgui/interfaces/EmojiInputModal.jsx
diff --git a/tgui/packages/tgui/interfaces/EngravedMessage.js b/tgui/packages/tgui/interfaces/EngravedMessage.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/EngravedMessage.js
rename to tgui/packages/tgui/interfaces/EngravedMessage.jsx
diff --git a/tgui/packages/tgui/interfaces/ExosuitControlConsole.js b/tgui/packages/tgui/interfaces/ExosuitControlConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ExosuitControlConsole.js
rename to tgui/packages/tgui/interfaces/ExosuitControlConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/Filteriffic.js b/tgui/packages/tgui/interfaces/Filteriffic.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Filteriffic.js
rename to tgui/packages/tgui/interfaces/Filteriffic.jsx
diff --git a/tgui/packages/tgui/interfaces/FishCatalog.js b/tgui/packages/tgui/interfaces/FishCatalog.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/FishCatalog.js
rename to tgui/packages/tgui/interfaces/FishCatalog.jsx
diff --git a/tgui/packages/tgui/interfaces/Folder.js b/tgui/packages/tgui/interfaces/Folder.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Folder.js
rename to tgui/packages/tgui/interfaces/Folder.jsx
diff --git a/tgui/packages/tgui/interfaces/ForbiddenLore.js b/tgui/packages/tgui/interfaces/ForbiddenLore.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ForbiddenLore.js
rename to tgui/packages/tgui/interfaces/ForbiddenLore.jsx
diff --git a/tgui/packages/tgui/interfaces/FugitiveCaptureConsole.js b/tgui/packages/tgui/interfaces/FugitiveCaptureConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/FugitiveCaptureConsole.js
rename to tgui/packages/tgui/interfaces/FugitiveCaptureConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/GenPop.js b/tgui/packages/tgui/interfaces/GenPop.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/GenPop.js
rename to tgui/packages/tgui/interfaces/GenPop.jsx
diff --git a/tgui/packages/tgui/interfaces/GhostPoolProtection.js b/tgui/packages/tgui/interfaces/GhostPoolProtection.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/GhostPoolProtection.js
rename to tgui/packages/tgui/interfaces/GhostPoolProtection.jsx
diff --git a/tgui/packages/tgui/interfaces/GlandDispenser.js b/tgui/packages/tgui/interfaces/GlandDispenser.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/GlandDispenser.js
rename to tgui/packages/tgui/interfaces/GlandDispenser.jsx
diff --git a/tgui/packages/tgui/interfaces/Gps.js b/tgui/packages/tgui/interfaces/Gps.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Gps.js
rename to tgui/packages/tgui/interfaces/Gps.jsx
diff --git a/tgui/packages/tgui/interfaces/GravityGenerator.js b/tgui/packages/tgui/interfaces/GravityGenerator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/GravityGenerator.js
rename to tgui/packages/tgui/interfaces/GravityGenerator.jsx
diff --git a/tgui/packages/tgui/interfaces/GulagItemReclaimer.js b/tgui/packages/tgui/interfaces/GulagItemReclaimer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/GulagItemReclaimer.js
rename to tgui/packages/tgui/interfaces/GulagItemReclaimer.jsx
diff --git a/tgui/packages/tgui/interfaces/GulagTeleporterConsole.js b/tgui/packages/tgui/interfaces/GulagTeleporterConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/GulagTeleporterConsole.js
rename to tgui/packages/tgui/interfaces/GulagTeleporterConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/Holodeck.js b/tgui/packages/tgui/interfaces/Holodeck.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Holodeck.js
rename to tgui/packages/tgui/interfaces/Holodeck.jsx
diff --git a/tgui/packages/tgui/interfaces/HypnoChair.js b/tgui/packages/tgui/interfaces/HypnoChair.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/HypnoChair.js
rename to tgui/packages/tgui/interfaces/HypnoChair.jsx
diff --git a/tgui/packages/tgui/interfaces/ImplantChair.js b/tgui/packages/tgui/interfaces/ImplantChair.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ImplantChair.js
rename to tgui/packages/tgui/interfaces/ImplantChair.jsx
diff --git a/tgui/packages/tgui/interfaces/InfraredEmitter.js b/tgui/packages/tgui/interfaces/InfraredEmitter.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/InfraredEmitter.js
rename to tgui/packages/tgui/interfaces/InfraredEmitter.jsx
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/BasicInput.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/BasicInput.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/IntegratedCircuit/BasicInput.js
rename to tgui/packages/tgui/interfaces/IntegratedCircuit/BasicInput.jsx
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/CircuitInfo.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/CircuitInfo.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/IntegratedCircuit/CircuitInfo.js
rename to tgui/packages/tgui/interfaces/IntegratedCircuit/CircuitInfo.jsx
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/Connections.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/Connections.jsx
similarity index 96%
rename from tgui/packages/tgui/interfaces/IntegratedCircuit/Connections.js
rename to tgui/packages/tgui/interfaces/IntegratedCircuit/Connections.jsx
index 02cd6f4fc9094..6a59708121341 100644
--- a/tgui/packages/tgui/interfaces/IntegratedCircuit/Connections.js
+++ b/tgui/packages/tgui/interfaces/IntegratedCircuit/Connections.jsx
@@ -1,6 +1,6 @@
import { CSS_COLORS } from '../../constants';
import { SVG_CURVE_INTENSITY } from './constants';
-import { classes } from '../../../common/react';
+import { classes } from 'common/react';
export const Connections = (props, context) => {
const { connections } = props;
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/DisplayName.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/DisplayName.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/IntegratedCircuit/DisplayName.js
rename to tgui/packages/tgui/interfaces/IntegratedCircuit/DisplayName.jsx
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/FundamentalTypes.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/FundamentalTypes.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/IntegratedCircuit/FundamentalTypes.js
rename to tgui/packages/tgui/interfaces/IntegratedCircuit/FundamentalTypes.jsx
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/ObjectComponent.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/ObjectComponent.jsx
similarity index 97%
rename from tgui/packages/tgui/interfaces/IntegratedCircuit/ObjectComponent.js
rename to tgui/packages/tgui/interfaces/IntegratedCircuit/ObjectComponent.jsx
index 4c435a0bcbed9..39f7b25184dbf 100644
--- a/tgui/packages/tgui/interfaces/IntegratedCircuit/ObjectComponent.js
+++ b/tgui/packages/tgui/interfaces/IntegratedCircuit/ObjectComponent.jsx
@@ -1,7 +1,7 @@
import { useBackend } from '../../backend';
-import { Box, Stack, Button, Dropdown } from '../../components';
+import { Box, Stack, Button } from '../../components';
import { Component } from 'inferno';
-import { shallowDiffers } from '../../../common/react';
+import { shallowDiffers } from 'common/react';
import { ABSOLUTE_Y_OFFSET } from './constants';
import { Port } from './Port';
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/Port.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/Port.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/IntegratedCircuit/Port.js
rename to tgui/packages/tgui/interfaces/IntegratedCircuit/Port.jsx
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/VariableMenu.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/VariableMenu.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/IntegratedCircuit/VariableMenu.js
rename to tgui/packages/tgui/interfaces/IntegratedCircuit/VariableMenu.jsx
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/constants.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/constants.ts
similarity index 100%
rename from tgui/packages/tgui/interfaces/IntegratedCircuit/constants.js
rename to tgui/packages/tgui/interfaces/IntegratedCircuit/constants.ts
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/index.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/index.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/IntegratedCircuit/index.js
rename to tgui/packages/tgui/interfaces/IntegratedCircuit/index.jsx
diff --git a/tgui/packages/tgui/interfaces/Intellicard.js b/tgui/packages/tgui/interfaces/Intellicard.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Intellicard.js
rename to tgui/packages/tgui/interfaces/Intellicard.jsx
diff --git a/tgui/packages/tgui/interfaces/Interview.js b/tgui/packages/tgui/interfaces/Interview.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Interview.js
rename to tgui/packages/tgui/interfaces/Interview.jsx
diff --git a/tgui/packages/tgui/interfaces/InterviewManager.js b/tgui/packages/tgui/interfaces/InterviewManager.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/InterviewManager.js
rename to tgui/packages/tgui/interfaces/InterviewManager.jsx
diff --git a/tgui/packages/tgui/interfaces/Jukebox.js b/tgui/packages/tgui/interfaces/Jukebox.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Jukebox.js
rename to tgui/packages/tgui/interfaces/Jukebox.jsx
diff --git a/tgui/packages/tgui/interfaces/KeycardAuth.js b/tgui/packages/tgui/interfaces/KeycardAuth.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/KeycardAuth.js
rename to tgui/packages/tgui/interfaces/KeycardAuth.jsx
diff --git a/tgui/packages/tgui/interfaces/LaborClaimConsole.js b/tgui/packages/tgui/interfaces/LaborClaimConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/LaborClaimConsole.js
rename to tgui/packages/tgui/interfaces/LaborClaimConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/LanguageMenu.js b/tgui/packages/tgui/interfaces/LanguageMenu.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/LanguageMenu.js
rename to tgui/packages/tgui/interfaces/LanguageMenu.jsx
diff --git a/tgui/packages/tgui/interfaces/LaunchpadConsole.js b/tgui/packages/tgui/interfaces/LaunchpadConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/LaunchpadConsole.js
rename to tgui/packages/tgui/interfaces/LaunchpadConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/LaunchpadRemote.js b/tgui/packages/tgui/interfaces/LaunchpadRemote.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/LaunchpadRemote.js
rename to tgui/packages/tgui/interfaces/LaunchpadRemote.jsx
diff --git a/tgui/packages/tgui/interfaces/ListInputModal.tsx b/tgui/packages/tgui/interfaces/ListInputModal.tsx
index 77666ec984e48..052aa8ee717f7 100644
--- a/tgui/packages/tgui/interfaces/ListInputModal.tsx
+++ b/tgui/packages/tgui/interfaces/ListInputModal.tsx
@@ -2,8 +2,8 @@ import { Loader } from './common/Loader';
import { InputButtons } from './common/InputButtons';
import { Button, Input, Section, Stack } from '../components';
import { useBackend, useLocalState } from '../backend';
-import { decodeHtmlEntities } from '../../common/string';
-import { KEY_A, KEY_DOWN, KEY_ESCAPE, KEY_ENTER, KEY_UP, KEY_Z } from '../../common/keycodes';
+import { capitalizeFirst, decodeHtmlEntities } from 'common/string';
+import { KEY_A, KEY_DOWN, KEY_ESCAPE, KEY_ENTER, KEY_UP, KEY_Z } from 'common/keycodes';
import { Window } from '../layouts';
type ListInputData = {
@@ -187,7 +187,7 @@ const ListDisplay = (props, context) => {
'animation': 'none',
'transition': 'none',
}}>
- {item.replace(/^\w/, (c) => c.toUpperCase())}
+ {capitalizeFirst(item)}
);
})}
diff --git a/tgui/packages/tgui/interfaces/MechBayPowerConsole.js b/tgui/packages/tgui/interfaces/MechBayPowerConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/MechBayPowerConsole.js
rename to tgui/packages/tgui/interfaces/MechBayPowerConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/MessageMonitor.js b/tgui/packages/tgui/interfaces/MessageMonitor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/MessageMonitor.js
rename to tgui/packages/tgui/interfaces/MessageMonitor.jsx
diff --git a/tgui/packages/tgui/interfaces/MiningVendor.js b/tgui/packages/tgui/interfaces/MiningVendor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/MiningVendor.js
rename to tgui/packages/tgui/interfaces/MiningVendor.jsx
diff --git a/tgui/packages/tgui/interfaces/Mint.js b/tgui/packages/tgui/interfaces/Mint.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Mint.js
rename to tgui/packages/tgui/interfaces/Mint.jsx
diff --git a/tgui/packages/tgui/interfaces/ModularFabricator.js b/tgui/packages/tgui/interfaces/ModularFabricator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ModularFabricator.js
rename to tgui/packages/tgui/interfaces/ModularFabricator.jsx
diff --git a/tgui/packages/tgui/interfaces/Morph.js b/tgui/packages/tgui/interfaces/Morph.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Morph.js
rename to tgui/packages/tgui/interfaces/Morph.jsx
diff --git a/tgui/packages/tgui/interfaces/Mule.js b/tgui/packages/tgui/interfaces/Mule.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Mule.js
rename to tgui/packages/tgui/interfaces/Mule.jsx
diff --git a/tgui/packages/tgui/interfaces/NaniteChamberControl.js b/tgui/packages/tgui/interfaces/NaniteChamberControl.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NaniteChamberControl.js
rename to tgui/packages/tgui/interfaces/NaniteChamberControl.jsx
diff --git a/tgui/packages/tgui/interfaces/NaniteCloudControl.js b/tgui/packages/tgui/interfaces/NaniteCloudControl.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NaniteCloudControl.js
rename to tgui/packages/tgui/interfaces/NaniteCloudControl.jsx
diff --git a/tgui/packages/tgui/interfaces/NaniteProgramHub.js b/tgui/packages/tgui/interfaces/NaniteProgramHub.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NaniteProgramHub.js
rename to tgui/packages/tgui/interfaces/NaniteProgramHub.jsx
diff --git a/tgui/packages/tgui/interfaces/NaniteProgrammer.js b/tgui/packages/tgui/interfaces/NaniteProgrammer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NaniteProgrammer.js
rename to tgui/packages/tgui/interfaces/NaniteProgrammer.jsx
diff --git a/tgui/packages/tgui/interfaces/NaniteRemote.js b/tgui/packages/tgui/interfaces/NaniteRemote.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NaniteRemote.js
rename to tgui/packages/tgui/interfaces/NaniteRemote.jsx
diff --git a/tgui/packages/tgui/interfaces/Newscaster.js b/tgui/packages/tgui/interfaces/Newscaster.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Newscaster.js
rename to tgui/packages/tgui/interfaces/Newscaster.jsx
diff --git a/tgui/packages/tgui/interfaces/NoticeBoard.js b/tgui/packages/tgui/interfaces/NoticeBoard.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NoticeBoard.js
rename to tgui/packages/tgui/interfaces/NoticeBoard.jsx
diff --git a/tgui/packages/tgui/interfaces/NtnetRelay.js b/tgui/packages/tgui/interfaces/NtnetRelay.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtnetRelay.js
rename to tgui/packages/tgui/interfaces/NtnetRelay.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosAiRestorer.js b/tgui/packages/tgui/interfaces/NtosAiRestorer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosAiRestorer.js
rename to tgui/packages/tgui/interfaces/NtosAiRestorer.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosAirlockControl.js b/tgui/packages/tgui/interfaces/NtosAirlockControl.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosAirlockControl.js
rename to tgui/packages/tgui/interfaces/NtosAirlockControl.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosArcade.js b/tgui/packages/tgui/interfaces/NtosArcade.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosArcade.js
rename to tgui/packages/tgui/interfaces/NtosArcade.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosAtmos.js b/tgui/packages/tgui/interfaces/NtosAtmos.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosAtmos.js
rename to tgui/packages/tgui/interfaces/NtosAtmos.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosBountyBoard.js b/tgui/packages/tgui/interfaces/NtosBountyBoard.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosBountyBoard.js
rename to tgui/packages/tgui/interfaces/NtosBountyBoard.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosBountyConsole.js b/tgui/packages/tgui/interfaces/NtosBountyConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosBountyConsole.js
rename to tgui/packages/tgui/interfaces/NtosBountyConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosCard.js b/tgui/packages/tgui/interfaces/NtosCard.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosCard.js
rename to tgui/packages/tgui/interfaces/NtosCard.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosCargo.js b/tgui/packages/tgui/interfaces/NtosCargo.jsx
similarity index 85%
rename from tgui/packages/tgui/interfaces/NtosCargo.js
rename to tgui/packages/tgui/interfaces/NtosCargo.jsx
index b5009c0aeaf50..b478e42e5aecd 100644
--- a/tgui/packages/tgui/interfaces/NtosCargo.js
+++ b/tgui/packages/tgui/interfaces/NtosCargo.jsx
@@ -1,4 +1,4 @@
-import { CargoContent } from './Cargo.js';
+import { CargoContent } from './Cargo';
import { NtosWindow } from '../layouts';
export const NtosCargo = (props, context) => {
diff --git a/tgui/packages/tgui/interfaces/NtosConfiguration.js b/tgui/packages/tgui/interfaces/NtosConfiguration.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosConfiguration.js
rename to tgui/packages/tgui/interfaces/NtosConfiguration.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosCrewManifest.js b/tgui/packages/tgui/interfaces/NtosCrewManifest.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosCrewManifest.js
rename to tgui/packages/tgui/interfaces/NtosCrewManifest.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js b/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js
rename to tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitorSyndicate.js b/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitorSyndicate.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitorSyndicate.js
rename to tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitorSyndicate.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosCyborgSelfMonitor.js b/tgui/packages/tgui/interfaces/NtosCyborgSelfMonitor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosCyborgSelfMonitor.js
rename to tgui/packages/tgui/interfaces/NtosCyborgSelfMonitor.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosEmagConsole.js b/tgui/packages/tgui/interfaces/NtosEmagConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosEmagConsole.js
rename to tgui/packages/tgui/interfaces/NtosEmagConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosFileManager.js b/tgui/packages/tgui/interfaces/NtosFileManager.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosFileManager.js
rename to tgui/packages/tgui/interfaces/NtosFileManager.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosGhostRbmkStats.js b/tgui/packages/tgui/interfaces/NtosGhostRbmkStats.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosGhostRbmkStats.js
rename to tgui/packages/tgui/interfaces/NtosGhostRbmkStats.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosJobManager.js b/tgui/packages/tgui/interfaces/NtosJobManager.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosJobManager.js
rename to tgui/packages/tgui/interfaces/NtosJobManager.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosLogViewer.js b/tgui/packages/tgui/interfaces/NtosLogViewer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosLogViewer.js
rename to tgui/packages/tgui/interfaces/NtosLogViewer.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosMain.js b/tgui/packages/tgui/interfaces/NtosMain.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosMain.js
rename to tgui/packages/tgui/interfaces/NtosMain.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosMessenger.js b/tgui/packages/tgui/interfaces/NtosMessenger.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosMessenger.js
rename to tgui/packages/tgui/interfaces/NtosMessenger.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosNetChat.js b/tgui/packages/tgui/interfaces/NtosNetChat.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosNetChat.js
rename to tgui/packages/tgui/interfaces/NtosNetChat.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosNetDos.js b/tgui/packages/tgui/interfaces/NtosNetDos.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosNetDos.js
rename to tgui/packages/tgui/interfaces/NtosNetDos.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosNetDownloader.js b/tgui/packages/tgui/interfaces/NtosNetDownloader.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosNetDownloader.js
rename to tgui/packages/tgui/interfaces/NtosNetDownloader.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosNetMonitor.js b/tgui/packages/tgui/interfaces/NtosNetMonitor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosNetMonitor.js
rename to tgui/packages/tgui/interfaces/NtosNetMonitor.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosNewscaster.js b/tgui/packages/tgui/interfaces/NtosNewscaster.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosNewscaster.js
rename to tgui/packages/tgui/interfaces/NtosNewscaster.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosNotepad.js b/tgui/packages/tgui/interfaces/NtosNotepad.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosNotepad.js
rename to tgui/packages/tgui/interfaces/NtosNotepad.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosPhysScanner.js b/tgui/packages/tgui/interfaces/NtosPhysScanner.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosPhysScanner.js
rename to tgui/packages/tgui/interfaces/NtosPhysScanner.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosPortraitPrinter.js b/tgui/packages/tgui/interfaces/NtosPortraitPrinter.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosPortraitPrinter.js
rename to tgui/packages/tgui/interfaces/NtosPortraitPrinter.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosPowerMonitor.js b/tgui/packages/tgui/interfaces/NtosPowerMonitor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosPowerMonitor.js
rename to tgui/packages/tgui/interfaces/NtosPowerMonitor.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosRadar.js b/tgui/packages/tgui/interfaces/NtosRadar.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosRadar.js
rename to tgui/packages/tgui/interfaces/NtosRadar.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosRadarSyndicate.js b/tgui/packages/tgui/interfaces/NtosRadarSyndicate.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosRadarSyndicate.js
rename to tgui/packages/tgui/interfaces/NtosRadarSyndicate.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosRbmkStats.js b/tgui/packages/tgui/interfaces/NtosRbmkStats.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosRbmkStats.js
rename to tgui/packages/tgui/interfaces/NtosRbmkStats.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosRecords.js b/tgui/packages/tgui/interfaces/NtosRecords.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosRecords.js
rename to tgui/packages/tgui/interfaces/NtosRecords.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosRevelation.js b/tgui/packages/tgui/interfaces/NtosRevelation.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosRevelation.js
rename to tgui/packages/tgui/interfaces/NtosRevelation.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosRoboControl.js b/tgui/packages/tgui/interfaces/NtosRoboControl.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosRoboControl.js
rename to tgui/packages/tgui/interfaces/NtosRoboControl.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosSecurEye.js b/tgui/packages/tgui/interfaces/NtosSecurEye.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosSecurEye.js
rename to tgui/packages/tgui/interfaces/NtosSecurEye.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosSignaller.js b/tgui/packages/tgui/interfaces/NtosSignaller.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosSignaller.js
rename to tgui/packages/tgui/interfaces/NtosSignaller.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosStationAlertConsole.js b/tgui/packages/tgui/interfaces/NtosStationAlertConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosStationAlertConsole.js
rename to tgui/packages/tgui/interfaces/NtosStationAlertConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/NtosSupermatterMonitor.js b/tgui/packages/tgui/interfaces/NtosSupermatterMonitor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NtosSupermatterMonitor.js
rename to tgui/packages/tgui/interfaces/NtosSupermatterMonitor.jsx
diff --git a/tgui/packages/tgui/interfaces/NuclearBomb.js b/tgui/packages/tgui/interfaces/NuclearBomb.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/NuclearBomb.js
rename to tgui/packages/tgui/interfaces/NuclearBomb.jsx
diff --git a/tgui/packages/tgui/interfaces/Objective.js b/tgui/packages/tgui/interfaces/Objective.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Objective.js
rename to tgui/packages/tgui/interfaces/Objective.jsx
diff --git a/tgui/packages/tgui/interfaces/OperatingComputer.js b/tgui/packages/tgui/interfaces/OperatingComputer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/OperatingComputer.js
rename to tgui/packages/tgui/interfaces/OperatingComputer.jsx
diff --git a/tgui/packages/tgui/interfaces/Orbit.js b/tgui/packages/tgui/interfaces/Orbit.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Orbit.js
rename to tgui/packages/tgui/interfaces/Orbit.jsx
diff --git a/tgui/packages/tgui/interfaces/OrbitalMap.js b/tgui/packages/tgui/interfaces/OrbitalMap.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/OrbitalMap.js
rename to tgui/packages/tgui/interfaces/OrbitalMap.jsx
diff --git a/tgui/packages/tgui/interfaces/OreBox.js b/tgui/packages/tgui/interfaces/OreBox.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/OreBox.js
rename to tgui/packages/tgui/interfaces/OreBox.jsx
diff --git a/tgui/packages/tgui/interfaces/OreRedemptionMachine.js b/tgui/packages/tgui/interfaces/OreRedemptionMachine.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/OreRedemptionMachine.js
rename to tgui/packages/tgui/interfaces/OreRedemptionMachine.jsx
diff --git a/tgui/packages/tgui/interfaces/OutfitEditor.js b/tgui/packages/tgui/interfaces/OutfitEditor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/OutfitEditor.js
rename to tgui/packages/tgui/interfaces/OutfitEditor.jsx
diff --git a/tgui/packages/tgui/interfaces/OutfitManager.js b/tgui/packages/tgui/interfaces/OutfitManager.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/OutfitManager.js
rename to tgui/packages/tgui/interfaces/OutfitManager.jsx
diff --git a/tgui/packages/tgui/interfaces/PDAInputModal.js b/tgui/packages/tgui/interfaces/PDAInputModal.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PDAInputModal.js
rename to tgui/packages/tgui/interfaces/PDAInputModal.jsx
diff --git a/tgui/packages/tgui/interfaces/PaiInterface.tsx b/tgui/packages/tgui/interfaces/PaiInterface.tsx
index b4d3acb86e140..09153e2f8c245 100644
--- a/tgui/packages/tgui/interfaces/PaiInterface.tsx
+++ b/tgui/packages/tgui/interfaces/PaiInterface.tsx
@@ -1,4 +1,5 @@
import { BooleanLike } from 'common/react';
+import { capitalizeAll } from 'common/string';
import { useBackend, useSharedState } from '../backend';
import { Box, Button, LabeledList, Icon, NoticeBox, ProgressBar, Section, Stack, Table, Tabs, Tooltip } from '../components';
import { Window } from '../layouts';
@@ -290,7 +291,7 @@ const InstalledSoftware = (props, context) => {
installed.map((software) => {
return (
);
})
@@ -310,18 +311,7 @@ const InstalledInfo = (props) => {
return ;
} else {
return (
- letter.toUpperCase()
- // eslint-disable-next-line react/jsx-indent
- )
- }>
+
{software && (
{SOFTWARE_DESC[software] || ''}
@@ -516,7 +506,7 @@ const AvailableRow = (props, context) => {
return (
- {software.name.replace(/^\w/, (c) => c.toUpperCase())}
+ {capitalizeAll(software.name)}
diff --git a/tgui/packages/tgui/interfaces/Pandemic.js b/tgui/packages/tgui/interfaces/Pandemic.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Pandemic.js
rename to tgui/packages/tgui/interfaces/Pandemic.jsx
diff --git a/tgui/packages/tgui/interfaces/ParticleAccelerator.js b/tgui/packages/tgui/interfaces/ParticleAccelerator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ParticleAccelerator.js
rename to tgui/packages/tgui/interfaces/ParticleAccelerator.jsx
diff --git a/tgui/packages/tgui/interfaces/Particool.js b/tgui/packages/tgui/interfaces/Particool.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Particool.js
rename to tgui/packages/tgui/interfaces/Particool.jsx
diff --git a/tgui/packages/tgui/interfaces/PersonalCrafting.js b/tgui/packages/tgui/interfaces/PersonalCrafting.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PersonalCrafting.js
rename to tgui/packages/tgui/interfaces/PersonalCrafting.jsx
diff --git a/tgui/packages/tgui/interfaces/Photocopier.js b/tgui/packages/tgui/interfaces/Photocopier.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Photocopier.js
rename to tgui/packages/tgui/interfaces/Photocopier.jsx
diff --git a/tgui/packages/tgui/interfaces/PhysicalNewscaster.js b/tgui/packages/tgui/interfaces/PhysicalNewscaster.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PhysicalNewscaster.js
rename to tgui/packages/tgui/interfaces/PhysicalNewscaster.jsx
diff --git a/tgui/packages/tgui/interfaces/PictureSelectModal.js b/tgui/packages/tgui/interfaces/PictureSelectModal.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PictureSelectModal.js
rename to tgui/packages/tgui/interfaces/PictureSelectModal.jsx
diff --git a/tgui/packages/tgui/interfaces/PlantDNAManipulator.js b/tgui/packages/tgui/interfaces/PlantDNAManipulator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PlantDNAManipulator.js
rename to tgui/packages/tgui/interfaces/PlantDNAManipulator.jsx
diff --git a/tgui/packages/tgui/interfaces/PlayerPanel.js b/tgui/packages/tgui/interfaces/PlayerPanel.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PlayerPanel.js
rename to tgui/packages/tgui/interfaces/PlayerPanel.jsx
diff --git a/tgui/packages/tgui/interfaces/PortableGenerator.js b/tgui/packages/tgui/interfaces/PortableGenerator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PortableGenerator.js
rename to tgui/packages/tgui/interfaces/PortableGenerator.jsx
diff --git a/tgui/packages/tgui/interfaces/PortablePump.js b/tgui/packages/tgui/interfaces/PortablePump.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PortablePump.js
rename to tgui/packages/tgui/interfaces/PortablePump.jsx
diff --git a/tgui/packages/tgui/interfaces/PortableScrubber.js b/tgui/packages/tgui/interfaces/PortableScrubber.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PortableScrubber.js
rename to tgui/packages/tgui/interfaces/PortableScrubber.jsx
diff --git a/tgui/packages/tgui/interfaces/PortableThermomachine.js b/tgui/packages/tgui/interfaces/PortableThermomachine.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PortableThermomachine.js
rename to tgui/packages/tgui/interfaces/PortableThermomachine.jsx
diff --git a/tgui/packages/tgui/interfaces/PortraitPicker.js b/tgui/packages/tgui/interfaces/PortraitPicker.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PortraitPicker.js
rename to tgui/packages/tgui/interfaces/PortraitPicker.jsx
diff --git a/tgui/packages/tgui/interfaces/PowerMonitor.js b/tgui/packages/tgui/interfaces/PowerMonitor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PowerMonitor.js
rename to tgui/packages/tgui/interfaces/PowerMonitor.jsx
diff --git a/tgui/packages/tgui/interfaces/ProbingConsole.js b/tgui/packages/tgui/interfaces/ProbingConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ProbingConsole.js
rename to tgui/packages/tgui/interfaces/ProbingConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/ProximitySensor.js b/tgui/packages/tgui/interfaces/ProximitySensor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ProximitySensor.js
rename to tgui/packages/tgui/interfaces/ProximitySensor.jsx
diff --git a/tgui/packages/tgui/interfaces/PsychicPlane.js b/tgui/packages/tgui/interfaces/PsychicPlane.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/PsychicPlane.js
rename to tgui/packages/tgui/interfaces/PsychicPlane.jsx
diff --git a/tgui/packages/tgui/interfaces/RDConsole.js b/tgui/packages/tgui/interfaces/RDConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/RDConsole.js
rename to tgui/packages/tgui/interfaces/RDConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/Radio.js b/tgui/packages/tgui/interfaces/Radio.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Radio.js
rename to tgui/packages/tgui/interfaces/Radio.jsx
diff --git a/tgui/packages/tgui/interfaces/RadioactiveMicrolaser.js b/tgui/packages/tgui/interfaces/RadioactiveMicrolaser.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/RadioactiveMicrolaser.js
rename to tgui/packages/tgui/interfaces/RadioactiveMicrolaser.jsx
diff --git a/tgui/packages/tgui/interfaces/RapidPipeDispenser.js b/tgui/packages/tgui/interfaces/RapidPipeDispenser.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/RapidPipeDispenser.js
rename to tgui/packages/tgui/interfaces/RapidPipeDispenser.jsx
diff --git a/tgui/packages/tgui/interfaces/RbmkControlRods.js b/tgui/packages/tgui/interfaces/RbmkControlRods.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/RbmkControlRods.js
rename to tgui/packages/tgui/interfaces/RbmkControlRods.jsx
diff --git a/tgui/packages/tgui/interfaces/ReligiousTool.js b/tgui/packages/tgui/interfaces/ReligiousTool.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ReligiousTool.js
rename to tgui/packages/tgui/interfaces/ReligiousTool.jsx
diff --git a/tgui/packages/tgui/interfaces/RemoteRobotControl.js b/tgui/packages/tgui/interfaces/RemoteRobotControl.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/RemoteRobotControl.js
rename to tgui/packages/tgui/interfaces/RemoteRobotControl.jsx
diff --git a/tgui/packages/tgui/interfaces/RequestManager.js b/tgui/packages/tgui/interfaces/RequestManager.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/RequestManager.js
rename to tgui/packages/tgui/interfaces/RequestManager.jsx
diff --git a/tgui/packages/tgui/interfaces/RoboticsControlConsole.js b/tgui/packages/tgui/interfaces/RoboticsControlConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/RoboticsControlConsole.js
rename to tgui/packages/tgui/interfaces/RoboticsControlConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/SatelliteControl.js b/tgui/packages/tgui/interfaces/SatelliteControl.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/SatelliteControl.js
rename to tgui/packages/tgui/interfaces/SatelliteControl.jsx
diff --git a/tgui/packages/tgui/interfaces/ScannerGate.js b/tgui/packages/tgui/interfaces/ScannerGate.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ScannerGate.js
rename to tgui/packages/tgui/interfaces/ScannerGate.jsx
diff --git a/tgui/packages/tgui/interfaces/SeedExtractor.js b/tgui/packages/tgui/interfaces/SeedExtractor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/SeedExtractor.js
rename to tgui/packages/tgui/interfaces/SeedExtractor.jsx
diff --git a/tgui/packages/tgui/interfaces/SelectEquipment.js b/tgui/packages/tgui/interfaces/SelectEquipment.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/SelectEquipment.js
rename to tgui/packages/tgui/interfaces/SelectEquipment.jsx
diff --git a/tgui/packages/tgui/interfaces/ShuttleDesignator.js b/tgui/packages/tgui/interfaces/ShuttleDesignator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ShuttleDesignator.js
rename to tgui/packages/tgui/interfaces/ShuttleDesignator.jsx
diff --git a/tgui/packages/tgui/interfaces/ShuttleManipulator.js b/tgui/packages/tgui/interfaces/ShuttleManipulator.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ShuttleManipulator.js
rename to tgui/packages/tgui/interfaces/ShuttleManipulator.jsx
diff --git a/tgui/packages/tgui/interfaces/Signaler.js b/tgui/packages/tgui/interfaces/Signaler.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Signaler.js
rename to tgui/packages/tgui/interfaces/Signaler.jsx
diff --git a/tgui/packages/tgui/interfaces/SimpleBot.tsx b/tgui/packages/tgui/interfaces/SimpleBot.tsx
index 5022addced540..67a1cbc7d7252 100644
--- a/tgui/packages/tgui/interfaces/SimpleBot.tsx
+++ b/tgui/packages/tgui/interfaces/SimpleBot.tsx
@@ -1,8 +1,8 @@
-import { multiline } from '../../common/string';
-import { useBackend } from '../backend';
-import { Button, Icon, LabeledControls, NoticeBox, Section, Slider, Stack, Tooltip, Flex } from '../components';
-import { Window } from '../layouts';
-import { getGasLabel } from '../constants';
+import { capitalizeAll, multiline } from 'common/string';
+import { useBackend } from 'tgui/backend';
+import { Button, Icon, LabeledControls, NoticeBox, Section, Slider, Stack, Tooltip, Flex } from 'tgui/components';
+import { Window } from 'tgui/layouts';
+import { getGasLabel } from 'tgui/constants';
type SimpleBotContext = {
can_hack: number;
@@ -169,10 +169,7 @@ const ControlsDisplay = (_, context) => {
return ;
}
return (
- letter.toUpperCase())}>
+
);
diff --git a/tgui/packages/tgui/interfaces/Sleeper.js b/tgui/packages/tgui/interfaces/Sleeper.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Sleeper.js
rename to tgui/packages/tgui/interfaces/Sleeper.jsx
diff --git a/tgui/packages/tgui/interfaces/SlimeBodySwapper.js b/tgui/packages/tgui/interfaces/SlimeBodySwapper.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/SlimeBodySwapper.js
rename to tgui/packages/tgui/interfaces/SlimeBodySwapper.jsx
diff --git a/tgui/packages/tgui/interfaces/SmartVend.js b/tgui/packages/tgui/interfaces/SmartVend.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/SmartVend.js
rename to tgui/packages/tgui/interfaces/SmartVend.jsx
diff --git a/tgui/packages/tgui/interfaces/Smelter.js b/tgui/packages/tgui/interfaces/Smelter.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Smelter.js
rename to tgui/packages/tgui/interfaces/Smelter.jsx
diff --git a/tgui/packages/tgui/interfaces/Smes.js b/tgui/packages/tgui/interfaces/Smes.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Smes.js
rename to tgui/packages/tgui/interfaces/Smes.jsx
diff --git a/tgui/packages/tgui/interfaces/SmokeMachine.js b/tgui/packages/tgui/interfaces/SmokeMachine.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/SmokeMachine.js
rename to tgui/packages/tgui/interfaces/SmokeMachine.jsx
diff --git a/tgui/packages/tgui/interfaces/SolarControl.js b/tgui/packages/tgui/interfaces/SolarControl.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/SolarControl.js
rename to tgui/packages/tgui/interfaces/SolarControl.jsx
diff --git a/tgui/packages/tgui/interfaces/SpawnersMenu.js b/tgui/packages/tgui/interfaces/SpawnersMenu.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/SpawnersMenu.js
rename to tgui/packages/tgui/interfaces/SpawnersMenu.jsx
diff --git a/tgui/packages/tgui/interfaces/Stack.js b/tgui/packages/tgui/interfaces/Stack.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Stack.js
rename to tgui/packages/tgui/interfaces/Stack.jsx
diff --git a/tgui/packages/tgui/interfaces/StationAlertConsole.js b/tgui/packages/tgui/interfaces/StationAlertConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/StationAlertConsole.js
rename to tgui/packages/tgui/interfaces/StationAlertConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/SupermatterMonitor.js b/tgui/packages/tgui/interfaces/SupermatterMonitor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/SupermatterMonitor.js
rename to tgui/packages/tgui/interfaces/SupermatterMonitor.jsx
diff --git a/tgui/packages/tgui/interfaces/SyndContractor.js b/tgui/packages/tgui/interfaces/SyndContractor.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/SyndContractor.js
rename to tgui/packages/tgui/interfaces/SyndContractor.jsx
diff --git a/tgui/packages/tgui/interfaces/TachyonArray.js b/tgui/packages/tgui/interfaces/TachyonArray.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/TachyonArray.js
rename to tgui/packages/tgui/interfaces/TachyonArray.jsx
diff --git a/tgui/packages/tgui/interfaces/Tank.js b/tgui/packages/tgui/interfaces/Tank.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Tank.js
rename to tgui/packages/tgui/interfaces/Tank.jsx
diff --git a/tgui/packages/tgui/interfaces/TankDispenser.js b/tgui/packages/tgui/interfaces/TankDispenser.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/TankDispenser.js
rename to tgui/packages/tgui/interfaces/TankDispenser.jsx
diff --git a/tgui/packages/tgui/interfaces/TechFab.js b/tgui/packages/tgui/interfaces/TechFab.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/TechFab.js
rename to tgui/packages/tgui/interfaces/TechFab.jsx
diff --git a/tgui/packages/tgui/interfaces/Techweb.js b/tgui/packages/tgui/interfaces/Techweb.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Techweb.js
rename to tgui/packages/tgui/interfaces/Techweb.jsx
diff --git a/tgui/packages/tgui/interfaces/Telecomms.js b/tgui/packages/tgui/interfaces/Telecomms.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Telecomms.js
rename to tgui/packages/tgui/interfaces/Telecomms.jsx
diff --git a/tgui/packages/tgui/interfaces/Teleporter.js b/tgui/packages/tgui/interfaces/Teleporter.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Teleporter.js
rename to tgui/packages/tgui/interfaces/Teleporter.jsx
diff --git a/tgui/packages/tgui/interfaces/ThermoMachine.js b/tgui/packages/tgui/interfaces/ThermoMachine.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/ThermoMachine.js
rename to tgui/packages/tgui/interfaces/ThermoMachine.jsx
diff --git a/tgui/packages/tgui/interfaces/TicketBrowser.js b/tgui/packages/tgui/interfaces/TicketBrowser.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/TicketBrowser.js
rename to tgui/packages/tgui/interfaces/TicketBrowser.jsx
diff --git a/tgui/packages/tgui/interfaces/TicketMessenger.js b/tgui/packages/tgui/interfaces/TicketMessenger.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/TicketMessenger.js
rename to tgui/packages/tgui/interfaces/TicketMessenger.jsx
diff --git a/tgui/packages/tgui/interfaces/Timer.js b/tgui/packages/tgui/interfaces/Timer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Timer.js
rename to tgui/packages/tgui/interfaces/Timer.jsx
diff --git a/tgui/packages/tgui/interfaces/TrackedPlaytime.js b/tgui/packages/tgui/interfaces/TrackedPlaytime.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/TrackedPlaytime.js
rename to tgui/packages/tgui/interfaces/TrackedPlaytime.jsx
diff --git a/tgui/packages/tgui/interfaces/TraitorBackstoryMenu.js b/tgui/packages/tgui/interfaces/TraitorBackstoryMenu.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/TraitorBackstoryMenu.js
rename to tgui/packages/tgui/interfaces/TraitorBackstoryMenu.jsx
diff --git a/tgui/packages/tgui/interfaces/TransferValve.js b/tgui/packages/tgui/interfaces/TransferValve.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/TransferValve.js
rename to tgui/packages/tgui/interfaces/TransferValve.jsx
diff --git a/tgui/packages/tgui/interfaces/TurbineComputer.js b/tgui/packages/tgui/interfaces/TurbineComputer.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/TurbineComputer.js
rename to tgui/packages/tgui/interfaces/TurbineComputer.jsx
diff --git a/tgui/packages/tgui/interfaces/TurretControl.js b/tgui/packages/tgui/interfaces/TurretControl.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/TurretControl.js
rename to tgui/packages/tgui/interfaces/TurretControl.jsx
diff --git a/tgui/packages/tgui/interfaces/Uplink.js b/tgui/packages/tgui/interfaces/Uplink.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Uplink.js
rename to tgui/packages/tgui/interfaces/Uplink.jsx
diff --git a/tgui/packages/tgui/interfaces/Vendatray.js b/tgui/packages/tgui/interfaces/Vendatray.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Vendatray.js
rename to tgui/packages/tgui/interfaces/Vendatray.jsx
diff --git a/tgui/packages/tgui/interfaces/Vending.tsx b/tgui/packages/tgui/interfaces/Vending.tsx
index 271d4acda2f2b..c8a066a131aea 100644
--- a/tgui/packages/tgui/interfaces/Vending.tsx
+++ b/tgui/packages/tgui/interfaces/Vending.tsx
@@ -1,4 +1,5 @@
import { classes } from 'common/react';
+import { capitalizeAll } from 'common/string';
import { useBackend, useLocalState } from 'tgui/backend';
import { Box, Button, Icon, LabeledList, NoticeBox, Section, Stack, Table } from 'tgui/components';
import { Window } from 'tgui/layouts';
@@ -213,7 +214,7 @@ const VendingRow = (props, context) => {
- {product.name.replace(/^\w/, (c) => c.toUpperCase())}
+ {capitalizeAll(product.name)}
{!!productStock?.colorable && }
diff --git a/tgui/packages/tgui/interfaces/Vote.js b/tgui/packages/tgui/interfaces/Vote.jsx
similarity index 91%
rename from tgui/packages/tgui/interfaces/Vote.js
rename to tgui/packages/tgui/interfaces/Vote.jsx
index a6578901e31b8..eda5f107c3ca5 100644
--- a/tgui/packages/tgui/interfaces/Vote.js
+++ b/tgui/packages/tgui/interfaces/Vote.jsx
@@ -1,22 +1,14 @@
-import { useBackend } from '../backend';
-import { Box, Icon, Flex, Button, Section, Collapsible } from '../components';
-import { Window } from '../layouts';
-import { logger } from '../logging';
+import { capitalizeFirst } from 'common/string';
+import { useBackend } from 'tgui/backend';
+import { Box, Icon, Flex, Button, Section, Collapsible } from 'tgui/components';
+import { Window } from 'tgui/layouts';
export const Vote = (props, context) => {
const { data } = useBackend(context);
const { mode, question, lower_admin } = data;
return (
- c.toUpperCase()) : mode.replace(/^\w/, (c) => c.toUpperCase())}`
- : ''
- }`}
- width={400}
- height={500}>
+
{!!lower_admin && }
@@ -147,7 +139,7 @@ const DisplayChoices = (props, context) => {
});
}}
disabled={choice === props.choices[selectedChoice - props.startIndex - 1]}>
- {choice.name?.replace(/^\w/, (c) => c.toUpperCase())}
+ {capitalizeFirst(choice.name)}
{choice === props.choices[selectedChoice - props.startIndex - 1] && }
diff --git a/tgui/packages/tgui/interfaces/Wires.js b/tgui/packages/tgui/interfaces/Wires.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Wires.js
rename to tgui/packages/tgui/interfaces/Wires.jsx
diff --git a/tgui/packages/tgui/interfaces/Workshop.js b/tgui/packages/tgui/interfaces/Workshop.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/Workshop.js
rename to tgui/packages/tgui/interfaces/Workshop.jsx
diff --git a/tgui/packages/tgui/interfaces/XenoartifactConsole.js b/tgui/packages/tgui/interfaces/XenoartifactConsole.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/XenoartifactConsole.js
rename to tgui/packages/tgui/interfaces/XenoartifactConsole.jsx
diff --git a/tgui/packages/tgui/interfaces/XenoartifactLabeler.js b/tgui/packages/tgui/interfaces/XenoartifactLabeler.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/XenoartifactLabeler.js
rename to tgui/packages/tgui/interfaces/XenoartifactLabeler.jsx
diff --git a/tgui/packages/tgui/interfaces/common/AccessList.js b/tgui/packages/tgui/interfaces/common/AccessList.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/common/AccessList.js
rename to tgui/packages/tgui/interfaces/common/AccessList.jsx
diff --git a/tgui/packages/tgui/interfaces/common/AtmosControls.js b/tgui/packages/tgui/interfaces/common/AtmosControls.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/common/AtmosControls.js
rename to tgui/packages/tgui/interfaces/common/AtmosControls.jsx
diff --git a/tgui/packages/tgui/interfaces/common/BeakerContents.js b/tgui/packages/tgui/interfaces/common/BeakerContents.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/common/BeakerContents.js
rename to tgui/packages/tgui/interfaces/common/BeakerContents.jsx
diff --git a/tgui/packages/tgui/interfaces/common/InterfaceLockNoticeBox.js b/tgui/packages/tgui/interfaces/common/InterfaceLockNoticeBox.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/common/InterfaceLockNoticeBox.js
rename to tgui/packages/tgui/interfaces/common/InterfaceLockNoticeBox.jsx
diff --git a/tgui/packages/tgui/interfaces/common/PortableAtmos.js b/tgui/packages/tgui/interfaces/common/PortableAtmos.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/common/PortableAtmos.js
rename to tgui/packages/tgui/interfaces/common/PortableAtmos.jsx
diff --git a/tgui/packages/tgui/interfaces/manually-routed/KitchenSink.js b/tgui/packages/tgui/interfaces/manually-routed/KitchenSink.jsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/manually-routed/KitchenSink.js
rename to tgui/packages/tgui/interfaces/manually-routed/KitchenSink.jsx
diff --git a/tgui/packages/tgui/layouts/Layout.js b/tgui/packages/tgui/layouts/Layout.jsx
similarity index 100%
rename from tgui/packages/tgui/layouts/Layout.js
rename to tgui/packages/tgui/layouts/Layout.jsx
diff --git a/tgui/packages/tgui/layouts/NtosWindow.js b/tgui/packages/tgui/layouts/NtosWindow.jsx
similarity index 100%
rename from tgui/packages/tgui/layouts/NtosWindow.js
rename to tgui/packages/tgui/layouts/NtosWindow.jsx
diff --git a/tgui/packages/tgui/layouts/Pane.js b/tgui/packages/tgui/layouts/Pane.jsx
similarity index 100%
rename from tgui/packages/tgui/layouts/Pane.js
rename to tgui/packages/tgui/layouts/Pane.jsx
diff --git a/tgui/packages/tgui/layouts/Window.js b/tgui/packages/tgui/layouts/Window.jsx
similarity index 100%
rename from tgui/packages/tgui/layouts/Window.js
rename to tgui/packages/tgui/layouts/Window.jsx
diff --git a/tgui/packages/tgui/layouts/index.js b/tgui/packages/tgui/layouts/index.ts
similarity index 100%
rename from tgui/packages/tgui/layouts/index.js
rename to tgui/packages/tgui/layouts/index.ts
diff --git a/tgui/packages/tgui/links.js b/tgui/packages/tgui/links.js
deleted file mode 100644
index 0aaca2a1d7eec..0000000000000
--- a/tgui/packages/tgui/links.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-/**
- * Prevents baby jailing the user when he clicks an external link.
- */
-export const captureExternalLinks = () => {
- // Click handler
- const listenerFn = (e) => {
- const tagName = String(e.target.tagName).toLowerCase();
- const href = String(e.target.href);
- // Must be a link
- if (tagName !== 'a') {
- return;
- }
- // Leave BYOND links alone
- const isByondLink = href.charAt(0) === '?' || href.startsWith(location.origin) || href.startsWith('byond://');
- if (isByondLink) {
- return;
- }
- // Prevent default action
- e.preventDefault();
- // Open the link
- Byond.sendMessage({
- type: 'openLink',
- url: href,
- });
- };
- // Subscribe to all document clicks
- document.addEventListener('click', listenerFn);
-};
diff --git a/tgui/packages/tgui/links.test.ts b/tgui/packages/tgui/links.test.ts
new file mode 100644
index 0000000000000..96b010a653931
--- /dev/null
+++ b/tgui/packages/tgui/links.test.ts
@@ -0,0 +1,76 @@
+import { captureExternalLinks } from './links';
+
+describe('captureExternalLinks', () => {
+ let addEventListenerSpy;
+ let clickHandler;
+
+ beforeEach(() => {
+ addEventListenerSpy = jest.spyOn(document, 'addEventListener');
+ captureExternalLinks();
+ clickHandler = addEventListenerSpy.mock.calls[0][1];
+ });
+
+ afterEach(() => {
+ addEventListenerSpy.mockRestore();
+ });
+
+ it('should subscribe to document clicks', () => {
+ expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function));
+ });
+
+ it('should preventDefault and send a message when a non-BYOND external link is clicked', () => {
+ const externalLink = {
+ tagName: 'A',
+ getAttribute: () => 'https://example.com',
+ parentElement: document.body,
+ };
+ const byond = { sendMessage: jest.fn() };
+ // @ts-ignore
+ global.Byond = byond;
+
+ const evt = { target: externalLink, preventDefault: jest.fn() };
+ clickHandler(evt);
+
+ expect(evt.preventDefault).toHaveBeenCalled();
+ expect(byond.sendMessage).toHaveBeenCalledWith({
+ type: 'openLink',
+ url: 'https://example.com',
+ });
+ });
+
+ it('should not preventDefault or send a message when a BYOND link is clicked', () => {
+ const byondLink = {
+ tagName: 'A',
+ getAttribute: () => 'byond://server-address',
+ parentElement: document.body,
+ };
+ const byond = { sendMessage: jest.fn() };
+ // @ts-ignore
+ global.Byond = byond;
+
+ const evt = { target: byondLink, preventDefault: jest.fn() };
+ clickHandler(evt);
+
+ expect(evt.preventDefault).not.toHaveBeenCalled();
+ expect(byond.sendMessage).not.toHaveBeenCalled();
+ });
+
+ it('should add https:// to www links', () => {
+ const wwwLink = {
+ tagName: 'A',
+ getAttribute: () => 'www.example.com',
+ parentElement: document.body,
+ };
+ const byond = { sendMessage: jest.fn() };
+ // @ts-ignore
+ global.Byond = byond;
+
+ const evt = { target: wwwLink, preventDefault: jest.fn() };
+ clickHandler(evt);
+
+ expect(byond.sendMessage).toHaveBeenCalledWith({
+ type: 'openLink',
+ url: 'https://www.example.com',
+ });
+ });
+});
diff --git a/tgui/packages/tgui/links.ts b/tgui/packages/tgui/links.ts
new file mode 100644
index 0000000000000..7c3036b922352
--- /dev/null
+++ b/tgui/packages/tgui/links.ts
@@ -0,0 +1,45 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+/**
+ * Prevents baby jailing the user when he clicks an external link.
+ */
+export const captureExternalLinks = () => {
+ // Subscribe to all document clicks
+ document.addEventListener('click', (evt: MouseEvent) => {
+ let target = evt.target as HTMLElement;
+ // Recurse down the tree to find a valid link
+ while (true) {
+ // Reached the end, bail.
+ if (!target || target === document.body) {
+ return;
+ }
+ const tagName = String(target.tagName).toLowerCase();
+ if (tagName === 'a') {
+ break;
+ }
+ target = target.parentElement as HTMLElement;
+ }
+ const hrefAttr = target.getAttribute('href') || '';
+ // Leave BYOND links alone
+ const isByondLink = hrefAttr.charAt(0) === '?' || hrefAttr.startsWith('byond://');
+ if (isByondLink) {
+ return;
+ }
+ // Prevent default action
+ evt.preventDefault();
+ // Normalize the URL
+ let url = hrefAttr;
+ if (url.toLowerCase().startsWith('www')) {
+ url = 'https://' + url;
+ }
+ // Open the link
+ Byond.sendMessage({
+ type: 'openLink',
+ url,
+ });
+ });
+};
diff --git a/tgui/packages/tgui/logging.js b/tgui/packages/tgui/logging.ts
similarity index 56%
rename from tgui/packages/tgui/logging.js
rename to tgui/packages/tgui/logging.ts
index e2b2404d649eb..cc2e9d2f01874 100644
--- a/tgui/packages/tgui/logging.js
+++ b/tgui/packages/tgui/logging.ts
@@ -12,15 +12,23 @@ const LEVEL_INFO = 2;
const LEVEL_WARN = 3;
const LEVEL_ERROR = 4;
-const log = (level, ns, ...args) => {
+interface Logger {
+ debug: (...args: any[]) => void;
+ log: (...args: any[]) => void;
+ info: (...args: any[]) => void;
+ warn: (...args: any[]) => void;
+ error: (...args: any[]) => void;
+}
+
+const log = (level: number, namespace = 'Generic', ...args: any[]): void => {
// Send logs to a remote log collector
if (process.env.NODE_ENV !== 'production') {
- sendLogEntry(level, ns, ...args);
+ sendLogEntry(level, namespace, ...args);
}
// Send important logs to the backend
if (level >= LEVEL_INFO) {
const logEntry =
- [ns, ...args]
+ [namespace, ...args]
.map((value) => {
if (typeof value === 'string') {
return value;
@@ -36,18 +44,19 @@ const log = (level, ns, ...args) => {
navigator.userAgent;
Byond.sendMessage({
type: 'log',
+ ns: namespace,
message: logEntry,
});
}
};
-export const createLogger = (ns) => {
+export const createLogger = (namespace?: string): Logger => {
return {
- debug: (...args) => log(LEVEL_DEBUG, ns, ...args),
- log: (...args) => log(LEVEL_LOG, ns, ...args),
- info: (...args) => log(LEVEL_INFO, ns, ...args),
- warn: (...args) => log(LEVEL_WARN, ns, ...args),
- error: (...args) => log(LEVEL_ERROR, ns, ...args),
+ debug: (...args) => log(LEVEL_DEBUG, namespace, ...args),
+ log: (...args) => log(LEVEL_LOG, namespace, ...args),
+ info: (...args) => log(LEVEL_INFO, namespace, ...args),
+ warn: (...args) => log(LEVEL_WARN, namespace, ...args),
+ error: (...args) => log(LEVEL_ERROR, namespace, ...args),
};
};
@@ -56,4 +65,4 @@ export const createLogger = (ns) => {
*
* Does not have a namespace associated with it.
*/
-export const logger = createLogger();
+export const logger: Logger = createLogger();
diff --git a/tgui/packages/tgui/package.json b/tgui/packages/tgui/package.json
index 310a8247848e3..05c3efb1069fd 100644
--- a/tgui/packages/tgui/package.json
+++ b/tgui/packages/tgui/package.json
@@ -1,16 +1,16 @@
{
"private": true,
"name": "tgui",
- "version": "4.3.0",
+ "version": "4.4.0",
"dependencies": {
- "@popperjs/core": "^2.9.3",
+ "@popperjs/core": "^2.11.8",
"@types/marked": "^4.0.8",
"common": "workspace:*",
- "csstype": "^3.1.1",
+ "csstype": "^3.1.3",
"dateformat": "^5.0.3",
"dompurify": "^3.0.1",
- "inferno": "^8.2.1",
- "inferno-vnode-flags": "^8.2.1",
+ "inferno": "^8.2.3",
+ "inferno-vnode-flags": "^8.2.3",
"js-yaml": "^4.1.0",
"marked": "^4.2.12",
"tgui-dev-server": "workspace:*",
diff --git a/tgui/packages/tgui/routes.js b/tgui/packages/tgui/routes.tsx
similarity index 79%
rename from tgui/packages/tgui/routes.js
rename to tgui/packages/tgui/routes.tsx
index 7a19cbd679596..6fd4cd850a872 100644
--- a/tgui/packages/tgui/routes.js
+++ b/tgui/packages/tgui/routes.tsx
@@ -4,14 +4,16 @@
* @license MIT
*/
-import { selectBackend } from './backend';
import { Icon, Section, Stack } from './components';
-import { selectDebug } from './debug/selectors';
+
+import { Store } from 'common/redux';
import { Window } from './layouts';
+import { selectBackend } from './backend';
+import { selectDebug } from './debug/selectors';
const requireInterface = require.context('./interfaces');
-const routingError = (type, name) => () => {
+const routingError = (type: 'notFound' | 'missingExport', name: string) => () => {
return (
@@ -30,6 +32,7 @@ const routingError = (type, name) => () => {
);
};
+// Displays an empty Window with scrollable content
const SuspendedWindow = () => {
return (
@@ -38,6 +41,7 @@ const SuspendedWindow = () => {
);
};
+// Displays a loading screen with a spinning icon
const RefreshingWindow = () => {
return (
@@ -55,7 +59,8 @@ const RefreshingWindow = () => {
);
};
-export const getRoutedComponent = (store) => {
+// Get the component for the current route
+export const getRoutedComponent = (store: Store) => {
const state = store.getState();
const { suspended, config } = selectBackend(state);
if (suspended) {
@@ -73,14 +78,14 @@ export const getRoutedComponent = (store) => {
}
const name = config?.interface;
const interfacePathBuilders = [
- (name) => `./${name}.tsx`,
- (name) => `./${name}.js`,
- (name) => `./${name}/index.tsx`,
- (name) => `./${name}/index.js`,
+ (name: string) => `./${name}.tsx`,
+ (name: string) => `./${name}.jsx`,
+ (name: string) => `./${name}/index.tsx`,
+ (name: string) => `./${name}/index.jsx`,
];
let esModule;
while (!esModule && interfacePathBuilders.length > 0) {
- const interfacePathBuilder = interfacePathBuilders.shift();
+ const interfacePathBuilder = interfacePathBuilders.shift()!;
const interfacePath = interfacePathBuilder(name);
try {
esModule = requireInterface(interfacePath);
diff --git a/tgui/packages/tgui/sanitize.test.ts b/tgui/packages/tgui/sanitize.test.ts
new file mode 100644
index 0000000000000..38416e9ef1aa5
--- /dev/null
+++ b/tgui/packages/tgui/sanitize.test.ts
@@ -0,0 +1,33 @@
+import { sanitizeText } from './sanitize';
+
+describe('sanitizeText', () => {
+ it('should sanitize basic HTML input', () => {
+ const input = 'Hello, world!';
+ const expected = 'Hello, world!';
+ const result = sanitizeText(input);
+ expect(result).toBe(expected);
+ });
+
+ it('should sanitize advanced HTML input when advHtml flag is true', () => {
+ const input = 'Hello, world!';
+ const expected = 'Hello, world!';
+ const result = sanitizeText(input, true);
+ expect(result).toBe(expected);
+ });
+
+ it('should allow specific HTML tags when tags array is provided', () => {
+ const input = 'Hello, world!Goodbye, world!';
+ const tags = ['b'];
+ const expected = 'Hello, world!Goodbye, world!';
+ const result = sanitizeText(input, false, tags);
+ expect(result).toBe(expected);
+ });
+
+ it('should allow advanced HTML tags when advTags array is provided and advHtml flag is true', () => {
+ const input = 'Hello, world!';
+ const advTags = ['iframe'];
+ const expected = 'Hello, world!';
+ const result = sanitizeText(input, true, undefined, undefined, advTags);
+ expect(result).toBe(expected);
+ });
+});
diff --git a/tgui/packages/tgui/sanitize.js b/tgui/packages/tgui/sanitize.ts
similarity index 68%
rename from tgui/packages/tgui/sanitize.js
rename to tgui/packages/tgui/sanitize.ts
index 8077efa9b5037..f2473df894b83 100644
--- a/tgui/packages/tgui/sanitize.js
+++ b/tgui/packages/tgui/sanitize.ts
@@ -54,17 +54,17 @@ let defAttr = ['class', 'style'];
/**
* Feed it a string and it should spit out a sanitized version.
*
- * @param {string} input
- * @param {boolean} advHtml
- * @param {array} tags
- * @param {array} forbidAttr
- * @param {array} advTags
+ * @param input - Input HTML string to sanitize
+ * @param advHtml - Flag to enable/disable advanced HTML
+ * @param tags - List of allowed HTML tags
+ * @param forbidAttr - List of forbidden HTML attributes
+ * @param advTags - List of advanced HTML tags allowed for trusted sources
*/
-export const sanitizeText = (input, advHtml, tags = defTag, forbidAttr = defAttr, advTags = advTag) => {
+export const sanitizeText = (input: string, advHtml = false, tags = defTag, forbidAttr = defAttr, advTags = advTag) => {
// This is VERY important to think first if you NEED
// the tag you put in here. We are pushing all this
// though dangerouslySetInnerHTML and even though
- // the default DOMPurify kills javascript, it dosn't
+ // the default DOMPurify kills javascript, it doesn't
// kill href links or such
if (advHtml) {
tags = tags.concat(advTags);
diff --git a/tgui/packages/tgui/store.js b/tgui/packages/tgui/store.js
deleted file mode 100644
index c89db0f5941de..0000000000000
--- a/tgui/packages/tgui/store.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { flow } from 'common/fp';
-import { applyMiddleware, combineReducers, createStore } from 'common/redux';
-import { Component } from 'inferno';
-import { assetMiddleware } from './assets';
-import { backendMiddleware, backendReducer } from './backend';
-import { debugMiddleware, debugReducer, relayMiddleware } from './debug';
-import { createLogger } from './logging';
-
-const logger = createLogger('store');
-
-export const configureStore = (options = {}) => {
- const { sideEffects = true } = options;
- const reducer = flow([
- combineReducers({
- debug: debugReducer,
- backend: backendReducer,
- }),
- options.reducer,
- ]);
- const middleware = !sideEffects
- ? []
- : [...(options.middleware?.pre || []), assetMiddleware, backendMiddleware, ...(options.middleware?.post || [])];
- if (process.env.NODE_ENV !== 'production') {
- // We are using two if statements because Webpack is capable of
- // removing this specific block as dead code.
- if (sideEffects) {
- middleware.unshift(loggingMiddleware, debugMiddleware, relayMiddleware);
- }
- }
- const enhancer = applyMiddleware(...middleware);
- const store = createStore(reducer, enhancer);
- // Globals
- window.__store__ = store;
- window.__augmentStack__ = createStackAugmentor(store);
- return store;
-};
-
-const loggingMiddleware = (store) => (next) => (action) => {
- const { type, payload } = action;
- if (type === 'update' || type === 'backend/update') {
- logger.debug('action', { type });
- } else {
- logger.debug('action', action);
- }
- return next(action);
-};
-
-/**
- * Creates a function, which can be assigned to window.__augmentStack__
- * to augment reported stack traces with useful data for debugging.
- */
-const createStackAugmentor = (store) => (stack, error) => {
- if (!error) {
- error = new Error(stack.split('\n')[0]);
- error.stack = stack;
- } else if (typeof error === 'object' && !error.stack) {
- error.stack = stack;
- }
- logger.log('FatalError:', error);
- const state = store.getState();
- const config = state?.backend?.config;
- let augmentedStack = stack;
- augmentedStack += '\nUser Agent: ' + navigator.userAgent;
- augmentedStack +=
- '\nState: ' +
- JSON.stringify({
- ckey: config?.client?.ckey,
- interface: config?.interface,
- window: config?.window,
- });
- return augmentedStack;
-};
-
-/**
- * Store provider for Inferno apps.
- */
-export class StoreProvider extends Component {
- getChildContext() {
- const { store } = this.props;
- return { store };
- }
-
- render() {
- return this.props.children;
- }
-}
diff --git a/tgui/packages/tgui/store.ts b/tgui/packages/tgui/store.ts
new file mode 100644
index 0000000000000..b18e611bcd414
--- /dev/null
+++ b/tgui/packages/tgui/store.ts
@@ -0,0 +1,111 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { Middleware, Reducer, Store, applyMiddleware, combineReducers, createStore } from 'common/redux';
+import { backendMiddleware, backendReducer } from './backend';
+import { debugMiddleware, debugReducer, relayMiddleware } from './debug';
+
+import { Component } from 'inferno';
+import { assetMiddleware } from './assets';
+import { createLogger } from './logging';
+import { flow } from 'common/fp';
+
+type ConfigureStoreOptions = {
+ sideEffects?: boolean;
+ reducer?: Reducer;
+ middleware?: {
+ pre?: Middleware[];
+ post?: Middleware[];
+ };
+};
+
+type StackAugmentor = (stack: string, error?: Error) => string;
+
+type StoreProviderProps = {
+ store: Store;
+ children: any;
+};
+
+const logger = createLogger('store');
+
+export const configureStore = (options: ConfigureStoreOptions = {}): Store => {
+ const { sideEffects = true, reducer, middleware } = options;
+ const rootReducer: Reducer = flow([
+ combineReducers({
+ debug: debugReducer,
+ backend: backendReducer,
+ }),
+ reducer,
+ ]);
+
+ const middlewares: Middleware[] = !sideEffects
+ ? []
+ : [...(middleware?.pre || []), assetMiddleware, backendMiddleware, ...(middleware?.post || [])];
+
+ if (process.env.NODE_ENV !== 'production') {
+ // We are using two if statements because Webpack is capable of
+ // removing this specific block as dead code.
+ if (sideEffects) {
+ middlewares.unshift(loggingMiddleware, debugMiddleware, relayMiddleware);
+ }
+ }
+
+ const enhancer = applyMiddleware(...middlewares);
+ const store = createStore(rootReducer, enhancer);
+
+ // Globals
+ window.__store__ = store;
+ window.__augmentStack__ = createStackAugmentor(store);
+
+ return store;
+};
+
+const loggingMiddleware: Middleware = (store) => (next) => (action) => {
+ const { type } = action;
+ logger.debug('action', type === 'update' || type === 'backend/update' ? { type } : action);
+ return next(action);
+};
+
+/**
+ * Creates a function, which can be assigned to window.__augmentStack__
+ * to augment reported stack traces with useful data for debugging.
+ */
+const createStackAugmentor =
+ (store: Store): StackAugmentor =>
+ (stack, error) => {
+ error = error || new Error(stack.split('\n')[0]);
+ error.stack = error.stack || stack;
+
+ logger.log('FatalError:', error);
+ const state = store.getState();
+ const config = state?.backend?.config;
+
+ return (
+ stack +
+ '\nUser Agent: ' +
+ navigator.userAgent +
+ '\nState: ' +
+ JSON.stringify({
+ ckey: config?.client?.ckey,
+ interface: config?.interface,
+ window: config?.window,
+ })
+ );
+ };
+
+/**
+ * Store provider for Inferno apps.
+ */
+export class StoreProvider extends Component {
+ getChildContext() {
+ const { store } = this.props;
+ return { store };
+ }
+
+ render() {
+ return this.props.children;
+ }
+}
diff --git a/tgui/packages/tgui/stories/Blink.stories.js b/tgui/packages/tgui/stories/Blink.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Blink.stories.js
rename to tgui/packages/tgui/stories/Blink.stories.jsx
diff --git a/tgui/packages/tgui/stories/BlockQuote.stories.js b/tgui/packages/tgui/stories/BlockQuote.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/BlockQuote.stories.js
rename to tgui/packages/tgui/stories/BlockQuote.stories.jsx
diff --git a/tgui/packages/tgui/stories/Box.stories.js b/tgui/packages/tgui/stories/Box.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Box.stories.js
rename to tgui/packages/tgui/stories/Box.stories.jsx
diff --git a/tgui/packages/tgui/stories/Button.stories.js b/tgui/packages/tgui/stories/Button.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Button.stories.js
rename to tgui/packages/tgui/stories/Button.stories.jsx
diff --git a/tgui/packages/tgui/stories/ByondUi.stories.js b/tgui/packages/tgui/stories/ByondUi.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/ByondUi.stories.js
rename to tgui/packages/tgui/stories/ByondUi.stories.jsx
diff --git a/tgui/packages/tgui/stories/Collapsible.stories.js b/tgui/packages/tgui/stories/Collapsible.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Collapsible.stories.js
rename to tgui/packages/tgui/stories/Collapsible.stories.jsx
diff --git a/tgui/packages/tgui/stories/Flex.stories.js b/tgui/packages/tgui/stories/Flex.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Flex.stories.js
rename to tgui/packages/tgui/stories/Flex.stories.jsx
diff --git a/tgui/packages/tgui/stories/Input.stories.js b/tgui/packages/tgui/stories/Input.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Input.stories.js
rename to tgui/packages/tgui/stories/Input.stories.jsx
diff --git a/tgui/packages/tgui/stories/Popper.stories.js b/tgui/packages/tgui/stories/Popper.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Popper.stories.js
rename to tgui/packages/tgui/stories/Popper.stories.jsx
diff --git a/tgui/packages/tgui/stories/ProgressBar.stories.js b/tgui/packages/tgui/stories/ProgressBar.stories.js
deleted file mode 100644
index bd2ce2455f655..0000000000000
--- a/tgui/packages/tgui/stories/ProgressBar.stories.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * @file
- * @copyright 2021 Aleksej Komarov
- * @license MIT
- */
-
-import { useLocalState } from '../backend';
-import { Box, Button, ProgressBar, Section } from '../components';
-
-export const meta = {
- title: 'ProgressBar',
- render: () => ,
-};
-
-const Story = (props, context) => {
- const [progress, setProgress] = useLocalState(context, 'progress', 0.5);
- return (
-
-
- Value: {Number(progress).toFixed(1)}
-
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/stories/ProgressBar.stories.jsx b/tgui/packages/tgui/stories/ProgressBar.stories.jsx
new file mode 100644
index 0000000000000..dfee21bcdef26
--- /dev/null
+++ b/tgui/packages/tgui/stories/ProgressBar.stories.jsx
@@ -0,0 +1,47 @@
+/**
+ * @file
+ * @copyright 2021 Aleksej Komarov
+ * @license MIT
+ */
+
+import { useLocalState } from '../backend';
+import { Box, Button, Input, LabeledList, ProgressBar, Section } from '../components';
+
+export const meta = {
+ title: 'ProgressBar',
+ render: () => ,
+};
+
+const Story = (props, context) => {
+ const [progress, setProgress] = useLocalState(context, 'progress', 0.5);
+ const [color, setColor] = useLocalState(context, 'color', '');
+
+ const color_data = color
+ ? { color: color }
+ : {
+ ranges: {
+ good: [0.5, Infinity],
+ bad: [-Infinity, 0.1],
+ average: [0, 0.5],
+ },
+ };
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/stories/Stack.stories.js b/tgui/packages/tgui/stories/Stack.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Stack.stories.js
rename to tgui/packages/tgui/stories/Stack.stories.jsx
diff --git a/tgui/packages/tgui/stories/Storage.stories.js b/tgui/packages/tgui/stories/Storage.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Storage.stories.js
rename to tgui/packages/tgui/stories/Storage.stories.jsx
diff --git a/tgui/packages/tgui/stories/Tabs.stories.js b/tgui/packages/tgui/stories/Tabs.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Tabs.stories.js
rename to tgui/packages/tgui/stories/Tabs.stories.jsx
diff --git a/tgui/packages/tgui/stories/Themes.stories.js b/tgui/packages/tgui/stories/Themes.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Themes.stories.js
rename to tgui/packages/tgui/stories/Themes.stories.jsx
diff --git a/tgui/packages/tgui/stories/Tooltip.stories.js b/tgui/packages/tgui/stories/Tooltip.stories.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/Tooltip.stories.js
rename to tgui/packages/tgui/stories/Tooltip.stories.jsx
diff --git a/tgui/packages/tgui/stories/common.js b/tgui/packages/tgui/stories/common.jsx
similarity index 100%
rename from tgui/packages/tgui/stories/common.js
rename to tgui/packages/tgui/stories/common.jsx
diff --git a/tgui/packages/tgui/styles/components/Dropdown.scss b/tgui/packages/tgui/styles/components/Dropdown.scss
index bd670681e2be3..c8b4610b6b076 100644
--- a/tgui/packages/tgui/styles/components/Dropdown.scss
+++ b/tgui/packages/tgui/styles/components/Dropdown.scss
@@ -51,6 +51,11 @@
line-height: base.em(17px);
transition: background-color 100ms ease-out;
+ &.selected {
+ background-color: rgba(255, 255, 255, 0.5) !important;
+ transition: background-color 0ms;
+ }
+
&:hover {
background-color: rgba(255, 255, 255, 0.2);
transition: background-color 0ms;
diff --git a/tgui/packages/tgui/styles/components/ProgressBar.scss b/tgui/packages/tgui/styles/components/ProgressBar.scss
index 611c65a7cc835..a544ee2e930e7 100644
--- a/tgui/packages/tgui/styles/components/ProgressBar.scss
+++ b/tgui/packages/tgui/styles/components/ProgressBar.scss
@@ -17,6 +17,8 @@ $bg-map: colors.$bg-map !default;
position: relative;
width: 100%;
padding: 0 0.5em;
+ border-width: base.em(1px) !important;
+ border-style: solid !important;
border-radius: $border-radius;
background-color: $background-color;
transition: border-color 900ms ease-out;
@@ -50,7 +52,7 @@ $bg-map: colors.$bg-map !default;
@each $color-name, $color-value in $bg-map {
.ProgressBar--color--#{$color-name} {
- border: base.em(1px) solid $color-value !important;
+ border-color: $color-value !important;
.ProgressBar__fill {
background-color: $color-value;
diff --git a/tgui/public/tgui-polyfill.min.js b/tgui/public/tgui-polyfill.min.js
index 40061479ec948..68c21147e6d93 100644
--- a/tgui/public/tgui-polyfill.min.js
+++ b/tgui/public/tgui-polyfill.min.js
@@ -1 +1 @@
-(function(window,document){var version="3.7.3";var options=window.html5||{};var reSkip=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;var saveClones=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;var supportsHtml5Styles;var expando="_html5shiv";var expanID=0;var expandoData={};var supportsUnknownElements;(function(){try{var a=document.createElement("a");a.innerHTML="";supportsHtml5Styles="hidden"in a;supportsUnknownElements=a.childNodes.length==1||function(){document.createElement("a");var frag=document.createDocumentFragment();return typeof frag.cloneNode=="undefined"||typeof frag.createDocumentFragment=="undefined"||typeof frag.createElement=="undefined"}()}catch(e){supportsHtml5Styles=true;supportsUnknownElements=true}})();function addStyleSheet(ownerDocument,cssText){var p=ownerDocument.createElement("p"),parent=ownerDocument.getElementsByTagName("head")[0]||ownerDocument.documentElement;p.innerHTML="x";return parent.insertBefore(p.lastChild,parent.firstChild)}function getElements(){var elements=html5.elements;return typeof elements=="string"?elements.split(" "):elements}function addElements(newElements,ownerDocument){var elements=html5.elements;if(typeof elements!="string"){elements=elements.join(" ")}if(typeof newElements!="string"){newElements=newElements.join(" ")}html5.elements=elements+" "+newElements;shivDocument(ownerDocument)}function getExpandoData(ownerDocument){var data=expandoData[ownerDocument[expando]];if(!data){data={};expanID++;ownerDocument[expando]=expanID;expandoData[expanID]=data}return data}function createElement(nodeName,ownerDocument,data){if(!ownerDocument){ownerDocument=document}if(supportsUnknownElements){return ownerDocument.createElement(nodeName)}if(!data){data=getExpandoData(ownerDocument)}var node;if(data.cache[nodeName]){node=data.cache[nodeName].cloneNode()}else if(saveClones.test(nodeName)){node=(data.cache[nodeName]=data.createElem(nodeName)).cloneNode()}else{node=data.createElem(nodeName)}return node.canHaveChildren&&!reSkip.test(nodeName)&&!node.tagUrn?data.frag.appendChild(node):node}function createDocumentFragment(ownerDocument,data){if(!ownerDocument){ownerDocument=document}if(supportsUnknownElements){return ownerDocument.createDocumentFragment()}data=data||getExpandoData(ownerDocument);var clone=data.frag.cloneNode(),i=0,elems=getElements(),l=elems.length;for(;i3?getModifier(init):null,key=String(init.key),chr=String(init.char),location=init.location,keyCode=init.keyCode||(init.keyCode=key)&&key.charCodeAt(0)||0,charCode=init.charCode||(init.charCode=chr)&&chr.charCodeAt(0)||0,bubbles=init.bubbles,cancelable=init.cancelable,repeat=init.repeat,locale=init.locale,view=init.view||window,args;if(!init.which)init.which=init.keyCode;if("initKeyEvent"in out){out.initKeyEvent(type,bubbles,cancelable,view,ctrlKey,altKey,shiftKey,metaKey,keyCode,charCode)}else if(0>>0);var proto=Element.prototype;var querySelector=proto.querySelector;var querySelectorAll=proto.querySelectorAll;proto.querySelector=function qS(css){return find(this,querySelector,css)};proto.querySelectorAll=function qSA(css){return find(this,querySelectorAll,css)};function find(node,method,css){node.setAttribute(dataScope,null);var result=method.call(node,String(css).replace(/(^|,\s*)(:scope([ >]|$))/g,(function($0,$1,$2,$3){return $1+"["+dataScope+"]"+($3||" ")})));node.removeAttribute(dataScope);return result}})()}})(window);(function(global){"use strict";var DOMMap=global.WeakMap||function(){var counter=0,dispatched=false,drop=false,value;function dispatch(key,ce,shouldDrop){drop=shouldDrop;dispatched=false;value=undefined;key.dispatchEvent(ce)}function Handler(value){this.value=value}Handler.prototype.handleEvent=function handleEvent(e){dispatched=true;if(drop){e.currentTarget.removeEventListener(e.type,this,false)}else{value=this.value}};function DOMMap(){counter++;this.__ce__=new Event("@DOMMap:"+counter+Math.random())}DOMMap.prototype={constructor:DOMMap,"delete":function del(key){return dispatch(key,this.__ce__,true),dispatched},get:function get(key){dispatch(key,this.__ce__,false);var v=value;value=undefined;return v},has:function has(key){return dispatch(key,this.__ce__,false),dispatched},set:function set(key,value){dispatch(key,this.__ce__,true);key.addEventListener(this.__ce__.type,new Handler(value),false);return this}};return DOMMap}();function Dict(){}Dict.prototype=(Object.create||Object)(null);function createEventListener(type,callback,options){function eventListener(e){if(eventListener.once){e.currentTarget.removeEventListener(e.type,callback,eventListener);eventListener.removed=true}if(eventListener.passive){e.preventDefault=createEventListener.preventDefault}if(typeof eventListener.callback==="function"){eventListener.callback.call(this,e)}else if(eventListener.callback){eventListener.callback.handleEvent(e)}if(eventListener.passive){delete e.preventDefault}}eventListener.type=type;eventListener.callback=callback;eventListener.capture=!!options.capture;eventListener.passive=!!options.passive;eventListener.once=!!options.once;eventListener.removed=false;return eventListener}createEventListener.preventDefault=function preventDefault(){};var Event=global.CustomEvent,dE=global.dispatchEvent,aEL=global.addEventListener,rEL=global.removeEventListener,counter=0,increment=function(){counter++},indexOf=[].indexOf||function indexOf(value){var length=this.length;while(length--){if(this[length]===value){break}}return length},getListenerKey=function(options){return"".concat(options.capture?"1":"0",options.passive?"1":"0",options.once?"1":"0")},augment;try{aEL("_",increment,{once:true});dE(new Event("_"));dE(new Event("_"));rEL("_",increment,{once:true})}catch(o_O){}if(counter!==1){(function(){var dm=new DOMMap;function createAEL(aEL){return function addEventListener(type,handler,options){if(options&&typeof options!=="boolean"){var info=dm.get(this),key=getListenerKey(options),i,tmp,wrap;if(!info)dm.set(this,info=new Dict);if(!(type in info))info[type]={handler:[],wrap:[]};tmp=info[type];i=indexOf.call(tmp.handler,handler);if(i<0){i=tmp.handler.push(handler)-1;tmp.wrap[i]=wrap=new Dict}else{wrap=tmp.wrap[i]}if(!(key in wrap)){wrap[key]=createEventListener(type,handler,options);aEL.call(this,type,wrap[key],wrap[key].capture)}}else{aEL.call(this,type,handler,options)}}}function createREL(rEL){return function removeEventListener(type,handler,options){if(options&&typeof options!=="boolean"){var info=dm.get(this),key,i,tmp,wrap;if(info&&type in info){tmp=info[type];i=indexOf.call(tmp.handler,handler);if(-1>>0;if(typeof callback!=="function"){throw new TypeError(callback+" is not a function")}if(arguments.length>1){T=thisArg}k=0;while(k