interface: route new joining flow

This commit is contained in:
Liam Fitzgerald 2021-11-17 16:33:57 -05:00
parent 16e9381176
commit fd64a627c3
3 changed files with 184 additions and 90 deletions

View File

@ -1,19 +1,23 @@
/* eslint-disable max-lines-per-function */
import { Box, Icon, Row, Text } from '@tlon/indigo-react';
import React, { ReactElement } from 'react';
import { Helmet } from 'react-helmet';
import { Route } from 'react-router-dom';
import styled from 'styled-components';
import useHarkState from '~/logic/state/hark';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import { JoinGroup } from '~/views/landscape/components/JoinGroup';
import { NewGroup } from '~/views/landscape/components/NewGroup';
import Groups from './components/Groups';
import ModalButton from './components/ModalButton';
import Tiles from './components/tiles';
import Tile from './components/tiles/tile';
import { Invite } from './components/Invite';
import './css/custom.css';
import { Box, Icon, Row, Text, Button } from "@tlon/indigo-react";
import React, { ReactElement } from "react";
import { Helmet } from "react-helmet";
import { Route, useHistory } from "react-router-dom";
import styled from "styled-components";
import useHarkState from "~/logic/state/hark";
import useSettingsState, { selectCalmState } from "~/logic/state/settings";
import { JoinGroup } from "~/views/landscape/components/JoinGroup";
import { NewGroup } from "~/views/landscape/components/NewGroup";
import Groups from "./components/Groups";
import ModalButton from "./components/ModalButton";
import Tiles from "./components/tiles";
import Tile from "./components/tiles/tile";
import { Invite } from "./components/Invite";
import "./css/custom.css";
import { join } from "@urbit/api/groups";
import { joinGraph } from "@urbit/api/graph";
import airlock from "~/logic/api";
import { Join, JoinRoute } from "~/views/landscape/components/Join";
const ScrollbarLessBox = styled(Box)`
scrollbar-width: none !important;
@ -28,73 +32,85 @@ interface LaunchAppProps {
}
export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
const notificationsCount = useHarkState(state => state.notificationsCount);
const notificationsCount = useHarkState((state) => state.notificationsCount);
const calmState = useSettingsState(selectCalmState);
const { hideUtilities, hideGroups } = calmState;
const history = useHistory();
return (
<>
<Helmet defer={false}>
<title>{ notificationsCount ? `(${String(notificationsCount) }) `: '' }Groups</title>
<title>
{notificationsCount ? `(${String(notificationsCount)}) ` : ""}Groups
</title>
</Helmet>
<Route path="/invites/:app/:uid">
<Invite />
<Route path="/join/:ship/:name">
<JoinRoute modal />
</Route>
<ScrollbarLessBox height='100%' overflowY='scroll' display="flex" flexDirection="column">
<ScrollbarLessBox
height="100%"
overflowY="scroll"
display="flex"
flexDirection="column"
>
<Box
mx={2}
display='grid'
gridTemplateColumns='repeat(auto-fill, minmax(128px, 1fr))'
display="grid"
gridTemplateColumns="repeat(auto-fill, minmax(128px, 1fr))"
gridGap={3}
p={2}
pt={0}
>
{!hideUtilities && <>
<Tile
bg="white"
color="scales.black20"
to="/~landscape/home"
p={0}
>
<Box
p={2}
height='100%'
width='100%'
bg='scales.black20'
border={1}
borderColor="lightGray"
>
<Row alignItems='center'>
<Icon
color="black"
icon="Home"
/>
<Text ml={2} mt='1px' color="black">My Channels</Text>
</Row>
</Box>
</Tile>
<Tiles />
<ModalButton
icon="Plus"
bg="washedGray"
color="black"
text="New Group"
style={{ gridColumnStart: 1 }}
>
<NewGroup />
</ModalButton>
<ModalButton
icon="BootNode"
bg="washedGray"
color="black"
text="Join Group"
>
{dismiss => <JoinGroup dismiss={dismiss} />}
</ModalButton>
</>}
{!hideGroups &&
(<Groups />)
}
{!hideUtilities && (
<>
<Tile
bg="white"
color="scales.black20"
to="/~landscape/home"
p={0}
>
<Box
p={2}
height="100%"
width="100%"
bg="scales.black20"
border={1}
borderColor="lightGray"
>
<Row alignItems="center">
<Icon color="black" icon="Home" />
<Text ml={2} mt="1px" color="black">
My Channels
</Text>
</Row>
</Box>
</Tile>
<Tiles />
<ModalButton
icon="Plus"
bg="washedGray"
color="black"
text="New Group"
style={{ gridColumnStart: 1 }}
>
<NewGroup />
</ModalButton>
<Button
bg="washedGray"
color="black"
border={0}
p={0}
borderRadius={2}
onClick={() => history.push({ search: "?join-kind=group" })}
>
<Row gapX="2" p={2} height="100%" width="100%" alignItems="center">
<Icon icon="BootNode" />
<Text fontWeight="medium" whiteSpace="nowrap">Join Group</Text>
</Row>
</Button>
</>
)}
{!hideGroups && <Groups />}
</Box>
</ScrollbarLessBox>
</>

View File

@ -1,16 +1,27 @@
import { Box, Col, Text } from '@tlon/indigo-react';
import { Association, Associations, Unreads } from '@urbit/api';
import f from 'lodash/fp';
import React from 'react';
import { getNotificationCount } from '~/logic/lib/hark';
import { alphabeticalOrder } from '~/logic/lib/util';
import useGroupState from '~/logic/state/group';
import useHarkState, { selHarkGraph } from '~/logic/state/hark';
import useMetadataState from '~/logic/state/metadata';
import { Box, Col, Text } from "@tlon/indigo-react";
import {
Association,
Associations,
resourceAsPath,
resourceFromPath,
Unreads,
} from "@urbit/api";
import f from "lodash/fp";
import _ from "lodash";
import React from "react";
import { useHistory } from "react-router-dom";
import { getNotificationCount } from "~/logic/lib/hark";
import { alphabeticalOrder } from "~/logic/lib/util";
import useGroupState from "~/logic/state/group";
import useHarkState, { selHarkGraph } from "~/logic/state/hark";
import useInviteState from "~/logic/state/invite";
import useMetadataState, { usePreview } from "~/logic/state/metadata";
import useSettingsState, {
selectCalmState
} from '~/logic/state/settings';
import Tile from '../components/tiles/tile';
selectCalmState,
SettingsState,
} from "~/logic/state/settings";
import Tile from "../components/tiles/tile";
import { useQuery } from "~/logic/lib/useQuery";
const sortGroupsAlph = (a: Association, b: Association) =>
alphabeticalOrder(a.metadata.title, b.metadata.title);
@ -25,7 +36,7 @@ const getGraphUnreads = (associations: Associations) => {
return (path: string) =>
f.flow(
f.pickBy((a: Association) => a.group === path),
f.map('resource'),
f.map("resource"),
f.map(selUnread),
f.reduce(f.add, 0)
)(associations.graph);
@ -37,18 +48,18 @@ const getGraphNotifications = (
) => (path: string) =>
f.flow(
f.pickBy((a: Association) => a.group === path),
f.map('resource'),
f.map(rid => getNotificationCount(unreads, rid)),
f.map("resource"),
f.map((rid) => getNotificationCount(unreads, rid)),
f.reduce(f.add, 0)
)(associations.graph);
export default function Groups(props: Parameters<typeof Box>[0]) {
const unreads = useHarkState(state => state.unreads);
const groupState = useGroupState(state => state.groups);
const associations = useMetadataState(state => state.associations);
const unreads = useHarkState((state) => state.unreads);
const groupState = useGroupState((state) => state.groups);
const associations = useMetadataState((state) => state.associations);
const groups = Object.values(associations?.groups || {})
.filter(e => e?.group in groupState)
.filter((e) => e?.group in groupState)
.sort(sortGroupsAlph);
const graphUnreads = getGraphUnreads(associations || ({} as Associations));
const graphNotifications = getGraphNotifications(
@ -56,6 +67,20 @@ export default function Groups(props: Parameters<typeof Box>[0]) {
unreads
);
const joining = useGroupState((s) =>
_.omit(
_.pickBy(s.pendingJoin || {}, req => req.app === 'groups'),
groups.map((g) => g.group)
)
);
const invites = useInviteState(
(s) =>
Object.values(s.invites?.["groups"] || {}).map((inv) =>
resourceAsPath(inv.resource)
) || []
);
const pending = _.union(invites, Object.keys(joining));
return (
<>
{groups.map((group, index) => {
@ -73,10 +98,60 @@ export default function Groups(props: Parameters<typeof Box>[0]) {
/>
);
})}
{pending.map((group, idx) => (
<PendingGroup
key={group}
path={group}
first={idx === 0 && groups.length === 0}
/>
))}
</>
);
}
interface PendingGroupProps {
path: string;
first?: boolean;
}
function PendingGroup(props: PendingGroupProps) {
const { path, first } = props;
const history = useHistory();
const { preview, error } = usePreview(path);
const title = preview?.metadata?.title || path;
const { toQuery } = useQuery();
const onClick = () => {
const { ship, name } = resourceFromPath(path);
history.push(toQuery({ "join-kind": "groups", "join-path": path }));
};
const joining = useGroupState((s) => s.pendingJoin[path]?.progress);
return (
<Tile gridColumnStart={first ? 1 : undefined}>
<Col
onClick={onClick}
width="100%"
height="100%"
justifyContent="space-between"
>
<Box>
<Text gray>{title}</Text>
</Box>
<Box>
{!joining ? (
<Text color="blue">Invited</Text>
) : joining !== "done" ? (
<Text gray>Joining...</Text>
) : (
<Text color="blue">Recently joined</Text>
)}
</Box>
</Col>
</Tile>
);
}
interface GroupProps {
path: string;
title: string;
@ -87,6 +162,7 @@ interface GroupProps {
function Group(props: GroupProps) {
const { path, title, unreads, updates, first = false } = props;
const { hideUnreads } = useSettingsState(selectCalmState);
const request = useGroupState((s) => s.pendingJoin[path]);
return (
<Tile
position="relative"
@ -97,9 +173,10 @@ function Group(props: GroupProps) {
<Text>{title}</Text>
{!hideUnreads && (
<Col>
{!!request ? <Text color="blue">New group</Text> : null}
{updates > 0 && (
<Text mt={1} color="blue">
{updates} update{updates !== 1 && 's'}{' '}
{updates} update{updates !== 1 && "s"}{" "}
</Text>
)}
{unreads > 0 && <Text color="lightGray">{unreads}</Text>}

View File

@ -15,6 +15,7 @@ import { useShortcut } from '~/logic/state/settings';
import Landscape from '~/views/landscape/index';
import GraphApp from '../../apps/graph/App';
import { getNotificationRedirect } from '~/logic/lib/notificationRedirects';
import {JoinRoute} from './Join';
export const Container = styled(Box)`
flex-grow: 1;
@ -68,11 +69,11 @@ export const Content = (props) => {
return (
<Container>
<JoinRoute />
<Switch>
<Route
exact
path={['/', '/invites/:app/:uid']}
render={p => (
path="/" render={p => (
<LaunchApp
location={p.location}
match={p.match}