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 d275fd5191..17a16a643f 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 } 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/BoxUI.jsx b/frontend/src/Editor/BoxUI.jsx index 982af034cf..b8b8798e6f 100644 --- a/frontend/src/Editor/BoxUI.jsx +++ b/frontend/src/Editor/BoxUI.jsx @@ -32,12 +32,11 @@ const BoxUI = (props) => { changeCanDrag, removeComponent, canvasWidth, - // exposedVariables, - // fireEvent, parentId, customResolvables, currentLayout, readOnly, + currentPageId, } = props; const darkMode = localStorage.getItem('darkMode') === 'true'; @@ -120,10 +119,9 @@ const BoxUI = (props) => {
{ dataCy={`draggable-widget-${String(component.name).toLowerCase()}`} currentLayout={currentLayout} currentState={currentState} + currentPageId={currentPageId} + getContainerProps={component.component === 'Form' ? getContainerProps : null} />
diff --git a/frontend/src/Editor/CodeBuilder/CodeBuilder.jsx b/frontend/src/Editor/CodeBuilder/CodeBuilder.jsx index d28fb36256..e7e6a10adf 100644 --- a/frontend/src/Editor/CodeBuilder/CodeBuilder.jsx +++ b/frontend/src/Editor/CodeBuilder/CodeBuilder.jsx @@ -95,7 +95,6 @@ export function CodeBuilder({ initialValue, onChange, components }) { return { item: item }; }); } else { - console.log(currentWord); filteredVariables = fuse.search(currentWord); } return filteredVariables.map((variable) => renderVariable(type, key, variable.item.name)); diff --git a/frontend/src/Editor/CodeEditor/utils.js b/frontend/src/Editor/CodeEditor/utils.js index 6bf6dbd7ca..960e735edf 100644 --- a/frontend/src/Editor/CodeEditor/utils.js +++ b/frontend/src/Editor/CodeEditor/utils.js @@ -243,10 +243,9 @@ 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)) { + + if (!jsExpMatch && toResolveReference && lookupTable.hints.has(toResolveReference)) { const idToLookUp = lookupTable.hints.get(toResolveReference); resolvedValue = lookupTable.resolvedRefs.get(idToLookUp); diff --git a/frontend/src/Editor/Components/Form/Form.jsx b/frontend/src/Editor/Components/Form/Form.jsx index 81f69c84bc..0a9e4615e6 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} /> ); diff --git a/frontend/src/Editor/Container.jsx b/frontend/src/Editor/Container.jsx index 312bf32ffb..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, @@ -372,6 +366,86 @@ export const Container = ({ zoomLevel ); + // Logic to add default child components + const childrenBoxes = {}; + if (componentMeta.defaultChildren) { + const parentMeta = componentMeta; + const widgetResolvables = Object.freeze({ + Listview: 'listItem', + }); + const customResolverVariable = widgetResolvables[parentMeta?.component]; + const defaultChildren = _.cloneDeep(parentMeta)['defaultChildren']; + const parentId = newComponent.id; + + 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 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 +453,8 @@ export const Container = ({ layouts: { ...newComponent.layout, }, - withDefaultChildren: newComponent.withDefaultChildren, }, + ...childrenBoxes, }; setBoxes(newBoxes); @@ -445,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)); @@ -679,8 +751,6 @@ export const Container = ({ }, [components]); const getContainerProps = React.useCallback((componentId) => { - const withDefaultChildren = boxes[componentId]?.withDefaultChildren; - return { mode, snapToGrid, @@ -696,7 +766,6 @@ export const Container = ({ currentLayout, selectedComponents, darkMode, - addDefaultChildren: withDefaultChildren, currentPageId, childComponents: childComponents[componentId], parentGridWidth: gridWidth, @@ -785,6 +854,7 @@ export const Container = ({ isMultipleComponentsSelected={selectedComponents?.length > 1 ? true : false} getContainerProps={getContainerProps} isVersionReleased={isVersionReleased} + currentPageId={currentPageId} /> ); @@ -803,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} @@ -862,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 } : {}), }; @@ -907,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 }; @@ -930,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/ControlledComponentToRender.jsx b/frontend/src/Editor/ControlledComponentToRender.jsx index 0415ce6354..a13b27252d 100644 --- a/frontend/src/Editor/ControlledComponentToRender.jsx +++ b/frontend/src/Editor/ControlledComponentToRender.jsx @@ -1,8 +1,8 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { getComponentToRender } from '@/_helpers/editorHelpers'; import _ from 'lodash'; -import { flushComponentsToRender, getComponentsToRenders } from '@/_stores/editorStore'; +import { getComponentsToRenders } from '@/_stores/editorStore'; function deepEqualityCheckusingLoDash(obj1, obj2) { return _.isEqual(obj1, obj2); @@ -17,6 +17,7 @@ export const shouldUpdate = (prevProps, nextProps) => { if (componentId) { const componentToRender = listToRender.find((item) => item === componentId); + if (componentToRender) { needToRender = true; } @@ -34,15 +35,6 @@ export const shouldUpdate = (prevProps, nextProps) => { const ComponentWrapper = React.memo(({ componentName, ...props }) => { const ComponentToRender = getComponentToRender(componentName); - const [shouldFlush, setShouldFlush] = useState(false); - - useEffect(() => { - if (shouldFlush) { - flushComponentsToRender(); - setShouldFlush(false); // Reset the condition - } - }, [shouldFlush]); - return ; }, shouldUpdate); 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 7b08f27c34..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}%`; } @@ -54,6 +52,7 @@ const DraggableBox = React.memo( customResolvables, parentId, getContainerProps, + currentPageId, }) => { const isResizing = useGridStore((state) => state.resizingComponentId === id); const [canDrag, setCanDrag] = useState(true); @@ -222,6 +221,7 @@ const DraggableBox = React.memo( customResolvables={customResolvables} parentId={parentId} getContainerProps={getContainerProps} + currentPageId={currentPageId} /> @@ -238,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 2a21c2af09..38ab16ec5e 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(); @@ -212,7 +212,8 @@ const EditorComponent = (props) => { const selectionRef = useRef(null); const prevAppDefinition = useRef(appDefinition); - const prevEventsStoreRef = useRef(events); + + const onAppLoadAndPageLoadEventsAreTriggered = useRef(false); useLayoutEffect(() => { resetAllStores(); @@ -287,27 +288,57 @@ const EditorComponent = (props) => { 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 })]); - const currentStateDiff = useEditorStore.getState().currentStateDiff; - useEffect(() => { - const isEditorReady = useCurrentStateStore.getState().isEditorReady; + const prevCurrentStateRef = useRef(currentState); - if (isEditorReady && currentStateDiff?.length > 0) { + /** + ** 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); + } + + const lastUpdatedRef = useResolveStore((state) => state.lastUpdatedRefs, shallow); + + useEffect(() => { + if (lastUpdatedRef.length > 0) { const currentComponents = useEditorStore.getState().appDefinition?.pages?.[currentPageId]?.components || {}; - const componentIdsWithReferences = findComponentsWithReferences(currentComponents, currentStateDiff); + const componentIdsWithReferences = findComponentsWithReferences(currentComponents, lastUpdatedRef); if (componentIdsWithReferences.length > 0) { - updateComponentsNeedsUpdateOnNextRender(componentIdsWithReferences); + batchUpdateComponents(componentIdsWithReferences); } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(currentStateDiff)]); + }, [lastUpdatedRef]); useEffect( () => { @@ -345,14 +376,6 @@ const EditorComponent = (props) => { } }, [currentLayout, mounted]); - const handleYmapEventUpdates = () => { - props.ymap?.set('eventHandlersUpdated', { - currentVersionId: currentVersionId, - currentSessionId: currentSessionId, - update: true, - }); - }; - const handleMessage = (event) => { const { data } = event; @@ -482,11 +505,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); - // } }); }; @@ -606,14 +624,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: { @@ -624,16 +634,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) => { return onEvent(getEditorRef(), eventName, event, options, 'edit'); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleRunQuery = (queryId, queryName) => runQuery(getEditorRef(), queryId, queryName); @@ -672,7 +675,6 @@ const EditorComponent = (props) => { }; const getPagesWithIds = () => { - //! Needs attention return Object.entries(appDefinition?.pages).map(([id, page]) => ({ ...page, id })); }; @@ -741,13 +743,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 }); @@ -898,6 +899,7 @@ const EditorComponent = (props) => { handleLowPriorityWork(async () => { await runQueries(useDataQueriesStore.getState().dataQueries, editorRef, true); await handleEvent('onPageLoad', currentPageEvents, {}, true); + await handleLowPriorityWork(() => (onAppLoadAndPageLoadEventsAreTriggered.current = true)); }); }); }; 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/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/Editor/SubContainer.jsx b/frontend/src/Editor/SubContainer.jsx index 6e0231abaf..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, @@ -51,25 +45,21 @@ export const SubContainer = ({ sideBarDebugger, onOptionChange, exposedVariables, - addDefaultChildren = false, height = '100%', currentPageId, 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) => ({ @@ -80,7 +70,6 @@ export const SubContainer = ({ const resizingComponentId = useResizingComponentId(); - // const [noOfGrids] = useNoOfGrid(); const noOfGrids = 43; const { isGridActive } = useGridStore((state) => ({ isGridActive: state.activeGrid === parent }), shallow); @@ -97,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(() => { @@ -110,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(() => { @@ -137,24 +126,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 +191,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 = { @@ -369,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, @@ -415,7 +235,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( @@ -591,21 +410,17 @@ export const SubContainer = ({ appDefinition, appDefinitionChanged, currentState, - // onComponentOptionChanged, - // onComponentOptionsChanged, appLoading, zoomLevel, setSelectedComponent, removeComponent, currentLayout, - // deviceWindowWidth, selectedComponents, darkMode, readOnly, onComponentHover, hoveredComponent, sideBarDebugger, - addDefaultChildren, currentPageId, childComponents, }; @@ -631,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; @@ -685,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 } @@ -703,37 +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, - // addDefaultChildren, - // currentPageId, - // childComponents, - // setSubContainerWidths, - // }} getContainerProps={getContainerProps} /> @@ -758,7 +538,6 @@ export const SubContainer = ({ )} - {/* ); }; @@ -816,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'); @@ -855,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/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) => { diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index c521b7ca7f..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', @@ -110,41 +111,40 @@ 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; + + const path = option_name ? `components.${componentName}.${option_name}` : null; + 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; - duplicateCurrentState = { ...components, [componentName]: componentData }; - if (option_name !== 'id') { - debouncedChange(); - } else if (!componentData?.id) { - debouncedChange(); + 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 }; + debouncedChange(); } return Promise.resolve(); @@ -1112,6 +1112,14 @@ export function runQuery( }, errors: {}, }); + useResolveStore.getState().actions.addAppSuggestions({ + queries: { + [queryName]: { + data: [], + isLoading: true, + }, + }, + }); } let queryExecutionPromise = null; if (query.kind === 'runjs') { @@ -1288,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/_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/_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 ( + + + + ); +}; 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/currentStateStore.js b/frontend/src/_stores/currentStateStore.js index c7284c5d67..bb7af52df0 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'; @@ -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/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' } - ) -); diff --git a/frontend/src/_stores/editorStore.js b/frontend/src/_stores/editorStore.js index 042b230fb5..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 = []; @@ -36,7 +37,6 @@ const initialState = { queryConfirmationList: [], currentPageId: null, currentSessionId: uuid(), - currentStateDiff: [], componentsNeedsUpdateOnNextRender: [], }; @@ -64,9 +64,18 @@ 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: [] })), + updateComponentsNeedsUpdateOnNextRender: (componentsNeedsUpdateOnNextRender) => { + set(() => ({ componentsNeedsUpdateOnNextRender })); + }, + flushComponentsNeedsUpdateOnNextRender: (toRemoveIds = []) => { + const currentComponents = get().componentsNeedsUpdateOnNextRender; + + if (currentComponents.length === 0 || toRemoveIds.length === 0) return; + + const updatedComponents = currentComponents.filter((item) => !toRemoveIds.includes(item)); + + set(() => ({ componentsNeedsUpdateOnNextRender: updatedComponents })); + }, updateQueryConfirmationList: (queryConfirmationList) => set({ queryConfirmationList }), setHoveredComponent: (hoveredComponent) => @@ -105,6 +114,9 @@ export const getComponentsToRenders = () => { return useEditorStore.getState().componentsNeedsUpdateOnNextRender; }; -export const flushComponentsToRender = () => { - useEditorStore.getState().actions.flushComponentsNeedsUpdateOnNextRender(); +export const flushComponentsToRender = (componentIds = []) => { + if (!componentIds.length) return; + + useEditorStore.getState().actions.flushComponentsNeedsUpdateOnNextRender(componentIds); + useResolveStore.getState().actions.getLastUpdatedRefs(); }; 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')) ?? {}; 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 }); } 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);