mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-05 13:55:54 +03:00
j
This commit is contained in:
parent
8acabefcc5
commit
04a7d0075d
@ -9,7 +9,7 @@
|
||||
"@reach/menu-button": "^0.10.5",
|
||||
"@reach/tabs": "^0.10.5",
|
||||
"@tlon/indigo-light": "^1.0.3",
|
||||
"@tlon/indigo-react": "1.2.6",
|
||||
"@tlon/indigo-react": "urbit/indigo-react#lf/1.2.8",
|
||||
"aws-sdk": "^2.726.0",
|
||||
"classnames": "^2.2.6",
|
||||
"codemirror": "^5.55.0",
|
||||
|
@ -6,16 +6,20 @@ import React, {
|
||||
useCallback,
|
||||
} from "react";
|
||||
import styled from "styled-components";
|
||||
import _ from 'lodash';
|
||||
import { Box, Col } from "@tlon/indigo-react";
|
||||
import { useOutsideClick } from "~/logic/lib/useOutsideClick";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Portal } from "./Portal";
|
||||
|
||||
type AlignY = "top" | "bottom";
|
||||
type AlignX = "left" | "right";
|
||||
|
||||
interface DropdownProps {
|
||||
children: ReactNode;
|
||||
options: ReactNode;
|
||||
alignY: "top" | "bottom";
|
||||
alignX: "left" | "right";
|
||||
alignY: AlignY | AlignY[];
|
||||
alignX: AlignX | AlignX[];
|
||||
width?: string;
|
||||
}
|
||||
|
||||
@ -31,7 +35,7 @@ const DropdownOptions = styled(Box)`
|
||||
`;
|
||||
|
||||
export function Dropdown(props: DropdownProps) {
|
||||
const { children, options, alignX, alignY } = props;
|
||||
const { children, options } = props;
|
||||
const dropdownRef = useRef<HTMLElement>(null);
|
||||
const anchorRef = useRef<HTMLElement>(null);
|
||||
const { pathname } = useLocation();
|
||||
@ -47,10 +51,34 @@ export function Dropdown(props: DropdownProps) {
|
||||
bottom: document.documentElement.clientHeight - rect.bottom,
|
||||
right: document.documentElement.clientWidth - rect.right,
|
||||
};
|
||||
const alignX = _.isArray(props.alignX) ? props.alignX : [props.alignX];
|
||||
const alignY = _.isArray(props.alignY) ? props.alignY : [props.alignY];
|
||||
|
||||
let newCoords = {
|
||||
[alignX]: `${bounds[alignX]}px`,
|
||||
[alignY]: `${bounds[alignY]}px`,
|
||||
..._.reduce(
|
||||
alignX,
|
||||
(acc, a, idx) => ({
|
||||
...acc,
|
||||
[a]: _.zipWith(
|
||||
[...Array(idx), `${bounds[a]}px`],
|
||||
acc[a] || [],
|
||||
(a, b) => a || b || null
|
||||
),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
..._.reduce(
|
||||
alignY,
|
||||
(acc, a, idx) => ({
|
||||
...acc,
|
||||
[a]: _.zipWith(
|
||||
[...Array(idx), `${bounds[a]}px`],
|
||||
acc[a] || [],
|
||||
(a, b) => a || b || null
|
||||
),
|
||||
}),
|
||||
{}
|
||||
)
|
||||
};
|
||||
setCoords(newCoords);
|
||||
}
|
||||
|
@ -40,9 +40,10 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
const relativePath = (path: string) => baseUrl + path;
|
||||
const groupPath = getGroupFromWorkspace(workspace);
|
||||
|
||||
const groupContacts = groupPath && contacts[groupPath] || undefined;
|
||||
const groupAssociation = groupPath && associations.contacts[groupPath] || undefined;
|
||||
const group = groupPath && groups[groupPath] || undefined;
|
||||
const groupContacts = (groupPath && contacts[groupPath]) || undefined;
|
||||
const groupAssociation =
|
||||
(groupPath && associations.contacts[groupPath]) || undefined;
|
||||
const group = (groupPath && groups[groupPath]) || undefined;
|
||||
const [recentGroups, setRecentGroups] = useLocalStorageState<string[]>(
|
||||
"recent-groups",
|
||||
[]
|
||||
@ -126,13 +127,14 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={relativePath("/join/:app/:host/:name")}
|
||||
path={relativePath("/join/:app/(ship)?/:host/:name")}
|
||||
render={(routeProps) => {
|
||||
const { app, host, name } = routeProps.match.params;
|
||||
const appName = app as AppName;
|
||||
const appPath = `/${host}/${name}`;
|
||||
const association = associations[appName][appPath];
|
||||
const resourceUrl = `${baseUrl}/join/${app}/${host}/${name}`;
|
||||
const isShip = app === "link";
|
||||
const appPath = `${isShip ? '/ship/' : '/'}${host}/${name}`;
|
||||
const association = isShip ? associations.graph[appPath] : associations[appName][appPath];
|
||||
const resourceUrl = `${baseUrl}/join/${app}${appPath}`;
|
||||
return (
|
||||
<Skeleton
|
||||
recentGroups={recentGroups}
|
||||
@ -141,7 +143,13 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
{...props}
|
||||
baseUrl={baseUrl}
|
||||
>
|
||||
<UnjoinedResource association={association} />
|
||||
<UnjoinedResource
|
||||
notebooks={props.notebooks}
|
||||
inbox={props.inbox}
|
||||
baseUrl={baseUrl}
|
||||
api={api}
|
||||
association={association}
|
||||
/>
|
||||
{popovers(routeProps, resourceUrl)}
|
||||
</Skeleton>
|
||||
);
|
||||
|
@ -144,7 +144,7 @@ export function ShipSearch(props: InviteSearchProps) {
|
||||
borderColor="washedGrey"
|
||||
color="black"
|
||||
fontSize={0}
|
||||
mb={2}
|
||||
mt={2}
|
||||
mr={2}
|
||||
>
|
||||
<Text fontFamily="mono">{cite(s)}</Text>
|
||||
|
@ -1,201 +0,0 @@
|
||||
import React, { ReactNode, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Row,
|
||||
Text,
|
||||
Icon,
|
||||
MenuItem as _MenuItem,
|
||||
IconButton,
|
||||
Button,
|
||||
} from "@tlon/indigo-react";
|
||||
import { capitalize } from "lodash";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { SidebarInvite } from "./SidebarInvite";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { AppName } from "~/types/noun";
|
||||
import { alphabeticalOrder } from "~/logic/lib/util";
|
||||
import { GroupSwitcher } from "~/views/apps/groups/components/GroupSwitcher";
|
||||
import { AppInvites, Associations, AppAssociations, Workspace } from "~/types";
|
||||
import { SidebarItem } from "./SidebarItem";
|
||||
import {
|
||||
SidebarListHeader,
|
||||
SidebarListConfig,
|
||||
SidebarSort,
|
||||
} from "./SidebarListHeader";
|
||||
import { useLocalStorageState } from "~/logic/lib/useLocalStorageState";
|
||||
import {getGroupFromWorkspace} from "~/logic/lib/workspace";
|
||||
|
||||
interface SidebarAppConfig {
|
||||
name: string;
|
||||
makeRouteForResource: (appPath: string) => string;
|
||||
getStatus: (appPath: string) => SidebarItemStatus | undefined;
|
||||
}
|
||||
|
||||
export type SidebarAppConfigs = { [a in AppName]: SidebarAppConfig };
|
||||
|
||||
export type SidebarItemStatus =
|
||||
| "unread"
|
||||
| "mention"
|
||||
| "unsubscribed"
|
||||
| "disconnected"
|
||||
| "loading";
|
||||
|
||||
function sidebarSort(
|
||||
associations: AppAssociations
|
||||
): Record<SidebarSort, (a: string, b: string) => number> {
|
||||
const alphabetical = (a: string, b: string) => {
|
||||
const aAssoc = associations[a];
|
||||
const bAssoc = associations[b];
|
||||
const aTitle = aAssoc?.metadata?.title || b;
|
||||
const bTitle = bAssoc?.metadata?.title || b;
|
||||
|
||||
return alphabeticalOrder(aTitle, bTitle);
|
||||
};
|
||||
|
||||
return {
|
||||
asc: alphabetical,
|
||||
desc: (a, b) => alphabetical(b, a),
|
||||
};
|
||||
}
|
||||
|
||||
const apps = ["chat", "publish", "link"];
|
||||
|
||||
function SidebarItems(props: {
|
||||
apps: SidebarAppConfigs;
|
||||
config: SidebarListConfig;
|
||||
associations: Associations;
|
||||
group?: string;
|
||||
selected?: string;
|
||||
}) {
|
||||
const { selected, group, config } = props;
|
||||
const associations = {
|
||||
...props.associations.chat,
|
||||
...props.associations.publish,
|
||||
...props.associations.link,
|
||||
...props.associations.graph,
|
||||
};
|
||||
|
||||
const ordered = Object.keys(associations)
|
||||
.filter((a) => {
|
||||
const assoc = associations[a];
|
||||
console.log(a);
|
||||
return group
|
||||
? assoc["group-path"] === group
|
||||
: !(assoc["group-path"] in props.associations.contacts);
|
||||
})
|
||||
.sort(sidebarSort(associations)[config.sortBy]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ordered.map((path) => {
|
||||
const assoc = associations[path];
|
||||
return (
|
||||
<SidebarItem
|
||||
key={path}
|
||||
path={path}
|
||||
selected={path === selected}
|
||||
association={assoc}
|
||||
apps={props.apps}
|
||||
hideUnjoined={config.hideUnjoined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface SidebarProps {
|
||||
children: ReactNode;
|
||||
recentGroups: string[];
|
||||
invites: AppInvites;
|
||||
api: GlobalApi;
|
||||
associations: Associations;
|
||||
selected?: string;
|
||||
selectedGroup?: string;
|
||||
includeUnmanaged?: boolean;
|
||||
apps: SidebarAppConfigs;
|
||||
baseUrl: string;
|
||||
mobileHide?: boolean;
|
||||
workspace: Workspace;
|
||||
}
|
||||
|
||||
export function Sidebar(props: SidebarProps) {
|
||||
const { invites, api, associations, selected, apps, workspace } = props;
|
||||
const groupPath = getGroupFromWorkspace(workspace)
|
||||
const groupAsssociation =
|
||||
groupPath && associations.contacts[groupPath];
|
||||
const display = props.mobileHide ? ["none", "flex"] : "flex";
|
||||
if (!associations) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [config, setConfig] = useLocalStorageState<SidebarListConfig>(
|
||||
`group-config:${groupPath || "home"}`,
|
||||
{
|
||||
sortBy: "asc",
|
||||
hideUnjoined: false,
|
||||
}
|
||||
);
|
||||
return (
|
||||
<Box
|
||||
display={display}
|
||||
flexDirection="column"
|
||||
width="100%"
|
||||
height="100%"
|
||||
gridRow="1/2"
|
||||
gridColumn="1/2"
|
||||
borderRight={1}
|
||||
borderRightColor="washedGray"
|
||||
overflowY="auto"
|
||||
fontSize={0}
|
||||
bg="white"
|
||||
position="relative"
|
||||
>
|
||||
<GroupSwitcher
|
||||
associations={associations}
|
||||
recentGroups={props.recentGroups}
|
||||
baseUrl={props.baseUrl}
|
||||
workspace={props.workspace}
|
||||
/>
|
||||
{Object.keys(invites).map((appPath) =>
|
||||
Object.keys(invites[appPath]).map((uid) => (
|
||||
<SidebarInvite
|
||||
key={uid}
|
||||
invite={props.invites[uid]}
|
||||
onAccept={() => props.api.invite.accept(appPath, uid)}
|
||||
onDecline={() => props.api.invite.decline(appPath, uid)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
<SidebarListHeader initialValues={config} handleSubmit={setConfig} />
|
||||
<SidebarItems
|
||||
config={config}
|
||||
associations={associations}
|
||||
selected={selected}
|
||||
group={groupPath}
|
||||
apps={props.apps}
|
||||
/>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
position="sticky"
|
||||
bottom="8px"
|
||||
width="100%"
|
||||
my={2}
|
||||
>
|
||||
<Link to={`/~groups${props.selectedGroup}/new`}>
|
||||
<Box
|
||||
bg="white"
|
||||
p={2}
|
||||
borderRadius={1}
|
||||
border={1}
|
||||
borderColor="lightGray"
|
||||
>
|
||||
+ New Channel
|
||||
</Box>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Invite } from '~/types/invite-update';
|
||||
|
||||
export class SidebarInvite extends Component<{invite: Invite, onAccept: Function, onDecline: Function}, {}> {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
return (
|
||||
<div className='w-100 bg-white bg-gray0-d pa4 bb b--gray4 b--gray1-d z-5' style={{position: 'sticky', top: 0}}>
|
||||
<div className='w-100 v-mid'>
|
||||
<p className="dib f8 mono gray4-d">
|
||||
{props.invite.text ? props.invite.text : props.invite.path}
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
className="dib pointer pa2 f9 bg-green2 white mt4"
|
||||
onClick={this.props.onAccept.bind(this)}
|
||||
>
|
||||
Accept Invite
|
||||
</a>
|
||||
<a
|
||||
className="dib pointer ml4 pa2 f9 bg-black bg-gray0-d white mt4"
|
||||
onClick={this.props.onDecline.bind(this)}
|
||||
>
|
||||
Decline
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SidebarInvite;
|
@ -1,106 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { Icon, Row, Box, Text } from "@tlon/indigo-react";
|
||||
|
||||
import { Association } from "~/types/metadata-update";
|
||||
|
||||
import { SidebarAppConfigs, SidebarItemStatus } from "./Sidebar";
|
||||
import { HoverBoxLink } from "./HoverBox";
|
||||
import {Groups} from "~/types";
|
||||
|
||||
function SidebarItemIndicator(props: { status?: SidebarItemStatus }) {
|
||||
switch (props.status) {
|
||||
case "disconnected":
|
||||
return <Icon ml={2} fill="red" icon="X" />;
|
||||
case "unsubscribed":
|
||||
return <Icon ml={2} icon="Circle" fill="gray" />;
|
||||
case "mention":
|
||||
return <Icon ml={2} icon="Circle" />;
|
||||
case "loading":
|
||||
return <Icon ml={2} icon="Bullet" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const getAppIcon = (app: string, module: string) => {
|
||||
if (app === "graph") {
|
||||
if (module === "link") {
|
||||
return "Links";
|
||||
}
|
||||
return _.capitalize(module);
|
||||
}
|
||||
return _.capitalize(app);
|
||||
};
|
||||
|
||||
export function SidebarItem(props: {
|
||||
hideUnjoined: boolean;
|
||||
association: Association;
|
||||
groups: Groups;
|
||||
path: string;
|
||||
selected: boolean;
|
||||
apps: SidebarAppConfigs;
|
||||
}) {
|
||||
const { association, path, selected, apps, groups } = props;
|
||||
const title = association?.metadata?.title || path;
|
||||
const appName = association?.["app-name"];
|
||||
const module = association?.metadata?.module || appName;
|
||||
const appPath = association?.["app-path"];
|
||||
const groupPath = association?.["group-path"];
|
||||
const app = apps[module];
|
||||
const isUnmanaged = groups?.[groupPath]?.hidden || false;
|
||||
if (!app) {
|
||||
return null;
|
||||
}
|
||||
const status = app.getStatus(path);
|
||||
const hasUnread = status === "unread" || status === "mention";
|
||||
|
||||
const isSynced = status !== "unsubscribed";
|
||||
|
||||
const baseUrl = isUnmanaged ? `/~groups/home` : `/~groups${groupPath}`;
|
||||
|
||||
const to = isSynced
|
||||
? `${baseUrl}/resource/${module}${appPath}`
|
||||
: `${baseUrl}/join/${module}${appPath}`;
|
||||
|
||||
const color = selected ? "black" : isSynced ? "gray" : "lightGray";
|
||||
|
||||
if (props.hideUnjoined && !isSynced) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HoverBoxLink
|
||||
to={to}
|
||||
bg="white"
|
||||
bgActive="washedGray"
|
||||
width="100%"
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
py={1}
|
||||
pl={4}
|
||||
pr={2}
|
||||
selected={selected}
|
||||
>
|
||||
<Row alignItems="center">
|
||||
<Icon
|
||||
display="block"
|
||||
fill="rgba(0,0,0,0)"
|
||||
stroke={color}
|
||||
icon={getAppIcon(appName, module)}
|
||||
/>
|
||||
<Box
|
||||
flexShrink={2}
|
||||
ml={2}
|
||||
lineHeight="1.33"
|
||||
fontWeight={hasUnread ? "600" : "400"}
|
||||
>
|
||||
<Text color={selected || isSynced ? "black" : "lightGray"}>
|
||||
{title}
|
||||
</Text>
|
||||
</Box>
|
||||
</Row>
|
||||
</HoverBoxLink>
|
||||
);
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
import React, { useCallback } from "react";
|
||||
import * as Yup from "yup";
|
||||
import {
|
||||
Row,
|
||||
Box,
|
||||
Icon,
|
||||
ManagedRadioButtonField as Radio,
|
||||
ManagedCheckboxField as Checkbox,
|
||||
Col,
|
||||
} from "@tlon/indigo-react";
|
||||
import { FormikOnBlur } from "./FormikOnBlur";
|
||||
import { Dropdown } from "./Dropdown";
|
||||
import { FormikHelpers } from "formik";
|
||||
|
||||
export type SidebarSort = "asc" | "desc";
|
||||
|
||||
export interface SidebarListConfig {
|
||||
sortBy: SidebarSort;
|
||||
hideUnjoined: boolean;
|
||||
}
|
||||
|
||||
export function SidebarListHeader(props: {
|
||||
initialValues: SidebarListConfig;
|
||||
handleSubmit: (c: SidebarListConfig) => void;
|
||||
}) {
|
||||
const onSubmit = useCallback(
|
||||
(values: SidebarListConfig, actions: FormikHelpers<SidebarListConfig>) => {
|
||||
props.handleSubmit(values);
|
||||
actions.setSubmitting(false);
|
||||
},
|
||||
[props.handleSubmit]
|
||||
);
|
||||
|
||||
return (
|
||||
<Row alignItems="center" justifyContent="space-between" py={2} px={3}>
|
||||
<Box>
|
||||
{props.initialValues.hideUnjoined ? "Joined Channels" : "All Channels"}
|
||||
</Box>
|
||||
<Dropdown
|
||||
width="200px"
|
||||
alignY="top"
|
||||
options={
|
||||
<FormikOnBlur initialValues={props.initialValues} onSubmit={onSubmit}>
|
||||
<Col>
|
||||
<Col borderBottom={1} borderBottomColor="washedGray" p={2}>
|
||||
<Box color="gray" mt={2} mb={4}>
|
||||
Sort Order
|
||||
</Box>
|
||||
<Radio label="A -> Z" id="asc" name="sortBy" />
|
||||
<Radio label="Z -> A" id="desc" name="sortBy" />
|
||||
</Col>
|
||||
<Col px={2}>
|
||||
<Checkbox
|
||||
mt={4}
|
||||
id="hideUnjoined"
|
||||
label="Hide Unsubscribed Channels"
|
||||
/>
|
||||
</Col>
|
||||
</Col>
|
||||
</FormikOnBlur>
|
||||
}
|
||||
>
|
||||
<Icon stroke="gray" icon="Circle" />
|
||||
</Dropdown>
|
||||
</Row>
|
||||
);
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import React, { ReactNode, useEffect } from "react";
|
||||
import React, { ReactNode, useEffect, useMemo } from "react";
|
||||
import { Box, Text } from "@tlon/indigo-react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { Sidebar } from "./Sidebar/Sidebar";
|
||||
import { ChatHookUpdate } from "~/types/chat-hook-update";
|
||||
import { Inbox } from "~/types/chat-update";
|
||||
import { Associations } from "~/types/metadata-update";
|
||||
@ -12,14 +12,18 @@ import { Path, AppName } from "~/types/noun";
|
||||
import { LinkCollections } from "~/types/link-update";
|
||||
import styled from "styled-components";
|
||||
import GlobalSubscription from "~/logic/subscription/global";
|
||||
import {Workspace} from "~/types";
|
||||
import { Workspace, Groups, Graphs } from "~/types";
|
||||
import { useChat, usePublish, useLinks } from "./Sidebar/Apps";
|
||||
import { Body } from "./Body";
|
||||
|
||||
interface SkeletonProps {
|
||||
children: ReactNode;
|
||||
recentGroups: string[];
|
||||
groups: Groups;
|
||||
associations: Associations;
|
||||
chatSynced: ChatHookUpdate | null;
|
||||
graphKeys: Set<string>;
|
||||
graphs: Graphs;
|
||||
linkListening: Set<Path>;
|
||||
links: LinkCollections;
|
||||
notebooks: Notebooks;
|
||||
@ -32,99 +36,55 @@ interface SkeletonProps {
|
||||
subscription: GlobalSubscription;
|
||||
includeUnmanaged: boolean;
|
||||
workspace: Workspace;
|
||||
hideSidebar?: boolean;
|
||||
}
|
||||
|
||||
export function Skeleton(props: SkeletonProps) {
|
||||
const chatConfig = {
|
||||
name: "chat",
|
||||
getStatus: (s: string) => {
|
||||
if (!(s in (props.chatSynced || {}))) {
|
||||
return "unsubscribed";
|
||||
}
|
||||
const mailbox = props?.inbox?.[s];
|
||||
if(!mailbox) {
|
||||
return undefined;
|
||||
}
|
||||
const { config } = mailbox;
|
||||
if (config?.read !== config?.length) {
|
||||
return "unread";
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
const publishConfig = {
|
||||
name: "chat",
|
||||
getStatus: (s: string) => {
|
||||
const [, host, name] = s.split("/");
|
||||
const notebook = props.notebooks?.[host]?.[name];
|
||||
if (!notebook) {
|
||||
return "unsubscribed";
|
||||
}
|
||||
if (notebook["num-unread"]) {
|
||||
return "unread";
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
const linkConfig = {
|
||||
name: "link",
|
||||
getStatus: (s: string) => {
|
||||
const [, , host, name] = s.split("/");
|
||||
const graphKey = `${host.slice(1)}/${name}`;
|
||||
|
||||
if (!props.graphKeys.has(graphKey)) {
|
||||
return "unsubscribed";
|
||||
}
|
||||
const link = props.links[s];
|
||||
if (!link) {
|
||||
return undefined;
|
||||
}
|
||||
if (link.unseenCount > 0) {
|
||||
return "unread";
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
const config = {
|
||||
publish: publishConfig,
|
||||
link: linkConfig,
|
||||
chat: chatConfig,
|
||||
};
|
||||
const chatConfig = useChat(props.inbox, props.chatSynced);
|
||||
const publishConfig = usePublish(props.notebooks);
|
||||
const linkConfig = useLinks(props.graphKeys, props.graphs);
|
||||
const config = useMemo(
|
||||
() => ({
|
||||
publish: publishConfig,
|
||||
link: linkConfig,
|
||||
chat: chatConfig,
|
||||
}),
|
||||
[publishConfig, linkConfig, chatConfig]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
props.api.publish.fetchNotebooks();
|
||||
props.subscription.startApp("chat");
|
||||
props.subscription.startApp("publish");
|
||||
props.subscription.startApp("graph");
|
||||
|
||||
return () => {
|
||||
props.subscription.stopApp("chat");
|
||||
props.subscription.stopApp("publish");
|
||||
props.subscription.stopApp("graph");
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box fontSize={0} px={[0, 3]} pb={[0, 3]} height="100%" width="100%">
|
||||
<Box
|
||||
bg="white"
|
||||
height="100%"
|
||||
width="100%"
|
||||
display="grid"
|
||||
borderRadius={1}
|
||||
border={[0, 1]}
|
||||
borderColor={["washedGray", "washedGray"]}
|
||||
gridTemplateColumns={["1fr", "250px 1fr"]}
|
||||
gridTemplateRows="1fr"
|
||||
>
|
||||
<Body
|
||||
display="grid"
|
||||
gridTemplateColumns={["1fr", "250px 1fr"]}
|
||||
gridTemplateRows="1fr"
|
||||
>
|
||||
{!props.hideSidebar && (
|
||||
<Sidebar
|
||||
recentGroups={props.recentGroups}
|
||||
selected={props.selected}
|
||||
selectedApp={props.selectedApp}
|
||||
associations={props.associations}
|
||||
invites={{}}
|
||||
apps={config}
|
||||
baseUrl={props.baseUrl}
|
||||
includeUnmanaged={!props.selectedGroup}
|
||||
groups={props.groups}
|
||||
mobileHide={props.mobileHide}
|
||||
workspace={props.workspace}
|
||||
></Sidebar>
|
||||
{props.children}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{props.children}
|
||||
</Body>
|
||||
);
|
||||
}
|
||||
|
@ -51,17 +51,6 @@ const StatusBar = (props) => {
|
||||
{metaKey}/
|
||||
</Text>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem
|
||||
onClick={() => props.history.push('/~groups')}
|
||||
badge={Object.keys(invites).length > 0}>
|
||||
<img
|
||||
className='invert-d v-mid'
|
||||
src='/~landscape/img/groups.png'
|
||||
height='15'
|
||||
width='15'
|
||||
/>
|
||||
<Text display={["none", "inline"]} ml={2}>Groups</Text>
|
||||
</StatusBarItem>
|
||||
<ReconnectButton
|
||||
connection={props.connection}
|
||||
subscription={props.subscription}
|
||||
|
@ -1,17 +1,52 @@
|
||||
import React from "react";
|
||||
import { Association } from "~/types/metadata-update";
|
||||
import { Box, Text, Button, Col, Center } from "@tlon/indigo-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useHistory } from "react-router-dom";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import {useWaitForProps} from "~/logic/lib/useWaitForProps";
|
||||
import { StatelessAsyncButton as AsyncButton } from './StatelessAsyncButton';
|
||||
import {Notebooks, Graphs, Inbox} from "~/types";
|
||||
|
||||
interface UnjoinedResourceProps {
|
||||
association: Association;
|
||||
api: GlobalApi;
|
||||
baseUrl: string;
|
||||
notebooks: Notebooks;
|
||||
graphs: Graphs;
|
||||
inbox: Inbox;
|
||||
}
|
||||
|
||||
export function UnjoinedResource(props: UnjoinedResourceProps) {
|
||||
const { api } = props;
|
||||
const history = useHistory();
|
||||
const appPath = props.association["app-path"];
|
||||
const appName = props.association["app-name"];
|
||||
const { title, description } = props.association.metadata;
|
||||
const to = `/~${appName}/join${appPath}`;
|
||||
const { title, description, module } = props.association.metadata;
|
||||
const waiter = useWaitForProps(props);
|
||||
const app = module || appName;
|
||||
|
||||
const onJoin = async () => {
|
||||
let ship, name;
|
||||
switch(app) {
|
||||
case 'link':
|
||||
[,,ship,name] = appPath.split('/');
|
||||
await api.graph.joinGraph(ship, name);
|
||||
break;
|
||||
case 'publish':
|
||||
[,ship,name] = appPath.split('/');
|
||||
await api.publish.subscribeNotebook(ship.slice(1), name);
|
||||
await waiter(p => !!p?.notebooks?.[ship]?.[name])
|
||||
break;
|
||||
case 'chat':
|
||||
[,ship,name] = appPath.split('/');
|
||||
await api.chat.join(ship, appPath, true);
|
||||
await waiter(p => !!p?.inbox?.[appPath])
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown resource type");
|
||||
}
|
||||
history.push(`${props.baseUrl}/resource/${app}${appPath}`);
|
||||
};
|
||||
return (
|
||||
<Center p={6}>
|
||||
<Col maxWidth="400px" p={4} border={1} borderColor="washedGray">
|
||||
@ -21,11 +56,9 @@ export function UnjoinedResource(props: UnjoinedResourceProps) {
|
||||
<Box mb={4}>
|
||||
<Text color="gray">{description}</Text>
|
||||
</Box>
|
||||
<Link to={to}>
|
||||
<Button mx="auto" border>
|
||||
<AsyncButton onClick={onJoin} mx="auto" border>
|
||||
Join Channel
|
||||
</Button>
|
||||
</Link>
|
||||
</AsyncButton>
|
||||
</Col>
|
||||
</Center>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user