mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-11 10:43:20 +00:00
[MIRROR] PDA Embedded Images (#8970)
Co-authored-by: Heroman3003 <31296024+Heroman3003@users.noreply.github.com> Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
@@ -69,3 +69,9 @@
|
||||
savefile_key = "AUTO_AFK"
|
||||
default_value = TRUE
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
/datum/preference/toggle/messenger_embeds
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
savefile_key = "MessengerEmbeds"
|
||||
default_value = TRUE
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
data["silent"] = notify_silent // does the pda make noise when it receives a message?
|
||||
data["toff"] = toff // is the messenger function turned off?
|
||||
data["active_conversation"] = active_conversation // Which conversation are we following right now?
|
||||
data["enable_message_embeds"] = user?.client?.prefs?.read_preference(/datum/preference/toggle/messenger_embeds)
|
||||
|
||||
has_back = active_conversation
|
||||
if(active_conversation)
|
||||
|
||||
@@ -11,5 +11,6 @@ import 'core-js/es';
|
||||
import 'core-js/web/immediate';
|
||||
import 'core-js/web/queue-microtask';
|
||||
import 'core-js/web/timers';
|
||||
import 'core-js/full/url';
|
||||
import 'regenerator-runtime/runtime';
|
||||
import 'unfetch/polyfill';
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex, Icon, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, Flex, Icon, LabeledList, Section } from 'tgui/components';
|
||||
import { Window } from 'tgui/layouts';
|
||||
/* This is all basically stolen from routes.js. */
|
||||
import { routingError } from '../routes';
|
||||
import { routingError } from 'tgui/routes';
|
||||
|
||||
type Data = {
|
||||
owner: string;
|
||||
@@ -25,7 +24,7 @@ type Data = {
|
||||
};
|
||||
};
|
||||
|
||||
const requirePdaInterface = require.context('./pda', false, /\.tsx$/);
|
||||
const requirePdaInterface = require.context('./pda_screens', false, /\.tsx$/);
|
||||
|
||||
function getPdaApp(name: string) {
|
||||
let appModule: __WebpackModuleApi.RequireContext;
|
||||
@@ -1,8 +1,7 @@
|
||||
import { filter } from 'common/collections';
|
||||
import { decodeHtmlEntities } from 'common/string';
|
||||
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, LabeledList } from '../../components';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, LabeledList } from 'tgui/components';
|
||||
|
||||
type Data = {
|
||||
aircontents: aircontent[];
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, LabeledList, Section } from '../../components';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, LabeledList, Section } from 'tgui/components';
|
||||
|
||||
type Data = {
|
||||
janitor: {
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BooleanLike } from 'common/react';
|
||||
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, Button, LabeledList, Section } from '../../components';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, LabeledList, Section } from 'tgui/components';
|
||||
|
||||
type Data = {
|
||||
owner: string;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box } from '../../components';
|
||||
import { CrewManifestContent } from '../CrewManifest';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box } from 'tgui/components';
|
||||
|
||||
import { CrewManifestContent } from '../../CrewManifest';
|
||||
|
||||
export const pda_manifest = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, Button, LabeledList, Section } from '../../components';
|
||||
import { GeneralRecord, MedicalRecord, RecordList } from './pda_types';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, LabeledList, Section } from 'tgui/components';
|
||||
|
||||
import { GeneralRecord, MedicalRecord, RecordList } from '../pda_types';
|
||||
|
||||
type Data = {
|
||||
records: {
|
||||
463
tgui/packages/tgui/interfaces/Pda/pda_screens/pda_messenger.tsx
Normal file
463
tgui/packages/tgui/interfaces/Pda/pda_screens/pda_messenger.tsx
Normal file
@@ -0,0 +1,463 @@
|
||||
import { filter } from 'common/collections';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { decodeHtmlEntities } from 'common/string';
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, Image, LabeledList, Section } from 'tgui/components';
|
||||
|
||||
import { fetchRetry } from '../../../http';
|
||||
|
||||
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 }[];
|
||||
enable_message_embeds: BooleanLike;
|
||||
};
|
||||
|
||||
type pda = {
|
||||
Name: string;
|
||||
Reference: string;
|
||||
Detonate: string;
|
||||
inconvo: string;
|
||||
};
|
||||
|
||||
type message = {
|
||||
sent: BooleanLike;
|
||||
owner: string;
|
||||
job: string;
|
||||
message: string;
|
||||
target: string;
|
||||
};
|
||||
|
||||
// Really cursed old API that was deprecated before IE8 but still works in IE11 because lol lmao
|
||||
type IeWindow = Window &
|
||||
typeof globalThis & {
|
||||
clipboardData: {
|
||||
setData: (type: 'Text', text: string) => {};
|
||||
};
|
||||
};
|
||||
|
||||
const CopyToClipboardButton = (props: { messages: message[] }) => {
|
||||
const [showCompletion, setShowCompletion] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (showCompletion) {
|
||||
let timeout = setTimeout(() => {
|
||||
setShowCompletion(false);
|
||||
}, 1000);
|
||||
return () => clearTimeout(timeout);
|
||||
}
|
||||
}, [showCompletion]);
|
||||
|
||||
const { messages } = props;
|
||||
return (
|
||||
<Button
|
||||
icon="clipboard"
|
||||
onClick={() => {
|
||||
copyToClipboard(messages);
|
||||
setShowCompletion(true);
|
||||
}}
|
||||
selected={showCompletion}
|
||||
>
|
||||
{showCompletion ? 'Copied!' : 'Copy to Clipboard'}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const copyToClipboard = (messages: message[]) => {
|
||||
let string = '';
|
||||
|
||||
for (let message of messages) {
|
||||
if (message.sent) {
|
||||
string += `You: ${message.message}\n`;
|
||||
} else {
|
||||
string += `Them: ${message.message}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
let ie_window = window as IeWindow;
|
||||
ie_window.clipboardData.setData('Text', string);
|
||||
};
|
||||
|
||||
export const pda_messenger = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
|
||||
const { active_conversation } = data;
|
||||
|
||||
if (active_conversation) {
|
||||
return <ActiveConversation />;
|
||||
}
|
||||
return <MessengerList />;
|
||||
};
|
||||
|
||||
const MessengerList = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
|
||||
const { convopdas, pdas, charges, silent, toff } = data;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Messenger Functions">
|
||||
<Button
|
||||
selected={!silent}
|
||||
icon={silent ? 'volume-mute' : 'volume-up'}
|
||||
onClick={() => act('Toggle Ringer')}
|
||||
>
|
||||
Ringer: {silent ? 'Off' : 'On'}
|
||||
</Button>
|
||||
<Button
|
||||
color={toff ? 'bad' : 'green'}
|
||||
icon="power-off"
|
||||
onClick={() => act('Toggle Messenger')}
|
||||
>
|
||||
Messenger: {toff ? 'Off' : 'On'}
|
||||
</Button>
|
||||
<Button icon="bell" onClick={() => act('Ringtone')}>
|
||||
Set Ringtone
|
||||
</Button>
|
||||
<Button
|
||||
icon="trash"
|
||||
color="bad"
|
||||
onClick={() => act('Clear', { option: 'All' })}
|
||||
>
|
||||
Delete All Conversations
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
{(!toff && (
|
||||
<Box>
|
||||
{!!charges && <Box>{charges} charges left.</Box>}
|
||||
{(!convopdas.length && !pdas.length && (
|
||||
<Box>No other PDAs located.</Box>
|
||||
)) || (
|
||||
<Box>
|
||||
<PDAList
|
||||
title="Current Conversations"
|
||||
pdas={convopdas}
|
||||
msgAct="Select Conversation"
|
||||
/>
|
||||
<PDAList title="Other PDAs" pdas={pdas} msgAct="Message" />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)) || (
|
||||
<Box color="bad" mt={2}>
|
||||
Messenger Offline.
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const PDAList = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
|
||||
const { pdas, title, msgAct } = props;
|
||||
|
||||
const { charges, plugins } = data;
|
||||
|
||||
if (!pdas || !pdas.length) {
|
||||
return <Section title={title}>No PDAs found.</Section>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Section title={title}>
|
||||
{pdas.map((pda) => (
|
||||
<Box key={pda.Reference}>
|
||||
<Button
|
||||
icon="arrow-circle-down"
|
||||
onClick={() => act(msgAct, { target: pda.Reference })}
|
||||
>
|
||||
{pda.Name}
|
||||
</Button>
|
||||
{!!charges &&
|
||||
plugins.map((plugin) => (
|
||||
<Button
|
||||
key={plugin.ref}
|
||||
icon={plugin.icon}
|
||||
onClick={() =>
|
||||
act('Messenger Plugin', {
|
||||
plugin: plugin.ref,
|
||||
target: pda.Reference,
|
||||
})
|
||||
}
|
||||
>
|
||||
{plugin.name}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const ActiveConversation = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { convo_name, convo_job, messages, active_conversation } = data;
|
||||
const [asciiMode, setAsciiMode] = useState(false);
|
||||
|
||||
return (
|
||||
<Section
|
||||
title={`Conversation with ${convo_name} (${convo_job})`}
|
||||
buttons={
|
||||
<>
|
||||
<Button
|
||||
icon="eye"
|
||||
selected={asciiMode}
|
||||
tooltip="ASCII Mode"
|
||||
tooltipPosition="bottom-end"
|
||||
onClick={() => setAsciiMode(!asciiMode)}
|
||||
/>
|
||||
<Button
|
||||
icon="reply"
|
||||
tooltip="Reply"
|
||||
tooltipPosition="bottom-end"
|
||||
onClick={() => act('Message', { target: active_conversation })}
|
||||
/>
|
||||
<Button.Confirm
|
||||
icon="trash"
|
||||
color="bad"
|
||||
tooltip="Delete Conversation"
|
||||
tooltipPosition="bottom-end"
|
||||
onClick={() => act('Clear', { option: 'Convo' })}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ScrollOnMount>
|
||||
{asciiMode ? (
|
||||
<ActiveConversationASCII
|
||||
messages={messages}
|
||||
active_conversation={active_conversation}
|
||||
/>
|
||||
) : (
|
||||
<ActiveConversationTinder
|
||||
messages={messages}
|
||||
active_conversation={active_conversation}
|
||||
/>
|
||||
)}
|
||||
</ScrollOnMount>
|
||||
<Button
|
||||
mt={1}
|
||||
icon="comment"
|
||||
onClick={() => act('Message', { target: active_conversation })}
|
||||
>
|
||||
Reply
|
||||
</Button>
|
||||
<CopyToClipboardButton
|
||||
messages={messages.filter((i) => i.target === active_conversation)}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Scrolls to the bottom of section on mount.
|
||||
*/
|
||||
const ScrollOnMount = (props: { children: ReactNode }) => {
|
||||
const { children } = props;
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current?.scrollIntoView();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Section fill height="63vh" scrollable>
|
||||
{children}
|
||||
<div ref={ref} />
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const ActiveConversationASCII = (props: {
|
||||
messages: message[];
|
||||
active_conversation: string;
|
||||
}) => {
|
||||
const { messages, active_conversation } = props;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{filter(messages, (im: message) => im.target === active_conversation).map(
|
||||
(im, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
className={
|
||||
im.sent ? 'ClassicMessage_Sent' : 'ClassicMessage_Received'
|
||||
}
|
||||
>
|
||||
{im.sent ? 'You:' : 'Them:'} {decodeHtmlEntities(im.message)}
|
||||
</Box>
|
||||
),
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const findClassMessage = (im, lastIndex, filterArray) => {
|
||||
if (lastIndex < 0 || lastIndex > filterArray.length) {
|
||||
return im.sent
|
||||
? 'TinderMessage_First_Sent'
|
||||
: 'TinderMessage_First_Received';
|
||||
}
|
||||
|
||||
let lastSent = filterArray[lastIndex].sent;
|
||||
if (im.sent && lastSent) {
|
||||
return 'TinderMessage_Subsequent_Sent';
|
||||
} else if (!im.sent && !lastSent) {
|
||||
return 'TinderMessage_Subsequent_Received';
|
||||
}
|
||||
return im.sent ? 'TinderMessage_First_Sent' : 'TinderMessage_First_Received';
|
||||
};
|
||||
|
||||
const ActiveConversationTinder = (props: {
|
||||
messages: message[];
|
||||
active_conversation: string;
|
||||
}) => {
|
||||
const { messages, active_conversation } = props;
|
||||
return (
|
||||
<Box>
|
||||
{messages
|
||||
.filter((im: message) => im.target === active_conversation)
|
||||
.map((im, i, filterArr) => (
|
||||
<TinderMessage
|
||||
key={i}
|
||||
im={im}
|
||||
className={findClassMessage(im, i - 1, filterArr)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const TinderMessage = (props: { im: message; className: string }) => {
|
||||
const { data } = useBackend<Data>();
|
||||
const { enable_message_embeds } = data;
|
||||
const { im, className } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box textAlign={im.sent ? 'right' : 'left'} mb={1}>
|
||||
<Box maxWidth="75%" className={className} inline>
|
||||
{decodeHtmlEntities(im.message)}
|
||||
</Box>
|
||||
</Box>
|
||||
{!!enable_message_embeds && (
|
||||
<TinderMessageEmbedAttempt im={im} className={className} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ALLOWED_HOSTNAMES = ['cdn.discordapp.com', 'i.imgur.com', 'imgur.com'];
|
||||
|
||||
const TinderMessageEmbedAttempt = (props: {
|
||||
im: message;
|
||||
className: string;
|
||||
}) => {
|
||||
const { im, className } = props;
|
||||
const [elem, setElem] = useState<ReactNode>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let link = decodeHtmlEntities(im.message.trim());
|
||||
|
||||
// Early easy check
|
||||
if (!link.startsWith('https://')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We assume the entire message is a URL, so any spaces disqualify it
|
||||
if (link.includes(' ')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to parse it as a URL.
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(link);
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Okay, we're pretty damn confident this is a URL now: Check for allowed domains.
|
||||
if (!ALLOWED_HOSTNAMES.includes(url.hostname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
async function resolveUrlToImg(url: URL): Promise<string | null> {
|
||||
const headers = {
|
||||
Accept: 'image/jpeg, image/png, image/gif',
|
||||
'User-Agent': 'SS13-Virgo-ImageEmbeds/1.0',
|
||||
};
|
||||
|
||||
const response = await fetchRetry(url.toString(), { headers });
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let type = response.headers.get('Content-Type');
|
||||
|
||||
// If we just fetched an image, use it!
|
||||
if (
|
||||
type === 'image/jpeg' ||
|
||||
type === 'image/png' ||
|
||||
type === 'image/gif'
|
||||
) {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
resolveUrlToImg(url).then((val) => {
|
||||
if (val) {
|
||||
setElem(<TinderMessageEmbed im={im} className={className} img={val} />);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return elem;
|
||||
};
|
||||
|
||||
const TinderMessageEmbed = (props: {
|
||||
im: message;
|
||||
className: string;
|
||||
img: string;
|
||||
}) => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const { im, className, img } = props;
|
||||
return (
|
||||
<Box textAlign={im.sent ? 'right' : 'left'} mb={1}>
|
||||
<Box maxWidth="75%" className={className} inline>
|
||||
<Box fontSize={0.9}>Embed</Box>
|
||||
{show ? (
|
||||
<Image fixBlur={false} src={img} maxWidth={30} maxHeight={30} />
|
||||
) : (
|
||||
<Button
|
||||
width={30}
|
||||
height={20}
|
||||
onClick={() => setShow(true)}
|
||||
color="black"
|
||||
textAlign="center"
|
||||
verticalAlignContent="middle"
|
||||
fontSize={3}
|
||||
>
|
||||
Click to
|
||||
<br />
|
||||
Show
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,7 @@
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { decodeHtmlEntities } from 'common/string';
|
||||
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, Button, Image, Section } from '../../components';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, Image, Section } from 'tgui/components';
|
||||
|
||||
type Data = {
|
||||
feeds: feed[];
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint react/no-danger: "off" */
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, Button, Section, Table } from '../../components';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, Section, Table } from 'tgui/components';
|
||||
|
||||
type Data = { note: string; notename: string };
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PowerMonitorContent } from '../../PowerMonitor/PowerMonitorContent';
|
||||
|
||||
export const pda_power = (props) => {
|
||||
return <PowerMonitorContent />;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, Button, LabeledList, Section } from '../../components';
|
||||
import { GeneralRecord, RecordList, SecurityRecord } from './pda_types';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, LabeledList, Section } from 'tgui/components';
|
||||
|
||||
import { GeneralRecord, RecordList, SecurityRecord } from '../pda_types';
|
||||
|
||||
type Data = {
|
||||
records: {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SignalerContent } from '../Signaler';
|
||||
import { SignalerContent } from '../../Signaler';
|
||||
|
||||
export const pda_signaller = (props) => {
|
||||
return <SignalerContent />;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, Button, LabeledList } from '../../components';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, LabeledList } from 'tgui/components';
|
||||
|
||||
type Data = { records: { message1: string; message2: string } | null };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, LabeledList, Section } from '../../components';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, LabeledList, Section } from 'tgui/components';
|
||||
|
||||
type Data = {
|
||||
supply: {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconProps } from '../../components/Icon';
|
||||
import { IconProps } from 'tgui/components/Icon';
|
||||
/**
|
||||
* Gernal Record data
|
||||
*/
|
||||
@@ -72,3 +72,11 @@ export const AUTO_AFK: FeatureToggle = {
|
||||
'When enabled, you will automatically be marked as AFK if you are idle for too long.',
|
||||
component: CheckboxInput,
|
||||
};
|
||||
|
||||
export const MessengerEmbeds: FeatureToggle = {
|
||||
name: 'Messenger Embeds',
|
||||
category: 'UI',
|
||||
description:
|
||||
'When enabled, PDAs and Communicators will attempt to embed links from discord & imgur.',
|
||||
component: CheckboxInput,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { BooleanLike } from 'common/react';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { GeneralRecord, MedicalRecord, RecordList } from './pda/pda_types';
|
||||
import { GeneralRecord, MedicalRecord, RecordList } from './Pda/pda_types';
|
||||
|
||||
type Data = {
|
||||
records: RecordList;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { BooleanLike } from 'common/react';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { GeneralRecord, RecordList, SecurityRecord } from './pda/pda_types';
|
||||
import { GeneralRecord, RecordList, SecurityRecord } from './Pda/pda_types';
|
||||
|
||||
type Data = {
|
||||
records: RecordList;
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
import { filter } from 'common/collections';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { decodeHtmlEntities } from 'common/string';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, Button, LabeledList, Section } from '../../components';
|
||||
|
||||
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 }[];
|
||||
};
|
||||
|
||||
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<Data>();
|
||||
|
||||
const { active_conversation } = data;
|
||||
|
||||
if (active_conversation) {
|
||||
return <ActiveConversation />;
|
||||
}
|
||||
return <MessengerList />;
|
||||
};
|
||||
|
||||
const findClassMessage = (im, lastIndex, filterArray) => {
|
||||
if (lastIndex < 0 || lastIndex > filterArray.length) {
|
||||
return im.sent
|
||||
? 'TinderMessage_First_Sent'
|
||||
: 'TinderMessage_First_Received';
|
||||
}
|
||||
|
||||
let lastSent = filterArray[lastIndex].sent;
|
||||
if (im.sent && lastSent) {
|
||||
return 'TinderMessage_Subsequent_Sent';
|
||||
} else if (!im.sent && !lastSent) {
|
||||
return 'TinderMessage_Subsequent_Received';
|
||||
}
|
||||
return im.sent ? 'TinderMessage_First_Sent' : 'TinderMessage_First_Received';
|
||||
};
|
||||
|
||||
const ActiveConversation = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
|
||||
const { convo_name, convo_job, messages, active_conversation } = data;
|
||||
|
||||
const [clipboardMode, setClipboardMode] = useState(false);
|
||||
|
||||
let body = (
|
||||
<Section
|
||||
title={'Conversation with ' + convo_name + ' (' + convo_job + ')'}
|
||||
buttons={
|
||||
<Button
|
||||
icon="eye"
|
||||
selected={clipboardMode}
|
||||
tooltip="Enter Clipboard Mode"
|
||||
tooltipPosition="bottom-end"
|
||||
onClick={() => setClipboardMode(!clipboardMode)}
|
||||
/>
|
||||
}
|
||||
height="450px"
|
||||
stretchContents
|
||||
>
|
||||
<Button
|
||||
icon="comment"
|
||||
onClick={() => act('Message', { target: active_conversation })}
|
||||
>
|
||||
Reply
|
||||
</Button>
|
||||
<Section
|
||||
style={{
|
||||
height: '97%',
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
{filter(
|
||||
messages,
|
||||
(im: message) => im.target === active_conversation,
|
||||
).map((im, i, filterArr) => (
|
||||
<Box textAlign={im.sent ? 'right' : 'left'} mb={1} key={i}>
|
||||
<Box
|
||||
maxWidth="75%"
|
||||
className={findClassMessage(im, i - 1, filterArr)}
|
||||
inline
|
||||
>
|
||||
{decodeHtmlEntities(im.message)}
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
<Button
|
||||
icon="comment"
|
||||
onClick={() => act('Message', { target: active_conversation })}
|
||||
>
|
||||
Reply
|
||||
</Button>
|
||||
</Section>
|
||||
);
|
||||
|
||||
if (clipboardMode) {
|
||||
body = (
|
||||
<Section
|
||||
title={'Conversation with ' + convo_name + ' (' + convo_job + ')'}
|
||||
buttons={
|
||||
<Button
|
||||
icon="eye"
|
||||
selected={clipboardMode}
|
||||
tooltip="Exit Clipboard Mode"
|
||||
tooltipPosition="bottom-end"
|
||||
onClick={() => setClipboardMode(!clipboardMode)}
|
||||
/>
|
||||
}
|
||||
height="450px"
|
||||
stretchContents
|
||||
>
|
||||
<Button
|
||||
icon="comment"
|
||||
onClick={() => act('Message', { target: active_conversation })}
|
||||
>
|
||||
Reply
|
||||
</Button>
|
||||
<Section
|
||||
style={{
|
||||
height: '97%',
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
{filter(
|
||||
messages,
|
||||
(im: message) => im.target === active_conversation,
|
||||
).map((im, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
className={
|
||||
im.sent ? 'ClassicMessage_Sent' : 'ClassicMessage_Received'
|
||||
}
|
||||
>
|
||||
{im.sent ? 'You:' : 'Them:'} {decodeHtmlEntities(im.message)}
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
<Button
|
||||
icon="comment"
|
||||
onClick={() => act('Message', { target: active_conversation })}
|
||||
>
|
||||
Reply
|
||||
</Button>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Messenger Functions">
|
||||
<Button
|
||||
icon="trash"
|
||||
color="bad"
|
||||
onClick={() => act('Clear', { option: 'Convo' })}
|
||||
>
|
||||
Delete Conversations
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
{body}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const MessengerList = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
|
||||
const { convopdas, pdas, charges, silent, toff } = data;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Messenger Functions">
|
||||
<Button
|
||||
selected={!silent}
|
||||
icon={silent ? 'volume-mute' : 'volume-up'}
|
||||
onClick={() => act('Toggle Ringer')}
|
||||
>
|
||||
Ringer: {silent ? 'Off' : 'On'}
|
||||
</Button>
|
||||
<Button
|
||||
color={toff ? 'bad' : 'green'}
|
||||
icon="power-off"
|
||||
onClick={() => act('Toggle Messenger')}
|
||||
>
|
||||
Messenger: {toff ? 'Off' : 'On'}
|
||||
</Button>
|
||||
<Button icon="bell" onClick={() => act('Ringtone')}>
|
||||
Set Ringtone
|
||||
</Button>
|
||||
<Button
|
||||
icon="trash"
|
||||
color="bad"
|
||||
onClick={() => act('Clear', { option: 'All' })}
|
||||
>
|
||||
Delete All Conversations
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
{(!toff && (
|
||||
<Box>
|
||||
{!!charges && <Box>{charges} charges left.</Box>}
|
||||
{(!convopdas.length && !pdas.length && (
|
||||
<Box>No other PDAs located.</Box>
|
||||
)) || (
|
||||
<Box>
|
||||
<PDAList
|
||||
title="Current Conversations"
|
||||
pdas={convopdas}
|
||||
msgAct="Select Conversation"
|
||||
/>
|
||||
<PDAList title="Other PDAs" pdas={pdas} msgAct="Message" />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)) || (
|
||||
<Box color="bad" mt={2}>
|
||||
Messenger Offline.
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const PDAList = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
|
||||
const { pdas, title, msgAct } = props;
|
||||
|
||||
const { charges, plugins } = data;
|
||||
|
||||
if (!pdas || !pdas.length) {
|
||||
return <Section title={title}>No PDAs found.</Section>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Section title={title}>
|
||||
{pdas.map((pda) => (
|
||||
<Box key={pda.Reference}>
|
||||
<Button
|
||||
icon="arrow-circle-down"
|
||||
onClick={() => act(msgAct, { target: pda.Reference })}
|
||||
>
|
||||
{pda.Name}
|
||||
</Button>
|
||||
{!!charges &&
|
||||
plugins.map((plugin) => (
|
||||
<Button
|
||||
key={plugin.ref}
|
||||
icon={plugin.icon}
|
||||
onClick={() =>
|
||||
act('Messenger Plugin', {
|
||||
plugin: plugin.ref,
|
||||
target: pda.Reference,
|
||||
})
|
||||
}
|
||||
>
|
||||
{plugin.name}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
import { PowerMonitorContent } from '../PowerMonitor/PowerMonitorContent';
|
||||
|
||||
export const pda_power = (props) => {
|
||||
return <PowerMonitorContent />;
|
||||
};
|
||||
Reference in New Issue
Block a user