Merge pull request #3892 from urbit/lf/hark-chat-hook

hark-chat-hook: Notifications
This commit is contained in:
matildepark 2020-11-06 08:54:55 -05:00 committed by GitHub
commit f31dd0c047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 608 additions and 69 deletions

View File

@ -5,7 +5,7 @@
/- glob
/+ default-agent, verb, dbug
|%
++ hash 0v4.cf9m1.t0ofg.dtig4.av3jh.2f2db
++ hash 0v6.9vk2h.hr87m.nn63p.8kmo5.k4ljt
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states
$% state-0

View File

@ -0,0 +1,184 @@
:: hark-chat-hook: notifications for chat-store [landscape]
::
/- store=hark-store, post, group-store, metadata-store, hook=hark-chat-hook
/+ resource, metadata, default-agent, dbug, chat-store
::
~% %hark-chat-hook-top ..is ~
|%
+$ card card:agent:gall
+$ versioned-state
$% state-0
==
::
+$ state-0
$: %0
watching=(set path)
mentions=_&
==
::
--
::
=| state-0
=* state -
::
=<
%- agent:dbug
^- agent:gall
~% %hark-chat-hook-agent ..card ~
|_ =bowl:gall
+* this .
ha ~(. +> bowl)
def ~(. (default-agent this %|) bowl)
met ~(. metadata bowl)
::
++ on-init
:_ this
~[watch-chat:ha]
::
++ on-save !>(state)
++ on-load
|= old=vase
^- (quip card _this)
`this(state !<(state-0 old))
::
++ on-watch
|= =path
^- (quip card _this)
=^ cards state
?+ path (on-watch:def path)
::
[%updates ~]
:_ state
%+ give:ha ~
:* %initial
watching
==
==
[cards this]
::
++ on-poke
~/ %hark-chat-hook-poke
|= [=mark =vase]
^- (quip card _this)
|^
?> (team:title our.bowl src.bowl)
=^ cards state
?+ mark (on-poke:def mark vase)
%hark-chat-hook-action
(hark-chat-hook-action !<(action:hook vase))
==
[cards this]
::
++ hark-chat-hook-action
|= =action:hook
^- (quip card _state)
|^
?- -.action
%listen (listen +.action)
%ignore (ignore +.action)
%set-mentions (set-mentions +.action)
==
++ listen
|= chat=path
^- (quip card _state)
:- (give:ha ~[/updates] [%listen chat])
state(watching (~(put in watching) chat))
::
++ ignore
|= chat=path
^- (quip card _state)
:- (give:ha ~[/updates] [%ignore chat])
state(watching (~(del in watching) chat))
::
++ set-mentions
|= ment=?
^- (quip card _state)
:- (give:ha ~[/updates] [%set-mentions ment])
state(mentions ment)
--
--
::
++ on-agent
~/ %hark-chat-hook-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
|^
?+ -.sign (on-agent:def wire sign)
%kick
:_ this
?. ?=([%chat ~] wire)
~
~[watch-chat:ha]
::
%fact
?. ?=(%chat-update p.cage.sign)
(on-agent:def wire sign)
=^ cards state
(chat-update !<(update:chat-store q.cage.sign))
[cards this]
==
::
++ chat-update
|= =update:chat-store
^- (quip card _state)
:_ state
?+ -.update ~
%message (process-envelope path.update envelope.update)
::
%messages
%- zing
(turn envelopes.update (cury process-envelope path.update))
==
++ is-mention
|= [=path =envelope:chat-store]
?. ?=(%text -.letter.envelope) %.n
?& mentions
?= ^
(find (scow %p our.bowl) (trip text.letter.envelope))
==
::
++ is-notification
|= [=path =envelope:chat-store]
?& (~(has in watching) path)
!=(author.envelope our.bowl)
==
::
++ process-envelope
|= [=path =envelope:chat-store]
^- (list card)
=/ mention=?
(is-mention path envelope)
?. ?|(mention (is-notification path envelope))
~
=/ =index:store
[%chat path mention]
=/ =contents:store
[%chat ~[envelope]]
~[(poke-store %add index when.envelope %.n contents)]
::
++ poke-store
|= =action:store
^- card
=/ =cage
hark-action+!>(action)
[%pass /store %agent [our.bowl %hark-store] %poke cage]
--
::
++ on-peek on-peek:def
::
++ on-leave on-leave:def
++ on-arvo on-arvo:def
++ on-fail on-fail:def
--
|_ =bowl:gall
::
::
++ give
|= [paths=(list path) =update:hook]
^- (list card)
[%give %fact paths hark-chat-hook-update+!>(update)]~
::
++ watch-chat
^- card
[%pass /chat %agent [our.bowl %chat-store] %watch /updates]
--

View File

@ -236,6 +236,10 @@
|= [existing=notification:store new=notification:store]
^- notification:store
?- -.contents.existing
::
%chat
?> ?=(%chat -.contents.new)
existing(list.contents (weld list.contents.existing list.contents.new))
::
%graph
?> ?=(%graph -.contents.new)

View File

@ -24,6 +24,6 @@
<div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script>
<script src="/~landscape/js/bundle/index.20c57e72701ff6afa573.js"></script>
<script src="/~landscape/js/bundle/index.5f9890df6be59a4ff9c5.js"></script>
</body>
</html>

View File

@ -0,0 +1,33 @@
/- sur=hark-chat-hook
^?
=< [. sur]
=, sur
|%
::
++ dejs
=, dejs:format
|%
::
++ action
%- of
:~ listen+pa
ignore+pa
set-mentions+bo
==
--
::
++ enjs
=, enjs:format
|%
::
++ update
|= upd=^update
%+ frond -.upd
?- -.upd
?(%listen %ignore) (path chat.upd)
%set-mentions b+mentions.upd
%initial a+(turn ~(tap in watching.upd) path)
==
--
--

View File

@ -1,5 +1,5 @@
/- sur=hark-store, post
/+ resource, graph-store, group-store
/+ resource, graph-store, group-store, chat-store
^?
=< [. sur]
=, sur
@ -11,6 +11,13 @@
%- of
:~ graph+graph-index
group+group-index
chat+chat-index
==
::
++ chat-index
%- ot
:~ chat+pa
mention+bo
==
::
++ group-index
@ -106,7 +113,16 @@
?- -.index
%graph (graph-index +.index)
%group (group-index +.index)
%chat (chat-index +.index)
==
::
++ chat-index
|= [chat=^path mention=?]
^- json
%- pairs
:~ chat+(path chat)
mention+b+mention
==
::
++ graph-index
|= [group=resource graph=resource module=@t description=@t]
@ -144,8 +160,15 @@
?- -.contents
%graph (graph-contents +.contents)
%group (group-contents +.contents)
%chat (chat-contents +.contents)
==
::
++ chat-contents
|= =(list envelope:chat-store)
^- json
:- %a
(turn list envelope:enjs:chat-store)
::
++ graph-contents
|= =(list post:post)
^- json

View File

@ -0,0 +1,13 @@
/+ *hark-chat-hook
|_ act=action
++ grad %noun
++ grow
|%
++ noun act
--
++ grab
|%
++ noun action
++ json action:dejs
--
--

View File

@ -0,0 +1,16 @@
/+ *hark-chat-hook
|_ upd=update
++ grad %noun
++ grow
|%
++ noun upd
++ json
%+ frond:enjs:format
%hark-chat-hook-update
(update:enjs upd)
--
++ grab
|%
++ noun update
--
--

View File

@ -0,0 +1,16 @@
^?
|%
::
+$ action
$% [?(%listen %ignore) chat=path]
[%set-mentions mentions=?]
==
::
+$ update
$%
action
$: %initial
watching=(set path)
==
==
--

View File

@ -1,10 +1,11 @@
/- *resource, graph-store, post, group-store, metadata-store
/- *resource, graph-store, post, group-store, metadata-store, chat-store
^?
|%
::
+$ index
$% [%graph group=resource graph=resource module=@t description=@t]
[%group group=resource description=@t]
[%chat chat=path mention=?]
==
::
+$ group-contents
@ -19,6 +20,7 @@
+$ contents
$% [%graph =(list post:post)]
[%group =(list group-contents)]
[%chat =(list envelope:chat-store)]
==
::
+$ timebox

View File

@ -2,6 +2,7 @@ import BaseApi from "./base";
import { StoreState } from "../store/type";
import { dateToDa, decToUd } from "../lib/util";
import {NotifIndex} from "~/types";
import { BigInteger } from 'big-integer';
export class HarkApi extends BaseApi<StoreState> {
private harkAction(action: any): Promise<any> {
@ -15,6 +16,10 @@ export class HarkApi extends BaseApi<StoreState> {
private groupHookAction(action: any) {
return this.action("hark-group-hook", "hark-group-hook-action", action);
}
private chatHookAction(action: any) {
return this.action("hark-chat-hook", "hark-chat-hook-action", action);
}
private actOnNotification(frond: string, intTime: BigInteger, index: NotifIndex) {
const time = decToUd(intTime.toString());
@ -26,12 +31,11 @@ export class HarkApi extends BaseApi<StoreState> {
});
}
private graphHookAction(action: any) {
return this.action("hark-graph-hook", "hark-graph-hook-action", action);
}
setMentions(mentions: boolean) {
return this.graphHookAction({
async setMentions(mentions: boolean) {
await this.graphHookAction({
'set-mentions': mentions
});
return this.chatHookAction({
'set-mentions': mentions
});
}
@ -73,6 +77,9 @@ export class HarkApi extends BaseApi<StoreState> {
const { group } = index.group;
return this.ignoreGroup(group);
}
if('chat' in index) {
return this.ignoreChat(index.chat);
}
return Promise.resolve();
}
@ -83,6 +90,9 @@ export class HarkApi extends BaseApi<StoreState> {
if('group' in index) {
return this.listenGroup(index.group.group);
}
if('chat' in index) {
return this.listenChat(index.chat);
}
return Promise.resolve();
}
@ -98,6 +108,12 @@ export class HarkApi extends BaseApi<StoreState> {
})
}
ignoreChat(chat: string) {
return this.chatHookAction({
ignore: chat
});
}
listenGroup(group: string) {
return this.groupHookAction({
@ -111,6 +127,12 @@ export class HarkApi extends BaseApi<StoreState> {
})
}
listenChat(chat: string) {
return this.chatHookAction({
listen: chat
});
}
async getTimeSubset(start?: Date, end?: Date) {
const s = start ? dateToDa(start) : "-";
const e = end ? dateToDa(end) : "-";

View File

@ -1,6 +1,5 @@
import {
Notifications,
Notification,
NotifIndex,
NotificationGraphConfig,
GroupNotificationsConfig,
@ -14,6 +13,7 @@ type HarkState = {
notificationsCount: number;
notificationsGraphConfig: NotificationGraphConfig;
notificationsGroupConfig: GroupNotificationsConfig;
notificationsChatConfig: string[];
};
export const HarkReducer = (json: any, state: HarkState) => {
@ -36,8 +36,39 @@ export const HarkReducer = (json: any, state: HarkState) => {
groupListen(groupHookData, state);
groupIgnore(groupHookData, state);
}
const chatHookData = _.get(json, "hark-chat-hook-update", false);
if(chatHookData) {
chatInitial(chatHookData, state);
chatListen(chatHookData, state);
chatIgnore(chatHookData, state);
}
};
function chatInitial(json: any, state: HarkState) {
const data = _.get(json, "initial", false);
if (data) {
state.notificationsChatConfig = data;
}
}
function chatListen(json: any, state: HarkState) {
const data = _.get(json, "listen", false);
if (data) {
state.notificationsChatConfig = [...state.notificationsChatConfig, data];
}
}
function chatIgnore(json: any, state: HarkState) {
const data = _.get(json, "ignore", false);
if (data) {
state.notificationsChatConfig = state.notificationsChatConfig.filter(x => x !== data);
}
}
function groupInitial(json: any, state: HarkState) {
const data = _.get(json, "initial", false);
if (data) {
@ -177,6 +208,9 @@ function notifIdxEqual(a: NotifIndex, b: NotifIndex) {
a.group.group === b.group.group &&
a.group.description === b.group.description
);
} else if ("chat" in a && "chat" in b) {
return a.chat.chat === b.chat.chat &&
a.chat.mention === b.chat.mention;
}
return false;
}

View File

@ -100,6 +100,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
notifications: new BigIntOrderedMap<Timebox>(),
archivedNotifications: new BigIntOrderedMap<Timebox>(),
notificationsGroupConfig: [],
notificationsChatConfig: [],
notificationsGraphConfig: {
watchOnSelf: false,
mentions: false,

View File

@ -62,6 +62,7 @@ export interface StoreState {
notifications: Notifications;
notificationsGraphConfig: NotificationGraphConfig;
notificationsGroupConfig: GroupNotificationsConfig;
notificationsChatConfig: string[];
notificationsCount: number,
doNotDisturb: boolean;
}

View File

@ -54,6 +54,7 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
this.subscribe('/updates', 'hark-store');
this.subscribe('/updates', 'hark-graph-hook');
this.subscribe('/updates', 'hark-group-hook');
this.subscribe('/updates', 'hark-chat-hook');
}
restart() {

View File

@ -2,6 +2,7 @@ import _ from "lodash";
import { Post } from "./graph-update";
import { GroupUpdate } from "./group-update";
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
import { Envelope } from './chat-update';
type GraphNotifDescription = "link" | "comment";
@ -17,17 +18,26 @@ export interface GroupNotifIndex {
description: string;
}
export interface ChatNotifIndex {
chat: string;
mention: boolean;
}
export type NotifIndex =
| { graph: GraphNotifIndex }
| { group: GroupNotifIndex };
| { group: GroupNotifIndex }
| { chat: ChatNotifIndex };
export type GraphNotificationContents = Post[];
export type GroupNotificationContents = GroupUpdate[];
export type ChatNotificationContents = Envelope[];
export type NotificationContents =
| { graph: GraphNotificationContents }
| { group: GroupNotificationContents };
| { group: GroupNotificationContents }
| { chat: ChatNotificationContents };
interface Notification {
read: boolean;

View File

@ -1,4 +1,4 @@
import React, { useRef, useCallback } from "react";
import React, { useRef, useCallback, useEffect } from "react";
import { RouteComponentProps } from "react-router-dom";
import { Col } from "@tlon/indigo-react";
import _ from 'lodash';
@ -94,6 +94,15 @@ export function ChatResource(props: ChatResourceProps) {
station,
]);
const scrollTo = new URLSearchParams(location.search).get('msg');
useEffect(() => {
const clear = () => {
props.history.replace(location.pathname);
}
setTimeout(clear, 10000);
return clear;
}, [station]);
return (
<Col {...bind} height="100%" overflow="hidden" position="relative">
{dragging && <SubmitDragger />}
@ -118,6 +127,7 @@ export function ChatResource(props: ChatResourceProps) {
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
location={props.location}
scrollTo={scrollTo ? parseInt(scrollTo, 10) : undefined}
/>
<ChatInput
ref={chatInput}

View File

@ -31,8 +31,8 @@ export const DayBreak = ({ when }) => (
interface ChatMessageProps {
measure(element): void;
msg: Envelope | IMessage;
previousMsg: Envelope | IMessage | undefined;
nextMsg: Envelope | IMessage | undefined;
previousMsg?: Envelope | IMessage;
nextMsg?: Envelope | IMessage;
isLastRead: boolean;
group: Group;
association: Association;
@ -48,6 +48,7 @@ interface ChatMessageProps {
unreadMarkerRef: React.RefObject<HTMLDivElement>;
history: any;
api: any;
highlighted?: boolean;
}
export default class ChatMessage extends Component<ChatMessageProps> {
@ -84,7 +85,8 @@ export default class ChatMessage extends Component<ChatMessageProps> {
isLastMessage,
unreadMarkerRef,
history,
api
api,
highlighted
} = this.props;
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
@ -115,7 +117,8 @@ export default class ChatMessage extends Component<ChatMessageProps> {
isPending,
history,
api,
scrollWindow
scrollWindow,
highlighted
};
const unreadContainerStyle = {
@ -124,6 +127,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
return (
<Box
bg={highlighted ? 'washedBlue' : 'white'}
width='100%'
display='flex'
flexWrap='wrap'
@ -165,6 +169,8 @@ interface MessageProps {
};
export class MessageWithSigil extends PureComponent<MessageProps> {
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
render() {
const {
msg,
@ -176,8 +182,8 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
hideAvatars,
remoteContentPolicy,
measure,
history,
api,
history,
scrollWindow
} = this.props;
@ -185,8 +191,8 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
const contact = msg.author in contacts ? contacts[msg.author] : false;
const showNickname = !hideNicknames && contact && contact.nickname;
const name = showNickname ? contact.nickname : cite(msg.author);
const color = contact ? `#${uxToHex(contact.color)}` : '#000000';
const sigilClass = contact ? '' : 'mix-blend-diff';
const color = contact ? `#${uxToHex(contact.color)}` : this.isDark ? '#000000' :'#FFFFFF'
const sigilClass = contact ? '' : this.isDark ? 'mix-blend-diff' : 'mix-blend-darken';
let nameSpan = null;
@ -213,7 +219,7 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
scrollWindow={scrollWindow}
history={history}
api={api}
className="fl pr3 v-top bg-white bg-gray0-d pt1"
className="fl pr3 v-top pt1"
/>
<Box flexGrow='1' display='block' className="clamp-message">
<Box
@ -239,7 +245,7 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
<Text gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
</Box>
<Box fontSize='14px'><MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure} /></Box>
<Box fontSize='14px'><MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure} /></Box>
</Box>
</>
);

View File

@ -43,6 +43,7 @@ type ChatWindowProps = RouteComponentProps<{
hideNicknames: boolean;
hideAvatars: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
scrollTo?: number;
}
interface ChatWindowState {
@ -84,6 +85,10 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
window.addEventListener('focus', this.handleWindowFocus);
this.initialFetch();
setTimeout(() => {
if(this.props.scrollTo) {
this.scrollToUnread();
}
this.setState({ initialized: true });
}, this.INITIALIZATION_MAX_TIME);
}
@ -167,8 +172,9 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
}
scrollToUnread() {
const { mailboxSize, unreadCount } = this.props;
this.virtualList?.scrollToData(mailboxSize - unreadCount);
const { mailboxSize, unreadCount, scrollTo } = this.props;
const target = scrollTo || (mailboxSize - unreadCount);
this.virtualList?.scrollToData(target);
}
dismissUnread() {
@ -297,7 +303,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
const isPending: boolean = 'pending' in msg && Boolean(msg.pending);
const isLastMessage: boolean = Boolean(index === lastMessage)
const isLastRead: boolean = Boolean(!isLastMessage && index === this.state.lastRead);
const props = { measure, scrollWindow, isPending, isLastRead, isLastMessage, msg, ...messageProps };
const highlighted = index === this.props.scrollTo;
const props = { measure, highlighted, scrollWindow, isPending, isLastRead, isLastMessage, msg, ...messageProps };
return (
<ChatMessage
key={index}

View File

@ -87,6 +87,10 @@ h2 {
mix-blend-mode: difference;
}
.mix-blend-darken {
mix-blend-mode: darken;
}
.placeholder-inter::placeholder {
font-family: "Inter", sans-serif;
}

View File

@ -0,0 +1,99 @@
import React, { useCallback } from "react";
import _ from "lodash";
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(mention: boolean, lent: number) {
const msg = pluralize("message", lent !== 1);
if (mention) {
return `mentioned you in ${msg} in`;
}
return `sent ${msg} 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 { chat, mention } = index;
const association = props.associations.chat[chat];
const groupPath = association["group-path"];
const appPath = association["app-path"];
const group = props.groups[groupPath];
const desc = describeNotification(mention, 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={chat}
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

@ -1,4 +1,5 @@
import React, { useCallback, useState } from "react";
import _ from 'lodash';
import { Box, Col, Text, Row } from "@tlon/indigo-react";
import { Link, Switch, Route } from "react-router-dom";

View File

@ -15,7 +15,7 @@ export function FormikOnBlur<
) {
const { values } = formikBag;
formikBag.submitForm().then(() => {
formikBag.resetForm({ values });
formikBag.resetForm({ values, touched: {} });
});
}
}, [

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}
/>