diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index 7067cd540d..937fe45b2c 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]); @@ -30,30 +31,22 @@ 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; - } + 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; } - - autoScrollTo(data.id); + setSelectedComponents([computedComponentId]); + const target = document.getElementById(computedComponentId); + target.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }; const callbackActions = [ 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 14971be9d7..c4448a3bbf 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -44,6 +44,7 @@ const initialState = { currentPageHandle: null, showWidgetDeleteConfirmation: false, focusedParentId: null, + modalsOpenOnCanvas: [], }; export const createComponentsSlice = (set, get) => ({ @@ -1867,4 +1868,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; + }); + }, }); diff --git a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js index 98decac629..367ca4cf0c 100644 --- a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js @@ -37,4 +37,85 @@ export const createLeftSideBarSlice = (set, get) => ({ toggleLeftSidebar(true); } }, + getComponentIdToAutoScroll: (componentId) => { + const { getCurrentPageComponents, getAllExposedValues, modalsOpenOnCanvas } = get(); + const currentPageComponents = getCurrentPageComponents(); + + let targetComponentId = componentId; + let current = componentId; + const visited = new Set(); + 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 parentId = currentPageComponents?.[current]?.component?.parent; + if (!parentId) break; + + 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 + 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); + + nextPossibleCandidate = tabId; + if (tabIndex !== activeTabIndex) { + isComponentVisibleInParent = false; + } + } + + // If the component exists inside a modal component + if (currentPageComponents?.[parentId]?.component?.component === 'Modal') { + nextPossibleCandidate = parentId; + if (!modalsOpenOnCanvas.includes(parentId)) { + isComponentVisibleInParent = false; + } + } + + // If the component exists inside the kanban component's modal + if (parentId.endsWith('-modal')) { + nextPossibleCandidate = parentId.replace(/-modal$/, ''); // Extract kanban id from parent id + if (!modalsOpenOnCanvas.includes(parentId)) { + isComponentVisibleInParent = false; + } + } + + // If the open modal contains the component + if (modalsOpenOnCanvas[modalsOpenOnCanvas.length - 1] === parentId) { + isInsideOpenModal = true; + } + + if (!isComponentVisibleInParent) { + targetComponentId = nextPossibleCandidate; + } + current = nextPossibleCandidate; + } + + if (modalsOpenOnCanvas.length > 0 && !isInsideOpenModal) { + 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 { + isAccessible: true, + computedComponentId: targetComponentId, + }; + }, });