From 7c7f20b4ba0df68534fde42f6207fcefc638d10a Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 27 Mar 2025 14:06:16 +0530 Subject: [PATCH 1/3] Added support for tab navigation in editor and viewer. --- .../src/AppBuilder/AppCanvas/Container.jsx | 40 ++++++++++++++++++- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 15 +++++++ frontend/src/_stores/gridStore.js | 9 +++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index e622e1a2cd..9de350b810 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -58,9 +58,13 @@ export const Container = React.memo( const currentMode = useStore((state) => state.currentMode, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow); + const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents, shallow); const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; }, [componentType, index, currentMode]); + const reorderContainerChildren = useGridStore((state) => state.reorderContainerChildren); + const prevForceUpdateRef = useRef(0); + const prevComponentsOrder = useRef(components); const [{ isOverCurrent }, drop] = useDrop({ accept: 'box', @@ -146,6 +150,40 @@ export const Container = React.memo( [setLastCanvasClickPosition] ); + // Function to sort the components based on position in container for tab navigation + const sortedComponents = useMemo(() => { + const { triggerUpdate, containerId } = reorderContainerChildren; + + // If a forced update occurred for a different container, return the previous order + const isForcedUpdate = prevForceUpdateRef.current !== triggerUpdate; + if (isForcedUpdate) { + prevForceUpdateRef.current = triggerUpdate; + if (containerId !== id) { + return prevComponentsOrder.current; + } + } + + const currentPageComponents = getCurrentPageComponents() + + const newComponentsOrder = [...components].sort((a, b) => { + const aTop = currentPageComponents?.[a]?.layouts?.[currentLayout]?.top; + const bTop = currentPageComponents?.[b]?.layouts?.[currentLayout]?.top; + if (aTop !== bTop) { + return aTop - bTop; + } else { + const aLeft = currentPageComponents?.[a]?.layouts?.[currentLayout]?.left; + const bLeft = currentPageComponents?.[b]?.layouts?.[currentLayout]?.left; + if (aLeft !== bLeft) { + return aLeft - bLeft; + } + } + }); + + prevComponentsOrder.current = newComponentsOrder; + return newComponentsOrder; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [components, currentLayout, reorderContainerChildren.triggerUpdate]); + return (
- {components.map((id) => ( + {sortedComponents.map((id) => ( ', error); } @@ -775,6 +777,11 @@ export default function Grid({ gridWidth, currentLayout }) { ev.target.style.transform = `translate(${posX}px, ${posY}px)`; }); } + + const groupParentId = + boxList.find(({ id }) => id === groupResizeDataRef.current[0].target.id)?.parent ?? 'canvas'; + useGridStore.getState().actions.setReorderContainerChildren(groupParentId); + groupResizeDataRef.current = []; reloadGrid(); } catch (error) { @@ -841,6 +848,8 @@ export default function Grid({ gridWidth, currentLayout }) { useStore.getState().setDraggingComponentId(null); isDraggingRef.current = false; } + + const oldParentId = boxList.find((b) => b.id === e.target.id)?.parent ?? 'canvas'; prevDragParentId.current = null; newDragParentId.current = null; setDragParentId(null); @@ -880,6 +889,12 @@ export default function Grid({ gridWidth, currentLayout }) { // Apply transform for smooth transition e.target.style.transform = `translate(${left}px, ${top}px)`; + // Force reordering of conatiner if the parent has not changed + const newParentId = target.slotId === 'real-canvas' ? 'canvas' : target.slotId; + if (oldParentId === newParentId) { + useGridStore.getState().actions.setReorderContainerChildren(newParentId); + } + // Select the dragged component after drop setTimeout(() => setSelectedComponents([dragged.id])); } catch (error) { diff --git a/frontend/src/_stores/gridStore.js b/frontend/src/_stores/gridStore.js index 213f07ac16..d7112dd9ef 100644 --- a/frontend/src/_stores/gridStore.js +++ b/frontend/src/_stores/gridStore.js @@ -12,6 +12,10 @@ const initialState = { idGroupDragged: false, openModalWidgetId: null, subContainerWidths: {}, + reorderContainerChildren: { + containerId: null, + triggerUpdate: 0, + }, }; export const useGridStore = create( @@ -26,6 +30,11 @@ export const useGridStore = create( setOpenModalWidgetId: (openModalWidgetId) => set({ openModalWidgetId }), setSubContainerWidths: (id, width) => set((state) => ({ subContainerWidths: { ...state.subContainerWidths, [id]: width } })), + setReorderContainerChildren: (containerId) => + // Function to trigger reordering of specific container for tab navigation + set((state) => ({ + reorderContainerChildren: { containerId, triggerUpdate: state.reorderContainerChildren.triggerUpdate + 1 }, + })), }, }), { name: 'Grid Store' } From 6d5665177fffcb73bb0b10231529e57f15bb609f Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 3 Apr 2025 13:13:56 +0530 Subject: [PATCH 2/3] Fix: Move state from gridStore to gridSlice. --- frontend/src/AppBuilder/AppCanvas/Container.jsx | 2 +- frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx | 9 +++++---- frontend/src/AppBuilder/_stores/slices/gridSlice.js | 10 ++++++++++ frontend/src/_stores/gridStore.js | 9 --------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 9de350b810..19d97f0061 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -62,7 +62,7 @@ export const Container = React.memo( const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; }, [componentType, index, currentMode]); - const reorderContainerChildren = useGridStore((state) => state.reorderContainerChildren); + const reorderContainerChildren = useStore((state) => state.reorderContainerChildren, shallow); const prevForceUpdateRef = useRef(0); const prevComponentsOrder = useRef(components); diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index b7c9ebd6b6..174c68b475 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -67,6 +67,7 @@ export default function Grid({ gridWidth, currentLayout }) { const prevDragParentId = useRef(null); const newDragParentId = useRef(null); const [isGroupDragging, setIsGroupDragging] = useState(false); + const setReorderContainerChildren = useStore((state) => state.setReorderContainerChildren, shallow); useEffect(() => { const selectedSet = new Set(selectedComponents); @@ -536,7 +537,7 @@ export default function Grid({ gridWidth, currentLayout }) { }) ); } - useGridStore.getState().actions.setReorderContainerChildren(draggedOverElemId ?? 'canvas'); + setReorderContainerChildren(draggedOverElemId ?? 'canvas'); } catch (error) { console.error('Error dragging group', error); } @@ -697,7 +698,7 @@ export default function Grid({ gridWidth, currentLayout }) { resizeData.gw = _gridWidth; } handleResizeStop([resizeData]); - useGridStore.getState().actions.setReorderContainerChildren(currentWidget?.parent ?? 'canvas'); + setReorderContainerChildren(currentWidget?.parent ?? 'canvas'); } catch (error) { console.error('ResizeEnd error ->', error); } @@ -780,7 +781,7 @@ export default function Grid({ gridWidth, currentLayout }) { const groupParentId = boxList.find(({ id }) => id === groupResizeDataRef.current[0].target.id)?.parent ?? 'canvas'; - useGridStore.getState().actions.setReorderContainerChildren(groupParentId); + setReorderContainerChildren(groupParentId); groupResizeDataRef.current = []; reloadGrid(); @@ -892,7 +893,7 @@ export default function Grid({ gridWidth, currentLayout }) { // Force reordering of conatiner if the parent has not changed const newParentId = target.slotId === 'real-canvas' ? 'canvas' : target.slotId; if (oldParentId === newParentId) { - useGridStore.getState().actions.setReorderContainerChildren(newParentId); + setReorderContainerChildren(newParentId); } // Select the dragged component after drop diff --git a/frontend/src/AppBuilder/_stores/slices/gridSlice.js b/frontend/src/AppBuilder/_stores/slices/gridSlice.js index 642266a32b..37de5cf81a 100644 --- a/frontend/src/AppBuilder/_stores/slices/gridSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/gridSlice.js @@ -8,6 +8,10 @@ const initialState = { lastCanvasIdClick: '', lastCanvasClickPosition: null, draggingComponentId: null, + reorderContainerChildren: { + containerId: null, + triggerUpdate: 0, + }, }; export const createGridSlice = (set, get) => ({ @@ -73,4 +77,10 @@ export const createGridSlice = (set, get) => ({ setLastCanvasClickPosition: (position) => { set({ lastCanvasClickPosition: position }); }, + setReorderContainerChildren: (containerId) => { + // Function to trigger reordering of specific container for tab navigation + set((state) => ({ + reorderContainerChildren: { containerId, triggerUpdate: state.reorderContainerChildren.triggerUpdate + 1 }, + })); + }, }); diff --git a/frontend/src/_stores/gridStore.js b/frontend/src/_stores/gridStore.js index d7112dd9ef..213f07ac16 100644 --- a/frontend/src/_stores/gridStore.js +++ b/frontend/src/_stores/gridStore.js @@ -12,10 +12,6 @@ const initialState = { idGroupDragged: false, openModalWidgetId: null, subContainerWidths: {}, - reorderContainerChildren: { - containerId: null, - triggerUpdate: 0, - }, }; export const useGridStore = create( @@ -30,11 +26,6 @@ export const useGridStore = create( setOpenModalWidgetId: (openModalWidgetId) => set({ openModalWidgetId }), setSubContainerWidths: (id, width) => set((state) => ({ subContainerWidths: { ...state.subContainerWidths, [id]: width } })), - setReorderContainerChildren: (containerId) => - // Function to trigger reordering of specific container for tab navigation - set((state) => ({ - reorderContainerChildren: { containerId, triggerUpdate: state.reorderContainerChildren.triggerUpdate + 1 }, - })), }, }), { name: 'Grid Store' } From 4ea5f33265699ae58319f0f9f39fac557308a08f Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 3 Apr 2025 13:36:41 +0530 Subject: [PATCH 3/3] Fix: Extracted the sorting logic to a custom hook. --- .../src/AppBuilder/AppCanvas/Container.jsx | 39 +-------------- .../AppBuilder/_hooks/useSortedComponents.js | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 frontend/src/AppBuilder/_hooks/useSortedComponents.js diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 19d97f0061..fcc0a95d4e 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -20,6 +20,7 @@ import NoComponentCanvasContainer from './NoComponentCanvasContainer'; import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; import { isPDFSupported } from '@/_helpers/appUtils'; import toast from 'react-hot-toast'; +import useSortedComponents from '../_hooks/useSortedComponents'; //TODO: Revisit the logic of height (dropRef) @@ -58,13 +59,9 @@ export const Container = React.memo( const currentMode = useStore((state) => state.currentMode, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow); - const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents, shallow); const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; }, [componentType, index, currentMode]); - const reorderContainerChildren = useStore((state) => state.reorderContainerChildren, shallow); - const prevForceUpdateRef = useRef(0); - const prevComponentsOrder = useRef(components); const [{ isOverCurrent }, drop] = useDrop({ accept: 'box', @@ -150,39 +147,7 @@ export const Container = React.memo( [setLastCanvasClickPosition] ); - // Function to sort the components based on position in container for tab navigation - const sortedComponents = useMemo(() => { - const { triggerUpdate, containerId } = reorderContainerChildren; - - // If a forced update occurred for a different container, return the previous order - const isForcedUpdate = prevForceUpdateRef.current !== triggerUpdate; - if (isForcedUpdate) { - prevForceUpdateRef.current = triggerUpdate; - if (containerId !== id) { - return prevComponentsOrder.current; - } - } - - const currentPageComponents = getCurrentPageComponents() - - const newComponentsOrder = [...components].sort((a, b) => { - const aTop = currentPageComponents?.[a]?.layouts?.[currentLayout]?.top; - const bTop = currentPageComponents?.[b]?.layouts?.[currentLayout]?.top; - if (aTop !== bTop) { - return aTop - bTop; - } else { - const aLeft = currentPageComponents?.[a]?.layouts?.[currentLayout]?.left; - const bLeft = currentPageComponents?.[b]?.layouts?.[currentLayout]?.left; - if (aLeft !== bLeft) { - return aLeft - bLeft; - } - } - }); - - prevComponentsOrder.current = newComponentsOrder; - return newComponentsOrder; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [components, currentLayout, reorderContainerChildren.triggerUpdate]); + const sortedComponents = useSortedComponents(components, currentLayout, id); return (
{ + const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents, shallow); + const reorderContainerChildren = useStore((state) => state.reorderContainerChildren, shallow); + const prevForceUpdateRef = useRef(0); + const prevComponentsOrder = useRef(components); + + // Function to sort the components based on position in container for tab navigation + const sortedComponents = useMemo(() => { + const { triggerUpdate, containerId } = reorderContainerChildren; + + // If a forced update occurred for a different container, return the previous order + const isForcedUpdate = prevForceUpdateRef.current !== triggerUpdate; + if (isForcedUpdate) { + prevForceUpdateRef.current = triggerUpdate; + if (containerId !== id) { + return prevComponentsOrder.current; + } + } + + const currentPageComponents = getCurrentPageComponents(); + + const newComponentsOrder = [...components].sort((a, b) => { + const aTop = currentPageComponents?.[a]?.layouts?.[currentLayout]?.top; + const bTop = currentPageComponents?.[b]?.layouts?.[currentLayout]?.top; + if (aTop !== bTop) { + return aTop - bTop; + } else { + const aLeft = currentPageComponents?.[a]?.layouts?.[currentLayout]?.left; + const bLeft = currentPageComponents?.[b]?.layouts?.[currentLayout]?.left; + if (aLeft !== bLeft) { + return aLeft - bLeft; + } + return 0; + } + }); + + prevComponentsOrder.current = newComponentsOrder; + return newComponentsOrder; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [components, currentLayout, reorderContainerChildren.triggerUpdate, id]); + + return sortedComponents; +}; + +export default useSortedComponents;