mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-09-21 15:38:59 +03:00
Merge pull request #3444 from urbit/lf/publish-fixup
publish: don't timeout, show groupify
This commit is contained in:
commit
0ddb4a4887
@ -82,6 +82,17 @@ export default class PublishApi extends BaseApi {
|
||||
return this.action('publish', 'publish-action', act);
|
||||
}
|
||||
|
||||
groupify(bookId: string, group: Path | null) {
|
||||
return this.publishAction({
|
||||
groupify: {
|
||||
book: bookId,
|
||||
target: group,
|
||||
inclusive: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
newBook(bookId: string, title: string, description: string, group?: Path) {
|
||||
const groupInfo = group ? { 'group-path': group,
|
||||
invitees: [],
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
|
||||
export function useWaitForProps<P>(props: P, timeout: number) {
|
||||
export function useWaitForProps<P>(props: P, timeout: number = 0) {
|
||||
const [resolve, setResolve] = useState<() => void>(() => () => {});
|
||||
const [ready, setReady] = useState<(p: P) => boolean | undefined>();
|
||||
|
||||
@ -24,9 +24,11 @@ export function useWaitForProps<P>(props: P, timeout: number) {
|
||||
setReady(() => r);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
setResolve(() => resolve);
|
||||
setTimeout(() => {
|
||||
reject(new Error("Timed out"));
|
||||
}, timeout);
|
||||
if(timeout > 0) {
|
||||
setTimeout(() => {
|
||||
reject(new Error("Timed out"));
|
||||
}, timeout);
|
||||
}
|
||||
});
|
||||
},
|
||||
[setResolve, setReady, timeout]
|
||||
|
@ -78,6 +78,7 @@ export default class GroupReducer<S extends GroupState> {
|
||||
this.addGroup(data, state);
|
||||
this.removeGroup(data, state);
|
||||
this.changePolicy(data, state);
|
||||
this.expose(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,6 +188,15 @@ export default class GroupReducer<S extends GroupState> {
|
||||
}
|
||||
}
|
||||
|
||||
expose(json: GroupUpdate, state: S) {
|
||||
if( 'expose' in json && state) {
|
||||
const { resource } = json.expose;
|
||||
const resourcePath = resourceAsPath(resource);
|
||||
state.groups[resourcePath].hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private inviteChangePolicy(diff: InvitePolicyDiff, policy: InvitePolicy) {
|
||||
if ('addInvites' in diff) {
|
||||
const { addInvites } = diff;
|
||||
|
@ -163,6 +163,7 @@ export default function PublishApp(props: PublishAppProps) {
|
||||
hideNicknames={hideNicknames}
|
||||
hideAvatars={hideAvatars}
|
||||
remoteContentPolicy={remoteContentPolicy}
|
||||
associations={associations}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
@ -0,0 +1,77 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Box, Col, Button, InputLabel, InputCaption } from "@tlon/indigo-react";
|
||||
import * as Yup from "yup";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Notebook } from "~/types/publish-update";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
|
||||
import { MetadataForm } from "./MetadataForm";
|
||||
import { Groups, Associations } from "~/types";
|
||||
import { Formik, FormikHelpers, Form } from "formik";
|
||||
import GroupSearch from "~/views/components/GroupSearch";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
|
||||
const formSchema = Yup.object({
|
||||
group: Yup.string().nullable(),
|
||||
});
|
||||
|
||||
interface FormSchema {
|
||||
group: string | null;
|
||||
}
|
||||
|
||||
interface GroupifyFormProps {
|
||||
host: string;
|
||||
book: string;
|
||||
notebook: Notebook;
|
||||
groups: Groups;
|
||||
api: GlobalApi;
|
||||
associations: Associations;
|
||||
}
|
||||
|
||||
export function GroupifyForm(props: GroupifyFormProps) {
|
||||
const onGroupify = async (
|
||||
values: FormSchema,
|
||||
actions: FormikHelpers<FormSchema>
|
||||
) => {
|
||||
try {
|
||||
await props.api.publish.groupify(props.book, values.group);
|
||||
actions.setStatus({ success: null });
|
||||
} catch (e) {
|
||||
actions.setStatus({ error: e.message });
|
||||
}
|
||||
};
|
||||
|
||||
const groupPath = props.notebook?.["writers-group-path"];
|
||||
|
||||
const isUnmanaged = props.groups?.[groupPath]?.hidden || false;
|
||||
|
||||
if (!isUnmanaged) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const initialValues: FormSchema = {
|
||||
group: null
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={initialValues}
|
||||
onSubmit={onGroupify}
|
||||
>
|
||||
<Form style={{ display: "contents" }}>
|
||||
<GroupSearch
|
||||
id="group"
|
||||
label="Group"
|
||||
caption="What group should this notebook be added to? If blank, a new group will be made for the notebook"
|
||||
associations={props.associations}
|
||||
/>
|
||||
<AsyncButton loadingText="Groupifying..." border>
|
||||
Groupify
|
||||
</AsyncButton>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
export default GroupifyForm;
|
@ -17,7 +17,7 @@ export function JoinScreen(props: JoinScreenProps & RouteComponentProps) {
|
||||
const [error, setError] = useState(false);
|
||||
const joining = useRef(false);
|
||||
|
||||
const waiter = useWaitForProps(props, 10000);
|
||||
const waiter = useWaitForProps(props);
|
||||
|
||||
const onJoin = useCallback(async () => {
|
||||
joining.current = true;
|
||||
|
@ -0,0 +1,104 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { AsyncButton } from "../../../../components/AsyncButton";
|
||||
import * as Yup from "yup";
|
||||
import {
|
||||
Box,
|
||||
Input,
|
||||
Checkbox,
|
||||
Col,
|
||||
InputLabel,
|
||||
InputCaption,
|
||||
Button,
|
||||
Center,
|
||||
} from "@tlon/indigo-react";
|
||||
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Notebook } from "~/types/publish-update";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
import { FormError } from "~/views/components/FormError";
|
||||
import { RouteComponentProps, useHistory } from "react-router-dom";
|
||||
|
||||
interface MetadataFormProps {
|
||||
host: string;
|
||||
book: string;
|
||||
notebook: Notebook;
|
||||
contacts: Contacts;
|
||||
api: GlobalApi;
|
||||
}
|
||||
|
||||
interface FormSchema {
|
||||
name: string;
|
||||
description: string;
|
||||
comments: boolean;
|
||||
}
|
||||
|
||||
const formSchema = Yup.object({
|
||||
name: Yup.string().required("Notebook must have a name"),
|
||||
description: Yup.string(),
|
||||
comments: Yup.boolean(),
|
||||
});
|
||||
|
||||
const ResetOnPropsChange = (props: { init: FormSchema; book: string }) => {
|
||||
const { resetForm } = useFormikContext<FormSchema>();
|
||||
useEffect(() => {
|
||||
resetForm({ values: props.init });
|
||||
}, [props.book]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
export function MetadataForm(props: MetadataFormProps) {
|
||||
const { host, notebook, api, book } = props;
|
||||
const initialValues: FormSchema = {
|
||||
name: notebook?.title,
|
||||
description: notebook?.about,
|
||||
comments: notebook?.comments,
|
||||
};
|
||||
|
||||
const onSubmit = async (
|
||||
values: FormSchema,
|
||||
actions: FormikHelpers<FormSchema>
|
||||
) => {
|
||||
try {
|
||||
const { name, description, comments } = values;
|
||||
await api.publish.editBook(book, name, description, comments);
|
||||
api.publish.fetchNotebook(host, book);
|
||||
actions.setStatus({ success: null });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
actions.setStatus({ error: e.message });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={initialValues}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form style={{ display: "contents" }}>
|
||||
<Input
|
||||
id="name"
|
||||
label="Rename"
|
||||
caption="Change the name of this notebook"
|
||||
/>
|
||||
<Input
|
||||
id="description"
|
||||
label="Change description"
|
||||
caption="Change the description of this notebook"
|
||||
/>
|
||||
<Checkbox
|
||||
id="comments"
|
||||
label="Comments"
|
||||
caption="Subscribers may comment when enabled"
|
||||
/>
|
||||
<ResetOnPropsChange init={initialValues} book={book} />
|
||||
<AsyncButton primary loadingText="Updating.." border>
|
||||
Save
|
||||
</AsyncButton>
|
||||
<FormError message="Failed to update settings" />
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
@ -20,6 +20,7 @@ import { Groups } from "~/types/group-update";
|
||||
import { Contacts, Rolodex } from "~/types/contact-update";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import styled from "styled-components";
|
||||
import {Associations} from "~/types";
|
||||
|
||||
const TabList = styled(_TabList)`
|
||||
margin-bottom: ${(p) => p.theme.space[4]}px;
|
||||
@ -38,6 +39,7 @@ interface NotebookProps {
|
||||
contacts: Rolodex;
|
||||
groups: Groups;
|
||||
hideNicknames: boolean;
|
||||
associations: Associations;
|
||||
}
|
||||
|
||||
export function Notebook(props: NotebookProps & RouteComponentProps) {
|
||||
@ -130,6 +132,8 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
|
||||
api={api}
|
||||
notebook={notebook}
|
||||
contacts={notebookContacts}
|
||||
associations={props.associations}
|
||||
groups={groups}
|
||||
/>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
|
@ -9,7 +9,7 @@ import { Contacts, Rolodex } from "../../../../types/contact-update";
|
||||
import Notebook from "./Notebook";
|
||||
import NewPost from "./new-post";
|
||||
import { NoteRoutes } from './NoteRoutes';
|
||||
import { LocalUpdateRemoteContentPolicy } from "~/types";
|
||||
import { LocalUpdateRemoteContentPolicy, Associations } from "~/types";
|
||||
|
||||
interface NotebookRoutesProps {
|
||||
api: GlobalApi;
|
||||
@ -23,6 +23,7 @@ interface NotebookRoutesProps {
|
||||
hideAvatars: boolean;
|
||||
hideNicknames: boolean;
|
||||
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
||||
associations: Associations;
|
||||
}
|
||||
|
||||
export function NotebookRoutes(
|
||||
|
@ -1,128 +1,65 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { AsyncButton } from "../../../../components/AsyncButton";
|
||||
import * as Yup from "yup";
|
||||
import {
|
||||
Box,
|
||||
Input,
|
||||
Checkbox,
|
||||
Col,
|
||||
InputLabel,
|
||||
InputCaption,
|
||||
Button,
|
||||
Center,
|
||||
} from "@tlon/indigo-react";
|
||||
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
|
||||
import { Box, Col, Button, InputLabel, InputCaption } from "@tlon/indigo-react";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Notebook } from "~/types/publish-update";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
import { FormError } from "~/views/components/FormError";
|
||||
import { RouteComponentProps, useHistory } from "react-router-dom";
|
||||
|
||||
import { MetadataForm } from "./MetadataForm";
|
||||
import { Groups, Associations } from "~/types";
|
||||
import GroupifyForm from "./GroupifyForm";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
interface SettingsProps {
|
||||
host: string;
|
||||
book: string;
|
||||
notebook: Notebook;
|
||||
contacts: Contacts;
|
||||
groups: Groups;
|
||||
api: GlobalApi;
|
||||
associations: Associations;
|
||||
}
|
||||
|
||||
interface FormSchema {
|
||||
name: string;
|
||||
description: string;
|
||||
comments: boolean;
|
||||
}
|
||||
|
||||
const formSchema = Yup.object({
|
||||
name: Yup.string().required("Notebook must have a name"),
|
||||
description: Yup.string(),
|
||||
comments: Yup.boolean(),
|
||||
});
|
||||
|
||||
const ResetOnPropsChange = (props: { init: FormSchema; book: string }) => {
|
||||
const { resetForm } = useFormikContext<FormSchema>();
|
||||
useEffect(() => {
|
||||
resetForm({ values: props.init });
|
||||
}, [props.book]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Divider = (props) => (
|
||||
<Box {...props} mb={4} borderBottom={1} borderBottomColor="lightGray" />
|
||||
);
|
||||
export function Settings(props: SettingsProps) {
|
||||
const { host, notebook, api, book } = props;
|
||||
const history = useHistory();
|
||||
const initialValues: FormSchema = {
|
||||
name: notebook?.title,
|
||||
description: notebook?.about,
|
||||
comments: notebook?.comments,
|
||||
};
|
||||
|
||||
const onSubmit = async (
|
||||
values: FormSchema,
|
||||
actions: FormikHelpers<FormSchema>
|
||||
) => {
|
||||
try {
|
||||
const { name, description, comments } = values;
|
||||
await api.publish.editBook(book, name, description, comments);
|
||||
api.publish.fetchNotebook(host, book);
|
||||
actions.setStatus({ success: null });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
actions.setStatus({ error: e.message });
|
||||
}
|
||||
};
|
||||
|
||||
const onDelete = async () => {
|
||||
await api.publish.delBook(book);
|
||||
await props.api.publish.delBook(props.book);
|
||||
history.push("/~publish");
|
||||
};
|
||||
const groupPath = props.notebook?.["writers-group-path"];
|
||||
|
||||
const isUnmanaged = props.groups?.[groupPath]?.hidden || false;
|
||||
|
||||
return (
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={initialValues}
|
||||
onSubmit={onSubmit}
|
||||
<Box
|
||||
mx="auto"
|
||||
maxWidth="300px"
|
||||
mb={4}
|
||||
gridTemplateColumns="1fr"
|
||||
gridAutoRows="auto"
|
||||
display="grid"
|
||||
>
|
||||
<Form>
|
||||
<Box
|
||||
maxWidth="300px"
|
||||
mb={4}
|
||||
gridTemplateColumns="1fr"
|
||||
gridAutoRows="auto"
|
||||
display="grid"
|
||||
>
|
||||
<Col mb={4}>
|
||||
<InputLabel>Delete Notebook</InputLabel>
|
||||
<InputCaption>
|
||||
Permanently delete this notebook. (All current members will no
|
||||
longer see this notebook.)
|
||||
</InputCaption>
|
||||
<Button onClick={onDelete} mt={1} border error>
|
||||
Delete this notebook
|
||||
</Button>
|
||||
</Col>
|
||||
<Input
|
||||
id="name"
|
||||
label="Rename"
|
||||
caption="Change the name of this notebook"
|
||||
/>
|
||||
<Input
|
||||
id="description"
|
||||
label="Change description"
|
||||
caption="Change the description of this notebook"
|
||||
/>
|
||||
<Checkbox
|
||||
id="comments"
|
||||
label="Comments"
|
||||
caption="Subscribers may comment when enabled"
|
||||
/>
|
||||
<ResetOnPropsChange init={initialValues} book={book} />
|
||||
<AsyncButton loadingText="Updating.." border>
|
||||
Save
|
||||
</AsyncButton>
|
||||
<FormError message="Failed to update settings" />
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
{isUnmanaged && (
|
||||
<>
|
||||
<GroupifyForm {...props} />
|
||||
<Divider mt={4} />
|
||||
</>
|
||||
)}
|
||||
<MetadataForm {...props} />
|
||||
<Divider />
|
||||
<Col mb={4}>
|
||||
<InputLabel>Delete Notebook</InputLabel>
|
||||
<InputCaption>
|
||||
Permanently delete this notebook. (All current members will no longer
|
||||
see this notebook.)
|
||||
</InputCaption>
|
||||
<Button onClick={onDelete} mt={1} border error>
|
||||
Delete this notebook
|
||||
</Button>
|
||||
</Col>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ export function GroupSearch(props: InviteSearchProps) {
|
||||
caption={props.caption}
|
||||
candidates={groups}
|
||||
renderCandidate={renderCandidate}
|
||||
disabled={value.length !== 0}
|
||||
disabled={value && value.length !== 0}
|
||||
search={(s: string, a: Association) =>
|
||||
a.metadata.title.toLowerCase().startsWith(s.toLowerCase())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user