From 550feae6b59c76d036286527c3ee1a1d35511164 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 27 Feb 2025 23:59:12 +0530 Subject: [PATCH 01/42] Fixed issue where the search text was not getting highlighted correctly and added shortcut for find previous match. --- .../src/AppBuilder/CodeEditor/SearchBox.jsx | 27 +++++++++++++++++-- .../src/AppBuilder/CodeEditor/styles.scss | 4 ++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/CodeEditor/SearchBox.jsx b/frontend/src/AppBuilder/CodeEditor/SearchBox.jsx index 2b807f718b..28f7451b95 100644 --- a/frontend/src/AppBuilder/CodeEditor/SearchBox.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SearchBox.jsx @@ -1,3 +1,4 @@ +/* eslint-disable import/no-unresolved */ import React, { useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { @@ -9,12 +10,13 @@ import { replaceNext, replaceAll, openSearchPanel, - // eslint-disable-next-line import/no-unresolved } from '@codemirror/search'; import './SearchBox.scss'; import InputComponent from '@/components/ui/Input/Index.jsx'; import { Button as ButtonComponent } from '@/components/ui/Button/Button.jsx'; import { ToolTip } from '@/_components/ToolTip'; +import { SelectionRange } from '@codemirror/state'; +import { useHotkeys } from 'react-hotkeys-hook'; export const handleSearchPanel = (view) => { const dom = document.createElement('div'); @@ -35,6 +37,11 @@ function SearchPanel({ view }) { replace: replaceTerm, }); view.dispatch({ effects: setSearchQuery.of(query) }); + + const currentPos = view.state.selection.main.head; + view.dispatch({ + selection: SelectionRange.create(currentPos, currentPos), + }); }; useEffect(() => { @@ -44,12 +51,28 @@ function SearchPanel({ view }) { return () => clearTimeout(handler); }, [searchText, replaceText]); + const [shortcutEnabled, setShortcutEnabled] = useState(false); + + // Shortcuts for search input field + useHotkeys( + ['shift+enter', 'enter'], + (event, handler) => { + if (handler.shift && handler.keys[0] === 'enter') findPrevious(view); + else if (handler.keys[0] === 'enter') findNext(view); + }, + { + enabled: shortcutEnabled, + enableOnFormTags: true, + } + ); + const displaySearchField = () => (
setSearchText(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && findNext(view)} + onFocus={() => setShortcutEnabled(true)} + onBlur={() => setShortcutEnabled(false)} placeholder="Find" size="small" value={searchText} diff --git a/frontend/src/AppBuilder/CodeEditor/styles.scss b/frontend/src/AppBuilder/CodeEditor/styles.scss index c646fd30b1..6c8e114871 100644 --- a/frontend/src/AppBuilder/CodeEditor/styles.scss +++ b/frontend/src/AppBuilder/CodeEditor/styles.scss @@ -644,11 +644,13 @@ } .cm-searchMatch { + background-color: #F9E71A !important; + .cm-selectionMatch { background-color: #F9E71A !important; } } .cm-searchMatch.cm-searchMatch-selected { - background-color: #F28F2D; + background-color: #F28F2D !important; } \ No newline at end of file From 5a1427e2e426d05d1c958c5de1e0554b429c608a Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 28 Feb 2025 01:48:53 +0530 Subject: [PATCH 02/42] Added auto scroll functionality if the component list is long in inspector. --- .../LeftSidebarInspector/LeftSidebarInspector.jsx | 14 ++++++++++++-- frontend/src/_ui/JSONTreeViewer/JSONNode.jsx | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index dc4b2cf7af..3adca4be98 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -88,9 +88,19 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]); - const handleNodeExpansion = (path) => { + const handleNodeExpansion = (path, data, currentNode) => { if (pathToBeInspected && path?.length > 0) { - return pathToBeInspected.includes(path[path.length - 1]); + const shouldExpand = pathToBeInspected.includes(path[path.length - 1]); + + // Scroll to the component in the inspector + if (path?.length === 2 && path?.[0] === 'components' && shouldExpand) { + const target = document.getElementById(`inspector-node-${String(currentNode).toLowerCase()}`); + if (target) { + target.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } + + return shouldExpand; } else return false; }; diff --git a/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx b/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx index 65a4ece91d..8913dbb277 100644 --- a/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx +++ b/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx @@ -53,7 +53,7 @@ export const JSONNode = ({ data, ...restProps }) => { React.useEffect(() => { if (typeof shouldExpandNode === 'function') { - set(shouldExpandNode(path, data)); + set(shouldExpandNode(path, data, currentNode)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathToBeInspected]); @@ -337,6 +337,7 @@ export const JSONNode = ({ data, ...restProps }) => { 'group-object-container': shouldDisplayIntendedBlock, 'mx-2': typeofCurrentNode !== 'Object' && typeofCurrentNode !== 'Array', })} + id={`inspector-node-${String(currentNode).toLowerCase()}`} data-cy={`inspector-node-${String(currentNode).toLowerCase()}`} > {$NODEIcon &&
{$NODEIcon}
} From 1ad1d54c82b86358f5185aced3723f055fe07cbe Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 28 Feb 2025 16:26:07 +0530 Subject: [PATCH 03/42] 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 04/42] 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 05/42] 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 06/42] 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 94d2a64828d837941074378f74a45a170141ed56 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 7 Mar 2025 16:58:47 +0530 Subject: [PATCH 07/42] Fix: Hide search button when search panel is open. --- frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx index 033d266e03..f95baaa328 100644 --- a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx @@ -300,10 +300,10 @@ const MultiLineCodeEditor = (props) => { editable={editable} //for transformations in query manager onCreateEditor={(view) => setEditorView(view)} onUpdate={(view) => { - const icon = document.querySelector('.codehinter-search-btn-wrapper'); + const icon = document.querySelector('.codehinter-search-btn'); if (searchPanelOpen(view.state)) { - icon.style.top = '44px'; - } else icon.style.top = '0px'; + icon.style.display = 'none'; + } else icon.style.display = 'block'; }} />
From 1ef408f11394a1dd1af09d0b3fd828b400eeaae3 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 7 Mar 2025 18:11:29 +0530 Subject: [PATCH 08/42] 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, + }; }, }); From 3933a9e7a25694d829cf563a452b546fcfd2a510 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Sat, 8 Mar 2025 00:25:24 +0530 Subject: [PATCH 09/42] Adds header and footer slots for Form --- .../AppBuilder/AppCanvas/appCanvasUtils.js | 9 +- .../Inspector/Components/Form.jsx | 52 +++- .../AppBuilder/WidgetManager/widgets/form.js | 111 +++++-- frontend/src/AppBuilder/Widgets/Form/Form.jsx | 282 +++++++++++------- .../src/Editor/WidgetManager/configs/form.js | 110 +++++-- .../apps/services/widget-config/form.js | 110 +++++-- 6 files changed, 503 insertions(+), 171 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index 4e7b56ea70..41bc116ec3 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -206,6 +206,7 @@ export const getAllChildComponents = (allComponents, parentId) => { allComponents[parentId]?.component?.component === 'Calendar' || allComponents[parentId]?.component?.component === 'Kanban' || allComponents[parentId]?.component?.component === 'Container' || + allComponents[parentId]?.component?.component === 'Form' || allComponents[parentId]?.component?.component === 'ModalV2'; if (componentParentId && isParentTabORCalendar) { @@ -327,6 +328,7 @@ const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentI parentComponent.component.component === 'Tabs' || parentComponent.component.component === 'Calendar' || parentComponent.component.component === 'Container' || + parentComponent.component.component === 'Form' || parentComponent.component.component === 'ModalV2' ); } @@ -665,11 +667,14 @@ export const computeViewerBackgroundColor = (isAppDarkMode, canvasBgColor) => { return canvasBgColor; }; -export const getParentComponentIdByType = ({ child, parentComponent, parentId, slotName = 'header' }) => { +export const getParentComponentIdByType = ({ child, parentComponent, parentId, slotName }) => { const { tab } = child; if (parentComponent === 'Tabs') return `${parentId}-${tab}`; - else if (parentComponent === 'Container' || parentComponent === 'ModalV2') { + else if ( + slotName && + (parentComponent === 'Form' || parentComponent === 'Container' || parentComponent === 'ModalV2') + ) { return `${parentId}-${slotName}`; } return parentId; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx index d4676ad4b6..b39924854e 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx @@ -19,16 +19,34 @@ export const Form = ({ allComponents, pages, }) => { - const properties = Object.keys(componentMeta.properties); + const tempComponentMeta = deepClone(componentMeta); + + let properties = []; + let additionalActions = []; + let dataProperties = []; + const events = Object.keys(componentMeta.events); const validations = Object.keys(componentMeta.validation || {}); - const tempComponentMeta = deepClone(componentMeta); + + for (const [key] of Object.entries(componentMeta?.properties)) { + if (componentMeta?.properties[key]?.section === 'additionalActions') { + additionalActions.push(key); + } else if (componentMeta?.properties[key]?.accordian === 'Data') { + dataProperties.push(key); + } else { + properties.push(key); + } + } const { id } = component; const newOptions = [{ name: 'None', value: 'none' }]; - Object.entries(allComponents).forEach(([componentId, component]) => { - if (component.component.parent === id && component?.component?.component === 'Button') { - newOptions.push({ name: component.component.name, value: componentId }); + Object.entries(allComponents).forEach(([componentId, _component]) => { + const validParent = + _component.component.parent === id || + _component.component.parent === `${id}-footer` || + _component.component.parent === `${id}-header`; + if (validParent && _component?.component?.component === 'Button') { + newOptions.push({ name: _component.component.name, value: componentId }); } }); @@ -48,7 +66,8 @@ export const Form = ({ allComponents, validations, darkMode, - pages + pages, + additionalActions ); return ; @@ -68,7 +87,8 @@ export const baseComponentProperties = ( allComponents, validations, darkMode, - pages + pages, + additionalActions ) => { let items = []; if (properties.length > 0) { @@ -90,6 +110,24 @@ export const baseComponentProperties = ( }); } + items.push({ + title: 'Additional actions', + isOpen: true, + children: additionalActions?.map((property) => + renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + property, + 'properties', + currentState, + allComponents, + darkMode + ) + ), + }); + if (events.length > 0) { items.push({ title: `${i18next.t('widget.common.events', 'Events')}`, diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/form.js b/frontend/src/AppBuilder/WidgetManager/widgets/form.js index 0e9f5f4ce3..7ab4bd26a6 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/form.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/form.js @@ -4,9 +4,40 @@ export const formConfig = { description: 'Wrapper for multiple components', defaultSize: { width: 13, - height: 330, + height: 480, }, defaultChildren: [ + { + componentName: 'Text', + slotName: 'header', + layout: { + top: 10, + left: 1, + height: 40, + }, + properties: ['text'], + accessorKey: 'text', + styles: ['fontWeight', 'textSize', 'textColor'], + defaultValue: { + text: 'Form title', + textSize: 20, + textColor: '#000', + }, + }, + { + componentName: 'Button', + slotName: 'footer', + layout: { + top: 12, + left: 32, + height: 36, + }, + properties: ['text'], + defaultValue: { + text: 'Button2', + padding: 'none', + }, + }, { componentName: 'Text', layout: { @@ -225,6 +256,7 @@ export const formConfig = { loadingState: { type: 'toggle', displayName: 'Loading state', + section: 'additionalActions', validation: { schema: { type: 'boolean' }, defaultValue: false, @@ -242,12 +274,64 @@ export const formConfig = { value: true, }, }, + showHeader: { type: 'toggle', displayName: 'Header' }, + showFooter: { type: 'toggle', displayName: 'Footer' }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + section: 'additionalActions', + validation: { + schema: { type: 'boolean' }, + defaultValue: true, + }, + }, + disabledState: { + type: 'toggle', + displayName: 'Disable', + section: 'additionalActions', + validation: { + schema: { type: 'boolean' }, + defaultValue: false, + }, + }, }, events: { onSubmit: { displayName: 'On submit' }, onInvalid: { displayName: 'On invalid' }, }, styles: { + headerBackgroundColor: { + type: 'color', + displayName: 'Header background color', + validation: { + schema: { type: 'string' }, + defaultValue: '#ffffffff', + }, + }, + footerBackgroundColor: { + type: 'color', + displayName: 'Footer background color', + validation: { + schema: { type: 'string' }, + defaultValue: '#ffffffff', + }, + }, + headerHeight: { + type: 'code', + displayName: 'Header height', + validation: { + schema: { type: 'string' }, + defaultValue: '80px', + }, + }, + footerHeight: { + type: 'code', + displayName: 'Footer height', + validation: { + schema: { type: 'string' }, + defaultValue: '80px', + }, + }, backgroundColor: { type: 'color', displayName: 'Background color', @@ -274,22 +358,6 @@ export const formConfig = { defaultValue: '#fff', }, }, - visibility: { - type: 'toggle', - displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - defaultValue: true, - }, - }, - disabledState: { - type: 'toggle', - displayName: 'Disable', - validation: { - schema: { type: 'boolean' }, - defaultValue: false, - }, - }, }, exposedVariables: { data: {}, @@ -317,15 +385,18 @@ export const formConfig = { value: "{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}", }, - buttonToSubmit: { value: '{{"none"}}' }, + showHeader: { value: '{{false}}' }, + showFooter: { value: '{{false}}' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, }, events: [], styles: { backgroundColor: { value: '#fff' }, borderRadius: { value: '0' }, borderColor: { value: '#fff' }, - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, + headerHeight: { value: '60px' }, + footerHeight: { value: '60px' }, }, }, }; diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx index 674d707f03..c9ea9b7601 100644 --- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx @@ -13,6 +13,12 @@ import RenderSchema from './RenderSchema'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +const getCanvasHeight = (height) => { + const parsedHeight = height.includes('px') ? parseInt(height, 10) : height; + + return Math.ceil(parsedHeight); +}; + export const Form = function Form(props) { const { id, @@ -29,8 +35,25 @@ export const Form = function Form(props) { dataCy, } = props; const childComponents = useStore((state) => state.getChildComponents(id), shallow); - const { visibility, disabledState, borderRadius, borderColor, boxShadow } = styles; - const { buttonToSubmit, loadingState, advanced, JSONSchema } = properties; + const { + borderRadius, + borderColor, + boxShadow, + headerHeight, + footerHeight, + footerBackgroundColor, + headerBackgroundColor, + } = styles; + const { + buttonToSubmit, + loadingState, + advanced, + JSONSchema, + showHeader = false, + showFooter = false, + visibility, + disabledState, + } = properties; const backgroundColor = ['#fff', '#ffffffff'].includes(styles.backgroundColor) && darkMode ? '#232E3C' : styles.backgroundColor; const computedStyles = { @@ -40,16 +63,32 @@ export const Form = function Form(props) { height, display: visibility ? 'flex' : 'none', position: 'relative', - overflow: 'hidden auto', boxShadow, + flexDirection: 'column', }; - const childIdNameMap = useMemo(() => { - return Object.keys(childComponents).reduce((acc, id) => { - const component = childComponents[id]?.component?.component; - return { ...acc, [id]: component?.name }; - }, {}); - }, [childComponents]); + const formHeader = { + flexShrink: 0, + // height: headerHeight, + padding: '10px 6px', + borderBottom: '1px solid var(--border-weak)', + backgroundColor: + ['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor, + }; + const formFooter = { + flexShrink: 0, + // height: footerHeight, + padding: '10px 6px', + borderTop: '1px solid var(--border-weak)', + backgroundColor: + ['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor, + }; + const formContent = { + overflow: 'hidden auto', + display: 'flex', + height: '100%', + padding: '10px 6px', + }; const parentRef = useRef(null); const childDataRef = useRef({}); @@ -58,6 +97,8 @@ export const Form = function Form(props) { const [isValid, setValidation] = useState(true); const [uiComponents, setUIComponents] = useState([]); const mounted = useMounted(); + const canvasHeaderHeight = getCanvasHeight(headerHeight) / 10; + const canvasFooterHeight = getCanvasHeight(footerHeight) / 10; useEffect(() => { const exposedVariables = { @@ -155,7 +196,7 @@ export const Form = function Form(props) { }; setExposedVariables(exposedVariables); setValidation(childValidation); - }, [childrenData, advanced, JSON.stringify(childIdNameMap)]); + }, [childrenData, advanced]); useEffect(() => { document.addEventListener('submitForm', handleFormSubmission); @@ -245,105 +286,138 @@ export const Form = function Form(props) { if (e.target.className === 'real-canvas') onComponentClick(id, component); }} //Hack, should find a better solution - to prevent losing z index+1 when container element is clicked > - {loadingState ? ( -
-
-
-
+ {showHeader && ( +
+
- ) : ( -
- {!advanced && ( -
- - {/* - */} -
- )} - {advanced && - uiComponents?.map((item, index) => { - return ( -
-
- + {loadingState ? ( +
+
+
+
+
+ ) : ( +
+ {!advanced && ( +
+ + {/* + */} +
+ )} + {advanced && + uiComponents?.map((item, index) => { + return ( +
+
+ +
+ {/* + removeComponent={removeComponent} + // canvasWidth={width} + // readOnly={readOnly} + // customResolvables={customResolvables} + parentId={id} + getContainerProps={getContainerProps} + onOptionChanged={onComponentOptionChangedForSubcontainer} + onOptionsChanged={onComponentOptionsChanged} + isFromSubContainer={true} + /> */}
- {/* */} -
- ); - })} -
+ ); + })} + + )} +
+ {showFooter && ( +
+ +
)} ); diff --git a/frontend/src/Editor/WidgetManager/configs/form.js b/frontend/src/Editor/WidgetManager/configs/form.js index ac82fcb171..7ab4bd26a6 100644 --- a/frontend/src/Editor/WidgetManager/configs/form.js +++ b/frontend/src/Editor/WidgetManager/configs/form.js @@ -4,9 +4,40 @@ export const formConfig = { description: 'Wrapper for multiple components', defaultSize: { width: 13, - height: 330, + height: 480, }, defaultChildren: [ + { + componentName: 'Text', + slotName: 'header', + layout: { + top: 10, + left: 1, + height: 40, + }, + properties: ['text'], + accessorKey: 'text', + styles: ['fontWeight', 'textSize', 'textColor'], + defaultValue: { + text: 'Form title', + textSize: 20, + textColor: '#000', + }, + }, + { + componentName: 'Button', + slotName: 'footer', + layout: { + top: 12, + left: 32, + height: 36, + }, + properties: ['text'], + defaultValue: { + text: 'Button2', + padding: 'none', + }, + }, { componentName: 'Text', layout: { @@ -225,6 +256,7 @@ export const formConfig = { loadingState: { type: 'toggle', displayName: 'Loading state', + section: 'additionalActions', validation: { schema: { type: 'boolean' }, defaultValue: false, @@ -242,12 +274,64 @@ export const formConfig = { value: true, }, }, + showHeader: { type: 'toggle', displayName: 'Header' }, + showFooter: { type: 'toggle', displayName: 'Footer' }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + section: 'additionalActions', + validation: { + schema: { type: 'boolean' }, + defaultValue: true, + }, + }, + disabledState: { + type: 'toggle', + displayName: 'Disable', + section: 'additionalActions', + validation: { + schema: { type: 'boolean' }, + defaultValue: false, + }, + }, }, events: { onSubmit: { displayName: 'On submit' }, onInvalid: { displayName: 'On invalid' }, }, styles: { + headerBackgroundColor: { + type: 'color', + displayName: 'Header background color', + validation: { + schema: { type: 'string' }, + defaultValue: '#ffffffff', + }, + }, + footerBackgroundColor: { + type: 'color', + displayName: 'Footer background color', + validation: { + schema: { type: 'string' }, + defaultValue: '#ffffffff', + }, + }, + headerHeight: { + type: 'code', + displayName: 'Header height', + validation: { + schema: { type: 'string' }, + defaultValue: '80px', + }, + }, + footerHeight: { + type: 'code', + displayName: 'Footer height', + validation: { + schema: { type: 'string' }, + defaultValue: '80px', + }, + }, backgroundColor: { type: 'color', displayName: 'Background color', @@ -274,22 +358,6 @@ export const formConfig = { defaultValue: '#fff', }, }, - visibility: { - type: 'toggle', - displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - defaultValue: true, - }, - }, - disabledState: { - type: 'toggle', - displayName: 'Disable', - validation: { - schema: { type: 'boolean' }, - defaultValue: false, - }, - }, }, exposedVariables: { data: {}, @@ -317,14 +385,18 @@ export const formConfig = { value: "{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}", }, + showHeader: { value: '{{false}}' }, + showFooter: { value: '{{false}}' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, }, events: [], styles: { backgroundColor: { value: '#fff' }, borderRadius: { value: '0' }, borderColor: { value: '#fff' }, - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, + headerHeight: { value: '60px' }, + footerHeight: { value: '60px' }, }, }, }; diff --git a/server/src/modules/apps/services/widget-config/form.js b/server/src/modules/apps/services/widget-config/form.js index ac82fcb171..7ab4bd26a6 100644 --- a/server/src/modules/apps/services/widget-config/form.js +++ b/server/src/modules/apps/services/widget-config/form.js @@ -4,9 +4,40 @@ export const formConfig = { description: 'Wrapper for multiple components', defaultSize: { width: 13, - height: 330, + height: 480, }, defaultChildren: [ + { + componentName: 'Text', + slotName: 'header', + layout: { + top: 10, + left: 1, + height: 40, + }, + properties: ['text'], + accessorKey: 'text', + styles: ['fontWeight', 'textSize', 'textColor'], + defaultValue: { + text: 'Form title', + textSize: 20, + textColor: '#000', + }, + }, + { + componentName: 'Button', + slotName: 'footer', + layout: { + top: 12, + left: 32, + height: 36, + }, + properties: ['text'], + defaultValue: { + text: 'Button2', + padding: 'none', + }, + }, { componentName: 'Text', layout: { @@ -225,6 +256,7 @@ export const formConfig = { loadingState: { type: 'toggle', displayName: 'Loading state', + section: 'additionalActions', validation: { schema: { type: 'boolean' }, defaultValue: false, @@ -242,12 +274,64 @@ export const formConfig = { value: true, }, }, + showHeader: { type: 'toggle', displayName: 'Header' }, + showFooter: { type: 'toggle', displayName: 'Footer' }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + section: 'additionalActions', + validation: { + schema: { type: 'boolean' }, + defaultValue: true, + }, + }, + disabledState: { + type: 'toggle', + displayName: 'Disable', + section: 'additionalActions', + validation: { + schema: { type: 'boolean' }, + defaultValue: false, + }, + }, }, events: { onSubmit: { displayName: 'On submit' }, onInvalid: { displayName: 'On invalid' }, }, styles: { + headerBackgroundColor: { + type: 'color', + displayName: 'Header background color', + validation: { + schema: { type: 'string' }, + defaultValue: '#ffffffff', + }, + }, + footerBackgroundColor: { + type: 'color', + displayName: 'Footer background color', + validation: { + schema: { type: 'string' }, + defaultValue: '#ffffffff', + }, + }, + headerHeight: { + type: 'code', + displayName: 'Header height', + validation: { + schema: { type: 'string' }, + defaultValue: '80px', + }, + }, + footerHeight: { + type: 'code', + displayName: 'Footer height', + validation: { + schema: { type: 'string' }, + defaultValue: '80px', + }, + }, backgroundColor: { type: 'color', displayName: 'Background color', @@ -274,22 +358,6 @@ export const formConfig = { defaultValue: '#fff', }, }, - visibility: { - type: 'toggle', - displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - defaultValue: true, - }, - }, - disabledState: { - type: 'toggle', - displayName: 'Disable', - validation: { - schema: { type: 'boolean' }, - defaultValue: false, - }, - }, }, exposedVariables: { data: {}, @@ -317,14 +385,18 @@ export const formConfig = { value: "{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}", }, + showHeader: { value: '{{false}}' }, + showFooter: { value: '{{false}}' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, }, events: [], styles: { backgroundColor: { value: '#fff' }, borderRadius: { value: '0' }, borderColor: { value: '#fff' }, - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, + headerHeight: { value: '60px' }, + footerHeight: { value: '60px' }, }, }, }; From cd8e330208dd4396ac0cd88bee448740e7ea190a Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 17 Jan 2025 15:06:47 +0530 Subject: [PATCH 10/42] Fix: Not able to copy value in debugger --- .../src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx | 12 +++++++++++- frontend/src/_ui/JSONTreeViewer/JSONNode.jsx | 8 +++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx b/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx index 66548cbef8..aba2ca6af1 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx +++ b/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx @@ -5,6 +5,7 @@ import JSONTreeViewer from '@/_ui/JSONTreeViewer'; import cx from 'classnames'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import useStore from '@/AppBuilder/_stores/store'; +import { toast } from 'react-hot-toast'; function Logs({ logProps, idx }) { const [open, setOpen] = React.useState(false); @@ -52,10 +53,19 @@ function Logs({ logProps, idx }) { } }; + const copyToClipboard = (data) => { + const stringified = JSON.stringify(data, null, 2).replace(/\\/g, ''); + navigator.clipboard.writeText(stringified); + return toast.success('Value copied to clipboard', { position: 'top-center' }); + }; + const callbackActions = [ { for: 'all', - actions: [{ name: 'Select Widget', dispatchAction: handleSelectComponentOnEditor, icon: false, onSelect: true }], + actions: [ + { name: 'Copy value', dispatchAction: copyToClipboard, icon: false }, + { name: 'Select Widget', dispatchAction: handleSelectComponentOnEditor, icon: false, onSelect: true }, + ], enableForAllChildren: true, enableFor1stLevelChildren: true, }, diff --git a/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx b/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx index 65a4ece91d..c6b2a504fa 100644 --- a/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx +++ b/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx @@ -269,7 +269,7 @@ export const JSONNode = ({ data, ...restProps }) => { return (
- {enableCopyToClipboard && ( + {enableCopyToClipboard ? ( { @@ -281,6 +281,12 @@ export const JSONNode = ({ data, ...restProps }) => { + ) : ( + // Temporary fix for hover issue for copy value button. Need to remove this once inspector gets revamped. + )} Date: Fri, 17 Jan 2025 15:40:53 +0530 Subject: [PATCH 11/42] Revamped fix for copy button on hover in debugger. --- frontend/src/_ui/JSONTreeViewer/JSONNode.jsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx b/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx index c6b2a504fa..d1ce9518d5 100644 --- a/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx +++ b/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx @@ -268,8 +268,16 @@ export const JSONNode = ({ data, ...restProps }) => { }; return ( -
- {enableCopyToClipboard ? ( +
+ {enableCopyToClipboard && ( { @@ -281,12 +289,6 @@ export const JSONNode = ({ data, ...restProps }) => { - ) : ( - // Temporary fix for hover issue for copy value button. Need to remove this once inspector gets revamped. - )} Date: Thu, 16 Jan 2025 12:27:07 +0530 Subject: [PATCH 12/42] Fix: Date picker gets hidden inside table and user needs to scroll to see it. --- frontend/src/AppBuilder/Widgets/Table/Datepicker.jsx | 3 +++ frontend/src/AppBuilder/Widgets/Table/Table.jsx | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/Datepicker.jsx b/frontend/src/AppBuilder/Widgets/Table/Datepicker.jsx index 4e5a481dd2..930021c994 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Datepicker.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Datepicker.jsx @@ -263,6 +263,9 @@ export const Datepicker = function Datepicker({ } setIsDateInputFocussed(false); }} + closeOnScroll={(e) => { + return e.target.className === 'table-responsive jet-data-table false false'; + }} />
); diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index 68b1609748..7d378e7d70 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx @@ -1114,10 +1114,9 @@ export const Table = React.memo(
{items.map((virtualRow) => { From ac36acd9c1d41186fd8c051918595ab7d13705e3 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 9 Jan 2025 12:17:52 +0530 Subject: [PATCH 13/42] onSelect event gets triggered when dropdown value is cleared using clear button. --- frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx index 72e2b39664..a01ed895e0 100644 --- a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx +++ b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx @@ -436,6 +436,7 @@ export const DropdownV2 = ({ onChange={(selectedOption, actionProps) => { if (actionProps.action === 'clear') { setInputValue(null); + fireEvent('onSelect'); } if (actionProps.action === 'select-option') { setInputValue(selectedOption.value); From f2bd5a54b8b32516e9bd71f57690389da99a195a Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 16 Jan 2025 23:16:01 +0530 Subject: [PATCH 14/42] Fix: Table component's max row height not getting updated. --- .../CodeBuilder/Elements/TableRowHeightInput.jsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/frontend/src/AppBuilder/CodeBuilder/Elements/TableRowHeightInput.jsx b/frontend/src/AppBuilder/CodeBuilder/Elements/TableRowHeightInput.jsx index b9fb85fcae..099cd3763a 100644 --- a/frontend/src/AppBuilder/CodeBuilder/Elements/TableRowHeightInput.jsx +++ b/frontend/src/AppBuilder/CodeBuilder/Elements/TableRowHeightInput.jsx @@ -5,19 +5,13 @@ const MIN_TABLE_ROW_HEIGHT_DEFAULT = 45; const TableRowHeightInput = ({ value, onChange, cyLabel, staticText, styleDefinition }) => { const [inputValue, setInputValue] = useState(value); + const minValue = + styleDefinition.cellSize?.value === 'condensed' ? MIN_TABLE_ROW_HEIGHT_CONDENSED : MIN_TABLE_ROW_HEIGHT_DEFAULT; + useEffect(() => { setInputValue(value < minValue ? minValue : value); // eslint-disable-next-line react-hooks/exhaustive-deps }, [value, styleDefinition.cellSize?.value]); - useEffect(() => { - onChange( - styleDefinition.cellSize?.value === 'condensed' ? MIN_TABLE_ROW_HEIGHT_CONDENSED : MIN_TABLE_ROW_HEIGHT_DEFAULT - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const minValue = - styleDefinition.cellSize?.value === 'condensed' ? MIN_TABLE_ROW_HEIGHT_CONDENSED : MIN_TABLE_ROW_HEIGHT_DEFAULT; const handleBlur = () => { const newValue = Math.max(inputValue, minValue); From 369afb1c21deb336bde11243f926b8e890436c78 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Wed, 8 Jan 2025 11:47:11 +0530 Subject: [PATCH 15/42] Fix: Not able to pass parameters to run query in custom component. --- .../src/Editor/Components/CustomComponent/CustomComponent.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Editor/Components/CustomComponent/CustomComponent.jsx b/frontend/src/Editor/Components/CustomComponent/CustomComponent.jsx index 5bfe9818e5..4d4b7a2e48 100644 --- a/frontend/src/Editor/Components/CustomComponent/CustomComponent.jsx +++ b/frontend/src/Editor/Components/CustomComponent/CustomComponent.jsx @@ -42,7 +42,7 @@ export const CustomComponent = (props) => { setCustomProps({ ...customPropRef.current, ...e.data.updatedObj }); } else if (e.data.message === 'RUN_QUERY') { const options = { - parameters: e.data.parameters, + parameters: JSON.parse(e.data.parameters), queryName: e.data.queryName, }; onEvent('onTrigger', [], options); From 868b36ebcb65e22f0c531fcd593266339d978d03 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 6 Jan 2025 02:59:50 +0530 Subject: [PATCH 16/42] Add JSON in column type dropdown. --- .../Table/ColumnManager/PropertiesTabElements.jsx | 1 + .../Table/ColumnManager/StylesTabElements.jsx | 15 ++++++++++++--- .../Inspector/Components/Table/Table.jsx | 2 ++ .../Widgets/Table/GenerateEachCellValue.jsx | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx index 27ba100f52..c161156e8b 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx @@ -52,6 +52,7 @@ export const PropertiesTabElements = ({ { label: 'Boolean', value: 'boolean' }, { label: 'Image', value: 'image' }, { label: 'Link', value: 'link' }, + { label: 'JSON', value: 'json' }, // Following column types are deprecated { label: 'Default', value: 'default' }, { label: 'Dropdown', value: 'dropdown' }, diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx index e647626438..5091e56c6c 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx @@ -122,9 +122,18 @@ export const StylesTabElements = ({
)} - {['string', 'default', undefined, 'number', 'boolean', 'select', 'text', 'newMultiSelect', 'datepicker'].includes( - column.columnType - ) && ( + {[ + 'string', + 'default', + undefined, + 'number', + 'json', + 'boolean', + 'select', + 'text', + 'newMultiSelect', + 'datepicker', + ].includes(column.columnType) && ( <> {column.columnType !== 'boolean' && (
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx index 5a2175cfe1..b11a675e80 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx @@ -624,6 +624,8 @@ class TableComponent extends React.Component { return 'Select'; case 'newMultiSelect': return 'Multiselect'; + case 'json': + return 'JSON'; default: capitalize(text ?? ''); } diff --git a/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx b/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx index 0d2d8530c7..312e57b760 100644 --- a/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx @@ -29,7 +29,7 @@ export default function GenerateEachCellValue({ const [showHighlightedCells, setHighlighterCells] = React.useState(globalFilter ? true : false); // const [isNullCellClicked, setIsNullCellClicked] = React.useState(false); - const columnTypeAllowToRenderMarkElement = ['text', 'string', 'default', 'number', undefined]; + const columnTypeAllowToRenderMarkElement = ['text', 'string', 'default', 'number', 'json', undefined]; const ref = useRef(); const [showOverlay, setShowOverlay] = useState(false); const [hovered, setHovered] = useState(false); From 8972ff730ae144cdf8b3376c800dd8e89445f455 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 6 Jan 2025 04:16:55 +0530 Subject: [PATCH 17/42] Added basic logic for autogenerating JSON columns. --- .../AppBuilder/Widgets/Table/columns/autogenerateColumns.js | 6 ++++-- frontend/src/AppBuilder/Widgets/Table/columns/index.jsx | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js index 98faa12da0..83c07fc0ac 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js +++ b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js @@ -50,6 +50,7 @@ export default function autogenerateColumns( const currentKey = parentKey ? `${parentKey}.${key}` : key; if (isValueIsPlainObject(value)) { // if value is object particularly, then we only want nested keys till one level of nesting + accumulator.push(currentKey); accumulator.push(...limitToOneLevelNestingHelperFunc(value, currentKey)); } else if (isValueIsPremitiveOrArray(value)) { // check if value is premitive or array then simply push current key in the accumulator. @@ -98,7 +99,7 @@ export default function autogenerateColumns( id: uuidv4(), name: key, key: key, - columnType: convertDataTypeToColumnType(dataType), + columnType: convertDataTypeToColumnType(dataType, firstRow[key]), autogenerated: true, })); @@ -123,7 +124,8 @@ const dataTypeToColumnTypeMapping = { boolean: 'boolean', }; -const convertDataTypeToColumnType = (dataType) => { +const convertDataTypeToColumnType = (dataType, value) => { if (Object.keys(dataTypeToColumnTypeMapping).includes(dataType)) return dataTypeToColumnTypeMapping[dataType]; + if (dataType === 'object' && !Array.isArray(value)) return 'json'; else return 'string'; }; diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx index b4875d4b31..c6a5945a49 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx @@ -186,6 +186,7 @@ export default function generateColumnsData({ switch (columnType) { case 'string': + case 'json': case undefined: case 'default': { const cellTextColor = getResolvedValue(column.textColor, { cellValue, rowData }) ?? ''; From f30eae736639cdb80dd0538216cd279de7197a4a Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 7 Jan 2025 16:48:52 +0530 Subject: [PATCH 18/42] Added JSON component for rendering JSON column cell with formatting. --- .../Widgets/Table/GenerateEachCellValue.jsx | 2 +- .../src/AppBuilder/Widgets/Table/Json.jsx | 191 ++++++++++++++++++ .../Widgets/Table/columns/index.jsx | 21 +- 3 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 frontend/src/AppBuilder/Widgets/Table/Json.jsx diff --git a/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx b/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx index 312e57b760..0d2d8530c7 100644 --- a/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx @@ -29,7 +29,7 @@ export default function GenerateEachCellValue({ const [showHighlightedCells, setHighlighterCells] = React.useState(globalFilter ? true : false); // const [isNullCellClicked, setIsNullCellClicked] = React.useState(false); - const columnTypeAllowToRenderMarkElement = ['text', 'string', 'default', 'number', 'json', undefined]; + const columnTypeAllowToRenderMarkElement = ['text', 'string', 'default', 'number', undefined]; const ref = useRef(); const [showOverlay, setShowOverlay] = useState(false); const [hovered, setHovered] = useState(false); diff --git a/frontend/src/AppBuilder/Widgets/Table/Json.jsx b/frontend/src/AppBuilder/Widgets/Table/Json.jsx new file mode 100644 index 0000000000..9eda847d3f --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/Table/Json.jsx @@ -0,0 +1,191 @@ +/* eslint-disable no-undef */ +import React, { useState, useEffect } from 'react'; +import { determineJustifyContentValue } from '@/_helpers/utils'; +import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; + +const Json = ({ + isEditable, + darkMode, + handleCellValueChange, + cellTextColor, + cellValue, + column, + containerWidth, + cell, + horizontalAlignment, + isMaxRowHeightAuto, + cellSize, + maxRowHeightValue, +}) => { + const jsonIndentation = false; + const ref = React.useRef(null); + + const [hovered, setHovered] = useState(false); + const [isEditing, setIsEditing] = useState(false); + + const cellStyles = { + color: cellTextColor ?? 'inherit', + }; + + useEffect(() => { + if (!isEditable && isEditing) { + setIsEditing(false); + } + }, [isEditable]); + + function format(obj) { + if (typeof obj !== 'object' || obj === null) { + return typeof obj === 'string' ? `"${obj}"` : obj; + } + if (Array.isArray(obj)) { + return `[ ${obj.map(format).join(', ')} ]`; + } + return `{ ${Object.entries(obj) + .map(([key, value]) => `"${key}": ${format(value)}`) + .join(', ')} }`; + } + + const formatCellValue = (value, overlay = false) => { + try { + if (typeof value === 'object') { + if (jsonIndentation === true && !overlay) { + return JSON.stringify(value, null, 4).replace(/":/g, '": '); + } + const formattedJSON = format(value); + return formattedJSON; + } else { + if (jsonIndentation === true && !overlay) { + return JSON.stringify(JSON.parse(value), null, 4).replace(/":/g, '": '); + } + const formattedJSON = format(JSON.parse(value)); + return formattedJSON; + } + } catch (error) { + return value; + } + }; + + const _renderString = () => ( +
{ + setIsEditing(false); + if (cellValue !== e.target.textContent) { + const value = JSON.stringify(JSON.parse(e.target.textContent.replace(/\n/g, ''))); + handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original); + } + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + ref.current.blur(); + if (cellValue !== e.target.textContent) { + const value = JSON.stringify(JSON.parse(e.target.textContent.replace(/\n/g, ''))); + handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original); + } + } + }} + onFocus={(e) => { + setIsEditing(true); + e.stopPropagation(); + }} + > + {String(formatCellValue(cellValue))} +
+ ); + + const getOverlay = () => { + return ( +
setHovered(true)} + onMouseLeave={() => setHovered(false)} + style={{ color: 'var(--text-primary)' }} + > + + {String(formatCellValue(cellValue, true))} + +
+ ); + }; + + const _showOverlay = + ref?.current && + (ref?.current?.clientWidth < ref?.current?.children[0]?.offsetWidth || + ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight); + + return ( + <> +
} + trigger={_showOverlay && ['hover', 'focus']} + rootClose={true} + show={_showOverlay && hovered && !isEditing} + > + {!isEditable ? ( +
{ + if (!hovered) setHovered(true); + }} + onMouseLeave={() => { + setHovered(false); + }} + ref={ref} + > + + {String(formatCellValue(cellValue))} + +
+ ) : ( +
+
{ + if (!hovered) setHovered(true); + }} + onMouseLeave={() => setHovered(false)} + className={`${isEditing ? 'h-100 content-editing' : ''} h-100`} + > + {_renderString()} +
+
+ )} + + + ); +}; + +export default Json; diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx index c6a5945a49..e7013d0b1e 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx @@ -14,6 +14,7 @@ import { CustomSelect } from '../CustomSelect'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import Text from '../Text'; import StringColumn from '../String'; +import Json from '../Json'; export default function generateColumnsData({ columnProperties, @@ -186,7 +187,6 @@ export default function generateColumnsData({ switch (columnType) { case 'string': - case 'json': case undefined: case 'default': { const cellTextColor = getResolvedValue(column.textColor, { cellValue, rowData }) ?? ''; @@ -715,6 +715,25 @@ export default function generateColumnsData({
); } + case 'json': { + return ( + + ); + } } return cellValue || ''; }, From 8304a7245833b8b7f0f1f12f934433108225724c Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 7 Jan 2025 17:59:16 +0530 Subject: [PATCH 19/42] Add toggle for indentation. --- .../ColumnManager/PropertiesTabElements.jsx | 18 ++++++++++++++++++ .../Table/ProgramaticallyHandleProperties.jsx | 9 ++++++++- frontend/src/AppBuilder/Widgets/Table/Json.jsx | 2 +- .../AppBuilder/Widgets/Table/columns/index.jsx | 2 ++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx index c161156e8b..2e975109d2 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx @@ -267,6 +267,24 @@ export const PropertiesTabElements = ({ )}
)} + {column.columnType === 'json' && ( +
+
+ +
+
+ )}
{ // adding error handling mechanism for fxActiveFieldsProperty , if props.fxActiveFields is array , then return props.fxActiveFields or else return [], this will make sure, fxActiveFields wil always be array diff --git a/frontend/src/AppBuilder/Widgets/Table/Json.jsx b/frontend/src/AppBuilder/Widgets/Table/Json.jsx index 9eda847d3f..fcae72459e 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Json.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Json.jsx @@ -5,6 +5,7 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; const Json = ({ isEditable, + jsonIndentation, darkMode, handleCellValueChange, cellTextColor, @@ -17,7 +18,6 @@ const Json = ({ cellSize, maxRowHeightValue, }) => { - const jsonIndentation = false; const ref = React.useRef(null); const [hovered, setHovered] = useState(false); diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx index e7013d0b1e..257de7c4ae 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx @@ -716,9 +716,11 @@ export default function generateColumnsData({ ); } case 'json': { + const jsonIndentation = getResolvedValue(column?.jsonIndentation) ?? true; return ( Date: Tue, 7 Jan 2025 18:04:32 +0530 Subject: [PATCH 20/42] Removed autogeneration logic. --- .../AppBuilder/Widgets/Table/columns/autogenerateColumns.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js index 83c07fc0ac..98faa12da0 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js +++ b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js @@ -50,7 +50,6 @@ export default function autogenerateColumns( const currentKey = parentKey ? `${parentKey}.${key}` : key; if (isValueIsPlainObject(value)) { // if value is object particularly, then we only want nested keys till one level of nesting - accumulator.push(currentKey); accumulator.push(...limitToOneLevelNestingHelperFunc(value, currentKey)); } else if (isValueIsPremitiveOrArray(value)) { // check if value is premitive or array then simply push current key in the accumulator. @@ -99,7 +98,7 @@ export default function autogenerateColumns( id: uuidv4(), name: key, key: key, - columnType: convertDataTypeToColumnType(dataType, firstRow[key]), + columnType: convertDataTypeToColumnType(dataType), autogenerated: true, })); @@ -124,8 +123,7 @@ const dataTypeToColumnTypeMapping = { boolean: 'boolean', }; -const convertDataTypeToColumnType = (dataType, value) => { +const convertDataTypeToColumnType = (dataType) => { if (Object.keys(dataTypeToColumnTypeMapping).includes(dataType)) return dataTypeToColumnTypeMapping[dataType]; - if (dataType === 'object' && !Array.isArray(value)) return 'json'; else return 'string'; }; From cc84324c40628c9c337d7a3a2ec866400c5327d6 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Thu, 13 Feb 2025 17:02:06 +0530 Subject: [PATCH 21/42] Fix gutter issue at the bottom of the container --- .../src/AppBuilder/AppCanvas/Container.jsx | 17 ++++++++++++--- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 2 +- .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 4 ++-- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 2 +- .../AppCanvas/appCanvasConstants.js | 8 +++++++ frontend/src/AppBuilder/Widgets/Container.jsx | 15 ++++++++++--- .../AppBuilder/_stores/slices/gridSlice.js | 2 ++ frontend/src/Editor/DraggableBox.jsx | 4 ++-- frontend/src/Editor/SubContainer.jsx | 4 ++-- frontend/src/_stores/gridStore.js | 21 ------------------- 10 files changed, 44 insertions(+), 35 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index fc163939f6..c9466dc1dd 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -6,7 +6,15 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { useDrop } from 'react-dnd'; import { addChildrenWidgetsToParent, addNewWidgetToTheEditor, computeViewerBackgroundColor } from './appCanvasUtils'; -import { CANVAS_WIDTHS, NO_OF_GRIDS, WIDGETS_WITH_DEFAULT_CHILDREN } from './appCanvasConstants'; +import { + CANVAS_WIDTHS, + NO_OF_GRIDS, + WIDGETS_WITH_DEFAULT_CHILDREN, + GRID_HEIGHT, + CONTAINER_CANVAS_PADDING, + CONTAINER_CANVAS_BORDER_WIDTH, + BOX_PADDING, +} from './appCanvasConstants'; import { useGridStore } from '@/_stores/gridStore'; import NoComponentCanvasContainer from './NoComponentCanvasContainer'; import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; @@ -95,7 +103,10 @@ export const Container = React.memo( if (canvasWidth !== undefined) { if (componentType === 'Listview' && listViewMode == 'grid') return canvasWidth / columns - 2; if (id === 'canvas') return canvasWidth; - return canvasWidth - 2; + if (componentType === 'Container') { + return canvasWidth - (2 * CONTAINER_CANVAS_PADDING + 2 * CONTAINER_CANVAS_BORDER_WIDTH + 2 * BOX_PADDING); + } + return canvasWidth - 2; // Need to update this 2 to correct value for other subcontainers } return realCanvasRef?.current?.offsetWidth; } @@ -143,7 +154,7 @@ export const Container = React.memo( }} style={{ height: id === 'canvas' ? `${canvasHeight}` : '100%', - backgroundSize: `${gridWidth}px ${10}px`, + backgroundSize: `${gridWidth}px ${GRID_HEIGHT}px`, backgroundColor: currentMode === 'view' ? computeViewerBackgroundColor(darkMode, canvasBgColor) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 6821f43ce2..6403d63201 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -830,7 +830,7 @@ export default function Grid({ gridWidth, currentLayout }) { onDragEnd={(e) => { try { if (isDraggingRef.current) { - useGridStore.getState().actions.setDraggingComponentId(null); + useStore.getState().setDraggingComponentId(null); isDraggingRef.current = false; } prevDragParentId.current = null; diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index 427cced97b..b26586dc08 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -6,7 +6,7 @@ import { OverlayTrigger } from 'react-bootstrap'; import { renderTooltip } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; import ErrorBoundary from '@/_ui/ErrorBoundary'; - +import { BOX_PADDING } from './appCanvasConstants'; const shouldAddBoxShadowAndVisibility = [ 'Table', 'TextInput', @@ -164,7 +164,7 @@ const RenderWidget = ({
state.getComponentDefinition(id)?.layouts?.[currentLayout], shallow); const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow); - const isDragging = useGridStore((state) => state.draggingComponentId === id); + const isDragging = useStore((state) => state.draggingComponentId === id); const isResizing = useGridStore((state) => state.resizingComponentId === id); const componentType = useStore((state) => state.getComponentDefinition(id)?.component?.component, shallow); const setHoveredComponentForGrid = useStore((state) => state.setHoveredComponentForGrid, shallow); diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js index 75b3402331..1c598f8475 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js @@ -1,5 +1,7 @@ export const NO_OF_GRIDS = 43; +export const GRID_HEIGHT = 10; + export const CANVAS_WIDTHS = Object.freeze({ deviceWindowWidth: 450, leftSideBarWidth: 48, @@ -15,3 +17,9 @@ export const APP_HEADER_HEIGHT = 47; export const LEFT_SIDEBAR_WIDTH = 348; // exclusive of border export const SUBCONTAINER_WIDGETS = ['Container', 'Tabs', 'Listview', 'Kanban', 'Form']; + +export const CONTAINER_CANVAS_PADDING = 2; + +export const CONTAINER_CANVAS_BORDER_WIDTH = 1; + +export const BOX_PADDING = 2; diff --git a/frontend/src/AppBuilder/Widgets/Container.jsx b/frontend/src/AppBuilder/Widgets/Container.jsx index 1334098423..bcdb423561 100644 --- a/frontend/src/AppBuilder/Widgets/Container.jsx +++ b/frontend/src/AppBuilder/Widgets/Container.jsx @@ -2,6 +2,9 @@ import React, { useMemo } from 'react'; import { Container as ContainerComponent } from '@/AppBuilder/AppCanvas/Container'; import Spinner from '@/_ui/Spinner'; import { useExposeState } from '@/AppBuilder/_hooks/useExposeVariables'; +import { shallow } from 'zustand/shallow'; +import { CONTAINER_CANVAS_PADDING, CONTAINER_CANVAS_BORDER_WIDTH } from '@/AppBuilder/AppCanvas/appCanvasConstants'; +import useStore from '@/AppBuilder/_stores/store'; export const Container = ({ id, @@ -23,6 +26,11 @@ export const Container = ({ setExposedVariable ); + const isWidgetInContainerDragging = useStore( + (state) => state.containerChildrenMapping[id].includes(state.draggingComponentId), + shallow + ); + const contentBgColor = useMemo(() => { return { backgroundColor: @@ -42,8 +50,9 @@ export const Container = ({ const computedStyles = { backgroundColor: contentBgColor.backgroundColor, borderRadius: borderRadius ? parseFloat(borderRadius) : 0, - border: `1px solid ${borderColor}`, + border: `${CONTAINER_CANVAS_BORDER_WIDTH}px solid ${borderColor}`, height, + padding: `${CONTAINER_CANVAS_PADDING}px`, display: isVisible ? 'flex' : 'none', flexDirection: 'column', position: 'relative', @@ -61,9 +70,9 @@ export const Container = ({ const computedContentStyles = { ...contentBgColor, flex: 1, - overflow: 'auto', + // Prevent the scroll when dragging a widget inside the container or moving out of the container + overflow: isWidgetInContainerDragging ? 'hidden' : 'hidden auto', }; - return (
({ @@ -21,6 +22,7 @@ export const createGridSlice = (set, get) => ({ debouncedToggleCanvasUpdater: debounce(() => { get().toggleCanvasUpdater(); }, 200), + setDraggingComponentId: (id) => set(() => ({ draggingComponentId: id })), moveComponentPosition: (direction) => { const { setComponentLayout, currentLayout, getSelectedComponentsDefinition, debouncedToggleCanvasUpdater } = get(); let layouts = {}; diff --git a/frontend/src/Editor/DraggableBox.jsx b/frontend/src/Editor/DraggableBox.jsx index ae2740bc84..3cdad272b7 100644 --- a/frontend/src/Editor/DraggableBox.jsx +++ b/frontend/src/Editor/DraggableBox.jsx @@ -10,7 +10,7 @@ import { resolveWidgetFieldValue } from '@/_helpers/utils'; import ErrorBoundary from './ErrorBoundary'; import { useEditorStore } from '@/_stores/editorStore'; import { shallow } from 'zustand/shallow'; -import { useNoOfGrid, useGridStore } from '@/_stores/gridStore'; +import { useGridStore } from '@/_stores/gridStore'; import WidgetBox from './WidgetBox'; import * as Sentry from '@sentry/react'; import { findHighestLevelofSelection } from './DragContainer'; @@ -61,7 +61,7 @@ const DraggableBox = React.memo( }) => { const isResizing = useGridStore((state) => state.resizingComponentId === id); const [canDrag, setCanDrag] = useState(true); - const noOfGrid = useNoOfGrid(); + const noOfGrid = 43; const { currentLayout, setHoveredComponent, diff --git a/frontend/src/Editor/SubContainer.jsx b/frontend/src/Editor/SubContainer.jsx index 01335af55a..fc78875819 100644 --- a/frontend/src/Editor/SubContainer.jsx +++ b/frontend/src/Editor/SubContainer.jsx @@ -23,7 +23,7 @@ import { useEditorStore } from '@/_stores/editorStore'; // eslint-disable-next-line import/no-unresolved import { diff } from 'deep-object-diff'; -import { useGridStore, useResizingComponentId } from '@/_stores/gridStore'; +import { useGridStore } from '@/_stores/gridStore'; import GhostWidget from './GhostWidget'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; @@ -68,7 +68,7 @@ export const SubContainer = ({ shallow ); - const resizingComponentId = useResizingComponentId(); + const resizingComponentId = useGridStore((state) => state.resizingComponentId, shallow); const noOfGrids = 43; const { isGridActive } = useGridStore((state) => ({ isGridActive: state.activeGrid === parent }), shallow); diff --git a/frontend/src/_stores/gridStore.js b/frontend/src/_stores/gridStore.js index aa070e4a89..213f07ac16 100644 --- a/frontend/src/_stores/gridStore.js +++ b/frontend/src/_stores/gridStore.js @@ -7,7 +7,6 @@ const initialState = { noOfGrid: 43, draggedSubContainer: false, resizingComponentId: null, - draggingComponentId: null, dragTarget: null, isGroupHandleHoverd: false, idGroupDragged: false, @@ -20,11 +19,7 @@ export const useGridStore = create( (set) => ({ ...initialState, actions: { - setActiveGrid: (gridId) => set({ activeGrid: gridId }), - setNoOfGrid: (noOfGrid) => set({ noOfGrid }), - setDraggedSubContainer: (draggedSubContainer) => set({ draggedSubContainer }), setResizingComponentId: (id) => set({ resizingComponentId: id }), - setDraggingComponentId: (id) => set({ draggingComponentId: id }), setDragTarget: (dragTarget) => set({ dragTarget }), setIsGroupHandleHoverd: (isGroupHandleHoverd) => set({ isGroupHandleHoverd }), setIdGroupDragged: (idGroupDragged) => set({ idGroupDragged }), @@ -46,21 +41,5 @@ useGridStore.subscribe(({ draggingComponentId }) => { } }); -// useEditorStore.subscribe(({ hoveredComponent }) => { -// console.log('hoveredComponent--', hoveredComponent); -// if (hoveredComponent) { -// document.querySelector(`[data-hovered-control]`)?.removeAttribute('data-hovered-control'); -// document.querySelector(`[target-id='${hoveredComponent}']`)?.setAttribute('data-hovered-control', true); -// } else if (document.querySelector(`[data-hovered-control]`)) { -// document.querySelector(`[data-hovered-control]`)?.removeAttribute('data-hovered-control'); -// } -// }); - -export const useActiveGrid = () => useGridStore((state) => state.activeGrid, shallow); -export const useNoOfGrid = () => useGridStore((state) => state.noOfGrid, shallow); -export const useDraggedSubContainer = () => useGridStore((state) => state.draggedSubContainer, shallow); -export const useGridStoreActions = () => useGridStore((state) => state.actions, shallow); -export const useDragTarget = () => useGridStore((state) => state.dragTarget, shallow); -export const useResizingComponentId = () => useGridStore((state) => state.resizingComponentId, shallow); export const useIsGroupHandleHoverd = () => useGridStore((state) => state.isGroupHandleHoverd, shallow); export const useOpenModalWidgetId = () => useGridStore((state) => state.openModalWidgetId, shallow); From 36cb2b04c8a692b7862a06bcb4a8427dfa368263 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Fri, 14 Feb 2025 12:02:59 +0530 Subject: [PATCH 22/42] fix --- frontend/src/AppBuilder/Widgets/Container.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/Widgets/Container.jsx b/frontend/src/AppBuilder/Widgets/Container.jsx index bcdb423561..23bfbb0eee 100644 --- a/frontend/src/AppBuilder/Widgets/Container.jsx +++ b/frontend/src/AppBuilder/Widgets/Container.jsx @@ -27,7 +27,7 @@ export const Container = ({ ); const isWidgetInContainerDragging = useStore( - (state) => state.containerChildrenMapping[id].includes(state.draggingComponentId), + (state) => state.containerChildrenMapping?.[id]?.includes(state?.draggingComponentId), shallow ); From 05637ef28123018ce6a445f8782d51bef3948b3a Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 17 Feb 2025 12:02:39 +0530 Subject: [PATCH 23/42] Prevent component handle adding scroll to the container --- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 15 +++++++++------ .../src/AppBuilder/AppCanvas/WidgetWrapper.jsx | 1 - 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index bb4d4fc69b..f0dd4266e4 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -3,9 +3,12 @@ import { shallow } from 'zustand/shallow'; import './configHandle.scss'; import useStore from '@/AppBuilder/_stores/store'; import { findHighestLevelofSelection } from '../Grid/gridUtils'; + +const CONFIG_HANDLE_HEIGHT = 20; +const BUFFER_HEIGHT = 1; + export const ConfigHandle = ({ id, - position, widgetTop, widgetHeight, setSelectedComponentAsModal = () => null, //! Only Modal widget passes this uses props down. All other widgets use selecto lib @@ -27,6 +30,7 @@ export const ConfigHandle = ({ (state) => componentType === 'Tabs' && state.getExposedValueOfComponent(id)?.currentTab, shallow ); + const position = widgetTop < 15 ? 'bottom' : 'top'; const setComponentToInspect = useStore((state) => state.setComponentToInspect); const isModal = componentType === 'Modal' || componentType === 'ModalV2'; @@ -36,9 +40,7 @@ export const ConfigHandle = ({ // If one component is hovered and one is selected, show the handle for the hovered component return ( isWidgetHovered || - (showHandle && - (!isMultipleComponentsSelected || (isModal && isModalOpen)) && - !anyComponentHovered) + (showHandle && (!isMultipleComponentsSelected || (isModal && isModalOpen)) && !anyComponentHovered) ); }, shallow); let height = visibility === false ? 10 : widgetHeight; @@ -48,7 +50,7 @@ export const ConfigHandle = ({ className={`config-handle ${customClassName}`} widget-id={id} style={{ - top: position === 'top' ? '-20px' : widgetTop + height - (widgetTop < 10 ? 15 : 10), + top: position === 'top' ? '-20px' : `${height - (CONFIG_HANDLE_HEIGHT + BUFFER_HEIGHT)}px`, visibility: _showHandle ? 'visible' : 'hidden', left: '-1px', }} @@ -63,7 +65,8 @@ export const ConfigHandle = ({ > diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index e4bbef6802..9d9cade876 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -84,7 +84,6 @@ const WidgetWrapper = memo( {mode == 'edit' && ( Date: Tue, 18 Feb 2025 10:40:24 +0530 Subject: [PATCH 24/42] Add support for handle should still be displayed for hidden components in editor mode --- .../AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx | 10 ++++++++-- frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx | 1 - 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index f0dd4266e4..e3e38999f3 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -50,7 +50,12 @@ export const ConfigHandle = ({ className={`config-handle ${customClassName}`} widget-id={id} style={{ - top: position === 'top' ? '-20px' : `${height - (CONFIG_HANDLE_HEIGHT + BUFFER_HEIGHT)}px`, + top: + componentType === 'Modal' && isModalOpen + ? '0px' + : position === 'top' + ? '-20px' + : `${height - (CONFIG_HANDLE_HEIGHT + BUFFER_HEIGHT)}px`, visibility: _showHandle ? 'visible' : 'hidden', left: '-1px', }} @@ -65,7 +70,8 @@ export const ConfigHandle = ({ > Date: Wed, 19 Feb 2025 19:17:31 +0530 Subject: [PATCH 25/42] add padding for Container and form --- .../src/AppBuilder/AppCanvas/Container.jsx | 13 +-- .../AppCanvas/appCanvasConstants.js | 4 +- .../Widgets/{ => Container}/Container.jsx | 83 +++++++++-------- .../Widgets/Container/container.scss | 13 +++ frontend/src/AppBuilder/Widgets/Form/Form.jsx | 92 ++++++------------- .../src/AppBuilder/Widgets/Form/form.scss | 25 +++++ .../AppBuilder/Widgets/Kanban/KanbanBoard.jsx | 1 + frontend/src/AppBuilder/Widgets/Listview.jsx | 35 +------ frontend/src/AppBuilder/Widgets/Tabs.jsx | 1 + .../src/AppBuilder/_helpers/editorHelpers.js | 2 +- 10 files changed, 126 insertions(+), 143 deletions(-) rename frontend/src/AppBuilder/Widgets/{ => Container}/Container.jsx (52%) create mode 100644 frontend/src/AppBuilder/Widgets/Container/container.scss create mode 100644 frontend/src/AppBuilder/Widgets/Form/form.scss diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index c9466dc1dd..e622e1a2cd 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -11,8 +11,8 @@ import { NO_OF_GRIDS, WIDGETS_WITH_DEFAULT_CHILDREN, GRID_HEIGHT, - CONTAINER_CANVAS_PADDING, - CONTAINER_CANVAS_BORDER_WIDTH, + CONTAINER_FORM_CANVAS_PADDING, + SUBCONTAINER_CANVAS_BORDER_WIDTH, BOX_PADDING, } from './appCanvasConstants'; import { useGridStore } from '@/_stores/gridStore'; @@ -43,10 +43,10 @@ export const Container = React.memo( canvasMaxWidth, isViewerSidebarPinned, pageSidebarStyle, + componentType, }) => { const realCanvasRef = useRef(null); const components = useStore((state) => state.getContainerChildrenMapping(id), shallow); - const componentType = useStore((state) => state.getComponentTypeFromId(id), shallow); const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow); const setLastCanvasClickPosition = useStore((state) => state.setLastCanvasClickPosition, shallow); @@ -103,15 +103,16 @@ export const Container = React.memo( if (canvasWidth !== undefined) { if (componentType === 'Listview' && listViewMode == 'grid') return canvasWidth / columns - 2; if (id === 'canvas') return canvasWidth; - if (componentType === 'Container') { - return canvasWidth - (2 * CONTAINER_CANVAS_PADDING + 2 * CONTAINER_CANVAS_BORDER_WIDTH + 2 * BOX_PADDING); + if (componentType === 'Container' || componentType === 'Form') { + return ( + canvasWidth - (2 * CONTAINER_FORM_CANVAS_PADDING + 2 * SUBCONTAINER_CANVAS_BORDER_WIDTH + 2 * BOX_PADDING) + ); } return canvasWidth - 2; // Need to update this 2 to correct value for other subcontainers } return realCanvasRef?.current?.offsetWidth; } const gridWidth = getContainerCanvasWidth() / NO_OF_GRIDS; - useEffect(() => { useGridStore.getState().actions.setSubContainerWidths(id, getContainerCanvasWidth() / NO_OF_GRIDS); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js index 1c598f8475..e6a789fba0 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js @@ -18,8 +18,8 @@ export const LEFT_SIDEBAR_WIDTH = 348; // exclusive of border export const SUBCONTAINER_WIDGETS = ['Container', 'Tabs', 'Listview', 'Kanban', 'Form']; -export const CONTAINER_CANVAS_PADDING = 2; +export const CONTAINER_FORM_CANVAS_PADDING = 7; -export const CONTAINER_CANVAS_BORDER_WIDTH = 1; +export const SUBCONTAINER_CANVAS_BORDER_WIDTH = 1; export const BOX_PADDING = 2; diff --git a/frontend/src/AppBuilder/Widgets/Container.jsx b/frontend/src/AppBuilder/Widgets/Container/Container.jsx similarity index 52% rename from frontend/src/AppBuilder/Widgets/Container.jsx rename to frontend/src/AppBuilder/Widgets/Container/Container.jsx index 23bfbb0eee..4978427370 100644 --- a/frontend/src/AppBuilder/Widgets/Container.jsx +++ b/frontend/src/AppBuilder/Widgets/Container/Container.jsx @@ -3,8 +3,12 @@ import { Container as ContainerComponent } from '@/AppBuilder/AppCanvas/Containe import Spinner from '@/_ui/Spinner'; import { useExposeState } from '@/AppBuilder/_hooks/useExposeVariables'; import { shallow } from 'zustand/shallow'; -import { CONTAINER_CANVAS_PADDING, CONTAINER_CANVAS_BORDER_WIDTH } from '@/AppBuilder/AppCanvas/appCanvasConstants'; +import { + CONTAINER_FORM_CANVAS_PADDING, + SUBCONTAINER_CANVAS_BORDER_WIDTH, +} from '@/AppBuilder/AppCanvas/appCanvasConstants'; import useStore from '@/AppBuilder/_stores/store'; +import './container.scss'; export const Container = ({ id, @@ -16,8 +20,6 @@ export const Container = ({ setExposedVariables, setExposedVariable, }) => { - const { borderRadius, borderColor, boxShadow, headerHeight = 80 } = styles; - const { isDisabled, isVisible, isLoading } = useExposeState( properties.loadingState, properties.visibility, @@ -31,6 +33,7 @@ export const Container = ({ shallow ); + const { borderRadius, borderColor, boxShadow, headerHeight = 80 } = styles; const contentBgColor = useMemo(() => { return { backgroundColor: @@ -50,58 +53,66 @@ export const Container = ({ const computedStyles = { backgroundColor: contentBgColor.backgroundColor, borderRadius: borderRadius ? parseFloat(borderRadius) : 0, - border: `${CONTAINER_CANVAS_BORDER_WIDTH}px solid ${borderColor}`, + border: `${SUBCONTAINER_CANVAS_BORDER_WIDTH}px solid ${borderColor}`, height, - padding: `${CONTAINER_CANVAS_PADDING}px`, display: isVisible ? 'flex' : 'none', flexDirection: 'column', position: 'relative', boxShadow, }; - const computedHeaderStyles = { - ...headerBgColor, - height: `${headerHeight}px`, + const containerHeaderStyles = { flexShrink: 0, - flexGrow: 0, - borderBottom: `1px solid var(--border-weak)`, + padding: `${CONTAINER_FORM_CANVAS_PADDING}px ${CONTAINER_FORM_CANVAS_PADDING}px 3px ${CONTAINER_FORM_CANVAS_PADDING}px`, + ...headerBgColor, }; - const computedContentStyles = { - ...contentBgColor, - flex: 1, - // Prevent the scroll when dragging a widget inside the container or moving out of the container - overflow: isWidgetInContainerDragging ? 'hidden' : 'hidden auto', + const containerContentStyles = { + overflow: 'hidden auto', + display: 'flex', + height: '100%', + padding: `${CONTAINER_FORM_CANVAS_PADDING}px`, }; + return (
- {properties.showHeader && ( - - )} {isLoading ? ( -
- -
+ ) : ( - + <> + {properties.showHeader && ( +
+ +
+ )} +
+ +
+ )}
); diff --git a/frontend/src/AppBuilder/Widgets/Container/container.scss b/frontend/src/AppBuilder/Widgets/Container/container.scss new file mode 100644 index 0000000000..323f5e8c9a --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/Container/container.scss @@ -0,0 +1,13 @@ +.wj-container-header { + position: relative; + &::after { + content: ''; + position: absolute; + bottom: 0; + left: -7px; + right: -7px; + height: 1px; + background-color: var(--border-weak); + } + } + \ No newline at end of file diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx index c9ea9b7601..b61d40c194 100644 --- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx @@ -1,21 +1,22 @@ -import React, { useRef, useState, useEffect, useMemo } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import { Container as SubContainer } from '@/AppBuilder/AppCanvas/Container'; // eslint-disable-next-line import/no-unresolved -import { diff } from 'deep-object-diff'; import _, { debounce, omit } from 'lodash'; -import { Box } from '@/Editor/Box'; import { generateUIComponents } from './FormUtils'; import { useMounted } from '@/_hooks/use-mount'; import { onComponentClick, removeFunctionObjects } from '@/_helpers/appUtils'; -import { useAppInfo } from '@/_stores/appDataStore'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import RenderSchema from './RenderSchema'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { + CONTAINER_FORM_CANVAS_PADDING, + SUBCONTAINER_CANVAS_BORDER_WIDTH, +} from '@/AppBuilder/AppCanvas/appCanvasConstants'; +import './form.scss'; const getCanvasHeight = (height) => { const parsedHeight = height.includes('px') ? parseInt(height, 10) : height; - return Math.ceil(parsedHeight); }; @@ -59,7 +60,7 @@ export const Form = function Form(props) { const computedStyles = { backgroundColor, borderRadius: borderRadius ? parseFloat(borderRadius) : 0, - border: `1px solid ${borderColor}`, + border: `${SUBCONTAINER_CANVAS_BORDER_WIDTH}px solid ${borderColor}`, height, display: visibility ? 'flex' : 'none', position: 'relative', @@ -69,25 +70,29 @@ export const Form = function Form(props) { const formHeader = { flexShrink: 0, - // height: headerHeight, - padding: '10px 6px', - borderBottom: '1px solid var(--border-weak)', + paddingBottom: '3px', + paddingTop: '7px', + paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, + paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, backgroundColor: ['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor, }; - const formFooter = { - flexShrink: 0, - // height: footerHeight, - padding: '10px 6px', - borderTop: '1px solid var(--border-weak)', - backgroundColor: - ['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor, - }; + const formContent = { overflow: 'hidden auto', display: 'flex', height: '100%', - padding: '10px 6px', + paddingTop: `${CONTAINER_FORM_CANVAS_PADDING}px`, + paddingBottom: showFooter ? '3px' : '7px', + paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, + paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, + }; + + const formFooter = { + flexShrink: 0, + padding: `${CONTAINER_FORM_CANVAS_PADDING}px`, + backgroundColor: + ['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor, }; const parentRef = useRef(null); @@ -287,7 +292,7 @@ export const Form = function Form(props) { }} //Hack, should find a better solution - to prevent losing z index+1 when container element is clicked > {showHeader && ( -
+
)} @@ -320,29 +326,8 @@ export const Form = function Form(props) { onOptionsChange={onOptionsChange} styles={{ backgroundColor: computedStyles.backgroundColor }} darkMode={darkMode} + componentType="Form" /> - {/* - */}
)} {advanced && @@ -375,28 +360,6 @@ export const Form = function Form(props) { onOptionsChange={onComponentOptionsChangedForSubcontainer} />
- {/* */}
); })} @@ -404,7 +367,7 @@ export const Form = function Form(props) { )}
{showFooter && ( -
+
)} diff --git a/frontend/src/AppBuilder/Widgets/Form/form.scss b/frontend/src/AppBuilder/Widgets/Form/form.scss new file mode 100644 index 0000000000..88a5ad055e --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/Form/form.scss @@ -0,0 +1,25 @@ +.wj-form-header { + position: relative; + &::after { + content: ''; + position: absolute; + bottom: 0; + left: -7px; + right: -7px; + height: 1px; + background-color: var(--border-weak); + } +} + +.wj-form-footer { + position: relative; + &::after { + content: ''; + position: absolute; + top: 0; + left: -7px; + right: -7px; + height: 1px; + background-color: var(--border-weak); + } +} diff --git a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx index 34efc57221..649e79f451 100644 --- a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx +++ b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx @@ -410,6 +410,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) { width: `${(Number(cardWidth) || 300) + 48}px`, }} kanbanProps={kanbanProps} + componentType="Kanban" > {items[columnId] && ( diff --git a/frontend/src/AppBuilder/Widgets/Listview.jsx b/frontend/src/AppBuilder/Widgets/Listview.jsx index 5ec5abbb17..285f9e5bf9 100644 --- a/frontend/src/AppBuilder/Widgets/Listview.jsx +++ b/frontend/src/AppBuilder/Widgets/Listview.jsx @@ -12,11 +12,8 @@ import { shallow } from 'zustand/shallow'; export const Listview = function Listview({ id, - component, width, height, - containerProps, - removeComponent, properties, styles, fireEvent, @@ -270,38 +267,8 @@ export const Listview = function Listview({ columns={positiveColumns} listViewMode={mode} darkMode={darkMode} + componentType="Listview" /> - {/* { - const changedData = { [component.name]: { [optionName]: value } }; - const existingDataAtIndex = prevData[index] ?? {}; - const newDataAtIndex = { - ...prevData[index], - [component.name]: { - ...existingDataAtIndex[component.name], - ...changedData[component.name], - id: componentId, - }, - }; - const newChildrenData = { ...prevData, [index]: newDataAtIndex }; - return { ...prevData, ...newChildrenData }; - }); - }} - /> */}
))}
diff --git a/frontend/src/AppBuilder/Widgets/Tabs.jsx b/frontend/src/AppBuilder/Widgets/Tabs.jsx index 3a93fa698b..7f4fb527e4 100644 --- a/frontend/src/AppBuilder/Widgets/Tabs.jsx +++ b/frontend/src/AppBuilder/Widgets/Tabs.jsx @@ -126,6 +126,7 @@ export const Tabs = function Tabs({ allowContainerSelect={true} styles={{ backgroundColor: bgColor }} darkMode={darkMode} + componentType="Tabs" /> ); diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js index 38e9abd955..5e817c7f7e 100644 --- a/frontend/src/AppBuilder/_helpers/editorHelpers.js +++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js @@ -59,7 +59,7 @@ import { BoundedBox } from '@/Editor/Components/BoundedBox/BoundedBox'; import { isPDFSupported } from '@/_helpers/appUtils'; import { resolveWidgetFieldValue } from '@/_helpers/utils'; import { useEditorStore } from '@/_stores/editorStore'; -import { Container } from '@/AppBuilder/Widgets/Container'; +import { Container } from '@/AppBuilder/Widgets/Container/Container'; import { Listview } from '@/AppBuilder/Widgets/Listview'; import { Tabs } from '@/AppBuilder/Widgets/Tabs'; import { Kanban } from '@/AppBuilder/Widgets/Kanban/Kanban'; From c031d5971865c25e03919037fcaecdc763cf66b4 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Wed, 19 Feb 2025 21:00:12 +0530 Subject: [PATCH 26/42] replace settings icon with ne solid icon --- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 62 ++++++++++--------- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 2 + 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index e3e38999f3..4deb50ea2e 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -3,6 +3,7 @@ import { shallow } from 'zustand/shallow'; import './configHandle.scss'; import useStore from '@/AppBuilder/_stores/store'; import { findHighestLevelofSelection } from '../Grid/gridUtils'; +import SolidIcon from '@/_ui/Icon/solidIcons/index'; const CONFIG_HANDLE_HEIGHT = 20; const BUFFER_HEIGHT = 1; @@ -73,6 +74,7 @@ export const ConfigHandle = ({ background: visibility === false ? '#c6cad0' : componentType === 'Modal' && isModalOpen ? '#c6cad0' : '#4D72FA', border: position === 'bottom' ? '1px solid white' : 'none', + color: visibility === false && 'var(--text-placeholder)', }} className="badge handle-content" > @@ -86,42 +88,42 @@ export const ConfigHandle = ({ data-cy={`${componentName?.toLowerCase()}-config-handle`} className="text-truncate" > - + {/* Settings Icon */} + + + {componentName} + {/* Divider */} +
+ {/* Delete Button */} {!isMultipleComponentsSelected && !shouldFreeze && ( -
- { + deleteComponents([id]); + }} + > + setComponentToInspect(componentName)} - data-cy={`${componentName.toLowerCase()}-inspect-button`} - className="config-handle-inspect" + fill={visibility === false ? 'var(--text-placeholder)' : '#fff'} /> - { - deleteComponents([id]); - }} - data-cy={`${componentName.toLowerCase()}-delete-button`} - className="delete-icon" - /> -
+ )} diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index ff22963444..895697091f 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -52,7 +52,9 @@ const WidgetWrapper = memo( height: visibility === false ? '10px' : `${height}px`, transform: `translate(${layoutData.left * gridWidth}px, ${layoutData.top}px)`, WebkitFontSmoothing: 'antialiased', + border: visibility === false ? `1px solid var(--border-default)` : 'none', }; + if (!componentType) return null; return ( <> From 53e7de02477c756b48de24f141dc55aec915ba75 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 10 Mar 2025 13:25:20 +0530 Subject: [PATCH 27/42] Fix config handle --- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 34 +++++++++++++------ .../AppCanvas/ConfigHandle/configHandle.scss | 17 +--------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index 4deb50ea2e..4c558f3cc2 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -111,19 +111,33 @@ export const ConfigHandle = ({ {/* Delete Button */} {!isMultipleComponentsSelected && !shouldFreeze && ( - { - deleteComponents([id]); - }} - > - + setComponentToInspect(componentName)} + data-cy={`${componentName.toLowerCase()}-inspect-button`} + className="config-handle-inspect" /> - + { + deleteComponents([id]); + }} + data-cy={`${componentName.toLowerCase()}-delete-button`} + > + + + )} diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss index e7322959e5..5cb1b94268 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss @@ -31,22 +31,7 @@ .badge { font-size: 9px; border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - - .delete-part { - margin-left: 10px; - float: right; - } - - .delete-part::before { - height: 12px; - display: inline-block; - width: 2px; - background-color: rgba(255, 255, 255, 0.8); - opacity: 0.5; - content: ""; - vertical-align: middle; - } + border-bottom-right-radius: 0 } } From 753f3384e9623a9d73bcef10f1b2d405b86c5eb5 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 10 Mar 2025 13:27:19 +0530 Subject: [PATCH 28/42] fix conflict --- frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 6403d63201..559c70fe86 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -884,7 +884,7 @@ export default function Grid({ gridWidth, currentLayout }) { onDrag={(e) => { // Since onDrag is called multiple times when dragging, hence we are using isDraggingRef to prevent setting state again and again if (!isDraggingRef.current) { - useGridStore.getState().actions.setDraggingComponentId(e.target.id); + useStore.getState().setDraggingComponentId(e.target.id); showGridLines(); isDraggingRef.current = true; } From bb7ef4e761f86e6e6c13f9dcbbbe564e970d0ca0 Mon Sep 17 00:00:00 2001 From: Vijaykant Yadav Date: Mon, 10 Mar 2025 13:38:17 +0530 Subject: [PATCH 29/42] fix: appbuilder modularisation bugs --- .../QueryManager/Components/DataSourceSelect.jsx | 8 ++++++-- server/src/modules/versions/util.service.ts | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx b/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx index 0cdcdb844b..f0a3bbe811 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx @@ -7,7 +7,7 @@ import { getWorkspaceId, decodeEntities } from '@/_helpers/utils'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import { useDataSources, useGlobalDataSources, useSampleDataSource } from '@/_stores/dataSourcesStore'; import { useDataQueriesActions } from '@/_stores/dataQueriesStore'; -import { staticDataSources as staticDatasources } from '../constants'; +import { defaultSources, staticDataSources as staticDatasources } from '../constants'; import { useQueryPanelActions } from '@/_stores/queryPanelStore'; import Search from '@/_ui/Icon/solidIcons/Search'; import { Tooltip } from 'react-tooltip'; @@ -135,7 +135,7 @@ function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSourc
{' '} - {source.name} + {defaultSources[cleanWord(source.name)].name}
), @@ -178,6 +178,10 @@ function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSourc } }; + function cleanWord(word) { + return word.replace(/default/g, ''); + } + return (