Merge pull request #4279 from tylershuster/hark/inbox-layout

notifications: fix layout
This commit is contained in:
matildepark 2021-01-14 14:27:03 -05:00 committed by GitHub
commit faef9b1d42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 102 additions and 82 deletions

View File

@ -1,4 +1,4 @@
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import _ from "lodash"; import _ from "lodash";
import f, { memoize } from "lodash/fp"; import f, { memoize } from "lodash/fp";
import bigInt, { BigInteger } from "big-integer"; import bigInt, { BigInteger } from "big-integer";
@ -361,4 +361,13 @@ export function pluralize(text: string, isPlural = false, vowel = false) {
export function useShowNickname(contact: Contact | null, hide?: boolean): boolean { export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
const hideNicknames = typeof hide !== 'undefined' ? hide : useLocalState(state => state.hideNicknames); const hideNicknames = typeof hide !== 'undefined' ? hide : useLocalState(state => state.hideNicknames);
return !!(contact && contact.nickname && !hideNicknames); return !!(contact && contact.nickname && !hideNicknames);
}
export function useHovering() {
const [hovering, setHovering] = useState(false);
const bind = {
onMouseEnter: () => setHovering(true),
onMouseLeave: () => setHovering(false)
};
return { hovering, bind };
} }

View File

@ -31,7 +31,7 @@ const useLocalState = create<LocalState>(persist((set, get) => ({
suspendedFocus: undefined, suspendedFocus: undefined,
toggleOmnibox: () => set(produce(state => { toggleOmnibox: () => set(produce(state => {
state.omniboxShown = !state.omniboxShown; state.omniboxShown = !state.omniboxShown;
if (state.suspendedFocus) { if (typeof state.suspendedFocus?.focus === 'function') {
state.suspendedFocus.focus(); state.suspendedFocus.focus();
state.suspendedFocus = undefined; state.suspendedFocus = undefined;
} else { } else {

View File

@ -90,6 +90,8 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
content={contents} content={contents}
group={group} group={group}
contacts={contacts} contacts={contacts}
fontSize='14px'
lineHeight="tall"
/> />
} else if (idx[1] === "1") { } else if (idx[1] === "1") {
const [{ text: header }, { text: body }] = contents; const [{ text: header }, { text: body }] = contents;
@ -164,13 +166,14 @@ const GraphNode = ({
group, group,
read, read,
onRead, onRead,
showContact = false,
remoteContentPolicy remoteContentPolicy
}) => { }) => {
const { contents } = post; const { contents } = post;
author = deSig(author); author = deSig(author);
const history = useHistory(); const history = useHistory();
const img = ( const img = showContact ? (
<Sigil <Sigil
ship={`~${author}`} ship={`~${author}`}
size={16} size={16}
@ -178,7 +181,7 @@ const GraphNode = ({
color={`#000000`} color={`#000000`}
classes="mix-blend-diff" classes="mix-blend-diff"
/> />
); ) : <Box style={{ width: '16px' }}></Box>;
const groupContacts = contacts[groupPath] ?? {}; const groupContacts = contacts[groupPath] ?? {};
@ -192,10 +195,10 @@ const GraphNode = ({
}, [read, onRead]); }, [read, onRead]);
return ( return (
<Row onClick={onClick} gapX="2" pt="2"> <Row onClick={onClick} gapX="2" pt={showContact ? 2 : 0}>
<Col>{img}</Col> <Col>{img}</Col>
<Col flexGrow={1} alignItems="flex-start"> <Col flexGrow={1} alignItems="flex-start">
<Row {showContact && <Row
mb="2" mb="2"
height="16px" height="16px"
alignItems="center" alignItems="center"
@ -208,8 +211,8 @@ const GraphNode = ({
<Text ml="2" gray> <Text ml="2" gray>
{moment(time).format("HH:mm")} {moment(time).format("HH:mm")}
</Text> </Text>
</Row> </Row>}
<Row width="100%" p="1"> <Row width="100%" p="1" flexDirection="column">
<GraphNodeContent <GraphNodeContent
contacts={groupContacts} contacts={groupContacts}
post={post} post={post}
@ -253,7 +256,7 @@ export function GraphNotification(props: {
}, [api, timebox, index, read]); }, [api, timebox, index, read]);
return ( return (
<Col flexGrow={1} width="100%" p="2"> <>
<Header <Header
onClick={onClick} onClick={onClick}
archived={props.archived} archived={props.archived}
@ -267,7 +270,7 @@ return (
description={desc} description={desc}
associations={props.associations} associations={props.associations}
/> />
<Col flexGrow={1} width="100%" pl="5"> <Box flexGrow={1} width="100%" pl={5} gridArea="main">
{_.map(contents, (content, idx) => ( {_.map(contents, (content, idx) => (
<GraphNode <GraphNode
post={content} post={content}
@ -282,9 +285,10 @@ return (
groupPath={group} groupPath={group}
read={read} read={read}
onRead={onClick} onRead={onClick}
showContact={idx === 0}
/> />
))} ))}
</Col> </Box>
</Col> </>
); );
} }

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Text as NormalText, Row, Icon, Rule } from "@tlon/indigo-react"; import { Text as NormalText, Row, Icon, Rule, Box } from "@tlon/indigo-react";
import f from "lodash/fp"; import f from "lodash/fp";
import _ from "lodash"; import _ from "lodash";
import moment from "moment"; import moment from "moment";
@ -71,11 +71,11 @@ export function Header(props: {
channel; channel;
return ( return (
<Row onClick={props.onClick} p="2" flexWrap="wrap" gapX="1" alignItems="center"> <Row onClick={props.onClick} p="2" flexWrap="wrap" alignItems="center" gridArea="header">
{!props.archived && ( {!props.archived && (
<Icon <Icon
display="block" display="block"
mr="1" mr={2}
icon={read ? "Circle" : "Bullet"} icon={read ? "Circle" : "Bullet"}
color="blue" color="blue"
/> />
@ -84,13 +84,13 @@ export function Header(props: {
{authorDesc} {authorDesc}
</Text> </Text>
<Text mr="1">{description}</Text> <Text mr="1">{description}</Text>
{!!moduleIcon && <Icon icon={moduleIcon as any} />} {!!moduleIcon && <Icon icon={moduleIcon as any} mr={1} />}
{!!channel && <Text fontWeight="500">{channelTitle}</Text>} {!!channel && <Text fontWeight="500" mr={1}>{channelTitle}</Text>}
<Rule vertical height="12px" /> <Rule vertical height="12px" mr={1} />
{groupTitle && {groupTitle &&
<> <>
<Text fontWeight="500">{groupTitle}</Text> <Text fontWeight="500" mr={1}>{groupTitle}</Text>
<Rule vertical height="12px"/> <Rule vertical height="12px" mr={1} />
</> </>
} }
<Text fontWeight="regular" color="lightGray"> <Text fontWeight="regular" color="lightGray">

View File

@ -61,20 +61,36 @@ export default function Inbox(props: {
}; };
}, []); }, []);
const [newNotifications, ...notifications] = const notifications =
Array.from(props.showArchive ? props.archive : props.notifications) || []; Array.from(props.showArchive ? props.archive : props.notifications) || [];
const calendar = {
...MOMENT_CALENDAR_DATE, sameDay: function (now) {
if (this.subtract(6, 'hours').isBefore(now)) {
return "[Earlier Today]";
} else {
return MOMENT_CALENDAR_DATE.sameDay;
}
}
};
const notificationsByDay = f.flow( let notificationsByDay = f.flow(
f.map<DatedTimebox>(([date, nots]) => [ f.map<DatedTimebox>(([date, nots]) => [
date, date,
nots.filter(filterNotification(associations, props.filter)), nots.filter(filterNotification(associations, props.filter)),
]), ]),
f.groupBy<DatedTimebox>(([date]) => f.groupBy<DatedTimebox>(([date]) => {
moment(daToUnix(date)).format("DDMMYYYY") date = moment(daToUnix(date));
), if (moment().subtract(6, 'hours').isBefore(date)) {
f.values, return 'latest';
f.reverse } else {
return date.format("YYYYMMDD");
}
}),
)(notifications); )(notifications);
notificationsByDay = new Map(Object.keys(notificationsByDay).sort().reverse().map(timebox => {
return [timebox, notificationsByDay[timebox]];
}));
useEffect(() => { useEffect(() => {
api.hark.getMore(props.showArchive); api.hark.getMore(props.showArchive);
@ -133,38 +149,24 @@ export default function Inbox(props: {
<Col zIndex={4} gapY={2} bg="white" top="0px" position="sticky"> <Col zIndex={4} gapY={2} bg="white" top="0px" position="sticky">
{inviteItems(invites, api)} {inviteItems(invites, api)}
</Col> </Col>
{newNotifications && ( {[...notificationsByDay.keys()].map((day, index) => {
<DaySection const timeboxes = notificationsByDay.get(day);
latest return timeboxes.length > 0 && (
timeboxes={[newNotifications]} <DaySection
contacts={props.contacts} key={day}
archive={!!props.showArchive} label={day === 'latest' ? 'Today' : moment(day).calendar(null, calendar)}
associations={props.associations} timeboxes={timeboxes}
groups={props.groups} contacts={props.contacts}
graphConfig={props.notificationsGraphConfig} archive={!!props.showArchive}
groupConfig={props.notificationsGroupConfig} associations={props.associations}
chatConfig={props.notificationsChatConfig} api={api}
api={api} groups={props.groups}
/> graphConfig={props.notificationsGraphConfig}
)} groupConfig={props.notificationsGroupConfig}
{_.map( chatConfig={props.notificationsChatConfig}
notificationsByDay, />
(timeboxes, idx) => );
timeboxes.length > 0 && ( })}
<DaySection
key={idx}
timeboxes={timeboxes}
contacts={props.contacts}
archive={!!props.showArchive}
associations={props.associations}
api={api}
groups={props.groups}
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
/>
)
)}
</Col> </Col>
); );
} }
@ -181,21 +183,18 @@ function sortIndexedNotification(
} }
function DaySection({ function DaySection({
label,
contacts, contacts,
groups, groups,
archive, archive,
timeboxes, timeboxes,
latest = false,
associations, associations,
api, api,
groupConfig, groupConfig,
graphConfig, graphConfig,
chatConfig, chatConfig,
remoteContentPolicy
}) { }) {
const calendar = latest
? MOMENT_CALENDAR_DATE
: { ...MOMENT_CALENDAR_DATE, sameDay: "[Earlier Today]" };
const lent = timeboxes.map(([,nots]) => nots.length).reduce(f.add, 0); const lent = timeboxes.map(([,nots]) => nots.length).reduce(f.add, 0);
if (lent === 0 || timeboxes.length === 0) { if (lent === 0 || timeboxes.length === 0) {
return null; return null;
@ -206,7 +205,7 @@ function DaySection({
<Box position="sticky" zIndex="3" top="-1px" bg="white"> <Box position="sticky" zIndex="3" top="-1px" bg="white">
<Box p="2" bg="scales.black05"> <Box p="2" bg="scales.black05">
<Text> <Text>
{moment(daToUnix(timeboxes[0][0])).calendar(null, calendar)} {label}
</Text> </Text>
</Box> </Box>
</Box> </Box>

View File

@ -1,5 +1,5 @@
import React, { ReactNode, useCallback, useMemo } from "react"; import React, { ReactNode, useCallback, useMemo, useState } from "react";
import { Row, Box, Col, Text, Anchor, Icon, Action } from "@tlon/indigo-react"; import { Row, Box } from "@tlon/indigo-react";
import _ from "lodash"; import _ from "lodash";
import { import {
GraphNotificationContents, GraphNotificationContents,
@ -7,7 +7,6 @@ import {
GroupNotificationContents, GroupNotificationContents,
NotificationGraphConfig, NotificationGraphConfig,
GroupNotificationsConfig, GroupNotificationsConfig,
NotifIndex,
Groups, Groups,
Associations, Associations,
Contacts, Contacts,
@ -19,6 +18,7 @@ import { GroupNotification } from "./group";
import { GraphNotification } from "./graph"; import { GraphNotification } from "./graph";
import { ChatNotification } from "./chat"; import { ChatNotification } from "./chat";
import { BigInteger } from "big-integer"; import { BigInteger } from "big-integer";
import { useHovering } from "~/logic/lib/util";
interface NotificationProps { interface NotificationProps {
notification: IndexedNotification; notification: IndexedNotification;
@ -89,11 +89,21 @@ function NotificationWrapper(props: {
return api.hark[func](notif); return api.hark[func](notif);
}, [notif, api, isMuted]); }, [notif, api, isMuted]);
const { hovering, bind } = useHovering();
const changeMuteDesc = isMuted ? "Unmute" : "Mute"; const changeMuteDesc = isMuted ? "Unmute" : "Mute";
return ( return (
<Row width="100%" flexShrink={0} alignItems="top" justifyContent="space-between"> <Box
width="100%"
display="grid"
gridTemplateColumns="1fr 200px"
gridTemplateRows="auto"
gridTemplateAreas="'header actions' 'main main'"
pb={2}
{...bind}
>
{children} {children}
<Row gapX="2" p="2" pt='3' alignItems="top"> <Row gapX="2" p="2" pt='3' gridArea="actions" justifyContent="flex-end" opacity={[1, hovering ? 1 : 0]}>
<StatelessAsyncAction name={changeMuteDesc} onClick={onChangeMute} backgroundColor="transparent"> <StatelessAsyncAction name={changeMuteDesc} onClick={onChangeMute} backgroundColor="transparent">
{changeMuteDesc} {changeMuteDesc}
</StatelessAsyncAction> </StatelessAsyncAction>
@ -103,7 +113,7 @@ function NotificationWrapper(props: {
</StatelessAsyncAction> </StatelessAsyncAction>
)} )}
</Row> </Row>
</Row> </Box>
); );
} }

View File

@ -19,10 +19,10 @@ interface MentionTextProps {
group: Group; group: Group;
} }
export function MentionText(props: MentionTextProps) { export function MentionText(props: MentionTextProps) {
const { content, contacts, contact, group } = props; const { content, contacts, contact, group, ...rest } = props;
return ( return (
<RichText contacts={contacts} contact={contact} group={group}> <RichText contacts={contacts} contact={contact} group={group} {...rest}>
{content.reduce((accum, c) => { {content.reduce((accum, c) => {
if ("text" in c) { if ("text" in c) {
return accum + c.text; return accum + c.text;

View File

@ -27,19 +27,17 @@ const RichText = React.memo(({ disableRemoteContent, ...props }) => (
{...props} {...props}
renderers={{ renderers={{
link: (linkProps) => { link: (linkProps) => {
if (disableRemoteContent) { const remoteContentPolicy = disableRemoteContent ? {
linkProps.remoteContentPolicy = { imageShown: false,
imageShown: false, audioShown: false,
audioShown: false, videoShown: false,
videoShown: false, oembedShown: false
oembedShown: false } : null;
};
}
if (hasProvider(linkProps.href)) { if (hasProvider(linkProps.href)) {
return <RemoteContent className="mw-100" url={linkProps.href} />; return <RemoteContent className="mw-100" url={linkProps.href} />;
} }
return <BaseAnchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' {...linkProps}>{linkProps.children}</BaseAnchor>; return <BaseAnchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' remoteContentPolicy={remoteContentPolicy} {...linkProps}>{linkProps.children}</BaseAnchor>;
}, },
linkReference: (linkProps) => { linkReference: (linkProps) => {
const linkText = String(linkProps.children[0].props.children); const linkText = String(linkProps.children[0].props.children);