[WEB-1322] dev: conflict free pages collaboration (#4463)

* chore: pages realtime

* chore: empty binary response

* chore: added a ypy package

* feat: pages collaboration

* chore: update fetching logic

* chore: degrade ypy version

* chore: replace useEffect fetch logic with useSWR

* chore: move all the update logic to the page store

* refactor: remove react-hook-form

* chore: save description_html as well

* chore: migrate old data logic

* fix: added description_binary as field name

* fix: code cleanup

* refactor: create separate hook to handle page description

* fix: build errors

* chore: combine updates instead of using the whole document

* chore: removed ypy package

* chore: added conflict resolving logic to the client side

* chore: add a save changes button

* chore: add read-only validation

* chore: remove saving state information

* chore: added permission class

* chore: removed the migration file

* chore: corrected the model field

* chore: rename pageStore to page

* chore: update collaboration provider

* chore: add try catch to handle error

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
Aaryan Khandelwal 2024-05-26 16:37:10 +05:30 committed by sriram veeraghanta
parent a04ce5abfc
commit ff03c0b718
42 changed files with 1134 additions and 509 deletions

View file

@ -1,8 +1,7 @@
import { ReactElement, useEffect, useRef, useState } from "react";
import { ReactElement, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import Link from "next/link";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import useSWR from "swr";
// document-editor
import { EditorRefApi, useEditorMarkings } from "@plane/document-editor";
@ -38,38 +37,24 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
const { workspaceSlug, projectId, pageId } = router.query;
// store hooks
const { createPage, getPageById } = useProjectPages(projectId?.toString() ?? "");
const pageStore = usePage(pageId?.toString() ?? "");
const page = usePage(pageId?.toString() ?? "");
const { description_html, id, name } = page;
// editor markings hook
const { markings, updateMarkings } = useEditorMarkings();
// form info
const { handleSubmit, getValues, control } = useForm<TPage>({
defaultValues: {
name: "",
description_html: "",
},
});
// fetching page details
const {
data: swrPageDetails,
isValidating,
error: pageDetailsError,
} = useSWR(pageId ? `PAGE_DETAILS_${pageId}` : null, pageId ? () => getPageById(pageId.toString()) : null, {
revalidateIfStale: false,
revalidateOnFocus: true,
revalidateOnReconnect: true,
});
useEffect(
() => () => {
if (pageStore.cleanup) pageStore.cleanup();
},
[pageStore]
// fetch page details
const { error: pageDetailsError } = useSWR(
pageId ? `PAGE_DETAILS_${pageId}` : null,
pageId ? () => getPageById(pageId.toString()) : null,
{
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
if ((!pageStore || !pageStore.id) && !pageDetailsError)
if ((!page || !id) && !pageDetailsError)
return (
<div className="h-full w-full grid place-items-center">
<div className="size-full grid place-items-center">
<LogoSpinner />
</div>
);
@ -90,28 +75,12 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
</div>
);
// we need to get the values of title and description from the page store but we don't have to subscribe to those values
const pageTitle = pageStore?.name;
const handleCreatePage = async (payload: Partial<TPage>) => await createPage(payload);
const handleUpdatePage = async (formData: TPage) => {
let updatedDescription = formData.description_html;
if (!updatedDescription || updatedDescription.trim() === "") updatedDescription = "<p></p>";
pageStore.updateDescription(updatedDescription);
};
const handleDuplicatePage = async () => {
const currentPageValues = getValues();
if (!currentPageValues?.description_html) {
// TODO: We need to get latest data the above variable will give us stale data
currentPageValues.description_html = pageStore.description_html;
}
const formData: Partial<TPage> = {
name: "Copy of " + pageStore.name,
description_html: currentPageValues.description_html,
name: "Copy of " + name,
description_html: description_html ?? "<p></p>",
};
await handleCreatePage(formData)
@ -127,7 +96,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
return (
<>
<PageHead title={pageTitle} />
<PageHead title={name} />
<div className="flex h-full flex-col justify-between">
<div className="h-full w-full flex-shrink-0 flex flex-col overflow-hidden">
{projectId && (
@ -137,24 +106,20 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
editorReady={editorReady}
readOnlyEditorReady={readOnlyEditorReady}
handleDuplicatePage={handleDuplicatePage}
isSyncing={isValidating}
markings={markings}
pageStore={pageStore}
page={page}
projectId={projectId.toString()}
sidePeekVisible={sidePeekVisible}
setSidePeekVisible={(state) => setSidePeekVisible(state)}
/>
)}
<PageEditorBody
swrPageDetails={swrPageDetails}
control={control}
editorRef={editorRef}
handleEditorReady={(val) => setEditorReady(val)}
readOnlyEditorRef={readOnlyEditorRef}
handleReadOnlyEditorReady={() => setReadOnlyEditorReady(true)}
handleSubmit={() => handleSubmit(handleUpdatePage)()}
markings={markings}
pageStore={pageStore}
page={page}
sidePeekVisible={sidePeekVisible}
updateMarkings={updateMarkings}
/>