From 0f355e58d9b926251c79d19403101c22f3e214b6 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 8 Sep 2020 11:10:55 +1000 Subject: [PATCH 1/5] interface: allow useWaitForProps to not timeout --- pkg/interface/src/logic/lib/useWaitForProps.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/interface/src/logic/lib/useWaitForProps.ts b/pkg/interface/src/logic/lib/useWaitForProps.ts index 5a2d752e6..80e92e92a 100644 --- a/pkg/interface/src/logic/lib/useWaitForProps.ts +++ b/pkg/interface/src/logic/lib/useWaitForProps.ts @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; -export function useWaitForProps

(props: P, timeout: number) { +export function useWaitForProps

(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

(props: P, timeout: number) { setReady(() => r); return new Promise((resolve, reject) => { setResolve(() => resolve); - setTimeout(() => { - reject(new Error("Timed out")); - }, timeout); + if(timeout > 0) { + setTimeout(() => { + reject(new Error("Timed out")); + }, timeout); + } }); }, [setResolve, setReady, timeout] From 07c0cb1006e8ab0f882b8b4ed624a235ca8563b8 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 8 Sep 2020 11:11:57 +1000 Subject: [PATCH 2/5] publish: do not timeout join flow --- pkg/interface/src/views/apps/publish/components/lib/Join.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/interface/src/views/apps/publish/components/lib/Join.tsx b/pkg/interface/src/views/apps/publish/components/lib/Join.tsx index 3c57d3f08..ff7a91c14 100644 --- a/pkg/interface/src/views/apps/publish/components/lib/Join.tsx +++ b/pkg/interface/src/views/apps/publish/components/lib/Join.tsx @@ -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; From 168478eb745dbfbd7cb27c772b6fe94cb0fd2be3 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 8 Sep 2020 12:11:45 +1000 Subject: [PATCH 3/5] publish: support groupify poke --- pkg/interface/src/logic/api/publish.ts | 11 +++++++++++ pkg/interface/src/logic/reducers/group-update.ts | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/pkg/interface/src/logic/api/publish.ts b/pkg/interface/src/logic/api/publish.ts index 02648f651..ad99dc077 100644 --- a/pkg/interface/src/logic/api/publish.ts +++ b/pkg/interface/src/logic/api/publish.ts @@ -82,6 +82,17 @@ export default class PublishApi extends BaseApi { return this.action('publish', 'publish-action', act); } + groupify(bookId: string) { + return this.publishAction({ + groupify: { + book: bookId, + target: null, + inclusive: false + } + }); + } + + newBook(bookId: string, title: string, description: string, group?: Path) { const groupInfo = group ? { 'group-path': group, invitees: [], diff --git a/pkg/interface/src/logic/reducers/group-update.ts b/pkg/interface/src/logic/reducers/group-update.ts index 09adb35b2..5862aaca1 100644 --- a/pkg/interface/src/logic/reducers/group-update.ts +++ b/pkg/interface/src/logic/reducers/group-update.ts @@ -78,6 +78,7 @@ export default class GroupReducer { this.addGroup(data, state); this.removeGroup(data, state); this.changePolicy(data, state); + this.expose(data, state); } } @@ -187,6 +188,15 @@ export default class GroupReducer { } } + 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; From 73881aa25f51275d49ec9ea1ccad9dab8d7d1b9d Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 8 Sep 2020 12:12:08 +1000 Subject: [PATCH 4/5] publish: add groupify to settings --- .../publish/components/lib/MetadataForm.tsx | 120 ++++++++++++++++ .../apps/publish/components/lib/Notebook.tsx | 1 + .../apps/publish/components/lib/Settings.tsx | 129 ++++-------------- 3 files changed, 148 insertions(+), 102 deletions(-) create mode 100644 pkg/interface/src/views/apps/publish/components/lib/MetadataForm.tsx diff --git a/pkg/interface/src/views/apps/publish/components/lib/MetadataForm.tsx b/pkg/interface/src/views/apps/publish/components/lib/MetadataForm.tsx new file mode 100644 index 000000000..35932b701 --- /dev/null +++ b/pkg/interface/src/views/apps/publish/components/lib/MetadataForm.tsx @@ -0,0 +1,120 @@ +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(); + useEffect(() => { + resetForm({ values: props.init }); + }, [props.book]); + + return null; +}; + + +export function MetadataForm(props: MetadataFormProps) { + 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 + ) => { + 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); + history.push("/~publish"); + }; + + return ( + +

+ + Delete Notebook + + Permanently delete this notebook. (All current members will no + longer see this notebook.) + + + + + + + + + Save + + + + + ); +} diff --git a/pkg/interface/src/views/apps/publish/components/lib/Notebook.tsx b/pkg/interface/src/views/apps/publish/components/lib/Notebook.tsx index 86c94dabc..22febfcb7 100644 --- a/pkg/interface/src/views/apps/publish/components/lib/Notebook.tsx +++ b/pkg/interface/src/views/apps/publish/components/lib/Notebook.tsx @@ -130,6 +130,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) { api={api} notebook={notebook} contacts={notebookContacts} + groups={groups} /> diff --git a/pkg/interface/src/views/apps/publish/components/lib/Settings.tsx b/pkg/interface/src/views/apps/publish/components/lib/Settings.tsx index aba15abf1..29c9004b1 100644 --- a/pkg/interface/src/views/apps/publish/components/lib/Settings.tsx +++ b/pkg/interface/src/views/apps/publish/components/lib/Settings.tsx @@ -1,128 +1,53 @@ 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 } from "~/types"; interface SettingsProps { host: string; book: string; notebook: Notebook; contacts: Contacts; + groups: Groups; 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(); - useEffect(() => { - resetForm({ values: props.init }); - }, [props.book]); - - return null; -}; - 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 onGroupify = () => { + return props.api.publish.groupify(props.book); }; - const onSubmit = async ( - values: FormSchema, - actions: FormikHelpers - ) => { - 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 groupPath = props.notebook?.["writers-group-path"]; - const onDelete = async () => { - await api.publish.delBook(book); - history.push("/~publish"); - }; + const isUnmanaged = props.groups?.[groupPath]?.hidden || false; return ( - -
- + {isUnmanaged && ( + <> - Delete Notebook - - Permanently delete this notebook. (All current members will no - longer see this notebook.) - - - - - - - - Save - - - -
-
+ + + )} + + ); } From f3206312302b50a584fa1a25d63ced9812298f69 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 8 Sep 2020 15:04:45 +1000 Subject: [PATCH 5/5] publish: choose group for groupify --- pkg/interface/src/logic/api/publish.ts | 4 +- pkg/interface/src/views/apps/publish/app.tsx | 1 + .../publish/components/lib/GroupifyForm.tsx | 77 +++++++++++++++++++ .../publish/components/lib/MetadataForm.tsx | 16 ---- .../apps/publish/components/lib/Notebook.tsx | 3 + .../publish/components/lib/NotebookRoutes.tsx | 3 +- .../apps/publish/components/lib/Settings.tsx | 36 ++++++--- .../src/views/components/GroupSearch.tsx | 2 +- 8 files changed, 110 insertions(+), 32 deletions(-) create mode 100644 pkg/interface/src/views/apps/publish/components/lib/GroupifyForm.tsx diff --git a/pkg/interface/src/logic/api/publish.ts b/pkg/interface/src/logic/api/publish.ts index ad99dc077..5b49aabfc 100644 --- a/pkg/interface/src/logic/api/publish.ts +++ b/pkg/interface/src/logic/api/publish.ts @@ -82,11 +82,11 @@ export default class PublishApi extends BaseApi { return this.action('publish', 'publish-action', act); } - groupify(bookId: string) { + groupify(bookId: string, group: Path | null) { return this.publishAction({ groupify: { book: bookId, - target: null, + target: group, inclusive: false } }); diff --git a/pkg/interface/src/views/apps/publish/app.tsx b/pkg/interface/src/views/apps/publish/app.tsx index 3bcedd386..cc161f171 100644 --- a/pkg/interface/src/views/apps/publish/app.tsx +++ b/pkg/interface/src/views/apps/publish/app.tsx @@ -163,6 +163,7 @@ export default function PublishApp(props: PublishAppProps) { hideNicknames={hideNicknames} hideAvatars={hideAvatars} remoteContentPolicy={remoteContentPolicy} + associations={associations} {...props} /> ); diff --git a/pkg/interface/src/views/apps/publish/components/lib/GroupifyForm.tsx b/pkg/interface/src/views/apps/publish/components/lib/GroupifyForm.tsx new file mode 100644 index 000000000..3ceb35089 --- /dev/null +++ b/pkg/interface/src/views/apps/publish/components/lib/GroupifyForm.tsx @@ -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 + ) => { + 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 ( + +
+ + + Groupify + + +
+ ); +} + +export default GroupifyForm; diff --git a/pkg/interface/src/views/apps/publish/components/lib/MetadataForm.tsx b/pkg/interface/src/views/apps/publish/components/lib/MetadataForm.tsx index 35932b701..4209817ef 100644 --- a/pkg/interface/src/views/apps/publish/components/lib/MetadataForm.tsx +++ b/pkg/interface/src/views/apps/publish/components/lib/MetadataForm.tsx @@ -50,7 +50,6 @@ const ResetOnPropsChange = (props: { init: FormSchema; book: string }) => { export function MetadataForm(props: MetadataFormProps) { const { host, notebook, api, book } = props; - const history = useHistory(); const initialValues: FormSchema = { name: notebook?.title, description: notebook?.about, @@ -72,11 +71,6 @@ export function MetadataForm(props: MetadataFormProps) { } }; - const onDelete = async () => { - await api.publish.delBook(book); - history.push("/~publish"); - }; - return (
- - Delete Notebook - - Permanently delete this notebook. (All current members will no - longer see this notebook.) - - - 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,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) { api={api} notebook={notebook} contacts={notebookContacts} + associations={props.associations} groups={groups} /> diff --git a/pkg/interface/src/views/apps/publish/components/lib/NotebookRoutes.tsx b/pkg/interface/src/views/apps/publish/components/lib/NotebookRoutes.tsx index 385c31637..efe2f4449 100644 --- a/pkg/interface/src/views/apps/publish/components/lib/NotebookRoutes.tsx +++ b/pkg/interface/src/views/apps/publish/components/lib/NotebookRoutes.tsx @@ -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( diff --git a/pkg/interface/src/views/apps/publish/components/lib/Settings.tsx b/pkg/interface/src/views/apps/publish/components/lib/Settings.tsx index 29c9004b1..3bb3693c8 100644 --- a/pkg/interface/src/views/apps/publish/components/lib/Settings.tsx +++ b/pkg/interface/src/views/apps/publish/components/lib/Settings.tsx @@ -5,7 +5,9 @@ import { Notebook } from "~/types/publish-update"; import { Contacts } from "~/types/contact-update"; import { MetadataForm } from "./MetadataForm"; -import { Groups } from "~/types"; +import { Groups, Associations } from "~/types"; +import GroupifyForm from "./GroupifyForm"; +import { useHistory } from "react-router-dom"; interface SettingsProps { host: string; @@ -14,13 +16,18 @@ interface SettingsProps { contacts: Contacts; groups: Groups; api: GlobalApi; + associations: Associations; } +const Divider = (props) => ( + +); export function Settings(props: SettingsProps) { - const onGroupify = () => { - return props.api.publish.groupify(props.book); + const history = useHistory(); + const onDelete = async () => { + await props.api.publish.delBook(props.book); + history.push("/~publish"); }; - const groupPath = props.notebook?.["writers-group-path"]; const isUnmanaged = props.groups?.[groupPath]?.hidden || false; @@ -36,17 +43,22 @@ export function Settings(props: SettingsProps) { > {isUnmanaged && ( <> - - Groupify - Turn this notebook into a group - - - + + )} + + + Delete Notebook + + Permanently delete this notebook. (All current members will no longer + see this notebook.) + + + ); } diff --git a/pkg/interface/src/views/components/GroupSearch.tsx b/pkg/interface/src/views/components/GroupSearch.tsx index 5315d34c2..1cf3032df 100644 --- a/pkg/interface/src/views/components/GroupSearch.tsx +++ b/pkg/interface/src/views/components/GroupSearch.tsx @@ -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()) }