From 1ad1d54c82b86358f5185aced3723f055fe07cbe Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 28 Feb 2025 16:26:07 +0530 Subject: [PATCH 1/5] Refactored the code and added conditions for tabs, modal and kanban components as parent component. --- .../useCallbackActions.js | 27 ++---------- .../_stores/slices/leftSideBarSlice.js | 43 +++++++++++++++++++ 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index 7067cd540d..6fea25c151 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js @@ -30,30 +30,11 @@ const useCallbackActions = () => { return toast.success('Copied to the clipboard', { position: 'top-center' }); }; - const autoScrollTo = (id) => { - setSelectedComponents([id]); - const target = document.getElementById(id); - target.scrollIntoView({ behavior: 'smooth', block: 'center' }); - }; - const handleAutoScrollToComponent = (data) => { - const currentPageComponents = useStore.getState().getCurrentPageComponents(); - const component = currentPageComponents?.[data.id]; - - let parentId = component?.component?.parent; - if (parentId) { - const regex = /-\d+$/; - if (regex.test(parentId)) { - parentId = parentId.replace(regex, ''); // To get parentId without tab index if parent type is Tab - } - const parentType = currentPageComponents?.[parentId]?.component?.component; - if (parentType && (parentType === 'Modal' || parentType === 'Tabs')) { - autoScrollTo(parentId); // To scroll to parent component if parent type is Modal or Tabs - return; - } - } - - autoScrollTo(data.id); + const computedComponentId = useStore.getState().getComponentIdToAutoScroll(data.id); + setSelectedComponents([computedComponentId]); + const target = document.getElementById(computedComponentId); + target.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }; const callbackActions = [ diff --git a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js index 98decac629..77e8b5f284 100644 --- a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js @@ -37,4 +37,47 @@ export const createLeftSideBarSlice = (set, get) => ({ toggleLeftSidebar(true); } }, + getComponentIdToAutoScroll: (componentId) => { + const { getCurrentPageComponents, getAllExposedValues } = get(); + const currentPageComponents = getCurrentPageComponents(); + const component = currentPageComponents?.[componentId]; + let parentId = component?.component?.parent; + + if (!parentId) { + return componentId; + } + + // If the component exists inside a tab component + const regForTabs = /-(?!\d{12}$)\d+$/; // Parent id for tabs follow the format 'id-index' and index is not UUIDv4 id segment + if (regForTabs.test(parentId)) { + const reg = /-(\d+)$/; + const tabIndex = Number(parentId.match(reg)[1]); // Tab index inside which the component exists + + parentId = parentId.replace(regForTabs, ''); // Extract tab id from parent id + const { currentTab } = getAllExposedValues().components[parentId]; + const activeTabIndex = Number(currentTab); + + if (tabIndex !== activeTabIndex) { + return parentId; + } else return componentId; + } + + const parentExposedValues = getAllExposedValues().components[parentId]; + const parentComponent = currentPageComponents?.[parentId]; + + // If the component exists inside a modal component + if (parentComponent?.component?.component === 'Modal') { + if (parentExposedValues?.show) { + return componentId; + } else return parentId; + } + + // If the component exists inside the kanban component's modal + if (parentId.endsWith('-modal')) { + return parentId.replace(/-modal$/, ''); // Extract kanban id from parent id + } + + // If the component exists inside any other component + return componentId; + }, }); From a6e66889c27eb53de62935c6f6bcd76aca2d6182 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 3 Mar 2025 14:07:47 +0530 Subject: [PATCH 2/5] Added state variable to keep track of open modals on canvas. --- .../src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx | 2 ++ frontend/src/AppBuilder/Widgets/Modal.jsx | 2 ++ .../AppBuilder/_stores/slices/componentsSlice.js | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx index 34efc57221..2621708532 100644 --- a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx +++ b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx @@ -56,6 +56,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) { const [containers, setContainers] = useState([]); const [showModal, setShowModal] = useState(false); + const setModalOpenOnCanvas = useStore((state) => state.setModalOpenOnCanvas); const [activeId, setActiveId] = useState(null); const cardMovementRef = useRef(null); const shouldUpdateData = useRef(false); @@ -117,6 +118,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) { } /**** End - Logic to reduce the zIndex of modal control box ****/ } + setModalOpenOnCanvas(`${id}-modal`, showModal); }, [showModal]); useEffect(() => { diff --git a/frontend/src/AppBuilder/Widgets/Modal.jsx b/frontend/src/AppBuilder/Widgets/Modal.jsx index 5543ce4ee0..e0f099205f 100644 --- a/frontend/src/AppBuilder/Widgets/Modal.jsx +++ b/frontend/src/AppBuilder/Widgets/Modal.jsx @@ -49,6 +49,7 @@ export const Modal = function Modal({ const size = properties.size ?? 'lg'; const [modalWidth, setModalWidth] = useState(); const mode = useStore((state) => state.currentMode, shallow); + const setModalOpenOnCanvas = useStore((state) => state.setModalOpenOnCanvas); /**** Start - Logic to reset the zIndex of modal control box ****/ useEffect(() => { @@ -63,6 +64,7 @@ export const Modal = function Modal({ useGridStore.getState().actions.setOpenModalWidgetId(null); } } + setModalOpenOnCanvas(id, showModal); }, [showModal, id, mode]); /**** End - Logic to reset the zIndex of modal control box ****/ diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index 1f6ce18405..8690c428f9 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -40,6 +40,7 @@ const initialState = { currentPageHandle: null, showWidgetDeleteConfirmation: false, focusedParentId: null, + modalsOpenOnCanvas: [], }; export const createComponentsSlice = (set, get) => ({ @@ -1860,4 +1861,17 @@ export const createComponentsSlice = (set, get) => ({ const currentPage = getCurrentPage(moduleId); return currentPage?.autoComputeLayout; }, + setModalOpenOnCanvas: (modalId, isOpen) => { + const { modalsOpenOnCanvas } = get(); + let newModalOpenOnCanvas = []; + + if (isOpen) { + newModalOpenOnCanvas = [...modalsOpenOnCanvas, modalId]; + } else { + newModalOpenOnCanvas = modalsOpenOnCanvas.filter((id) => id !== modalId); + } + set((state) => { + state.modalsOpenOnCanvas = newModalOpenOnCanvas; + }); + }, }); From 6dd933fa63c77df1047cbccc1eec1459fdd65cb1 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 3 Mar 2025 14:18:22 +0530 Subject: [PATCH 3/5] Implemented logic for handling nested components. --- .../_stores/slices/leftSideBarSlice.js | 108 ++++++++++++------ 1 file changed, 73 insertions(+), 35 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js index 77e8b5f284..3cc424f790 100644 --- a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js @@ -38,46 +38,84 @@ export const createLeftSideBarSlice = (set, get) => ({ } }, getComponentIdToAutoScroll: (componentId) => { - const { getCurrentPageComponents, getAllExposedValues } = get(); + const { getCurrentPageComponents, getAllExposedValues, modalsOpenOnCanvas } = get(); const currentPageComponents = getCurrentPageComponents(); - const component = currentPageComponents?.[componentId]; - let parentId = component?.component?.parent; - if (!parentId) { - return componentId; + let candidate = componentId; + let current = componentId; + const visited = new Set(); + let closedUnusedModals = false; // Flag to check if we have closed all unused modals on the canvas + + // eslint-disable-next-line no-constant-condition + while (true) { + if (visited.has(current)) break; + visited.add(current); + + const component = currentPageComponents?.[current]; + if (!component) break; + + const parentId = component?.component?.parent; + if (!parentId) break; + + let isParentInactive = false; + let newCandidate = candidate; + + // If the component exists inside a tab component + const regForTabs = /-(?!\d{12}$)\d+$/; // Parent id for tabs follow the format 'id-index' and index is not UUIDv4 id segment + if (regForTabs.test(parentId)) { + const reg = /-(\d+)$/; + const tabIndex = Number(parentId.match(reg)[1]); // Tab index inside which the component exists + + const tabId = parentId.replace(regForTabs, ''); // Extract tab id from parent id + const { currentTab } = getAllExposedValues().components[tabId]; + const activeTabIndex = Number(currentTab); + + if (tabIndex !== activeTabIndex) { + isParentInactive = true; + newCandidate = tabId; + } + } + + const parentExposedValues = getAllExposedValues().components[parentId]; + + // If the component exists inside a modal component + if (currentPageComponents?.[parentId]?.component?.component === 'Modal') { + // Close all modals that are open on the canvas until we get to the parent modal + if (modalsOpenOnCanvas.length > 0 && !closedUnusedModals) { + if (!modalsOpenOnCanvas.includes(parentId)) { + modalsOpenOnCanvas.map((modalId) => getAllExposedValues().components[modalId]?.close()); + } else { + const idx = modalsOpenOnCanvas.indexOf(parentId); + modalsOpenOnCanvas.slice(idx + 1).map((modalId) => getAllExposedValues().components[modalId]?.close()); + } + closedUnusedModals = true; + } + + if (!parentExposedValues?.show) { + isParentInactive = true; + newCandidate = parentId; + } + } + + // If the component exists inside the kanban component's modal + if (parentId.endsWith('-modal')) { + isParentInactive = true; + newCandidate = parentId.replace(/-modal$/, ''); // Extract kanban id from parent id + } + + if (isParentInactive) { + candidate = newCandidate; + current = newCandidate; + } else { + current = parentId; + } } - // If the component exists inside a tab component - const regForTabs = /-(?!\d{12}$)\d+$/; // Parent id for tabs follow the format 'id-index' and index is not UUIDv4 id segment - if (regForTabs.test(parentId)) { - const reg = /-(\d+)$/; - const tabIndex = Number(parentId.match(reg)[1]); // Tab index inside which the component exists - - parentId = parentId.replace(regForTabs, ''); // Extract tab id from parent id - const { currentTab } = getAllExposedValues().components[parentId]; - const activeTabIndex = Number(currentTab); - - if (tabIndex !== activeTabIndex) { - return parentId; - } else return componentId; + // Close all modals that are open on the canvas if the component is not inside any of the modals + if (modalsOpenOnCanvas.length > 0 && !closedUnusedModals) { + modalsOpenOnCanvas.map((modalId) => getAllExposedValues().components[modalId]?.close()); } - const parentExposedValues = getAllExposedValues().components[parentId]; - const parentComponent = currentPageComponents?.[parentId]; - - // If the component exists inside a modal component - if (parentComponent?.component?.component === 'Modal') { - if (parentExposedValues?.show) { - return componentId; - } else return parentId; - } - - // If the component exists inside the kanban component's modal - if (parentId.endsWith('-modal')) { - return parentId.replace(/-modal$/, ''); // Extract kanban id from parent id - } - - // If the component exists inside any other component - return componentId; + return candidate; }, }); From 46ab01fead51186eba26fb88cc5243d296090e14 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 4 Mar 2025 03:09:50 +0530 Subject: [PATCH 4/5] Removed logic for closing modals and refactored the code for better readability. --- .../useCallbackActions.js | 4 +- .../_stores/slices/leftSideBarSlice.js | 67 ++++++++----------- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index 6fea25c151..bf661ea20d 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js @@ -9,6 +9,7 @@ const useCallbackActions = () => { const currentPageComponents = useStore((state) => state?.getCurrentPageComponents(), shallow); const shouldFreeze = useStore((state) => state.getShouldFreeze()); const runQuery = useStore((state) => state.queryPanel.runQuery); + const getComponentIdToAutoScroll = useStore((state) => state.getComponentIdToAutoScroll); const handleRemoveComponent = (component) => { deleteComponents([component.id]); @@ -31,7 +32,8 @@ const useCallbackActions = () => { }; const handleAutoScrollToComponent = (data) => { - const computedComponentId = useStore.getState().getComponentIdToAutoScroll(data.id); + const computedComponentId = getComponentIdToAutoScroll(data.id); + if (!computedComponentId) return; setSelectedComponents([computedComponentId]); const target = document.getElementById(computedComponentId); target.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); diff --git a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js index 3cc424f790..da520544b2 100644 --- a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js @@ -41,24 +41,22 @@ export const createLeftSideBarSlice = (set, get) => ({ const { getCurrentPageComponents, getAllExposedValues, modalsOpenOnCanvas } = get(); const currentPageComponents = getCurrentPageComponents(); - let candidate = componentId; + let targetComponentId = componentId; let current = componentId; const visited = new Set(); - let closedUnusedModals = false; // Flag to check if we have closed all unused modals on the canvas + let isInsideOpenModal = false; + // Bubble up to the outermost parent to find the target component // eslint-disable-next-line no-constant-condition while (true) { if (visited.has(current)) break; visited.add(current); - const component = currentPageComponents?.[current]; - if (!component) break; - - const parentId = component?.component?.parent; + const parentId = currentPageComponents?.[current]?.component?.parent; if (!parentId) break; - let isParentInactive = false; - let newCandidate = candidate; + let isComponentVisibleInParent = true; + let nextPossibleCandidate = parentId; // If the component exists inside a tab component const regForTabs = /-(?!\d{12}$)\d+$/; // Parent id for tabs follow the format 'id-index' and index is not UUIDv4 id segment @@ -67,55 +65,46 @@ export const createLeftSideBarSlice = (set, get) => ({ const tabIndex = Number(parentId.match(reg)[1]); // Tab index inside which the component exists const tabId = parentId.replace(regForTabs, ''); // Extract tab id from parent id - const { currentTab } = getAllExposedValues().components[tabId]; + + const { currentTab } = getAllExposedValues().components?.[tabId] || {}; const activeTabIndex = Number(currentTab); + nextPossibleCandidate = tabId; if (tabIndex !== activeTabIndex) { - isParentInactive = true; - newCandidate = tabId; + isComponentVisibleInParent = false; } } - const parentExposedValues = getAllExposedValues().components[parentId]; - // If the component exists inside a modal component if (currentPageComponents?.[parentId]?.component?.component === 'Modal') { - // Close all modals that are open on the canvas until we get to the parent modal - if (modalsOpenOnCanvas.length > 0 && !closedUnusedModals) { - if (!modalsOpenOnCanvas.includes(parentId)) { - modalsOpenOnCanvas.map((modalId) => getAllExposedValues().components[modalId]?.close()); - } else { - const idx = modalsOpenOnCanvas.indexOf(parentId); - modalsOpenOnCanvas.slice(idx + 1).map((modalId) => getAllExposedValues().components[modalId]?.close()); - } - closedUnusedModals = true; - } - - if (!parentExposedValues?.show) { - isParentInactive = true; - newCandidate = parentId; + nextPossibleCandidate = parentId; + if (!modalsOpenOnCanvas.includes(parentId)) { + isComponentVisibleInParent = false; } } // If the component exists inside the kanban component's modal if (parentId.endsWith('-modal')) { - isParentInactive = true; - newCandidate = parentId.replace(/-modal$/, ''); // Extract kanban id from parent id + nextPossibleCandidate = parentId.replace(/-modal$/, ''); // Extract kanban id from parent id + if (!modalsOpenOnCanvas.includes(parentId)) { + isComponentVisibleInParent = false; + } } - if (isParentInactive) { - candidate = newCandidate; - current = newCandidate; - } else { - current = parentId; + // If the open modal contains the component + if (modalsOpenOnCanvas[modalsOpenOnCanvas.length - 1] === parentId) { + isInsideOpenModal = true; } + + if (!isComponentVisibleInParent) { + targetComponentId = nextPossibleCandidate; + } + current = nextPossibleCandidate; } - // Close all modals that are open on the canvas if the component is not inside any of the modals - if (modalsOpenOnCanvas.length > 0 && !closedUnusedModals) { - modalsOpenOnCanvas.map((modalId) => getAllExposedValues().components[modalId]?.close()); + if (modalsOpenOnCanvas.length > 0 && !isInsideOpenModal) { + return undefined; } - - return candidate; + return targetComponentId; }, }); From 1ef408f11394a1dd1af09d0b3fd828b400eeaae3 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 7 Mar 2025 18:11:29 +0530 Subject: [PATCH 5/5] Changed function response to handle non accessible components. --- .../LeftSidebarInspector/useCallbackActions.js | 14 ++++++++++++-- .../AppBuilder/_stores/slices/leftSideBarSlice.js | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index bf661ea20d..937fe45b2c 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js @@ -32,8 +32,18 @@ const useCallbackActions = () => { }; const handleAutoScrollToComponent = (data) => { - const computedComponentId = getComponentIdToAutoScroll(data.id); - if (!computedComponentId) return; + const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(data.id); + if (!isAccessible) { + if (isOnCanvas) { + toast.success( + `This component can't be opened because it's on the main canvas. Close ${computedComponentId} and click "Go to component" to view it there` + ); + } else + toast.success( + `This component can't be opened because it's inside ${computedComponentId}. Open ${computedComponentId} and click "Go to component"to view it.` + ); + return; + } setSelectedComponents([computedComponentId]); const target = document.getElementById(computedComponentId); target.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); diff --git a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js index da520544b2..367ca4cf0c 100644 --- a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js @@ -103,8 +103,19 @@ export const createLeftSideBarSlice = (set, get) => ({ } if (modalsOpenOnCanvas.length > 0 && !isInsideOpenModal) { - return undefined; + const targetId = visited.size === 1 ? modalsOpenOnCanvas[modalsOpenOnCanvas.length - 1] : current; + const componentName = currentPageComponents?.[targetId]?.component?.name; + + return { + isAccessible: false, + computedComponentId: componentName, + isOnCanvas: visited.size === 1, + }; } - return targetComponentId; + + return { + isAccessible: true, + computedComponentId: targetComponentId, + }; }, });