diff --git a/tgui/.eslintignore b/tgui/.eslintignore index 1e076d72e0..d3c0ac79cd 100644 --- a/tgui/.eslintignore +++ b/tgui/.eslintignore @@ -14,6 +14,3 @@ **.woff2 **.eot **.ttf - -# CHOMPEdit - Until removed -/packages/tgui_ch/** diff --git a/tgui/.prettierignore b/tgui/.prettierignore index 73259d8a07..c517b12e2e 100644 --- a/tgui/.prettierignore +++ b/tgui/.prettierignore @@ -18,6 +18,3 @@ ## Build artifacts /public/.tmp/**/* /public/*.map - -## CHOMPEdit - Until removed -/packages/tgui_ch diff --git a/tgui/packages/tgui_ch/assets.ts b/tgui/packages/tgui_ch/assets.ts deleted file mode 100644 index e519d0c2f7..0000000000 --- a/tgui/packages/tgui_ch/assets.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { Action, AnyAction, Middleware } from '../common/redux'; - -import { Dispatch } from 'common/redux'; - -const EXCLUDED_PATTERNS = [/v4shim/i]; -const loadedMappings: Record = {}; - -export const resolveAsset = (name: string): string => - loadedMappings[name] || name; - -export const assetMiddleware: Middleware = - (storeApi) => - (next: Dispatch) => - (action: ActionType) => { - const { type, payload } = action as AnyAction; - if (type === 'asset/stylesheet') { - Byond.loadCss(payload); - return; - } - 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; - } - next(action); - }; diff --git a/tgui/packages/tgui_ch/assets/bg-nanotrasen.svg b/tgui/packages/tgui_ch/assets/bg-nanotrasen.svg deleted file mode 100644 index d21b9f0a2a..0000000000 --- a/tgui/packages/tgui_ch/assets/bg-nanotrasen.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/tgui/packages/tgui_ch/assets/bg-neutral.svg b/tgui/packages/tgui_ch/assets/bg-neutral.svg deleted file mode 100644 index e380def535..0000000000 --- a/tgui/packages/tgui_ch/assets/bg-neutral.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/tgui/packages/tgui_ch/assets/bg-syndicate.svg b/tgui/packages/tgui_ch/assets/bg-syndicate.svg deleted file mode 100644 index c2863b790d..0000000000 --- a/tgui/packages/tgui_ch/assets/bg-syndicate.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tgui/packages/tgui_ch/assets/bg-wizard.svg b/tgui/packages/tgui_ch/assets/bg-wizard.svg deleted file mode 100644 index 7b7c728a0c..0000000000 --- a/tgui/packages/tgui_ch/assets/bg-wizard.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/tgui/packages/tgui_ch/backend.ts b/tgui/packages/tgui_ch/backend.ts deleted file mode 100644 index 016ae7a0a0..0000000000 --- a/tgui/packages/tgui_ch/backend.ts +++ /dev/null @@ -1,373 +0,0 @@ -/** - * This file provides a clear separation layer between backend updates - * and what state our React app sees. - * - * Sometimes backend can response without a "data" field, but our final - * state will still contain previous "data" because we are merging - * the response with already existing state. - * - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { perf } from 'common/perf'; -import { createAction } from 'common/redux'; -import { setupDrag } from './drag'; -import { globalEvents } from './events'; -import { focusMap } from './focus'; -import { createLogger } from './logging'; -import { resumeRenderer, suspendRenderer } from './renderer'; - -const logger = createLogger('backend'); - -export const backendUpdate = createAction('backend/update'); -export const backendSetSharedState = createAction('backend/setSharedState'); -export const backendSuspendStart = createAction('backend/suspendStart'); - -export const backendSuspendSuccess = () => ({ - type: 'backend/suspendSuccess', - payload: { - timestamp: Date.now(), - }, -}); - -const initialState = { - config: {}, - data: {}, - shared: {}, - // Start as suspended - suspended: Date.now(), - suspending: false, -}; - -export const backendReducer = (state = initialState, action) => { - const { type, payload } = action; - - if (type === 'backend/update') { - // Merge config - const config = { - ...state.config, - ...payload.config, - }; - // Merge data - const data = { - ...state.data, - ...payload.static_data, - ...payload.data, - }; - // Merge shared states - const shared = { ...state.shared }; - if (payload.shared) { - for (let key of Object.keys(payload.shared)) { - const value = payload.shared[key]; - if (value === '') { - shared[key] = undefined; - } else { - shared[key] = JSON.parse(value); - } - } - } - // Return new state - return { - ...state, - config, - data, - shared, - suspended: false, - }; - } - - if (type === 'backend/setSharedState') { - const { key, nextState } = payload; - return { - ...state, - shared: { - ...state.shared, - [key]: nextState, - }, - }; - } - - if (type === 'byond/ctrldown') { - globalEvents.emit('byond/ctrldown'); - } - - if (type === 'byond/ctrlup') { - globalEvents.emit('byond/ctrlup'); - } - - if (type === 'backend/suspendStart') { - return { - ...state, - suspending: true, - }; - } - - if (type === 'backend/suspendSuccess') { - const { timestamp } = payload; - return { - ...state, - data: {}, - shared: {}, - config: { - ...state.config, - title: '', - status: 1, - }, - suspending: false, - suspended: timestamp, - }; - } - - return state; -}; - -export const backendMiddleware = (store) => { - let fancyState; - let suspendInterval; - - return (next) => (action) => { - const { suspended } = selectBackend(store.getState()); - const { type, payload } = action; - - if (type === 'update') { - store.dispatch(backendUpdate(payload)); - return; - } - - if (type === 'suspend') { - store.dispatch(backendSuspendSuccess()); - return; - } - - if (type === 'ping') { - Byond.sendMessage('ping/reply'); - return; - } - - if (type === 'byond/mousedown') { - globalEvents.emit('byond/mousedown'); - } - - if (type === 'byond/mouseup') { - globalEvents.emit('byond/mouseup'); - } - - if (type === 'backend/suspendStart' && !suspendInterval) { - logger.log(`suspending (${Byond.windowId})`); - // Keep sending suspend messages until it succeeds. - // It may fail multiple times due to topic rate limiting. - const suspendFn = () => Byond.sendMessage('suspend'); - suspendFn(); - suspendInterval = setInterval(suspendFn, 2000); - } - - if (type === 'backend/suspendSuccess') { - suspendRenderer(); - clearInterval(suspendInterval); - suspendInterval = undefined; - Byond.winset(Byond.windowId, { - 'is-visible': false, - }); - setImmediate(() => focusMap()); - } - - if (type === 'backend/update') { - const fancy = payload.config?.window?.fancy; - // Initialize fancy state - if (fancyState === undefined) { - fancyState = fancy; - } - // React to changes in fancy - else if (fancyState !== fancy) { - logger.log('changing fancy mode to', fancy); - fancyState = fancy; - Byond.winset(Byond.windowId, { - titlebar: !fancy, - 'can-resize': !fancy, - }); - } - } - - // Resume on incoming update - if (type === 'backend/update' && suspended) { - // Show the payload - logger.log('backend/update', payload); - // Signal renderer that we have resumed - resumeRenderer(); - // Setup drag - setupDrag(); - // We schedule this for the next tick here because resizing and unhiding - // during the same tick will flash with a white background. - setImmediate(() => { - perf.mark('resume/start'); - // Doublecheck if we are not re-suspended. - const { suspended } = selectBackend(store.getState()); - if (suspended) { - return; - } - Byond.winset(Byond.windowId, { - 'is-visible': true, - }); - perf.mark('resume/finish'); - if (process.env.NODE_ENV !== 'production') { - logger.log( - 'visible in', - perf.measure('render/finish', 'resume/finish') - ); - } - }); - } - - return next(action); - }; -}; - -/** - * Sends an action to `ui_act` on `src_object` that this tgui window - * is associated with. - */ -export const sendAct = (action: string, payload: object = {}) => { - // Validate that payload is an object - // prettier-ignore - const isObject = typeof payload === 'object' - && payload !== null - && !Array.isArray(payload); - if (!isObject) { - logger.error(`Payload for act() must be an object, got this:`, payload); - return; - } - Byond.sendMessage('act/' + action, payload); -}; - -type BackendState = { - config: { - title: string; - status: number; - interface: string; - refreshing: boolean; - window: { - key: string; - size: [number, number]; - fancy: boolean; - locked: boolean; - }; - client: { - ckey: string; - address: string; - computer_id: string; - }; - user: { - name: string; - observer: number; - }; - }; - data: TData; - shared: Record; - suspending: boolean; - suspended: boolean; -}; - -/** - * Selects a backend-related slice of Redux state - */ -export const selectBackend = (state: any): BackendState => - state.backend || {}; - -/** - * Get data from tgui backend. - * - * Includes the `act` function for performing DM actions. - */ -export const useBackend = (context: any) => { - const { store } = context; - const state = selectBackend(store.getState()); - return { - ...state, - act: sendAct, - }; -}; - -/** - * A tuple that contains the state and a setter function for it. - */ -type StateWithSetter = [T, (nextState: T) => void]; - -/** - * Allocates state on Redux store without sharing it with other clients. - * - * Use it when you want to have a stateful variable in your component - * that persists between renders, but will be forgotten after you close - * the UI. - * - * It is a lot more performant than `setSharedState`. - * - * @param context React context. - * @param key Key which uniquely identifies this state in Redux store. - * @param initialState Initializes your global variable with this value. - */ -export const useLocalState = ( - context: any, - key: string, - initialState: T -): StateWithSetter => { - const { store } = context; - const state = selectBackend(store.getState()); - const sharedStates = state.shared ?? {}; - const sharedState = key in sharedStates ? sharedStates[key] : initialState; - return [ - sharedState, - (nextState) => { - store.dispatch( - backendSetSharedState({ - key, - nextState: - typeof nextState === 'function' - ? nextState(sharedState) - : nextState, - }) - ); - }, - ]; -}; - -/** - * Allocates state on Redux store, and **shares** it with other clients - * in the game. - * - * Use it when you want to have a stateful variable in your component - * that persists not only between renders, but also gets pushed to other - * clients that observe this UI. - * - * This makes creation of observable s - * - * @param context React context. - * @param key Key which uniquely identifies this state in Redux store. - * @param initialState Initializes your global variable with this value. - */ -export const useSharedState = ( - context: any, - key: string, - initialState: T -): StateWithSetter => { - const { store } = context; - const state = selectBackend(store.getState()); - const sharedStates = state.shared ?? {}; - const sharedState = key in sharedStates ? sharedStates[key] : initialState; - return [ - sharedState, - (nextState) => { - // prettier-ignore - Byond.sendMessage({ - type: 'setSharedState', - key, - value: JSON.stringify( - typeof nextState === 'function' - ? nextState(sharedState) - : nextState - ) || '', - }); - }, - ]; -}; diff --git a/tgui/packages/tgui_ch/components/AnimatedNumber.tsx b/tgui/packages/tgui_ch/components/AnimatedNumber.tsx deleted file mode 100644 index 53bdba90ab..0000000000 --- a/tgui/packages/tgui_ch/components/AnimatedNumber.tsx +++ /dev/null @@ -1,189 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { clamp, toFixed } from 'common/math'; -import { Component, createRef } from 'inferno'; - -const isSafeNumber = (value: number) => { - // prettier-ignore - return typeof value === 'number' - && Number.isFinite(value) - && !Number.isNaN(value); -}; - -export type AnimatedNumberProps = { - /** - * The target value to approach. - */ - value: number; - - /** - * If provided, the initial value displayed. By default, the same as `value`. - * If `initial` and `value` are different, the component immediately starts - * animating. - */ - initial?: number; - - /** - * If provided, a function that formats the inner string. By default, - * attempts to match the numeric precision of `value`. - */ - format?: (value: number) => string; -}; - -/** - * Animated numbers are animated at roughly 60 frames per second. - */ -const SIXTY_HZ = 1_000.0 / 60.0; - -/** - * The exponential moving average coefficient. Larger values result in a faster - * convergence. - */ -const Q = 0.8333; - -/** - * A small number. - */ -const EPSILON = 10e-4; - -/** - * An animated number label. Shows a number, formatted with an optionally - * provided function, and animates it towards its target value. - */ -export class AnimatedNumber extends Component { - /** - * The inner `` being updated sixty times per second. - */ - ref = createRef(); - - /** - * The interval being used to update the inner span. - */ - interval?: NodeJS.Timeout; - - /** - * The current value. This values approaches the target value. - */ - currentValue: number = 0; - - constructor(props: AnimatedNumberProps) { - super(props); - - const { initial, value } = props; - - if (initial !== undefined && isSafeNumber(initial)) { - this.currentValue = initial; - } else if (isSafeNumber(value)) { - this.currentValue = value; - } - } - - componentDidMount() { - if (this.currentValue !== this.props.value) { - this.startTicking(); - } - } - - componentWillUnmount() { - // Stop animating when the component is unmounted. - this.stopTicking(); - } - - shouldComponentUpdate(newProps: AnimatedNumberProps) { - if (newProps.value !== this.props.value) { - // The target value has been adjusted; start animating if we aren't - // already. - this.startTicking(); - } - - // We render the inner `span` directly using a ref to bypass inferno diffing - // and reach 60 frames per second--tell inferno not to re-render this tree. - return false; - } - - /** - * Starts animating the inner span. If the inner span is already animating, - * this is a no-op. - */ - startTicking() { - if (this.interval !== undefined) { - // We're already ticking; do nothing. - return; - } - - this.interval = setInterval(() => this.tick(), SIXTY_HZ); - } - - /** - * Stops animating the inner span. - */ - stopTicking() { - if (this.interval === undefined) { - // We're not ticking; do nothing. - return; - } - - clearInterval(this.interval); - - this.interval = undefined; - } - - /** - * Steps forward one frame. - */ - tick() { - const { currentValue } = this; - const { value } = this.props; - - if (isSafeNumber(value)) { - // Converge towards the value. - this.currentValue = currentValue * Q + value * (1 - Q); - } else { - // If the value is unsafe, we're never going to converge, so stop ticking. - this.stopTicking(); - } - - if ( - Math.abs(value - this.currentValue) < Math.max(EPSILON, EPSILON * value) - ) { - // We're about as close as we're going to get--snap to the value and - // stop ticking. - this.currentValue = value; - this.stopTicking(); - } - - if (this.ref.current) { - // Directly update the inner span, without bothering inferno. - this.ref.current.textContent = this.getText(); - } - } - - /** - * Gets the inner text of the span. - */ - getText() { - const { props, currentValue } = this; - const { format, value } = props; - - if (!isSafeNumber(value)) { - return String(value); - } - - if (format) { - return format(this.currentValue); - } - - const fraction = String(value).split('.')[1]; - const precision = fraction ? fraction.length : 0; - - return toFixed(currentValue, clamp(precision, 0, 8)); - } - - render() { - return {this.getText()}; - } -} diff --git a/tgui/packages/tgui_ch/components/Autofocus.tsx b/tgui/packages/tgui_ch/components/Autofocus.tsx deleted file mode 100644 index 28945dd7aa..0000000000 --- a/tgui/packages/tgui_ch/components/Autofocus.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Component, createRef } from 'inferno'; - -export class Autofocus extends Component { - ref = createRef(); - - componentDidMount() { - setTimeout(() => { - this.ref.current?.focus(); - }, 1); - } - - render() { - return ( -
- {this.props.children} -
- ); - } -} diff --git a/tgui/packages/tgui_ch/components/Blink.jsx b/tgui/packages/tgui_ch/components/Blink.jsx deleted file mode 100644 index bd781336b4..0000000000 --- a/tgui/packages/tgui_ch/components/Blink.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Component } from 'inferno'; - -const DEFAULT_BLINKING_INTERVAL = 1000; -const DEFAULT_BLINKING_TIME = 1000; - -export class Blink extends Component { - constructor() { - super(); - this.state = { - hidden: false, - }; - } - - createTimer() { - const { - interval = DEFAULT_BLINKING_INTERVAL, - time = DEFAULT_BLINKING_TIME, - } = this.props; - - clearInterval(this.interval); - clearTimeout(this.timer); - - this.setState({ - hidden: false, - }); - - this.interval = setInterval(() => { - this.setState({ - hidden: true, - }); - - this.timer = setTimeout(() => { - this.setState({ - hidden: false, - }); - }, time); - }, interval + time); - } - - componentDidMount() { - this.createTimer(); - } - - componentDidUpdate(prevProps) { - if ( - prevProps.interval !== this.props.interval || - prevProps.time !== this.props.time - ) { - this.createTimer(); - } - } - - componentWillUnmount() { - clearInterval(this.interval); - clearTimeout(this.timer); - } - - render(props) { - return ( - - {props.children} - - ); - } -} diff --git a/tgui/packages/tgui_ch/components/BlockQuote.jsx b/tgui/packages/tgui_ch/components/BlockQuote.jsx deleted file mode 100644 index 8c0f53a46a..0000000000 --- a/tgui/packages/tgui_ch/components/BlockQuote.jsx +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { classes } from 'common/react'; -import { Box } from './Box'; - -export const BlockQuote = (props) => { - const { className, ...rest } = props; - return ; -}; diff --git a/tgui/packages/tgui_ch/components/BodyZoneSelector.tsx b/tgui/packages/tgui_ch/components/BodyZoneSelector.tsx deleted file mode 100644 index cf8dc430e2..0000000000 --- a/tgui/packages/tgui_ch/components/BodyZoneSelector.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { Component, createRef } from 'inferno'; -import { resolveAsset } from '../assets'; -import { Box } from './Box'; - -export enum BodyZone { - Head = 'head', - Chest = 'chest', - LeftArm = 'l_arm', - RightArm = 'r_arm', - LeftLeg = 'l_leg', - RightLeg = 'r_leg', - Eyes = 'eyes', - Mouth = 'mouth', - Groin = 'groin', -} - -const bodyZonePixelToZone = (x: number, y: number): BodyZone | null => { - // TypeScript translation of /atom/movable/screen/zone_sel/proc/get_zone_at - if (y < 1) { - return null; - } else if (y < 10) { - if (x > 10 && x < 15) { - return BodyZone.RightLeg; - } else if (x > 17 && x < 22) { - return BodyZone.LeftLeg; - } - } else if (y < 13) { - if (x > 8 && x < 11) { - return BodyZone.RightArm; - } else if (x > 12 && x < 20) { - return BodyZone.Groin; - } else if (x > 21 && x < 24) { - return BodyZone.LeftArm; - } - } else if (y < 22) { - if (x > 8 && x < 11) { - return BodyZone.RightArm; - } else if (x > 12 && x < 20) { - return BodyZone.Chest; - } else if (x > 21 && x < 24) { - return BodyZone.LeftArm; - } - } else if (y < 30 && x > 12 && x < 20) { - if (y > 23 && y < 24 && x > 15 && x < 17) { - return BodyZone.Mouth; - } else if (y > 25 && y < 27 && x > 14 && x < 18) { - return BodyZone.Eyes; - } else { - return BodyZone.Head; - } - } - - return null; -}; - -type BodyZoneSelectorProps = { - onClick?: (zone: BodyZone) => void; - scale?: number; - selectedZone: BodyZone | null; - theme?: string; -}; - -type BodyZoneSelectorState = { - hoverZone: BodyZone | null; -}; - -export class BodyZoneSelector extends Component< - BodyZoneSelectorProps, - BodyZoneSelectorState -> { - ref = createRef(); - state: BodyZoneSelectorState = { - hoverZone: null, - }; - - render() { - const { hoverZone } = this.state; - const { scale = 3, selectedZone, theme = 'midnight' } = this.props; - - return ( -
- { - const onClick = this.props.onClick; - if (onClick && this.state.hoverZone) { - onClick(this.state.hoverZone); - } - }} - onMouseMove={(event) => { - if (!this.props.onClick) { - return; - } - - const rect = this.ref.current?.getBoundingClientRect(); - if (!rect) { - return; - } - - const x = event.clientX - rect.left; - const y = 32 * scale - (event.clientY - rect.top); - - this.setState({ - hoverZone: bodyZonePixelToZone(x / scale, y / scale), - }); - }} - style={{ - '-ms-interpolation-mode': 'nearest-neighbor', - 'position': 'absolute', - 'width': `${32 * scale}px`, - 'height': `${32 * scale}px`, - }} - /> - - {selectedZone && ( - - )} - - {hoverZone && hoverZone !== selectedZone && ( - - )} -
- ); - } -} diff --git a/tgui/packages/tgui_ch/components/Box.tsx b/tgui/packages/tgui_ch/components/Box.tsx deleted file mode 100644 index 96244d5bd7..0000000000 --- a/tgui/packages/tgui_ch/components/Box.tsx +++ /dev/null @@ -1,294 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; -import { createVNode, InfernoNode, SFC } from 'inferno'; -import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; -import { CSS_COLORS } from '../constants'; - -export type BoxProps = { - [key: string]: any; - as?: string; - className?: string | BooleanLike; - children?: InfernoNode; - position?: string | BooleanLike; - overflow?: string | BooleanLike; - overflowX?: string | BooleanLike; - overflowY?: string | BooleanLike; - top?: string | BooleanLike; - bottom?: string | BooleanLike; - left?: string | BooleanLike; - right?: string | BooleanLike; - width?: string | BooleanLike; - minWidth?: string | BooleanLike; - maxWidth?: string | BooleanLike; - height?: string | BooleanLike; - minHeight?: string | BooleanLike; - maxHeight?: string | BooleanLike; - fontSize?: string | BooleanLike; - fontFamily?: string; - lineHeight?: string | BooleanLike; - opacity?: number; - textAlign?: string | BooleanLike; - verticalAlign?: string | BooleanLike; - textTransform?: string | BooleanLike; // VOREStation Addition - inline?: BooleanLike; - bold?: BooleanLike; - italic?: BooleanLike; - nowrap?: BooleanLike; - preserveWhitespace?: BooleanLike; - m?: string | BooleanLike; - mx?: string | BooleanLike; - my?: string | BooleanLike; - mt?: string | BooleanLike; - mb?: string | BooleanLike; - ml?: string | BooleanLike; - mr?: string | BooleanLike; - p?: string | BooleanLike; - px?: string | BooleanLike; - py?: string | BooleanLike; - pt?: string | BooleanLike; - pb?: string | BooleanLike; - pl?: string | BooleanLike; - pr?: string | BooleanLike; - color?: string | BooleanLike; - textColor?: string | BooleanLike; - backgroundColor?: string | BooleanLike; - // VOREStation Addition Start - // Flex props - flexGrow?: string | BooleanLike; - flexWrap?: string | BooleanLike; - flexBasis?: string | BooleanLike; - flex?: string | BooleanLike; - // VOREStation Addition End - fillPositionedParent?: boolean; -}; - -/** - * Coverts our rem-like spacing unit into a CSS unit. - */ -export const unit = (value: unknown): string | undefined => { - if (typeof value === 'string') { - // Transparently convert pixels into rem units - if (value.endsWith('px') && !Byond.IS_LTE_IE8) { - return parseFloat(value) / 12 + 'rem'; - } - return value; - } - if (typeof value === 'number') { - if (Byond.IS_LTE_IE8) { - return value * 12 + 'px'; - } - return value + 'rem'; - } -}; - -/** - * Same as `unit`, but half the size for integers numbers. - */ -export const halfUnit = (value: unknown): string | undefined => { - if (typeof value === 'string') { - return unit(value); - } - if (typeof value === 'number') { - return unit(value * 0.5); - } -}; - -const isColorCode = (str: unknown) => !isColorClass(str); - -const isColorClass = (str: unknown): boolean => { - return typeof str === 'string' && CSS_COLORS.includes(str); -}; - -const mapRawPropTo = (attrName) => (style, value) => { - if (typeof value === 'number' || typeof value === 'string') { - style[attrName] = value; - } -}; - -const mapUnitPropTo = (attrName, unit) => (style, value) => { - if (typeof value === 'number' || typeof value === 'string') { - style[attrName] = unit(value); - } -}; - -const mapBooleanPropTo = (attrName, attrValue) => (style, value) => { - if (value) { - style[attrName] = attrValue; - } -}; - -const mapDirectionalUnitPropTo = (attrName, unit, dirs) => (style, value) => { - if (typeof value === 'number' || typeof value === 'string') { - for (let i = 0; i < dirs.length; i++) { - style[attrName + '-' + dirs[i]] = unit(value); - } - } -}; - -const mapColorPropTo = (attrName) => (style, value) => { - if (isColorCode(value)) { - style[attrName] = value; - } -}; - -const styleMapperByPropName = { - // Direct mapping - position: mapRawPropTo('position'), - overflow: mapRawPropTo('overflow'), - overflowX: mapRawPropTo('overflow-x'), - overflowY: mapRawPropTo('overflow-y'), - top: mapUnitPropTo('top', unit), - bottom: mapUnitPropTo('bottom', unit), - left: mapUnitPropTo('left', unit), - right: mapUnitPropTo('right', unit), - width: mapUnitPropTo('width', unit), - minWidth: mapUnitPropTo('min-width', unit), - maxWidth: mapUnitPropTo('max-width', unit), - height: mapUnitPropTo('height', unit), - minHeight: mapUnitPropTo('min-height', unit), - maxHeight: mapUnitPropTo('max-height', unit), - fontSize: mapUnitPropTo('font-size', unit), - fontFamily: mapRawPropTo('font-family'), - lineHeight: (style, value) => { - if (typeof value === 'number') { - style['line-height'] = value; - } else if (typeof value === 'string') { - style['line-height'] = unit(value); - } - }, - opacity: mapRawPropTo('opacity'), - textAlign: mapRawPropTo('text-align'), - verticalAlign: mapRawPropTo('vertical-align'), - textTransform: mapRawPropTo('text-transform'), // VOREStation Addition - // Boolean props - inline: mapBooleanPropTo('display', 'inline-block'), - bold: mapBooleanPropTo('font-weight', 'bold'), - italic: mapBooleanPropTo('font-style', 'italic'), - nowrap: mapBooleanPropTo('white-space', 'nowrap'), - preserveWhitespace: mapBooleanPropTo('white-space', 'pre-wrap'), - // Margins - m: mapDirectionalUnitPropTo('margin', halfUnit, [ - 'top', - 'bottom', - 'left', - 'right', - ]), - mx: mapDirectionalUnitPropTo('margin', halfUnit, ['left', 'right']), - my: mapDirectionalUnitPropTo('margin', halfUnit, ['top', 'bottom']), - mt: mapUnitPropTo('margin-top', halfUnit), - mb: mapUnitPropTo('margin-bottom', halfUnit), - ml: mapUnitPropTo('margin-left', halfUnit), - mr: mapUnitPropTo('margin-right', halfUnit), - // Margins - p: mapDirectionalUnitPropTo('padding', halfUnit, [ - 'top', - 'bottom', - 'left', - 'right', - ]), - px: mapDirectionalUnitPropTo('padding', halfUnit, ['left', 'right']), - py: mapDirectionalUnitPropTo('padding', halfUnit, ['top', 'bottom']), - pt: mapUnitPropTo('padding-top', halfUnit), - pb: mapUnitPropTo('padding-bottom', halfUnit), - pl: mapUnitPropTo('padding-left', halfUnit), - pr: mapUnitPropTo('padding-right', halfUnit), - // Color props - color: mapColorPropTo('color'), - textColor: mapColorPropTo('color'), - backgroundColor: mapColorPropTo('background-color'), - // VOREStation Addition Start - // Flex props - flexGrow: mapRawPropTo('flex-grow'), - flexWrap: mapRawPropTo('flex-wrap'), - flexBasis: mapRawPropTo('flex-basis'), - flex: mapRawPropTo('flex'), - // VOREStation Addition End - // Utility props - fillPositionedParent: (style, value) => { - if (value) { - style['position'] = 'absolute'; - style['top'] = 0; - style['bottom'] = 0; - style['left'] = 0; - style['right'] = 0; - } - }, -}; - -export const computeBoxProps = (props: BoxProps) => { - const computedProps: HTMLAttributes = {}; - const computedStyles = {}; - // Compute props - for (let propName of Object.keys(props)) { - if (propName === 'style') { - continue; - } - // IE8: onclick workaround - if (Byond.IS_LTE_IE8 && propName === 'onClick') { - computedProps.onclick = props[propName]; - continue; - } - const propValue = props[propName]; - const mapPropToStyle = styleMapperByPropName[propName]; - if (mapPropToStyle) { - mapPropToStyle(computedStyles, propValue); - } else { - computedProps[propName] = propValue; - } - } - // Concatenate styles - let style = ''; - for (let attrName of Object.keys(computedStyles)) { - const attrValue = computedStyles[attrName]; - style += attrName + ':' + attrValue + ';'; - } - if (props.style) { - for (let attrName of Object.keys(props.style)) { - const attrValue = props.style[attrName]; - style += attrName + ':' + attrValue + ';'; - } - } - if (style.length > 0) { - computedProps.style = style; - } - return computedProps; -}; - -export const computeBoxClassName = (props: BoxProps) => { - const color = props.textColor || props.color; - const backgroundColor = props.backgroundColor; - return classes([ - isColorClass(color) && 'color-' + color, - isColorClass(backgroundColor) && 'color-bg-' + backgroundColor, - ]); -}; - -export const Box: SFC = (props: BoxProps) => { - const { as = 'div', className, children, ...rest } = props; - // Render props - if (typeof children === 'function') { - return children(computeBoxProps(props)); - } - const computedClassName = - typeof className === 'string' - ? className + ' ' + computeBoxClassName(rest) - : computeBoxClassName(rest); - const computedProps = computeBoxProps(rest); - // Render a wrapper element - return createVNode( - VNodeFlags.HtmlElement, - as, - computedClassName, - children, - ChildFlags.UnknownChildren, - computedProps, - undefined - ); -}; - -Box.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui_ch/components/Button.jsx b/tgui/packages/tgui_ch/components/Button.jsx deleted file mode 100644 index 58d879d228..0000000000 --- a/tgui/packages/tgui_ch/components/Button.jsx +++ /dev/null @@ -1,365 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from 'common/keycodes'; -import { classes, pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; -import { createLogger } from '../logging'; -import { Box, computeBoxClassName, computeBoxProps } from './Box'; -import { Icon } from './Icon'; -import { Tooltip } from './Tooltip'; - -const logger = createLogger('Button'); - -export const Button = (props) => { - const { - className, - fluid, - icon, - iconRotation, - iconSpin, - iconColor, - iconPosition, - iconSize, // VOREStation Addition - color, - disabled, - selected, - tooltip, - tooltipPosition, - ellipsis, - compact, - circular, - content, - children, - onclick, - onClick, - verticalAlignContent, - ...rest - } = props; - const hasContent = !!(content || children); - // A warning about the lowercase onclick - if (onclick) { - logger.warn( - `Lowercase 'onclick' is not supported on Button and lowercase` + - ` prop names are discouraged in general. Please use a camelCase` + - `'onClick' instead and read: ` + - `https://infernojs.org/docs/guides/event-handling` - ); - } - rest.onClick = (e) => { - if (!disabled && onClick) { - onClick(e); - } - }; - // IE8: Use "unselectable" because "user-select" doesn't work. - if (Byond.IS_LTE_IE8) { - rest.unselectable = true; - } - let buttonContent = ( -
{ - if (props.captureKeys === false) { - return; - } - const keyCode = window.event ? e.which : e.keyCode; - // Simulate a click when pressing space or enter. - if (keyCode === KEY_SPACE || keyCode === KEY_ENTER) { - e.preventDefault(); - if (!disabled && onClick) { - onClick(e); - } - return; - } - // Refocus layout on pressing escape. - if (keyCode === KEY_ESCAPE) { - e.preventDefault(); - return; - } - }} - {...computeBoxProps(rest)}> -
- {icon && iconPosition !== 'right' && ( - - )} - {content} - {children} - {icon && iconPosition === 'right' && ( - - )} -
-
- ); - - if (tooltip) { - buttonContent = ( - - {buttonContent} - - ); - } - - return buttonContent; -}; - -Button.defaultHooks = pureComponentHooks; - -export const ButtonCheckbox = (props) => { - const { checked, ...rest } = props; - return ( - - - {buttons && ( -
{buttons}
- )} - - {open && {children}} -
- ); - } -} diff --git a/tgui/packages/tgui_ch/components/ColorBox.jsx b/tgui/packages/tgui_ch/components/ColorBox.jsx deleted file mode 100644 index a6203ca469..0000000000 --- a/tgui/packages/tgui_ch/components/ColorBox.jsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { classes, pureComponentHooks } from 'common/react'; -import { computeBoxClassName, computeBoxProps } from './Box'; - -export const ColorBox = (props) => { - // prettier-ignore - const { - content, - children, - className, - color, - backgroundColor, - ...rest - } = props; - rest.color = content ? null : 'transparent'; - rest.backgroundColor = color || backgroundColor; - return ( -
- {content || '.'} -
- ); -}; - -ColorBox.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui_ch/components/Dialog.tsx b/tgui/packages/tgui_ch/components/Dialog.tsx deleted file mode 100644 index 277a03b284..0000000000 --- a/tgui/packages/tgui_ch/components/Dialog.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @file - * @copyright 2022 raffclar - * @license MIT - */ -import { Box } from './Box'; -import { Button } from './Button'; - -type DialogProps = { - title: any; - onClose: () => void; - children: any; - width?: string; - height?: string; -}; - -export const Dialog = (props: DialogProps) => { - const { title, onClose, children, width, height } = props; - return ( -
- -
-
{title}
- -
- {children} -
-
- ); -}; - -type DialogButtonProps = { - onClick: () => void; - children: any; -}; - -const DialogButton = (props: DialogButtonProps) => { - const { onClick, children } = props; - return ( - - ); -}; - -Dialog.Button = DialogButton; - -type UnsavedChangesDialogProps = { - documentName: string; - onSave: () => void; - onDiscard: () => void; - onClose: () => void; -}; - -export const UnsavedChangesDialog = (props: UnsavedChangesDialogProps) => { - const { documentName, onSave, onDiscard, onClose } = props; - return ( - -
- Do you want to save changes to {documentName}? -
-
- Save - Don't Save - Cancel -
-
- ); -}; diff --git a/tgui/packages/tgui_ch/components/Dimmer.jsx b/tgui/packages/tgui_ch/components/Dimmer.jsx deleted file mode 100644 index 85e046ca0d..0000000000 --- a/tgui/packages/tgui_ch/components/Dimmer.jsx +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { classes } from 'common/react'; -import { Box } from './Box'; - -export const Dimmer = (props) => { - const { className, children, ...rest } = props; - return ( - -
{children}
-
- ); -}; diff --git a/tgui/packages/tgui_ch/components/Divider.jsx b/tgui/packages/tgui_ch/components/Divider.jsx deleted file mode 100644 index 8cbf9b77d7..0000000000 --- a/tgui/packages/tgui_ch/components/Divider.jsx +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { classes } from 'common/react'; - -export const Divider = (props) => { - const { vertical, hidden } = props; - return ( - - ); - } -} diff --git a/tgui/packages/tgui_ch/components/Input.jsx b/tgui/packages/tgui_ch/components/Input.jsx deleted file mode 100644 index ac7ce6eef3..0000000000 --- a/tgui/packages/tgui_ch/components/Input.jsx +++ /dev/null @@ -1,156 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { KEY_ENTER, KEY_ESCAPE } from 'common/keycodes'; -import { classes } from 'common/react'; -import { Component, createRef } from 'inferno'; -import { Box } from './Box'; - -// prettier-ignore -export const toInputValue = value => ( - typeof value !== 'number' && typeof value !== 'string' - ? '' - : String(value) -); - -export class Input extends Component { - constructor() { - super(); - this.inputRef = createRef(); - this.state = { - editing: false, - }; - this.handleInput = (e) => { - const { editing } = this.state; - const { onInput } = this.props; - if (!editing) { - this.setEditing(true); - } - if (onInput) { - onInput(e, e.target.value); - } - }; - this.handleFocus = (e) => { - const { editing } = this.state; - if (!editing) { - this.setEditing(true); - } - }; - this.handleBlur = (e) => { - const { editing } = this.state; - const { onChange } = this.props; - if (editing) { - this.setEditing(false); - if (onChange) { - onChange(e, e.target.value); - } - } - }; - this.handleKeyDown = (e) => { - const { onInput, onChange, onEnter } = this.props; - if (e.keyCode === KEY_ENTER) { - this.setEditing(false); - if (onChange) { - onChange(e, e.target.value); - } - if (onInput) { - onInput(e, e.target.value); - } - if (onEnter) { - onEnter(e, e.target.value); - } - if (this.props.selfClear) { - e.target.value = ''; - } else { - e.target.blur(); - } - return; - } - if (e.keyCode === KEY_ESCAPE) { - if (this.props.onEscape) { - this.props.onEscape(e); - return; - } - - this.setEditing(false); - e.target.value = toInputValue(this.props.value); - e.target.blur(); - return; - } - }; - } - - componentDidMount() { - const nextValue = this.props.value; - const input = this.inputRef.current; - if (input) { - input.value = toInputValue(nextValue); - } - - if (this.props.autoFocus || this.props.autoSelect) { - setTimeout(() => { - input.focus(); - - if (this.props.autoSelect) { - input.select(); - } - }, 1); - } - } - - componentDidUpdate(prevProps, prevState) { - const { editing } = this.state; - const prevValue = prevProps.value; - const nextValue = this.props.value; - const input = this.inputRef.current; - if (input && !editing && prevValue !== nextValue) { - input.value = toInputValue(nextValue); - } - } - - setEditing(editing) { - this.setState({ editing }); - } - - render() { - const { props } = this; - // Input only props - const { - selfClear, - onInput, - onChange, - onEnter, - value, - maxLength, - placeholder, - ...boxProps - } = props; - // Box props - const { className, fluid, monospace, ...rest } = boxProps; - return ( - -
.
- -
- ); - } -} diff --git a/tgui/packages/tgui_ch/components/KeyListener.tsx b/tgui/packages/tgui_ch/components/KeyListener.tsx deleted file mode 100644 index 62509cae96..0000000000 --- a/tgui/packages/tgui_ch/components/KeyListener.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Component } from 'inferno'; -import { KeyEvent } from '../events'; -import { listenForKeyEvents } from '../hotkeys'; - -type KeyListenerProps = Partial<{ - onKey: (key: KeyEvent) => void; - onKeyDown: (key: KeyEvent) => void; - onKeyUp: (key: KeyEvent) => void; -}>; - -export class KeyListener extends Component { - dispose: () => void; - - constructor() { - super(); - - this.dispose = listenForKeyEvents((key) => { - if (this.props.onKey) { - this.props.onKey(key); - } - - if (key.isDown() && this.props.onKeyDown) { - this.props.onKeyDown(key); - } - - if (key.isUp() && this.props.onKeyUp) { - this.props.onKeyUp(key); - } - }); - } - - componentWillUnmount() { - this.dispose(); - } - - render() { - return null; - } -} diff --git a/tgui/packages/tgui_ch/components/Knob.jsx b/tgui/packages/tgui_ch/components/Knob.jsx deleted file mode 100644 index 1d40ff1ed2..0000000000 --- a/tgui/packages/tgui_ch/components/Knob.jsx +++ /dev/null @@ -1,138 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { keyOfMatchingRange, scale } from 'common/math'; -import { classes } from 'common/react'; -import { computeBoxClassName, computeBoxProps } from './Box'; -import { DraggableControl } from './DraggableControl'; -import { NumberInput } from './NumberInput'; - -export const Knob = (props) => { - // IE8: I don't want to support a yet another component on IE8. - // IE8: It also can't handle SVG. - if (Byond.IS_LTE_IE8) { - return ; - } - const { - // Draggable props (passthrough) - animated, - format, - maxValue, - minValue, - unclamped, - onChange, - onDrag, - step, - stepPixelSize, - suppressFlicker, - unit, - value, - // Own props - className, - style, - fillValue, - color, - ranges = {}, - size = 1, - bipolar, - children, - ...rest - } = props; - return ( - - {(control) => { - const { - dragging, - editing, - value, - displayValue, - displayElement, - inputElement, - handleDragStart, - } = control; - const scaledFillValue = scale( - fillValue ?? displayValue, - minValue, - maxValue - ); - const scaledDisplayValue = scale(displayValue, minValue, maxValue); - const effectiveColor = - color || keyOfMatchingRange(fillValue ?? value, ranges) || 'default'; - const rotation = Math.min((scaledDisplayValue - 0.5) * 270, 225); - return ( -
-
-
-
-
-
- {dragging && ( -
{displayElement}
- )} - - - - - - - {inputElement} -
- ); - }} - - ); -}; diff --git a/tgui/packages/tgui_ch/components/LabeledControls.jsx b/tgui/packages/tgui_ch/components/LabeledControls.jsx deleted file mode 100644 index eea3b2c29b..0000000000 --- a/tgui/packages/tgui_ch/components/LabeledControls.jsx +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { Flex } from './Flex'; - -export const LabeledControls = (props) => { - const { children, wrap, ...rest } = props; - return ( - - {children} - - ); -}; - -const LabeledControlsItem = (props) => { - const { label, children, mx = 1, ...rest } = props; - return ( - - - - {children} - {label} - - - ); -}; - -LabeledControls.Item = LabeledControlsItem; diff --git a/tgui/packages/tgui_ch/components/LabeledList.tsx b/tgui/packages/tgui_ch/components/LabeledList.tsx deleted file mode 100644 index 283c0a2b91..0000000000 --- a/tgui/packages/tgui_ch/components/LabeledList.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; -import { InfernoNode } from 'inferno'; -import { Box, unit } from './Box'; -import { Divider } from './Divider'; - -type LabeledListProps = { - children?: any; -}; - -export const LabeledList = (props: LabeledListProps) => { - const { children } = props; - return {children}
; -}; - -LabeledList.defaultHooks = pureComponentHooks; - -type LabeledListItemProps = { - className?: string | BooleanLike; - label?: string | InfernoNode | BooleanLike; - labelColor?: string | BooleanLike; - labelWrap?: boolean; - color?: string | BooleanLike; - textAlign?: string | BooleanLike; - buttons?: InfernoNode; - /** @deprecated */ - content?: any; - children?: InfernoNode; - verticalAlign?: string; -}; - -const LabeledListItem = (props: LabeledListItemProps) => { - const { - className, - label, - labelColor = 'label', - labelWrap, - color, - textAlign, - buttons, - content, - children, - verticalAlign = 'baseline', - } = props; - return ( - - - {label ? (typeof label === 'string' ? label + ':' : label) : null} - - - {content} - {children} - - {buttons && ( - {buttons} - )} - - ); -}; - -LabeledListItem.defaultHooks = pureComponentHooks; - -type LabeledListDividerProps = { - size?: number; -}; - -const LabeledListDivider = (props: LabeledListDividerProps) => { - const padding = props.size ? unit(Math.max(0, props.size - 1)) : 0; - return ( - - - - - - ); -}; - -LabeledListDivider.defaultHooks = pureComponentHooks; - -LabeledList.Item = LabeledListItem; -LabeledList.Divider = LabeledListDivider; diff --git a/tgui/packages/tgui_ch/components/MenuBar.tsx b/tgui/packages/tgui_ch/components/MenuBar.tsx deleted file mode 100644 index 35de61ec9c..0000000000 --- a/tgui/packages/tgui_ch/components/MenuBar.tsx +++ /dev/null @@ -1,231 +0,0 @@ -/** - * @file - * @copyright 2022 Aleksej Komarov - * @license MIT - */ - -import { classes } from 'common/react'; -import { Component, createRef, InfernoNode, RefObject } from 'inferno'; -import { Box } from './Box'; -import { logger } from '../logging'; -import { Icon } from './Icon'; - -type MenuProps = { - children: any; - width: string; - menuRef: RefObject; - onOutsideClick: () => void; -}; - -class Menu extends Component { - private readonly handleClick: (event) => void; - - constructor(props) { - super(props); - this.handleClick = (event) => { - if (!this.props.menuRef.current) { - logger.log(`Menu.handleClick(): No ref`); - return; - } - - if (this.props.menuRef.current.contains(event.target)) { - logger.log(`Menu.handleClick(): Inside`); - } else { - logger.log(`Menu.handleClick(): Outside`); - this.props.onOutsideClick(); - } - }; - } - - // eslint-disable-next-line react/no-deprecated - componentWillMount() { - window.addEventListener('click', this.handleClick); - } - - componentWillUnmount() { - window.removeEventListener('click', this.handleClick); - } - - render() { - const { width, children } = this.props; - return ( -
- {children} -
- ); - } -} - -type MenuBarDropdownProps = { - open: boolean; - openWidth: string; - children: any; - disabled?: boolean; - display: any; - onMouseOver: () => void; - onClick: () => void; - onOutsideClick: () => void; - className?: string; -}; - -class MenuBarButton extends Component { - private readonly menuRef: RefObject; - - constructor(props) { - super(props); - this.menuRef = createRef(); - } - - render() { - const { props } = this; - const { - open, - openWidth, - children, - disabled, - display, - onMouseOver, - onClick, - onOutsideClick, - ...boxProps - } = props; - const { className, ...rest } = boxProps; - - return ( -
- - {display} - - {open && ( - - {children} - - )} -
- ); - } -} - -type MenuBarItemProps = { - entry: string; - children: any; - openWidth: string; - display: InfernoNode; - setOpenMenuBar: (entry: string | null) => void; - openMenuBar: string | null; - setOpenOnHover: (flag: boolean) => void; - openOnHover: boolean; - disabled?: boolean; - className?: string; -}; - -export const Dropdown = (props: MenuBarItemProps) => { - const { - entry, - children, - openWidth, - display, - setOpenMenuBar, - openMenuBar, - setOpenOnHover, - openOnHover, - disabled, - className, - } = props; - - return ( - { - const open = openMenuBar === entry ? null : entry; - setOpenMenuBar(open); - setOpenOnHover(!openOnHover); - }} - onOutsideClick={() => { - setOpenMenuBar(null); - setOpenOnHover(false); - }} - onMouseOver={() => { - if (openOnHover) { - setOpenMenuBar(entry); - } - }}> - {children} - - ); -}; - -const MenuItemToggle = (props) => { - const { value, displayText, onClick, checked } = props; - return ( - onClick(value)}> -
- {checked && } -
- {displayText} -
- ); -}; - -Dropdown.MenuItemToggle = MenuItemToggle; - -const MenuItem = (props) => { - const { value, displayText, onClick } = props; - return ( - onClick(value)}> - {displayText} - - ); -}; - -Dropdown.MenuItem = MenuItem; - -const Separator = () => { - return
; -}; - -Dropdown.Separator = Separator; - -type MenuBarProps = { - children: any; -}; - -export const MenuBar = (props: MenuBarProps) => { - const { children } = props; - return {children}; -}; - -MenuBar.Dropdown = Dropdown; diff --git a/tgui/packages/tgui_ch/components/Modal.jsx b/tgui/packages/tgui_ch/components/Modal.jsx deleted file mode 100644 index 54c8c090ec..0000000000 --- a/tgui/packages/tgui_ch/components/Modal.jsx +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { classes } from 'common/react'; -import { computeBoxClassName, computeBoxProps } from './Box'; -import { Dimmer } from './Dimmer'; - -export const Modal = (props) => { - const { className, children, onEnter, ...rest } = props; - // VOREStation Addition start - let handleKeyDown; - if (onEnter) { - handleKeyDown = (e) => { - let key = e.which || e.keyCode; - if (key === 13) { - onEnter(e); - } - }; - } - // VOREStation Addition end - return ( - -
- {children} -
-
- ); -}; diff --git a/tgui/packages/tgui_ch/components/NanoMap.jsx b/tgui/packages/tgui_ch/components/NanoMap.jsx deleted file mode 100644 index 4ee5199119..0000000000 --- a/tgui/packages/tgui_ch/components/NanoMap.jsx +++ /dev/null @@ -1,223 +0,0 @@ -import { Component } from 'inferno'; -import { Box, Button, Icon, Tooltip, LabeledList, Slider } from '.'; -import { useBackend } from '../backend'; - -const pauseEvent = (e) => { - if (e.stopPropagation) { - e.stopPropagation(); - } - if (e.preventDefault) { - e.preventDefault(); - } - e.cancelBubble = true; - e.returnValue = false; - return false; -}; - -const zoomScale = 280; - -export class NanoMap extends Component { - constructor(props) { - super(props); - - // Auto center based on window size - const Xcenter = window.innerWidth / 2 - 256; - const Ycenter = window.innerHeight / 2 - 256; - - this.state = { - offsetX: Xcenter, - offsetY: Ycenter, - transform: 'none', - dragging: false, - originX: null, - originY: null, - zoom: 1, - }; - - // Dragging - this.handleDragStart = (e) => { - this.ref = e.target; - this.setState({ - dragging: false, - originX: e.screenX, - originY: e.screenY, - }); - document.addEventListener('mousemove', this.handleDragMove); - document.addEventListener('mouseup', this.handleDragEnd); - pauseEvent(e); - }; - - this.handleDragMove = (e) => { - this.setState((prevState) => { - const state = { ...prevState }; - const newOffsetX = e.screenX - state.originX; - const newOffsetY = e.screenY - state.originY; - if (prevState.dragging) { - state.offsetX += newOffsetX; - state.offsetY += newOffsetY; - state.originX = e.screenX; - state.originY = e.screenY; - } else { - state.dragging = true; - } - return state; - }); - pauseEvent(e); - }; - - this.handleDragEnd = (e) => { - this.setState({ - dragging: false, - originX: null, - originY: null, - }); - document.removeEventListener('mousemove', this.handleDragMove); - document.removeEventListener('mouseup', this.handleDragEnd); - pauseEvent(e); - }; - - this.handleOnClick = (e) => { - let byondX = e.offsetX / this.state.zoom / zoomScale; - let byondY = 1 - e.offsetY / this.state.zoom / zoomScale; // Byond origin is bottom left, this is top left - - e.byondX = byondX; - e.byondY = byondY; - if (typeof this.props.onClick === 'function') { - this.props.onClick(e); - } - }; - - this.handleZoom = (_e, value) => { - this.setState((state) => { - const newZoom = Math.min(Math.max(value, 1), 8); - let zoomDiff = (newZoom - state.zoom) * 1.5; - state.zoom = newZoom; - - let newOffsetX = state.offsetX - 262 * zoomDiff; - if (newOffsetX < -500) { - newOffsetX = -500; - } - if (newOffsetX > 500) { - newOffsetX = 500; - } - - let newOffsetY = state.offsetY - 256 * zoomDiff; - if (newOffsetY < -200) { - newOffsetY = -200; - } - if (newOffsetY > 200) { - newOffsetY = 200; - } - - state.offsetX = newOffsetX; - state.offsetY = newOffsetY; - if (props.onZoom) { - props.onZoom(state.zoom); - } - return state; - }); - }; - } - - render() { - const { config } = useBackend(this.context); - const { dragging, offsetX, offsetY, zoom = 1 } = this.state; - const { children } = this.props; - - const mapUrl = config.map + '_nanomap_z' + config.mapZLevel + '.png'; - // (x * zoom), x Needs to be double the turf- map size. (for virgo, 140x140) - const mapSize = zoomScale * zoom + 'px'; - const newStyle = { - width: mapSize, - height: mapSize, - 'margin-top': offsetY + 'px', - 'margin-left': offsetX + 'px', - 'overflow': 'hidden', - 'position': 'relative', - 'background-image': 'url(' + mapUrl + ')', - 'background-size': 'cover', - 'background-repeat': 'no-repeat', - 'text-align': 'center', - 'cursor': dragging ? 'move' : 'auto', - }; - - return ( - - - {children} - - - - ); - } -} - -const NanoMapMarker = (props, context) => { - const { x, y, zoom = 1, icon, tooltip, color, onClick } = props; - - const handleOnClick = (e) => { - pauseEvent(e); - if (onClick) { - onClick(e); - } - }; - - const rx = x * 2 * zoom - zoom - 3; - const ry = y * 2 * zoom - zoom - 3; - return ( -
- - - - -
- ); -}; - -NanoMap.Marker = NanoMapMarker; - -const NanoMapZoomer = (props, context) => { - const { act, config, data } = useBackend(context); - return ( - - - - v + 'x'} - value={props.zoom} - onDrag={(e, v) => props.onZoom(e, v)} - /> - - - {data.map_levels - .sort((a, b) => Number(a) - Number(b)) - .map((level) => ( -