This commit is contained in:
Vijaykant Yadav 2024-11-18 18:38:39 +05:30
commit 0224d3760a
135 changed files with 2729 additions and 868 deletions

View file

@ -20,18 +20,24 @@ jobs:
if: "${{ github.event.release }}"
steps:
- name: Checkout code
if: "!contains(github.event.release.tag_name, 'CE-LTS')"
- name: Checkout code to main
if: "!contains(github.event.release.tag_name, 'ce-lts')"
uses: actions/checkout@v2
with:
ref: refs/heads/main
- name: Checkout code
if: "contains(github.event.release.tag_name, 'CE-LTS')"
- name: Checkout code to LTS-2.50
if: "contains(github.event.release.tag_name, '2.50')"
uses: actions/checkout@v2
with:
ref: refs/heads/lts-2.50
- name: Checkout code to LTS-3.0
if: "contains(github.event.release.tag_name, '-ce-lts')"
uses: actions/checkout@v2
with:
ref: refs/heads/lts-3.0
# Create Docker Buildx builder with platform configuration
- name: Set up Docker Buildx
run: |
@ -53,27 +59,40 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image for non-LTS tag
if: "!contains(github.event.release.tag_name, 'CE-LTS')"
- name: Build and Push Docker image for beta tag
if: "!contains(github.event.release.tag_name, '-ce-lts')"
uses: docker/build-push-action@v4
with:
context: .
file: docker/production.Dockerfile
push: true
tags: tooljet/tooljet-ce:${{ github.event.release.tag_name }},tooljet/tooljet-ce:latest
tags: tooljet/tooljet-ce:${{ github.event.release.tag_name }},tooljet/tooljet-ce:ce-latest
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image for LTS tag
if: "contains(github.event.release.tag_name, 'CE-LTS')"
- name: Build and Push Docker image for LTS 2.50 tag
if: "contains(github.event.release.tag_name, '2.50')"
uses: docker/build-push-action@v4
with:
context: .
file: docker/production.Dockerfile
push: true
tags: tooljet/tooljet-ce:${{ github.event.release.tag_name }},tooljet/tooljet-ce:CE-LTS-latest
tags: tooljet/tooljet-ce:${{ github.event.release.tag_name }}
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image for LTS 3.0 tag
if: "contains(github.event.release.tag_name, '-ce-lts')"
uses: docker/build-push-action@v4
with:
context: .
file: docker/production.Dockerfile
push: true
tags: tooljet/tooljet-ce:${{ github.event.release.tag_name }},tooljet/tooljet-ce:ce-lts-latest
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
@ -186,7 +205,7 @@ jobs:
sudo docker images
# Update docker-compose.yml with the new image for tooljet service
sed -i '/^[[:space:]]*tooljet:/,/^[[:space:]]*[^:]*$/ { /^[[:space:]]*image:[[:space:]]*tooljet\/tooljet-ce/s|\(image:[[:space:]]*\).*|\1tooljet/tooljet-ce:'"${{ github.event.release.tag_name }}"'| }' docker-compose.yml
sed -i '/^[[:space:]]*tooljet:/,/^[[:space:]]*[^[:space:]]/ { /^[[:space:]]*image:/s|image:.*|image: tooljet/tooljet-ce:'"${{ github.event.release.tag_name }}"'| }' docker-compose.yml
# check the updated docker-compose.yml file
cat docker-compose.yml
@ -195,4 +214,4 @@ jobs:
sudo docker-compose up -d
#View containers
sudo docker ps
sudo docker ps

View file

@ -1 +1 @@
3.0.0-ce-beta
3.0.2-ce-lts

View file

@ -1 +1 @@
3.0.0-ce-beta
3.0.2-ce-lts

View file

@ -215,6 +215,7 @@
"@tooljet-plugins/graphql": "file:packages/graphql",
"@tooljet-plugins/grpc": "file:packages/grpc",
"@tooljet-plugins/influxdb": "file:packages/influxdb",
"@tooljet-plugins/jira": "file:packages/jira",
"@tooljet-plugins/mailgun": "file:packages/mailgun",
"@tooljet-plugins/mariadb": "file:packages/mariadb",
"@tooljet-plugins/minio": "file:packages/minio",

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) => {
@ -595,7 +596,10 @@ export default function Grid({ gridWidth, currentLayout }) {
isDragOnTableORCalendar = tableElem.contains(e.inputEvent.target);
}
if (box?.component?.component === 'Calendar') {
const calenderElem = e.target.querySelector('.rbc-month-view');
const calenderElem =
e.target.querySelector('.rbc-month-view') ||
e.target.querySelector('.rbc-time-view') ||
e.target.querySelector('.rbc-day-view');
isDragOnTableORCalendar = calenderElem.contains(e.inputEvent.target);
}
@ -722,7 +726,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 +861,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

@ -201,8 +201,9 @@ export const CreateVersion = ({ showCreateAppVersion, setShowCreateAppVersion })
width: '100%',
}}
>
{/* EE - change to development */}
<div className="" data-cy="workspace-constant-helper-text">
The new version will be created in development environment
The new version will be created in production environment
</div>
</div>
</Alert>

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

@ -371,7 +371,7 @@ const JoinConstraint = ({ darkMode, index, onRemove, onChange, data }) => {
variant="ghostBlue"
size="sm"
onClick={() => {
const newData = { ...data };
const newData = deepClone(data);
set(newData, 'conditions.conditionsList', [...conditionsList, { operator: '=' }]);
onChange(newData);
}}

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

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { Calendar as ReactCalendar, momentLocalizer } from 'react-big-calendar';
import moment from 'moment';
import 'react-big-calendar/lib/css/react-big-calendar.css';
@ -54,7 +54,7 @@ export const Calendar = function ({
const [currentDate, setCurrentDate] = useState(defaultDate);
const [eventPopoverOptions, setEventPopoverOptions] = useState({ show: false });
const [defaultView, setDefaultValue] = useState(allowedCalendarViews[0]);
const isInitialRender = useRef(true);
const eventPropGetter = (event) => {
const backgroundColor = event.color;
@ -100,10 +100,10 @@ export const Calendar = function ({
const view = allowedCalendarViews.includes(properties.defaultView)
? properties.defaultView
: allowedCalendarViews[0];
if (currentView !== view) {
setDefaultValue(view);
if (currentView !== view || isInitialRender.current) {
setExposedVariable('currentView', view);
setCurrentView(view);
isInitialRender.current = false;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.defaultView]);
@ -145,10 +145,9 @@ export const Calendar = function ({
endAccessor="end"
style={style}
views={allowedCalendarViews}
defaultView={defaultView}
view={defaultView}
defaultView={properties.defaultView || allowedCalendarViews[0]}
view={currentView}
onView={(view) => {
setDefaultValue(view);
setExposedVariable('currentView', view);
setCurrentView(view);
fireEvent('onCalendarViewChange');

View file

@ -19,7 +19,6 @@ export const Form = function Form(props) {
component,
width,
height,
removeComponent,
styles,
setExposedVariable,
setExposedVariables,
@ -28,11 +27,6 @@ export const Form = function Form(props) {
properties,
resetComponent = () => {},
dataCy,
paramUpdated,
currentLayout,
mode,
getContainerProps,
containerProps,
} = props;
const childComponents = useStore((state) => state.getChildComponents(id), shallow);
const { visibility, disabledState, borderRadius, borderColor, boxShadow } = styles;
@ -307,12 +301,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,10 @@ const RenderSchema = ({ component, id, onOptionChange, onOptionsChange }) => {
setExposedVariable={setExposedVariable}
setExposedVariables={setExposedVariables}
validate={validate}
fireEvent={() => {}}
darkMode={darkMode}
fireEvent={fireEvent}
formId={formId}
id={id}
/>
);
};

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,10 @@ export const Table = React.memo(
});
const changesToBeSavedAndExposed = { dataUpdates: newDataUpdates, changeSet: newChangeset };
mergeToTableDetails(changesToBeSavedAndExposed);
fireEvent('onCellValueChanged');
return setExposedVariables({ ...changesToBeSavedAndExposed, updatedData: clonedTableData });
setExposedVariables({ ...changesToBeSavedAndExposed, updatedData: clonedTableData });
// Need to add a timeout here as changes are happening in the next render
setTimeout(() => fireEvent('onCellValueChanged'), 0);
return;
}
const copyOfTableDetails = useRef(tableDetails);
@ -565,12 +567,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 +581,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 +883,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]);
@ -156,17 +158,40 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId }
let editorEnvironment = result.editorEnvironment;
const editorEnvironmentId = result.editing_version?.current_environment_id;
if (isPreviewForVersion) {
const rawDataQueries = appData?.data_queries;
const rawEditingVersionDataQueries = appData?.editing_version?.data_queries;
appData = convertAllKeysToSnakeCase(appData);
appData.data_queries = rawDataQueries;
if (appData.editing_version && rawEditingVersionDataQueries) {
appData.editing_version.data_queries = rawEditingVersionDataQueries;
}
editorEnvironment = {
id: environmentId,
name: queryParams.env,
};
}
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;
@ -181,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,
@ -194,7 +224,6 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId }
setPages(pages, moduleId);
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);
@ -339,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,75 @@ 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 oldComponent = get().modules[moduleId].pages[currentPageIndex].components[componentId].component;
const { events, exposedVariables, ...filteredDefinition } = oldComponent.definition || {};
const diff = {
[componentId]: {
component: {
...oldComponent,
definition: filteredDefinition,
},
},
};
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'
@ -1100,8 +1244,16 @@ export const createComponentsSlice = (set, get) => ({
);
}
const oldComponent = get().modules[moduleId].pages[currentPageIndex].components[componentId].component;
const { events, exposedVariables, ...filteredDefinition } = oldComponent.definition || {};
const diff = {
[componentId]: { component: get().modules[moduleId].pages[currentPageIndex].components[componentId].component },
[componentId]: {
component: {
...oldComponent,
definition: filteredDefinition,
},
},
};
if (saveAfterAction) {
@ -1130,9 +1282,8 @@ export const createComponentsSlice = (set, get) => ({
const {
currentPageIndex,
saveComponentChanges,
updateResolvedValues,
checkValueAndResolve,
getComponentDefinition,
generateDependencyGraphForRefs,
getComponentTypeFromId,
setResolvedComponent,
withUndoRedo,
@ -1189,19 +1340,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 +1575,8 @@ export const createComponentsSlice = (set, get) => ({
getNodeData,
getEntityResolvedValueLength,
updateChildComponentResolvedValues,
getComponentTypeFromId,
getResolvedComponent,
} = get();
const dependecies = getDependencies(path, moduleId);
if (dependecies?.length) {
@ -1436,7 +1586,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 +1606,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 +1772,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;
@ -164,7 +165,7 @@ export const Multiselect = function Multiselect({
<label
style={{ marginRight: label ? '1rem' : '', marginBottom: 0 }}
className={`form-label py-1 ${darkMode ? 'text-light' : 'text-secondary'}`}
data-cy={`multiselect-label-${componentName.toLowerCase()}`}
data-cy={`multiselect-label-${componentName?.toLowerCase()}`}
>
{label}
</label>
@ -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

@ -13,7 +13,7 @@ export const CommonlyUsedDataSources = Object.keys(allManifests)
.reduce((accumulator, currentValue) => {
const sourceName = allManifests[currentValue]?.source?.name;
if (
sourceName === 'Rest API' ||
sourceName === 'REST API' ||
sourceName === 'MongoDB' ||
sourceName === 'Airtable' ||
sourceName === 'Google Sheets' ||
@ -27,7 +27,7 @@ export const CommonlyUsedDataSources = Object.keys(allManifests)
return accumulator;
}, [])
.sort((a, b) => {
const order = ['Rest API', 'PostgreSQL', 'Google Sheets', 'Airtable', 'MongoDB'];
const order = ['REST API', 'PostgreSQL', 'Google Sheets', 'Airtable', 'MongoDB'];
return order.indexOf(a.name) - order.indexOf(b.name);
});

View file

@ -114,8 +114,6 @@ export default function AppCard({
<ToolTip message={app.created_at && moment(app.created_at).format('dddd, MMMM Do YYYY, h:mm:ss a')}>
<span>{updated === 'just now' ? `Edited ${updated}` : `Edited ${updated} ago`}</span>
</ToolTip>
&nbsp;by{' '}
{`${app.user?.first_name ? app.user.first_name : ''} ${app.user?.last_name ? app.user.last_name : ''}`}
</div>
)}
</div>

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

@ -17,6 +17,7 @@ import { BreadCrumbContext } from '@/App';
import './ConstantFormStyle.scss';
import { Constants, redirectToWorkspace } from '@/_helpers/utils';
import { SearchBox } from '@/_components/SearchBox';
import { OrganizationList } from '@/_components/OrganizationManager/List';
const MODES = Object.freeze({
CREATE: 'create',
EDIT: 'edit',
@ -429,156 +430,166 @@ const ManageOrgConstantsComponent = ({ darkMode }) => {
/>
</Drawer>
)}
<div className="align-items-center d-flex justify-content-between" style={{ marginBottom: '10px' }}>
<div className="tj-text-sm font-weight-500" data-cy="env-name">
{capitalize(activeTabEnvironment?.name)} ({globalCount + secretCount})
</div>
<div className="workspace-setting-buttons-wrap">
{canCreateVariable() && (
<ButtonSolid
data-cy="add-new-constant-button"
variant="primary"
onClick={() => {
setMode(() => MODES.CREATE);
setIsManageVarDrawerOpen(() => true);
}}
className="add-new-constant-button"
customStyles={{ minWidth: '200px', height: '32px' }}
disabled={isManageVarDrawerOpen}
>
+ Create new constant
</ButtonSolid>
)}
</div>
</div>
<div className="constant-page-wrapper">
<div className="container-xl">
<div>
<div className="workspace-constant-header">
<div className="tabs-and-search">
<div className="tabs">
<button
className={`tab ${activeTab === Constants.Global ? 'active' : ''}`}
onClick={() => handleTabChange(Constants.Global)}
>
Global constants
<span className={`tab-count ${activeTab === Constants.Global ? 'active' : ''}`}>
({globalCount})
</span>
</button>
<button
className={`tab ${activeTab === Constants.Secret ? 'active' : ''}`}
onClick={() => handleTabChange(Constants.Secret)}
>
Secrets
<span className={`tab-count ${activeTab === Constants.Secret ? 'active' : ''}`}>
({secretCount})
</span>
</button>
</div>
<div className="search-bar">
<SearchBox
width={250}
callBack={handleSearchChange}
customClass="tj-common-search-input-group"
autoFocus={true}
placeholder={activeTab === Constants.Global ? 'Search global constants' : 'Search secrets'}
onClearCallback={handleSearchClear}
/>
</div>
<div className="row gx-0">
<div className="organization-page-sidebar col">
<div className="workspace-nav-list-wrap">
<ManageOrgConstantsComponent.EnvironmentsTabs
allEnvironments={environments}
currentEnvironment={activeTabEnvironment}
setActiveTabEnvironment={setActiveTabEnvironment}
isLoading={isLoading}
allConstants={constants}
/>
</div>
<OrganizationList />
</div>
<div className="page-wrapper mt-4">
<div className="container-xl" style={{ width: '880px' }}>
<div className="align-items-center d-flex justify-content-between">
<div className="tj-text-sm font-weight-500" data-cy="env-name">
{capitalize(activeTabEnvironment?.name)} ({globalCount + secretCount})
</div>
<div className="workspace-setting-buttons-wrap">
{canCreateVariable() && (
<ButtonSolid
data-cy="add-new-constant-button"
variant="primary"
onClick={() => {
setMode(() => MODES.CREATE);
setIsManageVarDrawerOpen(() => true);
}}
className="add-new-constant-button"
customStyles={{ minWidth: '200px', height: '32px' }}
disabled={isManageVarDrawerOpen}
>
+ Create new constant
</ButtonSolid>
)}
</div>
</div>
</div>
</div>
<div className="workspace-variable-container-wrap mt-2">
<div className="container-xl" style={{ width: '880px', padding: '0px' }}>
<div className="workspace-constant-table-card">
<div className="mt-3">
<Alert svg="tj-info">
<div
className="d-flex align-items-center"
style={{
justifyContent: 'space-between',
flexWrap: 'wrap',
width: '100%',
}}
>
<div className="text-muted" data-cy="workspace-constant-helper-text">
{activeTab === Constants.Global ? (
<>
To resolve a global workspace constant use{' '}
<strong style={{ fontWeight: 500, color: '#3E63DD' }}>{'{{constants.access_token}}'}</strong>
</>
) : (
<>
To resolve a secret workspace constant use{' '}
<strong style={{ fontWeight: 500, color: '#3E63DD' }}>{'{{secrets.access_token}}'}</strong>
</>
)}
</div>
<div>
<Button
// Todo: Update link to documentation: workspace constants
onClick={() =>
window.open(
'https://docs.tooljet.com/docs/org-management/workspaces/workspace_constants/',
'_blank'
)
}
darkMode={darkMode}
size="sm"
styles={{
width: '100%',
fontSize: '12px',
fontWeight: 500,
}}
>
<Button.Content title={'Read documentation'} iconSrc="assets/images/icons/student.svg" />
</Button>
</div>
<div className="workspace-variable-container-wrap constants-list mt-2">
<div className="container-xl constant-page-wrapper">
<div className="workspace-constant-header">
<div className="tabs-and-search">
<div className="tabs">
<button
className={`tab ${activeTab === Constants.Global ? 'active' : ''}`}
onClick={() => handleTabChange(Constants.Global)}
>
Global constants
<span className={`tab-count ${activeTab === Constants.Global ? 'active' : ''}`}>
({globalCount})
</span>
</button>
<button
className={`tab ${activeTab === Constants.Secret ? 'active' : ''}`}
onClick={() => handleTabChange(Constants.Secret)}
>
Secrets
<span className={`tab-count ${activeTab === Constants.Secret ? 'active' : ''}`}>
({secretCount})
</span>
</button>
</div>
</Alert>
</div>
<div className="manage-sso-container h-100">
<div className="d-flex manage-constant-wrapper-card">
<ManageOrgConstantsComponent.EnvironmentsTabs
allEnvironments={environments}
currentEnvironment={activeTabEnvironment}
setActiveTabEnvironment={setActiveTabEnvironment}
isLoading={isLoading}
allConstants={constants}
/>
{(activeTab === Constants.Global && globalCount > 0) ||
(activeTab === Constants.Secret && secretCount > 0) ? (
<div className="w-100">
<ConstantTable
constants={currentTableData}
onEditBtnClicked={onEditBtnClicked}
onDeleteBtnClicked={onDeleteBtnClicked}
isLoading={isLoading}
canUpdateDeleteConstant={canUpdateVariable() || canDeleteVariable()}
/>
<ManageOrgConstantsComponent.Footer
darkMode={darkMode}
totalPage={totalPages}
pageCount={currentPage}
dataLoading={false}
gotoNextPage={goToNextPage}
gotoPreviousPage={goToPreviousPage}
showPagination={constants.length > 0}
/>
</div>
) : (
<EmptyState
canCreateVariable={canCreateVariable()}
setIsManageVarDrawerOpen={setIsManageVarDrawerOpen}
isLoading={isLoading}
searchTerm={searchTerm}
<div className="search-bar">
<SearchBox
width={250}
callBack={handleSearchChange}
customClass="tj-common-search-input-group"
autoFocus={true}
placeholder={activeTab === Constants.Global ? 'Search global constants' : 'Search secrets'}
onClearCallback={handleSearchClear}
/>
)}
</div>
</div>
</div>
<div className="workspace-constant-table-card">
<div className="mt-3">
<Alert svg="tj-info">
<div
className="d-flex align-items-center"
style={{
justifyContent: 'space-between',
flexWrap: 'wrap',
width: '100%',
}}
>
<div className="text-muted" data-cy="workspace-constant-helper-text">
{activeTab === Constants.Global ? (
<>
To resolve a global workspace constant use{' '}
<strong style={{ fontWeight: 500, color: '#3E63DD' }}>
{'{{constants.access_token}}'}
</strong>
</>
) : (
<>
To resolve a secret workspace constant use{' '}
<strong style={{ fontWeight: 500, color: '#3E63DD' }}>{'{{secrets.access_token}}'}</strong>
</>
)}
</div>
<div>
<Button
onClick={() =>
window.open(
'https://docs.tooljet.com/docs/org-management/workspaces/workspace_constants/',
'_blank'
)
}
darkMode={darkMode}
size="sm"
styles={{
width: '100%',
fontSize: '12px',
fontWeight: 500,
}}
>
<Button.Content title={'Read documentation'} iconSrc="assets/images/icons/student.svg" />
</Button>
</div>
</div>
</Alert>
</div>
<div className="manage-sso-container h-100">
<div className="d-flex manage-constant-wrapper-card">
{(activeTab === Constants.Global && globalCount > 0) ||
(activeTab === Constants.Secret && secretCount > 0) ? (
<div className="w-100">
<ConstantTable
constants={currentTableData}
onEditBtnClicked={onEditBtnClicked}
onDeleteBtnClicked={onDeleteBtnClicked}
isLoading={isLoading}
canUpdateDeleteConstant={canUpdateVariable() || canDeleteVariable()}
/>
<ManageOrgConstantsComponent.Footer
darkMode={darkMode}
totalPage={totalPages}
pageCount={currentPage}
dataLoading={false}
gotoNextPage={goToNextPage}
gotoPreviousPage={goToPreviousPage}
showPagination={constants.length > 0}
/>
</div>
) : (
<EmptyState
canCreateVariable={canCreateVariable()}
setIsManageVarDrawerOpen={setIsManageVarDrawerOpen}
isLoading={isLoading}
searchTerm={searchTerm}
/>
)}
</div>
</div>
</div>
</div>

View file

@ -6,6 +6,7 @@ import { authenticationService } from '../_services/authentication.service';
import { BreadCrumbContext } from '@/App/App';
import { useNavigate } from 'react-router-dom';
import { pageTitles, fetchAndSetWindowTitle } from '@white-label/whiteLabelling';
import { hasBuilderRole } from '@/_helpers/utils';
export const TooljetDatabaseContext = createContext({
organizationId: null,
@ -72,8 +73,9 @@ export const TooljetDatabase = (props) => {
};
const navigate = useNavigate();
const { admin } = authenticationService.currentSessionValue;
const isBuilder = hasBuilderRole(authenticationService?.currentSessionValue?.role ?? {});
if (!admin) {
if (!admin && !isBuilder) {
navigate('/');
}

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

@ -4,7 +4,8 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import { authenticationService } from '@/_services';
import { getPrivateRoute, redirectToDashboard, dashboardUrl } from '@/_helpers/routes';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import AppLogo from './AppLogo';
import AppLogo from '@/_components/AppLogo';
import { hasBuilderRole } from '@/_helpers/utils';
export default function LogoNavDropdown({ darkMode }) {
const handleBackClick = (e) => {
@ -15,6 +16,16 @@ export default function LogoNavDropdown({ darkMode }) {
const getOverlay = () => {
const { admin } = authenticationService?.currentSessionValue ?? {};
const data_source_group_permissions = authenticationService?.currentSessionValue?.data_source_group_permissions;
const showDataSource =
authenticationService?.currentSessionValue?.user_permissions?.data_source_create === true ||
authenticationService?.currentSessionValue?.user_permissions?.data_source_delete === true ||
data_source_group_permissions?.usable_data_sources_id?.length ||
data_source_group_permissions?.is_all_usable ||
data_source_group_permissions?.configurable_data_source_id?.length ||
data_source_group_permissions?.is_all_configurable;
const isBuilder = hasBuilderRole(authenticationService?.currentSessionValue?.role ?? {});
return (
<div className={`logo-nav-card settings-card card ${darkMode && 'dark-theme'}`}>
<Link
@ -27,7 +38,7 @@ export default function LogoNavDropdown({ darkMode }) {
<span>Back to apps</span>
</Link>
<div className="divider"></div>
{admin && (
{(admin || isBuilder) && (
<Link
target="_blank"
to={getPrivateRoute('database')}

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { authenticationService } from '@/_services';
import { CustomSelect } from './CustomSelect';
import { getAvatar, decodeEntities } from '@/_helpers/utils';
@ -29,14 +29,21 @@ export const OrganizationList = function () {
fetchOrganizations();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const switchOrganization = (id) => {
const newTabRef = useRef(false);
const switchOrganization = (id, newTab = false) => {
newTabRef.current = newTab;
const organization = organizationList.find((org) => org.id === id);
if (![id, organization.slug].includes(getWorkspaceIdOrSlugFromURL())) {
const newPath = appendWorkspaceId(organization.slug || id, location.pathname, true);
window.open(newPath, '_blank');
newTab ? window.open(newPath, '_blank') : (window.location = newPath);
}
};
const handleOnChange = (id) => {
if (!newTabRef.current) {
switchOrganization(id, false);
}
newTabRef.current = false;
};
const options = organizationList
.map((org) => ({
@ -81,7 +88,7 @@ export const OrganizationList = function () {
<div
className="current-org-indicator"
data-cy="current-org-indicator"
onClick={() => switchOrganization(org.id)}
onClick={() => switchOrganization(org.id, true)}
>
<SolidIcon name="newtab" fill="var(--icon-strong)" width="16" className="add-new-workspace-icon" />
</div>
@ -103,7 +110,7 @@ export const OrganizationList = function () {
isLoading={isGettingOrganizations}
options={options}
value={current_organization_id}
onChange={(id) => switchOrganization(id)}
onChange={handleOnChange}
className={`tj-org-select ${darkMode && 'dark-theme'}`}
/>
</div>

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

@ -1465,3 +1465,14 @@ export const validatePassword = (value) => {
return 'Password can be at max 100 characters long';
}
};
export const checkConditionsForRoute = (conditions, conditionsObj) => {
if (!conditions || conditions.length === 0) {
return true;
}
return conditions.every((condition) => conditionsObj?.[condition] === true);
};
export const hasBuilderRole = (roleObj) => {
return roleObj.name === 'builder';
};

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

@ -179,6 +179,7 @@
.datasource-list-container {
overflow-y: auto;
max-height: calc(100vh - 64px);
padding-left: 20px;
.datasource-list {
width: 976px;

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;
@ -3656,6 +3656,9 @@ input:focus-visible {
}
.main-wrapper.theme-dark {
position: relative;
min-height: 100%;
min-width: 100%;
background-color: #2b394b;
}
@ -3872,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;
}
@ -7796,7 +7811,11 @@ tbody {
.audit-log-nav-item {
bottom: 40px;
}
.workspace-content-wrapper{
overflow-x: auto;
overflow-y: auto;
padding-left: 20px;
}
.workspace-content-wrapper,
.database-page-content-wrap {
background-color: var(--page-default);
@ -9188,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;
@ -10560,6 +10590,7 @@ tbody {
.manage-groups-body {
padding: 12px 12px 10px 12px;
font-size: 12px;
overflow-y: auto;
// overflow-y: auto;
height: calc(100vh - 300px);
@ -12693,7 +12724,7 @@ color: var(--text-default);
position: absolute;
display: flex;
justify-content: center;
top: 55px;
top: 65px;
.released-version-popup-cover {
width: 250px;
@ -15538,9 +15569,10 @@ color: var(--text-default);
background-color: var(--page-default);
height: calc(100vh - 64px);
display: flex;
align-items: center;
justify-content: center;
padding-top: 1.5rem;
//Uncomment for EE
// align-items: center;
// justify-content: center;
// padding-top: 1.5rem;
}
.blank-page-wrapper {

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

@ -13,6 +13,8 @@ import { ConfirmDialog } from '@/_components';
import useGlobalDatasourceUnsavedChanges from '@/_hooks/useGlobalDatasourceUnsavedChanges';
import Settings from '@/_components/Settings';
import { retrieveWhiteLabelLogo, fetchWhiteLabelDetails } from '@white-label/whiteLabelling';
import '../../_styles/left-sidebar.scss';
import { hasBuilderRole } from '@/_helpers/utils';
function Layout({
children,
@ -27,6 +29,7 @@ function Layout({
const admin = currentUserValue?.admin;
fetchWhiteLabelDetails();
const whiteLabelLogo = retrieveWhiteLabelLogo();
const isBuilder = hasBuilderRole(authenticationService?.currentSessionValue?.role ?? {});
const {
checkForUnsavedChanges,
@ -86,7 +89,7 @@ function Layout({
</Link>
</ToolTip>
</li>
{admin && (
{(admin || isBuilder) && (
<li className="text-center cursor-pointer" data-cy={`database-icon`}>
<ToolTip message="ToolJet Database" placement="right">
<Link

View file

@ -21,19 +21,15 @@ export default ({
}
function removeKeyValuePair(index) {
options.splice(index, 1);
optionchanged(getter, options);
const newOptions = [...options];
newOptions.splice(index, 1);
optionchanged(getter, newOptions);
}
function keyValuePairValueChanged(value, keyIndex, index) {
if (!isRenderedAsQueryEditor) {
const newOptions = deepClone(options);
newOptions[index][keyIndex] = value;
options.length - 1 === index ? addNewKeyValuePair(newOptions) : optionchanged(getter, newOptions);
} else {
options[index][keyIndex] = value;
optionchanged(getter, options);
}
const newOptions = deepClone(options);
newOptions[index][keyIndex] = value;
options.length - 1 === index ? addNewKeyValuePair(newOptions) : optionchanged(getter, newOptions);
}
const commonProps = {

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

@ -1,3 +1,4 @@
import * as utils from './utils';
import * as timeUtils from './timeUtils';
export { utils };
export { utils, timeUtils };

View file

@ -0,0 +1,29 @@
const moment = require('moment-timezone');
/**
* Get the user's country based on their time zone.
* @param {string} userTimeZone - The user's time zone.
* @returns {string} The user's country or the original time zone if not found.
*/
export function getCountryByTimeZone() {
let userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
/* Some times the browser gives Asia/Calcutta */
if (userTimeZone === 'Asia/Calcutta') {
userTimeZone = 'Asia/Kolkata';
}
// Get a list of countries from moment-timezone
const countries = moment.tz.countries();
// Iterate through the countries and check if the time zone is associated with any country
for (const country of countries) {
const timeZones = moment.tz.zonesForCountry(country);
if (timeZones.includes(userTimeZone)) {
// Use Intl.DisplayNames to get the full country name
const countryName = new Intl.DisplayNames(['en'], { type: 'region' }).of(country);
return countryName;
}
}
// Return the original time zone if no matching country is found
return userTimeZone;
}

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

@ -38,13 +38,15 @@ const SignupPage = ({ configs, organizationId }) => {
});
}, []);
const handleSignup = (formData, onSuccess = () => {}, onFaluire = () => {}) => {
const handleSignup = (formData, onSuccess = () => {}, onFailure = () => {}) => {
const { email, name, password } = formData;
if (organizationToken) {
authenticationService
.activateAccountWithToken(email, password, organizationToken)
.then((response) => onInvitedUserSignUpSuccess(response, navigate))
.then((response) => {
onInvitedUserSignUpSuccess(response, navigate);
onSuccess();
})
.catch((errorObj) => {
let errorMessage;
const isThereAnyErrorsArray = errorObj?.error?.length && typeof errorObj?.error?.[0] === 'string';
@ -54,6 +56,7 @@ const SignupPage = ({ configs, organizationId }) => {
errorMessage = errorObj?.error?.error;
}
errorMessage && toast.error(errorMessage);
onFailure();
});
} else {
authenticationService
@ -69,7 +72,7 @@ const SignupPage = ({ configs, organizationId }) => {
toast.error(e?.error || 'Something went wrong!', {
position: 'top-center',
});
onFaluire();
onFailure();
});
}
};

View file

@ -118,9 +118,11 @@ const SignupForm = ({
onSubmit(
formData,
() => {
// Success callback
setIsLoading(false);
},
() => {
// Error callback
setIsLoading(false);
}
);

View file

@ -2,6 +2,7 @@ import config from 'config';
import { handleResponse } from '@/_helpers';
import { updateCurrentSession } from '@/_helpers/authorizeWorkspace';
import queryString from 'query-string';
import { getCountryByTimeZone } from '@/modules/common/helpers/timeUtils';
function setupFirstUser({ companyName, buildPurpose, name, workspaceName, password, email }) {
const requestOptions = {
@ -15,6 +16,7 @@ function setupFirstUser({ companyName, buildPurpose, name, workspaceName, passwo
workspaceName,
email,
password,
region: getCountryByTimeZone(),
}),
};
return fetch(`${config.apiUrl}/onboarding/setup-first-user`, requestOptions)

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

@ -196,7 +196,6 @@ module.exports = {
COMMENT_FEATURE_ENABLE: process.env.COMMENT_FEATURE_ENABLE ?? true,
ENABLE_MULTIPLAYER_EDITING: true,
ENABLE_MARKETPLACE_DEV_MODE: process.env.ENABLE_MARKETPLACE_DEV_MODE,
TJDB_SQL_MODE_DISABLE: process.env.TJDB_SQL_MODE_DISABLE ?? false,
TOOLJET_MARKETPLACE_URL:
process.env.TOOLJET_MARKETPLACE_URL || 'https://tooljet-plugins-production.s3.us-east-2.amazonaws.com',
}),

View file

@ -4907,6 +4907,73 @@
"dev": true,
"license": "MIT"
},
"node_modules/@grpc/grpc-js": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.2.tgz",
"integrity": "sha512-bgxdZmgTrJZX50OjyVwz3+mNEnCTNkh3cIqGPWVNeW9jX6bn1ZkU80uPd+67/ZpIJIjRQ9qaHCjhavyoWYxumg==",
"dependencies": {
"@grpc/proto-loader": "^0.7.13",
"@js-sdsl/ordered-map": "^4.4.2"
},
"engines": {
"node": ">=12.10.0"
}
},
"node_modules/@grpc/proto-loader": {
"version": "0.7.13",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz",
"integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==",
"dependencies": {
"lodash.camelcase": "^4.3.0",
"long": "^5.0.0",
"protobufjs": "^7.2.5",
"yargs": "^17.7.2"
},
"bin": {
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@grpc/proto-loader/node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@grpc/proto-loader/node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@grpc/proto-loader/node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"engines": {
"node": ">=12"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.8",
"dev": true,
@ -5572,6 +5639,15 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@js-sdsl/ordered-map": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
"integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/js-sdsl"
}
},
"node_modules/@lerna/child-process": {
"version": "6.6.2",
"dev": true,
@ -7415,6 +7491,17 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@pinecone-database/pinecone": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@pinecone-database/pinecone/-/pinecone-3.0.3.tgz",
"integrity": "sha512-0cAG0d/6knVZgVyXM1II4qG3dyOepLuAQsCXTOJomdA7iQxf+/Om9mq9Cw4QObr56oZ+lqtptlw5qz0BQaBX2Q==",
"dependencies": {
"encoding": "^0.1.13"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"dev": true,
@ -7428,6 +7515,60 @@
"version": "1.0.0",
"license": "Apache-2.0"
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
},
"node_modules/@sigstore/bundle": {
"version": "1.1.0",
"dev": true,
@ -8305,10 +8446,6 @@
"resolved": "plugins/engagespot",
"link": true
},
"node_modules/@tooljet-marketplace/gemini": {
"resolved": "plugins/gemini",
"link": true
},
"node_modules/@tooljet-marketplace/github": {
"resolved": "plugins/github",
"link": true
@ -8321,6 +8458,10 @@
"resolved": "plugins/openai",
"link": true
},
"node_modules/@tooljet-marketplace/pinecone": {
"resolved": "plugins/pinecone",
"link": true
},
"node_modules/@tooljet-marketplace/plivo": {
"resolved": "plugins/plivo",
"link": true
@ -8329,6 +8470,10 @@
"resolved": "plugins/pocketbase",
"link": true
},
"node_modules/@tooljet-marketplace/portkey": {
"resolved": "plugins/portkey",
"link": true
},
"node_modules/@tooljet-marketplace/presto": {
"resolved": "plugins/presto",
"link": true
@ -10376,13 +10521,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/depd": {
"version": "2.0.0",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/deprecation": {
"version": "2.3.1",
"dev": true,
@ -10525,7 +10663,6 @@
"node_modules/encoding": {
"version": "0.1.13",
"license": "MIT",
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
@ -10533,7 +10670,6 @@
"node_modules/encoding/node_modules/iconv-lite": {
"version": "0.6.3",
"license": "MIT",
"optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
@ -10596,7 +10732,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"dev": true,
"engines": {
"node": ">=6"
}
@ -11309,7 +11444,6 @@
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"dev": true,
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
@ -14177,6 +14311,11 @@
"version": "4.17.21",
"license": "MIT"
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"license": "MIT"
@ -14235,6 +14374,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/long": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
"license": "MIT",
@ -16817,6 +16961,29 @@
"dev": true,
"license": "ISC"
},
"node_modules/protobufjs": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
"integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
"hasInstallScript": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/protocols": {
"version": "2.0.1",
"dev": true,
@ -17296,7 +17463,6 @@
},
"node_modules/require-directory": {
"version": "2.1.1",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -18768,7 +18934,6 @@
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
@ -18926,7 +19091,6 @@
},
"node_modules/y18n": {
"version": "5.0.8",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
@ -19025,6 +19189,7 @@
"plugins/gemini": {
"name": "@tooljet-marketplace/gemini",
"version": "1.0.0",
"extraneous": true,
"dependencies": {
"@tooljet-marketplace/common": "^1.0.0",
"portkey-ai": "^1.3.1"
@ -19079,6 +19244,19 @@
"typescript": "^4.7.4"
}
},
"plugins/pinecone": {
"name": "@tooljet-marketplace/pinecone",
"version": "1.0.0",
"dependencies": {
"@grpc/grpc-js": "^1.12.2",
"@pinecone-database/pinecone": "^3.0.3",
"@tooljet-marketplace/common": "^1.0.0"
},
"devDependencies": {
"@vercel/ncc": "^0.34.0",
"typescript": "^4.7.4"
}
},
"plugins/plivo": {
"name": "@tooljet-marketplace/plivo",
"version": "1.0.0",
@ -19103,6 +19281,18 @@
"typescript": "^4.7.4"
}
},
"plugins/portkey": {
"name": "@tooljet-marketplace/portkey",
"version": "1.0.0",
"dependencies": {
"@tooljet-marketplace/common": "^1.0.0",
"portkey-ai": "^1.3.1"
},
"devDependencies": {
"@vercel/ncc": "^0.34.0",
"typescript": "^4.7.4"
}
},
"plugins/presto": {
"name": "@tooljet-marketplace/presto",
"version": "1.0.0",
@ -19176,4 +19366,4 @@
}
}
}
}
}

View file

@ -0,0 +1,5 @@
node_modules
lib/*.d.*
lib/*.js
lib/*.js.map
dist/*

View file

@ -0,0 +1,4 @@
# Pinecone
Documentation on: https://docs.tooljet.com/docs/data-sources/pinecone

Some files were not shown because too many files have changed in this diff Show more