mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-14 17:41:33 +03:00
interface: clean up publish
This commit is contained in:
parent
0436598a99
commit
ec657cb8f8
@ -5,7 +5,7 @@ import GlobalApi from "~/logic/api/global";
|
||||
import { StoreState } from "~/logic/store/type";
|
||||
import { Association } from "~/types";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { NotebookRoutes } from "./components/lib/NotebookRoutes";
|
||||
import { NotebookRoutes } from "./components/NotebookRoutes";
|
||||
|
||||
type PublishResourceProps = StoreState & {
|
||||
association: Association;
|
||||
|
@ -1,176 +0,0 @@
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import { Route, Switch, useLocation, withRouter } from "react-router-dom";
|
||||
import { Center, Text } from "@tlon/indigo-react";
|
||||
import _ from "lodash";
|
||||
import Helmet from "react-helmet";
|
||||
|
||||
import "./css/custom.css";
|
||||
|
||||
import { Skeleton } from "./components/skeleton";
|
||||
import { NewScreen } from "./components/lib/new";
|
||||
import { JoinScreen } from "./components/lib/Join";
|
||||
import { StoreState } from "~/logic/store/type";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import GlobalSubscription from "~/logic/subscription/global";
|
||||
import { NotebookRoutes } from "./components/lib/NotebookRoutes";
|
||||
|
||||
type PublishAppProps = StoreState & {
|
||||
api: GlobalApi;
|
||||
ship: string;
|
||||
subscription: GlobalSubscription;
|
||||
};
|
||||
|
||||
const RouterSkeleton = withRouter(Skeleton);
|
||||
|
||||
export default function PublishApp(props: PublishAppProps) {
|
||||
const unreadTotal = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
document.title =
|
||||
unreadTotal.current > 0
|
||||
? `(${unreadTotal.current}) OS1 - Publish`
|
||||
: "OS1 - Publish";
|
||||
}, [unreadTotal.current]);
|
||||
|
||||
useEffect(() => {
|
||||
props.subscription.startApp("publish");
|
||||
props.api.publish.fetchNotebooks();
|
||||
|
||||
return () => {
|
||||
props.subscription.stopApp("publish");
|
||||
};
|
||||
}, []);
|
||||
|
||||
const contacts = props.contacts ? props.contacts : {};
|
||||
|
||||
const notebooks = props.notebooks ? props.notebooks : {};
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
unreadTotal.current = _.chain(notebooks)
|
||||
.values()
|
||||
.map(_.values)
|
||||
.flatten() // flatten into array of notebooks
|
||||
.map("num-unread")
|
||||
.reduce((acc, count) => acc + count, 0)
|
||||
.value();
|
||||
|
||||
const {
|
||||
api,
|
||||
groups,
|
||||
sidebarShown,
|
||||
invites,
|
||||
associations,
|
||||
hideNicknames,
|
||||
hideAvatars,
|
||||
remoteContentPolicy
|
||||
} = props;
|
||||
|
||||
const active = location.pathname.endsWith("/~publish")
|
||||
? "sidebar"
|
||||
: "rightPanel";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet defer={false}>
|
||||
<title>{unreadTotal > 0 ? `(${unreadTotal}) ` : ""}OS1 - Publish</title>
|
||||
</Helmet>
|
||||
<Route
|
||||
path={[
|
||||
"/~publish/notebook/:ship/:notebook/note/:noteId",
|
||||
"/~publish/notebook/:ship/:notebook/*",
|
||||
"/~publish/notebook/:ship/:notebook",
|
||||
"/~publish",
|
||||
]}
|
||||
>
|
||||
<RouterSkeleton
|
||||
active={active}
|
||||
sidebarShown={sidebarShown}
|
||||
invites={invites}
|
||||
notebooks={notebooks}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
api={api}
|
||||
>
|
||||
<Switch>
|
||||
<Route exact path="/~publish">
|
||||
<Center width="100%" height="100%">
|
||||
<Text color="lightGray">
|
||||
Select or create a notebook to begin.
|
||||
</Text>
|
||||
</Center>
|
||||
</Route>
|
||||
<Route
|
||||
exact
|
||||
path="/~publish/new"
|
||||
render={(props) => {
|
||||
return (
|
||||
<NewScreen
|
||||
associations={associations}
|
||||
api={api}
|
||||
notebooks={notebooks}
|
||||
groups={groups}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/~publish/join/:ship/:notebook"
|
||||
render={(props) => {
|
||||
const ship = props.match.params.ship || "";
|
||||
const notebook = props.match.params.notebook || "";
|
||||
return (
|
||||
<JoinScreen
|
||||
notebooks={notebooks}
|
||||
ship={ship}
|
||||
book={notebook}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path="/~publish/notebook/:ship/:notebook"
|
||||
render={(props) => {
|
||||
const view = props.match.params.view
|
||||
? props.match.params.view
|
||||
: "posts";
|
||||
|
||||
const ship = props.match.params.ship || "";
|
||||
const book = props.match.params.notebook || "";
|
||||
|
||||
const bookGroupPath =
|
||||
notebooks?.[ship]?.[book]?.["subscribers-group-path"];
|
||||
|
||||
const notebookContacts =
|
||||
bookGroupPath in contacts ? contacts[bookGroupPath] : {};
|
||||
|
||||
const notebook = notebooks?.[ship]?.[book];
|
||||
return (
|
||||
<NotebookRoutes
|
||||
notebook={notebook}
|
||||
ship={ship}
|
||||
book={book}
|
||||
groups={groups}
|
||||
contacts={contacts}
|
||||
notebookContacts={notebookContacts}
|
||||
sidebarShown={sidebarShown}
|
||||
api={api}
|
||||
hideNicknames={hideNicknames}
|
||||
hideAvatars={hideAvatars}
|
||||
remoteContentPolicy={remoteContentPolicy}
|
||||
associations={associations}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
</RouterSkeleton>
|
||||
</Route>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import * as Yup from "yup";
|
||||
import { Formik, FormikHelpers, Form, useFormikContext } from "formik";
|
||||
import { AsyncButton } from "../../../../components/AsyncButton";
|
||||
import { AsyncButton } from "../../../components/AsyncButton";
|
||||
import { ManagedTextAreaField as TextArea } from "@tlon/indigo-react";
|
||||
|
||||
interface FormSchema {
|
@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { AsyncButton } from "../../../../components/AsyncButton";
|
||||
import { AsyncButton } from "../../../components/AsyncButton";
|
||||
import * as Yup from "yup";
|
||||
import {
|
||||
Box,
|
@ -6,7 +6,7 @@ import {
|
||||
Row,
|
||||
Col,
|
||||
} from "@tlon/indigo-react";
|
||||
import { AsyncButton } from "../../../../components/AsyncButton";
|
||||
import { AsyncButton } from "../../../components/AsyncButton";
|
||||
import { Formik, Form, FormikHelpers } from "formik";
|
||||
import { MarkdownField } from "./MarkdownField";
|
||||
|
@ -1,15 +1,16 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { RouteComponentProps, Link, Route, Switch } from "react-router-dom";
|
||||
import { Box, Text } from "@tlon/indigo-react";
|
||||
import GlobalApi from "../../../../api/global";
|
||||
import { PublishContent } from "./PublishContent";
|
||||
import { Notebook as INotebook } from "../../../../types/publish-update";
|
||||
import { Groups } from "../../../../types/group-update";
|
||||
import { Contacts, Rolodex } from "../../../../types/contact-update";
|
||||
import GlobalApi from "~/api/global";
|
||||
import { Notebook as INotebook } from "~/types/publish-update";
|
||||
import { Groups } from "~/types/group-update";
|
||||
import { Contacts, Rolodex } from "~/types/contact-update";
|
||||
import { LocalUpdateRemoteContentPolicy, Associations } from "~/types";
|
||||
|
||||
import Notebook from "./Notebook";
|
||||
import NewPost from "./new-post";
|
||||
import { NoteRoutes } from './NoteRoutes';
|
||||
import { LocalUpdateRemoteContentPolicy, Associations } from "~/types";
|
||||
|
||||
|
||||
interface NotebookRoutesProps {
|
||||
api: GlobalApi;
|
@ -1,45 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Box, Text } from "@tlon/indigo-react";
|
||||
import { NotebookItem } from './NotebookItem';
|
||||
|
||||
export class GroupItem extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
const association = props.association ? props.association : {};
|
||||
|
||||
let title = association['app-path'] ? association['app-path'] : 'Unmanaged Notebooks';
|
||||
if (association.metadata && association.metadata.title) {
|
||||
title = association.metadata.title !== ''
|
||||
? association.metadata.title : title;
|
||||
}
|
||||
|
||||
const groupedBooks = props.groupedBooks ? props.groupedBooks : [];
|
||||
const first = (props.index === 0) ? 'pt1' : 'pt6';
|
||||
|
||||
const notebookItems = groupedBooks.map((each, i) => {
|
||||
const unreads = props.notebooks[each]['num-unread'] || 0;
|
||||
let title = each.substr(1);
|
||||
if (props.notebooks[each].title) {
|
||||
title = (props.notebooks[each].title !== '')
|
||||
? props.notebooks[each].title : title;
|
||||
}
|
||||
return (
|
||||
<NotebookItem
|
||||
key={i}
|
||||
unreadCount={unreads}
|
||||
title={title}
|
||||
path={each}
|
||||
selected={(props.path === each)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Box className={first}>
|
||||
<Box fontSize={0} px={3} fontWeight="700" pb={2} color="lightGray">{title}</Box>
|
||||
{notebookItems}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default GroupItem;
|
@ -1,54 +0,0 @@
|
||||
import React, { useCallback, useState, useRef, useEffect } from "react";
|
||||
import { Col, Text, ErrorLabel } from "@tlon/indigo-react";
|
||||
import { Spinner } from "~/views/components/Spinner";
|
||||
import { Notebooks } from "~/types/publish-update";
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { deSig } from "~/logic/lib/util";
|
||||
|
||||
interface JoinScreenProps {
|
||||
api: any; // GlobalApi;
|
||||
ship: string;
|
||||
book: string;
|
||||
notebooks: Notebooks;
|
||||
}
|
||||
|
||||
export function JoinScreen(props: JoinScreenProps & RouteComponentProps) {
|
||||
const { book, ship, api } = props;
|
||||
const [error, setError] = useState(false);
|
||||
const joining = useRef(false);
|
||||
|
||||
const waiter = useWaitForProps(props);
|
||||
|
||||
const onJoin = useCallback(async () => {
|
||||
joining.current = true;
|
||||
|
||||
try {
|
||||
await api.publish.subscribeNotebook(deSig(ship), book);
|
||||
await waiter((p) => !!p.notebooks?.[ship]?.[book]);
|
||||
props.history.replace(`/~publish/notebook/${ship}/${book}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setError(true);
|
||||
} finally {
|
||||
joining.current = false;
|
||||
}
|
||||
}, [waiter, api, ship, book]);
|
||||
|
||||
useEffect(() => {
|
||||
if (joining.current) {
|
||||
return;
|
||||
}
|
||||
onJoin();
|
||||
}, [onJoin]);
|
||||
|
||||
return (
|
||||
<Col p={4}>
|
||||
<Text fontSize={1}>Joining Notebook</Text>
|
||||
<Spinner awaiting text="Joining..." />
|
||||
{error && <ErrorLabel>Unable to join notebook</ErrorLabel>}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
export default JoinScreen;
|
@ -1,52 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import { Box, Text } from "@tlon/indigo-react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { HoverBox } from "../../../../components/HoverBox";
|
||||
|
||||
interface NotebookItemProps {
|
||||
selected: boolean;
|
||||
title: string;
|
||||
path: string;
|
||||
unreadCount: number;
|
||||
}
|
||||
|
||||
function UnreadCount(props: { unread: number }) {
|
||||
return (
|
||||
<Box
|
||||
px={1}
|
||||
fontWeight="700"
|
||||
py={1}
|
||||
borderRadius={1}
|
||||
flexShrink='0'
|
||||
color="white"
|
||||
bg="lightGray"
|
||||
>
|
||||
{props.unread}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function NotebookItem(props: NotebookItemProps) {
|
||||
return (
|
||||
<Link to={"/~publish/notebook/" + props.path}>
|
||||
<HoverBox
|
||||
bg="white"
|
||||
bgActive="washedGray"
|
||||
selected={props.selected}
|
||||
width="100%"
|
||||
fontSize={0}
|
||||
px={3}
|
||||
py={1}
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box py='1' pr='1'>{props.title}</Box>
|
||||
{props.unreadCount > 0 && <UnreadCount unread={props.unreadCount} />}
|
||||
</HoverBox>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotebookItem;
|
@ -1,128 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import { Box, Text, Col } from "@tlon/indigo-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import SidebarInvite from "~/views/components/Sidebar/SidebarInvite";
|
||||
import { Welcome } from "./Welcome";
|
||||
import { GroupItem } from "./GroupItem";
|
||||
import { alphabetiseAssociations } from "~/logic/lib/util";
|
||||
|
||||
export function Sidebar(props: any) {
|
||||
const sidebarInvites = !(props.invites && props.invites["/publish"])
|
||||
? null
|
||||
: Object.keys(props.invites["/publish"]).map((uid) => {
|
||||
return (
|
||||
<SidebarInvite
|
||||
key={uid}
|
||||
invite={props.invites["/publish"][uid]}
|
||||
onAccept={() => props.api.invite.accept("/publish", uid)}
|
||||
onDecline={() => props.api.invite.decline("/publish", uid)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const associations =
|
||||
props.associations && "contacts" in props.associations
|
||||
? alphabetiseAssociations(props.associations.contacts)
|
||||
: {};
|
||||
|
||||
const notebooks = {};
|
||||
Object.keys(props.notebooks).map((host) => {
|
||||
Object.keys(props.notebooks[host]).map((notebook) => {
|
||||
const title = `${host}/${notebook}`;
|
||||
notebooks[title] = props.notebooks[host][notebook];
|
||||
});
|
||||
});
|
||||
|
||||
const groupedNotebooks = {};
|
||||
Object.keys(notebooks).map((book) => {
|
||||
const path = notebooks[book]["subscribers-group-path"]
|
||||
? notebooks[book]["subscribers-group-path"]
|
||||
: book;
|
||||
if (path in associations) {
|
||||
if (groupedNotebooks[path]) {
|
||||
const array = groupedNotebooks[path];
|
||||
array.push(book);
|
||||
groupedNotebooks[path] = array;
|
||||
} else {
|
||||
groupedNotebooks[path] = [book];
|
||||
}
|
||||
} else {
|
||||
if (groupedNotebooks["/~/"]) {
|
||||
const array = groupedNotebooks["/~/"];
|
||||
array.push(book);
|
||||
groupedNotebooks["/~/"] = array;
|
||||
} else {
|
||||
groupedNotebooks["/~/"] = [book];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
|
||||
const groupedItems = Object.keys(associations)
|
||||
.map((each, i) => {
|
||||
const books = groupedNotebooks[each] || [];
|
||||
if (books.length === 0) return;
|
||||
if (
|
||||
selectedGroups.length === 0 &&
|
||||
groupedNotebooks["/~/"] &&
|
||||
groupedNotebooks["/~/"].length !== 0
|
||||
) {
|
||||
i = i + 1;
|
||||
}
|
||||
return (
|
||||
<GroupItem
|
||||
key={i}
|
||||
index={i}
|
||||
association={associations[each]}
|
||||
groupedBooks={books}
|
||||
notebooks={notebooks}
|
||||
path={props.path}
|
||||
/>
|
||||
);
|
||||
});
|
||||
if (
|
||||
selectedGroups.length === 0 &&
|
||||
groupedNotebooks["/~/"] &&
|
||||
groupedNotebooks["/~/"].length !== 0
|
||||
) {
|
||||
groupedItems.unshift(
|
||||
<GroupItem
|
||||
key={"/~/"}
|
||||
index={0}
|
||||
association={"/~/"}
|
||||
groupedBooks={groupedNotebooks["/~/"]}
|
||||
notebooks={notebooks}
|
||||
path={props.path}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const display = props.hidden ? ['none', 'block'] : 'block';
|
||||
|
||||
return (
|
||||
<Col
|
||||
borderRight={[0, 1]}
|
||||
borderRightColor={["washedGray", "washedGray"]}
|
||||
height="100%"
|
||||
pt={[3, 0]}
|
||||
overflowY="auto"
|
||||
display={display}
|
||||
flexShrink={0}
|
||||
width={["auto", "250px"]}
|
||||
>
|
||||
<Box>
|
||||
<Link to="/~publish/new" className="green2 pa4 f9 dib">
|
||||
<Box color="green">New Notebook</Box>
|
||||
</Link>
|
||||
</Box>
|
||||
<Box
|
||||
className="overflow-y-auto pb1"
|
||||
>
|
||||
<Welcome mx={2} />
|
||||
{sidebarInvites}
|
||||
{groupedItems}
|
||||
</Box>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sidebar;
|
@ -1,32 +0,0 @@
|
||||
import React from "react";
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
import { useLocalStorageState } from "~/logic/lib/useLocalStorageState";
|
||||
|
||||
export function Welcome(props: Parameters<typeof Box>[0]) {
|
||||
const [wasWelcomed, setWasWelcomed] = useLocalStorageState(
|
||||
"urbit-publish:wasWelcomed",
|
||||
false
|
||||
);
|
||||
|
||||
if (wasWelcomed) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Box {...props} p={2} border={1} >
|
||||
<Box lineHeight="1.6" fontSize={0}>
|
||||
Notebooks are for longer-form writing and discussion. Each Notebook is a
|
||||
collection of Markdown-formatted notes with optional comments.
|
||||
</Box>
|
||||
<Box
|
||||
fontSize={0}
|
||||
mt={2}
|
||||
className="f8 pt2 dib pointer bb"
|
||||
onClick={() => { setWasWelcomed(true) }}
|
||||
>
|
||||
Close this
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Welcome;
|
@ -1,103 +0,0 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { Box, ManagedTextInputField as Input, Col } from "@tlon/indigo-react";
|
||||
import { Formik, Form } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import { FormError } from "~/views/components/FormError";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { stringToSymbol } from "~/logic/lib/util";
|
||||
import GroupSearch from "~/views/components/GroupSearch";
|
||||
import { Associations } from "~/types/metadata-update";
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import { Notebooks } from "~/types/publish-update";
|
||||
import { Groups } from "~/types/group-update";
|
||||
|
||||
interface FormSchema {
|
||||
name: string;
|
||||
description: string;
|
||||
group?: string;
|
||||
}
|
||||
|
||||
const formSchema = Yup.object({
|
||||
name: Yup.string().required("Notebook must have a name"),
|
||||
description: Yup.string(),
|
||||
group: Yup.string(),
|
||||
});
|
||||
|
||||
interface NewScreenProps {
|
||||
api: GlobalApi;
|
||||
associations: Associations;
|
||||
notebooks: Notebooks;
|
||||
groups: Groups;
|
||||
}
|
||||
|
||||
export function NewScreen(props: NewScreenProps & RouteComponentProps) {
|
||||
const { history } = props;
|
||||
|
||||
const waiter = useWaitForProps(props, 5000);
|
||||
|
||||
const onSubmit = async (values: FormSchema, actions) => {
|
||||
const bookId = stringToSymbol(values.name);
|
||||
try {
|
||||
const { name, description, group } = values;
|
||||
await props.api.publish.newBook(bookId, name, description, group);
|
||||
await waiter((p) => !!p?.notebooks?.[`~${window.ship}`]?.[bookId]);
|
||||
if (!group) {
|
||||
await waiter((p) => !!p?.groups?.[`/ship/~${window.ship}/${bookId}`]);
|
||||
}
|
||||
actions.setStatus({ success: null });
|
||||
history.push(`/~publish/notebook/~${window.ship}/${bookId}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: "Notebook creation failed" });
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Col p={3}>
|
||||
<Box mb={4} color="black">New Notebook</Box>
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={{ name: "", description: "", group: "" }}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form>
|
||||
<Box
|
||||
display="grid"
|
||||
gridTemplateRows="auto"
|
||||
gridRowGap={2}
|
||||
gridTemplateColumns="300px"
|
||||
>
|
||||
<Input
|
||||
id="name"
|
||||
label="Name"
|
||||
caption="Provide a name for your notebook"
|
||||
placeholder="eg. My Journal"
|
||||
/>
|
||||
<Input
|
||||
id="description"
|
||||
label="Description"
|
||||
caption="What's your notebook about?"
|
||||
placeholder="Notebook description"
|
||||
/>
|
||||
<GroupSearch
|
||||
id="group"
|
||||
label="Group"
|
||||
caption="What group is the notebook for?"
|
||||
associations={props.associations}
|
||||
/>
|
||||
|
||||
<Box justifySelf="start">
|
||||
<AsyncButton loadingText="Creating..." type="submit" border>
|
||||
Create Notebook
|
||||
</AsyncButton>
|
||||
</Box>
|
||||
<FormError message="Notebook Creation failed" />
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
export default NewScreen;
|
@ -1,133 +0,0 @@
|
||||
import React, { useRef, SyntheticEvent, useEffect } from "react";
|
||||
import { Box, Center } from "@tlon/indigo-react";
|
||||
import { Sidebar } from "./lib/Sidebar";
|
||||
import ErrorBoundary from "~/views/components/ErrorBoundary";
|
||||
import { Notebooks } from "~/types/publish-update";
|
||||
import { Rolodex } from "~/types/contact-update";
|
||||
import { Invites } from "~/types/invite-update";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Associations } from "~/types/metadata-update";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
|
||||
type SkeletonProps = RouteComponentProps<{
|
||||
ship?: string;
|
||||
notebook?: string;
|
||||
noteId?: string;
|
||||
}> & {
|
||||
notebooks: Notebooks;
|
||||
invites: Invites;
|
||||
associations: Associations;
|
||||
contacts: Rolodex;
|
||||
api: GlobalApi;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export function Skeleton(props: SkeletonProps) {
|
||||
const { api, notebooks } = props;
|
||||
const { ship, notebook, noteId } = props.match.params;
|
||||
const scrollRef = useRef<HTMLDivElement>();
|
||||
|
||||
const path =
|
||||
(ship &&
|
||||
notebook &&
|
||||
`${props.match.params.ship}/${props.match.params.notebook}`) ||
|
||||
undefined;
|
||||
|
||||
const onScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const { scrollHeight, scrollTop, clientHeight } = e.target as HTMLDivElement;
|
||||
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
||||
if (noteId && notebook && ship) {
|
||||
const note = notebooks?.[ship]?.[notebook]?.notes?.[noteId];
|
||||
if (!note || !note.comments) {
|
||||
return;
|
||||
}
|
||||
const loadedComments = note.comments?.length;
|
||||
const fullyLoaded = note["num-comments"] === loadedComments;
|
||||
if (distanceFromBottom < 40) {
|
||||
if (!fullyLoaded) {
|
||||
api.publish.fetchCommentsPage(
|
||||
ship,
|
||||
notebook,
|
||||
noteId,
|
||||
loadedComments,
|
||||
30
|
||||
);
|
||||
}
|
||||
if (!note.read) {
|
||||
api.publish.publishAction({
|
||||
read: {
|
||||
who: ship.slice(1),
|
||||
book: notebook,
|
||||
note: noteId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (notebook && ship) {
|
||||
}
|
||||
};
|
||||
|
||||
// send read immediately if we aren't in a scrollable container
|
||||
useEffect(() => {
|
||||
if(!(noteId && notebook && ship)) {
|
||||
return;
|
||||
}
|
||||
const note = notebooks?.[ship]?.[notebook]?.notes?.[noteId];
|
||||
setTimeout(() => {
|
||||
const { clientHeight, scrollHeight } = scrollRef.current;
|
||||
const isScrolling = clientHeight < scrollHeight;
|
||||
if(!isScrolling && note) {
|
||||
api.publish.publishAction({
|
||||
read: {
|
||||
who: ship.slice(1),
|
||||
book: notebook,
|
||||
note: noteId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}, 1500);
|
||||
}, [noteId, notebook, ship, notebooks])
|
||||
|
||||
const hideSidebar = path || props.location.pathname.endsWith('/new')
|
||||
|
||||
const panelDisplay = !hideSidebar ? ["none", "block"] : "block";
|
||||
return (
|
||||
<Box height="100%" width="100%" px={[0, 3]} pb={[0, 3]}>
|
||||
<Box
|
||||
bg="white"
|
||||
display="flex"
|
||||
border={[0, 1]}
|
||||
borderColor={["washedGray", "washedGray"]}
|
||||
borderRadius={1}
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
<Sidebar
|
||||
notebooks={props.notebooks}
|
||||
contacts={props.contacts}
|
||||
path={path}
|
||||
hidden={hideSidebar}
|
||||
invites={props.invites}
|
||||
associations={props.associations}
|
||||
api={props.api}
|
||||
/>
|
||||
<Box
|
||||
ref={scrollRef}
|
||||
display={panelDisplay}
|
||||
width="100%"
|
||||
height="100%"
|
||||
position="relative"
|
||||
px={[3, 4]}
|
||||
fontSize={0}
|
||||
overflowY="scroll"
|
||||
onScroll={onScroll}
|
||||
>
|
||||
<ErrorBoundary>{props.children}</ErrorBoundary>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Skeleton;
|
Loading…
Reference in New Issue
Block a user