Merge pull request #11319 from ToolJet/fixes/release-v3.0/appbuilder

LTS 3.0 Patch release
This commit is contained in:
Johnson Cherian 2024-11-14 17:35:32 +05:30 committed by GitHub
commit a16b006e4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
83 changed files with 1226 additions and 575 deletions

View file

@ -1 +1 @@
3.0.0-ce-lts
3.0.1-ce-lts

View file

@ -1 +1 @@
3.0.0-ce-lts
3.0.1-ce-lts

View file

@ -2,12 +2,11 @@ import React, { useState, useEffect, useRef } from 'react';
import { Container } from './Container';
import Grid from './Grid';
import { EditorSelecto } from './Selecto';
import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext';
import { HotkeyProvider } from './HotkeyProvider';
import './appCanvas.scss';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import { getCanvasWidth } from './appCanvasUtils';
import { getCanvasWidth, computeViewerBackgroundColor } from './appCanvasUtils';
import { NO_OF_GRIDS } from './appCanvasConstants';
import cx from 'classnames';
import FreezeVersionInfo from '@/AppBuilder/Header/FreezeVersionInfo';
@ -38,8 +37,9 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
const setIsComponentLayoutReady = useStore((state) => state.setIsComponentLayoutReady, shallow);
const canvasMaxWidth = useAppCanvasMaxWidth({ mode: currentMode });
const editorMarginLeft = useSidebarMargin(canvasContainerRef);
const pageSwitchInProgress = useStore((state) => state.pageSwitchInProgress);
const setPageSwitchInProgress = useStore((state) => state.setPageSwitchInProgress);
const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow);
const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow);
useEffect(() => {
// Need to remove this if we shift setExposedVariable Logic outside of components
// Currently present to run onLoadQueries after the component is mounted
@ -60,21 +60,27 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
return (
<div className={cx(`main main-editor-canvas`, {})} id="main-editor-canvas" onMouseUp={handleCanvasContainerMouseUp}>
{creationMode === 'GIT' && <FreezeVersionInfo info={'Apps imported from git repository cannot be edited'} />}
{creationMode !== 'GIT' && <FreezeVersionInfo hide={currentMode !== 'edit'} />}
<div
ref={canvasContainerRef}
className={cx(
'canvas-container align-items-center page-container',
{ 'dark-theme theme-dark': isAppDarkMode, close: !isViewerSidebarPinned }
// { 'hide-sidebar': !showLeftSidebar }
{ 'dark-theme theme-dark': isAppDarkMode, close: !isViewerSidebarPinned },
{ 'overflow-x-auto': (currentMode === 'edit' && isSidebarOpen) || currentMode === 'view' }
)}
style={{
// transform: `scale(1)`,
borderLeft: editorMarginLeft + 'px solid',
borderLeft: currentMode === 'edit' && editorMarginLeft + 'px solid',
height: currentMode === 'edit' ? canvasContainerHeight : '100%',
backgroundColor: canvasBgColor,
// background: !isAppDarkMode ? '#EBEBEF' : '#2E3035',
background:
currentMode === 'view'
? computeViewerBackgroundColor(isAppDarkMode, canvasBgColor)
: !isAppDarkMode
? '#EBEBEF'
: '#2F3C4C',
marginLeft:
isViewerSidebarPinned && currentLayout !== 'mobile' && currentMode !== 'edit'
isViewerSidebarPinned && !isPagesSidebarHidden && currentLayout !== 'mobile' && currentMode !== 'edit'
? pageSidebarStyle === 'icon'
? '65px'
: '210px'
@ -87,9 +93,9 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
}}
className={`app-${appId}`}
>
<AutoComputeMobileLayoutAlert currentLayout={currentLayout} darkMode={isAppDarkMode} />
{creationMode === 'GIT' && <FreezeVersionInfo info={'Apps imported from git repository cannot be edited'} />}
{creationMode !== 'GIT' && <FreezeVersionInfo hide={currentMode !== 'edit'} />}
{currentMode === 'edit' && (
<AutoComputeMobileLayoutAlert currentLayout={currentLayout} darkMode={isAppDarkMode} />
)}
<DeleteWidgetConfirmation darkMode={isAppDarkMode} />
<HotkeyProvider mode={currentMode} canvasMaxWidth={canvasMaxWidth} currentLayout={currentLayout}>
{environmentLoadingState !== 'loading' && (

View file

@ -77,6 +77,7 @@ export default function AutoComputeMobileLayoutAlert({ currentLayout, darkMode }
padding: 'var(--7, 16px)',
background: 'var(--base)',
margin: '10px',
zIndex: '1',
}}
className="d-flex flex-row"
>

View file

@ -5,7 +5,7 @@ import WidgetWrapper from './WidgetWrapper';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import { useDrop } from 'react-dnd';
import { addChildrenWidgetsToParent, addNewWidgetToTheEditor } from './appCanvasUtils';
import { addChildrenWidgetsToParent, addNewWidgetToTheEditor, computeViewerBackgroundColor } from './appCanvasUtils';
import { CANVAS_WIDTHS, NO_OF_GRIDS, WIDGETS_WITH_DEFAULT_CHILDREN } from './appCanvasConstants';
import { useGridStore } from '@/_stores/gridStore';
import NoComponentCanvasContainer from './NoComponentCanvasContainer';
@ -40,7 +40,6 @@ export const Container = React.memo(
const components = useStore((state) => state.getContainerChildrenMapping(id), shallow);
const componentType = useStore((state) => state.getComponentTypeFromId(id), shallow);
const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow);
const setSelectedComponents = useStore((state) => state.setSelectedComponents, shallow);
const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow);
const canvasBgColor = useStore(
(state) => (id === 'canvas' ? state.getCanvasBackgroundColor('canvas', darkMode) : ''),
@ -124,7 +123,12 @@ export const Container = React.memo(
height: id === 'canvas' ? `${canvasHeight}` : '100%',
// backgroundSize: '25.3953px 10px',
backgroundSize: `${gridWidth}px 10px`,
backgroundColor: id === 'canvas' ? canvasBgColor : '#f0f0f0',
backgroundColor:
currentMode === 'view'
? computeViewerBackgroundColor(darkMode, canvasBgColor)
: id === 'canvas'
? canvasBgColor
: '#f0f0f0',
width: getCanvasWidth(),
maxWidth: (() => {
// For Main Canvas

View file

@ -44,7 +44,8 @@ export default function Grid({ gridWidth, currentLayout }) {
const isGroupHandleHoverd = useIsGroupHandleHoverd();
const openModalWidgetId = useOpenModalWidgetId();
const moveableRef = useRef(null);
const [triggerCanvasUpdater, setTriggerCanvasUpdater] = useState(false);
const triggerCanvasUpdater = useStore((state) => state.triggerCanvasUpdater, shallow);
const toggleCanvasUpdater = useStore((state) => state.toggleCanvasUpdater, shallow);
const groupResizeDataRef = useRef([]);
const isDraggingRef = useRef(false);
const canvasWidth = NO_OF_GRIDS * gridWidth;
@ -347,7 +348,7 @@ export default function Grid({ gridWidth, currentLayout }) {
return layouts;
}, {});
setComponentLayout(updatedLayouts, newParent, undefined, { updateParent: true });
setTriggerCanvasUpdater((prev) => !prev);
toggleCanvasUpdater();
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[boxList, currentLayout, gridWidth]
@ -488,7 +489,7 @@ export default function Grid({ gridWidth, currentLayout }) {
console.error('ResizeEnd error ->', error);
}
useGridStore.getState().actions.setDragTarget();
setTriggerCanvasUpdater((prev) => !prev);
toggleCanvasUpdater();
}}
onResizeStart={(e) => {
if (!isComponentVisible(e.target.id)) {
@ -575,7 +576,7 @@ export default function Grid({ gridWidth, currentLayout }) {
} catch (error) {
console.error('Error resizing group', error);
}
setTriggerCanvasUpdater((prev) => !prev);
toggleCanvasUpdater();
}}
checkInput
onDragStart={(e) => {
@ -722,7 +723,7 @@ export default function Grid({ gridWidth, currentLayout }) {
element.classList.add('hide-grid');
});
document.getElementById('real-canvas')?.classList.remove('show-grid');
setTriggerCanvasUpdater((prev) => !prev);
toggleCanvasUpdater();
}}
onDrag={(e) => {
// Since onDrag is called multiple times when dragging, hence we are using isDraggingRef to prevent setting state again and again
@ -857,7 +858,7 @@ export default function Grid({ gridWidth, currentLayout }) {
} catch (error) {
console.error('Error dragging group', error);
}
setTriggerCanvasUpdater((prev) => !prev);
toggleCanvasUpdater();
}}
// throttleDrag={1}
// edgeDraggable={false}

View file

@ -80,7 +80,7 @@ const RenderWidget = ({
...{ validationObject: unResolvedValidation },
customResolveObjects: customResolvables,
}),
[validateWidget, customResolvables, unResolvedValidation]
[validateWidget, customResolvables, unResolvedValidation, resolvedValidation]
);
const resetComponent = useCallback(() => {

View file

@ -350,7 +350,14 @@ export function pasteComponents(parentId, copiedComponentObj) {
const currentPageId = useStore.getState().getCurrentPageId();
const { isCut = false, newComponents: pastedComponents = [], pageId, isCloning = false } = copiedComponentObj;
// Prevent pasting if the parent subcontainer was deleted during a cut operation
if (parentId && !Object.keys(components).find((key) => parentId === key)) {
if (
parentId &&
!Object.keys(components).find(
(key) =>
parentId === key ||
(components?.[key]?.component.component === 'Tabs' && parentId?.split('-')?.slice(0, -1)?.join('-') === key)
)
) {
return;
}
if (parentId) {
@ -444,10 +451,9 @@ export const getCanvasWidth = (currentLayout) => {
}
};
export const computeCanvasBackgroundColor = (isAppDarkMode, canvasBgColor) => {
const canvasBackgroundColor = canvasBgColor ? canvasBgColor : '#edeff5';
if (['#2f3c4c', '#edeff5'].includes(canvasBackgroundColor)) {
return isAppDarkMode ? '#2f3c4c' : '#edeff5';
export const computeViewerBackgroundColor = (isAppDarkMode, canvasBgColor) => {
if (['#2f3c4c', '#F2F2F5', '#edeff5'].includes(canvasBgColor)) {
return isAppDarkMode ? '#2f3c4c' : '#F2F2F5';
}
return canvasBackgroundColor;
return canvasBgColor;
};

View file

@ -6,11 +6,13 @@ import { LEFT_SIDEBAR_WIDTH } from './appCanvasConstants';
const useSidebarMargin = (canvasContainerRef) => {
const [editorMarginLeft, setEditorMarginLeft] = useState(0);
const selectedSidebarItem = useStore((state) => state.selectedSidebarItem, shallow);
const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow);
const mode = useStore((state) => state.currentMode, shallow);
useEffect(() => {
setEditorMarginLeft(selectedSidebarItem ? LEFT_SIDEBAR_WIDTH : 0);
}, [selectedSidebarItem]);
if (mode !== 'view') setEditorMarginLeft(isSidebarOpen ? LEFT_SIDEBAR_WIDTH : 0);
else setEditorMarginLeft(0);
}, [isSidebarOpen, mode]);
useEffect(() => {
if (!isEmpty(canvasContainerRef?.current)) {

View file

@ -30,6 +30,12 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
const [currentValue, setCurrentValue] = useState('');
const [errorStateActive, setErrorStateActive] = useState(false);
const [cursorInsidePreview, setCursorInsidePreview] = useState(false);
const componentDefinition = useStore((state) => state.getComponentDefinition(componentId), shallow);
const parentId = componentDefinition?.component?.parent;
const customResolvables = useStore((state) => state.resolvedStore.modules.canvas?.customResolvables, shallow);
const customVariables = customResolvables?.[parentId]?.[0] || {};
const isPreviewFocused = useRef(false);
const wrapperRef = useRef(null);
@ -52,8 +58,10 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
// ? resolveReferences(newInitialValue, validation, customVariables)
// : [true, null];
// Need to add customVariables while resolving the value like above
const [valid, _error] = !isEmpty(validation) ? resolveReferences(newInitialValue, validation) : [true, null];
//!TODO use the updated new resolver
const [valid, _error] = !isEmpty(validation)
? resolveReferences(newInitialValue, validation, customVariables)
: [true, null];
if (!valid) {
setErrorStateActive(true);
@ -90,6 +98,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
style={{ width: '100%', height: restProps?.lang === 'jsx' && '320px' }}
>
<PreviewBox.Container
customVariables={customVariables}
enablePreview={enablePreview}
currentValue={currentValue}
isFocused={isFocused}
@ -103,7 +112,6 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
errorStateActive={errorStateActive}
previewPlacement={restProps?.cyLabel === 'canvas-bg-colour' ? 'top' : 'left-start'}
isPortalOpen={restProps?.portalProps?.isOpen}
// customVariables={customVariables}
>
<div className="code-editor-basic-wrapper d-flex">
<div className="codehinter-container w-100">

View file

@ -125,10 +125,7 @@ export const AppVersionsManager = function ({ darkMode }) {
deleteVersionAction(
appId,
versionId,
(newVersionDef) => {
if (newVersionDef) {
setCurrentVersionId(newVersionDef.id);
}
() => {
toast.dismiss(deleteingToastId);
toast.success(`Version - ${decodeEntities(versionName)} Deleted`);
resetDeleteModal();

View file

@ -18,13 +18,6 @@ export const EditorHeader = ({ darkMode }) => {
isSaving: state.app.isSaving,
saveError: state.app.saveError,
isVersionReleased: state.isVersionReleased,
user: state.user,
app: state.app,
appId: state.app.appId,
editingVersion: state.editingVersion,
updateReleasedVersionId: state.updateReleasedVersionId,
updateEditingVersion: state.updateEditingVersion,
featureAccess: state.featureAccess,
}),
shallow
);

View file

@ -62,12 +62,13 @@ export const LeftSidebar = ({ darkMode = false, switchDarkMode }) => {
};
useEffect(() => {
setPopoverContentHeight(((window.innerHeight - queryPanelHeight - 45) / window.innerHeight) * 100);
// eslint-disable-next-line react-hooks/exhaustive-deps
setPopoverContentHeight(
((window.innerHeight - (queryPanelHeight == 0 ? 40 : queryPanelHeight) - 45) / window.innerHeight) * 100
); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [queryPanelHeight]);
const renderPopoverContent = () => {
if (selectedSidebarItem === null) return null;
if (selectedSidebarItem === null || !isSidebarOpen) return null;
switch (selectedSidebarItem) {
case 'page':
return (

View file

@ -85,7 +85,7 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
return jsontreeData;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables]);
}, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]);
return (
<div

View file

@ -70,7 +70,7 @@ export default function IconSelector({ iconName, iconColor, pageId }) {
};
// eslint-disable-next-line import/namespace
const IconElement = Icons?.[iconName] ?? Icons?.['IconHome2'];
const IconElement = Icons?.[iconName] ?? Icons?.['IconFileDescription'];
return (
<OverlayTrigger

View file

@ -1,7 +1,6 @@
import React, { useContext, useEffect, useRef } from 'react';
import React from 'react';
import { Overlay, Popover } from 'react-bootstrap';
import { Button } from '@/_ui/LeftSidebar';
import { shallow } from 'zustand/shallow';
import useStore from '@/AppBuilder/_stores/store';
export const PageHandlerMenu = ({ darkMode }) => {
@ -57,7 +56,7 @@ export const PageHandlerMenu = ({ darkMode }) => {
return (
<Overlay
placement="auto"
placement="right"
target={targetContainer}
show={showMenu}
rootClose
@ -228,8 +227,8 @@ const Field = ({ id, text, iconSrc, customClass = '', closeMenu, disabled = fals
const handleOnClick = (e) => {
e.preventDefault();
e.stopPropagation();
closeMenu();
callback(id);
closeMenu();
};
return (

View file

@ -47,8 +47,9 @@ export const PageMenuItem = withRouter(
const isEditingPage = editingPage?.id === page?.id;
const icon = () => {
const iconName = isHomePage && !page.icon ? 'IconHome2' : page.icon;
if (!isDisabled && !isHidden) {
return <IconSelector iconColor={computedStyles?.icon?.color} iconName={page.icon} pageId={page.id} />;
return <IconSelector iconColor={computedStyles?.icon?.color} iconName={iconName} pageId={page.id} />;
}
if (isDisabled || (isDisabled && isHidden)) {
return (
@ -136,8 +137,21 @@ export const PageMenuItem = withRouter(
setCurrentPageHandle(page.handle);
}, [currentPageId, page.id, page.handle, switchPage, setCurrentPageHandle]);
const handlePageMenuSettings = useCallback(
(event) => {
event.stopPropagation();
openPageEditPopover(page, popoverRef);
},
[popoverRef.current, page]
);
return (
<div onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
<div
ref={popoverRef}
id={`edit-popover-${page.id}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{editingPageName && editingPage?.id === page?.id ? (
<>
<RenameInput
@ -160,9 +174,9 @@ export const PageMenuItem = withRouter(
>
<div className="left">
{icon()}
<div style={{ ...computedStyles?.text }} className="page-name">
<OverflowTooltip>{page.name}</OverflowTooltip>
</div>
<OverflowTooltip childrenClassName="page-name" style={{ ...computedStyles?.text }}>
{page.name}
</OverflowTooltip>
<span
style={{
marginLeft: '8px',
@ -188,10 +202,7 @@ export const PageMenuItem = withRouter(
}),
}}
className="edit-page-overlay-toggle"
onClick={(event) => {
event.stopPropagation();
openPageEditPopover(page, popoverRef);
}}
onClick={handlePageMenuSettings}
>
<SolidIcon width="20" dataCy={`page-menu`} name="morevertical" />
</button>
@ -200,15 +211,6 @@ export const PageMenuItem = withRouter(
</div>
</>
)}
<div
id={`edit-popover-${page.id}`}
style={{
position: 'absolute',
right: 0,
top: 0,
}}
ref={popoverRef}
></div>
</div>
);
})

View file

@ -311,6 +311,7 @@ export const QueryManagerBody = ({ darkMode, options, setOptions, activeTab }) =
);
};
// if (selectedQueryId !== selectedQuery?.id) return;
const hasPermissions =
selectedDataSource?.scope === 'global' && selectedDataSource?.type !== DATA_SOURCE_TYPE.SAMPLE
? canUpdateDataSource(selectedQuery?.data_source_id) ||

View file

@ -21,20 +21,22 @@ export const QueryDataPane = ({ darkMode }) => {
const { t } = useTranslation();
const loadingDataQueries = useStore((state) => state.queryPanel.loadingDataQueries);
const setQueryPanelSearchTerm = useStore((state) => state.queryPanel.setQueryPanelSearchTerm);
const storedSearchTerm = useStore((state) => state.queryPanel.queryPanelSearchTem);
const dataQueries = useStore((state) => state.dataQuery.queries.modules.canvas);
const dataSources = useStore((state) => state.dataSources);
const [filteredQueries, setFilteredQueries] = useState(dataQueries);
const [showSearchBox, setShowSearchBox] = useState(false);
const [showSearchBox, setShowSearchBox] = useState(!!storedSearchTerm);
const searchBoxRef = useRef(null);
const [dataSourcesForFilters, setDataSourcesForFilters] = useState([]);
const [searchTermForFilters, setSearchTermForFilters] = useState();
const [searchTermForFilters, setSearchTermForFilters] = useState(storedSearchTerm ?? '');
function isDataSourceLocal(dataQuery) {
return dataSources.some((dataSource) => dataSource.id === dataQuery.data_source_id);
}
useEffect(() => {
setQueryPanelSearchTerm(searchTermForFilters);
// Create a copy of the dataQueries array to perform filtering without modifying the original data.
let filteredDataQueries = [...dataQueries];
@ -84,7 +86,7 @@ export const QueryDataPane = ({ darkMode }) => {
};
useEffect(() => {
showSearchBox && searchBoxRef.current.focus();
showSearchBox && !storedSearchTerm && searchBoxRef.current.focus();
}, [showSearchBox]);
return (
@ -135,6 +137,7 @@ export const QueryDataPane = ({ darkMode }) => {
placeholder={t('globals.search', 'Search')}
customClass="query-manager-search-box-wrapper flex-grow-1"
showClearButton
clearTextOnBlur={false}
/>
<ButtonSolid
size="sm"

View file

@ -21,7 +21,7 @@ const DesktopHeader = ({
}) => {
const { showDarkModeToggle, isReleasedVersionId } = useStore(
(state) => ({
isReleasedVersionId: state?.releasedVersionId == state.selectedVersion?.id || state.isVersionReleased,
isReleasedVersionId: state?.releasedVersionId == state.currentVersionId || state.isVersionReleased,
showDarkModeToggle: state.globalSettings.appMode === 'auto' || !state.globalSettings.appMode,
}),
shallow

View file

@ -22,7 +22,12 @@ const MobileHeader = ({
setAppDefinitionFromVersion,
showViewerNavigation,
}) => {
const isVersionReleased = useStore((state) => state.isVersionReleased);
const { isReleasedVersionId } = useStore(
(state) => ({
isReleasedVersionId: state?.releasedVersionId == state.currentVersionId || state.isVersionReleased,
}),
shallow
);
const editingVersion = useStore((state) => state.editingVersion);
const showDarkModeToggle = useStore((state) => state.globalSettings.appMode === 'auto');
@ -33,7 +38,7 @@ const MobileHeader = ({
const _renderAppNameAndLogo = () => (
<div
className={classNames('d-flex', 'align-items-center')}
style={{ visibility: showHeader || isVersionReleased ? 'visible' : 'hidden' }}
style={{ visibility: showHeader || isReleasedVersionId ? 'visible' : 'hidden' }}
>
<h1 className="navbar-brand d-none-navbar-horizontal pe-0">
<Link
@ -66,14 +71,15 @@ const MobileHeader = ({
/>
);
const _renderPreviewSettings = () => (
<PreviewSettings
isMobileLayout
showHeader={showHeader}
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
darkMode={darkMode}
/>
);
const _renderPreviewSettings = () =>
!isReleasedVersionId && (
<PreviewSettings
isMobileLayout
showHeader={showHeader}
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
darkMode={darkMode}
/>
);
const _renderDarkModeBtn = (args) => {
if (!showDarkModeToggle) return null;
@ -88,11 +94,11 @@ const MobileHeader = ({
);
};
if (!showHeader) {
if (!showHeader && isReleasedVersionId) {
return <>{showViewerNavigation ? _renderMobileNavigationMenu() : _renderDarkModeBtn()}</>;
}
if (!showHeader && !isVersionReleased) {
if (!showHeader && !isReleasedVersionId) {
return (
<>
<Header
@ -122,7 +128,7 @@ const MobileHeader = ({
<span style={{}}>{_renderAppNameAndLogo()}</span>
{_renderMobileNavigationMenu()}
</div>
{!isVersionReleased && !isEmpty(editingVersion) && _renderPreviewSettings()}
{!isReleasedVersionId && !isEmpty(editingVersion) && _renderPreviewSettings()}
{!showViewerNavigation && _renderDarkModeBtn({ styles: { top: '2px' } })}
</Header>
);

View file

@ -13,8 +13,6 @@ import DesktopHeader from './DesktopHeader';
import MobileHeader from './MobileHeader';
import ViewerSidebarNavigation from './ViewerSidebarNavigation';
import { shallow } from 'zustand/shallow';
import { computeCanvasBackgroundColor } from '@/AppBuilder/AppCanvas/appCanvasUtils';
import { resolveReferences } from '@/_helpers/utils';
import Popups from '../Popups';
import TooljetBanner from './TooljetBanner';
import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext';
@ -41,6 +39,7 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
homePageId,
isMaintenanceOn,
setIsViewer,
toggleCurrentLayout,
} = useStore(
(state) => ({
isEditorLoading: state.isEditorLoading,
@ -60,14 +59,15 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
updateCanvasHeight: state.updateCanvasBottomHeight,
isMaintenanceOn: state.app.isMaintenanceOn,
setIsViewer: state.setIsViewer,
toggleCurrentLayout: state.toggleCurrentLayout,
}),
shallow
);
const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents, shallow);
const currentPageComponents = useMemo(() => getCurrentPageComponents(), [getCurrentPageComponents]);
const changeDarkMode = useStore((state) => state.changeDarkMode);
const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents(), shallow);
const currentPageComponents = useMemo(() => getCurrentPageComponents, [getCurrentPageComponents]);
const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow);
const canvasBgColor = useStore((state) => state.getCanvasBackgroundColor('canvas', darkMode), shallow);
const deviceWindowWidth = window.screen.width - 5;
const computeCanvasMaxWidth = useCallback(() => {
if (globalSettings?.maxCanvasWidth) {
@ -92,7 +92,6 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
const isLoading = false;
const isMobilePreviewMode = selectedVersion?.id && currentLayout === 'mobile';
const isAppLoaded = !!editingVersion;
const deviceWindowWidth = window.screen.width - 5;
const isMobileDevice = deviceWindowWidth < 600;
const switchPage = useStore((state) => state.switchPage);
@ -106,6 +105,8 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
switchDarkMode(newMode);
};
useEffect(() => {
const isMobileDevice = deviceWindowWidth < 600;
toggleCurrentLayout(isMobileDevice ? 'mobile' : 'desktop');
setIsViewer(true);
return () => {
setIsViewer(false);
@ -170,7 +171,7 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
}}
>
<div className={`areas d-flex flex-rows app-${appId}`}>
{!isPagesSidebarHidden && (
{currentLayout !== 'mobile' && !isPagesSidebarHidden && (
<ViewerSidebarNavigation
showHeader={showHeader}
isMobileDevice={currentLayout === 'mobile'}
@ -200,6 +201,7 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
maxWidth: isMobilePreviewMode ? '450px' : computeCanvasMaxWidth(),
margin: 0,
padding: 0,
position: 'relative',
}}
>
{currentLayout === 'mobile' && isMobilePreviewMode && (

View file

@ -7,6 +7,7 @@ import FolderList from '@/_ui/FolderList/FolderList';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import useStore from '@/AppBuilder/_stores/store';
import { APP_HEADER_HEIGHT } from '../AppCanvas/appCanvasConstants';
import OverflowTooltip from '@/_components/OverflowTooltip';
export const ViewerSidebarNavigation = ({
isMobileDevice,
@ -21,6 +22,7 @@ export const ViewerSidebarNavigation = ({
const { definition: { styles = {}, properties = {} } = {} } = useStore((state) => state.pageSettings) || {};
const selectedVersionName = useStore((state) => state.selectedVersion?.name);
const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name);
const homePageId = useStore((state) => state.app.homePageId);
if (isMobileDevice) {
return null;
@ -127,8 +129,10 @@ export const ViewerSidebarNavigation = ({
></ButtonSolid>
<div className={cx('page-handler-wrapper', { 'dark-theme': darkMode })}>
{pages.map((page) => {
const isHomePage = page.id === homePageId;
const iconName = isHomePage && !page.icon ? 'IconHome2' : page.icon;
// eslint-disable-next-line import/namespace
const IconElement = Icons?.[page.icon] ?? Icons?.['IconHome2'];
const IconElement = Icons?.[iconName] ?? Icons?.['IconFileDescription'];
return page.hidden || page.disabled ? null : (
<FolderList
key={page.handle}
@ -139,8 +143,10 @@ export const ViewerSidebarNavigation = ({
darkMode={darkMode}
>
{!labelStyle?.label?.hidden && (
<span data-cy={`pages-name-${String(page?.name).toLowerCase()}`} className="mx-2 text-wrap page-name">
{_.truncate(page?.name, { length: 18 })}
<span data-cy={`pages-name-${String(page?.name).toLowerCase()}`}>
<OverflowTooltip style={{ width: '110px' }} childrenClassName={'mx-2 page-name'}>
{page.name}
</OverflowTooltip>
</span>
)}
</FolderList>

View file

@ -146,8 +146,8 @@ export const buttonGroupConfig = {
visibility: { value: '{{true}}' },
borderRadius: { value: '{{4}}' },
disabledState: { value: '{{false}}' },
selectedTextColor: { value: '' },
selectedBackgroundColor: { value: '' },
selectedTextColor: { value: '#FFFFFF' },
selectedBackgroundColor: { value: '#4368E3' },
},
},
};

View file

@ -307,12 +307,16 @@ export const Form = function Form(props) {
}
key={index}
>
<RenderSchema
component={item}
id={index}
onOptionChange={onComponentOptionChangedForSubcontainer}
onOptionsChange={onComponentOptionsChangedForSubcontainer}
/>
<div style={{ position: 'relative' }} className={`form-ele form-${id}-${index}`}>
<RenderSchema
component={item}
parent={id}
id={index}
darkMode={darkMode}
onOptionChange={onComponentOptionChangedForSubcontainer}
onOptionsChange={onComponentOptionsChangedForSubcontainer}
/>
</div>
{/* <Box
{...props}
component={item}

View file

@ -77,6 +77,7 @@ export function generateUIComponents(JSONSchema, advanced, componentName = '') {
const itemType = typeResolver(value?.type);
if (itemType) {
uiComponentsDraft.push(getComponentDefinition('Text'));
uiComponentsDraft.push(getComponentDefinition(itemType));
//only add if there is a valid item type
} else {
// useCurrentStateStore.getState().actions.setErrors({
@ -89,8 +90,8 @@ export function generateUIComponents(JSONSchema, advanced, componentName = '') {
// },
// });
uiComponentsDraft.push(undefined);
uiComponentsDraft.push(undefined);
}
uiComponentsDraft.push(getComponentDefinition(itemType));
});
Object.entries(JSONSchema?.properties).forEach(([key, value], index) => {
if (uiComponentsDraft?.length > 0 && uiComponentsDraft[index * 2 + 1]) {
@ -148,7 +149,7 @@ export function generateUIComponents(JSONSchema, advanced, componentName = '') {
uiComponentsDraft[index * 2 + 1]['definition']['properties']['display_values'] = value?.displayValues;
if (value?.label) uiComponentsDraft[index * 2 + 1]['definition']['properties']['label'] = value?.label;
if (value?.value) uiComponentsDraft[index * 2 + 1]['definition']['properties']['value'] = value?.value;
if (value?.values) uiComponentsDraft[index * 2 + 1]['definition']['properties']['value'] = value?.values;
if (value?.values) uiComponentsDraft[index * 2 + 1]['definition']['properties']['values'] = value?.values;
if (value?.loading)
uiComponentsDraft[index * 2 + 1]['definition']['properties']['loadingState'] = value?.loading;
break;
@ -362,7 +363,7 @@ export function generateUIComponents(JSONSchema, advanced, componentName = '') {
uiComponentsDraft[index * 2 + 1]['definition']['properties']['display_values'] = value?.displayValues;
if (value?.label) uiComponentsDraft[index * 2 + 1]['definition']['properties']['label'] = value?.label;
if (value?.value) uiComponentsDraft[index * 2 + 1]['definition']['properties']['value'] = value?.value;
if (value?.values) uiComponentsDraft[index * 2 + 1]['definition']['properties']['value'] = value?.values;
if (value?.values) uiComponentsDraft[index * 2 + 1]['definition']['properties']['values'] = value?.values;
if (value?.showAllOption)
uiComponentsDraft[index * 2 + 1]['definition']['properties']['showAllOption'] = value?.showAllOption;
break;

View file

@ -3,7 +3,7 @@ import { getComponentToRender } from '@/AppBuilder/_helpers/editorHelpers';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
const RenderSchema = ({ component, id, onOptionChange, onOptionsChange }) => {
const RenderSchema = ({ component, parent, id, onOptionChange, onOptionsChange, darkMode }) => {
const ComponentToRender = useMemo(() => getComponentToRender(component?.component), [component?.component]);
const validateWidget = useStore((state) => state.validateWidget, shallow);
@ -21,13 +21,21 @@ const RenderSchema = ({ component, id, onOptionChange, onOptionsChange }) => {
[id, onOptionsChange]
);
const validate = (value) => {
return validateWidget({
...{ widgetValue: value },
...{ validationObject: component.definition.validation },
});
};
const validate = useCallback(
(value) => {
return validateWidget({
...{ widgetValue: value },
...{ validationObject: component.definition.validation },
});
},
[component.definition.validation]
);
const fireEvent = useCallback(() => {
return Promise.resolve();
}, []);
const formId = `${parent}-${id}`;
return (
<ComponentToRender
properties={component?.definition?.properties}
@ -39,7 +47,9 @@ const RenderSchema = ({ component, id, onOptionChange, onOptionsChange }) => {
setExposedVariable={setExposedVariable}
setExposedVariables={setExposedVariables}
validate={validate}
fireEvent={() => {}}
darkMode={darkMode}
fireEvent={fireEvent}
formId={formId}
/>
);
};

View file

@ -72,34 +72,22 @@ export const Modal = function Modal({
setShowModal(true);
},
close: async function () {
setShowModal(false);
setExposedVariable('show', false);
setShowModal(false);
},
};
setExposedVariables(exposedVariables);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (isInitialRender.current) {
isInitialRender.current = false;
return;
}
fireEvent(!showModal ? 'onClose' : 'onOpen');
const inputRef = document?.getElementsByClassName('tj-text-input-widget')?.[0];
inputRef?.blur();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showModal]);
function hideModal() {
setShowModal(false);
setExposedVariable('show', false);
setShowModal(false);
}
function openModal() {
setShowModal(true);
setExposedVariable('show', true);
setShowModal(true);
}
useEffect(() => {
@ -149,6 +137,19 @@ export const Modal = function Modal({
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showModal, modalHeight]);
useEffect(() => {
if (isInitialRender.current) {
isInitialRender.current = false;
return;
}
fireEvent(!showModal ? 'onClose' : 'onOpen');
const inputRef = document?.getElementsByClassName('tj-text-input-widget')?.[0];
inputRef?.blur();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showModal]);
const backwardCompatibilityCheck = height == '34' || modalHeight != undefined ? true : false;
const customStyles = {

View file

@ -85,16 +85,20 @@ export const TableRow = React.memo(
: ''
}`}
{...rowProps}
onClickCapture={async () => {
onClickCapture={() => {
// toggleRowSelected will triggered useRededcuer function in useTable and in result will get the selectedFlatRows consisting row which are selected
const selectedRow = row.original;
const selectedRowId = row.id;
setExposedVariables({ selectedRow, selectedRowId });
fireEvent('onRowClicked');
}}
onClick={async () => {
if (allowSelection) {
await toggleRowSelected(row.id);
}
const selectedRow = row.original;
const selectedRowId = row.id;
setExposedVariables({ selectedRow, selectedRowId });
mergeToTableDetails({ selectedRow, selectedRowId });
fireEvent('onRowClicked');
}}
onMouseOver={() => {
if (hoverAdded) {

View file

@ -67,7 +67,7 @@ export const Pagination = function Pagination({
{!serverSide && tableWidth > 460 && (
<ButtonSolid
variant="ghostBlack"
className="tj-text-xsm"
className="tj-text-xsm table-pagination-btn"
style={{
minWidth: '28px',
width: '28px',
@ -92,7 +92,7 @@ export const Pagination = function Pagination({
)}
<ButtonSolid
variant="ghostBlack"
className="tj-text-xsm"
className="tj-text-xsm table-pagination-btn"
style={{
minWidth: '28px',
width: '28px',
@ -144,7 +144,7 @@ export const Pagination = function Pagination({
<div className="d-flex">
<ButtonSolid
variant="ghostBlack"
className="tj-text-xsm"
className="tj-text-xsm table-pagination-btn"
style={{
minWidth: '28px',
width: '28px',
@ -169,7 +169,7 @@ export const Pagination = function Pagination({
{!serverSide && tableWidth > 460 && (
<ButtonSolid
variant="ghostBlack"
className="tj-text-xsm"
className="tj-text-xsm table-pagination-btn"
style={{
minWidth: '28px',
width: '28px',

View file

@ -236,8 +236,9 @@ export const Table = React.memo(
});
const changesToBeSavedAndExposed = { dataUpdates: newDataUpdates, changeSet: newChangeset };
mergeToTableDetails(changesToBeSavedAndExposed);
setExposedVariables({ ...changesToBeSavedAndExposed, updatedData: clonedTableData });
fireEvent('onCellValueChanged');
return setExposedVariables({ ...changesToBeSavedAndExposed, updatedData: clonedTableData });
return;
}
const copyOfTableDetails = useRef(tableDetails);
@ -565,12 +566,12 @@ export const Table = React.memo(
useEffect(() => {
if (
tableData.length != 0 &&
properties.data.length != 0 &&
properties.autogenerateColumns &&
(useDynamicColumn || mode === 'edit' || mode === 'view')
) {
const generatedColumnFromData = autogenerateColumns(
tableData,
properties.data,
properties.columns,
properties?.columnDeletionHistory ?? [],
useDynamicColumn,
@ -579,9 +580,25 @@ export const Table = React.memo(
properties.autogenerateColumns ?? false,
id
);
useDynamicColumn && setGeneratedColumn(generatedColumnFromData);
if (useDynamicColumn) {
const dynamicColumnHasId = dynamicColumn && dynamicColumn.every((column) => 'id' in column);
if (!dynamicColumnHasId) {
// if dynamic columns do not have an id then we need to manually compare the generated columns with the columns in the state because the id that we generate for columns without id is a uuid and it will be different every time
const generatedColumnsWithoutIds = generatedColumnFromData.map(({ id, ...rest }) => ({
...rest,
}));
const columnsFromStateWithoutIds = generatedColumn.map(({ id, ...rest }) => ({
...rest,
}));
!isEqual(generatedColumnsWithoutIds, columnsFromStateWithoutIds) &&
setGeneratedColumn(generatedColumnFromData);
return;
}
setGeneratedColumn(generatedColumnFromData);
}
}
}, [tableData, JSON.stringify(dynamicColumn)]);
// }, [tableData, JSON.stringify(dynamicColumn)]);
}, [JSON.stringify(properties.data), JSON.stringify(dynamicColumn)]);
const computedStyles = {
// width: `${width}px`,
@ -865,7 +882,6 @@ export const Table = React.memo(
currentData: data,
selectedRow: [],
selectedRowId: null,
pageIndex: pageIndex + 1,
});
if (tableDetails.selectedRowId || !isEmpty(tableDetails.selectedRowDetails)) {
toggleAllRowsSelected(false);

View file

@ -107,7 +107,14 @@ export default function autogenerateColumns(
finalKeys.includes(column?.key || column?.name)
);
setTimeout(() => setProperty(id, 'columns', finalColumns, 'properties'), 10);
setTimeout(
() =>
setProperty(id, 'columns', finalColumns, 'properties', 'value', false, 'canvas', {
skipUndoRedo: true,
saveAfterAction: true,
}),
10
);
}
const dataTypeToColumnTypeMapping = {

View file

@ -69,14 +69,14 @@ export const Tabs = function Tabs({
}, [parsedDefaultTab]);
useEffect(() => {
const currentTabData = parsedTabs.filter((tab) => tab.id === currentTab);
const currentTabData = parsedTabs.filter((tab) => tab.id == currentTab);
setBgColor(currentTabData[0]?.backgroundColor ? currentTabData[0]?.backgroundColor : darkMode ? '#324156' : '#fff');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentTab, darkMode]);
function computeTabDisplay(componentId, id) {
let tabVisibility = 'none';
if (id !== currentTab) {
if (id != currentTab) {
return tabVisibility;
}
@ -87,14 +87,13 @@ export const Tabs = function Tabs({
}
}
return id === currentTab ? 'block' : 'none';
return id == currentTab ? 'block' : 'none';
}
useEffect(() => {
const exposedVariables = {
setTab: async function (id) {
id = typeof id === 'number' ? String(id) : id;
if (id && currentTab !== id) {
if (currentTab != id) {
setCurrentTab(id);
setExposedVariable('currentTab', id);
fireEvent('onTabSwitch');
@ -134,7 +133,7 @@ export const Tabs = function Tabs({
function shouldRenderTabContent(tab) {
if (parsedRenderOnlyActiveTab) {
return tab.id === currentTab;
return tab.id == currentTab;
}
return true; // Render by default if no specific conditions are met
}
@ -162,7 +161,7 @@ export const Tabs = function Tabs({
className="nav-item"
style={{ opacity: tab?.disabled && '0.5', width: tabWidth == 'split' && equalSplitWidth + '%' }}
onClick={() => {
if (currentTab === tab.id) return;
if (currentTab == tab.id) return;
!tab?.disabled && setCurrentTab(tab.id);
!tab?.disabled && setExposedVariable('currentTab', tab.id);
@ -193,7 +192,7 @@ export const Tabs = function Tabs({
<div
className="tab-content"
ref={(newCurrent) => {
if (currentTab === tab.id) {
if (currentTab == tab.id) {
parentRef.current = newCurrent;
}
}}
@ -202,7 +201,7 @@ export const Tabs = function Tabs({
>
{shouldRenderTabContent(tab) && renderTabContent(id, tab)}
{/* {tab.id === currentTab && <SubContainer id={`${id}-${tab.id}`} canvasHeight={'200'} canvasWidth={width} />} */}
{/* {tab.id == currentTab && <SubContainer id={`${id}-${tab.id}`} canvasHeight={'200'} canvasWidth={width} />} */}
</div>
))}
</div>

View file

@ -16,7 +16,7 @@ import { usePrevious } from '@dnd-kit/utilities';
import { deepCamelCase } from '@/_helpers/appUtils';
import { useEventActions } from '../_stores/slices/eventsSlice';
import useRouter from '@/_hooks/use-router';
import { navigate } from '../_utils/misc';
import { extractEnvironmentConstantsFromConstantsList, navigate } from '../_utils/misc';
import { getWorkspaceId } from '@/_helpers/utils';
import { shallow } from 'zustand/shallow';
import { fetchAndSetWindowTitle, pageTitles, defaultWhiteLabellingSettings } from '@white-label/whiteLabelling';
@ -93,7 +93,9 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId }
if (pageSwitchInProgress) {
const currentPageEvents = events.filter((event) => event.target === 'page' && event.sourceId === currentPageId);
setPageSwitchInProgress(false);
handleEvent('onPageLoad', currentPageEvents, {});
setTimeout(() => {
handleEvent('onPageLoad', currentPageEvents, {});
}, 0);
}
}, [pageSwitchInProgress, currentPageId]);
@ -171,10 +173,25 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId }
};
}
const constantsResp =
isPublicAccess && appData.is_public
? await orgEnvironmentConstantService.getConstantsFromPublicApp(slug)
: await orgEnvironmentConstantService.getConstantsFromApp();
let constantsResp;
if (mode === 'edit') {
let defaultEnvId = null;
if (editorEnvironment?.id == null) {
const envs = await appEnvironmentService.getAllEnvironments(appId);
const defaultEnv = envs.environments.find((env) => env?.is_default === true);
defaultEnvId = defaultEnv ? defaultEnv.id : null;
}
constantsResp = await orgEnvironmentConstantService.getConstantsFromEnvironment(
editorEnvironment?.id || defaultEnvId
);
} else {
constantsResp =
isPublicAccess && appData.is_public
? await orgEnvironmentConstantService.getConstantsFromPublicApp(slug)
: await orgEnvironmentConstantService.getConstantsFromApp(slug);
}
constantsResp.constants = extractEnvironmentConstantsFromConstantsList(constantsResp?.constants, 'production');
const pages = appData.pages.map((page) => {
return page;
@ -189,7 +206,12 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId }
appId: appData.id,
slug: appData.slug,
currentAppEnvironmentId: editorEnvironmentId,
isMaintenanceOn: result.is_maintenance_on,
isMaintenanceOn:
'is_maintenance_on' in result
? result.is_maintenance_on
: 'isMaintenanceOn' in result
? result.isMaintenanceOn
: false,
organizationId: appData.organizationId || appData.organization_id,
homePageId: homePageId,
isPublic: appData.is_public,
@ -201,8 +223,7 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId }
);
setPages(pages, moduleId);
setPageSettings(deepCamelCase(appData?.editing_version?.page_settings));
setPageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings));
// set starting page as homepage initially
let startingPage = appData.pages.find((page) => page.id === homePageId);
@ -347,7 +368,12 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId }
appId: appData.id,
slug: appData.slug,
creationMode: appData.creationMode,
isMaintenanceOn: appData.is_maintenance_on,
isMaintenanceOn:
'is_maintenance_on' in appData
? appData.is_maintenance_on
: 'isMaintenanceOn' in appData
? appData.isMaintenanceOn
: false,
organizationId: appData.organizationId || appData.organization_id,
homePageId: appData.editing_version.homePageId,
isPublic: appData.isPublic,

View file

@ -1,9 +1,38 @@
const acorn = require('acorn');
const walk = require('acorn-walk');
function findExpression(input) {
const matches = [];
let startIdx = -1;
let braceCount = 0;
for (let i = 0; i < input.length; i++) {
if (input[i] === '{' && input[i + 1] === '{' && braceCount === 0) {
startIdx = i;
braceCount = 2;
i++; // Skip the second '{'
} else if (input[i] === '{' && braceCount > 0) {
braceCount++;
} else if (input[i] === '}' && braceCount > 0) {
braceCount--;
if (braceCount === 0 && startIdx !== -1) {
matches.push({
fullMatch: input.slice(startIdx, i + 1),
expression: input.slice(startIdx + 2, i - 1).trim(),
index: startIdx,
});
startIdx = -1;
}
}
}
return matches;
}
export function extractAndReplaceReferencesFromString(input, componentIdNameMapping = {}, queryIdNameMapping = {}) {
// Quick check for relevant keywords
const regexForQuickCheck = /\b(components|queries|globals|variables|page|parameters|secrets)(?:\[\S*|\.\S*|\?\.\S*)/;
const regexForQuickCheck =
/\b(components|queries|globals|variables|page|parameters|secrets|constants)(?:\[\S*|\.\S*|\?\.\S*)/;
if (!regexForQuickCheck.test(input)) {
return {
allRefs: [],
@ -12,7 +41,7 @@ export function extractAndReplaceReferencesFromString(input, componentIdNameMapp
};
}
const relevantKeywords = /\b(components|queries|globals|variables|page|parameters|secrets)\b/;
const relevantKeywords = /\b(components|queries|globals|variables|page|parameters|secrets|constants)\b/;
const expressionRegex = /{{(.*?)}}/gs;
const results = [];
let lastIndex = 0;
@ -24,12 +53,93 @@ export function extractAndReplaceReferencesFromString(input, componentIdNameMapp
/\b(components|queries)(\??\.|\??\.?\[['"]?)([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(['"]?\])?/g;
let match;
if (input.startsWith('{{{') && input.endsWith('}}}')) {
input = input.replace(/\{\{(.*)\}\}/, '{{($1)}}');
const matches = findExpression(input);
for (const match of matches) {
const { fullMatch, expression, index } = match;
// Check if the expression contains relevant keywords
if (!relevantKeywords.test(expression)) {
replacedString += input.slice(lastIndex, index);
bracketNotationString += input.slice(lastIndex, index);
replacedString += fullMatch;
bracketNotationString += fullMatch;
lastIndex = index + fullMatch.length;
continue;
}
try {
const { processedExpression, uuidMappings } = preprocessExpression(
expression,
uuidRegex,
componentIdNameMapping,
queryIdNameMapping
);
const parsedResult = parseExpression(
processedExpression,
componentIdNameMapping,
queryIdNameMapping,
uuidMappings
);
replacedString += input.slice(lastIndex, index);
bracketNotationString += input.slice(lastIndex, index);
const replacedExpression = replaceIdsInExpression(
processedExpression,
componentIdNameMapping,
queryIdNameMapping,
false,
uuidMappings
);
const bracketNotationExpression = replaceIdsInExpression(
processedExpression,
componentIdNameMapping,
queryIdNameMapping,
true,
uuidMappings
);
replacedString += `{{${replacedExpression}}}`;
bracketNotationString += `{{${bracketNotationExpression}}}`;
results.push({
allRefs: parsedResult.references,
valueWithId: `{{${replacedExpression}}}`,
valueWithBrackets: `{{${bracketNotationExpression}}}`,
});
} catch (error) {
replacedString += fullMatch;
bracketNotationString += fullMatch;
results.push({
allRefs: [],
valueWithId: fullMatch,
valueWithBrackets: fullMatch,
});
}
lastIndex = index + fullMatch.length;
}
replacedString += input.slice(lastIndex);
bracketNotationString += input.slice(lastIndex);
// remove the parentheses that were added
return {
valueWithId: `{{${replacedString.slice(3, -3)}}}`,
valueWithBrackets: `{{${bracketNotationString.slice(3, -3)}}}`,
allRefs: results.flatMap((r) => r.allRefs),
};
}
while ((match = expressionRegex.exec(input)) !== null) {
const fullMatch = match[0];
const expression = match[1].trim();
// Check if the expression contains relevant keywords
if (!relevantKeywords.test(expression)) {
replacedString += input.slice(lastIndex, match.index);
bracketNotationString += input.slice(lastIndex, match.index);
replacedString += fullMatch;
bracketNotationString += fullMatch;
lastIndex = match.index + fullMatch.length;

View file

@ -6,6 +6,7 @@ import DependencyGraph from './DependencyClass';
import { getWorkspaceId } from '@/_helpers/utils';
import { navigate } from '@/AppBuilder/_utils/misc';
import queryString from 'query-string';
import { replaceEntityReferencesWithIds } from '../utils';
const initialState = {
app: {},
@ -63,15 +64,19 @@ export const createAppSlice = (set, get) => ({
}
});
},
globalSettingsChanged: async (newOptions) => {
for (const [key, value] of Object.entries(newOptions)) {
globalSettingsChanged: async (options) => {
const componentNameIdMapping = get().modules.canvas.componentNameIdMapping;
const queryNameIdMapping = get().modules.canvas.queryNameIdMapping;
for (const [key, value] of Object.entries(options)) {
if (value?.[1]?.a == undefined) {
newOptions[key] = value;
options[key] = value;
} else {
const hexCode = `${value?.[0]}${decimalToHex(value?.[1]?.a)}`;
newOptions[key] = hexCode;
options[key] = hexCode;
}
}
// Replace entity references with ids if present
const newOptions = replaceEntityReferencesWithIds(options, componentNameIdMapping, queryNameIdMapping);
const { app, currentVersionId, currentPageId } = get();
try {
const res = await appVersionService.autoSaveApp(

View file

@ -4,12 +4,12 @@ import {
resolveDynamicValues,
// extractAndReplaceReferencesFromString,
checkSubstringRegex,
hasArrayNotation,
parsePropertyPath,
} from '@/AppBuilder/_stores/utils';
import { extractAndReplaceReferencesFromString } from '@/AppBuilder/_stores/ast';
import { componentTypeDefinitionMap } from '@/AppBuilder/WidgetManager';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
import { v4 as uuidv4 } from 'uuid';
import _, { cloneDeep, merge } from 'lodash';
import { cloneDeep, merge, set as lodashSet } from 'lodash';
import { computeComponentName, getAllChildComponents } from '@/AppBuilder/AppCanvas/appCanvasUtils';
import { pageConfig } from '@/AppBuilder/RightSideBar/PageSettingsTab/pageConfig';
import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants';
@ -243,7 +243,8 @@ export const createComponentsSlice = (set, get) => ({
property,
value,
component,
componentResolvedValues = {}, // If componentResolvedValues is null, then it is from a setComponentProperty call
componentResolvedValues = {},
updatePassedValue = true,
moduleId
) => {
const {
@ -281,7 +282,7 @@ export const createComponentsSlice = (set, get) => ({
allRefs.push(customResolvablePath);
}
}
if (componentResolvedValues !== null)
if (updatePassedValue)
setAllValueToComponent(
componentDetails,
valueWithBrackets,
@ -294,7 +295,7 @@ export const createComponentsSlice = (set, get) => ({
return { updatedValue: valueWithId, allRefs, unResolvedValue: valueWithBrackets, componentResolvedValues };
} else {
if (componentResolvedValues !== null)
if (updatePassedValue)
setAllValueToComponent(
componentDetails,
value,
@ -317,7 +318,7 @@ export const createComponentsSlice = (set, get) => ({
componentResolvedValues = {},
moduleId
) => {
const { getAllExposedValues } = get();
const { getAllExposedValues, getComponentTypeFromId } = get();
const { componentId, paramType, property } = componentDetails;
const length = Object.keys(customResolvables).length;
if (length === 0) {
@ -334,12 +335,31 @@ export const createComponentsSlice = (set, get) => ({
if (!componentResolvedValues[componentId][index][paramType]) {
componentResolvedValues[componentId][index][paramType] = {};
}
componentResolvedValues[componentId][index][paramType][property] = resolvedValue;
if (hasArrayNotation(property)) {
const keys = parsePropertyPath(property);
lodashSet(
componentResolvedValues,
[componentId, index, paramType, ...keys],
getComponentTypeFromId(componentId) === 'Table' ? value : resolvedValue
);
} else {
componentResolvedValues[componentId][index][paramType][property] = resolvedValue;
}
} else {
if (!componentResolvedValues[componentId][paramType]) {
componentResolvedValues[componentId][paramType] = {};
}
componentResolvedValues[componentId][paramType][property] = resolvedValue;
if (hasArrayNotation(property)) {
const keys = parsePropertyPath(property);
lodashSet(
componentResolvedValues,
[componentId, paramType, ...keys],
getComponentTypeFromId(componentId) === 'Table' ? value : resolvedValue
);
} else {
componentResolvedValues[componentId][paramType][property] = resolvedValue;
}
}
} else {
// Loop all the index and set the resolved value
@ -564,6 +584,83 @@ export const createComponentsSlice = (set, get) => ({
};
},
// This function checks whether the property value is an array or not and then resolves the value accordingly
// Cases like Table column, Dropdown options, etc.
checkValueAndResolve: (
componentId,
paramType,
property,
value,
component,
resolvedComponentValues,
updatePassedValue = true,
moduleId
) => {
const { updateResolvedValues, generateDependencyGraphForRefs } = get();
const updatedPropertyValue = cloneDeep(value);
if (Array.isArray(value)) {
value.forEach((val, index) => {
//This code assumes that the array always consists of objects the else condition is to handle the case when the value is an array of strings/numbers
if (val && typeof val === 'object') {
Object.entries(val).forEach(([key, keyValue]) => {
const propertyWithArrayValue = `${property}[${index}].${key}`;
const keys = [key];
if (keyValue?.value) {
keys.push('value');
}
const { allRefs, unResolvedValue, updatedValue } = updateResolvedValues(
componentId,
paramType,
propertyWithArrayValue,
keyValue?.value ?? keyValue,
component,
resolvedComponentValues,
updatePassedValue,
moduleId
);
lodashSet(updatedPropertyValue, [index, ...keys], updatedValue);
if (allRefs.length) {
generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue);
}
});
} else {
const propertyWithArrayValue = `${property}[${index}]`;
const { allRefs, unResolvedValue, updatedValue } = updateResolvedValues(
componentId,
paramType,
propertyWithArrayValue,
val,
component,
resolvedComponentValues,
updatePassedValue,
moduleId
);
updatedPropertyValue[index] = updatedValue;
console.log('updatedPropertyValue', updatedPropertyValue);
if (allRefs.length) {
generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue);
}
}
});
return { updatedValue: updatedPropertyValue };
} else {
const { allRefs, unResolvedValue, updatedValue } = updateResolvedValues(
componentId,
paramType,
property,
value,
component,
resolvedComponentValues,
updatePassedValue,
moduleId
);
if (allRefs.length) {
generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue);
}
return { allRefs, unResolvedValue, updatedValue };
}
},
updateDependencyGraphAndResolvedValues: (
moduleId,
componentId,
@ -572,22 +669,20 @@ export const createComponentsSlice = (set, get) => ({
resolvedComponentValues = {},
paramType
) => {
const { updateResolvedValues, generateDependencyGraphForRefs, setAllValueToComponent } = get();
const { checkValueAndResolve, setAllValueToComponent } = get();
if (component.definition[paramType] === undefined) return;
Object.entries(component.definition[paramType]).forEach(([property, value]) => {
if (!value?.skipResolve) {
const { allRefs, unResolvedValue } = updateResolvedValues(
checkValueAndResolve(
componentId,
paramType,
property,
value?.value,
component,
resolvedComponentValues,
true,
moduleId
);
if (allRefs.length) {
generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue);
}
} else {
const componentDetails = { componentId, paramType, property };
setAllValueToComponent(componentDetails, value?.value, false, null, {}, resolvedComponentValues, moduleId);
@ -925,11 +1020,10 @@ export const createComponentsSlice = (set, get) => ({
saveComponentChanges,
withUndoRedo,
getComponentTypeFromId,
updateResolvedValues,
generateDependencyGraphForRefs,
setResolvedComponent,
getComponentDefinition,
currentLayout,
checkValueAndResolve,
} = get();
let hasParentChanged = false;
let oldParentId;
@ -1003,19 +1097,16 @@ export const createComponentsSlice = (set, get) => ({
objectsToUpdate.forEach((paramType) => {
if (component.definition[paramType]) {
Object.entries(component.definition[paramType]).forEach(([property, value]) => {
const { allRefs, unResolvedValue } = updateResolvedValues(
checkValueAndResolve(
componentId,
paramType,
property,
value.value,
component,
resolvedComponentValues,
true,
moduleId
);
if (allRefs.length) {
generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, true);
}
});
}
});
@ -1066,22 +1157,67 @@ export const createComponentsSlice = (set, get) => ({
removeDependency,
getComponentDefinition,
setValueToComponent,
checkValueAndResolve,
getResolvedComponent,
setResolvedComponent,
} = get();
const { component } = getComponentDefinition(componentId, moduleId);
const oldValue = component.definition[paramType][property];
if (Array.isArray(oldValue?.value)) {
const resolvedComponent = { [componentId]: deepClone(getResolvedComponent(componentId) ?? {}) };
resolvedComponent[componentId][paramType][property] = [];
const { updatedValue } = checkValueAndResolve(
componentId,
paramType,
property,
value,
component,
resolvedComponent,
true,
moduleId
);
setResolvedComponent(componentId, resolvedComponent[componentId], moduleId);
// If the value is not changed, return
if (oldValue?.[attr] === updatedValue || oldValue === updatedValue) return;
set(
withUndoRedo((state) => {
const pageComponent = state.modules[moduleId].pages[currentPageIndex].components[componentId].component;
lodashSet(pageComponent, ['definition', paramType, property, attr], updatedValue);
}, skipUndoRedo),
false,
'setComponentProperty'
);
const diff = {
[componentId]: { component: get().modules[moduleId].pages[currentPageIndex].components[componentId].component },
};
if (saveAfterAction) {
const currentMode = get().currentMode;
if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update');
get().multiplayer.broadcastUpdates({ componentId, property, value, paramType, attr }, 'components', 'update');
}
return;
}
// Update the value and get new dependencies
const { updatedValue, allRefs, unResolvedValue } =
attr === 'value' && !skipResolve
? updateResolvedValues(componentId, paramType, property, value, component, null, moduleId)
? updateResolvedValues(componentId, paramType, property, value, component, null, false, moduleId)
: { updatedValue: value, allRefs: [], unResolvedValue: value };
// If the value is not changed, return
const oldValue = component.definition[paramType][property];
if (oldValue?.[attr] === updatedValue || oldValue === updatedValue) return;
set(
withUndoRedo((state) => {
const pageComponent = state.modules[moduleId].pages[currentPageIndex].components[componentId].component;
_.set(pageComponent, ['definition', paramType, property, attr], updatedValue);
lodashSet(pageComponent, ['definition', paramType, property, attr], updatedValue);
}, skipUndoRedo),
false,
'setComponentProperty'
@ -1130,9 +1266,8 @@ export const createComponentsSlice = (set, get) => ({
const {
currentPageIndex,
saveComponentChanges,
updateResolvedValues,
checkValueAndResolve,
getComponentDefinition,
generateDependencyGraphForRefs,
getComponentTypeFromId,
setResolvedComponent,
withUndoRedo,
@ -1189,19 +1324,16 @@ export const createComponentsSlice = (set, get) => ({
objectsToUpdate.forEach((paramType) => {
if (component.definition[paramType]) {
Object.entries(component.definition[paramType]).forEach(([property, value]) => {
const { allRefs, unResolvedValue } = updateResolvedValues(
checkValueAndResolve(
componentId,
paramType,
property,
value.value,
component,
resolvedComponentValues,
true,
moduleId
);
if (allRefs.length) {
generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, true);
}
});
}
});
@ -1427,6 +1559,8 @@ export const createComponentsSlice = (set, get) => ({
getNodeData,
getEntityResolvedValueLength,
updateChildComponentResolvedValues,
getComponentTypeFromId,
getResolvedComponent,
} = get();
const dependecies = getDependencies(path, moduleId);
if (dependecies?.length) {
@ -1436,7 +1570,8 @@ export const createComponentsSlice = (set, get) => ({
if (itemsLength) {
updateChildComponentResolvedValues(dependency, path, itemsLength, moduleId);
} else {
const [entityType, entityId, type, key] = dependency.split('.');
const [entityType, entityId, type, ...keys] = dependency.split('.');
const key = keys.join('.');
const unResolvedValue = getNodeData(dependency);
const resolvedValue = resolveDynamicValues(unResolvedValue, getAllExposedValues(), {}, false, []);
@ -1455,13 +1590,46 @@ export const createComponentsSlice = (set, get) => ({
? get().debugger.validateProperty(entityId, type, key, resolvedValue)
: resolvedValue;
set(
(state) => {
state.resolvedStore.modules[moduleId][entityType][entityId][type][key] = validatedValue;
},
false,
'updateDependencyValues'
);
// logic to handle the key like options[0].visible. It will resolve the visible directly and update the resolved store
if (hasArrayNotation(key)) {
const keys = parsePropertyPath(key);
// Triggering a re-render of the table component if any of the dependent component is updated
// This is done to calculate the callValues in the table component
// Need to find a better way to handle this
if (getComponentTypeFromId(entityId, moduleId) === 'Table') {
set(
(state) => {
lodashSet(
state.resolvedStore.modules[moduleId][entityType][entityId],
['properties', 'shouldRender'],
(getResolvedComponent(entityId)?.['properties']?.['shouldRender'] ?? 0) + 1
);
},
false,
'updateDependencyValues'
);
} else {
set(
(state) => {
lodashSet(
state.resolvedStore.modules[moduleId][entityType][entityId],
[type, ...keys],
getComponentTypeFromId(entityId, moduleId) === 'Table' ? unResolvedValue + ' ' : validatedValue
);
},
false,
'updateDependencyValues'
);
}
} else {
set(
(state) => {
state.resolvedStore.modules[moduleId][entityType][entityId][type][key] = validatedValue;
},
false,
'updateDependencyValues'
);
}
}
}
});
@ -1588,7 +1756,7 @@ export const createComponentsSlice = (set, get) => ({
};
const regex =
/(components|queries)(\??\.|\??\.?\[['"]?)([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})(['"]?\])?(\??\.?)([^\s|},[\])?]+)?/g;
/(components|queries)(\??\.|\??\.?\[['"]?)([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})(['"]?\])?(\??\.|\[['"]?)([^\s:?[\]'"+\-&|]+)/g;
return input.replace(regex, (match, category, prefix, id, suffix, optionalChaining, property) => {
if (mappings[category] && mappings[category][id]) {

View file

@ -211,7 +211,7 @@ export const createEventsSlice = (set, get) => ({
state.eventsSlice.module[moduleId].events = newEvents;
});
},
setTablePageIndex: (tableId, index) => {
setTablePageIndex: (tableId, index = 1) => {
const { getExposedValueOfComponent } = get();
if (_.isEmpty(tableId)) {
console.log('No table is associated with this event.');

View file

@ -1,7 +1,9 @@
import { NO_OF_GRIDS } from '@/AppBuilder/AppCanvas/appCanvasConstants';
import { debounce } from 'lodash';
const initialState = {
hoveredComponentForGrid: '',
triggerCanvasUpdater: false,
};
export const createGridSlice = (set, get) => ({
@ -9,8 +11,13 @@ export const createGridSlice = (set, get) => ({
setHoveredComponentForGrid: (id) =>
set(() => ({ hoveredComponentForGrid: id }), false, { type: 'setHoveredComponentForGrid', id }),
getHoveredComponentForGrid: () => get().hoveredComponentForGrid,
toggleCanvasUpdater: () =>
set((state) => ({ triggerCanvasUpdater: !state.triggerCanvasUpdater }), false, { type: 'toggleCanvasUpdater' }),
debouncedToggleCanvasUpdater: debounce(() => {
get().toggleCanvasUpdater();
}, 200),
moveComponentPosition: (direction) => {
const { setComponentLayout, currentLayout, getSelectedComponentsDefinition } = get();
const { setComponentLayout, currentLayout, getSelectedComponentsDefinition, debouncedToggleCanvasUpdater } = get();
let layouts = {};
const selectedComponents = getSelectedComponentsDefinition();
selectedComponents.forEach((selectedComponent) => {
@ -53,5 +60,6 @@ export const createGridSlice = (set, get) => ({
};
});
setComponentLayout(layouts);
debouncedToggleCanvasUpdater();
},
});

View file

@ -1,21 +1,23 @@
const selectedItem = localStorage.getItem('selectedSidebarItem');
const isLeftSideBarPinned = !!selectedItem;
const selectedSidebarItem = selectedItem;
const validSidebarItems = ['page', 'inspect', 'debugger', 'settings'];
const storedIsSidebarPinned = localStorage.getItem('isLeftSideBarPinned') === 'true' ? true : false;
const storedSelectedSidebarItem = !storedIsSidebarPinned
? null
: localStorage.getItem('selectedSidebarItem') &&
validSidebarItems.includes(localStorage.getItem('selectedSidebarItem'))
? localStorage.getItem('selectedSidebarItem')
: 'page';
const initialState = {
isSidebarOpen: isLeftSideBarPinned || !!selectedSidebarItem,
isLeftSideBarPinned,
selectedSidebarItem,
isLeftSideBarPinned: storedIsSidebarPinned,
selectedSidebarItem: storedIsSidebarPinned ? storedSelectedSidebarItem : null,
isSidebarOpen: storedIsSidebarPinned,
};
export const createLeftSideBarSlice = (set, get) => ({
export const createLeftSideBarSlice = (set) => ({
...initialState,
setIsLeftSideBarPinned: (status) => {
status
? localStorage.setItem('selectedSidebarItem', get().selectedSidebarItem)
: localStorage.removeItem('selectedSidebarItem');
set(() => ({ isLeftSideBarPinned: status }));
localStorage.setItem('isLeftSideBarPinned', status === true ? 'true' : 'false');
set(() => ({ isLeftSideBarPinned: status }), false, 'setIsLeftSideBarPinned');
},
setSelectedSidebarItem: (selectedSidebarItem) =>
set(() => ({ selectedSidebarItem }), false, 'setSelectedSidebarItem'),

View file

@ -111,8 +111,8 @@ export const createPageMenuSlice = (set, get) => {
set((state) => {
state.editingPage = page;
if (ref) {
state.showEditingPopover = true;
state.popoverTargetId = ref?.current?.id;
state.showEditingPopover = true;
}
}),

View file

@ -26,11 +26,20 @@ const initialState = {
previewPanelExpanded: false,
loadingDataQueries: false,
isPreviewQueryLoading: false,
queryPanelSearchTem: '',
};
export const createQueryPanelSlice = (set, get) => ({
queryPanel: {
...initialState,
setQueryPanelSearchTerm: (searchTerm) =>
set(
(state) => {
state.queryPanel.queryPanelSearchTem = searchTerm;
},
false,
'setQueryPanelSearchTerm'
),
setIsDraggingQueryPane: (isDraggingQueryPane) =>
set(
(state) => {

View file

@ -394,7 +394,22 @@ export const createResolvedSlice = (set, get) => ({
return get().resolvedStore?.modules?.[moduleId]?.components?.[componentId];
},
getExposedValueOfComponent: (componentId, moduleId = 'canvas') => {
return get().resolvedStore.modules[moduleId].exposedValues.components[componentId] || {};
try {
const components = get().getCurrentPageComponents();
const {
component: { parent: parentId, name: componentName },
} = components[componentId];
if (parentId) {
// if parent is form get exposed values from children
const { component: parentComopnent } = components?.[parentId] || {};
if (parentComopnent?.component === 'Form') {
return get().resolvedStore.modules[moduleId].exposedValues.components[parentId].children[componentName] || {};
}
}
return get().resolvedStore.modules[moduleId].exposedValues.components[componentId] || {};
} catch (error) {
return {};
}
},
getAllExposedValues: (moduleId = 'canvas') => {
return get().resolvedStore.modules[moduleId].exposedValues;

View file

@ -106,6 +106,7 @@ export const createUndoRedoSlice = (set, get) => {
componenetPropertiesToUpdate.paramType,
componenetPropertiesToUpdate.attr,
undefined,
undefined,
{ skipUndoRedo: true }
);
}

View file

@ -672,3 +672,34 @@ export function convertAllKeysToSnakeCase(o) {
// return { suggestionList, hintsMap, resolvedRefs };
// }
export const hasArrayNotation = (property) => {
// Regular expression to match array notation pattern
const arrayPattern = /\[\d+\]/;
return arrayPattern.test(property);
};
export const parsePropertyPath = (property) => {
// Split the property path into segments
const segments = property.split('.');
const result = [];
for (const segment of segments) {
// Check if segment contains array notation
if (hasArrayNotation(segment)) {
// Extract the property name and array index
const [name, ...rest] = segment.split('[');
if (name) result.push(name);
// Extract and clean up array indices
for (const item of rest) {
const index = parseInt(item.replace(']', ''));
result.push(index);
}
} else {
result.push(segment);
}
}
return result;
};

View file

@ -24,6 +24,26 @@ export async function copyToClipboard(text) {
}
}
export const extractEnvironmentConstantsFromConstantsList = (constantsList = [], environmentName = 'development') => {
try {
return constantsList.map((constant) => {
if (constant.values && Array.isArray(constant.values)) {
const { value } = constant.values.find((value) => value.environmentName === environmentName);
return {
id: constant.id,
name: constant.name,
value,
type: constant.type,
};
} else {
return constant;
}
});
} catch (error) {
return [];
}
};
export function setTablePageIndex(tableId, index) {
if (_.isEmpty(tableId)) {
console.log('No table is associated with this event.');

View file

@ -34,6 +34,12 @@ export const ButtonGroup = function Button({
display: visibility ? '' : 'none',
};
const disabledStyles = {
opacity: 0.5,
pointerEvents: 'none',
cursor: 'not-allowed',
};
const [defaultActive, setDefaultActive] = useState(defaultSelected);
const [data, setData] = useState(values);
@ -62,7 +68,7 @@ export const ButtonGroup = function Button({
const buttonClick = (index) => {
if (defaultActive?.includes(values[index]) && multiSelection) {
const copyDefaultActive = defaultActive;
const copyDefaultActive = [...defaultActive];
copyDefaultActive?.splice(copyDefaultActive?.indexOf(values[index]), 1);
setDefaultActive(copyDefaultActive);
setExposedVariable('selected', copyDefaultActive.join(','));
@ -100,6 +106,7 @@ export const ButtonGroup = function Button({
color: defaultActive?.includes(values[index]) ? selectedTextColor : textColor,
transition: 'all .1s ease',
boxShadow,
...(disabledState && disabledStyles),
}}
key={index}
disabled={disabledState}

View file

@ -20,7 +20,6 @@ export const Checkbox = ({
const isMandatory = validation?.mandatory ?? false;
const [defaultValue, setDefaultValue] = useState(defaultValueFromProperties);
const [checked, setChecked] = useState(defaultValueFromProperties);
const [value, setValue] = React.useState(defaultValueFromProperties);
const [userInteracted, setUserInteracted] = useState(false);
const { label } = properties;
@ -33,14 +32,12 @@ export const Checkbox = ({
const [loading, setLoading] = useState(properties?.loadingState);
const [disable, setDisable] = useState(disabledState || loadingState);
const [visibility, setVisibility] = useState(properties.visibility);
const { isValid, validationError } = validate(checked);
const [validationStatus, setValidationStatus] = useState(validate(checked));
const { isValid, validationError } = validationStatus;
const toggleValue = (e) => {
const isChecked = e.target.checked;
setChecked(isChecked);
setValue(isChecked);
setExposedVariable('value', isChecked);
setInputValue(isChecked);
if (isChecked) {
fireEvent('onCheck');
} else {
@ -52,9 +49,8 @@ export const Checkbox = ({
useEffect(() => {
if (isInitialRender.current) return;
setDefaultValue(defaultValueFromProperties);
setChecked(defaultValueFromProperties);
setValue(defaultValueFromProperties);
setExposedVariable('value', defaultValueFromProperties);
setInputValue(defaultValueFromProperties);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultValueFromProperties]);
@ -104,20 +100,29 @@ export const Checkbox = ({
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isValid', isValid);
const validationStatus = validate(checked);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
}, [validate]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('toggle', async function () {
setInputValue(!checked);
fireEvent('onChange');
setUserInteracted(true);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isValid]);
}, [checked]);
useEffect(() => {
const setCheckedAndNotify = async (status) => {
await setExposedVariable('value', status);
setInputValue(status);
if (status) {
fireEvent('onCheck');
} else {
fireEvent('onUnCheck');
}
setChecked(status);
setValue(status);
};
const exposedVariables = {
@ -137,14 +142,9 @@ export const Checkbox = ({
setExposedVariable('isDisabled', disable);
},
toggle: () => {
setExposedVariable('toggle', async function () {
setExposedVariable('value', !checked);
fireEvent('onChange');
setChecked(!checked);
setValue(!checked);
setUserInteracted(true);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
setInputValue(!checked);
fireEvent('onChange');
setUserInteracted(true);
},
label: label,
isMandatory: isMandatory,
@ -154,10 +154,6 @@ export const Checkbox = ({
isValid: isValid,
};
setDefaultValue(defaultValueFromProperties);
setChecked(defaultValueFromProperties);
setValue(defaultValueFromProperties);
setExposedVariables(exposedVariables);
isInitialRender.current = false;
@ -167,9 +163,7 @@ export const Checkbox = ({
const handleToggleChange = () => {
const newCheckedState = !checked;
setChecked(newCheckedState);
setValue(newCheckedState);
setExposedVariable('value', newCheckedState);
setInputValue(newCheckedState);
fireEvent('onChange');
if (newCheckedState) {
fireEvent('onCheck');
@ -179,6 +173,14 @@ export const Checkbox = ({
setUserInteracted(true);
};
const setInputValue = (value) => {
setChecked(value);
setExposedVariable('value', value);
const validationStatus = validate(value);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
};
const renderCheckBox = () => (
<>
<div
@ -251,7 +253,7 @@ export const Checkbox = ({
</>
)}
</div>
{validationError && visibility && !checked && userInteracted && (
{!isValid && visibility && userInteracted && (
<div
data-cy={`${String(componentName).toLowerCase()}-invalid-feedback`}
style={{

View file

@ -39,11 +39,12 @@ export const Datepicker = function Datepicker({
}
};
const [validationStatus, setValidationStatus] = useState(validate(computeDateString(date)));
const { isValid, validationError } = validationStatus;
const onDateChange = (date) => {
setShowValidationError(true);
setDate(date);
const dateString = computeDateString(date);
setExposedVariable('value', dateString);
setInputValue(date);
fireEvent('onSelect');
};
@ -51,11 +52,9 @@ export const Datepicker = function Datepicker({
if (isInitialRender.current) return;
const dateMomentInstance = defaultValue && moment(defaultValue, selectedDateFormat);
if (dateMomentInstance && dateMomentInstance.isValid()) {
setDate(dateMomentInstance.toDate());
setExposedVariable('value', defaultValue);
setInputValue(dateMomentInstance.toDate());
} else {
setDate(null);
setExposedVariable('value', undefined);
setInputValue(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultValue]);
@ -73,32 +72,28 @@ export const Datepicker = function Datepicker({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disabledDates, format]);
const validationData = validate(computeDateString(date));
const { isValid, validationError } = validationData;
useEffect(() => {
if (isInitialRender.current) return;
const validationStatus = validate(computeDateString(date));
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
}, [validate]);
useEffect(() => {
isInitialRender.current = false;
setExposedVariable('isValid', isValid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isValid]);
useEffect(() => {
const exposedVariables = {
isValid,
};
const dateMomentInstance = defaultValue && moment(defaultValue, selectedDateFormat);
if (dateMomentInstance && dateMomentInstance.isValid()) {
setDate(dateMomentInstance.toDate());
exposedVariables.value = defaultValue;
} else {
setDate(null);
exposedVariables.value = undefined;
}
setExposedVariables(exposedVariables);
setInputValue(dateMomentInstance && dateMomentInstance.isValid() ? dateMomentInstance.toDate() : null);
isInitialRender.current = false;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const setInputValue = (value) => {
setDate(value);
setExposedVariable('value', value ? computeDateString(value) : undefined);
const validationStatus = validate(computeDateString(value));
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
};
return (
<div
data-disabled={disabledState}

View file

@ -16,6 +16,7 @@ export const DaterangePicker = function DaterangePicker({
fireEvent,
dataCy,
id,
formId,
}) {
const isInitialRender = useRef(true);
const { borderRadius, visibility, disabledState, boxShadow } = styles;
@ -82,10 +83,14 @@ export const DaterangePicker = function DaterangePicker({
function focusChanged(focus) {
setFocusedInput(focus);
if (focus) {
document.querySelector(`.ele-${id}`).style.zIndex = 3;
} else {
document.querySelector(`.ele-${id}`).style.zIndex = '';
const currentComponent = document.querySelector(`.ele-${id}`);
if (currentComponent) {
currentComponent.style.zIndex = focus ? 3 : '';
}
const formComponent = formId && document.querySelector(`.form-${formId}`);
if (formComponent) {
formComponent.style.zIndex = focus ? 4 : '';
}
}

View file

@ -22,8 +22,8 @@ export const DropDown = function DropDown({
const { selectedTextColor, borderRadius, visibility, disabledState, justifyContent, boxShadow } = styles;
const [currentValue, setCurrentValue] = useState(() => (advanced ? findDefaultItem(schema) : value));
const [showValidationError, setShowValidationError] = useState(false);
const validationData = validate(value);
const { isValid, validationError } = validationData;
const [validationStatus, setValidationStatus] = useState(validate(value));
const { isValid, validationError } = validationStatus;
function findDefaultItem(schema) {
const foundItem = schema?.find((item) => item?.default === true);
return !hasVisibleFalse(foundItem?.value) ? foundItem?.value : undefined;
@ -59,15 +59,11 @@ export const DropDown = function DropDown({
}
const setExposedItem = (value, index, onSelectFired = false) => {
setCurrentValue(value);
const selectedOptionLabel = index === undefined ? undefined : display_values?.[index];
setInputValue(value, selectedOptionLabel);
if (onSelectFired) {
fireEvent('onSelect');
}
const exposedVariables = {
value,
selectedOptionLabel: index === undefined ? undefined : display_values?.[index],
};
setExposedVariables(exposedVariables);
};
function selectOption(value) {
@ -80,17 +76,6 @@ export const DropDown = function DropDown({
setExposedItem(undefined, undefined, true);
}
}
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isValid', isValid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isValid]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('value', currentValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentValue]);
useEffect(() => {
if (isInitialRender.current) return;
@ -105,6 +90,13 @@ export const DropDown = function DropDown({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [label]);
useEffect(() => {
if (isInitialRender.current) return;
const validationStatus = validate(currentValue);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
}, [validate]);
useEffect(() => {
if (isInitialRender.current) return;
if (advanced) {
@ -113,7 +105,7 @@ export const DropDown = function DropDown({
schema?.filter((item) => item?.visible)?.map((item) => item.label)
);
if (hasVisibleFalse(currentValue)) {
setCurrentValue(findDefaultItem(schema));
setInputValue(findDefaultItem(schema));
}
} else setExposedVariable('optionLabels', display_values);
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -137,14 +129,21 @@ export const DropDown = function DropDown({
};
setExposedVariables(exposedVariables);
if (hasVisibleFalse(currentValue)) {
setCurrentValue(findDefaultItem(schema));
}
isInitialRender.current = false;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const exposedVariables = {
selectOption: async function (value) {
selectOption(value);
},
};
setExposedVariables(exposedVariables);
}, [JSON.stringify(properties.values)]);
useEffect(() => {
let newValue = undefined;
let index = null;
@ -184,6 +183,14 @@ export const DropDown = function DropDown({
}
};
const setInputValue = (value, label) => {
setCurrentValue(value);
setExposedVariables({ value, selectedOptionLabel: label });
const validationStatus = validate(value);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
};
const customStyles = {
control: (provided, state) => ({
...provided,
@ -276,10 +283,8 @@ export const DropDown = function DropDown({
onChange={(selectedOption, actionProps) => {
setShowValidationError(true);
if (actionProps.action === 'select-option') {
setCurrentValue(selectedOption.value);
setExposedVariable('value', selectedOption.value);
setInputValue(selectedOption.value, selectedOption.label);
fireEvent('onSelect');
setExposedVariable('selectedOptionLabel', selectedOption.label);
}
}}
options={selectOptions}

View file

@ -90,17 +90,19 @@ export const DropdownV2 = ({
} = styles;
const isInitialRender = useRef(true);
const [currentValue, setCurrentValue] = useState(() => (advanced ? findDefaultItem(schema) : value));
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
const isMandatory = validation?.mandatory ?? false;
const options = properties?.options;
const validationData = validate(currentValue);
const { isValid, validationError } = validationData;
const [validationStatus, setValidationStatus] = useState(validate(currentValue));
const { isValid, validationError } = validationStatus;
const ref = React.useRef(null);
const dropdownRef = React.useRef(null);
const [visibility, setVisibility] = useState(properties.visibility);
const [isDropdownLoading, setIsDropdownLoading] = useState(dropdownLoadingState);
const [isDropdownDisabled, setIsDropdownDisabled] = useState(disabledState);
const [isFocused, setIsFocused] = useState(false);
const [searchInputValue, setSearchInputValue] = useState('');
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [userInteracted, setUserInteracted] = useState(false);
const _height = padding === 'default' ? `${height}px` : `${height + 4}px`;
const labelRef = useRef();
function findDefaultItem(schema) {
@ -115,12 +117,12 @@ export const DropdownV2 = ({
let _options = advanced ? schema : options;
if (Array.isArray(_options)) {
let _selectOptions = _options
.filter((data) => getResolvedValue(advanced ? data?.visible : data?.visible?.value) ?? true)
.filter((data) => data?.visible ?? true)
.map((data) => ({
...data,
label: String(getResolvedValue(data?.label)),
value: getResolvedValue(data?.value),
isDisabled: getResolvedValue(advanced ? data?.disable : data?.disable?.value) ?? false,
label: data?.label,
value: data?.value,
isDisabled: data?.disable ?? false,
}));
return _selectOptions;
@ -132,7 +134,7 @@ export const DropdownV2 = ({
function selectOption(value) {
const val = selectOptions.filter((option) => !option.isDisabled)?.find((option) => option.value === value);
if (val) {
setCurrentValue(value);
setInputValue(value);
fireEvent('onSelect');
}
}
@ -157,24 +159,48 @@ export const DropdownV2 = ({
const handleOutsideClick = (e) => {
let menu = ref.current.querySelector('.select__menu');
if (!ref.current.contains(e.target) || !menu || !menu.contains(e.target)) {
setIsFocused(false);
setSearchInputValue('');
}
if (dropdownRef.current && !dropdownRef.current?.contains(e.target) && !menu && !menu?.contains(e.target)) {
if (isDropdownOpen) {
fireEvent('onBlur');
}
setIsDropdownOpen(false);
}
};
const setInputValue = (value) => {
setCurrentValue(value);
const _selectedOption = selectOptions.find((option) => option.value === value);
setExposedVariables({
value,
selectedOption: pick(_selectedOption, ['label', 'value']),
});
const validationStatus = validate(value);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
};
useEffect(() => {
if (advanced) {
setCurrentValue(findDefaultItem(schema));
} else setCurrentValue(value);
setInputValue(findDefaultItem(schema));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [advanced, value, JSON.stringify(schema)]);
}, [advanced, JSON.stringify(schema)]);
useEffect(() => {
if (!advanced) {
setInputValue(value);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [advanced, value]);
useEffect(() => {
document.addEventListener('mousedown', handleOutsideClick);
return () => {
document.removeEventListener('mousedown', handleOutsideClick);
};
}, []);
}, [isDropdownOpen]);
useEffect(() => {
if (visibility !== properties.visibility) setVisibility(properties.visibility);
@ -186,18 +212,6 @@ export const DropdownV2 = ({
// Exposed variables
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('value', currentValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentValue]);
useEffect(() => {
const _selectedOption = selectOptions.find((option) => option.value === currentValue);
setExposedVariable('selectedOption', pick(_selectedOption, ['label', 'value']));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentValue, JSON.stringify(selectOptions)]);
useEffect(() => {
if (isInitialRender.current) return;
const _options = selectOptions?.map(({ label, value }) => ({ label, value }));
@ -221,11 +235,6 @@ export const DropdownV2 = ({
setExposedVariable('searchText', searchInputValue);
}, [searchInputValue]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isValid', isValid);
}, [isValid]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isVisible', properties.visibility);
@ -246,11 +255,18 @@ export const DropdownV2 = ({
setExposedVariable('isMandatory', isMandatory);
}, [isMandatory]);
useEffect(() => {
if (isInitialRender.current) return;
const validationStatus = validate(currentValue);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
}, [validate]);
useEffect(() => {
const _options = selectOptions?.map(({ label, value }) => ({ label, value }));
const exposedVariables = {
clear: async function () {
setCurrentValue(null);
setInputValue(null);
},
setVisibility: async function (value) {
setVisibility(value);
@ -303,6 +319,7 @@ export const DropdownV2 = ({
accentColor,
isLoading: isDropdownLoading,
isDisabled: isDropdownDisabled,
userInteracted,
}),
backgroundColor: getInputBackgroundColor({
fieldBackgroundColor,
@ -406,6 +423,7 @@ export const DropdownV2 = ({
return (
<>
<div
ref={dropdownRef}
data-cy={`label-${String(componentName).toLowerCase()} `}
className={cx('dropdown-widget', 'd-flex', {
[alignment === 'top' &&
@ -442,32 +460,36 @@ export const DropdownV2 = ({
_width={_width}
top={'1px'}
/>
<div className="w-100 px-0 h-100" ref={ref}>
<div
className="w-100 px-0 h-100"
onClick={() => {
if (!isDropdownDisabled) {
fireEvent('onFocus');
setIsDropdownOpen((prev) => !prev);
}
}}
ref={ref}
>
<Select
isDisabled={isDropdownDisabled}
value={selectOptions.filter((option) => option.value === currentValue)[0] ?? null}
onChange={(selectedOption, actionProps) => {
if (actionProps.action === 'clear') {
setCurrentValue(null);
setInputValue(null);
}
if (actionProps.action === 'select-option') {
setCurrentValue(selectedOption.value);
setInputValue(selectedOption.value);
fireEvent('onSelect');
}
setIsFocused(false);
setIsDropdownOpen(false);
setUserInteracted(true);
}}
options={selectOptions}
styles={customStyles}
isLoading={isDropdownLoading}
menuIsOpen={isDropdownOpen}
onInputChange={onSearchTextChange}
inputValue={searchInputValue}
onMenuOpen={() => {
fireEvent('onFocus');
}}
onMenuInputFocus={() => setIsFocused(true)}
onBlur={() => {
fireEvent('onBlur');
}}
placeholder={placeholder}
menuPortalTarget={document.body}
components={{
@ -479,11 +501,6 @@ export const DropdownV2 = ({
ClearIndicator: CustomClearIndicator,
}}
isClearable
{...{
menuIsOpen: isFocused || undefined,
isFocused: isFocused || undefined,
}}
// select props
icon={icon}
doShowIcon={iconVisibility}
iconColor={iconColor}
@ -494,19 +511,20 @@ export const DropdownV2 = ({
/>
</div>
</div>
<div
className={`${isValid ? '' : visibility ? 'd-flex' : 'none'}`}
style={{
color: errTextColor,
justifyContent: direction === 'right' ? 'flex-start' : 'flex-end',
fontSize: '11px',
fontWeight: '400',
lineHeight: '16px',
display: visibility ? 'block' : 'none',
}}
>
{!isValid && validationError}
</div>
{userInteracted && visibility && !isValid && (
<div
className={'d-flex'}
style={{
color: errTextColor,
justifyContent: direction === 'right' ? 'flex-start' : 'flex-end',
fontSize: '11px',
fontWeight: '400',
lineHeight: '16px',
}}
>
{validationError}
</div>
)}
</>
);
};

View file

@ -7,8 +7,16 @@ export const getInputFocusedColor = ({ accentColor }) => {
return 'var(--primary-accent-strong)';
};
export const getInputBorderColor = ({ isValid, isFocused, fieldBorderColor, accentColor, isLoading, isDisabled }) => {
if (!isValid) {
export const getInputBorderColor = ({
isValid,
isFocused,
fieldBorderColor,
accentColor,
isLoading,
isDisabled,
userInteracted,
}) => {
if (userInteracted && !isValid) {
return 'var(--status-error-strong)';
}

View file

@ -35,6 +35,7 @@ export const Multiselect = function Multiselect({
fireEvent,
componentName,
dataCy,
formId,
}) {
const { label, value, values, display_values, showAllOption } = properties;
const { borderRadius, visibility, disabledState, boxShadow } = styles;
@ -210,6 +211,11 @@ export const Multiselect = function Multiselect({
}
});
}
const formComponent = formId && document.querySelector(`.form-${formId}`);
if (formComponent) {
formComponent.style.zIndex = isOpen ? 4 : '';
}
}}
/>
</div>

View file

@ -12,8 +12,6 @@ import Label from '@/_ui/Label';
const tinycolor = require('tinycolor2');
import { CustomDropdownIndicator, CustomClearIndicator } from '../DropdownV2/DropdownV2';
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor } from '../DropdownV2/utils';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
export const MultiselectV2 = ({
id,
@ -28,7 +26,6 @@ export const MultiselectV2 = ({
validate,
validation,
componentName,
width,
}) => {
let {
label,
@ -65,16 +62,18 @@ export const MultiselectV2 = ({
const isMandatory = validation?.mandatory ?? false;
const multiselectRef = React.useRef(null);
const labelRef = React.useRef(null);
const validationData = validate(selected?.length ? selected?.map((option) => option.value) : null);
const { isValid, validationError } = validationData;
const [validationStatus, setValidationStatus] = useState(
validate(selected?.length ? selected?.map((option) => option.value) : null)
);
const { isValid, validationError } = validationStatus;
const valueContainerRef = React.useRef(null);
const [visibility, setVisibility] = useState(properties.visibility);
const [isMultiSelectLoading, setIsMultiSelectLoading] = useState(multiSelectLoadingState);
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
const [isMultiSelectDisabled, setIsMultiSelectDisabled] = useState(disabledState);
const [isSelectAllSelected, setIsSelectAllSelected] = useState(false);
const [searchInputValue, setSearchInputValue] = useState('');
const _height = padding === 'default' ? `${height}px` : `${height + 4}px`;
const [userInteracted, setUserInteracted] = useState(false);
const [isMultiselectOpen, setIsMultiselectOpen] = useState(false);
useEffect(() => {
@ -89,12 +88,12 @@ export const MultiselectV2 = ({
const _options = advanced ? schema : options;
let _selectOptions = Array.isArray(_options)
? _options
.filter((data) => getResolvedValue(advanced ? data?.visible : data?.visible?.value) ?? true)
.filter((data) => data?.visible ?? true)
.map((data) => ({
...data,
label: getResolvedValue(data?.label),
value: getResolvedValue(data?.value),
isDisabled: getResolvedValue(advanced ? data?.disable : data?.disable?.value) ?? false,
label: data?.label,
value: data?.value,
isDisabled: data?.disable ?? false,
}))
: [];
return _selectOptions;
@ -126,39 +125,41 @@ export const MultiselectV2 = ({
return false;
}
const onChangeHandler = (items, action) => {
setSelected(items);
if (action.action === 'select-option') {
setExposedVariable(
'values',
items.map((item) => item.value)
);
fireEvent('onSelect');
}
setInputValue(items);
fireEvent('onSelect');
setUserInteracted(true);
};
useEffect(() => {
let foundItem = findDefaultItem(values, advanced);
setSelected(foundItem);
setInputValue(foundItem);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectOptions]);
useEffect(() => {
let foundItem = findDefaultItem(values, advanced, true);
setSelected(foundItem);
if (advanced) {
let foundItem = findDefaultItem(values, advanced, true);
setInputValue(foundItem);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [advanced, JSON.stringify(schema), JSON.stringify(values)]);
}, [advanced, JSON.stringify(values), JSON.stringify(schema)]);
useEffect(() => {
if (!advanced) {
let foundItem = findDefaultItem(values, advanced, true);
setInputValue(foundItem);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [advanced, JSON.stringify(values)]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable(
'selectedOptions',
Array.isArray(selected) && selected?.map(({ label, value }) => ({ label, value }))
);
setExposedVariable(
'options',
Array.isArray(selectOptions) && selectOptions?.map(({ label, value }) => ({ label, value }))
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(selected), selectOptions]);
}, [selectOptions]);
useEffect(() => {
if (isInitialRender.current) return;
@ -187,13 +188,17 @@ export const MultiselectV2 = ({
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isValid', isValid);
}, [isValid]);
const validationStatus = validate(selected?.length ? selected?.map((option) => option.value) : null);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
}, [validate]);
useEffect(() => {
const defaultItems = findDefaultItem(values, advanced, true);
const exposedVariables = {
clear: async function () {
setSelected([]);
setInputValue([]);
},
setVisibility: async function (value) {
setVisibility(value);
@ -210,7 +215,7 @@ export const MultiselectV2 = ({
isDisabled: disabledState,
isMandatory: isMandatory,
isValid: isValid,
selectedOptions: Array.isArray(selected) && selected?.map(({ label, value }) => ({ label, value })),
selectedOptions: Array.isArray(defaultItems) && defaultItems?.map(({ label, value }) => ({ label, value })),
options: Array.isArray(selectOptions) && selectOptions?.map(({ label, value }) => ({ label, value })),
};
setExposedVariables(exposedVariables);
@ -237,7 +242,7 @@ export const MultiselectV2 = ({
newSelected.push(...optionsToAdd);
}
});
setSelected(newSelected);
setInputValue(newSelected);
}
});
@ -247,7 +252,7 @@ export const MultiselectV2 = ({
// Check if array provided is a list of objects with value key
const _value = value.map((val) => (isObject(val) && has(val, 'value') ? val.value : val));
const newSelected = selected.filter((option) => !_value.includes(option.value));
setSelected(newSelected);
setInputValue(newSelected);
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -276,6 +281,17 @@ export const MultiselectV2 = ({
}
};
const setInputValue = (values) => {
setSelected(values);
setExposedVariables({
values: values.map((item) => item.value),
selectedOptions: Array.isArray(values) && values?.map(({ label, value }) => ({ label, value })),
});
const validationStatus = validate(values?.length ? values?.map((option) => option.value) : null);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
};
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside, { capture: true });
return () => {
@ -312,6 +328,7 @@ export const MultiselectV2 = ({
accentColor,
isLoading: isMultiSelectLoading,
isDisabled: isMultiSelectDisabled,
userInteracted,
}),
backgroundColor: getInputBackgroundColor({
fieldBackgroundColor,
@ -400,7 +417,6 @@ export const MultiselectV2 = ({
}),
};
const _width = (labelWidth / 100) * 70; // Max width which label can go is 70% for better UX calculate width based on this value
return (
<>
<div
@ -484,8 +500,13 @@ export const MultiselectV2 = ({
containerRef={valueContainerRef}
showAllOption={showAllOption}
isSelectAllSelected={isSelectAllSelected}
setIsSelectAllSelected={setIsSelectAllSelected}
setSelected={setSelected}
setIsSelectAllSelected={(value) => {
setIsSelectAllSelected(value);
if (!value) {
fireEvent('onSelect');
}
}}
setSelected={setInputValue}
iconColor={iconColor}
optionsLoadingState={optionsLoadingState && advanced}
darkMode={darkMode}
@ -495,19 +516,20 @@ export const MultiselectV2 = ({
/>
</div>
</div>
<div
className={`${isValid ? '' : visibility ? 'd-flex' : 'none'}`}
style={{
color: errTextColor,
justifyContent: direction === 'right' ? 'flex-start' : 'flex-end',
fontSize: '11px',
fontWeight: '400',
lineHeight: '16px',
display: visibility ? 'block' : 'none',
}}
>
{!isValid && validationError}
</div>
{userInteracted && visibility && !isValid && (
<div
className={'d-flex'}
style={{
color: errTextColor,
justifyContent: direction === 'right' ? 'flex-start' : 'flex-end',
fontSize: '11px',
fontWeight: '400',
lineHeight: '16px',
}}
>
{validationError}
</div>
)}
</>
);
};

View file

@ -49,7 +49,8 @@ export const NumberInput = function NumberInput({
const [loading, setLoading] = useState(loadingState);
const [showValidationError, setShowValidationError] = useState(false);
const [value, setValue] = React.useState(Number(parseFloat(properties.value).toFixed(properties.decimalPlaces)));
const { isValid, validationError } = validate(value);
const [validationStatus, setValidationStatus] = useState(validate(value));
const { isValid, validationError } = validationStatus;
const [isFocused, setIsFocused] = useState(false);
const inputRef = useRef(null);
@ -65,31 +66,22 @@ export const NumberInput = function NumberInput({
}, [label]);
useEffect(() => {
setValue(Number(parseFloat(value).toFixed(properties.decimalPlaces)));
setInputValue(Number(parseFloat(value).toFixed(properties.decimalPlaces)));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.decimalPlaces]);
useEffect(() => {
setValue(Number(parseFloat(properties.value).toFixed(properties.decimalPlaces)));
setInputValue(Number(parseFloat(properties.value).toFixed(properties.decimalPlaces)));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.value]);
const handleBlur = (e) => {
setValue(Number(parseFloat(e.target.value).toFixed(properties.decimalPlaces)));
setInputValue(Number(parseFloat(e.target.value).toFixed(properties.decimalPlaces)));
setShowValidationError(true);
e.stopPropagation();
fireEvent('onBlur');
setIsFocused(false);
};
useEffect(() => {
if (isInitialRender.current) return;
if (!isNaN(value)) {
setExposedVariable('value', value);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isMandatory', isMandatory);
@ -114,12 +106,6 @@ export const NumberInput = function NumberInput({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disable]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isValid', isValid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isValid]);
useEffect(() => {
disable !== disabledState && setDisable(disabledState);
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -128,11 +114,19 @@ export const NumberInput = function NumberInput({
visibility !== properties.visibility && setVisibility(properties.visibility);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.visibility]);
useEffect(() => {
loading !== loadingState && setLoading(loadingState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loadingState]);
useEffect(() => {
if (isInitialRender.current) return;
const validationStatus = validate(value);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
}, [validate]);
useEffect(() => {
const exposedVariables = {
setFocus: async function () {
@ -144,14 +138,12 @@ export const NumberInput = function NumberInput({
setText: async function (text) {
if (text) {
const newValue = Number(parseFloat(text));
setValue(newValue);
setExposedVariable('value', text);
setInputValue(newValue);
fireEvent('onChange');
}
},
clear: async function () {
setValue('');
setExposedVariable('value', '');
setInputValue('');
fireEvent('onChange');
},
setLoading: async function (loading) {
@ -244,14 +236,13 @@ export const NumberInput = function NumberInput({
// eslint-disable-next-line import/namespace
const handleChange = (e) => {
setValue(Number(parseFloat(e.target.value)));
if (e.target.value == '') {
setValue(null);
setExposedVariable('value', null);
setInputValue(null);
fireEvent('onChange');
} else {
setInputValue(Number(parseFloat(e.target.value)));
}
if (!isNaN(Number(parseFloat(e.target.value)))) {
setExposedVariable('value', Number(parseFloat(e.target.value)));
fireEvent('onChange');
}
};
@ -260,22 +251,30 @@ export const NumberInput = function NumberInput({
e.preventDefault(); // Prevent the default button behavior (form submission, page reload)
const newValue = (value || 0) + 1;
setValue(newValue);
setInputValue(newValue);
if (!isNaN(newValue)) {
setExposedVariable('value', newValue);
fireEvent('onChange');
}
};
const handleDecrement = (e) => {
e.preventDefault();
const newValue = (value || 0) - 1;
setValue(newValue);
setInputValue(newValue);
if (!isNaN(newValue)) {
setExposedVariable('value', newValue);
fireEvent('onChange');
}
};
const setInputValue = (value) => {
setValue(value);
if (!isNaN(value)) {
setExposedVariable('value', value);
}
const validationStatus = validate(value);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
};
const loaderStyle = {
right:
direction === 'right' &&
@ -375,8 +374,7 @@ export const NumberInput = function NumberInput({
autoComplete="off"
onKeyUp={(e) => {
if (e.key === 'Enter') {
setValue(e.target.value);
setExposedVariable('value', e.target.value);
setInputValue(e.target.value);
fireEvent('onEnterPressed');
}
}}

View file

@ -48,7 +48,8 @@ export const PasswordInput = function PasswordInput({
const [disable, setDisable] = useState(disabledState || loadingState);
const [passwordValue, setPasswordValue] = useState(properties.value);
const [visibility, setVisibility] = useState(properties.visibility);
const { isValid, validationError } = validate(passwordValue);
const [validationStatus, setValidationStatus] = useState(validate(passwordValue));
const { isValid, validationError } = validationStatus;
const [showValidationError, setShowValidationError] = useState(false);
const [labelWidth, setLabelWidth] = useState(0);
const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side';
@ -152,12 +153,6 @@ export const PasswordInput = function PasswordInput({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loadingState]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isValid', isValid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isValid]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isMandatory', isMandatory);
@ -184,11 +179,17 @@ export const PasswordInput = function PasswordInput({
useEffect(() => {
if (isInitialRender.current) return;
setPasswordValue(properties.value);
setExposedVariable('value', properties?.value ?? '');
setInputValue(properties?.value || '');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.value]);
useEffect(() => {
if (isInitialRender.current) return;
const validationStatus = validate(passwordValue);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
}, [validate]);
useEffect(() => {
const exposedVariables = {
setFocus: async function () {
@ -198,13 +199,11 @@ export const PasswordInput = function PasswordInput({
textInputRef.current.blur();
},
setText: async function (text) {
setPasswordValue(text);
setExposedVariable('value', text);
setInputValue(text);
fireEvent('onChange');
},
clear: async function () {
setPasswordValue('');
setExposedVariable('value', '');
setInputValue('');
fireEvent('onChange');
},
setLoading: async function (loading) {
@ -228,7 +227,6 @@ export const PasswordInput = function PasswordInput({
value: properties?.value ?? '',
};
setPasswordValue(properties.value ?? '');
setExposedVariables(exposedVariables);
isInitialRender.current = false;
@ -254,6 +252,15 @@ export const PasswordInput = function PasswordInput({
}
return false;
});
const setInputValue = (value) => {
setPasswordValue(value);
setExposedVariable('value', value);
const validationStatus = validate(value);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
};
const renderInput = () => (
<>
<div
@ -361,14 +368,12 @@ export const PasswordInput = function PasswordInput({
autoComplete="new-password"
onKeyUp={(e) => {
if (e.key === 'Enter') {
setPasswordValue(e.target.value);
setExposedVariable('value', e.target.value);
setInputValue(e.target.value);
fireEvent('onEnterPressed');
}
}}
onChange={(e) => {
setPasswordValue(e.target.value);
setExposedVariable('value', e.target.value);
setInputValue(e.target.value);
fireEvent('onChange');
}}
onBlur={(e) => {

View file

@ -47,8 +47,8 @@ export const TextInput = function TextInput({
const [disable, setDisable] = useState(disabledState || loadingState);
const [value, setValue] = useState(properties.value);
const [visibility, setVisibility] = useState(properties.visibility);
// const isValid = true; // TODO: remove this and uncomment the below line
const { isValid, validationError } = validate(value);
const [validationStatus, setValidationStatus] = useState(validate(value));
const { isValid, validationError } = validationStatus;
const [showValidationError, setShowValidationError] = useState(false);
const [labelWidth, setLabelWidth] = useState(0);
@ -156,18 +156,18 @@ export const TextInput = function TextInput({
}, [loadingState]);
useEffect(() => {
setExposedVariable('isValid', isValid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isValid]);
if (isInitialRender.current) return;
const validationStatus = validate(value);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
}, [validate]);
useEffect(() => {
if (isInitialRender.current) return;
if (properties.value === undefined) {
setValue('');
setExposedVariable('value', '');
setInputValue('');
} else {
setValue(properties.value);
setExposedVariable('value', properties.value);
setInputValue(properties.value);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -200,13 +200,11 @@ export const TextInput = function TextInput({
useEffect(() => {
const exposedVariables = {
setText: async function (text) {
setValue(text);
setExposedVariable('value', text);
setInputValue(text);
fireEvent('onChange');
},
clear: async function () {
setValue('');
setExposedVariable('value', '');
setInputValue('');
fireEvent('onChange');
},
setFocus: async function () {
@ -241,13 +239,20 @@ export const TextInput = function TextInput({
isVisible: visibility,
isDisabled: disable,
};
setValue(properties.value);
setExposedVariables(exposedVariables);
isInitialRender.current = false;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const setInputValue = (value) => {
setValue(value);
setExposedVariable('value', value);
const validationStatus = validate(value);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
};
const iconName = styles.icon; // Replace with the name of the icon you want
// eslint-disable-next-line import/namespace
const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName];
@ -320,14 +325,12 @@ export const TextInput = function TextInput({
} validation-without-icon`}
onKeyUp={(e) => {
if (e.key === 'Enter') {
setValue(e.target.value);
setExposedVariable('value', e.target.value);
setInputValue(e.target.value);
fireEvent('onEnterPressed');
}
}}
onChange={(e) => {
setValue(e.target.value);
setExposedVariable('value', e.target.value);
setInputValue(e.target.value);
fireEvent('onChange');
}}
onBlur={(e) => {

View file

@ -12,13 +12,11 @@ const Switch = ({
borderColor,
setOn,
styles,
setExposedVariable,
fireEvent,
setUserInteracted,
}) => {
const handleToggleChange = () => {
setOn(!on);
setExposedVariable('value', !on);
fireEvent('onChange');
setUserInteracted(true);
};
@ -105,8 +103,8 @@ export const ToggleSwitchV2 = ({
const [on, setOn] = useState(Boolean(defaultValue));
const label = properties.label;
const isMandatory = validation?.mandatory ?? false;
const { isValid, validationError } = validate(on);
const [showValidationError, setShowValidationError] = useState(true);
const [validationStatus, setValidationStatus] = useState(validate(on));
const { isValid, validationError } = validationStatus;
const [loading, setLoading] = useState(properties?.loadingState);
const [disable, setDisable] = useState(properties.disabledState || properties.loadingState);
const [visibility, setVisibility] = useState(properties.visibility);
@ -123,17 +121,24 @@ export const ToggleSwitchV2 = ({
};
// Exposing the initially set false value once on load
const setInputValue = (value) => {
setOn(value);
setExposedVariable('value', value);
const validationStatus = validate(value);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
};
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('value', defaultValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
setOn(defaultValue);
setInputValue(defaultValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultValue]);
const toggle = () => {
setOn(!on);
setInputValue(!on);
setUserInteracted(true);
};
@ -164,6 +169,13 @@ export const ToggleSwitchV2 = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMandatory]);
useEffect(() => {
if (isInitialRender.current) return;
const validationStatus = validate(on);
setValidationStatus(validationStatus);
setExposedVariable('isValid', validationStatus?.isValid);
}, [validate]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isLoading', loading);
@ -180,17 +192,11 @@ export const ToggleSwitchV2 = ({
setExposedVariable('isDisabled', disable);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disable]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('isValid', isValid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isValid]);
useEffect(() => {
const exposedVariables = {
setValue: async function (value) {
setOn(value);
setExposedVariable('value', value);
setInputValue(value);
setUserInteracted(true);
},
setVisibility: async function (state) {
@ -214,15 +220,13 @@ export const ToggleSwitchV2 = ({
value: defaultValue,
};
setExposedVariables(exposedVariables);
setOn(defaultValue);
isInitialRender.current = false;
}, []);
useEffect(() => {
setExposedVariable('toggle', async function () {
setExposedVariable('value', !on);
setInputValue(!on);
fireEvent('onChange');
setOn(!on);
setUserInteracted(true);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -270,15 +274,11 @@ export const ToggleSwitchV2 = ({
onChange={toggleValue}
color={toggleSwitchColor}
alignment={alignment}
validationError={validationError}
isValid={isValid}
showValidationError={showValidationError}
properties={properties}
setShowValidationError={setShowValidationError}
borderColor={borderColor}
setOn={setOn}
setOn={setInputValue}
styles={styles}
setExposedVariable={setExposedVariable}
fireEvent={fireEvent}
setUserInteracted={setUserInteracted}
/>
@ -294,7 +294,7 @@ export const ToggleSwitchV2 = ({
}}
>
{renderInput()}
{showValidationError && isMandatory && userInteracted && !on && visibility && (
{userInteracted && visibility && !isValid && (
<div
data-cy={`${String(componentName).toLowerCase()}-invalid-feedback`}
style={{

View file

@ -742,9 +742,9 @@ class ManageGroupPermissionResourcesComponent extends React.Component {
<p className="tj-text-sm d-flex align-items-center">
<Avatar
className="name-avatar"
avatarId={user.avatarId}
text={`${user.first_name ? user.first_name[0] : ''}${
user.last_name ? user.last_name[0] : ''
avatarId={user?.avatarId}
text={`${user.firstName ? user.firstName[0] : ''}${
user.lastName ? user.lastName[0] : ''
}`}
/>
<span>{`${user?.firstName ?? ''} ${user?.lastName ?? ''}`}</span>

View file

@ -4,6 +4,8 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Tooltip from 'react-bootstrap/Tooltip';
import { useTranslation } from 'react-i18next';
import classnames from 'classnames';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
export const DarkModeToggle = function DarkModeToggle({
darkMode = false,
@ -11,8 +13,14 @@ export const DarkModeToggle = function DarkModeToggle({
tooltipPlacement = 'bottom',
showText = false,
}) {
const setResolvedGlobals = useStore((state) => state.setResolvedGlobals, shallow);
const appMode = useStore((state) => state.globalSettings.appMode, shallow);
const toggleDarkMode = () => {
switchDarkMode(!darkMode);
if (appMode === 'auto') {
setResolvedGlobals('theme', { name: !darkMode ? 'dark' : 'light' });
}
};
const { t } = useTranslation();

View file

@ -21,6 +21,7 @@ export const SearchBox = forwardRef(
autoFocus = false,
showClearButton,
initialValue = '',
clearTextOnBlur = true,
},
ref
) => {
@ -39,7 +40,7 @@ export const SearchBox = forwardRef(
};
const handleClickOutside = (event) => {
if (ref?.current && !ref.current.contains(event.target)) {
if (ref?.current && !ref.current.contains(event.target) && clearTextOnBlur) {
clearSearchText();
// Your function to be triggered
}

View file

@ -10,10 +10,10 @@ export function SortableList({ items, onChange, renderItem }) {
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: { delay: 150 },
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
// useSensor(KeyboardSensor, {
// coordinateGetter: sortableKeyboardCoordinates,
// })
);
const shouldFreeze = useStore((state) => state.isVersionReleased || state.isEditorFreezed);

View file

@ -123,7 +123,6 @@ $btn-dark-color: #FFFFFF;
.page-selector-panel-body {
height: 100%;
padding: 12px 16px;
border-right: 1px solid #DFE3E6;

View file

@ -12,7 +12,7 @@
}
.page-name {
font-size: 14px;
font-size: 12px;
}
.navigation-area {

View file

@ -766,7 +766,7 @@ button {
right: 300px;
left: 48px;
overflow-y: scroll;
overflow-x: auto;
overflow-x: hidden;
-webkit-box-pack: center;
justify-content: center;
-webkit-box-align: center;
@ -3875,6 +3875,18 @@ input[type="text"] {
}
}
.form-ele{
.DateRangePicker_picker{
top: 40px !important;
}
.daterange-picker-widget{
.DateInput_fang {
visibility: hidden !important;
}
}
}
.fw-400 {
font-weight: 400;
}
@ -9195,6 +9207,17 @@ tbody {
.spin-loader {
position: fixed;
width: 100%;
justify-content: center;
align-items: center;
height: 100vh;
&.theme-dark {
background-color: #1f2936;
.load {
background-color: #1f2936;
}
}
.load {
display: flex;
@ -12701,7 +12724,7 @@ color: var(--text-default);
position: absolute;
display: flex;
justify-content: center;
top: 55px;
top: 65px;
.released-version-popup-cover {
width: 250px;

View file

@ -21,6 +21,16 @@
align-items: center;
}
&.table-pagination-btn{
&:disabled {
svg {
path {
fill: var(--slate8) !important;
}
}
}
}
&:disabled {
background: var(--slate6) !important;
color: var(--slate9) !important;

View file

@ -25,6 +25,7 @@
justify-content: center;
align-items: center;
height: 100vh;
background-color: white;
}
.load.dark-loader {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 365 KiB

After

Width:  |  Height:  |  Size: 364 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 342 KiB

After

Width:  |  Height:  |  Size: 342 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -47,7 +47,12 @@ const WorkspaceNameFormCE = () => {
shallow
);
useEnterKeyPress(() => handleSubmit());
const { setOnboardingStepsCompleted } = useOnboardingStore(
(state) => ({
setOnboardingStepsCompleted: state.setOnboardingStepsCompleted,
}),
shallow
);
const [formData, setFormData] = useState({ workspaceName: workspaceName });
const [error, setError] = useState('');
const [isFormValid, setIsFormValid] = useState(true);
@ -121,6 +126,7 @@ const WorkspaceNameFormCE = () => {
try {
await setWorkspaceName(formData.workspaceName);
await onboardUserOrCreateAdmin();
setOnboardingStepsCompleted();
} catch (error) {
const errorMessage = error?.error || 'Something went wrong. Please try again.';
toast.error(errorMessage);

View file

@ -3,8 +3,12 @@ import OnboardingFormWrapper from '../OnboardingFormWrapper/OnboardingFormWrappe
import './resources/styles/onboarding-ui-wrapper.styles.scss';
const OnboardingUIWrapper = ({ children: components }) => {
const pageLocation = window.location.pathname;
if (pageLocation == '/setup') {
const isEmptyPath = window.location.pathname == '/';
const isSetupRoute = window.location.pathname.split('/').pop().toLowerCase() === 'setup';
const pathEndSegments = window.location.pathname.split('/').filter(Boolean).slice(-2);
const isInvitationRoute =
pathEndSegments.length === 2 && pathEndSegments[0] === 'invitations' && pathEndSegments[1]?.length > 0;
if (isSetupRoute || isInvitationRoute || isEmptyPath) {
return (
<div className="onboarding-setup-wrapper">
<OnboardingFormWrapper>{components}</OnboardingFormWrapper>

View file

@ -7,8 +7,9 @@ import invitationsStore from '@/modules/onboarding/stores/invitationsStore';
import { LinkExpiredPage } from '@/ConfirmationPage/LinkExpiredPage';
import { utils } from '@/modules/common/helpers';
import { getSubpath } from '@/_helpers/routes';
const PostOnboardingComponent = () => null;
import { TJLoader } from '@/_ui/TJLoader/TJLoader';
import useOnboardingStore from '@/modules/onboarding/stores/onboardingStore';
const PostOnboardingComponent = () => <TJLoader />;
export const InvitationPage = (darkMode = false) => {
const [isLoading, setIsLoading] = useState(true);
@ -23,8 +24,8 @@ export const InvitationPage = (darkMode = false) => {
const source = searchParams.get('source');
const redirectTo = searchParams.get('redirectTo');
const { initiateInvitedUserOnboarding, isOnboardingStepsCompleted } = invitationsStore();
const { initiateInvitedUserOnboarding } = invitationsStore();
const { isOnboardingStepsCompleted } = useOnboardingStore();
useEffect(() => {
getUserDetails();
// eslint-disable-next-line react-hooks/exhaustive-deps

View file

@ -1,19 +1,23 @@
import React from 'react';
import React, { useEffect } from 'react';
import { OnboardingBackgroundWrapper, OnboardingQuestions } from '@/modules/onboarding/components';
import { SetupAdminForm } from './components';
import { GeneralFeatureImage } from '@/modules/common/components';
import useOnboardingStore from '@/modules/onboarding/stores/onboardingStore';
import { shallow } from 'zustand/shallow';
import { TJLoader } from '@/_ui/TJLoader/TJLoader';
const PostOnboardingComponent = () => <TJLoader />;
const SetupAdminPageCE = () => {
const { currentStep } = useOnboardingStore(
const { currentStep, isOnboardingStepsCompleted } = useOnboardingStore(
(state) => ({
currentStep: state.currentStep,
isOnboardingStepsCompleted: state.isOnboardingStepsCompleted,
}),
shallow
);
if (currentStep > 0) {
if (isOnboardingStepsCompleted && PostOnboardingComponent) {
return <PostOnboardingComponent />;
} else if (currentStep > 0) {
return <OnboardingQuestions />;
}

View file

@ -21,6 +21,7 @@ const useCEOnboardingStore = create(
currentStep: 0,
totalSteps: 1,
accountCreated: false,
isOnboardingStepsCompleted: false,
// Action to update admin details
setAdminDetails: (details) =>
@ -40,6 +41,7 @@ const useCEOnboardingStore = create(
// action to set current step
setCurrentStep: (step) => set({ currentStep: step }),
setOnboardingStepsCompleted: () => set({ isOnboardingStepsCompleted: true }),
// Action to reset the store
resetStore: () =>
set({
@ -75,7 +77,8 @@ const useCEOnboardingStore = create(
const path = getSubpath()
? `${getSubpath()}/${session?.current_organization_slug}/apps/${appId}`
: `/${session?.current_organization_slug}/apps/${appId}`;
window.location.href = path;
history.pushState(null, null, path);
window.location.reload();
},
setAccountCreated: (value) => set({ accountCreated: value }),

View file

@ -1 +1 @@
3.0.0-ce-lts
3.0.1-ce-lts

View file

@ -59,7 +59,7 @@ export class OrganizationConstantController {
@Get('public/:app_slug')
async getConstantsFromPublicApp(@App() app) {
const result = await this.organizationConstantsService.allEnvironmentConstants(
app.OrganizationId,
app.organizationId,
false,
OrganizationConstantType.GLOBAL
);

View file

@ -36,11 +36,13 @@ export const buildComponentMetaDefinition = (components = {}) => {
componentMeta.definition.properties,
currentComponentData?.component?.definition?.properties,
(objValue, srcValue) => {
if (
['Table', 'DropdownV2', 'MultiselectV2'].includes(currentComponentData?.component?.component) &&
if (['Table'].includes(currentComponentData?.component?.component) && isArray(objValue)) {
return srcValue;
} else if (
['DropdownV2', 'MultiselectV2'].includes(currentComponentData?.component?.component) &&
isArray(objValue)
) {
return srcValue;
return isArray(srcValue) ? srcValue : Object.values(srcValue);
}
}
),

View file

@ -146,8 +146,8 @@ export const buttonGroupConfig = {
visibility: { value: '{{true}}' },
borderRadius: { value: '{{4}}' },
disabledState: { value: '{{false}}' },
selectedTextColor: { value: '' },
selectedBackgroundColor: { value: '' },
selectedTextColor: { value: '#FFFFFF' },
selectedBackgroundColor: { value: '#4368E3' },
},
},
};

View file

@ -325,6 +325,20 @@ export class AppImportExportService {
await manager.save(toUpdateDataQueries);
}
}
// update Global settings of created versions
const appVersionIds = Object.values(resourceMapping.appVersionMapping);
const newAppVersions = await manager.find(AppVersion, {
where: {
id: In(appVersionIds),
},
select: ['id', 'globalSettings'],
});
for (const appVersion of newAppVersions) {
if (appVersion.globalSettings) {
const updatedGlobalSettings = updateEntityReferences(appVersion.globalSettings, mappings);
await manager.update(AppVersion, { id: appVersion.id }, { globalSettings: updatedGlobalSettings });
}
}
}
async createImportedAppForUser(manager: EntityManager, appParams: any, user: User, isGitApp = false): Promise<App> {
@ -1260,6 +1274,23 @@ export class AppImportExportService {
return appResourceMappings;
}
createViewerNavigationVisibilityForImportedApp(importedVersion: AppVersion) {
let pageSettings = {};
if (importedVersion.pageSettings) {
pageSettings = { ...importedVersion.pageSettings };
} else {
pageSettings = {
properties: {
disableMenu: {
value: `{{${!importedVersion.showViewerNavigation}}}`,
fxActive: false,
},
},
};
}
return pageSettings;
}
async createAppVersionsForImportedApp(
manager: EntityManager,
user: User,
@ -1300,7 +1331,7 @@ export class AppImportExportService {
version.showViewerNavigation = appVersion.showViewerNavigation;
version.homePageId = appVersion.homePageId;
version.globalSettings = appVersion.globalSettings;
version.pageSettings = appVersion.pageSettings;
version.pageSettings = this.createViewerNavigationVisibilityForImportedApp(appVersion);
} else {
version.showViewerNavigation = appVersion.definition?.showViewerNavigation || true;
version.homePageId = appVersion.definition?.homePageId;
@ -1318,7 +1349,7 @@ export class AppImportExportService {
};
} else {
version.globalSettings = appVersion.definition?.globalSettings;
version.pageSettings = appVersion.definition?.pageSettings;
version.pageSettings = this.createViewerNavigationVisibilityForImportedApp(appVersion);
}
}

View file

@ -392,6 +392,15 @@ export class AppsService {
dataQueryMapping: oldDataQueryToNewMapping,
});
if (appVersion.globalSettings) {
const globalSettings = appVersion.globalSettings;
const updatedGlobalSettings = updateEntityReferences(globalSettings, {
...oldDataQueryToNewMapping,
...oldComponentToNewComponentMapping,
});
await manager.update(AppVersion, { id: appVersion.id }, { globalSettings: updatedGlobalSettings });
}
await this.updateEventActionsForNewVersionWithNewMappingIds(
manager,
appVersion.id,

View file

@ -97,13 +97,13 @@ export class ComponentsService {
componentData[column === 'others' ? 'displayPreferences' : column],
updatedDefinition[column],
(objValue, srcValue) => {
if (
(componentData.type === 'Table' ||
componentData.type === 'DropdownV2' ||
componentData.type === 'MultiselectV2') &&
if (componentData.type === 'Table' && _.isArray(objValue)) {
return srcValue;
} else if (
(componentData.type === 'DropdownV2' || componentData.type === 'MultiselectV2') &&
_.isArray(objValue)
) {
return srcValue;
return _.isArray(srcValue) ? srcValue : Object.values(srcValue);
}
}
);