mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-14 17:41:33 +03:00
Merge pull request #3915 from urbit/lf/hark-qa-fixes
hark: lazier notifications loading, final bugfixes
This commit is contained in:
commit
118e7d2c8b
@ -48,7 +48,7 @@
|
||||
(on-watch:def path)
|
||||
:_ this
|
||||
=; =cage
|
||||
[%give %fact ~[/updates] cage]~
|
||||
[%give %fact ~ cage]~
|
||||
:- %hark-group-hook-update
|
||||
!> ^- update:hook
|
||||
[%initial watching]
|
||||
|
@ -15,7 +15,7 @@
|
||||
=notifications:store
|
||||
archive=notifications:store
|
||||
last-seen=@da
|
||||
dnd=?
|
||||
dnd=_|
|
||||
==
|
||||
+$ inflated-state
|
||||
$: state-0
|
||||
@ -25,6 +25,7 @@
|
||||
:: albeit expensively
|
||||
+$ cache
|
||||
$: unread-count=@ud
|
||||
graph-unreads=(map resource @ud)
|
||||
~
|
||||
==
|
||||
::
|
||||
@ -71,14 +72,17 @@
|
||||
^- update:store
|
||||
:- %more
|
||||
^- (list update:store)
|
||||
:- [%graph-unreads graph-unreads]
|
||||
:+ [%set-dnd dnd]
|
||||
[%count unread-count]
|
||||
%+ weld
|
||||
%+ turn
|
||||
(tap-nonempty archive)
|
||||
%+ scag 5
|
||||
(tap-nonempty:ha archive)
|
||||
(timebox-update &)
|
||||
%+ turn
|
||||
(tap-nonempty notifications)
|
||||
%+ scag 5
|
||||
(tap-nonempty:ha notifications)
|
||||
(timebox-update |)
|
||||
::
|
||||
++ timebox-update
|
||||
@ -86,12 +90,6 @@
|
||||
|= [time=@da =timebox:store]
|
||||
^- update:store
|
||||
[%timebox time archived ~(tap by timebox)]
|
||||
::
|
||||
++ tap-nonempty
|
||||
|= =notifications:store
|
||||
^- (list [@da timebox:store])
|
||||
%+ skip (tap:orm notifications)
|
||||
|=([@da =timebox:store] =(0 ~(wyt by timebox)))
|
||||
--
|
||||
::
|
||||
++ on-peek
|
||||
@ -104,11 +102,13 @@
|
||||
(slav %ud i.t.t.path)
|
||||
=/ length=@ud
|
||||
(slav %ud i.t.t.t.path)
|
||||
:^ ~ ~ %noun
|
||||
:^ ~ ~ %hark-update
|
||||
!> ^- update:store
|
||||
:- %more
|
||||
%+ turn
|
||||
(scag length (slag offset (tap:orm notifications)))
|
||||
%+ scag length
|
||||
%+ slag offset
|
||||
(tap-nonempty:ha notifications)
|
||||
|= [time=@da =timebox:store]
|
||||
^- update:store
|
||||
:^ %timebox time %.n
|
||||
@ -154,6 +154,7 @@
|
||||
(~(put by timebox) index new)
|
||||
:- (give:ha [/updates]~ %added last-seen index new)
|
||||
%_ state
|
||||
+ ?~(existing-notif (upd-unreads:ha index %.n) +.state)
|
||||
notifications (put:orm notifications last-seen new-timebox)
|
||||
unread-count ?~(existing-notif +(unread-count) unread-count)
|
||||
==
|
||||
@ -169,7 +170,7 @@
|
||||
(~(del by timebox) index)
|
||||
:- (give:ha [/updates]~ %archive time index)
|
||||
%_ state
|
||||
unread-count ?.(read.notification (dec unread-count) unread-count)
|
||||
+ ?.(read.notification (upd-unreads:ha index %.y) +.state)
|
||||
::
|
||||
notifications
|
||||
(put:orm notifications time new-timebox)
|
||||
@ -186,6 +187,7 @@
|
||||
^- (quip card _state)
|
||||
:- (give:ha [/updates]~ %read time index)
|
||||
%_ state
|
||||
+ (upd-unreads:ha index %.y)
|
||||
unread-count (dec unread-count)
|
||||
notifications (change-read-status:ha time index %.y)
|
||||
==
|
||||
@ -195,6 +197,7 @@
|
||||
^- (quip card _state)
|
||||
:- (give:ha [/updates]~ %unread time index)
|
||||
%_ state
|
||||
+ (upd-unreads:ha index %.n)
|
||||
unread-count +(unread-count)
|
||||
notifications (change-read-status:ha time index %.n)
|
||||
==
|
||||
@ -231,6 +234,12 @@
|
||||
|_ =bowl:gall
|
||||
+* met ~(. metadata bowl)
|
||||
::
|
||||
++ tap-nonempty
|
||||
|= =notifications:store
|
||||
^- (list [@da timebox:store])
|
||||
%+ skip (tap:orm notifications)
|
||||
|=([@da =timebox:store] =(0 ~(wyt by timebox)))
|
||||
::
|
||||
++ merge-notification
|
||||
|= [existing=notification:store new=notification:store]
|
||||
^- notification:store
|
||||
@ -292,22 +301,37 @@
|
||||
^- (list card)
|
||||
[%give %fact paths [%hark-update !>(update)]]~
|
||||
::
|
||||
++ upd-unreads
|
||||
|= [=index:store read=?]
|
||||
^+ +.state
|
||||
=/ f=$-(@ @)
|
||||
?: read
|
||||
dec
|
||||
|=(a=@ +(a))
|
||||
=. unread-count (f unread-count)
|
||||
?. ?=(%graph -.index)
|
||||
+.state
|
||||
=/ curr-unread=@ud
|
||||
(~(gut by graph-unreads) graph.index 0)
|
||||
+.state(graph-unreads (~(put by graph-unreads) graph.index (f curr-unread)))
|
||||
::
|
||||
++ inflate-cache
|
||||
|= state-0
|
||||
^- cache
|
||||
:_ ~
|
||||
%+ roll
|
||||
^+ +.state
|
||||
=/ nots=(list [p=@da =timebox:store])
|
||||
(tap:orm notifications)
|
||||
|= [[time=@da =timebox:store] out=@ud]
|
||||
=/ unreads ~(tap by timebox)
|
||||
|-
|
||||
?~ unreads out
|
||||
|- =* outer $
|
||||
?~ nots
|
||||
+.state
|
||||
=/ unreads ~(tap by timebox.i.nots)
|
||||
|- =* inner $
|
||||
?~ unreads
|
||||
outer(nots t.nots)
|
||||
=* notification q.i.unreads
|
||||
=* index p.i.unreads
|
||||
?: read.notification
|
||||
out
|
||||
%_ $
|
||||
unreads t.unreads
|
||||
::
|
||||
out +(out)
|
||||
==
|
||||
inner(unreads t.unreads)
|
||||
=. +.state
|
||||
(upd-unreads index read.notification)
|
||||
inner(unreads t.unreads)
|
||||
--
|
||||
|
@ -78,12 +78,22 @@
|
||||
%timebox (timebox +.upd)
|
||||
%set-dnd b+dnd.upd
|
||||
%count (numb count.upd)
|
||||
%graph-unreads (graph-unreads map.upd)
|
||||
%more (more +.upd)
|
||||
::
|
||||
?(%archive %read %unread)
|
||||
(notif-ref +.upd)
|
||||
==
|
||||
::
|
||||
++ graph-unreads
|
||||
|= =(map resource @ud)
|
||||
^- json
|
||||
%- pairs
|
||||
%+ turn
|
||||
~(tap by map)
|
||||
|= [rid=resource unread=@ud]
|
||||
(enjs-path:resource rid)^(numb unread)
|
||||
::
|
||||
++ added
|
||||
|= [tim=@da idx=^index not=^notification]
|
||||
^- json
|
||||
|
@ -46,5 +46,6 @@
|
||||
[%added time=@da =index =notification]
|
||||
[%timebox time=@da archived=? =(list [index notification])]
|
||||
[%count count=@ud]
|
||||
[%graph-unreads =(map resource @ud)]
|
||||
==
|
||||
--
|
||||
|
@ -149,10 +149,21 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
});
|
||||
}
|
||||
|
||||
getMore() {
|
||||
const offset = this.store.state.notifications.size;
|
||||
const count = 10;
|
||||
return this.getSubset(offset,count);
|
||||
}
|
||||
|
||||
async getSubset(offset:number, count:number) {
|
||||
const data = await this.scry("hark-store", `/recent/${offset}/${count}`);
|
||||
this.store.handleEvent({ data });
|
||||
}
|
||||
|
||||
async getTimeSubset(start?: Date, end?: Date) {
|
||||
const s = start ? dateToDa(start) : "-";
|
||||
const e = end ? dateToDa(end) : "-";
|
||||
const result = await this.scry("hark-hook", `/time-subset/${s}/${e}`);
|
||||
const result = await this.scry("hark-hook", `/recent/${s}/${e}`);
|
||||
this.store.handleEvent({
|
||||
data: result,
|
||||
});
|
||||
|
@ -23,7 +23,6 @@ export const HarkReducer = (json: any, state: HarkState) => {
|
||||
}
|
||||
const graphHookData = _.get(json, "hark-graph-hook-update", false);
|
||||
if (graphHookData) {
|
||||
console.log(graphHookData);
|
||||
graphInitial(graphHookData, state);
|
||||
graphIgnore(graphHookData, state);
|
||||
graphListen(graphHookData, state);
|
||||
@ -133,6 +132,7 @@ function graphWatchSelf(json: any, state: HarkState) {
|
||||
}
|
||||
|
||||
function reduce(data: any, state: HarkState) {
|
||||
console.log(data);
|
||||
unread(data, state);
|
||||
read(data, state);
|
||||
archive(data, state);
|
||||
@ -141,6 +141,14 @@ function reduce(data: any, state: HarkState) {
|
||||
dnd(data, state);
|
||||
count(data, state);
|
||||
added(data, state);
|
||||
graphUnreads(data, state);
|
||||
}
|
||||
|
||||
function graphUnreads(json: any, state: HarkState) {
|
||||
const data = _.get(json, 'graph-unreads');
|
||||
if(data) {
|
||||
state.graphUnreads = data;
|
||||
}
|
||||
}
|
||||
|
||||
function added(json: any, state: HarkState) {
|
||||
@ -158,6 +166,10 @@ function added(json: any, state: HarkState) {
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -243,6 +255,11 @@ function read(json: any, state: HarkState) {
|
||||
if (data) {
|
||||
const { time, index } = data;
|
||||
state.notificationsCount--;
|
||||
if('graph' in index) {
|
||||
const curr = state.graphUnreads[index.graph.graph] || 0;
|
||||
state.graphUnreads[index.graph.graph] = curr-1;
|
||||
}
|
||||
|
||||
setRead(time, index, true, state);
|
||||
}
|
||||
}
|
||||
@ -252,6 +269,10 @@ function unread(json: any, state: HarkState) {
|
||||
if (data) {
|
||||
const { time, index } = data;
|
||||
state.notificationsCount++;
|
||||
if('graph' in index) {
|
||||
const curr = state.graphUnreads[index.graph.graph] || 0;
|
||||
state.graphUnreads[index.graph.graph] = curr+1;
|
||||
}
|
||||
setRead(time, index, false, state);
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +106,8 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
mentions: false,
|
||||
watching: [],
|
||||
},
|
||||
notificationsCount: 0
|
||||
notificationsCount: 0,
|
||||
graphUnreads: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -65,4 +65,5 @@ export interface StoreState {
|
||||
notificationsChatConfig: string[];
|
||||
notificationsCount: number,
|
||||
doNotDisturb: boolean;
|
||||
graphUnreads: Record<string, number>;
|
||||
}
|
||||
|
@ -86,7 +86,8 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
unreadMarkerRef,
|
||||
history,
|
||||
api,
|
||||
highlighted
|
||||
highlighted,
|
||||
fontSize
|
||||
} = this.props;
|
||||
|
||||
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
|
||||
@ -118,7 +119,8 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
history,
|
||||
api,
|
||||
scrollWindow,
|
||||
highlighted
|
||||
highlighted,
|
||||
fontSize
|
||||
};
|
||||
|
||||
const unreadContainerStyle = {
|
||||
@ -131,7 +133,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
width='100%'
|
||||
display='flex'
|
||||
flexWrap='wrap'
|
||||
pt={renderSigil ? 3 : 0}
|
||||
pt={this.props.pt ? this.props.pt : renderSigil ? 3 : 0}
|
||||
pr={3}
|
||||
pb={isLastMessage ? 3 : 0}
|
||||
ref={this.divRef}
|
||||
@ -184,7 +186,8 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
||||
measure,
|
||||
api,
|
||||
history,
|
||||
scrollWindow
|
||||
scrollWindow,
|
||||
fontSize
|
||||
} = this.props;
|
||||
|
||||
const datestamp = moment.unix(msg.when / 1000).format(DATESTAMP_FORMAT);
|
||||
@ -245,7 +248,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={fontSize ? fontSize : '14px'}><MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure} fontSize={fontSize} /></Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
@ -261,12 +264,12 @@ export const MessageWithoutSigil = ({ timestamp, msg, remoteContentPolicy, measu
|
||||
</>
|
||||
);
|
||||
|
||||
export const MessageContent = ({ content, remoteContentPolicy, measure }) => {
|
||||
export const MessageContent = ({ content, remoteContentPolicy, measure, fontSize }) => {
|
||||
if ('code' in content) {
|
||||
return <CodeContent content={content} />;
|
||||
} else if ('url' in content) {
|
||||
return (
|
||||
<Text fontSize='14px' lineHeight="tall" color='black'>
|
||||
<Text fontSize={fontSize ? fontSize : '14px'} lineHeight="tall" color='black'>
|
||||
<RemoteContent
|
||||
url={content.url}
|
||||
remoteContentPolicy={remoteContentPolicy}
|
||||
@ -282,13 +285,13 @@ export const MessageContent = ({ content, remoteContentPolicy, measure }) => {
|
||||
);
|
||||
} else if ('me' in content) {
|
||||
return (
|
||||
<Text fontStyle='italic' fontSize='14px' lineHeight='tall' color='black'>
|
||||
<Text fontStyle='italic' fontSize={fontSize ? fontSize : '14px'} lineHeight='tall' color='black'>
|
||||
{content.me}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
else if ('text' in content) {
|
||||
return <TextContent content={content} />;
|
||||
return <TextContent fontSize={fontSize} content={content} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ export default class CodeContent extends Component {
|
||||
p='1'
|
||||
my='0'
|
||||
borderRadius='1'
|
||||
fontSize='14px'
|
||||
overflow='auto'
|
||||
maxHeight='10em'
|
||||
maxWidth='100%'
|
||||
@ -35,7 +34,6 @@ export default class CodeContent extends Component {
|
||||
my='0'
|
||||
p='1'
|
||||
borderRadius='1'
|
||||
fontSize='14px'
|
||||
overflow='auto'
|
||||
maxHeight='10em'
|
||||
maxWidth='100%'
|
||||
|
@ -26,13 +26,12 @@ const DISABLED_INLINE_TOKENS = [
|
||||
|
||||
const renderers = {
|
||||
inlineCode: ({language, value}) => {
|
||||
return <Text mono fontSize='14px' backgroundColor='washedGray' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
|
||||
return <Text mono backgroundColor='washedGray' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
|
||||
},
|
||||
code: ({language, value}) => {
|
||||
return <Text
|
||||
p='1'
|
||||
className='clamp-message'
|
||||
fontSize='14px'
|
||||
display='block'
|
||||
borderRadius='1'
|
||||
mono
|
||||
@ -84,7 +83,7 @@ export default class TextContent extends Component {
|
||||
&& (urbitOb.isValidPatp(group[2]) // valid patp?
|
||||
&& (group[0] === content.text))) { // entire message is room name?
|
||||
return (
|
||||
<Text fontSize='14px' color='black' lineHeight="tall">
|
||||
<Text fontSize={props.fontSize ? props.fontSize : '14px'} color='black' lineHeight="tall">
|
||||
<Link
|
||||
className="bb b--black b--white-d mono"
|
||||
to={'/~landscape/join/' + group.input}>
|
||||
@ -94,7 +93,7 @@ export default class TextContent extends Component {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Text color='black' fontSize='14px' lineHeight="tall" style={{ overflowWrap: 'break-word' }}>
|
||||
<Text color='black' fontSize={props.fontSize ? props.fontSize : '14px'} lineHeight="tall" style={{ overflowWrap: 'break-word' }}>
|
||||
<MessageMarkdown source={content.text} />
|
||||
</Text>
|
||||
);
|
||||
|
@ -83,6 +83,8 @@ export function ChatNotification(props: {
|
||||
isLastRead={false}
|
||||
group={group}
|
||||
contacts={groupContacts}
|
||||
fontSize='0'
|
||||
pt='2'
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
|
@ -51,8 +51,8 @@ function describeNotification(description: string, plural: boolean) {
|
||||
}
|
||||
|
||||
const GraphUrl = ({ url, title }) => (
|
||||
<Box borderRadius="1" p="2" bg="washedGray">
|
||||
<Anchor target="_blank" color="gray" href={url}>
|
||||
<Box borderRadius="2" p="2" bg="scales.black05">
|
||||
<Anchor underline={false} target="_blank" color="black" href={url}>
|
||||
<Icon verticalAlign="bottom" mr="2" icon="ArrowExternal" />
|
||||
{title}
|
||||
</Anchor>
|
||||
@ -140,7 +140,7 @@ const GraphNode = ({
|
||||
|
||||
return (
|
||||
<Link to={nodeUrl}>
|
||||
<Row gapX="2" py="2">
|
||||
<Row gapX="2" pt="2">
|
||||
<Col>{img}</Col>
|
||||
<Col alignItems="flex-start">
|
||||
<Row
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useCallback } from "react";
|
||||
import f from "lodash/fp";
|
||||
import _ from "lodash";
|
||||
import { Icon, Col, Row, Box, Text, Anchor } from "@tlon/indigo-react";
|
||||
@ -69,13 +69,20 @@ export default function Inbox(props: {
|
||||
f.values
|
||||
)(notifications);
|
||||
|
||||
const onScroll = useCallback((e) => {
|
||||
let container = e.target;
|
||||
if(!props.showArchive && (container.scrollHeight - container.scrollTop === container.clientHeight)) {
|
||||
api.hark.getMore();
|
||||
}
|
||||
}, [api]);
|
||||
|
||||
const incomingGroups = Object.values(invites?.['contacts'] || {});
|
||||
|
||||
const getKeyByValue = (object, value) => {
|
||||
const getKeyByValue = (object, value) => {
|
||||
return Object.keys(object).find(key => object[key] === value);
|
||||
};
|
||||
|
||||
const acceptInvite = (invite) => {
|
||||
const acceptInvite = (invite) => {
|
||||
const resource = {
|
||||
ship: `~${invite.resource.ship}`,
|
||||
name: invite.resource.name
|
||||
@ -86,11 +93,9 @@ const acceptInvite = (invite) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Col overflowY="auto" flexGrow="1">
|
||||
<Col onScroll={onScroll} overflowY="auto" flexGrow="1" minHeight='0' flexShrink='0'>
|
||||
{incomingGroups.map((invite) => (
|
||||
<Box
|
||||
height='100%'
|
||||
width='100%'
|
||||
bg='white'
|
||||
p='3'
|
||||
fontSize='0'>
|
||||
|
@ -87,9 +87,9 @@ function NotificationWrapper(props: {
|
||||
|
||||
const changeMuteDesc = isMuted ? "Unmute" : "Mute";
|
||||
return (
|
||||
<Row alignItems="center" justifyContent="space-between">
|
||||
<Row alignItems="top" justifyContent="space-between">
|
||||
{children}
|
||||
<Row gapX="2" p="2" alignItems="center">
|
||||
<Row gapX="2" p="2" pt='3' alignItems="top">
|
||||
<StatelessAsyncAction name={changeMuteDesc} onClick={onChangeMute} backgroundColor="transparent">
|
||||
{changeMuteDesc}
|
||||
</StatelessAsyncAction>
|
||||
|
@ -53,7 +53,7 @@ export default function NotificationsScreen(props: any) {
|
||||
const { view } = routeProps.match.params;
|
||||
return (
|
||||
<Body>
|
||||
<Col height="100%">
|
||||
<Col height="100%" minHeight='0' overflowY='scroll'>
|
||||
<Row
|
||||
p="3"
|
||||
alignItems="center"
|
||||
|
36
pkg/interface/src/views/components/StatelessAsyncToggle.tsx
Normal file
36
pkg/interface/src/views/components/StatelessAsyncToggle.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import React, { ReactNode, useState, useEffect, useCallback } from "react";
|
||||
|
||||
import {
|
||||
StatelessToggleSwitchField as Toggle,
|
||||
LoadingSpinner,
|
||||
Text
|
||||
} from "@tlon/indigo-react";
|
||||
import { useFormikContext } from "formik";
|
||||
|
||||
import { useStatelessAsyncClickable } from "~/logic/lib/useStatelessAsyncClickable";
|
||||
|
||||
interface AsyncToggleProps {
|
||||
name?: string;
|
||||
onClick: (e: React.MouseEvent) => Promise<void>;
|
||||
}
|
||||
|
||||
export function StatelessAsyncToggle({
|
||||
onClick,
|
||||
name = "",
|
||||
...rest
|
||||
}: AsyncToggleProps & Parameters<typeof Toggle>[0]) {
|
||||
const {
|
||||
onClick: handleClick,
|
||||
buttonState: state,
|
||||
} = useStatelessAsyncClickable(onClick, name);
|
||||
|
||||
return state === "error" ? (
|
||||
<Text mr="2">Error</Text>
|
||||
) : state === "loading" ? (
|
||||
<LoadingSpinner mr="2" foreground={"white"} background="gray" />
|
||||
) : state === "success" ? (
|
||||
<Text mr="2">Done</Text>
|
||||
) : (
|
||||
<Toggle onClick={handleClick} {...rest} />
|
||||
);
|
||||
}
|
@ -26,7 +26,7 @@ const StatusBar = (props) => {
|
||||
|
||||
<StatusBarItem mr={2} onClick={() => props.api.local.setOmnibox()}>
|
||||
{ !props.doNotDisturb && (props.notificationsCount > 0 || invites.length > 0) &&
|
||||
(<Box display="block" right="-5px" top="-5px" position="absolute" >
|
||||
(<Box display="block" right="-8px" top="-8px" position="absolute" >
|
||||
<Icon color="blue" icon="Bullet" />
|
||||
</Box>
|
||||
)}
|
||||
|
@ -20,6 +20,7 @@ export function StatusBarItem({
|
||||
color="washedGray"
|
||||
bg="white"
|
||||
px={2}
|
||||
overflow='visible'
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
@ -9,9 +9,9 @@ import {
|
||||
Col,
|
||||
Label,
|
||||
Button,
|
||||
LoadingSpinner,
|
||||
BaseLabel
|
||||
} from "@tlon/indigo-react";
|
||||
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
|
||||
import { FormError } from "~/views/components/FormError";
|
||||
import { Group, GroupPolicy } from "~/types/group-update";
|
||||
import { Enc } from "~/types/noun";
|
||||
import { Association } from "~/types/metadata-update";
|
||||
@ -24,6 +24,7 @@ import { useHistory } from "react-router-dom";
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
import { FormikOnBlur } from "~/views/components/FormikOnBlur";
|
||||
import {GroupNotificationsConfig} from "~/types";
|
||||
import {StatelessAsyncToggle} from "~/views/components/StatelessAsyncToggle";
|
||||
|
||||
function DeleteGroup(props: {
|
||||
owner: boolean;
|
||||
@ -42,7 +43,7 @@ function DeleteGroup(props: {
|
||||
const action = props.owner ? "Delete" : "Leave";
|
||||
const description = props.owner
|
||||
? "Permanently delete this group. (All current members will no longer see this group.)"
|
||||
: "Leave this group. You can rejoin if it is an open group, or if you are reinvited";
|
||||
: "You can rejoin if it is an open group, or if you are reinvited";
|
||||
|
||||
return (
|
||||
<Col>
|
||||
@ -50,7 +51,7 @@ function DeleteGroup(props: {
|
||||
<Label gray mt="2">
|
||||
{description}
|
||||
</Label>
|
||||
<StatelessAsyncButton onClick={onDelete} mt={2} destructive>
|
||||
<StatelessAsyncButton onClick={onDelete} mt={2} destructive={props.owner}>
|
||||
{action} this group
|
||||
</StatelessAsyncButton>
|
||||
</Col>
|
||||
@ -71,27 +72,27 @@ export function GroupPersonalSettings(props: {
|
||||
|
||||
const watching = props.notificationsGroupConfig.findIndex(g => g === groupPath) !== -1;
|
||||
|
||||
const initialValues: FormSchema = {
|
||||
watching
|
||||
};
|
||||
const onSubmit = async (values: FormSchema) => {
|
||||
if(values.watching === watching) {
|
||||
return;
|
||||
}
|
||||
const func = values.watching ? 'listenGroup' : 'ignoreGroup';
|
||||
const onClick = async () => {
|
||||
const func = !watching ? 'listenGroup' : 'ignoreGroup';
|
||||
await props.api.hark[func](groupPath);
|
||||
};
|
||||
|
||||
const owner = (props.group?.tags?.role?.admin.has(window.ship) || false);
|
||||
|
||||
return (
|
||||
<Col gapY="4">
|
||||
<FormikOnBlur initialValues={initialValues} onSubmit={onSubmit}>
|
||||
<Toggle
|
||||
id="watching"
|
||||
label="Notify me on group activity"
|
||||
caption="Send me notifications when this group changes"
|
||||
/>
|
||||
</FormikOnBlur>
|
||||
<DeleteGroup association={props.association} owner api={props.api} />
|
||||
<BaseLabel
|
||||
htmlFor="asyncToggle"
|
||||
display="flex"
|
||||
cursor="pointer"
|
||||
>
|
||||
<StatelessAsyncToggle selected={watching} onClick={onClick} />
|
||||
<Col>
|
||||
<Label>Notify me on group activity</Label>
|
||||
<Label mt="2" gray>Send me notifications when this group changes</Label>
|
||||
</Col>
|
||||
</BaseLabel>
|
||||
<DeleteGroup association={props.association} owner={owner} api={props.api} />
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
@ -42,9 +42,13 @@ export function useChat(
|
||||
export function useGraphModule(
|
||||
graphKeys: Set<string>,
|
||||
graphs: Graphs,
|
||||
graphUnreads: Record<string, number>
|
||||
): SidebarAppConfig {
|
||||
const getStatus = useCallback(
|
||||
(s: string) => {
|
||||
if((graphUnreads[s] || 0) > 0) {
|
||||
return 'unread';
|
||||
}
|
||||
const [, , host, name] = s.split("/");
|
||||
const graphKey = `${host.slice(1)}/${name}`;
|
||||
|
||||
|
@ -43,7 +43,7 @@ interface SkeletonProps {
|
||||
|
||||
export function Skeleton(props: SkeletonProps) {
|
||||
const chatConfig = useChat(props.inbox, props.chatSynced);
|
||||
const graphConfig = useGraphModule(props.graphKeys, props.graphs);
|
||||
const graphConfig = useGraphModule(props.graphKeys, props.graphs, props.graphUnreads);
|
||||
const config = useMemo(
|
||||
() => ({
|
||||
graph: graphConfig,
|
||||
|
Loading…
Reference in New Issue
Block a user