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

View File

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

View File

@ -8,12 +8,14 @@ import {
NotificationGraphConfig,
GroupNotificationsConfig,
NotifIndex,
Associations
Associations,
} from "~/types";
import GlobalApi from "~/logic/api/global";
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
import { GroupNotification } from "./group";
import { GraphNotification } from "./graph";
import { ChatNotification } from "./chat";
import { BigInteger } from "big-integer";
interface NotificationProps {
notification: IndexedNotification;
@ -23,12 +25,14 @@ interface NotificationProps {
archived: boolean;
graphConfig: NotificationGraphConfig;
groupConfig: GroupNotificationsConfig;
chatConfig: string[];
}
function getMuted(
idx: NotifIndex,
groups: GroupNotificationsConfig,
graphs: NotificationGraphConfig
graphs: NotificationGraphConfig,
chat: string[]
) {
if ("graph" in idx) {
const { graph } = idx.graph;
@ -37,6 +41,9 @@ function getMuted(
if ("group" in idx) {
return _.findIndex(groups || [], (g) => g === idx.group.group) === -1;
}
if ("chat" in idx) {
return _.findIndex(chat || [], (c) => c === idx.chat) === -1;
}
return false;
}
@ -48,6 +55,7 @@ function NotificationWrapper(props: {
archived: boolean;
graphConfig: NotificationGraphConfig;
groupConfig: GroupNotificationsConfig;
chatConfig: string[];
}) {
const { api, time, notif, children } = props;
@ -55,7 +63,12 @@ function NotificationWrapper(props: {
return api.hark.archive(time, notif.index);
}, [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 func = isMuted ? "unmute" : "mute";
@ -84,19 +97,26 @@ export function Notification(props: NotificationProps) {
const { notification, associations, archived } = props;
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) {
const index = notification.index.graph;
const c: GraphNotificationContents = (contents as any).graph;
return (
<NotificationWrapper
archived={archived}
notif={notification}
time={props.time}
api={props.api}
graphConfig={props.graphConfig}
groupConfig={props.groupConfig}
>
<Wrapper>
<GraphNotification
api={props.api}
index={index}
@ -108,21 +128,14 @@ export function Notification(props: NotificationProps) {
time={time}
associations={associations}
/>
</NotificationWrapper>
</Wrapper>
);
}
if ("group" in notification.index) {
const index = notification.index.group;
const c: GroupNotificationContents = (contents as any).group;
return (
<NotificationWrapper
archived={archived}
notif={notification}
time={props.time}
api={props.api}
graphConfig={props.graphConfig}
groupConfig={props.groupConfig}
>
<Wrapper>
<GroupNotification
api={props.api}
index={index}
@ -134,7 +147,27 @@ export function Notification(props: NotificationProps) {
time={time}
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 GlobalApi from "~/logic/api/global";
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
import {appIsGraph} from "~/logic/lib/util";
import { appIsGraph } from "~/logic/lib/util";
const ChannelMenuItem = ({
icon,
@ -29,7 +29,8 @@ const ChannelMenuItem = ({
interface ChannelMenuProps {
association: Association;
api: GlobalApi;
notificationConfig: NotificationGraphConfig;
graphNotificationConfig: NotificationGraphConfig;
chatNotificationConfig: string[];
}
export function ChannelMenu(props: ChannelMenuProps) {
@ -49,10 +50,19 @@ export function ChannelMenu(props: ChannelMenuProps) {
const isOurs = ship.slice(1) === window.ship;
const isMuted =
props.notificationConfig.watching.findIndex((a) => a === appPath) === -1;
const isMuted = appIsGraph(app)
? props.graphNotificationConfig.watching.findIndex((a) => a === appPath) ===
-1
: props.chatNotificationConfig.findIndex((a) => a === appPath) === -1;
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);
};
const onUnsubscribe = useCallback(async () => {
@ -100,18 +110,16 @@ export function ChannelMenu(props: ChannelMenuProps) {
borderRadius={1}
borderColor="lightGray"
>
{appIsGraph(metadata.module) && (
<ChannelMenuItem color="blue" icon="Inbox">
<StatelessAsyncAction
m="2"
bg="white"
name="notif"
onClick={onChangeMute}
>
{isMuted ? "Unmute" : "Mute"} this channel
</StatelessAsyncAction>
</ChannelMenuItem>
)}
<ChannelMenuItem color="blue" icon="Inbox">
<StatelessAsyncAction
m="2"
bg="white"
name="notif"
onClick={onChangeMute}
>
{isMuted ? "Unmute" : "Mute"} this channel
</StatelessAsyncAction>
</ChannelMenuItem>
{isOurs ? (
<>
<ChannelMenuItem color="red" icon="TrashCan">

View File

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

View File

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