♻️(frontend) create useProviderStore
We created useProviderStore, a store dedicated to managing the provider of the document. We created as well a new hook useCollaboration, it will be use to interact with the provider store. This refacto is a first step to implement the long polling.
This commit is contained in:
@@ -16,6 +16,7 @@ and this project adheres to
|
|||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
- 🏗️(yjs-server) organize yjs server #528
|
- 🏗️(yjs-server) organize yjs server #528
|
||||||
|
- ♻️(frontend) better separation collaboration process #528
|
||||||
|
|
||||||
|
|
||||||
## [1.10.0] - 2024-12-17
|
## [1.10.0] - 2024-12-17
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const createDoc = async (
|
|||||||
.click();
|
.click();
|
||||||
|
|
||||||
await page.getByRole('heading', { name: 'Untitled document' }).click();
|
await page.getByRole('heading', { name: 'Untitled document' }).click();
|
||||||
await page.keyboard.type(randomDocs[i]);
|
await page.keyboard.type(randomDocs[i], { delay: 100 });
|
||||||
await page.getByText('Created at ').click();
|
await page.getByText('Created at ').click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { DocHeader } from '@/features/docs/doc-header';
|
|||||||
import {
|
import {
|
||||||
Doc,
|
Doc,
|
||||||
base64ToBlocknoteXmlFragment,
|
base64ToBlocknoteXmlFragment,
|
||||||
useDocStore,
|
useProviderStore,
|
||||||
} from '@/features/docs/doc-management';
|
} from '@/features/docs/doc-management';
|
||||||
import { Versions, useDocVersion } from '@/features/docs/doc-versioning/';
|
import { Versions, useDocVersion } from '@/features/docs/doc-versioning/';
|
||||||
import { useResponsiveStore } from '@/stores';
|
import { useResponsiveStore } from '@/stores';
|
||||||
@@ -33,8 +33,7 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
|
|||||||
|
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
|
|
||||||
const { providers } = useDocStore();
|
const { provider } = useProviderStore();
|
||||||
const provider = providers?.[doc.id];
|
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
export * from './useCollaboration';
|
||||||
export * from './useTrans';
|
export * from './useTrans';
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { useCollaborationUrl } from '@/core/config';
|
||||||
|
import { useBroadcastStore } from '@/stores';
|
||||||
|
|
||||||
|
import { useProviderStore } from '../stores/useProviderStore';
|
||||||
|
import { Base64 } from '../types';
|
||||||
|
|
||||||
|
export const useCollaboration = (room?: string, initialContent?: Base64) => {
|
||||||
|
const collaborationUrl = useCollaborationUrl(room);
|
||||||
|
const { setBroadcastProvider } = useBroadcastStore();
|
||||||
|
const { provider, createProvider, destroyProvider } = useProviderStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!room || !collaborationUrl || provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newProvider = createProvider(collaborationUrl, room, initialContent);
|
||||||
|
setBroadcastProvider(newProvider);
|
||||||
|
}, [
|
||||||
|
provider,
|
||||||
|
collaborationUrl,
|
||||||
|
room,
|
||||||
|
initialContent,
|
||||||
|
createProvider,
|
||||||
|
setBroadcastProvider,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
destroyProvider();
|
||||||
|
};
|
||||||
|
}, [destroyProvider]);
|
||||||
|
};
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from './useDocStore';
|
export * from './useDocStore';
|
||||||
|
export * from './useProviderStore';
|
||||||
|
|||||||
@@ -1,53 +1,14 @@
|
|||||||
import { HocuspocusProvider } from '@hocuspocus/provider';
|
|
||||||
import * as Y from 'yjs';
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
import { Base64, Doc } from '@/features/docs/doc-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
export interface UseDocStore {
|
export interface UseDocStore {
|
||||||
currentDoc?: Doc;
|
currentDoc?: Doc;
|
||||||
providers: {
|
|
||||||
[storeId: string]: HocuspocusProvider;
|
|
||||||
};
|
|
||||||
createProvider: (
|
|
||||||
providerUrl: string,
|
|
||||||
storeId: string,
|
|
||||||
initialDoc: Base64,
|
|
||||||
) => HocuspocusProvider;
|
|
||||||
setProviders: (storeId: string, providers: HocuspocusProvider) => void;
|
|
||||||
setCurrentDoc: (doc: Doc | undefined) => void;
|
setCurrentDoc: (doc: Doc | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDocStore = create<UseDocStore>((set, get) => ({
|
export const useDocStore = create<UseDocStore>((set) => ({
|
||||||
currentDoc: undefined,
|
currentDoc: undefined,
|
||||||
providers: {},
|
|
||||||
createProvider: (providerUrl, storeId, initialDoc) => {
|
|
||||||
const doc = new Y.Doc({
|
|
||||||
guid: storeId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (initialDoc) {
|
|
||||||
Y.applyUpdate(doc, Buffer.from(initialDoc, 'base64'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = new HocuspocusProvider({
|
|
||||||
url: providerUrl,
|
|
||||||
name: storeId,
|
|
||||||
document: doc,
|
|
||||||
});
|
|
||||||
|
|
||||||
get().setProviders(storeId, provider);
|
|
||||||
|
|
||||||
return provider;
|
|
||||||
},
|
|
||||||
setProviders: (storeId, provider) => {
|
|
||||||
set(({ providers }) => ({
|
|
||||||
providers: {
|
|
||||||
...providers,
|
|
||||||
[storeId]: provider,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
setCurrentDoc: (doc) => {
|
setCurrentDoc: (doc) => {
|
||||||
set({ currentDoc: doc });
|
set({ currentDoc: doc });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { HocuspocusProvider } from '@hocuspocus/provider';
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
import { Base64 } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
|
export interface UseCollaborationStore {
|
||||||
|
createProvider: (
|
||||||
|
providerUrl: string,
|
||||||
|
storeId: string,
|
||||||
|
initialDoc?: Base64,
|
||||||
|
) => HocuspocusProvider;
|
||||||
|
destroyProvider: () => void;
|
||||||
|
provider: HocuspocusProvider | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
provider: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
|
||||||
|
...defaultValues,
|
||||||
|
createProvider: (wsUrl, storeId, initialDoc) => {
|
||||||
|
const doc = new Y.Doc({
|
||||||
|
guid: storeId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (initialDoc) {
|
||||||
|
Y.applyUpdate(doc, Buffer.from(initialDoc, 'base64'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = new HocuspocusProvider({
|
||||||
|
url: wsUrl,
|
||||||
|
name: storeId,
|
||||||
|
document: doc,
|
||||||
|
});
|
||||||
|
|
||||||
|
set({
|
||||||
|
provider,
|
||||||
|
});
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
},
|
||||||
|
destroyProvider: () => {
|
||||||
|
const provider = get().provider;
|
||||||
|
if (provider) {
|
||||||
|
provider.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
set(defaultValues);
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -13,9 +13,9 @@ import { Box, Text } from '@/components';
|
|||||||
import {
|
import {
|
||||||
Doc,
|
Doc,
|
||||||
base64ToYDoc,
|
base64ToYDoc,
|
||||||
useDocStore,
|
useProviderStore,
|
||||||
useUpdateDoc,
|
useUpdateDoc,
|
||||||
} from '@/features/docs/doc-management';
|
} from '@/features/docs/doc-management/';
|
||||||
|
|
||||||
import { useDocVersion } from '../api';
|
import { useDocVersion } from '../api';
|
||||||
import { KEY_LIST_DOC_VERSIONS } from '../api/useDocVersions';
|
import { KEY_LIST_DOC_VERSIONS } from '../api/useDocVersions';
|
||||||
@@ -40,7 +40,7 @@ export const ModalVersion = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToastProvider();
|
const { toast } = useToastProvider();
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
const { providers } = useDocStore();
|
const { provider } = useProviderStore();
|
||||||
const { mutate: updateDoc } = useUpdateDoc({
|
const { mutate: updateDoc } = useUpdateDoc({
|
||||||
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
|
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -49,14 +49,14 @@ export const ModalVersion = ({
|
|||||||
void push(`/docs/${docId}`);
|
void push(`/docs/${docId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!providers?.[docId] || !version?.content) {
|
if (!provider || !version?.content) {
|
||||||
onDisplaySuccess();
|
onDisplaySuccess();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
revertUpdate(
|
revertUpdate(
|
||||||
providers[docId].document,
|
provider.document,
|
||||||
providers[docId].document,
|
provider.document,
|
||||||
base64ToYDoc(version.content),
|
base64ToYDoc(version.content),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,15 @@ import { useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { Box, Text } from '@/components';
|
import { Box, Text } from '@/components';
|
||||||
import { TextErrors } from '@/components/TextErrors';
|
import { TextErrors } from '@/components/TextErrors';
|
||||||
import { useCollaborationUrl } from '@/core';
|
|
||||||
import { useAuthStore } from '@/core/auth';
|
import { useAuthStore } from '@/core/auth';
|
||||||
import { DocEditor } from '@/features/docs/doc-editor';
|
import { DocEditor } from '@/features/docs/doc-editor';
|
||||||
import { KEY_DOC, useDoc, useDocStore } from '@/features/docs/doc-management';
|
import {
|
||||||
|
Doc,
|
||||||
|
KEY_DOC,
|
||||||
|
useCollaboration,
|
||||||
|
useDoc,
|
||||||
|
useDocStore,
|
||||||
|
} from '@/features/docs/doc-management/';
|
||||||
import { MainLayout } from '@/layouts';
|
import { MainLayout } from '@/layouts';
|
||||||
import { useBroadcastStore } from '@/stores';
|
import { useBroadcastStore } from '@/stores';
|
||||||
import { NextPageWithLayout } from '@/types/next';
|
import { NextPageWithLayout } from '@/types/next';
|
||||||
@@ -41,14 +46,25 @@ interface DocProps {
|
|||||||
|
|
||||||
const DocPage = ({ id }: DocProps) => {
|
const DocPage = ({ id }: DocProps) => {
|
||||||
const { login } = useAuthStore();
|
const { login } = useAuthStore();
|
||||||
const { data: docQuery, isError, error } = useDoc({ id });
|
const {
|
||||||
const [doc, setDoc] = useState(docQuery);
|
data: docQuery,
|
||||||
const { setCurrentDoc, createProvider, providers } = useDocStore();
|
isError,
|
||||||
const { setBroadcastProvider, addTask } = useBroadcastStore();
|
isFetching,
|
||||||
|
error,
|
||||||
|
} = useDoc(
|
||||||
|
{ id },
|
||||||
|
{
|
||||||
|
staleTime: 0,
|
||||||
|
queryKey: [KEY_DOC, { id }],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const [doc, setDoc] = useState<Doc>();
|
||||||
|
const { setCurrentDoc } = useDocStore();
|
||||||
|
const { addTask } = useBroadcastStore();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { replace } = useRouter();
|
const { replace } = useRouter();
|
||||||
const provider = providers?.[id];
|
useCollaboration(doc?.id, doc?.content);
|
||||||
const collaborationUrl = useCollaborationUrl(doc?.id);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (doc?.title) {
|
if (doc?.title) {
|
||||||
@@ -59,26 +75,13 @@ const DocPage = ({ id }: DocProps) => {
|
|||||||
}, [doc?.title]);
|
}, [doc?.title]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!docQuery) {
|
if (!docQuery || isFetching) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDoc(docQuery);
|
setDoc(docQuery);
|
||||||
setCurrentDoc(docQuery);
|
setCurrentDoc(docQuery);
|
||||||
}, [docQuery, setCurrentDoc]);
|
}, [docQuery, setCurrentDoc, isFetching]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!doc?.id || !collaborationUrl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let newProvider = provider;
|
|
||||||
if (!provider || provider.document.guid !== doc.id) {
|
|
||||||
newProvider = createProvider(collaborationUrl, doc.id, doc.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
setBroadcastProvider(newProvider);
|
|
||||||
}, [createProvider, doc, provider, setBroadcastProvider, collaborationUrl]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We add a broadcast task to reset the query cache
|
* We add a broadcast task to reset the query cache
|
||||||
|
|||||||
Reference in New Issue
Block a user