diff --git a/code/modules/pda/core_apps.dm b/code/modules/pda/core_apps.dm
index 68608e969aa..4b561c98444 100644
--- a/code/modules/pda/core_apps.dm
+++ b/code/modules/pda/core_apps.dm
@@ -124,4 +124,83 @@
if(isnull(results))
results = list(list("entry" = "pressure", "units" = "kPa", "val" = "0", "bad_high" = 120, "poor_high" = 110, "poor_low" = 95, "bad_low" = 80))
- data["aircontents"] = results
\ No newline at end of file
+ data["aircontents"] = results
+
+/datum/data/pda/app/news
+ name = "News"
+ icon = "newspaper"
+ template = "pda_news"
+
+ var/newsfeed_channel
+
+/datum/data/pda/app/news/update_ui(mob/user as mob, list/data)
+ data["feeds"] = compile_news()
+ data["latest_news"] = get_recent_news()
+ if(newsfeed_channel)
+ data["target_feed"] = data["feeds"][newsfeed_channel]
+ else
+ data["target_feed"] = null
+
+/datum/data/pda/app/news/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
+ if(..())
+ return TRUE
+ switch(action)
+ if("newsfeed")
+ newsfeed_channel = text2num(params["newsfeed"])
+
+/datum/data/pda/app/news/proc/compile_news()
+ var/list/feeds = list()
+ for(var/datum/feed_channel/channel in news_network.network_channels)
+ var/list/messages = list()
+ if(!channel.censored)
+ var/index = 0
+ for(var/datum/feed_message/FM in channel.messages)
+ index++
+ var/list/msgdata = list(
+ "author" = FM.author,
+ "body" = FM.body,
+ "img" = null,
+ "message_type" = FM.message_type,
+ "time_stamp" = FM.time_stamp,
+ "caption" = FM.caption,
+ "index" = index
+ )
+ if(FM.img)
+ msgdata["img"] = icon2base64(FM.img)
+ messages[++messages.len] = msgdata
+
+ feeds[++feeds.len] = list(
+ "name" = channel.channel_name,
+ "censored" = channel.censored,
+ "author" = channel.author,
+ "messages" = messages,
+ "index" = feeds.len + 1
+ )
+ return feeds
+
+/datum/data/pda/app/news/proc/get_recent_news()
+ var/list/news = list()
+
+ // Compile all the newscasts
+ for(var/datum/feed_channel/channel in news_network.network_channels)
+ if(!channel.censored)
+ for(var/datum/feed_message/FM in channel.messages)
+ var/body = replacetext(FM.body, "\n", "
")
+ news[++news.len] = list(
+ "channel" = channel.channel_name,
+ "author" = FM.author,
+ "body" = body,
+ "message_type" = FM.message_type,
+ "time_stamp" = FM.time_stamp,
+ "has_image" = (FM.img != null),
+ "caption" = FM.caption,
+ "time" = FM.post_time
+ )
+
+ // Cut out all but the youngest three
+ if(news.len > 3)
+ sortByKey(news, "time")
+ news.Cut(1, news.len - 2) // Last three have largest timestamps, youngest posts
+ news.Swap(1, 3) // List is sorted in ascending order of timestamp, we want descending
+
+ return news
\ No newline at end of file
diff --git a/code/modules/pda/pda.dm b/code/modules/pda/pda.dm
index 10126ff9314..3c9b03ce1ed 100644
--- a/code/modules/pda/pda.dm
+++ b/code/modules/pda/pda.dm
@@ -51,6 +51,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
var/list/programs = list(
new/datum/data/pda/app/main_menu,
new/datum/data/pda/app/notekeeper,
+ new/datum/data/pda/app/news,
new/datum/data/pda/app/messenger,
new/datum/data/pda/app/manifest,
new/datum/data/pda/app/atmos_scanner,
diff --git a/tgui/packages/tgui/interfaces/Pda.js b/tgui/packages/tgui/interfaces/Pda.js
index 11a5fab01c8..a1f52b283ac 100644
--- a/tgui/packages/tgui/interfaces/Pda.js
+++ b/tgui/packages/tgui/interfaces/Pda.js
@@ -1,7 +1,7 @@
import { round } from 'common/math';
import { Fragment } from 'inferno';
-import { useBackend } from "../backend";
-import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components";
+import { useBackend, useLocalState } from "../backend";
+import { Box, Button, Flex, Icon, LabeledList, Modal, ProgressBar, Section } from "../components";
import { Window } from "../layouts";
/* This is all basically stolen from routes.js. */
@@ -49,22 +49,28 @@ export const Pda = (props, context) => {
let App = getPdaApp(app.template);
+ const [settingsMode, setSettingsMode] = useLocalState(context, 'settingsMode', false);
+
return (
-
-
-
- {app.name}
-
- }
- p={1}>
-
-
+
+ {settingsMode && (
+
+ ) || (
+
+
+ {app.name}
+
+ }
+ p={1}>
+
+
+ )}
-
+
);
@@ -73,6 +79,11 @@ export const Pda = (props, context) => {
const PDAHeader = (props, context) => {
const { act, data } = useBackend(context);
+ const {
+ settingsMode,
+ setSettingsMode,
+ } = props;
+
const {
idInserted,
idLink,
@@ -82,45 +93,79 @@ const PDAHeader = (props, context) => {
return (
-
+
{!!idInserted && (
)}
- {!!cartridge_name && (
-
-
- )}
-
-
);
};
+const PDASettings = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ idInserted,
+ idLink,
+ cartridge_name,
+ } = data;
+
+ return (
+
+
+
+ act("Retro")} />
+
+ {!!cartridge_name && (
+
+ act("Eject")}
+ content={cartridge_name} />
+
+ )}
+ {!!idInserted && (
+
+ act("Authenticate")}
+ content={idLink} />
+
+ )}
+
+
+ );
+};
+
const PDAFooter = (props, context) => {
const { act, data } = useBackend(context);
+ const {
+ setSettingsMode,
+ } = props;
+
const {
app,
useRetro,
@@ -149,7 +194,10 @@ const PDAFooter = (props, context) => {
icon="home"
mb={0}
fontSize={1.7}
- onClick={() => act("Home")} />
+ onClick={() => {
+ setSettingsMode(false);
+ act("Home");
+ }} />
diff --git a/tgui/packages/tgui/interfaces/pda/pda_manifest.js b/tgui/packages/tgui/interfaces/pda/pda_manifest.js
index 3b9e1079357..5608049b832 100644
--- a/tgui/packages/tgui/interfaces/pda/pda_manifest.js
+++ b/tgui/packages/tgui/interfaces/pda/pda_manifest.js
@@ -8,7 +8,7 @@ export const pda_manifest = (props, context) => {
const { act, data } = useBackend(context);
return (
-
+
);
diff --git a/tgui/packages/tgui/interfaces/pda/pda_news.js b/tgui/packages/tgui/interfaces/pda/pda_news.js
new file mode 100644
index 00000000000..85c2041c75b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/pda/pda_news.js
@@ -0,0 +1,125 @@
+import { filter } from 'common/collections';
+import { decodeHtmlEntities, toTitleCase } from 'common/string';
+import { Fragment } from 'inferno';
+import { useBackend, useLocalState } from "../../backend";
+import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../../components";
+
+// Stolen wholesale from communicators. TGUITODO: Merge PDA & Communicator shared code once both are in
+/* News */
+export const pda_news = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ feeds,
+ target_feed,
+ } = data;
+
+ return (
+
+ {!feeds.length && (
+
+ Error: No newsfeeds available. Please try again later.
+
+ ) || target_feed && (
+
+ ) || (
+
+ )}
+
+ );
+};
+
+const NewsTargetFeed = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ target_feed,
+ } = data;
+
+ return (
+ act("newsfeed", { newsfeed: null })} />
+ }>
+ {target_feed.messages.length && target_feed.messages.map(message => (
+
+ - {decodeHtmlEntities(message.body)}
+ {!!message.img && (
+
+
+ {decodeHtmlEntities(message.caption) || null}
+
+ )}
+
+ [{message.message_type} by {decodeHtmlEntities(message.author)} - {message.time_stamp}]
+
+
+ )) || (
+
+ No stories found in {target_feed.name}.
+
+ )}
+
+ );
+};
+
+const NewsFeed = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ feeds,
+ latest_news,
+ } = data;
+
+ return (
+
+
+ {latest_news.length && (
+
+ {latest_news.map(news => (
+
+
+ {decodeHtmlEntities(news.channel)}
+ act("newsfeed", { newsfeed: news.index })}
+ content="Go to" />
+
+ - {decodeHtmlEntities(news.body)}
+ {!!news.img && (
+
+ [image omitted, view story for more details]
+ {news.caption || null}
+
+ )}
+
+ [{news.message_type} by {news.author} - {news.time_stamp}]
+
+
+ ))}
+
+ ) || (
+
+ No recent stories found.
+
+ )}
+
+
+ {feeds.map(feed => (
+ act("newsfeed", { newsfeed: feed.index })}
+ content={feed.name} />
+ ))}
+
+
+ );
+};
\ No newline at end of file