diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d5b07ed7735a9..d0e0eb63731ee 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -47,5 +47,5 @@ Dockerfile @crossedfall /tgui/packages/tgui/interfaces/ /tgui/packages/tgui/styles/interfaces/ -/tgui/packages/tgui-panel/styles/goon/chat-dark.scss -/tgui/packages/tgui-panel/styles/goon/chat-light.scss +/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss diff --git a/code/controllers/subsystem/tgui.dm b/code/controllers/subsystem/tgui.dm index f83c2edfcfce3..14046bc9510d7 100644 --- a/code/controllers/subsystem/tgui.dm +++ b/code/controllers/subsystem/tgui.dm @@ -29,6 +29,7 @@ SUBSYSTEM_DEF(tgui) var/polyfill = file2text('tgui/public/tgui-polyfill.min.js') polyfill = "" basehtml = replacetextEx(basehtml, "", polyfill) + basehtml = replacetextEx(basehtml, "", "Nanotrasen (c) 2525-[GLOB.year_integer+YEAR_OFFSET]") /datum/controller/subsystem/tgui/Shutdown() close_all_uis() diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm index f5624fe9aed9d..1fd93a1131a1d 100644 --- a/interface/stylesheet.dm +++ b/interface/stylesheet.dm @@ -2,7 +2,7 @@ /// !!!!!!!!!!HEY LISTEN!!!!!!!!!!!!!!!!!!!!!!!! /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// If you modify this file you ALSO need to modify code/modules/goonchat/browserAssets/browserOutput.css +// If you modify this file you ALSO need to modify tgui/packages/tgui-panel/styles/tgchat/chat-light.scss and chat-dark.scss // BUT you have to use PX font sizes with are on a x8 scale of these font sizes // Sample font-size: DM: 8 CSS: 64px diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml index 44c4b1ebe5623..b3b3af7e7d263 100644 --- a/tgui/.eslintrc.yml +++ b/tgui/.eslintrc.yml @@ -651,7 +651,7 @@ rules: ## Enforce ES5 or ES6 class for React Components react/prefer-es6-class: error ## Enforce that props are read-only - react/prefer-read-only-props: error + # react/prefer-read-only-props: error ## Enforce stateless React Components to be written as a pure function react/prefer-stateless-function: error ## Prevent missing props validation in a React component definition diff --git a/tgui/.yarnrc.yml b/tgui/.yarnrc.yml index 6e0f3f922216d..51d463ba52cdd 100644 --- a/tgui/.yarnrc.yml +++ b/tgui/.yarnrc.yml @@ -6,6 +6,11 @@ logFilters: - code: YN0062 level: discard +packageExtensions: + 'babel-plugin-inferno@*': + dependencies: + '@babel/core': '*' + plugins: - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs spec: '@yarnpkg/plugin-interactive-tools' diff --git a/tgui/babel.config.js b/tgui/babel.config.js index e702c9a7119d6..5f86bada5064d 100644 --- a/tgui/babel.config.js +++ b/tgui/babel.config.js @@ -23,7 +23,7 @@ const createBabelConfig = (options) => { ...presets, ].filter(Boolean), plugins: [ - [require.resolve('@babel/plugin-proposal-class-properties'), { + [require.resolve('@babel/plugin-transform-class-properties'), { loose: true, }], require.resolve('@babel/plugin-transform-jscript'), diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md index a8a22274409e4..57e0efe8067ea 100644 --- a/tgui/docs/component-reference.md +++ b/tgui/docs/component-reference.md @@ -773,7 +773,11 @@ percentage and how filled the bar is. - `maxValue: number` - Highest possible value. - `ranges: { color: [from, to] }` - Applies a `color` to the progress bar based on whether the value lands in the range between `from` and `to`. -- `color: string` - Color of the progress bar. +- `color: string` - Color of the progress bar. Can take any of the following formats: + - `#ffffff` - Hex format + - `rgb(r,g,b) / rgba(r,g,b,a)` - RGB format + - `` - the name of a `color-` CSS class. See `CSS_COLORS` in `constants.js`. + - `` - the name of a base CSS color, if not overridden by the definitions above. - `children: any` - Content to render inside the progress bar. ### `Section` diff --git a/tgui/global.d.ts b/tgui/global.d.ts index af2db9ac7fd59..bb6e208f20f93 100644 --- a/tgui/global.d.ts +++ b/tgui/global.d.ts @@ -201,4 +201,6 @@ const Byond: ByondType; interface Window { Byond: ByondType; + __store__: Store; + __augmentStack__: (store: Store) => StackAugmentor; } diff --git a/tgui/jest.config.js b/tgui/jest.config.js index de06e1c229add..bc1eb21555cfd 100644 --- a/tgui/jest.config.js +++ b/tgui/jest.config.js @@ -1,12 +1,12 @@ module.exports = { roots: ['/packages'], - testMatch: ['/packages/**/__tests__/*.{js,ts,tsx}', '/packages/**/*.{spec,test}.{js,ts,tsx}'], + testMatch: ['/packages/**/__tests__/*.{js,jsx,ts,tsx}', '/packages/**/*.{spec,test}.{js,jsx,ts,tsx}'], testPathIgnorePatterns: ['/packages/tgui-bench'], testEnvironment: 'jsdom', testRunner: require.resolve('jest-circus/runner'), transform: { - '^.+\\.(js|cjs|ts|tsx)$': require.resolve('babel-jest'), + '^.+\\.(js|jsx|cjs|ts|tsx)$': require.resolve('babel-jest'), }, - moduleFileExtensions: ['js', 'cjs', 'ts', 'tsx', 'json'], + moduleFileExtensions: ['js', 'jsx', 'cjs', 'ts', 'tsx', 'json'], resetMocks: true, }; diff --git a/tgui/package.json b/tgui/package.json index 4893883c3847a..1a5db3ba868a1 100644 --- a/tgui/package.json +++ b/tgui/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "tgui-workspace", - "version": "4.3.0", + "version": "4.4.0", "packageManager": "yarn@3.4.1", "workspaces": [ "packages/*" @@ -11,58 +11,58 @@ "tgui:bench": "webpack --env TGUI_BENCH=1 && node packages/tgui-bench/index.js", "tgui:build": "BROWSERSLIST_IGNORE_OLD_DATA=true webpack", "tgui:dev": "node --experimental-modules packages/tgui-dev-server/index.js", - "tgui:lint": "eslint packages --ext .js,.cjs,.ts,.tsx", + "tgui:lint": "eslint packages --ext .js,.cjs,.jsx,.ts,.tsx", "tgui:prettier": "prettierx --check .", "tgui:prettier-format": "prettierx --write packages", - "tgui:sonar": "eslint packages --ext .js,.cjs,.ts,.tsx -c .eslintrc-sonar.yml", + "tgui:sonar": "eslint packages --ext .js,.cjs,.jsx,.ts,.tsx -c .eslintrc-sonar.yml", "tgui:test": "jest --watch", "tgui:test-simple": "CI=true jest --color", "tgui:test-ci": "CI=true jest --color --collect-coverage", "tgui:tsc": "tsc" }, "dependencies": { - "@babel/core": "^7.21.0", - "@babel/eslint-parser": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/plugin-transform-jscript": "^7.20.2", - "@babel/preset-env": "^7.20.2", - "@babel/preset-typescript": "^7.21.0", - "@types/jest": "^29.4.0", - "@types/jsdom": "^21.1.0", + "@babel/core": "^7.26.0", + "@babel/eslint-parser": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-jscript": "^7.25.9", + "@babel/preset-env": "^7.26.0", + "@babel/preset-typescript": "^7.26.0", + "@types/jest": "^29.5.14", + "@types/jsdom": "^21.1.7", "@types/node": "18.x", - "@types/webpack": "^5.28.0", - "@types/webpack-env": "^1.18.0", - "@typescript-eslint/parser": "^5.54.1", - "babel-jest": "^29.5.0", - "babel-loader": "^9.1.2", - "babel-plugin-inferno": "^6.6.0", + "@types/webpack": "^5.28.5", + "@types/webpack-env": "^1.18.5", + "@typescript-eslint/parser": "^5.62.0", + "babel-jest": "^29.7.0", + "babel-loader": "9.1.2", + "babel-plugin-inferno": "^6.7.2", "babel-plugin-transform-remove-console": "^6.9.4", "common": "workspace:*", - "css-loader": "^6.7.3", + "css-loader": "6.8.1", + "esbuild-loader": "^4.2.2", "eslint": "^8.35.0", "eslint-config-prettier": "^8.8.0", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-sonarjs": "^0.18.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-unused-imports": "^2.0.0", "file-loader": "^6.2.0", - "inferno": "^8.2.1", - "jest": "^29.5.0", - "jest-circus": "^29.5.0", - "jest-environment-jsdom": "^29.5.0", - "jsdom": "^21.1.0", - "mini-css-extract-plugin": "^2.7.3", + "inferno": "^8.2.3", + "jest": "^29.7.0", + "jest-circus": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jsdom": "^25.0.1", + "mini-css-extract-plugin": "2.7.6", "prettier": "npm:prettierx@0.19.0", - "sass": "^1.58.3", - "sass-loader": "^13.2.0", - "style-loader": "^3.3.1", - "terser-webpack-plugin": "^5.3.7", - "ts-plugin-inferno": "^6.0.0", + "sass": "1.69.5", + "sass-loader": "13.3.2", + "style-loader": "3.3.3", + "ts-plugin-inferno": "^6.1.0", "typescript": "^4.9.5", "url-loader": "^4.1.1", - "webpack": "^5.81.0", - "webpack-bundle-analyzer": "^4.8.0", - "webpack-cli": "^5.0.1" + "webpack": "^5.97.1", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-cli": "^6.0.1" }, "resolutions": { "minimist": "^1.2.6", diff --git a/tgui/packages/common/color.ts b/tgui/packages/common/color.ts index d16022849b0eb..fd8a284f407a8 100644 --- a/tgui/packages/common/color.ts +++ b/tgui/packages/common/color.ts @@ -20,7 +20,15 @@ export class Color { } toString() { - return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${this.a | 0})`; + // Alpha component needs to permit fractional values, so cannot use | + let alpha = this.a; + if (typeof alpha === 'string') { + alpha = parseFloat(this.a as any); + } + if (isNaN(alpha)) { + alpha = 1; + } + return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${alpha})`; } /** diff --git a/tgui/packages/common/keycodes.js b/tgui/packages/common/keycodes.ts similarity index 100% rename from tgui/packages/common/keycodes.js rename to tgui/packages/common/keycodes.ts diff --git a/tgui/packages/common/keys.ts b/tgui/packages/common/keys.ts new file mode 100644 index 0000000000000..61b79992b486b --- /dev/null +++ b/tgui/packages/common/keys.ts @@ -0,0 +1,39 @@ +/** + * ### Key codes. + * event.keyCode is deprecated, use this reference instead. + * + * Handles modifier keys (Shift, Alt, Control) and arrow keys. + * + * For alphabetical keys, use the actual character (e.g. 'a') instead of the key code. + * + * Something isn't here that you want? Just add it: + * @url https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + * @usage + * ```ts + * import { KEY } from 'tgui/common/keys'; + * + * if (event.key === KEY.Enter) { + * // do something + * } + * ``` + */ +export enum KEY { + Alt = 'Alt', + Backspace = 'Backspace', + Control = 'Control', + Delete = 'Delete', + Down = 'Down', + End = 'End', + Enter = 'Enter', + Escape = 'Esc', + Home = 'Home', + Insert = 'Insert', + Left = 'Left', + PageDown = 'PageDown', + PageUp = 'PageUp', + Right = 'Right', + Shift = 'Shift', + Space = ' ', + Tab = 'Tab', + Up = 'Up', +} diff --git a/tgui/packages/common/redux.test.ts b/tgui/packages/common/redux.test.ts new file mode 100644 index 0000000000000..238426f331bb1 --- /dev/null +++ b/tgui/packages/common/redux.test.ts @@ -0,0 +1,58 @@ +import { Action, Reducer, applyMiddleware, combineReducers, createAction, createStore } from './redux'; + +// Dummy Reducer +const counterReducer: Reducer> = (state = 0, action) => { + switch (action.type) { + case 'INCREMENT': + return state + 1; + case 'DECREMENT': + return state - 1; + default: + return state; + } +}; + +// Dummy Middleware +const loggingMiddleware = (storeApi) => (next) => (action) => { + console.log('Middleware:', action); + return next(action); +}; + +// Dummy Action Creators +const increment = createAction('INCREMENT'); +const decrement = createAction('DECREMENT'); + +describe('Redux implementation tests', () => { + test('createStore works', () => { + const store = createStore(counterReducer); + expect(store.getState()).toBe(0); + }); + + test('createStore with applyMiddleware works', () => { + const store = createStore(counterReducer, applyMiddleware(loggingMiddleware)); + expect(store.getState()).toBe(0); + }); + + test('dispatch works', () => { + const store = createStore(counterReducer); + store.dispatch(increment()); + expect(store.getState()).toBe(1); + store.dispatch(decrement()); + expect(store.getState()).toBe(0); + }); + + test('combineReducers works', () => { + const rootReducer = combineReducers({ + counter: counterReducer, + }); + const store = createStore(rootReducer); + expect(store.getState()).toEqual({ counter: 0 }); + }); + + test('createAction works', () => { + const incrementAction = increment(); + expect(incrementAction).toEqual({ type: 'INCREMENT' }); + const decrementAction = decrement(); + expect(decrementAction).toEqual({ type: 'DECREMENT' }); + }); +}); diff --git a/tgui/packages/common/redux.js b/tgui/packages/common/redux.ts similarity index 50% rename from tgui/packages/common/redux.js rename to tgui/packages/common/redux.ts index 8718dccc0cd73..7b2ecc9eee60c 100644 --- a/tgui/packages/common/redux.js +++ b/tgui/packages/common/redux.ts @@ -4,27 +4,65 @@ * @license MIT */ -import { compose } from './fp'; +export type Reducer = ( + state: State | undefined, + action: ActionType +) => State; + +export type Store = { + dispatch: Dispatch; + subscribe: (listener: () => void) => void; + getState: () => State; +}; + +type MiddlewareAPI = { + getState: () => State; + dispatch: Dispatch; +}; + +export type Middleware = ( + storeApi: MiddlewareAPI +) => (next: Dispatch) => Dispatch; + +export type Action = { + type: TType; +}; + +export type AnyAction = Action & { + [extraProps: string]: any; +}; + +export type Dispatch = (action: ActionType) => void; + +type StoreEnhancer = (createStoreFunction: Function) => Function; + +type PreparedAction = { + payload?: any; + meta?: any; +}; /** * Creates a Redux store. */ -export const createStore = (reducer, enhancer) => { +export const createStore = ( + reducer: Reducer, + enhancer?: StoreEnhancer +): Store => { // Apply a store enhancer (applyMiddleware is one of them). if (enhancer) { return enhancer(createStore)(reducer); } - let currentState; - let listeners = []; + let currentState: State; + let listeners: Array<() => void> = []; - const getState = () => currentState; + const getState = (): State => currentState; - const subscribe = (listener) => { + const subscribe = (listener: () => void): void => { listeners.push(listener); }; - const dispatch = (action) => { + const dispatch = (action: ActionType): void => { currentState = reducer(currentState, action); for (let i = 0; i < listeners.length; i++) { listeners[i](); @@ -33,9 +71,7 @@ export const createStore = (reducer, enhancer) => { // This creates the initial store by causing each reducer to be called // with an undefined state - dispatch({ - type: '@@INIT', - }); + dispatch({ type: '@@INIT' } as ActionType); return { dispatch, @@ -48,28 +84,29 @@ export const createStore = (reducer, enhancer) => { * Creates a store enhancer which applies middleware to all dispatched * actions. */ -export const applyMiddleware = (...middlewares) => { - return (createStore) => - (reducer, ...args) => { - const store = createStore(reducer, ...args); +export const applyMiddleware = (...middlewares: Middleware[]): StoreEnhancer => { + return (createStoreFunction: (reducer: Reducer, enhancer?: StoreEnhancer) => Store) => { + return (reducer, ...args): Store => { + const store = createStoreFunction(reducer, ...args); - let dispatch = () => { + let dispatch: Dispatch = () => { throw new Error('Dispatching while constructing your middleware is not allowed.'); }; - const storeApi = { + const storeApi: MiddlewareAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args), }; const chain = middlewares.map((middleware) => middleware(storeApi)); - dispatch = compose(...chain)(store.dispatch); + dispatch = chain.reduceRight((next, middleware) => middleware(next), store.dispatch); return { ...store, dispatch, }; }; + }; }; /** @@ -80,20 +117,24 @@ export const applyMiddleware = (...middlewares) => { * in the state that are not present in the reducers object. This function * is also more flexible than the redux counterpart. */ -export const combineReducers = (reducersObj) => { +export const combineReducers = (reducersObj: Record): Reducer => { const keys = Object.keys(reducersObj); - let hasChanged = false; + return (prevState = {}, action) => { const nextState = { ...prevState }; - for (let key of keys) { + let hasChanged = false; + + for (const key of keys) { const reducer = reducersObj[key]; const prevDomainState = prevState[key]; const nextDomainState = reducer(prevDomainState, action); + if (prevDomainState !== nextDomainState) { hasChanged = true; nextState[key] = nextDomainState; } } + return hasChanged ? nextState : prevState; }; }; @@ -114,37 +155,42 @@ export const combineReducers = (reducersObj) => { * * @public */ -export const createAction = (type, prepare = null) => { - const actionCreator = (...args) => { - if (!prepare) { - return { type, payload: args[0] }; - } - const prepared = prepare(...args); - if (!prepared) { - throw new Error('prepare function did not return an object'); - } - const action = { type }; - if ('payload' in prepared) { - action.payload = prepared.payload; - } - if ('meta' in prepared) { - action.meta = prepared.meta; +export const createAction = (type: TAction, prepare?: (...args: any[]) => PreparedAction) => { + const actionCreator = (...args: any[]) => { + let action: Action & PreparedAction = { type }; + + if (prepare) { + const prepared = prepare(...args); + if (!prepared) { + throw new Error('prepare function did not return an object'); + } + action = { ...action, ...prepared }; + } else { + action.payload = args[0]; } + return action; }; - actionCreator.toString = () => '' + type; + + actionCreator.toString = () => type; actionCreator.type = type; actionCreator.match = (action) => action.type === type; + return actionCreator; }; // Implementation specific // -------------------------------------------------------- -export const useDispatch = (context) => { +export const useDispatch = (context: { + store: Store; +}): Dispatch => { return context.store.dispatch; }; -export const useSelector = (context, selector) => { +export const useSelector = ( + context: { store: Store }, + selector: (state: State) => Selected +): Selected => { return selector(context.store.getState()); }; diff --git a/tgui/packages/common/string.js b/tgui/packages/common/string.js index 3edd6dae826fb..4567aa3568fe5 100644 --- a/tgui/packages/common/string.js +++ b/tgui/packages/common/string.js @@ -81,6 +81,13 @@ export const createSearch = (searchText, stringifier) => { }; }; +/** + * Capitalizes a word and lowercases the rest. + * @param {string} str + * @returns {string} capitalized string + * + * @example capitalize('heLLo') === 'Hello' + */ export const capitalize = (str) => { // Handle array if (Array.isArray(str)) { @@ -90,6 +97,31 @@ export const capitalize = (str) => { return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); }; +/** + * Similar to capitalize, this takes a string and replaces all first letters + * of any words. + * + * @param {string} str + * @return {string} The string with the first letters capitalized. + * + * @example capitalizeAll('heLLo woRLd') === 'HeLLo WoRLd' + */ +export const capitalizeAll = (str) => { + return str.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => letter.toUpperCase()); +}; + +/** + * Capitalizes only the first letter of the str. + * + * @param {string} str + * @return {string} capitalized string + * + * @example capitalizeFirst('heLLo woRLd') === 'HeLLo woRLd' + */ +export const capitalizeFirst = (str) => { + return str.replace(/^\w/, (letter) => letter.toUpperCase()); +}; + export const toTitleCase = (str) => { // Handle array if (Array.isArray(str)) { diff --git a/tgui/packages/common/timer.js b/tgui/packages/common/timer.ts similarity index 54% rename from tgui/packages/common/timer.js rename to tgui/packages/common/timer.ts index 9de7ee1d523bc..99e0c1b1029c0 100644 --- a/tgui/packages/common/timer.js +++ b/tgui/packages/common/timer.ts @@ -10,9 +10,13 @@ * called for N milliseconds. If `immediate` is passed, trigger the * function on the leading edge, instead of the trailing. */ -export const debounce = (fn, time, immediate = false) => { - let timeout; - return (...args) => { +export const debounce = any>( + fn: F, + time: number, + immediate = false +): ((...args: Parameters) => void) => { + let timeout: ReturnType | null; + return (...args: Parameters) => { const later = () => { timeout = null; if (!immediate) { @@ -20,7 +24,7 @@ export const debounce = (fn, time, immediate = false) => { } }; const callNow = immediate && !timeout; - clearTimeout(timeout); + clearTimeout(timeout!); timeout = setTimeout(later, time); if (callNow) { fn(...args); @@ -32,16 +36,18 @@ export const debounce = (fn, time, immediate = false) => { * Returns a function, that, when invoked, will only be triggered at most once * during a given window of time. */ -export const throttle = (fn, time) => { - let previouslyRun, queuedToRun; - return function invokeFn(...args) { +export const throttle = any>(fn: F, time: number): ((...args: Parameters) => void) => { + let previouslyRun: number | null, queuedToRun: ReturnType | null; + return function invokeFn(...args: Parameters) { const now = Date.now(); - queuedToRun = clearTimeout(queuedToRun); + if (queuedToRun) { + clearTimeout(queuedToRun); + } if (!previouslyRun || now - previouslyRun >= time) { fn.apply(null, args); previouslyRun = now; } else { - queuedToRun = setTimeout(invokeFn.bind(null, ...args), time - (now - previouslyRun)); + queuedToRun = setTimeout(() => invokeFn(...args), time - (now - (previouslyRun ?? 0))); } }; }; @@ -51,4 +57,4 @@ export const throttle = (fn, time) => { * * @param {number} time */ -export const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)); +export const sleep = (time: number): Promise => new Promise((resolve) => setTimeout(resolve, time)); diff --git a/tgui/packages/tgui-bench/index.js b/tgui/packages/tgui-bench/index.js index 9db479007b5b2..b10e0a3e0b68d 100644 --- a/tgui/packages/tgui-bench/index.js +++ b/tgui/packages/tgui-bench/index.js @@ -29,7 +29,7 @@ const setup = async () => { const publicDir = path.resolve(__dirname, '../../public'); const page = fs.readFileSync(path.join(publicDir, 'tgui.html'), 'utf-8').replace('\n', assets); - server.register(require('fastify-static'), { + server.register(require('@fastify/static'), { root: publicDir, }); diff --git a/tgui/packages/tgui-bench/package.json b/tgui/packages/tgui-bench/package.json index ca808012b0492..6ccddde1f596c 100644 --- a/tgui/packages/tgui-bench/package.json +++ b/tgui/packages/tgui-bench/package.json @@ -1,13 +1,13 @@ { "private": true, "name": "tgui-bench", - "version": "4.3.0", + "version": "4.4.0", "dependencies": { + "@fastify/static": "^6.12.0", "common": "workspace:*", - "fastify": "^4.14.1", - "fastify-static": "^4.7.0", - "inferno": "^8.2.1", - "inferno-vnode-flags": "^8.2.1", + "fastify": "^3.29.5", + "inferno": "^8.2.3", + "inferno-vnode-flags": "^8.2.3", "lodash": "^4.17.21", "platform": "^1.3.6", "tgui": "workspace:*" diff --git a/tgui/packages/tgui-bench/tests/DisposalUnit.test.tsx b/tgui/packages/tgui-bench/tests/DisposalUnit.test.tsx index 843bd70367a5d..1ae610e2e2e15 100644 --- a/tgui/packages/tgui-bench/tests/DisposalUnit.test.tsx +++ b/tgui/packages/tgui-bench/tests/DisposalUnit.test.tsx @@ -1,9 +1,10 @@ -import { backendUpdate } from 'tgui/backend'; +import { StoreProvider, configureStore } from 'tgui/store'; + import { DisposalUnit } from 'tgui/interfaces/DisposalUnit'; +import { backendUpdate } from 'tgui/backend'; import { createRenderer } from 'tgui/renderer'; -import { configureStore, StoreProvider } from 'tgui/store'; -const store = configureStore({ sideEffets: false }); +const store = configureStore({ sideEffects: false }); const renderUi = createRenderer((dataJson: string) => { store.dispatch( diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json index a45a38c163d09..3121011fc7d1a 100644 --- a/tgui/packages/tgui-dev-server/package.json +++ b/tgui/packages/tgui-dev-server/package.json @@ -1,13 +1,13 @@ { "private": true, "name": "tgui-dev-server", - "version": "4.3.0", + "version": "4.4.0", "type": "module", "dependencies": { - "axios": "^1.6.0", + "axios": "^1.7.9", "glob": "^7.1.7", "source-map": "^0.7.4", "stacktrace-parser": "^0.1.10", - "ws": "^8.12.1" + "ws": "^8.18.0" } } diff --git a/tgui/packages/tgui-panel/Notifications.js b/tgui/packages/tgui-panel/Notifications.jsx similarity index 100% rename from tgui/packages/tgui-panel/Notifications.js rename to tgui/packages/tgui-panel/Notifications.jsx diff --git a/tgui/packages/tgui-panel/Panel.js b/tgui/packages/tgui-panel/Panel.jsx similarity index 98% rename from tgui/packages/tgui-panel/Panel.js rename to tgui/packages/tgui-panel/Panel.jsx index 42c651bc99723..f56d025dac272 100644 --- a/tgui/packages/tgui-panel/Panel.js +++ b/tgui/packages/tgui-panel/Panel.jsx @@ -15,8 +15,8 @@ import { Notifications } from './Notifications'; import { PingIndicator } from './ping'; import { ReconnectButtons } from './reconnect'; import { SettingsPanel, useSettings } from './settings'; -import { useLocalState } from '../tgui/backend'; -import { Box, Divider, DraggableControl } from '../tgui/components'; +import { useLocalState } from 'tgui/backend'; +import { Box, Divider, DraggableControl } from 'tgui/components'; import { updateSettings } from './settings/actions'; export const Panel = (props, context) => { diff --git a/tgui/packages/tgui-panel/audio/NowPlayingWidget.js b/tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx similarity index 100% rename from tgui/packages/tgui-panel/audio/NowPlayingWidget.js rename to tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx diff --git a/tgui/packages/tgui-panel/audio/index.js b/tgui/packages/tgui-panel/audio/index.ts similarity index 100% rename from tgui/packages/tgui-panel/audio/index.js rename to tgui/packages/tgui-panel/audio/index.ts diff --git a/tgui/packages/tgui-panel/chat/ChatPageSettings.js b/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx similarity index 100% rename from tgui/packages/tgui-panel/chat/ChatPageSettings.js rename to tgui/packages/tgui-panel/chat/ChatPageSettings.jsx diff --git a/tgui/packages/tgui-panel/chat/ChatPanel.js b/tgui/packages/tgui-panel/chat/ChatPanel.jsx similarity index 100% rename from tgui/packages/tgui-panel/chat/ChatPanel.js rename to tgui/packages/tgui-panel/chat/ChatPanel.jsx diff --git a/tgui/packages/tgui-panel/chat/ChatTabs.js b/tgui/packages/tgui-panel/chat/ChatTabs.jsx similarity index 100% rename from tgui/packages/tgui-panel/chat/ChatTabs.js rename to tgui/packages/tgui-panel/chat/ChatTabs.jsx diff --git a/tgui/packages/tgui-panel/chat/constants.js b/tgui/packages/tgui-panel/chat/constants.ts similarity index 100% rename from tgui/packages/tgui-panel/chat/constants.js rename to tgui/packages/tgui-panel/chat/constants.ts diff --git a/tgui/packages/tgui-panel/chat/index.js b/tgui/packages/tgui-panel/chat/index.ts similarity index 100% rename from tgui/packages/tgui-panel/chat/index.js rename to tgui/packages/tgui-panel/chat/index.ts diff --git a/tgui/packages/tgui-panel/chat/renderer.js b/tgui/packages/tgui-panel/chat/renderer.jsx similarity index 99% rename from tgui/packages/tgui-panel/chat/renderer.js rename to tgui/packages/tgui-panel/chat/renderer.jsx index f08e8ac85e981..ac44b956f34bd 100644 --- a/tgui/packages/tgui-panel/chat/renderer.js +++ b/tgui/packages/tgui-panel/chat/renderer.jsx @@ -11,7 +11,7 @@ import { COMBINE_MAX_MESSAGES, COMBINE_MAX_TIME_WINDOW, IMAGE_RETRY_DELAY, IMAGE import { render } from 'inferno'; import { canPageAcceptType, createMessage, isSameMessage } from './model'; import { highlightNode, linkifyNode } from './replaceInTextNode'; -import { Tooltip, RadarChart } from '../../tgui/components'; +import { Tooltip, RadarChart } from 'tgui/components'; const logger = createLogger('chatRenderer'); diff --git a/tgui/packages/tgui-panel/game/constants.js b/tgui/packages/tgui-panel/game/constants.ts similarity index 100% rename from tgui/packages/tgui-panel/game/constants.js rename to tgui/packages/tgui-panel/game/constants.ts diff --git a/tgui/packages/tgui-panel/game/index.js b/tgui/packages/tgui-panel/game/index.ts similarity index 100% rename from tgui/packages/tgui-panel/game/index.js rename to tgui/packages/tgui-panel/game/index.ts diff --git a/tgui/packages/tgui-panel/index.js b/tgui/packages/tgui-panel/index.jsx similarity index 100% rename from tgui/packages/tgui-panel/index.js rename to tgui/packages/tgui-panel/index.jsx diff --git a/tgui/packages/tgui-panel/package.json b/tgui/packages/tgui-panel/package.json index 9182fc4144756..6635c2c249ae3 100644 --- a/tgui/packages/tgui-panel/package.json +++ b/tgui/packages/tgui-panel/package.json @@ -5,7 +5,7 @@ "dependencies": { "common": "workspace:*", "dompurify": "^3.0.1", - "inferno": "^8.2.1", + "inferno": "^8.2.3", "tgui": "workspace:*", "tgui-dev-server": "workspace:*", "tgui-polyfill": "workspace:*" diff --git a/tgui/packages/tgui-panel/ping/PingIndicator.js b/tgui/packages/tgui-panel/ping/PingIndicator.jsx similarity index 100% rename from tgui/packages/tgui-panel/ping/PingIndicator.js rename to tgui/packages/tgui-panel/ping/PingIndicator.jsx diff --git a/tgui/packages/tgui-panel/ping/constants.js b/tgui/packages/tgui-panel/ping/constants.ts similarity index 100% rename from tgui/packages/tgui-panel/ping/constants.js rename to tgui/packages/tgui-panel/ping/constants.ts diff --git a/tgui/packages/tgui-panel/ping/index.js b/tgui/packages/tgui-panel/ping/index.ts similarity index 100% rename from tgui/packages/tgui-panel/ping/index.js rename to tgui/packages/tgui-panel/ping/index.ts diff --git a/tgui/packages/tgui-panel/settings/SettingsPanel.js b/tgui/packages/tgui-panel/settings/SettingsPanel.jsx similarity index 100% rename from tgui/packages/tgui-panel/settings/SettingsPanel.js rename to tgui/packages/tgui-panel/settings/SettingsPanel.jsx diff --git a/tgui/packages/tgui-panel/settings/constants.js b/tgui/packages/tgui-panel/settings/constants.ts similarity index 100% rename from tgui/packages/tgui-panel/settings/constants.js rename to tgui/packages/tgui-panel/settings/constants.ts diff --git a/tgui/packages/tgui-panel/settings/index.js b/tgui/packages/tgui-panel/settings/index.ts similarity index 100% rename from tgui/packages/tgui-panel/settings/index.js rename to tgui/packages/tgui-panel/settings/index.ts diff --git a/tgui/packages/tgui-panel/styles/main.scss b/tgui/packages/tgui-panel/styles/main.scss index 4369351b7ebae..72faac7c45a4d 100644 --- a/tgui/packages/tgui-panel/styles/main.scss +++ b/tgui/packages/tgui-panel/styles/main.scss @@ -56,8 +56,8 @@ // @include meta.load-css('~tgui/styles/layouts/TitleBar.scss'); @include meta.load-css('~tgui/styles/layouts/Window.scss'); -// Goonchat styles -@include meta.load-css('./goon/chat-dark.scss'); +// tgchat styles +@include meta.load-css('./tgchat/chat-dark.scss'); // Chat formats (color, size, font, etc) @include meta.load-css('./chat-format/chat-dark-theme.scss'); diff --git a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss similarity index 100% rename from tgui/packages/tgui-panel/styles/goon/chat-dark.scss rename to tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss diff --git a/tgui/packages/tgui-panel/styles/goon/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss similarity index 100% rename from tgui/packages/tgui-panel/styles/goon/chat-light.scss rename to tgui/packages/tgui-panel/styles/tgchat/chat-light.scss diff --git a/tgui/packages/tgui-panel/styles/themes/light.scss b/tgui/packages/tgui-panel/styles/themes/light.scss index 66e48ed9e6a98..8bd900b01c264 100644 --- a/tgui/packages/tgui-panel/styles/themes/light.scss +++ b/tgui/packages/tgui-panel/styles/themes/light.scss @@ -69,8 +69,8 @@ $with: ('text-color': rgba(0, 0, 0, 0.75), 'background-color': base.$color-bg, 'shadow-color-core': rgba(0, 0, 0, 0.25)) ); - // Goonchat styles - @include meta.load-css('../goon/chat-light.scss'); + // tgchat styles + @include meta.load-css('../tgchat/chat-light.scss'); // Chat formats (color, size, font, etc) @include meta.load-css('../chat-format/chat-light-theme.scss'); diff --git a/tgui/packages/tgui-polyfill/00-html5shiv.js b/tgui/packages/tgui-polyfill/00-html5shiv.js deleted file mode 100644 index c2f2060a1db9f..0000000000000 --- a/tgui/packages/tgui-polyfill/00-html5shiv.js +++ /dev/null @@ -1,330 +0,0 @@ -/** - * @file - * @copyright 2014 Alexander Farkas - * @license MIT - */ - -/* eslint-disable */ -(function(window, document) { -/*jshint evil:true */ - /** version */ - var version = '3.7.3'; - - /** Preset options */ - var options = window.html5 || {}; - - /** Used to skip problem elements */ - var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; - - /** Not all elements can be cloned in IE **/ - 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; - - /** Detect whether the browser supports default html5 styles */ - var supportsHtml5Styles; - - /** Name of the expando, to work with multiple documents or to re-shiv one document */ - var expando = '_html5shiv'; - - /** The id for the the documents expando */ - var expanID = 0; - - /** Cached data for each document */ - var expandoData = {}; - - /** Detect whether the browser supports unknown elements */ - var supportsUnknownElements; - - (function() { - try { - var a = document.createElement('a'); - a.innerHTML = ''; - //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles - supportsHtml5Styles = ('hidden' in a); - - supportsUnknownElements = a.childNodes.length == 1 || (function() { - // assign a false positive if unable to shiv - (document.createElement)('a'); - var frag = document.createDocumentFragment(); - return ( - typeof frag.cloneNode == 'undefined' || - typeof frag.createDocumentFragment == 'undefined' || - typeof frag.createElement == 'undefined' - ); - }()); - } catch(e) { - // assign a false positive if detection fails => unable to shiv - supportsHtml5Styles = true; - supportsUnknownElements = true; - } - - }()); - - /*--------------------------------------------------------------------------*/ - - /** - * Creates a style sheet with the given CSS text and adds it to the document. - * @private - * @param {Document} ownerDocument The document. - * @param {String} cssText The CSS text. - * @returns {StyleSheet} The style element. - */ - 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); - } - - /** - * Returns the value of `html5.elements` as an array. - * @private - * @returns {Array} An array of shived element node names. - */ - function getElements() { - var elements = html5.elements; - return typeof elements == 'string' ? elements.split(' ') : elements; - } - - /** - * Extends the built-in list of html5 elements - * @memberOf html5 - * @param {String|Array} newElements whitespace separated list or array of new element names to shiv - * @param {Document} ownerDocument The context document. - */ - 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); - } - - /** - * Returns the data associated to the given document - * @private - * @param {Document} ownerDocument The document. - * @returns {Object} An object of data. - */ - function getExpandoData(ownerDocument) { - var data = expandoData[ownerDocument[expando]]; - if (!data) { - data = {}; - expanID++; - ownerDocument[expando] = expanID; - expandoData[expanID] = data; - } - return data; - } - - /** - * returns a shived element for the given nodeName and document - * @memberOf html5 - * @param {String} nodeName name of the element - * @param {Document|DocumentFragment} ownerDocument The context document. - * @returns {Object} The shived element. - */ - 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); - } - - // Avoid adding some elements to fragments in IE < 9 because - // * Attributes like `name` or `type` cannot be set/changed once an element - // is inserted into a document/fragment - // * Link elements with `src` attributes that are inaccessible, as with - // a 403 response, will cause the tab/window to crash - // * Script elements appended to fragments will execute when their `src` - // or `text` property is set - return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; - } - - /** - * returns a shived DocumentFragment for the given document - * @memberOf html5 - * @param {Document} ownerDocument The context document. - * @returns {Object} The shived DocumentFragment. - */ - 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(;i 3 ? 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 < initType && 'initKeyboardEvent' in out) { - args = [type, bubbles, cancelable, view]; - switch (initType) { - case 1: - args.push(key, location, ctrlKey, shiftKey, altKey, metaKey, altGraphKey); - break; - case 2: - args.push(ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode); - break; - case 3: - args.push(key, location, ctrlKey, altKey, shiftKey, metaKey, altGraphKey); - break; - case 4: - args.push(key, location, modifiers, repeat, locale); - break; - default: - args.push(char, key, location, modifiers, repeat, locale); - } - out.initKeyboardEvent.apply(out, args); - } else { - out.initEvent(type, bubbles, cancelable); - } - for (key in out) { - if (defaults.hasOwnProperty(key) && out[key] !== init[key]) { - withInitValues(key, out, init); - } - } - return out; - } - KeyboardEvent.prototype = $KeyboardEvent.prototype; - return KeyboardEvent; - }(window.KeyboardEvent || function KeyboardEvent() {})); - defineProperty(window, 'KeyboardEvent', {value: o_O}); - // Android 4 gotcha - if (KeyboardEvent !== o_O) KeyboardEvent = o_O; - } - - // window.MouseEvent as constructor - try { new MouseEvent('_', {}); } catch (o_O) { - /* jshint -W022 */ - o_O = (function ($MouseEvent) { - function MouseEvent(type, init) { - enoughArguments(arguments.length, 'MouseEvent'); - var out = document.createEvent('MouseEvent'); - if (!init) init = {}; - out.initMouseEvent( - type, - !!init.bubbles, - !!init.cancelable, - init.view || window, - init.detail || 1, - init.screenX || 0, - init.screenY || 0, - init.clientX || 0, - init.clientY || 0, - !!init.ctrlKey, - !!init.altKey, - !!init.shiftKey, - !!init.metaKey, - init.button || 0, - init.relatedTarget || null - ); - return out; - } - MouseEvent.prototype = $MouseEvent.prototype; - return MouseEvent; - }(window.MouseEvent || function MouseEvent() {})); - defineProperty(window, 'MouseEvent', {value: o_O}); - // Android 4 gotcha - if (MouseEvent !== o_O) MouseEvent = o_O; - } - - if (!document.querySelectorAll('*').forEach) { - (function () { - function patch(what) { - var querySelectorAll = what.querySelectorAll; - what.querySelectorAll = function qSA(css) { - var result = querySelectorAll.call(this, css); - result.forEach = Array.prototype.forEach; - return result; - }; - } - patch(document); - patch(Element.prototype); - }()); - } - - try { - // https://drafts.csswg.org/selectors-4/#the-scope-pseudo - document.querySelector(':scope *'); - } catch(o_O) { - (function () { - var dataScope = 'data-scope-' + (Math.random() * 1e9 >>> 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'; - - // a WeakMap fallback for DOM nodes only used as key - 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++; // make id clashing highly improbable - 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); - - // https://dom.spec.whatwg.org/#interface-eventtarget - - 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') { - /* jshint validthis: true */ - 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; - // currently pointless but specs say to use it, so ... - 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 < i) { - key = getListenerKey(options); - wrap = tmp.wrap[i]; - if (key in wrap) { - rEL.call(this, type, wrap[key], wrap[key].capture); - delete wrap[key]; - // return if there are other wraps - for (key in wrap) return; - // otherwise remove all the things - tmp.handler.splice(i, 1); - tmp.wrap.splice(i, 1); - // if there are no other handlers - if (tmp.handler.length === 0) - // drop the info[type] entirely - delete info[type]; - } - } - } - } else { - rEL.call(this, type, handler, options); - } - }; - } - - augment = function (Constructor) { - if (!Constructor) return; - var proto = Constructor.prototype; - proto.addEventListener = createAEL(proto.addEventListener); - proto.removeEventListener = createREL(proto.removeEventListener); - }; - - if (global.EventTarget) { - augment(EventTarget); - } else { - augment(global.Text); - augment(global.Element || global.HTMLElement); - augment(global.HTMLDocument); - augment(global.Window || {prototype:global}); - augment(global.XMLHttpRequest); - } - - }()); - } - -}(window)); diff --git a/tgui/packages/tgui-polyfill/03-css-om.js b/tgui/packages/tgui-polyfill/03-css-om.js deleted file mode 100644 index 757d14f928583..0000000000000 --- a/tgui/packages/tgui-polyfill/03-css-om.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * CSS Object Model patches - * - * Adapted from: https://github.com/shawnbot/aight - * - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -/* eslint-disable */ -(function(Proto) { - 'use strict'; - - if (typeof Proto.setAttribute !== 'undefined') { - function toAttr(prop) { - return prop.replace(/-[a-z]/g, function (bit) { - return bit[1].toUpperCase(); - }); - } - - Proto.setProperty = function (prop, value) { - var attr = toAttr(prop); - if (!value) { - return this.removeAttribute(attr); - } - var str = String(value); - return this.setAttribute(attr, str); - }; - - Proto.getPropertyValue = function (prop) { - var attr = toAttr(prop); - return this.getAttribute(attr) || null; - }; - - Proto.removeProperty = function (prop) { - var attr = toAttr(prop); - var value = this.getAttribute(attr); - this.removeAttribute(attr); - return value; - }; - } - -})(CSSStyleDeclaration.prototype); diff --git a/tgui/packages/tgui-polyfill/1-misc.js b/tgui/packages/tgui-polyfill/1-misc.js new file mode 100644 index 0000000000000..bbb73e1040eb2 --- /dev/null +++ b/tgui/packages/tgui-polyfill/1-misc.js @@ -0,0 +1,51 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +/* eslint-disable */ +(function () { + 'use strict'; + + // Inferno needs Int32Array, and it is not covered by core-js. + if (!window.Int32Array) { + window.Int32Array = Array; + } + + // ie11 polyfills + !(function () { + // append + function t() { + var e = Array.prototype.slice.call(arguments), + n = document.createDocumentFragment(); + e.forEach(function (e) { + var t = e instanceof Node; + n.appendChild(t ? e : document.createTextNode(String(e))); + }), + this.appendChild(n); + } + // remove + function n() { + this.parentNode && this.parentNode.removeChild(this); + } + + // add to prototype + [Element.prototype, Document.prototype, DocumentFragment.prototype].forEach(function (e) { + e.hasOwnProperty('append') || + Object.defineProperty(e, 'append', { + configurable: !0, + enumerable: !0, + writable: !0, + value: t, + }); + e.hasOwnProperty('remove') || + Object.defineProperty(e, 'remove', { + configurable: !0, + enumerable: !0, + writable: !0, + value: n, + }); + }); + })(); +})(); diff --git a/tgui/packages/tgui-polyfill/10-misc.js b/tgui/packages/tgui-polyfill/10-misc.js deleted file mode 100644 index 5a1849e13558a..0000000000000 --- a/tgui/packages/tgui-polyfill/10-misc.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -/* eslint-disable */ -(function () { - 'use strict'; - - // Necessary polyfill to make Webpack code splitting work on IE8 - if (!Function.prototype.bind) (function () { - var slice = Array.prototype.slice; - Function.prototype.bind = function () { - var thatFunc = this, thatArg = arguments[0]; - var args = slice.call(arguments, 1); - if (typeof thatFunc !== 'function') { - // closest thing possible to the ECMAScript 5 - // internal IsCallable function - throw new TypeError('Function.prototype.bind - ' + - 'what is trying to be bound is not callable'); - } - return function () { - var funcArgs = args.concat(slice.call(arguments)) - return thatFunc.apply(thatArg, funcArgs); - }; - }; - })(); - - if (!Array.prototype['forEach']) { - Array.prototype.forEach = function (callback, thisArg) { - if (this == null) { - throw new TypeError('Array.prototype.forEach called on null or undefined'); - } - var T, k; - var O = Object(this); - var len = O.length >>> 0; - if (typeof callback !== "function") { - throw new TypeError(callback + ' is not a function'); - } - if (arguments.length > 1) { - T = thisArg; - } - k = 0; - while (k < len) { - var kValue; - if (k in O) { - kValue = O[k]; - callback.call(T, kValue, k, O); - } - k++; - } - }; - } - - // Inferno needs Int32Array, and it is not covered by core-js. - if (!window.Int32Array) { - window.Int32Array = Array; - } - -})(); diff --git a/tgui/packages/tgui-polyfill/package.json b/tgui/packages/tgui-polyfill/package.json index 6f0080b617d23..12241f48a683a 100644 --- a/tgui/packages/tgui-polyfill/package.json +++ b/tgui/packages/tgui-polyfill/package.json @@ -1,16 +1,16 @@ { "private": true, "name": "tgui-polyfill", - "version": "4.3.0", + "version": "4.4.0", "scripts": { - "tgui-polyfill:build": "terser 00-html5shiv.js 01-ie8.js 02-dom4.js 03-css-om.js 10-misc.js --ie8 -f ascii_only,comments=false -o ../../public/tgui-polyfill.min.js" + "tgui-polyfill:build": "terser 1-misc.js -f ascii_only,comments=false -o ../../public/tgui-polyfill.min.js" }, "dependencies": { - "core-js": "^3.29.0", - "regenerator-runtime": "^0.13.11", + "core-js": "^3.39.0", + "regenerator-runtime": "^0.14.1", "unfetch": "^5.0.0" }, "devDependencies": { - "terser": "^5.16.5" + "terser": "^5.37.0" } } diff --git a/tgui/packages/tgui-say/package.json b/tgui/packages/tgui-say/package.json index 4038f13527f1e..4d76c98927d3f 100644 --- a/tgui/packages/tgui-say/package.json +++ b/tgui/packages/tgui-say/package.json @@ -5,7 +5,7 @@ "dependencies": { "common": "workspace:*", "dompurify": "^3.0.1", - "inferno": "^8.2.1", + "inferno": "^8.2.3", "tgui": "workspace:*", "tgui-dev-server": "workspace:*", "tgui-polyfill": "workspace:*" diff --git a/tgui/packages/tgui/assets.ts b/tgui/packages/tgui/assets.ts index 7ae36cb126e9f..df9cf7a428c3c 100644 --- a/tgui/packages/tgui/assets.ts +++ b/tgui/packages/tgui/assets.ts @@ -4,44 +4,41 @@ * @license MIT */ +import { Action, AnyAction, Middleware } from '../common/redux'; + +import { Dispatch } from 'common/redux'; + const EXCLUDED_PATTERNS = [/v4shim/i]; -export const loadedMappings = {}; +export const loadedMappings: Record = {}; -export const resolveAsset = (name) => loadedMappings[name] || name; +export const resolveAsset = (name: string): string => loadedMappings[name] || name; -type Action = - | { - type: 'asset/stylesheet'; - payload: string; +export const assetMiddleware: Middleware = + (storeApi) => + (next: Dispatch) => + (action: ActionType) => { + const { type, payload } = action as AnyAction; + if (type === 'asset/stylesheet') { + Byond.loadCss(payload); + return; } - | { - type: 'asset/mappings'; - payload: Record; - }; - -export const assetMiddleware = (store) => (next) => (action: Action) => { - const { type, payload } = action; - if (type === 'asset/stylesheet') { - Byond.loadCss(payload); - return; - } - if (type === 'asset/mappings') { - for (let name of Object.keys(payload)) { - // Skip anything that matches excluded patterns - if (EXCLUDED_PATTERNS.some((regex) => regex.test(name))) { - continue; - } - const url = payload[name]; - const ext = name.split('.').pop(); - loadedMappings[name] = url; - if (ext === 'css') { - Byond.loadCss(url); - } - if (ext === 'js') { - Byond.loadJs(url); + if (type === 'asset/mappings') { + for (const name of Object.keys(payload)) { + // Skip anything that matches excluded patterns + if (EXCLUDED_PATTERNS.some((regex) => regex.test(name))) { + continue; + } + const url = payload[name]; + const ext = name.split('.').pop(); + loadedMappings[name] = url; + if (ext === 'css') { + Byond.loadCss(url); + } + if (ext === 'js') { + Byond.loadJs(url); + } } + return; } - return; - } - next(action); -}; + next(action); + }; diff --git a/tgui/packages/tgui/components/AnimatedNumber.js b/tgui/packages/tgui/components/AnimatedNumber.jsx similarity index 100% rename from tgui/packages/tgui/components/AnimatedNumber.js rename to tgui/packages/tgui/components/AnimatedNumber.jsx diff --git a/tgui/packages/tgui/components/Blink.js b/tgui/packages/tgui/components/Blink.jsx similarity index 100% rename from tgui/packages/tgui/components/Blink.js rename to tgui/packages/tgui/components/Blink.jsx diff --git a/tgui/packages/tgui/components/BlockQuote.js b/tgui/packages/tgui/components/BlockQuote.jsx similarity index 100% rename from tgui/packages/tgui/components/BlockQuote.js rename to tgui/packages/tgui/components/BlockQuote.jsx diff --git a/tgui/packages/tgui/components/BufferedTextArea.tsx b/tgui/packages/tgui/components/BufferedTextArea.tsx index d51c6686f1402..8061c85b2b21e 100644 --- a/tgui/packages/tgui/components/BufferedTextArea.tsx +++ b/tgui/packages/tgui/components/BufferedTextArea.tsx @@ -18,7 +18,7 @@ interface BufferedTextAreaPropsUnique { type BufferedTextAreaProps = BufferedTextAreaPropsUnique & Record; export class BufferedTextArea extends Component { - bufferTimer: NodeJS.Timer; + bufferTimer: NodeJS.Timeout; state = { bufferedText: this.props.value || '', textChanged: false, diff --git a/tgui/packages/tgui/components/Button.js b/tgui/packages/tgui/components/Button.jsx similarity index 100% rename from tgui/packages/tgui/components/Button.js rename to tgui/packages/tgui/components/Button.jsx diff --git a/tgui/packages/tgui/components/ByondUi.js b/tgui/packages/tgui/components/ByondUi.jsx similarity index 100% rename from tgui/packages/tgui/components/ByondUi.js rename to tgui/packages/tgui/components/ByondUi.jsx diff --git a/tgui/packages/tgui/components/Chart.js b/tgui/packages/tgui/components/Chart.jsx similarity index 100% rename from tgui/packages/tgui/components/Chart.js rename to tgui/packages/tgui/components/Chart.jsx diff --git a/tgui/packages/tgui/components/Collapsible.js b/tgui/packages/tgui/components/Collapsible.jsx similarity index 100% rename from tgui/packages/tgui/components/Collapsible.js rename to tgui/packages/tgui/components/Collapsible.jsx diff --git a/tgui/packages/tgui/components/CollapsibleSection.js b/tgui/packages/tgui/components/CollapsibleSection.jsx similarity index 100% rename from tgui/packages/tgui/components/CollapsibleSection.js rename to tgui/packages/tgui/components/CollapsibleSection.jsx diff --git a/tgui/packages/tgui/components/ColorBox.js b/tgui/packages/tgui/components/ColorBox.jsx similarity index 100% rename from tgui/packages/tgui/components/ColorBox.js rename to tgui/packages/tgui/components/ColorBox.jsx diff --git a/tgui/packages/tgui/components/Dimmer.js b/tgui/packages/tgui/components/Dimmer.jsx similarity index 100% rename from tgui/packages/tgui/components/Dimmer.js rename to tgui/packages/tgui/components/Dimmer.jsx diff --git a/tgui/packages/tgui/components/Divider.js b/tgui/packages/tgui/components/Divider.jsx similarity index 100% rename from tgui/packages/tgui/components/Divider.js rename to tgui/packages/tgui/components/Divider.jsx diff --git a/tgui/packages/tgui/components/DraggableClickableControl.js b/tgui/packages/tgui/components/DraggableClickableControl.jsx similarity index 100% rename from tgui/packages/tgui/components/DraggableClickableControl.js rename to tgui/packages/tgui/components/DraggableClickableControl.jsx diff --git a/tgui/packages/tgui/components/DraggableControl.js b/tgui/packages/tgui/components/DraggableControl.jsx similarity index 100% rename from tgui/packages/tgui/components/DraggableControl.js rename to tgui/packages/tgui/components/DraggableControl.jsx diff --git a/tgui/packages/tgui/components/Dropdown.tsx b/tgui/packages/tgui/components/Dropdown.tsx index 8c2be0402882e..e618295f55917 100644 --- a/tgui/packages/tgui/components/Dropdown.tsx +++ b/tgui/packages/tgui/components/Dropdown.tsx @@ -74,24 +74,16 @@ export class Dropdown extends Component { getBoundingClientRect: () => Dropdown.currentOpenMenu?.getBoundingClientRect() ?? NULL_RECT, }; menuContents: any; - handleClick: any; state: DropdownState = { open: false, + selected: this.props.selected, }; - constructor(props) { - super(props); - - this.state = { - selected: props.selected, - open: props.open, - }; - this.handleClick = () => { - if (this.state.open) { - this.setOpen(false); - } - }; - } + handleClick = () => { + if (this.state.open) { + this.setOpen(false); + } + }; getDOMNode() { return findDOMFromVNode(this.$LI, true); @@ -179,7 +171,7 @@ export class Dropdown extends Component { return (
{ 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 ( +
+ + Value: {Number(progress).toFixed(1)} + + + + +
+ ); +}; 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