Merge pull request #4537 from urbit/lf/invite-fixes

invites: don't pin to top
This commit is contained in:
matildepark 2021-03-04 14:23:20 -05:00 committed by GitHub
commit 3ac19c34d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 234 additions and 117 deletions

View File

@ -4,23 +4,30 @@
|%
++ card card:agent:gall
::
+$ base-state
+$ base-state-0
joining=(map rid=resource [=ship =progress:view])
::
+$ base-state-1
joining=(map rid=resource request:view)
::
+$ state-zero
[%0 base-state]
[%0 base-state-0]
::
+$ state-one
[%1 base-state]
[%1 base-state-0]
::
+$ state-two
[%2 base-state-1]
::
+$ versioned-state
$% state-zero
state-one
state-two
==
::
++ view view-sur
--
=| state-one
=| state-two
=* state -
::
%- agent:dbug
@ -41,10 +48,29 @@
|= =vase
=+ !<(old=versioned-state vase)
=| cards=(list card)
|-
?: ?=(%1 -.old)
`this(state old)
$(-.old %1, cards :_(cards (poke-self:pass:io noun+!>(%cleanup))))
|^
?- -.old
%2 [cards this(state old)]
%1 $(-.old %2, +.old (base-state-to-1 +.old))
%0 $(-.old %1, cards :_(cards (poke-self:pass:io noun+!>(%cleanup))))
==
::
++ base-state-to-1
|= base-state-0
%- ~(gas by *(map resource request:view))
(turn ~(tap by joining) request-to-1)
::
++ request-to-1
|= [rid=resource =ship =progress:view]
^- [resource request:view]
:- rid
%* . *request:view
started now.bowl
hidden %.n
ship ship
progress progress
==
--
::
++ on-poke
|= [=mark =vase]
@ -56,9 +82,11 @@
?. ?=(%group-view-action mark)
(on-poke:def mark vase)
=+ !<(=action:view vase)
?> ?=(%join -.action)
=^ cards state
jn-abet:(jn-start:join:gc +.action)
?+ -.action !!
%join jn-abet:(jn-start:join:gc +.action)
%hide (hide:gc +.action)
==
[cards this]
::
++ on-watch
@ -69,8 +97,7 @@
:_ ~
%+ fact:io
:- %group-view-update
!> ^- update:view
[%initial (~(run by joining) |=([=ship =progress:view] progress))]
!>(`update:view`[%initial joining])
~
==
::
@ -97,6 +124,11 @@
++ grp ~(. grpl bowl)
++ io ~(. agentio bowl)
++ con ~(. conl bowl)
++ hide
|= rid=resource
^- (quip card _state)
:- (fact:io group-view-update+!>([%hide rid]) /all ~)^~
state(joining (~(jab by joining) rid |=(request:view +<(hidden %.y))))
::
++ has-joined
|= rid=resource
@ -107,10 +139,10 @@
::
++ poke-noun
^- (quip card _state)
=; new-joining=(map resource [ship progress:view])
=; new-joining=(map resource request:view)
`state(joining new-joining)
%+ roll ~(tap by joining)
|= [[rid=resource =ship =progress:view] out=_joining]
|= [[rid=resource =request:view] out=_joining]
?. (has-joined rid) out
(~(del by out) rid)
::
@ -128,7 +160,7 @@
++ tx-progress
|= =progress:view
=. joining
(~(put by joining) rid [ship progress])
(~(jab by joining) rid |=(request:view +<(progress progress)))
=; =cage
(emit (fact:io cage /all tx+(en-path:resource rid) ~))
group-view-update+!>([%progress rid progress])
@ -145,9 +177,9 @@
::
++ jn-abed
|= r=resource
=/ [s=^ship =progress:view]
=/ =request:view
(~(got by joining) r)
jn-core(rid r, ship s)
jn-core(rid r, ship ship.request)
::
++ jn-abet
^- (quip card _state)
@ -158,7 +190,7 @@
^+ jn-core
?< (~(has by joining) rid)
=. joining
(~(put by joining) rid [ship %start])
(~(put by joining) rid [%.n now.bowl ship %start])
=. jn-core
(jn-abed rid)
?< ~|("already joined {<rid>}" (has-joined rid))

View File

@ -15,6 +15,7 @@
join+join
leave+leave
invite+invite
hide+dejs-path:resource
==
::
++ create
@ -53,6 +54,7 @@
?- -.upd
%initial (initial +.upd)
%progress (progress +.upd)
%hide s+(enjs-path:resource +.upd)
==
::
++ progress
@ -61,13 +63,21 @@
:~ resource+s+(enjs-path:resource rid)
progress+s+prog
==
++ request
|= req=^request
%- pairs
:~ hidden+b+hidden.req
started+(time started.req)
ship+(ship ship.req)
progress+s+progress.req
==
::
++ initial
|= init=(map resource ^progress)
|= init=(map resource ^request)
%- pairs
%+ turn ~(tap by init)
|= [rid=resource prog=^progress]
:_ s+prog
|= [rid=resource req=^request]
:_ (request req)
(enjs-path:resource rid)
--
++ cleanup-md

View File

@ -2,6 +2,13 @@
^?
|%
::
+$ request
$: hidden=?
started=time
=ship
=progress
==
::
+$ action
$% :: host side
[%create name=term =policy title=@t description=@t]
@ -11,6 +18,8 @@
[%leave =resource]
::
[%invite =resource ships=(set ship) description=@t]
:: pending ops
[%hide =resource]
==
::
@ -21,7 +30,8 @@
?(%no-perms %strange %done)
::
+$ update
$% [%initial initial=(map resource progress)]
$% [%initial initial=(map resource request)]
[%progress =resource =progress]
[%hide =resource]
==
--

View File

@ -78,6 +78,10 @@ export default class GroupsApi extends BaseApi<StoreState> {
});
}
hide(resource: string) {
return this.viewAction({ hide: resource });
}
private proxyAction(action: GroupAction) {
return this.action('group-push-hook', 'group-update', action);
}

View File

@ -41,10 +41,9 @@ export function useLazyScroll(
}, [count]);
useEffect(() => {
if (!ref.current) {
if (!ref.current || isDone) {
return;
}
setIsDone(false);
const scroll = ref.current;
loadUntil(scroll);

View File

@ -11,7 +11,7 @@ const progress = (json: any, state: any) => {
const data = json.progress;
if(data) {
const { progress, resource } = data;
state.pendingJoin = { ...state.pendingJoin, [resource]: progress };
state.pendingJoin[resource].progress = progress;
if(progress === 'done') {
setTimeout(() => {
delete state.pendingJoin[resource];
@ -20,10 +20,19 @@ const progress = (json: any, state: any) => {
}
};
const hide = (json: any, state: any) => {
const data = json.hide;
if(data) {
state.pendingJoin[data].hidden = true;
}
}
export const GroupViewReducer = (json: any, state: any) => {
const data = json['group-view-update'];
if(data) {
progress(data, state);
initial(data, state);
hide(data, state);
}
};

View File

@ -119,7 +119,7 @@ export default function Inbox(props: {
return (
<Col ref={scrollRef} position="relative" height="100%" overflowY="auto">
<Invites groups={props.groups} pendingJoin={props.pendingJoin} invites={invites} api={api} associations={associations} />
<Invites contacts={props.contacts} groups={props.groups} pendingJoin={props.pendingJoin} invites={invites} api={api} associations={associations} />
{[...notificationsByDayMap.keys()].sort().reverse().map((day, index) => {
const timeboxes = notificationsByDayMap.get(day)!;
return timeboxes.length > 0 && (
@ -137,15 +137,16 @@ export default function Inbox(props: {
/>
);
})}
{isDone && (
{isDone ? (
<Center mt="2" borderTop={notifications.length !== 0 ? 1 : 0} borderTopColor="washedGray" width="100%" height="96px">
<Text gray fontSize="1">No more notifications</Text>
</Center>
)}
{isLoading && (
) : isLoading ? (
<Center mt="2" borderTop={notifications.length !== 0 ? 1 : 0} borderTopColor="washedGray" width="100%" height="96px">
<LoadingSpinner />
</Center>
) : (
<Box mt="2" height="96px" />
)}
</Col>

View File

@ -1,12 +1,22 @@
import React, { ReactElement } from 'react';
import _ from 'lodash';
import React, { ReactElement, ReactNode } from "react";
import _ from "lodash";
import { Col } from '@tlon/indigo-react';
import { Invites as IInvites, Associations, Invite, JoinRequests, Groups, Contacts, AppInvites, JoinProgress } from '@urbit/api';
import { Col, Box, Text } from "@tlon/indigo-react";
import {
Invites as IInvites,
Associations,
Invite,
JoinRequests,
Groups,
Contacts,
AppInvites,
JoinProgress,
JoinRequest,
} from "@urbit/api";
import GlobalApi from '~/logic/api/global';
import { resourceAsPath, alphabeticalOrder } from '~/logic/lib/util';
import InviteItem from '~/views/components/Invite';
import GlobalApi from "~/logic/api/global";
import { resourceAsPath, alphabeticalOrder } from "~/logic/lib/util";
import InviteItem from "~/views/components/Invite";
interface InvitesProps {
api: GlobalApi;
@ -19,38 +29,49 @@ interface InvitesProps {
interface InviteRef {
uid: string;
app: string
app: string;
invite: Invite;
}
export function Invites(props: InvitesProps): ReactElement {
const { api, invites, pendingJoin } = props;
export function Invites(props: InvitesProps): ReactNode {
const { api, invites } = props;
const inviteArr: InviteRef[] = _.reduce(invites, (acc: InviteRef[], val: AppInvites, app: string) => {
const appInvites = _.reduce(val, (invs: InviteRef[], invite: Invite, uid: string) => {
const inviteArr: InviteRef[] = _.reduce(
invites,
(acc: InviteRef[], val: AppInvites, app: string) => {
const appInvites = _.reduce(
val,
(invs: InviteRef[], invite: Invite, uid: string) => {
return [...invs, { invite, uid, app }];
}, []);
},
[]
);
return [...acc, ...appInvites];
}, []);
},
[]
);
const invitesAndStatus: { [rid: string]: JoinProgress | InviteRef } =
{ ..._.keyBy(inviteArr, ({ invite }) => resourceAsPath(invite.resource)), ...props.pendingJoin };
const pendingJoin = _.omitBy(props.pendingJoin, "hidden");
const invitesAndStatus: { [rid: string]: JoinRequest | InviteRef } = {
..._.keyBy(inviteArr, ({ invite }) => resourceAsPath(invite.resource)),
...pendingJoin,
};
return (
<Col
zIndex={4}
gapY={2}
bg="white"
top="0px"
position="sticky"
flexShrink={0}
>
{ Object
.keys(invitesAndStatus)
<>
{Object.keys(invitesAndStatus).length > 0 && (
<Box position="sticky" zIndex={3} top="-1px" bg="white">
<Box p="2" bg="scales.black05">
<Text>Invites</Text>
</Box>
</Box>
)}
{Object.keys(invitesAndStatus)
.sort(alphabeticalOrder)
.map((resource) => {
const inviteOrStatus = invitesAndStatus[resource];
if(typeof inviteOrStatus === 'string') {
if ("progress" in inviteOrStatus) {
return (
<InviteItem
key={resource}
@ -81,6 +102,6 @@ export function Invites(props: InvitesProps): ReactElement {
);
}
})}
</Col>
</>
);
}

View File

@ -1,13 +1,18 @@
import React from 'react';
import { Row, Text, SegmentedProgressBar, Box } from '@tlon/indigo-react';
import React, {useCallback} from 'react';
import { Action, Row, Text, SegmentedProgressBar, Box } from '@tlon/indigo-react';
import {
JoinProgress,
joinProgress,
joinError
joinError,
JoinRequest
} from '@urbit/api';
import GlobalApi from '~/logic/api/global';
import {StatelessAsyncAction} from '~/views/components/StatelessAsyncAction';
interface JoiningStatusProps {
status: JoinProgress;
status: JoinRequest;
api: GlobalApi;
resource: string;
}
const description: string[] = [
@ -19,11 +24,13 @@ const description: string[] = [
];
export function JoiningStatus(props: JoiningStatusProps) {
const { status } = props;
const { status, resource, api } = props;
const current = joinProgress.indexOf(status);
const current = joinProgress.indexOf(status.progress);
const desc = description?.[current] || '';
const isError = joinError.indexOf(status as any) !== -1;
const isError = joinError.indexOf(status.progress as any) !== -1;
const onHide = useCallback(() => api.groups.hide(resource)
, [resource, api])
return (
<Row
display={['flex-column', 'flex']}
@ -37,6 +44,7 @@ export function JoiningStatus(props: JoiningStatusProps) {
<Text display="block" flexShrink={0} color={isError ? 'red' : 'gray'}>
{desc}
</Text>
<StatelessAsyncAction onClick={onHide} flexShrink={1}>Hide</StatelessAsyncAction>
</Row>
);
}

View File

@ -2,21 +2,24 @@ import React, { ReactElement, ReactNode } from 'react';
import { Text, Box, Icon, Row } from '@tlon/indigo-react';
import { cite } from '~/logic/lib/util';
import { MetadataUpdatePreview, JoinProgress, Invite } from '@urbit/api';
import { MetadataUpdatePreview, JoinProgress, Invite, JoinRequest } from '@urbit/api';
import { GroupSummary } from '~/views/landscape/components/GroupSummary';
import { InviteSkeleton } from './InviteSkeleton';
import { JoinSkeleton } from './JoinSkeleton';
import GlobalApi from '~/logic/api/global';
interface GroupInviteProps {
preview: MetadataUpdatePreview;
status?: JoinProgress;
status?: JoinRequest;
invite?: Invite;
resource: string;
api: GlobalApi;
onAccept: () => Promise<any>;
onDecline: () => Promise<any>;
}
export function GroupInvite(props: GroupInviteProps): ReactElement {
const { preview, invite, status, onAccept, onDecline } = props;
const { resource, api, preview, invite, status, onAccept, onDecline } = props;
const { metadata, members } = props.preview;
let inner: ReactNode = null;
@ -31,7 +34,7 @@ export function GroupInvite(props: GroupInviteProps): ReactElement {
</Text>
);
Outer = ({ children }) => (
<JoinSkeleton gapY="3" status={status}>
<JoinSkeleton resource={resource} api={api} gapY="3" status={status}>
{children}
</JoinSkeleton>
);

View File

@ -47,7 +47,7 @@ export function InviteSkeleton(
</StatelessAsyncAction>
</Row>
</Col>
<Rule />
<Rule borderColor="washedGray" />
</>
);
}

View File

@ -1,23 +1,26 @@
import React, { ReactElement, ReactNode } from 'react';
import { Col, Rule } from '@tlon/indigo-react';
import { JoiningStatus } from '~/views/apps/notifications/joining';
import { JoinProgress } from '@urbit/api';
import { PropFunc } from '~/types/util';
import React, { ReactElement, ReactNode } from "react";
import { Action, Row, Col, Rule } from "@tlon/indigo-react";
import { JoiningStatus } from "~/views/apps/notifications/joining";
import { JoinProgress, JoinRequest } from "@urbit/api";
import { PropFunc } from "~/types/util";
import GlobalApi from "~/logic/api/global";
type JoinSkeletonProps = {
children: ReactNode;
status: JoinProgress;
status: JoinRequest;
api: GlobalApi;
resource: string;
} & PropFunc<typeof Col>;
export function JoinSkeleton(props: JoinSkeletonProps): ReactElement {
const { children, status, ...rest } = props;
const { api, resource, children, status, ...rest } = props;
return (
<>
<Col p="1" {...rest}>
{children}
<JoiningStatus status={status} />
<JoiningStatus api={api} resource={resource} status={status} />
</Col>
<Rule />
<Rule borderColor="washedGray" />
</>
);
}

View File

@ -94,6 +94,8 @@ export function InviteItem(props: InviteItemProps) {
if (preview) {
return (
<GroupInvite
resource={resource}
api={api}
preview={preview}
invite={invite}
status={status}
@ -119,7 +121,7 @@ export function InviteItem(props: InviteItemProps) {
);
} else if (status && name.startsWith('dm--')) {
return (
<JoinSkeleton status={status} gapY="3">
<JoinSkeleton api={api} resource={resource} status={status} gapY="3">
<Row py="1" alignItems="center">
<Icon display="block" color="blue" icon="Bullet" mr="2" />
<Text mr="1">Joining direct message...</Text>
@ -131,6 +133,7 @@ export function InviteItem(props: InviteItemProps) {
<InviteSkeleton
acceptDesc="Accept Invite"
declineDesc="Decline Invite"
resource={resource}
{...handlers}
gapY="3"
>
@ -148,7 +151,7 @@ export function InviteItem(props: InviteItemProps) {
} else if (status) {
const [, , ship, name] = resource.split('/');
return (
<JoinSkeleton status={status}>
<JoinSkeleton api={api} resource={resource} status={status}>
<Row py="1" alignItems="center">
<Icon display="block" color="blue" icon="Bullet" mr="2" />
<Text mr="1">

View File

@ -1,4 +1,5 @@
import { joinError, joinProgress, joinResult } from ".";
import {Patp} from "../lib";
export type JoinError = typeof joinError[number];
@ -7,6 +8,19 @@ export type JoinResult = typeof joinResult[number];
export type JoinProgress = typeof joinProgress[number];
export interface JoinRequests {
[rid: string]: JoinProgress;
export interface JoinRequest {
/**
* Whether to display the join request or not
*/
hidden: boolean;
/**
* Timestamp of when the request started
*/
started: number;
ship: Patp;
progress: JoinProgress;
}
export interface JoinRequests {
[rid: string]: JoinRequest;
}