From 9b88e9f8b234c4bd5c5416aea5b5afcf0e481b72 Mon Sep 17 00:00:00 2001 From: arpitnath Date: Wed, 3 Apr 2024 11:45:24 +0530 Subject: [PATCH 01/13] fixes: component -extra re-rendering issue on current state changes. --- frontend/src/Editor/BoxUI.jsx | 4 +- frontend/src/Editor/Editor.jsx | 78 ++++++++---- frontend/src/Editor/SubContainer.jsx | 1 - frontend/src/_helpers/appUtils.js | 137 ++++++++++++---------- frontend/src/_helpers/editorHelpers.js | 10 +- frontend/src/_stores/currentStateStore.js | 51 ++------ frontend/src/_stores/editorStore.js | 3 +- 7 files changed, 148 insertions(+), 136 deletions(-) diff --git a/frontend/src/Editor/BoxUI.jsx b/frontend/src/Editor/BoxUI.jsx index 982af034cf..e65ba0e38b 100644 --- a/frontend/src/Editor/BoxUI.jsx +++ b/frontend/src/Editor/BoxUI.jsx @@ -128,8 +128,8 @@ const BoxUI = (props) => { > { if (mounted && didAppDefinitionChanged && currentPageId) { const components = appDefinition?.pages[currentPageId]?.components || {}; - computeComponentState(components); + // computeComponentState(components); if (appDiffOptions?.skipAutoSave === true || appDiffOptions?.entityReferenceUpdated === true) return; @@ -294,20 +294,61 @@ const EditorComponent = (props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify({ appDefinition, currentPageId, dataQueries })]); - const currentStateDiff = useEditorStore.getState().currentStateDiff; - useEffect(() => { - const isEditorReady = useCurrentStateStore.getState().isEditorReady; + const prevCurrentStateRef = useRef(currentState); + + function generatePath(obj, targetKey, currentPath = '') { + for (const key in obj) { + const newPath = currentPath ? currentPath + '.' + key : key; + + if (key === targetKey) { + return newPath; + } + + if (typeof obj[key] === 'object' && obj[key] !== null) { + const result = generatePath(obj[key], targetKey, newPath); + if (result) { + return result; + } + } + } + return null; + } + + useEffect(() => { + const diffState = diff(prevCurrentStateRef.current, currentState); + + if (Object.keys(diffState).length > 0) { + const entitiesToTrack = ['queries', 'components', 'variables', 'page', 'constants', 'layout']; + + const entitiesChanged = Object.keys(diffState).filter((entity) => entitiesToTrack.includes(entity)); + + const diffObj = entitiesChanged.reduce((acc, entity) => { + acc[entity] = diffState[entity]; + return acc; + }, {}); + + const allPaths = entitiesChanged.reduce((acc, entity) => { + const paths = Object.keys(diffObj[entity]).map((key) => { + return generatePath(diffObj[entity], key); + }); + + acc[entity] = paths.map((path) => `${entity}.${path}`); + return acc; + }, {}); + + const currentStatePaths = Object.values(allPaths).flat(); - if (isEditorReady && currentStateDiff?.length > 0) { const currentComponents = useEditorStore.getState().appDefinition?.pages?.[currentPageId]?.components || {}; - const componentIdsWithReferences = findComponentsWithReferences(currentComponents, currentStateDiff); + const componentIdsWithReferences = findComponentsWithReferences(currentComponents, currentStatePaths); if (componentIdsWithReferences.length > 0) { updateComponentsNeedsUpdateOnNextRender(componentIdsWithReferences); } + + prevCurrentStateRef.current = currentState; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(currentStateDiff)]); + }, [JSON.stringify(currentState)]); useEffect( () => { @@ -345,14 +386,6 @@ const EditorComponent = (props) => { } }, [currentLayout, mounted]); - const handleYmapEventUpdates = () => { - props.ymap?.set('eventHandlersUpdated', { - currentVersionId: currentVersionId, - currentSessionId: currentSessionId, - update: true, - }); - }; - const handleMessage = (event) => { const { data } = event; @@ -743,13 +776,12 @@ const EditorComponent = (props) => { useCurrentStateStore.getState().actions.setCurrentState({ page: currentpageData, - }); - - updateEditorState({ - isLoading: false, - appDefinition: appJson, - isUpdatingEditorStateInProcess: false, - }); + }), + updateEditorState({ + isLoading: false, + appDefinition: appJson, + isUpdatingEditorStateInProcess: false, + }); updateState({ components: appJson.pages[homePageId]?.components }); @@ -889,6 +921,8 @@ const EditorComponent = (props) => { events: newEvents, }); } + + computeComponentState(currentComponents); }) .finally(async () => { const currentPageEvents = data.events.filter( diff --git a/frontend/src/Editor/SubContainer.jsx b/frontend/src/Editor/SubContainer.jsx index 6e0231abaf..d3e779a1b5 100644 --- a/frontend/src/Editor/SubContainer.jsx +++ b/frontend/src/Editor/SubContainer.jsx @@ -415,7 +415,6 @@ export const SubContainer = ({ if (didDrop && !parent) { return; } - console.log('---arpit::: drop --- on subcontainer', { item, isOver, isOverCurrent, didDrop }); if (item.component.component === 'PDF' && !isPDFSupported()) { toast.error( diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index c521b7ca7f..9923260ce4 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -73,79 +73,85 @@ const debouncedChange = _.debounce(() => { }, 100); export function onComponentOptionsChanged(component, options) { - const componentName = component.name; const { isEditorReady } = getCurrentState(); - if (isEditorReady) { - if (duplicateCurrentState !== null) { - duplicateCurrentState = null; - } + if (!isEditorReady) return Promise.resolve(); - const components = getCurrentState().components; - let componentData = components[componentName]; - componentData = componentData || {}; + const componentName = component.name; - for (const option of options) { - componentData[option[0]] = option[1]; - } + // if (duplicateCurrentState !== null) { + // duplicateCurrentState = null; + // } - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); - } else { - const components = duplicateCurrentState === null ? getCurrentState().components : duplicateCurrentState; - let componentData = components[componentName]; - componentData = componentData || {}; + const components = getCurrentState().components; + let componentData = components[componentName]; + componentData = componentData || {}; - for (const option of options) { - componentData[option[0]] = option[1]; - } - - duplicateCurrentState = { ...components, [componentName]: componentData }; - - debouncedChange(); + for (const option of options) { + componentData[option[0]] = option[1]; } + + useCurrentStateStore.getState().actions.setCurrentState({ + components: { ...components, [componentName]: componentData }, + }); return Promise.resolve(); } +// export function onComponentOptionChanged(component, option_name, value) { +// const componentName = component.name; +// const { isEditorReady } = getCurrentState(); +// if (isEditorReady) { +// if (duplicateCurrentState !== null) { +// duplicateCurrentState = null; +// } +// const components = getCurrentState().components; +// let componentData = components[componentName]; +// componentData = componentData || {}; +// componentData[option_name] = value; + +// if (option_name !== 'id') { +// useCurrentStateStore.getState().actions.setCurrentState({ +// components: { ...components, [componentName]: componentData }, +// }); +// } else if (!componentData?.id) { +// useCurrentStateStore.getState().actions.setCurrentState({ +// components: { ...components, [componentName]: componentData }, +// }); +// } + +// useCurrentStateStore.getState().actions.setCurrentState({ +// components: { ...components, [componentName]: componentData }, +// }); +// } else { +// const components = duplicateCurrentState === null ? getCurrentState().components : duplicateCurrentState; +// let componentData = components[componentName]; +// componentData = componentData || {}; +// componentData[option_name] = value; + +// duplicateCurrentState = { ...components, [componentName]: componentData }; +// if (option_name !== 'id') { +// debouncedChange(); +// } else if (!componentData?.id) { +// debouncedChange(); +// } +// } + +// return Promise.resolve(); +// } + export function onComponentOptionChanged(component, option_name, value) { + const { isEditorReady, components: currentComponents } = getCurrentState(); + + if (!isEditorReady) return Promise.resolve(); + const componentName = component.name; - const { isEditorReady } = getCurrentState(); - if (isEditorReady) { - if (duplicateCurrentState !== null) { - duplicateCurrentState = null; - } - const components = getCurrentState().components; - let componentData = components[componentName]; - componentData = componentData || {}; - componentData[option_name] = value; + const components = duplicateCurrentState === null ? currentComponents : duplicateCurrentState; + let componentData = components[componentName] || {}; + componentData[option_name] = value; - if (option_name !== 'id') { - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); - } else if (!componentData?.id) { - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); - } - - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); - } else { - const components = duplicateCurrentState === null ? getCurrentState().components : duplicateCurrentState; - let componentData = components[componentName]; - componentData = componentData || {}; - componentData[option_name] = value; - - duplicateCurrentState = { ...components, [componentName]: componentData }; - if (option_name !== 'id') { - debouncedChange(); - } else if (!componentData?.id) { - debouncedChange(); - } - } + useCurrentStateStore.getState().actions.setCurrentState({ + components: { ...components, [componentName]: componentData }, + }); return Promise.resolve(); } @@ -1379,11 +1385,14 @@ export function computeComponentState(components = {}) { } }); - useCurrentStateStore.getState().actions.setCurrentState({ - components: { - ...componentState, + useCurrentStateStore.getState().actions.setCurrentState( + { + components: { + ...componentState, + }, }, - }); + 'componentStateComputed' + ); return new Promise((resolve) => { useEditorStore.getState().actions.updateEditorState({ diff --git a/frontend/src/_helpers/editorHelpers.js b/frontend/src/_helpers/editorHelpers.js index 1018b3c4ff..5482c188e2 100644 --- a/frontend/src/_helpers/editorHelpers.js +++ b/frontend/src/_helpers/editorHelpers.js @@ -139,9 +139,13 @@ function findReferenceInComponent(node, changedCurrentState) { const value = node[key]; if (typeof value === 'string' && value.includes('{{') && value.includes('}}')) { // Check if the referenced entity is in the state - if (changedCurrentState.some((state) => value.includes(state))) { - return true; - } + + // check if the current node's value has the reference + const hasFound = changedCurrentState.some((state) => { + return value.includes(state); + }); + + return hasFound; } else if (typeof value === 'object') { const found = findReferenceInComponent(value, changedCurrentState); diff --git a/frontend/src/_stores/currentStateStore.js b/frontend/src/_stores/currentStateStore.js index c7284c5d67..4e10864d06 100644 --- a/frontend/src/_stores/currentStateStore.js +++ b/frontend/src/_stores/currentStateStore.js @@ -1,6 +1,6 @@ import { shallow } from 'zustand/shallow'; import { create, zustandDevTools } from './utils'; -import _, { omit } from 'lodash'; +import _, { debounce, merge, omit } from 'lodash'; import { useResolveStore } from './resolverStore'; // eslint-disable-next-line import/no-unresolved import { diff } from 'deep-object-diff'; @@ -49,46 +49,10 @@ export const useCurrentStateStore = create( (set, get) => ({ ...initialState, actions: { - setCurrentState: (currentState) => { - const currentStateEntites = Object.keys(currentState); + setCurrentState: (currentState, from = null) => { + const newState = merge({}, get(), currentState); - const existingStateOfEntities = currentStateEntites.reduce((acc, entity) => { - acc[entity] = get()[entity]; - return acc; - }, {}); - - const diffState = diff(existingStateOfEntities, currentState); - - if (_.isEmpty(diffState)) return; - - set({ ...currentState }, false, { type: 'SET_CURRENT_STATE', currentState }); - - //need to track only queries, components, variables, page, constants, layout - // from the diff, if any of these entities are changed, we need to update the store - - if (get().isEditorReady) { - const entitiesToTrack = ['queries', 'components', 'variables', 'page', 'constants', 'layout']; - - const entitiesChanged = Object.keys(diffState).filter((entity) => entitiesToTrack.includes(entity)); - - const diffObj = entitiesChanged.reduce((acc, entity) => { - acc[entity] = diffState[entity]; - return acc; - }, {}); - - const allPaths = entitiesChanged.reduce((acc, entity) => { - const paths = Object.keys(diffObj[entity]).map((key) => { - return generatePath(diffObj[entity], key); - }); - - acc[entity] = paths.map((path) => `${entity}.${path}`).join(','); - return acc; - }, {}); - - const currentStatePaths = Object.values(allPaths); - - useEditorStore.getState().actions.updateCurrentStateDiff(currentStatePaths); - } + set({ ...newState }); }, setErrors: (error) => { set({ errors: { ...get().errors, ...error } }, false, { type: 'SET_ERRORS', error }); @@ -100,9 +64,9 @@ export const useCurrentStateStore = create( ) ); -export const useCurrentState = () => +export const useCurrentState = () => { // Omitting 'actions' here because we don't want to expose it to user - useCurrentStateStore((state) => { + const currentState = useCurrentStateStore((state) => { return { queries: state.queries, components: state.components, @@ -118,6 +82,9 @@ export const useCurrentState = () => }; }, shallow); + return JSON.parse(JSON.stringify(currentState)); +}; + useCurrentStateStore.subscribe((state) => { const isEditorReady = state.isEditorReady; diff --git a/frontend/src/_stores/editorStore.js b/frontend/src/_stores/editorStore.js index 042b230fb5..199feaef82 100644 --- a/frontend/src/_stores/editorStore.js +++ b/frontend/src/_stores/editorStore.js @@ -36,7 +36,6 @@ const initialState = { queryConfirmationList: [], currentPageId: null, currentSessionId: uuid(), - currentStateDiff: [], componentsNeedsUpdateOnNextRender: [], }; @@ -63,7 +62,7 @@ export const useEditorStore = create( }, setIsEditorActive: (isEditorActive) => set(() => ({ isEditorActive })), updateEditorState: (state) => set((prev) => ({ ...prev, ...state })), - updateCurrentStateDiff: (currentStateDiff) => set(() => ({ currentStateDiff })), + updateComponentsNeedsUpdateOnNextRender: (componentsNeedsUpdateOnNextRender) => set(() => ({ componentsNeedsUpdateOnNextRender })), flushComponentsNeedsUpdateOnNextRender: () => set(() => ({ componentsNeedsUpdateOnNextRender: [] })), From c12abf07df9c20fa951aa7cf8d193e5b80623e68 Mon Sep 17 00:00:00 2001 From: arpitnath Date: Wed, 3 Apr 2024 12:22:02 +0530 Subject: [PATCH 02/13] fixes: extra re-render for each compoennt dnd updates --- .../src/Editor/ControlledComponentToRender.jsx | 14 +++++++------- frontend/src/_hooks/useRenderCount.js | 2 +- frontend/src/_stores/editorStore.js | 11 +++++++++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/frontend/src/Editor/ControlledComponentToRender.jsx b/frontend/src/Editor/ControlledComponentToRender.jsx index 0415ce6354..8eb2f9f124 100644 --- a/frontend/src/Editor/ControlledComponentToRender.jsx +++ b/frontend/src/Editor/ControlledComponentToRender.jsx @@ -1,8 +1,9 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { getComponentToRender } from '@/_helpers/editorHelpers'; import _ from 'lodash'; import { flushComponentsToRender, getComponentsToRenders } from '@/_stores/editorStore'; +import useRenderCount from '@/_hooks/useRenderCount'; function deepEqualityCheckusingLoDash(obj1, obj2) { return _.isEqual(obj1, obj2); @@ -17,6 +18,7 @@ export const shouldUpdate = (prevProps, nextProps) => { if (componentId) { const componentToRender = listToRender.find((item) => item === componentId); + if (componentToRender) { needToRender = true; } @@ -34,14 +36,12 @@ export const shouldUpdate = (prevProps, nextProps) => { const ComponentWrapper = React.memo(({ componentName, ...props }) => { const ComponentToRender = getComponentToRender(componentName); - const [shouldFlush, setShouldFlush] = useState(false); + const renderCount = useRenderCount(); useEffect(() => { - if (shouldFlush) { - flushComponentsToRender(); - setShouldFlush(false); // Reset the condition - } - }, [shouldFlush]); + flushComponentsToRender(props?.id); + }), + [renderCount]; return ; }, shouldUpdate); diff --git a/frontend/src/_hooks/useRenderCount.js b/frontend/src/_hooks/useRenderCount.js index 5a5965a538..3ad64f6b9e 100644 --- a/frontend/src/_hooks/useRenderCount.js +++ b/frontend/src/_hooks/useRenderCount.js @@ -12,7 +12,7 @@ function useRenderCount(componentName) { renderCountRef.current++; console.log(`CountingRender- Component ${componentName} rendered ${renderCountRef.current} times.`); - // return renderCountRef.current; + return renderCountRef.current; } export default useRenderCount; diff --git a/frontend/src/_stores/editorStore.js b/frontend/src/_stores/editorStore.js index 199feaef82..d18261202d 100644 --- a/frontend/src/_stores/editorStore.js +++ b/frontend/src/_stores/editorStore.js @@ -65,7 +65,13 @@ export const useEditorStore = create( updateComponentsNeedsUpdateOnNextRender: (componentsNeedsUpdateOnNextRender) => set(() => ({ componentsNeedsUpdateOnNextRender })), - flushComponentsNeedsUpdateOnNextRender: () => set(() => ({ componentsNeedsUpdateOnNextRender: [] })), + flushComponentsNeedsUpdateOnNextRender: () => { + const currentComponents = get().componentsNeedsUpdateOnNextRender; + + if (currentComponents.length === 0) return; + + set(() => ({ componentsNeedsUpdateOnNextRender: [] })); + }, updateQueryConfirmationList: (queryConfirmationList) => set({ queryConfirmationList }), setHoveredComponent: (hoveredComponent) => @@ -104,6 +110,7 @@ export const getComponentsToRenders = () => { return useEditorStore.getState().componentsNeedsUpdateOnNextRender; }; -export const flushComponentsToRender = () => { +export const flushComponentsToRender = (componentId) => { + console.log('--arpit:: flush', componentId); useEditorStore.getState().actions.flushComponentsNeedsUpdateOnNextRender(); }; From 800b4b794749b0f8178e3344309df68b8d926fe8 Mon Sep 17 00:00:00 2001 From: arpitnath Date: Wed, 3 Apr 2024 12:58:08 +0530 Subject: [PATCH 03/13] reverting computeComponentState back to autoSave callaback --- frontend/src/Editor/Editor.jsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index 64509845ba..520cd03b84 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -283,13 +283,11 @@ const EditorComponent = (props) => { if (mounted && didAppDefinitionChanged && currentPageId) { const components = appDefinition?.pages[currentPageId]?.components || {}; - // computeComponentState(components); + computeComponentState(components); if (appDiffOptions?.skipAutoSave === true || appDiffOptions?.entityReferenceUpdated === true) return; - if (useEditorStore.getState().isUpdatingEditorStateInProcess) { - handleLowPriorityWork(() => autoSave()); - } + handleLowPriorityWork(() => autoSave()); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify({ appDefinition, currentPageId, dataQueries })]); @@ -922,7 +920,7 @@ const EditorComponent = (props) => { }); } - computeComponentState(currentComponents); + // computeComponentState(currentComponents); }) .finally(async () => { const currentPageEvents = data.events.filter( @@ -1179,6 +1177,7 @@ const EditorComponent = (props) => { }); useResolveStore.getState().actions.addEntitiesToMap(componentEntityArray); + // console.log('----arpit:: component', { newComponentsExposedData }); useResolveStore.getState().actions.addAppSuggestions({ components: newComponentsExposedData, }); From 655979b14c866ee9407d15f5087410cb4030153d Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 3 Apr 2024 13:22:18 +0530 Subject: [PATCH 04/13] Fixed showing default children --- frontend/src/Editor/BoxUI.jsx | 2 + frontend/src/Editor/Components/Form/Form.jsx | 8 +- frontend/src/Editor/Container.jsx | 86 +++++++++++- frontend/src/Editor/DraggableBox.jsx | 2 + frontend/src/Editor/Editor.jsx | 3 + frontend/src/Editor/SubContainer.jsx | 134 ------------------- 6 files changed, 95 insertions(+), 140 deletions(-) diff --git a/frontend/src/Editor/BoxUI.jsx b/frontend/src/Editor/BoxUI.jsx index e65ba0e38b..9e0d6866be 100644 --- a/frontend/src/Editor/BoxUI.jsx +++ b/frontend/src/Editor/BoxUI.jsx @@ -38,6 +38,7 @@ const BoxUI = (props) => { customResolvables, currentLayout, readOnly, + currentPageId, } = props; const darkMode = localStorage.getItem('darkMode') === 'true'; @@ -166,6 +167,7 @@ const BoxUI = (props) => { dataCy={`draggable-widget-${String(component.name).toLowerCase()}`} currentLayout={currentLayout} currentState={currentState} + currentPageId={currentPageId} /> diff --git a/frontend/src/Editor/Components/Form/Form.jsx b/frontend/src/Editor/Components/Form/Form.jsx index 81f69c84bc..00b80ee0cb 100644 --- a/frontend/src/Editor/Components/Form/Form.jsx +++ b/frontend/src/Editor/Components/Form/Form.jsx @@ -24,17 +24,19 @@ export const Form = function Form(props) { fireEvent, properties, resetComponent, - childComponents, onEvent, dataCy, paramUpdated, currentLayout, mode, getContainerProps, + containerProps, } = props; const { events: allAppEvents } = useAppInfo(); + const { childComponents } = containerProps; + const formEvents = allAppEvents.filter((event) => event.target === 'component' && event.sourceId === id); const { visibility, disabledState, borderRadius, borderColor, boxShadow } = styles; const { buttonToSubmit, loadingState, advanced, JSONSchema } = properties; @@ -135,7 +137,7 @@ export const Form = function Form(props) { formattedChildData = extractData(childrenData); childValidation = checkJsonChildrenValidtion(); } else { - Object.keys(childComponents).forEach((childId) => { + Object.keys(childComponents ?? {}).forEach((childId) => { if (childrenData[childId]?.name) { formattedChildData[childrenData[childId].name] = { ...omit(childrenData[childId], 'name'), id: childId }; childValidation = childValidation && (childrenData[childId]?.isValid ?? true); @@ -252,6 +254,8 @@ export const Form = function Form(props) { onOptionChange({ component, optionName, value, componentId }); } }} + currentPageId={props.currentPageId} + {...props} /> { + const { componentName, layout, incrementWidth, properties, accessorKey, tab, defaultValue, styles } = child; + + const componentMeta = _.cloneDeep( + componentTypes.find((component) => component.component === componentName) + ); + const componentData = JSON.parse(JSON.stringify(componentMeta)); + + const width = layout.width ? layout.width : (componentMeta.defaultSize.width * 100) / noOfGrids; + const height = layout.height ? layout.height : componentMeta.defaultSize.height; + const newComponentDefinition = { + ...componentData.definition.properties, + }; + + if (_.isArray(properties) && properties.length > 0) { + properties.forEach((prop) => { + const accessor = customResolverVariable + ? `{{${customResolverVariable}.${accessorKey}}}` + : defaultValue[prop] || ''; + + _.set(newComponentDefinition, prop, { + value: accessor, + }); + }); + _.set(componentData, 'definition.properties', newComponentDefinition); + } + + if (_.isArray(styles) && styles.length > 0) { + styles.forEach((prop) => { + const accessor = customResolverVariable + ? `{{${customResolverVariable}.${accessorKey}}}` + : defaultValue[prop] || ''; + + _.set(newComponentDefinition, prop, { + value: accessor, + }); + }); + _.set(componentData, 'definition.styles', newComponentDefinition); + } + + const newChildComponent = addNewWidgetToTheEditor( + componentData, + {}, + boxes, + {}, + item.currentLayout, + snapToGrid, + zoomLevel, + true, + true + ); + + _.set(childrenBoxes, newChildComponent.id, { + component: { + ...newChildComponent.component, + parent: parentMeta.component === 'Tabs' ? parentId + '-' + tab : parentId, + }, + + layouts: { + [currentLayout]: { + ...layout, + width: incrementWidth ? width * incrementWidth : width, + height: height, + }, + }, + }); + }); + } + const newBoxes = { ...boxes, [newComponent.id]: { @@ -379,8 +459,8 @@ export const Container = ({ layouts: { ...newComponent.layout, }, - withDefaultChildren: newComponent.withDefaultChildren, }, + ...childrenBoxes, }; setBoxes(newBoxes); @@ -679,8 +759,6 @@ export const Container = ({ }, [components]); const getContainerProps = React.useCallback((componentId) => { - const withDefaultChildren = boxes[componentId]?.withDefaultChildren; - return { mode, snapToGrid, @@ -696,7 +774,6 @@ export const Container = ({ currentLayout, selectedComponents, darkMode, - addDefaultChildren: withDefaultChildren, currentPageId, childComponents: childComponents[componentId], parentGridWidth: gridWidth, @@ -785,6 +862,7 @@ export const Container = ({ isMultipleComponentsSelected={selectedComponents?.length > 1 ? true : false} getContainerProps={getContainerProps} isVersionReleased={isVersionReleased} + currentPageId={currentPageId} /> ); diff --git a/frontend/src/Editor/DraggableBox.jsx b/frontend/src/Editor/DraggableBox.jsx index 7b08f27c34..d83608b23e 100644 --- a/frontend/src/Editor/DraggableBox.jsx +++ b/frontend/src/Editor/DraggableBox.jsx @@ -54,6 +54,7 @@ const DraggableBox = React.memo( customResolvables, parentId, getContainerProps, + currentPageId, }) => { const isResizing = useGridStore((state) => state.resizingComponentId === id); const [canDrag, setCanDrag] = useState(true); @@ -222,6 +223,7 @@ const DraggableBox = React.memo( customResolvables={customResolvables} parentId={parentId} getContainerProps={getContainerProps} + currentPageId={currentPageId} /> diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index 64509845ba..e98635b2d6 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -666,6 +666,7 @@ const EditorComponent = (props) => { // }; const handleEvent = React.useCallback((eventName, event, options) => { + console.log('here--- handleEvent'); return onEvent(getEditorRef(), eventName, event, options, 'edit'); }, []); @@ -1132,6 +1133,8 @@ const EditorComponent = (props) => { const paramDiff = computeComponentPropertyDiff(appDefinitionDiff, appDefinition, appDiffOptions); const updateDiff = computeAppDiff(paramDiff, currentPageId, appDiffOptions, currentLayout); + console.log('here--- updateDiff--- ', paramDiff, updateDiff); + if (updateDiff['error']) { const platform = navigator?.userAgentData?.platform || navigator?.platform || 'unknown'; const isPlatformMac = platform.toLowerCase().indexOf('mac') > -1; diff --git a/frontend/src/Editor/SubContainer.jsx b/frontend/src/Editor/SubContainer.jsx index d3e779a1b5..e6cf387104 100644 --- a/frontend/src/Editor/SubContainer.jsx +++ b/frontend/src/Editor/SubContainer.jsx @@ -51,7 +51,6 @@ export const SubContainer = ({ sideBarDebugger, onOptionChange, exposedVariables, - addDefaultChildren = false, height = '100%', currentPageId, childComponents = null, @@ -137,24 +136,6 @@ export const SubContainer = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(allChildComponents), parent]); - // useEffect(() => { - // try { - // const isParentScrollable = SUBCONTAINER_WITH_SCROLL.has(allComponents[parent]?.component?.component); - // const canvasBounds = parentRef.current.getBoundingClientRect(); - // const subContainerHeight = canvasBounds.height - 30; - // const componentBottom = Object.values(childWidgets).reduce(function (max, currentElement) { - // let currentSum = currentElement.layouts[currentLayout].top + currentElement.layouts[currentLayout].height; - // return Math.max(max, currentSum); - // }, 0); - - // if (isParentScrollable && subContainerHeight <= componentBottom) { - // subContainerHeightRef.current = componentBottom + 100; - // } - // } catch (error) { - // console.error('console.error', error); - // } - // }, [childWidgets]); - const containerWidth = getContainerCanvasWidth(); const placeComponentInsideParent = (newComponent, canvasBoundingRect) => { @@ -220,119 +201,6 @@ export const SubContainer = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [containerWidth]); - // useEffect(() => { - // if (mounted) { - // //find children with parent prop - // const children = Object.keys(allComponents).filter((key) => { - // if (key === parent) return false; - // return allComponents[key].parent === parent; - // }); - - // if (children.length === 0 && addDefaultChildren === true) { - // const defaultChildren = _.cloneDeep(parentComponent)['defaultChildren']; - // const childrenBoxes = {}; - // const parentId = - // parentComponent.component !== 'Tabs' - // ? parentRef.current.id - // : parentRef.current.id?.substring(0, parentRef.current.id.lastIndexOf('-')); - - // const _allComponents = JSON.parse(JSON.stringify(allComponents)); - - // defaultChildren.forEach((child) => { - // const { componentName, layout, incrementWidth, properties, accessorKey, tab, defaultValue, styles } = child; - - // const componentMeta = _.cloneDeep(componentTypes.find((component) => component.component === componentName)); - // const componentData = JSON.parse(JSON.stringify(componentMeta)); - - // const width = layout.width ? layout.width : (componentMeta.defaultSize.width * 100) / noOfGrids; - // const height = layout.height ? layout.height : componentMeta.defaultSize.height; - // const newComponentDefinition = { - // ...componentData.definition.properties, - // }; - - // if (_.isArray(properties) && properties.length > 0) { - // properties.forEach((prop) => { - // const accessor = customResolverVariable - // ? `{{${customResolverVariable}.${accessorKey}}}` - // : defaultValue[prop] || ''; - - // _.set(newComponentDefinition, prop, { - // value: accessor, - // }); - // }); - // _.set(componentData, 'definition.properties', newComponentDefinition); - // } - - // if (_.isArray(styles) && styles.length > 0) { - // styles.forEach((prop) => { - // const accessor = customResolverVariable - // ? `{{${customResolverVariable}.${accessorKey}}}` - // : defaultValue[prop] || ''; - - // _.set(newComponentDefinition, prop, { - // value: accessor, - // }); - // }); - // _.set(componentData, 'definition.styles', newComponentDefinition); - // } - - // const newComponent = addNewWidgetToTheEditor( - // componentData, - // {}, - // { ..._allComponents, ...childrenBoxes }, - // {}, - // currentLayout, - // snapToGrid, - // zoomLevel, - // true, - // true - // ); - - // _.set(childrenBoxes, newComponent.id, { - // component: { - // ...newComponent.component, - // parent: parentComponent.component === 'Tabs' ? parentId + '-' + tab : parentId, - // }, - - // layouts: { - // [currentLayout]: { - // ...layout, - // width: incrementWidth ? width * incrementWidth : width, - // height: height, - // }, - // }, - // }); - // }); - - // // _allComponents[parentId] = { - // // ...allComponents[parentId], - // // withDefaultChildren: false, - // // }; - // const allChildren = getChildWidgets(allComponents); - - // setChildWidgets(allChildren); - // // setBoxes({ - // // ..._allComponents, - // // ...childrenBoxes, - // // }); - // } - // } - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, [mounted]); - - const moveBox = useCallback( - (id, left, top) => { - setChildWidgets( - update(childWidgets, { - [id]: { - $merge: { left, top }, - }, - }) - ); - }, - [childWidgets] - ); - useEffect(() => { if (appDefinitionChanged) { const newDefinition = { @@ -604,7 +472,6 @@ export const SubContainer = ({ onComponentHover, hoveredComponent, sideBarDebugger, - addDefaultChildren, currentPageId, childComponents, }; @@ -728,7 +595,6 @@ export const SubContainer = ({ // onComponentHover, // hoveredComponent, // sideBarDebugger, - // addDefaultChildren, // currentPageId, // childComponents, // setSubContainerWidths, From 5fadcd9f5963068cbdb9cfb843d97408144a9e80 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 3 Apr 2024 17:03:18 +0530 Subject: [PATCH 05/13] Reverted commit 9b88e9f which causes infinite loop --- frontend/src/Editor/BoxUI.jsx | 4 +- frontend/src/Editor/Editor.jsx | 49 +------- frontend/src/_helpers/appUtils.js | 137 ++++++++++------------ frontend/src/_helpers/editorHelpers.js | 10 +- frontend/src/_stores/currentStateStore.js | 49 ++++++-- frontend/src/_stores/editorStore.js | 2 +- 6 files changed, 116 insertions(+), 135 deletions(-) diff --git a/frontend/src/Editor/BoxUI.jsx b/frontend/src/Editor/BoxUI.jsx index 9e0d6866be..e3bdd4410c 100644 --- a/frontend/src/Editor/BoxUI.jsx +++ b/frontend/src/Editor/BoxUI.jsx @@ -129,8 +129,8 @@ const BoxUI = (props) => { > { // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify({ appDefinition, currentPageId, dataQueries })]); - const prevCurrentStateRef = useRef(currentState); + // const prevCurrentStateRef = useRef(currentState); - function generatePath(obj, targetKey, currentPath = '') { - for (const key in obj) { - const newPath = currentPath ? currentPath + '.' + key : key; - - if (key === targetKey) { - return newPath; - } - - if (typeof obj[key] === 'object' && obj[key] !== null) { - const result = generatePath(obj[key], targetKey, newPath); - if (result) { - return result; - } - } - } - return null; - } + const currentStateDiff = useEditorStore.getState().currentStateDiff; useEffect(() => { - const diffState = diff(prevCurrentStateRef.current, currentState); - - if (Object.keys(diffState).length > 0) { - const entitiesToTrack = ['queries', 'components', 'variables', 'page', 'constants', 'layout']; - - const entitiesChanged = Object.keys(diffState).filter((entity) => entitiesToTrack.includes(entity)); - - const diffObj = entitiesChanged.reduce((acc, entity) => { - acc[entity] = diffState[entity]; - return acc; - }, {}); - - const allPaths = entitiesChanged.reduce((acc, entity) => { - const paths = Object.keys(diffObj[entity]).map((key) => { - return generatePath(diffObj[entity], key); - }); - - acc[entity] = paths.map((path) => `${entity}.${path}`); - return acc; - }, {}); - - const currentStatePaths = Object.values(allPaths).flat(); - + const isEditorReady = useCurrentStateStore.getState().isEditorReady; + if (isEditorReady && currentStateDiff?.length > 0) { const currentComponents = useEditorStore.getState().appDefinition?.pages?.[currentPageId]?.components || {}; - const componentIdsWithReferences = findComponentsWithReferences(currentComponents, currentStatePaths); + const componentIdsWithReferences = findComponentsWithReferences(currentComponents, currentStateDiff); if (componentIdsWithReferences.length > 0) { updateComponentsNeedsUpdateOnNextRender(componentIdsWithReferences); } - - prevCurrentStateRef.current = currentState; } // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(currentState)]); diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 9923260ce4..c521b7ca7f 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -73,85 +73,79 @@ const debouncedChange = _.debounce(() => { }, 100); export function onComponentOptionsChanged(component, options) { + const componentName = component.name; const { isEditorReady } = getCurrentState(); - if (!isEditorReady) return Promise.resolve(); + if (isEditorReady) { + if (duplicateCurrentState !== null) { + duplicateCurrentState = null; + } - const componentName = component.name; + const components = getCurrentState().components; + let componentData = components[componentName]; + componentData = componentData || {}; - // if (duplicateCurrentState !== null) { - // duplicateCurrentState = null; - // } + for (const option of options) { + componentData[option[0]] = option[1]; + } - const components = getCurrentState().components; - let componentData = components[componentName]; - componentData = componentData || {}; + useCurrentStateStore.getState().actions.setCurrentState({ + components: { ...components, [componentName]: componentData }, + }); + } else { + const components = duplicateCurrentState === null ? getCurrentState().components : duplicateCurrentState; + let componentData = components[componentName]; + componentData = componentData || {}; - for (const option of options) { - componentData[option[0]] = option[1]; + for (const option of options) { + componentData[option[0]] = option[1]; + } + + duplicateCurrentState = { ...components, [componentName]: componentData }; + + debouncedChange(); } - - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); return Promise.resolve(); } -// export function onComponentOptionChanged(component, option_name, value) { -// const componentName = component.name; -// const { isEditorReady } = getCurrentState(); -// if (isEditorReady) { -// if (duplicateCurrentState !== null) { -// duplicateCurrentState = null; -// } -// const components = getCurrentState().components; -// let componentData = components[componentName]; -// componentData = componentData || {}; -// componentData[option_name] = value; - -// if (option_name !== 'id') { -// useCurrentStateStore.getState().actions.setCurrentState({ -// components: { ...components, [componentName]: componentData }, -// }); -// } else if (!componentData?.id) { -// useCurrentStateStore.getState().actions.setCurrentState({ -// components: { ...components, [componentName]: componentData }, -// }); -// } - -// useCurrentStateStore.getState().actions.setCurrentState({ -// components: { ...components, [componentName]: componentData }, -// }); -// } else { -// const components = duplicateCurrentState === null ? getCurrentState().components : duplicateCurrentState; -// let componentData = components[componentName]; -// componentData = componentData || {}; -// componentData[option_name] = value; - -// duplicateCurrentState = { ...components, [componentName]: componentData }; -// if (option_name !== 'id') { -// debouncedChange(); -// } else if (!componentData?.id) { -// debouncedChange(); -// } -// } - -// return Promise.resolve(); -// } - export function onComponentOptionChanged(component, option_name, value) { - const { isEditorReady, components: currentComponents } = getCurrentState(); - - if (!isEditorReady) return Promise.resolve(); - const componentName = component.name; - const components = duplicateCurrentState === null ? currentComponents : duplicateCurrentState; - let componentData = components[componentName] || {}; - componentData[option_name] = value; + const { isEditorReady } = getCurrentState(); + if (isEditorReady) { + if (duplicateCurrentState !== null) { + duplicateCurrentState = null; + } + const components = getCurrentState().components; + let componentData = components[componentName]; + componentData = componentData || {}; + componentData[option_name] = value; - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); + if (option_name !== 'id') { + useCurrentStateStore.getState().actions.setCurrentState({ + components: { ...components, [componentName]: componentData }, + }); + } else if (!componentData?.id) { + useCurrentStateStore.getState().actions.setCurrentState({ + components: { ...components, [componentName]: componentData }, + }); + } + + useCurrentStateStore.getState().actions.setCurrentState({ + components: { ...components, [componentName]: componentData }, + }); + } else { + const components = duplicateCurrentState === null ? getCurrentState().components : duplicateCurrentState; + let componentData = components[componentName]; + componentData = componentData || {}; + componentData[option_name] = value; + + duplicateCurrentState = { ...components, [componentName]: componentData }; + if (option_name !== 'id') { + debouncedChange(); + } else if (!componentData?.id) { + debouncedChange(); + } + } return Promise.resolve(); } @@ -1385,14 +1379,11 @@ export function computeComponentState(components = {}) { } }); - useCurrentStateStore.getState().actions.setCurrentState( - { - components: { - ...componentState, - }, + useCurrentStateStore.getState().actions.setCurrentState({ + components: { + ...componentState, }, - 'componentStateComputed' - ); + }); return new Promise((resolve) => { useEditorStore.getState().actions.updateEditorState({ diff --git a/frontend/src/_helpers/editorHelpers.js b/frontend/src/_helpers/editorHelpers.js index 5482c188e2..1018b3c4ff 100644 --- a/frontend/src/_helpers/editorHelpers.js +++ b/frontend/src/_helpers/editorHelpers.js @@ -139,13 +139,9 @@ function findReferenceInComponent(node, changedCurrentState) { const value = node[key]; if (typeof value === 'string' && value.includes('{{') && value.includes('}}')) { // Check if the referenced entity is in the state - - // check if the current node's value has the reference - const hasFound = changedCurrentState.some((state) => { - return value.includes(state); - }); - - return hasFound; + if (changedCurrentState.some((state) => value.includes(state))) { + return true; + } } else if (typeof value === 'object') { const found = findReferenceInComponent(value, changedCurrentState); diff --git a/frontend/src/_stores/currentStateStore.js b/frontend/src/_stores/currentStateStore.js index 4e10864d06..a01d26951a 100644 --- a/frontend/src/_stores/currentStateStore.js +++ b/frontend/src/_stores/currentStateStore.js @@ -49,10 +49,46 @@ export const useCurrentStateStore = create( (set, get) => ({ ...initialState, actions: { - setCurrentState: (currentState, from = null) => { - const newState = merge({}, get(), currentState); + setCurrentState: (currentState) => { + const currentStateEntites = Object.keys(currentState); - set({ ...newState }); + const existingStateOfEntities = currentStateEntites.reduce((acc, entity) => { + acc[entity] = get()[entity]; + return acc; + }, {}); + + const diffState = diff(existingStateOfEntities, currentState); + + if (_.isEmpty(diffState)) return; + + set({ ...currentState }, false, { type: 'SET_CURRENT_STATE', currentState }); + + //need to track only queries, components, variables, page, constants, layout + // from the diff, if any of these entities are changed, we need to update the store + + if (get().isEditorReady) { + const entitiesToTrack = ['queries', 'components', 'variables', 'page', 'constants', 'layout']; + + const entitiesChanged = Object.keys(diffState).filter((entity) => entitiesToTrack.includes(entity)); + + const diffObj = entitiesChanged.reduce((acc, entity) => { + acc[entity] = diffState[entity]; + return acc; + }, {}); + + const allPaths = entitiesChanged.reduce((acc, entity) => { + const paths = Object.keys(diffObj[entity]).map((key) => { + return generatePath(diffObj[entity], key); + }); + + acc[entity] = paths.map((path) => `${entity}.${path}`).join(','); + return acc; + }, {}); + + const currentStatePaths = Object.values(allPaths); + + useEditorStore.getState().actions.updateCurrentStateDiff(currentStatePaths); + } }, setErrors: (error) => { set({ errors: { ...get().errors, ...error } }, false, { type: 'SET_ERRORS', error }); @@ -64,9 +100,9 @@ export const useCurrentStateStore = create( ) ); -export const useCurrentState = () => { +export const useCurrentState = () => // Omitting 'actions' here because we don't want to expose it to user - const currentState = useCurrentStateStore((state) => { + useCurrentStateStore((state) => { return { queries: state.queries, components: state.components, @@ -82,9 +118,6 @@ export const useCurrentState = () => { }; }, shallow); - return JSON.parse(JSON.stringify(currentState)); -}; - useCurrentStateStore.subscribe((state) => { const isEditorReady = state.isEditorReady; diff --git a/frontend/src/_stores/editorStore.js b/frontend/src/_stores/editorStore.js index d18261202d..1ad5e681ce 100644 --- a/frontend/src/_stores/editorStore.js +++ b/frontend/src/_stores/editorStore.js @@ -62,7 +62,7 @@ export const useEditorStore = create( }, setIsEditorActive: (isEditorActive) => set(() => ({ isEditorActive })), updateEditorState: (state) => set((prev) => ({ ...prev, ...state })), - + updateCurrentStateDiff: (currentStateDiff) => set(() => ({ currentStateDiff })), updateComponentsNeedsUpdateOnNextRender: (componentsNeedsUpdateOnNextRender) => set(() => ({ componentsNeedsUpdateOnNextRender })), flushComponentsNeedsUpdateOnNextRender: () => { From 3057b11f56f09bf67cfa1f43dd011367abc4a223 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Thu, 4 Apr 2024 10:37:00 +0530 Subject: [PATCH 06/13] Removed debuggerStore & unwanted props --- .../SidebarDebugger/useDebugger.js | 1 - frontend/src/_stores/debuggerStore.js | 27 ------------------- 2 files changed, 28 deletions(-) delete mode 100644 frontend/src/_stores/debuggerStore.js diff --git a/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js b/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js index 75f9124fa4..a92a655430 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js +++ b/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js @@ -14,7 +14,6 @@ const useDebugger = ({ currentPageId, isDebuggerOpen }) => { const { errors, succededQuery } = useCurrentStateStore( (state) => ({ errors: state.errors, - queries: state.queries, succededQuery: state.succededQuery, }), shallow diff --git a/frontend/src/_stores/debuggerStore.js b/frontend/src/_stores/debuggerStore.js deleted file mode 100644 index 326f3a74f0..0000000000 --- a/frontend/src/_stores/debuggerStore.js +++ /dev/null @@ -1,27 +0,0 @@ -import { create, zustandDevTools } from './utils'; - -const initialState = { - errors: {}, - succededQuery: {}, -}; - -export const useDebuggerStore = create( - zustandDevTools( - (set, get) => ({ - ...initialState, - actions: { - setErrors: (error) => { - set({ errors: { ...get().errors, ...error } }, false, { type: 'SET_ERRORS', error }); - }, - clearErrors: () => set({ errors: {} }, false, { type: 'CLEAR_ERRORS' }), - setSuccededQuery: (queryDetails) => { - set({ succededQuery: { ...get().succededQuery, ...queryDetails } }, false, { - type: 'SET_SUCCEDED_QUERY', - queryDetails, - }); - }, - }, - }), - { name: 'Debugger Store' } - ) -); From c59f091c0d22e47ebcd9574113bb305e6b254cc7 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Thu, 4 Apr 2024 11:58:40 +0530 Subject: [PATCH 07/13] Added profiler hoc --- frontend/src/_hoc/withProfiler.jsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 frontend/src/_hoc/withProfiler.jsx diff --git a/frontend/src/_hoc/withProfiler.jsx b/frontend/src/_hoc/withProfiler.jsx new file mode 100644 index 0000000000..9c6bc58623 --- /dev/null +++ b/frontend/src/_hoc/withProfiler.jsx @@ -0,0 +1,18 @@ +import React, { Profiler } from 'react'; + +export const withProfiler = (WrappedComponent) => (props) => { + function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) { + const markName = `⚛ ${id} (${phase})`; + performance.measure(markName, { + end: commitTime, + start: startTime, + }); + performance.clearMeasures(markName); + } + + return ( + + + + ); +}; From 1bc689e61465ffa1a653c5e4b79f53109400ce82 Mon Sep 17 00:00:00 2001 From: Arpit Date: Thu, 4 Apr 2024 13:55:47 +0530 Subject: [PATCH 08/13] =?UTF-8?q?Implement=20batch=20processing=20and=20se?= =?UTF-8?q?lective=20flushing=20for=20efficient=20state=E2=80=A6=20(#9278)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement batch processing and selective flushing for efficient state updates in React components, optimizing performance for large-scale applications. * clean up * fix: fixed issues with delay of rerender (#9291) * fix: fixed issues with delay of rerender * fix: removed unused logs * fix: removed unused logs * fix: removed unused logs * clean up --------- Co-authored-by: arpitnath --------- Co-authored-by: Johnson Cherian --- frontend/src/Editor/Box.jsx | 8 ++ .../Editor/ControlledComponentToRender.jsx | 12 +-- frontend/src/Editor/Editor.jsx | 77 +++++++++++++++++-- frontend/src/_helpers/appUtils.js | 37 ++------- frontend/src/_helpers/editorHelpers.js | 18 +++++ frontend/src/_stores/currentStateStore.js | 38 --------- frontend/src/_stores/editorStore.js | 20 +++-- frontend/src/_stores/queryPanelStore.js | 2 +- 8 files changed, 118 insertions(+), 94 deletions(-) diff --git a/frontend/src/Editor/Box.jsx b/frontend/src/Editor/Box.jsx index d275fd5191..00b2972b23 100644 --- a/frontend/src/Editor/Box.jsx +++ b/frontend/src/Editor/Box.jsx @@ -2,6 +2,8 @@ import React from 'react'; import HydrateWithResolveReferences from './Middlewares/HydrateWithResolveReferences'; import BoxUI from './BoxUI'; import _ from 'lodash'; +import { useEditorStore, flushComponentsToRender } from '@/_stores/editorStore'; +import { shallow } from 'zustand/shallow'; function deepEqualityCheckusingLoDash(obj1, obj2) { return _.isEqual(obj1, obj2); @@ -19,6 +21,12 @@ export const shouldUpdate = (prevProps, nextProps) => { export const Box = (props) => { const { id, component, mode, customResolvables } = props; + /** + * !This component does not consume the value returned from the below hook. + * Only purpose of the hook is to force one rerender the component + * */ + useEditorStore((state) => state.componentsNeedsUpdateOnNextRender.find((compId) => compId === id), shallow); + return ( diff --git a/frontend/src/Editor/ControlledComponentToRender.jsx b/frontend/src/Editor/ControlledComponentToRender.jsx index 8eb2f9f124..a13b27252d 100644 --- a/frontend/src/Editor/ControlledComponentToRender.jsx +++ b/frontend/src/Editor/ControlledComponentToRender.jsx @@ -1,9 +1,8 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { getComponentToRender } from '@/_helpers/editorHelpers'; import _ from 'lodash'; -import { flushComponentsToRender, getComponentsToRenders } from '@/_stores/editorStore'; -import useRenderCount from '@/_hooks/useRenderCount'; +import { getComponentsToRenders } from '@/_stores/editorStore'; function deepEqualityCheckusingLoDash(obj1, obj2) { return _.isEqual(obj1, obj2); @@ -36,13 +35,6 @@ export const shouldUpdate = (prevProps, nextProps) => { const ComponentWrapper = React.memo(({ componentName, ...props }) => { const ComponentToRender = getComponentToRender(componentName); - const renderCount = useRenderCount(); - - useEffect(() => { - flushComponentsToRender(props?.id); - }), - [renderCount]; - return ; }, shouldUpdate); diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index 949ac99671..a16b5ec5de 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -70,7 +70,7 @@ import { resetAllStores, } from '@/_stores/utils'; import { setCookie } from '@/_helpers/cookie'; -import { EMPTY_ARRAY, useEditorActions, useEditorStore } from '@/_stores/editorStore'; +import { EMPTY_ARRAY, flushComponentsToRender, useEditorActions, useEditorStore } from '@/_stores/editorStore'; import { useAppDataActions, useAppDataStore } from '@/_stores/appDataStore'; import { useNoOfGrid } from '@/_stores/gridStore'; import { useMounted } from '@/_hooks/use-mount'; @@ -89,7 +89,7 @@ import { HotkeysProvider } from 'react-hotkeys-hook'; import { useResolveStore } from '@/_stores/resolverStore'; import { dfs } from '@/_stores/handleReferenceTransactions'; import { decimalToHex } from './editorConstants'; -import { findComponentsWithReferences, handleLowPriorityWork } from '@/_helpers/editorHelpers'; +import { findComponentsWithReferences, generatePath, handleLowPriorityWork } from '@/_helpers/editorHelpers'; setAutoFreeze(false); enablePatches(); @@ -214,6 +214,8 @@ const EditorComponent = (props) => { const prevAppDefinition = useRef(appDefinition); const prevEventsStoreRef = useRef(events); + const onAppLoadAndPageLoadEventsAreTriggered = useRef(false); + useLayoutEffect(() => { resetAllStores(); }, []); @@ -292,19 +294,77 @@ const EditorComponent = (props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify({ appDefinition, currentPageId, dataQueries })]); - // const prevCurrentStateRef = useRef(currentState); + const prevCurrentStateRef = useRef(currentState); - const currentStateDiff = useEditorStore.getState().currentStateDiff; + /** + ** Async updates components in batches to optimize and processing efficiency. + * This function iterates over an array of component IDs, updating them in fixed-size batches, + * and introduces a delay after each batch to allow the UI thread to manage other tasks, such as rendering updates. + * After all batches are processed, it flushes the updates to clear any flags or temporary states indicating pending updates, + * ensuring the system is ready for the next cycle of updates. + * + * @param {Array} componentIds An array of component IDs that need updates. + * @returns {Promise} A promise that resolves once all batches have been processed and flushed. + */ + + async function batchUpdateComponents(componentIds) { + if (componentIds.length === 0) return; + + let updatedComponentIds = []; + + for (let i = 0; i < componentIds.length; i += 10) { + const batch = componentIds.slice(i, i + 10); + batch.forEach((id) => { + updatedComponentIds.push(id); + }); + + updateComponentsNeedsUpdateOnNextRender(batch); + // Delay to allow UI to process + await new Promise((resolve) => setTimeout(resolve, 0)); + } + + // Flush only updated components + flushComponentsToRender(updatedComponentIds); + } useEffect(() => { const isEditorReady = useCurrentStateStore.getState().isEditorReady; - if (isEditorReady && currentStateDiff?.length > 0) { + + if (!isEditorReady || !onAppLoadAndPageLoadEventsAreTriggered.current) return; + + const diffState = diff(prevCurrentStateRef.current, currentState); + + if (Object.keys(diffState).length > 0) { + const entitiesToTrack = ['queries', 'components', 'variables', 'page', 'constants']; + + const entitiesChanged = Object.keys(diffState).filter((entity) => entitiesToTrack.includes(entity)); + + if (entitiesChanged.length === 0) return; + + const diffObj = entitiesChanged.reduce((acc, entity) => { + acc[entity] = diffState[entity]; + return acc; + }, {}); + + const allPaths = entitiesChanged.reduce((acc, entity) => { + const paths = Object.keys(diffObj[entity]).map((key) => { + return generatePath(diffObj[entity], key); + }); + + acc[entity] = paths.map((path) => `${entity}.${path}`); + return acc; + }, {}); + + const currentStatePaths = Object.values(allPaths).flat(); + const currentComponents = useEditorStore.getState().appDefinition?.pages?.[currentPageId]?.components || {}; - const componentIdsWithReferences = findComponentsWithReferences(currentComponents, currentStateDiff); + const componentIdsWithReferences = findComponentsWithReferences(currentComponents, currentStatePaths); if (componentIdsWithReferences.length > 0) { - updateComponentsNeedsUpdateOnNextRender(componentIdsWithReferences); + batchUpdateComponents(componentIdsWithReferences); } + + prevCurrentStateRef.current = JSON.parse(JSON.stringify(currentState)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(currentState)]); @@ -894,6 +954,7 @@ const EditorComponent = (props) => { handleLowPriorityWork(async () => { await runQueries(useDataQueriesStore.getState().dataQueries, editorRef, true); await handleEvent('onPageLoad', currentPageEvents, {}, true); + await handleLowPriorityWork(() => (onAppLoadAndPageLoadEventsAreTriggered.current = true)); }); }); }; @@ -1141,7 +1202,7 @@ const EditorComponent = (props) => { }); useResolveStore.getState().actions.addEntitiesToMap(componentEntityArray); - // console.log('----arpit:: component', { newComponentsExposedData }); + useResolveStore.getState().actions.addAppSuggestions({ components: newComponentsExposedData, }); diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index c521b7ca7f..2aa1b8d5c1 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -110,41 +110,20 @@ export function onComponentOptionsChanged(component, options) { export function onComponentOptionChanged(component, option_name, value) { const componentName = component.name; - const { isEditorReady } = getCurrentState(); + const { isEditorReady, components: currentComponents } = getCurrentState(); + const components = duplicateCurrentState === null ? currentComponents : duplicateCurrentState; + let componentData = components[componentName] || {}; + componentData[option_name] = value; + if (isEditorReady) { - if (duplicateCurrentState !== null) { - duplicateCurrentState = null; - } - const components = getCurrentState().components; - let componentData = components[componentName]; - componentData = componentData || {}; - componentData[option_name] = value; - - if (option_name !== 'id') { - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); - } else if (!componentData?.id) { - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); - } - + // Always update the current state if editor is ready useCurrentStateStore.getState().actions.setCurrentState({ components: { ...components, [componentName]: componentData }, }); } else { - const components = duplicateCurrentState === null ? getCurrentState().components : duplicateCurrentState; - let componentData = components[componentName]; - componentData = componentData || {}; - componentData[option_name] = value; - + // Update the duplicate state if editor is not ready duplicateCurrentState = { ...components, [componentName]: componentData }; - if (option_name !== 'id') { - debouncedChange(); - } else if (!componentData?.id) { - debouncedChange(); - } + debouncedChange(); } return Promise.resolve(); diff --git a/frontend/src/_helpers/editorHelpers.js b/frontend/src/_helpers/editorHelpers.js index 1018b3c4ff..2971cd3bc0 100644 --- a/frontend/src/_helpers/editorHelpers.js +++ b/frontend/src/_helpers/editorHelpers.js @@ -178,3 +178,21 @@ export function handleLowPriorityWork(callback, timeout = null) { const options = timeout ? { timeout } : {}; window.requestIdleCallback(callback, options); } + +export function generatePath(obj, targetKey, currentPath = '') { + for (const key in obj) { + const newPath = currentPath ? currentPath + '.' + key : key; + + if (key === targetKey) { + return newPath; + } + + if (typeof obj[key] === 'object' && obj[key] !== null) { + const result = generatePath(obj[key], targetKey, newPath); + if (result) { + return result; + } + } + } + return null; +} diff --git a/frontend/src/_stores/currentStateStore.js b/frontend/src/_stores/currentStateStore.js index a01d26951a..bb7af52df0 100644 --- a/frontend/src/_stores/currentStateStore.js +++ b/frontend/src/_stores/currentStateStore.js @@ -50,45 +50,7 @@ export const useCurrentStateStore = create( ...initialState, actions: { setCurrentState: (currentState) => { - const currentStateEntites = Object.keys(currentState); - - const existingStateOfEntities = currentStateEntites.reduce((acc, entity) => { - acc[entity] = get()[entity]; - return acc; - }, {}); - - const diffState = diff(existingStateOfEntities, currentState); - - if (_.isEmpty(diffState)) return; - set({ ...currentState }, false, { type: 'SET_CURRENT_STATE', currentState }); - - //need to track only queries, components, variables, page, constants, layout - // from the diff, if any of these entities are changed, we need to update the store - - if (get().isEditorReady) { - const entitiesToTrack = ['queries', 'components', 'variables', 'page', 'constants', 'layout']; - - const entitiesChanged = Object.keys(diffState).filter((entity) => entitiesToTrack.includes(entity)); - - const diffObj = entitiesChanged.reduce((acc, entity) => { - acc[entity] = diffState[entity]; - return acc; - }, {}); - - const allPaths = entitiesChanged.reduce((acc, entity) => { - const paths = Object.keys(diffObj[entity]).map((key) => { - return generatePath(diffObj[entity], key); - }); - - acc[entity] = paths.map((path) => `${entity}.${path}`).join(','); - return acc; - }, {}); - - const currentStatePaths = Object.values(allPaths); - - useEditorStore.getState().actions.updateCurrentStateDiff(currentStatePaths); - } }, setErrors: (error) => { set({ errors: { ...get().errors, ...error } }, false, { type: 'SET_ERRORS', error }); diff --git a/frontend/src/_stores/editorStore.js b/frontend/src/_stores/editorStore.js index 1ad5e681ce..c55842e8a6 100644 --- a/frontend/src/_stores/editorStore.js +++ b/frontend/src/_stores/editorStore.js @@ -63,14 +63,17 @@ export const useEditorStore = create( setIsEditorActive: (isEditorActive) => set(() => ({ isEditorActive })), updateEditorState: (state) => set((prev) => ({ ...prev, ...state })), updateCurrentStateDiff: (currentStateDiff) => set(() => ({ currentStateDiff })), - updateComponentsNeedsUpdateOnNextRender: (componentsNeedsUpdateOnNextRender) => - set(() => ({ componentsNeedsUpdateOnNextRender })), - flushComponentsNeedsUpdateOnNextRender: () => { + updateComponentsNeedsUpdateOnNextRender: (componentsNeedsUpdateOnNextRender) => { + set(() => ({ componentsNeedsUpdateOnNextRender })); + }, + flushComponentsNeedsUpdateOnNextRender: (toRemoveIds = []) => { const currentComponents = get().componentsNeedsUpdateOnNextRender; - if (currentComponents.length === 0) return; + if (currentComponents.length === 0 || toRemoveIds.length === 0) return; - set(() => ({ componentsNeedsUpdateOnNextRender: [] })); + const updatedComponents = currentComponents.filter((item) => !toRemoveIds.includes(item)); + + set(() => ({ componentsNeedsUpdateOnNextRender: updatedComponents })); }, updateQueryConfirmationList: (queryConfirmationList) => set({ queryConfirmationList }), @@ -110,7 +113,8 @@ export const getComponentsToRenders = () => { return useEditorStore.getState().componentsNeedsUpdateOnNextRender; }; -export const flushComponentsToRender = (componentId) => { - console.log('--arpit:: flush', componentId); - useEditorStore.getState().actions.flushComponentsNeedsUpdateOnNextRender(); +export const flushComponentsToRender = (componentIds = []) => { + if (!componentIds.length) return; + + useEditorStore.getState().actions.flushComponentsNeedsUpdateOnNextRender(componentIds); }; diff --git a/frontend/src/_stores/queryPanelStore.js b/frontend/src/_stores/queryPanelStore.js index 83187b80e5..7b28ae84b8 100644 --- a/frontend/src/_stores/queryPanelStore.js +++ b/frontend/src/_stores/queryPanelStore.js @@ -1,5 +1,5 @@ import { create, zustandDevTools } from './utils'; - +import { shallow } from 'zustand/shallow'; import { useDataQueriesStore } from '@/_stores/dataQueriesStore'; const queryManagerPreferences = JSON.parse(localStorage.getItem('queryManagerPreferences')) ?? {}; From 848784ce6ca075d80323963ba77b67777c658c54 Mon Sep 17 00:00:00 2001 From: arpitnath Date: Thu, 4 Apr 2024 14:10:42 +0530 Subject: [PATCH 09/13] clean up --- frontend/package-lock.json | 13 --- frontend/package.json | 1 - frontend/src/Editor/Box.jsx | 2 +- frontend/src/Editor/BoxUI.jsx | 5 +- .../src/Editor/CodeBuilder/CodeBuilder.jsx | 1 - frontend/src/Editor/Container.jsx | 26 +---- frontend/src/Editor/DragContainer.jsx | 37 +------ frontend/src/Editor/DraggableBox.jsx | 7 -- frontend/src/Editor/Editor.jsx | 29 +---- frontend/src/Editor/EditorSelecto.jsx | 3 - frontend/src/Editor/SubContainer.jsx | 100 ++---------------- frontend/src/Editor/SubCustomDragLayer.jsx | 2 - frontend/src/index.jsx | 5 +- 13 files changed, 16 insertions(+), 215 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2db02042d7..808f779784 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -147,7 +147,6 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^14.4.3", - "@welldone-software/why-did-you-render": "^8.0.1", "babel-loader": "^9.1.2", "babel-plugin-console-source": "^2.0.5", "babel-plugin-import": "^1.13.6", @@ -12288,18 +12287,6 @@ } } }, - "node_modules/@welldone-software/why-did-you-render": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@welldone-software/why-did-you-render/-/why-did-you-render-8.0.1.tgz", - "integrity": "sha512-PtLBjiHNX04gDPheMeAQP16S24JV3SOW6wGDUrm4bFPZmofmmflgvd4Kacf/jhB8zlX6equ8m3t6CS+OxA3Q4g==", - "dev": true, - "dependencies": { - "lodash": "^4" - }, - "peerDependencies": { - "react": "^18" - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3ff8d11fbf..57661348ea 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -142,7 +142,6 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^14.4.3", - "@welldone-software/why-did-you-render": "^8.0.1", "babel-loader": "^9.1.2", "babel-plugin-console-source": "^2.0.5", "babel-plugin-import": "^1.13.6", diff --git a/frontend/src/Editor/Box.jsx b/frontend/src/Editor/Box.jsx index 00b2972b23..17a16a643f 100644 --- a/frontend/src/Editor/Box.jsx +++ b/frontend/src/Editor/Box.jsx @@ -2,7 +2,7 @@ import React from 'react'; import HydrateWithResolveReferences from './Middlewares/HydrateWithResolveReferences'; import BoxUI from './BoxUI'; import _ from 'lodash'; -import { useEditorStore, flushComponentsToRender } from '@/_stores/editorStore'; +import { useEditorStore } from '@/_stores/editorStore'; import { shallow } from 'zustand/shallow'; function deepEqualityCheckusingLoDash(obj1, obj2) { diff --git a/frontend/src/Editor/BoxUI.jsx b/frontend/src/Editor/BoxUI.jsx index e3bdd4410c..3c0139aaea 100644 --- a/frontend/src/Editor/BoxUI.jsx +++ b/frontend/src/Editor/BoxUI.jsx @@ -32,8 +32,6 @@ const BoxUI = (props) => { changeCanDrag, removeComponent, canvasWidth, - // exposedVariables, - // fireEvent, parentId, customResolvables, currentLayout, @@ -121,10 +119,9 @@ const BoxUI = (props) => {
renderVariable(type, key, variable.item.name)); diff --git a/frontend/src/Editor/Container.jsx b/frontend/src/Editor/Container.jsx index f7b96bd261..0322489fc6 100644 --- a/frontend/src/Editor/Container.jsx +++ b/frontend/src/Editor/Container.jsx @@ -128,9 +128,7 @@ export const Container = ({ : updatedBoxes[id].layouts.desktop; }); setBoxes({ ...updatedBoxes }); - // console.log('currentLayout', data); } - // setNoOfGrids(currentLayout === 'mobile' ? 12 : 43); }, [currentLayout]); const paramUpdatesOptsRef = useRef({}); @@ -316,10 +314,6 @@ export const Container = ({ [setCanvasHeight, currentLayout, mode] ); - // useEffect(() => { - // setIsDragging(draggingState); - // }, [draggingState]); - const [{ isOver, isOverCurrent }, drop] = useDrop( () => ({ accept: ItemTypes.BOX, @@ -525,8 +519,6 @@ export const Container = ({ setBoxes(updatedBoxes); updateCanvasHeight(updatedBoxes); }; - // [boxes, updateCanvasHeight, canvasWidth, gridWidth, currentLayout] - // ); function onDragStop(boxPositions) { const copyOfBoxes = JSON.parse(JSON.stringify(boxes)); @@ -881,10 +873,7 @@ export const Container = ({ onDrag={onDragStop} gridWidth={gridWidth} selectedComponents={selectedComponents} - // setIsDragging={setIsDragging} - // setIsResizing={setIsResizing} currentLayout={currentLayout} - // subContainerWidths={subContainerWidths} currentPageId={currentPageId} draggedSubContainer={draggedSubContainer} mode={isVersionReleased ? 'view' : mode} @@ -940,7 +929,6 @@ const WidgetWrapper = ({ children, widget, id, gridWidth, currentLayout, isResiz width: width + 'px', height: layoutData.height + 'px', transform: `translate(${layoutData.left * gridWidth}px, ${layoutData.top}px)`, - // ...(isGhostComponent ? { opacity: 0.5 } : isResizing ? { opacity: 0 } : {}), ...(isGhostComponent ? { opacity: 0.5 } : {}), ...(isWidgetActive ? { zIndex: 3 } : {}), }; @@ -985,18 +973,7 @@ function DragGhostWidget() { ); } -function ContainerWrapper({ - children, - canvasHeight, - // isDragging, - // isResizing, - isDropping, - showComments, - handleAddThread, - containerRef, - styles, -}) { - // const [dragTarget] = useDragTarget(); +function ContainerWrapper({ children, canvasHeight, isDropping, showComments, handleAddThread, containerRef, styles }) { const { resizingComponentId, draggingComponentId, dragTarget } = useGridStore((state) => { const { resizingComponentId, draggingComponentId, dragTarget } = state; return { resizingComponentId, draggingComponentId, dragTarget }; @@ -1008,7 +985,6 @@ function ContainerWrapper({ ref={containerRef} style={{ ...styles, height: canvasHeight }} className={cx('real-canvas', { - // 'show-grid': isDragging || isResizing || dragTarget === 'canvas', 'show-grid': (!!resizingComponentId && !dragTarget) || (!!draggingComponentId && !dragTarget) || isDropping, })} id="real-canvas" diff --git a/frontend/src/Editor/DragContainer.jsx b/frontend/src/Editor/DragContainer.jsx index 380bdbafb4..7c719cb851 100644 --- a/frontend/src/Editor/DragContainer.jsx +++ b/frontend/src/Editor/DragContainer.jsx @@ -24,10 +24,7 @@ export default function DragContainer({ onDrag, gridWidth, selectedComponents = [], - // setIsDragging, - // setIsResizing, currentLayout, - // subContainerWidths, draggedSubContainer, }) { const lastDraggedEventsRef = useRef(null); @@ -112,8 +109,6 @@ export default function DragContainer({ }, }; - // const [dragTarget, useGridStore.getState().actions.setDragTarget] = useDragTarget(); - // const [draggedTarget, setDraggedTarget] = useState(); const moveableRef = useRef(); const draggedOverElemRef = useRef(null); const childMoveableRefs = useRef({}); @@ -171,7 +166,6 @@ export default function DragContainer({ console.error('Error---->', error); } }, [hoveredComponent, reloadGrid]); - // }, [JSON.stringify(selectedComponents), JSON.stringify(boxes), hoveredComponent]); useEffect(() => { setList(boxList); @@ -233,16 +227,12 @@ export default function DragContainer({ } }, [selectedComponents]); - // window.reloadGrid = reloadGrid; - useEffect(() => { setList(boxList); }, [JSON.stringify(boxes)]); const groupedTargets = [ - ...findHighestLevelofSelection(selectedComponents) - // .filter((component) => !component?.component?.parent) - .map((component) => '.ele-' + component.id), + ...findHighestLevelofSelection(selectedComponents).map((component) => '.ele-' + component.id), ]; useEffect(() => { @@ -257,8 +247,6 @@ export default function DragContainer({ lastDraggedEventsRef.current = posWithParent; }; - // const handleResize = useCallback((e) => handleWidgetResize(e, list, boxes, gridWidth), [list, boxes, gridWidth]); - return mode === 'edit' ? ( <> { performance.mark('onResizeStart'); useGridStore.getState().actions.setResizingComponentId(e.target.id); - // setIsResizing(true); e.setMin([gridWidth, 10]); - // if (currentLayout === 'mobile' && autoComputeLayout) { - // turnOffAutoLayout(); - // return false; - // } }} onResizeGroupStart={({ events }) => { const parentElm = events[0].target.closest('.real-canvas'); @@ -500,15 +483,12 @@ export default function DragContainer({ if (hoveredComponent !== e.target.id) { return false; } - // setDraggedTarget(e.target.id); }} onDragEnd={(e) => { try { if (isDraggingRef.current) { - // setTimeout(() => useGridStore.getState().actions.setDraggingComponentId(null)); useGridStore.getState().actions.setDraggingComponentId(null); isDraggingRef.current = false; - // setIsDragging(false); } if (draggedSubContainer) { @@ -612,20 +592,16 @@ export default function DragContainer({ element.classList.remove('show-grid'); element.classList.add('hide-grid'); }); - // setDraggedTarget(); }} onDrag={(e) => { if (!isDraggingRef.current) { useGridStore.getState().actions.setDraggingComponentId(e.target.id); isDraggingRef.current = true; - // setIsDragging(true); } if (draggedSubContainer) { return; } - // if (e.target.id !== draggedTarget) { - // setDraggedTarget(e.target.id); - // } + if (!draggedSubContainer) { const parentComponent = widgets[widgets[e.target.id]?.component?.parent]; let top = e.translate[1]; @@ -667,7 +643,7 @@ export default function DragContainer({ }); const parentWidgetId = draggedOverContainer.getAttribute('data-parent') || draggedOverElem?.id; document.getElementById('canvas-' + parentWidgetId)?.classList.add('show-grid'); - console.log('dragTarget-- parentWidgetId', parentWidgetId); + useGridStore.getState().actions.setDragTarget(parentWidgetId); if ( @@ -679,7 +655,7 @@ export default function DragContainer({ draggedOverElemRef.current = draggedOverContainer; } } - console.log('getOffset--', getOffset(e.target, document.querySelector('#real-canvas'))); + const offset = getOffset(e.target, document.querySelector('#real-canvas')); if (document.getElementById('moveable-drag-ghost')) { document.getElementById('moveable-drag-ghost').style.transform = `translate(${offset.x}px, ${offset.y}px)`; @@ -862,8 +838,3 @@ function getOffset(childElement, grandparentElement) { return { x: offsetX, y: offsetY }; } - -DragContainer.whyDidYouRender = { - logOnDifferentValues: true, - customName: 'WDYRDragContainer', -}; diff --git a/frontend/src/Editor/DraggableBox.jsx b/frontend/src/Editor/DraggableBox.jsx index d83608b23e..653a5186ff 100644 --- a/frontend/src/Editor/DraggableBox.jsx +++ b/frontend/src/Editor/DraggableBox.jsx @@ -16,8 +16,6 @@ import WidgetBox from './WidgetBox'; import * as Sentry from '@sentry/react'; import { findHighestLevelofSelection } from './DragContainer'; -// const noOfGrid = 43; - function computeWidth(currentLayoutOptions) { return `${currentLayoutOptions?.width}%`; } @@ -240,9 +238,4 @@ const DraggableBox = React.memo( } ); -// DraggableBox.whyDidYouRender = { -// logOnDifferentValues: true, -// customName: 'WDYRDraggableBox', -// }; - export { DraggableBox }; diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index a16b5ec5de..70dffb8aa8 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -212,7 +212,6 @@ const EditorComponent = (props) => { const selectionRef = useRef(null); const prevAppDefinition = useRef(appDefinition); - const prevEventsStoreRef = useRef(events); const onAppLoadAndPageLoadEventsAreTriggered = useRef(false); @@ -534,11 +533,6 @@ const EditorComponent = (props) => { socket?.addEventListener('message', (event) => { const data = event.data.replace(/^"(.+(?="$))"$/, '$1'); if (data === 'versionReleased') fetchApp(); - // else if (data === 'dataQueriesChanged') { - // fetchDataQueries(editingVersion?.id); - // } else if (data === 'dataSourcesChanged') { - // fetchDataSources(editingVersion?.id); - // } }); }; @@ -658,14 +652,6 @@ const EditorComponent = (props) => { const handleQueryPaneDragging = (bool) => setIsQueryPaneDragging(bool); const handleQueryPaneExpanding = (bool) => setIsQueryPaneExpanded(bool); - const handleOnComponentOptionChanged = (component, optionName, value) => { - return onComponentOptionChanged(component, optionName, value); - }; - - const handleOnComponentOptionsChanged = (component, options) => { - return onComponentOptionsChanged(component, options); - }; - const changeDarkMode = (newMode) => { useCurrentStateStore.getState().actions.setCurrentState({ globals: { @@ -676,17 +662,9 @@ const EditorComponent = (props) => { props.switchDarkMode(newMode); }; - // const handleEvent = memoizeFunction((eventName, event, options) => { - // return onEvent(getEditorRef(), eventName, event, options, 'edit'); - // }); - - // const handleEvent = (eventName, event, options) => { - // return onEvent(getEditorRef(), eventName, event, options, 'edit'); - // }; - const handleEvent = React.useCallback((eventName, event, options) => { - console.log('here--- handleEvent'); return onEvent(getEditorRef(), eventName, event, options, 'edit'); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleRunQuery = (queryId, queryName) => runQuery(getEditorRef(), queryId, queryName); @@ -725,7 +703,6 @@ const EditorComponent = (props) => { }; const getPagesWithIds = () => { - //! Needs attention return Object.entries(appDefinition?.pages).map(([id, page]) => ({ ...page, id })); }; @@ -941,8 +918,6 @@ const EditorComponent = (props) => { events: newEvents, }); } - - // computeComponentState(currentComponents); }) .finally(async () => { const currentPageEvents = data.events.filter( @@ -1153,8 +1128,6 @@ const EditorComponent = (props) => { const paramDiff = computeComponentPropertyDiff(appDefinitionDiff, appDefinition, appDiffOptions); const updateDiff = computeAppDiff(paramDiff, currentPageId, appDiffOptions, currentLayout); - console.log('here--- updateDiff--- ', paramDiff, updateDiff); - if (updateDiff['error']) { const platform = navigator?.userAgentData?.platform || navigator?.platform || 'unknown'; const isPlatformMac = platform.toLowerCase().indexOf('mac') > -1; diff --git a/frontend/src/Editor/EditorSelecto.jsx b/frontend/src/Editor/EditorSelecto.jsx index 746f089db5..db85ebda18 100644 --- a/frontend/src/Editor/EditorSelecto.jsx +++ b/frontend/src/Editor/EditorSelecto.jsx @@ -27,7 +27,6 @@ const EditorSelecto = ({ const onAreaSelectionStart = useCallback( (e) => { - console.log('onAreaSelectionStart', e); const isMultiSelect = e.inputEvent.shiftKey || useEditorStore.getState().selectedComponents.length > 0; setSelectionInProgress(true); setSelectedComponents([...(isMultiSelect ? useEditorStore.getState().selectedComponents : EMPTY_ARRAY)]); @@ -36,7 +35,6 @@ const EditorSelecto = ({ ); const onAreaSelection = useCallback((e) => { - console.log('onAreaSelection', e); e.added.forEach((el) => { el.classList.add('resizer-select'); }); @@ -52,7 +50,6 @@ const EditorSelecto = ({ const onAreaSelectionEnd = useCallback( (e) => { - console.log('onAreaSelectionEnd', e); setSelectionInProgress(false); e.selected.forEach((el, index) => { const id = el.getAttribute('widgetid'); diff --git a/frontend/src/Editor/SubContainer.jsx b/frontend/src/Editor/SubContainer.jsx index e6cf387104..1fd04a9fa0 100644 --- a/frontend/src/Editor/SubContainer.jsx +++ b/frontend/src/Editor/SubContainer.jsx @@ -1,7 +1,7 @@ /* eslint-disable import/no-named-as-default */ -import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; import { useDrop } from 'react-dnd'; -import { EditorConstants, ItemTypes } from './editorConstants'; +import { ItemTypes } from './editorConstants'; import { DraggableBox } from './DraggableBox'; import update from 'immutability-helper'; import _, { isEmpty } from 'lodash'; @@ -21,23 +21,17 @@ import { useGridStore, useResizingComponentId } from '@/_stores/gridStore'; import { isPDFSupported } from '@/_stores/utils'; import GhostWidget from './GhostWidget'; -const deviceWindowWidth = EditorConstants.deviceWindowWidth; - export const SubContainer = ({ mode, snapToGrid, onComponentClick, onEvent, - // appDefinition, appDefinitionChanged, - // onComponentOptionChanged, - // onComponentOptionsChanged, appLoading, zoomLevel, parent, parentRef, setSelectedComponent, - // deviceWindowWidth, selectedComponent, currentLayout, removeComponent, @@ -56,19 +50,16 @@ export const SubContainer = ({ childComponents = null, listmode = null, columns = 1, - // setSubContainerWidths, parentWidgetId, - // turnOffAutoLayout, }) => { //Todo add custom resolve vars for other widgets too - const mounted = useMounted(); + const widgetResolvables = Object.freeze({ Listview: 'listItem', }); const appDefinition = useEditorStore((state) => state.appDefinition, shallow); - const customResolverVariable = widgetResolvables[parentComponent?.component]; const currentState = useCurrentState(); const { selectedComponents } = useEditorStore( (state) => ({ @@ -79,7 +70,6 @@ export const SubContainer = ({ const resizingComponentId = useResizingComponentId(); - // const [noOfGrids] = useNoOfGrid(); const noOfGrids = 43; const { isGridActive } = useGridStore((state) => ({ isGridActive: state.activeGrid === parent }), shallow); @@ -96,6 +86,7 @@ export const SubContainer = ({ zoomLevel = zoomLevel || 1; + // eslint-disable-next-line react-hooks/exhaustive-deps const allComponents = appDefinition.pages[currentPageId]?.components ?? {}; const allChildComponents = useMemo(() => { @@ -109,11 +100,10 @@ export const SubContainer = ({ return _childWidgets; }, [allComponents, parent]); - // const [boxes, setBoxes] = useState(allComponents); const [childWidgets, setChildWidgets] = useState(() => allChildComponents); const [isDragging, setIsDragging] = useState(false); const [isResizing, setIsResizing] = useState(false); - // const [subContainerHeight, setSubContainerHeight] = useState('100%'); //used to determine the height of the sub container for modal + const subContainerHeightRef = useRef(height ?? '100%'); useEffect(() => { @@ -237,44 +227,6 @@ export const SubContainer = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [childWidgets]); - // const { draggingState } = useDragLayer((monitor) => { - // // TODO: Need to move to a performant version of the block below - // if (monitor.getItem()) { - // if (monitor.getItem().id === undefined) { - // if (parentRef.current) { - // const currentOffset = monitor.getSourceClientOffset(); - // if (currentOffset) { - // const canvasBoundingRect = parentRef?.current - // ?.getElementsByClassName('real-canvas')[0] - // ?.getBoundingClientRect(); - // if (!canvasBoundingRect) return { draggingState: false }; - // if ( - // currentOffset.x > canvasBoundingRect.x && - // currentOffset.x < canvasBoundingRect.x + canvasBoundingRect.width - // ) { - // return { draggingState: true }; - // } - // } - // } - // } - // } - - // if (monitor.isDragging() && monitor.getItem().parent) { - // if (monitor.getItem().parent === parent) { - // return { draggingState: true }; - // } else { - // return { draggingState: false }; - // } - // } else { - // return { draggingState: false }; - // } - // }); - - //!Todo: need to check: this never gets called as draggingState is always false - // useEffect(() => { - // setIsDragging(draggingState); - // }, [draggingState]); - const [{ isOver, isOverCurrent }, drop] = useDrop( () => ({ accept: ItemTypes.BOX, @@ -458,14 +410,11 @@ export const SubContainer = ({ appDefinition, appDefinitionChanged, currentState, - // onComponentOptionChanged, - // onComponentOptionsChanged, appLoading, zoomLevel, setSelectedComponent, removeComponent, currentLayout, - // deviceWindowWidth, selectedComponents, darkMode, readOnly, @@ -497,8 +446,6 @@ export const SubContainer = ({ {checkParentVisibility() && Object.entries({ ...childWidgets, - // ...(resizingComponentId && - // childWidgets[resizingComponentId] && { resizingComponentId: childWidgets[resizingComponentId] }), }).map(([key, box]) => { const canShowInCurrentLayout = box.component.definition.others[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop'].value; @@ -551,13 +498,9 @@ export const SubContainer = ({ allComponents={allComponents} {...box} mode={mode} - // resizingStatusChanged={(status) => setIsResizing(status)} - // draggingStatusChanged={(status) => setIsDragging(status)} inCanvas={true} zoomLevel={zoomLevel} - // setSelectedComponent={setSelectedComponent} selectedComponent={selectedComponent} - // deviceWindowWidth={deviceWindowWidth} isSelectedComponent={ mode === 'edit' ? selectedComponents.find((component) => component.id === key) : false } @@ -569,36 +512,8 @@ export const SubContainer = ({ onComponentHover={onComponentHover} hoveredComponent={hoveredComponent} parentId={parent} - // sideBarDebugger={sideBarDebugger} isMultipleComponentsSelected={selectedComponents?.length > 1 ? true : false} exposedVariables={exposedVariables ?? {}} - // childComponents={childComponents[key]} - // containerProps={{ - // mode, - // snapToGrid, - // onComponentClick, - // onEvent, - // appDefinition, - // appDefinitionChanged, - // currentState, - // onComponentOptionChanged, - // onComponentOptionsChanged, - // appLoading, - // zoomLevel, - // setSelectedComponent, - // removeComponent, - // currentLayout, - // deviceWindowWidth, - // selectedComponents, - // darkMode, - // readOnly, - // onComponentHover, - // hoveredComponent, - // sideBarDebugger, - // currentPageId, - // childComponents, - // setSubContainerWidths, - // }} getContainerProps={getContainerProps} /> @@ -623,7 +538,6 @@ export const SubContainer = ({
)} - {/* ); }; @@ -681,7 +595,7 @@ const SubWidgetWrapper = ({ useEffect(() => { const controlBox = document.querySelector(`[target-id="${id}"]`); - console.log('controlBox', { hide: !isOnScreen && isSelected && !isDragging && !isResizing, isOnScreen }); + // console.log('controlBox', { hide: !isOnScreen && isSelected && !isDragging && !isResizing, isOnScreen }); //adding attribute instead of class since react-moveable seems to replace classes internally on scroll stop if (!isOnScreen && isSelected && !isDragging && !isResizing) { controlBox?.classList.add('hide-control'); @@ -720,7 +634,6 @@ const SubWidgetWrapper = ({ }; const SubContianerWrapper = ({ children, isDragging, isResizing, isGridActive, readOnly, drop, styles, parent }) => { - // const [dragTarget] = useDragTarget(); return (
diff --git a/frontend/src/Editor/SubCustomDragLayer.jsx b/frontend/src/Editor/SubCustomDragLayer.jsx index 4672e3345a..306abc2424 100644 --- a/frontend/src/Editor/SubCustomDragLayer.jsx +++ b/frontend/src/Editor/SubCustomDragLayer.jsx @@ -47,8 +47,6 @@ function getItemStyles(delta, item, initialOffset, currentOffset, parentRef, par [x, y] = snapToGrid(canvasWidth, x, y); - console.log(`translate(${x}px, ${y}px)`); - const transform = `translate(${x}px, ${y}px)`; return { transform, diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index efaa211628..6b87da50a1 100755 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -1,7 +1,6 @@ -// import './wdyr'; // <--- first import import React from 'react'; import { render } from 'react-dom'; -// import { createRoot } from 'react-dom/client'; + import * as Sentry from '@sentry/react'; import { useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from 'react-router-dom'; import { appService } from '@/_services'; @@ -9,7 +8,7 @@ import { App } from './App'; // eslint-disable-next-line import/no-unresolved import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; -// import LanguageDetector from 'i18next-browser-languagedetector'; + import Backend from 'i18next-http-backend'; const AppWithProfiler = Sentry.withProfiler(App); From 1c4c272c9f23933308a0385aac9c32d29f7de79d Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Thu, 4 Apr 2024 16:44:02 +0530 Subject: [PATCH 10/13] Fixed the crash on Form component --- frontend/src/Editor/BoxUI.jsx | 1 + frontend/src/Editor/Components/Form/Form.jsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/Editor/BoxUI.jsx b/frontend/src/Editor/BoxUI.jsx index 3c0139aaea..b8b8798e6f 100644 --- a/frontend/src/Editor/BoxUI.jsx +++ b/frontend/src/Editor/BoxUI.jsx @@ -165,6 +165,7 @@ const BoxUI = (props) => { currentLayout={currentLayout} currentState={currentState} currentPageId={currentPageId} + getContainerProps={component.component === 'Form' ? getContainerProps : null} />
diff --git a/frontend/src/Editor/Components/Form/Form.jsx b/frontend/src/Editor/Components/Form/Form.jsx index 00b80ee0cb..0a9e4615e6 100644 --- a/frontend/src/Editor/Components/Form/Form.jsx +++ b/frontend/src/Editor/Components/Form/Form.jsx @@ -280,6 +280,7 @@ export const Form = function Form(props) { key={index} > ); From d4e9f45ae2b1fdf38a590d2a3107e725ad765e2e Mon Sep 17 00:00:00 2001 From: arpitnath Date: Thu, 4 Apr 2024 20:54:13 +0530 Subject: [PATCH 11/13] Optimize reference update tracking by sourcing from direct modifications rather than state diff comparisons. --- frontend/src/Editor/CodeEditor/utils.js | 6 ++-- frontend/src/Editor/Editor.jsx | 39 +++------------------ frontend/src/_helpers/appUtils.js | 35 +++++++++++++++++++ frontend/src/_stores/editorStore.js | 2 ++ frontend/src/_stores/resolverStore.js | 45 ++++++++++++++++++++++++- frontend/src/_stores/utils.js | 7 +++- 6 files changed, 95 insertions(+), 39 deletions(-) diff --git a/frontend/src/Editor/CodeEditor/utils.js b/frontend/src/Editor/CodeEditor/utils.js index 6bf6dbd7ca..2a0d301af4 100644 --- a/frontend/src/Editor/CodeEditor/utils.js +++ b/frontend/src/Editor/CodeEditor/utils.js @@ -243,10 +243,10 @@ export const resolveReferences = (query, validationSchema, customResolvers = {}, if (fxActive && (value.startsWith('#') || value.includes('table-'))) { value = JSON.stringify(value); } - const { toResolveReference, jsExpression, jsExpMatch } = inferJSExpAndReferences(value, lookupTable.hints); - const isComponentValue = toResolveReference?.startsWith('components.') || false; //!Notes: As we removed the updating of references on currentState changes, exposed variable of components are dynamic and cannot be controlled in any form, so we are resolving only components references with our legacy approach. - if (!isComponentValue && !jsExpMatch && toResolveReference && lookupTable.hints.has(toResolveReference)) { + + // const isComponentValue = toResolveReference?.startsWith('components.') || false; //!Notes: As we removed the updating of references on currentState changes, exposed variable of components are dynamic and cannot be controlled in any form, so we are resolving only components references with our legacy approach. + if (!jsExpMatch && toResolveReference && lookupTable.hints.has(toResolveReference)) { const idToLookUp = lookupTable.hints.get(toResolveReference); resolvedValue = lookupTable.resolvedRefs.get(idToLookUp); diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index 70dffb8aa8..696c271da6 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -326,47 +326,19 @@ const EditorComponent = (props) => { flushComponentsToRender(updatedComponentIds); } + const lastUpdatedRef = useResolveStore((state) => state.lastUpdatedRefs, shallow); + useEffect(() => { - const isEditorReady = useCurrentStateStore.getState().isEditorReady; - - if (!isEditorReady || !onAppLoadAndPageLoadEventsAreTriggered.current) return; - - const diffState = diff(prevCurrentStateRef.current, currentState); - - if (Object.keys(diffState).length > 0) { - const entitiesToTrack = ['queries', 'components', 'variables', 'page', 'constants']; - - const entitiesChanged = Object.keys(diffState).filter((entity) => entitiesToTrack.includes(entity)); - - if (entitiesChanged.length === 0) return; - - const diffObj = entitiesChanged.reduce((acc, entity) => { - acc[entity] = diffState[entity]; - return acc; - }, {}); - - const allPaths = entitiesChanged.reduce((acc, entity) => { - const paths = Object.keys(diffObj[entity]).map((key) => { - return generatePath(diffObj[entity], key); - }); - - acc[entity] = paths.map((path) => `${entity}.${path}`); - return acc; - }, {}); - - const currentStatePaths = Object.values(allPaths).flat(); - + if (lastUpdatedRef.length > 0) { const currentComponents = useEditorStore.getState().appDefinition?.pages?.[currentPageId]?.components || {}; - const componentIdsWithReferences = findComponentsWithReferences(currentComponents, currentStatePaths); + const componentIdsWithReferences = findComponentsWithReferences(currentComponents, lastUpdatedRef); if (componentIdsWithReferences.length > 0) { batchUpdateComponents(componentIdsWithReferences); } - - prevCurrentStateRef.current = JSON.parse(JSON.stringify(currentState)); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(currentState)]); + }, [lastUpdatedRef]); useEffect( () => { @@ -1175,7 +1147,6 @@ const EditorComponent = (props) => { }); useResolveStore.getState().actions.addEntitiesToMap(componentEntityArray); - useResolveStore.getState().actions.addAppSuggestions({ components: newComponentsExposedData, }); diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 2aa1b8d5c1..28788a0c0d 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -37,6 +37,7 @@ import { useAppDataStore } from '@/_stores/appDataStore'; import { useEditorStore } from '@/_stores/editorStore'; import { useGridStore } from '@/_stores/gridStore'; import { useResolveStore } from '@/_stores/resolverStore'; +import { handleLowPriorityWork } from './editorHelpers'; const ERROR_TYPES = Object.freeze({ ReferenceError: 'ReferenceError', @@ -115,11 +116,31 @@ export function onComponentOptionChanged(component, option_name, value) { let componentData = components[componentName] || {}; componentData[option_name] = value; + const path = option_name ? `components.${componentName}.${option_name}` : null; + if (isEditorReady) { // Always update the current state if editor is ready useCurrentStateStore.getState().actions.setCurrentState({ components: { ...components, [componentName]: componentData }, }); + + if (!_.isEmpty(useResolveStore.getState().lookupTable?.resolvedRefs) && path) { + const lookUpTable = useResolveStore.getState().lookupTable; + + const existingRef = lookUpTable.resolvedRefs?.get(lookUpTable.hints?.get(path)); + + if (typeof existingRef === 'function') return; + + const shouldUpdateRef = existingRef !== componentData[option_name]; + + if (shouldUpdateRef) { + handleLowPriorityWork(() => { + useResolveStore + .getState() + .actions.updateResolvedRefsOfHints([{ hint: path, newRef: componentData[option_name] }]); + }); + } + } } else { // Update the duplicate state if editor is not ready duplicateCurrentState = { ...components, [componentName]: componentData }; @@ -1091,6 +1112,14 @@ export function runQuery( }, errors: {}, }); + useResolveStore.getState().actions.addAppSuggestions({ + queries: { + [queryName]: { + data: [], + isLoading: true, + }, + }, + }); } let queryExecutionPromise = null; if (query.kind === 'runjs') { @@ -1267,9 +1296,15 @@ export function runQuery( queries: { [queryName]: { data: finalData, + isLoading: false, }, }, }); + + const basePath = `queries.${queryName}`; + + useResolveStore.getState().actions.updateLastUpdatedRefs([`${basePath}.data`, `${basePath}.isLoading`]); + resolve({ status: 'ok', data: finalData }); onEvent(_self, 'onDataQuerySuccess', queryEvents, mode); } diff --git a/frontend/src/_stores/editorStore.js b/frontend/src/_stores/editorStore.js index c55842e8a6..cc540c6992 100644 --- a/frontend/src/_stores/editorStore.js +++ b/frontend/src/_stores/editorStore.js @@ -1,6 +1,7 @@ import _ from 'lodash'; import { create } from './utils'; import { v4 as uuid } from 'uuid'; +import { useResolveStore } from './resolverStore'; const STORE_NAME = 'Editor'; export const EMPTY_ARRAY = []; @@ -117,4 +118,5 @@ export const flushComponentsToRender = (componentIds = []) => { if (!componentIds.length) return; useEditorStore.getState().actions.flushComponentsNeedsUpdateOnNextRender(componentIds); + useResolveStore.getState().actions.getLastUpdatedRefs(); }; diff --git a/frontend/src/_stores/resolverStore.js b/frontend/src/_stores/resolverStore.js index 29d608f35b..48c6b94fcd 100644 --- a/frontend/src/_stores/resolverStore.js +++ b/frontend/src/_stores/resolverStore.js @@ -58,7 +58,7 @@ const initialState = { hints: {}, resolvedRefs: {}, }, - + lastUpdatedRefs: [], referenceMapper: new ReferencesBiMap(), }; @@ -78,6 +78,16 @@ export const useResolveStore = create( })); }, + flushLastUpdatedRefs: () => { + set(() => ({ lastUpdatedRefs: [] })); + }, + getLastUpdatedRefs: () => { + return get().lastUpdatedRefs; + }, + //for queries references used in component definitons + updateLastUpdatedRefs: (updatedRefs) => { + set(() => ({ lastUpdatedRefs: updatedRefs })); + }, addAppSuggestions: (partialRefState) => { if (Object.keys(partialRefState).length === 0) return; @@ -86,6 +96,8 @@ export const useResolveStore = create( const lookupHintsMap = new Map([...get().lookupTable.hints]); const lookupResolvedRefs = new Map([...get().lookupTable.resolvedRefs]); + const newUpdatedrefs = []; + hintsMap.forEach((value, key) => { const alreadyExists = lookupHintsMap.has(key); @@ -97,6 +109,7 @@ export const useResolveStore = create( resolvedRefs.delete(value); resolvedRefs.set(existingLookupId, newResolvedRef); + newUpdatedrefs.push(key); } }); @@ -118,6 +131,7 @@ export const useResolveStore = create( hints: lookupHintsMap, resolvedRefs: lookupResolvedRefs, }, + lastUpdatedRefs: newUpdatedrefs, })); }, @@ -158,6 +172,35 @@ export const useResolveStore = create( }); }, + updateResolvedRefsOfHints: (resolvedRefs = []) => { + const lookupResolvedRefs = new Map([...get().lookupTable.resolvedRefs]); + const hintsMap = new Map([...get().lookupTable.hints]); + + const updatedList = []; + + resolvedRefs.forEach((ref) => { + if (!ref.hint || !ref.newRef || !hintsMap.has(ref.hint)) return; + + const refId = hintsMap.get(ref.hint); + const currentRef = lookupResolvedRefs.get(refId); + + if (currentRef !== ref.newRef) { + lookupResolvedRefs.set(refId, ref.newRef); + updatedList.push(ref.hint); + } + }); + + if (updatedList.length > 0) { + set(() => ({ + lookupTable: { + ...get().lookupTable, + resolvedRefs: lookupResolvedRefs, + }, + lastUpdatedRefs: updatedList, + })); + } + }, + updateJSHints: () => { const hints = createJavaScriptSuggestions(); set(() => ({ suggestions: { ...get().suggestions, jsHints: hints } })); diff --git a/frontend/src/_stores/utils.js b/frontend/src/_stores/utils.js index 9f89e41497..e7d2df97e4 100644 --- a/frontend/src/_stores/utils.js +++ b/frontend/src/_stores/utils.js @@ -465,7 +465,12 @@ export function createReferencesLookup(refState, forQueryParams = false, initalL } if (_type === 'Array') { map.set(newPath, { type: _type }); - buildMap(value, newPath); + + if (path.startsWith('queries') && key === 'data' && value.length > 2) { + // do nothing + } else { + buildMap(value, newPath); + } } else { map.set(newPath, { type: _type }); } From 061636d0256db09bcc8d0ffe2ef26e2f6a8716af Mon Sep 17 00:00:00 2001 From: arpitnath Date: Thu, 4 Apr 2024 21:01:40 +0530 Subject: [PATCH 12/13] clean up --- frontend/src/Editor/CodeEditor/utils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/Editor/CodeEditor/utils.js b/frontend/src/Editor/CodeEditor/utils.js index 2a0d301af4..960e735edf 100644 --- a/frontend/src/Editor/CodeEditor/utils.js +++ b/frontend/src/Editor/CodeEditor/utils.js @@ -245,7 +245,6 @@ export const resolveReferences = (query, validationSchema, customResolvers = {}, } const { toResolveReference, jsExpression, jsExpMatch } = inferJSExpAndReferences(value, lookupTable.hints); - // const isComponentValue = toResolveReference?.startsWith('components.') || false; //!Notes: As we removed the updating of references on currentState changes, exposed variable of components are dynamic and cannot be controlled in any form, so we are resolving only components references with our legacy approach. if (!jsExpMatch && toResolveReference && lookupTable.hints.has(toResolveReference)) { const idToLookUp = lookupTable.hints.get(toResolveReference); resolvedValue = lookupTable.resolvedRefs.get(idToLookUp); From f3cb2a04b506a05ae6e3d247168b02ed7d01de2f Mon Sep 17 00:00:00 2001 From: Johnson Cherian Date: Fri, 5 Apr 2024 11:54:49 +0530 Subject: [PATCH 13/13] fix: update appdefinition to editorstore in viewert (#9297) --- frontend/src/Editor/Viewer.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index 9695e5cd8f..3b4276a7cc 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -96,6 +96,10 @@ class ViewerComponent extends React.Component { appDefinition: { ...appDefData }, pages: appDefData.pages, }); + + useEditorStore.getState().actions.updateEditorState({ + appDefinition: { ...appDefData }, + }); }; setStateForContainer = async (data, appVersionId) => {