diff --git a/frontend/ee b/frontend/ee
index 387bb6e55b..9458c8d66f 160000
--- a/frontend/ee
+++ b/frontend/ee
@@ -1 +1 @@
-Subproject commit 387bb6e55bc6a7600b7125bb9e22ca2b17dfe65d
+Subproject commit 9458c8d66f29f8334765b5757dd096139a8d53d2
diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx
index c9fe887257..ac22aff055 100644
--- a/frontend/src/AppBuilder/AppCanvas/Container.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx
@@ -143,10 +143,11 @@ export const Container = React.memo(
if (canvasWidth !== undefined) {
if (componentType === 'Listview' && listViewMode == 'grid') return canvasWidth / columns - 2;
if (id === 'canvas') return canvasWidth;
- return getSubContainerWidthAfterPadding(canvasWidth, componentType, id);
+ return getSubContainerWidthAfterPadding(canvasWidth, componentType, id, realCanvasRef);
}
return realCanvasRef?.current?.offsetWidth;
}
+
const gridWidth = getContainerCanvasWidth() / NO_OF_GRIDS;
useEffect(() => {
diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js
index eab22333b2..955880a173 100644
--- a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js
+++ b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js
@@ -39,3 +39,5 @@ export const DROPPABLE_PARENTS = new Set([
export const TAB_CANVAS_PADDING = 7.5;
export const MODAL_CANVAS_PADDING = 5;
+
+export const LISTVIEW_CANVAS_PADDING = 7;
diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
index 228af4225e..109b17207b 100644
--- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
+++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
@@ -15,6 +15,7 @@ import {
BOX_PADDING,
TAB_CANVAS_PADDING,
MODAL_CANVAS_PADDING,
+ LISTVIEW_CANVAS_PADDING,
} from './appCanvasConstants';
export function snapToGrid(canvasWidth, x, y) {
@@ -779,7 +780,7 @@ export const getSubContainerIdWithSlots = (parentId) => {
return cleanParentId;
};
-export const getSubContainerWidthAfterPadding = (canvasWidth, componentType, componentId) => {
+export const getSubContainerWidthAfterPadding = (canvasWidth, componentType, componentId, realCanvasRef) => {
let padding = 2; //Need to update this 2 to correct value for other subcontainers
if (componentType === 'Container' || componentType === 'Form') {
padding = 2 * CONTAINER_FORM_CANVAS_PADDING + 2 * SUBCONTAINER_CANVAS_BORDER_WIDTH + 2 * BOX_PADDING;
@@ -797,5 +798,8 @@ export const getSubContainerWidthAfterPadding = (canvasWidth, componentType, com
padding = 2 * MODAL_CANVAS_PADDING;
}
}
+ if (componentType === 'Listview') {
+ padding = 2 * LISTVIEW_CANVAS_PADDING + 5; // 5 is accounting for scrollbar
+ }
return canvasWidth - padding;
};
diff --git a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx
index 98af1dc9e4..325c5c8ac5 100644
--- a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx
+++ b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx
@@ -58,7 +58,7 @@ const MultiLineCodeEditor = (props) => {
const replaceIdsWithName = useStore((state) => state.replaceIdsWithName, shallow);
const wrapperRef = useRef(null);
const getSuggestions = useStore((state) => state.getSuggestions, shallow);
- const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
+ const getServerSideGlobalResolveSuggestions = useStore((state) => state.getServerSideGlobalResolveSuggestions, shallow);
const isInsideQueryPane = !!document.querySelector('.code-hinter-wrapper')?.closest('.query-details');
const isInsideQueryManager = useMemo(
@@ -116,7 +116,7 @@ const MultiLineCodeEditor = (props) => {
const hints = getSuggestions();
- const serverHints = getServerSideGlobalSuggestions(isInsideQueryManager);
+ const serverHints = getServerSideGlobalResolveSuggestions(isInsideQueryManager);
const allHints = {
...hints,
diff --git a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx
index 5c422b1eb3..a24c41543f 100644
--- a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx
+++ b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx
@@ -98,7 +98,7 @@ export const PreviewBox = ({
const [largeDataset, setLargeDataset] = useState(false);
const globals = useStore((state) => state.getAllExposedValues(moduleId).constants || {}, shallow);
const secrets = useStore((state) => state.getSecrets(), shallow);
- const globalServerConstantsRegex = /^\{\{.*globals\.server.*\}\}$/;
+ const globalServerConstantsRegex = /\{\{.*globals\.server.*\}\}/;
const getPreviewContent = (content, type) => {
if (content === undefined || content === null) return currentValue;
@@ -251,7 +251,10 @@ const RenderResolvedValue = ({
isServerConstant = false,
isLargeDataset,
}) => {
- const isServerSideGlobalEnabled = useStore((state) => !!state?.license?.featureAccess?.serverSideGlobal, shallow);
+ const isServerSideGlobalResolveEnabled = useStore(
+ (state) => !!state?.license?.featureAccess?.serverSideGlobalResolve,
+ shallow
+ );
const computeCoersionPreview = (resolvedValue, coersionData) => {
if (coersionData?.typeBeforeCoercion === coersionData?.typeAfterCoercion) return resolvedValue;
@@ -276,7 +279,7 @@ const RenderResolvedValue = ({
: previewType;
const previewContent = isServerConstant
- ? isServerSideGlobalEnabled
+ ? isServerSideGlobalResolveEnabled
? 'Server variables would be resolved at runtime'
: 'Server variables are only available in paid plans'
: isSecretConstant
diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx
index 9c85e0bf43..fba7322bb2 100644
--- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx
+++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx
@@ -216,7 +216,7 @@ const EditorInput = ({
const getSuggestions = useStore((state) => state.getSuggestions, shallow);
const [codeMirrorView, setCodeMirrorView] = useState(undefined);
- const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
+ const getServerSideGlobalResolveSuggestions = useStore((state) => state.getServerSideGlobalResolveSuggestions, shallow);
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onBlurUpdate, currentValue, 'singleline');
@@ -226,7 +226,7 @@ const EditorInput = ({
);
function autoCompleteExtensionConfig(context) {
const hintsWithoutParamHints = getSuggestions();
- const serverHints = getServerSideGlobalSuggestions(isInsideQueryManager);
+ const serverHints = getServerSideGlobalResolveSuggestions(isInsideQueryManager);
let word = context.matchBefore(/\w*/);
diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx
index d85917c797..9f8379f85c 100644
--- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx
+++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx
@@ -10,12 +10,13 @@ import AppModeToggle from './AppModeToggle';
import { ThemeSelect } from '@/modules/Appbuilder/components';
import MaintenanceMode from './MaintenanceMode';
import HideHeaderToggle from './HideHeaderToggle';
+import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext';
const GlobalSettings = ({ darkMode }) => {
const shouldFreeze = useStore((state) => state.getShouldFreeze());
return (
- <>
+
@@ -44,7 +45,7 @@ const GlobalSettings = ({ darkMode }) => {
- >
+
);
};
diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx
index d4b8573c7d..68a54c2ca4 100644
--- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx
+++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx
@@ -12,11 +12,12 @@ import './style.scss';
import { SortableTree } from './Tree/SortableTree';
import { PageGroupMenu } from './AddPageButton';
import { PageHandlerMenu } from './PageHandlerMenu.jsx';
+import AppPermissionsModal from '@/modules/Appbuilder/components/AppPermissionsModal';
import { EditModal } from './EditModal';
import { SettingsModal } from './SettingsModal';
import { DeletePageConfirmationModal } from './DeletePageConfirmationModal';
import SolidIcon from '@/_ui/Icon/SolidIcons';
-import PagePermission from './PagePermission';
+import { appPermissionService } from '@/_services';
export const PageMenu = ({ darkMode, switchPage, pinned, setPinned }) => {
const showAddNewPageInput = useStore((state) => state.showAddNewPageInput);
@@ -27,6 +28,12 @@ export const PageMenu = ({ darkMode, switchPage, pinned, setPinned }) => {
const shouldFreeze = useStore((state) => state.getShouldFreeze());
const enableReleasedVersionPopupState = useStore((state) => state.enableReleasedVersionPopupState);
const closePageEditPopover = useStore((state) => state.closePageEditPopover);
+ const editingPageId = useStore((state) => state.editingPage?.id);
+ const editingPageName = useStore((state) => state.editingPage?.name);
+ const showPagePermissionModal = useStore((state) => state.showPagePermissionModal);
+ const togglePagePermissionModal = useStore((state) => state.togglePagePermissionModal);
+ const updatePageWithPermissions = useStore((state) => state.updatePageWithPermissions);
+
useEffect(() => {
return () => {
closePageEditPopover();
@@ -95,7 +102,21 @@ export const PageMenu = ({ darkMode, switchPage, pinned, setPinned }) => {
>
- {isLicensed ?
: <>>}
+ {isLicensed && (
+
appPermissionService.getPagePermission(appId, id)}
+ createPermission={(id, appId, body) => appPermissionService.createPagePermission(appId, id, body)}
+ updatePermission={(id, appId, body) => appPermissionService.updatePagePermission(appId, id, body)}
+ deletePermission={(id, appId) => appPermissionService.deletePagePermission(appId, id)}
+ onSuccess={(data) => updatePageWithPermissions(editingPageId, data)}
+ />
+ )}
diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx
deleted file mode 100644
index 160a941ebe..0000000000
--- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx
+++ /dev/null
@@ -1,509 +0,0 @@
-import React, { useEffect, useMemo, useState } from 'react';
-import { components } from 'react-select';
-import ModalBase from '@/_ui/Modal';
-import Select from '@/_ui/Select';
-import SolidIcon from '@/_ui/Icon/SolidIcons';
-import useStore from '@/AppBuilder/_stores/store';
-import { appPermissionService } from '@/_services';
-import { ConfirmDialog } from '@/_components';
-import toast from 'react-hot-toast';
-import Spinner from '@/_ui/Spinner';
-import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
-
-const PERMISSION_TYPES = {
- single: 'SINGLE',
- group: 'GROUP',
- all: 'ALL',
-};
-
-export default function PagePermission({ darkMode }) {
- const { moduleId } = useModuleContext();
- const showPagePermissionModal = useStore((state) => state.showPagePermissionModal);
- const togglePagePermissionModal = useStore((state) => state.togglePagePermissionModal);
- const editingPage = useStore((state) => state.editingPage);
- const appId = useStore((state) => state.appStore.modules[moduleId].app.appId);
- const selectedUserGroups = useStore((state) => state.selectedUserGroups);
- const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups);
- const selectedUsers = useStore((state) => state.selectedUsers);
- const setSelectedUsers = useStore((state) => state.setSelectedUsers);
- const pagePermission = useStore((state) => state.pagePermission);
- const setPagePermission = useStore((state) => state.setPagePermission);
- const updatePageWithPermissions = useStore((state) => state.updatePageWithPermissions);
-
- const [pagePermissionType, setPagePermissionType] = useState('all');
- const [showUserGroupSelect, toggleUserGroupSelect] = useState(false);
- const [showUsersSelect, toggleUsersSelect] = useState(false);
- const [showConfirmDelete, setShowConfirmDelete] = useState(false);
- const [isLoading, setIsLoading] = useState(false);
- const [isPermissionsLoading, setPermissionsLoading] = useState(true);
- const [initialSelectedGroups, setInitialSelectedGroups] = useState([]);
- const [initialSelectedUsers, setInitialSelectedUsers] = useState([]);
- const [initalPagePermissionType, setInitialPagePermissionType] = useState('all');
-
- useEffect(() => {
- if (!showPagePermissionModal) return;
- const fetchPagePermission = () => {
- appPermissionService.getPagePermission(appId, editingPage?.id).then((data) => {
- if (data) {
- if (data[0] && data[0]?.type === PERMISSION_TYPES.group) {
- const groups =
- data[0]?.groups?.map((user) => ({
- label: user?.permissionGroup?.name,
- value: user?.permissionGroup?.id,
- count: user?.permissionGroup?.count,
- })) ?? [];
- setPagePermissionType(data[0]?.type?.toLowerCase());
- setInitialPagePermissionType(data[0]?.type?.toLowerCase());
- setPagePermission(data);
- toggleUserGroupSelect(true);
- setInitialSelectedGroups(groups);
- data?.length && setSelectedUserGroups(groups);
- } else if (data[0] && data[0]?.type === PERMISSION_TYPES.single) {
- const users =
- data[0]?.users?.map(({ user }) => {
- const firstName = user.firstName || '';
- const lastName = user.lastName || '';
- return {
- value: user.id,
- label: `${firstName} ${lastName}`.trim(),
- email: user.email,
- initials: `${firstName[0] || ''}${lastName[0] || ''}`.toUpperCase(),
- };
- }) ?? [];
- setPagePermissionType(data[0]?.type?.toLowerCase());
- setInitialPagePermissionType(data[0]?.type?.toLowerCase());
- setPagePermission(data);
- toggleUsersSelect(true);
- setInitialSelectedUsers(users);
- data?.length && setSelectedUsers(users);
- }
- }
- setPermissionsLoading(false);
- });
- };
- fetchPagePermission();
- }, [showPagePermissionModal]);
-
- const isSelectionUnchanged = useMemo(() => {
- if (pagePermissionType === 'group') {
- if (!selectedUserGroups.length) return true;
- const current = selectedUserGroups
- .map((g) => g.value)
- .sort()
- .join(',');
- const initial = initialSelectedGroups
- .map((g) => g.value)
- .sort()
- .join(',');
- return current === initial;
- } else if (pagePermissionType === 'single') {
- if (!selectedUsers.length) return true;
- const current = selectedUsers
- .map((u) => u.value)
- .sort()
- .join(',');
- const initial = initialSelectedUsers
- .map((u) => u.value)
- .sort()
- .join(',');
- return current === initial;
- } else {
- if (!pagePermission?.length) {
- return true;
- } else {
- return initalPagePermissionType == pagePermissionType;
- }
- }
- }, [
- pagePermissionType,
- selectedUserGroups,
- initialSelectedGroups,
- selectedUsers,
- initialSelectedUsers,
- initalPagePermissionType,
- ]);
-
- const permissionTypeOptions = useMemo(
- () => [
- {
- label: 'All users with access to the app',
- value: 'all',
- icon: 'globe',
- },
- {
- label: 'Users',
- value: 'single',
- icon: 'user',
- },
- {
- label: 'User groups',
- value: 'group',
- icon: 'usergroup',
- },
- ],
- []
- );
- const handlePermissionTypeChange = (value) => {
- switch (value) {
- case 'group': {
- toggleUserGroupSelect(true);
- toggleUsersSelect(false);
- setPagePermissionType('group');
- break;
- }
- case 'single': {
- toggleUsersSelect(true);
- toggleUserGroupSelect(false);
- setPagePermissionType('single');
- break;
- }
- case 'all': {
- toggleUsersSelect(false);
- toggleUserGroupSelect(false);
- setPagePermissionType('all');
- }
- }
- };
-
- const handlePagePermissionModalClose = () => {
- togglePagePermissionModal(false);
- toggleUserGroupSelect(false);
- toggleUsersSelect(false);
- setPagePermissionType('all');
- setPagePermission(null);
- setSelectedUsers([]);
- setSelectedUserGroups([]);
- setInitialSelectedGroups([]);
- setInitialSelectedUsers([]);
- };
-
- const createPagePermission = () => {
- const body = {
- pageId: editingPage?.id,
- type: PERMISSION_TYPES[pagePermissionType],
- ...(pagePermissionType === 'group'
- ? { groups: selectedUserGroups.map((group) => group?.value) }
- : { users: selectedUsers.map((user) => user?.value) }),
- };
- setIsLoading(true);
- appPermissionService
- .createPagePermission(appId, editingPage?.id, body)
- .then((data) => {
- toast.success('Permission successfully created!', {
- className: 'text-nowrap w-auto mw-100',
- });
- updatePageWithPermissions(editingPage?.id, data);
- })
- .catch(() => {
- toast.error('Permission could not be created. Please try again!', {
- className: 'text-nowrap w-auto mw-100',
- });
- })
- .finally(() => {
- setIsLoading(false);
- handlePagePermissionModalClose();
- });
- };
-
- const updatePagePermission = () => {
- const body = {
- pageId: editingPage?.id,
- type: PERMISSION_TYPES[pagePermissionType],
- ...(pagePermissionType === 'group'
- ? { groups: selectedUserGroups.map((group) => group?.value) }
- : { users: selectedUsers.map((user) => user?.value) }),
- };
- setIsLoading(true);
- appPermissionService
- .updatePagePermission(appId, editingPage?.id, body)
- .then((data) => {
- toast.success('Permission successfully updated!', {
- className: 'text-nowrap w-auto mw-100',
- });
- updatePageWithPermissions(editingPage?.id, data);
- })
- .catch(() => {
- toast.error('Permission could not be updated. Please try again!', {
- className: 'text-nowrap w-auto mw-100',
- });
- })
- .finally(() => {
- setIsLoading(false);
- handlePagePermissionModalClose();
- });
- };
-
- const deletePagePermission = () => {
- setIsLoading(true);
- appPermissionService
- .deletePagePermission(appId, editingPage?.id)
- .then((data) => {
- toast.success('Permission successfully deleted!', {
- className: 'text-nowrap w-auto mw-100',
- });
- updatePageWithPermissions(editingPage?.id, []);
- })
- .catch(() => {
- toast.error('Permission could not be deleted. Please try again!', {
- className: 'text-nowrap w-auto mw-100',
- });
- setShowConfirmDelete(false);
- togglePagePermissionModal(true);
- })
- .finally(() => {
- setIsLoading(false);
- setShowConfirmDelete(false);
- });
- };
-
- const renderPermissionTypeOptions = ({ label, icon }) => {
- return (
-
- );
- };
-
- return (
- <>
-
- Page permission
-
- }
- handleConfirm={!pagePermission ? createPagePermission : updatePagePermission}
- show={showPagePermissionModal}
- isLoading={isLoading}
- handleClose={handlePagePermissionModalClose}
- confirmBtnProps={{
- title: pagePermission
- ? 'Save changes'
- : pagePermissionType === 'all'
- ? 'Default permission'
- : 'Create permission',
- disabled: isPermissionsLoading || isSelectionUnchanged,
- tooltipMessage: '',
- leftIcon: pagePermission && 'save',
- className: 'action-btn-page-permission',
- }}
- darkMode={darkMode}
- className="page-permissions-modal"
- >
-
- {isPermissionsLoading ? (
-
-
-
- ) : (
- <>
-
-
-
-
-
-
-
- Only selected users will be allowed to access this page. Read docs to know more.
-
-
-
-
-
-
- {showUserGroupSelect &&
}
- {showUsersSelect &&
}
- >
- )}
-
-
- {showConfirmDelete && (
- deletePagePermission()}
- onCancel={() => setShowConfirmDelete(false)}
- confirmButtonText={'Delete'}
- darkMode={darkMode}
- confirmButtonIcon={'trash'}
- confirmButtonIconWidth="20"
- confirmButtonIconFill={'var(--slate3)'}
- />
- )}
- >
- );
-}
-
-const UserGroupSelect = () => {
- const { moduleId } = useModuleContext();
- const appId = useStore((state) => state.appStore.modules[moduleId].app.appId);
- const selectedUserGroups = useStore((state) => state.selectedUserGroups);
- const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups);
- const [userGroups, setUserGroups] = useState([]);
- useEffect(() => {
- const fetchUserGroups = () => {
- appPermissionService.getUsers(appId, 'user-groups').then((data) => {
- if (data?.length) {
- const groups = [];
- data.map((group) => {
- groups.push({ value: group.id, label: group.name, count: group.count });
- });
- setUserGroups(groups);
- }
- });
- };
- fetchUserGroups();
- }, []);
-
- const CustomOption = (props) => {
- const { data, isFocused, isSelected } = props;
-
- return (
-
-
-
-
-
{data.label}
-
{data.count} users
-
-
-
- );
- };
-
- return (
-
-
-
- );
-};
-
-const UserSelect = () => {
- const { moduleId } = useModuleContext();
- const appId = useStore((state) => state.appStore.modules[moduleId].app.appId);
- const editingPage = useStore((state) => state.editingPage);
- const selectedUsers = useStore((state) => state.selectedUsers);
- const setSelectedUsers = useStore((state) => state.setSelectedUsers);
- const [users, setUsers] = useState([]);
- useEffect(() => {
- const fetchUsers = () => {
- appPermissionService.getUsers(appId, 'users').then((data) => {
- if (data?.length) {
- const users = [];
- data.map((user) => {
- const firstName = user.firstName || '';
- const lastName = user.lastName || '';
- users.push({
- value: user.id,
- label: `${firstName} ${lastName}`.trim(),
- email: user.email,
- initials: `${firstName[0] || ''}${lastName[0] || ''}`.toUpperCase(),
- });
- });
- setUsers(users);
- }
- });
- };
- fetchUsers();
- }, []);
-
- const CustomOption = (props) => {
- const { data, isFocused, isSelected } = props;
- return (
-
-
-
-
{data.initials}
-
-
{data.label}
-
{data.email}
-
-
-
- );
- };
-
- const selectStyles = {
- option: (base) => ({
- ...base,
- padding: '8px 0px',
- }),
- };
- return (
-
-
-
- );
-};
-
-const CustomMenuList = (props) => {
- const { info } = props.selectProps;
- return (
-
-
- {props.children}
-
- );
-};
diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/style.scss b/frontend/src/AppBuilder/LeftSidebar/PageMenu/style.scss
index bad5bae2af..4510123efc 100644
--- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/style.scss
+++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/style.scss
@@ -289,115 +289,3 @@
}
}
}
-
-.page-permission {
- .info-container {
- display: flex;
- width: auto;
- height: auto;
- padding: 10px 12px 8px 12px;
- border: 1px solid var(--slate5);
- background: var(--slate2);
- border-radius: 6px 6px 6px 6px;
- margin-bottom: 13px;
- margin-top: 0px;
- }
-
- .permission-type-select {
- align-items: center;
-
- .col-auto {
- padding-right: 0px;
- }
- }
-}
-
-.page-permissions-modal {
- #header-actions {
- display: flex;
- align-items: center;
- gap: 12px;
- }
-
- .react-select__option {
- padding: 8px 0px;
-
- input {
- margin-right: 10px;
- }
- }
-
- .react-select__menu-list {
- overflow-y: unset !important;
- }
-
- .user-select-option {
- display: flex;
- align-items: center;
- padding: 8px 12px;
- cursor: pointer;
-
- &.focused {
- background-color: #f3f4f6; // Tailwind's gray-100 vibe
- }
-
- .avatar {
- background-color: var(--slate5); // light gray
- color: var(--slate12); // dark text
- font-weight: 500;
- font-size: 16px;
- width: 36px;
- height: 36px;
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 12px;
- flex-shrink: 0;
- }
-
- .user-info {
- display: flex;
- flex-direction: column;
-
- .name {
- font-weight: 500;
- font-size: 14px;
- color: var(--slate12);
- }
-
- .email {
- font-size: 12px;
- color: var(--slate10);
- }
- }
-
- .group-info {
- display: flex;
- flex-direction: row;
- gap: 8px;
- align-items: center;
-
- .name {
- font-weight: 400;
- font-size: 14px;
- color: var(--slate12);
- }
-
- .count {
- font-size: 12px;
- color: var(--slate9);
- }
- }
- }
-}
-
-.page-permission {
- .spinner-center {
- min-height: 250px;
- }
-}
-
-.modal-base .modal-footer .action-btn-page-permission svg path {
- fill: var(--indigo1) !important;
-}
\ No newline at end of file
diff --git a/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx b/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx
index dc6f075d33..2d192ec666 100644
--- a/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx
+++ b/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx
@@ -1,10 +1,10 @@
-import React, { useState, useCallback } from 'react';
+import React, { useState } from 'react';
import { Tooltip } from 'react-tooltip';
+import { ToolTip } from '@/_components/ToolTip';
import { updateQuerySuggestions } from '@/_helpers/appUtils';
// import { Confirm } from '../Viewer/Confirm';
import { toast } from 'react-hot-toast';
import { shallow } from 'zustand/shallow';
-import Copy from '@/_ui/Icon/solidIcons/Copy';
import DataSourceIcon from '../QueryManager/Components/DataSourceIcon';
import { isQueryRunnable, decodeEntities } from '@/_helpers/utils';
import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers';
@@ -12,13 +12,10 @@ import useStore from '@/AppBuilder/_stores/store';
//TODO: Remove this
import { Confirm } from '@/Editor/Viewer/Confirm';
// TODO: enable delete query confirmation popup
-import { debounce } from 'lodash';
-import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
+import { Button as ButtonComponent } from '@/components/ui/Button/Button.jsx';
+import SolidIcon from '@/_ui/Icon/SolidIcons';
export const QueryCard = ({ dataQuery, darkMode = false, localDs }) => {
- const { moduleId } = useModuleContext();
- const appId = useStore((state) => state.appStore.modules[moduleId].app.appId);
-
const isQuerySelected = useStore((state) => state.queryPanel.isQuerySelected(dataQuery.id), shallow);
const setSelectedQuery = useStore((state) => state.queryPanel.setSelectedQuery);
const checkExistingQueryName = useStore((state) => state.dataQuery.checkExistingQueryName);
@@ -26,9 +23,16 @@ export const QueryCard = ({ dataQuery, darkMode = false, localDs }) => {
const isDeletingQueryInProcess = useStore((state) => state.dataQuery.isDeletingQueryInProcess);
const renameQuery = useStore((state) => state.dataQuery.renameQuery);
const deleteDataQueries = useStore((state) => state.dataQuery.deleteDataQueries);
- const duplicateQuery = useStore((state) => state.dataQuery.duplicateQuery);
const setPreviewData = useStore((state) => state.queryPanel.setPreviewData);
- const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
+ const shouldFreeze = useStore((state) => state.getShouldFreeze());
+
+ const renamingQueryId = useStore((state) => state.queryPanel.renamingQueryId);
+ const deletingQueryId = useStore((state) => state.queryPanel.deletingQueryId);
+ const setRenamingQuery = useStore((state) => state.queryPanel.setRenamingQuery);
+ const deleteDataQuery = useStore((state) => state.queryPanel.deleteDataQuery);
+ const isRenaming = renamingQueryId === dataQuery.id;
+ const isDeleting = deletingQueryId === dataQuery.id;
+
const hasPermissions =
selectedDataSourceScope === 'global'
? canUpdateDataSource(dataQuery?.data_source_id) ||
@@ -36,57 +40,77 @@ export const QueryCard = ({ dataQuery, darkMode = false, localDs }) => {
canDeleteDataSource()
: true;
- const shouldFreeze = useStore((state) => state.getShouldFreeze());
-
- const [renamingQuery, setRenamingQuery] = useState(false);
-
- const deleteDataQuery = (e) => {
- e.stopPropagation();
- setShowDeleteConfirmation(true);
- };
+ const toggleQueryHandlerMenu = useStore((state) => state.queryPanel.toggleQueryHandlerMenu);
+ const featureAccess = useStore((state) => state?.license?.featureAccess, shallow);
+ const licenseValid = !featureAccess?.licenseStatus?.isExpired && featureAccess?.licenseStatus?.isLicenseValid;
+ const isRestricted = dataQuery.permissions && dataQuery.permissions.length !== 0;
const updateQueryName = (dataQuery, newName) => {
const { name } = dataQuery;
if (name === newName) {
- return setRenamingQuery(false);
+ return setRenamingQuery(null);
}
const isNewQueryNameAlreadyExists = checkExistingQueryName(newName);
if (newName && !isNewQueryNameAlreadyExists) {
renameQuery(dataQuery?.id, newName);
- setRenamingQuery(false);
+ setRenamingQuery(null);
updateQuerySuggestions(name, newName);
} else {
if (isNewQueryNameAlreadyExists) {
toast.error('Query name already exists');
}
- setRenamingQuery(false);
+ setRenamingQuery(null);
}
};
const executeDataQueryDeletion = () => {
- setShowDeleteConfirmation(false);
+ deleteDataQuery(null);
deleteDataQueries(dataQuery?.id);
setPreviewData(null);
};
- // To prevent user clicking from continuous clicks
- const debouncedDuplicateQuery = useCallback(
- debounce((queryId, appId) => {
- duplicateQuery(queryId, appId);
- setPreviewData(null);
- }, 500),
- [duplicateQuery]
- );
+ const getTooltip = () => {
+ const permission = dataQuery.permissions?.[0];
+ if (!permission) return null;
+
+ const users = permission.groups || permission.users || [];
+ if (users.length === 0) return null;
+
+ const isSingle = permission.type === 'SINGLE';
+ const isGroup = permission.type === 'GROUP';
+
+ if (isSingle) {
+ return users.length === 1
+ ? `Access restricted to ${users[0].user.email}`
+ : `Access restricted to ${users.length} users`;
+ }
+
+ if (isGroup) {
+ return users.length === 1
+ ? `Access restricted to ${users[0].permission_group?.name || users[0].permissionGroup?.name} group`
+ : `Access restricted to ${users.length} user groups`;
+ }
+
+ return null;
+ };
return (
<>
{
+ onClick={(e) => {
if (isQuerySelected) return;
- setSelectedQuery(dataQuery?.id);
- setPreviewData(null);
+ const menuBtn = document.getElementById(`query-handler-menu-${dataQuery?.id}`);
+ if (menuBtn.contains(e.target)) {
+ e.stopPropagation();
+ } else {
+ toggleQueryHandlerMenu(false);
+ }
+ setTimeout(() => {
+ setSelectedQuery(dataQuery?.id);
+ setPreviewData(null);
+ }, 0);
}}
role="button"
>
@@ -94,7 +118,7 @@ export const QueryCard = ({ dataQuery, darkMode = false, localDs }) => {
- {renamingQuery ? (
+ {isRenaming ? (
{
data-tooltip-dynamic="true"
>
{decodeEntities(dataQuery.name)}
- {' '}
+
+
+
+ {licenseValid && isRestricted && }
+
+ {' '}
{!isQueryRunnable(dataQuery) &&
Draft}
{localDs && (
<>
@@ -143,80 +172,24 @@ export const QueryCard = ({ dataQuery, darkMode = false, localDs }) => {
)}
- {!shouldFreeze && isQuerySelected && (
-
-
setRenamingQuery(true)}
- >
-
-
-
-
-
debouncedDuplicateQuery(dataQuery?.id, appId)}
- >
-
-
-
-
-
- {isDeletingQueryInProcess ? (
-
- ) : (
-
-
-
-
-
- )}
-
-
-
- )}
+
+ toggleQueryHandlerMenu(true, `query-handler-menu-${dataQuery?.id}`)}
+ size="small"
+ variant="outline"
+ className=""
+ id={`query-handler-menu-${dataQuery?.id}`}
+ />
+
setShowDeleteConfirmation(false)}
+ onCancel={() => deleteDataQuery(null)}
darkMode={darkMode}
/>
>
diff --git a/frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx b/frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx
new file mode 100644
index 0000000000..a9e3030b51
--- /dev/null
+++ b/frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx
@@ -0,0 +1,174 @@
+import React, { useCallback } from 'react';
+import { Overlay, Popover } from 'react-bootstrap';
+import useStore from '@/AppBuilder/_stores/store';
+import classNames from 'classnames';
+import Edit from '@/_ui/Icon/bulkIcons/Edit';
+import Trash from '@/_ui/Icon/solidIcons/Trash';
+import Copy from '@/_ui/Icon/solidIcons/Copy';
+import SolidIcon from '@/_ui/Icon/SolidIcons';
+import { shallow } from 'zustand/shallow';
+import { ToolTip } from '@/_components/ToolTip';
+import { debounce } from 'lodash';
+import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
+import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
+
+const QueryCardMenu = ({ darkMode }) => {
+ const { moduleId } = useModuleContext();
+ const appId = useStore((state) => state.appStore.modules[moduleId].app.appId);
+ const selectedQuery = useStore((state) => state.queryPanel.selectedQuery);
+ const toggleQueryPermissionModal = useStore((state) => state.queryPanel.toggleQueryPermissionModal);
+ const featureAccess = useStore((state) => state?.license?.featureAccess, shallow);
+ const licenseValid = !featureAccess?.licenseStatus?.isExpired && featureAccess?.licenseStatus?.isLicenseValid;
+ const targetBtnForMenu = useStore((state) => state.queryPanel.targetBtnForMenu);
+ const targetElement = document.getElementById(targetBtnForMenu);
+ const showQueryHandlerMenu = useStore((state) => state.queryPanel.showQueryHandlerMenu);
+ const toggleQueryHandlerMenu = useStore((state) => state.queryPanel.toggleQueryHandlerMenu);
+ const duplicateQuery = useStore((state) => state.dataQuery.duplicateQuery);
+ const setPreviewData = useStore((state) => state.queryPanel.setPreviewData);
+ const setRenamingQuery = useStore((state) => state.queryPanel.setRenamingQuery);
+ const deleteDataQuery = useStore((state) => state.queryPanel.deleteDataQuery);
+
+ const QUERY_MENU_OPTIONS = [
+ {
+ label: 'Rename',
+ value: 'rename',
+ icon: ,
+ showTooltip: false,
+ },
+ {
+ label: 'Duplicate',
+ value: 'duplicate',
+ icon: ,
+ showTooltip: false,
+ },
+ {
+ label: 'Query permission',
+ value: 'permission',
+ icon: (
+
+ ),
+ trailingIcon: ,
+ },
+ {
+ label: 'Delete',
+ value: 'delete',
+ icon: ,
+ showTooltip: false,
+ },
+ ];
+
+ // To prevent user clicking from continuous clicks
+ const debouncedDuplicateQuery = useCallback(
+ debounce((queryId, appId) => {
+ duplicateQuery(queryId, appId);
+ setPreviewData(null);
+ }, 500),
+ [duplicateQuery]
+ );
+
+ const handleQueryMenuActions = (value) => {
+ if (value === 'rename') {
+ setRenamingQuery(selectedQuery?.id);
+ }
+ if (value === 'duplicate') {
+ debouncedDuplicateQuery(selectedQuery?.id, appId);
+ }
+ if (value === 'permission') {
+ if (!licenseValid) return;
+ toggleQueryPermissionModal(true);
+ }
+ if (value === 'delete') {
+ deleteDataQuery(selectedQuery?.id);
+ }
+ toggleQueryHandlerMenu(false);
+ };
+
+ usePopoverObserver(
+ document.getElementsByClassName('query-list')[0],
+ targetElement,
+ document.getElementById('query-list-menu'),
+ showQueryHandlerMenu,
+ () => (document.getElementById('query-list-menu').style.display = 'block'),
+ () => (document.getElementById('query-list-menu').style.display = 'none')
+ );
+
+ return (
+ toggleQueryHandlerMenu(false)}
+ popperConfig={{
+ modifiers: [
+ {
+ name: 'flip',
+ options: {
+ fallbackPlacements: ['top-start'],
+ flipVariations: true,
+ allowedAutoPlacements: ['top', 'bottom'],
+ boundary: 'viewport',
+ },
+ },
+ {
+ name: 'offset',
+ options: {
+ offset: [0, 3],
+ },
+ },
+ ],
+ }}
+ >
+ {(props) => (
+
+ )}
+
+ );
+};
+
+export default QueryCardMenu;
diff --git a/frontend/src/AppBuilder/QueryPanel/QueryDataPane.jsx b/frontend/src/AppBuilder/QueryPanel/QueryDataPane.jsx
index 9ac052ae51..97b4daa68f 100644
--- a/frontend/src/AppBuilder/QueryPanel/QueryDataPane.jsx
+++ b/frontend/src/AppBuilder/QueryPanel/QueryDataPane.jsx
@@ -16,6 +16,10 @@ import DataSourceSelect from '../QueryManager/Components/DataSourceSelect';
import { OverlayTrigger, Popover } from 'react-bootstrap';
import FolderEmpty from '@/_ui/Icon/solidIcons/FolderEmpty';
import useStore from '@/AppBuilder/_stores/store';
+import AppPermissionsModal from '@/modules/Appbuilder/components/AppPermissionsModal';
+import { shallow } from 'zustand/shallow';
+import { appPermissionService } from '@/_services';
+import QueryCardMenu from './QueryCardMenu';
export const QueryDataPane = ({ darkMode }) => {
const { t } = useTranslation();
@@ -34,6 +38,12 @@ export const QueryDataPane = ({ darkMode }) => {
function isDataSourceLocal(dataQuery) {
return dataSources.some((dataSource) => dataSource.id === dataQuery.data_source_id);
}
+ const featureAccess = useStore((state) => state?.license?.featureAccess, shallow);
+ const licenseValid = !featureAccess?.licenseStatus?.isExpired && featureAccess?.licenseStatus?.isLicenseValid;
+ const selectedQuery = useStore((state) => state.queryPanel.selectedQuery);
+ const showQueryPermissionModal = useStore((state) => state.queryPanel.showQueryPermissionModal);
+ const toggleQueryPermissionModal = useStore((state) => state.queryPanel.toggleQueryPermissionModal);
+ const setQueries = useStore((state) => state.dataQuery.setQueries);
useEffect(() => {
setQueryPanelSearchTerm(searchTermForFilters);
@@ -171,6 +181,33 @@ export const QueryDataPane = ({ darkMode }) => {
{filteredQueries.map((query) => (
))}
+
+ {licenseValid && (
+ appPermissionService.getQueryPermission(appId, id)}
+ createPermission={(id, appId, body) => appPermissionService.createQueryPermission(appId, id, body)}
+ updatePermission={(id, appId, body) => appPermissionService.updateQueryPermission(appId, id, body)}
+ deletePermission={(id, appId) => appPermissionService.deleteQueryPermission(appId, id)}
+ onSuccess={(data) => {
+ const updatedDataQueries = dataQueries.map((query) => {
+ if (query.id === selectedQuery.id) {
+ return {
+ ...query,
+ permissions: data.length === 0 || data.length === undefined ? [] : [data[0]],
+ };
+ }
+ return query;
+ });
+ setQueries(updatedDataQueries);
+ }}
+ />
+ )}
);
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/container.js b/frontend/src/AppBuilder/WidgetManager/widgets/container.js
index 04ddf805d9..2bff0d84c5 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/container.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/container.js
@@ -47,11 +47,6 @@ export const containerConfig = {
defaultValue: true,
},
},
- headerHeight: {
- type: 'numberInput',
- displayName: 'Header height',
- validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
- },
},
defaultChildren: [
{
@@ -61,10 +56,10 @@ export const containerConfig = {
top: 20,
left: 1,
height: 40,
+ width: 20,
},
displayName: 'ContainerText',
properties: ['text'],
- slotName: 'header',
accessorKey: 'text',
styles: ['fontWeight', 'textSize', 'textColor'],
defaultValue: {
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/listview.js b/frontend/src/AppBuilder/WidgetManager/widgets/listview.js
index 6c599bc2fc..c3ebdcfa19 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/listview.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/listview.js
@@ -13,7 +13,7 @@ export const listviewConfig = {
top: 15,
left: 3,
height: 100,
- width: 7,
+ width: 4,
},
properties: ['source'],
accessorKey: 'imageURL',
@@ -24,6 +24,7 @@ export const listviewConfig = {
top: 50,
left: 11,
height: 30,
+ width: 4,
},
properties: ['text'],
accessorKey: 'text',
@@ -49,12 +50,14 @@ export const listviewConfig = {
data: {
type: 'code',
displayName: 'List data',
- schema: {
- type: 'union',
- schemas: [
- { type: 'array', element: { type: 'object' } },
- { type: 'array', element: { type: 'string' } },
- ],
+ validation: {
+ schema: {
+ type: 'union',
+ schemas: [
+ { type: 'array', element: { type: 'object' } },
+ { type: 'array', element: { type: 'string' } },
+ ],
+ },
defaultValue: "[{text: 'Sample text 1'}]",
},
},
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/modalV2.js b/frontend/src/AppBuilder/WidgetManager/widgets/modalV2.js
index a3c89acf93..2318a986f8 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/modalV2.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/modalV2.js
@@ -92,18 +92,6 @@ export const modalV2Config = {
accordian: 'Data',
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 400 },
},
- headerHeight: {
- type: 'numberInput',
- displayName: 'Header height',
- accordian: 'Data',
- validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
- },
- footerHeight: {
- type: 'numberInput',
- displayName: 'Footer height',
- accordian: 'Data',
- validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
- },
hideOnEsc: { type: 'toggle', displayName: 'Close on escape key', section: 'additionalActions' },
closeOnClickingOutside: { type: 'toggle', displayName: 'Close on clicking outside', section: 'additionalActions' },
hideCloseButton: { type: 'toggle', displayName: 'Hide close button', section: 'additionalActions' },
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/table.js b/frontend/src/AppBuilder/WidgetManager/widgets/table.js
index 9f0c4fd723..facb62f7d4 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/table.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/table.js
@@ -275,7 +275,7 @@ export const tableConfig = {
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
},
defaultSize: {
- width: 35,
+ width: 25,
height: 456,
},
events: {
diff --git a/frontend/src/AppBuilder/Widgets/Container/Container.jsx b/frontend/src/AppBuilder/Widgets/Container/Container.jsx
index a706d29069..07492d4415 100644
--- a/frontend/src/AppBuilder/Widgets/Container/Container.jsx
+++ b/frontend/src/AppBuilder/Widgets/Container/Container.jsx
@@ -9,6 +9,8 @@ import {
} from '@/AppBuilder/AppCanvas/appCanvasConstants';
import useStore from '@/AppBuilder/_stores/store';
import './container.scss';
+import { useActiveSlot } from '@/AppBuilder/_hooks/useActiveSlot';
+import { HorizontalSlot } from '@/AppBuilder/Widgets/Form/Components/HorizontalSlot';
export const Container = ({
id,
@@ -33,8 +35,13 @@ export const Container = ({
shallow
);
+ const isEditing = useStore((state) => state.currentMode === 'edit');
+ const setComponentProperty = useStore((state) => state.setComponentProperty, shallow);
+
+ const activeSlot = useActiveSlot(isEditing ? id : null); // Track the active slot for this widget
const { borderRadius, borderColor, boxShadow } = styles;
const { headerHeight = 80 } = properties;
+ const headerMaxHeight = parseInt(height, 10) - 100 - 10;
const contentBgColor = useMemo(() => {
return {
backgroundColor:
@@ -65,9 +72,9 @@ export const Container = ({
const containerHeaderStyles = {
flexShrink: 0,
padding: `${CONTAINER_FORM_CANVAS_PADDING}px ${CONTAINER_FORM_CANVAS_PADDING}px 3px ${CONTAINER_FORM_CANVAS_PADDING}px`,
+ maxHeight: `${headerMaxHeight}px`,
...headerBgColor,
};
-
const containerContentStyles = {
overflow: 'hidden auto',
display: 'flex',
@@ -75,6 +82,11 @@ export const Container = ({
padding: `${CONTAINER_FORM_CANVAS_PADDING}px`,
};
+ const updateHeaderSizeInStore = ({ newHeight }) => {
+ const _height = parseInt(newHeight, 10);
+ setComponentProperty(id, `headerHeight`, _height, 'properties', 'value', false);
+ };
+
return (
{properties.showHeader && (
-
-
-
+
)}
{
const parsedHeight = parseInt(height, 10);
-
- const { getRootProps, getHandleProps, getResizeState } = useResizable({
+ const { getRootProps, getHandleProps, getResizeState } = useSubContainerResizable({
initialHeight: parsedHeight,
initialWidth: '100%', // Now respects parent's width
minHeight: 10,
@@ -34,12 +34,11 @@ export const HorizontalSlot = React.memo(
});
const { height: resizedHeight, isDragging } = getResizeState();
-
useEffect(() => {
if (isDragging) {
- showGridLinesOnSlot(id);
+ showGridLines();
} else {
- hideGridLinesOnSlot(id);
+ hideGridLines();
}
}, [isDragging, id]);
@@ -50,7 +49,10 @@ export const HorizontalSlot = React.memo(
};
return (
-
+
diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx
index 4dd8cc500b..cab5e0ea52 100644
--- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx
+++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx
@@ -341,6 +341,7 @@ export const Form = function Form(props) {
isDisabled={isDisabled}
isActive={activeSlot === `${id}-header`}
onResize={updateHeaderSizeInStore}
+ componentType="Form"
/>
)}
@@ -417,6 +418,7 @@ export const Form = function Form(props) {
isDisabled={isDisabled}
onResize={updateFooterSizeInStore}
isActive={activeSlot === `${id}-footer`}
+ componentType="Form"
/>
)}
diff --git a/frontend/src/AppBuilder/Widgets/Kanban/Components/Item.jsx b/frontend/src/AppBuilder/Widgets/Kanban/Components/Item.jsx
index 7b4b2a56cf..d147c1b135 100644
--- a/frontend/src/AppBuilder/Widgets/Kanban/Components/Item.jsx
+++ b/frontend/src/AppBuilder/Widgets/Kanban/Components/Item.jsx
@@ -81,7 +81,6 @@ export const Item = React.memo(
>
e.stopPropagation()}>
diff --git a/frontend/src/AppBuilder/Widgets/Listview.jsx b/frontend/src/AppBuilder/Widgets/Listview.jsx
index 856ef57e49..e08735eb2c 100644
--- a/frontend/src/AppBuilder/Widgets/Listview.jsx
+++ b/frontend/src/AppBuilder/Widgets/Listview.jsx
@@ -55,6 +55,8 @@ export const Listview = function Listview({
display: visibility ? 'flex' : 'none',
borderRadius: borderRadius ?? 0,
boxShadow,
+ padding: '7px 2px 7px 7px',
+ scrollbarGutter: 'stable',
};
const computeCanvasBackgroundColor = useMemo(() => {
@@ -235,7 +237,6 @@ export const Listview = function Listview({
// Update the customResolvables with the new listItems
if (listItems.length > 0) updateCustomResolvables(id, listItems, 'listItem', moduleId);
}
-
return (
containerProps.onComponentClick(id, component)}
data-cy={dataCy}
>
-
+
{filteredData.map((listItem, index) => (
{
- const canvasFooterHeight = getCanvasHeight(footerHeight);
- return (
-
-
- {isDisabled && (
-