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 (
-
- );
-};
-
-/** @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 (
+
+ );
+}
+
+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."
- />
-
-
-
-
-
-
-
- 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,
- })
- }
- />
-
- );
-};
-
-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('one_access')}
+ >
+ {oneAccess ? 'One' : 'All'}
+
+
+
+
+ act('direc_set', {
+ unres_direction: '1',
+ })
+ }
+ >
+ North
+
+
+ act('direc_set', {
+ unres_direction: '2',
+ })
+ }
+ >
+ South
+
+
+ act('direc_set', {
+ unres_direction: '4',
+ })
+ }
+ >
+ East
+
+
+ act('direc_set', {
+ unres_direction: '8',
+ })
+ }
+ >
+ West
+
+
+
+
+ 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
-
-
+
+
act('gene', {
choice: choiceA,
})
}
- />
-
-
+ >
+ {choiceA}
+
+
+
act('gene', {
choice: choiceB,
})
}
- />
-
-
+ >
+ {choiceB}
+
+
+
)}
);
-};
+}
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: answer.answer,
})
}
- />
+ >
+ {toTitleCase(answer.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
+
}
>
-
-
+
+
act('authorize')}
- />
-
-
+ >
+ AUTHORIZE
+
+
+
act('repeal')}
- />
-
-
+ >
+ REPEAL
+
+
+
@@ -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 (
-
-
-
- act('move_pos', {
- x: -1,
- y: 1,
- })
- }
- />
-
- act('move_pos', {
- x: -1,
- })
- }
- />
-
- act('move_pos', {
- x: -1,
- y: -1,
- })
- }
- />
-
-
-
- act('move_pos', {
- y: 1,
- })
- }
- />
-
- act('set_pos', {
- x: 0,
- y: 0,
- })
- }
- />
-
- act('move_pos', {
- y: -1,
- })
- }
- />
-
-
-
- act('move_pos', {
- x: 1,
- y: 1,
- })
- }
- />
-
- act('move_pos', {
- x: 1,
- })
- }
- />
-
- act('move_pos', {
- x: 1,
- y: -1,
- })
- }
- />
-
-
- );
-};
-
-export const LaunchpadControl = (props) => {
- const { topLevel } = props;
- const { act, data } = useBackend();
- const { x, y, pad_name, range } = data;
- return (
-
- act('rename', {
- name: value,
- })
- }
- />
- }
- level={topLevel ? 1 : 2}
- buttons={
- act('remove')}
- />
- }
- >
-
-
-
-
-
-
-
-
-
- X:
-
-
- act('set_pos', {
- x: value,
- })
- }
- />
-
-
-
- Y:
-
-
- act('set_pos', {
- y: value,
- })
- }
- />
-
-
-
-
-
-
-
- act('launch')}
- />
-
-
- act('pull')}
- />
-
-
-
- );
-};
-
-export const LaunchpadConsole = (props) => {
- const { act, data } = useBackend();
- const { launchpads = [], selected_id } = data;
- return (
-
-
- {(launchpads.length === 0 && (
- No Pads Connected
- )) || (
-
-
-
- {launchpads.map((launchpad) => (
-
- act('select_pad', {
- id: launchpad.id,
- })
- }
- />
- ))}
-
-
-
-
-
- {(selected_id && ) || (
- Please select a pad
- )}
-
-
-
- )}
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/LaunchpadConsole.tsx b/tgui/packages/tgui/interfaces/LaunchpadConsole.tsx
new file mode 100644
index 00000000000..fdad9955894
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/LaunchpadConsole.tsx
@@ -0,0 +1,259 @@
+import { useBackend } from '../backend';
+import {
+ Box,
+ Button,
+ Icon,
+ Input,
+ NoticeBox,
+ NumberInput,
+ Section,
+ Stack,
+ Tabs,
+} from '../components';
+import { Window } from '../layouts';
+
+type Data = {
+ launchpads: LaunchPad[];
+ pad_active: number;
+ pad_name: string;
+ range: number;
+ selected_id: number;
+ selected_pad: string;
+ x: number;
+ y: number;
+};
+
+type LaunchPad = {
+ id: number;
+ name: string;
+};
+
+const buttonConfigs = [
+ [
+ { icon: 'arrow-left', iconRotation: 45, x: -1, y: 1 },
+ { icon: 'arrow-left', x: -1 },
+ { icon: 'arrow-down', iconRotation: 45, x: -1, y: -1 },
+ ],
+ [
+ { icon: 'arrow-up', y: 1 },
+ { text: 'R', x: 0, y: 0 },
+ { icon: 'arrow-down', y: -1 },
+ ],
+ [
+ { icon: 'arrow-up', iconRotation: 45, x: 1, y: 1 },
+ { icon: 'arrow-right', x: 1 },
+ { icon: 'arrow-right', iconRotation: 45, x: 1, y: -1 },
+ ],
+] as const;
+
+export function LaunchpadConsole(props) {
+ const { data } = useBackend();
+ const { launchpads = [], selected_id } = data;
+
+ return (
+
+
+ {launchpads.length === 0 ? (
+ No Pads Connected
+ ) : (
+
+
+
+
+
+
+ {!selected_id ? (
+ Please select a pad
+ ) : (
+
+ )}
+
+
+ )}
+
+
+ );
+}
+
+export function LaunchpadControl(props) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function LaunchpadTabs(props) {
+ const { act, data } = useBackend();
+ const { launchpads = [], selected_id } = data;
+
+ return (
+
+
+ {launchpads.map((pad) => (
+
+ act('select_pad', {
+ id: pad.id,
+ })
+ }
+ >
+ {pad.name}
+
+ ))}
+
+
+ );
+}
+
+function LaunchpadTitle(props) {
+ const { act, data } = useBackend();
+ const { pad_name } = data;
+
+ return (
+
+ );
+}
+
+function LaunchpadButtonPad(props) {
+ const { act } = useBackend();
+
+ return (
+
+
+ {buttonConfigs.map((buttonRow, i) => (
+
+ {buttonRow.map((buttonConfig, j) => (
+
+ act('move_pos', {
+ x: buttonConfig.x,
+ y: buttonConfig.y,
+ })
+ }
+ >
+ {buttonConfig.text}
+
+ ))}
+
+ ))}
+
+
+ );
+}
+
+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 (
+
+
+
+ act('launch')}
+ textAlign="center"
+ >
+ Launch
+
+
+
+ act('pull')}
+ textAlign="center"
+ >
+ Pull
+
+
+
+
+ );
+}
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
-
-
-
-
-
-
-
-
-
- HP
-
-
-
-
-
-
-
-
- act('Attack')}
- content="Attack!"
- />
- act('Heal')}
- content="Heal!"
- />
- act('Recharge_Power')}
- content="Recharge!"
- />
-
-
- act('Start_Game')}
- content="Begin Game"
- />
- act('Dispense_Tickets')}
- content="Claim Tickets"
- />
-
- = 1 ? 'good' : 'normal'}>
- Earned Tickets: {data.TicketCount}
-
-
-
-
- );
-};
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 (
+ <>
+ act('Attack')}
+ >
+ Attack!
+
+ act('Heal')}
+ >
+ Heal!
+
+ act('Recharge_Power')}
+ >
+ Recharge!
+
+
+
+ act('Start_Game')}
+ >
+ Begin Game
+
+ act('Dispense_Tickets')}
+ >
+ Claim Tickets
+
+
+ = 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) => (
{
'NuclearBomb__Button--' + key,
])}
onClick={() => act('keypad', { digit: key })}
- />
+ >
+ {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}
-
-
+
+
act('eject_disk')}
/>
-
-
-
-
+
+
+
+
-
-
+
+
act('arm')}
- />
+ >
+ ARM
+
act('anchor')}
- />
+ >
+ ANCHOR
+
-
-
+
+
);
-};
+}
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 (
-
- act('ChangeBetType', { type: value })}
- >
- {text}
-
-
- );
-};
-
-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}
-
-
-
- act('ChangeBetAmount', {
- amount: 10,
- })
- }
- />
-
- act('ChangeBetAmount', {
- amount: 50,
- })
- }
- />
-
- act('ChangeBetAmount', {
- amount: 100,
- })
- }
- />
-
- act('ChangeBetAmount', {
- amount: 500,
- })
- }
- />
-
-
-
- act('ChangeBetAmount', {
- amount: customBet,
- })
- }
- />
-
-
- setCustomBet(value)}
- />
-
-
-
-
-
-
-
-
- Swipe an ID card with a connected account to spin!
-
-
-
-
-
-
- House Balance:
-
-
- {data.HouseBalance ? data.HouseBalance + ' cr' : 'None'}
-
-
-
- act('anchor')}
- />
-
-
-
- );
-};
-
-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}
+
+
+
+ act('ChangeBetAmount', {
+ amount: 10,
+ })
+ }
+ >
+ Bet 10 cr
+
+
+ act('ChangeBetAmount', {
+ amount: 50,
+ })
+ }
+ >
+ Bet 50 cr
+
+
+ act('ChangeBetAmount', {
+ amount: 100,
+ })
+ }
+ >
+ Bet 100 cr
+
+
+ act('ChangeBetAmount', {
+ amount: 500,
+ })
+ }
+ >
+ Bet 500 cr
+
+
+
+
+ act('ChangeBetAmount', {
+ amount: customBet,
+ })
+ }
+ >
+ Bet custom amount...
+
+
+
+ setCustomBet(value)}
+ />
+
+
+
+
+
+
+
+
+ Swipe an ID card with a connected account to spin!
+
+
+
+
+
+
+ House Balance:
+
+ {HouseBalance ? HouseBalance + ' cr' : 'None'}
+
+
+ act('anchor')}
+ >
+ {IsAnchored ? 'Bolted' : 'Unbolted'}
+
+
+
+
+ );
+}
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 (
+
+ act('ChangeBetType', { type: value })}
+ >
+ {text}
+
+
+ );
+}
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}
- act('PRG_clear_error')} />
+ act('PRG_clear_error')}>Dismiss
@@ -136,11 +135,9 @@ export const SyndicateContractorContent = (props) => {
return (
- act('PRG_login')}
- />
+ act('PRG_login')}>
+ REGISTER USER
+
{!!error && {error}}
@@ -167,11 +164,12 @@ export const SyndicateContractorContent = (props) => {
act('PRG_toggle_info')}
- />
+ >
+ CONTINUE
+
>
);
}
@@ -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
- act('PRG_toggle_info')}
- />
- >
+ buttons={
+ act('PRG_toggle_info')}
+ >
+ View Information Again
+
}
+ 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={
act('PRG_call_extraction')}
- />
+ >
+ Call Extraction
+
}
>
{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`}
act('PRG_contract' + (active ? '_abort' : '-accept'), {
contract_id: contract.id,
})
}
- />
+ >
+ {active ? 'Abort' : 'Accept'}
+
>
}
>
-
- {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 (
-
- grantAll()}
- />
- denyAll()}
- />
- >
- }
- >
-
-
-
- {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}
-
- );
- })}
-
-
-
-
-
- grantDep(selectedAccess.name)}
- />
-
-
- denyDep(selectedAccess.name)}
- />
-
-
- {selectedAccessEntries.map((entry) => (
- accessMod(entry.ref)}
- />
- ))}
-
-
-
- );
-};
-
-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 (
+
+
+ Grant All
+
+
+ Deny All
+
+ >
+ }
+ >
+
+
+
+ {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 (
+
+
+
+
+ grantDep(selectedAccess.name)}
+ >
+ Grant Region
+
+
+
+ denyDep(selectedAccess.name)}
+ >
+ Deny Region
+
+
+
+
+
+
+
+ {selectedAccessEntries.map((entry) => (
+ accessMod(entry.ref)}
+ >
+ {entry.desc}
+
+ ))}
+
+
+
+ );
+}