diff --git a/tgui/packages/tgui/components/Grid.jsx b/tgui/packages/tgui/components/Grid.jsx deleted file mode 100644 index f5593c9e00a..00000000000 --- a/tgui/packages/tgui/components/Grid.jsx +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { Table } from './Table'; - -/** @deprecated */ -export const Grid = (props) => { - const { children, ...rest } = props; - return ( - - {children} -
- ); -}; - -/** @deprecated */ -export const GridColumn = (props) => { - const { size = 1, style, ...rest } = props; - return ( - - ); -}; - -Grid.Column = GridColumn; diff --git a/tgui/packages/tgui/components/Grid.tsx b/tgui/packages/tgui/components/Grid.tsx new file mode 100644 index 00000000000..91c9256753b --- /dev/null +++ b/tgui/packages/tgui/components/Grid.tsx @@ -0,0 +1,44 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { PropsWithChildren } from 'react'; + +import { logger } from '../logging'; +import { BoxProps } from './Box'; +import { Table } from './Table'; + +/** @deprecated Do not use. Use stack instead. */ +export function Grid(props: PropsWithChildren) { + const { children, ...rest } = props; + logger.error('Grid component is deprecated. Use a Stack instead.'); + return ( + + {children} +
+ ); +} + +type Props = Partial<{ + /** Width of the column in percentage. */ + size: number; +}> & + BoxProps; + +/** @deprecated Do not use. Use stack instead. */ +export function GridColumn(props: Props) { + const { size = 1, style, ...rest } = props; + return ( + + ); +} + +Grid.Column = GridColumn; diff --git a/tgui/packages/tgui/components/Table.tsx b/tgui/packages/tgui/components/Table.tsx index 87edfbb8fc9..f4dcf6f3f52 100644 --- a/tgui/packages/tgui/components/Table.tsx +++ b/tgui/packages/tgui/components/Table.tsx @@ -64,6 +64,8 @@ type CellProps = Partial<{ colSpan: number; /** Whether this is a header cell. */ header: boolean; + /** Rows for this cell to expand, assuming there is room. */ + rowSpan: number; }> & BoxProps; diff --git a/tgui/packages/tgui/interfaces/AirlockElectronics.tsx b/tgui/packages/tgui/interfaces/AirlockElectronics.tsx index 7e0ebba0bb4..b90e8ac3b57 100644 --- a/tgui/packages/tgui/interfaces/AirlockElectronics.tsx +++ b/tgui/packages/tgui/interfaces/AirlockElectronics.tsx @@ -1,21 +1,31 @@ import { BooleanLike } from 'common/react'; import { useBackend } from '../backend'; -import { Button, Input, LabeledList, Section } from '../components'; +import { Button, Input, LabeledList, Section, Stack } from '../components'; import { Window } from '../layouts'; -import { AccessConfig } from './common/AccessConfig'; +import { AccessConfig, Region } from './common/AccessConfig'; type Data = { + accesses: string[]; oneAccess: BooleanLike; - unres_direction: number; - passedName: string; passedCycleId: number; - regions: string[]; - accesses: string[]; + passedName: string; + regions: Region[]; shell: BooleanLike; + unres_direction: number; }; -export const AirLockMainSection = (props) => { +export function AirlockElectronics(props) { + return ( + + + + + + ); +} + +export function AirLockMainSection(props) { const { act, data } = useBackend(); const { accesses = [], @@ -28,123 +38,125 @@ export const AirLockMainSection = (props) => { } = data; return ( -
- - - { - act('set_shell', { on: !shell }); - }} - tooltip="Whether this airlock can have an integrated circuit placed inside of it or not." - /> - - -
- ); -}; - -export const AirlockElectronics = (props) => { - return ( - - - - - + + +
+ + + { + act('set_shell', { on: !shell }); + }} + tooltip="Whether this airlock can have an integrated circuit placed inside of it or not." + > + Shell + + + + + + + + + + + + + + act('passedName', { + passedName: value, + }) + } + /> + + + + act('passedCycleId', { + passedCycleId: value, + }) + } + /> + + +
+
+ + + act('set', { + access: ref, + }) + } + grantAll={() => act('grant_all')} + denyAll={() => act('clear_all')} + grantDep={(ref) => + act('grant_region', { + region: ref, + }) + } + denyDep={(ref) => + act('deny_region', { + region: ref, + }) + } + /> + +
); -}; +} diff --git a/tgui/packages/tgui/interfaces/CircuitAccessChecker.tsx b/tgui/packages/tgui/interfaces/CircuitAccessChecker.tsx index 0c81311a431..f37414a86f5 100644 --- a/tgui/packages/tgui/interfaces/CircuitAccessChecker.tsx +++ b/tgui/packages/tgui/interfaces/CircuitAccessChecker.tsx @@ -3,11 +3,11 @@ import { BooleanLike } from 'common/react'; import { useBackend } from '../backend'; import { Button, LabeledList } from '../components'; import { Window } from '../layouts'; -import { AccessConfig } from './common/AccessConfig'; +import { AccessConfig, Region } from './common/AccessConfig'; type Data = { oneAccess: BooleanLike; - regions: string[]; + regions: Region[]; accesses: string[]; }; @@ -28,8 +28,8 @@ export const CircuitAccessChecker = (props) => { act('set', { access: ref, diff --git a/tgui/packages/tgui/interfaces/ClockworkSlab.jsx b/tgui/packages/tgui/interfaces/ClockworkSlab.jsx index 4bc7191d5c2..fc9c7462322 100644 --- a/tgui/packages/tgui/interfaces/ClockworkSlab.jsx +++ b/tgui/packages/tgui/interfaces/ClockworkSlab.jsx @@ -7,10 +7,10 @@ import { Button, Collapsible, Divider, - Grid, Icon, ProgressBar, Section, + Stack, Table, } from '../components'; import { TableRow } from '../components/Table'; @@ -311,12 +311,12 @@ const ClockworkOverviewStat = (props) => { const { title, iconName, amount, maxAmount, unit, overrideText } = props; return ( - - + + - - {title} - + + {title} + { > {overrideText ? overrideText : amount + ' ' + unit} - - + + ); }; diff --git a/tgui/packages/tgui/interfaces/DnaVault.jsx b/tgui/packages/tgui/interfaces/DnaVault.tsx similarity index 75% rename from tgui/packages/tgui/interfaces/DnaVault.jsx rename to tgui/packages/tgui/interfaces/DnaVault.tsx index 5ac7711412d..2b6781d6cf7 100644 --- a/tgui/packages/tgui/interfaces/DnaVault.jsx +++ b/tgui/packages/tgui/interfaces/DnaVault.tsx @@ -1,28 +1,44 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, Button, - Grid, LabeledList, ProgressBar, Section, + Stack, } from '../components'; import { Window } from '../layouts'; -export const DnaVault = (props) => { - const { act, data } = useBackend(); +type Data = { + animals_max: number; + animals: number; + choiceA: string; + choiceB: string; + completed: BooleanLike; + dna_max: number; + dna: number; + plants_max: number; + plants: number; + used: BooleanLike; +}; + +export function DnaVault(props) { + const { act, data } = useBackend(); const { - completed, - used, + animals_max, + animals, choiceA, choiceB, - dna, + completed, dna_max, - plants, + dna, plants_max, - animals, - animals_max, + plants, + used, } = data; + return ( @@ -50,37 +66,39 @@ export const DnaVault = (props) => { Applicable Gene Therapy Treatments - - + + + + + + )} ); -}; +} diff --git a/tgui/packages/tgui/interfaces/EightBallVote.tsx b/tgui/packages/tgui/interfaces/EightBallVote.tsx index 30ab7baae48..f85b8724ffe 100644 --- a/tgui/packages/tgui/interfaces/EightBallVote.tsx +++ b/tgui/packages/tgui/interfaces/EightBallVote.tsx @@ -2,50 +2,51 @@ import { BooleanLike } from 'common/react'; import { toTitleCase } from 'common/string'; import { useBackend } from '../backend'; -import { Box, Button, Grid, NoticeBox, Section } from '../components'; +import { Box, Button, NoticeBox, Section, Stack } from '../components'; import { Window } from '../layouts'; type Data = { - shaking: BooleanLike; - question: string; answers: Answer[]; + question: string; + shaking: BooleanLike; }; type Answer = { - answer: string; amount: number; + answer: string; selected: BooleanLike; }; -export const EightBallVote = (props) => { - const { act, data } = useBackend(); +export function EightBallVote(props) { + const { data } = useBackend(); const { shaking } = data; + return ( - {(!shaking && ( + {(shaking && ( No question is currently being asked. )) || } ); -}; +} -const EightBallVoteQuestion = (props) => { +function EightBallVoteQuestion(props) { const { act, data } = useBackend(); const { question, answers = [] } = data; + return (
"{question}" - + {answers.map((answer) => ( - + {answer.amount} - + ))} - +
); -}; +} diff --git a/tgui/packages/tgui/interfaces/EmergencyShuttleConsole.jsx b/tgui/packages/tgui/interfaces/EmergencyShuttleConsole.tsx similarity index 73% rename from tgui/packages/tgui/interfaces/EmergencyShuttleConsole.jsx rename to tgui/packages/tgui/interfaces/EmergencyShuttleConsole.tsx index b7b09bdca6a..04214e092d6 100644 --- a/tgui/packages/tgui/interfaces/EmergencyShuttleConsole.jsx +++ b/tgui/packages/tgui/interfaces/EmergencyShuttleConsole.tsx @@ -1,17 +1,34 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; -import { Box, Button, Grid, Section } from '../components'; +import { Box, Button, Section, Stack } from '../components'; import { Window } from '../layouts'; -export const EmergencyShuttleConsole = (props) => { - const { act, data } = useBackend(); +type Data = { + authorizations_remaining: number; + authorizations: Authorization[]; + emagged: BooleanLike; + enabled: BooleanLike; + engines_started: BooleanLike; + timer_str: string; +}; + +type Authorization = { + job: string; + name: string; +}; + +export function EmergencyShuttleConsole(props) { + const { act, data } = useBackend(); const { - timer_str, - enabled, + authorizations = [], + authorizations_remaining, emagged, + enabled, engines_started, - authorizations_remaining, - authorizations = [], + timer_str, } = data; + return ( @@ -29,41 +46,42 @@ export const EmergencyShuttleConsole = (props) => {
act('abort')} - /> + > + Repeal All + } > - - + + + + + +
@@ -71,7 +89,11 @@ export const EmergencyShuttleConsole = (props) => { } > - {authorizations.length > 0 ? ( + {authorizations.length === 0 ? ( + + No Active Authorizations + + ) : ( authorizations.map((authorization) => ( { {authorization.name} ({authorization.job}) )) - ) : ( - - No Active Authorizations - )}
@@ -93,4 +111,4 @@ export const EmergencyShuttleConsole = (props) => {
); -}; +} diff --git a/tgui/packages/tgui/interfaces/LaunchpadConsole.jsx b/tgui/packages/tgui/interfaces/LaunchpadConsole.jsx deleted file mode 100644 index c3145844fc2..00000000000 --- a/tgui/packages/tgui/interfaces/LaunchpadConsole.jsx +++ /dev/null @@ -1,276 +0,0 @@ -import { useBackend } from '../backend'; -import { - Box, - Button, - Divider, - Flex, - Grid, - Input, - NoticeBox, - NumberInput, - Section, -} from '../components'; -import { Window } from '../layouts'; - -const LaunchpadButtonPad = (props) => { - const { act } = useBackend(); - return ( - - - + + + + ); +} + +function LaunchpadButtonPad(props) { + const { act } = useBackend(); + + return ( +
+ + {buttonConfigs.map((buttonRow, i) => ( + + {buttonRow.map((buttonConfig, j) => ( + + ))} + + ))} + +
+ ); +} + +function TargetingControls(props) { + const { act, data } = useBackend(); + const { x, y, range } = data; + + const inputConfigs = [ + { value: x, axis: 'x', icon: 'arrows-alt-h' }, + { value: y, axis: 'y', icon: 'arrows-alt-v' }, + ]; + + return ( +
+ {inputConfigs.map((inputConfig, i) => ( + + + + {inputConfig.axis.toUpperCase()} + + + + + act('set_pos', { + [inputConfig.axis]: value, + }) + } + step={1} + stepPixelSize={10} + value={inputConfig.value} + width="90px" + /> + + + ))} +
+ ); +} + +function DeliveryButtons(props) { + const { act } = useBackend(); + + return ( +
+ + + + + + + + +
+ ); +} diff --git a/tgui/packages/tgui/interfaces/Mecha/data.ts b/tgui/packages/tgui/interfaces/Mecha/data.ts index 993c7a1d4be..0bccc7fa5c1 100644 --- a/tgui/packages/tgui/interfaces/Mecha/data.ts +++ b/tgui/packages/tgui/interfaces/Mecha/data.ts @@ -1,5 +1,7 @@ import { BooleanLike } from 'common/react'; +import { Region } from '../common/AccessConfig'; + export type AccessData = { name: string; number: number; @@ -25,7 +27,7 @@ export type MainData = { overclock_temp_percentage: number; one_access: BooleanLike; - regions: string[]; + regions: Region[]; accesses: string[]; servo_rating: number; diff --git a/tgui/packages/tgui/interfaces/NtosArcade.jsx b/tgui/packages/tgui/interfaces/NtosArcade.jsx deleted file mode 100644 index b8c9aa05109..00000000000 --- a/tgui/packages/tgui/interfaces/NtosArcade.jsx +++ /dev/null @@ -1,135 +0,0 @@ -import { resolveAsset } from '../assets'; -import { useBackend } from '../backend'; -import { - AnimatedNumber, - Box, - Button, - Grid, - LabeledList, - ProgressBar, - Section, -} from '../components'; -import { NtosWindow } from '../layouts'; - -export const NtosArcade = (props) => { - const { act, data } = useBackend(); - return ( - - -
- - - - - - - - {data.PlayerHitpoints}HP - - - - - {data.PlayerMP}MP - - - - -
- {data.Status} -
-
- - - - HP - - -
- -
-
-
- -
-
-
- ); -}; diff --git a/tgui/packages/tgui/interfaces/NtosArcade.tsx b/tgui/packages/tgui/interfaces/NtosArcade.tsx new file mode 100644 index 00000000000..ea63e62e037 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosArcade.tsx @@ -0,0 +1,178 @@ +import { BooleanLike } from 'common/react'; + +import { resolveAsset } from '../assets'; +import { useBackend } from '../backend'; +import { + AnimatedNumber, + Box, + Button, + Divider, + LabeledList, + NoticeBox, + ProgressBar, + Section, + Stack, +} from '../components'; +import { NtosWindow } from '../layouts'; + +type Data = { + BossID: string; + GameActive: BooleanLike; + Hitpoints: number; + PauseState: BooleanLike; + PlayerHitpoints: number; + PlayerMP: number; + Status: string; + TicketCount: number; +}; + +export function NtosArcade(props) { + return ( + + +
+ + + + + + + + + +
+
+
+ ); +} + +function PlayerStats(props) { + const { data } = useBackend(); + const { PauseState, PlayerHitpoints, PlayerMP, Status } = data; + + return ( + <> + + + + {PlayerHitpoints}HP + + + + + {PlayerMP}MP + + + + + {Status} + + ); +} + +function BossBar(props) { + const { data } = useBackend(); + const { BossID, Hitpoints } = data; + + return ( + <> + + + HP + + +
+ +
+ + ); +} + +function BottomButtons(props) { + const { act, data } = useBackend(); + const { GameActive, PauseState, TicketCount } = data; + + return ( + <> + + + + + + + + + = 1 ? 'good' : 'normal'}> + Earned Tickets: {TicketCount} + + + ); +} diff --git a/tgui/packages/tgui/interfaces/NuclearBomb.jsx b/tgui/packages/tgui/interfaces/NuclearBomb.tsx similarity index 64% rename from tgui/packages/tgui/interfaces/NuclearBomb.jsx rename to tgui/packages/tgui/interfaces/NuclearBomb.tsx index 81b2e622e9d..5cda6d97cbb 100644 --- a/tgui/packages/tgui/interfaces/NuclearBomb.jsx +++ b/tgui/packages/tgui/interfaces/NuclearBomb.tsx @@ -1,32 +1,40 @@ -import { classes } from 'common/react'; +import { BooleanLike, classes } from 'common/react'; import { useBackend } from '../backend'; -import { Box, Button, Flex, Grid, Icon } from '../components'; +import { Box, Button, Icon, Stack } from '../components'; import { Window } from '../layouts'; +type Data = { + disk_present: BooleanLike; + status1: string; + status2: string; + anchored: BooleanLike; +}; + +const KEYPAD = [ + ['1', '4', '7', 'C'], + ['2', '5', '8', '0'], + ['3', '6', '9', 'E'], +] as const; + // This ui is so many manual overrides and !important tags // and hand made width sets that changing pretty much anything // is going to require a lot of tweaking it get it looking correct again // I'm sorry, but it looks bangin -export const NukeKeypad = (props) => { +export function NukeKeypad(props) { const { act } = useBackend(); - const keypadKeys = [ - ['1', '4', '7', 'C'], - ['2', '5', '8', '0'], - ['3', '6', '9', 'E'], - ]; + return ( - - {keypadKeys.map((keyColumn) => ( - + + {KEYPAD.map((keyColumn) => ( + {keyColumn.map((key) => ( ))} - + ))} - + ); -}; +} + +export function NuclearBomb(props) { + const { act, data } = useBackend(); + const { status1, status2 } = data; -export const NuclearBomb = (props) => { - const { act, data } = useBackend(); - const { anchored, disk_present, status1, status2 } = data; return ( - - + + {status1} - - + + {status2} - - + + - - + + ); -}; +} diff --git a/tgui/packages/tgui/interfaces/Roulette.jsx b/tgui/packages/tgui/interfaces/Roulette.jsx deleted file mode 100644 index e07cf4e0ef4..00000000000 --- a/tgui/packages/tgui/interfaces/Roulette.jsx +++ /dev/null @@ -1,313 +0,0 @@ -import { classes } from 'common/react'; -import { useState } from 'react'; - -import { useBackend } from '../backend'; -import { Box, Button, Grid, NumberInput, Table } from '../components'; -import { Window } from '../layouts'; - -const getNumberColor = (number) => { - const inRedOddRange = - (number >= 1 && number <= 10) || (number >= 19 && number <= 28); - - if (number % 2 === 1) { - return inRedOddRange ? 'red' : 'black'; - } - return inRedOddRange ? 'black' : 'red'; -}; - -export const RouletteNumberCell = (props) => { - const { - buttonClass = null, - cellClass = null, - color, - colspan = '1', - rowspan = '1', - text, - value, - } = props; - const { act } = useBackend(); - - return ( - - - - ); -}; - -export const RouletteBoard = () => { - const firstRow = [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36]; - const secondRow = [2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35]; - const thirdRow = [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34]; - const fourthRow = { - 's1-12': '1st 12', - 's13-24': '2nd 12', - 's25-36': '3rd 12', - }; - const fifthRow = [ - { color: 'transparent', text: '1-18', value: 's1-18' }, - { color: 'transparent', text: 'Even', value: 'even' }, - { color: 'black', text: 'Black', value: 'black' }, - { color: 'red', text: 'Red', value: 'red' }, - { color: 'transparent', text: 'Odd', value: 'odd' }, - { color: 'transparent', text: '19-36', value: 's19-36' }, - ]; - - return ( - - - - - {firstRow.map((number) => ( - - ))} - - - - {secondRow.map((number) => ( - - ))} - - - - {thirdRow.map((number) => ( - - ))} - - - - - {Object.entries(fourthRow).map(([value, text]) => ( - - ))} - - - - {fifthRow.map((cell) => ( - - ))} - -
-
- ); -}; - -export const RouletteBetTable = (props) => { - const { act, data } = useBackend(); - - const [customBet, setCustomBet] = useState(500); - - let { BetType } = data; - - if (BetType.startsWith('s')) { - BetType = BetType.substring(1, BetType.length); - } - - return ( - - - - Last Spin: - - - Current Bet: - - - - - {data.LastSpin} - - - - {data.BetAmount} cr on {BetType} - - -
- ); -}; - -export const Roulette = (props) => { - return ( - - - - - - - ); -}; diff --git a/tgui/packages/tgui/interfaces/Roulette/BetTable.tsx b/tgui/packages/tgui/interfaces/Roulette/BetTable.tsx new file mode 100644 index 00000000000..8abf081b288 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Roulette/BetTable.tsx @@ -0,0 +1,171 @@ +import { BooleanLike, classes } from 'common/react'; +import { useState } from 'react'; + +import { useBackend } from '../../backend'; +import { Box, Button, NumberInput, Stack, Table } from '../../components'; +import { getNumberColor } from './helpers'; + +type Data = { + IsAnchored: BooleanLike; + BetAmount: number; + BetType: string; + HouseBalance: number; + LastSpin: number; + Spinning: BooleanLike; + AccountBalance: number; + CanUnbolt: BooleanLike; +}; + +export function RouletteBetTable(props) { + const { act, data } = useBackend(); + const { LastSpin, HouseBalance, BetAmount, IsAnchored } = data; + + const [customBet, setCustomBet] = useState(500); + + let BetType = data.BetType; + + if (BetType.startsWith('s')) { + BetType = BetType.substring(1, BetType.length); + } + + return ( + + + + Last Spin: + + + Current Bet: + + + + + {LastSpin} + + + + {BetAmount} cr on {BetType} + + + + + + + + + + + + setCustomBet(value)} + /> + + + + + + + + + Swipe an ID card with a connected account to spin! + + + + + + + House Balance: + + {HouseBalance ? HouseBalance + ' cr' : 'None'} + + + + + +
+ ); +} diff --git a/tgui/packages/tgui/interfaces/Roulette/Board.tsx b/tgui/packages/tgui/interfaces/Roulette/Board.tsx new file mode 100644 index 00000000000..0f0fbcdc681 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Roulette/Board.tsx @@ -0,0 +1,107 @@ +import { Box, Table } from '../../components'; +import { getNumberColor } from './helpers'; +import { RouletteNumberCell } from './NumberCell'; + +const firstRow = [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36] as const; +const secondRow = [2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35] as const; +const thirdRow = [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34] as const; +const fourthRow = { + 's1-12': '1st 12', + 's13-24': '2nd 12', + 's25-36': '3rd 12', +} as const; +const fifthRow = [ + { color: 'transparent', text: '1-18', value: 's1-18' }, + { color: 'transparent', text: 'Even', value: 'even' }, + { color: 'black', text: 'Black', value: 'black' }, + { color: 'red', text: 'Red', value: 'red' }, + { color: 'transparent', text: 'Odd', value: 'odd' }, + { color: 'transparent', text: '19-36', value: 's19-36' }, +] as const; + +export function RouletteBoard(props) { + return ( + + + + + {firstRow.map((number) => ( + + ))} + + + + {secondRow.map((number) => ( + + ))} + + + + {thirdRow.map((number) => ( + + ))} + + + + + {Object.entries(fourthRow).map(([value, text]) => ( + + ))} + + + + {fifthRow.map((cell) => ( + + ))} + +
+
+ ); +} diff --git a/tgui/packages/tgui/interfaces/Roulette/NumberCell.tsx b/tgui/packages/tgui/interfaces/Roulette/NumberCell.tsx new file mode 100644 index 00000000000..1b8143905c3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Roulette/NumberCell.tsx @@ -0,0 +1,48 @@ +import { classes } from 'common/react'; + +import { useBackend } from '../../backend'; +import { Button, Table } from '../../components'; + +type Props = { + color: string; + text: string; + value: string; +} & Partial<{ + buttonClass: string; + cellClass: string; + colspan: number; + rowspan: number; +}>; + +export function RouletteNumberCell(props: Props) { + const { + buttonClass, + cellClass, + color, + colspan = 1, + rowspan = 1, + text, + value, + } = props; + const { act } = useBackend(); + + return ( + + + + ); +} diff --git a/tgui/packages/tgui/interfaces/Roulette/helpers.tsx b/tgui/packages/tgui/interfaces/Roulette/helpers.tsx new file mode 100644 index 00000000000..b5c4f9b9b33 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Roulette/helpers.tsx @@ -0,0 +1,9 @@ +export function getNumberColor(number: number): 'red' | 'black' { + const inRedOddRange = + (number >= 1 && number <= 10) || (number >= 19 && number <= 28); + + if (number % 2 === 1) { + return inRedOddRange ? 'red' : 'black'; + } + return inRedOddRange ? 'black' : 'red'; +} diff --git a/tgui/packages/tgui/interfaces/Roulette/index.tsx b/tgui/packages/tgui/interfaces/Roulette/index.tsx new file mode 100644 index 00000000000..8c27a052a02 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Roulette/index.tsx @@ -0,0 +1,14 @@ +import { Window } from '../../layouts'; +import { RouletteBetTable } from './BetTable'; +import { RouletteBoard } from './Board'; + +export function Roulette(props) { + return ( + + + + + + + ); +} diff --git a/tgui/packages/tgui/interfaces/SyndContractor.jsx b/tgui/packages/tgui/interfaces/SyndContractor.jsx index 52e0b801f3b..265694f3e0b 100644 --- a/tgui/packages/tgui/interfaces/SyndContractor.jsx +++ b/tgui/packages/tgui/interfaces/SyndContractor.jsx @@ -6,12 +6,12 @@ import { Box, Button, Flex, - Grid, Icon, LabeledList, Modal, NoticeBox, Section, + Stack, Table, Tabs, } from '../components'; @@ -182,8 +182,8 @@ export const StatusPane = (props) => {
} > - - + + { {String(data.earned_tc)} - - + + {String(data.contracts_completed)} ACTIVE - - + + ); }; @@ -287,15 +287,15 @@ const ContractsTab = (props) => { } > - - {contract.message} - + + {contract.message} + Dropoff Location: {contract.dropoff} - - + + ); })} diff --git a/tgui/packages/tgui/interfaces/SyndicateContractor.tsx b/tgui/packages/tgui/interfaces/SyndicateContractor.tsx index 81e01d682d7..66c07263981 100644 --- a/tgui/packages/tgui/interfaces/SyndicateContractor.tsx +++ b/tgui/packages/tgui/interfaces/SyndicateContractor.tsx @@ -5,32 +5,21 @@ import { Box, Button, Flex, - Grid, Icon, LabeledList, Modal, NoticeBox, Section, + Stack, } from '../components'; import { FakeTerminal } from '../components/FakeTerminal'; import { NtosWindow } from '../layouts'; -const CONTRACT_STATUS_INACTIVE = 1; -const CONTRACT_STATUS_ACTIVE = 2; -const CONTRACT_STATUS_BOUNTY_CONSOLE_ACTIVE = 3; -const CONTRACT_STATUS_EXTRACTING = 4; -const CONTRACT_STATUS_COMPLETE = 5; -const CONTRACT_STATUS_ABORTED = 6; - -export const SyndicateContractor = (props) => { - return ( - - - - - - ); -}; +enum CONTRACT { + Inactive = 1, + Active = 2, + Complete = 5, +} type Data = { contracts_completed: number; @@ -59,7 +48,47 @@ type ContractData = { target: string; }; -export const SyndicateContractorContent = (props) => { +const infoEntries = [ + 'SyndTract v2.0', + '', + "We've identified potentional high-value targets that are", + 'currently assigned to your mission area. They are believed', + 'to hold valuable information which could be of immediate', + 'importance to our organisation.', + '', + 'Listed below are all of the contracts available to you. You', + 'are to bring the specified target to the designated', + 'drop-off, and contact us via this uplink. We will send', + 'a specialised extraction unit to put the body into.', + '', + 'We want targets alive - but we will sometimes pay slight', + "amounts if they're not, you just won't receive the shown", + 'bonus. You can redeem your payment through this uplink in', + 'the form of raw telecrystals, which can be put into your', + 'regular Syndicate uplink to purchase whatever you may need.', + 'We provide you with these crystals the moment you send the', + 'target up to us, which can be collected at anytime through', + 'this system.', + '', + 'Targets extracted will be ransomed back to the station once', + 'their use to us is fulfilled, with us providing you a small', + 'percentage cut. You may want to be mindful of them', + 'identifying you when they come back. We provide you with', + 'a standard contractor loadout, which will help cover your', + 'identity.', +] as const; + +export function SyndicateContractor(props) { + return ( + + + + + + ); +} + +function SyndicateContractorContent(props) { const { data, act } = useBackend(); const { error, logged_in, first_load, info_screen } = data; @@ -84,37 +113,7 @@ export const SyndicateContractorContent = (props) => { 'Searching for available contracts...', 'CONTRACTS FOUND', 'WELCOME, AGENT', - ]; - - const infoEntries = [ - 'SyndTract v2.0', - '', - "We've identified potentional high-value targets that are", - 'currently assigned to your mission area. They are believed', - 'to hold valuable information which could be of immediate', - 'importance to our organisation.', - '', - 'Listed below are all of the contracts available to you. You', - 'are to bring the specified target to the designated', - 'drop-off, and contact us via this uplink. We will send', - 'a specialised extraction unit to put the body into.', - '', - 'We want targets alive - but we will sometimes pay slight', - "amounts if they're not, you just won't receive the shown", - 'bonus. You can redeem your payment through this uplink in', - 'the form of raw telecrystals, which can be put into your', - 'regular Syndicate uplink to purchase whatever you may need.', - 'We provide you with these crystals the moment you send the', - 'target up to us, which can be collected at anytime through', - 'this system.', - '', - 'Targets extracted will be ransomed back to the station once', - 'their use to us is fulfilled, with us providing you a small', - 'percentage cut. You may want to be mindful of them', - 'identifying you when they come back. We provide you with', - 'a standard contractor loadout, which will help cover your', - 'identity.', - ]; + ] as const; const errorPane = !!error && ( @@ -126,7 +125,7 @@ export const SyndicateContractorContent = (props) => { {error} - @@ -136,11 +135,9 @@ export const SyndicateContractorContent = (props) => { return (
- {!!error && {error}}
@@ -167,11 +164,12 @@ export const SyndicateContractorContent = (props) => { ); } @@ -183,38 +181,38 @@ export const SyndicateContractorContent = (props) => { ); -}; +} -export const StatusPane = (props) => { +function StatusPane(props) { const { act, data } = useBackend(); const { redeemable_tc, earned_tc, contracts_completed } = data; return (
- Contractor Status - } + title="Contractor Status" > - - + + act('PRG_redeem_TC')} - /> + > + Claim + } > {String(redeemable_tc)} @@ -223,24 +221,28 @@ export const StatusPane = (props) => { {String(earned_tc)} - - + + {String(contracts_completed)} ACTIVE - - + +
); -}; +} -const ContractsTab = (props) => { +function ContractsTab(props) { const { act, data } = useBackend(); - const { contracts, ongoing_contract, extraction_enroute, dropoff_direction } = - data; + const { + contracts = [], + ongoing_contract, + extraction_enroute, + dropoff_direction, + } = data; return ( <> @@ -248,18 +250,19 @@ const ContractsTab = (props) => { title="Available Contracts" buttons={ } > {contracts.map((contract) => { - if (ongoing_contract && contract.status !== CONTRACT_STATUS_ACTIVE) { + if (ongoing_contract && contract.status !== CONTRACT.Active) { return; } - const active = contract.status > CONTRACT_STATUS_INACTIVE; - if (contract.status >= CONTRACT_STATUS_COMPLETE) { + const active = contract.status > CONTRACT.Inactive; + if (contract.status >= CONTRACT.Complete) { return; } return ( @@ -276,27 +279,28 @@ const ContractsTab = (props) => { {`${contract.payout} (+${contract.payout_bonus}) TC`} } > - - {contract.message} - + + {contract.message} + Dropoff Location: {contract.dropoff} - - + + ); })} @@ -310,4 +314,4 @@ const ContractsTab = (props) => { ); -}; +} diff --git a/tgui/packages/tgui/interfaces/common/AccessConfig.jsx b/tgui/packages/tgui/interfaces/common/AccessConfig.jsx deleted file mode 100644 index e91abecbde7..00000000000 --- a/tgui/packages/tgui/interfaces/common/AccessConfig.jsx +++ /dev/null @@ -1,136 +0,0 @@ -import { sortBy } from 'common/collections'; -import { useState } from 'react'; - -import { Button, Flex, Grid, Section, Tabs } from '../../components'; - -export const AccessConfig = (props) => { - const { - accesses = [], - selectedList = [], - accessMod, - grantAll, - denyAll, - grantDep, - denyDep, - } = props; - const [selectedAccessName, setSelectedAccessName] = useState( - accesses[0]?.name, - ); - const selectedAccess = accesses.find( - (access) => access.name === selectedAccessName, - ); - const selectedAccessEntries = sortBy((entry) => entry.desc)( - selectedAccess?.accesses || [], - ); - - const checkAccessIcon = (accesses) => { - let oneAccess = false; - let oneInaccess = false; - for (let element of accesses) { - if (selectedList.includes(element.ref)) { - oneAccess = true; - } else { - oneInaccess = true; - } - } - if (!oneAccess && oneInaccess) { - return 0; - } else if (oneAccess && oneInaccess) { - return 1; - } else { - return 2; - } - }; - - return ( -
-
- ); -}; - -const diffMap = { - 0: { - icon: 'times-circle', - color: 'bad', - }, - 1: { - icon: 'stop-circle', - color: null, - }, - 2: { - icon: 'check-circle', - color: 'good', - }, -}; diff --git a/tgui/packages/tgui/interfaces/common/AccessConfig.tsx b/tgui/packages/tgui/interfaces/common/AccessConfig.tsx new file mode 100644 index 00000000000..99f783dbdbc --- /dev/null +++ b/tgui/packages/tgui/interfaces/common/AccessConfig.tsx @@ -0,0 +1,203 @@ +import { sortBy } from 'common/collections'; +import { useState } from 'react'; + +import { Button, Section, Stack, Tabs } from '../../components'; + +type BaseProps = { + accessMod: (ref: string) => void; + denyDep: (ref: string) => void; + grantDep: (ref: string) => void; +}; + +type ConfigProps = { + accesses: Region[]; + denyAll: () => void; + grantAll: () => void; + selectedList: string[]; +} & BaseProps; + +type AccessButtonProps = { + selectedAccess: Region; + selectedAccessEntries: Area[]; + selectedList: string[]; +} & BaseProps; + +export type Region = { + accesses: Area[]; + name: string; +}; + +export type Area = { + desc: string; + ref: string; +}; + +enum ACCESS { + Denied = 0, + Partial = 1, + Granted = 2, +} + +const DIFFMAP = [ + { + icon: 'times-circle', + color: 'bad', + }, + { + icon: 'stop-circle', + color: null, + }, + { + icon: 'check-circle', + color: 'good', + }, +] as const; + +export function AccessConfig(props: ConfigProps) { + const { + accesses = [], + selectedList = [], + accessMod, + grantAll, + denyAll, + grantDep, + denyDep, + } = props; + + const [selectedAccessName, setSelectedAccessName] = useState( + accesses[0]?.name, + ); + + const selectedAccess = + accesses.find((access) => access.name === selectedAccessName) || + accesses[0]; + + const selectedAccessEntries = sortBy((entry: Area) => entry.desc)( + selectedAccess?.accesses || [], + ); + + function checkAccessIcon(accesses: Area[]) { + let oneAccess = false; + let oneInaccess = false; + for (let element of accesses) { + if (selectedList.includes(element.ref)) { + oneAccess = true; + } else { + oneInaccess = true; + } + } + if (!oneAccess && oneInaccess) { + return ACCESS.Denied; + } else if (oneAccess && oneInaccess) { + return ACCESS.Partial; + } else { + return ACCESS.Granted; + } + } + + return ( +
+ + + + } + > + + + + {accesses.map((access) => { + const entries = access.accesses || []; + const icon = DIFFMAP[checkAccessIcon(entries)].icon; + const color = DIFFMAP[checkAccessIcon(entries)].color; + return ( + setSelectedAccessName(access.name)} + > + {access.name} + + ); + })} + + + + + + + +
+ ); +} + +function AccessButtons(props: AccessButtonProps) { + const { + selectedAccessEntries, + selectedList, + accessMod, + grantDep, + denyDep, + selectedAccess, + } = props; + + return ( + + + + + + + + + + + + + +
+ {selectedAccessEntries.map((entry) => ( + accessMod(entry.ref)} + > + {entry.desc} + + ))} +
+
+
+ ); +}