From cd2350db463e1a9bec89167ad01ef2281b1a1564 Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Mon, 25 Nov 2024 12:04:17 +0530 Subject: [PATCH 01/18] chore: version bump --- .version | 2 +- frontend/.version | 2 +- server/.version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.version b/.version index 2ae831adb3..ec05a45df1 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.0.4-ce-lts +3.1.4-ce diff --git a/frontend/.version b/frontend/.version index 2ae831adb3..96c83a48d0 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.0.4-ce-lts +3.1.4-ce \ No newline at end of file diff --git a/server/.version b/server/.version index 2ae831adb3..ec05a45df1 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -3.0.4-ce-lts +3.1.4-ce From 022e554812a73c933df9652538909e5e85a57de3 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade <133095394+nakulnagargade@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:49:06 +0530 Subject: [PATCH 02/18] [feat] : Add support for clicking anywhere on the edge should select the component (#11425) --- .../AppCanvas/ConfigHandle/configHandle.scss | 6 +---- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 24 +++++++++++++------ frontend/src/AppBuilder/AppCanvas/Selecto.jsx | 3 +-- 3 files changed, 19 insertions(+), 14 deletions(-) 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..baff04861c 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'); 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; } From c3b14dac270e6d2ed6d58797ebc675e6e71f5647 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade <133095394+nakulnagargade@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:51:11 +0530 Subject: [PATCH 03/18] feat: Introduce table styles property to set the casing of table column names (#11424) * feat: Introduce table styles property to set the casing of table column names * Update table default data keys * Fix --- .../AppBuilder/WidgetManager/widgets/table.js | 16 ++++++++++++++++ .../Widgets/Table/Components/TableHeader.jsx | 2 ++ frontend/src/AppBuilder/Widgets/Table/Table.jsx | 2 ++ .../Widgets/Table/load-properties-and-styles.js | 2 ++ .../src/Editor/WidgetManager/configs/table.js | 16 ++++++++++++++++ server/src/helpers/widget-config/table.js | 16 ++++++++++++++++ 6 files changed, 54 insertions(+) diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/table.js b/frontend/src/AppBuilder/WidgetManager/widgets/table.js index 9be59e9dc3..ff815bab07 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/table.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/table.js @@ -310,6 +310,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 +539,7 @@ export const tableConfig = { value: [ { name: 'id', + key: 'id', id: 'e3ecbf7fa52c4d7210a93edb8f43776267a489bad52bd108be9588f790126737', autogenerated: true, fxActiveFields: [], @@ -548,6 +559,7 @@ export const tableConfig = { }, { name: 'name', + key: 'name', id: '5d2a3744a006388aadd012fcc15cc0dbcb5f9130e0fbb64c558561c97118754a', autogenerated: true, fxActiveFields: [], @@ -556,6 +568,7 @@ export const tableConfig = { }, { name: 'email', + key: 'email', id: 'afc9a5091750a1bd4760e38760de3b4be11a43452ae8ae07ce2eebc569fe9a7f', autogenerated: true, fxActiveFields: [], @@ -564,6 +577,7 @@ export const tableConfig = { }, { name: 'date', + key: 'date', id: '27b75c8af9d34d1eaa1f9bb7f8f9f7b0abf1823e799748c8bb57e74f53b2c1dc', autogenerated: true, fxActiveFields: [], @@ -576,6 +590,7 @@ export const tableConfig = { }, { name: 'mobile_number', + key: 'mobile_number', id: '9c2e3c40572a4aefb8e179ee39a0e1ac9dc2b2e6634be56e1c05be13c3d1de56', autogenerated: true, fxActiveFields: [], @@ -652,6 +667,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/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..c38294eab7 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); @@ -1096,6 +1097,7 @@ export const Table = React.memo( columnHeaderWrap={columnHeaderWrap} setResizingColumnId={setResizingColumnId} resizingColumnId={resizingColumnId} + headerCasing={headerCasing} /> {page.length > 0 && !loadingState && ( Date: Wed, 4 Dec 2024 13:52:33 +0530 Subject: [PATCH 04/18] Fixed value of value CSA when no default value is set and clear() action is used. (#11409) --- .../src/AppBuilder/WidgetManager/widgets/numberinput.js | 2 +- frontend/src/Editor/Components/NumberInput.jsx | 7 ++++++- frontend/src/Editor/WidgetManager/configs/numberinput.js | 2 +- server/src/helpers/widget-config/numberinput.js | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/numberinput.js b/frontend/src/AppBuilder/WidgetManager/widgets/numberinput.js index ba7f2bc227..b0f72e6d81 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/numberinput.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/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/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/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/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, From f5cab90b5ccb42b78d1360618d2245b5a6c87f71 Mon Sep 17 00:00:00 2001 From: Devanshu Rastogi Date: Wed, 4 Dec 2024 13:53:28 +0530 Subject: [PATCH 05/18] Fix: Content overflow issue in rich text editor (#11405) * Fixed the content overflow issue in rich text editor. * Fixed issue where content overflowed when height is resized below a certain height. --- .../src/Editor/Components/DraftEditor.jsx | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/frontend/src/Editor/Components/DraftEditor.jsx b/frontend/src/Editor/Components/DraftEditor.jsx index 71b9a9de04..1df78ee1bf 100644 --- a/frontend/src/Editor/Components/DraftEditor.jsx +++ b/frontend/src/Editor/Components/DraftEditor.jsx @@ -152,6 +152,9 @@ class DraftEditor extends React.Component { editorState: EditorState.createWithContent(ContentState.createFromText(this.props.defaultValue)), }; + this.editorContainerRef = React.createRef(); + this.controlsRef = React.createRef(); + this.focus = () => this.refs.editor.focus(); this.onChange = (editorState) => { let html = stateToHTML(editorState.getCurrentContent()); @@ -165,6 +168,27 @@ 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); + } + } + + componentWillUnmount() { + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + } + } + componentDidUpdate(prevProps) { if (prevProps.defaultValue !== this.props.defaultValue) { const newContentState = ContentState.createFromText(this.props.defaultValue); @@ -219,12 +243,12 @@ class DraftEditor extends React.Component { } return ( -
-
+
+
-
+
Date: Wed, 4 Dec 2024 00:24:08 -0800 Subject: [PATCH 06/18] chore: Set modal as the selected widget when clicked (#11363) --- frontend/src/AppBuilder/Widgets/Modal.jsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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 && ( Date: Wed, 4 Dec 2024 13:54:43 +0530 Subject: [PATCH 07/18] Fix container and kanban can't be moved by dragging on the body of the component (#11355) --- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 23 +++++++++++++------ server/src/helpers/utils.helper.ts | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index baff04861c..deea310a10 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -592,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 */ @@ -603,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) { 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 +}; From aa4cd1fc2e2279f9fd6e3c0a35b5366334faa705 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Wed, 4 Dec 2024 00:26:38 -0800 Subject: [PATCH 08/18] chore: Exposes CSAs for Container widget (#11229) * chore: Exposes CSAs for Container widget * Introduces hook to expose variables * Updates hook to expose variable after setting the state * Update frontend/src/AppBuilder/_hooks/useExposeVariables.js Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> * Removes unwanted imports * Exposing CSA to control component actions * Moves CSA's to additional actions section * Fixes wrong handle param --------- Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> --- .../Inspector/Components/DefaultComponent.jsx | 1 + .../WidgetManager/widgets/container.js | 63 +++++++++++++------ frontend/src/AppBuilder/Widgets/Container.jsx | 31 +++++++-- .../AppBuilder/_hooks/useExposeVariables.js | 51 +++++++++++++++ .../Editor/WidgetManager/configs/container.js | 61 ++++++++++++------ server/src/helpers/widget-config/container.js | 63 +++++++++++++------ 6 files changed, 206 insertions(+), 64 deletions(-) create mode 100644 frontend/src/AppBuilder/_hooks/useExposeVariables.js diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx index 74ae7c6e47..c183a644c0 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', diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/container.js b/frontend/src/AppBuilder/WidgetManager/widgets/container.js index 32d4158ffc..dc2dcf34ec 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/container.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/container.js @@ -15,6 +15,25 @@ 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, @@ -50,40 +69,44 @@ 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' }, borderRadius: { value: '4' }, borderColor: { value: '#fff' }, - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, }, }, }; diff --git a/frontend/src/AppBuilder/Widgets/Container.jsx b/frontend/src/AppBuilder/Widgets/Container.jsx index b54b94c9a3..2b6f8008ed 100644 --- a/frontend/src/AppBuilder/Widgets/Container.jsx +++ b/frontend/src/AppBuilder/Widgets/Container.jsx @@ -1,9 +1,28 @@ 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, + setExposedVariables, + setExposedVariable, +}) => { + const { borderRadius, borderColor, boxShadow } = styles; + + const { isDisabled, isVisible, isLoading } = useExposeState( + properties.loadingState, + properties.visibility, + properties.disabledState, + setExposedVariables, + setExposedVariable + ); -export const Container = ({ id, properties, styles, darkMode, height, width }) => { - const { visibility, disabledState, borderRadius, borderColor, boxShadow } = styles; const bgColor = useMemo(() => { return { backgroundColor: @@ -16,19 +35,19 @@ export const Container = ({ id, properties, styles, darkMode, height, width }) = borderRadius: borderRadius ? parseFloat(borderRadius) : 0, border: `1px solid ${borderColor}`, height, - display: visibility ? 'flex' : 'none', + display: isVisible ? 'flex' : 'none', overflow: 'hidden auto', position: 'relative', boxShadow, }; return (
- {properties.loadingState ? ( + {isLoading ? ( ) : ( diff --git a/frontend/src/AppBuilder/_hooks/useExposeVariables.js b/frontend/src/AppBuilder/_hooks/useExposeVariables.js new file mode 100644 index 0000000000..3c929f829b --- /dev/null +++ b/frontend/src/AppBuilder/_hooks/useExposeVariables.js @@ -0,0 +1,51 @@ +import { useEffect, useState } from 'react'; + +export const useExposeState = (loadingState, visibleState, disabledState, setExposedVariables, setExposedVariable) => { + 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/Editor/WidgetManager/configs/container.js b/frontend/src/Editor/WidgetManager/configs/container.js index 32d4158ffc..062b2791c4 100644 --- a/frontend/src/Editor/WidgetManager/configs/container.js +++ b/frontend/src/Editor/WidgetManager/configs/container.js @@ -15,6 +15,25 @@ 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, @@ -50,32 +69,38 @@ 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: { diff --git a/server/src/helpers/widget-config/container.js b/server/src/helpers/widget-config/container.js index 32d4158ffc..ef7f60855d 100644 --- a/server/src/helpers/widget-config/container.js +++ b/server/src/helpers/widget-config/container.js @@ -15,6 +15,25 @@ 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, @@ -50,40 +69,44 @@ 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: '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' }, borderRadius: { value: '4' }, borderColor: { value: '#fff' }, - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, }, }, }; From fef97269aed8f9bdbd01235264a9ff519bec9881 Mon Sep 17 00:00:00 2001 From: Devanshu Rastogi Date: Wed, 4 Dec 2024 13:58:41 +0530 Subject: [PATCH 09/18] Added loading state in pie chart. (#11404) --- .../Inspector/Components/Chart.jsx | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) 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', From af1dda8ec9f3df659513f56a53f4cd12605a06c5 Mon Sep 17 00:00:00 2001 From: Devanshu Rastogi Date: Wed, 4 Dec 2024 13:59:08 +0530 Subject: [PATCH 10/18] Feature: Added Radio button V2 component (#11408) * Initial setup for Radio Button V2 * Made changes in Radio Button component and its properties. * Added a loader. * Added disabled states in Radio Button V2. * Fixes * CSA fixes * fixes * Fixed switch case label for Radio Button V2 and updated config files. * Added Radio Button component in Inspector and refactored the component and config file. * Refactored code for updating exposed variables and fixed default value for non dynamic options. * Fixed sonarlint issue - Unexpected duplicate font-size. * Made minor styling changes. * Removed currentStateStore logic. * Implemented single source of truth for changing value and resolved review comments. * Changed font weight of options to 400. --------- Co-authored-by: Nakul Nagargade --- .../assets/images/icons/widgets/index.jsx | 5 +- .../images/icons/widgets/radiobuttonV2.jsx | 17 + .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 1 + .../ComponentsManagerTab.jsx | 2 +- .../ComponentsManagerTab/constants.js | 2 +- .../RightSideBar/Inspector/Inspector.jsx | 2 + .../RightSideBar/Inspector/Utils.js | 1 + .../RightSideBar/WidgetBox/WidgetBox.jsx | 4 +- .../WidgetManager/configs/widgetConfig.js | 2 + .../AppBuilder/WidgetManager/widgets/index.js | 4 +- .../WidgetManager/widgets/radioButtonV2.js | 295 ++++++++++++++++++ .../WidgetManager/widgets/radiobutton.js | 4 +- .../src/AppBuilder/_helpers/editorHelpers.js | 3 +- .../_stores/slices/componentsSlice.js | 6 +- .../CodeEditor/SingleLineCodeEditor.jsx | 4 +- .../RadioButtonV2/RadioButtonV2.jsx | 295 ++++++++++++++++++ .../RadioButtonV2/radioButtonV2.scss | 66 ++++ .../Inspector/Elements/Components/ToolTip.jsx | 8 +- .../src/Editor/WidgetManager/configs/index.js | 4 +- .../WidgetManager/configs/radioButtonV2.js | 295 ++++++++++++++++++ .../WidgetManager/configs/radiobutton.js | 4 +- .../src/Editor/WidgetManager/constants.js | 2 +- .../src/Editor/WidgetManager/widgetConfig.js | 2 + server/src/helpers/widget-config/index.js | 4 +- .../helpers/widget-config/radioButtonV2.js | 295 ++++++++++++++++++ 25 files changed, 1308 insertions(+), 19 deletions(-) create mode 100644 frontend/assets/images/icons/widgets/radiobuttonV2.jsx create mode 100644 frontend/src/AppBuilder/WidgetManager/widgets/radioButtonV2.js create mode 100644 frontend/src/Editor/Components/RadioButtonV2/RadioButtonV2.jsx create mode 100644 frontend/src/Editor/Components/RadioButtonV2/radioButtonV2.scss create mode 100644 frontend/src/Editor/WidgetManager/configs/radioButtonV2.js create mode 100644 server/src/helpers/widget-config/radioButtonV2.js 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/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/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/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 { + 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/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 ( - -