landscape: refine group joining interface

This commit is contained in:
Liam Fitzgerald 2021-09-29 14:37:31 +10:00
parent d1c9b4cdf6
commit 003657890b
7 changed files with 132 additions and 44 deletions

View File

@ -1,5 +1,5 @@
import { BaseState } from '../state/base';
import { GroupState as State } from '../state/group';
import useGroupState, { GroupState as State } from '../state/group';
type GroupState = State & BaseState<State>;
@ -27,7 +27,9 @@ const progress = (json: any, state: GroupState): GroupState => {
state.pendingJoin[resource].progress = progress;
if(progress === 'done') {
setTimeout(() => {
delete state.pendingJoin[resource];
useGroupState.getState().set((state) => {
delete state.pendingJoin[resource];
});
}, 10000);
}
}

View File

@ -1,4 +1,4 @@
import { Association, Group, JoinRequests } from '@urbit/api';
import { Association, Group, hideGroup, JoinRequests } from '@urbit/api';
import { useCallback } from 'react';
import { reduce } from '../reducers/group-update';
import _ from 'lodash';
@ -8,21 +8,29 @@ import {
createSubscription,
reduceStateN
} from './base';
import api from '~/logic/api';
export interface GroupState {
groups: {
[group: string]: Group;
};
pendingJoin: JoinRequests;
hidePending: (group: string) => Promise<void>;
}
// @ts-ignore investigate zustand types
const useGroupState = createState<GroupState>(
'Group',
{
(set, get) => ({
groups: {},
pendingJoin: {}
},
pendingJoin: {},
hidePending: async (group) => {
get().set((draft) => {
delete draft.pendingJoin[group];
});
await api.poke(hideGroup(group));
}
}),
['groups'],
[
(set, get) =>

View File

@ -206,7 +206,7 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
color="black"
text="Join Group"
>
<JoinGroup />
{dismiss => <JoinGroup dismiss={dismiss} />}
</ModalButton>
</>}
{!hideGroups &&

View File

@ -1,20 +1,19 @@
import { LoadingSpinner } from "@tlon/indigo-react";
import React, { useState } from "react";
import { Box, Row, Col, Text } from "@tlon/indigo-react";
import { PropFunc } from "~/types";
import _ from "lodash";
import * as Dialog from "@radix-ui/react-dialog";
import { StatusBarItem } from "./StatusBarItem";
import useGroupState from "~/logic/state/group";
import { JoinRequest, joinProgress } from "@urbit/api";
import { usePreview } from "~/logic/state/metadata";
import { Dropdown } from "./Dropdown";
import { MetadataIcon } from "../landscape/components/MetadataIcon";
import { LoadingSpinner, Button } from '@tlon/indigo-react';
import React from 'react';
import { Box, Row, Col, Text } from '@tlon/indigo-react';
import { PropFunc } from '~/types';
import _ from 'lodash';
import { StatusBarItem } from './StatusBarItem';
import useGroupState from '~/logic/state/group';
import { JoinRequest, joinProgress } from '@urbit/api';
import { usePreview } from '~/logic/state/metadata';
import { Dropdown } from './Dropdown';
import { MetadataIcon } from '../landscape/components/MetadataIcon';
function Elbow(
props: { size?: number; color?: string } & PropFunc<typeof Box>
) {
const { size = 12, color = "lightGray", ...rest } = props;
const { size = 12, color = 'lightGray', ...rest } = props;
return (
<Box
@ -39,10 +38,9 @@ function Elbow(
}
export function StatusBarJoins() {
const pendingJoin = useGroupState((s) => s.pendingJoin);
const [isOpen, setIsOpen] = useState(false);
const pendingJoin = useGroupState(s => s.pendingJoin);
if (
Object.keys(_.omitBy(pendingJoin, (j) => j.progress === "done")).length ===
Object.keys(_.omitBy(pendingJoin, j => j.progress === 'done')).length ===
0
) {
return null;
@ -50,25 +48,24 @@ export function StatusBarJoins() {
return (
<Dropdown
dropWidth="256px"
dropWidth="325px"
options={
<Col
left="0px"
top="120%"
position="absolute"
zIndex={10}
alignItems="center"
alignItems="flex-start"
p="2"
gapY="4"
gapY="3"
border="1"
borderColor="lightGray"
borderRadius="1"
backgroundColor="white"
>
<Col>
{Object.keys(pendingJoin).map((g) => (
{Object.keys(pendingJoin).map(g => (
<JoinStatus key={g} group={g} join={pendingJoin[g]} />
))}
</Col>
</Col>
}
alignX="left"
@ -82,24 +79,28 @@ export function StatusBarJoins() {
}
const description: string[] = [
"Contacting host...",
"Retrieving data...",
"Finished join",
"Unable to join, you do not have the correct permissions",
"Internal error, please file an issue",
'Contacting host...',
'Retrieving data...',
'Finished join',
'Unable to join, you do not have the correct permissions',
'Internal error, please file an issue'
];
export function JoinStatus({
group,
join,
join
}: {
group: string;
join: JoinRequest;
}) {
const { preview, error } = usePreview(group);
const { preview } = usePreview(group);
const current = join && joinProgress.indexOf(join.progress);
const desc = _.isNumber(current) && description[current];
const onHide = () => {
useGroupState.getState().hidePending(group);
};
return (
<Row alignItems="center" gapX="3">
<Col gapY="2">
<Row alignItems="center" gapX="2">
{preview ? (
@ -112,5 +113,9 @@ export function JoinStatus({
<Text>{desc}</Text>
</Row>
</Col>
<Button onClick={onHide}>
Hide
</Button>
</Row>
);
}

View File

@ -94,6 +94,10 @@ function getDmRedirect(link: string) {
const [,,ship] = link.split('/');
return `/~landscape/messages/dm/${ship}`;
}
function getGroupRedirect(link: string) {
const [,,ship,name] = link.split('/');
return `/~landscape/ship/${ship}/${name}`;
}
function getNotificationRedirect(link: string) {
if(link.startsWith('/graph-validator')) {
@ -102,6 +106,8 @@ function getNotificationRedirect(link: string) {
return getInviteRedirect(link);
} else if (link.startsWith('/dm')) {
return getDmRedirect(link);
} else if (link.startsWith('/groups')) {
return getGroupRedirect(link);
}
}

View File

@ -1,8 +1,9 @@
import {
Box, Col,
Icon,
ManagedTextInputField as Input, Row,
Text
Box, Col,
Icon,
ManagedTextInputField as Input, Row,
Text,
Button
} from '@tlon/indigo-react';
import { join, MetadataUpdatePreview } from '@urbit/api';
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
@ -42,6 +43,7 @@ interface FormSchema {
interface JoinGroupProps {
autojoin?: string;
dismiss?: () => void;
}
function Autojoin(props: { autojoin: string | null }) {
@ -57,8 +59,9 @@ function Autojoin(props: { autojoin: string | null }) {
}
export function JoinGroup(props: JoinGroupProps): ReactElement {
const { autojoin } = props;
const { autojoin, dismiss } = props;
const { associations, getPreview } = useMetadataState();
const [timedOut, setTimedOut] = useState(false);
const groups = useGroupState(state => state.groups);
const history = useHistory();
const initialValues: FormSchema = {
@ -104,6 +107,7 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
history.push(`/~landscape${group}`);
}
} catch (e) {
setTimedOut(true);
console.error(e);
}
}, [waiter, history, associations, groups]);
@ -143,7 +147,17 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
Join a Group
</Text>
</Box>
{_.isString(preview) ? (
{ timedOut ? (
<Col width="100%" gapY={4}>
<Text>The host is not responding. You will receive a notification when the join requests succeeds
</Text>
<Button primary onClick={dismiss}>
Dismiss
</Button>
</Col>
) : _.isString(preview) ? (
<Col width="100%" gapY={4}>
<Text>The host appears to be offline. Join anyway?</Text>
<StatelessAsyncButton

View File

@ -1,4 +1,4 @@
/- view-sur=group-view, group-store, *group, metadata=metadata-store
/- view-sur=group-view, group-store, *group, metadata=metadata-store, hark=hark-store
/+ default-agent, agentio, mdl=metadata,
resource, dbug, grpl=group, conl=contact, verb
|%
@ -327,13 +327,66 @@
%- (slog u.err)
(cleanup %strange)
::
++ notify
%- emit
%+ poke-our:(jn-pass-io /hark) %hark-store
=- hark-action+!>(-)
^- action:hark
|^
[%add-note bin body]
++ bin
^- bin:hark
[/ [q.byk.bowl /join/(scot %p entity.rid)/[name.rid]]]
++ title
|= [name=@t rest=@t]
text/(rap 3 'Joining group: "' name '" ' rest ~)
++ body
^- body:hark
=/ =request:view (~(got by joining) rid)
?> ?=(final:view progress.request)
=/ name (rap 3 (scot %p entity.rid) '/' name.rid ~)
?- progress.request
::
%done
=/ =metadatum:metadata (need (peek-metadatum:met %groups rid))
:* ~[(title title.metadatum 'succeeded')]
~
now.bowl
/
/groups/(scot %p entity.rid)/[name.rid]
==
::
%strange
:* ~[(title name 'errored unexpectedly')]
~
now.bowl
/
/
==
::
%no-perms
:* ~[(title name 'failed, you are not permitted to join the group')]
~
now.bowl
/
/
==
==
--
::
++ cleanup
|= =progress:view
=. jn-core
(tx-progress progress)
=. jn-core
(emit (leave-our:(jn-pass-io /groups) %group-store))
(emit (leave-our:(jn-pass-io /md) %metadata-store))
=. jn-core
(emit (leave-our:(jn-pass-io /md) %metadata-store))
=/ =request:view (~(got by joining) rid)
=? jn-core (lte (sub now.bowl started.request) ~s30)
notify
=. joining (~(del by joining) rid)
jn-core
--
--
--