interface: show unread counts on group tiles

This commit is contained in:
Liam Fitzgerald 2020-11-16 12:23:42 +10:00
parent ec580cf8c1
commit fb09c36fbe
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
9 changed files with 111 additions and 52 deletions

View File

@ -61,6 +61,12 @@ export class HarkApi extends BaseApi<StoreState> {
return this.actOnNotification('read', time, index); return this.actOnNotification('read', time, index);
} }
readIndex(index: NotifIndex) {
return this.harkAction({
'read-index': index
});
}
unread(time: BigInteger, index: NotifIndex) { unread(time: BigInteger, index: NotifIndex) {
return this.actOnNotification('unread', time, index); return this.actOnNotification('unread', time, index);
} }

View File

@ -6,19 +6,21 @@ import {
} from "~/types"; } from "~/types";
import { makePatDa } from "~/logic/lib/util"; import { makePatDa } from "~/logic/lib/util";
import _ from "lodash"; import _ from "lodash";
import {StoreState} from "../store/type";
type HarkState = { type HarkState = Pick<StoreState,
notifications: Notifications; "notificationsChatConfig"
archivedNotifications: Notifications; | "notificationsGroupConfig"
notificationsCount: number; | "notificationsGraphConfig"
notificationsGraphConfig: NotificationGraphConfig; | "notifications"
notificationsGroupConfig: GroupNotificationsConfig; | "notificationsCount"
notificationsChatConfig: string[]; | "archivedNotifications"
}; | "unreads">;
export const HarkReducer = (json: any, state: HarkState) => { export const HarkReducer = (json: any, state: HarkState) => {
const data = _.get(json, "harkUpdate", false); const data = _.get(json, "harkUpdate", false);
if (data) { if (data) {
console.log(data);
reduce(data, state); reduce(data, state);
} }
const graphHookData = _.get(json, "hark-graph-hook-update", false); const graphHookData = _.get(json, "hark-graph-hook-update", false);
@ -139,15 +141,30 @@ function reduce(data: any, state: HarkState) {
timebox(data, state); timebox(data, state);
more(data, state); more(data, state);
dnd(data, state); dnd(data, state);
count(data, state);
added(data, state); added(data, state);
graphUnreads(data, state); unreads(data, state);
} }
function graphUnreads(json: any, state: HarkState) { function unreads(json: any, state: HarkState) {
const data = _.get(json, 'graph-unreads'); const data = _.get(json, 'unreads');
if(data) { if(data) {
state.graphUnreads = data; data.forEach(({ index, unread }) => {
updateUnreads(state, index, x => x + unread);
});
}
}
function updateUnreads(state: HarkState, index: NotifIndex, f: (u: number) => number) {
state.notificationsCount = f(state.notificationsCount);
if('graph' in index) {
const curr = state.unreads.graph[index.graph.graph] || 0;
state.unreads.graph[index.graph.graph] = f(curr);
} else if('group' in index) {
const curr = state.unreads.group[index.group.group] || 0;
state.unreads.group[index.group.group] = f(curr);
} else if('chat' in index) {
const curr = state.unreads.chat[index.chat.chat] || 0
state.unreads.chat[index.chat.chat] = f(curr);
} }
} }
@ -157,28 +174,21 @@ function added(json: any, state: HarkState) {
const { index, notification } = data; const { index, notification } = data;
const time = makePatDa(data.time); const time = makePatDa(data.time);
const timebox = state.notifications.get(time) || []; const timebox = state.notifications.get(time) || [];
const arrIdx = timebox.findIndex((idxNotif) => const arrIdx = timebox.findIndex((idxNotif) =>
notifIdxEqual(index, idxNotif.index) notifIdxEqual(index, idxNotif.index)
); );
if (arrIdx !== -1) { if (arrIdx !== -1) {
if(timebox[arrIdx]?.notification?.read) {
updateUnreads(state, index, x => x+1);
}
timebox[arrIdx] = { index, notification }; timebox[arrIdx] = { index, notification };
state.notifications.set(time, timebox); state.notifications.set(time, timebox);
} else { } else {
updateUnreads(state, index, x => x+1);
state.notifications.set(time, [...timebox, { index, notification }]); state.notifications.set(time, [...timebox, { index, notification }]);
state.notificationsCount++;
if('graph' in index) {
const curr = state.graphUnreads[index.graph.graph] || 0;
state.graphUnreads[index.graph.graph] = curr+1;
} }
} }
}
}
function count(json: any, state: HarkState) {
const data = _.get(json, "count", false);
if (data !== false) {
state.notificationsCount = data;
}
} }
const dnd = (json: any, state: HarkState) => { const dnd = (json: any, state: HarkState) => {
@ -254,12 +264,7 @@ function read(json: any, state: HarkState) {
const data = _.get(json, "read", false); const data = _.get(json, "read", false);
if (data) { if (data) {
const { time, index } = data; const { time, index } = data;
state.notificationsCount--; updateUnreads(state, index, x => x-1);
if('graph' in index) {
const curr = state.graphUnreads[index.graph.graph] || 0;
state.graphUnreads[index.graph.graph] = curr-1;
}
setRead(time, index, true, state); setRead(time, index, true, state);
} }
} }
@ -268,11 +273,7 @@ function unread(json: any, state: HarkState) {
const data = _.get(json, "unread", false); const data = _.get(json, "unread", false);
if (data) { if (data) {
const { time, index } = data; const { time, index } = data;
state.notificationsCount++; updateUnreads(state, index, x => x+1);
if('graph' in index) {
const curr = state.graphUnreads[index.graph.graph] || 0;
state.graphUnreads[index.graph.graph] = curr+1;
}
setRead(time, index, false, state); setRead(time, index, false, state);
} }
} }
@ -292,9 +293,10 @@ function archive(json: any, state: HarkState) {
); );
state.notifications.set(time, unarchived); state.notifications.set(time, unarchived);
const archiveBox = state.archivedNotifications.get(time) || []; const archiveBox = state.archivedNotifications.get(time) || [];
state.notificationsCount -= archived.filter( const readCount = archived.filter(
({ notification }) => !notification.read ({ notification }) => !notification.read
).length; ).length;
updateUnreads(state, index, x => x - readCount);
state.archivedNotifications.set(time, [ state.archivedNotifications.set(time, [
...archiveBox, ...archiveBox,
...archived.map(({ notification, index }) => ({ ...archived.map(({ notification, index }) => ({

View File

@ -107,7 +107,11 @@ export default class GlobalStore extends BaseStore<StoreState> {
watching: [], watching: [],
}, },
notificationsCount: 0, notificationsCount: 0,
graphUnreads: {} unreads: {
graph: {},
group: {},
chat: {},
}
}; };
} }

View File

@ -14,7 +14,8 @@ import {
NotificationGraphConfig, NotificationGraphConfig,
GroupNotificationsConfig, GroupNotificationsConfig,
LocalUpdateRemoteContentPolicy, LocalUpdateRemoteContentPolicy,
BackgroundConfig BackgroundConfig,
Unreads
} from "~/types"; } from "~/types";
export interface StoreState { export interface StoreState {
@ -59,11 +60,12 @@ export interface StoreState {
inbox: Inbox; inbox: Inbox;
pendingMessages: Map<Path, Envelope[]>; pendingMessages: Map<Path, Envelope[]>;
archivedNotifications: Notifications;
notifications: Notifications; notifications: Notifications;
notificationsGraphConfig: NotificationGraphConfig; notificationsGraphConfig: NotificationGraphConfig;
notificationsGroupConfig: GroupNotificationsConfig; notificationsGroupConfig: GroupNotificationsConfig;
notificationsChatConfig: string[]; notificationsChatConfig: string[];
notificationsCount: number, notificationsCount: number,
doNotDisturb: boolean; doNotDisturb: boolean;
graphUnreads: Record<string, number>; unreads: Unreads;
} }

View File

@ -60,6 +60,11 @@ export interface NotificationGraphConfig {
watching: WatchedIndex[] watching: WatchedIndex[]
} }
export interface Unreads {
chat: Record<string, number>;
group: Record<string, number>;
graph: Record<string, number>;
}
interface WatchedIndex { interface WatchedIndex {
graph: string; graph: string;

View File

@ -181,6 +181,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
if (this.state.fetchPending) return; if (this.state.fetchPending) return;
if (this.props.unreadCount === 0) return; if (this.props.unreadCount === 0) return;
this.props.api.chat.read(this.props.station); this.props.api.chat.read(this.props.station);
this.props.api.hark.readIndex({ chat: { chat: this.props.station, mention: false }});
} }
fetchMessages(start, end, force = false): Promise<void> { fetchMessages(start, end, force = false): Promise<void> {

View File

@ -63,7 +63,7 @@ export default class LaunchApp extends React.Component {
weather={props.weather} weather={props.weather}
/> />
<Box display={["none", "block"]} width="100%" gridColumn="1 / -1"></Box> <Box display={["none", "block"]} width="100%" gridColumn="1 / -1"></Box>
<Groups groups={props.groups} associations={props.associations} /> <Groups unreads={props.unreads} groups={props.groups} associations={props.associations} />
</Box> </Box>
</ScrollbarLessBox> </ScrollbarLessBox>
<Box <Box

View File

@ -1,9 +1,11 @@
import React from "react"; import React from "react";
import { Box, Text } from "@tlon/indigo-react"; import { Box, Text, Col } from "@tlon/indigo-react";
import f from "lodash/fp";
import _ from "lodash";
import { Associations, Association } from "~/types"; import { Associations, Association, Unreads } from "~/types";
import { alphabeticalOrder } from "~/logic/lib/util"; import { alphabeticalOrder } from "~/logic/lib/util";
import Tile from '../components/tiles/tile'; import Tile from "../components/tiles/tile";
interface GroupsProps { interface GroupsProps {
associations: Associations; associations: Associations;
@ -12,20 +14,57 @@ interface GroupsProps {
const sortGroupsAlph = (a: Association, b: Association) => const sortGroupsAlph = (a: Association, b: Association) =>
alphabeticalOrder(a.metadata.title, b.metadata.title); alphabeticalOrder(a.metadata.title, b.metadata.title);
const getKindUnreads = (associations: Associations) => (path: string) => (
kind: "chat" | "graph"
): ((unreads: Unreads) => number) =>
f.flow(
(x) => x[kind],
f.pickBy((_v, key) => associations[kind]?.[key]["group-path"] === path),
f.values,
f.reduce(f.add, 0)
);
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) { export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
const { associations, ...boxProps } = props; const { associations, unreads, ...boxProps } = props;
const groups = Object.values(associations?.contacts || {}) const groups = Object.values(associations?.contacts || {})
.filter(e => e['group-path'] in props.groups) .filter((e) => e["group-path"] in props.groups)
.sort(sortGroupsAlph); .sort(sortGroupsAlph);
const getUnreads = getKindUnreads(associations || {});
return ( return (
<> <>
{groups.map((group) => ( {groups.map((group) => {
<Tile to={`/~landscape${group["group-path"]}`}> const path = group["group-path"];
<Text>{group.metadata.title}</Text> const unreadCount = (["chat", "graph"] as const)
</Tile> .map(getUnreads(path))
))} .map((f) => f(unreads))
.reduce(f.add, 0);
return (
<Group
unreads={unreadCount}
path={group["group-path"]}
title={group.metadata.title}
/>
);
})}
</> </>
); );
} }
interface GroupProps {
path: string;
title: string;
unreads: number;
}
function Group(props: GroupProps) {
const { path, title, unreads } = props;
return (
<Tile to={`/~landscape${path}`}>
<Col height="100%" justifyContent="space-between">
<Text>{title}</Text>
{unreads > 0 && <Text gray>{unreads} unread </Text>}
</Col>
</Tile>
);
}

View File

@ -43,7 +43,7 @@ interface SkeletonProps {
export function Skeleton(props: SkeletonProps) { export function Skeleton(props: SkeletonProps) {
const chatConfig = useChat(props.inbox, props.chatSynced); const chatConfig = useChat(props.inbox, props.chatSynced);
const graphConfig = useGraphModule(props.graphKeys, props.graphs, props.graphUnreads); const graphConfig = useGraphModule(props.graphKeys, props.graphs, props.unreads.graph);
const config = useMemo( const config = useMemo(
() => ({ () => ({
graph: graphConfig, graph: graphConfig,