mirror of
https://github.com/urbit/shrub.git
synced 2024-12-02 21:34:04 +03:00
landscape: refine group joining interface
This commit is contained in:
parent
d1c9b4cdf6
commit
003657890b
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) =>
|
||||
|
@ -206,7 +206,7 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
color="black"
|
||||
text="Join Group"
|
||||
>
|
||||
<JoinGroup />
|
||||
{dismiss => <JoinGroup dismiss={dismiss} />}
|
||||
</ModalButton>
|
||||
</>}
|
||||
{!hideGroups &&
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
--
|
||||
--
|
||||
--
|
||||
|
Loading…
Reference in New Issue
Block a user