{genre_songs[genre].map((track) => (
@@ -232,14 +253,14 @@ export const Jukebox = (props) => {
setNewTitle(val)}
+ onChange={(e, val: string) => setNewTitle(val)}
/>
setNewUrl(val)}
+ onChange={(e, val: string) => setNewUrl(val)}
/>
@@ -247,15 +268,15 @@ export const Jukebox = (props) => {
value={newDuration}
minValue={0}
maxValue={3600}
- onChange={(e, val) => setNewDuration(val)}
- format={(val) => formatTime(round(val * 10))}
+ onChange={(e, val: number) => setNewDuration(val)}
+ format={(val) => formatTime(round(val * 10, 0))}
/>
setNewArtist(val)}
+ onChange={(e, val: string) => setNewArtist(val)}
/>
@@ -265,7 +286,7 @@ export const Jukebox = (props) => {
setNewGenre(val)}
+ onChange={(e, val: string) => setNewGenre(val)}
/>
) : (
{newGenre}
@@ -275,7 +296,7 @@ export const Jukebox = (props) => {
handleUnlockGenre()}
+ onClick={() => handleUnlockGenre()}
/>
@@ -283,13 +304,13 @@ export const Jukebox = (props) => {
setNewSecret(!newSecret)}
+ onClick={() => setNewSecret(!newSecret)}
/>
setNewLobby(!newLobby)}
+ onClick={() => setNewLobby(!newLobby)}
/>
diff --git a/tgui/packages/tgui/interfaces/LawManager.jsx b/tgui/packages/tgui/interfaces/LawManager.tsx
similarity index 61%
rename from tgui/packages/tgui/interfaces/LawManager.jsx
rename to tgui/packages/tgui/interfaces/LawManager.tsx
index de820e06a4..3632166b60 100644
--- a/tgui/packages/tgui/interfaces/LawManager.jsx
+++ b/tgui/packages/tgui/interfaces/LawManager.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend, useSharedState } from '../backend';
import {
Button,
@@ -10,16 +12,63 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ ion_law_nr: string;
+ ion_law: string;
+ zeroth_law: string;
+ inherent_law: string;
+ supplied_law: string;
+ supplied_law_position: number;
+ zeroth_laws: law[];
+ ion_laws: law[];
+ inherent_laws: law[];
+ supplied_laws: law[];
+ has_zeroth_laws: number;
+ has_ion_laws: number;
+ has_inherent_laws: number;
+ has_supplied_laws: number;
+ isAI: BooleanLike;
+ isMalf: BooleanLike;
+ isSlaved: BooleanLike;
+ isAdmin: BooleanLike;
+ channel: string;
+ channels: { channel: string }[];
+ law_sets: law_pack[];
+};
+
+type law_pack = {
+ name: string;
+ header: string;
+ ref: string;
+ laws: {
+ zeroth_laws: law[];
+ has_zeroth_laws: number;
+ ion_laws: law[];
+ has_ion_laws: number;
+ inherent_laws: law[];
+ has_inherent_laws: number;
+ supplied_laws: law[];
+ has_supplied_laws: number;
+ };
+};
+
+type law = {
+ law: string;
+ index: number;
+ state: number;
+ ref: string;
+ zero: boolean; // Local UI var
+};
+
export const LawManager = (props) => {
- const { act, data } = useBackend();
+ const { data } = useBackend
();
const { isSlaved } = data;
return (
- {(isSlaved && Law-synced to {isSlaved}) ||
- null}
+ {isSlaved ? Law-synced to {isSlaved} : ''}
@@ -27,7 +76,12 @@ export const LawManager = (props) => {
};
const LawManagerContent = (props) => {
- const [tabIndex, setTabIndex] = useSharedState('lawsTabIndex', 0);
+ const [tabIndex, setTabIndex] = useSharedState('lawsTabIndex', 0);
+
+ const tab: React.JSX.Element[] = [];
+
+ tab[0] = ;
+ tab[1] = ;
return (
<>
@@ -39,14 +93,13 @@ const LawManagerContent = (props) => {
Law Sets
- {(tabIndex === 0 && ) || null}
- {(tabIndex === 1 && ) || null}
+ {tab[tabIndex]}
>
);
};
const LawManagerLaws = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
ion_law_nr,
@@ -79,19 +132,22 @@ const LawManagerLaws = (props) => {
return (
- {(has_ion_laws && (
+ {has_ion_laws ? (
- )) ||
- null}
- {((has_zeroth_laws || has_inherent_laws) && (
+ ) : (
+ ''
+ )}
+ {has_zeroth_laws || has_inherent_laws ? (
- )) ||
- null}
- {(has_supplied_laws && (
+ ) : (
+ ''
+ )}
+ {has_supplied_laws ? (
- )) ||
- null}
-
+ ) : (
+ ''
+ )}
+
{channels.map((chan) => (
@@ -111,18 +167,19 @@ const LawManagerLaws = (props) => {
State Laws
- {(isAI && (
+ {isAI ? (
- )) ||
- null}
+ ) : (
+ ''
+ )}
- {(isMalf && (
-
+ {isMalf ? (
+
Type
@@ -130,14 +187,14 @@ const LawManagerLaws = (props) => {
Index
Add
- {(isAdmin && !has_zeroth_laws && (
+ {isAdmin && !has_zeroth_laws ? (
Zero
+ onChange={(e, val: string) =>
act('change_zeroth_law', { val: val })
}
/>
@@ -149,15 +206,18 @@ const LawManagerLaws = (props) => {
- )) ||
- null}
+ ) : (
+ ''
+ )}
Ion
act('change_ion_law', { val: val })}
+ onChange={(e, val: string) =>
+ act('change_ion_law', { val: val })
+ }
/>
N/A
@@ -173,7 +233,7 @@ const LawManagerLaws = (props) => {
+ onChange={(e, val: string) =>
act('change_inherent_law', { val: val })
}
/>
@@ -191,7 +251,7 @@ const LawManagerLaws = (props) => {
+ onChange={(e, val: string) =>
act('change_supplied_law', { val: val })
}
/>
@@ -212,39 +272,47 @@ const LawManagerLaws = (props) => {
- )) ||
- null}
+ ) : (
+ ''
+ )}
);
};
-const LawsTable = (props) => {
- const { act, data } = useBackend();
+const LawsTable = (props: {
+ title: string;
+ noButtons?: BooleanLike;
+ [rest: string]: any;
+}) => {
+ const { act, data } = useBackend();
const { isMalf, isAdmin } = data;
const { laws, title, noButtons, ...rest } = props;
return (
-
+
Index
Law
- {(!noButtons && State) || null}
- {(isMalf && !noButtons && (
+ {!noButtons ? State : ''}
+ {isMalf && !noButtons ? (
<>
Edit
Delete
>
- )) ||
- null}
+ ) : (
+ ''
+ )}
- {laws.map((law) => (
+ {laws.map((law: law) => (
{law.index}.
- {law.law}
- {(!noButtons && (
+
+ {law.law}
+
+ {!noButtons ? (
- )) ||
- null}
- {(isMalf && !noButtons && (
+ ) : (
+ ''
+ )}
+ {isMalf && !noButtons ? (
<>
>
- )) ||
- null}
+ ) : (
+ ''
+ )}
))}
@@ -291,9 +361,9 @@ const LawsTable = (props) => {
};
const LawManagerLawSets = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
- const { isMalf, law_sets } = data;
+ const { isMalf, law_sets, ion_law_nr } = data;
return (
<>
@@ -301,60 +371,63 @@ const LawManagerLawSets = (props) => {
Remember: Stating laws other than those currently loaded may be grounds
for decommissioning! - NanoTrasen
- {(law_sets.length &&
- law_sets.map((laws) => (
-
-
-
- >
- }
- >
- {(laws.laws.has_ion_laws && (
-
- )) ||
- null}
- {((laws.laws.has_zeroth_laws || laws.laws.has_inherent_laws) && (
-
- )) ||
- null}
- {(laws.laws.has_supplied_laws && (
-
- )) ||
- null}
-
- ))) ||
- null}
+ {law_sets.length
+ ? law_sets.map((laws) => (
+
+
+
+ >
+ }
+ >
+ {laws.laws.has_ion_laws ? (
+
+ ) : (
+ ''
+ )}
+ {laws.laws.has_zeroth_laws || laws.laws.has_inherent_laws ? (
+
+ ) : (
+ ''
+ )}
+ {laws.laws.has_supplied_laws ? (
+
+ ) : (
+ ''
+ )}
+
+ ))
+ : ''}
>
);
};
diff --git a/tgui/packages/tgui/interfaces/LookingGlass.jsx b/tgui/packages/tgui/interfaces/LookingGlass.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/LookingGlass.jsx
rename to tgui/packages/tgui/interfaces/LookingGlass.tsx
index 4ddbc51f15..6182fe1f35 100644
--- a/tgui/packages/tgui/interfaces/LookingGlass.jsx
+++ b/tgui/packages/tgui/interfaces/LookingGlass.tsx
@@ -1,9 +1,18 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ supportedPrograms: string[];
+ currentProgram: string;
+ immersion: BooleanLike;
+ gravity: BooleanLike;
+};
+
export const LookingGlass = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { supportedPrograms, currentProgram, immersion, gravity } = data;
diff --git a/tgui/packages/tgui/interfaces/MechaControlConsole.jsx b/tgui/packages/tgui/interfaces/MechaControlConsole.tsx
similarity index 86%
rename from tgui/packages/tgui/interfaces/MechaControlConsole.jsx
rename to tgui/packages/tgui/interfaces/MechaControlConsole.tsx
index d625017f56..41f0a86be4 100644
--- a/tgui/packages/tgui/interfaces/MechaControlConsole.jsx
+++ b/tgui/packages/tgui/interfaces/MechaControlConsole.tsx
@@ -12,17 +12,37 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ beacons: {
+ ref: string;
+ charge: string;
+ name: string;
+ health: number;
+ maxHealth: number;
+ cell: string;
+ cellCharge: number;
+ cellMaxCharge: number;
+ airtank: number;
+ pilot: string;
+ location: string;
+ active: string | null;
+ cargoUsed: number;
+ cargoMax: number;
+ }[];
+ stored_data: { time: string; year: number; message: string }[];
+};
+
export const MechaControlConsole = (props) => {
- const { act, data } = useBackend();
- const { beacons, stored_data } = data;
+ const { act, data } = useBackend();
+ const { beacons = [], stored_data = [] } = data;
return (
- {(stored_data.length && (
+ {stored_data.length ? (
act('clear_log')} />
@@ -38,8 +58,9 @@ export const MechaControlConsole = (props) => {
))}
- )) ||
- null}
+ ) : (
+ ''
+ )}
{(beacons.length &&
beacons.map((beacon) => (
{
{beacon.active || 'None'}
- {(beacon.cargoMax && (
+ {beacon.cargoMax ? (
{
maxValue={beacon.cargoMax}
/>
- )) ||
- null}
+ ) : (
+ ''
+ )}
))) || No mecha beacons found.}
diff --git a/tgui/packages/tgui/interfaces/Medbot.jsx b/tgui/packages/tgui/interfaces/Medbot.tsx
similarity index 85%
rename from tgui/packages/tgui/interfaces/Medbot.jsx
rename to tgui/packages/tgui/interfaces/Medbot.tsx
index f3cb03315c..b4d09e6cc6 100644
--- a/tgui/packages/tgui/interfaces/Medbot.jsx
+++ b/tgui/packages/tgui/interfaces/Medbot.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
Box,
@@ -9,8 +11,25 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ on: BooleanLike;
+ open: BooleanLike;
+ beaker: BooleanLike;
+ beaker_total: number;
+ beaker_max: number;
+ locked: BooleanLike;
+ heal_threshold: number | null;
+ heal_threshold_max: number;
+ injection_amount_min: number;
+ injection_amount: number | null;
+ injection_amount_max: number;
+ use_beaker: BooleanLike;
+ declare_treatment: BooleanLike;
+ vocal: BooleanLike;
+};
+
export const Medbot = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
on,
@@ -82,7 +101,9 @@ export const Medbot = (props) => {
minValue={0}
maxValue={heal_threshold_max}
value={heal_threshold}
- onDrag={(e, val) => act('adj_threshold', { val: val })}
+ onDrag={(e, val: number) =>
+ act('adj_threshold', { val: val })
+ }
/>
@@ -91,7 +112,7 @@ export const Medbot = (props) => {
minValue={injection_amount_min}
maxValue={injection_amount_max}
value={injection_amount}
- onDrag={(e, val) => act('adj_inject', { val: val })}
+ onDrag={(e, val: number) => act('adj_inject', { val: val })}
/>
diff --git a/tgui/packages/tgui/interfaces/MentorTicketPanel.tsx b/tgui/packages/tgui/interfaces/MentorTicketPanel.tsx
index 61981450e6..bb338b486e 100644
--- a/tgui/packages/tgui/interfaces/MentorTicketPanel.tsx
+++ b/tgui/packages/tgui/interfaces/MentorTicketPanel.tsx
@@ -28,7 +28,6 @@ export const MentorTicketPanel = (props) => {
const { act, data } = useBackend();
const {
id,
- title,
name,
state,
opened_at,
diff --git a/tgui/packages/tgui/interfaces/MessageMonitor.jsx b/tgui/packages/tgui/interfaces/MessageMonitor.jsx
deleted file mode 100644
index 74f68a9abe..0000000000
--- a/tgui/packages/tgui/interfaces/MessageMonitor.jsx
+++ /dev/null
@@ -1,440 +0,0 @@
-import { decodeHtmlEntities } from 'common/string';
-import { useState } from 'react';
-
-import { useBackend } from '../backend';
-import {
- Box,
- Button,
- Dropdown,
- Flex,
- Icon,
- Input,
- LabeledList,
- Section,
- Tabs,
-} from '../components';
-import { Window } from '../layouts';
-import { FullscreenNotice } from './common/FullscreenNotice';
-import { TemporaryNotice } from './common/TemporaryNotice';
-
-export const MessageMonitor = (props) => {
- const { act, data } = useBackend();
-
- const { auth, linkedServer, message, hacking, emag } = data;
-
- let body;
- if (hacking || emag) {
- body = ;
- } else if (!auth) {
- body = ;
- } else if (linkedServer) {
- body = ;
- } else {
- body = ERROR;
- }
-
- return (
-
-
-
- {body}
-
-
- );
-};
-
-const MessageMonitorHack = (props) => {
- const { act, data } = useBackend();
-
- const { isMalfAI } = data;
-
- return (
-
- {isMalfAI ? (
-
- Brute-forcing for server key. It will take 20 seconds for every
- character that the password has.
-
- ) : (
-
- 01000010011100100111010101110100011001010010110
-
- 10110011001101111011100100110001101101001011011100110011
-
- 10010000001100110011011110111001000100000011100110110010
-
- 10111001001110110011001010111001000100000011010110110010
-
- 10111100100101110001000000100100101110100001000000111011
-
- 10110100101101100011011000010000001110100011000010110101
-
- 10110010100100000001100100011000000100000011100110110010
-
- 10110001101101111011011100110010001110011001000000110011
-
- 00110111101110010001000000110010101110110011001010111001
-
- 00111100100100000011000110110100001100001011100100110000
-
- 10110001101110100011001010111001000100000011101000110100
-
- 00110000101110100001000000111010001101000011001010010000
-
- 00111000001100001011100110111001101110111011011110111001
-
- 00110010000100000011010000110000101110011001011100010000
-
- 00100100101101110001000000111010001101000011001010010000
-
- 00110110101100101011000010110111001110100011010010110110
-
- 10110010100101100001000000111010001101000011010010111001
-
- 10010000001100011011011110110111001110011011011110110110
-
- 00110010100100000011000110110000101101110001000000111001
-
- 00110010101110110011001010110000101101100001000000111100
-
- 10110111101110101011100100010000001110100011100100111010
-
- 10110010100100000011010010110111001110100011001010110111
-
- 00111010001101001011011110110111001110011001000000110100
-
- 10110011000100000011110010110111101110101001000000110110
-
- 00110010101110100001000000111001101101111011011010110010
-
- 10110111101101110011001010010000001100001011000110110001
-
- 10110010101110011011100110010000001101001011101000010111
-
- 00010000001001101011000010110101101100101001000000111001
-
- 10111010101110010011001010010000001101110011011110010000
-
- 00110100001110101011011010110000101101110011100110010000
-
- 00110010101101110011101000110010101110010001000000111010
-
- 00110100001100101001000000111001001101111011011110110110
-
- 10010000001100100011101010111001001101001011011100110011
-
- 10010000001110100011010000110000101110100001000000111010
-
- 001101001011011010110010100101110
-
- )}
-
- );
-};
-
-const MessageMonitorLogin = (props) => {
- const { act, data } = useBackend();
-
- const { isMalfAI } = data;
-
- return (
-
-
-
- Unauthorized
-
-
- Decryption Key:
- act('auth', { key: val })}
- />
-
- {!!isMalfAI && (
-
- )}
-
- Please authenticate with the server in order to show additional options.
-
-
- );
-};
-
-const MessageMonitorContent = (props) => {
- const { act, data } = useBackend();
-
- const { linkedServer } = data;
-
- const [tabIndex, setTabIndex] = useState(0);
-
- let body;
- if (tabIndex === 0) {
- body = ;
- } else if (tabIndex === 1) {
- body = ;
- } else if (tabIndex === 2) {
- body = ;
- } else if (tabIndex === 3) {
- body = ;
- } else if (tabIndex === 4) {
- body = ;
- }
-
- return (
- <>
-
- setTabIndex(0)}
- >
- Main Menu
-
- setTabIndex(1)}
- >
- Message Logs
-
- setTabIndex(2)}
- >
- Request Logs
-
- setTabIndex(3)}
- >
- Admin Messaging
-
- setTabIndex(4)}
- >
- Spam Filter
-
- act('deauth')}>
- Log Out
-
-
- {body}
- >
- );
-};
-
-const MessageMonitorMain = (props) => {
- const { act, data } = useBackend();
-
- const { linkedServer } = data;
-
- return (
-
-
-
- >
- }
- >
-
-
- Good
-
-
-
-
- Clear Message Logs
-
-
- Clear Request Logs
-
-
- );
-};
-
-const MessageMonitorLogs = (props) => {
- const { act, data } = useBackend();
-
- const { logs, pda, rc } = props;
-
- return (
- act(pda ? 'del_pda' : 'del_rc')}
- >
- Delete All
-
- }
- >
-
- {logs.map((log, i) => (
-
- ' + log.recipient}
- buttons={
-
- act('delete', {
- id: log.ref,
- type: rc ? 'rc' : 'pda',
- })
- }
- />
- }
- >
- {rc ? (
-
-
- {log.message}
-
-
- {decodeHtmlEntities(log.id_auth)}
-
- {log.stamp}
-
- ) : (
- log.message
- )}
-
-
- ))}
-
-
- );
-};
-
-const MessageMonitorAdmin = (props) => {
- const { act, data } = useBackend();
-
- const {
- possibleRecipients,
- customsender,
- customrecepient,
- customjob,
- custommessage,
- } = data;
-
- const recipientOptions = Object.keys(possibleRecipients);
-
- return (
-
- );
-};
-
-const MessageMonitorSpamFilter = (props) => {
- const { act, data } = useBackend();
-
- const { linkedServer } = data;
-
- return (
-
-
- {linkedServer.spamFilter.map((spam) => (
- act('deltoken', { deltoken: spam.index })}
- >
- Delete
-
- }
- >
- {spam.token}
-
- ))}
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorContent.tsx b/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorContent.tsx
new file mode 100644
index 0000000000..744bb72097
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorContent.tsx
@@ -0,0 +1,73 @@
+import { useState } from 'react';
+
+import { useBackend } from '../../backend';
+import { Box, Icon, Tabs } from '../../components';
+import {
+ MessageMonitorAdmin,
+ MessageMonitorLogs,
+ MessageMonitorMain,
+ MessageMonitorSpamFilter,
+} from './MessageMonitorTabs';
+import { Data } from './types';
+
+export const MessageMonitorContent = (props) => {
+ const { act, data } = useBackend();
+
+ const { linkedServer } = data;
+
+ const [tabIndex, setTabIndex] = useState(0);
+
+ let tab: React.JSX.Element[] = [];
+
+ tab[0] = ;
+ tab[1] = ;
+ tab[2] = ;
+ tab[3] = ;
+ tab[4] = ;
+
+ return (
+ <>
+
+ setTabIndex(0)}
+ >
+ Main Menu
+
+ setTabIndex(1)}
+ >
+ Message Logs
+
+ setTabIndex(2)}
+ >
+ Request Logs
+
+ setTabIndex(3)}
+ >
+ Admin Messaging
+
+ setTabIndex(4)}
+ >
+ Spam Filter
+
+ act('deauth')}>
+ Log Out
+
+
+ {tab[tabIndex]}
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorHack.tsx b/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorHack.tsx
new file mode 100644
index 0000000000..6e76a83eec
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorHack.tsx
@@ -0,0 +1,93 @@
+import { useBackend } from '../../backend';
+import { Box } from '../../components';
+import { FullscreenNotice } from '../common/FullscreenNotice';
+import { Data } from './types';
+
+export const MessageMonitorHack = (props) => {
+ const { data } = useBackend();
+
+ const { isMalfAI } = data;
+
+ return (
+
+ {isMalfAI ? (
+
+ Brute-forcing for server key. It will take 20 seconds for every
+ character that the password has.
+
+ ) : (
+
+ 01000010011100100111010101110100011001010010110
+
+ 10110011001101111011100100110001101101001011011100110011
+
+ 10010000001100110011011110111001000100000011100110110010
+
+ 10111001001110110011001010111001000100000011010110110010
+
+ 10111100100101110001000000100100101110100001000000111011
+
+ 10110100101101100011011000010000001110100011000010110101
+
+ 10110010100100000001100100011000000100000011100110110010
+
+ 10110001101101111011011100110010001110011001000000110011
+
+ 00110111101110010001000000110010101110110011001010111001
+
+ 00111100100100000011000110110100001100001011100100110000
+
+ 10110001101110100011001010111001000100000011101000110100
+
+ 00110000101110100001000000111010001101000011001010010000
+
+ 00111000001100001011100110111001101110111011011110111001
+
+ 00110010000100000011010000110000101110011001011100010000
+
+ 00100100101101110001000000111010001101000011001010010000
+
+ 00110110101100101011000010110111001110100011010010110110
+
+ 10110010100101100001000000111010001101000011010010111001
+
+ 10010000001100011011011110110111001110011011011110110110
+
+ 00110010100100000011000110110000101101110001000000111001
+
+ 00110010101110110011001010110000101101100001000000111100
+
+ 10110111101110101011100100010000001110100011100100111010
+
+ 10110010100100000011010010110111001110100011001010110111
+
+ 00111010001101001011011110110111001110011001000000110100
+
+ 10110011000100000011110010110111101110101001000000110110
+
+ 00110010101110100001000000111001101101111011011010110010
+
+ 10110111101101110011001010010000001100001011000110110001
+
+ 10110010101110011011100110010000001101001011101000010111
+
+ 00010000001001101011000010110101101100101001000000111001
+
+ 10111010101110010011001010010000001101110011011110010000
+
+ 00110100001110101011011010110000101101110011100110010000
+
+ 00110010101101110011101000110010101110010001000000111010
+
+ 00110100001100101001000000111001001101111011011110110110
+
+ 10010000001100100011101010111001001101001011011100110011
+
+ 10010000001110100011010000110000101110100001000000111010
+
+ 001101001011011010110010100101110
+
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorLogin.tsx b/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorLogin.tsx
new file mode 100644
index 0000000000..39e09203a7
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorLogin.tsx
@@ -0,0 +1,40 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Icon, Input } from '../../components';
+import { FullscreenNotice } from '../common/FullscreenNotice';
+import { Data } from './types';
+
+export const MessageMonitorLogin = (props) => {
+ const { act, data } = useBackend();
+
+ const { isMalfAI } = data;
+
+ return (
+
+
+
+ Unauthorized
+
+
+ Decryption Key:
+ act('auth', { key: val })}
+ />
+
+ {!!isMalfAI && (
+
+ )}
+
+ Please authenticate with the server in order to show additional options.
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorTabs.tsx b/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorTabs.tsx
new file mode 100644
index 0000000000..57cdd89c55
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorTabs.tsx
@@ -0,0 +1,230 @@
+import { BooleanLike } from 'common/react';
+import { decodeHtmlEntities } from 'common/string';
+
+import { useBackend } from '../../backend';
+import {
+ Box,
+ Button,
+ Dropdown,
+ Flex,
+ Input,
+ LabeledList,
+ Section,
+} from '../../components';
+import { Data } from './types';
+
+export const MessageMonitorMain = (props) => {
+ const { act, data } = useBackend();
+
+ const { linkedServer } = data;
+
+ return (
+
+
+
+ >
+ }
+ >
+
+
+ Good
+
+
+
+
+ Clear Message Logs
+
+
+ Clear Request Logs
+
+
+ );
+};
+
+export const MessageMonitorLogs = (props: {
+ logs: {
+ ref: string;
+ sender: string;
+ recipient: string;
+ message: string;
+ stamp?: string;
+ id_auth?: string;
+ priority?: string;
+ }[];
+ pda?: BooleanLike;
+ rc?: BooleanLike;
+}) => {
+ const { act } = useBackend();
+
+ const { logs, pda, rc } = props;
+
+ return (
+ act(pda ? 'del_pda' : 'del_rc')}
+ >
+ Delete All
+
+ }
+ >
+
+ {logs.map((log, i) => (
+
+ ' + log.recipient}
+ buttons={
+
+ act('delete', {
+ id: log.ref,
+ type: rc ? 'rc' : 'pda',
+ })
+ }
+ />
+ }
+ >
+ {rc ? (
+
+
+ {log.message}
+
+
+ {decodeHtmlEntities(log.id_auth)}
+
+ {log.stamp}
+
+ ) : (
+ log.message
+ )}
+
+
+ ))}
+
+
+ );
+};
+
+export const MessageMonitorAdmin = (props) => {
+ const { act, data } = useBackend();
+
+ const {
+ possibleRecipients,
+ customsender,
+ customrecepient,
+ customjob,
+ custommessage,
+ } = data;
+
+ const recipientOptions = Object.keys(possibleRecipients);
+
+ return (
+
+ );
+};
+
+export const MessageMonitorSpamFilter = (props) => {
+ const { act, data } = useBackend();
+
+ const { linkedServer } = data;
+
+ return (
+
+
+ {linkedServer.spamFilter.map((spam) => (
+ act('deltoken', { deltoken: spam.index })}
+ >
+ Delete
+
+ }
+ >
+ {spam.token}
+
+ ))}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/MessageMonitor/index.tsx b/tgui/packages/tgui/interfaces/MessageMonitor/index.tsx
new file mode 100644
index 0000000000..11b59c7c7b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MessageMonitor/index.tsx
@@ -0,0 +1,34 @@
+import { useBackend } from '../../backend';
+import { Box } from '../../components';
+import { Window } from '../../layouts';
+import { TemporaryNotice } from '../common/TemporaryNotice';
+import { MessageMonitorContent } from './MessageMonitorContent';
+import { MessageMonitorHack } from './MessageMonitorHack';
+import { MessageMonitorLogin } from './MessageMonitorLogin';
+import { Data } from './types';
+
+export const MessageMonitor = (props) => {
+ const { data } = useBackend();
+
+ const { auth, linkedServer, hacking, emag } = data;
+
+ let body: React.JSX.Element;
+ if (hacking || emag) {
+ body = ;
+ } else if (!auth) {
+ body = ;
+ } else if (linkedServer) {
+ body = ;
+ } else {
+ body = ERROR;
+ }
+
+ return (
+
+
+
+ {body}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/MessageMonitor/types.ts b/tgui/packages/tgui/interfaces/MessageMonitor/types.ts
new file mode 100644
index 0000000000..9ec9d76c23
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MessageMonitor/types.ts
@@ -0,0 +1,38 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ customsender: string;
+ customrecepient: string;
+ customjob: string;
+ custommessage: string;
+ temp: string | null;
+ hacking: BooleanLike;
+ emag: BooleanLike;
+ auth: BooleanLike;
+ linkedServer: {
+ active: BooleanLike;
+ broke: BooleanLike;
+ pda_msgs: pda_msgs[];
+ rc_msgs: rc_message[];
+ spamFilter: { index: number; token: string }[];
+ };
+ possibleRecipients: Record[];
+ isMalfAI: BooleanLike;
+};
+
+type pda_msgs = {
+ ref: string;
+ sender: string;
+ recipient: string;
+ message: string;
+};
+
+type rc_message = {
+ ref: string;
+ sender: string;
+ recipient: string;
+ message: string;
+ stamp: string;
+ id_auth: string;
+ priority: string;
+};
diff --git a/tgui/packages/tgui/interfaces/Microwave.jsx b/tgui/packages/tgui/interfaces/Microwave.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/Microwave.jsx
rename to tgui/packages/tgui/interfaces/Microwave.tsx
index 6b40c58074..e087adc9e9 100644
--- a/tgui/packages/tgui/interfaces/Microwave.jsx
+++ b/tgui/packages/tgui/interfaces/Microwave.tsx
@@ -1,9 +1,18 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ broken: BooleanLike;
+ operating: BooleanLike;
+ dirty: BooleanLike;
+ items: { name: string; amt: number; extra: string }[];
+};
+
export const Microwave = (props) => {
- const { act, config, data } = useBackend();
+ const { act, config, data } = useBackend();
const { broken, operating, dirty, items } = data;
@@ -35,7 +44,6 @@ export const Microwave = (props) => {
)) ||
(items.length && (
diff --git a/tgui/packages/tgui/interfaces/MiningOreProcessingConsole.jsx b/tgui/packages/tgui/interfaces/MiningOreProcessingConsole.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/MiningOreProcessingConsole.jsx
rename to tgui/packages/tgui/interfaces/MiningOreProcessingConsole.tsx
index c37d74c7cd..169e171f8d 100644
--- a/tgui/packages/tgui/interfaces/MiningOreProcessingConsole.jsx
+++ b/tgui/packages/tgui/interfaces/MiningOreProcessingConsole.tsx
@@ -1,3 +1,4 @@
+import { BooleanLike } from 'common/react';
import { toTitleCase } from 'common/string';
import { useBackend } from '../backend';
@@ -12,10 +13,23 @@ import {
import { Window } from '../layouts';
import { MiningUser } from './common/Mining';
-export const MiningOreProcessingConsole = (props) => {
- const { act, data } = useBackend();
+type Data = {
+ unclaimedPoints: number;
+ ores: {
+ ore: string;
+ name: string;
+ amount: number;
+ processing: number;
+ }[];
+ showAllOres: BooleanLike;
+ power: BooleanLike;
+ speed: BooleanLike;
+};
- const { unclaimedPoints, ores, showAllOres, power, speed } = data;
+export const MiningOreProcessingConsole = (props) => {
+ const { act, data } = useBackend();
+
+ const { unclaimedPoints, power, speed } = data;
return (
@@ -113,8 +127,8 @@ const oreSorter = (a, b) => {
};
const MOPCOres = (props) => {
- const { act, data } = useBackend();
- const { ores, showAllOres, power } = data;
+ const { act, data } = useBackend();
+ const { ores, showAllOres } = data;
return (
{
(ore.processing === 0 && 'red') ||
(ore.processing === 1 && 'green') ||
(ore.processing === 2 && 'blue') ||
- (ore.processing === 3 && 'yellow')
+ (ore.processing === 3 && 'yellow') ||
+ undefined
}
options={processingOptions}
selected={processingOptions[ore.processing]}
diff --git a/tgui/packages/tgui/interfaces/MiningStackingConsole.jsx b/tgui/packages/tgui/interfaces/MiningStackingConsole.tsx
similarity index 92%
rename from tgui/packages/tgui/interfaces/MiningStackingConsole.jsx
rename to tgui/packages/tgui/interfaces/MiningStackingConsole.tsx
index 7db2960f4a..7a797473db 100644
--- a/tgui/packages/tgui/interfaces/MiningStackingConsole.jsx
+++ b/tgui/packages/tgui/interfaces/MiningStackingConsole.tsx
@@ -10,8 +10,13 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ stacktypes: { type: string; amt: number }[];
+ stackingAmt: number;
+};
+
export const MiningStackingConsole = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { stacktypes, stackingAmt } = data;
diff --git a/tgui/packages/tgui/interfaces/MiningVendor.jsx b/tgui/packages/tgui/interfaces/MiningVendor.tsx
similarity index 74%
rename from tgui/packages/tgui/interfaces/MiningVendor.jsx
rename to tgui/packages/tgui/interfaces/MiningVendor.tsx
index 151ed8a193..10502a73a7 100644
--- a/tgui/packages/tgui/interfaces/MiningVendor.jsx
+++ b/tgui/packages/tgui/interfaces/MiningVendor.tsx
@@ -14,10 +14,19 @@ import {
import { Window } from '../layouts';
import { MiningUser } from './common/Mining';
+type Data = {
+ has_id: boolean;
+ id: { id: string; points: number };
+ items: Record;
+};
+
+type sortable = { name: string; affordable: number; price: number };
+
const sortTypes = {
- Alphabetical: (a, b) => a.name > b.name,
- 'By availability': (a, b) => -(a.affordable - b.affordable),
- 'By price': (a, b) => a.price - b.price,
+ Alphabetical: (a: sortable, b: sortable) => a.name > b.name,
+ 'By availability': (a: sortable, b: sortable) =>
+ -(a.affordable - b.affordable),
+ 'By price': (a: sortable, b: sortable) => a.price - b.price,
};
export const MiningVendor = (props) => {
@@ -25,15 +34,15 @@ export const MiningVendor = (props) => {
const [sortOrder, setSortOrder] = useState('Alphabetical');
const [descending, setDescending] = useState(false);
- function handleSearchText(value) {
+ function handleSearchText(value: string) {
setSearchText(value);
}
- function handleSortOrder(value) {
+ function handleSortOrder(value: string) {
setSortOrder(value);
}
- function handleDescending(value) {
+ function handleDescending(value: boolean) {
setDescending(value);
}
@@ -53,20 +62,21 @@ export const MiningVendor = (props) => {
searchText={searchText}
sortOrder={sortOrder}
descending={descending}
- onSearchText={handleSearchText}
- onSortOrder={handleSortOrder}
- onDescending={handleDescending}
/>
);
};
-const MiningVendorItems = (props) => {
- const { act, data } = useBackend();
+const MiningVendorItems = (props: {
+ searchText: string;
+ sortOrder: string;
+ descending: boolean;
+}) => {
+ const { act, data } = useBackend();
const { has_id, id, items } = data;
// Search thingies
- const searcher = createSearch(props.searchText, (item) => {
+ const searcher = createSearch(props.searchText, (item: sortable) => {
return item[0];
});
@@ -75,7 +85,7 @@ const MiningVendorItems = (props) => {
let items_in_cat = Object.entries(kv[1])
.filter(searcher)
.map((kv2) => {
- kv2[1].affordable = has_id && id.points >= kv2[1].price;
+ kv2[1].affordable = +(has_id && id.points >= kv2[1].price);
return kv2[1];
})
.sort(sortTypes[props.sortOrder]);
@@ -108,20 +118,28 @@ const MiningVendorItems = (props) => {
);
};
-const MiningVendorSearch = (props) => {
+const MiningVendorSearch = (props: {
+ searchText: string;
+ sortOrder: string;
+ descending: boolean;
+ onSearchText: Function;
+ onSortOrder: Function;
+ onDescending: Function;
+}) => {
return (
props.onSearchText(value)}
/>
{
);
};
-const MiningVendorItemsCategory = (props) => {
- const { act, data } = useBackend();
+const MiningVendorItemsCategory = (props: {
+ key: string;
+ title: string;
+ items: sortable[];
+}) => {
+ const { act, data } = useBackend();
+
+ const { has_id, id } = data;
+
const { title, items, ...rest } = props;
return (
{items.map((item) => (
{
{item.name}
@@ -332,7 +356,7 @@ const GeneralMobSettings = (props) => {
@@ -364,8 +388,6 @@ const GeneralMobSettings = (props) => {
};
const VoreMobSettings = (props) => {
- const { act, data } = useBackend();
-
return (
This Tab is still under construction!
diff --git a/tgui/packages/tgui/interfaces/NTNetRelay.jsx b/tgui/packages/tgui/interfaces/NTNetRelay.tsx
similarity index 84%
rename from tgui/packages/tgui/interfaces/NTNetRelay.jsx
rename to tgui/packages/tgui/interfaces/NTNetRelay.tsx
index f1ab3747ef..74400fa60c 100644
--- a/tgui/packages/tgui/interfaces/NTNetRelay.jsx
+++ b/tgui/packages/tgui/interfaces/NTNetRelay.tsx
@@ -1,14 +1,23 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, Icon, LabeledList, Section } from '../components';
import { Window } from '../layouts';
import { FullscreenNotice } from './common/FullscreenNotice';
+type Data = {
+ enabled: BooleanLike;
+ dos_capacity: number;
+ dos_overload: number;
+ dos_crashed: BooleanLike;
+};
+
export const NTNetRelay = (props) => {
- const { act, data } = useBackend();
+ const { data } = useBackend();
- const { dos_crashed, enabled, dos_overload, dos_capacity } = data;
+ const { dos_crashed } = data;
- let body = ;
+ let body: React.JSX.Element = ;
if (dos_crashed) {
body = ;
@@ -22,9 +31,9 @@ export const NTNetRelay = (props) => {
};
const NTNetRelayContent = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
- const { dos_crashed, enabled, dos_overload, dos_capacity } = data;
+ const { enabled, dos_overload, dos_capacity } = data;
return (
{
};
const NTNetRelayCrash = (props) => {
- const { act, data } = useBackend();
+ const { act } = useBackend();
return (
diff --git a/tgui/packages/tgui/interfaces/NtosAccessDecrypter.tsx b/tgui/packages/tgui/interfaces/NtosAccessDecrypter.tsx
index 1fbb9b7657..a6ccf8a55b 100644
--- a/tgui/packages/tgui/interfaces/NtosAccessDecrypter.tsx
+++ b/tgui/packages/tgui/interfaces/NtosAccessDecrypter.tsx
@@ -6,7 +6,7 @@ import { NtosWindow } from '../layouts';
import { IdentificationComputerRegions } from './IdentificationComputer';
type Data = {
- message: string;
+ message: string | null;
running: BooleanLike;
rate: number;
factor: number;
diff --git a/tgui/packages/tgui/interfaces/NtosArcade.jsx b/tgui/packages/tgui/interfaces/NtosArcade.tsx
similarity index 75%
rename from tgui/packages/tgui/interfaces/NtosArcade.jsx
rename to tgui/packages/tgui/interfaces/NtosArcade.tsx
index 1db4b7f428..99590e5ec2 100644
--- a/tgui/packages/tgui/interfaces/NtosArcade.jsx
+++ b/tgui/packages/tgui/interfaces/NtosArcade.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { resolveAsset } from '../assets';
import { useBackend } from '../backend';
import {
@@ -11,8 +13,31 @@ import {
} from '../components';
import { NtosWindow } from '../layouts';
+type Data = {
+ Hitpoints: number;
+ PlayerHitpoints: number;
+ PlayerMP: number;
+ TicketCount: number;
+ GameActive: BooleanLike;
+ PauseState: BooleanLike;
+ Status: string;
+ BossID: string;
+};
+
export const NtosArcade = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
+
+ const {
+ PlayerHitpoints,
+ PlayerMP,
+ PauseState,
+ Status,
+ Hitpoints,
+ BossID,
+ GameActive,
+ TicketCount,
+ } = data;
+
return (
@@ -25,7 +50,7 @@ export const NtosArcade = (props) => {
{
bad: [-Infinity, 10],
}}
>
- {data.PlayerHitpoints}HP
+ {PlayerHitpoints}HP
{
bad: [-Infinity, 3],
}}
>
- {data.PlayerMP}MP
+ {PlayerMP}MP
- {data.Status}
+ {Status}
{
bad: [-Infinity, 5],
}}
>
-
+
HP
-
+
@@ -88,7 +111,7 @@ export const NtosArcade = (props) => {
icon="fist-raised"
tooltip="Go in for the kill!"
tooltipPosition="top"
- disabled={data.GameActive === 0 || data.PauseState === 1}
+ disabled={GameActive === 0 || PauseState === 1}
onClick={() => act('Attack')}
>
Attack!
@@ -97,7 +120,7 @@ export const NtosArcade = (props) => {
icon="band-aid"
tooltip="Heal yourself!"
tooltipPosition="top"
- disabled={data.GameActive === 0 || data.PauseState === 1}
+ disabled={GameActive === 0 || PauseState === 1}
onClick={() => act('Heal')}
>
Heal!
@@ -106,7 +129,7 @@ export const NtosArcade = (props) => {
icon="magic"
tooltip="Recharge your magic!"
tooltipPosition="top"
- disabled={data.GameActive === 0 || data.PauseState === 1}
+ disabled={GameActive === 0 || PauseState === 1}
onClick={() => act('Recharge_Power')}
>
Recharge!
@@ -117,7 +140,7 @@ export const NtosArcade = (props) => {
icon="sync-alt"
tooltip="One more game couldn't hurt."
tooltipPosition="top"
- disabled={data.GameActive === 1}
+ disabled={GameActive === 1}
onClick={() => act('Start_Game')}
>
Begin Game
@@ -126,14 +149,14 @@ export const NtosArcade = (props) => {
icon="ticket-alt"
tooltip="Claim at your local Arcade Computer for Prizes!"
tooltipPosition="top"
- disabled={data.GameActive === 1}
+ disabled={GameActive === 1}
onClick={() => act('Dispense_Tickets')}
>
Claim Tickets
- = 1 ? 'good' : 'normal'}>
- Earned Tickets: {data.TicketCount}
+ = 1 ? 'good' : 'normal'}>
+ Earned Tickets: {TicketCount}
diff --git a/tgui/packages/tgui/interfaces/NtosCameraConsole.jsx b/tgui/packages/tgui/interfaces/NtosCameraConsole.tsx
similarity index 59%
rename from tgui/packages/tgui/interfaces/NtosCameraConsole.jsx
rename to tgui/packages/tgui/interfaces/NtosCameraConsole.tsx
index 5c299ac46e..b5ce8e7c6e 100644
--- a/tgui/packages/tgui/interfaces/NtosCameraConsole.jsx
+++ b/tgui/packages/tgui/interfaces/NtosCameraConsole.tsx
@@ -1,51 +1,19 @@
-import { filter, sortBy } from '../../common/collections';
-import { flow } from '../../common/fp';
-import { createSearch } from '../../common/string';
import { useBackend } from '../backend';
import { Button, ByondUi } from '../components';
import { NtosWindow } from '../layouts';
-import { CameraConsoleContent } from './CameraConsole';
-
-/**
- * Returns previous and next camera names relative to the currently
- * active camera.
- */
-export const prevNextCamera = (cameras, activeCamera) => {
- if (!activeCamera) {
- return [];
- }
- const index = cameras.findIndex(
- (camera) => camera.name === activeCamera.name,
- );
- return [cameras[index - 1]?.name, cameras[index + 1]?.name];
-};
-
-/**
- * Camera selector.
- *
- * Filters cameras, applies search terms and sorts the alphabetically.
- */
-export const selectCameras = (cameras, searchText = '', networkFilter = '') => {
- const testSearch = createSearch(searchText, (camera) => camera.name);
- return flow([
- // Null camera filter
- filter((camera) => camera?.name),
- // Optional search term
- searchText && filter(testSearch),
- // Optional network filter
- networkFilter &&
- filter((camera) => camera.networks.includes(networkFilter)),
- // Slightly expensive, but way better than sorting in BYOND
- sortBy((camera) => camera.name),
- ])(cameras);
-};
+import {
+ CameraConsoleContent,
+ Data,
+ prevNextCamera,
+ selectCameras,
+} from './CameraConsole';
export const NtosCameraConsole = (props) => {
- const { act, data } = useBackend();
- const { mapRef, activeCamera } = data;
- const cameras = selectCameras(data.cameras);
+ const { act, data } = useBackend();
+ const { mapRef, activeCamera, cameras } = data;
+ const selected_cameras = selectCameras(cameras);
const [prevCameraName, nextCameraName] = prevNextCamera(
- cameras,
+ selected_cameras,
activeCamera,
);
return (
diff --git a/tgui/packages/tgui/interfaces/NtosCommunicationsConsole.tsx b/tgui/packages/tgui/interfaces/NtosCommunicationsConsole.tsx
index 05ee11cc00..46870cc808 100644
--- a/tgui/packages/tgui/interfaces/NtosCommunicationsConsole.tsx
+++ b/tgui/packages/tgui/interfaces/NtosCommunicationsConsole.tsx
@@ -1,5 +1,5 @@
import { NtosWindow } from '../layouts';
-import { CommunicationsConsoleContent } from './CommunicationsConsole';
+import { CommunicationsConsoleContent } from './CommunicationsConsole/CommunicationsConsoleContent';
export const NtosCommunicationsConsole = () => {
return (
diff --git a/tgui/packages/tgui/interfaces/Pda.jsx b/tgui/packages/tgui/interfaces/Pda.jsx
index be5f73fa4d..180bdc8be4 100644
--- a/tgui/packages/tgui/interfaces/Pda.jsx
+++ b/tgui/packages/tgui/interfaces/Pda.jsx
@@ -6,12 +6,12 @@ import { Window } from '../layouts';
/* This is all basically stolen from routes.js. */
import { routingError } from '../routes';
-const requirePdaInterface = require.context('./pda', false, /\.jsx$/);
+const requirePdaInterface = require.context('./pda', false, /\.tsx$/);
const getPdaApp = (name) => {
let appModule;
try {
- appModule = requirePdaInterface(`./${name}.jsx`);
+ appModule = requirePdaInterface(`./${name}.tsx`);
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
return routingError('notFound', name);
diff --git a/tgui/packages/tgui/interfaces/PipeDispenser.jsx b/tgui/packages/tgui/interfaces/PipeDispenser.tsx
similarity index 83%
rename from tgui/packages/tgui/interfaces/PipeDispenser.jsx
rename to tgui/packages/tgui/interfaces/PipeDispenser.tsx
index 0ed7e6f956..7553fecf70 100644
--- a/tgui/packages/tgui/interfaces/PipeDispenser.jsx
+++ b/tgui/packages/tgui/interfaces/PipeDispenser.tsx
@@ -1,3 +1,4 @@
+import { BooleanLike } from 'common/react';
import { useState } from 'react';
import { useBackend } from '../backend';
@@ -5,8 +6,24 @@ import { Box, Button, Section, Tabs } from '../components';
import { Window } from '../layouts';
import { ICON_BY_CATEGORY_NAME } from './RapidPipeDispenser';
+type Data = {
+ disposals: BooleanLike;
+ p_layer: number;
+ pipe_layers: {
+ Regular: number;
+ Supply: number;
+ Scrubber: number;
+ Fuel: number;
+ Aux: number;
+ }[];
+ categories: {
+ cat_name: string;
+ recipes: { pipe_name: string; ref: string; bent: BooleanLike }[];
+ }[];
+};
+
export const PipeDispenser = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { disposals, p_layer, pipe_layers, categories = [] } = data;
const [categoryName, setCategoryName] = useState('categoryName');
@@ -38,9 +55,8 @@ export const PipeDispenser = (props) => {
)}
- {categories.map((category, i) => (
+ {categories.map((category) => (
{
key={recipe.pipe_name}
fluid
ellipsis
- title={recipe.pipe_name}
onClick={() =>
act('dispense_pipe', {
ref: recipe.ref,
diff --git a/tgui/packages/tgui/interfaces/PlantAnalyzer.jsx b/tgui/packages/tgui/interfaces/PlantAnalyzer.tsx
similarity index 76%
rename from tgui/packages/tgui/interfaces/PlantAnalyzer.jsx
rename to tgui/packages/tgui/interfaces/PlantAnalyzer.tsx
index 3c9cb2254b..228dcb832d 100644
--- a/tgui/packages/tgui/interfaces/PlantAnalyzer.jsx
+++ b/tgui/packages/tgui/interfaces/PlantAnalyzer.tsx
@@ -1,17 +1,36 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ no_seed: BooleanLike;
+ seed: {
+ name: string;
+ uid: number;
+ endurance: string;
+ yield: string;
+ maturation_time: string;
+ production_time: string;
+ potency: string;
+ trait_info: string[];
+ };
+ reagents: { name: string; volume: number }[];
+};
+
export const PlantAnalyzer = (props) => {
- const { data } = useBackend();
+ const { data } = useBackend();
+
+ const { seed, reagents } = data;
let calculatedHeight = 250;
- if (data.seed) {
- calculatedHeight += 18 * data.seed.trait_info.length;
+ if (seed) {
+ calculatedHeight += 18 * seed.trait_info.length;
}
- if (data.reagents && data.reagents.length) {
+ if (reagents && reagents.length) {
calculatedHeight += 55;
- calculatedHeight += 20 * data.reagents.length;
+ calculatedHeight += 20 * reagents.length;
}
// Resizable just in case the calculatedHeight fails
@@ -25,7 +44,7 @@ export const PlantAnalyzer = (props) => {
};
const PlantAnalyzerContent = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { no_seed, seed, reagents } = data;
@@ -68,7 +87,7 @@ const PlantAnalyzerContent = (props) => {
{seed.potency}
{(reagents.length && (
-
+
{reagents.map((r) => (
@@ -79,7 +98,7 @@ const PlantAnalyzerContent = (props) => {
)) ||
null}
-
+
{seed.trait_info.map((trait) => (
{trait}
diff --git a/tgui/packages/tgui/interfaces/PointDefenseControl.tsx b/tgui/packages/tgui/interfaces/PointDefenseControl.tsx
index 654fd6dfd0..a8c052b73b 100644
--- a/tgui/packages/tgui/interfaces/PointDefenseControl.tsx
+++ b/tgui/packages/tgui/interfaces/PointDefenseControl.tsx
@@ -10,9 +10,9 @@ type Data = {
id: string;
active: BooleanLike;
ref: string;
- effective_range;
- reaction_wheel_delay;
- recharge_time;
+ effective_range: string;
+ reaction_wheel_delay: string;
+ recharge_time: string;
}[];
};
diff --git a/tgui/packages/tgui/interfaces/PortableGenerator.jsx b/tgui/packages/tgui/interfaces/PortableGenerator.tsx
similarity index 55%
rename from tgui/packages/tgui/interfaces/PortableGenerator.jsx
rename to tgui/packages/tgui/interfaces/PortableGenerator.tsx
index df8cd8a3e5..ddb1455d89 100644
--- a/tgui/packages/tgui/interfaces/PortableGenerator.jsx
+++ b/tgui/packages/tgui/interfaces/PortableGenerator.tsx
@@ -1,4 +1,5 @@
import { toFixed } from 'common/math';
+import { BooleanLike } from 'common/react';
import { useBackend } from '../backend';
import {
@@ -11,37 +12,74 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ fuel_stored: number;
+ fuel_capacity: number;
+ anchored: BooleanLike;
+ active: BooleanLike;
+ ready_to_boot: BooleanLike;
+ sheet_name: string;
+ fuel_usage: number;
+ temperature_current: number;
+ temperature_max: number;
+ temperature_overheat: number;
+ unsafe_output: BooleanLike;
+ power_output: number;
+ power_generated: number;
+ connected: BooleanLike;
+ power_available: number;
+};
+
export const PortableGenerator = (props) => {
- const { act, data } = useBackend();
- const stack_percent = data.fuel_stored / data.fuel_capacity;
- const stackPercentState =
+ const { act, data } = useBackend();
+
+ const {
+ fuel_stored,
+ fuel_capacity,
+ anchored,
+ active,
+ ready_to_boot,
+ sheet_name,
+ fuel_usage,
+ temperature_current,
+ temperature_max,
+ temperature_overheat,
+ unsafe_output,
+ power_output,
+ power_generated,
+ connected,
+ power_available,
+ } = data;
+
+ const stack_percent: number = fuel_stored / fuel_capacity;
+ const stackPercentState: string =
(stack_percent >= 0.5 && 'good') ||
(stack_percent > 0.15 && 'average') ||
'bad';
return (
- {!data.anchored && Generator not anchored.}
+ {!anchored && Generator not anchored.}
= 1 && (
+ fuel_stored >= 1 && (
- {data.fuel_stored}cm³ / {data.fuel_capacity}cm³
+ {fuel_stored}cm³ / {fuel_capacity}cm³
- {data.fuel_usage} cm³/s
+ {fuel_usage} cm³/s
- {toFixed(data.temperature_current)}°C
+ {toFixed(temperature_current)}°C
@@ -83,21 +121,21 @@ export const PortableGenerator = (props) => {
- {data.power_output}
+ {power_output}
-
- {data.connected ? data.power_available : 'Unconnected'}
+
+ {connected ? power_available : 'Unconnected'}
diff --git a/tgui/packages/tgui/interfaces/PortablePump.jsx b/tgui/packages/tgui/interfaces/PortablePump.tsx
similarity index 90%
rename from tgui/packages/tgui/interfaces/PortablePump.jsx
rename to tgui/packages/tgui/interfaces/PortablePump.tsx
index 23145a5bcf..824d2a6ff3 100644
--- a/tgui/packages/tgui/interfaces/PortablePump.jsx
+++ b/tgui/packages/tgui/interfaces/PortablePump.tsx
@@ -1,10 +1,20 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Button, LabeledList, Section, Slider } from '../components';
import { Window } from '../layouts';
import { PortableBasicInfo } from './common/PortableAtmos';
+type Data = {
+ direction: BooleanLike;
+ target_pressure: number;
+ default_pressure: number;
+ min_pressure: number;
+ max_pressure: number;
+};
+
export const PortablePump = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
direction,
target_pressure,
diff --git a/tgui/packages/tgui/interfaces/PortableTurret.jsx b/tgui/packages/tgui/interfaces/PortableTurret.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/PortableTurret.jsx
rename to tgui/packages/tgui/interfaces/PortableTurret.tsx
index 4c020c49ae..df2d9766b7 100644
--- a/tgui/packages/tgui/interfaces/PortableTurret.jsx
+++ b/tgui/packages/tgui/interfaces/PortableTurret.tsx
@@ -1,9 +1,27 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Button, LabeledList, NoticeBox, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ locked: BooleanLike;
+ on: BooleanLike;
+ lethal: BooleanLike;
+ lethal_is_configurable: BooleanLike;
+ targetting_is_configurable: BooleanLike;
+ check_weapons: BooleanLike;
+ neutralize_noaccess: BooleanLike;
+ neutralize_norecord: BooleanLike;
+ neutralize_criminals: BooleanLike;
+ neutralize_all: BooleanLike;
+ neutralize_nonsynth: BooleanLike;
+ neutralize_unidentified: BooleanLike;
+ neutralize_down: BooleanLike;
+};
+
export const PortableTurret = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
locked,
on,
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole.jsx b/tgui/packages/tgui/interfaces/ResearchConsole.jsx
deleted file mode 100644
index 719d8b6d13..0000000000
--- a/tgui/packages/tgui/interfaces/ResearchConsole.jsx
+++ /dev/null
@@ -1,921 +0,0 @@
-import { toTitleCase } from 'common/string';
-import { Fragment, useEffect, useState } from 'react';
-
-import { useBackend, useSharedState } from '../backend';
-import {
- Box,
- Button,
- Divider,
- Flex,
- Icon,
- Input,
- LabeledList,
- NumberInput,
- ProgressBar,
- Section,
- Table,
- Tabs,
-} from '../components';
-import { Window } from '../layouts';
-
-const ResearchConsoleViewResearch = (props) => {
- const { act, data } = useBackend();
-
- const { tech } = data;
-
- return (
- act('print', { print: 1 })}>
- Print This Page
-
- }
- >
-
- {tech.map((thing) => (
-
-
- {thing.name}
- - Level {thing.level}
-
-
- {thing.desc}
-
-
- ))}
-
-
- );
-};
-
-const PaginationTitle = (props) => {
- const { data } = useBackend();
-
- const { title, target } = props;
-
- let page = data[target];
- if (typeof page === 'number') {
- return title + ' - Page ' + (page + 1);
- }
-
- return title;
-};
-
-const PaginationChevrons = (props) => {
- const { act } = useBackend();
-
- const { target } = props;
-
- return (
- <>
-
- );
-};
-
-const TechDisk = (props) => {
- const { act, data } = useBackend();
-
- const { tech } = data;
-
- const { disk } = props;
-
- if (!disk || !disk.present) {
- return null;
- }
-
- if (props.saveDialog) {
- return (
- props.onSaveDialog(false)}>
- Back
-
- }
- >
-
- {tech.map((level) => (
-
- {
- props.onSaveDialog(false);
- act('copy_tech', { copy_tech_ID: level.id });
- }}
- >
- Copy To Disk
-
-
- ))}
-
-
- );
- }
-
- return (
-
-
-
- (Technology Data Disk)
-
-
- {(disk.stored && (
-
- {disk.name}
- Level: {disk.level}
- Description: {disk.desc}
-
- act('updt_tech')}>
- Upload to Database
-
- act('clear_tech')}>
- Clear Disk
-
- act('eject_tech')}>
- Eject Disk
-
-
-
- )) || (
-
- This disk has no data stored on it.
- props.onSaveDialog(true)}>
- Load Tech To Disk
-
- act('eject_tech')}>
- Eject Disk
-
-
- )}
-
- );
-};
-
-const DataDisk = (props) => {
- const { act, data } = useBackend();
-
- const { designs } = data;
-
- const { disk } = props;
-
- if (!disk || !disk.present) {
- return null;
- }
-
- if (props.saveDialog) {
- return (
-
- }
- buttons={
- <>
- props.onSaveDialog(false)}>
- Back
-
- { || null}
- >
- }
- >
- act('search', { search: v })}
- mb={1}
- />
- {(designs && designs.length && (
-
- {designs.map((item) => (
-
- {
- props.onSaveDialog(false);
- act('copy_design', { copy_design_ID: item.id });
- }}
- >
- Copy To Disk
-
-
- ))}
-
- )) || No designs found.}
-
- );
- }
-
- return (
-
- {(disk.stored && (
-
-
- {disk.name}
-
- {disk.build_type}
-
-
- {Object.keys(disk.materials).map((mat) => (
-
- {mat} x {disk.materials[mat]}
-
- ))}
-
-
-
- act('updt_design')}>
- Upload to Database
-
- act('clear_design')}>
- Clear Disk
-
- act('eject_design')}>
- Eject Disk
-
-
-
- )) || (
-
- This disk has no data stored on it.
- props.onSaveDialog(true)}>
- Load Design To Disk
-
- act('eject_design')}>
- Eject Disk
-
-
- )}
-
- );
-};
-
-const ResearchConsoleDisk = (props) => {
- const { act, data } = useBackend();
-
- const { d_disk, t_disk } = data.info;
-
- if (!d_disk.present && !t_disk.present) {
- return ;
- }
-
- return (
-
- );
-};
-
-const ResearchConsoleDestructiveAnalyzer = (props) => {
- const { act, data } = useBackend();
-
- const { linked_destroy } = data.info;
-
- if (!linked_destroy.present) {
- return (
-
- No destructive analyzer found.
-
- );
- }
-
- const { loaded_item, origin_tech } = linked_destroy;
-
- return (
-
- {(loaded_item && (
-
-
- {loaded_item}
-
-
- {(origin_tech.length &&
- origin_tech.map((tech) => (
-
- {tech.level}
- {tech.current && '(Current: ' + tech.current + ')'}
-
- ))) || (
-
- No origin tech found.
-
- )}
-
-
-
- act('deconstruct')}
- >
- Deconstruct Item
-
- act('eject_item')}>
- Eject Item
-
-
- )) || No Item Loaded. Standing-by...}
-
- );
-};
-
-const ResearchConsoleBuildMenu = (props) => {
- const { act, data } = useBackend();
-
- const { target, designs, buildName, buildFiveName } = props;
-
- if (!target) {
- return Error;
- }
-
- return (
- }
- buttons={}
- >
- act('search', { search: v })}
- mb={1}
- />
- {designs && designs.length ? (
- designs.map((design) => (
-
-
-
- {design.name}
-
-
-
- act(buildName, { build: design.id, imprint: design.id })
- }
- >
- Build
-
- {buildFiveName && (
-
- act(buildFiveName, {
- build: design.id,
- imprint: design.id,
- })
- }
- >
- x5
-
- )}
-
-
-
- {design.mat_list.join(' ')}
-
-
- {design.chem_list.join(' ')}
-
-
-
-
-
- ))
- ) : (
-
- No items could be found matching the parameters (page or search).
-
- )}
-
- );
-};
-
-/* Lathe + Circuit Imprinter all in one */
-const ResearchConsoleConstructor = (props) => {
- const { act, data } = useBackend();
-
- const { name } = props;
-
- let linked = null;
- let designs = null;
-
- if (name === 'Protolathe') {
- linked = data.info.linked_lathe;
- designs = data.lathe_designs;
- } else {
- linked = data.info.linked_imprinter;
- designs = data.imprinter_designs;
- }
-
- if (!linked || !linked.present) {
- return ;
- }
-
- const {
- total_materials,
- max_materials,
- total_volume,
- max_volume,
- busy,
- mats,
- reagents,
- queue,
- } = linked;
-
- let queueColor = 'transparent';
- let queueSpin = false;
- let queueIcon = 'layer-group';
- if (busy) {
- queueIcon = 'hammer';
- queueColor = 'average';
- queueSpin = true;
- } else if (queue && queue.length) {
- queueIcon = 'sync';
- queueColor = 'green';
- queueSpin = true;
- }
-
- // Proto vs Circuit differences
- let removeQueueAction = name === 'Protolathe' ? 'removeP' : 'removeI';
- let ejectSheetAction =
- name === 'Protolathe' ? 'lathe_ejectsheet' : 'imprinter_ejectsheet';
- let ejectChemAction = name === 'Protolathe' ? 'disposeP' : 'disposeI';
- let ejectAllChemAction =
- name === 'Protolathe' ? 'disposeallP' : 'disposeallI';
-
- return (
- ) || null}>
-
-
-
- {total_materials} cm³ / {max_materials} cm³
-
-
-
-
- {total_volume}u / {max_volume}u
-
-
-
-
- props.onProtoTab(0)}
- >
- Build
-
- props.onProtoTab(1)}
- >
- Queue
-
- props.onProtoTab(2)}
- >
- Mat Storage
-
- props.onProtoTab(3)}
- >
- Chem Storage
-
-
- {(props.protoTab === 0 && (
-
- )) ||
- (props.protoTab === 1 && (
-
- {(queue.length &&
- queue.map((item, index) => {
- if (item.index === 1) {
- return (
-
- {!busy ? (
-
- (Awaiting Materials)
-
- act(removeQueueAction, {
- [removeQueueAction]: item.index,
- })
- }
- >
- Remove
-
-
- ) : (
-
- Remove
-
- )}
-
- );
- }
- return (
-
-
- act(removeQueueAction, {
- [removeQueueAction]: item.index,
- })
- }
- >
- Remove
-
-
- );
- })) || Queue Empty.}
-
- )) ||
- (props.protoTab === 2 && (
-
- {mats.map((mat) => {
- return (
-
-
- props.onMatsState({
- ...props.matsStates,
- [mat.name]: val,
- })
- }
- />
- {
- props.onMatsState({
- ...props.matsStates,
- [mat.name]: 0,
- });
- act(ejectSheetAction, {
- [ejectSheetAction]: mat.name,
- amount: props.matsStates[mat.name] || 0,
- });
- }}
- >
- Num
-
-
- act(ejectSheetAction, {
- [ejectSheetAction]: mat.name,
- amount: 50,
- })
- }
- >
- All
-
- >
- }
- >
- {mat.amount} cm³
-
- );
- })}
-
- )) ||
- (props.protoTab === 3 && (
-
-
- {(reagents.length &&
- reagents.map((chem) => (
-
- {chem.volume}u
- act(ejectChemAction, { dispose: chem.id })}
- >
- Purge
-
-
- ))) || (
-
- No chems detected
-
- )}
-
- act(ejectAllChemAction)}>
- Disposal All Chemicals In Storage
-
-
- )) || Error}
-
- );
-};
-
-const ResearchConsoleSettings = (props) => {
- const { act, data } = useBackend();
-
- const { is_public, sync, linked_destroy, linked_imprinter, linked_lathe } =
- data.info;
-
- return (
-
-
- props.onSettingsTab(0)}
- selected={props.settingsTab === 0}
- >
- General
-
- props.onSettingsTab(1)}
- selected={props.settingsTab === 1}
- >
- Device Linkages
-
-
- {(props.settingsTab === 0 && (
-
- {!is_public &&
- ((sync && (
- <>
- act('sync')}>
- Sync Database with Network
-
- act('togglesync')}>
- Disconnect from Research Network
-
- >
- )) || (
- act('togglesync')}>
- Connect to Research Network
-
- ))}
- act('lock')}>
- Lock Console
-
- act('reset')}>
- Reset R&D Database
-
-
- )) ||
- (props.settingsTab === 1 && (
-
- act('find_device')}>
- Re-sync with Nearby Devices
-
-
- {(linked_destroy.present && (
-
- act('disconnect', { disconnect: 'destroy' })}
- >
- Disconnect
-
-
- )) ||
- null}
- {(linked_lathe.present && (
-
- act('disconnect', { disconnect: 'lathe' })}
- >
- Disconnect
-
-
- )) ||
- null}
- {(linked_imprinter.present && (
-
-
- act('disconnect', { disconnect: 'imprinter' })
- }
- >
- Disconnect
-
-
- )) ||
- null}
-
-
- )) || Error}
-
- );
-};
-
-const menus = [
- {
- name: 'Protolathe',
- icon: 'wrench',
- },
- {
- name: 'Circuit Imprinter',
- icon: 'digital-tachograph',
- },
- {
- name: 'Destructive Analyzer',
- icon: 'eraser',
- },
- {
- name: 'Settings',
- icon: 'cog',
- },
- {
- name: 'Research List',
- icon: 'flask',
- },
- {
- name: 'Design List',
- icon: 'file',
- },
- { name: 'Disk Operations', icon: 'save' },
-];
-
-export const ResearchConsole = (props) => {
- const { act, data } = useBackend();
-
- const { busy_msg, locked } = data;
-
- const [menu, setMenu] = useSharedState('rdmenu', 0);
- const [protoTab, setProtoTab] = useSharedState('protoTab', 0);
- const [settingsTab, setSettingsTab] = useSharedState('settingsTab', 0);
- const [saveDialogTech, setSaveDialogTech] = useSharedState(
- 'saveDialogTech',
- false,
- );
- const [saveDialogDesign, setSaveDialogDesign] = useSharedState(
- 'saveDialogData',
- false,
- );
-
- const [matsStates, setMatsState] = useState({});
-
- useEffect(() => {
- setMatsState({});
- }, [menu]);
-
- let allTabsDisabled = false;
- if (busy_msg || locked) {
- allTabsDisabled = true;
- }
-
- return (
-
-
-
- {menus.map((obj, i) => (
- setMenu(i)}
- >
- {obj.name}
-
- ))}
-
- {(busy_msg && ) ||
- (locked && (
-
- act('lock')} icon="lock-open">
- Unlock
-
-
- )) ||
- (menu === 0 ? (
-
- ) : (
- ''
- )) ||
- (menu === 1 && (
-
- )) ||
- (menu === 2 && (
-
- )) ||
- (menu === 3 && (
-
- )) ||
- (menu === 4 && ) ||
- (menu === 5 && ) ||
- (menu === 6 && (
-
- ))}
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleBuildMenu.tsx b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleBuildMenu.tsx
new file mode 100644
index 0000000000..6a670814f4
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleBuildMenu.tsx
@@ -0,0 +1,99 @@
+import { Fragment } from 'react';
+
+import { useBackend } from '../../backend';
+import { Box, Button, Divider, Flex, Input, Section } from '../../components';
+import { paginationTitle } from './constants';
+import { Data, design } from './types';
+
+export const ResearchConsoleBuildMenu = (props) => {
+ const { act, data } = useBackend();
+
+ const { target, designs, buildName, buildFiveName } = props;
+
+ if (!target) {
+ return Error;
+ }
+
+ return (
+ }
+ >
+ act('search', { search: v })}
+ mb={1}
+ />
+ {designs && designs.length ? (
+ designs.map((design: design) => (
+
+
+
+ {design.name}
+
+
+
+ act(buildName, { build: design.id, imprint: design.id })
+ }
+ >
+ Build
+
+ {buildFiveName && (
+
+ act(buildFiveName, {
+ build: design.id,
+ imprint: design.id,
+ })
+ }
+ >
+ x5
+
+ )}
+
+
+
+ {design.mat_list.join(' ')}
+
+
+ {design.chem_list.join(' ')}
+
+
+
+
+
+ ))
+ ) : (
+
+ No items could be found matching the parameters (page or search).
+
+ )}
+
+ );
+};
+
+export const PaginationChevrons = (props: { target: string }) => {
+ const { act } = useBackend();
+
+ const { target } = props;
+
+ return (
+ <>
+ act(target, { reset: true })} />
+ act(target, { reverse: -1 })}
+ />
+ act(target, { reverse: 1 })}
+ />
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleConstructor.tsx b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleConstructor.tsx
new file mode 100644
index 0000000000..fee9a2fcfb
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleConstructor.tsx
@@ -0,0 +1,139 @@
+import { useBackend } from '../../backend';
+import {
+ Box,
+ Icon,
+ LabeledList,
+ ProgressBar,
+ Section,
+ Tabs,
+} from '../../components';
+import {
+ ResearchConsoleConstructorChems,
+ ResearchConsoleConstructorMats,
+ ResearchConsoleConstructorMenue,
+ ResearchConsoleConstructorQueue,
+} from './ResearchConsoleConstructorTabs';
+import { Data, design, mat, modularDevice } from './types';
+
+/* Lathe + Circuit Imprinter all in one */
+export const ResearchConsoleConstructor = (props: {
+ name: string;
+ linked: modularDevice;
+ designs: design[];
+ protoTab: number;
+ matsStates: mat[];
+ onProtoTab: Function;
+ onMatsState: Function;
+}) => {
+ const { act, data } = useBackend();
+
+ const {
+ name,
+ matsStates,
+ onMatsState,
+ protoTab,
+ onProtoTab,
+ linked,
+ designs,
+ } = props;
+
+ if (!linked || !linked.present) {
+ return ;
+ }
+
+ const {
+ total_materials,
+ max_materials,
+ total_volume,
+ max_volume,
+ busy,
+ mats,
+ reagents,
+ queue,
+ } = linked;
+
+ let queueColor: string = 'transparent';
+ let queueSpin: boolean = false;
+ let queueIcon: string = 'layer-group';
+ if (busy) {
+ queueIcon = 'hammer';
+ queueColor = 'average';
+ queueSpin = true;
+ } else if (queue && queue.length) {
+ queueIcon = 'sync';
+ queueColor = 'green';
+ queueSpin = true;
+ }
+
+ const tab: React.JSX.Element[] = [];
+
+ tab[0] = (
+
+ );
+ tab[1] = (
+
+ );
+ tab[2] = (
+
+ );
+ tab[3] = ;
+
+ return (
+ ) || null}>
+
+
+
+ {total_materials} cm³ / {max_materials} cm³
+
+
+
+
+ {total_volume}u / {max_volume}u
+
+
+
+
+ onProtoTab(0)}
+ >
+ Build
+
+ onProtoTab(1)}
+ >
+ Queue
+
+ onProtoTab(2)}
+ >
+ Mat Storage
+
+ onProtoTab(3)}
+ >
+ Chem Storage
+
+
+ {tab[protoTab] || Error}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleConstructorTabs.tsx b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleConstructorTabs.tsx
new file mode 100644
index 0000000000..d39fac8ca2
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleConstructorTabs.tsx
@@ -0,0 +1,201 @@
+import { BooleanLike } from 'common/react';
+import { toTitleCase } from 'common/string';
+
+import { useBackend } from '../../backend';
+import { Box, Button, LabeledList, NumberInput } from '../../components';
+import { ResearchConsoleBuildMenu } from './ResearchConsoleBuildMenu';
+import { Data, design, mat, modularDevice, reagent } from './types';
+
+export const ResearchConsoleConstructorMenue = (props: {
+ name: string;
+ linked: modularDevice;
+ designs: design[];
+}) => {
+ const { name, linked, designs } = props;
+
+ return (
+
+ );
+};
+
+export const ResearchConsoleConstructorQueue = (props: {
+ name: string;
+ busy: BooleanLike;
+ queue: { name: string; index: number }[];
+}) => {
+ const { act } = useBackend();
+
+ const { queue, name, busy } = props;
+
+ const removeQueueAction: string =
+ name === 'Protolathe' ? 'removeP' : 'removeI';
+ return (
+
+ {(queue.length &&
+ queue.map((item, index) => {
+ if (item.index === 1) {
+ return (
+
+ {!busy ? (
+
+ (Awaiting Materials)
+
+ act(removeQueueAction, {
+ [removeQueueAction]: item.index,
+ })
+ }
+ >
+ Remove
+
+
+ ) : (
+
+ Remove
+
+ )}
+
+ );
+ }
+ return (
+
+
+ act(removeQueueAction, {
+ [removeQueueAction]: item.index,
+ })
+ }
+ >
+ Remove
+
+
+ );
+ })) || Queue Empty.}
+
+ );
+};
+
+export const ResearchConsoleConstructorMats = (props: {
+ name: string;
+ mats: mat[];
+ matsStates: mat[];
+ onMatsState: Function;
+}) => {
+ const { act } = useBackend();
+
+ const { name, mats } = props;
+
+ const ejectSheetAction: string =
+ name === 'Protolathe' ? 'lathe_ejectsheet' : 'imprinter_ejectsheet';
+
+ return (
+
+ {mats.map((mat) => {
+ return (
+
+
+ props.onMatsState({
+ ...props.matsStates,
+ [mat.name]: val,
+ })
+ }
+ />
+ {
+ props.onMatsState({
+ ...props.matsStates,
+ [mat.name]: 0,
+ });
+ act(ejectSheetAction, {
+ [ejectSheetAction]: mat.name,
+ amount: props.matsStates[mat.name] || 0,
+ });
+ }}
+ >
+ Num
+
+
+ act(ejectSheetAction, {
+ [ejectSheetAction]: mat.name,
+ amount: 50,
+ })
+ }
+ >
+ All
+
+ >
+ }
+ >
+ {mat.amount} cm³
+
+ );
+ })}
+
+ );
+};
+
+export const ResearchConsoleConstructorChems = (props: {
+ name: string;
+ reagents: reagent[];
+}) => {
+ const { act } = useBackend();
+
+ const { name, reagents } = props;
+
+ const ejectChemAction: string =
+ name === 'Protolathe' ? 'disposeP' : 'disposeI';
+ const ejectAllChemAction: string =
+ name === 'Protolathe' ? 'disposeallP' : 'disposeallI';
+ return (
+
+
+ {(reagents.length &&
+ reagents.map((chem) => (
+
+ {chem.volume}u
+ act(ejectChemAction, { dispose: chem.id })}
+ >
+ Purge
+
+
+ ))) || (
+ No chems detected
+ )}
+
+ act(ejectAllChemAction)}
+ >
+ Disposal All Chemicals In Storage
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleDestructiveAnalyzer.tsx b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleDestructiveAnalyzer.tsx
new file mode 100644
index 0000000000..4d77a554e2
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleDestructiveAnalyzer.tsx
@@ -0,0 +1,56 @@
+import { useBackend } from '../../backend';
+import { Box, Button, LabeledList, Section } from '../../components';
+import { Data, destroyer } from './types';
+
+export const ResearchConsoleDestructiveAnalyzer = (props: {
+ name: string;
+ linked_destroy: destroyer;
+}) => {
+ const { act, data } = useBackend();
+
+ const { linked_destroy, name } = props;
+
+ if (!linked_destroy.present) {
+ return No destructive analyzer found.;
+ }
+
+ const { loaded_item, origin_tech } = linked_destroy;
+
+ return (
+
+ {(loaded_item && (
+
+
+ {loaded_item}
+
+
+ {(origin_tech.length &&
+ origin_tech.map((tech) => (
+
+ {tech.level}
+ {tech.current && '(Current: ' + tech.current + ')'}
+
+ ))) || (
+
+ No origin tech found.
+
+ )}
+
+
+
+ act('deconstruct')}
+ >
+ Deconstruct Item
+
+ act('eject_item')}>
+ Eject Item
+
+
+ )) || No Item Loaded. Standing-by...}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleDisk.tsx b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleDisk.tsx
new file mode 100644
index 0000000000..10652d7add
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleDisk.tsx
@@ -0,0 +1,217 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Input, LabeledList, Section } from '../../components';
+import { paginationTitle } from './constants';
+import { PaginationChevrons } from './ResearchConsoleBuildMenu';
+import { d_disk, Data, t_disk } from './types';
+
+export const TechDisk = (props) => {
+ const { act, data } = useBackend();
+
+ const { tech } = data;
+
+ const { disk, saveDialog, onSaveDialog } = props;
+
+ if (!disk || !disk.present) {
+ return null;
+ }
+
+ if (saveDialog) {
+ return (
+ onSaveDialog(false)}>
+ Back
+
+ }
+ >
+
+ {tech.map((level) => (
+
+ {
+ onSaveDialog(false);
+ act('copy_tech', { copy_tech_ID: level.id });
+ }}
+ >
+ Copy To Disk
+
+
+ ))}
+
+
+ );
+ }
+
+ return (
+
+
+
+ (Technology Data Disk)
+
+
+ {(disk.stored && (
+
+ {disk.name}
+ Level: {disk.level}
+ Description: {disk.desc}
+
+ act('updt_tech')}>
+ Upload to Database
+
+ act('clear_tech')}>
+ Clear Disk
+
+ act('eject_tech')}>
+ Eject Disk
+
+
+
+ )) || (
+
+ This disk has no data stored on it.
+ onSaveDialog(true)}>
+ Load Tech To Disk
+
+ act('eject_tech')}>
+ Eject Disk
+
+
+ )}
+
+ );
+};
+
+const DataDisk = (props) => {
+ const { act, data } = useBackend();
+
+ const { designs } = data;
+
+ const { disk, saveDialog, onSaveDialog } = props;
+
+ if (!disk || !disk.present) {
+ return null;
+ }
+
+ if (saveDialog) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {(disk.stored && (
+
+
+ {disk.name}
+
+ {disk.build_type}
+
+
+ {Object.keys(disk.materials).map((mat) => (
+
+ {mat} x {disk.materials[mat]}
+
+ ))}
+
+
+
+ act('updt_design')}>
+ Upload to Database
+
+ act('clear_design')}>
+ Clear Disk
+
+ act('eject_design')}>
+ Eject Disk
+
+
+
+ )) || (
+
+ This disk has no data stored on it.
+ onSaveDialog(true)}>
+ Load Design To Disk
+
+ act('eject_design')}>
+ Eject Disk
+
+
+ )}
+
+ );
+};
+
+export const ResearchConsoleDisk = (props: {
+ saveDialogTech: boolean;
+ saveDialogDesign: boolean;
+ onSaveDialogTech: Function;
+ onSaveDialogDesign: Function;
+ d_disk: d_disk;
+ t_disk: t_disk;
+}) => {
+ const { d_disk, t_disk } = props;
+
+ const {
+ saveDialogTech,
+ onSaveDialogTech,
+ saveDialogDesign,
+ onSaveDialogDesign,
+ } = props;
+
+ if (!d_disk.present && !t_disk.present) {
+ return ;
+ }
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleSettings.tsx b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleSettings.tsx
new file mode 100644
index 0000000000..2452070760
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleSettings.tsx
@@ -0,0 +1,139 @@
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../../backend';
+import { Box, Button, LabeledList, Section, Tabs } from '../../components';
+import { Data, destroyer, info, modularDevice } from './types';
+
+export const ResearchConsoleSettings = (props: {
+ info: info;
+ settingsTab: number;
+ onSettingsTab: Function;
+}) => {
+ const { data } = useBackend();
+
+ const { settingsTab, onSettingsTab, info } = props;
+
+ const { is_public, sync, linked_destroy, linked_imprinter, linked_lathe } =
+ info;
+
+ const tab: React.JSX.Element[] = [];
+
+ tab[0] = ;
+ tab[1] = (
+
+ );
+
+ return (
+
+
+ onSettingsTab(0)}
+ selected={settingsTab === 0}
+ >
+ General
+
+ onSettingsTab(1)}
+ selected={settingsTab === 1}
+ >
+ Device Linkages
+
+
+ {tab[settingsTab] || Error}
+
+ );
+};
+
+const ResearchConsoleSettingsSync = (props: {
+ is_public: BooleanLike;
+ sync: BooleanLike;
+}) => {
+ const { act } = useBackend();
+
+ const { sync, is_public } = props;
+
+ return (
+
+ {!is_public &&
+ ((sync && (
+ <>
+ act('sync')}>
+ Sync Database with Network
+
+ act('togglesync')}>
+ Disconnect from Research Network
+
+ >
+ )) || (
+ act('togglesync')}>
+ Connect to Research Network
+
+ ))}
+ act('lock')}>
+ Lock Console
+
+ act('reset')}>
+ Reset R&D Database
+
+
+ );
+};
+
+const ResearchConsoleSettingsLink = (props: {
+ linked_destroy: destroyer;
+ linked_lathe: modularDevice;
+ linked_imprinter: modularDevice;
+}) => {
+ const { act } = useBackend();
+
+ const { linked_destroy, linked_lathe, linked_imprinter } = props;
+
+ return (
+
+ act('find_device')}>
+ Re-sync with Nearby Devices
+
+
+ {(linked_destroy.present && (
+
+ act('disconnect', { disconnect: 'destroy' })}
+ >
+ Disconnect
+
+
+ )) ||
+ null}
+ {(linked_lathe.present && (
+
+ act('disconnect', { disconnect: 'lathe' })}
+ >
+ Disconnect
+
+
+ )) ||
+ null}
+ {(linked_imprinter.present && (
+
+ act('disconnect', { disconnect: 'imprinter' })}
+ >
+ Disconnect
+
+
+ )) ||
+ null}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleViewDesigns.tsx b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleViewDesigns.tsx
new file mode 100644
index 0000000000..2c9b8a87c8
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleViewDesigns.tsx
@@ -0,0 +1,45 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Input, LabeledList, Section } from '../../components';
+import { paginationTitle } from './constants';
+import { PaginationChevrons } from './ResearchConsoleBuildMenu';
+import { Data } from './types';
+
+export const ResearchConsoleViewDesigns = (props) => {
+ const { act, data } = useBackend();
+
+ const { designs } = data;
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleViewResearch.tsx b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleViewResearch.tsx
new file mode 100644
index 0000000000..c115721480
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleViewResearch.tsx
@@ -0,0 +1,34 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Section, Table } from '../../components';
+import { Data } from './types';
+
+export const ResearchConsoleViewResearch = (props) => {
+ const { act, data } = useBackend();
+
+ const { tech } = data;
+
+ return (
+ act('print', { print: 1 })}>
+ Print This Page
+
+ }
+ >
+
+ {tech.map((thing) => (
+
+
+ {thing.name}
+ - Level {thing.level}
+
+
+ {thing.desc}
+
+
+ ))}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/constants.ts b/tgui/packages/tgui/interfaces/ResearchConsole/constants.ts
new file mode 100644
index 0000000000..15fb7a2eb7
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/constants.ts
@@ -0,0 +1,37 @@
+import { menue } from './types';
+
+export const menus: menue = [
+ {
+ name: 'Protolathe',
+ icon: 'wrench',
+ },
+ {
+ name: 'Circuit Imprinter',
+ icon: 'digital-tachograph',
+ },
+ {
+ name: 'Destructive Analyzer',
+ icon: 'eraser',
+ },
+ {
+ name: 'Settings',
+ icon: 'cog',
+ },
+ {
+ name: 'Research List',
+ icon: 'flask',
+ },
+ {
+ name: 'Design List',
+ icon: 'file',
+ },
+ { name: 'Disk Operations', icon: 'save' },
+];
+
+export function paginationTitle(title: string, page: number) {
+ if (typeof page === 'number') {
+ return title + ' - Page ' + (page + 1);
+ }
+
+ return title;
+}
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/index.tsx b/tgui/packages/tgui/interfaces/ResearchConsole/index.tsx
new file mode 100644
index 0000000000..81c576b9c7
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/index.tsx
@@ -0,0 +1,128 @@
+import { useEffect, useState } from 'react';
+
+import { useBackend, useSharedState } from '../../backend';
+import { Button, Section, Tabs } from '../../components';
+import { Window } from '../../layouts';
+import { menus } from './constants';
+import { ResearchConsoleConstructor } from './ResearchConsoleConstructor';
+import { ResearchConsoleDestructiveAnalyzer } from './ResearchConsoleDestructiveAnalyzer';
+import { ResearchConsoleDisk } from './ResearchConsoleDisk';
+import { ResearchConsoleSettings } from './ResearchConsoleSettings';
+import { ResearchConsoleViewDesigns } from './ResearchConsoleViewDesigns';
+import { ResearchConsoleViewResearch } from './ResearchConsoleViewResearch';
+import { Data, mat } from './types';
+
+export const ResearchConsole = (props) => {
+ const { act, data } = useBackend();
+
+ const { busy_msg, locked, info, imprinter_designs, lathe_designs } = data;
+
+ const [menu, setMenu] = useSharedState('rdmenu', 0);
+ const [protoTab, setProtoTab] = useSharedState('protoTab', 0);
+ const [settingsTab, setSettingsTab] = useSharedState(
+ 'settingsTab',
+ 0,
+ );
+ const [saveDialogTech, setSaveDialogTech] = useSharedState(
+ 'saveDialogTech',
+ false,
+ );
+ const [saveDialogDesign, setSaveDialogDesign] = useSharedState(
+ 'saveDialogData',
+ false,
+ );
+
+ const [matsStates, setMatsState] = useState({} as mat[]);
+
+ useEffect(() => {
+ setMatsState({} as mat[]);
+ }, [menu]);
+
+ let allTabsDisabled = false;
+ if (busy_msg || locked) {
+ allTabsDisabled = true;
+ }
+
+ const tab: React.JSX.Element[] = [];
+
+ tab[0] = (
+
+ );
+ tab[1] = (
+
+ );
+
+ tab[2] = (
+
+ );
+
+ tab[3] = (
+
+ );
+
+ tab[4] = ;
+
+ tab[5] = ;
+
+ tab[6] = (
+
+ );
+
+ return (
+
+
+
+ {menus.map((obj, i) => (
+ setMenu(i)}
+ >
+ {obj.name}
+
+ ))}
+
+ {(busy_msg && ) ||
+ (locked && (
+
+ act('lock')} icon="lock-open">
+ Unlock
+
+
+ )) ||
+ tab[menu]}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ResearchConsole/types.ts b/tgui/packages/tgui/interfaces/ResearchConsole/types.ts
new file mode 100644
index 0000000000..4b4a6dc30f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchConsole/types.ts
@@ -0,0 +1,76 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ tech: { name: string; level: number; desc: string; id: string }[];
+ designs: { name: string; desc: string; id: string }[];
+ lathe_designs: design[];
+ imprinter_designs: design[];
+ locked: BooleanLike;
+ busy_msg: string | null;
+ search: string | null;
+ builder_page: number;
+ design_page: number;
+ info: info | null;
+};
+
+export type menue = { name: string; icon: string }[];
+
+export type destroyer = {
+ present: BooleanLike;
+ loaded_item: string | undefined;
+ origin_tech: { name: string; level: number; current: number | null }[];
+};
+
+export type modularDevice = {
+ present: BooleanLike;
+ total_materials: number;
+ max_materials: number;
+ total_volume: number;
+ max_volume: number;
+ busy: BooleanLike;
+ mats: mat[];
+ reagents: reagent[];
+ queue: { name: string; index: number }[];
+};
+
+export type design = {
+ name: string;
+ id: string;
+ mat_list: string[];
+ chem_list: string[];
+};
+
+export type mat = {
+ name: string;
+ amount: number;
+ sheets: number;
+ removable: BooleanLike;
+};
+
+export type reagent = { name: string; id: string; volume: number };
+
+export type info = {
+ sync: BooleanLike;
+ is_public: BooleanLike;
+ linked_destroy: destroyer;
+ linked_lathe: modularDevice;
+ linked_imprinter: modularDevice;
+ t_disk: t_disk;
+ d_disk: d_disk;
+};
+
+export type d_disk = {
+ present: BooleanLike;
+ stored: BooleanLike;
+ name: string | undefined;
+ build_type: number | undefined;
+ materials: Record[] | undefined;
+};
+
+export type t_disk = {
+ present: BooleanLike;
+ stored: BooleanLike;
+ name: string | undefined;
+ level: number | undefined;
+ desc: string | undefined;
+};
diff --git a/tgui/packages/tgui/interfaces/TEGenerator.jsx b/tgui/packages/tgui/interfaces/TEGenerator.tsx
similarity index 85%
rename from tgui/packages/tgui/interfaces/TEGenerator.jsx
rename to tgui/packages/tgui/interfaces/TEGenerator.tsx
index a35c7b9d5b..983ec5e458 100644
--- a/tgui/packages/tgui/interfaces/TEGenerator.jsx
+++ b/tgui/packages/tgui/interfaces/TEGenerator.tsx
@@ -5,8 +5,26 @@ import { Box, Flex, LabeledList, ProgressBar, Section } from '../components';
import { formatPower, formatSiUnit } from '../format';
import { Window } from '../layouts';
+type Data = {
+ totalOutput: number;
+ maxTotalOutput: number;
+ thermalOutput: number;
+ primary: circulator;
+ secondary: circulator;
+};
+
+type circulator = {
+ dir: string;
+ output: number;
+ flowCapacity: number;
+ inletPressure: number;
+ inletTemperature: number;
+ outletPressure: number;
+ outletTemperature: number;
+};
+
export const TEGenerator = (props) => {
- const { data } = useBackend();
+ const { data } = useBackend();
const { totalOutput, maxTotalOutput, thermalOutput, primary, secondary } =
data;
@@ -46,7 +64,7 @@ export const TEGenerator = (props) => {
);
};
-const TEGCirculator = (props) => {
+const TEGCirculator = (props: { name: string; values: circulator }) => {
const { name, values } = props;
const {
diff --git a/tgui/packages/tgui/interfaces/Tank.jsx b/tgui/packages/tgui/interfaces/Tank.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/Tank.jsx
rename to tgui/packages/tgui/interfaces/Tank.tsx
index 2aabef35de..ba0bb7e159 100644
--- a/tgui/packages/tgui/interfaces/Tank.jsx
+++ b/tgui/packages/tgui/interfaces/Tank.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
Button,
@@ -8,8 +10,19 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ connected: BooleanLike;
+ showToggle: BooleanLike;
+ maskConnected: BooleanLike;
+ tankPressure: number;
+ releasePressure: number;
+ defaultReleasePressure: number;
+ minReleasePressure: number;
+ maxReleasePressure: number;
+};
+
export const Tank = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
connected,
@@ -72,7 +85,7 @@ export const Tank = (props) => {
/>
[];
+ categories: Record[];
+ tutorials: Record[];
selection: string;
};
diff --git a/tgui/packages/tgui/interfaces/TurbineControl.jsx b/tgui/packages/tgui/interfaces/TurbineControl.tsx
similarity index 90%
rename from tgui/packages/tgui/interfaces/TurbineControl.jsx
rename to tgui/packages/tgui/interfaces/TurbineControl.tsx
index 721c466429..1b47ed8505 100644
--- a/tgui/packages/tgui/interfaces/TurbineControl.jsx
+++ b/tgui/packages/tgui/interfaces/TurbineControl.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
AnimatedNumber,
@@ -9,11 +11,22 @@ import {
import { formatPower } from '../format';
import { Window } from '../layouts';
+type Data = {
+ connected: BooleanLike;
+ compressor_broke: BooleanLike;
+ turbine_broke: BooleanLike;
+ broken: BooleanLike;
+ door_status: BooleanLike;
+ online: BooleanLike;
+ power: number;
+ rpm: number;
+ temp: number;
+};
+
export const TurbineControl = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
- connected,
compressor_broke,
turbine_broke,
broken,
diff --git a/tgui/packages/tgui/interfaces/Wires.jsx b/tgui/packages/tgui/interfaces/Wires.tsx
similarity index 86%
rename from tgui/packages/tgui/interfaces/Wires.jsx
rename to tgui/packages/tgui/interfaces/Wires.tsx
index c3e3fb2379..e197b96618 100644
--- a/tgui/packages/tgui/interfaces/Wires.jsx
+++ b/tgui/packages/tgui/interfaces/Wires.tsx
@@ -1,11 +1,25 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
-export const Wires = (props) => {
- const { act, data } = useBackend();
+type Data = {
+ wires: {
+ seen_color: string;
+ color_name: string;
+ color: string;
+ wire: string | null;
+ cut: BooleanLike;
+ attached: BooleanLike;
+ }[];
+ status: string[];
+};
- const wires = data.wires || [];
+export const Wires = (props) => {
+ const { act, data } = useBackend();
+
+ const { wires = [] } = data;
const statuses = data.status || [];
return (
diff --git a/tgui/packages/tgui/interfaces/XenoarchArtifactHarvester.jsx b/tgui/packages/tgui/interfaces/XenoarchArtifactHarvester.tsx
similarity index 78%
rename from tgui/packages/tgui/interfaces/XenoarchArtifactHarvester.jsx
rename to tgui/packages/tgui/interfaces/XenoarchArtifactHarvester.tsx
index 4802fba6a7..521cd06816 100644
--- a/tgui/packages/tgui/interfaces/XenoarchArtifactHarvester.jsx
+++ b/tgui/packages/tgui/interfaces/XenoarchArtifactHarvester.tsx
@@ -1,9 +1,26 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ info: {
+ no_scanner: BooleanLike;
+ harvesting: number;
+ inserted_battery: battery;
+ };
+};
+
+type battery = {
+ name: string;
+ stored_charge: number;
+ capacity: number;
+ artifact_id: string;
+};
+
export const XenoarchArtifactHarvester = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { no_scanner, harvesting, inserted_battery } = data.info;
@@ -19,7 +36,9 @@ export const XenoarchArtifactHarvester = (props) => {
Please wait. Harvesting in progress.
-
+
{
Please wait. Energy dump in progress.
-
+
{
{inserted_battery.name}
-
+
{inserted_battery.artifact_id}
@@ -82,10 +105,8 @@ export const XenoarchArtifactHarvester = (props) => {
);
};
-const ArtHarvestBatteryProgress = (props) => {
- const { act, data } = useBackend();
-
- const { inserted_battery } = data.info;
+const ArtHarvestBatteryProgress = (props: { inserted_battery: battery }) => {
+ const { inserted_battery } = props;
if (!Object.keys(inserted_battery).length) {
return No battery inserted.;
diff --git a/tgui/packages/tgui/interfaces/XenoarchHandheldPowerUtilizer.tsx b/tgui/packages/tgui/interfaces/XenoarchHandheldPowerUtilizer.tsx
index 1ae54733da..597309ba05 100644
--- a/tgui/packages/tgui/interfaces/XenoarchHandheldPowerUtilizer.tsx
+++ b/tgui/packages/tgui/interfaces/XenoarchHandheldPowerUtilizer.tsx
@@ -13,13 +13,13 @@ import { Window } from '../layouts';
type Data = {
inserted_battery: BooleanLike;
- anomaly: string;
- charge: number;
- capacity: number;
- timeleft: number;
+ anomaly: string | null;
+ charge: number | null;
+ capacity: number | null;
+ timeleft: number | null;
activated: BooleanLike;
- duration: number;
- interval: number;
+ duration: number | null;
+ interval: number | null;
};
export const XenoarchHandheldPowerUtilizer = (props) => {
@@ -60,7 +60,7 @@ export const XenoarchHandheldPowerUtilizer = (props) => {
{anomaly || 'N/A'}
-
+
{charge} / {capacity}
@@ -80,7 +80,7 @@ export const XenoarchHandheldPowerUtilizer = (props) => {
value={duration}
stepPixelSize={4}
maxValue={30}
- onDrag={(e, val) =>
+ onDrag={(e, val: number) =>
act('changeduration', { duration: val * 10 })
}
/>
@@ -93,7 +93,7 @@ export const XenoarchHandheldPowerUtilizer = (props) => {
value={interval}
stepPixelSize={10}
maxValue={10}
- onDrag={(e, val) =>
+ onDrag={(e, val: number) =>
act('changeinterval', { interval: val * 10 })
}
/>
diff --git a/tgui/packages/tgui/interfaces/XenoarchReplicator.tsx b/tgui/packages/tgui/interfaces/XenoarchReplicator.tsx
index 5f8ad32a73..9d1dca8527 100644
--- a/tgui/packages/tgui/interfaces/XenoarchReplicator.tsx
+++ b/tgui/packages/tgui/interfaces/XenoarchReplicator.tsx
@@ -1,9 +1,10 @@
import { useBackend } from '../backend';
import { Button } from '../components';
import { Window } from '../layouts';
+import { tgui_construction } from './common/CommonTypes';
type Data = {
- tgui_construction: { key; background; icon; foreground }[];
+ tgui_construction: tgui_construction;
};
export const XenoarchReplicator = (props) => {
diff --git a/tgui/packages/tgui/interfaces/XenoarchReplicator_clothing_vr.jsx b/tgui/packages/tgui/interfaces/XenoarchReplicator_clothing_vr.tsx
similarity index 81%
rename from tgui/packages/tgui/interfaces/XenoarchReplicator_clothing_vr.jsx
rename to tgui/packages/tgui/interfaces/XenoarchReplicator_clothing_vr.tsx
index 71d0322d08..cafaa0a326 100644
--- a/tgui/packages/tgui/interfaces/XenoarchReplicator_clothing_vr.jsx
+++ b/tgui/packages/tgui/interfaces/XenoarchReplicator_clothing_vr.tsx
@@ -1,9 +1,14 @@
import { useBackend } from '../backend';
import { Button } from '../components';
import { Window } from '../layouts';
+import { tgui_construction } from './common/CommonTypes';
+
+type Data = {
+ tgui_construction: tgui_construction;
+};
export const XenoarchReplicator_clothing_vr = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { tgui_construction } = data;
diff --git a/tgui/packages/tgui/interfaces/XenoarchReplicator_voremob_vr.jsx b/tgui/packages/tgui/interfaces/XenoarchReplicator_voremob_vr.tsx
similarity index 81%
rename from tgui/packages/tgui/interfaces/XenoarchReplicator_voremob_vr.jsx
rename to tgui/packages/tgui/interfaces/XenoarchReplicator_voremob_vr.tsx
index c4572f05bd..3792f3cc46 100644
--- a/tgui/packages/tgui/interfaces/XenoarchReplicator_voremob_vr.jsx
+++ b/tgui/packages/tgui/interfaces/XenoarchReplicator_voremob_vr.tsx
@@ -1,9 +1,14 @@
import { useBackend } from '../backend';
import { Button } from '../components';
import { Window } from '../layouts';
+import { tgui_construction } from './common/CommonTypes';
+
+type Data = {
+ tgui_construction: tgui_construction;
+};
export const XenoarchReplicator_voremob_vr = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { tgui_construction } = data;
diff --git a/tgui/packages/tgui/interfaces/common/AtmosControls.jsx b/tgui/packages/tgui/interfaces/common/AtmosControls.tsx
similarity index 94%
rename from tgui/packages/tgui/interfaces/common/AtmosControls.jsx
rename to tgui/packages/tgui/interfaces/common/AtmosControls.tsx
index 7bae683d66..213269f9cf 100644
--- a/tgui/packages/tgui/interfaces/common/AtmosControls.jsx
+++ b/tgui/packages/tgui/interfaces/common/AtmosControls.tsx
@@ -2,8 +2,12 @@ import { decodeHtmlEntities } from 'common/string';
import { useBackend } from '../../backend';
import { Button, LabeledList, NumberInput, Section } from '../../components';
+import { single_scrubber, single_vent } from './CommonTypes';
-export const Vent = (props) => {
+type vent = { vent: single_vent };
+type scrubber = { scrubber: single_scrubber };
+
+export const Vent = (props: vent) => {
const { vent } = props;
const { act } = useBackend();
const {
@@ -21,7 +25,6 @@ export const Vent = (props) => {
} = vent;
return (
{
);
};
-export const Scrubber = (props) => {
+export const Scrubber = (props: scrubber) => {
const { scrubber } = props;
const { act } = useBackend();
- const { long_name, power, scrubbing, id_tag, widenet, filters } = scrubber;
+ const { long_name, power, scrubbing, id_tag, filters } = scrubber;
return (
{
act(filter.command, {
diff --git a/tgui/packages/tgui/interfaces/common/BeakerContents.jsx b/tgui/packages/tgui/interfaces/common/BeakerContents.tsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/common/BeakerContents.jsx
rename to tgui/packages/tgui/interfaces/common/BeakerContents.tsx
diff --git a/tgui/packages/tgui/interfaces/common/CommonTypes.ts b/tgui/packages/tgui/interfaces/common/CommonTypes.ts
new file mode 100644
index 0000000000..841a987559
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/common/CommonTypes.ts
@@ -0,0 +1,39 @@
+import { BooleanLike } from 'common/react';
+
+export type single_vent = {
+ id_tag: string;
+ long_name: string;
+ power: BooleanLike;
+ checks: BooleanLike;
+ excheck: BooleanLike;
+ incheck: BooleanLike;
+ direction: string;
+ external: number;
+ internal: number;
+ extdefault: BooleanLike;
+ intdefault: BooleanLike;
+};
+
+export type single_scrubber = {
+ id_tag: string;
+ long_name: string;
+ power: BooleanLike;
+ scrubbing: BooleanLike;
+ panic: BooleanLike;
+ filters: { name: string; command: string; val: BooleanLike }[];
+};
+
+export type mat = {
+ name: string;
+ ref: string;
+ amount: number;
+ sheets: number;
+ removable: BooleanLike;
+};
+
+export type tgui_construction = {
+ key: string;
+ background: string;
+ icon: string;
+ foreground: string;
+}[];
diff --git a/tgui/packages/tgui/interfaces/common/ComplexModal.jsx b/tgui/packages/tgui/interfaces/common/ComplexModal.tsx
similarity index 60%
rename from tgui/packages/tgui/interfaces/common/ComplexModal.jsx
rename to tgui/packages/tgui/interfaces/common/ComplexModal.tsx
index c60cd1ce37..31931bdfb2 100644
--- a/tgui/packages/tgui/interfaces/common/ComplexModal.jsx
+++ b/tgui/packages/tgui/interfaces/common/ComplexModal.tsx
@@ -1,6 +1,9 @@
+import React from 'react';
+
import { useBackend } from '../../backend';
import { Box, Button, Dropdown, Flex, Input, Modal } from '../../components';
+type Data = { modal: { id: string; args: {}; text: string; type: string } };
let bodyOverrides = {};
/**
@@ -8,9 +11,12 @@ let bodyOverrides = {};
* @param {string} id The identifier of the modal
* @param {object=} args The arguments to pass to the modal
*/
-export const modalOpen = (id, args) => {
- const { act, data } = useBackend();
- const newArgs = Object.assign(data.modal ? data.modal.args : {}, args || {});
+export const modalOpen = (id, args = {}) => {
+ const { act, data } = useBackend();
+
+ const { modal } = data;
+
+ const newArgs = Object.assign(modal ? modal.args : {}, args || {});
act('modal_open', {
id: id,
@@ -24,17 +30,23 @@ export const modalOpen = (id, args) => {
* @param {function} bodyOverride The override function that returns the
* modal contents
*/
-export const modalRegisterBodyOverride = (id, bodyOverride) => {
+export const modalRegisterBodyOverride = (
+ id: string,
+ bodyOverride: Function,
+) => {
bodyOverrides[id] = bodyOverride;
};
-const modalAnswer = (id, answer, args) => {
- const { act, data } = useBackend();
- if (!data.modal) {
+const modalAnswer = (id: string, answer: string, args: {}) => {
+ const { act, data } = useBackend();
+
+ const { modal } = data;
+
+ if (!modal) {
return;
}
- const newArgs = Object.assign(data.modal.args || {}, args || {});
+ const newArgs = Object.assign(modal.args || {}, args || {});
act('modal_answer', {
id: id,
answer: answer,
@@ -42,13 +54,23 @@ const modalAnswer = (id, answer, args) => {
});
};
-const modalClose = (id) => {
+const modalClose = (id: string | null) => {
const { act } = useBackend();
act('modal_close', {
id: id,
});
};
+type complexData = Data &
+ Partial<{
+ modal: {
+ value: string;
+ choices: string[];
+ no_text: string;
+ yes_text: string;
+ };
+ }>;
+
/**
* Displays a modal and its actions. Passed data must have a valid modal field
*
@@ -66,30 +88,33 @@ const modalClose = (id) => {
* @param {object} props
*/
export const ComplexModal = (props) => {
- const { data } = useBackend();
- if (!data.modal) {
+ const { data } = useBackend();
+
+ const { modal } = data;
+
+ if (!modal) {
return;
}
- const { id, text, type } = data.modal;
+ const { id, text, type } = modal;
- let modalOnEnter;
- let modalBody;
- let modalFooter = (
- modalClose()}>
+ let modalOnEnter: Function | undefined;
+ let modalBody: React.JSX.Element | undefined;
+ let modalFooter: React.JSX.Element = (
+ modalClose(null)}>
Cancel
);
// Different contents depending on the type
if (bodyOverrides[id]) {
- modalBody = bodyOverrides[id](data.modal);
+ modalBody = bodyOverrides[id](modal);
} else if (type === 'input') {
- let curValue = data.modal.value;
- modalOnEnter = (e) => modalAnswer(id, curValue);
+ let curValue = modal.value;
+ modalOnEnter = (e) => modalAnswer(id, curValue, {});
modalBody = (
{
);
modalFooter = (
- modalClose()}>
+ modalClose(null)}>
Cancel
modalAnswer(id, curValue)}
+ onClick={() => modalAnswer(id, curValue, {})}
>
Confirm
-
+
);
} else if (type === 'choice') {
const realChoices =
- typeof data.modal.choices === 'object'
- ? Object.values(data.modal.choices)
- : data.modal.choices;
+ typeof modal.choices === 'object'
+ ? Object.values(modal.choices)
+ : modal.choices;
modalBody = (
modalAnswer(id, val)}
+ onSelected={(val) => modalAnswer(id, val, {})}
/>
);
} else if (type === 'bento') {
modalBody = (
- {data.modal.choices.map((c, i) => (
+ {modal.choices.map((c, i) => (
modalAnswer(id, i + 1)}
+ selected={i + 1 === parseInt(modal.value, 10)}
+ onClick={() => modalAnswer(id, (i + 1).toString(), {})}
>
@@ -149,11 +180,11 @@ export const ComplexModal = (props) => {
} else if (type === 'bentospritesheet') {
modalBody = (
- {data.modal.choices.map((c, i) => (
+ {modal.choices.map((c, i) => (
modalAnswer(id, i + 1)}
+ selected={i + 1 === parseInt(modal.value, 10)}
+ onClick={() => modalAnswer(id, (i + 1).toString(), {})}
>
@@ -167,22 +198,30 @@ export const ComplexModal = (props) => {
modalAnswer(id, 0)}
+ onClick={() => modalAnswer(id, '0', {})}
>
- {data.modal.no_text}
+ {modal.no_text}
modalAnswer(id, 1)}
+ onClick={() => modalAnswer(id, '1', {})}
>
- {data.modal.yes_text}
+ {modal.yes_text}
-
+
);
}
diff --git a/tgui/packages/tgui/interfaces/common/FullscreenNotice.jsx b/tgui/packages/tgui/interfaces/common/FullscreenNotice.tsx
similarity index 78%
rename from tgui/packages/tgui/interfaces/common/FullscreenNotice.jsx
rename to tgui/packages/tgui/interfaces/common/FullscreenNotice.tsx
index 2f58b56025..e84a5dae75 100644
--- a/tgui/packages/tgui/interfaces/common/FullscreenNotice.jsx
+++ b/tgui/packages/tgui/interfaces/common/FullscreenNotice.tsx
@@ -3,7 +3,10 @@ import { Flex, Section } from '../../components';
/**
* Just a generic wrapper for fullscreen notices.
*/
-export const FullscreenNotice = (props) => {
+export const FullscreenNotice = (props: {
+ children?: React.ReactNode | undefined;
+ title?: string;
+}) => {
const { children, title = 'Welcome' } = props;
return (
diff --git a/tgui/packages/tgui/interfaces/common/InterfaceLockNoticeBox.jsx b/tgui/packages/tgui/interfaces/common/InterfaceLockNoticeBox.tsx
similarity index 89%
rename from tgui/packages/tgui/interfaces/common/InterfaceLockNoticeBox.jsx
rename to tgui/packages/tgui/interfaces/common/InterfaceLockNoticeBox.tsx
index d2612ec546..63ef8f6624 100644
--- a/tgui/packages/tgui/interfaces/common/InterfaceLockNoticeBox.jsx
+++ b/tgui/packages/tgui/interfaces/common/InterfaceLockNoticeBox.tsx
@@ -1,6 +1,14 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../../backend';
import { Button, Flex, NoticeBox } from '../../components';
+type Data = {
+ siliconUser: BooleanLike;
+ locked: BooleanLike;
+ preventLocking: BooleanLike;
+};
+
/**
* This component by expects the following fields to be returned
* from ui_data:
@@ -16,7 +24,7 @@ import { Button, Flex, NoticeBox } from '../../components';
* it's preferred to stick to defaults.
*/
export const InterfaceLockNoticeBox = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
siliconUser = data.siliconUser,
locked = data.locked,
diff --git a/tgui/packages/tgui/interfaces/common/LoginInfo.jsx b/tgui/packages/tgui/interfaces/common/LoginInfo.tsx
similarity index 72%
rename from tgui/packages/tgui/interfaces/common/LoginInfo.jsx
rename to tgui/packages/tgui/interfaces/common/LoginInfo.tsx
index 0f45f70d58..a25c1f5d1f 100644
--- a/tgui/packages/tgui/interfaces/common/LoginInfo.jsx
+++ b/tgui/packages/tgui/interfaces/common/LoginInfo.tsx
@@ -1,6 +1,8 @@
import { useBackend } from '../../backend';
import { Box, Button, NoticeBox } from '../../components';
+type Data = { authenticated: string; rank: string };
+
/**
* Displays a notice box showing the
* `authenticated` and `rank` data fields if they exist.
@@ -9,25 +11,31 @@ import { Box, Button, NoticeBox } from '../../components';
* @param {object} _properties
*/
export const LoginInfo = (_properties) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { authenticated, rank } = data;
if (!data) {
return;
}
return (
-
+
Logged in as: {authenticated} ({rank})
act('logout')}
>
Logout and Eject ID
-
+
);
};
diff --git a/tgui/packages/tgui/interfaces/common/LoginScreen.jsx b/tgui/packages/tgui/interfaces/common/LoginScreen.tsx
similarity index 86%
rename from tgui/packages/tgui/interfaces/common/LoginScreen.jsx
rename to tgui/packages/tgui/interfaces/common/LoginScreen.tsx
index 7d27cf7d60..c97c47614a 100644
--- a/tgui/packages/tgui/interfaces/common/LoginScreen.jsx
+++ b/tgui/packages/tgui/interfaces/common/LoginScreen.tsx
@@ -1,7 +1,12 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../../backend';
import { Box, Button, Icon } from '../../components';
import { FullscreenNotice } from './FullscreenNotice';
+type machine = { machineType: string };
+type Data = { scan: BooleanLike; isAI: BooleanLike; isRobot: BooleanLike };
+
/**
* Displays a login screen that users can interact with
* using an ID card in their hand.
@@ -25,8 +30,8 @@ import { FullscreenNotice } from './FullscreenNotice';
* The AI and robot login buttons are only visible if the user is one
* @param {object} _properties
*/
-export const LoginScreen = (_properties) => {
- const { act, data } = useBackend();
+export const LoginScreen = (_properties: machine) => {
+ const { act, data } = useBackend();
const { scan, isAI, isRobot } = data;
const { machineType } = _properties;
return (
@@ -78,7 +83,7 @@ export const LoginScreen = (_properties) => {
)}
-
+
);
@@ -90,12 +95,12 @@ export const LoginScreen = (_properties) => {
* specialType definitions are defined in LoginScreen.js SpecialMachineInteraction
* currently supported: "Fax"
*/
-export const SpecialMachineInteraction = (_properties) => {
+export const SpecialMachineInteraction = (_properties: machine) => {
const { act } = useBackend();
- const { specialType } = _properties;
- if (!specialType) {
+ const { machineType } = _properties;
+ if (!machineType) {
return null;
- } else if (specialType === 'Fax') {
+ } else if (machineType === 'Fax') {
return (
{
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { insertIdText } = props;
const { has_id, id } = data;
return (
@@ -10,7 +12,7 @@ export const MiningUser = (props) => {
{has_id ? (
<>
{
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { disableLimiterControls } = props;
@@ -14,7 +24,7 @@ export const OvermapFlightData = (props) => {
{ETAnext}
- {speed} Gm/h
+ {toFixed(speed, 2)} Gm/h
{accel} Gm/h
{heading}°
diff --git a/tgui/packages/tgui/interfaces/common/PortableAtmos.jsx b/tgui/packages/tgui/interfaces/common/PortableAtmos.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/common/PortableAtmos.jsx
rename to tgui/packages/tgui/interfaces/common/PortableAtmos.tsx
index a2d3da6c0d..67960f96bb 100644
--- a/tgui/packages/tgui/interfaces/common/PortableAtmos.jsx
+++ b/tgui/packages/tgui/interfaces/common/PortableAtmos.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../../backend';
import {
AnimatedNumber,
@@ -8,8 +10,18 @@ import {
Section,
} from '../../components';
+type Data = {
+ connected: BooleanLike;
+ holding: { name: string; pressure: number } | null;
+ on: BooleanLike;
+ pressure: number;
+ powerDraw: number;
+ cellCharge: number;
+ cellMaxCharge: number;
+};
+
export const PortableBasicInfo = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
connected,
diff --git a/tgui/packages/tgui/interfaces/common/RankIcon.jsx b/tgui/packages/tgui/interfaces/common/RankIcon.tsx
similarity index 97%
rename from tgui/packages/tgui/interfaces/common/RankIcon.jsx
rename to tgui/packages/tgui/interfaces/common/RankIcon.tsx
index f520af5cb3..646a4e43ad 100644
--- a/tgui/packages/tgui/interfaces/common/RankIcon.jsx
+++ b/tgui/packages/tgui/interfaces/common/RankIcon.tsx
@@ -100,7 +100,9 @@ const rank2icon = {
'Emergency Responder': 'fighter-jet',
};
-export const RankIcon = (props) => {
+type rank_icon = { rank: string; color: string };
+
+export const RankIcon = (props: rank_icon) => {
const { rank, color = 'label' } = props;
let rankObj = rank2icon[rank];
diff --git a/tgui/packages/tgui/interfaces/common/TemporaryNotice.jsx b/tgui/packages/tgui/interfaces/common/TemporaryNotice.tsx
similarity index 77%
rename from tgui/packages/tgui/interfaces/common/TemporaryNotice.jsx
rename to tgui/packages/tgui/interfaces/common/TemporaryNotice.tsx
index e67e7dde61..a94816416b 100644
--- a/tgui/packages/tgui/interfaces/common/TemporaryNotice.jsx
+++ b/tgui/packages/tgui/interfaces/common/TemporaryNotice.tsx
@@ -3,6 +3,8 @@ import { decodeHtmlEntities } from 'common/string';
import { useBackend } from '../../backend';
import { Box, Button, NoticeBox } from '../../components';
+type Data = { temp: { style: string; text: string } };
+
/**
* Displays a notice box with text and style dictated by the
* `temp` data field if it exists.
@@ -17,7 +19,7 @@ import { Box, Button, NoticeBox } from '../../components';
*/
export const TemporaryNotice = (_properties) => {
const { decode } = _properties;
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { temp } = data;
if (!temp) {
return;
@@ -25,15 +27,10 @@ export const TemporaryNotice = (_properties) => {
const temporaryProperty = { [temp.style]: true };
return (
-
+
{decode ? decodeHtmlEntities(temp.text) : temp.text}
- act('cleartemp')}
- />
-
+ act('cleartemp')} />
);
};
diff --git a/tgui/packages/tgui/interfaces/pda/pda_atmos_scan.jsx b/tgui/packages/tgui/interfaces/pda/pda_atmos_scan.tsx
similarity index 81%
rename from tgui/packages/tgui/interfaces/pda/pda_atmos_scan.jsx
rename to tgui/packages/tgui/interfaces/pda/pda_atmos_scan.tsx
index 30bcafcfe1..4b37500468 100644
--- a/tgui/packages/tgui/interfaces/pda/pda_atmos_scan.jsx
+++ b/tgui/packages/tgui/interfaces/pda/pda_atmos_scan.tsx
@@ -4,6 +4,20 @@ import { decodeHtmlEntities } from 'common/string';
import { useBackend } from '../../backend';
import { Box, LabeledList } from '../../components';
+type Data = {
+ aircontents: aircontent[];
+};
+
+type aircontent = {
+ val: string;
+ units: string;
+ entry: string;
+ bad_low: number;
+ poor_low: number;
+ poor_high: number;
+ bad_high: number;
+};
+
const getItemColor = (value, min2, min1, max1, max2) => {
if (value < min2) {
return 'bad';
@@ -18,7 +32,7 @@ const getItemColor = (value, min2, min1, max1, max2) => {
};
export const pda_atmos_scan = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { aircontents } = data;
@@ -26,7 +40,7 @@ export const pda_atmos_scan = (props) => {
{filter(
- (i) =>
+ (i: aircontent) =>
i.val !== '0' ||
i.entry === 'Pressure' ||
i.entry === 'Temperature',
diff --git a/tgui/packages/tgui/interfaces/pda/pda_janitor.jsx b/tgui/packages/tgui/interfaces/pda/pda_janitor.tsx
similarity index 71%
rename from tgui/packages/tgui/interfaces/pda/pda_janitor.jsx
rename to tgui/packages/tgui/interfaces/pda/pda_janitor.tsx
index 08411805d5..6edd4a70ef 100644
--- a/tgui/packages/tgui/interfaces/pda/pda_janitor.jsx
+++ b/tgui/packages/tgui/interfaces/pda/pda_janitor.tsx
@@ -1,8 +1,34 @@
import { useBackend } from '../../backend';
import { Box, LabeledList, Section } from '../../components';
+type Data = {
+ janitor: {
+ user_loc: { x: number; y: number };
+ mops: { x: number; y: number; dir: string; status: string }[] | null;
+ buckets:
+ | {
+ x: number;
+ y: number;
+ dir: string;
+ volume: number;
+ max_volume: number;
+ }[]
+ | null;
+ cleanbots: { x: number; y: number; dir: string; status: string }[] | null;
+ carts:
+ | {
+ x: number;
+ y: number;
+ dir: string;
+ volume: number;
+ max_volume: number;
+ }[]
+ | null;
+ };
+};
+
export const pda_janitor = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { janitor } = data;
@@ -17,7 +43,7 @@ export const pda_janitor = (props) => {
)}
-
+
{(janitor.mops && (
{janitor.mops.map((mop, i) => (
@@ -28,7 +54,7 @@ export const pda_janitor = (props) => {
)) || No mops detected nearby.}
-
+
{(janitor.buckets && (
{janitor.buckets.map((bucket, i) => (
@@ -40,7 +66,7 @@ export const pda_janitor = (props) => {
)) || No buckets detected nearby.}
-
+
{(janitor.cleanbots && (
{janitor.cleanbots.map((cleanbot, i) => (
@@ -52,7 +78,7 @@ export const pda_janitor = (props) => {
)) || No cleanbots detected nearby.}
-
+
{(janitor.carts && (
{janitor.carts.map((cart, i) => (
diff --git a/tgui/packages/tgui/interfaces/pda/pda_main_menu.jsx b/tgui/packages/tgui/interfaces/pda/pda_main_menu.tsx
similarity index 71%
rename from tgui/packages/tgui/interfaces/pda/pda_main_menu.jsx
rename to tgui/packages/tgui/interfaces/pda/pda_main_menu.tsx
index 0453469638..ec02d4d5e7 100644
--- a/tgui/packages/tgui/interfaces/pda/pda_main_menu.jsx
+++ b/tgui/packages/tgui/interfaces/pda/pda_main_menu.tsx
@@ -1,10 +1,29 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../../backend';
import { Box, Button, LabeledList, Section } from '../../components';
-export const pda_main_menu = (props) => {
- const { act, data } = useBackend();
+type Data = {
+ owner: string;
+ ownjob: string;
+ idInserted: boolean;
+ categories: string[];
+ apps: Record[];
+ pai: BooleanLike;
+ notifying: Record;
+};
- const { owner, ownjob, idInserted, categories, pai, notifying } = data;
+type category = {
+ name: string;
+ icon: string;
+ notify_icon: string;
+ ref: string;
+};
+
+export const pda_main_menu = (props) => {
+ const { act, data } = useBackend();
+
+ const { owner, ownjob, idInserted, categories, pai, notifying, apps } = data;
return (
<>
@@ -24,17 +43,17 @@ export const pda_main_menu = (props) => {
-
+
{categories.map((name) => {
- let apps = data.apps[name];
+ let valid_apps = apps[name];
- if (!apps || !apps.length) {
+ if (!valid_apps || !valid_apps.length) {
return null;
} else {
return (
- {apps.map((app) => (
+ {valid_apps.map((app: category) => (
{
{!!pai && (
-
+
act('pai', { option: 1 })}>
Configuration
diff --git a/tgui/packages/tgui/interfaces/pda/pda_manifest.jsx b/tgui/packages/tgui/interfaces/pda/pda_manifest.tsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/pda/pda_manifest.jsx
rename to tgui/packages/tgui/interfaces/pda/pda_manifest.tsx
diff --git a/tgui/packages/tgui/interfaces/pda/pda_medical.jsx b/tgui/packages/tgui/interfaces/pda/pda_medical.tsx
similarity index 79%
rename from tgui/packages/tgui/interfaces/pda/pda_medical.jsx
rename to tgui/packages/tgui/interfaces/pda/pda_medical.tsx
index b7be5ca88e..97acc11040 100644
--- a/tgui/packages/tgui/interfaces/pda/pda_medical.jsx
+++ b/tgui/packages/tgui/interfaces/pda/pda_medical.tsx
@@ -1,8 +1,34 @@
import { useBackend } from '../../backend';
import { Box, Button, LabeledList, Section } from '../../components';
+import { GeneralRecord, RecordList } from './pda_types';
+
+type Data = {
+ records: {
+ general: GeneralRecord;
+ medical: {
+ id: string;
+ name: string;
+ species: string;
+ b_type: string;
+ b_dna: string;
+ id_gender: string;
+ brain_type: string;
+ mi_dis: string;
+ mi_dis_d: string;
+ ma_dis: string;
+ ma_dis_d: string;
+ alg: string;
+ alg_d: string;
+ cdi: string;
+ cdi_d: string;
+ notes: string;
+ };
+ };
+ recordsList: RecordList;
+};
export const pda_medical = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { recordsList, records } = data;
@@ -11,7 +37,7 @@ export const pda_medical = (props) => {
return (
-
+
{(general && (
{general.name}
@@ -33,7 +59,7 @@ export const pda_medical = (props) => {
)) || General record lost!}
-
+
{(medical && (
@@ -63,7 +89,7 @@ export const pda_medical = (props) => {
{medical.cdi_d}
-
+
{medical.notes}
@@ -74,7 +100,7 @@ export const pda_medical = (props) => {
}
return (
-
+
{recordsList.map((record) => (
{
- const { act, data } = useBackend();
+type Data = {
+ active_conversation: string;
+ convo_name: string;
+ convo_job: string;
+ messages: message[];
+ toff: BooleanLike;
+ silent: BooleanLike;
+ convopdas: pda[];
+ pdas: pda[];
+ charges: number;
+ plugins: { name: string; icon: string; ref: string }[];
+};
- const { auto_scroll, convo_name, convo_job, messages, active_conversation } =
- data;
+type pda = {
+ Name: string;
+ Reference: string;
+ Detonate: string;
+ inconvo: string;
+};
+
+type message = {
+ sent: BooleanLike;
+ owner: string;
+ job: string;
+ message: string;
+ target: string;
+};
+
+export const pda_messenger = (props) => {
+ const { act, data } = useBackend();
+
+ const { active_conversation } = data;
if (active_conversation) {
return ;
@@ -34,22 +62,14 @@ const findClassMessage = (im, lastIndex, filterArray) => {
};
const ActiveConversation = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
- const {
- auto_scroll,
- convo_name,
- convo_job,
- messages,
- active_conversation,
- useRetro,
- } = data;
+ const { convo_name, convo_job, messages, active_conversation } = data;
const [clipboardMode, setClipboardMode] = useState(false);
let body = (
{
- {filter((im) => im.target === active_conversation)(messages).map(
- (im, i, filterArr) => (
-
-
- {decodeHtmlEntities(im.message)}
-
+ {filter((im: message) => im.target === active_conversation)(
+ messages,
+ ).map((im, i, filterArr) => (
+
+
+ {decodeHtmlEntities(im.message)}
- ),
- )}
+
+ ))}
{
if (clipboardMode) {
body = (
{
- {filter((im) => im.target === active_conversation)(messages).map(
- (im, i) => (
-
- {im.sent ? 'You:' : 'Them:'} {decodeHtmlEntities(im.message)}
-
- ),
- )}
+ {filter((im: message) => im.target === active_conversation)(
+ messages,
+ ).map((im, i) => (
+
+ {im.sent ? 'You:' : 'Them:'} {decodeHtmlEntities(im.message)}
+
+ ))}
{
};
const MessengerList = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
- const { auto_scroll, convopdas, pdas, charges, plugins, silent, toff } = data;
+ const { convopdas, pdas, charges, silent, toff } = data;
return (
@@ -229,22 +248,18 @@ const MessengerList = (props) => {
};
const PDAList = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { pdas, title, msgAct } = props;
const { charges, plugins } = data;
if (!pdas || !pdas.length) {
- return (
-
- );
+ return ;
}
return (
-
+
{pdas.map((pda) => (
{
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { feeds, target_feed } = data;
@@ -17,15 +50,17 @@ export const pda_news = (props) => {
Error: No newsfeeds available. Please try again later.
)) ||
- (target_feed && ) || }
+ (target_feed && ) || (
+
+ )}
);
};
-const NewsTargetFeed = (props) => {
- const { act, data } = useBackend();
+const NewsTargetFeed = (props: { target_feed: feed }) => {
+ const { act } = useBackend();
- const { target_feed } = data;
+ const { target_feed } = props;
return (
{
' by ' +
decodeHtmlEntities(target_feed.author)
}
- level={2}
buttons={
{
>
{(target_feed.messages.length &&
target_feed.messages.map((message) => (
-
+
- {decodeHtmlEntities(message.body)}
{!!message.img && (
@@ -65,13 +99,13 @@ const NewsTargetFeed = (props) => {
};
const NewsFeed = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { feeds, latest_news } = data;
return (
<>
-
+
{(latest_news.length && (
{latest_news.map((news) => (
@@ -87,7 +121,7 @@ const NewsFeed = (props) => {
- {decodeHtmlEntities(news.body)}
- {!!news.img && (
+ {!!news.has_image && (
[image omitted, view story for more details]
{news.caption || null}
@@ -105,7 +139,7 @@ const NewsFeed = (props) => {
)) || No recent stories found.}
-
+
{feeds.map((feed) => (
{
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { note, notename } = data;
diff --git a/tgui/packages/tgui/interfaces/pda/pda_power.jsx b/tgui/packages/tgui/interfaces/pda/pda_power.tsx
similarity index 100%
rename from tgui/packages/tgui/interfaces/pda/pda_power.jsx
rename to tgui/packages/tgui/interfaces/pda/pda_power.tsx
diff --git a/tgui/packages/tgui/interfaces/pda/pda_security.jsx b/tgui/packages/tgui/interfaces/pda/pda_security.tsx
similarity index 81%
rename from tgui/packages/tgui/interfaces/pda/pda_security.jsx
rename to tgui/packages/tgui/interfaces/pda/pda_security.tsx
index 9e338f1ea8..041f84eb58 100644
--- a/tgui/packages/tgui/interfaces/pda/pda_security.jsx
+++ b/tgui/packages/tgui/interfaces/pda/pda_security.tsx
@@ -1,8 +1,28 @@
import { useBackend } from '../../backend';
import { Box, Button, LabeledList, Section } from '../../components';
+import { GeneralRecord, RecordList } from './pda_types';
+
+type Data = {
+ records: {
+ general: GeneralRecord;
+ security: {
+ name: string;
+ species: string;
+ id: string;
+ brain_type: string;
+ criminal: string;
+ mi_crim: string;
+ mi_crim_d: string;
+ ma_crim: string;
+ ma_crim_d: string;
+ notes: string;
+ };
+ };
+ recordsList: RecordList;
+};
export const pda_security = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { recordsList, records } = data;
@@ -11,7 +31,7 @@ export const pda_security = (props) => {
return (
-
+
{(general && (
{general.name}
@@ -33,7 +53,7 @@ export const pda_security = (props) => {
)) || General record lost!}
-
+
{(security && (
@@ -64,7 +84,7 @@ export const pda_security = (props) => {
}
return (
-
+
{recordsList.map((record) => (
{
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { records } = data;
@@ -44,7 +46,7 @@ export const pda_status_display = (props) => {
icon="pen"
onClick={() => act('Status', { statdisp: 'setmsg1' })}
>
- {records.message1 + ' (set)'}
+ {records && records.message1 + ' (set)'}
@@ -52,7 +54,7 @@ export const pda_status_display = (props) => {
icon="pen"
onClick={() => act('Status', { statdisp: 'setmsg2' })}
>
- {records.message2 + ' (set)'}
+ {records && records.message2 + ' (set)'}
diff --git a/tgui/packages/tgui/interfaces/pda/pda_supply.jsx b/tgui/packages/tgui/interfaces/pda/pda_supply.tsx
similarity index 76%
rename from tgui/packages/tgui/interfaces/pda/pda_supply.jsx
rename to tgui/packages/tgui/interfaces/pda/pda_supply.tsx
index 8a9876bc90..44a83561fa 100644
--- a/tgui/packages/tgui/interfaces/pda/pda_supply.jsx
+++ b/tgui/packages/tgui/interfaces/pda/pda_supply.tsx
@@ -1,8 +1,28 @@
import { useBackend } from '../../backend';
import { Box, LabeledList, Section } from '../../components';
+type Data = {
+ supply: {
+ shuttle_moving: number;
+ shuttle_eta: number;
+ shuttle_loc: string;
+ approved: {
+ Number: number;
+ Name: string;
+ ApprovedBy: string;
+ Comment: string;
+ }[];
+ requests: {
+ Number: string;
+ Name: string;
+ OrderedBy: string;
+ Comment: string;
+ }[];
+ };
+};
+
export const pda_supply = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { supply } = data;
@@ -22,7 +42,7 @@ export const pda_supply = (props) => {
{(supply.approved.length &&
supply.approved.map((crate) => (
- #{crate.Number} - {crate.Name} approved by {crate.OrderedBy}
+ #{crate.Number} - {crate.Name} approved by {crate.ApprovedBy}
{crate.Comment}
diff --git a/tgui/packages/tgui/interfaces/pda/pda_types.ts b/tgui/packages/tgui/interfaces/pda/pda_types.ts
new file mode 100644
index 0000000000..f7a1bac39b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/pda/pda_types.ts
@@ -0,0 +1,30 @@
+import { IconProps } from '../../components/Icon';
+/**
+ * Gernal Record data
+ */
+export type GeneralRecord = {
+ name: string;
+ id: string;
+ real_rank: string;
+ rank: string;
+ age: number;
+ languages: string;
+ brain_type: string;
+ fingerprint: string;
+ p_stat: string;
+ m_stat: string;
+ sex: string;
+ species: string;
+ home_system: string;
+ birthplace: string;
+ citizenship: string;
+ faction: string;
+ religion: string;
+ photo_front: IconProps;
+ photo_side: IconProps;
+ 'photo-south': string;
+ 'photo-west': string;
+ notes: string;
+};
+
+export type RecordList = { Name: string; ref: string }[];
diff --git a/tgui/packages/tgui/layouts/Pane.jsx b/tgui/packages/tgui/layouts/Pane.tsx
similarity index 64%
rename from tgui/packages/tgui/layouts/Pane.jsx
rename to tgui/packages/tgui/layouts/Pane.tsx
index 1f38df5c99..f34205545f 100644
--- a/tgui/packages/tgui/layouts/Pane.jsx
+++ b/tgui/packages/tgui/layouts/Pane.tsx
@@ -8,15 +8,19 @@ import { classes } from 'common/react';
import { useBackend } from '../backend';
import { Box } from '../components';
+import { BoxProps } from '../components/Box';
+import { useDebug } from '../debug';
import { Layout } from './Layout';
-export const Pane = (props) => {
+type Props = Partial<{
+ theme: string;
+}> &
+ BoxProps;
+
+export function Pane(props: Props) {
const { theme, children, className, ...rest } = props;
- const { suspended, debug } = useBackend();
- let debugLayout = false;
- if (debug) {
- debugLayout = debug.debugLayout;
- }
+ const { suspended } = useBackend();
+ const { debugLayout = false } = useDebug();
return (
@@ -25,20 +29,29 @@ export const Pane = (props) => {
);
-};
+}
-const PaneContent = (props) => {
+type ContentProps = Partial<{
+ fitted: boolean;
+ scrollable: boolean;
+}> &
+ BoxProps;
+
+function PaneContent(props: ContentProps) {
const { className, fitted, children, ...rest } = props;
+
return (
- {(fitted && children) || (
+ {fitted ? (
+ children
+ ) : (
{children}
)}
);
-};
+}
Pane.Content = PaneContent;