diff --git a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx index 04f8daf553..8ab7fdf403 100644 --- a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx +++ b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx @@ -351,7 +351,7 @@ export function CodeHinter({ height={'100%'} onFocus={() => setFocused(true)} onBlur={(editor, e) => { - e.stopPropagation(); + e?.stopPropagation(); const value = editor.getValue()?.trimEnd(); onChange(value); if (!isPreviewFocused.current) { diff --git a/frontend/src/Editor/Container.jsx b/frontend/src/Editor/Container.jsx index 6e573c7e4a..126f2485ce 100644 --- a/frontend/src/Editor/Container.jsx +++ b/frontend/src/Editor/Container.jsx @@ -57,8 +57,9 @@ export const Container = ({ const components = useMemo( () => appDefinition.pages[currentPageId]?.components ?? {}, // eslint-disable-next-line react-hooks/exhaustive-deps - [appDefinition.pages[currentPageId]] + [JSON.stringify(appDefinition)] ); + const currentState = useCurrentState(); const { appVersionsId, enableReleasedVersionPopupState, isVersionReleased } = useAppVersionStore( (state) => ({ @@ -180,7 +181,7 @@ export const Container = ({ }, }; - appDefinitionChanged(newDefinition); + appDefinitionChanged(newDefinition, { containerChanges: true }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [boxes]); diff --git a/frontend/src/Editor/EditorFunc.jsx b/frontend/src/Editor/EditorFunc.jsx index af72c4174a..4280cf6b07 100644 --- a/frontend/src/Editor/EditorFunc.jsx +++ b/frontend/src/Editor/EditorFunc.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { appService, authenticationService, appVersionService, orgEnvironmentVariableService } from '@/_services'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; -import _, { defaults, cloneDeep, isEqual, isEmpty, debounce, omit, update } from 'lodash'; +import _, { defaults, cloneDeep, isEqual, isEmpty, debounce, omit, update, difference } from 'lodash'; import { Container } from './Container'; import { EditorKeyHooks } from './EditorKeyHooks'; import { CustomDragLayer } from './CustomDragLayer'; @@ -53,6 +53,8 @@ import { shallow } from 'zustand/shallow'; import { useEditorActions, useEditorState, useEditorStore } from '@/_stores/editorStore'; import { useAppDataActions, useAppDataStore, useAppInfo } from '@/_stores/appDataStore'; import { useMounted } from '@/_hooks/use-mount'; +// eslint-disable-next-line import/no-unresolved +import { diff } from 'deep-object-diff'; setAutoFreeze(false); enablePatches(); @@ -132,6 +134,8 @@ const EditorComponent = (props) => { const selectionDragRef = useRef(null); const selectionRef = useRef(null); + const prevAppDefinition = useRef(appDefinition); + useLayoutEffect(() => { resetAllStores(); }, []); @@ -181,12 +185,12 @@ const EditorComponent = (props) => { subscription.unsubscribe(); if (config.ENABLE_MULTIPLAYER_EDITING) props?.provider?.disconnect(); useEditorStore.getState().actions.setIsEditorActive(false); + prevAppDefinition.current = null; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Ref to store the previous appDefinition for comparison - const prevAppDefinition = useRef(appDefinition); useEffect(() => { if (currentUser?.current_organization_id) { @@ -198,9 +202,17 @@ const EditorComponent = (props) => { useEffect(() => { const didAppDefinitionChanged = !_.isEqual(appDefinition, prevAppDefinition.current); - console.log('---arpit-- appDefinitionChanged', { dataQueries, didAppDefinitionChanged, appDefinition }); + console.log('---arpit-- appDefinitionChanged [useEffect]', { didAppDefinitionChanged, isSaving }); + + if (didAppDefinitionChanged) { + console.log('---arpit-- updating [prevAppDefinition] [useEffect]', { + prev: prevAppDefinition.current, + curr: appDefinition, + }); + prevAppDefinition.current = appDefinition; + } + if (mounted && didAppDefinitionChanged && currentPageId) { - console.log('---arpit-- appDefinitionChanged [mounted=true]', { appDefinition }); const components = appDefinition?.pages[currentPageId]?.components || {}; computeComponentState(components); @@ -209,7 +221,6 @@ const EditorComponent = (props) => { autoSave(); } } - prevAppDefinition.current = appDefinition; }, [JSON.stringify({ appDefinition, currentPageId, dataQueries })]); const editorRef = { @@ -298,7 +309,7 @@ const EditorComponent = (props) => { }; const $componentDidMount = async () => { - console.log('---arpit-- componentDidMounted effect', { appDefinition, currentPageId }); + // console.log('---arpit-- componentDidMounted effect', { appDefinition, currentPageId }); window.addEventListener('message', handleMessage); // autoSave(); @@ -612,13 +623,16 @@ const EditorComponent = (props) => { await fetchDataSources(data.editing_version?.id); await fetchDataQueries(data.editing_version?.id, true, true); - // let dataDefinition = defaults(data.definition, defaultDefinition(props.darkMode)); let dataDefinition = data.definition ?? defaults(data.definition, defaultDefinition(props.darkMode)); - console.log('---arpit-- fetching app data', { startingPageHandle, data, dataDefinition }); const pages = Object.entries(dataDefinition.pages).map(([pageId, page]) => ({ id: pageId, ...page })); const startingPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id; - const homePageId = startingPageId || dataDefinition.homePageId; + const homePageId = !startingPageHandle || startingPageId === 'null' ? dataDefinition.homePageId : startingPageId; + + console.log('---arpit-- fetching app data', { + data, + dataDefinition, + }); setCurrentPageId(homePageId); @@ -647,11 +661,6 @@ const EditorComponent = (props) => { appDefinition: dataDefinition, }); - appDefinitionChanged(dataDefinition, { - skipAutoSave: true, - skipYmapUpdate: true, - }); - for (const event of dataDefinition.pages[homePageId]?.events ?? []) { await handleEvent(event.eventId, event); } @@ -670,6 +679,7 @@ const EditorComponent = (props) => { // !-------- const setAppDefinitionFromVersion = (version, shouldWeEditVersion = true) => { + console.log('---arpit [setAppFromVersion]--', version); if (version?.id !== props.editingVersion?.id) { appDefinitionChanged(defaults(version.definition, defaultDefinition(props.darkMode)), { skipAutoSave: true, @@ -696,8 +706,11 @@ const EditorComponent = (props) => { }; const appDefinitionChanged = (newDefinition, opts = {}) => { - console.log('--arpit | appDefinitionChanged func()'); - if (_.isEqual(appDefinition, newDefinition)) return; + console.log('--arpit | appDefinitionChanged func() called', { + opts, + }); + + // if (_.isEqual(prevAppDefinition.current, newDefinition)) return; if (config.ENABLE_MULTIPLAYER_EDITING && !opts.skipYmapUpdate) { props.ymap?.set('appDef', { newDefinition, @@ -711,40 +724,44 @@ const EditorComponent = (props) => { return new Promise((resolve) => { updateEditorState({ isSaving: true, - appDefinition: newDefinition, - // appDefinitionLocalVersion: uuid(), }); - // if (!opts.skipAutoSave) autoSave(); - resolve(); }); } // Create a new copy of appDefinition with lodash's cloneDeep const updatedAppDefinition = _.cloneDeep(appDefinition); + + if (_.isEmpty(updatedAppDefinition)) return; + const currentPageComponents = newDefinition.pages[currentPageId]?.components; - if (!updatedAppDefinition.pages[currentPageId]) { - updatedAppDefinition.pages[currentPageId] = {}; // Create a new object for the page if it doesn't exist - } + updatedAppDefinition.pages[currentPageId].components = currentPageComponents; - updatedAppDefinition.pages[currentPageId].components = currentPageComponents || {}; + const diffPatches = diff(appDefinition, updatedAppDefinition); - // Call handleAddPatch with the list of changes (if needed) - handleAddPatch(); - - // Update the editor state with the new appDefinition - updateEditorState({ - isSaving: true, - appDefinition: updatedAppDefinition, - // appDefinitionLocalVersion: uuid(), + console.log('--arpit | appDefinitionChanged func() | diffPatches', { + // appDefinition, + // updatedAppDefinition, + diffPatches, }); + if (!_.isEmpty(diffPatches)) { + updateEditorState({ + isSaving: true, + appDefinition: updatedAppDefinition, + }); + computeComponentState(updatedAppDefinition.pages[currentPageId]?.components); + } else { + toast.error('No changes in diff [appDefinitionChanged]'); + } + // if (!opts.skipAutoSave) autoSave(); }; const saveEditingVersion = (isUserSwitchedVersion = false) => { + console.log('---arpit [saving - editionversion]--'); if (props.isVersionReleased && !isUserSwitchedVersion) { updateEditorState({ isSaving: false, @@ -875,7 +892,6 @@ const EditorComponent = (props) => { }; const componentDefinitionChanged = (componentDefinition, props) => { - console.log('---arpit [componentDefinitionChanged]', { props, componentDefinition }); if (props?.isVersionReleased) { useAppVersionStore.getState().actions.enableReleasedVersionPopupState(); return; @@ -889,24 +905,31 @@ const EditorComponent = (props) => { updatedAppDefinition.pages[currentPageId].components[componentDefinition.id].component = componentDefinition.component; - // Call handleAddPatch with the list of changes (if needed) - handleAddPatch(); - - // Update the editor state with the new appDefinition + // // Update the editor state with the new appDefinition updateEditorState({ isSaving: true, - appDefinition: updatedAppDefinition, - // appDefinitionLocalVersion: uuid() }); - // Other actions can be performed here if needed, like autoSave, ymap, etc. - computeComponentState(updatedAppDefinition.pages[currentPageId]?.components); - // autoSave(); - props.ymap?.set('appDef', { - newDefinition: updatedAppDefinition, - editingVersionId: props.editingVersion?.id, + const diffPatches = diff(appDefinition, updatedAppDefinition); + console.log('---arpit [componentDefinitionChanged]', { + props, + diffPatches, }); + + if (!isEmpty(diffPatches)) { + // handleAddPatch(diffPatches, diff(updatedAppDefinition, appDefinition)); + appDefinitionChanged(updatedAppDefinition, { skipAutoSave: true, componentDefinitionChanged: true }); + } } + + // // Other actions can be performed here if needed, like autoSave, ymap, etc. + // // computeComponentState(updatedAppDefinition.pages[currentPageId]?.components); + // // autoSave(); + // // props.ymap?.set('appDef', { + // // newDefinition: updatedAppDefinition, + // // editingVersionId: props.editingVersion?.id, + // // }); + // } }; const removeComponent = (component) => { @@ -1168,10 +1191,6 @@ const EditorComponent = (props) => { width: currentLayout === 'desktop' ? '100%' : '450px', maxWidth: +appDefinition.globalSettings.canvasMaxWidth + appDefinition.globalSettings.canvasMaxWidthType, - /** - * minWidth will be min(default canvas min width, user set max width). Done to avoid conflict between two - * default canvas min width = calc(((screen width - width component editor side bar) - width of editor sidebar on left) - width of left sidebar popover) - **/ backgroundColor: computeCanvasBackgroundColor(), transform: 'translateZ(0)', //Hack to make modal position respect canvas container, else it positions w.r.t window. @@ -1278,7 +1297,6 @@ const EditorComponent = (props) => { key={selectedComponents[0].id} switchSidebarTab={switchSidebarTab} darkMode={props.darkMode} - // appDefinitionLocalVersion={appDefinitionLocalVersion} pages={getPagesWithIds()} > ) : ( diff --git a/frontend/src/Editor/Inspector/Inspector.jsx b/frontend/src/Editor/Inspector/Inspector.jsx index e5eb84f818..01928d1e98 100644 --- a/frontend/src/Editor/Inspector/Inspector.jsx +++ b/frontend/src/Editor/Inspector/Inspector.jsx @@ -99,9 +99,9 @@ export const Inspector = ({ return setInputFocus(); } if (validateQueryName(newName)) { - let newComponent = { ...component }; + let newComponent = JSON.parse(JSON.stringify(component)); newComponent.component.name = newName; - componentDefinitionChanged(newComponent); + componentDefinitionChanged(newComponent, { componentNameUpdated: true }); } else { toast.error( t( @@ -150,7 +150,7 @@ export const Inspector = ({ definition: newDefinition, }, }); - componentDefinitionChanged(newComponent); + componentDefinitionChanged(newComponent, { componentPropertyUpdated: true }); } function layoutPropertyChanged(param, attr, value, paramType) { @@ -158,9 +158,7 @@ export const Inspector = ({ // User wants to show the widget on mobile devices if (param.name === 'showOnMobile' && value === true) { - let newComponent = { - ...component, - }; + let newComponent = JSON.parse(JSON.stringify(component)); const { width, height } = newComponent.layouts['desktop']; @@ -174,7 +172,7 @@ export const Inspector = ({ }, }; - componentDefinitionChanged(newComponent); + componentDefinitionChanged(newComponent, { layoutPropertyChanged: true }); // Child components should also have a mobile layout const childComponents = Object.keys(allComponents).filter((key) => allComponents[key].parent === component.id); @@ -197,29 +195,29 @@ export const Inspector = ({ }, }; - componentDefinitionChanged(newChild); + componentDefinitionChanged(newChild, { withChildLayout: true }); }); } } function eventUpdated(event, actionId) { - let newDefinition = { ...component.component.definition }; + let newDefinition = JSON.parse(JSON.stringify(component.component.definition)); newDefinition.events[event.name] = { actionId }; let newComponent = { ...component, }; - componentDefinitionChanged(newComponent); + componentDefinitionChanged(newComponent, { eventUpdated: true }); } function eventsChanged(newEvents, isReordered = false) { let newDefinition; if (isReordered) { - newDefinition = { ...component.component }; + newDefinition = JSON.parse(JSON.stringify(component.component)); newDefinition.definition.events = newEvents; } else { - newDefinition = { ...component.component.definition }; + newDefinition = JSON.parse(JSON.stringify(component.component.definition)); newDefinition.events = newEvents; } @@ -227,13 +225,13 @@ export const Inspector = ({ ...component, }; - componentDefinitionChanged(newComponent); + componentDefinitionChanged(newComponent, { eventsChanged: true }); } function eventOptionUpdated(event, option, value) { console.log('eventOptionUpdated', event, option, value); - let newDefinition = { ...component.component.definition }; + let newDefinition = JSON.parse(JSON.stringify(component.component.definition)); let eventDefinition = newDefinition.events[event.name] || { options: {} }; newDefinition.events[event.name] = { ...eventDefinition, options: { ...eventDefinition.options, [option]: value } }; @@ -242,7 +240,7 @@ export const Inspector = ({ ...component, }; - componentDefinitionChanged(newComponent); + componentDefinitionChanged(newComponent, { eventOptionUpdated: true }); } const buildGeneralStyle = () => { diff --git a/frontend/src/Editor/ManageAppUsers.jsx b/frontend/src/Editor/ManageAppUsers.jsx index bb977ea4e8..9d7de6a07d 100644 --- a/frontend/src/Editor/ManageAppUsers.jsx +++ b/frontend/src/Editor/ManageAppUsers.jsx @@ -43,7 +43,8 @@ class ManageAppUsersComponent extends React.Component { ) .catch((error) => { this.setState({ isLoading: false }); - toast.error(error); + const errorMessage = error?.message || 'Something went wrong'; + toast.error(errorMessage); }); }; diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 7e981a29c7..1a97a53e9b 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -1313,7 +1313,7 @@ const updateNewComponents = (pageId, appDefinition, newComponents, updateAppDefi ); newAppDefinition.pages[pageId].components[newComponent.id] = newComponent; }); - updateAppDefinition(newAppDefinition); + updateAppDefinition(newAppDefinition, { addComponents: true }); }; export const cloneComponents = (