Cloud collaborator refactoring (#6424)

This commit is contained in:
Alexander Onnikov
2024-08-29 15:10:37 +07:00
committed by GitHub
parent 42b19c39c7
commit 4ff8a4559f
52 changed files with 762 additions and 998 deletions
@@ -16,14 +16,12 @@
-->
<script lang="ts">
import { type Class, type CollaborativeDoc, type Doc, type Ref } from '@hcengineering/core'
import { type DocumentId, type PlatformDocumentId } from '@hcengineering/collaborator-client'
import { getMetadata } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import { onDestroy, setContext } from 'svelte'
import textEditor, { CollaborationIds } from '@hcengineering/text-editor'
import { CollaborationIds } from '@hcengineering/text-editor'
import { TiptapCollabProvider, createTiptapCollaborationData } from '../provider/tiptap'
import { formatCollaborativeDocumentId, formatPlatformDocumentId } from '../provider/utils'
import { createTiptapCollaborationData } from '../provider/utils'
import { Provider } from '../provider/types'
import { formatDocumentId } from '@hcengineering/collaborator-client'
export let collaborativeDoc: CollaborativeDoc
export let initialCollaborativeDoc: CollaborativeDoc | undefined = undefined
@@ -32,50 +30,34 @@
export let objectId: Ref<Doc> | undefined = undefined
export let objectAttr: string | undefined = undefined
const token = getMetadata(presentation.metadata.Token) ?? ''
const collaboratorURL = getMetadata(textEditor.metadata.CollaboratorUrl) ?? ''
let provider: Provider | undefined
let initialContentId: DocumentId | undefined
let platformDocumentId: PlatformDocumentId | undefined
// while editing, collaborative doc may change, hence we need to
// build stable key to ensure we don't do unnecessary updates
$: documentId = formatDocumentId('', collaborativeDoc)
$: documentId = formatCollaborativeDocumentId(collaborativeDoc)
$: if (initialCollaborativeDoc !== undefined) {
initialContentId = formatCollaborativeDocumentId(initialCollaborativeDoc)
}
$: if (objectClass !== undefined && objectId !== undefined && objectAttr !== undefined) {
platformDocumentId = formatPlatformDocumentId(objectClass, objectId, objectAttr)
}
let _documentId: DocumentId | undefined
let provider: TiptapCollabProvider | undefined
let _documentId: string | undefined
$: if (_documentId !== documentId) {
_documentId = documentId
if (provider !== undefined) {
provider.disconnect()
void provider.destroy()
}
const data = createTiptapCollaborationData({
documentId,
initialContentId,
platformDocumentId,
collaboratorURL,
token
document: collaborativeDoc,
initialDocument: initialCollaborativeDoc,
objectClass,
objectId,
objectAttr
})
provider = data.provider
setContext(CollaborationIds.Doc, data.ydoc)
setContext(CollaborationIds.Provider, provider)
provider.on('status', (event: any) => {
console.log('Collaboration:', documentId, event.status) // logs "connected" or "disconnected"
})
provider.on('synced', (event: any) => {
console.log('Collaboration:', event) // logs "connected" or "disconnected"
})
}
onDestroy(() => {
provider?.destroy()
void provider?.destroy()
})
</script>
@@ -16,29 +16,32 @@
-->
<script lang="ts">
import type { AwarenessState, AwarenessStateMap } from '@hcengineering/text-editor'
import { AnySvelteComponent, Button, DelayedCaller } from '@hcengineering/ui'
import { onMount } from 'svelte'
import { Editor } from '@tiptap/core'
import { onMount } from 'svelte'
import { createRelativePositionFromJSON } from 'yjs'
import { relativePositionToAbsolutePosition, ySyncPluginKey } from 'y-prosemirror'
import { Provider } from '../provider/types'
import { TiptapCollabProvider } from '../provider/tiptap'
import { AwarenessChangeEvent, CollaborationUserState } from '@hcengineering/text-editor'
export let provider: TiptapCollabProvider
export let provider: Provider
export let editor: Editor
export let component: AnySvelteComponent
let states: CollaborationUserState[] = []
let states: AwarenessState[] = []
const debounce = new DelayedCaller(100)
const onAwarenessChange = (event: AwarenessChangeEvent): void => {
const onAwarenessChange = (): void => {
debounce.call(() => {
states = event.states.filter((p) => p.user != null).filter((p) => p.clientId !== provider.awareness?.clientID)
const map: AwarenessStateMap = provider.awareness?.states ?? new Map()
const entries: Array<[number, AwarenessState]> = Array.from(map.entries())
states = entries
.filter(([clientId, state]) => clientId !== provider.awareness?.clientID && state.user != null)
.map(([_, state]) => state)
})
}
function goToCursor (state: CollaborationUserState): void {
function goToCursor (state: AwarenessState): void {
const cursor = state.cursor
if (cursor?.head != null) {
try {
@@ -60,8 +63,8 @@
}
onMount(() => {
provider.on('awarenessUpdate', onAwarenessChange)
return () => provider.off('awarenessUpdate', onAwarenessChange)
provider.awareness?.on('update', onAwarenessChange)
return () => provider.awareness?.off('update', onAwarenessChange)
})
</script>
@@ -15,10 +15,9 @@
//
-->
<script lang="ts">
import { type DocumentId, type PlatformDocumentId } from '@hcengineering/collaborator-client'
import { type Space, type Class, type CollaborativeDoc, type Doc, type Ref } from '@hcengineering/core'
import { IntlString, getMetadata, translate } from '@hcengineering/platform'
import presentation, { getFileUrl, getImageSize } from '@hcengineering/presentation'
import { IntlString, translate } from '@hcengineering/platform'
import { getFileUrl, getImageSize } from '@hcengineering/presentation'
import { markupToJSON } from '@hcengineering/text'
import {
AnySvelteComponent,
@@ -43,9 +42,8 @@
import { deleteAttachment } from '../command/deleteAttachment'
import { textEditorCommandHandler } from '../commands'
import { EditorKitOptions, getEditorKit } from '../../src/kits/editor-kit'
import { IndexeddbProvider } from '../provider/indexeddb'
import { TiptapCollabProvider } from '../provider/tiptap'
import { formatCollaborativeDocumentId, formatPlatformDocumentId } from '../provider/utils'
import { Provider } from '../provider/types'
import { createLocalProvider, createRemoteProvider } from '../provider/utils'
import textEditor, {
CollaborationIds,
CollaborationUser,
@@ -103,37 +101,19 @@
const dispatch = createEventDispatcher()
const token = getMetadata(presentation.metadata.Token) ?? ''
const collaboratorURL = getMetadata(textEditor.metadata.CollaboratorUrl) ?? ''
const documentId = formatCollaborativeDocumentId(collaborativeDoc)
let initialContentId: DocumentId | undefined
if (initialCollaborativeDoc !== undefined) {
initialContentId = formatCollaborativeDocumentId(collaborativeDoc)
}
let platformDocumentId: PlatformDocumentId | undefined
if (objectClass !== undefined && objectId !== undefined && objectAttr !== undefined) {
platformDocumentId = formatPlatformDocumentId(objectClass, objectId, objectAttr)
}
const ydoc = getContext<YDoc>(CollaborationIds.Doc) ?? new YDoc()
const contextProvider = getContext<TiptapCollabProvider>(CollaborationIds.Provider)
const contextProvider = getContext<Provider>(CollaborationIds.Provider)
const localProvider = new IndexeddbProvider(collaborativeDoc, ydoc)
const localProvider = createLocalProvider(ydoc, collaborativeDoc)
const remoteProvider: TiptapCollabProvider =
const remoteProvider =
contextProvider ??
new TiptapCollabProvider({
url: collaboratorURL,
name: documentId,
document: ydoc,
token,
parameters: {
initialContentId,
platformDocumentId
}
createRemoteProvider(ydoc, {
document: collaborativeDoc,
initialDocument: initialCollaborativeDoc,
objectClass,
objectId,
objectAttr
})
let localSynced = false
@@ -478,7 +458,7 @@
} catch (err: any) {}
}
if (contextProvider === undefined) {
remoteProvider.destroy()
void remoteProvider.destroy()
}
void localProvider.destroy()
})