From 7c62cf032a075356cfb39dad35226b327bf8f503 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:19:04 +0530 Subject: [PATCH] [fix]: Added components exposed values to the suggestions (#10462) * commit: Added suggestion from the currentState to add all the hints to the resolved store. This is added because the hints were missing for few of the component values * fix: rerender component on visibility change * fix: remove unused logs * fix: remove duplicate hook call * Fix: Update reference resolution logic for query variables (#10434) * Fix: Update reference resolution logic for query variables * Fix: Update reference resolution logic for query variables this fix extends changes in commit https://github.com/ToolJet/ToolJet/commit/8e3f2ec47481a76584663e22c9a0e72e8fcacee7 to queries as well along with components * fix: rerender component on visibility change (#10493) * fix: rerender component on visibility change * fix: remove unused logs * fix: remove duplicate hook call * fix: reset hints when component option changes (#10425) * fix : multiple close events getting triggered in modal * fix: removed extra event trigger from component unmount * chore: added extra log for debugging * fix: reset hints related to an option whenever a component option changes * refactor: remove resolvestate functions with duplicate logic * refactor: remove unused logs * refactor: remove unused code * fix: correct wrong function call for resetting keys --------- Co-authored-by: stepinfwd * fix: removed already declared variables * fix: resolving the variables. This is fixed by moving the flushComponents function to component. The flush will happen after making sure the re-render reaches the component * fix: replacing the value with object type is causing an issue. Checked if the resolvedValue is an object type and if so, the value is resolved with the data from currentState without replace * chore: removed the commented lines * Revert "fix : Exposed actions become unavailable on reload (#10379)" This reverts commit 0070c2204287c47d724c1dfc8b41ca1cee7b5caa. * fix: flush componenttorerender after rendering parent --------- Co-authored-by: Johnson Cherian Co-authored-by: stepinfwd --- frontend/src/Editor/BoxUI.jsx | 3 +- frontend/src/Editor/CodeEditor/utils.js | 14 ++++-- frontend/src/Editor/Components/Button.jsx | 50 +++++++++---------- frontend/src/Editor/Components/Chart.jsx | 6 +-- frontend/src/Editor/Components/Checkbox.jsx | 5 +- .../src/Editor/Components/ColorPicker.jsx | 29 ++++------- .../Components/DropdownV2/DropdownV2.jsx | 8 +-- frontend/src/Editor/Components/Form/Form.jsx | 12 ++--- frontend/src/Editor/Components/Icon.jsx | 8 ++- .../src/Editor/Components/Kanban/Kanban.jsx | 2 +- .../Editor/Components/Kanban/KanbanBoard.jsx | 39 ++++++--------- frontend/src/Editor/Components/Listview.jsx | 19 ++----- frontend/src/Editor/Components/Modal.jsx | 9 ++-- .../src/Editor/Components/Multiselect.jsx | 8 ++- .../MultiselectV2/MultiselectV2.jsx | 8 ++- .../src/Editor/Components/RadioButton.jsx | 8 ++- .../src/Editor/Components/Table/Table.jsx | 26 ++++------ frontend/src/Editor/Components/Tabs.jsx | 8 ++- frontend/src/Editor/Components/TextArea.jsx | 9 ++-- frontend/src/Editor/Components/TextInput.jsx | 10 ++-- frontend/src/Editor/Components/TreeSelect.jsx | 13 ++--- frontend/src/Editor/Container.jsx | 31 ++++++++---- .../Editor/ControlledComponentToRender.jsx | 7 ++- frontend/src/Editor/Editor.jsx | 11 ++-- frontend/src/Editor/Viewer.jsx | 10 +++- frontend/src/_helpers/appUtils.js | 24 +++++++-- frontend/src/_helpers/editorHelpers.js | 13 +++++ frontend/src/_stores/resolverStore.js | 26 ++++++---- 28 files changed, 210 insertions(+), 206 deletions(-) diff --git a/frontend/src/Editor/BoxUI.jsx b/frontend/src/Editor/BoxUI.jsx index e6dd2dda75..1272302ce5 100644 --- a/frontend/src/Editor/BoxUI.jsx +++ b/frontend/src/Editor/BoxUI.jsx @@ -6,7 +6,7 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import '@/_styles/custom.scss'; import { EditorContext } from './Context/EditorContextWrapper'; import { validateWidget } from '@/_helpers/utils'; -import { useCurrentState, useCurrentStateStore } from '@/_stores/currentStateStore'; +import { useCurrentState } from '@/_stores/currentStateStore'; import { useAppDataStore } from '@/_stores/appDataStore'; import _ from 'lodash'; @@ -186,7 +186,6 @@ const BoxUI = (props) => { currentPageId={currentPageId} getContainerProps={component.component === 'Form' ? getContainerProps : null} childComponents={childComponents} - isEditorReady={isEditorReady} /> diff --git a/frontend/src/Editor/CodeEditor/utils.js b/frontend/src/Editor/CodeEditor/utils.js index 3ce951f644..26762fcaa1 100644 --- a/frontend/src/Editor/CodeEditor/utils.js +++ b/frontend/src/Editor/CodeEditor/utils.js @@ -188,7 +188,7 @@ function getDynamicVariables(text) { const resolveMultiDynamicReferences = (code, lookupTable, queryHasJSCode) => { let resolvedValue = code; - const isComponentValue = code.includes('components.') || false; + const isComponentValue = code.includes('components.') || code.includes('queries.') || false; const allDynamicVariables = getDynamicVariables(code) || []; let isJSCodeResolver = queryHasJSCode && (allDynamicVariables.length === 1 || allDynamicVariables.length === 0); @@ -301,10 +301,16 @@ export const resolveReferences = (query, validationSchema, customResolvers = {}) resolvedValue = lookupTable.resolvedRefs.get(idToLookUp); if (jsExpression) { - let jscode = value.replace(toResolveReference, resolvedValue); - jscode = value.replace(toResolveReference, `'${resolvedValue}'`); + let jscode = value; + if (!Array.isArray(resolvedValue) && typeof resolvedValue !== 'object' && resolvedValue !== null) { + jscode = value.replace(toResolveReference, resolvedValue).replace(toResolveReference, `'${resolvedValue}'`); + resolvedValue = resolveCode(jscode, customResolvers); + } else { + const [resolvedCode, errorRef] = resolveCode(value, customResolvers, true, [], true); - resolvedValue = resolveCode(jscode, customResolvers); + resolvedValue = resolvedCode; + error = errorRef || null; + } } } else { const [resolvedCode, errorRef] = resolveCode(value, customResolvers, true, [], true); diff --git a/frontend/src/Editor/Components/Button.jsx b/frontend/src/Editor/Components/Button.jsx index 040b329544..ab32b219f3 100644 --- a/frontend/src/Editor/Components/Button.jsx +++ b/frontend/src/Editor/Components/Button.jsx @@ -5,9 +5,7 @@ import * as Icons from '@tabler/icons-react'; import Loader from '@/ToolJetUI/Loader/Loader'; export const Button = function Button(props) { - const { height, properties, styles, fireEvent, id, dataCy, setExposedVariable, setExposedVariables, isEditorReady } = - props; - + const { height, properties, styles, fireEvent, id, dataCy, setExposedVariable, setExposedVariables } = props; const { backgroundColor, textColor, @@ -95,33 +93,31 @@ export const Button = function Button(props) { }; useEffect(() => { - if (isEditorReady) { - const exposedVariables = { - click: async function () { - if (!disable) { - fireEvent('onClick'); - } - }, - setText: async function (text) { - setLabel(text); - setExposedVariable('buttonText', text); - }, - disable: async function (value) { - setDisable(value); - }, - visibility: async function (value) { - setVisibility(value); - }, - loading: async function (value) { - setLoading(value); - }, - }; + const exposedVariables = { + click: async function () { + if (!disable) { + fireEvent('onClick'); + } + }, + setText: async function (text) { + setLabel(text); + setExposedVariable('buttonText', text); + }, + disable: async function (value) { + setDisable(value); + }, + visibility: async function (value) { + setVisibility(value); + }, + loading: async function (value) { + setLoading(value); + }, + }; - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [disable, isEditorReady]); + }, [disable]); useEffect(() => { setExposedVariable('setLoading', async function (loading) { diff --git a/frontend/src/Editor/Components/Chart.jsx b/frontend/src/Editor/Components/Chart.jsx index e77000cd85..3b83b0f3e4 100644 --- a/frontend/src/Editor/Components/Chart.jsx +++ b/frontend/src/Editor/Components/Chart.jsx @@ -18,7 +18,6 @@ export const Chart = function Chart({ setExposedVariable, setExposedVariables, dataCy, - isEditorReady, }) { const [loadingState, setLoadingState] = useState(false); @@ -81,9 +80,8 @@ export const Chart = function Chart({ xAxisTitle: xAxisTitle, yAxisTitle: yAxisTitle, }; - if (isEditorReady) setExposedVariables(exposedVariables); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(chartLayout, chartTitle), isEditorReady]); + setExposedVariables(exposedVariables); + }, [JSON.stringify(chartLayout, chartTitle)]); const layout = { width: width - 4, diff --git a/frontend/src/Editor/Components/Checkbox.jsx b/frontend/src/Editor/Components/Checkbox.jsx index 9e7842d976..bce29bad1f 100644 --- a/frontend/src/Editor/Components/Checkbox.jsx +++ b/frontend/src/Editor/Components/Checkbox.jsx @@ -14,7 +14,6 @@ export const Checkbox = ({ component, validate, width, - isEditorReady, }) => { const defaultValueFromProperties = properties.defaultValue ?? false; const [defaultValue, setDefaultValue] = useState(defaultValueFromProperties); @@ -69,10 +68,10 @@ export const Checkbox = ({ setDefaultValue(defaultValueFromProperties); setChecked(defaultValueFromProperties); setValue(defaultValueFromProperties); - if (isEditorReady) setExposedVariables(exposedVariables); + setExposedVariables(exposedVariables); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [defaultValueFromProperties, isEditorReady]); + }, [defaultValueFromProperties]); useEffect(() => { if (disable !== disabledState) setDisable(properties.disabledState); diff --git a/frontend/src/Editor/Components/ColorPicker.jsx b/frontend/src/Editor/Components/ColorPicker.jsx index eeadc3c339..6e4c3d682a 100644 --- a/frontend/src/Editor/Components/ColorPicker.jsx +++ b/frontend/src/Editor/Components/ColorPicker.jsx @@ -11,7 +11,6 @@ export const ColorPicker = function ({ height, fireEvent, dataCy, - isEditorReady, }) { const { visibility, boxShadow } = styles; const defaultColor = properties.defaultColor; @@ -58,9 +57,8 @@ export const ColorPicker = function ({ selectedColorRGB: hexToRgb(colorCode), selectedColorRGBA: hexToRgba(colorCode), }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); + fireEvent('onChange'); } } else { @@ -69,15 +67,14 @@ export const ColorPicker = function ({ selectedColorRGB: undefined, selectedColorRGBA: undefined, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); + fireEvent('onChange'); setColor('Invalid Color'); } }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setColor, isEditorReady]); + }, [setColor]); useEffect(() => { let exposedVariables = {}; @@ -88,9 +85,8 @@ export const ColorPicker = function ({ selectedColorRGB: hexToRgb(defaultColor), selectedColorRGBA: hexToRgba(defaultColor), }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); + setColor(defaultColor); } } else { @@ -99,13 +95,12 @@ export const ColorPicker = function ({ selectedColorRGB: undefined, selectedColorRGBA: undefined, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); + setColor(`Invalid Color`); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [defaultColor, isEditorReady]); + }, [defaultColor]); const handleColorChange = (colorCode) => { let exposedVariables = {}; @@ -118,9 +113,7 @@ export const ColorPicker = function ({ selectedColorRGB: `rgb(${r},${g},${b})`, selectedColorRGBA: `rgb(${r},${g},${b},${a})`, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); fireEvent('onChange'); } }; diff --git a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx index 0699eb34a6..c0a922b0a2 100644 --- a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx +++ b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx @@ -58,7 +58,6 @@ export const DropdownV2 = ({ component, exposedVariables, dataCy, - isEditorReady, }) => { const { label, value, advanced, schema, placeholder, loadingState: dropdownLoadingState, disabledState } = properties; const { @@ -218,11 +217,8 @@ export const DropdownV2 = ({ setIsDropdownDisabled(value); }, }; - - if (isEditorReady) { - setExposedVariables(exposedVariables); - } - }, [isEditorReady]); + setExposedVariables(exposedVariables); + }, []); const customStyles = { container: (base) => ({ diff --git a/frontend/src/Editor/Components/Form/Form.jsx b/frontend/src/Editor/Components/Form/Form.jsx index 51babaa340..7426dd365d 100644 --- a/frontend/src/Editor/Components/Form/Form.jsx +++ b/frontend/src/Editor/Components/Form/Form.jsx @@ -38,7 +38,6 @@ export const Form = function Form(props) { getContainerProps, containerProps, childComponents, - isEditorReady, } = props; const { events: allAppEvents } = useAppInfo(); @@ -133,9 +132,7 @@ export const Form = function Form(props) { ...(!advanced && { children: formattedChildData }), }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); return setValidation(childValidation); } @@ -161,10 +158,9 @@ export const Form = function Form(props) { isValid: childValidation, }; setValidation(childValidation); - if (isEditorReady) { - setExposedVariables(exposedVariables); - } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [childrenData, childComponents, advanced, JSON.stringify(JSONSchema), isEditorReady]); + setExposedVariables(exposedVariables); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [childrenData, childComponents, advanced, JSON.stringify(JSONSchema)]); useEffect(() => { const childIds = Object.keys(childrenData); diff --git a/frontend/src/Editor/Components/Icon.jsx b/frontend/src/Editor/Components/Icon.jsx index 6edcf75477..cec0bbb348 100644 --- a/frontend/src/Editor/Components/Icon.jsx +++ b/frontend/src/Editor/Components/Icon.jsx @@ -9,11 +9,11 @@ export const Icon = ({ fireEvent, width, height, + setExposedVariable, setExposedVariables, darkMode, dataCy, component, - isEditorReady, }) => { const { icon } = properties; const { iconColor, visibility, boxShadow } = styles; @@ -40,11 +40,9 @@ export const Icon = ({ fireEvent('onClick'); }, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setIconVisibility, isEditorReady]); + }, [setIconVisibility]); return (
{ - const { height, width, properties, styles, id, mode, isEditorReady } = props; + const { height, width, properties, styles, id, mode } = props; const { showDeleteButton } = properties; const { visibility, disabledState, boxShadow } = styles; diff --git a/frontend/src/Editor/Components/Kanban/KanbanBoard.jsx b/frontend/src/Editor/Components/Kanban/KanbanBoard.jsx index 9f9beb7265..433eca7ac3 100644 --- a/frontend/src/Editor/Components/Kanban/KanbanBoard.jsx +++ b/frontend/src/Editor/Components/Kanban/KanbanBoard.jsx @@ -38,7 +38,7 @@ const dropAnimation = { const TRASH_ID = 'void'; export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, mode, id }) { - const { properties, fireEvent, setExposedVariable, setExposedVariables, styles, isEditorReady } = kanbanProps; + const { properties, fireEvent, setExposedVariable, setExposedVariables, styles } = kanbanProps; const { columnData, cardData, cardWidth, cardHeight, showDeleteButton, enableAddCard } = properties; const { accentColor } = styles; const [lastSelectedCard, setLastSelectedCard] = useState({}); @@ -112,20 +112,17 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, mode, id }) cardDataAsObj[cardId] = value; const diffKeys = Object.keys(diff(cardToBeUpdated, value)); if (lastSelectedCard?.id === cardId) { - if (isEditorReady) { - setExposedVariables({ - lastSelectedCard: cardDataAsObj[cardId], - - lastUpdatedCard: cardDataAsObj[cardId], - lastCardUpdate: diffKeys.map((key) => { - return { - [key]: { oldValue: cardToBeUpdated[key], newValue: value[key] }, - }; - }), - updatedCardData: getData(cardDataAsObj), - }); - } + setExposedVariables({ + lastSelectedCard: cardDataAsObj[cardId], + lastUpdatedCard: cardDataAsObj[cardId], + lastCardUpdate: diffKeys.map((key) => { + return { + [key]: { oldValue: cardToBeUpdated[key], newValue: value[key] }, + }; + }), + updatedCardData: getData(cardDataAsObj), + }); fireEvent('onUpdate'); } else { setExposedVariable('updatedCardData', getData(cardDataAsObj)); @@ -133,7 +130,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, mode, id }) } }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [lastSelectedCard, JSON.stringify(cardDataAsObj), isEditorReady]); + }, [lastSelectedCard, JSON.stringify(cardDataAsObj)]); useEffect(() => { setExposedVariable('moveCard', async function (cardId, columnId) { @@ -172,13 +169,11 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, mode, id }) ...items, [columnId]: [...items[columnId], cardDetails.id], })); - if (isEditorReady) { - setExposedVariables({ lastAddedCard: { ...cardDetails }, updatedCardData: getData(cardDataAsObj) }); - } + setExposedVariables({ lastAddedCard: { ...cardDetails }, updatedCardData: getData(cardDataAsObj) }); fireEvent('onCardAdded'); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [items, JSON.stringify(cardDataAsObj), isEditorReady]); + }, [items, JSON.stringify(cardDataAsObj)]); useEffect(() => { setExposedVariable('deleteCard', async function (cardId) { @@ -191,13 +186,11 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, mode, id }) ...items, [columnId]: items[columnId].filter((id) => id !== cardId), })); - if (isEditorReady) { - setExposedVariables({ lastRemovedCard: { ...deletedCard }, updatedCardData: getData(cardDataAsObj) }); - } + setExposedVariables({ lastRemovedCard: { ...deletedCard }, updatedCardData: getData(cardDataAsObj) }); fireEvent('onCardRemoved'); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [showModal, JSON.stringify(cardDataAsObj), isEditorReady]); + }, [showModal, JSON.stringify(cardDataAsObj)]); const [clonedItems, setClonedItems] = useState(null); const sensors = useSensors( diff --git a/frontend/src/Editor/Components/Listview.jsx b/frontend/src/Editor/Components/Listview.jsx index f7f4673ab3..99efcf9cca 100644 --- a/frontend/src/Editor/Components/Listview.jsx +++ b/frontend/src/Editor/Components/Listview.jsx @@ -19,7 +19,6 @@ export const Listview = function Listview({ darkMode, dataCy, childComponents, - isEditorReady, }) { const fallbackProperties = { height: 100, showBorder: false, data: [] }; const fallbackStyles = { visibility: true, disabledState: false }; @@ -59,9 +58,7 @@ export const Listview = function Listview({ selectedRecordId: index, selectedRecord: childrenData[index], }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); fireEvent('onRecordClicked'); // eslint-disable-next-line react-hooks/exhaustive-deps } @@ -71,9 +68,7 @@ export const Listview = function Listview({ selectedRowId: index, selectedRow: childrenData[index], }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); fireEvent('onRowClicked'); // eslint-disable-next-line react-hooks/exhaustive-deps } @@ -90,20 +85,16 @@ export const Listview = function Listview({ data: removeFunctionObjects(childrenDataClone), children: childrenData, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); if (selectedRowIndex != undefined) { const exposedVariables = { selectedRowId: selectedRowIndex, selectedRow: childrenData[selectedRowIndex], }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [childrenData, childComponents, isEditorReady]); + }, [childrenData, childComponents]); function filterComponents() { if (!childrenData || childrenData.length === 0) { diff --git a/frontend/src/Editor/Components/Modal.jsx b/frontend/src/Editor/Components/Modal.jsx index 66f6565343..e63f25a141 100644 --- a/frontend/src/Editor/Components/Modal.jsx +++ b/frontend/src/Editor/Components/Modal.jsx @@ -20,7 +20,6 @@ export const Modal = function Modal({ dataCy, height, mode, - isEditorReady, }) { const [showModal, setShowModal] = useState(false); @@ -77,10 +76,9 @@ export const Modal = function Modal({ setExposedVariable('show', false); }, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setShowModal, isEditorReady]); + setExposedVariables(exposedVariables); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [setShowModal]); const isInitialRender = useRef(true); const prevShowValue = useRef(exposedVariables.show); @@ -108,6 +106,7 @@ export const Modal = function Modal({ function hideModal() { setShowModal(false); setExposedVariable('show', false); + console.log('Trigger close event =>', exposedVariables.show); fireEvent('onClose'); } useEffect(() => { diff --git a/frontend/src/Editor/Components/Multiselect.jsx b/frontend/src/Editor/Components/Multiselect.jsx index 8b2db437ca..2c57edf8aa 100644 --- a/frontend/src/Editor/Components/Multiselect.jsx +++ b/frontend/src/Editor/Components/Multiselect.jsx @@ -22,7 +22,6 @@ export const Multiselect = function Multiselect({ darkMode, fireEvent, dataCy, - isEditorReady, }) { const { label, value, values, display_values, showAllOption } = properties; const { borderRadius, visibility, disabledState, boxShadow } = styles; @@ -125,11 +124,10 @@ export const Multiselect = function Multiselect({ }, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); + // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selected, setSelected, isEditorReady]); + }, [selected, setSelected]); const filterOptions = (options, filter) => { setSearched(filter); diff --git a/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx b/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx index 1049532c4b..abd3de16ed 100644 --- a/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx +++ b/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx @@ -27,7 +27,7 @@ export const MultiselectV2 = ({ darkMode, fireEvent, validate, - isEditorReady, + width, }) => { let { label, @@ -179,10 +179,8 @@ export const MultiselectV2 = ({ setIsMultiSelectDisabled(value); }, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } - }, [isEditorReady]); + setExposedVariables(exposedVariables); + }, []); useEffect(() => { // Expose selectOption diff --git a/frontend/src/Editor/Components/RadioButton.jsx b/frontend/src/Editor/Components/RadioButton.jsx index a5f74cde19..51d19d8cbb 100644 --- a/frontend/src/Editor/Components/RadioButton.jsx +++ b/frontend/src/Editor/Components/RadioButton.jsx @@ -11,7 +11,6 @@ export const RadioButton = function RadioButton({ setExposedVariables, darkMode, dataCy, - isEditorReady, }) { const { label, value, values, display_values } = properties; const { visibility, disabledState, activeColor, boxShadow } = styles; @@ -44,10 +43,9 @@ export const RadioButton = function RadioButton({ onSelect(option); }, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [value, setValue, isEditorReady]); + setExposedVariables(exposedVariables); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value, setValue]); return (
{ - if (isEditorReady) { - setExposedVariables({ - currentData: tableData, - updatedData: tableData, - }); - } - }, [JSON.stringify(tableData), isEditorReady]); + setExposedVariables({ + currentData: tableData, + updatedData: tableData, + }); + }, [JSON.stringify(tableData)]); const columnDataForAddNewRows = generateColumnsData({ columnProperties: useDynamicColumn ? generatedColumn : component.definition.properties.columns.value, @@ -778,7 +775,7 @@ export function Table({ fireEvent('onRowClicked'); } }); - }, [JSON.stringify(tableData), JSON.stringify(tableDetails.selectedRow), isEditorReady]); + }, [JSON.stringify(tableData), JSON.stringify(tableDetails.selectedRow)]); useEffect(() => { setExposedVariable('deselectRow', async function () { @@ -790,7 +787,7 @@ export function Table({ } return; }); - }, [JSON.stringify(tableData), JSON.stringify(tableDetails.selectedRow), isEditorReady]); + }, [JSON.stringify(tableData), JSON.stringify(tableDetails.selectedRow)]); useEffect(() => { setExposedVariable('discardChanges', async function () { @@ -802,7 +799,7 @@ export function Table({ mergeToTableDetails({ dataUpdates: {}, changeSet: {} }); } }); - }, [JSON.stringify(tableData), JSON.stringify(tableDetails.changeSet), isEditorReady]); + }, [JSON.stringify(tableData), JSON.stringify(tableDetails.changeSet)]); useEffect(() => { setExposedVariable('discardNewlyAddedRows', async function () { @@ -821,7 +818,6 @@ export function Table({ JSON.stringify(tableDetails.addNewRowsDetails.newRowsChangeSet), tableDetails.addNewRowsDetails.addingNewRows, JSON.stringify(tableDetails.addNewRowsDetails.newRowsDataUpdates), - isEditorReady, ]); useEffect(() => { @@ -845,7 +841,7 @@ export function Table({ setExposedVariables({ selectedRow, selectedRowId }); mergeToTableDetails({ selectedRow, selectedRowId }); } - }, [selectedFlatRows.length, selectedFlatRows, isEditorReady]); + }, [selectedFlatRows.length, selectedFlatRows]); useEffect(() => { setExposedVariable('downloadTableData', async function (format) { @@ -859,7 +855,7 @@ export function Table({ mergeToTableDetails({ selectedRowsDetails: [], selectedRow: {}, selectedRowId: null }); toggleAllRowsSelected(false); } - }, [showBulkSelector, highlightSelectedRow, allowSelection, isEditorReady]); + }, [showBulkSelector, highlightSelectedRow, allowSelection]); React.useEffect(() => { if (enablePagination) { @@ -975,7 +971,7 @@ export function Table({ } //hack : in the initial render, data is undefined since, upon feeding data to the table from some query, query inside current state is {}. Hence we added data in the dependency array, now question is should we add data or rows? - }, [JSON.stringify(defaultSelectedRow), JSON.stringify(data), isEditorReady]); + }, [JSON.stringify(defaultSelectedRow), JSON.stringify(data)]); useEffect(() => { // csa for select all rows in table diff --git a/frontend/src/Editor/Components/Tabs.jsx b/frontend/src/Editor/Components/Tabs.jsx index 716cb9b66e..6d67108d89 100644 --- a/frontend/src/Editor/Components/Tabs.jsx +++ b/frontend/src/Editor/Components/Tabs.jsx @@ -17,7 +17,6 @@ export const Tabs = function Tabs({ styles, darkMode, dataCy, - isEditorReady, }) { const { tabWidth, boxShadow } = styles; @@ -104,11 +103,10 @@ export const Tabs = function Tabs({ }, currentTab: currentTab, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); + // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setCurrentTab, currentTab, isEditorReady]); + }, [setCurrentTab, currentTab]); const renderTabContent = (id, tab) => (
{ const exposedVariables = { @@ -189,9 +189,9 @@ export const TextInput = function TextInput({ fireEvent('onChange'); }, }; - if (isEditorReady) setExposedVariables(exposedVariables); + setExposedVariables(exposedVariables); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setValue, isEditorReady]); + }, [setValue]); const iconName = styles.icon; // Replace with the name of the icon you want // eslint-disable-next-line import/namespace const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName]; diff --git a/frontend/src/Editor/Components/TreeSelect.jsx b/frontend/src/Editor/Components/TreeSelect.jsx index 08f7456bb1..c66202637b 100644 --- a/frontend/src/Editor/Components/TreeSelect.jsx +++ b/frontend/src/Editor/Components/TreeSelect.jsx @@ -14,7 +14,6 @@ export const TreeSelect = ({ fireEvent, darkMode, dataCy, - isEditorReady, }) => { const { label } = properties; const { visibility, disabledState, checkboxColor, boxShadow } = styles; @@ -52,11 +51,10 @@ export const TreeSelect = ({ checkedPathStrings: checkedPathString, checked: checkedArr, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); + // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(checkedData), JSON.stringify(data), isEditorReady]); + }, [JSON.stringify(checkedData), JSON.stringify(data)]); useEffect(() => { setExposedVariable('expanded', expandedData); @@ -92,9 +90,8 @@ export const TreeSelect = ({ checkedPathStrings: checkedPathString, checked: checked, }; - if (isEditorReady) { - setExposedVariables(exposedVariables); - } + setExposedVariables(exposedVariables); + updatedNode.checked ? fireEvent('onCheck') : fireEvent('onUnCheck'); fireEvent('onChange'); setChecked(checked); diff --git a/frontend/src/Editor/Container.jsx b/frontend/src/Editor/Container.jsx index 6cc2efa620..7a0a9804eb 100644 --- a/frontend/src/Editor/Container.jsx +++ b/frontend/src/Editor/Container.jsx @@ -19,7 +19,7 @@ import { calculateMoveableBoxHeight, } from '@/_helpers/appUtils'; import { useAppVersionStore } from '@/_stores/appVersionStore'; -import { useEditorStore } from '@/_stores/editorStore'; +import { useEditorStore, flushComponentsToRender } from '@/_stores/editorStore'; import { useAppInfo } from '@/_stores/appDataStore'; import { shallow } from 'zustand/shallow'; import _, { isEmpty } from 'lodash'; @@ -878,12 +878,6 @@ export const Container = ({ }) .filter(([, box]) => isEmpty(box?.component?.parent)) .map(([id, box]) => { - const canShowInCurrentLayout = - box.component.definition.others[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop'].value; - - if (box.parent || !resolveWidgetFieldValue(canShowInCurrentLayout)) { - return ''; - } return ( { const isGhostComponent = id === 'resizingComponentId'; const { component: { parent }, layouts, } = widget; - const { isSelected, isHovered } = useEditorStore((state) => { + const { isSelected, isHovered, shouldRerender } = useEditorStore((state) => { const isSelected = !!(state.selectedComponents || []).find((selected) => selected?.id === id); const isHovered = state?.hoveredComponent == id; - return { isSelected, isHovered }; + /* + `shouldRerender` is added only for re-rendering the component when visibility/showOnMobile/showOnDesktop + updates since these attributes need update or WidgetWrapper rather than actual Widget itself + */ + const shouldRerender = state.componentsNeedsUpdateOnNextRender.some((compId) => compId === id); + return { isSelected, isHovered, shouldRerender }; }, shallow); const isDragging = useGridStore((state) => state?.draggingComponentId === id); + const canShowInCurrentLayout = otherDefinition[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop'].value; + + if (parent || !resolveWidgetFieldValue(canShowInCurrentLayout)) { + /* + Remove the component from the re-render queue + This is necessary because child components are not rendered, + so their flush functions won't be called from ControlledComponentToRender + */ + shouldRerender && flushComponentsToRender(id); + return ''; + } + let layoutData = layouts?.[currentLayout]; if (isEmpty(layoutData)) { layoutData = layouts?.['desktop']; diff --git a/frontend/src/Editor/ControlledComponentToRender.jsx b/frontend/src/Editor/ControlledComponentToRender.jsx index 0471bef462..226bdcd5a9 100644 --- a/frontend/src/Editor/ControlledComponentToRender.jsx +++ b/frontend/src/Editor/ControlledComponentToRender.jsx @@ -1,7 +1,7 @@ import React, { useState, useCallback } from 'react'; import { getComponentToRender } from '@/_helpers/editorHelpers'; import _ from 'lodash'; -import { getComponentsToRenders } from '@/_stores/editorStore'; +import { getComponentsToRenders, flushComponentsToRender } from '@/_stores/editorStore'; function deepEqualityCheckusingLoDash(obj1, obj2) { return _.isEqual(obj1, obj2); @@ -16,6 +16,7 @@ export const shouldUpdate = (prevProps, nextProps) => { if (componentId) { const componentToRender = listToRender.find((item) => item === componentId); + const parentReRendered = listToRender.find((item) => item === prevProps?.parentId); const grandParentReRendered = listToRender.find((item) => item === prevProps?.grandParentId); @@ -25,6 +26,9 @@ export const shouldUpdate = (prevProps, nextProps) => { } } + // Flushing the component after the function is called from ControlledComponentToRender component + if (nextProps?.componentName) flushComponentsToRender([prevProps?.id]); + // Added to render the default child components if (prevProps?.childComponents === null && nextProps?.childComponents) return false; @@ -38,7 +42,6 @@ export const shouldUpdate = (prevProps, nextProps) => { prevProps?.height === nextProps?.height && prevProps?.darkMode === nextProps?.darkMode && prevProps?.childComponents === nextProps?.childComponents && - prevProps?.isEditorReady === nextProps?.isEditorReady && !needToRender ); }; diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index fc20c1caa1..75799e827d 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -28,6 +28,7 @@ import { buildComponentMetaDefinition, getAllChildComponents, runQueries, + updateSuggestionsFromCurrentState, } from '@/_helpers/appUtils'; import { Confirm } from './Viewer/Confirm'; // eslint-disable-next-line import/no-unresolved @@ -308,14 +309,8 @@ const EditorComponent = (props) => { const isPageSwitched = useResolveStore.getState().isPageSwitched; if (isPageSwitched) { - const currentStateObj = useCurrentStateStore.getState(); - handleLowPriorityWork(() => { - useResolveStore.getState().actions.addAppSuggestions({ - queries: currentStateObj.queries, - components: currentStateObj.components, - page: currentStateObj.page, - }); + updateSuggestionsFromCurrentState(); useResolveStore.getState().actions.pageSwitched(false); }); } @@ -733,6 +728,7 @@ const EditorComponent = (props) => { await processNewAppDefinition(appData, startingPageHandle, false, ({ homePageId }) => { handleLowPriorityWork(() => { + updateSuggestionsFromCurrentState(); useResolveStore.getState().actions.updateLastUpdatedRefs(['constants', 'client']); commonLowPriorityActions(events, { homePageId }); }); @@ -825,6 +821,7 @@ const EditorComponent = (props) => { }); processNewAppDefinition(appData, null, true, ({ homePageId }) => { handleLowPriorityWork(async () => { + updateSuggestionsFromCurrentState(); await fetchDataSources(editing_version?.id); commonLowPriorityActions(events, homePageId); }); diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index 841fc95cd4..1989261728 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -21,6 +21,7 @@ import { runQuery, computeComponentState, buildAppDefinition, + updateSuggestionsFromCurrentState, } from '@/_helpers/appUtils'; import queryString from 'query-string'; import ViewerLogoIcon from './Icons/viewer-logo.svg'; @@ -45,7 +46,7 @@ import MobileHeader from './Viewer/MobileHeader'; import DesktopHeader from './Viewer/DesktopHeader'; import './Viewer/viewer.scss'; import { useResolveStore } from '@/_stores/resolverStore'; -import { findComponentsWithReferences } from '@/_helpers/editorHelpers'; +import { findComponentsWithReferences, handleLowPriorityWork } from '@/_helpers/editorHelpers'; import { findAllEntityReferences } from '@/_stores/utils'; import { dfs } from '@/_stores/handleReferenceTransactions'; import useAppDarkMode from '@/_hooks/useAppDarkMode'; @@ -265,6 +266,7 @@ class ViewerComponent extends React.Component { useCurrentStateStore.getState().actions.setEditorReady(true); if (loadType === 'appload') { + updateSuggestionsFromCurrentState(); this.runQueries(dataQueries); } @@ -807,6 +809,8 @@ class ViewerComponent extends React.Component { isSwitchingPage: true, }, }); + + useResolveStore.getState().actions.pageSwitched(true); this.onViewerLoadUpdateEntityReferences(id, 'page-switch'); }; @@ -1093,6 +1097,10 @@ const withStore = (Component) => (props) => { if (isPageSwitched) { const currentComponentsDef = appDefinition?.pages?.[currentPageId]?.components || {}; const currentComponents = Object.keys(currentComponentsDef); + handleLowPriorityWork(() => { + updateSuggestionsFromCurrentState(); + useResolveStore.getState().actions.pageSwitched(false); + }); setTimeout(() => { if (currentComponents.length > 0) { diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index a111e434b0..335785cf2a 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -89,6 +89,8 @@ const debouncedChange = _.debounce((duplicateCurrentState) => { }, 100); export function onComponentOptionsChanged(component, options, id) { + const resolveStoreActions = useResolveStore.getState().actions; + options.forEach((option) => resolveStoreActions.resetHintsByKey([`components.${component?.name}.${option[0]}`])); let componentName = component.name; const { isEditorReady, page } = useCurrentStateStore.getState(); @@ -183,6 +185,8 @@ export function onComponentOptionsChanged(component, options, id) { } export function onComponentOptionChanged(component, option_name, value, id) { + const resolveStoreActions = useResolveStore.getState().actions; + resolveStoreActions.resetHintsByKey(`components.${component?.name}.${option_name}`); if (!useEditorStore.getState()?.appDefinition?.pages[getCurrentState()?.page?.id]?.components) return; let componentName = component.name; @@ -633,12 +637,15 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { const value = resolveReferences(event.value, state, undefined, customVariables); const customAppVariables = { ...state.variables }; customAppVariables[key] = value; + const resp = useCurrentStateStore.getState().actions.setCurrentState({ + variables: customAppVariables, + }); + useResolveStore.getState().actions.addAppSuggestions({ variables: customAppVariables, }); - return useCurrentStateStore.getState().actions.setCurrentState({ - variables: customAppVariables, - }); + + return resp; } case 'get-custom-variable': { @@ -1144,7 +1151,7 @@ export function runQuery( //for resetting the hints when the query is run for large number of items if (mode == 'edit') { const resolveStoreActions = useResolveStore.getState().actions; - resolveStoreActions.resetHintsByQueryName(queryName); + resolveStoreActions.resetHintsByKey(`queries.${queryName}`); } let parameters = userSuppliedParameters; @@ -2342,3 +2349,12 @@ export const calculateMoveableBoxHeight = (componentType, layoutData, stylesDefi return newHeight; }; + +export const updateSuggestionsFromCurrentState = () => { + const currentStateObj = useCurrentStateStore.getState(); + useResolveStore.getState().actions.addAppSuggestions({ + queries: currentStateObj.queries, + components: currentStateObj.components, + page: currentStateObj.page, + }); +}; diff --git a/frontend/src/_helpers/editorHelpers.js b/frontend/src/_helpers/editorHelpers.js index 2869a072ac..ddc7604bd7 100644 --- a/frontend/src/_helpers/editorHelpers.js +++ b/frontend/src/_helpers/editorHelpers.js @@ -166,6 +166,19 @@ function convertToBracketNotation(base, accessors) { } function verifyDotAndBracketNotations(jsString) { + if ( + !( + jsString.includes('components.') || + jsString.includes('globals.') || + jsString.includes('queries.') || + jsString.includes('page.') || + jsString.includes('variables.') || + jsString.includes('constants.') + ) + ) { + return false; + } + const notations = findNotations(jsString); for (const { base, accessors } of notations) { diff --git a/frontend/src/_stores/resolverStore.js b/frontend/src/_stores/resolverStore.js index 57b4533a81..ce4b03d1d1 100644 --- a/frontend/src/_stores/resolverStore.js +++ b/frontend/src/_stores/resolverStore.js @@ -79,19 +79,21 @@ export const useResolveStore = create( resetStore: () => { set(() => ({ ...initialState, referenceMapper: new ReferencesBiMap() })); }, - resetHintsByQueryName: (queryName) => { + resetHintsByKey: (hintKey) => { set((state) => { // Filter out app hints related to the specified query - const newAppHints = state.suggestions.appHints.filter( - (hint) => !hint.hint.startsWith(`queries.${queryName}.`) - ); + const newAppHints = state.suggestions.appHints.filter((hint) => !hint.hint.startsWith(`${hintKey}.`)); + + if (!isIterable(state.lookupTable.hints) || !isIterable(state.lookupTable.resolvedRefs)) { + return { ...state }; + } const newHints = new Map(state.lookupTable.hints); const newResolvedRefs = new Map(state.lookupTable.resolvedRefs); // Remove entries from hints and resolvedRefs for (const [key, value] of newHints) { - if (key.startsWith(`queries.${queryName}.`)) { + if (key.startsWith(`${hintKey}.`)) { newHints.delete(key); newResolvedRefs.delete(value); } @@ -106,7 +108,7 @@ export const useResolveStore = create( hints: newHints, resolvedRefs: newResolvedRefs, }, - lastUpdatedRefs: state.lastUpdatedRefs.filter((ref) => !ref.startsWith(`queries.${queryName}.`)), + lastUpdatedRefs: state.lastUpdatedRefs.filter((ref) => !ref.startsWith(`${hintKey}.`)), }; }); }, @@ -425,10 +427,14 @@ async function batchUpdateComponents(componentIds) { useEditorStore.getState().actions.updateComponentsNeedsUpdateOnNextRender(batch); } - - // Flush only updated components - - flushComponentsToRender(updatedComponentIds); } export const useResolverStoreActions = () => useResolveStore.getState().actions; + +function isIterable(obj) { + // checks for null and undefined + if (obj == null) { + return false; + } + return typeof obj[Symbol.iterator] === 'function'; +}