mirror of
https://github.com/urbit/shrub.git
synced 2024-12-25 21:12:56 +03:00
hark: display chat notifications
This commit is contained in:
parent
e1b11d610d
commit
a0ea86098b
93
pkg/interface/src/views/apps/notifications/chat.tsx
Normal file
93
pkg/interface/src/views/apps/notifications/chat.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -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 (
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user