mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-07 07:30:23 +03:00
Merge pull request #4279 from tylershuster/hark/inbox-layout
notifications: fix layout
This commit is contained in:
commit
faef9b1d42
@ -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 };
|
||||||
}
|
}
|
@ -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 {
|
||||||
|
@ -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>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user