From 77730b07caab723f6da439c43b27d115274d4d9f Mon Sep 17 00:00:00 2001 From: arpitnath Date: Thu, 7 Sep 2023 13:18:13 +0530 Subject: [PATCH] fixes/multi-editor: users should be able to edit different version of the app at real time without sync --- frontend/src/Editor/EditorFunc.jsx | 35 +++++++++++-------------- frontend/src/Editor/RealtimeAvatars.jsx | 14 +++++++++- frontend/src/_stores/appDataStore.js | 1 + 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/frontend/src/Editor/EditorFunc.jsx b/frontend/src/Editor/EditorFunc.jsx index 326043e817..68341f46dc 100644 --- a/frontend/src/Editor/EditorFunc.jsx +++ b/frontend/src/Editor/EditorFunc.jsx @@ -125,6 +125,7 @@ const EditorComponent = (props) => { appDefinitionDiff, appDiffOptions, events, + areOthersOnSameVersionAndPage, } = useAppInfo(); const [currentPageId, setCurrentPageId] = useState(null); @@ -283,30 +284,32 @@ const EditorComponent = (props) => { updateEditorState({ canUndo: false, canRedo: false, - // currentVersion: uuid(), }); }; /** - * When a new update is received over-the-websocket connection - * the useEffect in Container.jsx is triggered, but already appDef had been updated - * to avoid ymap observe going into a infinite loop a check is added where if the - * current appDef is equal to the newAppDef then we do not trigger a realtimeSave + * Initializes real-time saving of application definitions if multiplayer editing is enabled. + * Monitors changes in the 'appDef' property of the provided 'ymap' object and triggers a real-time save + * when all conditions are met. */ const initRealtimeSave = () => { + // Check if multiplayer editing is enabled; if not, return early if (!config.ENABLE_MULTIPLAYER_EDITING) return null; + // Observe changes in the 'appDef' property of the 'ymap' object props.ymap?.observe(() => { const ymapUpdates = props.ymap?.get('appDef'); + // Check if there is a new session and if others are on the same version and page if (!ymapUpdates.currentSessionId || ymapUpdates.currentSessionId === currentSessionId) return; - // if (!isEqual(props.editingVersion?.id, props.ymap?.get('appDef').editingVersionId)) return; - if (isEqual(appDefinition, ymapUpdates.newDefinition)) return; - console.log('-----arpit real time ', { - x: ymapUpdates.currentSessionId, - y: currentSessionId, - }); + // Check if others are on the same version and page + if (!ymapUpdates.areOthersOnSameVersionAndPage) return; + + // Check if the new application definition is different from the current one + if (isEqual(appDefinition, ymapUpdates.newDefinition)) return; + + // Trigger real-time save with specific options realtimeSave(props.ymap?.get('appDef').newDefinition, { skipAutoSave: true, skipYmapUpdate: true, @@ -741,13 +744,6 @@ const EditorComponent = (props) => { }; const appDefinitionChanged = async (newDefinition, opts = {}) => { - if (config.ENABLE_MULTIPLAYER_EDITING && !opts.skipYmapUpdate) { - props.ymap?.set('appDef', { - newDefinition, - editingVersionId: props.editingVersion?.id, - }); - } - if (opts?.versionChanged) { setCurrentPageId(newDefinition.homePageId); @@ -812,11 +808,12 @@ const EditorComponent = (props) => { computeComponentState(updatedAppDefinition.pages[currentPageId]?.components); } - if (!opts?.skipYmapUpdate && opts?.currentSessionId !== currentSessionId) { + if (config.ENABLE_MULTIPLAYER_EDITING && !opts?.skipYmapUpdate && opts?.currentSessionId !== currentSessionId) { props.ymap?.set('appDef', { newDefinition: updatedAppDefinition, editingVersionId: props.editingVersion?.id, currentSessionId, + areOthersOnSameVersionAndPage, }); } }; diff --git a/frontend/src/Editor/RealtimeAvatars.jsx b/frontend/src/Editor/RealtimeAvatars.jsx index 2fb732143c..4008f84f31 100644 --- a/frontend/src/Editor/RealtimeAvatars.jsx +++ b/frontend/src/Editor/RealtimeAvatars.jsx @@ -1,8 +1,9 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import Popover from '@/_ui/Popover'; import Avatar from '@/_ui/Avatar'; // eslint-disable-next-line import/no-unresolved import { useOthers, useSelf } from '@y-presence/react'; +import { useAppDataActions, useAppInfo } from '@/_stores/appDataStore'; const MAX_DISPLAY_USERS = 2; const RealtimeAvatars = ({ darkMode }) => { @@ -17,6 +18,17 @@ const RealtimeAvatars = ({ darkMode }) => { const getAvatarText = (presence) => presence.firstName?.charAt(0) + presence.lastName?.charAt(0); const getAvatarTitle = (presence) => `${presence.firstName} ${presence.lastName}`; + const { updateState } = useAppDataActions(); + const { areOthersOnSameVersionAndPage, currentVersionId } = useAppInfo(); + + useEffect(() => { + const areActiveUsersOnSameVersionAndPage = othersOnSameVersionAndPage.length > 0; + const shouldUpdateState = areActiveUsersOnSameVersionAndPage !== areOthersOnSameVersionAndPage; + + if (shouldUpdateState) updateState({ areOthersOnSameVersionAndPage: areActiveUsersOnSameVersionAndPage }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify({ others, self, currentVersionId })]); + const popoverContent = () => { return othersOnSameVersionAndPage .slice(MAX_DISPLAY_USERS, othersOnSameVersionAndPage.length) diff --git a/frontend/src/_stores/appDataStore.js b/frontend/src/_stores/appDataStore.js index 85c763ccd5..d3eadee414 100644 --- a/frontend/src/_stores/appDataStore.js +++ b/frontend/src/_stores/appDataStore.js @@ -22,6 +22,7 @@ const initialState = { appDiffOptions: {}, isSaving: false, appId: null, + areOthersOnSameVersionAndPage: false, }; export const useAppDataStore = create(