- Brute Damage : {{:data.brute}}
-
- Oxygen Content : {{:data.oxy}}
-
- Toxin Content : {{:data.tox}}
-
-
- Burn Status : {{:data.burn}}
-
-
- User Temperature : {{:data.temp}}
-
-
-{{else}}
-
Error: No biological host found!
-{{/if}}
diff --git a/nano/templates/pai_chatroom.tmpl b/nano/templates/pai_chatroom.tmpl
deleted file mode 100644
index d669a375d86..00000000000
--- a/nano/templates/pai_chatroom.tmpl
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
- Receiver:
-
-
- {{if data.receiver_off}}
- {{:helper.link("On", '', {"stopic":"chatroom", "toggler":1})}}
- {{:helper.link("Off", '', {"stopic":"chatroom", "toggler":2}, 'selected')}}
- {{else}}
- {{:helper.link("On", '', {"stopic":"chatroom", "toggler":1}, 'selected')}}
- {{:helper.link("Off", '', {"stopic":"chatroom", "toggler":2})}}
- {{/if}}
-
-
-
-
-
- Ringer:
-
-
- {{if data.ringer_off}}
- {{:helper.link("On", '', {"stopic":"chatroom", "ringer":1})}}
- {{:helper.link("Off", '', {"stopic":"chatroom", "ringer":2}, 'selected')}}
- {{else}}
- {{:helper.link("On", '', {"stopic":"chatroom", "ringer":1}, 'selected')}}
- {{:helper.link("Off", '', {"stopic":"chatroom", "ringer":2})}}
- {{/if}}
-
-
-
-{{for data.rooms}}
-
- {{:helper.link(value.name, '', {"stopic":"chatroom", "select":value.ref})}}
- {{:helper.link("Quick Message", '', {"stopic":"chatroom", "target":value.ref})}}
-
-{{/for}}
-
-{{if data.disconnected}}
-
Connection Lost
- {{:helper.link("Reconnect", 'refresh', {"stopic":"chatroom", "reconnect":1})}}
-{{else data.current_room}}
-
#{{:data.current_room_name}}
-
- Topic:
-
{{:helper.link(data.current_room_topic, '', {"stopic":"chatroom", "topic":1})}}
-
- {{prevName = value.username;}}
- {{/for}}
- {{:helper.link("Send Message", '', {"stopic":"chatroom", "target":data.current_room})}}
-{{/if}}
-
-{{if data.current_name}}
- {{:helper.link("Clear Screen", '', {"stopic":"chatroom", "select":"*NONE*"})}}
-{{/if}}
diff --git a/nano/templates/pai_directives.tmpl b/nano/templates/pai_directives.tmpl
deleted file mode 100644
index 2caff849a26..00000000000
--- a/nano/templates/pai_directives.tmpl
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/nano/templates/pai_doorjack.tmpl b/nano/templates/pai_doorjack.tmpl
deleted file mode 100644
index 69abd861e80..00000000000
--- a/nano/templates/pai_doorjack.tmpl
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
- Cable:
-
-
- {{if data.machine}}
- {{:helper.link("Connected", '', {}, 'selected')}}
- {{else data.cable}}
- {{:helper.link("Extended", '', {}, 'yellowButton')}}
- {{else}}
- {{:helper.link("Retracted", '', {"stopic":"door_jack", "cable":1})}}
- {{/if}}
-
-
-
- Hack:
-
-
- {{if data.inprogress}}
- {{if data.progress_a <= 33}}
-
{{:helper.smoothRound(data.progress_a)}}.{{:helper.smoothRound(data.progress_b)}}%
- {{else data.progress_a <= 67}}
-
{{:helper.smoothRound(data.progress_a)}}.{{:helper.smoothRound(data.progress_b)}}%
- {{else}}
-
{{:helper.smoothRound(data.progress_a)}}.{{:helper.smoothRound(data.progress_b)}}%
- {{/if}}
- {{:helper.link("Cancel", '', {"stopic":"door_jack", "cancel":1}, null, 'redButton')}}
- {{else}}
- {{:helper.link("Start", '', {"stopic":"door_jack", "jack":1})}}
- {{/if}}
-
-
-{{/if}}
\ No newline at end of file
diff --git a/nano/templates/pai_interface.tmpl b/nano/templates/pai_interface.tmpl
deleted file mode 100644
index e6441932665..00000000000
--- a/nano/templates/pai_interface.tmpl
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/nano/templates/pai_manifest.tmpl b/nano/templates/pai_manifest.tmpl
deleted file mode 100644
index d6827594e35..00000000000
--- a/nano/templates/pai_manifest.tmpl
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
diff --git a/nano/templates/pai_medrecords.tmpl b/nano/templates/pai_medrecords.tmpl
deleted file mode 100644
index 37b1d7061ca..00000000000
--- a/nano/templates/pai_medrecords.tmpl
+++ /dev/null
@@ -1,86 +0,0 @@
-
-
-{{if data.records}}
- {{for data.records}}
-
- {{:helper.link(value.name, '', {"stopic":"med_records", "select":value.ref})}}
-
- Failed to find some records; the information above may be incomplete or missing.
-
-{{/if}}
\ No newline at end of file
diff --git a/nano/templates/pai_messenger.tmpl b/nano/templates/pai_messenger.tmpl
deleted file mode 100644
index fb20f826f27..00000000000
--- a/nano/templates/pai_messenger.tmpl
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
- Receiver:
-
-
- {{if data.receiver_off}}
- {{:helper.link("On", '', {"stopic":"messenger", "toggler":1})}}
- {{:helper.link("Off", '', {"stopic":"messenger", "toggler":2}, 'selected')}}
- {{else}}
- {{:helper.link("On", '', {"stopic":"messenger", "toggler":1}, 'selected')}}
- {{:helper.link("Off", '', {"stopic":"messenger", "toggler":2})}}
- {{/if}}
-
-
-
- Ringer:
-
-
- {{if data.ringer_off}}
- {{:helper.link("On", '', {"stopic":"messenger", "ringer":1})}}
- {{:helper.link("Off", '', {"stopic":"messenger", "ringer":2}, 'selected')}}
- {{else}}
- {{:helper.link("On", '', {"stopic":"messenger", "ringer":1}, 'selected')}}
- {{:helper.link("Off", '', {"stopic":"messenger", "ringer":2})}}
- {{/if}}
-
-
- {{:helper.link(value.name, '', {"stopic":"messenger", "select":value.owner})}}
- {{:helper.link("Quick Message", '', {"stopic":"messenger", "target":value.ref})}}
-
-{{/for}}
-
-{{if data.current_name}}
- {{:helper.link("Clear Screen", '', {"stopic":"messenger", "select":"*NONE*"})}}
-{{/if}}
diff --git a/nano/templates/pai_radio.tmpl b/nano/templates/pai_radio.tmpl
deleted file mode 100644
index bcdacdb48f1..00000000000
--- a/nano/templates/pai_radio.tmpl
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
- Microphone:
-
-
- {{if data.listening}}
- {{:helper.link("On", '', {"stopic":"radio", "talk":1}, 'selected')}}
- {{:helper.link("Off", '', {"stopic":"radio", "talk":1})}}
- {{else}}
- {{:helper.link("On", '', {"stopic":"radio", "talk":1})}}
- {{:helper.link("Off", '', {"stopic":"radio", "talk":1}, 'selected')}}
- {{/if}}
-
-
-
- {{:value.name}}
-
-
- {{if value.listening}}
- {{:helper.link("On", '', {"stopic":"radio", "channel":value.name, "listen":1}, 'selected')}}
- {{:helper.link("Off", '', {"stopic":"radio", "channel":value.name, "listen":1})}}
- {{else}}
- {{:helper.link("On", '', {"stopic":"radio", "channel":value.name, "listen":1})}}
- {{:helper.link("Off", '', {"stopic":"radio", "channel":value.name, "listen":1}, 'selected')}}
- {{/if}}
-
-
-{{/for}}
\ No newline at end of file
diff --git a/nano/templates/pai_secrecords.tmpl b/nano/templates/pai_secrecords.tmpl
deleted file mode 100644
index e139034c0de..00000000000
--- a/nano/templates/pai_secrecords.tmpl
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-{{if data.records}}
- {{for data.records}}
-
- {{:helper.link(value.name, '', {"stopic":"sec_records", "select":value.ref})}}
-
- Failed to find some records; the information above may be incomplete or missing.
-
-{{/if}}
\ No newline at end of file
diff --git a/nano/templates/pai_signaller.tmpl b/nano/templates/pai_signaller.tmpl
deleted file mode 100644
index 20c2781bc9b..00000000000
--- a/nano/templates/pai_signaller.tmpl
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/paradise.dme b/paradise.dme
index 2ba11f3a87f..4d1572bfa7a 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1893,8 +1893,10 @@
#include "code\modules\mob\living\silicon\pai\recruit.dm"
#include "code\modules\mob\living\silicon\pai\say.dm"
#include "code\modules\mob\living\silicon\pai\software.dm"
-#include "code\modules\mob\living\silicon\pai\software_modules.dm"
#include "code\modules\mob\living\silicon\pai\update_status.dm"
+#include "code\modules\mob\living\silicon\pai\software\_base.dm"
+#include "code\modules\mob\living\silicon\pai\software\pai_apps.dm"
+#include "code\modules\mob\living\silicon\pai\software\pai_toggles.dm"
#include "code\modules\mob\living\silicon\robot\component.dm"
#include "code\modules\mob\living\silicon\robot\death.dm"
#include "code\modules\mob\living\silicon\robot\emote.dm"
diff --git a/tgui/packages/tgui/interfaces/PAI.js b/tgui/packages/tgui/interfaces/PAI.js
new file mode 100644
index 00000000000..1025820ed31
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PAI.js
@@ -0,0 +1,58 @@
+import { useBackend } from "../backend";
+import { Button, Box, Section, Flex, Icon } from "../components";
+import { Window } from "../layouts";
+
+/* This is all basically stolen from routes.js. */
+import { routingError } from "../routes";
+
+const RequirePAIInterface = require.context('./pai', false, /\.js$/);
+
+const GetApp = name => {
+ let appModule;
+ try {
+ appModule = RequirePAIInterface(`./${name}.js`);
+ } catch (err) {
+ if (err.code === 'MODULE_NOT_FOUND') {
+ return routingError('notFound', name);
+ }
+ throw err;
+ }
+ const Component = appModule[name];
+ if (!Component) {
+ return routingError('missingExport', name);
+ }
+ return Component;
+};
+
+export const PAI = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ app_template,
+ app_icon,
+ app_title,
+ } = data;
+
+ const App = GetApp(app_template);
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/common/AtmosScan.js b/tgui/packages/tgui/interfaces/common/AtmosScan.js
new file mode 100644
index 00000000000..35e9acec031
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/common/AtmosScan.js
@@ -0,0 +1,46 @@
+import { filter } from 'common/collections';
+import { useBackend } from "../../backend";
+import { Box, LabeledList } from "../../components";
+
+const getItemColor = (value, min2, min1, max1, max2) => {
+ if (value < min2) {
+ return "bad";
+ } else if (value < min1) {
+ return "average";
+ } else if (value > max1) {
+ return "average";
+ } else if (value > max2) {
+ return "bad";
+ }
+ return "good";
+};
+
+export const AtmosScan = (props, context) => {
+ const {
+ aircontents,
+ } = props.data;
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/common/CrewManifest.js b/tgui/packages/tgui/interfaces/common/CrewManifest.js
index 403487c9e24..80b05ebb72b 100644
--- a/tgui/packages/tgui/interfaces/common/CrewManifest.js
+++ b/tgui/packages/tgui/interfaces/common/CrewManifest.js
@@ -67,7 +67,14 @@ const ManifestTable = group => {
};
export const CrewManifest = (props, context) => {
- const { act, data } = useBackend(context);
+ const { act } = useBackend(context);
+ let finalData;
+ if (props.data) {
+ finalData = props.data;
+ } else {
+ let { data } = useBackend(context);
+ finalData = data;
+ }
// HOW TO USE THIS THING
/*
GLOB.data_core.get_manifest_json()
@@ -78,7 +85,7 @@ export const CrewManifest = (props, context) => {
const {
manifest,
- } = data;
+ } = finalData;
const {
heads,
diff --git a/tgui/packages/tgui/interfaces/common/Signaler.js b/tgui/packages/tgui/interfaces/common/Signaler.js
new file mode 100644
index 00000000000..c1abb23ed1b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/common/Signaler.js
@@ -0,0 +1,53 @@
+import { toFixed } from 'common/math';
+import { useBackend } from "../../backend";
+import { Button, LabeledList, NumberInput, Section } from "../../components";
+
+export const Signaler = (props, context) => {
+ const { act } = useBackend(context);
+ const {
+ code,
+ frequency,
+ minFrequency,
+ maxFrequency,
+ } = props.data;
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/common/SimpleRecords.js b/tgui/packages/tgui/interfaces/common/SimpleRecords.js
new file mode 100644
index 00000000000..6d40197965b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/common/SimpleRecords.js
@@ -0,0 +1,205 @@
+import { useBackend, useLocalState } from "../../backend";
+import { createSearch } from 'common/string';
+import { flow } from 'common/fp';
+import { filter, sortBy } from 'common/collections';
+import { Box, Input, Button, Section, LabeledList } from "../../components";
+
+export const SimpleRecords = (props, context) => {
+ const {
+ records,
+ } = props.data;
+
+ return (
+
+ );
+};
+
+const SelectionView = (props, context) => {
+ const { act } = useBackend(context);
+ const {
+ recordsList,
+ } = props.data;
+
+ const [
+ searchText,
+ setSearchText,
+ ] = useLocalState(context, 'searchText', '');
+
+ // Search for peeps
+ const SelectMembers = (people, searchText = '') => {
+ const MemberSearch = createSearch(searchText, member => member.Name);
+ return flow([
+ // Null member filter
+ filter(member => member?.Name),
+ // Optional search term
+ searchText && filter(MemberSearch),
+ // Slightly expensive, but way better than sorting in BYOND
+ sortBy(member => member.Name),
+ ])(recordsList);
+ };
+
+ const formattedRecords = SelectMembers(recordsList, searchText);
+
+ return (
+
+ );
+};
+
+const RecordView = (props, context) => {
+ const { act } = useBackend(context);
+ const {
+ records,
+ } = props.data;
+
+ const {
+ general,
+ medical,
+ security,
+ } = records;
+
+ let secondaryRecord;
+ switch (props.recordType) {
+ case "MED":
+ secondaryRecord = (
+
+ );
+
+};
diff --git a/tgui/packages/tgui/interfaces/pai/pai_atmosphere.js b/tgui/packages/tgui/interfaces/pai/pai_atmosphere.js
new file mode 100644
index 00000000000..ce2bb187268
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/pai/pai_atmosphere.js
@@ -0,0 +1,8 @@
+import { useBackend } from "../../backend";
+import { AtmosScan } from "../common/AtmosScan";
+
+export const pai_atmosphere = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return
;
+};
diff --git a/tgui/packages/tgui/interfaces/pai/pai_bioscan.js b/tgui/packages/tgui/interfaces/pai/pai_bioscan.js
new file mode 100644
index 00000000000..b684c66e259
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/pai/pai_bioscan.js
@@ -0,0 +1,71 @@
+import { useBackend } from "../../backend";
+import { Box, LabeledList, ProgressBar } from "../../components";
+
+export const pai_bioscan = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ holder,
+ dead,
+ health,
+ brute,
+ oxy,
+ tox,
+ burn,
+ temp,
+ } = data.app_data;
+
+ if (!holder) {
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/pai/pai_directives.js b/tgui/packages/tgui/interfaces/pai/pai_directives.js
new file mode 100644
index 00000000000..61e6b95f979
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/pai/pai_directives.js
@@ -0,0 +1,59 @@
+import { useBackend } from "../../backend";
+import { Box, LabeledList, Button } from "../../components";
+
+export const pai_directives = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ master,
+ dna,
+ prime,
+ supplemental,
+ } = data.app_data;
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/pai/pai_doorjack.js b/tgui/packages/tgui/interfaces/pai/pai_doorjack.js
new file mode 100644
index 00000000000..f6a92afc8e8
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/pai/pai_doorjack.js
@@ -0,0 +1,71 @@
+import { useBackend } from "../../backend";
+import { LabeledList, Button, ProgressBar, Box } from "../../components";
+
+export const pai_doorjack = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ cable,
+ machine,
+ inprogress,
+ progress,
+ aborted,
+ } = data.app_data;
+
+ let cableContent;
+
+ if (machine) {
+ cableContent = (
+