diff --git a/.version b/.version
index a35c54823f..c9e290188f 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-3.0.5-ce
+3.1.0-ce
diff --git a/frontend/.version b/frontend/.version
index a35c54823f..c9e290188f 100644
--- a/frontend/.version
+++ b/frontend/.version
@@ -1 +1 @@
-3.0.5-ce
+3.1.0-ce
diff --git a/frontend/assets/images/icons/widgets/index.jsx b/frontend/assets/images/icons/widgets/index.jsx
index ebbb50eee0..8d6997b4c2 100644
--- a/frontend/assets/images/icons/widgets/index.jsx
+++ b/frontend/assets/images/icons/widgets/index.jsx
@@ -39,6 +39,7 @@ import Passwordinput from './passwordinput.jsx';
import Pdf from './pdf.jsx';
import Qrscanner from './qrscanner.jsx';
import RadioButton from './radio-button.jsx';
+import RadioButtonV2 from './radiobuttonV2.jsx';
import Rangeslider from './rangeslider.jsx';
import Rating from './rating.jsx';
import Spinner from './spinner.jsx';
@@ -140,8 +141,10 @@ const WidgetIcon = (props) => {
return ;
case 'qrscanner':
return ;
- case 'radiobutton':
+ case 'radiobuttonlegacy':
return ;
+ case 'radiobutton':
+ return ;
case 'rangeslider':
return ;
case 'rating':
diff --git a/frontend/assets/images/icons/widgets/radiobuttonV2.jsx b/frontend/assets/images/icons/widgets/radiobuttonV2.jsx
new file mode 100644
index 0000000000..28b6349eb7
--- /dev/null
+++ b/frontend/assets/images/icons/widgets/radiobuttonV2.jsx
@@ -0,0 +1,17 @@
+import React from 'react';
+
+const RadioButtonV2 = ({ fill = '#D7DBDF', width = 24, className = '', viewBox = '0 0 49 48' }) => (
+
+
+
+
+);
+
+export default RadioButtonV2;
diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss
index 4298cb9439..1f88e79baa 100644
--- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss
+++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss
@@ -78,10 +78,6 @@
visibility: visible !important;
}
-.main-editor-canvas .widget-target:hover .widget-target:hover > .config-handle {
- visibility: visible !important;
-}
-
.main-editor-canvas .widget-target:hover .widget-target:hover > .widget-target > .config-handle {
visibility: hidden !important;
-}
\ No newline at end of file
+}
diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
index a8a10caf55..c8f1311033 100644
--- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
@@ -353,6 +353,7 @@ export default function Grid({ gridWidth, currentLayout }) {
// eslint-disable-next-line react-hooks/exhaustive-deps
[boxList, currentLayout, gridWidth]
);
+
if (mode !== 'edit') return null;
return (
@@ -425,6 +426,22 @@ export default function Grid({ gridWidth, currentLayout }) {
document.getElementById('resize-ghost-widget').style.height = `${e.target.clientHeight}px`;
}
}}
+ onResizeStart={(e) => {
+ if (
+ e.target.id &&
+ useGridStore.getState().resizingComponentId !== e.target.id &&
+ !e.target.classList.contains('delete-icon')
+ ) {
+ // When clicked on widget boundary/resizer, select the component
+ setSelectedComponents([e.target.id]);
+ }
+
+ if (!isComponentVisible(e.target.id)) {
+ return false;
+ }
+ useGridStore.getState().actions.setResizingComponentId(e.target.id);
+ e.setMin([gridWidth, 10]);
+ }}
onResizeEnd={(e) => {
try {
useGridStore.getState().actions.setResizingComponentId(null);
@@ -491,13 +508,6 @@ export default function Grid({ gridWidth, currentLayout }) {
useGridStore.getState().actions.setDragTarget();
toggleCanvasUpdater();
}}
- onResizeStart={(e) => {
- if (!isComponentVisible(e.target.id)) {
- return false;
- }
- useGridStore.getState().actions.setResizingComponentId(e.target.id);
- e.setMin([gridWidth, 10]);
- }}
onResizeGroupStart={({ events }) => {
const parentElm = events[0].target.closest('.real-canvas');
parentElm.classList.add('show-grid');
@@ -582,7 +592,12 @@ export default function Grid({ gridWidth, currentLayout }) {
onDragStart={(e) => {
e?.moveable?.controlBox?.removeAttribute('data-off-screen');
const box = boxList.find((box) => box.id === e.target.id);
- let isDragOnTableORCalendar = false;
+
+ // This flag indicates whether the drag event originated on a child element within a component
+ // (e.g., inside a Table's columns, Calendar's dates, or Kanban's cards).
+ // When true, it prevents the parent component from being dragged, allowing the inner elements
+ // to handle their own interactions like column resizing or card dragging
+ let isDragOnInnerElement = false;
/* If the drag or click is on a calender popup draggable interactions are not executed so that popups and other components inside calender popup works.
Also user dont need to drag an calender from using popup */
@@ -593,20 +608,24 @@ export default function Grid({ gridWidth, currentLayout }) {
/* Checking if the dragged elemenent is a table. If its a table drag is disabled since it will affect column resizing and reordering */
if (box?.component?.component === 'Table') {
const tableElem = e.target.querySelector('.jet-data-table');
- isDragOnTableORCalendar = tableElem.contains(e.inputEvent.target);
+ isDragOnInnerElement = tableElem.contains(e.inputEvent.target);
}
if (box?.component?.component === 'Calendar') {
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);
+ isDragOnInnerElement = calenderElem.contains(e.inputEvent.target);
}
- if (
- ['RangeSlider', 'Container', 'BoundedBox', 'Kanban'].includes(box?.component?.component) ||
- isDragOnTableORCalendar
- ) {
+ if (box?.component?.component === 'Kanban') {
+ const handleContainers = e.target.querySelectorAll('.handle-container');
+ isDragOnInnerElement = Array.from(handleContainers).some((container) =>
+ container.contains(e.inputEvent.target)
+ );
+ }
+
+ if (['RangeSlider', 'BoundedBox'].includes(box?.component?.component) || isDragOnInnerElement) {
const targetElems = document.elementsFromPoint(e.clientX, e.clientY);
const isHandle = targetElems.find((ele) => ele.classList.contains('handle-content'));
if (!isHandle) {
@@ -666,7 +685,9 @@ export default function Grid({ gridWidth, currentLayout }) {
let left = e.lastEvent?.translate[0];
let top = e.lastEvent?.translate[1];
if (
- ['Listview', 'Kanban'].includes(boxList.find((box) => box.id === draggedOverElemId)?.component?.component)
+ ['Listview', 'Kanban', 'Container'].includes(
+ boxList.find((box) => box.id === draggedOverElemId)?.component?.component
+ )
) {
const elemContainer = e.target.closest('.real-canvas');
const containerHeight = elemContainer.clientHeight;
@@ -683,10 +704,19 @@ export default function Grid({ gridWidth, currentLayout }) {
if (draggedOverElemId !== currentParentId) {
if (isParentChangeAllowed) {
const draggedOverWidget = boxList.find((box) => box.id === draggedOverElemId);
+
+ let parentWidgetType = boxList.find((box) => box.id === draggedOverElemId)?.component?.component;
+ // @TODO - When dropping back to container from canvas, the boxList doesn't have canvas header,
+ // boxList will return null. But we need to tell getMouseDistanceFromParentDiv parentWidgetType is container
+ // As container id is like 'canvas-2375e23765e-123234'
+ if (parentId && !parentWidgetType && draggedOverElemId.includes('-header')) {
+ parentWidgetType = 'Container';
+ }
+
let { left: _left, top: _top } = getMouseDistanceFromParentDiv(
e,
draggedOverWidget?.component?.component === 'Kanban' ? draggedOverElem : draggedOverElemId,
- boxList.find((box) => box.id === draggedOverElemId)?.component?.component
+ parentWidgetType
);
left = _left;
top = _top;
diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx
index de89652d18..fe9c98b10d 100644
--- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx
@@ -18,6 +18,7 @@ const shouldAddBoxShadowAndVisibility = [
'ToggleSwitchV2',
'DropdownV2',
'MultiselectV2',
+ 'RadioButtonV2',
];
const RenderWidget = ({
diff --git a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx
index 6fcc85ea67..dae6fed99a 100644
--- a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx
@@ -61,9 +61,8 @@ export const EditorSelecto = () => {
if (selection) {
selection.removeAllRanges();
}
-
const target = e.inputEvent.target;
- // This condition is to ensure selection happens only on main app canvas and not on child containers
+ // This condition is to ensure selection happens only on main app canvas and not on subcontainers
if (target.getAttribute('component-id') === 'canvas') {
return true;
}
diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js
index 6d15fea9ad..a96dc9f9c7 100644
--- a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js
+++ b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js
@@ -6,7 +6,7 @@ export const CANVAS_WIDTHS = Object.freeze({
rightSideBarWidth: 300,
});
-export const WIDGETS_WITH_DEFAULT_CHILDREN = ['Listview', 'Tabs', 'Form', 'Kanban'];
+export const WIDGETS_WITH_DEFAULT_CHILDREN = ['Listview', 'Tabs', 'Form', 'Kanban', 'Container'];
export const DEFAULT_CANVAS_WIDTH = 1292;
diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
index 6171473174..c4e4ec6402 100644
--- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
+++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
@@ -3,7 +3,7 @@ import { deepClone } from '@/_helpers/utilities/utils.helpers';
import { componentTypes } from '../WidgetManager';
import useStore from '@/AppBuilder/_stores/store';
import { toast } from 'react-hot-toast';
-import { CANVAS_WIDTHS, NO_OF_GRIDS } from './appCanvasConstants';
+import { CANVAS_WIDTHS, NO_OF_GRIDS, WIDGETS_WITH_DEFAULT_CHILDREN } from './appCanvasConstants';
import _ from 'lodash';
export function snapToGrid(canvasWidth, x, y) {
@@ -42,8 +42,6 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre
componentData.definition.others.showOnMobile.value = `{{true}}`;
}
- const widgetsWithDefaultComponents = ['Listview', 'Tabs', 'Form', 'Kanban'];
-
const nonActiveLayout = currentLayout === 'desktop' ? 'mobile' : 'desktop';
const newComponent = {
id: uuidv4(),
@@ -66,7 +64,7 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre
height: defaultHeight,
},
},
- withDefaultChildren: widgetsWithDefaultComponents.includes(componentData.component),
+ withDefaultChildren: WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentData.component),
};
return newComponent;
@@ -136,13 +134,14 @@ export function addChildrenWidgetsToParent(componentType, parentId, currentLayou
}
const nonActiveLayout = currentLayout === 'desktop' ? 'mobile' : 'desktop';
+ const _parent = getParentComponentIdByType(child, parentMeta.component, parentId);
const newChildComponent = {
id: uuidv4(),
name: widgetName,
component: {
...componentData,
- parent: parentMeta.component === 'Tabs' ? parentId + '-' + tab : parentId,
+ parent: getParentComponentIdByType(child, parentMeta.component, parentId),
},
layouts: {
[currentLayout]: {
@@ -193,7 +192,8 @@ export const getAllChildComponents = (allComponents, parentId) => {
const isParentTabORCalendar =
allComponents[parentId]?.component?.component === 'Tabs' ||
allComponents[parentId]?.component?.component === 'Calendar' ||
- allComponents[parentId]?.component?.component === 'Kanban';
+ allComponents[parentId]?.component?.component === 'Kanban' ||
+ allComponents[parentId]?.component?.component === 'Container';
if (componentParentId && isParentTabORCalendar) {
let childComponent = deepClone(allComponents[componentId]);
@@ -336,7 +336,11 @@ const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentI
const parentComponent = allComponents?.[parentId];
if (parentComponent) {
- return parentComponent.component.component === 'Tabs' || parentComponent.component.component === 'Calendar';
+ return (
+ parentComponent.component.component === 'Tabs' ||
+ parentComponent.component.component === 'Calendar' ||
+ parentComponent.component.component === 'Container'
+ );
}
return false;
@@ -457,3 +461,11 @@ export const computeViewerBackgroundColor = (isAppDarkMode, canvasBgColor) => {
}
return canvasBgColor;
};
+
+export const getParentComponentIdByType = (child, parentComponent, parentId) => {
+ const { tab } = child;
+
+ if (parentComponent === 'Tabs') return `${parentId}-${tab}`;
+ else if (parentComponent === 'Container') return `${parentId}-header`;
+ return parentId;
+};
diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx
index a699b3d97f..aca9eff7cb 100644
--- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx
+++ b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx
@@ -124,7 +124,7 @@ export const ComponentsManagerTab = ({ darkMode }) => {
'MultiselectV2',
'RichTextEditor',
'Checkbox',
- 'RadioButton',
+ 'RadioButtonV2',
'Datepicker',
'DateRangePicker',
'FilePicker',
diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js
index 82bfbfc4d2..bc7d91d4cb 100644
--- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js
+++ b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js
@@ -1 +1 @@
-export const LEGACY_ITEMS = ['ToggleSwitch', 'DropDown', 'Multiselect'];
+export const LEGACY_ITEMS = ['ToggleSwitch', 'DropDown', 'Multiselect', 'RadioButton'];
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Chart.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Chart.jsx
index ebe03675e7..fe6c33341b 100644
--- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Chart.jsx
+++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Chart.jsx
@@ -181,22 +181,25 @@ class Chart extends React.Component {
),
});
}
+ }
- items.push({
- title: 'Options',
- children: (
- <>
- {renderElement(
- component,
- componentMeta,
- paramUpdated,
- dataQueries,
- 'loadingState',
- 'properties',
- currentState
- )}
- {renderElement(component, componentMeta, paramUpdated, dataQueries, 'showAxes', 'properties', currentState)}
- {renderElement(
+ items.push({
+ title: 'Options',
+ children: (
+ <>
+ {renderElement(
+ component,
+ componentMeta,
+ paramUpdated,
+ dataQueries,
+ 'loadingState',
+ 'properties',
+ currentState
+ )}
+ {chartType !== 'pie' &&
+ renderElement(component, componentMeta, paramUpdated, dataQueries, 'showAxes', 'properties', currentState)}
+ {chartType !== 'pie' &&
+ renderElement(
component,
componentMeta,
paramUpdated,
@@ -205,10 +208,9 @@ class Chart extends React.Component {
'properties',
currentState
)}
- >
- ),
- });
- }
+ >
+ ),
+ });
items.push({
title: 'Events',
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx
index 74ae7c6e47..8ab5c9356a 100644
--- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx
+++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx
@@ -12,6 +12,7 @@ import { shallow } from 'zustand/shallow';
const SHOW_ADDITIONAL_ACTIONS = [
'Text',
+ 'Container',
'TextInput',
'NumberInput',
'PasswordInput',
@@ -20,6 +21,7 @@ const SHOW_ADDITIONAL_ACTIONS = [
'DropdownV2',
'MultiselectV2',
'Button',
+ 'RichTextEditor',
];
const PROPERTIES_VS_ACCORDION_TITLE = {
Text: 'Data',
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx
index 09c813fc7c..c3add3f1cc 100644
--- a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx
+++ b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx
@@ -352,6 +352,7 @@ export const EventManager = ({
actionId: 'show-alert',
message: 'Hello world!',
alertType: 'info',
+ component: eventMetaDefinition.name,
...customEventRefs,
},
eventType: eventSourceType,
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx
index e09862bc38..fdebdaaab5 100644
--- a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx
+++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx
@@ -66,6 +66,7 @@ const NEW_REVAMPED_COMPONENTS = [
'Checkbox',
'DropdownV2',
'MultiselectV2',
+ 'RadioButtonV2',
'Button',
];
@@ -702,6 +703,7 @@ const GetAccordion = React.memo(
case 'DropdownV2':
case 'MultiselectV2':
+ case 'RadioButtonV2':
return ;
default: {
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js
index ff22348788..86d5753da0 100644
--- a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js
+++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js
@@ -48,6 +48,7 @@ export function renderCustomStyles(
componentConfig.component == 'Table' ||
componentConfig.component == 'DropdownV2' ||
componentConfig.component == 'MultiselectV2' ||
+ componentConfig.component == 'RadioButtonV2' ||
componentConfig.component == 'Button'
) {
const paramTypeConfig = componentMeta[paramType] || {};
diff --git a/frontend/src/AppBuilder/RightSideBar/WidgetBox/WidgetBox.jsx b/frontend/src/AppBuilder/RightSideBar/WidgetBox/WidgetBox.jsx
index 7297ec9058..348d330ff0 100644
--- a/frontend/src/AppBuilder/RightSideBar/WidgetBox/WidgetBox.jsx
+++ b/frontend/src/AppBuilder/RightSideBar/WidgetBox/WidgetBox.jsx
@@ -2,8 +2,8 @@ import React from 'react';
import WidgetIcon from '@/../assets/images/icons/widgets';
import { useTranslation } from 'react-i18next';
-const LEGACY_WIDGETS = ['ToggleSwitch', 'DropDown', 'Multiselect'];
-const NEW_WIDGETS = ['ToggleSwitchV2', 'DropdownV2', 'MultiselectV2'];
+const LEGACY_WIDGETS = ['ToggleSwitch', 'DropDown', 'Multiselect', 'RadioButton'];
+const NEW_WIDGETS = ['ToggleSwitchV2', 'DropdownV2', 'MultiselectV2', 'RadioButtonV2'];
export const WidgetBox = ({ component, darkMode }) => {
const { t } = useTranslation();
diff --git a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js
index e5125277e9..c19a2a167c 100644
--- a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js
+++ b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js
@@ -10,6 +10,7 @@ import {
datepickerConfig,
checkboxConfig,
radiobuttonConfig,
+ radiobuttonV2Config,
toggleswitchConfig,
toggleSwitchV2Config,
textareaConfig,
@@ -67,6 +68,7 @@ export const widgets = [
datepickerConfig,
checkboxConfig,
radiobuttonConfig,
+ radiobuttonV2Config,
toggleswitchConfig,
toggleSwitchV2Config,
textareaConfig,
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/button.js b/frontend/src/AppBuilder/WidgetManager/widgets/button.js
index 73b4a03bd4..67da105c55 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/button.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/button.js
@@ -132,10 +132,7 @@ export const buttonConfig = {
borderRadius: {
type: 'numberInput',
displayName: 'Border radius',
- validation: {
- validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } },
- defaultValue: false,
- },
+ validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: false },
accordian: 'button',
},
boxShadow: {
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/container.js b/frontend/src/AppBuilder/WidgetManager/widgets/container.js
index 32d4158ffc..f06c8ae182 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/container.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/container.js
@@ -15,22 +15,92 @@ export const containerConfig = {
loadingState: {
type: 'toggle',
displayName: 'Loading state',
+ section: 'additionalActions',
validation: {
schema: { type: 'boolean' },
defaultValue: false,
},
},
+ visibility: {
+ type: 'toggle',
+ displayName: 'Visibility',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: true,
+ },
+ },
+ disabledState: {
+ type: 'toggle',
+ displayName: 'Disable',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: false,
+ },
+ },
+ showHeader: {
+ type: 'toggle',
+ displayName: 'Show header',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: true,
+ },
+ },
+ headerHeight: {
+ type: 'numberInput',
+ displayName: 'Header height',
+ validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
+ accordian: 'field',
+ },
},
+ defaultChildren: [
+ {
+ componentName: 'Text',
+ layout: {
+ top: 20,
+ left: 1,
+ height: 40,
+ },
+ displayName: 'ContainerText',
+ properties: ['text'],
+ accessorKey: 'text',
+ styles: ['fontWeight', 'textSize', 'textColor'],
+ defaultValue: {
+ text: 'Container title',
+ fontWeight: 'bold',
+ textSize: 16,
+ textColor: '#000',
+ },
+ },
+ ],
events: {},
styles: {
backgroundColor: {
type: 'color',
- displayName: 'Background color',
+ displayName: 'Background',
validation: {
schema: { type: 'string' },
defaultValue: '#fff',
},
},
+ headerBackgroundColor: {
+ type: 'color',
+ displayName: 'Header',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: '#fff',
+ },
+ },
+ headerHeight: {
+ type: 'numberInput',
+ displayName: 'Header height',
+ validation: {
+ schema: { type: 'number' },
+ defaultValue: 80,
+ },
+ accordian: 'field',
+ },
borderRadius: {
type: 'code',
displayName: 'Border radius',
@@ -50,40 +120,46 @@ export const containerConfig = {
defaultValue: '#fff',
},
},
- visibility: {
- type: 'toggle',
- displayName: 'Visibility',
- validation: {
- schema: { type: 'boolean' },
- defaultValue: true,
- },
- },
- disabledState: {
- type: 'toggle',
- displayName: 'Disable',
- validation: {
- schema: { type: 'boolean' },
- },
- defaultValue: false,
- },
},
- exposedVariables: {},
+ exposedVariables: {
+ isVisible: true,
+ isDisabled: false,
+ isLoading: false,
+ },
+ actions: [
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set disable',
+ params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set loading',
+ params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ ],
definition: {
others: {
showOnDesktop: { value: '{{true}}' },
showOnMobile: { value: '{{false}}' },
},
properties: {
- visible: { value: '{{true}}' },
loadingState: { value: `{{false}}` },
+ visibility: { value: '{{true}}' },
+ disabledState: { value: '{{false}}' },
},
events: [],
styles: {
backgroundColor: { value: '#fff' },
+ headerBackgroundColor: { value: '#fff' },
borderRadius: { value: '4' },
borderColor: { value: '#fff' },
- visibility: { value: '{{true}}' },
- disabledState: { value: '{{false}}' },
+ headerHeight: { value: '80' },
},
},
};
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/image.js b/frontend/src/AppBuilder/WidgetManager/widgets/image.js
index 7f15fbb6c5..8334267391 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/image.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/image.js
@@ -82,10 +82,7 @@ export const imageConfig = {
padding: {
type: 'code',
displayName: 'Padding',
- validation: {
- schema: { type: 'number' },
- defaultValue: 0,
- },
+ validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 0 },
},
visibility: {
type: 'toggle',
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/index.js b/frontend/src/AppBuilder/WidgetManager/widgets/index.js
index 1a875d6fa2..d73cd8934f 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/index.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/index.js
@@ -9,6 +9,7 @@ import { passinputConfig } from './passwordinput';
import { datepickerConfig } from './datepicker';
import { checkboxConfig } from './checkbox';
import { radiobuttonConfig } from './radiobutton';
+import { radiobuttonV2Config } from './radioButtonV2';
import { toggleswitchConfig } from './toggleswitch';
import { toggleSwitchV2Config } from './toggleswitchv2';
import { textareaConfig } from './textarea';
@@ -65,7 +66,8 @@ export {
passinputConfig,
datepickerConfig,
checkboxConfig,
- radiobuttonConfig,
+ radiobuttonConfig, //!Depreciated
+ radiobuttonV2Config,
toggleswitchConfig, //!Depreciated
toggleSwitchV2Config,
textareaConfig,
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/numberinput.js b/frontend/src/AppBuilder/WidgetManager/widgets/numberinput.js
index ba7f2bc227..fe3947e47b 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/numberinput.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/numberinput.js
@@ -20,10 +20,7 @@ export const numberinputConfig = {
value: {
type: 'code',
displayName: 'Default value',
- validation: {
- schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
- defaultValue: 0,
- },
+ validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 0 },
},
placeholder: {
type: 'code',
@@ -236,7 +233,7 @@ export const numberinputConfig = {
},
],
exposedVariables: {
- value: 99,
+ value: 0,
isMandatory: false,
isVisible: true,
isDisabled: false,
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/radioButtonV2.js b/frontend/src/AppBuilder/WidgetManager/widgets/radioButtonV2.js
new file mode 100644
index 0000000000..2ff6ca6c7c
--- /dev/null
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/radioButtonV2.js
@@ -0,0 +1,295 @@
+export const radiobuttonV2Config = {
+ name: 'RadioButton',
+ displayName: 'Radio Button',
+ description: 'Select one from multiple choices',
+ component: 'RadioButtonV2',
+ defaultSize: {
+ width: 12,
+ height: 43,
+ },
+ others: {
+ showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
+ showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
+ },
+ validation: {
+ customRule: {
+ type: 'code',
+ displayName: 'Custom validation',
+ placeholder: `{{components.text2.text=='yes'&&'valid'}}`,
+ },
+ mandatory: { type: 'toggle', displayName: 'Make this field mandatory' },
+ },
+ properties: {
+ label: {
+ type: 'code',
+ displayName: 'Label',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: 'Select',
+ },
+ accordian: 'Data',
+ },
+ advanced: {
+ type: 'toggle',
+ displayName: 'Dynamic options',
+ validation: {
+ schema: { type: 'boolean' },
+ },
+ accordian: 'Options',
+ },
+ schema: {
+ type: 'code',
+ displayName: 'Schema',
+ conditionallyRender: {
+ key: 'advanced',
+ value: true,
+ },
+ accordian: 'Options',
+ },
+ optionsLoadingState: {
+ type: 'toggle',
+ displayName: 'Options loading state',
+ validation: {
+ schema: { type: 'boolean' },
+ },
+ accordian: 'Options',
+ },
+ loadingState: {
+ type: 'toggle',
+ displayName: 'Loading state',
+ validation: { schema: { type: 'boolean' }, defaultValue: true },
+ section: 'additionalActions',
+ },
+ visibility: {
+ type: 'toggle',
+ displayName: 'Visibility',
+ validation: { schema: { type: 'boolean' }, defaultValue: true },
+
+ section: 'additionalActions',
+ },
+ disabledState: {
+ type: 'toggle',
+ displayName: 'Disable',
+ validation: { schema: { type: 'boolean' }, defaultValue: true },
+ section: 'additionalActions',
+ },
+ tooltip: {
+ type: 'code',
+ displayName: 'Tooltip',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: 'Enter tooltip text',
+ },
+ section: 'additionalActions',
+ placeholder: 'Enter tooltip text',
+ },
+ },
+ events: {
+ onSelectionChange: { displayName: 'On select' },
+ },
+ styles: {
+ labelColor: {
+ type: 'color',
+ displayName: 'Color',
+ validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' },
+ accordian: 'label',
+ },
+ alignment: {
+ type: 'switch',
+ displayName: 'Alignment',
+ validation: { schema: { type: 'string' }, defaultValue: 'side' },
+ options: [
+ { displayName: 'Side', value: 'side' },
+ { displayName: 'Top', value: 'top' },
+ ],
+ accordian: 'label',
+ },
+ direction: {
+ type: 'switch',
+ displayName: 'Direction',
+ validation: { schema: { type: 'string' }, defaultValue: 'left' },
+ showLabel: false,
+ isIcon: true,
+ options: [
+ { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' },
+ { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' },
+ ],
+ accordian: 'label',
+ },
+ labelWidth: {
+ type: 'slider',
+ displayName: 'Width',
+ accordian: 'label',
+ conditionallyRender: {
+ key: 'alignment',
+ value: 'side',
+ },
+ isFxNotRequired: true,
+ },
+ auto: {
+ type: 'checkbox',
+ displayName: 'auto',
+ showLabel: false,
+ validation: { schema: { type: 'boolean' } },
+ accordian: 'label',
+ conditionallyRender: {
+ key: 'alignment',
+ value: 'side',
+ },
+ isFxNotRequired: true,
+ },
+ borderColor: {
+ type: 'color',
+ displayName: 'Border',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ },
+ switchOnBackgroundColor: {
+ type: 'color',
+ displayName: 'Checked background',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ tip: 'Checked background',
+ tooltipStyle: {},
+ tooltipPlacement: 'bottom',
+ },
+ switchOffBackgroundColor: {
+ type: 'color',
+ displayName: 'Unchecked background',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ tip: 'Unchecked background',
+ tooltipStyle: {},
+ tooltipPlacement: 'bottom',
+ },
+ handleColor: {
+ type: 'color',
+ displayName: 'Handle color',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ },
+ optionsTextColor: {
+ type: 'color',
+ displayName: 'Text',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ },
+ padding: {
+ type: 'switch',
+ displayName: 'Padding',
+ validation: {
+ schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
+ defaultValue: 'default',
+ },
+ isFxNotRequired: true,
+ options: [
+ { displayName: 'Default', value: 'default' },
+ { displayName: 'None', value: 'none' },
+ ],
+ accordian: 'container',
+ },
+ },
+ actions: [
+ {
+ handle: 'selectOption',
+ displayName: 'Select option',
+ params: [{ handle: 'option', displayName: 'Option' }],
+ },
+ {
+ handle: 'deselectOption',
+ displayName: 'Deselect option',
+ params: [{ handle: 'option', displayName: 'Option' }],
+ },
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: `{{true}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set loading',
+ params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set disable',
+ params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ ],
+ exposedVariables: {
+ label: 'Select',
+ },
+ definition: {
+ others: {
+ showOnDesktop: { value: '{{true}}' },
+ showOnMobile: { value: '{{false}}' },
+ },
+ validation: {
+ mandatory: { value: '{{false}}' },
+ },
+ properties: {
+ label: { value: 'Select' },
+ value: { value: '{{"2"}}' },
+ advanced: { value: `{{false}}` },
+ options: {
+ value: [
+ {
+ label: 'option1',
+ value: '1',
+ disable: { value: false },
+ visible: { value: true },
+ default: { value: false },
+ },
+ {
+ label: 'option2',
+ value: '2',
+ disable: { value: false },
+ visible: { value: true },
+ default: { value: true },
+ },
+ {
+ label: 'option3',
+ value: '3',
+ disable: { value: false },
+ visible: { value: true },
+ default: { value: false },
+ },
+ ],
+ },
+ visibility: { value: '{{true}}' },
+ disabledState: { value: '{{false}}' },
+ loadingState: { value: '{{false}}' },
+ optionsLoadingState: { value: '{{false}}' },
+ optionVisibility: { value: '{{[true, true, true]}}' },
+ optionDisable: { value: '{{[false, false, false]}}' },
+ schema: {
+ value:
+ "{{[\t{label: 'option1',value: '1',disable: false,visible: true,default: true},{label: 'option2',value: '2',disable: false,visible: true},{label: 'option3',value: '3',disable: false,visible: true}\t]}}",
+ },
+ },
+ events: [],
+ styles: {
+ labelColor: { value: '#11181C' },
+ direction: { value: 'left' },
+ alignment: { value: 'side' },
+ auto: { value: '{{false}}' },
+ labelWidth: { value: '20' },
+ borderColor: { value: '#FFFFFF' },
+ switchOffBackgroundColor: { value: '#FFFFFF' },
+ switchOnBackgroundColor: { value: '#4368E3' },
+ handleColor: { value: '#FFFFFF' },
+ optionsTextColor: { value: '#11181C' },
+ padding: { value: 'default' },
+ },
+ },
+};
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/radiobutton.js b/frontend/src/AppBuilder/WidgetManager/widgets/radiobutton.js
index de1e210894..d6ec057526 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/radiobutton.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/radiobutton.js
@@ -1,6 +1,6 @@
export const radiobuttonConfig = {
- name: 'RadioButton',
- displayName: 'Radio Button',
+ name: 'RadioButtonLegacy',
+ displayName: 'Radio Button (Legacy)',
description: 'Select one from multiple choices',
component: 'RadioButton',
defaultSize: {
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/richtextarea.js b/frontend/src/AppBuilder/WidgetManager/widgets/richtextarea.js
index eacee66529..c964d53d6b 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/richtextarea.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/richtextarea.js
@@ -28,6 +28,15 @@ export const richtextareaConfig = {
defaultValue: 'Default text',
},
},
+ loadingState: {
+ type: 'toggle',
+ displayName: 'Show loading state',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: false,
+ },
+ section: 'additionalActions',
+ },
},
events: {},
styles: {
@@ -55,6 +64,28 @@ export const richtextareaConfig = {
exposedVariables: {
value: '',
},
+ actions: [
+ {
+ handle: 'setValue',
+ displayName: 'Set value',
+ params: [{ handle: 'value', displayName: 'Value', defaultValue: 'New text' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set disable',
+ params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: `{{true}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set loading',
+ params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ ],
definition: {
others: {
showOnDesktop: { value: '{{true}}' },
@@ -63,6 +94,7 @@ export const richtextareaConfig = {
properties: {
placeholder: { value: 'Placeholder text' },
defaultValue: { value: '' },
+ loadingState: { value: `{{false}}` },
},
events: [],
styles: {
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/table.js b/frontend/src/AppBuilder/WidgetManager/widgets/table.js
index 9be59e9dc3..8a89701494 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/table.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/table.js
@@ -289,6 +289,7 @@ export const tableConfig = {
onCellValueChanged: { displayName: 'Cell value changed' },
onFilterChanged: { displayName: 'Filter changed' },
onNewRowsAdded: { displayName: 'Add new rows' },
+ onTableDataDownload: { displayName: 'Download data' },
},
styles: {
textColor: {
@@ -310,6 +311,16 @@ export const tableConfig = {
{ displayName: 'Wrap', value: 'wrap' },
],
},
+ headerCasing: {
+ type: 'switch',
+ displayName: 'Header casing',
+ validation: { schema: { type: 'string' } },
+ accordian: 'Data',
+ options: [
+ { displayName: 'AA', value: 'uppercase' },
+ { displayName: 'As typed', value: 'none' },
+ ],
+ },
tableType: {
type: 'select',
displayName: 'Row style',
@@ -529,6 +540,7 @@ export const tableConfig = {
value: [
{
name: 'id',
+ key: 'id',
id: 'e3ecbf7fa52c4d7210a93edb8f43776267a489bad52bd108be9588f790126737',
autogenerated: true,
fxActiveFields: [],
@@ -548,6 +560,7 @@ export const tableConfig = {
},
{
name: 'name',
+ key: 'name',
id: '5d2a3744a006388aadd012fcc15cc0dbcb5f9130e0fbb64c558561c97118754a',
autogenerated: true,
fxActiveFields: [],
@@ -556,6 +569,7 @@ export const tableConfig = {
},
{
name: 'email',
+ key: 'email',
id: 'afc9a5091750a1bd4760e38760de3b4be11a43452ae8ae07ce2eebc569fe9a7f',
autogenerated: true,
fxActiveFields: [],
@@ -564,6 +578,7 @@ export const tableConfig = {
},
{
name: 'date',
+ key: 'date',
id: '27b75c8af9d34d1eaa1f9bb7f8f9f7b0abf1823e799748c8bb57e74f53b2c1dc',
autogenerated: true,
fxActiveFields: [],
@@ -576,6 +591,7 @@ export const tableConfig = {
},
{
name: 'mobile_number',
+ key: 'mobile_number',
id: '9c2e3c40572a4aefb8e179ee39a0e1ac9dc2b2e6634be56e1c05be13c3d1de56',
autogenerated: true,
fxActiveFields: [],
@@ -652,6 +668,7 @@ export const tableConfig = {
styles: {
textColor: { value: '#000' },
columnHeaderWrap: { value: 'fixed' },
+ headerCasing: { value: 'uppercase' },
actionButtonRadius: { value: '0' },
cellSize: { value: 'regular' },
borderRadius: { value: '8' },
diff --git a/frontend/src/AppBuilder/Widgets/Container.jsx b/frontend/src/AppBuilder/Widgets/Container.jsx
index b54b94c9a3..2022a0a7ad 100644
--- a/frontend/src/AppBuilder/Widgets/Container.jsx
+++ b/frontend/src/AppBuilder/Widgets/Container.jsx
@@ -1,37 +1,99 @@
import React, { useMemo } from 'react';
import { Container as ContainerComponent } from '@/AppBuilder/AppCanvas/Container';
import Spinner from '@/_ui/Spinner';
+import { useExposeState } from '@/AppBuilder/_hooks/useExposeVariables';
-export const Container = ({ id, properties, styles, darkMode, height, width }) => {
- const { visibility, disabledState, borderRadius, borderColor, boxShadow } = styles;
- const bgColor = useMemo(() => {
+export const Container = ({
+ id,
+ properties,
+ styles,
+ darkMode,
+ height,
+ width,
+ setExposedVariables,
+ setExposedVariable,
+}) => {
+ const { isDisabled, isVisible, isLoading } = useExposeState(
+ properties.loadingState,
+ properties.visibility,
+ properties.disabledState,
+ setExposedVariables,
+ setExposedVariable
+ );
+
+ const { borderRadius, borderColor, boxShadow, headerHeight = 80 } = styles;
+ const contentBgColor = useMemo(() => {
return {
backgroundColor:
['#fff', '#ffffffff'].includes(styles.backgroundColor) && darkMode ? '#232E3C' : styles.backgroundColor,
};
}, [styles.backgroundColor, darkMode]);
+ const headerBgColor = useMemo(() => {
+ return {
+ backgroundColor:
+ ['#fff', '#ffffffff'].includes(styles.headerBackgroundColor) && darkMode
+ ? '#232E3C'
+ : styles.headerBackgroundColor,
+ };
+ }, [styles.headerBackgroundColor, darkMode]);
+
const computedStyles = {
- backgroundColor: bgColor.backgroundColor,
+ backgroundColor: contentBgColor.backgroundColor,
borderRadius: borderRadius ? parseFloat(borderRadius) : 0,
border: `1px solid ${borderColor}`,
height,
- display: visibility ? 'flex' : 'none',
+ display: isVisible ? 'flex' : 'none',
overflow: 'hidden auto',
position: 'relative',
boxShadow,
};
+
+ const computedHeaderStyles = {
+ ...headerBgColor,
+ height: `${headerHeight}px`,
+ flexShrink: 0,
+ flexGrow: 0,
+ borderBottom: `1px solid var(--border-weak)`,
+ };
+
+ const computedContentStyles = {
+ ...contentBgColor,
+ flex: 1,
+ overflow: 'auto',
+ };
+
return (
- {properties.loadingState ? (
+ {isLoading ? (
) : (
-
+ <>
+ {properties.showHeader && (
+
+ )}
+
+ >
)}
);
diff --git a/frontend/src/AppBuilder/Widgets/Modal.jsx b/frontend/src/AppBuilder/Widgets/Modal.jsx
index a88aff630f..3988e26a50 100644
--- a/frontend/src/AppBuilder/Widgets/Modal.jsx
+++ b/frontend/src/AppBuilder/Widgets/Modal.jsx
@@ -290,8 +290,19 @@ const Component = ({ children, ...restProps }) => {
} = restProps['modalProps'];
const setSelectedComponentAsModal = useStore((state) => state.setSelectedComponentAsModal, shallow);
+
+ // When the modal body is clicked capture it and use the callback to set the selected component as modal
+ const handleModalBodyClick = (event) => {
+ const clickedComponentId = event.target.getAttribute('component-id');
+
+ // Check if the clicked element is part of the modal canvas & same widget with id
+ if (id === clickedComponentId) {
+ setSelectedComponentAsModal(id);
+ }
+ };
+
return (
-
+
{showConfigHandler && (
{
function hideColumnsPopover() {
return (
@@ -223,32 +224,57 @@ export const Footer = React.memo(
)}
{!loadingState && showDownloadButton && (
-
-
- {
- if (document.activeElement === e.currentTarget) {
- e.currentTarget.blur();
- }
- }}
- >
-
+ {
+ // if server side pagination is enabled and download event is associated with the table, then directly fire download event without displaying popover
+ isDownloadTableDataEventAssociated && !clientSidePagination ? (
+ <>
+
+ fireEvent('onTableDataDownload')}
+ >
+ >
+ ) : (
+ <>
+
+
+ {
+ if (document.activeElement === e.currentTarget) {
+ e.currentTarget.blur();
+ }
+ }}
+ >
+
+ >
+ )
+ }
)}
{!loadingState && !hideColumnSelectorButton && (
diff --git a/frontend/src/AppBuilder/Widgets/Table/Components/TableHeader.jsx b/frontend/src/AppBuilder/Widgets/Table/Components/TableHeader.jsx
index 3df604e06d..3c8ba0258c 100644
--- a/frontend/src/AppBuilder/Widgets/Table/Components/TableHeader.jsx
+++ b/frontend/src/AppBuilder/Widgets/Table/Components/TableHeader.jsx
@@ -17,6 +17,7 @@ export const TableHeader = ({
columnHeaderWrap,
setResizingColumnId,
resizingColumnId,
+ headerCasing,
}) => {
const calculateWidthOfActionColumnHeader = (position) => {
let totalWidth = null;
@@ -193,6 +194,7 @@ export const TableHeader = ({
'text-truncate': getResolvedValue(columnHeaderWrap) === 'fixed',
'wrap-wrapper': getResolvedValue(columnHeaderWrap) === 'wrap',
})}
+ style={{ textTransform: headerCasing === 'uppercase' ? 'uppercase' : 'none' }}
>
{column.render('Header')}
diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx
index 986edca4f5..17714791cf 100644
--- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx
+++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx
@@ -135,6 +135,7 @@ export const Table = React.memo(
borderColor,
isMaxRowHeightAuto,
columnHeaderWrap,
+ headerCasing,
} = loadPropertiesAndStyles(properties, styles, darkMode);
const updatedDataReference = useRef([]);
const preSelectRow = useRef(false);
@@ -153,6 +154,7 @@ export const Table = React.memo(
const [tableDetails, dispatch] = useReducer(reducer, initialState());
const [hoverAdded, setHoverAdded] = useState(false);
const [generatedColumn, setGeneratedColumn] = useState([]);
+ const [isDownloadTableDataEventAssociated, setIsDownloadTableDataEventAssociated] = useState(false);
const mergeToTableDetails = useCallback((payload) => dispatch(reducerActions.mergeToTableDetails(payload)), []);
const mergeToFilterDetails = (payload) => dispatch(reducerActions.mergeToFilterDetails(payload));
@@ -174,6 +176,9 @@ export const Table = React.memo(
useEffect(() => mergeToTableDetails({ columnProperties: properties?.columns }), [properties?.columns]);
useEffect(() => {
+ const isDownloadTableDataEventAssociated = tableEvents.some((event) => event?.name === 'onTableDataDownload');
+ if (isDownloadTableDataEventAssociated) setIsDownloadTableDataEventAssociated(true);
+ else setIsDownloadTableDataEventAssociated(false);
const hoverEvent = tableEvents?.find(({ event }) => {
return event?.eventId == 'onRowHovered';
});
@@ -1096,6 +1101,7 @@ export const Table = React.memo(
columnHeaderWrap={columnHeaderWrap}
setResizingColumnId={setResizingColumnId}
resizingColumnId={resizingColumnId}
+ headerCasing={headerCasing}
/>
{page.length > 0 && !loadingState && (
: page.length === 0 ? : null}
{
+ const [isDisabled, setDisable] = useState(disabledState || false);
+ const [isVisible, setVisibility] = useState(visibleState || true);
+ const [isLoading, setLoading] = useState(loadingState || false);
+
+ // Effect to conditionally update state from properties passed to widget
+ useEffect(() => {
+ setDisable(disabledState);
+ }, [disabledState]);
+
+ useEffect(() => {
+ setVisibility(visibleState);
+ }, [visibleState]);
+
+ useEffect(() => {
+ setLoading(loadingState);
+ }, [loadingState]);
+
+ // exposed variables with state and async setters, happens on first time load
+ useEffect(() => {
+ setExposedVariables({
+ setDisable: async (value) => setDisable(value),
+ setVisibility: async (value) => setVisibility(value),
+ setLoading: async (value) => setLoading(value),
+ });
+ }, [setExposedVariables]);
+
+ //Side effect to state variables, these will run after the state is set and the values will be exposed
+ useEffect(() => {
+ setExposedVariable('isDisabled', isDisabled);
+ }, [isDisabled, setExposedVariable]);
+
+ useEffect(() => {
+ setExposedVariable('isVisible', isVisible);
+ }, [isVisible, setExposedVariable]);
+
+ useEffect(() => {
+ setExposedVariable('isLoading', isLoading);
+ }, [isLoading, setExposedVariable]);
+
+ return {
+ isDisabled,
+ setDisable,
+ isVisible,
+ setVisibility,
+ isLoading,
+ setLoading,
+ };
+};
diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js
index 7fb9650075..44344000a1 100644
--- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js
@@ -1822,7 +1822,11 @@ export const createComponentsSlice = (set, get) => ({
const label = componentDefinition?.component?.definition?.properties?.label;
const getAllExposedValues = get().getAllExposedValues;
// Early return for non input components
- if (!['TextInput', 'PasswordInput', 'NumberInput', 'DropdownV2', 'MultiselectV2'].includes(componentType)) {
+ if (
+ !['TextInput', 'PasswordInput', 'NumberInput', 'DropdownV2', 'MultiselectV2', 'RadioButtonV2'].includes(
+ componentType
+ )
+ ) {
return layoutData?.height;
}
const { alignment = { value: null }, width = { value: null }, auto = { value: null } } = stylesDefinition ?? {};
diff --git a/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js b/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js
index ac5b36c802..f4bab3d4ee 100644
--- a/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js
@@ -149,6 +149,7 @@ export const createDebuggerSlice = (set, get) => ({
componentId: id,
},
logLevel: 'error',
+ errorTarget: 'Component Property',
timestamp: moment().toISOString(),
}));
@@ -191,6 +192,7 @@ export const createDebuggerSlice = (set, get) => ({
effectiveProperty: { [property]: defaultValue },
componentId,
},
+ errorTarget: 'Component Property',
logLevel: 'error',
timestamp: moment().toISOString(),
});
diff --git a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js
index 6817e6aa39..3eb143b5d7 100644
--- a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js
@@ -15,6 +15,7 @@ import generateFile from '@/_lib/generate-file';
import urlJoin from 'url-join';
import { useCallback } from 'react';
import { navigate } from '@/AppBuilder/_utils/misc';
+import moment from 'moment';
// To unsubscribe from the changes when no longer needed
// unsubscribe();
@@ -211,32 +212,46 @@ export const createEventsSlice = (set, get) => ({
state.eventsSlice.module[moduleId].events = newEvents;
});
},
- setTablePageIndex: (tableId, index = 1) => {
- const { getExposedValueOfComponent } = get();
- if (_.isEmpty(tableId)) {
- console.log('No table is associated with this event.');
+ setTablePageIndex: (tableId, index, eventObj) => {
+ try {
+ const { getExposedValueOfComponent } = get();
+ if (typeof index !== 'number' && index !== undefined) {
+ throw new Error('Invalid page index.');
+ }
+ const exposedValue = getExposedValueOfComponent(tableId);
+ if (!exposedValue) {
+ throw new Error('No table is associated with this event.');
+ }
+ exposedValue.setPage(index);
return Promise.resolve();
+ } catch (error) {
+ get().eventsSlice.logError('set_table_page_index', 'set-table-page-index', error, eventObj, {
+ eventId: eventObj.eventType,
+ });
}
- const exposedValue = getExposedValueOfComponent(tableId);
- if (!exposedValue) {
- console.log('No table is associated with this event.');
- return Promise.resolve();
- }
- exposedValue.setPage(index);
- return Promise.resolve();
},
- showModal: (modal, show) => {
- const { getExposedValueOfComponent } = get();
- const modalId = modal?.id ?? modal;
- console.log('modalId', modalId);
- if (_.isEmpty(modalId)) {
- console.log('No modal is associated with this event.');
- return Promise.resolve();
- }
- const exposedValue = getExposedValueOfComponent(modalId);
- show ? exposedValue.open() : exposedValue.close();
+ showModal: (modal, show, eventObj) => {
+ try {
+ const { getExposedValueOfComponent } = get();
+ const modalId = modal?.id ?? modal;
+ if (_.isEmpty(modalId)) {
+ throw new Error('No modal is associated with this event.');
+ }
+ const exposedValue = getExposedValueOfComponent(modalId);
+ show ? exposedValue.open() : exposedValue.close();
- return Promise.resolve();
+ return Promise.resolve();
+ } catch (error) {
+ get().eventsSlice.logError(
+ show ? 'show_modal' : 'close_modal',
+ show ? 'show-modal' : 'close_modal',
+ error,
+ eventObj,
+ {
+ eventId: eventObj.eventType,
+ }
+ );
+ }
},
handleEvent: (eventName, events, options, moduleId = 'canvas') => {
const latestEvents = get().eventsSlice.getModuleEvents(moduleId);
@@ -361,6 +376,7 @@ export const createEventsSlice = (set, get) => ({
'onSubmit',
'onInvalid',
'onNewRowsAdded',
+ 'onTableDataDownload',
].includes(eventName)
) {
executeActionsForEventId(eventName, events, mode, customVariables);
@@ -381,13 +397,84 @@ export const createEventsSlice = (set, get) => ({
?.sort((a, b) => a.index - b.index);
for (const event of filteredEvents) {
- await get().eventsSlice.executeAction(event.event, mode, customVariables);
+ await get().eventsSlice.executeAction(event, mode, customVariables);
}
},
- executeAction: debounce(async (event, mode, customVariables = {}) => {
+ logError(errorType, errorKind, error, eventObj = '', options = {}, logLevel) {
+ const { event = eventObj } = eventObj;
+ const pages = get().modules.canvas.pages;
+ const currentPageId = get().currentPageId;
+ const currentPage = pages.find((page) => page.id === currentPageId);
+ const componentIdMapping = get().modules['canvas'].componentNameIdMapping;
+ const componentName = Object.keys(componentIdMapping).find(
+ (key) => componentIdMapping[key] === eventObj?.sourceId
+ );
+ const componentId = eventObj?.sourceId;
+
+ const getSource = () => {
+ if (eventObj.eventType) {
+ return eventObj.eventType === 'data_query' ? 'query' : eventObj.eventType;
+ }
+
+ const sourceMap = {
+ onDataQueryFailure: 'query',
+ onDataQuerySuccess: 'query',
+ onPageLoad: 'page',
+ };
+
+ return sourceMap[event.eventId] || 'component';
+ };
+
+ const getQueryName = () => {
+ const queries = get().dataQuery.queries.modules.canvas;
+ return queries.find((query) => query.id === eventObj?.sourceId || '')?.name || '';
+ };
+
+ const constructErrorHeader = () => {
+ const source = getSource();
+ const pageName = currentPage.name;
+
+ const headerMap = {
+ component: `[Page ${pageName}] [Component ${componentName}] [Event ${event?.eventId}] [Action ${event.actionId}]`,
+ page: `[Page ${pageName}] [Event ${event.eventId}] [Action ${event.actionId}]`,
+ query: `[Query ${getQueryName()}] [Event ${event.eventId}] [Action ${event.actionId}]`,
+ };
+
+ return headerMap[source] || '';
+ };
+
+ const constructErrorTarget = () => {
+ const source = getSource();
+
+ const errorTargetMap = {
+ page: 'Event Errors with page',
+ component: 'Component Event',
+ query: 'Event Errors with query',
+ };
+
+ return errorTargetMap[source];
+ };
+ useStore.getState().debugger.log({
+ logLevel: logLevel ? logLevel : 'error',
+ type: errorType ? errorType : 'event',
+ kind: errorKind,
+ key: constructErrorHeader(),
+ error: {
+ message: error.message,
+ description: JSON.stringify(error.message, null, 2),
+ ...(event.component && componentId && { componentId: componentId }),
+ },
+ errorTarget: constructErrorTarget(),
+ options: options,
+ strace: 'app_level',
+ timestamp: moment().toISOString(),
+ });
+ },
+ executeAction: debounce(async (eventObj, mode, customVariables = {}) => {
+ const { event = eventObj } = eventObj;
const { getExposedValueOfComponent, getResolvedValue } = get();
- if (event.runOnlyIf) {
+ if (event?.runOnlyIf) {
const shouldRun = getResolvedValue(event.runOnlyIf, customVariables);
if (!shouldRun) {
return false;
@@ -418,23 +505,37 @@ export const createEventsSlice = (set, get) => ({
return Promise.resolve();
}
case 'run-query': {
- const { queryId, queryName } = event;
- const params = event['parameters'];
- const resolvedParams = {};
- if (params) {
- Object.keys(params).map((param) => (resolvedParams[param] = getResolvedValue(params[param], undefined)));
+ try {
+ const { queryId, queryName, component, eventId } = event;
+ const params = event['parameters'];
+ if (!queryId && !queryName) {
+ throw new Error('No query selected');
+ }
+ const resolvedParams = {};
+ if (params) {
+ Object.keys(params).map(
+ (param) => (resolvedParams[param] = getResolvedValue(params[param], undefined))
+ );
+ }
+ // !Todo tackle confirm query part once done
+ return get().queryPanel.runQuery(
+ queryId,
+ queryName,
+ undefined,
+ undefined,
+ resolvedParams,
+ component,
+ eventId,
+ false,
+ false,
+ 'canvas'
+ );
+ } catch (error) {
+ get().eventsSlice.logError('run_query', 'run-query', error, eventObj, {
+ eventId: event.eventId,
+ });
+ return Promise.reject(error);
}
- // !Todo tackle confirm query part once done
- return get().queryPanel.runQuery(
- queryId,
- queryName,
- undefined,
- undefined,
- resolvedParams,
- false,
- false,
- 'canvas'
- );
}
case 'logout': {
return logoutAction();
@@ -447,39 +548,47 @@ export const createEventsSlice = (set, get) => ({
return Promise.resolve();
}
case 'go-to-app': {
- const resolvedValue = getResolvedValue(event.slug, customVariables);
- const slug = resolvedValue;
- const queryParams = event.queryParams?.reduce(
- (result, queryParam) => ({
- ...result,
- ...{
- [getResolvedValue(queryParam[0])]: getResolvedValue(queryParam[1], undefined, customVariables),
- },
- }),
- {}
- );
- let url = `/applications/${slug}`;
-
- if (queryParams) {
- const queryPart = serializeNestedObjectToQueryParams(queryParams);
-
- if (queryPart.length > 0) url = url + `?${queryPart}`;
- }
- if (mode === 'view') {
- navigate(url);
- } else {
- if (confirm('The app will be opened in a new tab as the action is triggered from the editor.')) {
- window.open(urlJoin(window.public_config?.TOOLJET_HOST, url));
+ try {
+ if (!event.slug) {
+ throw new Error('No application slug provided');
}
+ const resolvedValue = getResolvedValue(event.slug, customVariables);
+ const slug = resolvedValue;
+ const queryParams = event.queryParams?.reduce(
+ (result, queryParam) => ({
+ ...result,
+ ...{
+ [getResolvedValue(queryParam[0])]: getResolvedValue(queryParam[1], undefined, customVariables),
+ },
+ }),
+ {}
+ );
+ let url = `/applications/${slug}`;
+
+ if (queryParams) {
+ const queryPart = serializeNestedObjectToQueryParams(queryParams);
+
+ if (queryPart.length > 0) url = url + `?${queryPart}`;
+ }
+ if (mode === 'view') {
+ navigate(url);
+ } else {
+ if (confirm('The app will be opened in a new tab as the action is triggered from the editor.')) {
+ window.open(urlJoin(window.public_config?.TOOLJET_HOST, url));
+ }
+ }
+ return Promise.resolve();
+ } catch (error) {
+ get().eventsSlice.logError('go_to_app', 'go-to-app', error, eventObj, { eventId: event.eventId });
+ return Promise.reject();
}
- return Promise.resolve();
}
case 'show-modal':
- return get().eventsSlice.showModal(event.modal, true);
+ return get().eventsSlice.showModal(event.modal, true, eventObj);
case 'close-modal':
- return get().eventsSlice.showModal(event.modal, false);
+ return get().eventsSlice.showModal(event.modal, false, eventObj);
case 'copy-to-clipboard': {
const contentToCopy = getResolvedValue(event.contentToCopy, customVariables);
copyToClipboard(contentToCopy);
@@ -508,7 +617,7 @@ export const createEventsSlice = (set, get) => ({
}
case 'set-table-page': {
- get().eventsSlice.setTablePageIndex(event.table, getResolvedValue(event.pageIndex));
+ get().eventsSlice.setTablePageIndex(event.table, getResolvedValue(event.pageIndex), eventObj);
break;
}
@@ -631,85 +740,110 @@ export const createEventsSlice = (set, get) => ({
// return;
}
case 'control-component': {
- // let component = Object.values(getCurrentState()?.components ?? {}).filter(
- // (component) => component.id === event.componentId
- // )[0];
- const component = getExposedValueOfComponent(event.componentId);
- const action = component?.[event.componentSpecificActionHandle];
- // let action = '';
- // let actionArguments = '';
- // check if component id not found then try to find if its available as child widget else continue
- // with normal flow finding action
- // if (component == undefined) {
- // component = _ref.appDefinition.pages[getCurrentState()?.page?.id].components[event.componentId].component;
- // const parent = Object.values(getCurrentState()?.components ?? {}).find(
- // (item) => item.id === component.parent
- // );
- // const child = Object.values(parent?.children).find((item) => item.id === event.componentId);
- // if (child) {
- // action = child[event.componentSpecificActionHandle];
- // }
- // } else {
- // //normal component outside a container ex : form
- // action = component?.[event.componentSpecificActionHandle];
- // }
- // actionArguments = _.map(event.componentSpecificActionParams, (param) => ({
- // ...param,
- // value: resolveReferences(param.value, undefined, customVariables),
- // }));
- // console.log('actionArguments', event.componentSpecificActionParams);
- const actionArguments = event.componentSpecificActionParams.map((param) => {
- const value = getResolvedValue(param.value, customVariables);
- return {
- ...param,
- value: value,
- // value: resolveCode(re.valueWithBrackets, getAllExposedValues()),
- };
- });
- // const actionArguments = _.map(event.componentSpecificActionParams, (param) => ({
- // ...param,
- // value: resolveReferences(param.value, getAllExposedValues(), customVariables),
- // }));
+ try {
+ // let component = Object.values(getCurrentState()?.components ?? {}).filter(
+ // (component) => component.id === event.componentId
+ // )[0];
+ const { event } = eventObj;
+ if (!event.componentSpecificActionHandle) {
+ throw new Error('No component-specific action handle provided.');
+ }
+ const component = getExposedValueOfComponent(event.componentId);
+ if (!event.componentId || !Object.keys(component).length) {
+ throw new Error('No component ID provided for control-component action.');
+ }
+ const action = component?.[event.componentSpecificActionHandle];
+ // let action = '';
+ // let actionArguments = '';
+ // check if component id not found then try to find if its available as child widget else continue
+ // with normal flow finding action
+ // if (component == undefined) {
+ // component = _ref.appDefinition.pages[getCurrentState()?.page?.id].components[event.componentId].component;
+ // const parent = Object.values(getCurrentState()?.components ?? {}).find(
+ // (item) => item.id === component.parent
+ // );
+ // const child = Object.values(parent?.children).find((item) => item.id === event.componentId);
+ // if (child) {
+ // action = child[event.componentSpecificActionHandle];
+ // }
+ // } else {
+ // //normal component outside a container ex : form
+ // action = component?.[event.componentSpecificActionHandle];
+ // }
+ // actionArguments = _.map(event.componentSpecificActionParams, (param) => ({
+ // ...param,
+ // value: resolveReferences(param.value, undefined, customVariables),
+ // }));
+ // console.log('actionArguments', event.componentSpecificActionParams);
+ const actionArguments = event.componentSpecificActionParams.map((param) => {
+ const value = getResolvedValue(param.value, customVariables);
+ return {
+ ...param,
+ value: value,
+ // value: resolveCode(re.valueWithBrackets, getAllExposedValues()),
+ };
+ });
+ // const actionArguments = _.map(event.componentSpecificActionParams, (param) => ({
+ // ...param,
+ // value: resolveReferences(param.value, getAllExposedValues(), customVariables),
+ // }));
- const actionPromise = action && action(...actionArguments.map((argument) => argument.value));
- return actionPromise ?? Promise.resolve();
+ const actionPromise = action && action(...actionArguments.map((argument) => argument.value));
+ return actionPromise ?? Promise.resolve();
+ } catch (error) {
+ get().eventsSlice.logError('control_component', 'control-component', error, eventObj, {
+ eventId: event.eventId,
+ });
+
+ return Promise.reject(error);
+ }
}
case 'switch-page': {
- const { switchPage } = get();
- const page = get().modules.canvas.pages.find((page) => page.id === event.pageId);
- const queryParams = event.queryParams || [];
- if (!page.disabled) {
- const resolvedQueryParams = [];
- queryParams.forEach((param) => {
- resolvedQueryParams.push([
- getResolvedValue(param[0], customVariables),
- getResolvedValue(param[1], customVariables),
- ]);
- });
- const currentUrlParams = new URLSearchParams(window.location.search);
- currentUrlParams.forEach((value, key) => {
- if (key === 'version' || key === 'env') {
- // if version or env is in current url query param but not in resolved params then add it to resolvedQueryParams
- const exists = resolvedQueryParams.some(([resolvedKey]) => resolvedKey === key);
- if (!exists) {
- resolvedQueryParams.unshift([key, value]);
+ try {
+ const { pageId } = event;
+ if (!pageId) {
+ throw new Error('No page ID provided');
+ }
+ const { switchPage } = get();
+ const page = get().modules.canvas.pages.find((page) => page.id === event.pageId);
+ const queryParams = event.queryParams || [];
+ if (!page.disabled) {
+ const resolvedQueryParams = [];
+ queryParams.forEach((param) => {
+ resolvedQueryParams.push([
+ getResolvedValue(param[0], customVariables),
+ getResolvedValue(param[1], customVariables),
+ ]);
+ });
+ const currentUrlParams = new URLSearchParams(window.location.search);
+ currentUrlParams.forEach((value, key) => {
+ if (key === 'version' || key === 'env') {
+ // if version or env is in current url query param but not in resolved params then add it to resolvedQueryParams
+ const exists = resolvedQueryParams.some(([resolvedKey]) => resolvedKey === key);
+ if (!exists) {
+ resolvedQueryParams.unshift([key, value]);
+ }
}
- }
- });
- switchPage(page.id, page.handle, resolvedQueryParams);
- } else {
- toast.error('Page is disabled');
- //!TODO push to debugger
- get().debugger.log({
- logLevel: 'error',
- type: 'navToDisablePage',
- kind: 'page',
- message: `Attempt to switch to disabled page ${page.name} blocked.`,
- error: 'Page is disabled',
+ });
+ switchPage(page.id, page.handle, resolvedQueryParams);
+ } else {
+ toast.error('Page is disabled');
+ //!TODO push to debugger
+ get().debugger.log({
+ logLevel: 'error',
+ type: 'navToDisablePage',
+ kind: 'page',
+ message: `Attempt to switch to disabled page ${page.name} blocked.`,
+ error: 'Page is disabled',
+ });
+ }
+
+ return Promise.resolve();
+ } catch (error) {
+ get().eventsSlice.logError('switch_page', 'switch-page', error, eventObj, {
+ eventId: event.eventId,
});
}
-
- return Promise.resolve();
}
}
}
diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js
index c989df9051..cfc413aec5 100644
--- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js
@@ -201,6 +201,8 @@ export const createQueryPanelSlice = (set, get) => ({
confirmed = undefined,
mode = 'edit',
userSuppliedParameters = {},
+ component,
+ eventId,
shouldSetPreviewData = false,
isOnLoad = false,
moduleId = 'canvas'
@@ -247,8 +249,7 @@ export const createQueryPanelSlice = (set, get) => ({
if (query) {
dataQuery = JSON.parse(JSON.stringify(query));
} else {
- toast.error('No query has been associated with the action.');
- return;
+ throw new Error('No query selected');
}
if (_.isEmpty(parameters)) {
@@ -298,6 +299,7 @@ export const createQueryPanelSlice = (set, get) => ({
isLoading: true,
data: [],
rawData: [],
+ id: queryId,
});
let queryExecutionPromise = null;
@@ -367,6 +369,7 @@ export const createQueryPanelSlice = (set, get) => ({
kind: query.kind,
key: query.name,
message: errorData?.description,
+ errorTarget: 'Queries',
error:
query.kind === 'restapi'
? {
@@ -435,6 +438,7 @@ export const createQueryPanelSlice = (set, get) => ({
key: query.name,
message: 'Query executed successfully',
isQuerySuccessLog: true,
+ errorTarget: 'Queries',
});
setResolvedQuery(queryId, {
@@ -760,6 +764,7 @@ export const createQueryPanelSlice = (set, get) => ({
error: result,
isTransformation: true,
isQuerySuccessLog: result?.status === 'failed' ? false : true,
+ errorTarget: 'Queries',
});
return result;
},
diff --git a/frontend/src/Editor/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/Editor/CodeEditor/SingleLineCodeEditor.jsx
index 94707fcec3..8070b0e4cd 100644
--- a/frontend/src/Editor/CodeEditor/SingleLineCodeEditor.jsx
+++ b/frontend/src/Editor/CodeEditor/SingleLineCodeEditor.jsx
@@ -340,11 +340,11 @@ const DynamicEditorBridge = (props) => {
{paramLabel !== ' ' && !HIDDEN_CODE_HINTER_LABELS.includes(paramLabel) && (
-
+
diff --git a/frontend/src/Editor/Components/DraftEditor.jsx b/frontend/src/Editor/Components/DraftEditor.jsx
index 71b9a9de04..b2c8b61994 100644
--- a/frontend/src/Editor/Components/DraftEditor.jsx
+++ b/frontend/src/Editor/Components/DraftEditor.jsx
@@ -1,8 +1,10 @@
/* eslint-disable react/no-string-refs */
import React from 'react';
-import { Editor, EditorState, RichUtils, getDefaultKeyBinding, ContentState } from 'draft-js';
+import { Editor, EditorState, RichUtils, getDefaultKeyBinding, ContentState, convertFromHTML } from 'draft-js';
import 'draft-js/dist/Draft.css';
import { stateToHTML } from 'draft-js-export-html';
+import Loader from '@/ToolJetUI/Loader/Loader';
+import DOMPurify from 'dompurify';
// Custom overrides for "code" style.
const styleMap = {
@@ -148,10 +150,16 @@ const InlineStyleControls = (props) => {
class DraftEditor extends React.Component {
constructor(props) {
super(props);
+ const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(this.props.defaultValue));
this.state = {
- editorState: EditorState.createWithContent(ContentState.createFromText(this.props.defaultValue)),
+ editorState: EditorState.createWithContent(
+ ContentState.createFromBlockArray(blocksFromHTML.contentBlocks, blocksFromHTML.entityMap)
+ ),
};
+ this.editorContainerRef = React.createRef();
+ this.controlsRef = React.createRef();
+
this.focus = () => this.refs.editor.focus();
this.onChange = (editorState) => {
let html = stateToHTML(editorState.getCurrentContent());
@@ -165,9 +173,63 @@ class DraftEditor extends React.Component {
this.toggleInlineStyle = this._toggleInlineStyle.bind(this);
}
+ componentDidMount() {
+ //For resizing the editor container based on the height of rich text editor controls
+ this.resizeObserver = new ResizeObserver(() => {
+ if (this.controlsRef.current && this.editorContainerRef.current) {
+ const controlsHeight = this.controlsRef.current.offsetHeight;
+ const editorHeight = this.props.height - 46 - controlsHeight;
+ this.editorContainerRef.current.style.height = `${editorHeight}px`;
+ }
+ });
+
+ if (this.controlsRef.current) {
+ this.resizeObserver.observe(this.controlsRef.current);
+ }
+
+ const exposedVariables = {
+ value: this.props.defaultValue,
+ isDisabled: this.props.isDisabled,
+ isVisible: this.props.isVisible,
+ isLoading: this.props.isLoading,
+ setValue: async (text) => {
+ const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(text));
+ const newContentState = ContentState.createFromBlockArray(
+ blocksFromHTML.contentBlocks,
+ blocksFromHTML.entityMap
+ );
+ const newEditorState = EditorState.createWithContent(newContentState);
+ const html = stateToHTML(newContentState);
+ this.props.handleChange(html);
+ this.setState({ editorState: newEditorState });
+ },
+ setDisable: async (value) => {
+ this.props.setExposedVariable('isDisabled', value);
+ this.props.setIsDisabled(value);
+ },
+ setVisibility: async (value) => {
+ this.props.setExposedVariable('isVisible', value);
+ this.props.setIsVisible(value);
+ },
+ setLoading: async (value) => {
+ this.props.setExposedVariable('isLoading', value);
+ this.props.setIsLoading(value);
+ },
+ };
+ this.props.setExposedVariables(exposedVariables);
+ this.props.isInitialRender.current = false;
+ }
+
+ componentWillUnmount() {
+ if (this.resizeObserver) {
+ this.resizeObserver.disconnect();
+ }
+ }
+
componentDidUpdate(prevProps) {
if (prevProps.defaultValue !== this.props.defaultValue) {
- const newContentState = ContentState.createFromText(this.props.defaultValue);
+ const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(this.props.defaultValue));
+ const newContentState = ContentState.createFromBlockArray(blocksFromHTML.contentBlocks, blocksFromHTML.entityMap);
const newEditorState = EditorState.createWithContent(newContentState);
const html = stateToHTML(newContentState);
@@ -218,13 +280,19 @@ class DraftEditor extends React.Component {
}
}
- return (
-
-
+ return this.props.isLoading ? (
+
+
+
+
+
+ ) : (
+
+
-
+
+ }
ref="editor"
spellCheck={true}
/>
diff --git a/frontend/src/Editor/Components/NumberInput.jsx b/frontend/src/Editor/Components/NumberInput.jsx
index 5a130bf5cc..0092893734 100644
--- a/frontend/src/Editor/Components/NumberInput.jsx
+++ b/frontend/src/Editor/Components/NumberInput.jsx
@@ -72,6 +72,9 @@ export const NumberInput = function NumberInput({
useEffect(() => {
setInputValue(Number(parseFloat(properties.value).toFixed(properties.decimalPlaces)));
+ if (isNaN(Number(parseFloat(properties.value).toFixed(properties.decimalPlaces)))) {
+ setExposedVariable('value', null);
+ }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.value]);
@@ -144,6 +147,7 @@ export const NumberInput = function NumberInput({
},
clear: async function () {
setInputValue('');
+ setExposedVariable('value', null);
fireEvent('onChange');
},
setLoading: async function (loading) {
@@ -167,7 +171,8 @@ export const NumberInput = function NumberInput({
};
if (!isNaN(value)) {
exposedVariables.value = value;
- }
+ } else exposedVariables.value = null;
+
setExposedVariables(exposedVariables);
isInitialRender.current = false;
diff --git a/frontend/src/Editor/Components/RadioButtonV2/RadioButtonV2.jsx b/frontend/src/Editor/Components/RadioButtonV2/RadioButtonV2.jsx
new file mode 100644
index 0000000000..f1a5d171b4
--- /dev/null
+++ b/frontend/src/Editor/Components/RadioButtonV2/RadioButtonV2.jsx
@@ -0,0 +1,295 @@
+import React, { useEffect, useMemo, useState, useRef } from 'react';
+import Label from '@/_ui/Label';
+import cx from 'classnames';
+import './radioButtonV2.scss';
+import Loader from '@/ToolJetUI/Loader/Loader';
+import { has, isObject } from 'lodash';
+
+export const RadioButtonV2 = ({
+ properties,
+ styles,
+ fireEvent,
+ setExposedVariable,
+ setExposedVariables,
+ darkMode,
+ componentName,
+ validate,
+ validation,
+}) => {
+ const { label, value, options, disabledState, advanced, schema, optionsLoadingState, loadingState } = properties;
+
+ const {
+ activeColor,
+ direction,
+ auto: labelAutoWidth,
+ labelWidth,
+ optionsTextColor,
+ borderColor,
+ switchOffBackgroundColor,
+ handleColor,
+ switchOnBackgroundColor,
+ labelColor,
+ alignment,
+ } = styles;
+
+ const isInitialRender = useRef(true);
+
+ const [checkedValue, setCheckedValue] = useState(advanced ? findDefaultItem(schema) : value);
+ const [visibility, setVisibility] = useState(properties.visibility);
+ const [isLoading, setIsLoading] = useState(loadingState);
+ const [isDisabled, setIsDisabled] = useState(disabledState);
+
+ const isMandatory = validation?.mandatory ?? false;
+ const [validationStatus, setValidationStatus] = useState(validate(checkedValue));
+ const { isValid, validationError } = validationStatus;
+
+ const labelRef = useRef();
+ const radioBtnRef = useRef();
+
+ const selectOptions = useMemo(() => {
+ let _options = advanced ? schema : options;
+ if (Array.isArray(_options)) {
+ let _selectOptions = _options
+ .filter((data) => data?.visible ?? true)
+ .map((data) => ({
+ ...data,
+ label: data?.label,
+ value: data?.value,
+ isDisabled: data?.disable ?? false,
+ }));
+ return _selectOptions;
+ } else {
+ return [];
+ }
+ }, [advanced, schema, options]);
+
+ function findDefaultItem(optionSchema) {
+ if (!Array.isArray(optionSchema)) {
+ return undefined;
+ }
+ const foundItem = optionSchema?.find((item) => item?.default === true && item?.visible === true);
+ return foundItem?.value;
+ }
+
+ function onSelect(value) {
+ const _value = isObject(value) && has(value, 'value') ? value?.value : value;
+ setCheckedValue(_value);
+ setExposedVariable('value', _value);
+ const validationStatus = validate(_value);
+ setValidationStatus(validationStatus);
+ setExposedVariable('isValid', validationStatus?.isValid);
+ }
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ if (advanced) {
+ onSelect(findDefaultItem(schema));
+ } else onSelect(value);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [advanced, JSON.stringify(schema), value]);
+
+ useEffect(() => {
+ if (visibility !== properties.visibility) setVisibility(properties.visibility);
+ if (isLoading !== loadingState) setIsLoading(loadingState);
+ if (isDisabled !== disabledState) setIsDisabled(disabledState);
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [properties.visibility, loadingState, disabledState]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ const _options = selectOptions?.map(({ label, value }) => ({ label, value }));
+ setExposedVariable('options', _options);
+
+ setExposedVariable('selectOption', async function (value) {
+ onSelect(value);
+ fireEvent('onSelectionChange');
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [JSON.stringify(selectOptions)]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ setExposedVariable('label', label);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [label]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ const validationStatus = validate(checkedValue);
+ setValidationStatus(validationStatus);
+ setExposedVariable('isValid', validationStatus?.isValid);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [validate]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ setExposedVariable('isMandatory', isMandatory);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isMandatory]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ setExposedVariable('isLoading', loadingState);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [loadingState]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ setExposedVariable('isVisible', properties.visibility);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [properties.visibility]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ setExposedVariable('isDisabled', disabledState);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [disabledState]);
+
+ useEffect(() => {
+ const _options = selectOptions?.map(({ label, value }) => ({ label, value }));
+ const exposedVariables = {
+ value: checkedValue,
+ label: label,
+ options: _options,
+ isValid: isValid,
+ isMandatory: isMandatory,
+ isLoading: loadingState,
+ isVisible: properties.visibility,
+ isDisabled: disabledState,
+ selectOption: async function (value) {
+ onSelect(value);
+ fireEvent('onSelectionChange');
+ },
+ deselectOption: async function () {
+ onSelect(null);
+ fireEvent('onSelectionChange');
+ },
+ setVisibility: async function (value) {
+ setVisibility(value);
+ setExposedVariable('isVisible', value);
+ },
+ setDisable: async function (value) {
+ setIsDisabled(value);
+ setExposedVariable('isDisabled', value);
+ },
+ setLoading: async function (value) {
+ setIsLoading(value);
+ setExposedVariable('isLoading', value);
+ },
+ };
+ setExposedVariables(exposedVariables);
+ isInitialRender.current = false;
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const _width = (labelWidth / 100) * 70; // Max width which label can go is 70% for better UX calculate width based on this value
+
+ return (
+ <>
+
+
+
+
+ {isLoading || optionsLoadingState ? (
+
+ ) : (
+
+ {selectOptions.map((option, index) => {
+ const isChecked = checkedValue == option.value;
+ return (
+
+
+ {option.label}
+
+ {
+ onSelect(option.value);
+ fireEvent('onSelectionChange');
+ }}
+ disabled={option.isDisabled}
+ />
+
+
+ );
+ })}
+
+ )}
+
+
+
+ {!isValid && validationError}
+
+ >
+ );
+};
diff --git a/frontend/src/Editor/Components/RadioButtonV2/radioButtonV2.scss b/frontend/src/Editor/Components/RadioButtonV2/radioButtonV2.scss
new file mode 100644
index 0000000000..d7552b7bdd
--- /dev/null
+++ b/frontend/src/Editor/Components/RadioButtonV2/radioButtonV2.scss
@@ -0,0 +1,66 @@
+ /* label container */
+ .radio-button-container {
+ position: relative;
+ padding-left: 22px;
+ margin-bottom: 8px;
+ margin-right: 20px;
+ font-size: 14px;
+ line-height: 20px;
+ font-weight: 400;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
+ // /* Hide the browser's default radio button */
+ .radio-button-container input {
+ position: absolute;
+ opacity: 0;
+ cursor: pointer;
+ }
+
+ /* Create a custom radio button */
+ .checkmark {
+ position: absolute;
+ top: 2px;
+ left: 0;
+ height: 16px;
+ width: 16px;
+ border-radius: 50%;
+ }
+
+ // // /* On mouse-over, add a grey background color */
+ // .radio-button-container:focus input ~ .checkmark {
+ // border-color: red
+ // }
+
+ // /* When the radio button is checked */
+ .radio-button-container input:checked ~ .checkmark {
+ background-color: var(--selected-background-color);
+ border-color: var(--selected-border-color);
+ }
+
+
+ // /* Create the indicator (the dot/circle - hidden when not checked) */
+ .checkmark:after {
+ content: "";
+ position: absolute;
+ display: none;
+ }
+
+ // /* Show the indicator (dot/circle) when checked */
+ .radio-button-container input:checked ~ .checkmark:after {
+ display: block;
+ }
+
+ // /* Style the indicator (dot/circle) */
+ .radio-button-container .checkmark:after {
+ transform: translate(50%, 50%);
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--selected-handle-color);
+ }
+
\ No newline at end of file
diff --git a/frontend/src/Editor/Components/RichTextEditor.jsx b/frontend/src/Editor/Components/RichTextEditor.jsx
index 4115d8deb8..2a591afba0 100644
--- a/frontend/src/Editor/Components/RichTextEditor.jsx
+++ b/frontend/src/Editor/Components/RichTextEditor.jsx
@@ -1,4 +1,5 @@
-import React, { useEffect } from 'react';
+/* eslint-disable react-hooks/exhaustive-deps */
+import React, { useEffect, useRef, useState } from 'react';
import 'draft-js/dist/Draft.css';
import { DraftEditor } from './DraftEditor';
@@ -8,17 +9,38 @@ export const RichTextEditor = function RichTextEditor({
properties,
styles,
setExposedVariable,
+ setExposedVariables,
dataCy,
}) {
+ const isInitialRender = useRef(true);
const { visibility, disabledState, boxShadow } = styles;
const placeholder = properties.placeholder;
const defaultValue = properties?.defaultValue ?? '';
- // exposing the default value at first
+ const [isDisabled, setIsDisabled] = useState(disabledState);
+ const [isVisible, setIsVisible] = useState(visibility);
+ const [isLoading, setIsLoading] = useState(properties?.loadingState);
+
useEffect(() => {
- setExposedVariable('value', defaultValue);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ if (isDisabled !== disabledState) setIsDisabled(disabledState);
+ if (isVisible !== visibility) setIsVisible(visibility);
+ if (isLoading !== properties.loadingState) setIsLoading(properties.loadingState);
+ }, [properties.loadingState, styles.visibility, styles.disabledState]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ setExposedVariable('isDisabled', disabledState);
+ }, [disabledState]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ setExposedVariable('isVisible', visibility);
+ }, [visibility]);
+
+ useEffect(() => {
+ if (isInitialRender.current) return;
+ setExposedVariable('isLoading', isLoading);
+ }, [isLoading]);
function handleChange(html) {
setExposedVariable('value', html);
@@ -26,16 +48,25 @@ export const RichTextEditor = function RichTextEditor({
return (
);
diff --git a/frontend/src/Editor/Inspector/Elements/Components/ToolTip.jsx b/frontend/src/Editor/Inspector/Elements/Components/ToolTip.jsx
index 218bbe5e24..57e653ae4c 100644
--- a/frontend/src/Editor/Inspector/Elements/Components/ToolTip.jsx
+++ b/frontend/src/Editor/Inspector/Elements/Components/ToolTip.jsx
@@ -18,8 +18,12 @@ export const ToolTip = ({ label, meta, labelClass, bold = false }) => {
if (meta?.tip) {
return (
-
-
+
+
{label}
diff --git a/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx b/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx
index 2af66fd6e7..ea7f0e298b 100644
--- a/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx
+++ b/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx
@@ -8,12 +8,11 @@ import { useEditorActions, useEditorStore } from '@/_stores/editorStore';
function Logs({ logProps, idx }) {
const [open, setOpen] = React.useState(false);
- let titleLogType = logProps?.type;
- // need to change the titleLogType to query for transformations because if transformation fails, it is eventually a query failure
+ let titleLogType = logProps?.type !== 'event' ? logProps?.type : '';
if (titleLogType === 'transformations') {
titleLogType = 'query';
}
- const title = ` [${capitalize(titleLogType)} ${logProps?.key}]`;
+ const title = logProps?.key;
const message =
logProps?.type === 'navToDisablePage'
? logProps?.message
@@ -21,19 +20,21 @@ function Logs({ logProps, idx }) {
? 'Completed'
: logProps?.type === 'component'
? `Invalid property detected: ${logProps?.message}.`
+ : logProps?.type === 'Custom Log'
+ ? logProps?.description
: `${startCase(logProps?.type)} failed: ${
logProps?.description ||
- logProps?.message ||
+ (isString(logProps?.message) && logProps?.message) ||
(isString(logProps?.error?.description) && logProps?.error?.description) || //added string check since description can be an object. eg: runpy
- logProps?.error?.message
+ logProps?.error?.message.trim()
}`;
const defaultStyles = {
- transform: open ? 'rotate(90deg)' : 'rotate(0deg)',
+ transform: open ? 'rotate(0deg)' : 'rotate(-90deg)',
transition: '0.2s all',
display: logProps?.isQuerySuccessLog || logProps.type === 'navToDisablePage' ? 'none' : 'inline-block',
cursor: 'pointer',
- paddingTop: '8px',
+ top: '8px',
pointerEvents: logProps?.isQuerySuccessLog || logProps.type === 'navToDisablePage' ? 'none' : 'default',
};
@@ -85,20 +86,25 @@ function Logs({ logProps, idx }) {
onClick={(e) => {
setOpen((prev) => !prev);
}}
- style={{ pointerEvents: logProps?.isQuerySuccessLog ? 'none' : 'default' }}
+ style={{ pointerEvents: logProps?.isQuerySuccessLog ? 'none' : 'default', position: 'relative' }}
>
-
+
{logProps.type === 'navToDisablePage' ? (
renderNavToDisabledPageMessage()
) : (
<>
-
- {title}
+
+
{logProps?.errorTarget}
{moment(logProps?.timestamp).fromNow()}
-
+
+
+
+
+
+
typeof value === 'string' || value instanceof String;
export default Logs;
+
+const HighlightSecondWord = ({ text }) => {
+ const processedText = text.split(/(\[.*?\])/).map((segment, index) => {
+ if (segment.startsWith('[') && segment.endsWith(']')) {
+ const content = segment.slice(1, -1).split(' ');
+ const firstWord = content[0];
+ const secondWord = content[1];
+
+ return (
+
+ [{firstWord} {secondWord} ]
+
+ );
+ }
+ return segment;
+ });
+
+ return {processedText} ;
+};
diff --git a/frontend/src/Editor/WidgetManager/configs/container.js b/frontend/src/Editor/WidgetManager/configs/container.js
index 32d4158ffc..86e0e9d514 100644
--- a/frontend/src/Editor/WidgetManager/configs/container.js
+++ b/frontend/src/Editor/WidgetManager/configs/container.js
@@ -15,22 +15,86 @@ export const containerConfig = {
loadingState: {
type: 'toggle',
displayName: 'Loading state',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: false,
+ },
+ },
+ visibility: {
+ type: 'toggle',
+ displayName: 'Visibility',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: true,
+ },
+ },
+ disabledState: {
+ type: 'toggle',
+ section: 'additionalActions',
+ displayName: 'Disable',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: false,
+ },
+ },
+ showHeader: {
+ type: 'toggle',
+ displayName: 'Show header',
validation: {
schema: { type: 'boolean' },
defaultValue: false,
},
},
},
+ defaultChildren: [
+ {
+ componentName: 'Text',
+ layout: {
+ top: 20,
+ left: 1,
+ height: 40,
+ },
+ displayName: 'ContainerText',
+ properties: ['text'],
+ accessorKey: 'text',
+ styles: ['fontWeight', 'textSize', 'textColor'],
+ defaultValue: {
+ text: 'Container title',
+ fontWeight: 'bold',
+ textSize: 16,
+ textColor: '#000',
+ },
+ },
+ ],
events: {},
styles: {
backgroundColor: {
type: 'color',
- displayName: 'Background color',
+ displayName: 'Background',
validation: {
schema: { type: 'string' },
defaultValue: '#fff',
},
},
+ headerBackgroundColor: {
+ type: 'color',
+ displayName: 'Header',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: '#fff',
+ },
+ },
+ headerHeight: {
+ type: 'numberInput',
+ displayName: 'Header height',
+ validation: {
+ schema: { type: 'number' },
+ defaultValue: 80,
+ },
+ accordian: 'field',
+ },
borderRadius: {
type: 'code',
displayName: 'Border radius',
@@ -50,40 +114,48 @@ export const containerConfig = {
defaultValue: '#fff',
},
},
- visibility: {
- type: 'toggle',
- displayName: 'Visibility',
- validation: {
- schema: { type: 'boolean' },
- defaultValue: true,
- },
- },
- disabledState: {
- type: 'toggle',
- displayName: 'Disable',
- validation: {
- schema: { type: 'boolean' },
- },
- defaultValue: false,
- },
},
- exposedVariables: {},
+ exposedVariables: {
+ isVisible: true,
+ isDisabled: false,
+ isLoading: false,
+ },
+ actions: [
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set disable',
+ params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set loading',
+ params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ ],
definition: {
others: {
showOnDesktop: { value: '{{true}}' },
showOnMobile: { value: '{{false}}' },
},
properties: {
- visible: { value: '{{true}}' },
loadingState: { value: `{{false}}` },
+ visibility: { value: '{{true}}' },
+ disabledState: { value: '{{false}}' },
},
events: [],
styles: {
backgroundColor: { value: '#fff' },
+ headerBackgroundColor: { value: '#fff' },
borderRadius: { value: '4' },
borderColor: { value: '#fff' },
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
+ headerHeight: { value: '80' },
},
},
};
diff --git a/frontend/src/Editor/WidgetManager/configs/index.js b/frontend/src/Editor/WidgetManager/configs/index.js
index 1a875d6fa2..d73cd8934f 100644
--- a/frontend/src/Editor/WidgetManager/configs/index.js
+++ b/frontend/src/Editor/WidgetManager/configs/index.js
@@ -9,6 +9,7 @@ import { passinputConfig } from './passwordinput';
import { datepickerConfig } from './datepicker';
import { checkboxConfig } from './checkbox';
import { radiobuttonConfig } from './radiobutton';
+import { radiobuttonV2Config } from './radioButtonV2';
import { toggleswitchConfig } from './toggleswitch';
import { toggleSwitchV2Config } from './toggleswitchv2';
import { textareaConfig } from './textarea';
@@ -65,7 +66,8 @@ export {
passinputConfig,
datepickerConfig,
checkboxConfig,
- radiobuttonConfig,
+ radiobuttonConfig, //!Depreciated
+ radiobuttonV2Config,
toggleswitchConfig, //!Depreciated
toggleSwitchV2Config,
textareaConfig,
diff --git a/frontend/src/Editor/WidgetManager/configs/numberinput.js b/frontend/src/Editor/WidgetManager/configs/numberinput.js
index 148754d5be..f5bc2b1490 100644
--- a/frontend/src/Editor/WidgetManager/configs/numberinput.js
+++ b/frontend/src/Editor/WidgetManager/configs/numberinput.js
@@ -236,7 +236,7 @@ export const numberinputConfig = {
},
],
exposedVariables: {
- value: 99,
+ value: 0,
isMandatory: false,
isVisible: true,
isDisabled: false,
diff --git a/frontend/src/Editor/WidgetManager/configs/radioButtonV2.js b/frontend/src/Editor/WidgetManager/configs/radioButtonV2.js
new file mode 100644
index 0000000000..2ff6ca6c7c
--- /dev/null
+++ b/frontend/src/Editor/WidgetManager/configs/radioButtonV2.js
@@ -0,0 +1,295 @@
+export const radiobuttonV2Config = {
+ name: 'RadioButton',
+ displayName: 'Radio Button',
+ description: 'Select one from multiple choices',
+ component: 'RadioButtonV2',
+ defaultSize: {
+ width: 12,
+ height: 43,
+ },
+ others: {
+ showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
+ showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
+ },
+ validation: {
+ customRule: {
+ type: 'code',
+ displayName: 'Custom validation',
+ placeholder: `{{components.text2.text=='yes'&&'valid'}}`,
+ },
+ mandatory: { type: 'toggle', displayName: 'Make this field mandatory' },
+ },
+ properties: {
+ label: {
+ type: 'code',
+ displayName: 'Label',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: 'Select',
+ },
+ accordian: 'Data',
+ },
+ advanced: {
+ type: 'toggle',
+ displayName: 'Dynamic options',
+ validation: {
+ schema: { type: 'boolean' },
+ },
+ accordian: 'Options',
+ },
+ schema: {
+ type: 'code',
+ displayName: 'Schema',
+ conditionallyRender: {
+ key: 'advanced',
+ value: true,
+ },
+ accordian: 'Options',
+ },
+ optionsLoadingState: {
+ type: 'toggle',
+ displayName: 'Options loading state',
+ validation: {
+ schema: { type: 'boolean' },
+ },
+ accordian: 'Options',
+ },
+ loadingState: {
+ type: 'toggle',
+ displayName: 'Loading state',
+ validation: { schema: { type: 'boolean' }, defaultValue: true },
+ section: 'additionalActions',
+ },
+ visibility: {
+ type: 'toggle',
+ displayName: 'Visibility',
+ validation: { schema: { type: 'boolean' }, defaultValue: true },
+
+ section: 'additionalActions',
+ },
+ disabledState: {
+ type: 'toggle',
+ displayName: 'Disable',
+ validation: { schema: { type: 'boolean' }, defaultValue: true },
+ section: 'additionalActions',
+ },
+ tooltip: {
+ type: 'code',
+ displayName: 'Tooltip',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: 'Enter tooltip text',
+ },
+ section: 'additionalActions',
+ placeholder: 'Enter tooltip text',
+ },
+ },
+ events: {
+ onSelectionChange: { displayName: 'On select' },
+ },
+ styles: {
+ labelColor: {
+ type: 'color',
+ displayName: 'Color',
+ validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' },
+ accordian: 'label',
+ },
+ alignment: {
+ type: 'switch',
+ displayName: 'Alignment',
+ validation: { schema: { type: 'string' }, defaultValue: 'side' },
+ options: [
+ { displayName: 'Side', value: 'side' },
+ { displayName: 'Top', value: 'top' },
+ ],
+ accordian: 'label',
+ },
+ direction: {
+ type: 'switch',
+ displayName: 'Direction',
+ validation: { schema: { type: 'string' }, defaultValue: 'left' },
+ showLabel: false,
+ isIcon: true,
+ options: [
+ { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' },
+ { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' },
+ ],
+ accordian: 'label',
+ },
+ labelWidth: {
+ type: 'slider',
+ displayName: 'Width',
+ accordian: 'label',
+ conditionallyRender: {
+ key: 'alignment',
+ value: 'side',
+ },
+ isFxNotRequired: true,
+ },
+ auto: {
+ type: 'checkbox',
+ displayName: 'auto',
+ showLabel: false,
+ validation: { schema: { type: 'boolean' } },
+ accordian: 'label',
+ conditionallyRender: {
+ key: 'alignment',
+ value: 'side',
+ },
+ isFxNotRequired: true,
+ },
+ borderColor: {
+ type: 'color',
+ displayName: 'Border',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ },
+ switchOnBackgroundColor: {
+ type: 'color',
+ displayName: 'Checked background',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ tip: 'Checked background',
+ tooltipStyle: {},
+ tooltipPlacement: 'bottom',
+ },
+ switchOffBackgroundColor: {
+ type: 'color',
+ displayName: 'Unchecked background',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ tip: 'Unchecked background',
+ tooltipStyle: {},
+ tooltipPlacement: 'bottom',
+ },
+ handleColor: {
+ type: 'color',
+ displayName: 'Handle color',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ },
+ optionsTextColor: {
+ type: 'color',
+ displayName: 'Text',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ },
+ padding: {
+ type: 'switch',
+ displayName: 'Padding',
+ validation: {
+ schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
+ defaultValue: 'default',
+ },
+ isFxNotRequired: true,
+ options: [
+ { displayName: 'Default', value: 'default' },
+ { displayName: 'None', value: 'none' },
+ ],
+ accordian: 'container',
+ },
+ },
+ actions: [
+ {
+ handle: 'selectOption',
+ displayName: 'Select option',
+ params: [{ handle: 'option', displayName: 'Option' }],
+ },
+ {
+ handle: 'deselectOption',
+ displayName: 'Deselect option',
+ params: [{ handle: 'option', displayName: 'Option' }],
+ },
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: `{{true}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set loading',
+ params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set disable',
+ params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ ],
+ exposedVariables: {
+ label: 'Select',
+ },
+ definition: {
+ others: {
+ showOnDesktop: { value: '{{true}}' },
+ showOnMobile: { value: '{{false}}' },
+ },
+ validation: {
+ mandatory: { value: '{{false}}' },
+ },
+ properties: {
+ label: { value: 'Select' },
+ value: { value: '{{"2"}}' },
+ advanced: { value: `{{false}}` },
+ options: {
+ value: [
+ {
+ label: 'option1',
+ value: '1',
+ disable: { value: false },
+ visible: { value: true },
+ default: { value: false },
+ },
+ {
+ label: 'option2',
+ value: '2',
+ disable: { value: false },
+ visible: { value: true },
+ default: { value: true },
+ },
+ {
+ label: 'option3',
+ value: '3',
+ disable: { value: false },
+ visible: { value: true },
+ default: { value: false },
+ },
+ ],
+ },
+ visibility: { value: '{{true}}' },
+ disabledState: { value: '{{false}}' },
+ loadingState: { value: '{{false}}' },
+ optionsLoadingState: { value: '{{false}}' },
+ optionVisibility: { value: '{{[true, true, true]}}' },
+ optionDisable: { value: '{{[false, false, false]}}' },
+ schema: {
+ value:
+ "{{[\t{label: 'option1',value: '1',disable: false,visible: true,default: true},{label: 'option2',value: '2',disable: false,visible: true},{label: 'option3',value: '3',disable: false,visible: true}\t]}}",
+ },
+ },
+ events: [],
+ styles: {
+ labelColor: { value: '#11181C' },
+ direction: { value: 'left' },
+ alignment: { value: 'side' },
+ auto: { value: '{{false}}' },
+ labelWidth: { value: '20' },
+ borderColor: { value: '#FFFFFF' },
+ switchOffBackgroundColor: { value: '#FFFFFF' },
+ switchOnBackgroundColor: { value: '#4368E3' },
+ handleColor: { value: '#FFFFFF' },
+ optionsTextColor: { value: '#11181C' },
+ padding: { value: 'default' },
+ },
+ },
+};
diff --git a/frontend/src/Editor/WidgetManager/configs/radiobutton.js b/frontend/src/Editor/WidgetManager/configs/radiobutton.js
index de1e210894..d6ec057526 100644
--- a/frontend/src/Editor/WidgetManager/configs/radiobutton.js
+++ b/frontend/src/Editor/WidgetManager/configs/radiobutton.js
@@ -1,6 +1,6 @@
export const radiobuttonConfig = {
- name: 'RadioButton',
- displayName: 'Radio Button',
+ name: 'RadioButtonLegacy',
+ displayName: 'Radio Button (Legacy)',
description: 'Select one from multiple choices',
component: 'RadioButton',
defaultSize: {
diff --git a/frontend/src/Editor/WidgetManager/configs/richtextarea.js b/frontend/src/Editor/WidgetManager/configs/richtextarea.js
index eacee66529..c964d53d6b 100644
--- a/frontend/src/Editor/WidgetManager/configs/richtextarea.js
+++ b/frontend/src/Editor/WidgetManager/configs/richtextarea.js
@@ -28,6 +28,15 @@ export const richtextareaConfig = {
defaultValue: 'Default text',
},
},
+ loadingState: {
+ type: 'toggle',
+ displayName: 'Show loading state',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: false,
+ },
+ section: 'additionalActions',
+ },
},
events: {},
styles: {
@@ -55,6 +64,28 @@ export const richtextareaConfig = {
exposedVariables: {
value: '',
},
+ actions: [
+ {
+ handle: 'setValue',
+ displayName: 'Set value',
+ params: [{ handle: 'value', displayName: 'Value', defaultValue: 'New text' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set disable',
+ params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: `{{true}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set loading',
+ params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ ],
definition: {
others: {
showOnDesktop: { value: '{{true}}' },
@@ -63,6 +94,7 @@ export const richtextareaConfig = {
properties: {
placeholder: { value: 'Placeholder text' },
defaultValue: { value: '' },
+ loadingState: { value: `{{false}}` },
},
events: [],
styles: {
diff --git a/frontend/src/Editor/WidgetManager/configs/table.js b/frontend/src/Editor/WidgetManager/configs/table.js
index 554e6f6cc7..df2abc4cbd 100644
--- a/frontend/src/Editor/WidgetManager/configs/table.js
+++ b/frontend/src/Editor/WidgetManager/configs/table.js
@@ -289,6 +289,7 @@ export const tableConfig = {
onCellValueChanged: { displayName: 'Cell value changed' },
onFilterChanged: { displayName: 'Filter changed' },
onNewRowsAdded: { displayName: 'Add new rows' },
+ onTableDataDownload: { displayName: 'Download data' },
},
styles: {
textColor: {
@@ -310,6 +311,16 @@ export const tableConfig = {
{ displayName: 'Wrap', value: 'wrap' },
],
},
+ headerCasing: {
+ type: 'switch',
+ displayName: 'Header casing',
+ validation: { schema: { type: 'string' } },
+ accordian: 'Data',
+ options: [
+ { displayName: 'AA', value: 'uppercase' },
+ { displayName: 'As typed', value: 'none' },
+ ],
+ },
tableType: {
type: 'select',
displayName: 'Row style',
@@ -529,6 +540,7 @@ export const tableConfig = {
value: [
{
name: 'id',
+ key: 'id',
id: 'e3ecbf7fa52c4d7210a93edb8f43776267a489bad52bd108be9588f790126737',
autogenerated: true,
fxActiveFields: [],
@@ -548,6 +560,7 @@ export const tableConfig = {
},
{
name: 'name',
+ key: 'name',
id: '5d2a3744a006388aadd012fcc15cc0dbcb5f9130e0fbb64c558561c97118754a',
autogenerated: true,
fxActiveFields: [],
@@ -556,6 +569,7 @@ export const tableConfig = {
},
{
name: 'email',
+ key: 'email',
id: 'afc9a5091750a1bd4760e38760de3b4be11a43452ae8ae07ce2eebc569fe9a7f',
autogenerated: true,
fxActiveFields: [],
@@ -564,6 +578,7 @@ export const tableConfig = {
},
{
name: 'date',
+ key: 'date',
id: '27b75c8af9d34d1eaa1f9bb7f8f9f7b0abf1823e799748c8bb57e74f53b2c1dc',
autogenerated: true,
fxActiveFields: [],
@@ -576,6 +591,7 @@ export const tableConfig = {
},
{
name: 'mobile_number',
+ key: 'mobile_number',
id: '9c2e3c40572a4aefb8e179ee39a0e1ac9dc2b2e6634be56e1c05be13c3d1de56',
autogenerated: true,
fxActiveFields: [],
@@ -652,6 +668,7 @@ export const tableConfig = {
styles: {
textColor: { value: '#000' },
columnHeaderWrap: { value: 'fixed' },
+ headerCasing: { value: 'uppercase' },
actionButtonRadius: { value: '0' },
cellSize: { value: 'regular' },
borderRadius: { value: '8' },
diff --git a/frontend/src/Editor/WidgetManager/constants.js b/frontend/src/Editor/WidgetManager/constants.js
index 4653d6ff72..35ea9d5252 100644
--- a/frontend/src/Editor/WidgetManager/constants.js
+++ b/frontend/src/Editor/WidgetManager/constants.js
@@ -1 +1 @@
-export const LEGACY_ITEMS = ['ToggleSwitchLegacy', 'DropdownLegacy', 'MultiselectLegacy'];
+export const LEGACY_ITEMS = ['ToggleSwitchLegacy', 'DropdownLegacy', 'MultiselectLegacy', 'RadioButtonLegacy'];
diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js
index a7b020e451..30bfdfc7f2 100644
--- a/frontend/src/Editor/WidgetManager/widgetConfig.js
+++ b/frontend/src/Editor/WidgetManager/widgetConfig.js
@@ -10,6 +10,7 @@ import {
datepickerConfig,
checkboxConfig,
radiobuttonConfig,
+ radiobuttonV2Config,
toggleswitchConfig,
toggleSwitchV2Config,
textareaConfig,
@@ -68,6 +69,7 @@ export const widgets = [
datepickerConfig,
checkboxConfig,
radiobuttonConfig,
+ radiobuttonV2Config,
toggleswitchConfig,
toggleSwitchV2Config,
textareaConfig,
diff --git a/frontend/src/_styles/left-sidebar.scss b/frontend/src/_styles/left-sidebar.scss
index 0a5b441d66..5a9d022485 100644
--- a/frontend/src/_styles/left-sidebar.scss
+++ b/frontend/src/_styles/left-sidebar.scss
@@ -232,11 +232,24 @@
}
.debugger-content {
+ padding: 0px 16px;
background-color: var(--base);
+ cursor: pointer;
hr {
- margin-top: 0px !important;
- margin-bottom: 16px !important;
+ margin: 0px !important;
+ margin-top: 16px !important;
+ }
+
+ &:hover {
+ background-color: var(--slate3);
+ }
+
+ .error-target {
+ background-color: var(--interactive-overlays-fill-hover) !important;
+ padding: 4px 7px;
+ border-radius: 7px;
+ color: var(--slate10)
}
}
diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss
index c2713edf51..2fe54d0af5 100644
--- a/frontend/src/_styles/theme.scss
+++ b/frontend/src/_styles/theme.scss
@@ -14400,7 +14400,6 @@ color: var(--text-default);
.debugger-card-body {
margin-top: 8px;
margin-bottom: 16px;
- padding: 0px 16px;
}
.left-sidebar-header-btn {
@@ -14743,7 +14742,6 @@ color: var(--text-default);
.debugger-card-body {
margin-top: 8px;
margin-bottom: 16px;
- padding: 0px 16px;
}
.left-sidebar-header-btn {
@@ -15103,7 +15101,6 @@ color: var(--text-default);
.debugger-card-body {
margin-top: 8px;
margin-bottom: 16px;
- padding: 0px 16px;
}
.left-sidebar-header-btn {
diff --git a/server/.version b/server/.version
index a35c54823f..c9e290188f 100644
--- a/server/.version
+++ b/server/.version
@@ -1 +1 @@
-3.0.5-ce
+3.1.0-ce
diff --git a/server/package.json b/server/package.json
index 4f66f7de3d..708d9f910d 100644
--- a/server/package.json
+++ b/server/package.json
@@ -7,6 +7,7 @@
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
+ "postbuild": "npm run copy-schemas",
"lint": "eslint . '**/*.ts'",
"format": "eslint . --fix '**/*.ts'",
"start": "nest start",
@@ -32,7 +33,8 @@
"db:setup": "npm run db:create && npm run db:migrate",
"db:setup:prod": "npm run db:create:prod && npm run db:migrate:prod",
"db:reset": "npm run db:drop && npm run db:setup",
- "typeorm": "typeorm-ts-node-commonjs"
+ "typeorm": "typeorm-ts-node-commonjs",
+ "copy-schemas": "copyfiles -u 4 src/dto/validators/schemas/**/*.json dist/src/dto/validators/schemas"
},
"dependencies": {
"@casl/ability": "^5.3.1",
@@ -60,6 +62,7 @@
"class-validator": "^0.14.0",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
+ "copyfiles": "^2.4.1",
"dotenv": "^10.0.0",
"express-http-proxy": "^1.6.3",
"fast-csv": "^4.3.6",
@@ -76,8 +79,8 @@
"lodash": "^4.17.21",
"module-from-string": "^3.3.0",
"nestjs-pino": "^1.4.0",
- "node-sql-parser": "^5.3.1",
"node-mailer": "^0.1.1",
+ "node-sql-parser": "^5.3.1",
"nodemailer": "^6.6.3",
"passport": "^0.7.0",
"passport-jwt": "^4.0.0",
@@ -152,4 +155,4 @@
"node": "18.18.2",
"npm": "9.8.1"
}
-}
\ No newline at end of file
+}
diff --git a/server/src/helpers/utils.helper.ts b/server/src/helpers/utils.helper.ts
index 41dde11284..db24cae2f2 100644
--- a/server/src/helpers/utils.helper.ts
+++ b/server/src/helpers/utils.helper.ts
@@ -433,4 +433,4 @@ export const getSubpath = () => {
}
}
return subpath;
-};
\ No newline at end of file
+};
diff --git a/server/src/helpers/widget-config/container.js b/server/src/helpers/widget-config/container.js
index 32d4158ffc..ed0ba5537e 100644
--- a/server/src/helpers/widget-config/container.js
+++ b/server/src/helpers/widget-config/container.js
@@ -15,6 +15,33 @@ export const containerConfig = {
loadingState: {
type: 'toggle',
displayName: 'Loading state',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: false,
+ },
+ },
+ visibility: {
+ type: 'toggle',
+ displayName: 'Visibility',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: true,
+ },
+ },
+ disabledState: {
+ type: 'toggle',
+ displayName: 'Disable',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: false,
+ },
+ },
+ showHeader: {
+ type: 'toggle',
+ displayName: 'Show header',
validation: {
schema: { type: 'boolean' },
defaultValue: false,
@@ -25,12 +52,29 @@ export const containerConfig = {
styles: {
backgroundColor: {
type: 'color',
- displayName: 'Background color',
+ displayName: 'Background',
validation: {
schema: { type: 'string' },
defaultValue: '#fff',
},
},
+ headerBackgroundColor: {
+ type: 'color',
+ displayName: 'Header',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: '#fff',
+ },
+ },
+ headerHeight: {
+ type: 'numberInput',
+ displayName: 'Header height',
+ validation: {
+ schema: { type: 'number' },
+ defaultValue: 80,
+ },
+ accordian: 'field',
+ },
borderRadius: {
type: 'code',
displayName: 'Border radius',
@@ -50,40 +94,67 @@ export const containerConfig = {
defaultValue: '#fff',
},
},
- visibility: {
- type: 'toggle',
- displayName: 'Visibility',
- validation: {
- schema: { type: 'boolean' },
- defaultValue: true,
- },
- },
- disabledState: {
- type: 'toggle',
- displayName: 'Disable',
- validation: {
- schema: { type: 'boolean' },
- },
- defaultValue: false,
- },
},
- exposedVariables: {},
+
+ defaultChildren: [
+ {
+ componentName: 'Text',
+ layout: {
+ top: 20,
+ left: 1,
+ height: 40,
+ },
+ displayName: 'ContainerText',
+ properties: ['text'],
+ accessorKey: 'text',
+ styles: ['fontWeight', 'textSize', 'textColor'],
+ defaultValue: {
+ text: 'Container title',
+ fontWeight: 'bold',
+ textSize: 16,
+ textColor: '#000',
+ },
+ },
+ ],
+ exposedVariables: {
+ isVisible: true,
+ isDisabled: false,
+ isLoading: false,
+ },
+ actions: [
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set disable',
+ params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set loading',
+ params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ ],
definition: {
others: {
showOnDesktop: { value: '{{true}}' },
showOnMobile: { value: '{{false}}' },
},
properties: {
- visible: { value: '{{true}}' },
loadingState: { value: `{{false}}` },
+ visibility: { value: '{{true}}' },
+ disabledState: { value: '{{false}}' },
},
events: [],
styles: {
backgroundColor: { value: '#fff' },
+ headerBackgroundColor: { value: '#fff' },
borderRadius: { value: '4' },
borderColor: { value: '#fff' },
- visibility: { value: '{{true}}' },
- disabledState: { value: '{{false}}' },
+ headerHeight: { value: `80`, },
},
},
};
diff --git a/server/src/helpers/widget-config/index.js b/server/src/helpers/widget-config/index.js
index 54a6964eff..1482001a4b 100644
--- a/server/src/helpers/widget-config/index.js
+++ b/server/src/helpers/widget-config/index.js
@@ -9,6 +9,7 @@ import { passinputConfig } from './passwordinput';
import { datepickerConfig } from './datepicker';
import { checkboxConfig } from './checkbox';
import { radiobuttonConfig } from './radiobutton';
+import { radiobuttonV2Config } from './radioButtonV2';
import { toggleswitchConfig } from './toggleswitch';
import { toggleSwitchV2Config } from './toggleswitchv2';
import { textareaConfig } from './textarea';
@@ -65,7 +66,8 @@ const widgets = {
passinputConfig,
datepickerConfig,
checkboxConfig,
- radiobuttonConfig,
+ radiobuttonConfig, //!Depreciated
+ radiobuttonV2Config,
toggleswitchConfig, //!Depreciated
toggleSwitchV2Config,
textareaConfig,
diff --git a/server/src/helpers/widget-config/numberinput.js b/server/src/helpers/widget-config/numberinput.js
index ba7f2bc227..b0f72e6d81 100644
--- a/server/src/helpers/widget-config/numberinput.js
+++ b/server/src/helpers/widget-config/numberinput.js
@@ -236,7 +236,7 @@ export const numberinputConfig = {
},
],
exposedVariables: {
- value: 99,
+ value: 0,
isMandatory: false,
isVisible: true,
isDisabled: false,
diff --git a/server/src/helpers/widget-config/radioButtonV2.js b/server/src/helpers/widget-config/radioButtonV2.js
new file mode 100644
index 0000000000..2ff6ca6c7c
--- /dev/null
+++ b/server/src/helpers/widget-config/radioButtonV2.js
@@ -0,0 +1,295 @@
+export const radiobuttonV2Config = {
+ name: 'RadioButton',
+ displayName: 'Radio Button',
+ description: 'Select one from multiple choices',
+ component: 'RadioButtonV2',
+ defaultSize: {
+ width: 12,
+ height: 43,
+ },
+ others: {
+ showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
+ showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
+ },
+ validation: {
+ customRule: {
+ type: 'code',
+ displayName: 'Custom validation',
+ placeholder: `{{components.text2.text=='yes'&&'valid'}}`,
+ },
+ mandatory: { type: 'toggle', displayName: 'Make this field mandatory' },
+ },
+ properties: {
+ label: {
+ type: 'code',
+ displayName: 'Label',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: 'Select',
+ },
+ accordian: 'Data',
+ },
+ advanced: {
+ type: 'toggle',
+ displayName: 'Dynamic options',
+ validation: {
+ schema: { type: 'boolean' },
+ },
+ accordian: 'Options',
+ },
+ schema: {
+ type: 'code',
+ displayName: 'Schema',
+ conditionallyRender: {
+ key: 'advanced',
+ value: true,
+ },
+ accordian: 'Options',
+ },
+ optionsLoadingState: {
+ type: 'toggle',
+ displayName: 'Options loading state',
+ validation: {
+ schema: { type: 'boolean' },
+ },
+ accordian: 'Options',
+ },
+ loadingState: {
+ type: 'toggle',
+ displayName: 'Loading state',
+ validation: { schema: { type: 'boolean' }, defaultValue: true },
+ section: 'additionalActions',
+ },
+ visibility: {
+ type: 'toggle',
+ displayName: 'Visibility',
+ validation: { schema: { type: 'boolean' }, defaultValue: true },
+
+ section: 'additionalActions',
+ },
+ disabledState: {
+ type: 'toggle',
+ displayName: 'Disable',
+ validation: { schema: { type: 'boolean' }, defaultValue: true },
+ section: 'additionalActions',
+ },
+ tooltip: {
+ type: 'code',
+ displayName: 'Tooltip',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: 'Enter tooltip text',
+ },
+ section: 'additionalActions',
+ placeholder: 'Enter tooltip text',
+ },
+ },
+ events: {
+ onSelectionChange: { displayName: 'On select' },
+ },
+ styles: {
+ labelColor: {
+ type: 'color',
+ displayName: 'Color',
+ validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' },
+ accordian: 'label',
+ },
+ alignment: {
+ type: 'switch',
+ displayName: 'Alignment',
+ validation: { schema: { type: 'string' }, defaultValue: 'side' },
+ options: [
+ { displayName: 'Side', value: 'side' },
+ { displayName: 'Top', value: 'top' },
+ ],
+ accordian: 'label',
+ },
+ direction: {
+ type: 'switch',
+ displayName: 'Direction',
+ validation: { schema: { type: 'string' }, defaultValue: 'left' },
+ showLabel: false,
+ isIcon: true,
+ options: [
+ { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' },
+ { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' },
+ ],
+ accordian: 'label',
+ },
+ labelWidth: {
+ type: 'slider',
+ displayName: 'Width',
+ accordian: 'label',
+ conditionallyRender: {
+ key: 'alignment',
+ value: 'side',
+ },
+ isFxNotRequired: true,
+ },
+ auto: {
+ type: 'checkbox',
+ displayName: 'auto',
+ showLabel: false,
+ validation: { schema: { type: 'boolean' } },
+ accordian: 'label',
+ conditionallyRender: {
+ key: 'alignment',
+ value: 'side',
+ },
+ isFxNotRequired: true,
+ },
+ borderColor: {
+ type: 'color',
+ displayName: 'Border',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ },
+ switchOnBackgroundColor: {
+ type: 'color',
+ displayName: 'Checked background',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ tip: 'Checked background',
+ tooltipStyle: {},
+ tooltipPlacement: 'bottom',
+ },
+ switchOffBackgroundColor: {
+ type: 'color',
+ displayName: 'Unchecked background',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ tip: 'Unchecked background',
+ tooltipStyle: {},
+ tooltipPlacement: 'bottom',
+ },
+ handleColor: {
+ type: 'color',
+ displayName: 'Handle color',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ },
+ optionsTextColor: {
+ type: 'color',
+ displayName: 'Text',
+ validation: {
+ schema: { type: 'string' },
+ },
+ accordian: 'switch',
+ },
+ padding: {
+ type: 'switch',
+ displayName: 'Padding',
+ validation: {
+ schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
+ defaultValue: 'default',
+ },
+ isFxNotRequired: true,
+ options: [
+ { displayName: 'Default', value: 'default' },
+ { displayName: 'None', value: 'none' },
+ ],
+ accordian: 'container',
+ },
+ },
+ actions: [
+ {
+ handle: 'selectOption',
+ displayName: 'Select option',
+ params: [{ handle: 'option', displayName: 'Option' }],
+ },
+ {
+ handle: 'deselectOption',
+ displayName: 'Deselect option',
+ params: [{ handle: 'option', displayName: 'Option' }],
+ },
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: `{{true}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set loading',
+ params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set disable',
+ params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ ],
+ exposedVariables: {
+ label: 'Select',
+ },
+ definition: {
+ others: {
+ showOnDesktop: { value: '{{true}}' },
+ showOnMobile: { value: '{{false}}' },
+ },
+ validation: {
+ mandatory: { value: '{{false}}' },
+ },
+ properties: {
+ label: { value: 'Select' },
+ value: { value: '{{"2"}}' },
+ advanced: { value: `{{false}}` },
+ options: {
+ value: [
+ {
+ label: 'option1',
+ value: '1',
+ disable: { value: false },
+ visible: { value: true },
+ default: { value: false },
+ },
+ {
+ label: 'option2',
+ value: '2',
+ disable: { value: false },
+ visible: { value: true },
+ default: { value: true },
+ },
+ {
+ label: 'option3',
+ value: '3',
+ disable: { value: false },
+ visible: { value: true },
+ default: { value: false },
+ },
+ ],
+ },
+ visibility: { value: '{{true}}' },
+ disabledState: { value: '{{false}}' },
+ loadingState: { value: '{{false}}' },
+ optionsLoadingState: { value: '{{false}}' },
+ optionVisibility: { value: '{{[true, true, true]}}' },
+ optionDisable: { value: '{{[false, false, false]}}' },
+ schema: {
+ value:
+ "{{[\t{label: 'option1',value: '1',disable: false,visible: true,default: true},{label: 'option2',value: '2',disable: false,visible: true},{label: 'option3',value: '3',disable: false,visible: true}\t]}}",
+ },
+ },
+ events: [],
+ styles: {
+ labelColor: { value: '#11181C' },
+ direction: { value: 'left' },
+ alignment: { value: 'side' },
+ auto: { value: '{{false}}' },
+ labelWidth: { value: '20' },
+ borderColor: { value: '#FFFFFF' },
+ switchOffBackgroundColor: { value: '#FFFFFF' },
+ switchOnBackgroundColor: { value: '#4368E3' },
+ handleColor: { value: '#FFFFFF' },
+ optionsTextColor: { value: '#11181C' },
+ padding: { value: 'default' },
+ },
+ },
+};
diff --git a/server/src/helpers/widget-config/richtextarea.js b/server/src/helpers/widget-config/richtextarea.js
index eacee66529..c964d53d6b 100644
--- a/server/src/helpers/widget-config/richtextarea.js
+++ b/server/src/helpers/widget-config/richtextarea.js
@@ -28,6 +28,15 @@ export const richtextareaConfig = {
defaultValue: 'Default text',
},
},
+ loadingState: {
+ type: 'toggle',
+ displayName: 'Show loading state',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: false,
+ },
+ section: 'additionalActions',
+ },
},
events: {},
styles: {
@@ -55,6 +64,28 @@ export const richtextareaConfig = {
exposedVariables: {
value: '',
},
+ actions: [
+ {
+ handle: 'setValue',
+ displayName: 'Set value',
+ params: [{ handle: 'value', displayName: 'Value', defaultValue: 'New text' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set disable',
+ params: [{ handle: 'setDisable', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Value', defaultValue: `{{true}}`, type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set loading',
+ params: [{ handle: 'setLoading', displayName: 'Value', defaultValue: `{{false}}`, type: 'toggle' }],
+ },
+ ],
definition: {
others: {
showOnDesktop: { value: '{{true}}' },
@@ -63,6 +94,7 @@ export const richtextareaConfig = {
properties: {
placeholder: { value: 'Placeholder text' },
defaultValue: { value: '' },
+ loadingState: { value: `{{false}}` },
},
events: [],
styles: {
diff --git a/server/src/helpers/widget-config/table.js b/server/src/helpers/widget-config/table.js
index eb08ac88c8..735feccbc7 100644
--- a/server/src/helpers/widget-config/table.js
+++ b/server/src/helpers/widget-config/table.js
@@ -289,6 +289,7 @@ export const tableConfig = {
onCellValueChanged: { displayName: 'Cell value changed' },
onFilterChanged: { displayName: 'Filter changed' },
onNewRowsAdded: { displayName: 'Add new rows' },
+ onTableDataDownload: { displayName: 'Download data' },
},
styles: {
textColor: {
@@ -310,6 +311,16 @@ export const tableConfig = {
{ displayName: 'Wrap', value: 'wrap' },
],
},
+ headerCasing: {
+ type: 'switch',
+ displayName: 'Header casing',
+ validation: { schema: { type: 'string' } },
+ accordian: 'Data',
+ options: [
+ { displayName: 'AA', value: 'uppercase' },
+ { displayName: 'As typed', value: 'none' },
+ ],
+ },
tableType: {
type: 'select',
displayName: 'Row style',
@@ -530,6 +541,7 @@ export const tableConfig = {
value: [
{
name: 'id',
+ key: 'id',
id: 'e3ecbf7fa52c4d7210a93edb8f43776267a489bad52bd108be9588f790126737',
autogenerated: true,
fxActiveFields: [],
@@ -549,6 +561,7 @@ export const tableConfig = {
},
{
name: 'name',
+ key: 'name',
id: '5d2a3744a006388aadd012fcc15cc0dbcb5f9130e0fbb64c558561c97118754a',
autogenerated: true,
fxActiveFields: [],
@@ -557,6 +570,7 @@ export const tableConfig = {
},
{
name: 'email',
+ key: 'email',
id: 'afc9a5091750a1bd4760e38760de3b4be11a43452ae8ae07ce2eebc569fe9a7f',
autogenerated: true,
fxActiveFields: [],
@@ -565,6 +579,7 @@ export const tableConfig = {
},
{
name: 'date',
+ key: 'date',
id: '27b75c8af9d34d1eaa1f9bb7f8f9f7b0abf1823e799748c8bb57e74f53b2c1dc',
autogenerated: true,
fxActiveFields: [],
@@ -577,6 +592,7 @@ export const tableConfig = {
},
{
name: 'phone',
+ key: 'phone',
id: '9c2e3c40572a4aefb8e179ee39a0e1ac9dc2b2e6634be56e1c05be13c3d1de56',
autogenerated: true,
fxActiveFields: [],
@@ -653,6 +669,7 @@ export const tableConfig = {
styles: {
textColor: { value: '#000' },
columnHeaderWrap: { value: 'fixed' },
+ headerCasing: { value: 'uppercase' },
actionButtonRadius: { value: '0' },
cellSize: { value: 'regular' },
borderRadius: { value: '8' },
diff --git a/server/tsconfig.json b/server/tsconfig.json
index 3a77c5a9b0..f8d3eef313 100644
--- a/server/tsconfig.json
+++ b/server/tsconfig.json
@@ -1,5 +1,6 @@
{
"compilerOptions": {
+ "resolveJsonModule": true,
"module": "commonjs",
"declaration": true,
"removeComments": true,