diff --git a/src/application/awareness/dispatch.ts b/src/application/awareness/dispatch.ts index 4ed64c61..46328ca6 100644 --- a/src/application/awareness/dispatch.ts +++ b/src/application/awareness/dispatch.ts @@ -1,5 +1,6 @@ import dayjs from 'dayjs'; -import { useCallback, useRef } from 'react'; +import { debounce } from 'lodash-es'; +import { useCallback, useMemo, useRef } from 'react'; import { Editor } from 'slate'; import { Awareness } from 'y-protocols/awareness'; @@ -105,6 +106,10 @@ export function useDispatchCursorAwareness(awareness?: Awareness) { } }, [awareness]); + const debounceSyncCursor = useMemo(() => { + return debounce(syncCursor, 100); + }, [syncCursor]); + const dispatchCursor = useCallback( (userParams: UserAwarenessParams, editor?: Editor) => { if (!awareness || !editor) return; @@ -114,45 +119,9 @@ export function useDispatchCursorAwareness(awareness?: Awareness) { editorRef.current = editor; // Initial sync - syncCursor(); - - // Log cursor awareness setup - console.log('๐Ÿš€ Cursor awareness set up for user:', { - uid: userParams.uid, - device_id: userParams.device_id, - user_name: userParams.user_name, - }); - - // Set up onChange handler with debounce - let timeoutId: NodeJS.Timeout; - - const originalOnChange = editor.onChange; - - editor.onChange = (value) => { - // Call original onChange - if (originalOnChange) { - originalOnChange(value); - } - - // For selection changes, we just need to sync the cursor - // The onChange is called whenever the editor state changes - clearTimeout(timeoutId); - timeoutId = setTimeout(() => { - syncCursor(); - }, 100); - }; - - // Return cleanup function - return () => { - clearTimeout(timeoutId); - // Restore original onChange - editor.onChange = originalOnChange; - - // Log cleanup - console.log('๐Ÿงน Cursor awareness cleaned up for user:', userParams.uid); - }; + debounceSyncCursor(); }, - [awareness, syncCursor] + [awareness, debounceSyncCursor] ); return dispatchCursor; diff --git a/src/application/awareness/selector.ts b/src/application/awareness/selector.ts index 320399c3..fe7a419b 100644 --- a/src/application/awareness/selector.ts +++ b/src/application/awareness/selector.ts @@ -18,9 +18,6 @@ export function useUsersSelector(awareness?: Awareness) { const renderUsers = () => { const states = awareness?.getStates() as Map; - // Log the raw awareness states map for debugging - console.log('๐Ÿ‘ฅ Raw awareness states map:', states); - const users: AwarenessUser[] = []; states?.forEach((state) => { @@ -45,7 +42,7 @@ export function useUsersSelector(awareness?: Awareness) { uniqBy( users.sort((a, b) => b.timestamp - a.timestamp), 'uid' - ) + ).filter((user) => !!user.name) ); }; @@ -66,10 +63,6 @@ export function useRemoteSelectionsSelector(awareness?: Awareness) { useEffect(() => { const renderCursors = () => { const states = awareness?.getStates() as Map; - - // Log the raw awareness states map for cursor processing - console.log('๐ŸŽฏ Raw awareness states map for cursors:', states); - const cursors: Cursor[] = []; states?.forEach((state, clientId) => { diff --git a/src/application/database-yjs/context.ts b/src/application/database-yjs/context.ts index 4763e34d..b64dfad4 100644 --- a/src/application/database-yjs/context.ts +++ b/src/application/database-yjs/context.ts @@ -53,6 +53,7 @@ export interface DatabaseContextState { loadDatabasePrompts?: LoadDatabasePrompts; testDatabasePromptConfig?: TestDatabasePromptConfig; requestInstance?: AxiosInstance | null; + checkIfRowDocumentExists?: (documentId: string) => Promise; } export const DatabaseContext = createContext(null); diff --git a/src/application/database-yjs/group.ts b/src/application/database-yjs/group.ts index e6ff8692..e4590094 100644 --- a/src/application/database-yjs/group.ts +++ b/src/application/database-yjs/group.ts @@ -135,7 +135,7 @@ export function groupBySelectOption( return; } - const cellData = getCellData(row.id, fieldId, rowMetas); + const cellData = getCellData(row.id, fieldId, rowMetas) || ''; const selectedIds = (cellData as string)?.split(',') ?? []; diff --git a/src/application/database-yjs/selector.ts b/src/application/database-yjs/selector.ts index 4d2ea38b..2fb9b5e0 100644 --- a/src/application/database-yjs/selector.ts +++ b/src/application/database-yjs/selector.ts @@ -584,6 +584,7 @@ export function useRowsByGroup(groupId: string) { const { columns, fieldId } = useGroup(groupId); const rows = useRowDocMap(); const rowOrders = useRowOrdersSelector(); + const [visibleColumns, setVisibleColumns] = useState([]); const fields = useDatabaseFields(); @@ -607,7 +608,6 @@ export function useRowsByGroup(groupId: string) { return; } - const fieldType = Number(field.get(YjsDatabaseKey.type)) as FieldType; if (![FieldType.SingleSelect, FieldType.MultiSelect, FieldType.Checkbox].includes(fieldType)) { @@ -615,7 +615,7 @@ export function useRowsByGroup(groupId: string) { setGroupResult(newResult); return; } - + const filter = filters?.toArray().find((filter) => filter.get(YjsDatabaseKey.field_id) === fieldId); const groupResult = groupByField(rowOrders, rows, field, filter); @@ -632,9 +632,24 @@ export function useRowsByGroup(groupId: string) { fields.observeDeep(onConditionsChange); filters?.observeDeep(onConditionsChange); + + const debouncedConditionsChange = debounce(onConditionsChange, 150); + + const observerRowsEvent = () => { + debouncedConditionsChange(); + }; + + Object.values(rows).forEach((row) => { + row.getMap(YjsEditorKey.data_section).observeDeep(observerRowsEvent); + }); return () => { + debouncedConditionsChange.cancel(); + fields.unobserveDeep(onConditionsChange); filters?.unobserveDeep(onConditionsChange); + Object.values(rows).forEach((row) => { + row.getMap(YjsEditorKey.data_section).unobserveDeep(observerRowsEvent); + }); }; }, [fieldId, fields, rowOrders, rows, filters]); diff --git a/src/application/services/js-services/http/http_api.ts b/src/application/services/js-services/http/http_api.ts index 5db0dd48..70c289c3 100644 --- a/src/application/services/js-services/http/http_api.ts +++ b/src/application/services/js-services/http/http_api.ts @@ -2135,3 +2135,21 @@ export async function addRecentPages(workspaceId: string, viewIds: string[]) { return Promise.reject(response?.data); } + +export async function checkIfCollabExists(workspaceId: string, objectId: string) { + const url = `/api/workspace/${workspaceId}/collab/${objectId}/row-document-collab-exists`; + + const response = await axiosInstance?.get<{ + code: number; + message: string; + data: { + exists: boolean; + }; + }>(url); + + if (response?.data.code === 0 && response?.data.data.exists === true) { + return; + } + + return Promise.reject(response?.data); +} diff --git a/src/application/services/js-services/index.ts b/src/application/services/js-services/index.ts index 38af85d3..8ef241b8 100644 --- a/src/application/services/js-services/index.ts +++ b/src/application/services/js-services/index.ts @@ -201,6 +201,10 @@ export class AFClientService implements AFService { return deleteRowDoc(rowKey); } + async checkIfCollabExists(workspaceId: string, objectId: string) { + return APIService.checkIfCollabExists(workspaceId, objectId); + } + async getAppDatabaseViewRelations(workspaceId: string, databaseStorageId: string) { const res = await APIService.getCollab(workspaceId, databaseStorageId, Types.WorkspaceDatabase); const doc = new Y.Doc(); diff --git a/src/application/services/services.type.ts b/src/application/services/services.type.ts index 361fcf22..4b091c11 100644 --- a/src/application/services/services.type.ts +++ b/src/application/services/services.type.ts @@ -1,57 +1,58 @@ -import { - Invitation, - DuplicatePublishView, - FolderView, - User, - UserWorkspaceInfo, - View, - Workspace, - YDoc, - DatabaseRelations, - GetRequestAccessInfoResponse, - Subscriptions, - SubscriptionPlan, - SubscriptionInterval, - UpdatePagePayload, - CreatePagePayload, - CreateSpacePayload, - UpdateSpacePayload, - WorkspaceMember, - QuickNoteEditorData, - QuickNote, - Subscription, - CreateWorkspacePayload, - UpdateWorkspacePayload, - PublishViewPayload, - UploadPublishNamespacePayload, - UpdatePublishConfigPayload, - CreateFolderViewPayload, - GenerateAISummaryRowPayload, - GenerateAITranslateRowPayload, - GuestInvitation, - GuestConversionCodeInfo, - MentionablePerson, -} from '@/application/types'; +import { RepeatedChatMessage } from '@appflowyinc/ai-chat'; +import { AxiosInstance } from 'axios'; + import { GlobalComment, Reaction } from '@/application/comment.type'; import { ViewMeta } from '@/application/db/tables/view_metas'; import { Template, TemplateCategory, TemplateCategoryFormValues, - TemplateCreator, TemplateCreatorFormValues, TemplateSummary, + TemplateCreator, + TemplateCreatorFormValues, + TemplateSummary, UploadTemplatePayload, } from '@/application/template.type'; -import { AxiosInstance } from 'axios'; -import { RepeatedChatMessage } from '@appflowyinc/ai-chat'; +import { + CreateFolderViewPayload, + CreatePagePayload, + CreateSpacePayload, + CreateWorkspacePayload, + DatabaseRelations, + DuplicatePublishView, + FolderView, + GenerateAISummaryRowPayload, + GenerateAITranslateRowPayload, + GetRequestAccessInfoResponse, + GuestConversionCodeInfo, + GuestInvitation, + Invitation, + MentionablePerson, + PublishViewPayload, + QuickNote, + QuickNoteEditorData, + Subscription, + SubscriptionInterval, + SubscriptionPlan, + Subscriptions, + UpdatePagePayload, + UpdatePublishConfigPayload, + UpdateSpacePayload, + UpdateWorkspacePayload, + UploadPublishNamespacePayload, + User, + UserWorkspaceInfo, + View, + Workspace, + WorkspaceMember, + YDoc, +} from '@/application/types'; -export type AFService = - PublishService - & AppService - & WorkspaceService - & TemplateService - & QuickNoteService - & AIChatService - & { +export type AFService = PublishService & + AppService & + WorkspaceService & + TemplateService & + QuickNoteService & + AIChatService & { getClientId: () => number; getDeviceId: () => string; getAxiosInstance: () => AxiosInstance | null; @@ -85,9 +86,7 @@ export interface WorkspaceService { } export interface AppService { - getPageDoc: (workspaceId: string, viewId: string, errorCallback?: (error: { - code: number; - }) => void) => Promise; + getPageDoc: (workspaceId: string, viewId: string, errorCallback?: (error: { code: number }) => void) => Promise; createRowDoc: (rowKey: string) => Promise; deleteRowDoc: (rowKey: string) => void; getAppDatabaseViewRelations: (workspaceId: string, databaseStorageId: string) => Promise; @@ -98,7 +97,12 @@ export interface AppService { getAppTrash: (workspaceId: string) => Promise; loginAuth: (url: string) => Promise; signInMagicLink: (params: { email: string; redirectTo: string }) => Promise; - signInOTP: (params: { email: string; code: string; redirectTo: string; type?: 'magiclink' | 'recovery' }) => Promise; + signInOTP: (params: { + email: string; + code: string; + redirectTo: string; + type?: 'magiclink' | 'recovery'; + }) => Promise; signInWithPassword: (params: { email: string; password: string; redirectTo: string }) => Promise; forgotPassword: (params: { email: string }) => Promise; changePassword: (params: { password: string }) => Promise; @@ -131,7 +135,12 @@ export interface AppService { moveToTrash: (workspaceId: string, viewId: string) => Promise; restoreFromTrash: (workspaceId: string, viewId?: string) => Promise; movePage: (workspaceId: string, viewId: string, parentId: string, prevViewId?: string) => Promise; - uploadFile: (workspaceId: string, viewId: string, file: File, onProgress?: (progress: number) => void) => Promise; + uploadFile: ( + workspaceId: string, + viewId: string, + file: File, + onProgress?: (progress: number) => void + ) => Promise; duplicateAppPage: (workspaceId: string, viewId: string) => Promise; joinWorkspaceByInvitationCode: (code: string) => Promise; getWorkspaceInfoByInvitationCode: (code: string) => Promise<{ @@ -146,14 +155,18 @@ export interface AppService { generateAISummaryForRow: (workspaceId: string, payload: GenerateAISummaryRowPayload) => Promise; generateAITranslateForRow: (workspaceId: string, payload: GenerateAITranslateRowPayload) => Promise; createOrphanedView: (workspaceId: string, payload: { document_id: string }) => Promise; + checkIfCollabExists: (workspaceId: string, objectId: string) => Promise; } export interface QuickNoteService { - getQuickNoteList: (workspaceId: string, params: { - offset?: number; - limit?: number; - searchTerm?: string; - }) => Promise<{ + getQuickNoteList: ( + workspaceId: string, + params: { + offset?: number; + limit?: number; + searchTerm?: string; + } + ) => Promise<{ data: QuickNote[]; has_more: boolean; }>; @@ -170,10 +183,7 @@ export interface TemplateService { createTemplateCreator: (creator: TemplateCreatorFormValues) => Promise; deleteTemplateCreator: (creatorId: string) => Promise; getTemplateById: (id: string) => Promise