hark: display chat notifications

This commit is contained in:
Liam Fitzgerald 2020-11-05 15:34:48 +10:00
parent e1b11d610d
commit a0ea86098b
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
7 changed files with 187 additions and 42 deletions

View File

@ -0,0 +1,93 @@
import React, { useCallback } from "react";
import { Link } from "react-router-dom";
import GlobalApi from "~/logic/api/global";
import {
Rolodex,
Associations,
ChatNotifIndex,
ChatNotificationContents,
Groups,
} from "~/types";
import { BigInteger } from "big-integer";
import { Box, Col } from "@tlon/indigo-react";
import { Header } from "./header";
import { pluralize } from "~/logic/lib/util";
import ChatMessage from "../chat/components/ChatMessage";
function describeNotification(lent: number) {
return `sent ${pluralize("message", lent !== 1)} in`;
}
export function ChatNotification(props: {
index: ChatNotifIndex;
contents: ChatNotificationContents;
archived: boolean;
read: boolean;
time: number;
timebox: BigInteger;
associations: Associations;
contacts: Rolodex;
groups: Groups;
api: GlobalApi;
}) {
const { index, contents, read, time, api, timebox } = props;
const authors = _.map(contents, "author");
const association = props.associations.chat[index];
const groupPath = association["group-path"];
const appPath = association["app-path"];
const group = props.groups[groupPath];
const desc = describeNotification(contents.length);
const groupContacts = props.contacts[groupPath];
const onClick = useCallback(() => {
if (props.archived) {
return;
}
const func = read ? "unread" : "read";
return api.hark[func](timebox, { chat: index });
}, [api, timebox, index, read]);
return (
<Col onClick={onClick} flexGrow="1" p="2">
<Header
chat
associations={props.associations}
read={read}
archived={props.archived}
time={time}
authors={authors}
moduleIcon="Chat"
channel={index}
contacts={props.contacts}
group={groupPath}
description={desc}
/>
<Col pb="3" pl="5">
{_.map(_.take(contents, 5), (content, idx) => {
const to = `/~landscape${groupPath}/resource/chat${appPath}?msg=${content.number}`;
return (
<Link key={idx} to={to}>
<ChatMessage
measure={() => {}}
msg={content}
isLastRead={false}
group={group}
contacts={groupContacts}
/>
</Link>
);
})}
{contents.length > 5 && (
<Box ml="4" mt="3" mb="2" color="gray" fontSize="14px">
and {contents.length - 5} other message
{contents.length > 6 ? "s" : ""}
</Box>
)}
</Col>
</Col>
);
}

View File

@ -42,6 +42,7 @@ export function Header(props: {
time: number; time: number;
read: boolean; read: boolean;
associations: Associations; associations: Associations;
chat?: boolean;
}) { }) {
const { description, channel, group, moduleIcon, read } = props; const { description, channel, group, moduleIcon, read } = props;
const contacts = props.contacts[group] || {}; const contacts = props.contacts[group] || {};
@ -70,8 +71,9 @@ export function Header(props: {
const groupTitle = const groupTitle =
props.associations.contacts?.[props.group]?.metadata?.title || props.group; props.associations.contacts?.[props.group]?.metadata?.title || props.group;
const app = props.chat ? 'chat' : 'graph';
const channelTitle = const channelTitle =
(channel && props.associations.graph?.[channel]?.metadata?.title) || (channel && props.associations?.[app]?.[channel]?.metadata?.title) ||
channel; channel;
return ( return (

View File

@ -12,7 +12,7 @@ import { Associations } from "~/types";
type DatedTimebox = [BigInteger, Timebox]; type DatedTimebox = [BigInteger, Timebox];
function filterNotification(groups: string[]) { function filterNotification(associations: Associations, groups: string[]) {
if (groups.length === 0) { if (groups.length === 0) {
return () => true; return () => true;
} }
@ -23,6 +23,9 @@ function filterNotification(groups: string[]) {
} else if ("group" in n.index) { } else if ("group" in n.index) {
const { group } = n.index.group; const { group } = n.index.group;
return groups.findIndex((g) => group === g) !== -1; return groups.findIndex((g) => group === g) !== -1;
} else if ("chat" in n.index) {
const group = associations.chat[n.index.chat]?.["group-path"];
return groups.findIndex((g) => group === g) !== -1;
} }
return true; return true;
}; };
@ -56,7 +59,7 @@ export default function Inbox(props: {
const notificationsByDay = f.flow( const notificationsByDay = f.flow(
f.map<DatedTimebox>(([date, nots]) => [ f.map<DatedTimebox>(([date, nots]) => [
date, date,
nots.filter(filterNotification(props.filter)), nots.filter(filterNotification(associations, props.filter)),
]), ]),
f.groupBy<DatedTimebox>(([date]) => f.groupBy<DatedTimebox>(([date]) =>
moment(daToUnix(date)).format("DDMMYYYY") moment(daToUnix(date)).format("DDMMYYYY")
@ -75,6 +78,7 @@ export default function Inbox(props: {
associations={props.associations} associations={props.associations}
graphConfig={props.notificationsGraphConfig} graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig} groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
api={api} api={api}
/> />
)} )}
@ -92,6 +96,7 @@ export default function Inbox(props: {
api={api} api={api}
graphConfig={props.notificationsGraphConfig} graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig} groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
/> />
) )
)} )}
@ -119,6 +124,7 @@ function DaySection({
api, api,
groupConfig, groupConfig,
graphConfig, graphConfig,
chatConfig,
}) { }) {
const calendar = latest const calendar = latest
? MOMENT_CALENDAR_DATE ? MOMENT_CALENDAR_DATE
@ -143,6 +149,7 @@ function DaySection({
<Notification <Notification
graphConfig={graphConfig} graphConfig={graphConfig}
groupConfig={groupConfig} groupConfig={groupConfig}
chatConfig={chatConfig}
api={api} api={api}
associations={associations} associations={associations}
notification={not} notification={not}

View File

@ -8,12 +8,14 @@ import {
NotificationGraphConfig, NotificationGraphConfig,
GroupNotificationsConfig, GroupNotificationsConfig,
NotifIndex, NotifIndex,
Associations Associations,
} from "~/types"; } from "~/types";
import GlobalApi from "~/logic/api/global"; import GlobalApi from "~/logic/api/global";
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction"; import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
import { GroupNotification } from "./group"; import { GroupNotification } from "./group";
import { GraphNotification } from "./graph"; import { GraphNotification } from "./graph";
import { ChatNotification } from "./chat";
import { BigInteger } from "big-integer";
interface NotificationProps { interface NotificationProps {
notification: IndexedNotification; notification: IndexedNotification;
@ -23,12 +25,14 @@ interface NotificationProps {
archived: boolean; archived: boolean;
graphConfig: NotificationGraphConfig; graphConfig: NotificationGraphConfig;
groupConfig: GroupNotificationsConfig; groupConfig: GroupNotificationsConfig;
chatConfig: string[];
} }
function getMuted( function getMuted(
idx: NotifIndex, idx: NotifIndex,
groups: GroupNotificationsConfig, groups: GroupNotificationsConfig,
graphs: NotificationGraphConfig graphs: NotificationGraphConfig,
chat: string[]
) { ) {
if ("graph" in idx) { if ("graph" in idx) {
const { graph } = idx.graph; const { graph } = idx.graph;
@ -37,6 +41,9 @@ function getMuted(
if ("group" in idx) { if ("group" in idx) {
return _.findIndex(groups || [], (g) => g === idx.group.group) === -1; return _.findIndex(groups || [], (g) => g === idx.group.group) === -1;
} }
if ("chat" in idx) {
return _.findIndex(chat || [], (c) => c === idx.chat) === -1;
}
return false; return false;
} }
@ -48,6 +55,7 @@ function NotificationWrapper(props: {
archived: boolean; archived: boolean;
graphConfig: NotificationGraphConfig; graphConfig: NotificationGraphConfig;
groupConfig: GroupNotificationsConfig; groupConfig: GroupNotificationsConfig;
chatConfig: string[];
}) { }) {
const { api, time, notif, children } = props; const { api, time, notif, children } = props;
@ -55,7 +63,12 @@ function NotificationWrapper(props: {
return api.hark.archive(time, notif.index); return api.hark.archive(time, notif.index);
}, [time, notif]); }, [time, notif]);
const isMuted = getMuted(notif.index, props.groupConfig, props.graphConfig); const isMuted = getMuted(
notif.index,
props.groupConfig,
props.graphConfig,
props.chatConfig
);
const onChangeMute = useCallback(async () => { const onChangeMute = useCallback(async () => {
const func = isMuted ? "unmute" : "mute"; const func = isMuted ? "unmute" : "mute";
@ -84,19 +97,26 @@ export function Notification(props: NotificationProps) {
const { notification, associations, archived } = props; const { notification, associations, archived } = props;
const { read, contents, time } = notification.notification; const { read, contents, time } = notification.notification;
const Wrapper = ({ children }) => (
<NotificationWrapper
archived={archived}
notif={notification}
time={props.time}
api={props.api}
graphConfig={props.graphConfig}
groupConfig={props.groupConfig}
chatConfig={props.chatConfig}
>
{children}
</NotificationWrapper>
);
if ("graph" in notification.index) { if ("graph" in notification.index) {
const index = notification.index.graph; const index = notification.index.graph;
const c: GraphNotificationContents = (contents as any).graph; const c: GraphNotificationContents = (contents as any).graph;
return ( return (
<NotificationWrapper <Wrapper>
archived={archived}
notif={notification}
time={props.time}
api={props.api}
graphConfig={props.graphConfig}
groupConfig={props.groupConfig}
>
<GraphNotification <GraphNotification
api={props.api} api={props.api}
index={index} index={index}
@ -108,21 +128,14 @@ export function Notification(props: NotificationProps) {
time={time} time={time}
associations={associations} associations={associations}
/> />
</NotificationWrapper> </Wrapper>
); );
} }
if ("group" in notification.index) { if ("group" in notification.index) {
const index = notification.index.group; const index = notification.index.group;
const c: GroupNotificationContents = (contents as any).group; const c: GroupNotificationContents = (contents as any).group;
return ( return (
<NotificationWrapper <Wrapper>
archived={archived}
notif={notification}
time={props.time}
api={props.api}
graphConfig={props.graphConfig}
groupConfig={props.groupConfig}
>
<GroupNotification <GroupNotification
api={props.api} api={props.api}
index={index} index={index}
@ -134,7 +147,27 @@ export function Notification(props: NotificationProps) {
time={time} time={time}
associations={associations} associations={associations}
/> />
</NotificationWrapper> </Wrapper>
);
}
if ("chat" in notification.index) {
const index = notification.index.chat;
const c: ChatNotificationContents = (contents as any).chat;
return (
<Wrapper>
<ChatNotification
api={props.api}
index={index}
contents={c}
contacts={props.contacts}
read={read}
archived={archived}
groups={{}}
timebox={props.time}
time={time}
associations={associations}
/>
</Wrapper>
); );
} }

View File

@ -6,7 +6,7 @@ import { Dropdown } from "~/views/components/Dropdown";
import { Association, NotificationGraphConfig } from "~/types"; import { Association, NotificationGraphConfig } from "~/types";
import GlobalApi from "~/logic/api/global"; import GlobalApi from "~/logic/api/global";
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction"; import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
import {appIsGraph} from "~/logic/lib/util"; import { appIsGraph } from "~/logic/lib/util";
const ChannelMenuItem = ({ const ChannelMenuItem = ({
icon, icon,
@ -29,7 +29,8 @@ const ChannelMenuItem = ({
interface ChannelMenuProps { interface ChannelMenuProps {
association: Association; association: Association;
api: GlobalApi; api: GlobalApi;
notificationConfig: NotificationGraphConfig; graphNotificationConfig: NotificationGraphConfig;
chatNotificationConfig: string[];
} }
export function ChannelMenu(props: ChannelMenuProps) { export function ChannelMenu(props: ChannelMenuProps) {
@ -49,10 +50,19 @@ export function ChannelMenu(props: ChannelMenuProps) {
const isOurs = ship.slice(1) === window.ship; const isOurs = ship.slice(1) === window.ship;
const isMuted = const isMuted = appIsGraph(app)
props.notificationConfig.watching.findIndex((a) => a === appPath) === -1; ? props.graphNotificationConfig.watching.findIndex((a) => a === appPath) ===
-1
: props.chatNotificationConfig.findIndex((a) => a === appPath) === -1;
const onChangeMute = async () => { const onChangeMute = async () => {
const func = isMuted ? "listenGraph" : "ignoreGraph"; const func =
association["app-name"] === "chat"
? isMuted
? "listenChat"
: "ignoreChat"
: isMuted
? "listenGraph"
: "ignoreGraph";
await api.hark[func](appPath); await api.hark[func](appPath);
}; };
const onUnsubscribe = useCallback(async () => { const onUnsubscribe = useCallback(async () => {
@ -100,18 +110,16 @@ export function ChannelMenu(props: ChannelMenuProps) {
borderRadius={1} borderRadius={1}
borderColor="lightGray" borderColor="lightGray"
> >
{appIsGraph(metadata.module) && ( <ChannelMenuItem color="blue" icon="Inbox">
<ChannelMenuItem color="blue" icon="Inbox"> <StatelessAsyncAction
<StatelessAsyncAction m="2"
m="2" bg="white"
bg="white" name="notif"
name="notif" onClick={onChangeMute}
onClick={onChangeMute} >
> {isMuted ? "Unmute" : "Mute"} this channel
{isMuted ? "Unmute" : "Mute"} this channel </StatelessAsyncAction>
</StatelessAsyncAction> </ChannelMenuItem>
</ChannelMenuItem>
)}
{isOurs ? ( {isOurs ? (
<> <>
<ChannelMenuItem color="red" icon="TrashCan"> <ChannelMenuItem color="red" icon="TrashCan">

View File

@ -54,6 +54,7 @@ export function Resource(props: ResourceProps) {
render={(routeProps) => ( render={(routeProps) => (
<ResourceSkeleton <ResourceSkeleton
notificationsGraphConfig={props.notificationsGraphConfig} notificationsGraphConfig={props.notificationsGraphConfig}
notificationsChatConfig={props.notificationsChatConfig}
baseUrl={props.baseUrl} baseUrl={props.baseUrl}
{...skelProps} {...skelProps}
atRoot atRoot

View File

@ -99,7 +99,8 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
</TruncatedBox> </TruncatedBox>
<Box flexGrow={1} /> <Box flexGrow={1} />
<ChannelMenu <ChannelMenu
notificationConfig={props.notificationsGraphConfig} graphNotificationConfig={props.notificationsGraphConfig}
chatNotificationConfig={props.notificationsChatConfig}
association={association} association={association}
api={api} api={api}
/> />